Implemented Broadcast

Initial implementation of SRT streaming as listener. Changed stream terminology to distinguish network broadcasting and network sharing in local network. Updated user settings accordingly.
This commit is contained in:
Bruno Herbelin
2022-01-23 01:10:10 +01:00
parent 2b3696aab1
commit 5c3c26851c
13 changed files with 326 additions and 73 deletions

View File

@@ -341,6 +341,7 @@ set(VMIX_SRCS
Overlay.cpp
Metronome.cpp
ControlManager.cpp
VideoBroadcast.cpp
)

View File

@@ -847,7 +847,7 @@ void ImGuiVisitor::visit (NetworkSource& s)
{
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
ImGui::SameLine(0, IMGUI_SAME_LINE);
ImGui::Text("Network stream");
ImGui::Text("Shared stream");
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.9f));
ImGui::Text("%s", s.connection().c_str());

View File

@@ -274,7 +274,7 @@ void InfoVisitor::visit (NetworkSource& s)
else {
oss << s.connection() << std::endl;
oss << NetworkToolkit::stream_protocol_label[ns->protocol()];
oss << ", IP " << ns->serverAddress() << std::endl;
oss << " shared from IP " << ns->serverAddress() << std::endl;
oss << ns->resolution().x << " x " << ns->resolution().y << " ";
}

View File

@@ -235,7 +235,7 @@ void NetworkStream::update()
if (connected_) {
#ifdef NETWORK_DEBUG
Log::Info("Creating Network Stream %d (%d x %d)", config_.port, config_.width, config_.height);
Log::Info("Creating Shared Stream %d (%d x %d)", config_.port, config_.width, config_.height);
#endif
// prepare pipeline parameter with port given in config_
std::string parameter = std::to_string(config_.port);
@@ -352,6 +352,6 @@ glm::ivec2 NetworkSource::icon() const
std::string NetworkSource::info() const
{
return std::string("connected to '") + connection_name_ + "'";
return std::string("shared by '") + connection_name_ + "'";
}

View File

@@ -120,7 +120,7 @@ const char* NetworkToolkit::broadcast_protocol_label[NetworkToolkit::BROADCAST_D
};
const std::vector<std::string> NetworkToolkit::broadcast_pipeline {
"videoconvert ! x264enc tune=zerolatency ! video/x-h264, profile=high ! mpegtsmux ! srtsink uri=srt://:XXXX/",
"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",

View File

@@ -110,7 +110,8 @@ void Settings::Save(uint64_t runtime)
applicationNode->SetAttribute("show_tooptips", application.show_tooptips);
applicationNode->SetAttribute("accept_connections", application.accept_connections);
applicationNode->SetAttribute("pannel_history_mode", application.pannel_current_session_mode);
applicationNode->SetAttribute("stream_low_bandwidth", application.stream_low_bandwidth);
applicationNode->SetAttribute("stream_protocol", application.stream_protocol);
applicationNode->SetAttribute("broadcast_port", application.broadcast_port);
pRoot->InsertEndChild(applicationNode);
// Widgets
@@ -354,7 +355,8 @@ void Settings::Load()
applicationNode->QueryBoolAttribute("show_tooptips", &application.show_tooptips);
applicationNode->QueryBoolAttribute("accept_connections", &application.accept_connections);
applicationNode->QueryIntAttribute("pannel_history_mode", &application.pannel_current_session_mode);
applicationNode->QueryBoolAttribute("stream_low_bandwidth", &application.stream_low_bandwidth);
applicationNode->QueryIntAttribute("stream_protocol", &application.stream_protocol);
applicationNode->QueryIntAttribute("broadcast_port", &application.broadcast_port);
}
// Widgets

View File

@@ -223,6 +223,8 @@ struct Application
// connection settings
bool accept_connections;
int stream_protocol;
int broadcast_port;
// Settings of widgets
WidgetsConfig widget;
@@ -240,7 +242,6 @@ struct Application
// settings exporters
RecordConfig record;
bool stream_low_bandwidth;
// settings new source
SourceConfig source;
@@ -274,11 +275,12 @@ struct Application
action_history_follow_view = false;
show_tooptips = true;
accept_connections = false;
stream_protocol = 0;
broadcast_port = 8888;
pannel_current_session_mode = 0;
current_view = 1;
current_workspace= 1;
brush = glm::vec3(0.5f, 0.1f, 0.f);
stream_low_bandwidth = false;
windows = std::vector<WindowConfig>(3);
windows[0].name = APP_TITLE;
windows[0].w = 1600;

View File

@@ -271,7 +271,7 @@ void Streaming::addStream(const std::string &sender, int reply_to, const std::st
if ( NetworkToolkit::is_host_ip(conf.client_address) )
conf.protocol = NetworkToolkit::UDP_RAW;
// for non-localhost, if low bandwidth is requested, use H264 codec
else if (Settings::application.stream_low_bandwidth)
else if (Settings::application.stream_protocol > 0)
conf.protocol = NetworkToolkit::UDP_H264;
// TODO : ideal would be Shared Memory, but does not work with linux snap package...
@@ -456,7 +456,7 @@ void VideoStreamer::stop ()
Streaming::manager().removeStream(this);
// force finished
finished_ = true;
endofstream_ = true;
active_ = false;
}

View File

@@ -88,6 +88,7 @@ using namespace std;
#include "ImageShader.h"
#include "ImageProcessingShader.h"
#include "Metronome.h"
#include "VideoBroadcast.h"
#include "TextEditor.h"
TextEditor editor;
@@ -343,6 +344,9 @@ void UserInterface::handleKeyboard()
else
Rendering::manager().outputWindow().toggleFullscreen();
}
else if (ImGui::IsKeyPressed( GLFW_KEY_M )) {
Settings::application.widget.stats = !Settings::application.widget.stats;
}
else if (ImGui::IsKeyPressed( GLFW_KEY_N ) && shift_modifier_active) {
Mixer::manager().session()->addNote();
}
@@ -3504,7 +3508,8 @@ void SourceController::DrawButtonBar(ImVec2 bottom, float width)
/// OUTPUT PREVIEW
///
OutputPreview::OutputPreview() : WorkspaceWindow("OutputPreview"), video_recorder_(nullptr)
OutputPreview::OutputPreview() : WorkspaceWindow("OutputPreview"),
video_recorder_(nullptr), video_broadcaster_(nullptr)
{
#if defined(LINUX)
webcam_emulator_ = nullptr;
@@ -3557,6 +3562,9 @@ void OutputPreview::Update()
video_recorder_->stop();
}
// verify the video broadcaster is valid (change to nullptr if invalid)
FrameGrabbing::manager().verify( (FrameGrabber**) &video_broadcaster_);
#if defined(LINUX)
// verify the frame grabber for webcam emulator is valid
FrameGrabbing::manager().verify(&webcam_emulator_);
@@ -3591,6 +3599,17 @@ void OutputPreview::ToggleRecord(bool save_and_continue)
}
}
void OutputPreview::ToggleBroadcast()
{
if (video_broadcaster_) {
video_broadcaster_->stop();
}
else {
video_broadcaster_ = new VideoBroadcast;
FrameGrabbing::manager().add(video_broadcaster_);
}
}
void OutputPreview::Render()
{
@@ -3741,7 +3760,7 @@ void OutputPreview::Render()
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Share"))
if (ImGui::BeginMenu("Stream"))
{
#if defined(LINUX_NOT_YET_WORKING)
@@ -3761,20 +3780,34 @@ void OutputPreview::Render()
}
}
#endif
// Broadcasting menu
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f));
// Stop broadcast menu (broadcaster already exists)
if (video_broadcaster_) {
if ( ImGui::MenuItem( ICON_FA_SQUARE " Stop Broadcast") )
video_broadcaster_->stop();
}
// start broadcast (broadcaster does not exists)
else {
if ( ImGui::MenuItem( ICON_FA_GLOBE " Broadcast") ) {
video_broadcaster_ = new VideoBroadcast;
FrameGrabbing::manager().add(video_broadcaster_);
}
}
ImGui::PopStyleColor(1);
// Stream sharing menu
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.9f));
if ( ImGui::MenuItem( ICON_FA_SHARE_ALT_SQUARE " Accept connections ", NULL, &Settings::application.accept_connections) ) {
if ( ImGui::MenuItem( ICON_FA_SHARE_ALT_SQUARE " Share on local network", NULL, &Settings::application.accept_connections) ) {
Streaming::manager().enable(Settings::application.accept_connections);
}
ImGui::PopStyleColor(1);
if (Settings::application.accept_connections)
{
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::MenuItem( ICON_FA_LESS_THAN_EQUAL " Low bandwidth (H264)", NULL, &Settings::application.stream_low_bandwidth);
std::vector<std::string> ls = Streaming::manager().listStreams();
if (ls.size()>0) {
ImGui::Separator();
ImGui::MenuItem("Active streams", nullptr, false, false);
ImGui::MenuItem("Connected vimix", nullptr, false, false);
for (auto it = ls.begin(); it != ls.end(); ++it)
ImGui::Text(" %s", (*it).c_str() );
}
@@ -3797,24 +3830,15 @@ void OutputPreview::Render()
Rendering::manager().outputWindow().show();
///
/// Info overlays
/// Icons overlays
///
ImGui::SetCursorScreenPos(draw_pos + ImVec2(imagesize.x - ImGui::GetTextLineHeightWithSpacing(), 6));
ImGui::Text(ICON_FA_INFO_CIRCLE);
if (ImGui::IsItemHovered())
{
ImDrawList* draw_list = ImGui::GetWindowDrawList();
const float h = (Settings::application.accept_connections ? 2.f : 1.f) * ImGui::GetTextLineHeightWithSpacing();
draw_list->AddRectFilled(draw_pos, ImVec2(draw_pos.x + width, draw_pos.y + h), IMGUI_COLOR_OVERLAY);
ImGui::SetCursorScreenPos(draw_pos);
ImGui::Text(" %d x %d px, %.d fps", output->width(), output->height(), int(Mixer::manager().fps()) );
if (Settings::application.accept_connections){
ImGui::Text( " " ICON_FA_SHARE_ALT_SQUARE " %s", Connection::manager().info().name.c_str() );
}
}
const float r = ImGui::GetTextLineHeightWithSpacing();
// recording indicator overlay
// info indicator
ImGui::SetCursorScreenPos(draw_pos + ImVec2(imagesize.x - r, 6));
ImGui::Text(ICON_FA_INFO_CIRCLE);
bool drawoverlay = ImGui::IsItemHovered();
// recording indicator
if (video_recorder_)
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + r));
@@ -3835,38 +3859,47 @@ void OutputPreview::Render()
ImGui::PopStyleColor(1);
ImGui::PopFont();
}
// streaming indicator overlay
// broadcast indicator
if (video_broadcaster_)
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + r));
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
if (video_broadcaster_->busy())
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f));
else
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.4f));
ImGui::Text(ICON_FA_GLOBE);
ImGui::PopStyleColor(1);
ImGui::PopFont();
}
// streaming indicator
if (Settings::application.accept_connections)
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + width - 2.f * r, draw_pos.y + 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())
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.8f));
else
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.2f));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.4f));
ImGui::Text(ICON_FA_SHARE_ALT_SQUARE);
ImGui::PopStyleColor(1);
ImGui::PopFont();
}
// OUTPUT DISABLED indicator overlay
// OUTPUT DISABLED indicator
if (Settings::application.render.disabled)
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + (width / ar) - 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::Text(ICON_FA_EYE_SLASH);
ImGui::PopStyleColor(1);
ImGui::PopFont();
}
#if defined(LINUX)
// streaming indicator overlay
// streaming indicator
if (webcam_emulator_)
{
float r = ImGui::GetTextLineHeightWithSpacing();
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + width - 2.f * r, draw_pos.y + imagesize.y - 2.f * r));
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);
@@ -3875,6 +3908,26 @@ void OutputPreview::Render()
}
#endif
///
/// Info overlay over image
///
if (drawoverlay)
{
ImDrawList* draw_list = ImGui::GetWindowDrawList();
float h = 1.f;
h += (Settings::application.accept_connections ? 1.f : 0.f);
h += (video_broadcaster_ ? 1.f : 0.f);
draw_list->AddRectFilled(draw_pos, ImVec2(draw_pos.x + imagesize.x, draw_pos.y + h * r), IMGUI_COLOR_OVERLAY);
ImGui::SetCursorScreenPos(draw_pos);
ImGui::Text(" " ICON_FA_TV " %d x %d px, %.d fps", output->width(), output->height(), int(Mixer::manager().fps()) );
if (Settings::application.accept_connections)
ImGui::Text( " " ICON_FA_SHARE_ALT_SQUARE " %s (%d peer)",
Connection::manager().info().name.c_str(),
Streaming::manager().listStreams().size() );
if (video_broadcaster_)
ImGui::Text( " " ICON_FA_GLOBE " %s", video_broadcaster_->info().c_str() );
}
ImGui::End();
}
@@ -5733,6 +5786,12 @@ void Navigator::RenderMainPannelVimix()
if (ImGui::IsItemHovered())
tooltip_ = { TOOLTIP_NOTE, SHORTCUT_NOTE};
ImGui::SameLine(0, ImGui::GetTextLineHeight());
if ( ImGuiToolkit::IconButton( ICON_FA_TACHOMETER_ALT ) )
Settings::application.widget.stats = !Settings::application.widget.stats;
if (ImGui::IsItemHovered())
tooltip_ = { TOOLTIP_METRICS, SHORTCUT_METRICS};
ImGui::SameLine(0, ImGui::GetTextLineHeight());
if ( ImGuiToolkit::IconButton( ICON_FA_PLAY_CIRCLE ) )
UserInterface::manager().sourcecontrol.setVisible(!Settings::application.widget.media_player);
@@ -5770,23 +5829,21 @@ void Navigator::RenderMainPannelSettings()
//
// Appearance
//
ImGui::Text("Appearance");
ImGui::Text("Interface");
int v = Settings::application.accent_color;
if (ImGui::RadioButton("##Color", &v, v)){
Settings::application.accent_color = (v+1)%3;
ImGuiToolkit::SetAccentColor(static_cast<ImGuiToolkit::accent_color>(Settings::application.accent_color));
}
if (ImGui::IsItemHovered())
ImGuiToolkit::ToolTip("Change accent color");
ImGui::SameLine();
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if ( ImGui::DragFloat("Scale", &Settings::application.scale, 0.01f, 0.5f, 2.0f, "%.1f"))
ImGui::GetIO().FontGlobalScale = Settings::application.scale;
bool b = ImGui::RadioButton("Blue", &Settings::application.accent_color, 0); ImGui::SameLine();
bool o = ImGui::RadioButton("Orange", &Settings::application.accent_color, 1); ImGui::SameLine();
bool g = ImGui::RadioButton("Grey", &Settings::application.accent_color, 2);
if (b || o || g)
ImGuiToolkit::SetAccentColor(static_cast<ImGuiToolkit::accent_color>(Settings::application.accent_color));
//
// Options
//
ImGuiToolkit::Spacing();
ImGui::Text("Options");
ImGuiToolkit::ButtonSwitch( ICON_FA_MOUSE_POINTER " Smooth cursor", &Settings::application.smooth_cursor);
ImGuiToolkit::ButtonSwitch( ICON_FA_TACHOMETER_ALT " Metrics", &Settings::application.widget.stats);
//
// Recording preferences
@@ -5826,14 +5883,41 @@ void Navigator::RenderMainPannelSettings()
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::Combo("Priority", &Settings::application.record.priority_mode, "Duration\0Framerate\0");
//
// Networking preferences
//
ImGui::Text("Stream");
char msg[256];
sprintf(msg, "Port for broadcasting on Secure Reliable Transport (SRT) protocol\n"
"You can e.g. connect to:\n srt://%s:%d",
NetworkToolkit::host_ips()[1].c_str(), Settings::application.broadcast_port);
ImGuiToolkit::Indication(msg, ICON_FA_GLOBE);
ImGui::SameLine(0);
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
char bufport[7] = "";
sprintf(bufport, "%d", Settings::application.broadcast_port);
ImGui::InputTextWithHint("Broadcast", "8888", bufport, 7, ImGuiInputTextFlags_CharsDecimal);
if (ImGui::IsItemDeactivatedAfterEdit()){
if ( BaseToolkit::is_a_number(bufport, &Settings::application.broadcast_port))
Control::manager().init();
}
ImGuiToolkit::Indication("Sharing H264 stream requires less bandwidth but more resources for encoding.", ICON_FA_SHARE_ALT_SQUARE);
ImGui::SameLine(0);
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::Combo("Share", &Settings::application.stream_protocol, "JPEG\0H264\0");
//
// OSC preferences
//
ImGuiToolkit::Spacing();
ImGui::Text("OSC");
char msg[256];
sprintf(msg, "You can send OSC messages via UDP to the IP address %s", NetworkToolkit::host_ips()[1].c_str());
sprintf(msg, "You can send OSC messages via UDP to the local IP address %s on Port %d",
NetworkToolkit::host_ips()[1].c_str(), Settings::application.control.osc_port_receive);
ImGuiToolkit::Indication(msg, ICON_FA_NETWORK_WIRED);
ImGui::SameLine(0);
@@ -5841,7 +5925,7 @@ void Navigator::RenderMainPannelSettings()
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
char bufreceive[7] = "";
sprintf(bufreceive, "%d", Settings::application.control.osc_port_receive);
ImGui::InputTextWithHint("Port in", "UDP Port to receive", bufreceive, 7, ImGuiInputTextFlags_CharsDecimal);
ImGui::InputTextWithHint("Port in", "7000", bufreceive, 7, ImGuiInputTextFlags_CharsDecimal);
if (ImGui::IsItemDeactivatedAfterEdit()){
if ( BaseToolkit::is_a_number(bufreceive, &Settings::application.control.osc_port_receive))
Control::manager().init();
@@ -5851,7 +5935,7 @@ void Navigator::RenderMainPannelSettings()
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
char bufsend[7] = "";
sprintf(bufsend, "%d", Settings::application.control.osc_port_send);
ImGui::InputTextWithHint("Port out", "UDP Port to send", bufsend, 7, ImGuiInputTextFlags_CharsDecimal);
ImGui::InputTextWithHint("Port out", "7001", bufsend, 7, ImGuiInputTextFlags_CharsDecimal);
if (ImGui::IsItemDeactivatedAfterEdit()){
if ( BaseToolkit::is_a_number(bufsend, &Settings::application.control.osc_port_send))
Control::manager().init();
@@ -5883,7 +5967,9 @@ void Navigator::RenderMainPannelSettings()
bool change = false;
change |= ImGuiToolkit::ButtonSwitch( "Vertical synchronization", &vsync);
change |= ImGuiToolkit::ButtonSwitch( "Blit framebuffer", &blit);
#ifndef NDEBUG
change |= ImGuiToolkit::ButtonSwitch( "Antialiasing framebuffer", &multi);
#endif
// hardware support deserves more explanation
ImGuiToolkit::Indication("If enabled, tries to find a platform adapted hardware-accelerated "
"driver to decode (read) or encode (record) videos.", ICON_FA_MICROCHIP);

View File

@@ -62,6 +62,8 @@
#define TOOLTIP_NOTE "New note "
#define SHORTCUT_NOTE CTRL_MOD "Shift+N"
#define TOOLTIP_METRICS "Metrics "
#define SHORTCUT_METRICS CTRL_MOD "M"
#define TOOLTIP_PLAYER "Player "
#define SHORTCUT_PLAYER CTRL_MOD "P"
#define TOOLTIP_OUTPUT "Output "
@@ -89,6 +91,7 @@ class MediaPlayer;
class FrameBufferImage;
class FrameGrabber;
class VideoRecorder;
class VideoBroadcast;
class SourcePreview {
@@ -301,6 +304,8 @@ class OutputPreview : public WorkspaceWindow
{
// frame grabbers
VideoRecorder *video_recorder_;
VideoBroadcast *video_broadcaster_;
// delayed trigger for recording
std::vector< std::future<VideoRecorder *> > _video_recorders;
@@ -317,6 +322,9 @@ public:
void ToggleRecord(bool save_and_continue = false);
inline bool isRecording() const { return video_recorder_ != nullptr; }
void ToggleBroadcast();
inline bool isBroadcasting() const { return video_broadcaster_ != nullptr; }
void Render();
void setVisible(bool on);

View File

@@ -1,6 +1,158 @@
#include <sstream>
#include <iostream>
// gstreamer
#include <gst/gstformat.h>
#include <gst/video/video.h>
#include <gst/app/gstappsrc.h>
#include <gst/pbutils/pbutils.h>
#include "Log.h"
#include "Settings.h"
#include "GstToolkit.h"
#include "NetworkToolkit.h"
#include "VideoBroadcast.h"
VideoBroadcast::VideoBroadcast()
#ifndef NDEBUG
#define BROADCAST_DEBUG
#endif
VideoBroadcast::VideoBroadcast(NetworkToolkit::BroadcastProtocol proto, int port): FrameGrabber(), protocol_(proto), port_(port), stopped_(false)
{
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, BROADCAST_FPS); // fixed 30 FPS
}
std::string VideoBroadcast::init(GstCaps *caps)
{
// ignore
if (caps == nullptr)
return std::string("Invalid caps");
// create a gstreamer pipeline
std::string description = "appsrc name=src ! videoconvert ! queue ! ";
// choose pipeline for protocol
if (protocol_ == NetworkToolkit::BROADCAST_DEFAULT)
protocol_ = NetworkToolkit::BROADCAST_SRT;
description += NetworkToolkit::broadcast_pipeline[protocol_];
// setup streaming pipeline
if (protocol_ == NetworkToolkit::BROADCAST_SRT) {
// change the pipeline to include the broadcast port
std::string::size_type xxxx = description.find("XXXX");
if (xxxx != std::string::npos)
description = description.replace(xxxx, 4, std::to_string(port_));
else
return std::string("Video Broadcast : Failed to configure broadcast port.");
}
// parse pipeline descriptor
GError *error = NULL;
pipeline_ = gst_parse_launch (description.c_str(), &error);
if (error != NULL) {
std::string msg = std::string("Video Broadcast : Could not construct pipeline ") + description + "\n" + std::string(error->message);
g_clear_error (&error);
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
// 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, BROADCAST_FPS, 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("Video Broadcast : Failed to configure frame grabber.");
}
// start
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
return std::string("Video Broadcast : Failed to start frame grabber.");
}
// all good
initialized_ = true;
return std::string("Video Broadcast started.");
}
void VideoBroadcast::terminate()
{
// send EOS
gst_app_src_end_of_stream (src_);
Log::Notify("Broadcast terminated after %s s.",
GstToolkit::time_to_string(duration_).c_str());
}
void VideoBroadcast::stop ()
{
// stop recording
FrameGrabber::stop ();
// force finished
endofstream_ = true;
active_ = false;
}
std::string VideoBroadcast::info() const
{
std::ostringstream ret;
if (!initialized_)
ret << "Starting";
else if (active_) {
ret << NetworkToolkit::broadcast_protocol_label[protocol_];
ret << " ( Port " << port_ << " )";
}
else
ret << "Terminated";
return ret.str();
}

View File

@@ -8,21 +8,22 @@
class VideoBroadcast : public FrameGrabber
{
friend class Streaming;
std::string init(GstCaps *caps) override;
void terminate() override;
void stop() override;
// connection information
int port_;
std::atomic<bool> stopped_;
public:
VideoBroadcast();
VideoBroadcast(NetworkToolkit::BroadcastProtocol p = NetworkToolkit::BROADCAST_DEFAULT, int port = 8888);
virtual ~VideoBroadcast() {}
void stop() override;
std::string info() const override;
private:
std::string init(GstCaps *caps) override;
void terminate() override;
// connection information
NetworkToolkit::BroadcastProtocol protocol_;
int port_;
std::atomic<bool> stopped_;
};
#endif // VIDEOBROADCAST_H

View File

@@ -90,6 +90,7 @@
#define IMGUI_COLOR_CAPTURE 1.0, 0.55, 0.05
#define IMGUI_COLOR_RECORD 1.0, 0.05, 0.05
#define IMGUI_COLOR_STREAM 0.05, 0.8, 1.0
#define IMGUI_COLOR_BROADCAST 0.1, 0.9, 0.1
#define IMGUI_NOTIFICATION_DURATION 2.5f
#define IMGUI_TOOLTIP_TIMEOUT 80