mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-17 13:19:59 +01:00
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:
346
src/Loopback.cpp
346
src/Loopback.cpp
@@ -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());
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user