diff --git a/src/Settings.h b/src/Settings.h index 1882e64..28bbd5f 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -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; diff --git a/src/ShmdataBroadcast.cpp b/src/ShmdataBroadcast.cpp index 1878641..63e3734 100644 --- a/src/ShmdataBroadcast.cpp +++ b/src/ShmdataBroadcast.cpp @@ -1,4 +1,4 @@ -/* +/* * This file is part of vimix - video live mixer * * **Copyright** (C) 2019-2023 Bruno Herbelin @@ -19,6 +19,9 @@ #include #include +#include +#include + // gstreamer #include @@ -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 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(); } diff --git a/src/ShmdataBroadcast.h b/src/ShmdataBroadcast.h index 6bfb151..cf4fec0 100644 --- a/src/ShmdataBroadcast.h +++ b/src/ShmdataBroadcast.h @@ -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 diff --git a/src/UserInterfaceManager.cpp b/src/UserInterfaceManager.cpp index bfefd3f..67d1aaf 100644 --- a/src/UserInterfaceManager.cpp +++ b/src/UserInterfaceManager.cpp @@ -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 //