Added Output support for default Gstreamer shared memory

With option to select and configure socket path
This commit is contained in:
Bruno Herbelin
2023-04-03 19:20:57 +02:00
parent 62c7b8cdc1
commit 6fcfc2da34
4 changed files with 144 additions and 39 deletions

View File

@@ -277,6 +277,7 @@ struct Application
int broadcast_port; int broadcast_port;
KnownHosts recentSRT; KnownHosts recentSRT;
int loopback_camera; int loopback_camera;
int shm_method;
std::string shm_socket_path; std::string shm_socket_path;
// Settings of widgets // Settings of widgets
@@ -337,6 +338,7 @@ struct Application
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; loopback_camera = 0;
shm_method = 0;
shm_socket_path = ""; shm_socket_path = "";
pannel_current_session_mode = 0; pannel_current_session_mode = 0;
current_view = 1; current_view = 1;

View File

@@ -1,4 +1,4 @@
/* /*
* This file is part of vimix - video live mixer * This file is part of vimix - video live mixer
* *
* **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com> * **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com>
@@ -19,6 +19,9 @@
#include <sstream> #include <sstream>
#include <iostream> #include <iostream>
#include <vector>
#include <regex>
// gstreamer // gstreamer
#include <gst/gstformat.h> #include <gst/gstformat.h>
@@ -28,54 +31,78 @@
#include "Log.h" #include "Log.h"
#include "GstToolkit.h" #include "GstToolkit.h"
#include "SystemToolkit.h"
#include "ShmdataBroadcast.h" #include "ShmdataBroadcast.h"
// Test command // Test command shmdata
// gst-launch-1.0 --gst-plugin-path=/usr/local/lib/gstreamer-1.0/ shmdatasrc socket-path=/tmp/shmdata_vimix0 ! videoconvert ! autovideosink // gst-launch-1.0 --gst-plugin-path=/usr/local/lib/gstreamer-1.0/ shmdatasrc socket-path=/tmp/shmdata_vimix0 ! videoconvert ! autovideosink
std::string ShmdataBroadcast::shmdata_sink_ = "shmdatasink"; // Test command shmsrc
// gst-launch-1.0 shmsrc socket-path=/tmp/shmdata_vimix0 is-live=true ! video/x-raw, format=RGB, framerate=30/1, width=1152, height=720 ! videoconvert ! autovideosink
bool ShmdataBroadcast::available() // OBS receiver
// shmsrc socket-path=/tmp/shmdata_vimix0 is-live=true ! video/x-raw, format=RGB, framerate=30/1, width=1152, height=720 ! videoconvert ! video.
// !!! OBS needs access to shm & filesystem
// sudo flatpak override --device=shm com.obsproject.Studio
// sudo flatpak override --filesystem=/tmp com.obsproject.Studio
std::vector<std::string> shm_sink_ = {
"shmsink",
"shmdatasink"
};
bool ShmdataBroadcast::available(Method m)
{ {
// test for availability once on first run // test for availability once on first run
static bool _available = false, _tested = false; static bool _shm_available = false, _shmdata_available = false, _tested = false;
if (!_tested) { if (!_tested) {
_available = GstToolkit::has_feature(ShmdataBroadcast::shmdata_sink_); _shm_available = GstToolkit::has_feature(shm_sink_[SHM_SHMSINK]);
_shmdata_available = GstToolkit::has_feature(shm_sink_[SHM_SHMDATASINK]);
_tested = true; _tested = true;
} }
return _available; if (m == SHM_SHMSINK)
return _shm_available;
else if (m == SHM_SHMDATASINK)
return _shmdata_available;
else
return _shm_available | _shmdata_available;
} }
ShmdataBroadcast::ShmdataBroadcast(const std::string &socketpath): FrameGrabber(), socket_path_(socketpath) ShmdataBroadcast::ShmdataBroadcast(Method m, const std::string &socketpath): FrameGrabber(), method_(m), socket_path_(socketpath)
{ {
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, SHMDATA_FPS); // fixed 30 FPS frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, SHMDATA_FPS); // fixed 30 FPS
// default socket path
if (socket_path_.empty()) if (socket_path_.empty())
socket_path_ = SHMDATA_DEFAULT_PATH; socket_path_ = SHMDATA_DEFAULT_PATH;
// default to SHMSINK / ignore SHM_SHMDATASINK if not available
if (!GstToolkit::has_feature(shm_sink_[SHM_SHMDATASINK]) || m == SHM_SHMDATAANY)
method_ = SHM_SHMSINK;
} }
std::string ShmdataBroadcast::init(GstCaps *caps) std::string ShmdataBroadcast::init(GstCaps *caps)
{ {
if (!ShmdataBroadcast::available()) if (!ShmdataBroadcast::available())
return std::string("Shmdata Broadcast : Not available (missing shmdatasink plugin)"); return std::string("Shared Memory Broadcast : Not available (missing shmsink or shmdatasink plugin)");
// ignore // ignore
if (caps == nullptr) if (caps == nullptr)
return std::string("Shmdata Broadcast : Invalid caps"); return std::string("Shared Memory Broadcast : Invalid caps");
// create a gstreamer pipeline // create a gstreamer pipeline
std::string description = "appsrc name=src ! queue ! "; std::string description = "appsrc name=src ! queue ! ";
// complement pipeline with sink // complement pipeline with sink
description += ShmdataBroadcast::shmdata_sink_ + " name=sink"; description += shm_sink_[method_] + " name=sink";
// parse pipeline descriptor // parse pipeline descriptor
GError *error = NULL; GError *error = NULL;
pipeline_ = gst_parse_launch (description.c_str(), &error); pipeline_ = gst_parse_launch (description.c_str(), &error);
if (error != NULL) { if (error != NULL) {
std::string msg = std::string("Shmdata Broadcast : Could not construct pipeline ") + description + "\n" + std::string(error->message); std::string msg = std::string("Shared Memory Broadcast : Could not construct pipeline ") + description + "\n" + std::string(error->message);
g_clear_error (&error); g_clear_error (&error);
return msg; return msg;
} }
@@ -83,6 +110,7 @@ std::string ShmdataBroadcast::init(GstCaps *caps)
// setup shm sink // setup shm sink
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")), g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
"socket-path", socket_path_.c_str(), "socket-path", socket_path_.c_str(),
"wait-for-connection", FALSE,
NULL); NULL);
// setup custom app source // setup custom app source
@@ -123,19 +151,19 @@ std::string ShmdataBroadcast::init(GstCaps *caps)
} }
else { else {
return std::string("Shmdata Broadcast : Failed to configure frame grabber."); return std::string("Shared Memory Broadcast : Failed to configure frame grabber ") + shm_sink_[method_];
} }
// start // start
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING); GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) { if (ret == GST_STATE_CHANGE_FAILURE) {
return std::string("Shmdata Broadcast : Failed to start frame grabber."); return std::string("Shared Memory Broadcast : Failed to start frame grabber.");
} }
// all good // all good
initialized_ = true; initialized_ = true;
return std::string("Shared Memory with Shmdata started on ") + socket_path_; return std::string("Shared Memory Broadcast with '") + shm_sink_[method_] + "' started on " + socket_path_;
} }
@@ -145,21 +173,44 @@ void ShmdataBroadcast::terminate()
endofstream_ = true; endofstream_ = true;
active_ = false; active_ = false;
// delete socket
SystemToolkit::remove_file(socket_path_);
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());
} }
std::string ShmdataBroadcast::gst_pipeline() const
{
std::string pipeline;
pipeline += (method_ == SHM_SHMDATASINK) ? "shmdatasrc" : "shmsrc";
pipeline += " socket-path=";
pipeline += socket_path_;
pipeline += " is-live=true";
if (method_ == SHM_SHMSINK){
pipeline += " ! ";
pipeline += std::string( gst_caps_to_string(caps_) );
pipeline = std::regex_replace(pipeline, std::regex("\\(int\\)"), "");
pipeline = std::regex_replace(pipeline, std::regex("\\(fraction\\)"), "");
pipeline = std::regex_replace(pipeline, std::regex("\\(string\\)"), "");
}
return pipeline;
}
std::string ShmdataBroadcast::info() const std::string ShmdataBroadcast::info() const
{ {
std::ostringstream ret; std::ostringstream ret;
if (!initialized_) if (!initialized_)
ret << "Shmdata starting.."; ret << "Shared Memory starting..";
else if (active_) else if (active_)
ret << "Shmdata " << socket_path_; ret << "Shared Memory " << socket_path_;
else else
ret << "Shmdata terminated"; ret << "Shared Memory terminated";
return ret.str(); return ret.str();
} }

View File

@@ -3,18 +3,27 @@
#include "FrameGrabber.h" #include "FrameGrabber.h"
#define SHMDATA_DEFAULT_PATH "/tmp/shmdata_vimix" #define SHMDATA_DEFAULT_PATH "/tmp/shm_vimix"
#define SHMDATA_FPS 30 #define SHMDATA_FPS 30
class ShmdataBroadcast : public FrameGrabber class ShmdataBroadcast : public FrameGrabber
{ {
public: public:
ShmdataBroadcast(const std::string &socketpath = SHMDATA_DEFAULT_PATH);
enum Method {
SHM_SHMSINK,
SHM_SHMDATASINK,
SHM_SHMDATAANY
};
ShmdataBroadcast(Method m = SHM_SHMSINK, const std::string &socketpath = "");
virtual ~ShmdataBroadcast() {} virtual ~ShmdataBroadcast() {}
static bool available(); static bool available(Method m = SHM_SHMDATAANY);
inline Method method() const { return method_; }
inline std::string socket_path() const { return socket_path_; } inline std::string socket_path() const { return socket_path_; }
std::string gst_pipeline() const;
std::string info() const override; std::string info() const override;
@@ -23,9 +32,8 @@ private:
void terminate() override; void terminate() override;
// connection information // connection information
Method method_;
std::string socket_path_; std::string socket_path_;
static std::string shmdata_sink_;
}; };
#endif // SHMDATABROADCAST_H #endif // SHMDATABROADCAST_H

View File

@@ -4113,9 +4113,14 @@ void OutputPreview::ToggleSharedMemory()
shm_broadcaster_->stop(); shm_broadcaster_->stop();
} }
else { else {
if (Settings::application.shm_socket_path.empty()) // find a folder to put the socket for shm
Settings::application.shm_socket_path = SHMDATA_DEFAULT_PATH + std::to_string(Settings::application.instance_id); std::string _shm_socket_file = Settings::application.shm_socket_path;
shm_broadcaster_ = new ShmdataBroadcast(Settings::application.shm_socket_path); if (_shm_socket_file.empty() || !SystemToolkit::file_exists(_shm_socket_file))
_shm_socket_file = SystemToolkit::home_path();
_shm_socket_file = SystemToolkit::full_filename(_shm_socket_file, ".shm_vimix" + std::to_string(Settings::application.instance_id));
// create shhmdata broadcaster with method
shm_broadcaster_ = new ShmdataBroadcast( (ShmdataBroadcast::Method) Settings::application.shm_method, _shm_socket_file);
FrameGrabbing::manager().add(shm_broadcaster_); FrameGrabbing::manager().add(shm_broadcaster_);
} }
} }
@@ -4306,7 +4311,7 @@ void OutputPreview::Render()
{ {
// 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 " P2P Peer-to-peer sharing", NULL, &Settings::application.accept_connections) ) {
Streaming::manager().enable(Settings::application.accept_connections); Streaming::manager().enable(Settings::application.accept_connections);
} }
ImGui::PopStyleColor(1); ImGui::PopStyleColor(1);
@@ -4323,7 +4328,7 @@ void OutputPreview::Render()
// Shared Memory menu // Shared Memory menu
if (ShmdataBroadcast::available()) { if (ShmdataBroadcast::available()) {
if ( ImGui::MenuItem( ICON_FA_SHARE_ALT " Shared Memory", NULL, sharedMemoryEnabled()) ) if ( ImGui::MenuItem( ICON_FA_MEMORY " SHM Shared Memory", NULL, sharedMemoryEnabled()) )
ToggleSharedMemory(); ToggleSharedMemory();
} }
@@ -4363,8 +4368,8 @@ void OutputPreview::Render()
// copy text icon to give user the socket path to connect to // copy text icon to give user the socket path 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()) );
if (ImGuiToolkit::IconButton( ICON_FA_COPY, shm_broadcaster_->socket_path().c_str())) if (ImGuiToolkit::IconButton( ICON_FA_COPY, shm_broadcaster_->gst_pipeline().c_str()))
ImGui::SetClipboardText(shm_broadcaster_->socket_path().c_str()); ImGui::SetClipboardText(shm_broadcaster_->gst_pipeline().c_str());
ImGui::SetCursorPos(draw_pos); ImGui::SetCursorPos(draw_pos);
} }
@@ -4482,7 +4487,7 @@ void OutputPreview::Render()
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_SHARE_ALT); ImGui::Text(ICON_FA_MEMORY);
ImGui::PopStyleColor(1); ImGui::PopStyleColor(1);
vertical += 2.f * r; vertical += 2.f * r;
} }
@@ -8373,7 +8378,7 @@ void Navigator::RenderMainPannelSettings()
char buf[512]; sprintf(buf, "Buffer at %s can contain %ld frames (%dx%d), i.e. %.1f sec", VideoRecorder::buffering_preset_name[Settings::application.record.buffering_mode], char buf[512]; sprintf(buf, "Buffer at %s can contain %ld frames (%dx%d), i.e. %.1f sec", VideoRecorder::buffering_preset_name[Settings::application.record.buffering_mode],
(unsigned long)nb, output->width(), output->height(), (unsigned long)nb, output->width(), output->height(),
(float)nb / (float) VideoRecorder::framerate_preset_value[Settings::application.record.framerate_mode] ); (float)nb / (float) VideoRecorder::framerate_preset_value[Settings::application.record.framerate_mode] );
ImGuiToolkit::Indication(buf, ICON_FA_MEMORY); ImGuiToolkit::Indication(buf, ICON_FA_ELLIPSIS_H);
ImGui::SameLine(0); ImGui::SameLine(0);
} }
@@ -8391,22 +8396,22 @@ void Navigator::RenderMainPannelSettings()
ImGui::Combo("Priority", &Settings::application.record.priority_mode, "Duration\0Framerate\0"); ImGui::Combo("Priority", &Settings::application.record.priority_mode, "Duration\0Framerate\0");
// //
// Networking preferences // Steaming preferences
// //
ImGuiToolkit::Spacing(); ImGuiToolkit::Spacing();
ImGui::Text("Stream"); ImGui::Text("Stream");
ImGuiToolkit::Indication("Peer-to-peer sharing on local network\n\n" ImGuiToolkit::Indication("Peer-to-peer sharing local network\n\n"
"vimix can stream JPEG (default) or H264 (requires less bandwidth but more resources for encoding)", ICON_FA_SHARE_ALT_SQUARE); "vimix can stream JPEG (default) or H264 (less bandwidth, higher encoding cost)", ICON_FA_SHARE_ALT_SQUARE);
ImGui::SameLine(0); ImGui::SameLine(0);
ImGui::SetCursorPosX(width_); ImGui::SetCursorPosX(width_);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::Combo("Share P2P", &Settings::application.stream_protocol, "JPEG\0H264\0"); ImGui::Combo("P2P codec", &Settings::application.stream_protocol, "JPEG\0H264\0");
if (VideoBroadcast::available()) { if (VideoBroadcast::available()) {
char msg[256]; char msg[256];
ImFormatString(msg, IM_ARRAYSIZE(msg), "Broadcast SRT\n\n" ImFormatString(msg, IM_ARRAYSIZE(msg), "SRT Broadcast\n\n"
"vimix is listening to SRT requests on Port %d. " "vimix listens to SRT requests on Port %d. "
"Example network addresses to call:\n" "Example network addresses to call:\n"
" srt//%s:%d (localhost)\n" " srt//%s:%d (localhost)\n"
" srt//%s:%d (local IP)", " srt//%s:%d (local IP)",
@@ -8420,13 +8425,52 @@ void Navigator::RenderMainPannelSettings()
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
char bufport[7] = ""; char bufport[7] = "";
sprintf(bufport, "%d", Settings::application.broadcast_port); sprintf(bufport, "%d", Settings::application.broadcast_port);
ImGui::InputTextWithHint("Port SRT", "7070", bufport, 6, ImGuiInputTextFlags_CharsDecimal); ImGui::InputTextWithHint("SRT Port", "7070", bufport, 6, ImGuiInputTextFlags_CharsDecimal);
if (ImGui::IsItemDeactivatedAfterEdit()){ if (ImGui::IsItemDeactivatedAfterEdit()){
if ( BaseToolkit::is_a_number(bufport, &Settings::application.broadcast_port)) if ( BaseToolkit::is_a_number(bufport, &Settings::application.broadcast_port))
Settings::application.broadcast_port = CLAMP(Settings::application.broadcast_port, 1029, 49150); Settings::application.broadcast_port = CLAMP(Settings::application.broadcast_port, 1029, 49150);
} }
} }
if (ShmdataBroadcast::available()) {
std::string _shm_socket_file = Settings::application.shm_socket_path;
if (_shm_socket_file.empty() || !SystemToolkit::file_exists(_shm_socket_file))
_shm_socket_file = SystemToolkit::home_path();
_shm_socket_file = SystemToolkit::full_filename(_shm_socket_file, ".shm_vimix" + std::to_string(Settings::application.instance_id));
char msg[256];
if (ShmdataBroadcast::available(ShmdataBroadcast::SHM_SHMDATASINK)) {
ImFormatString(msg, IM_ARRAYSIZE(msg), "Shared Memory\n\n"
"vimix can share to RAM with "
"gstreamer default 'shmsink' "
"and with 'shmdatasink'.\n"
"Socket file to connect to:\n%s\n",
_shm_socket_file.c_str());
}
else {
ImFormatString(msg, IM_ARRAYSIZE(msg), "Shared Memory\n\n"
"vimix can share to RAM with "
"gstreamer 'shmsink'.\n"
"Socket file to connect to:\n%s\n",
_shm_socket_file.c_str());
}
ImGuiToolkit::Indication(msg, ICON_FA_MEMORY);
ImGui::SameLine(0);
ImGui::SetCursorPosX(width_);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
char bufsocket[64] = "";
sprintf(bufsocket, "%s", Settings::application.shm_socket_path.c_str());
ImGui::InputTextWithHint("SHM path", SystemToolkit::home_path().c_str(), bufsocket, 64);
if (ImGui::IsItemDeactivatedAfterEdit()) {
Settings::application.shm_socket_path = bufsocket;
}
if (ShmdataBroadcast::available(ShmdataBroadcast::SHM_SHMDATASINK)) {
ImGui::SetCursorPosX(width_);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::Combo("SHM plugin", &Settings::application.shm_method, "shmsink\0shmdatasink\0");
}
}
// //
// OSC preferences // OSC preferences
// //