mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-11 18:34:58 +01:00
More robust implementation of Video Broadcast
Testing GST features and using HW accelerated encoding if available
This commit is contained in:
@@ -114,17 +114,6 @@ const std::vector< std::pair<std::string, std::string> > NetworkToolkit::stream_
|
|||||||
{"vaapih264enc", "video/x-raw, format=NV12, framerate=30/1 ! queue max-size-buffers=10 ! vaapih264enc rate-control=cqp init-qp=26 ! video/x-h264, profile=(string)main ! rtph264pay aggregate-mode=1 ! udpsink name=sink"}
|
{"vaapih264enc", "video/x-raw, format=NV12, framerate=30/1 ! queue max-size-buffers=10 ! vaapih264enc rate-control=cqp init-qp=26 ! video/x-h264, profile=(string)main ! rtph264pay aggregate-mode=1 ! udpsink name=sink"}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const char* NetworkToolkit::broadcast_protocol_label[NetworkToolkit::BROADCAST_DEFAULT] = {
|
|
||||||
"SRT"
|
|
||||||
};
|
|
||||||
|
|
||||||
const std::vector<std::string> NetworkToolkit::broadcast_pipeline {
|
|
||||||
"x264enc tune=zerolatency ! video/x-h264, profile=high ! mpegtsmux ! srtsink uri=srt://:XXXX/ name=sink",
|
|
||||||
};
|
|
||||||
//"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! jpegenc idct-method=float ! rtpjpegpay ! rtpstreampay ! tcpserversink name=sink",
|
|
||||||
//"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! x264enc tune=\"zerolatency\" threads=2 ! rtph264pay ! rtpstreampay ! tcpserversink name=sink",
|
|
||||||
|
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
std::vector<std::string> ipstrings_;
|
std::vector<std::string> ipstrings_;
|
||||||
std::vector<unsigned long> iplongs_;
|
std::vector<unsigned long> iplongs_;
|
||||||
|
|||||||
@@ -66,13 +66,13 @@ struct StreamConfig {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum {
|
//typedef enum {
|
||||||
BROADCAST_SRT = 0,
|
// BROADCAST_SRT = 0,
|
||||||
BROADCAST_DEFAULT
|
// BROADCAST_DEFAULT
|
||||||
} BroadcastProtocol;
|
//} BroadcastProtocol;
|
||||||
|
|
||||||
extern const char* broadcast_protocol_label[BROADCAST_DEFAULT];
|
//extern const char* broadcast_protocol_label[BROADCAST_DEFAULT];
|
||||||
extern const std::vector<std::string> broadcast_pipeline;
|
//extern const std::vector<std::string> broadcast_pipeline;
|
||||||
|
|
||||||
std::string hostname();
|
std::string hostname();
|
||||||
std::vector<std::string> host_ips();
|
std::vector<std::string> host_ips();
|
||||||
|
|||||||
@@ -111,19 +111,19 @@ void Pattern::open( uint pattern, glm::ivec2 res )
|
|||||||
// if there is a XXXX parameter to enter
|
// if there is a XXXX parameter to enter
|
||||||
std::string::size_type xxxx = gstreamer_pattern.find("XXXX");
|
std::string::size_type xxxx = gstreamer_pattern.find("XXXX");
|
||||||
if (xxxx != std::string::npos)
|
if (xxxx != std::string::npos)
|
||||||
gstreamer_pattern = gstreamer_pattern.replace(xxxx, 4, std::to_string(res.x));
|
gstreamer_pattern.replace(xxxx, 4, std::to_string(res.x));
|
||||||
// if there is a YYYY parameter to enter
|
// if there is a YYYY parameter to enter
|
||||||
std::string::size_type yyyy = gstreamer_pattern.find("YYYY");
|
std::string::size_type yyyy = gstreamer_pattern.find("YYYY");
|
||||||
if (yyyy != std::string::npos)
|
if (yyyy != std::string::npos)
|
||||||
gstreamer_pattern = gstreamer_pattern.replace(yyyy, 4, std::to_string(res.y));
|
gstreamer_pattern.replace(yyyy, 4, std::to_string(res.y));
|
||||||
// if there is a XXX parameter to enter
|
// if there is a XXX parameter to enter
|
||||||
std::string::size_type xxx = gstreamer_pattern.find("XXX");
|
std::string::size_type xxx = gstreamer_pattern.find("XXX");
|
||||||
if (xxx != std::string::npos)
|
if (xxx != std::string::npos)
|
||||||
gstreamer_pattern = gstreamer_pattern.replace(xxx, 3, std::to_string(res.x/10));
|
gstreamer_pattern.replace(xxx, 3, std::to_string(res.x/10));
|
||||||
// if there is a YYY parameter to enter
|
// if there is a YYY parameter to enter
|
||||||
std::string::size_type yyy = gstreamer_pattern.find("YYY");
|
std::string::size_type yyy = gstreamer_pattern.find("YYY");
|
||||||
if (yyy != std::string::npos)
|
if (yyy != std::string::npos)
|
||||||
gstreamer_pattern = gstreamer_pattern.replace(yyy, 3, std::to_string(res.y/10));
|
gstreamer_pattern.replace(yyy, 3, std::to_string(res.y/10));
|
||||||
|
|
||||||
// remember if the pattern is to be updated once or animated
|
// remember if the pattern is to be updated once or animated
|
||||||
single_frame_ = !Pattern::patterns_[type_].animated;
|
single_frame_ = !Pattern::patterns_[type_].animated;
|
||||||
|
|||||||
@@ -3780,21 +3780,24 @@ void OutputPreview::Render()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// Broadcasting menu
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f));
|
if (VideoBroadcast::available()) {
|
||||||
// Stop broadcast menu (broadcaster already exists)
|
// Broadcasting menu
|
||||||
if (video_broadcaster_) {
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f));
|
||||||
if ( ImGui::MenuItem( ICON_FA_SQUARE " Stop Broadcast") )
|
// Stop broadcast menu (broadcaster already exists)
|
||||||
video_broadcaster_->stop();
|
if (video_broadcaster_) {
|
||||||
}
|
if ( ImGui::MenuItem( ICON_FA_SQUARE " Stop Broadcast") )
|
||||||
// start broadcast (broadcaster does not exists)
|
video_broadcaster_->stop();
|
||||||
else {
|
|
||||||
if ( ImGui::MenuItem( ICON_FA_GLOBE " Broadcast") ) {
|
|
||||||
video_broadcaster_ = new VideoBroadcast;
|
|
||||||
FrameGrabbing::manager().add(video_broadcaster_);
|
|
||||||
}
|
}
|
||||||
|
// start broadcast (broadcaster does not exists)
|
||||||
|
else {
|
||||||
|
if ( ImGui::MenuItem( ICON_FA_GLOBE " Broadcast") ) {
|
||||||
|
video_broadcaster_ = new VideoBroadcast(Settings::application.broadcast_port);
|
||||||
|
FrameGrabbing::manager().add(video_broadcaster_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor(1);
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor(1);
|
|
||||||
|
|
||||||
// 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));
|
||||||
|
|||||||
@@ -18,8 +18,52 @@
|
|||||||
#define BROADCAST_DEBUG
|
#define BROADCAST_DEBUG
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
std::string VideoBroadcast::srt_sink_;
|
||||||
|
std::string VideoBroadcast::h264_encoder_;
|
||||||
|
|
||||||
VideoBroadcast::VideoBroadcast(NetworkToolkit::BroadcastProtocol proto, int port): FrameGrabber(), protocol_(proto), port_(port), stopped_(false)
|
std::vector< std::pair<std::string, std::string> > pipeline_sink_ {
|
||||||
|
{"srtsink", "srtsink uri=srt://:XXXX/ name=sink"},
|
||||||
|
{"srtserversink", "srtserversink uri=srt://:XXXX/ name=sink"}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector< std::pair<std::string, std::string> > pipeline_encoder_ {
|
||||||
|
{"nvh264enc", "nvh264enc rc-mode=1 zerolatency=true ! video/x-h264, profile=high ! mpegtsmux ! "},
|
||||||
|
{"vaapih264enc", "vaapih264enc rate-control=cqp init-qp=26 ! video/x-h264, profile=high ! mpegtsmux ! "},
|
||||||
|
{"x264enc", "x264enc tune=zerolatency ! video/x-h264, profile=high ! mpegtsmux ! "}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool VideoBroadcast::available()
|
||||||
|
{
|
||||||
|
// test for installation on first run
|
||||||
|
static bool _tested = false;
|
||||||
|
if (!_tested) {
|
||||||
|
srt_sink_.clear();
|
||||||
|
for (auto config = pipeline_sink_.cbegin();
|
||||||
|
config != pipeline_sink_.cend() && srt_sink_.empty(); ++config) {
|
||||||
|
if ( GstToolkit::has_feature(config->first) ) {
|
||||||
|
srt_sink_ = config->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h264_encoder_.clear();
|
||||||
|
for (auto config = pipeline_encoder_.cbegin();
|
||||||
|
config != pipeline_encoder_.cend() && h264_encoder_.empty(); ++config) {
|
||||||
|
if ( GstToolkit::has_feature(config->first) ) {
|
||||||
|
h264_encoder_ = config->second;
|
||||||
|
if (config->first != pipeline_encoder_.back().first)
|
||||||
|
Log::Info("VideoBroadcast using hardware accelerated encoder (%s)", config->first.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// perform test only once
|
||||||
|
_tested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// video broadcast is installed if both srt and h264 are available
|
||||||
|
return (!srt_sink_.empty() && !h264_encoder_.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoBroadcast::VideoBroadcast(int port): FrameGrabber(), port_(port), stopped_(false)
|
||||||
{
|
{
|
||||||
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
|
||||||
|
|
||||||
@@ -29,25 +73,24 @@ std::string VideoBroadcast::init(GstCaps *caps)
|
|||||||
{
|
{
|
||||||
// ignore
|
// ignore
|
||||||
if (caps == nullptr)
|
if (caps == nullptr)
|
||||||
return std::string("Invalid caps");
|
return std::string("Video Broadcast : Invalid caps");
|
||||||
|
|
||||||
|
if (!VideoBroadcast::available())
|
||||||
|
return std::string("Video Broadcast : Not available (missing SRT or H264)");
|
||||||
|
|
||||||
// create a gstreamer pipeline
|
// create a gstreamer pipeline
|
||||||
std::string description = "appsrc name=src ! videoconvert ! queue ! ";
|
std::string description = "appsrc name=src ! videoconvert ! queue ! ";
|
||||||
|
|
||||||
// choose pipeline for protocol
|
// complement pipeline with encoder and sink
|
||||||
if (protocol_ == NetworkToolkit::BROADCAST_DEFAULT)
|
description += VideoBroadcast::h264_encoder_;
|
||||||
protocol_ = NetworkToolkit::BROADCAST_SRT;
|
description += VideoBroadcast::srt_sink_;
|
||||||
description += NetworkToolkit::broadcast_pipeline[protocol_];
|
|
||||||
|
|
||||||
// setup streaming pipeline
|
// change the placeholder to include the broadcast port
|
||||||
if (protocol_ == NetworkToolkit::BROADCAST_SRT) {
|
std::string::size_type xxxx = description.find("XXXX");
|
||||||
// change the pipeline to include the broadcast port
|
if (xxxx != std::string::npos)
|
||||||
std::string::size_type xxxx = description.find("XXXX");
|
description.replace(xxxx, 4, std::to_string(port_));
|
||||||
if (xxxx != std::string::npos)
|
else
|
||||||
description = description.replace(xxxx, 4, std::to_string(port_));
|
return std::string("Video Broadcast : Failed to configure broadcast port.");
|
||||||
else
|
|
||||||
return std::string("Video Broadcast : Failed to configure broadcast port.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse pipeline descriptor
|
// parse pipeline descriptor
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
@@ -58,14 +101,11 @@ std::string VideoBroadcast::init(GstCaps *caps)
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup streaming sink
|
|
||||||
if (protocol_ == NetworkToolkit::BROADCAST_SRT) {
|
|
||||||
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
|
|
||||||
"latency", 500,
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Configure options
|
// TODO Configure options
|
||||||
|
// setup SRT streaming sink properties
|
||||||
|
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
|
||||||
|
"latency", 500,
|
||||||
|
NULL);
|
||||||
|
|
||||||
// setup custom app source
|
// setup custom app source
|
||||||
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
|
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
|
||||||
@@ -117,7 +157,7 @@ std::string VideoBroadcast::init(GstCaps *caps)
|
|||||||
// all good
|
// all good
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
|
|
||||||
return std::string("Video Broadcast started.");
|
return std::string("Video Broadcast started SRT on port ") + std::to_string(port_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -127,7 +167,7 @@ void VideoBroadcast::terminate()
|
|||||||
// send EOS
|
// send EOS
|
||||||
gst_app_src_end_of_stream (src_);
|
gst_app_src_end_of_stream (src_);
|
||||||
|
|
||||||
Log::Notify("Broadcast terminated after %s s.",
|
Log::Notify("Video Broadcast terminated after %s s.",
|
||||||
GstToolkit::time_to_string(duration_).c_str());
|
GstToolkit::time_to_string(duration_).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,12 +187,10 @@ std::string VideoBroadcast::info() const
|
|||||||
|
|
||||||
if (!initialized_)
|
if (!initialized_)
|
||||||
ret << "Starting";
|
ret << "Starting";
|
||||||
else if (active_) {
|
else if (active_)
|
||||||
ret << NetworkToolkit::broadcast_protocol_label[protocol_];
|
ret << "Streaming SRT on Port " << port_;
|
||||||
ret << " ( Port " << port_ << " )";
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
ret << "Terminated";
|
ret << "Terminated";
|
||||||
|
|
||||||
return ret.str();
|
return ret.str();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ class VideoBroadcast : public FrameGrabber
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
VideoBroadcast(NetworkToolkit::BroadcastProtocol p = NetworkToolkit::BROADCAST_DEFAULT, int port = 8888);
|
VideoBroadcast(int port = 8888);
|
||||||
virtual ~VideoBroadcast() {}
|
virtual ~VideoBroadcast() {}
|
||||||
|
|
||||||
|
static bool available();
|
||||||
|
|
||||||
void stop() override;
|
void stop() override;
|
||||||
std::string info() const override;
|
std::string info() const override;
|
||||||
|
|
||||||
@@ -21,9 +23,12 @@ private:
|
|||||||
void terminate() override;
|
void terminate() override;
|
||||||
|
|
||||||
// connection information
|
// connection information
|
||||||
NetworkToolkit::BroadcastProtocol protocol_;
|
|
||||||
int port_;
|
int port_;
|
||||||
std::atomic<bool> stopped_;
|
std::atomic<bool> stopped_;
|
||||||
|
|
||||||
|
// pipeline elements
|
||||||
|
static std::string srt_sink_;
|
||||||
|
static std::string h264_encoder_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VIDEOBROADCAST_H
|
#endif // VIDEOBROADCAST_H
|
||||||
|
|||||||
Reference in New Issue
Block a user