UI Integration of output to SRT, Shmdata and V4L2

Improved user interface and messages for the activation of Output streaming with SRT, shared memory, or loopback camera with V4L2 under linux.
This commit is contained in:
Bruno Herbelin
2022-12-09 20:10:37 +01:00
parent c5884ec498
commit 941275a1b9
12 changed files with 340 additions and 345 deletions

View File

@@ -17,25 +17,179 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
**/ **/
// Desktop OpenGL function loader #include <vector>
#include <glad/glad.h> #include <sstream>
#include <iostream>
#include <stdexcept>
// gstreamer // gstreamer
#include <gst/gstformat.h> #include <gst/gstformat.h>
#include <gst/video/video.h> #include <gst/video/video.h>
#include <gst/app/gstappsrc.h>
#include <gst/pbutils/pbutils.h>
#include "defines.h" #include "defines.h"
#include "Settings.h"
#include "GstToolkit.h" #include "GstToolkit.h"
#include "SystemToolkit.h" #include "SystemToolkit.h"
#include "FrameBuffer.h"
#include "Log.h" #include "Log.h"
#include "Loopback.h" #include "Loopback.h"
bool Loopback::system_loopback_initialized = false; std::string Loopback::loopback_sink_;
#if defined(LINUX) std::vector< std::pair<std::string, std::string> > 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 v4l2src device=/dev/video10 ! videoconvert ! autovideosink
* $ gst-launch-1.0 videotestsrc ! v4l2sink device=/dev/video10 * $ gst-launch-1.0 videotestsrc ! v4l2sink device=/dev/video10
*/ */
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
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());
}

View File

@@ -1,30 +1,33 @@
#ifndef LOOPBACK_H #ifndef LOOPBACK_H
#define LOOPBACK_H #define LOOPBACK_H
#include <vector>
#include <gst/pbutils/pbutils.h>
#include <gst/app/gstappsrc.h>
#include "FrameGrabber.h" #include "FrameGrabber.h"
#define LOOPBACK_DEFAULT_DEVICE 10
#define LOOPBACK_FPS 30
class Loopback : public FrameGrabber class Loopback : public FrameGrabber
{ {
static std::string system_loopback_pipeline; public:
static std::string system_loopback_name; Loopback(int deviceid = LOOPBACK_DEFAULT_DEVICE);
static bool system_loopback_initialized; 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; std::string init(GstCaps *caps) override;
void terminate() override; void terminate() override;
public: // device information
int loopback_device_;
Loopback();
static bool systemLoopbackInitialized();
static bool initializeSystemLoopback();
static std::string loopback_sink_;
}; };

View File

@@ -127,6 +127,8 @@ void Settings::Save(uint64_t runtime)
applicationNode->SetAttribute("pannel_history_mode", application.pannel_current_session_mode); applicationNode->SetAttribute("pannel_history_mode", application.pannel_current_session_mode);
applicationNode->SetAttribute("stream_protocol", application.stream_protocol); applicationNode->SetAttribute("stream_protocol", application.stream_protocol);
applicationNode->SetAttribute("broadcast_port", application.broadcast_port); 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); pRoot->InsertEndChild(applicationNode);
// Widgets // Widgets
@@ -398,6 +400,12 @@ void Settings::Load()
applicationNode->QueryIntAttribute("pannel_history_mode", &application.pannel_current_session_mode); applicationNode->QueryIntAttribute("pannel_history_mode", &application.pannel_current_session_mode);
applicationNode->QueryIntAttribute("stream_protocol", &application.stream_protocol); applicationNode->QueryIntAttribute("stream_protocol", &application.stream_protocol);
applicationNode->QueryIntAttribute("broadcast_port", &application.broadcast_port); 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 // Widgets

View File

@@ -263,6 +263,8 @@ struct Application
int stream_protocol; int stream_protocol;
int broadcast_port; int broadcast_port;
KnownHosts recentSRT; KnownHosts recentSRT;
int loopback_camera;
std::string shm_socket_path;
// Settings of widgets // Settings of widgets
WidgetsConfig widget; WidgetsConfig widget;
@@ -317,9 +319,11 @@ struct Application
show_tooptips = true; show_tooptips = true;
accept_connections = false; accept_connections = false;
stream_protocol = 0; stream_protocol = 0;
broadcast_port = 7070; broadcast_port = 0;
recentSRT.protocol = "srt://"; recentSRT.protocol = "srt://";
recentSRT.default_host = { "127.0.0.1", "7070"}; recentSRT.default_host = { "127.0.0.1", "7070"};
loopback_camera = 0;
shm_socket_path = "";
pannel_current_session_mode = 0; pannel_current_session_mode = 0;
current_view = 1; current_view = 1;
current_workspace= 1; current_workspace= 1;

View File

@@ -27,7 +27,6 @@
#include <gst/pbutils/pbutils.h> #include <gst/pbutils/pbutils.h>
#include "Log.h" #include "Log.h"
#include "Settings.h"
#include "GstToolkit.h" #include "GstToolkit.h"
#include "ShmdataBroadcast.h" #include "ShmdataBroadcast.h"
@@ -49,11 +48,12 @@ bool ShmdataBroadcast::available()
return _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) std::string ShmdataBroadcast::init(GstCaps *caps)
@@ -105,7 +105,7 @@ std::string ShmdataBroadcast::init(GstCaps *caps)
GstCaps *tmp = gst_caps_copy( caps ); GstCaps *tmp = gst_caps_copy( caps );
GValue v = { 0, }; GValue v = { 0, };
g_value_init (&v, GST_TYPE_FRACTION); 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); gst_caps_set_value(tmp, "framerate", &v);
g_value_unset (&v); g_value_unset (&v);
@@ -141,22 +141,14 @@ std::string ShmdataBroadcast::init(GstCaps *caps)
void ShmdataBroadcast::terminate() void ShmdataBroadcast::terminate()
{ {
// send EOS // force finished
gst_app_src_end_of_stream (src_); endofstream_ = true;
active_ = false;
Log::Notify("Shared Memory terminated after %s s.", Log::Notify("Shared Memory terminated after %s s.",
GstToolkit::time_to_string(duration_).c_str()); 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 std::string ShmdataBroadcast::info() const
{ {

View File

@@ -3,16 +3,19 @@
#include "FrameGrabber.h" #include "FrameGrabber.h"
#define SHMDATA_DEFAULT_PATH "/tmp/shmdata_vimix"
#define SHMDATA_FPS 30
class ShmdataBroadcast : public FrameGrabber class ShmdataBroadcast : public FrameGrabber
{ {
public: public:
ShmdataBroadcast(const std::string &socketpath = "/tmp/shmdata_vimix"); ShmdataBroadcast(const std::string &socketpath = SHMDATA_DEFAULT_PATH);
virtual ~ShmdataBroadcast() {} virtual ~ShmdataBroadcast() {}
static bool available(); static bool available();
inline std::string socket_path() const { return socket_path_; } inline std::string socket_path() const { return socket_path_; }
void stop() override;
std::string info() const override; std::string info() const override;
private: private:
@@ -21,7 +24,6 @@ private:
// connection information // connection information
std::string socket_path_; std::string socket_path_;
std::atomic<bool> stopped_;
static std::string shmdata_sink_; static std::string shmdata_sink_;
}; };

View File

@@ -23,7 +23,7 @@
#define ICON_SOURCE_RENDER 0, 2 #define ICON_SOURCE_RENDER 0, 2
#define ICON_SOURCE_CLONE 9, 2 #define ICON_SOURCE_CLONE 9, 2
#define ICON_SOURCE_GSTREAMER 16, 16 #define ICON_SOURCE_GSTREAMER 16, 16
#define ICON_SOURCE_SRT 11, 8 #define ICON_SOURCE_SRT 15, 5
#define ICON_SOURCE 13, 11 #define ICON_SOURCE 13, 11
class Visitor; class Visitor;

View File

@@ -940,7 +940,8 @@ void UserInterface::showMenuEdit()
navigator.hidePannel(); navigator.hidePannel();
} }
if (ImGui::MenuItem( MENU_PASTE, SHORTCUT_PASTE, false, has_clipboard)) { if (ImGui::MenuItem( MENU_PASTE, SHORTCUT_PASTE, false, has_clipboard)) {
Mixer::manager().paste(clipboard); if (clipboard)
Mixer::manager().paste(clipboard);
navigator.hidePannel(); navigator.hidePannel();
} }
if (ImGui::MenuItem( MENU_SELECTALL, SHORTCUT_SELECTALL)) { if (ImGui::MenuItem( MENU_SELECTALL, SHORTCUT_SELECTALL)) {
@@ -3811,11 +3812,8 @@ void SourceController::DrawButtonBar(ImVec2 bottom, float width)
/// ///
OutputPreview::OutputPreview() : WorkspaceWindow("OutputPreview"), 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"); recordFolderDialog = new DialogToolkit::OpenFolderDialog("Recording Location");
} }
@@ -3864,14 +3862,11 @@ void OutputPreview::Update()
video_recorder_->stop(); 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**) &video_broadcaster_);
FrameGrabbing::manager().verify( (FrameGrabber**) &shm_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) { 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_) { if (video_broadcaster_) {
video_broadcaster_->stop(); video_broadcaster_->stop();
} }
else { 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_); FrameGrabbing::manager().add(video_broadcaster_);
} }
} }
@@ -3919,17 +3916,40 @@ void OutputPreview::ToggleSharedMemory()
shm_broadcaster_->stop(); shm_broadcaster_->stop();
} }
else { 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_); 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() void OutputPreview::Render()
{ {
#if defined(LINUX)
bool openInitializeSystemLoopback = false; bool openInitializeSystemLoopback = false;
#endif
FrameBuffer *output = Mixer::manager().session()->frame(); FrameBuffer *output = Mixer::manager().session()->frame();
if (output) if (output)
@@ -4090,25 +4110,6 @@ void OutputPreview::Render()
} }
if (ImGui::BeginMenu(ICON_FA_WIFI " Stream")) 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 // Stream sharing menu
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.9f)); 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) ) { 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: // list active streams:
std::vector<std::string> ls = Streaming::manager().listStreams(); std::vector<std::string> ls = Streaming::manager().listStreams();
// Broadcasting menu // Broadcasting menu
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.9f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.9f));
if (VideoBroadcast::available()) { if (VideoBroadcast::available()) {
bool enabled = (video_broadcaster_!=nullptr); if ( ImGui::MenuItem( ICON_FA_GLOBE " SRT Broadcast", NULL, videoBroadcastEnabled()) )
if ( ImGui::MenuItem( ICON_FA_PODCAST " SRT Broadcast", NULL, &enabled) ) ToggleVideoBroadcast();
ToggleBroadcast();
} }
// Shared Memory menu // Shared Memory menu
if (ShmdataBroadcast::available()) { if (ShmdataBroadcast::available()) {
bool enabled = (shm_broadcaster_!=nullptr); if ( ImGui::MenuItem( ICON_FA_SHARE_ALT " Shared Memory", NULL, sharedMemoryEnabled()) )
if ( ImGui::MenuItem( ICON_FA_PROJECT_DIAGRAM " Shared Memory", NULL, &enabled) )
ToggleSharedMemory(); ToggleSharedMemory();
} }
// Loopback camera menu
if (Loopback::available()) {
if ( ImGui::MenuItem( ICON_FA_VIDEO " Loopback Camera", NULL, loopbackCameraEnabled()) )
openInitializeSystemLoopback = ToggleLoopbackCamera();
}
ImGui::PopStyleColor(1); ImGui::PopStyleColor(1);
// Display list of active stream // Display list of active stream
if (ls.size()>0 || video_broadcaster_ || shm_broadcaster_) { if (ls.size()>0 || videoBroadcastEnabled() || sharedMemoryEnabled() || loopbackCameraEnabled()) {
ImGui::Separator(); ImGui::Separator();
ImGui::MenuItem("Active streaming", nullptr, false, false); ImGui::MenuItem("Active streams", nullptr, false, false);
// First the list of peer 2 peer // First the list of peer 2 peer
for (auto it = ls.begin(); it != ls.end(); ++it) for (auto it = ls.begin(); it != ls.end(); ++it)
ImGui::Text(" %s ", (*it).c_str() ); ImGui::Text(" %s ", (*it).c_str() );
// Second the SRT // SRT broadcast description
if (video_broadcaster_) { if (videoBroadcastEnabled()) {
ImGui::Text(" %s ", video_broadcaster_->info().c_str()); ImGui::Text(" %s ", video_broadcaster_->info().c_str());
// copy text icon to give user the srt link to connect to // copy text icon to give user the srt link to connect to
ImVec2 draw_pos = ImGui::GetCursorPos(); ImVec2 draw_pos = ImGui::GetCursorPos();
ImGui::SetCursorPos(draw_pos + ImVec2(ImGui::GetContentRegionAvailWidth() - 1.2 * ImGui::GetTextLineHeightWithSpacing(), -0.8 * ImGui::GetFrameHeight()) ); 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); ImGui::SetCursorPos(draw_pos);
} }
// Third the SHMdata // Shared memory broadcast description
if (shm_broadcaster_) { if (sharedMemoryEnabled()) {
ImGui::Text(" %s ", shm_broadcaster_->info().c_str()); ImGui::Text(" %s ", shm_broadcaster_->info().c_str());
// copy text icon to give user the socket path to connect to
// copy text icon to give user the socket path to connec to
ImVec2 draw_pos = ImGui::GetCursorPos(); ImVec2 draw_pos = ImGui::GetCursorPos();
ImGui::SetCursorPos(draw_pos + ImVec2(ImGui::GetContentRegionAvailWidth() - 1.2 * ImGui::GetTextLineHeightWithSpacing(), -0.8 * ImGui::GetFrameHeight()) ); 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())) if (ImGuiToolkit::IconButton( ICON_FA_COPY, shm_broadcaster_->socket_path().c_str()))
ImGui::SetClipboardText(shm_broadcaster_->socket_path().c_str()); ImGui::SetClipboardText(shm_broadcaster_->socket_path().c_str());
ImGui::SetCursorPos(draw_pos); 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(); ImGui::EndMenu();
@@ -4201,88 +4215,83 @@ void OutputPreview::Render()
ImGui::SetCursorScreenPos(draw_pos + ImVec2(imagesize.x - r, 6)); ImGui::SetCursorScreenPos(draw_pos + ImVec2(imagesize.x - r, 6));
ImGui::Text(ICON_FA_INFO_CIRCLE); ImGui::Text(ICON_FA_INFO_CIRCLE);
bool drawoverlay = ImGui::IsItemHovered(); bool drawoverlay = ImGui::IsItemHovered();
// icon indicators
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
// recording indicator // recording indicator
if (video_recorder_) if (video_recorder_)
{ {
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + r)); 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::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f));
ImGui::Text(ICON_FA_CIRCLE " %s", video_recorder_->info().c_str() ); ImGui::Text(ICON_FA_CIRCLE " %s", video_recorder_->info().c_str() );
ImGui::PopStyleColor(1); ImGui::PopStyleColor(1);
ImGui::PopFont();
} }
else if (!_video_recorders.empty()) else if (!_video_recorders.empty())
{ {
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + r)); ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + r));
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
static double anim = 0.f; static double anim = 0.f;
double a = sin(anim+=0.104); // 2 * pi / 60fps double a = sin(anim+=0.104); // 2 * pi / 60fps
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, a)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, a));
ImGui::Text(ICON_FA_CIRCLE); ImGui::Text(ICON_FA_CIRCLE);
ImGui::PopStyleColor(1); ImGui::PopStyleColor(1);
ImGui::PopFont();
} }
// broadcast indicator // broadcast indicator
float vertical = r;
if (video_broadcaster_) if (video_broadcaster_)
{ {
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + r)); ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + vertical));
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
if (video_broadcaster_->busy()) if (video_broadcaster_->busy())
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f));
else else
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.4f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.4f));
ImGui::Text(ICON_FA_PODCAST); ImGui::Text(ICON_FA_GLOBE);
ImGui::PopStyleColor(1); ImGui::PopStyleColor(1);
ImGui::PopFont(); vertical += 2.f * r;
} }
// shmdata indicator // shmdata indicator
if (shm_broadcaster_) if (shm_broadcaster_)
{ {
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 4.5f * r, draw_pos.y + r)); ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + vertical));
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
if (shm_broadcaster_->busy()) if (shm_broadcaster_->busy())
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f));
else else
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.4f)); 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::PopStyleColor(1);
ImGui::PopFont();
} }
// streaming indicator // streaming indicator
if (Settings::application.accept_connections) if (Settings::application.accept_connections)
{ {
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.4f * r, draw_pos.y + imagesize.y - 2.f*r)); 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()) if ( Streaming::manager().busy())
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.8f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.8f));
else else
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.4f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.4f));
ImGui::Text(ICON_FA_SHARE_ALT_SQUARE); ImGui::Text(ICON_FA_SHARE_ALT_SQUARE);
ImGui::PopStyleColor(1); ImGui::PopStyleColor(1);
ImGui::PopFont();
} }
// OUTPUT DISABLED indicator // OUTPUT DISABLED indicator
if (Settings::application.render.disabled) if (Settings::application.render.disabled)
{ {
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + imagesize.y - 2.f*r)); 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::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f));
ImGui::Text(ICON_FA_EYE_SLASH); ImGui::Text(ICON_FA_EYE_SLASH);
ImGui::PopStyleColor(1); ImGui::PopStyleColor(1);
ImGui::PopFont();
} }
#if defined(LINUX) ImGui::PopFont();
// 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
/// ///
/// Info overlay over image /// Info overlay over image
@@ -4292,64 +4301,66 @@ void OutputPreview::Render()
ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImDrawList* draw_list = ImGui::GetWindowDrawList();
float h = 1.f; float h = 1.f;
h += (Settings::application.accept_connections ? 1.f : 0.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); draw_list->AddRectFilled(draw_pos, ImVec2(draw_pos.x + imagesize.x, draw_pos.y + h * r), IMGUI_COLOR_OVERLAY);
ImGui::SetCursorScreenPos(draw_pos); ImGui::SetCursorScreenPos(draw_pos);
ImGui::Text(" " ICON_FA_TV " %d x %d px, %.d fps", output->width(), output->height(), int(Mixer::manager().fps()) ); ImGui::Text(" " ICON_FA_TV " %d x %d px, %.d fps", output->width(), output->height(), int(Mixer::manager().fps()) );
if (Settings::application.accept_connections) 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(), Connection::manager().info().name.c_str(),
Streaming::manager().listStreams().size() ); 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(); 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")) if (openInitializeSystemLoopback && !ImGui::IsPopupOpen("Initialize System Loopback"))
ImGui::OpenPopup("Initialize System Loopback"); ImGui::OpenPopup("Initialize System Loopback");
if (ImGui::BeginPopupModal("Initialize System Loopback", NULL, ImGuiWindowFlags_AlwaysAutoResize)) if (ImGui::BeginPopupModal("Initialize System Loopback", NULL, ImGuiWindowFlags_AlwaysAutoResize))
{ {
#if defined(LINUX)
int w = 600; int w = 600;
ImGui::Text("In order to enable the video4linux camera loopback,\n" ImGui::Text("In order to enable the video4linux camera loopback,\n"
"'v4l2loopack' has to be installed and initialized on your machine\n\n" "'v4l2loopack' has to be installed and initialized on your machine");
"To do so, the following commands should be executed (admin rights):\n"); 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]; static char dummy_str[512];
sprintf(dummy_str, "sudo apt install v4l2loopback-dkms"); 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::SetNextItemWidth(600-40);
ImGui::InputText("##cmd1", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); ImGui::InputText("##cmd1", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly);
ImGui::SameLine(); ImGui::SameLine();
ImGui::PushID(358794); 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::SetClipboardText(dummy_str);
ImGui::PopID(); ImGui::PopID();
sprintf(dummy_str, "sudo modprobe v4l2loopback exclusive_caps=1 video_nr=10 card_label=\"vimix loopback\""); sprintf(dummy_str, "sudo modprobe v4l2loopback exclusive_caps=1 video_nr=%d"
ImGui::Text("Initialize v4l2loopack (after reboot):"); " card_label=\"vimix loopback\"" , Settings::application.loopback_camera);
ImGui::NewLine();
ImGui::Text("Initialize v4l2loopack:");
ImGui::SetNextItemWidth(600-40); ImGui::SetNextItemWidth(600-40);
ImGui::InputText("##cmd2", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); ImGui::InputText("##cmd2", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly);
ImGui::SameLine(); ImGui::SameLine();
ImGui::PushID(899872); 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::SetClipboardText(dummy_str);
ImGui::PopID(); ImGui::PopID();
ImGui::Separator();
ImGui::NewLine();
ImGui::SetItemDefaultFocus(); ImGui::SetItemDefaultFocus();
if (ImGui::Button("Ok, I'll do this in a terminal and try again later.", ImVec2(w, 0)) ) { if (ImGui::Button("Ok, I'll do this in a terminal and try again later.", ImVec2(w, 0)) ) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
} }
#endif
ImGui::EndPopup(); ImGui::EndPopup();
} }
#endif
} }
/// ///

View File

@@ -111,6 +111,7 @@ class FrameGrabber;
class VideoRecorder; class VideoRecorder;
class VideoBroadcast; class VideoBroadcast;
class ShmdataBroadcast; class ShmdataBroadcast;
class Loopback;
class SourcePreview { class SourcePreview {
@@ -332,14 +333,11 @@ class OutputPreview : public WorkspaceWindow
VideoRecorder *video_recorder_; VideoRecorder *video_recorder_;
VideoBroadcast *video_broadcaster_; VideoBroadcast *video_broadcaster_;
ShmdataBroadcast *shm_broadcaster_; ShmdataBroadcast *shm_broadcaster_;
Loopback *loopback_broadcaster_;
// delayed trigger for recording // delayed trigger for recording
std::vector< std::future<VideoRecorder *> > _video_recorders; std::vector< std::future<VideoRecorder *> > _video_recorders;
#if defined(LINUX)
FrameGrabber *webcam_emulator_;
#endif
// dialog to select record location // dialog to select record location
DialogToolkit::OpenFolderDialog *recordFolderDialog; DialogToolkit::OpenFolderDialog *recordFolderDialog;
@@ -349,11 +347,14 @@ public:
void ToggleRecord(bool save_and_continue = false); void ToggleRecord(bool save_and_continue = false);
inline bool isRecording() const { return video_recorder_ != nullptr; } inline bool isRecording() const { return video_recorder_ != nullptr; }
void ToggleBroadcast(); void ToggleVideoBroadcast();
inline bool isBroadcasting() const { return video_broadcaster_ != nullptr; } inline bool videoBroadcastEnabled() const { return video_broadcaster_ != nullptr; }
void ToggleSharedMemory(); 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 Render();
void setVisible(bool on); void setVisible(bool on);

View File

@@ -17,6 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
**/ **/
#include <vector>
#include <sstream> #include <sstream>
#include <iostream> #include <iostream>
@@ -27,7 +28,6 @@
#include <gst/pbutils/pbutils.h> #include <gst/pbutils/pbutils.h>
#include "Log.h" #include "Log.h"
#include "Settings.h"
#include "GstToolkit.h" #include "GstToolkit.h"
#include "NetworkToolkit.h" #include "NetworkToolkit.h"
@@ -38,14 +38,14 @@
#endif #endif
std::string VideoBroadcast::srt_sink_; 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", "srtsink",
"srtserversink" "srtserversink"
}; };
std::vector< std::pair<std::string, std::string> > pipeline_encoder_ { std::vector< std::pair<std::string, std::string> > srt_encoder_alternatives_ {
{"nvh264enc", "nvh264enc zerolatency=true rc-mode=cbr-ld-hq bitrate=4000 ! "}, {"nvh264enc", "nvh264enc zerolatency=true rc-mode=cbr-ld-hq bitrate=4000 ! "},
{"vaapih264enc", "vaapih264enc rate-control=cqp init-qp=26 ! "}, {"vaapih264enc", "vaapih264enc rate-control=cqp init-qp=26 ! "},
{"x264enc", "x264enc tune=zerolatency ! "} {"x264enc", "x264enc tune=zerolatency ! "}
@@ -57,10 +57,10 @@ bool VideoBroadcast::available()
static bool _tested = false; static bool _tested = false;
if (!_tested) { if (!_tested) {
srt_sink_.clear(); srt_sink_.clear();
h264_encoder_.clear(); srt_encoder_.clear();
for (auto config = pipeline_sink_.cbegin(); for (auto config = srt_sink_alternatives_.cbegin();
config != pipeline_sink_.cend() && srt_sink_.empty(); ++config) { config != srt_sink_alternatives_.cend() && srt_sink_.empty(); ++config) {
if ( GstToolkit::has_feature(*config) ) { if ( GstToolkit::has_feature(*config) ) {
srt_sink_ = *config; srt_sink_ = *config;
} }
@@ -68,11 +68,11 @@ bool VideoBroadcast::available()
if (!srt_sink_.empty()) if (!srt_sink_.empty())
{ {
for (auto config = pipeline_encoder_.cbegin(); for (auto config = srt_encoder_alternatives_.cbegin();
config != pipeline_encoder_.cend() && h264_encoder_.empty(); ++config) { config != srt_encoder_alternatives_.cend() && srt_encoder_.empty(); ++config) {
if ( GstToolkit::has_feature(config->first) ) { if ( GstToolkit::has_feature(config->first) ) {
h264_encoder_ = config->second; srt_encoder_ = config->second;
if (config->first != pipeline_encoder_.back().first) if (config->first != srt_encoder_alternatives_.back().first)
Log::Info("Video Broadcast uses hardware-accelerated encoder (%s)", config->first.c_str()); 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 // 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 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) std::string VideoBroadcast::init(GstCaps *caps)
@@ -106,7 +108,7 @@ std::string VideoBroadcast::init(GstCaps *caps)
std::string description = "appsrc name=src ! videoconvert ! "; std::string description = "appsrc name=src ! videoconvert ! ";
// complement pipeline with encoder // complement pipeline with encoder
description += VideoBroadcast::h264_encoder_; description += VideoBroadcast::srt_encoder_;
description += "video/x-h264, profile=high ! queue ! h264parse config-interval=-1 ! mpegtsmux ! "; description += "video/x-h264, profile=high ! queue ! h264parse config-interval=-1 ! mpegtsmux ! ";
// complement pipeline with sink // 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_); return std::string("Video Broadcast started SRT on port ") + std::to_string(port_);
} }
void VideoBroadcast::terminate() void VideoBroadcast::terminate()
{ {
// send EOS // send EOS

View File

@@ -1,20 +1,21 @@
#ifndef VIDEOBROADCAST_H #ifndef VIDEOBROADCAST_H
#define VIDEOBROADCAST_H #define VIDEOBROADCAST_H
#include "NetworkToolkit.h"
#include "FrameGrabber.h" #include "FrameGrabber.h"
#include "StreamSource.h" #include "StreamSource.h"
#define BROADCAST_DEFAULT_PORT 7070
#define BROADCAST_FPS 30 #define BROADCAST_FPS 30
class VideoBroadcast : public FrameGrabber class VideoBroadcast : public FrameGrabber
{ {
public: public:
VideoBroadcast(int port = 7070); VideoBroadcast(int port = BROADCAST_DEFAULT_PORT);
virtual ~VideoBroadcast() {} virtual ~VideoBroadcast() {}
static bool available(); static bool available();
inline int port() const { return port_; }
void stop() override; void stop() override;
std::string info() const override; std::string info() const override;
@@ -25,11 +26,10 @@ private:
// connection information // connection information
int port_; int port_;
std::atomic<bool> stopped_;
// pipeline elements // pipeline elements
static std::string srt_sink_; static std::string srt_sink_;
static std::string h264_encoder_; static std::string srt_encoder_;
}; };

View File

@@ -81,8 +81,8 @@
#define IMGUI_COLOR_LIGHT_OVERLAY IM_COL32(5, 5, 5, 50) #define IMGUI_COLOR_LIGHT_OVERLAY IM_COL32(5, 5, 5, 50)
#define IMGUI_COLOR_CAPTURE 1.0, 0.55, 0.05 #define IMGUI_COLOR_CAPTURE 1.0, 0.55, 0.05
#define IMGUI_COLOR_RECORD 1.0, 0.05, 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_STREAM 0.1, 0.9, 1.0
#define IMGUI_COLOR_BROADCAST 0.1, 0.5, 1.0 #define IMGUI_COLOR_BROADCAST 1.0, 0.9, 0.3
#define IMGUI_NOTIFICATION_DURATION 2.5f #define IMGUI_NOTIFICATION_DURATION 2.5f
#define IMGUI_TOOLTIP_TIMEOUT 80 #define IMGUI_TOOLTIP_TIMEOUT 80