diff --git a/src/Loopback.cpp b/src/Loopback.cpp
index 588beba..f6379bb 100644
--- a/src/Loopback.cpp
+++ b/src/Loopback.cpp
@@ -17,25 +17,179 @@
* along with this program. If not, see .
**/
-// Desktop OpenGL function loader
-#include
+#include
+#include
+#include
+#include
// gstreamer
#include
#include
+#include
+#include
#include "defines.h"
-#include "Settings.h"
#include "GstToolkit.h"
#include "SystemToolkit.h"
-#include "FrameBuffer.h"
#include "Log.h"
#include "Loopback.h"
-bool Loopback::system_loopback_initialized = false;
+std::string Loopback::loopback_sink_;
-#if defined(LINUX)
+std::vector< std::pair > loopback_sink_alternatives_ {
+ {"v4l2sink", "videorate ! video/x-raw,format=RGB,framerate=30/1 ! v4l2sink"}
+ // TODO alternative sink for OSX? Windows?
+};
+
+bool Loopback::available()
+{
+ // test for availability once on first run
+ static bool _tested = false;
+ if (!_tested) {
+
+ loopback_sink_.clear();
+
+ for (auto config = loopback_sink_alternatives_.cbegin();
+ config != loopback_sink_alternatives_.cend() && loopback_sink_.empty(); ++config) {
+ if ( GstToolkit::has_feature(config->first) ) {
+ loopback_sink_ = config->second;
+ }
+ }
+
+ _tested = true;
+ }
+
+ return !loopback_sink_.empty();
+}
+
+Loopback::Loopback(int deviceid) : FrameGrabber(), loopback_device_(deviceid)
+{
+ frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, LOOPBACK_FPS); // fixed 30 FPS
+
+ if ( !SystemToolkit::file_exists(device_name()) )
+ throw std::runtime_error("Loopback system shall be initialized first.");
+}
+
+std::string Loopback::device_name() const
+{
+ return std::string("/dev/video") + std::to_string(loopback_device_);
+}
+
+std::string Loopback::init(GstCaps *caps)
+{
+ if (!Loopback::available())
+ return std::string("Loopback : Not available");
+
+ // ignore
+ if (caps == nullptr)
+ return std::string("Loopback : Invalid caps");
+
+ // create a gstreamer pipeline
+ std::string description = "appsrc name=src ! videoconvert ! ";
+
+ // complement pipeline with sink
+ description += Loopback::loopback_sink_ + " name=sink";
+
+ // parse pipeline descriptor
+ GError *error = NULL;
+ pipeline_ = gst_parse_launch (description.c_str(), &error);
+ if (error != NULL) {
+ std::string msg = std::string("Loopback : Could not construct pipeline ") + description + "\n" + std::string(error->message);
+ g_clear_error (&error);
+ return msg;
+ }
+
+ // setup device sink
+ g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
+ "device", device_name().c_str(),
+ "async", TRUE,
+ NULL);
+
+ // setup custom app source
+ src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
+ if (src_) {
+
+ g_object_set (G_OBJECT (src_),
+ "is-live", TRUE,
+ "format", GST_FORMAT_TIME,
+ NULL);
+
+ // configure stream
+ gst_app_src_set_stream_type( src_, GST_APP_STREAM_TYPE_STREAM);
+ gst_app_src_set_latency( src_, -1, 0);
+
+ // Set buffer size
+ gst_app_src_set_max_bytes( src_, buffering_size_ );
+
+ // specify streaming framerate in the given caps
+ GstCaps *tmp = gst_caps_copy( caps );
+ GValue v = { 0, };
+ g_value_init (&v, GST_TYPE_FRACTION);
+ gst_value_set_fraction (&v, LOOPBACK_FPS, 1); // fixed FPS
+ gst_caps_set_value(tmp, "framerate", &v);
+ g_value_unset (&v);
+
+ // instruct src to use the caps
+ caps_ = gst_caps_copy( tmp );
+ gst_app_src_set_caps (src_, caps_);
+ gst_caps_unref (tmp);
+
+ // setup callbacks
+ GstAppSrcCallbacks callbacks;
+ callbacks.need_data = FrameGrabber::callback_need_data;
+ callbacks.enough_data = FrameGrabber::callback_enough_data;
+ callbacks.seek_data = NULL; // stream type is not seekable
+ gst_app_src_set_callbacks( src_, &callbacks, this, NULL);
+
+ }
+ else {
+ return std::string("Loopback : Could not configure source.");
+ }
+
+ // start recording
+ GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
+ if (ret == GST_STATE_CHANGE_FAILURE) {
+ return std::string("Loopback : Could not open ") + device_name();
+ }
+
+ // all good
+ initialized_ = true;
+
+ return std::string("Loopback started on ") + device_name();
+}
+
+void Loopback::terminate()
+{
+ // send EOS
+ gst_app_src_end_of_stream (src_);
+
+ Log::Notify("Loopback to %s terminated.", device_name().c_str());
+}
+
+void Loopback::stop ()
+{
+ // stop recording
+ FrameGrabber::stop ();
+
+ // force finished
+ endofstream_ = true;
+ active_ = false;
+}
+
+std::string Loopback::info() const
+{
+ std::ostringstream ret;
+
+ if (!initialized_)
+ ret << "Loopback starting..";
+ else if (active_)
+ ret << "V4L2 Loopback on " << device_name();
+ else
+ ret << "Loopback terminated";
+
+ return ret.str();
+}
/**
*
@@ -74,183 +228,3 @@ bool Loopback::system_loopback_initialized = false;
* $ gst-launch-1.0 v4l2src device=/dev/video10 ! videoconvert ! autovideosink
* $ gst-launch-1.0 videotestsrc ! v4l2sink device=/dev/video10
*/
-
-#include
-#include
-#include
-#include
-
-std::string Loopback::system_loopback_name = "/dev/video10";
-std::string Loopback::system_loopback_pipeline = "appsrc name=src ! videoconvert ! videorate ! video/x-raw,framerate=30/1 ! v4l2sink sync=false name=sink";
-
-bool Loopback::initializeSystemLoopback()
-{
- if (!Loopback::systemLoopbackInitialized()) {
-
- // create script for asking sudo password
- std::string sudoscript = SystemToolkit::full_filename(SystemToolkit::settings_path(), "sudo.sh");
- FILE *file = fopen(sudoscript.c_str(), "w");
- if (file) {
- fprintf(file, "#!/bin/bash\n");
- fprintf(file, "zenity --password --title=Authentication\n");
- fclose(file);
-
- // make script executable
- int fildes = 0;
- fildes = open(sudoscript.c_str(), O_RDWR);
- fchmod(fildes, S_IRWXU | S_IRWXG | S_IROTH | S_IWOTH);
- close(fildes);
-
- // create command line for installing v4l2loopback
- std::string cmdline = "export SUDO_ASKPASS=\"" + sudoscript + "\"\n";
- cmdline += "sudo -A apt install v4l2loopback-dkms 2>&1\n";
- cmdline += "sudo -A modprobe -r v4l2loopback 2>&1\n";
- cmdline += "sudo -A modprobe v4l2loopback exclusive_caps=1 video_nr=10 card_label=\"vimix loopback\" 2>&1\n";
-
- // execute v4l2 command line
- std::string report;
- FILE *fp = popen(cmdline.c_str(), "r");
- if (fp != NULL) {
-
- // get stdout content from command line
- char linestdout[PATH_MAX];
- while (fgets(linestdout, PATH_MAX, fp) != NULL)
- report += linestdout;
-
- // error reported by pclose?
- if (pclose(fp) != 0 )
- Log::Warning("Failed to initialize system v4l2loopback\n%s", report.c_str());
- // okay, probaly all good...
- else
- system_loopback_initialized = true;
- }
- else
- Log::Warning("Failed to initialize system v4l2loopback\nCannot execute command line");
-
- }
- else
- Log::Warning("Failed to initialize system v4l2loopback\nCannot create script", sudoscript.c_str());
- }
-
- return system_loopback_initialized;
-}
-
-bool Loopback::systemLoopbackInitialized()
-{
- // test if already initialized
- if (!system_loopback_initialized) {
- // check the existence of loopback device
- if ( SystemToolkit::file_exists(system_loopback_name) )
- system_loopback_initialized = true;
- }
-
- return system_loopback_initialized;
-}
-
-#else
-
-std::string Loopback::system_loopback_name = "undefined";
-std::string Loopback::system_loopback_pipeline = "";
-
-
-bool Loopback::initializeSystemLoopback()
-{
- system_loopback_initialized = false;
- return false;
-}
-
-bool Loopback::systemLoopbackInitialized()
-{
- return false;
-}
-#endif
-
-
-Loopback::Loopback() : FrameGrabber()
-{
- frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // fixed 30 FPS
-}
-
-std::string Loopback::init(GstCaps *caps)
-{
- // ignore
- if (caps == nullptr)
- return std::string("Invalid caps");
-
- if (!Loopback::systemLoopbackInitialized()){
- return std::string("Loopback system shall be initialized first.");
- }
-
- // create a gstreamer pipeline
- std::string description = Loopback::system_loopback_pipeline;
-
- // parse pipeline descriptor
- GError *error = NULL;
- pipeline_ = gst_parse_launch (description.c_str(), &error);
- if (error != NULL) {
- std::string msg = std::string("Loopback : Could not construct pipeline ") + description + "\n" + std::string(error->message);
- g_clear_error (&error);
- return msg;
- }
-
- // setup device sink
- g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
- "device", Loopback::system_loopback_name.c_str(),
- NULL);
-
- // setup custom app source
- src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
- if (src_) {
-
- g_object_set (G_OBJECT (src_),
- "is-live", TRUE,
- NULL);
-
- // configure stream
- gst_app_src_set_stream_type( src_, GST_APP_STREAM_TYPE_STREAM);
- gst_app_src_set_latency( src_, -1, 0);
-
- // Set buffer size
- gst_app_src_set_max_bytes( src_, buffering_size_ );
-
- // specify streaming framerate in the given caps
- GstCaps *tmp = gst_caps_copy( caps );
- GValue v = { 0, };
- g_value_init (&v, GST_TYPE_FRACTION);
- gst_value_set_fraction (&v, 30, 1); // fixed 30 FPS
- gst_caps_set_value(tmp, "framerate", &v);
- g_value_unset (&v);
-
- // instruct src to use the caps
- caps_ = gst_caps_copy( tmp );
- gst_app_src_set_caps (src_, caps_);
- gst_caps_unref (tmp);
-
- // setup callbacks
- GstAppSrcCallbacks callbacks;
- callbacks.need_data = FrameGrabber::callback_need_data;
- callbacks.enough_data = FrameGrabber::callback_enough_data;
- callbacks.seek_data = NULL; // stream type is not seekable
- gst_app_src_set_callbacks( src_, &callbacks, this, NULL);
-
- }
- else {
- return std::string("Loopback : Could not configure source.");
- }
-
- // start recording
- GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
- if (ret == GST_STATE_CHANGE_FAILURE) {
- return std::string("Loopback : Could not open ") + Loopback::system_loopback_name;
- }
-
- // all good
- initialized_ = true;
-
- return std::string("Loopback started on ") + Loopback::system_loopback_name;
-}
-
-void Loopback::terminate()
-{
- Log::Notify("Loopback to %s terminated.", Loopback::system_loopback_name.c_str());
-}
diff --git a/src/Loopback.h b/src/Loopback.h
index 7f047c3..e92f784 100644
--- a/src/Loopback.h
+++ b/src/Loopback.h
@@ -1,30 +1,33 @@
#ifndef LOOPBACK_H
#define LOOPBACK_H
-#include
-
-#include
-#include
-
#include "FrameGrabber.h"
+#define LOOPBACK_DEFAULT_DEVICE 10
+#define LOOPBACK_FPS 30
class Loopback : public FrameGrabber
{
- static std::string system_loopback_pipeline;
- static std::string system_loopback_name;
- static bool system_loopback_initialized;
+public:
+ Loopback(int deviceid = LOOPBACK_DEFAULT_DEVICE);
+ virtual ~Loopback() {}
+ static bool available();
+
+ inline int device_id() const { return loopback_device_; }
+ std::string device_name() const;
+
+ void stop() override;
+ std::string info() const override;
+
+private:
std::string init(GstCaps *caps) override;
void terminate() override;
-public:
-
- Loopback();
-
- static bool systemLoopbackInitialized();
- static bool initializeSystemLoopback();
+ // device information
+ int loopback_device_;
+ static std::string loopback_sink_;
};
diff --git a/src/Settings.cpp b/src/Settings.cpp
index a319502..6fdfd3d 100644
--- a/src/Settings.cpp
+++ b/src/Settings.cpp
@@ -127,6 +127,8 @@ void Settings::Save(uint64_t runtime)
applicationNode->SetAttribute("pannel_history_mode", application.pannel_current_session_mode);
applicationNode->SetAttribute("stream_protocol", application.stream_protocol);
applicationNode->SetAttribute("broadcast_port", application.broadcast_port);
+ applicationNode->SetAttribute("loopback_camera", application.loopback_camera);
+ applicationNode->SetAttribute("shm_socket_path", application.shm_socket_path.c_str());
pRoot->InsertEndChild(applicationNode);
// Widgets
@@ -398,6 +400,12 @@ void Settings::Load()
applicationNode->QueryIntAttribute("pannel_history_mode", &application.pannel_current_session_mode);
applicationNode->QueryIntAttribute("stream_protocol", &application.stream_protocol);
applicationNode->QueryIntAttribute("broadcast_port", &application.broadcast_port);
+ applicationNode->QueryIntAttribute("loopback_camera", &application.loopback_camera);
+
+ // text attributes
+ const char *tmpstr = applicationNode->Attribute("shm_socket_path");
+ if (tmpstr)
+ application.shm_socket_path = std::string(tmpstr);
}
// Widgets
diff --git a/src/Settings.h b/src/Settings.h
index 0ce3e5f..4fa6ea0 100644
--- a/src/Settings.h
+++ b/src/Settings.h
@@ -263,6 +263,8 @@ struct Application
int stream_protocol;
int broadcast_port;
KnownHosts recentSRT;
+ int loopback_camera;
+ std::string shm_socket_path;
// Settings of widgets
WidgetsConfig widget;
@@ -317,9 +319,11 @@ struct Application
show_tooptips = true;
accept_connections = false;
stream_protocol = 0;
- broadcast_port = 7070;
+ broadcast_port = 0;
recentSRT.protocol = "srt://";
recentSRT.default_host = { "127.0.0.1", "7070"};
+ loopback_camera = 0;
+ shm_socket_path = "";
pannel_current_session_mode = 0;
current_view = 1;
current_workspace= 1;
diff --git a/src/ShmdataBroadcast.cpp b/src/ShmdataBroadcast.cpp
index 8721dde..ca6efd9 100644
--- a/src/ShmdataBroadcast.cpp
+++ b/src/ShmdataBroadcast.cpp
@@ -27,7 +27,6 @@
#include
#include "Log.h"
-#include "Settings.h"
#include "GstToolkit.h"
#include "ShmdataBroadcast.h"
@@ -49,11 +48,12 @@ bool ShmdataBroadcast::available()
return _available;
}
-ShmdataBroadcast::ShmdataBroadcast(const std::string &socketpath): FrameGrabber(), socket_path_(socketpath), stopped_(false)
+ShmdataBroadcast::ShmdataBroadcast(const std::string &socketpath): FrameGrabber(), socket_path_(socketpath)
{
- frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, DEFAULT_GRABBER_FPS); // fixed 30 FPS
+ frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, SHMDATA_FPS); // fixed 30 FPS
- socket_path_ += std::to_string(Settings::application.instance_id);
+ if (socket_path_.empty())
+ socket_path_ = SHMDATA_DEFAULT_PATH;
}
std::string ShmdataBroadcast::init(GstCaps *caps)
@@ -105,7 +105,7 @@ std::string ShmdataBroadcast::init(GstCaps *caps)
GstCaps *tmp = gst_caps_copy( caps );
GValue v = { 0, };
g_value_init (&v, GST_TYPE_FRACTION);
- gst_value_set_fraction (&v, DEFAULT_GRABBER_FPS, 1); // fixed 30 FPS
+ gst_value_set_fraction (&v, SHMDATA_FPS, 1); // fixed 30 FPS
gst_caps_set_value(tmp, "framerate", &v);
g_value_unset (&v);
@@ -141,22 +141,14 @@ std::string ShmdataBroadcast::init(GstCaps *caps)
void ShmdataBroadcast::terminate()
{
- // send EOS
- gst_app_src_end_of_stream (src_);
+ // force finished
+ endofstream_ = true;
+ active_ = false;
Log::Notify("Shared Memory terminated after %s s.",
GstToolkit::time_to_string(duration_).c_str());
}
-void ShmdataBroadcast::stop ()
-{
- // stop recording
- FrameGrabber::stop ();
-
- // force finished
- endofstream_ = true;
- active_ = false;
-}
std::string ShmdataBroadcast::info() const
{
diff --git a/src/ShmdataBroadcast.h b/src/ShmdataBroadcast.h
index 2c7fd04..6bfb151 100644
--- a/src/ShmdataBroadcast.h
+++ b/src/ShmdataBroadcast.h
@@ -3,16 +3,19 @@
#include "FrameGrabber.h"
+#define SHMDATA_DEFAULT_PATH "/tmp/shmdata_vimix"
+#define SHMDATA_FPS 30
+
class ShmdataBroadcast : public FrameGrabber
{
public:
- ShmdataBroadcast(const std::string &socketpath = "/tmp/shmdata_vimix");
+ ShmdataBroadcast(const std::string &socketpath = SHMDATA_DEFAULT_PATH);
virtual ~ShmdataBroadcast() {}
static bool available();
+
inline std::string socket_path() const { return socket_path_; }
- void stop() override;
std::string info() const override;
private:
@@ -21,7 +24,6 @@ private:
// connection information
std::string socket_path_;
- std::atomic stopped_;
static std::string shmdata_sink_;
};
diff --git a/src/Source.h b/src/Source.h
index df3f579..9c6d022 100644
--- a/src/Source.h
+++ b/src/Source.h
@@ -23,7 +23,7 @@
#define ICON_SOURCE_RENDER 0, 2
#define ICON_SOURCE_CLONE 9, 2
#define ICON_SOURCE_GSTREAMER 16, 16
-#define ICON_SOURCE_SRT 11, 8
+#define ICON_SOURCE_SRT 15, 5
#define ICON_SOURCE 13, 11
class Visitor;
diff --git a/src/UserInterfaceManager.cpp b/src/UserInterfaceManager.cpp
index 82e8cc7..5fe7599 100644
--- a/src/UserInterfaceManager.cpp
+++ b/src/UserInterfaceManager.cpp
@@ -940,7 +940,8 @@ void UserInterface::showMenuEdit()
navigator.hidePannel();
}
if (ImGui::MenuItem( MENU_PASTE, SHORTCUT_PASTE, false, has_clipboard)) {
- Mixer::manager().paste(clipboard);
+ if (clipboard)
+ Mixer::manager().paste(clipboard);
navigator.hidePannel();
}
if (ImGui::MenuItem( MENU_SELECTALL, SHORTCUT_SELECTALL)) {
@@ -3811,11 +3812,8 @@ void SourceController::DrawButtonBar(ImVec2 bottom, float width)
///
OutputPreview::OutputPreview() : WorkspaceWindow("OutputPreview"),
- video_recorder_(nullptr), video_broadcaster_(nullptr)
+ video_recorder_(nullptr), video_broadcaster_(nullptr), loopback_broadcaster_(nullptr)
{
-#if defined(LINUX)
- webcam_emulator_ = nullptr;
-#endif
recordFolderDialog = new DialogToolkit::OpenFolderDialog("Recording Location");
}
@@ -3864,14 +3862,11 @@ void OutputPreview::Update()
video_recorder_->stop();
}
- // verify the broadcasters are valid (change to nullptr if invalid)
+ // verify the frame grabbers are valid (change to nullptr if invalid)
FrameGrabbing::manager().verify( (FrameGrabber**) &video_broadcaster_);
FrameGrabbing::manager().verify( (FrameGrabber**) &shm_broadcaster_);
+ FrameGrabbing::manager().verify( (FrameGrabber**) &loopback_broadcaster_);
-#if defined(LINUX)
- // verify the frame grabber for webcam emulator is valid
- FrameGrabbing::manager().verify( (FrameGrabber**) &webcam_emulator_);
-#endif
}
VideoRecorder *delayTrigger(VideoRecorder *g, std::chrono::milliseconds delay) {
@@ -3902,13 +3897,15 @@ void OutputPreview::ToggleRecord(bool save_and_continue)
}
}
-void OutputPreview::ToggleBroadcast()
+void OutputPreview::ToggleVideoBroadcast()
{
if (video_broadcaster_) {
video_broadcaster_->stop();
}
else {
- video_broadcaster_ = new VideoBroadcast;
+ if (Settings::application.broadcast_port<1000)
+ Settings::application.broadcast_port = BROADCAST_DEFAULT_PORT;
+ video_broadcaster_ = new VideoBroadcast(Settings::application.broadcast_port);
FrameGrabbing::manager().add(video_broadcaster_);
}
}
@@ -3919,17 +3916,40 @@ void OutputPreview::ToggleSharedMemory()
shm_broadcaster_->stop();
}
else {
- shm_broadcaster_ = new ShmdataBroadcast;
+ if (Settings::application.shm_socket_path.empty())
+ Settings::application.shm_socket_path = SHMDATA_DEFAULT_PATH + std::to_string(Settings::application.instance_id);
+ shm_broadcaster_ = new ShmdataBroadcast(Settings::application.shm_socket_path);
FrameGrabbing::manager().add(shm_broadcaster_);
}
}
+bool OutputPreview::ToggleLoopbackCamera()
+{
+ bool need_initialization = false;
+ if (loopback_broadcaster_) {
+ loopback_broadcaster_->stop();
+ }
+ else {
+ if (Settings::application.loopback_camera < 1)
+ Settings::application.loopback_camera = LOOPBACK_DEFAULT_DEVICE;
+ Settings::application.loopback_camera += Settings::application.instance_id;
+
+ try {
+ loopback_broadcaster_ = new Loopback(Settings::application.loopback_camera);
+ FrameGrabbing::manager().add(loopback_broadcaster_);
+ }
+ catch (const std::runtime_error &e) {
+ need_initialization = true;
+ Log::Info("%s", e.what());
+ }
+ }
+ return need_initialization;
+}
+
+
void OutputPreview::Render()
{
-
-#if defined(LINUX)
bool openInitializeSystemLoopback = false;
-#endif
FrameBuffer *output = Mixer::manager().session()->frame();
if (output)
@@ -4090,25 +4110,6 @@ void OutputPreview::Render()
}
if (ImGui::BeginMenu(ICON_FA_WIFI " Stream"))
{
-
-#if defined(LINUX_NOT_YET_WORKING)
- bool on = webcam_emulator_ != nullptr;
- if ( ImGui::MenuItem( ICON_FA_CAMERA " Emulate video camera", NULL, &on) ) {
- if (on) {
- if (Loopback::systemLoopbackInitialized()) {
- webcam_emulator_ = new Loopback;
- FrameGrabbing::manager().add(webcam_emulator_);
- }
- else
- openInitializeSystemLoopback = true;
- }
- else if (webcam_emulator_ != nullptr) {
- webcam_emulator_->stop();
- webcam_emulator_ = nullptr;
- }
- }
-#endif
-
// Stream sharing menu
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.9f));
if ( ImGui::MenuItem( ICON_FA_SHARE_ALT_SQUARE " Peer-to-peer sharing", NULL, &Settings::application.accept_connections) ) {
@@ -4119,36 +4120,39 @@ void OutputPreview::Render()
// list active streams:
std::vector ls = Streaming::manager().listStreams();
-
// Broadcasting menu
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.9f));
if (VideoBroadcast::available()) {
- bool enabled = (video_broadcaster_!=nullptr);
- if ( ImGui::MenuItem( ICON_FA_PODCAST " SRT Broadcast", NULL, &enabled) )
- ToggleBroadcast();
+ if ( ImGui::MenuItem( ICON_FA_GLOBE " SRT Broadcast", NULL, videoBroadcastEnabled()) )
+ ToggleVideoBroadcast();
}
// Shared Memory menu
if (ShmdataBroadcast::available()) {
- bool enabled = (shm_broadcaster_!=nullptr);
- if ( ImGui::MenuItem( ICON_FA_PROJECT_DIAGRAM " Shared Memory", NULL, &enabled) )
+ if ( ImGui::MenuItem( ICON_FA_SHARE_ALT " Shared Memory", NULL, sharedMemoryEnabled()) )
ToggleSharedMemory();
}
+
+ // Loopback camera menu
+ if (Loopback::available()) {
+ if ( ImGui::MenuItem( ICON_FA_VIDEO " Loopback Camera", NULL, loopbackCameraEnabled()) )
+ openInitializeSystemLoopback = ToggleLoopbackCamera();
+ }
+
ImGui::PopStyleColor(1);
// Display list of active stream
- if (ls.size()>0 || video_broadcaster_ || shm_broadcaster_) {
+ if (ls.size()>0 || videoBroadcastEnabled() || sharedMemoryEnabled() || loopbackCameraEnabled()) {
ImGui::Separator();
- ImGui::MenuItem("Active streaming", nullptr, false, false);
+ ImGui::MenuItem("Active streams", nullptr, false, false);
// First the list of peer 2 peer
for (auto it = ls.begin(); it != ls.end(); ++it)
ImGui::Text(" %s ", (*it).c_str() );
- // Second the SRT
- if (video_broadcaster_) {
+ // SRT broadcast description
+ if (videoBroadcastEnabled()) {
ImGui::Text(" %s ", video_broadcaster_->info().c_str());
-
// copy text icon to give user the srt link to connect to
ImVec2 draw_pos = ImGui::GetCursorPos();
ImGui::SetCursorPos(draw_pos + ImVec2(ImGui::GetContentRegionAvailWidth() - 1.2 * ImGui::GetTextLineHeightWithSpacing(), -0.8 * ImGui::GetFrameHeight()) );
@@ -4159,17 +4163,27 @@ void OutputPreview::Render()
ImGui::SetCursorPos(draw_pos);
}
- // Third the SHMdata
- if (shm_broadcaster_) {
+ // Shared memory broadcast description
+ if (sharedMemoryEnabled()) {
ImGui::Text(" %s ", shm_broadcaster_->info().c_str());
-
- // copy text icon to give user the socket path to connec to
+ // copy text icon to give user the socket path to connect to
ImVec2 draw_pos = ImGui::GetCursorPos();
ImGui::SetCursorPos(draw_pos + ImVec2(ImGui::GetContentRegionAvailWidth() - 1.2 * ImGui::GetTextLineHeightWithSpacing(), -0.8 * ImGui::GetFrameHeight()) );
if (ImGuiToolkit::IconButton( ICON_FA_COPY, shm_broadcaster_->socket_path().c_str()))
ImGui::SetClipboardText(shm_broadcaster_->socket_path().c_str());
ImGui::SetCursorPos(draw_pos);
}
+
+ // Loopback camera description
+ if (loopbackCameraEnabled()) {
+ ImGui::Text(" %s ", loopback_broadcaster_->info().c_str());
+ // copy text icon to give user the device path to connect to
+ ImVec2 draw_pos = ImGui::GetCursorPos();
+ ImGui::SetCursorPos(draw_pos + ImVec2(ImGui::GetContentRegionAvailWidth() - 1.2 * ImGui::GetTextLineHeightWithSpacing(), -0.8 * ImGui::GetFrameHeight()) );
+ if (ImGuiToolkit::IconButton( ICON_FA_COPY, loopback_broadcaster_->device_name().c_str()))
+ ImGui::SetClipboardText(loopback_broadcaster_->device_name().c_str());
+ ImGui::SetCursorPos(draw_pos);
+ }
}
ImGui::EndMenu();
@@ -4201,88 +4215,83 @@ void OutputPreview::Render()
ImGui::SetCursorScreenPos(draw_pos + ImVec2(imagesize.x - r, 6));
ImGui::Text(ICON_FA_INFO_CIRCLE);
bool drawoverlay = ImGui::IsItemHovered();
+
+ // icon indicators
+ ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
// recording indicator
if (video_recorder_)
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + r));
- ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f));
ImGui::Text(ICON_FA_CIRCLE " %s", video_recorder_->info().c_str() );
ImGui::PopStyleColor(1);
- ImGui::PopFont();
}
else if (!_video_recorders.empty())
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + r));
- ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
static double anim = 0.f;
double a = sin(anim+=0.104); // 2 * pi / 60fps
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, a));
ImGui::Text(ICON_FA_CIRCLE);
ImGui::PopStyleColor(1);
- ImGui::PopFont();
}
// broadcast indicator
+ float vertical = r;
if (video_broadcaster_)
{
- ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + r));
- ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
+ ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + vertical));
if (video_broadcaster_->busy())
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f));
else
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.4f));
- ImGui::Text(ICON_FA_PODCAST);
+ ImGui::Text(ICON_FA_GLOBE);
ImGui::PopStyleColor(1);
- ImGui::PopFont();
+ vertical += 2.f * r;
}
// shmdata indicator
if (shm_broadcaster_)
{
- ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 4.5f * r, draw_pos.y + r));
- ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
+ ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + vertical));
if (shm_broadcaster_->busy())
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f));
else
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.4f));
- ImGui::Text(ICON_FA_PROJECT_DIAGRAM);
+ ImGui::Text(ICON_FA_SHARE_ALT);
+ ImGui::PopStyleColor(1);
+ vertical += 2.f * r;
+ }
+ // loopback camera indicator
+ if (loopback_broadcaster_)
+ {
+ ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + vertical));
+ if (loopback_broadcaster_->busy())
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f));
+ else
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.4f));
+ ImGui::Text(ICON_FA_VIDEO);
ImGui::PopStyleColor(1);
- ImGui::PopFont();
}
// streaming indicator
if (Settings::application.accept_connections)
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.4f * r, draw_pos.y + imagesize.y - 2.f*r));
- ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
if ( Streaming::manager().busy())
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.8f));
else
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.4f));
ImGui::Text(ICON_FA_SHARE_ALT_SQUARE);
ImGui::PopStyleColor(1);
- ImGui::PopFont();
}
// OUTPUT DISABLED indicator
if (Settings::application.render.disabled)
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + imagesize.y - 2.f*r));
- ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f));
ImGui::Text(ICON_FA_EYE_SLASH);
ImGui::PopStyleColor(1);
- ImGui::PopFont();
}
-#if defined(LINUX)
- // streaming indicator
- if (webcam_emulator_)
- {
- ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + 2.f * r));
- ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
- ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8f, 0.8f, 0.8f, 0.8f));
- ImGui::Text(ICON_FA_CAMERA);
- ImGui::PopStyleColor(1);
- ImGui::PopFont();
- }
-#endif
+ ImGui::PopFont();
+
///
/// Info overlay over image
@@ -4292,64 +4301,66 @@ void OutputPreview::Render()
ImDrawList* draw_list = ImGui::GetWindowDrawList();
float h = 1.f;
h += (Settings::application.accept_connections ? 1.f : 0.f);
- h += (video_broadcaster_ ? 1.f : 0.f);
- h += (shm_broadcaster_ ? 1.f : 0.f);
draw_list->AddRectFilled(draw_pos, ImVec2(draw_pos.x + imagesize.x, draw_pos.y + h * r), IMGUI_COLOR_OVERLAY);
ImGui::SetCursorScreenPos(draw_pos);
ImGui::Text(" " ICON_FA_TV " %d x %d px, %.d fps", output->width(), output->height(), int(Mixer::manager().fps()) );
if (Settings::application.accept_connections)
- ImGui::Text( " " ICON_FA_SHARE_ALT_SQUARE " %s (%ld peer)",
+ ImGui::Text( " " ICON_FA_SHARE_ALT_SQUARE " Available as %s (%ld peer connected)",
Connection::manager().info().name.c_str(),
Streaming::manager().listStreams().size() );
- if (video_broadcaster_)
- ImGui::Text( " " ICON_FA_PODCAST " %s", video_broadcaster_->info().c_str() );
- if (shm_broadcaster_)
- ImGui::Text( " " ICON_FA_PROJECT_DIAGRAM " %s", shm_broadcaster_->info().c_str() );
}
ImGui::End();
}
-#if defined(LINUX)
+ // Dialog to explain to the user how to initialize the loopback on the system
if (openInitializeSystemLoopback && !ImGui::IsPopupOpen("Initialize System Loopback"))
ImGui::OpenPopup("Initialize System Loopback");
if (ImGui::BeginPopupModal("Initialize System Loopback", NULL, ImGuiWindowFlags_AlwaysAutoResize))
{
+#if defined(LINUX)
int w = 600;
ImGui::Text("In order to enable the video4linux camera loopback,\n"
- "'v4l2loopack' has to be installed and initialized on your machine\n\n"
- "To do so, the following commands should be executed (admin rights):\n");
+ "'v4l2loopack' has to be installed and initialized on your machine");
+ ImGui::Spacing();
+ ImGuiToolkit::ButtonOpenUrl("More information online on v4l2loopback", "https://github.com/umlaeute/v4l2loopback");
+ ImGui::Spacing();
+ ImGui::Text("To do so, the following commands should be executed\n(with admin rights):");
static char dummy_str[512];
sprintf(dummy_str, "sudo apt install v4l2loopback-dkms");
- ImGui::Text("Install v4l2loopack (once):");
+ ImGui::NewLine();
+ ImGui::Text("Install v4l2loopack (only once, and reboot):");
ImGui::SetNextItemWidth(600-40);
ImGui::InputText("##cmd1", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly);
ImGui::SameLine();
ImGui::PushID(358794);
- if ( ImGuiToolkit::ButtonIcon(11,2, "Copy to clipboard") )
+ if ( ImGuiToolkit::IconButton(ICON_FA_COPY, "Copy to clipboard") )
ImGui::SetClipboardText(dummy_str);
ImGui::PopID();
- sprintf(dummy_str, "sudo modprobe v4l2loopback exclusive_caps=1 video_nr=10 card_label=\"vimix loopback\"");
- ImGui::Text("Initialize v4l2loopack (after reboot):");
+ sprintf(dummy_str, "sudo modprobe v4l2loopback exclusive_caps=1 video_nr=%d"
+ " card_label=\"vimix loopback\"" , Settings::application.loopback_camera);
+ ImGui::NewLine();
+ ImGui::Text("Initialize v4l2loopack:");
ImGui::SetNextItemWidth(600-40);
ImGui::InputText("##cmd2", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly);
ImGui::SameLine();
ImGui::PushID(899872);
- if ( ImGuiToolkit::ButtonIcon(11,2, "Copy to clipboard") )
+ if ( ImGuiToolkit::IconButton(ICON_FA_COPY, "Copy to clipboard") )
ImGui::SetClipboardText(dummy_str);
ImGui::PopID();
- ImGui::Separator();
+
+ ImGui::NewLine();
ImGui::SetItemDefaultFocus();
if (ImGui::Button("Ok, I'll do this in a terminal and try again later.", ImVec2(w, 0)) ) {
ImGui::CloseCurrentPopup();
}
+#endif
ImGui::EndPopup();
}
-#endif
}
///
diff --git a/src/UserInterfaceManager.h b/src/UserInterfaceManager.h
index f4ecc95..55e5561 100644
--- a/src/UserInterfaceManager.h
+++ b/src/UserInterfaceManager.h
@@ -111,6 +111,7 @@ class FrameGrabber;
class VideoRecorder;
class VideoBroadcast;
class ShmdataBroadcast;
+class Loopback;
class SourcePreview {
@@ -332,14 +333,11 @@ class OutputPreview : public WorkspaceWindow
VideoRecorder *video_recorder_;
VideoBroadcast *video_broadcaster_;
ShmdataBroadcast *shm_broadcaster_;
+ Loopback *loopback_broadcaster_;
// delayed trigger for recording
std::vector< std::future > _video_recorders;
-#if defined(LINUX)
- FrameGrabber *webcam_emulator_;
-#endif
-
// dialog to select record location
DialogToolkit::OpenFolderDialog *recordFolderDialog;
@@ -349,11 +347,14 @@ public:
void ToggleRecord(bool save_and_continue = false);
inline bool isRecording() const { return video_recorder_ != nullptr; }
- void ToggleBroadcast();
- inline bool isBroadcasting() const { return video_broadcaster_ != nullptr; }
+ void ToggleVideoBroadcast();
+ inline bool videoBroadcastEnabled() const { return video_broadcaster_ != nullptr; }
void ToggleSharedMemory();
- inline bool isSharingMemory() const { return shm_broadcaster_ != nullptr; }
+ inline bool sharedMemoryEnabled() const { return shm_broadcaster_ != nullptr; }
+
+ bool ToggleLoopbackCamera();
+ inline bool loopbackCameraEnabled() const { return loopback_broadcaster_!= nullptr; }
void Render();
void setVisible(bool on);
diff --git a/src/VideoBroadcast.cpp b/src/VideoBroadcast.cpp
index 76043da..c4c493e 100644
--- a/src/VideoBroadcast.cpp
+++ b/src/VideoBroadcast.cpp
@@ -17,6 +17,7 @@
* along with this program. If not, see .
**/
+#include
#include
#include
@@ -27,7 +28,6 @@
#include
#include "Log.h"
-#include "Settings.h"
#include "GstToolkit.h"
#include "NetworkToolkit.h"
@@ -38,14 +38,14 @@
#endif
std::string VideoBroadcast::srt_sink_;
-std::string VideoBroadcast::h264_encoder_;
+std::string VideoBroadcast::srt_encoder_;
-std::vector< std::string > pipeline_sink_ {
+std::vector< std::string > srt_sink_alternatives_ {
"srtsink",
"srtserversink"
};
-std::vector< std::pair > pipeline_encoder_ {
+std::vector< std::pair > srt_encoder_alternatives_ {
{"nvh264enc", "nvh264enc zerolatency=true rc-mode=cbr-ld-hq bitrate=4000 ! "},
{"vaapih264enc", "vaapih264enc rate-control=cqp init-qp=26 ! "},
{"x264enc", "x264enc tune=zerolatency ! "}
@@ -57,10 +57,10 @@ bool VideoBroadcast::available()
static bool _tested = false;
if (!_tested) {
srt_sink_.clear();
- h264_encoder_.clear();
+ srt_encoder_.clear();
- for (auto config = pipeline_sink_.cbegin();
- config != pipeline_sink_.cend() && srt_sink_.empty(); ++config) {
+ for (auto config = srt_sink_alternatives_.cbegin();
+ config != srt_sink_alternatives_.cend() && srt_sink_.empty(); ++config) {
if ( GstToolkit::has_feature(*config) ) {
srt_sink_ = *config;
}
@@ -68,11 +68,11 @@ bool VideoBroadcast::available()
if (!srt_sink_.empty())
{
- for (auto config = pipeline_encoder_.cbegin();
- config != pipeline_encoder_.cend() && h264_encoder_.empty(); ++config) {
+ for (auto config = srt_encoder_alternatives_.cbegin();
+ config != srt_encoder_alternatives_.cend() && srt_encoder_.empty(); ++config) {
if ( GstToolkit::has_feature(config->first) ) {
- h264_encoder_ = config->second;
- if (config->first != pipeline_encoder_.back().first)
+ srt_encoder_ = config->second;
+ if (config->first != srt_encoder_alternatives_.back().first)
Log::Info("Video Broadcast uses hardware-accelerated encoder (%s)", config->first.c_str());
}
}
@@ -85,12 +85,14 @@ bool VideoBroadcast::available()
}
// video broadcast is installed if both srt and h264 are available
- return (!srt_sink_.empty() && !h264_encoder_.empty());
+ return (!srt_sink_.empty() && !srt_encoder_.empty());
}
-VideoBroadcast::VideoBroadcast(int port): FrameGrabber(), port_(port), stopped_(false)
+VideoBroadcast::VideoBroadcast(int port): FrameGrabber(), port_(port)
{
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, BROADCAST_FPS); // fixed 30 FPS
+ if (port_ < 1000)
+ port_ = BROADCAST_DEFAULT_PORT;
}
std::string VideoBroadcast::init(GstCaps *caps)
@@ -106,7 +108,7 @@ std::string VideoBroadcast::init(GstCaps *caps)
std::string description = "appsrc name=src ! videoconvert ! ";
// complement pipeline with encoder
- description += VideoBroadcast::h264_encoder_;
+ description += VideoBroadcast::srt_encoder_;
description += "video/x-h264, profile=high ! queue ! h264parse config-interval=-1 ! mpegtsmux ! ";
// complement pipeline with sink
@@ -182,8 +184,6 @@ std::string VideoBroadcast::init(GstCaps *caps)
return std::string("Video Broadcast started SRT on port ") + std::to_string(port_);
}
-
-
void VideoBroadcast::terminate()
{
// send EOS
diff --git a/src/VideoBroadcast.h b/src/VideoBroadcast.h
index 4bd92e2..91f9558 100644
--- a/src/VideoBroadcast.h
+++ b/src/VideoBroadcast.h
@@ -1,20 +1,21 @@
#ifndef VIDEOBROADCAST_H
#define VIDEOBROADCAST_H
-#include "NetworkToolkit.h"
#include "FrameGrabber.h"
#include "StreamSource.h"
+#define BROADCAST_DEFAULT_PORT 7070
#define BROADCAST_FPS 30
class VideoBroadcast : public FrameGrabber
{
public:
- VideoBroadcast(int port = 7070);
+ VideoBroadcast(int port = BROADCAST_DEFAULT_PORT);
virtual ~VideoBroadcast() {}
static bool available();
+ inline int port() const { return port_; }
void stop() override;
std::string info() const override;
@@ -25,11 +26,10 @@ private:
// connection information
int port_;
- std::atomic stopped_;
// pipeline elements
static std::string srt_sink_;
- static std::string h264_encoder_;
+ static std::string srt_encoder_;
};
diff --git a/src/defines.h b/src/defines.h
index 9797d4e..b80f505 100644
--- a/src/defines.h
+++ b/src/defines.h
@@ -81,8 +81,8 @@
#define IMGUI_COLOR_LIGHT_OVERLAY IM_COL32(5, 5, 5, 50)
#define IMGUI_COLOR_CAPTURE 1.0, 0.55, 0.05
#define IMGUI_COLOR_RECORD 1.0, 0.05, 0.05
-#define IMGUI_COLOR_STREAM 0.05, 0.8, 1.0
-#define IMGUI_COLOR_BROADCAST 0.1, 0.5, 1.0
+#define IMGUI_COLOR_STREAM 0.1, 0.9, 1.0
+#define IMGUI_COLOR_BROADCAST 1.0, 0.9, 0.3
#define IMGUI_NOTIFICATION_DURATION 2.5f
#define IMGUI_TOOLTIP_TIMEOUT 80