mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-12 02:40:00 +01:00
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:
@@ -341,6 +341,7 @@ set(VMIX_SRCS
|
||||
Overlay.cpp
|
||||
Metronome.cpp
|
||||
ControlManager.cpp
|
||||
VideoBroadcast.cpp
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 << " ";
|
||||
}
|
||||
|
||||
|
||||
@@ -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_ + "'";
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user