mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-16 12:49:59 +01:00
Added Output support for default Gstreamer shared memory
With option to select and configure socket path
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user