Implement broadcast manager functionality and enhance FrameGrabber info and type methods

This commit is contained in:
brunoherbelin
2025-11-09 11:38:01 +01:00
parent 0ef06e400d
commit 08114e2cbe
16 changed files with 281 additions and 92 deletions

View File

@@ -42,6 +42,7 @@
#include "NetworkToolkit.h"
#include "UserInterfaceManager.h"
#include "Streamer.h"
#include "VideoBroadcast.h"
#include "ControlManager.h"
@@ -605,6 +606,16 @@ bool Control::receiveOutputAttribute(const std::string &attribute,
Mixer::manager().session()->setFadingTarget( Mixer::manager().session()->fading() + f * 0.01);
need_feedback = true;
}
else if ( attribute.compare(OSC_OUTPUT_SRT_START) == 0) {
float f = (float) Settings::application.broadcast_port;
// if argument is given, it is the connection port
if (!arguments.Eos())
arguments >> f >> osc::EndMessage;
Broadcast::manager().start( new VideoBroadcast((int) f), true);
}
else if ( attribute.compare(OSC_OUTPUT_SRT_STOP) == 0) {
Broadcast::manager().stop(FrameGrabber::GRABBER_BROADCAST);
}
// inform of invalid attribute name
else {
Log::Info(CONTROL_OSC_MSG "Unknown attribute '%s' for target 'output'", attribute.c_str());

View File

@@ -24,6 +24,8 @@
#define OSC_OUTPUT_FADING "/fading"
#define OSC_OUTPUT_FADE_IN "/fade-in"
#define OSC_OUTPUT_FADE_OUT "/fade-out"
#define OSC_OUTPUT_SRT_START "/srt-start"
#define OSC_OUTPUT_SRT_STOP "/srt-stop"
#define OSC_ALL "/all"
#define OSC_SELECTION "/selection"

View File

@@ -106,6 +106,28 @@ FrameGrabber *FrameGrabbing::get(uint64_t id)
return nullptr;
}
struct fgType
{
inline bool operator()(const FrameGrabber* elem) const {
return (elem && elem->type() == _t);
}
explicit fgType(FrameGrabber::Type t) : _t(t) { }
private:
FrameGrabber::Type _t;
};
FrameGrabber *FrameGrabbing::get(FrameGrabber::Type t)
{
if (grabbers_.size() > 0 )
{
std::list<FrameGrabber *>::iterator iter = std::find_if(grabbers_.begin(), grabbers_.end(), fgType(t));
if (iter != grabbers_.end())
return (*iter);
}
return nullptr;
}
void FrameGrabbing::stopAll()
{
std::list<FrameGrabber *>::iterator iter;
@@ -349,8 +371,37 @@ void FrameGrabber::stop ()
}
std::string FrameGrabber::info() const
std::string FrameGrabber::info(bool extended) const
{
if (extended) {
std::string typestring;
switch ( type() )
{
case GRABBER_PNG :
typestring = "Portable Network Graphics frame grabber";
break;
case GRABBER_VIDEO :
typestring = "Video file frame grabber";
break;
case GRABBER_P2P :
typestring = "Peer-to-Peer stream frame grabber";
break;
case GRABBER_BROADCAST :
typestring = "SRT Broarcast frame grabber";
break;
case GRABBER_SHM :
typestring = "Shared Memory frame grabber";
break;
case GRABBER_LOOPBACK :
typestring = "Loopback frame grabber";
break;
default:
typestring = "Generic frame grabber";
break;
}
return typestring;
}
if (!initialized_)
return "Initializing";
if (active_)
@@ -603,3 +654,56 @@ guint64 FrameGrabber::frames() const
{
return frame_count_;
}
uint64_t Broadcast::start(FrameGrabber *ptr, bool singleton)
{
if (singleton)
stop( ptr->type() );
uint64_t b = ptr->id();
FrameGrabbing::manager().add(ptr);
return b;
}
bool Broadcast::enabled(uint64_t b)
{
return ( FrameGrabbing::manager().get(b) != nullptr);
}
bool Broadcast::busy(uint64_t b)
{
FrameGrabber *ptr = FrameGrabbing::manager().get(b);
if (ptr != nullptr)
return ptr->busy();
return false;
}
std::string Broadcast::info(uint64_t b, bool extended)
{
FrameGrabber *ptr = FrameGrabbing::manager().get(b);
if (ptr != nullptr)
return ptr->info(extended);
return "Disabled";
}
void Broadcast::stop(uint64_t b)
{
FrameGrabber *ptr = FrameGrabbing::manager().get(b);
if (ptr != nullptr)
ptr->stop();
}
void Broadcast::stop(FrameGrabber::Type T)
{
FrameGrabber *prev = nullptr;
do {
prev = FrameGrabbing::manager().get( T );
if (prev != nullptr)
prev->stop();
}
while (prev != nullptr);
}

View File

@@ -41,8 +41,21 @@ public:
inline uint64_t id() const { return id_; }
// types of sub-classes of FrameGrabber
typedef enum {
GRABBER_GENERIC = 0,
GRABBER_PNG = 1,
GRABBER_VIDEO = 2,
GRABBER_P2P,
GRABBER_BROADCAST,
GRABBER_SHM,
GRABBER_LOOPBACK,
GRABBER_INVALID
} Type;
virtual Type type () const { return GRABBER_GENERIC; }
virtual void stop();
virtual std::string info() const;
virtual std::string info(bool extended = false) const;
virtual uint64_t duration() const;
virtual bool finished() const;
virtual bool busy() const;
@@ -133,6 +146,7 @@ public:
void verify(FrameGrabber **rec);
bool busy() const;
FrameGrabber *get(uint64_t id);
FrameGrabber *get(FrameGrabber::Type t);
void stopAll();
void clearAll();
@@ -154,6 +168,38 @@ private:
GstCaps *caps_;
};
/**
* @brief The Broadcast class gives a global access to launch and stop FrameGrabbers
*
* FrameGrabbers can terminate (normal behavior or failure) and the FrameGrabber
* is deleted automatically; pointer is then invalid and cannot be accessed. To avoid this,
* the Broadcast::manager() gives access to FrameGrabbers by id.
*
* Singleton mechanism also allows starting a unique instance of each FrameGrabber::Type
*/
class Broadcast
{
// Private Constructor
Broadcast() {};
Broadcast(Broadcast const& copy) = delete;
Broadcast& operator=(Broadcast const& copy) = delete;
public:
static Broadcast& manager ()
{
// The only instance
static Broadcast _instance;
return _instance;
}
uint64_t start(FrameGrabber *ptr, bool singleton = false);
bool enabled(uint64_t);
bool busy(uint64_t);
std::string info(uint64_t, bool extended = false);
void stop(uint64_t);
void stop(FrameGrabber::Type);
};
#endif // FRAMEGRABBER_H

View File

@@ -175,16 +175,21 @@ void Loopback::stop ()
active_ = false;
}
std::string Loopback::info() const
std::string Loopback::info(bool extended) const
{
std::ostringstream ret;
if (!initialized_)
ret << "Loopback starting..";
else if (active_)
ret << "V4L2 Loopback on " << device_name();
else
ret << "Loopback terminated";
if (extended) {
ret << device_name();
}
else {
if (!initialized_)
ret << "Loopback starting..";
else if (active_)
ret << "V4L2 Loopback on " << device_name();
else
ret << "Loopback terminated";
}
return ret.str();
}

View File

@@ -12,13 +12,15 @@ public:
Loopback(int deviceid = LOOPBACK_DEFAULT_DEVICE);
virtual ~Loopback() {}
FrameGrabber::Type type () const override { return FrameGrabber::GRABBER_LOOPBACK; }
static bool available();
inline int device_id() const { return loopback_device_; }
std::string device_name() const;
void stop() override;
std::string info() const override;
std::string info(bool extended = false) const override;
private:
std::string init(GstCaps *caps) override;

View File

@@ -41,7 +41,7 @@
OutputPreviewWindow::OutputPreviewWindow() : WorkspaceWindow("OutputPreview"),
video_recorder_(nullptr), video_broadcaster_(nullptr), loopback_broadcaster_(nullptr),
video_recorder_(nullptr), srt_(0), shm_(0), loopback_(0),
magnifying_glass(false)
{
@@ -96,11 +96,6 @@ void OutputPreviewWindow::Update()
video_recorder_->stop();
}
// verify the frame grabbers are valid (change to nullptr if invalid)
FrameGrabbing::manager().verify( (FrameGrabber**) &video_broadcaster_);
FrameGrabbing::manager().verify( (FrameGrabber**) &shm_broadcaster_);
FrameGrabbing::manager().verify( (FrameGrabber**) &loopback_broadcaster_);
}
VideoRecorder *delayTrigger(VideoRecorder *g, std::chrono::milliseconds delay) {
@@ -140,22 +135,19 @@ void OutputPreviewWindow::ToggleRecordPause()
void OutputPreviewWindow::ToggleVideoBroadcast()
{
if (video_broadcaster_) {
video_broadcaster_->stop();
}
if (Broadcast::manager().enabled(srt_) )
Broadcast::manager().stop(srt_);
else {
if (Settings::application.broadcast_port<1000)
Settings::application.broadcast_port = BROADCAST_DEFAULT_PORT;
video_broadcaster_ = new VideoBroadcast(Settings::application.broadcast_port);
FrameGrabbing::manager().add(video_broadcaster_);
srt_ = Broadcast::manager().start( new VideoBroadcast(Settings::application.broadcast_port), true);
}
}
void OutputPreviewWindow::ToggleSharedMemory()
{
if (shm_broadcaster_) {
shm_broadcaster_->stop();
}
if (Broadcast::manager().enabled(shm_) )
Broadcast::manager().stop(shm_);
else {
// find a folder to put the socket for shm
std::string _shm_socket_file = Settings::application.shm_socket_path;
@@ -164,25 +156,22 @@ void OutputPreviewWindow::ToggleSharedMemory()
_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_);
shm_ = Broadcast::manager().start( new ShmdataBroadcast( (ShmdataBroadcast::Method) Settings::application.shm_method, _shm_socket_file), true);
}
}
bool OutputPreviewWindow::ToggleLoopbackCamera()
{
bool need_initialization = false;
if (loopback_broadcaster_) {
loopback_broadcaster_->stop();
}
if (Broadcast::manager().enabled(loopback_))
Broadcast::manager().stop(loopback_);
else {
if (Settings::application.loopback_camera < 1)
Settings::application.loopback_camera = LOOPBACK_DEFAULT_DEVICE;
Settings::application.loopback_camera += Settings::application.instance_id;
try {
loopback_broadcaster_ = new Loopback(Settings::application.loopback_camera);
FrameGrabbing::manager().add(loopback_broadcaster_);
loopback_ = Broadcast::manager().start( new Loopback(Settings::application.loopback_camera), true );
}
catch (const std::runtime_error &e) {
need_initialization = true;
@@ -400,26 +389,27 @@ void OutputPreviewWindow::Render()
// Broadcasting menu
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.9f));
if (VideoBroadcast::available()) {
if ( ImGui::MenuItem( ICON_FA_GLOBE " SRT Broadcast", NULL, videoBroadcastEnabled()) )
if ( ImGui::MenuItem( ICON_FA_GLOBE " SRT Broadcast", NULL, Broadcast::manager().enabled(srt_) ) )
ToggleVideoBroadcast();
}
// Shared Memory menu
if (ShmdataBroadcast::available()) {
if ( ImGui::MenuItem( ICON_FA_MEMORY " SHM Shared Memory", NULL, sharedMemoryEnabled()) )
if ( ImGui::MenuItem( ICON_FA_MEMORY " SHM Shared Memory", NULL, Broadcast::manager().enabled(shm_) ) )
ToggleSharedMemory();
}
// Loopback camera menu
if (Loopback::available()) {
if ( ImGui::MenuItem( ICON_FA_VIDEO " Loopback Camera", NULL, loopbackCameraEnabled()) )
if ( ImGui::MenuItem( ICON_FA_VIDEO " Loopback Camera", NULL, Broadcast::manager().enabled(loopback_)) )
openInitializeSystemLoopback = ToggleLoopbackCamera();
}
ImGui::PopStyleColor(1);
// Display list of active stream
if (ls.size()>0 || videoBroadcastEnabled() || sharedMemoryEnabled() || loopbackCameraEnabled()) {
if (ls.size()>0 || Broadcast::manager().enabled(srt_) ||
Broadcast::manager().enabled(shm_) || Broadcast::manager().enabled(loopback_)) {
ImGui::Separator();
ImGui::MenuItem("Active streams:", nullptr, false, false);
@@ -428,37 +418,38 @@ void OutputPreviewWindow::Render()
ImGui::Text(" %s ", (*it).c_str() );
// SRT broadcast description
if (videoBroadcastEnabled()) {
ImGui::Text(" %s ", video_broadcaster_->info().c_str());
if (Broadcast::manager().enabled(srt_)) {
ImGui::Text(" %s ", Broadcast::manager().info(srt_).c_str());
// copy text icon to give user the srt link to connect to
ImVec2 draw_pos = ImGui::GetCursorPos();
ImGui::SetCursorPos(draw_pos + ImVec2(ImGui::GetContentRegionAvailWidth() - 1.2 * ImGui::GetTextLineHeightWithSpacing(), -0.8 * ImGui::GetFrameHeight()) );
char msg[256];
ImFormatString(msg, IM_ARRAYSIZE(msg), "srt//%s:%d", NetworkToolkit::host_ips()[1].c_str(), Settings::application.broadcast_port );
if (ImGuiToolkit::IconButton( ICON_FA_COPY, msg))
ImGui::SetClipboardText(msg);
std::string moreinfo = Broadcast::manager().info(srt_, true);
if (ImGuiToolkit::IconButton( ICON_FA_COPY, moreinfo.c_str()))
ImGui::SetClipboardText(moreinfo.c_str());
ImGui::SetCursorPos(draw_pos);
}
// Shared memory broadcast description
if (sharedMemoryEnabled()) {
ImGui::Text(" %s ", shm_broadcaster_->info().c_str());
if (Broadcast::manager().enabled(shm_)) {
ImGui::Text(" %s ", Broadcast::manager().info(shm_).c_str());
// 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_->gst_pipeline().c_str()))
ImGui::SetClipboardText(shm_broadcaster_->gst_pipeline().c_str());
std::string moreinfo = Broadcast::manager().info(shm_, true);
if (ImGuiToolkit::IconButton( ICON_FA_COPY, moreinfo.c_str()))
ImGui::SetClipboardText(moreinfo.c_str());
ImGui::SetCursorPos(draw_pos);
}
// Loopback camera description
if (loopbackCameraEnabled()) {
ImGui::Text(" %s ", loopback_broadcaster_->info().c_str());
if (Broadcast::manager().enabled(loopback_)) {
ImGui::Text(" %s ", Broadcast::manager().info(loopback_).c_str());
// copy text icon to give user the device 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, loopback_broadcaster_->device_name().c_str()))
ImGui::SetClipboardText(loopback_broadcaster_->device_name().c_str());
std::string moreinfo = Broadcast::manager().info(loopback_, true);
if (ImGuiToolkit::IconButton( ICON_FA_COPY, moreinfo.c_str()))
ImGui::SetClipboardText(moreinfo.c_str());
ImGui::SetCursorPos(draw_pos);
}
}
@@ -558,10 +549,10 @@ void OutputPreviewWindow::Render()
}
// broadcast indicator
float vertical = r;
if (video_broadcaster_)
if (Broadcast::manager().enabled(srt_))
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + vertical));
if (video_broadcaster_->busy())
if (Broadcast::manager().busy(srt_))
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f));
else
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.4f));
@@ -570,10 +561,10 @@ void OutputPreviewWindow::Render()
vertical += 2.f * r;
}
// shmdata indicator
if (shm_broadcaster_)
if (Broadcast::manager().enabled(shm_))
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + vertical));
if (shm_broadcaster_->busy())
if (Broadcast::manager().busy(shm_))
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f));
else
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.4f));
@@ -582,10 +573,10 @@ void OutputPreviewWindow::Render()
vertical += 2.f * r;
}
// loopback camera indicator
if (loopback_broadcaster_)
if (Broadcast::manager().enabled(loopback_))
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + vertical));
if (loopback_broadcaster_->busy())
if (Broadcast::manager().busy(loopback_))
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f));
else
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.4f));

View File

@@ -5,7 +5,6 @@
#include "WorkspaceWindow.h"
class VideoRecorder;
class VideoBroadcast;
class ShmdataBroadcast;
class Loopback;
@@ -13,9 +12,9 @@ class OutputPreviewWindow : public WorkspaceWindow
{
// frame grabbers
VideoRecorder *video_recorder_;
VideoBroadcast *video_broadcaster_;
ShmdataBroadcast *shm_broadcaster_;
Loopback *loopback_broadcaster_;
uint64_t srt_;
uint64_t shm_;
uint64_t loopback_;
// delayed trigger for recording
std::vector< std::future<VideoRecorder *> > _video_recorders;
@@ -34,13 +33,8 @@ public:
inline bool isRecording() const { return video_recorder_ != nullptr; }
void ToggleVideoBroadcast();
inline bool videoBroadcastEnabled() const { return video_broadcaster_ != nullptr; }
void ToggleSharedMemory();
inline bool sharedMemoryEnabled() const { return shm_broadcaster_ != nullptr; }
bool ToggleLoopbackCamera();
inline bool loopbackCameraEnabled() const { return loopback_broadcaster_!= nullptr; }
void Render();
void setVisible(bool on);

View File

@@ -516,8 +516,11 @@ void VideoRecorder::terminate()
Settings::application.recentRecordings.remove(filename_);
}
std::string VideoRecorder::info() const
std::string VideoRecorder::info(bool extended) const
{
if (extended)
return filename();
if (initialized_ && !active_ && !endofstream_)
return "Saving file...";

View File

@@ -20,6 +20,8 @@ public:
PNGRecorder(const std::string &basename = std::string());
std::string filename() const { return filename_; }
FrameGrabber::Type type () const override { return FrameGrabber::GRABBER_PNG; }
protected:
std::string init(GstCaps *caps) override;
@@ -60,7 +62,9 @@ public:
static const int framerate_preset_value[3];
VideoRecorder(const std::string &basename = std::string());
std::string info() const override;
FrameGrabber::Type type () const override { return FrameGrabber::GRABBER_VIDEO; }
std::string info(bool extended = false) const override;
std::string filename() const { return filename_; }
};

View File

@@ -203,16 +203,21 @@ std::string ShmdataBroadcast::gst_pipeline() const
return pipeline;
}
std::string ShmdataBroadcast::info() const
std::string ShmdataBroadcast::info(bool extended) const
{
std::ostringstream ret;
if (!initialized_)
ret << "Shared Memory starting..";
else if (active_)
ret << "Shared Memory " << socket_path_;
else
ret << "Shared Memory terminated";
if (extended) {
ret << gst_pipeline();
}
else {
if (!initialized_)
ret << "Shared Memory starting..";
else if (active_)
ret << "Shared Memory " << socket_path_;
else
ret << "Shared Memory terminated";
}
return ret.str();
}

View File

@@ -19,13 +19,15 @@ public:
ShmdataBroadcast(Method m = SHM_SHMSINK, const std::string &socketpath = "");
virtual ~ShmdataBroadcast() {}
FrameGrabber::Type type () const override { return FrameGrabber::GRABBER_SHM; }
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;
std::string info(bool extended = false) const override;
private:
std::string init(GstCaps *caps) override;

View File

@@ -516,17 +516,24 @@ void VideoStreamer::stop ()
active_ = false;
}
std::string VideoStreamer::info() const
std::string VideoStreamer::info(bool extended) const
{
std::ostringstream ret;
if (!initialized_)
ret << "Connecting";
else if (active_) {
ret << NetworkToolkit::stream_protocol_label[config_.protocol];
ret << " to ";
ret << config_.client_name;
if (extended) {
ret << NetworkToolkit::stream_send_pipeline[config_.protocol];
}
else
ret << "Streaming terminated.";
else {
if (!initialized_)
ret << "Connecting";
else if (active_) {
ret << NetworkToolkit::stream_protocol_label[config_.protocol];
ret << " to ";
ret << config_.client_name;
}
else
ret << "Streaming terminated.";
}
return ret.str();
}

View File

@@ -81,7 +81,9 @@ public:
VideoStreamer(const NetworkToolkit::StreamConfig &conf);
virtual ~VideoStreamer() {}
std::string info() const override;
FrameGrabber::Type type () const override { return FrameGrabber::GRABBER_P2P; }
std::string info(bool extended = false) const override;
};

View File

@@ -29,6 +29,7 @@
#include "Log.h"
#include "GstToolkit.h"
#include "NetworkToolkit.h"
#include "Settings.h"
#include "VideoBroadcast.h"
@@ -210,16 +211,24 @@ void VideoBroadcast::stop ()
active_ = false;
}
std::string VideoBroadcast::info() const
std::string VideoBroadcast::info(bool extended) const
{
std::ostringstream ret;
if (!initialized_)
ret << "SRT starting..";
else if (active_)
ret << "SRT Broadcast on port " << port_;
else
ret << "SRT terminated";
if (extended) {
ret << "srt://";
ret << NetworkToolkit::host_ips()[1];
ret << ":";
ret << port_;
}
else {
if (!initialized_)
ret << "SRT starting..";
else if (active_)
ret << "SRT Broadcast on port " << port_;
else
ret << "SRT terminated";
}
return ret.str();
}

View File

@@ -14,11 +14,13 @@ public:
VideoBroadcast(int port = BROADCAST_DEFAULT_PORT);
virtual ~VideoBroadcast() {}
FrameGrabber::Type type () const override { return FrameGrabber::GRABBER_BROADCAST; }
static bool available();
inline int port() const { return port_; }
void stop() override;
std::string info() const override;
std::string info(bool extended = false) const override;
private:
std::string init(GstCaps *caps) override;