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;
KnownHosts recentSRT;
int loopback_camera;
int shm_method;
std::string shm_socket_path;
// Settings of widgets
@@ -337,6 +338,7 @@ struct Application
recentSRT.protocol = "srt://";
recentSRT.default_host = { "127.0.0.1", "7070"};
loopback_camera = 0;
shm_method = 0;
shm_socket_path = "";
pannel_current_session_mode = 0;
current_view = 1;

View File

@@ -1,4 +1,4 @@
/*
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com>
@@ -19,6 +19,9 @@
#include <sstream>
#include <iostream>
#include <vector>
#include <regex>
// gstreamer
#include <gst/gstformat.h>
@@ -28,54 +31,78 @@
#include "Log.h"
#include "GstToolkit.h"
#include "SystemToolkit.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
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
static bool _available = false, _tested = false;
static bool _shm_available = false, _shmdata_available = false, _tested = false;
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;
}
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
// default socket path
if (socket_path_.empty())
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)
{
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
if (caps == nullptr)
return std::string("Shmdata Broadcast : Invalid caps");
return std::string("Shared Memory Broadcast : Invalid caps");
// create a gstreamer pipeline
std::string description = "appsrc name=src ! queue ! ";
// complement pipeline with sink
description += ShmdataBroadcast::shmdata_sink_ + " name=sink";
description += shm_sink_[method_] + " name=sink";
// parse pipeline descriptor
GError *error = NULL;
pipeline_ = gst_parse_launch (description.c_str(), &error);
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);
return msg;
}
@@ -83,6 +110,7 @@ std::string ShmdataBroadcast::init(GstCaps *caps)
// setup shm sink
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
"socket-path", socket_path_.c_str(),
"wait-for-connection", FALSE,
NULL);
// setup custom app source
@@ -123,19 +151,19 @@ std::string ShmdataBroadcast::init(GstCaps *caps)
}
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
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
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
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;
active_ = false;
// delete socket
SystemToolkit::remove_file(socket_path_);
Log::Notify("Shared Memory terminated after %s s.",
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::ostringstream ret;
if (!initialized_)
ret << "Shmdata starting..";
ret << "Shared Memory starting..";
else if (active_)
ret << "Shmdata " << socket_path_;
ret << "Shared Memory " << socket_path_;
else
ret << "Shmdata terminated";
ret << "Shared Memory terminated";
return ret.str();
}

View File

@@ -3,18 +3,27 @@
#include "FrameGrabber.h"
#define SHMDATA_DEFAULT_PATH "/tmp/shmdata_vimix"
#define SHMDATA_DEFAULT_PATH "/tmp/shm_vimix"
#define SHMDATA_FPS 30
class ShmdataBroadcast : public FrameGrabber
{
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() {}
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_; }
std::string gst_pipeline() const;
std::string info() const override;
@@ -23,9 +32,8 @@ private:
void terminate() override;
// connection information
Method method_;
std::string socket_path_;
static std::string shmdata_sink_;
};
#endif // SHMDATABROADCAST_H

View File

@@ -4113,9 +4113,14 @@ void OutputPreview::ToggleSharedMemory()
shm_broadcaster_->stop();
}
else {
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);
// find a folder to put the socket for shm
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));
// create shhmdata broadcaster with method
shm_broadcaster_ = new ShmdataBroadcast( (ShmdataBroadcast::Method) Settings::application.shm_method, _shm_socket_file);
FrameGrabbing::manager().add(shm_broadcaster_);
}
}
@@ -4306,7 +4311,7 @@ void OutputPreview::Render()
{
// Stream sharing menu
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);
}
ImGui::PopStyleColor(1);
@@ -4323,7 +4328,7 @@ void OutputPreview::Render()
// Shared Memory menu
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();
}
@@ -4363,8 +4368,8 @@ void OutputPreview::Render()
// copy text icon to give user the socket 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, shm_broadcaster_->socket_path().c_str()))
ImGui::SetClipboardText(shm_broadcaster_->socket_path().c_str());
if (ImGuiToolkit::IconButton( ICON_FA_COPY, shm_broadcaster_->gst_pipeline().c_str()))
ImGui::SetClipboardText(shm_broadcaster_->gst_pipeline().c_str());
ImGui::SetCursorPos(draw_pos);
}
@@ -4482,7 +4487,7 @@ void OutputPreview::Render()
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_SHARE_ALT);
ImGui::Text(ICON_FA_MEMORY);
ImGui::PopStyleColor(1);
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],
(unsigned long)nb, output->width(), output->height(),
(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);
}
@@ -8391,22 +8396,22 @@ void Navigator::RenderMainPannelSettings()
ImGui::Combo("Priority", &Settings::application.record.priority_mode, "Duration\0Framerate\0");
//
// Networking preferences
// Steaming preferences
//
ImGuiToolkit::Spacing();
ImGui::Text("Stream");
ImGuiToolkit::Indication("Peer-to-peer sharing on local network\n\n"
"vimix can stream JPEG (default) or H264 (requires less bandwidth but more resources for encoding)", ICON_FA_SHARE_ALT_SQUARE);
ImGuiToolkit::Indication("Peer-to-peer sharing local network\n\n"
"vimix can stream JPEG (default) or H264 (less bandwidth, higher encoding cost)", ICON_FA_SHARE_ALT_SQUARE);
ImGui::SameLine(0);
ImGui::SetCursorPosX(width_);
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()) {
char msg[256];
ImFormatString(msg, IM_ARRAYSIZE(msg), "Broadcast SRT\n\n"
"vimix is listening to SRT requests on Port %d. "
ImFormatString(msg, IM_ARRAYSIZE(msg), "SRT Broadcast\n\n"
"vimix listens to SRT requests on Port %d. "
"Example network addresses to call:\n"
" srt//%s:%d (localhost)\n"
" srt//%s:%d (local IP)",
@@ -8420,13 +8425,52 @@ void Navigator::RenderMainPannelSettings()
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
char bufport[7] = "";
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 ( BaseToolkit::is_a_number(bufport, &Settings::application.broadcast_port))
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
//