From 8f147e292f6bab95f0c9747622a74df36cae2f4e Mon Sep 17 00:00:00 2001 From: LufeBisect Date: Thu, 9 Jan 2025 15:29:45 +0000 Subject: [PATCH 1/4] Introduction of nmos receiver with dynamically changing pipelines --- .../nmos_plugin_node_config_receiver.json | 39 ++ ...on => nmos_plugin_node_config_sender.json} | 14 +- cpp/libs/CMakeLists.txt | 1 + .../include/bisect/nmoscpp/configuration.h | 1 + cpp/libs/bisect_sdp/lib/src/reader.cpp | 3 +- .../gst_nmos_receiver_plugin/CMakeLists.txt | 67 +++ .../gst_nmos_receiver_plugin.cpp | 460 ++++++++++++++++++ cpp/libs/gst_nmos_receiver_plugin/utils.cpp | 189 +++++++ cpp/libs/gst_nmos_receiver_plugin/utils.h | 49 ++ .../gst_nmos_sender_plugin.cpp | 35 +- cpp/libs/gst_nmos_sender_plugin/utils.cpp | 4 +- cpp/libs/gst_nmos_sender_plugin/utils.h | 2 +- 12 files changed, 835 insertions(+), 29 deletions(-) create mode 100644 cpp/demos/config/nmos_plugin_node_config_receiver.json rename cpp/demos/config/{nmos_plugin_node_config.json => nmos_plugin_node_config_sender.json} (73%) create mode 100644 cpp/libs/gst_nmos_receiver_plugin/CMakeLists.txt create mode 100644 cpp/libs/gst_nmos_receiver_plugin/gst_nmos_receiver_plugin.cpp create mode 100644 cpp/libs/gst_nmos_receiver_plugin/utils.cpp create mode 100644 cpp/libs/gst_nmos_receiver_plugin/utils.h diff --git a/cpp/demos/config/nmos_plugin_node_config_receiver.json b/cpp/demos/config/nmos_plugin_node_config_receiver.json new file mode 100644 index 0000000..f856643 --- /dev/null +++ b/cpp/demos/config/nmos_plugin_node_config_receiver.json @@ -0,0 +1,39 @@ +{ + "node": { + "id": "8968180c-0418-4422-bea1-f0c642389637", + "configuration": { + "label": "BISECT OSSRF Node Receiver", + "description": "BISECT OSSRF node receiver", + "host_addresses": [ + "192.168.1.36" + ], + "interfaces": [ + { + "chassis_id": "c9-94-02-f7-3e-eb", + "name": "wlp1s0", + "port_id": "00-e0-4c-68-01-8d" + } + ], + "clocks": [ + { + "name": "clk0", + "ref_type": "ptp", + "traceable": false, + "version": "IEEE1588-2008", + "gmid": "00-20-fc-ff-fe-35-9c-26", + "locked": true + } + ], + "registry_address": "192.168.1.36", + "registry_version": "v1.3", + "registration_port": 8010, + "system_address": "192.168.1.36", + "system_version": "v1.0", + "system_port": 8010, + "http_port": 5114 + } + }, + "device": {}, + "receivers": [], + "senders": [] +} diff --git a/cpp/demos/config/nmos_plugin_node_config.json b/cpp/demos/config/nmos_plugin_node_config_sender.json similarity index 73% rename from cpp/demos/config/nmos_plugin_node_config.json rename to cpp/demos/config/nmos_plugin_node_config_sender.json index 74df109..3e33ee6 100644 --- a/cpp/demos/config/nmos_plugin_node_config.json +++ b/cpp/demos/config/nmos_plugin_node_config_sender.json @@ -2,16 +2,16 @@ "node": { "id": "d5504cd1-fe68-489d-99d4-20d3f075f062", "configuration": { - "label": "BISECT OSSRF Node2", - "description": "BISECT OSSRF node2", + "label": "BISECT OSSRF Node Sender", + "description": "BISECT OSSRF node sender", "host_addresses": [ - "192.168.1.120" + "192.168.1.36" ], "interfaces": [ { "chassis_id": "c8-94-02-f7-3e-eb", "name": "wlp1s0", - "port_id": "00-e0-4c-68-01-8d" + "port_id": "01-e0-4c-68-01-8d" } ], "clocks": [ @@ -24,13 +24,13 @@ "locked": true } ], - "registry_address": "192.168.1.120", + "registry_address": "192.168.1.36", "registry_version": "v1.3", "registration_port": 8010, - "system_address": "192.168.1.120", + "system_address": "192.168.1.36", "system_version": "v1.0", "system_port": 8010, - "http_port": 5113 + "http_port": 6114 } }, "device": {}, diff --git a/cpp/libs/CMakeLists.txt b/cpp/libs/CMakeLists.txt index 1e0bd5d..515ef7c 100644 --- a/cpp/libs/CMakeLists.txt +++ b/cpp/libs/CMakeLists.txt @@ -3,6 +3,7 @@ add_subdirectory(bisect_nmoscpp) add_subdirectory(bisect_sdp) add_subdirectory(bisect_gst) add_subdirectory(gst_nmos_sender_plugin) +add_subdirectory(gst_nmos_receiver_plugin) add_subdirectory(ossrf_nmos_api) add_subdirectory(ossrf_gstreamer_api) add_subdirectory(bisect_json) diff --git a/cpp/libs/bisect_nmoscpp/lib/include/bisect/nmoscpp/configuration.h b/cpp/libs/bisect_nmoscpp/lib/include/bisect/nmoscpp/configuration.h index 4e8d2d9..6234892 100644 --- a/cpp/libs/bisect_nmoscpp/lib/include/bisect/nmoscpp/configuration.h +++ b/cpp/libs/bisect_nmoscpp/lib/include/bisect/nmoscpp/configuration.h @@ -107,6 +107,7 @@ namespace bisect::nmoscpp nmos::rational exact_framerate; std::string chroma_sub_sampling; nmos::interlace_mode structure; + int depth = 0; }; struct audio_sender_info_t diff --git a/cpp/libs/bisect_sdp/lib/src/reader.cpp b/cpp/libs/bisect_sdp/lib/src/reader.cpp index 2364b67..634ef5f 100644 --- a/cpp/libs/bisect_sdp/lib/src/reader.cpp +++ b/cpp/libs/bisect_sdp/lib/src/reader.cpp @@ -145,6 +145,7 @@ namespace .exact_framerate = nmos::rational(params.exactframerate.numerator(), params.exactframerate.denominator()), .chroma_sub_sampling = "YCbCr-4:2:2", .structure = params.interlace ? nmos::interlace_modes::interlaced_tff : nmos::interlace_modes::progressive, + .depth = static_cast(params.depth), }; } } // namespace @@ -196,4 +197,4 @@ expected bisect::sdp::parse_sdp(const std::string& sdp) { BST_FAIL("error parsing SDP: {}", ex.what()); } -} \ No newline at end of file +} diff --git a/cpp/libs/gst_nmos_receiver_plugin/CMakeLists.txt b/cpp/libs/gst_nmos_receiver_plugin/CMakeLists.txt new file mode 100644 index 0000000..fb91789 --- /dev/null +++ b/cpp/libs/gst_nmos_receiver_plugin/CMakeLists.txt @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 3.16) +project(gst_nmos_receiver_plugin LANGUAGES CXX) + +find_package(nlohmann_json REQUIRED) +find_package(PkgConfig REQUIRED) + +# Locate GLib package +pkg_check_modules(GLIB REQUIRED glib-2.0) + +# Locate GStreamer packages +pkg_search_module(GSTREAMER REQUIRED gstreamer-1.0>=1.4) +pkg_search_module(GSTREAMER_APP REQUIRED gstreamer-app-1.0>=1.4) +pkg_search_module(GSTREAMER_AUDIO REQUIRED gstreamer-audio-1.0>=1.4) +pkg_search_module(GSTREAMER_VIDEO REQUIRED gstreamer-video-1.0>=1.4) + +# Include GLib directories and link libraries +include_directories(${GLIB_INCLUDE_DIRS}) +link_directories(${GLIB_LIBRARY_DIRS}) +add_definitions(${GLIB_CFLAGS_OTHER}) + +# Include source files for gst_nmos_receiver_plugin +file(GLOB_RECURSE PLUGIN_SOURCE_FILES *.cpp *.h) + +# Include source files for utils +file(GLOB_RECURSE UTILS_SOURCE_FILES utils/*.cpp utils/*.h) + +# Combine utils and plugin sources +set(SOURCE_FILES ${PLUGIN_SOURCE_FILES} ${UTILS_SOURCE_FILES}) + +# Define the plugin as a shared library +add_library(${PROJECT_NAME} MODULE ${SOURCE_FILES}) + +# Include directories +target_include_directories(${PROJECT_NAME} + PRIVATE ${GSTREAMER_INCLUDE_DIRS} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/utils/include +) + +# Link GStreamer libraries +target_compile_options(${PROJECT_NAME} PRIVATE ${GSTREAMER_CFLAGS_OTHER}) +target_link_libraries(${PROJECT_NAME} + PRIVATE + ${GSTREAMER_LIBRARIES} + ${GSTREAMER_APP_LIBRARIES} + ${GSTREAMER_AUDIO_LIBRARIES} + ${GSTREAMER_VIDEO_LIBRARIES} + PUBLIC + bisect::project_warnings + bisect::expected + bisect::bisect_nmoscpp + bisect::bisect_json + nlohmann_json::nlohmann_json + ossrf::ossrf_nmos_api + ${GLIB_LIBRARIES} +) + +# Specify the output directory and the library name +set_target_properties(${PROJECT_NAME} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins + OUTPUT_NAME "gstnmosvideoreceiver" +) + +add_library(ossrf::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) + +# Install the plugin to the system's GStreamer plugin directory +install(TARGETS ${PROJECT_NAME} + LIBRARY DESTINATION ~/.local/lib/gstreamer-1.0) diff --git a/cpp/libs/gst_nmos_receiver_plugin/gst_nmos_receiver_plugin.cpp b/cpp/libs/gst_nmos_receiver_plugin/gst_nmos_receiver_plugin.cpp new file mode 100644 index 0000000..f192aac --- /dev/null +++ b/cpp/libs/gst_nmos_receiver_plugin/gst_nmos_receiver_plugin.cpp @@ -0,0 +1,460 @@ + +/* GStreamer + * + * Copyright (C) <2024> Bisect Lda + * @author: Luís Ferreira + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Suite 500, + * Boston, MA 02110-1335, USA. + */ + +#include "bisect/expected/macros.h" +#include "bisect/json.h" +#include "bisect/sdp.h" +#include "bisect/sdp/reader.h" +#include "bisect/nmoscpp/configuration.h" +#include "utils.h" +#include "ossrf/nmos/api/nmos_client.h" +#include +#include + +using namespace bisect; +using namespace bisect::sdp; + +GST_DEBUG_CATEGORY_STATIC(gst_nmosreceiver_debug_category); +#define GST_CAT_DEFAULT gst_nmosreceiver_debug_category +#define GST_TYPE_NMOSRECEIVER (gst_nmosreceiver_get_type()) +#define GST_NMOSRECEIVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_NMOSRECEIVER, GstNmosreceiver)) +#define GST_NMOSRECEIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_NMOSRECEIVER, GstNmosreceiverClass)) +#define GST_IS_NMOSRECEIVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_NMOSRECEIVER)) +#define GST_IS_NMOSRECEIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_NMOSRECEIVER)) + +typedef struct _GstNmosreceiver +{ + GstBin parent; + GstPad* element_pad; + GstPad* bin_pad; + GstClock* clock; + GstElement* bin; + GstElement* udp_src; + GstElement* rtp_video_depay; + GstElement* rtp_jitter_buffer; + GstElement* queue1; + GstElement* video_rate; + GstElement* identity; + ossrf::nmos_client_uptr client; + config_fields_t config; + sdp_settings_t sdp_settings; + bool nmos_active; +} GstNmosreceiver; + +typedef struct _GstNmosreceiverClass +{ + GstBinClass parent_class; +} GstNmosreceiverClass; + +G_DEFINE_TYPE_WITH_CODE(GstNmosreceiver, gst_nmosreceiver, GST_TYPE_BIN, + GST_DEBUG_CATEGORY_INIT(gst_nmosreceiver_debug_category, "nmosvideoreceiver", 0, + "NMOS Receiver Plugin")) +// Pad template +static GstStaticPadTemplate src_template = + GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS("video/x-raw, " + "format=(string){ I420, NV12, RGB, YUV422P, UYVP }; " + "application/x-rtp, " + "media=(string)video, " + "clock-rate=(int)90000")); + +enum class PropertyId : uint32_t +{ + NodeId = 1, + NodeConfigFileLocation = 2, + DeviceId = 3, + DeviceLabel = 4, + DeviceDescription = 5, + ReceiverId = 6, + ReceiverLabel = 7, + ReceiverDescription = 8, + DstAddress = 9 +}; + +// Set properties so element variables can change depending on them +static void gst_nmosreceiver_set_property(GObject* object, guint property_id, const GValue* value, GParamSpec* pspec) +{ + GstNmosreceiver* self = GST_NMOSRECEIVER(object); + + switch(static_cast(property_id)) + { + case PropertyId::NodeId: self->config.node.id = g_value_dup_string(value); break; + + case PropertyId::NodeConfigFileLocation: + self->config.node.configuration_location = g_value_dup_string(value); + break; + + case PropertyId::DeviceId: self->config.device.id = g_value_dup_string(value); break; + + case PropertyId::DeviceLabel: self->config.device.label = g_value_dup_string(value); break; + + case PropertyId::DeviceDescription: self->config.device.description = g_value_dup_string(value); break; + + case PropertyId::ReceiverId: self->config.id = g_value_dup_string(value); break; + + case PropertyId::ReceiverLabel: self->config.label = g_value_dup_string(value); break; + + case PropertyId::ReceiverDescription: self->config.description = g_value_dup_string(value); break; + + case PropertyId::DstAddress: self->config.address = g_value_dup_string(value); break; + + default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; + } +} + +static void gst_nmosreceiver_get_property(GObject* object, guint property_id, GValue* value, GParamSpec* pspec) +{ + GstNmosreceiver* self = GST_NMOSRECEIVER(object); + + switch(static_cast(property_id)) + { + + case PropertyId::NodeId: g_value_set_string(value, self->config.node.id.c_str()); break; + case PropertyId::NodeConfigFileLocation: + g_value_set_string(value, self->config.node.configuration_location.c_str()); + break; + case PropertyId::DeviceId: g_value_set_string(value, self->config.device.id.c_str()); break; + case PropertyId::DeviceLabel: g_value_set_string(value, self->config.device.label.c_str()); break; + case PropertyId::DeviceDescription: g_value_set_string(value, self->config.device.description.c_str()); break; + case PropertyId::ReceiverId: g_value_set_string(value, self->config.id.c_str()); break; + case PropertyId::ReceiverLabel: g_value_set_string(value, self->config.label.c_str()); break; + case PropertyId::ReceiverDescription: g_value_set_string(value, self->config.description.c_str()); break; + case PropertyId::DstAddress: g_value_set_string(value, self->config.address.c_str()); break; + + default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; + } +} + +static GstPadProbeReturn block_pad_probe_cb(GstPad* pad, GstPadProbeInfo* info, gpointer user_data) +{ + return GST_PAD_PROBE_DROP; +} + +void remove_old_bin(GstNmosreceiver* self) +{ + if(self->element_pad != nullptr) + { + gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_start()); + gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_stop(FALSE)); + + gulong block_id = + gst_pad_add_probe(self->element_pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, block_pad_probe_cb, NULL, NULL); + + self->clock = gst_element_get_clock(GST_ELEMENT(self)); + + gst_element_set_state(self->bin, GST_STATE_NULL); + + gst_bin_remove(GST_BIN(self), self->bin); + + if(block_id != 0) + { + gst_pad_remove_probe(self->element_pad, block_id); + } + gst_object_unref(self->bin); + } +} + +void construct_pipeline(GstNmosreceiver* self) +{ + auto format = self->sdp_settings.format; + try + { + bisect::nmoscpp::video_sender_info_t video_info = std::get(format); + auto& fr = video_info.exact_framerate; + g_print("\n===== SDP Settings =====\n" + "Video (from variant):\n" + " - height: %d\n" + " - width: %d\n" + " - framerate: %ld/%ld\n" + " - chroma_sub_sampling: %s\n" + " - structure (interlace mode): %s\n" + " - depth: %d\n" + "Primary leg:\n" + " - destination_ip: %s\n" + " - destination_port: %d\n" + "\n", + video_info.height, video_info.width, fr.numerator(), fr.denominator(), + video_info.chroma_sub_sampling.c_str(), video_info.structure.name.c_str(), video_info.depth, + self->sdp_settings.primary.destination_ip.value().c_str(), + self->sdp_settings.primary.destination_port.value()); + + self->bin = nullptr; + + self->queue1 = nullptr; + self->video_rate = nullptr; + self->udp_src = nullptr; + self->rtp_jitter_buffer = nullptr; + self->rtp_video_depay = nullptr; + self->identity = nullptr; + + GstElement* bin = gst_bin_new("dynamic-bin"); + if(bin == nullptr) + { + GST_ERROR_OBJECT(self, "Failed to create bin"); + return; + } + + self->queue1 = gst_element_factory_make("queue", nullptr); + self->video_rate = gst_element_factory_make("videorate", nullptr); + self->udp_src = gst_element_factory_make("udpsrc", nullptr); + self->rtp_jitter_buffer = gst_element_factory_make("rtpjitterbuffer", nullptr); + self->rtp_video_depay = gst_element_factory_make("rtpvrawdepay", nullptr); + self->identity = gst_element_factory_make("identity", nullptr); + + if(!self->udp_src || !self->rtp_jitter_buffer || !self->queue1 || !self->rtp_video_depay) + { + GST_ERROR_OBJECT(self, "Failed to create pipeline elements."); + return; + } + + g_object_set(G_OBJECT(self->udp_src), "address", self->sdp_settings.primary.destination_ip.value().c_str(), + "port", self->sdp_settings.primary.destination_port.value(), "buffer-size", 67108864, nullptr); + + // g_signal_connect(self->identity, "handoff", G_CALLBACK(identity_handoff_callback), NULL); + + GstCaps* caps = gst_caps_new_simple( + "application/x-rtp", "media", G_TYPE_STRING, "video", "clock-rate", G_TYPE_INT, 90000, "encoding-name", + G_TYPE_STRING, "RAW", "payload", G_TYPE_INT, 96, "width", G_TYPE_STRING, + std::to_string(video_info.width).c_str(), "height", G_TYPE_STRING, + std::to_string(video_info.height).c_str(), "format", G_TYPE_STRING, "UYVP", "sampling", G_TYPE_STRING, + video_info.chroma_sub_sampling.c_str(), "depth", G_TYPE_STRING, std::to_string(video_info.depth).c_str(), + "framerate", GST_TYPE_FRACTION, fr.numerator(), fr.denominator(), nullptr); + + g_object_set(G_OBJECT(self->udp_src), "caps", caps, nullptr); + + g_object_set(G_OBJECT(self->rtp_jitter_buffer), "do-retransmission", true, "do-lost", true, "mode", 2, + "latency", 0, nullptr); + + g_object_set(G_OBJECT(self->queue1), "max-size-buffers", 3, nullptr); + + gst_bin_add_many(GST_BIN(bin), self->udp_src, self->rtp_jitter_buffer, self->video_rate, self->queue1, + self->rtp_video_depay, self->identity, nullptr); + + gst_element_sync_state_with_parent(self->udp_src); + gst_element_sync_state_with_parent(self->rtp_jitter_buffer); + gst_element_sync_state_with_parent(self->queue1); + gst_element_sync_state_with_parent(self->rtp_video_depay); + gst_element_sync_state_with_parent(self->identity); + + self->bin = bin; + + if(gst_element_link_many(self->udp_src, self->rtp_jitter_buffer, self->queue1, self->rtp_video_depay, + nullptr) == false) + { + GST_ERROR_OBJECT(self, "Failed to link elements inside the bin."); + gst_object_unref(self->bin); + return; + } + + // Create a ghost pad from the depay's src + GstPad* bin_src_pad = gst_element_get_static_pad(self->rtp_video_depay, "src"); + if(bin_src_pad == nullptr) + { + GST_ERROR_OBJECT(self, "Failed to get src pad from rtp_video_depay."); + gst_object_unref(bin); + return; + } + + GstPad* bin_ghost_pad = gst_ghost_pad_new("src", bin_src_pad); + gst_object_unref(bin_src_pad); + self->bin_pad = bin_ghost_pad; + + if(bin_ghost_pad == nullptr || gst_element_add_pad(bin, bin_ghost_pad) == false) + { + GST_ERROR_OBJECT(self, "Failed to create or add ghost pad to bin."); + gst_object_unref(bin); + return; + } + + gst_bin_add(GST_BIN(self), bin); + + GstPad* plugin_pad = gst_element_get_static_pad(GST_ELEMENT(self), "src"); + if(plugin_pad == nullptr) + { + GST_ERROR_OBJECT(self, "Failed to get plugin ghost pad."); + gst_object_unref(bin); + return; + } + + self->element_pad = plugin_pad; + + if(gst_ghost_pad_set_target(GST_GHOST_PAD(self->element_pad), bin_ghost_pad) == false) + { + GST_ERROR_OBJECT(self, "Failed to link plugin ghost pad to bin's ghost pad."); + gst_object_unref(bin); + return; + } + + gst_object_unref(plugin_pad); + } + catch(std::bad_variant_access const& ex) + { + g_print("Invalid format sent for current receiver.\n"); + } +} + +void create_nmos(GstNmosreceiver* self) +{ + + const auto node_config_json = create_node_config(self->config); + const auto device_config_json = create_device_config(self->config); + nlohmann::json_abi_v3_11_3::json receiver_config_json = nullptr; + + receiver_config_json = create_receiver_config(self->config); + + // g_print("Node Configuration: %s\n", node_config_json.dump().c_str()); + // g_print("Device Configuration: %s\n", device_config_json.dump().c_str()); + // g_print("Receiver Configuration: %s\n", receiver_config_json.dump().c_str()); + + auto receiver_callback = [self](const std::optional& sdp, bool master_enabled) { + // Debug print + fmt::print("Receiver Activation Callback: SDP={}, Master Enabled={}\n", sdp.has_value() ? sdp.value() : "None", + master_enabled); + + if(sdp) + { + fmt::print("Received SDP: {}\n", sdp.value()); + auto sdp_settings = parse_sdp(sdp.value()); + if(sdp_settings.has_value()) + { + self->sdp_settings = sdp_settings.value(); + remove_old_bin(self); + construct_pipeline(self); + auto time = gst_element_get_base_time(GST_ELEMENT(self)); + gst_element_set_state(GST_ELEMENT(self), GST_STATE_PLAYING); + gst_pad_set_offset(self->bin_pad, (long)(gst_clock_get_time(self->clock) - time)); + } + } + else + { + fmt::print("No SDP provided.\n"); + } + fmt::print("Master enabled: {}\n", master_enabled); + }; + + auto result = ossrf::nmos_client_t::create(self->config.node.id, node_config_json.dump()); + if(result.has_value()) + { + self->client = std::move(result.value()); + self->client->add_device(device_config_json.dump()); + self->client->add_receiver(self->config.device.id, receiver_config_json.dump(), receiver_callback); + GST_INFO_OBJECT(self, "NMOS client initialized successfully."); + } + else + { + GST_ERROR_OBJECT(self, "Failed to initialize NMOS client."); + } +} + +static GstStateChangeReturn gst_nmosreceiver_change_state(GstElement* element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstNmosreceiver* self = GST_NMOSRECEIVER(element); + + switch(transition) + { + case GST_STATE_CHANGE_NULL_TO_READY: { + if(self->nmos_active != true) + { + create_nmos(self); + self->nmos_active = true; + } + } + break; + + default: break; + } + + ret = GST_ELEMENT_CLASS(gst_nmosreceiver_parent_class)->change_state(element, transition); + + return ret; +} + +static void add_property(GObjectClass* object_class, guint id, const gchar* name, const gchar* nick, const gchar* blurb, + const gchar* default_value) +{ + g_object_class_install_property(object_class, id, + g_param_spec_string(name, nick, blurb, default_value, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); +} + +// Class initialization +static void gst_nmosreceiver_class_init(GstNmosreceiverClass* klass) +{ + GObjectClass* object_class = G_OBJECT_CLASS(klass); + GstElementClass* element_class = GST_ELEMENT_CLASS(klass); + + gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&src_template)); + + object_class->set_property = gst_nmosreceiver_set_property; + object_class->get_property = gst_nmosreceiver_get_property; + + add_property(object_class, 1, "node-id", "Node ID", "The ID of the node", "d49c85db-1c33-4f21-b160-58edd2af1810"); + add_property( + object_class, 2, "node-config-file-location", "Node Config File Location", + "The location of the configuration file for the node", + "/home/nmos/repos/nmos-sender-receiver-framework/cpp/demos/config/nmos_plugin_node_config_receiver.json"); + add_property(object_class, 3, "device-id", "Device ID", "The ID of the device", + "1ad20d7c-8c58-4c84-8f14-3cf3e3af164c"); + add_property(object_class, 4, "device-label", "Device Label", "The label to identify the device", "OSSRF Device"); + add_property(object_class, 5, "device-description", "Device Description", "Description of the device", + "OSSRF Device Description"); + add_property(object_class, 6, "receiver-id", "Receiver ID", "UUID of the receiver", + "db9f46cf-2414-4e25-b6c6-2078159857f9"); + add_property(object_class, 7, "receiver-label", "Label of the receiver", "Label of the receiver", + "BISECT OSSRF Video Receiver"); + add_property(object_class, 8, "receiver-description", "Description of the receiver", "Description of the receiver", + "BISECT OSSRF Video Receiver"); + add_property(object_class, 9, "destination-address", "Destination Address", "Address of the destination", + "127.0.0.1"); + + gst_element_class_set_static_metadata(element_class, "NMOS Video Receiver", "Source/Network", + "Receives raw video from NMOS", "Luis Ferreira "); + + element_class->change_state = gst_nmosreceiver_change_state; +} + +// Object initialization +static void gst_nmosreceiver_init(GstNmosreceiver* self) +{ + GstPadTemplate* src_tmpl = gst_static_pad_template_get(&src_template); + GstPad* ghost_src = gst_ghost_pad_new_no_target_from_template("src", src_tmpl); + gst_object_unref(src_tmpl); + gst_element_add_pad(GST_ELEMENT(self), ghost_src); + + create_default_config_fields(&self->config); +} + +static gboolean plugin_init(GstPlugin* plugin) +{ + return gst_element_register(plugin, "nmosvideoreceiver", GST_RANK_NONE, GST_TYPE_NMOSRECEIVER); +} + +#define VERSION "0.1" +#define PACKAGE "gst-nmos-receiver-plugin" +#define PACKAGE_NAME "AMWA NMOS Sender and Receiver Framework Plugins" +#define GST_PACKAGE_ORIGIN "https://www.amwa.tv/" +#define GST_PACKAGE_LICENSE "Apache-2.0" + +GST_PLUGIN_DEFINE(GST_VERSION_MAJOR, GST_VERSION_MINOR, nmosvideoreceiver, + "Plugin to receive video stream from NMOS application", plugin_init, VERSION, GST_PACKAGE_LICENSE, + PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/cpp/libs/gst_nmos_receiver_plugin/utils.cpp b/cpp/libs/gst_nmos_receiver_plugin/utils.cpp new file mode 100644 index 0000000..044bddb --- /dev/null +++ b/cpp/libs/gst_nmos_receiver_plugin/utils.cpp @@ -0,0 +1,189 @@ +#include "utils.h" +#include "ossrf/nmos/api/nmos_client.h" +#include "bisect/nmoscpp/configuration.h" +#include "bisect/expected/macros.h" +#include "bisect/expected.h" +#include "bisect/json.h" +#include + +using namespace bisect; +using json = nlohmann::json; + +void create_default_config_fields(config_fields_t* config) +{ + if(!config) + { + g_critical("Config pointer is NULL"); + return; + } + + // Initialize node_fields_t + config->node.id = "d49c85db-1c33-4f21-b160-58edd2af1810"; + config->node.configuration_location = + "/home/nmos/repos/nmos-sender-receiver-framework/cpp/demos/config/nmos_plugin_node_config_receiver.json"; + + // Initialize device_fields_t + config->device.id = "1ad20d7c-8c58-4c84-8f14-3cf3e3af164c"; + config->device.label = "OSSRF Device2"; + config->device.description = "OSSRF Device2"; + + // Initialize config_fields_t + config->id = "db9f46cf-2414-4e25-b6c6-2078159857f9"; + config->label = "BISECT OSSRF Video Receiver"; + config->description = "BISECT OSSRF Video Receiver"; + config->interface_name = "wlp1s0"; + config->address = "192.168.1.36"; +} + +json create_node_config(config_fields_t& config) +{ + const std::string node_config_str = get_node_config(config.node.configuration_location); + json node_config = json::parse(node_config_str); + node_config["id"] = config.node.id; + + return node_config; +} + +json create_device_config(config_fields_t& config) +{ + json device = { + {"id", config.device.id}, {"label", config.device.label}, {"description", config.device.description}}; + return device; +} + +json create_receiver_config(config_fields_t& config) +{ + json sender = { + {"id", config.id}, + {"label", config.label}, + {"description", config.description}, + {"network", {{"primary", {{"interface_address", config.address}, {"interface_name", config.interface_name}}}}}, + {"capabilities", {"video/raw"}}}; + return sender; +} + +std::string translate_video_format(const std::string& gst_format) +{ + static const std::unordered_map video_format_map = {{"UYVP", "YCbCr-4:2:2"}, + {"RGBA", "RGBA-8:8:8:8"}}; + + auto it = video_format_map.find(gst_format); + if(it != video_format_map.end()) + { + return it->second; + } + return gst_format; +} + +std::string translate_audio_format(const std::string& gst_format) +{ + static const std::unordered_map audio_format_map = {{"S24BE", "L24"}, {"S16LE", "L16"}}; + + auto it = audio_format_map.find(gst_format); + if(it != audio_format_map.end()) + { + return it->second; + } + return gst_format; +} + +expected load_configuration_from_file(std::string_view config_file) +{ + std::ifstream ifs(config_file.data()); + BST_ENFORCE(ifs.is_open(), "Failed opening file {}", config_file); + std::ostringstream buffer; + buffer << ifs.rdbuf(); + return parse_json(buffer.str()); +} + +std::string get_node_id(char* node_configuration_location) +{ + const auto configuration_result = load_configuration_from_file(node_configuration_location); + + const json& configuration = configuration_result.value(); + + auto node_result = find(configuration, "node"); + + const json& node = node_result.value(); + + auto node_id_result = find(node, "id"); + auto node_config_result = find(node, "configuration"); + + if(node_id_result.has_value() && node_config_result.has_value()) + { + const std::string node_id = node_id_result.value(); + const std::string node_configuration = node_config_result.value().dump(); + + return node_id; + } + + return ""; +} + +std::string get_node_config(std::string node_configuration_location) +{ + const auto configuration_result = load_configuration_from_file(node_configuration_location.c_str()); + + const json& configuration = configuration_result.value(); + + auto node_result = find(configuration, "node"); + + const json& node = node_result.value(); + + auto node_id_result = find(node, "id"); + auto node_config_result = find(node, "configuration"); + + if(node_id_result.has_value() && node_config_result.has_value()) + { + const std::string node_id = node_id_result.value(); + const std::string node_configuration = node_config_result.value().dump(); + + return node_configuration; + } + + return ""; +} + +std::string get_device_id(char* device_configuration_location) +{ + const auto configuration_result = load_configuration_from_file(device_configuration_location); + + const json& configuration = configuration_result.value(); + + auto device_result = find(configuration, "device"); + + const json& device = device_result.value(); + + auto device_id_result = find(device, "id"); + + const std::string device_id = device_id_result.value(); + const std::string device_config = device.dump(); + + return device_id; +} + +std::string get_device_config(char* device_configuration_location) +{ + const auto configuration_result = load_configuration_from_file(device_configuration_location); + + const json& configuration = configuration_result.value(); + + auto device_result = find(configuration, "device"); + + const json& device = device_result.value(); + + auto device_id_result = find(device, "id"); + + const std::string device_id = device_id_result.value(); + const std::string device_config = device.dump(); + + return device_config; +} + +std::string get_sender_config(char* sender_configuration_location) +{ + // FIX ME: Gonna be hardcoded for now + const auto sender_config = load_configuration_from_file(sender_configuration_location); + + return sender_config.value().dump(); +} diff --git a/cpp/libs/gst_nmos_receiver_plugin/utils.h b/cpp/libs/gst_nmos_receiver_plugin/utils.h new file mode 100644 index 0000000..8a0de55 --- /dev/null +++ b/cpp/libs/gst_nmos_receiver_plugin/utils.h @@ -0,0 +1,49 @@ +#pragma once +#include +#include +#include + +typedef struct node_fields_t +{ + std::string id; + std::string configuration_location; +} node_fields_t; + +typedef struct device_fields_t +{ + std::string id; + std::string label; + std::string description; +} device_fields_t; + +typedef struct config_fields_t +{ + node_fields_t node; + device_fields_t device; + std::string id; + std::string label; + std::string description; + std::string address; + gint port; + std::string interface_name; +} config_fields_t; + +void create_default_config_fields(config_fields_t* config); + +nlohmann::json create_node_config(config_fields_t& config); +nlohmann::json create_device_config(config_fields_t& config); +nlohmann::json create_receiver_config(config_fields_t& config); + +std::string translate_video_format(const std::string& gst_format); + +std::string translate_audio_format(const std::string& gst_format); + +std::string get_node_id(char* node_configuration_location); + +std::string get_node_config(std::string node_configuration_location); + +std::string get_device_id(char* device_configuration_location); + +std::string get_device_config(char* device_configuration_location); + +std::string get_sender_config(char* sender_configuration_location); diff --git a/cpp/libs/gst_nmos_sender_plugin/gst_nmos_sender_plugin.cpp b/cpp/libs/gst_nmos_sender_plugin/gst_nmos_sender_plugin.cpp index 4d88232..b3884e9 100644 --- a/cpp/libs/gst_nmos_sender_plugin/gst_nmos_sender_plugin.cpp +++ b/cpp/libs/gst_nmos_sender_plugin/gst_nmos_sender_plugin.cpp @@ -109,18 +109,17 @@ static void gst_nmossender_set_property(GObject* object, guint property_id, cons case PropertyId::InterfaceName: self->config.network.interface_name = g_value_dup_string(value); - g_object_set(G_OBJECT(self->udpsink), "bind_address", self->config.network.interface_name.c_str(), NULL); + g_object_set(G_OBJECT(self->udpsink), "bind_address", self->config.network.interface_name.c_str(), nullptr); break; case PropertyId::DestinationAddress: self->config.network.destination_address = g_value_dup_string(value); - g_print("\n\n\n\n\n%s", self->config.network.destination_address.c_str()); - g_object_set(G_OBJECT(self->udpsink), "host", self->config.network.destination_address.c_str(), NULL); + g_object_set(G_OBJECT(self->udpsink), "host", self->config.network.destination_address.c_str(), nullptr); break; case PropertyId::DestinationPort: - self->config.network.destination_port = g_value_dup_string(value); - g_object_set(G_OBJECT(self->udpsink), "port", atoi(self->config.network.destination_port.c_str()), NULL); + self->config.network.destination_port = atoi(g_value_get_string(value)); + g_object_set(G_OBJECT(self->udpsink), "port", self->config.network.destination_port, nullptr); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; @@ -181,12 +180,12 @@ static gboolean gst_nmossender_sink_event(GstPad* pad, GstObject* parent, GstEve // g_print("Audio caps detected\n"); // GST_INFO_OBJECT(self, "Audio caps detected"); // - if(!gst_element_link(self->queue, self->audio_payloader)) + if(gst_element_link(self->queue, self->audio_payloader) == false) { GST_ERROR_OBJECT(self, "Failed to link queue to audio_payloader"); return false; } - if(!gst_element_link(self->audio_payloader, self->udpsink)) + if(gst_element_link(self->audio_payloader, self->udpsink) == false) { GST_ERROR_OBJECT(self, "Failed to link audio_payloader to udpsink"); return false; @@ -223,12 +222,12 @@ static gboolean gst_nmossender_sink_event(GstPad* pad, GstObject* parent, GstEve // g_print("Video caps detected\n"); // GST_INFO_OBJECT(self, "Video caps detected"); // - if(!gst_element_link(self->queue, self->video_payloader)) + if(gst_element_link(self->queue, self->video_payloader) == false) { GST_ERROR_OBJECT(self, "Failed to link queue to video_payloader"); return false; } - if(!gst_element_link(self->video_payloader, self->udpsink)) + if(gst_element_link(self->video_payloader, self->udpsink) == false) { GST_ERROR_OBJECT(self, "Failed to link video_payloader to udpsink"); return false; @@ -311,7 +310,7 @@ static GstStateChangeReturn gst_nmossender_change_state(GstElement* element, Gst // g_print("Sender Configuration: %s\n", sender_config_json.dump().c_str()); auto result = ossrf::nmos_client_t::create(self->config.node.id, node_config_json.dump()); - if(!result.has_value()) + if(result.has_value() == false) { GST_ERROR_OBJECT(self, "Failed to initialize NMOS client. Node ID: %s", self->config.node.id.c_str()); return GST_STATE_CHANGE_FAILURE; @@ -394,10 +393,10 @@ static void gst_nmossender_class_init(GstNmossenderClass* klass) /* Object initialization */ static void gst_nmossender_init(GstNmossender* self) { - self->queue = gst_element_factory_make("queue", NULL); - self->video_payloader = gst_element_factory_make("rtpvrawpay", NULL); - self->audio_payloader = gst_element_factory_make("rtpL24pay", NULL); - self->udpsink = gst_element_factory_make("udpsink", NULL); + self->queue = gst_element_factory_make("queue", nullptr); + self->video_payloader = gst_element_factory_make("rtpvrawpay", nullptr); + self->audio_payloader = gst_element_factory_make("rtpL24pay", nullptr); + self->udpsink = gst_element_factory_make("udpsink", nullptr); if(!self->queue || !self->video_payloader || !self->audio_payloader || !self->udpsink) { @@ -411,11 +410,11 @@ static void gst_nmossender_init(GstNmossender* self) g_object_set(G_OBJECT(self->udpsink), "host", "127.0.0.1", "port", 9999, NULL); create_default_config_fields(&self->config); - gst_bin_add_many(GST_BIN(self), self->queue, self->video_payloader, self->audio_payloader, self->udpsink, NULL); + gst_bin_add_many(GST_BIN(self), self->queue, self->video_payloader, self->audio_payloader, self->udpsink, nullptr); // create and configure the sink pad GstPad* queue_sinkpad = gst_element_get_static_pad(self->queue, "sink"); - if(!queue_sinkpad) + if(queue_sinkpad == nullptr) { GST_ERROR_OBJECT(self, "Failed to get static pad 'sink' from queue"); return; @@ -423,7 +422,7 @@ static void gst_nmossender_init(GstNmossender* self) GstPad* sink_ghost_pad = gst_ghost_pad_new("sink", queue_sinkpad); gst_object_unref(queue_sinkpad); - if(!sink_ghost_pad) + if(sink_ghost_pad == nullptr) { GST_ERROR_OBJECT(self, "Failed to create ghost pad for sink"); return; @@ -431,7 +430,7 @@ static void gst_nmossender_init(GstNmossender* self) gst_pad_set_event_function(sink_ghost_pad, gst_nmossender_sink_event); - if(!gst_element_add_pad(GST_ELEMENT(self), sink_ghost_pad)) + if(gst_element_add_pad(GST_ELEMENT(self), sink_ghost_pad) == false) { GST_ERROR_OBJECT(self, "Failed to add ghost pad to element; pad with same name might exist"); gst_object_unref(sink_ghost_pad); diff --git a/cpp/libs/gst_nmos_sender_plugin/utils.cpp b/cpp/libs/gst_nmos_sender_plugin/utils.cpp index 5513e0c..a197f82 100644 --- a/cpp/libs/gst_nmos_sender_plugin/utils.cpp +++ b/cpp/libs/gst_nmos_sender_plugin/utils.cpp @@ -20,7 +20,7 @@ void create_default_config_fields(config_fields_t* config) // Initialize node_fields_t config->node.id = "d5504cd1-fe68-489d-99d4-20d3f075f062"; config->node.configuration_location = - "/home/nmos/repos/nmos-sender-receiver-framework/cpp/demos/config/nmos_plugin_node_config.json"; + "/home/nmos/repos/nmos-sender-receiver-framework/cpp/demos/config/nmos_plugin_node_config_sender.json"; // Initialize device_fields_t config->device.id = "e92e628b-7421-4723-9fb9-c1f3b38af9d3"; @@ -45,7 +45,7 @@ void create_default_config_fields(config_fields_t* config) config->network.source_address = "192.168.1.120"; config->network.interface_name = "wlp1s0"; config->network.destination_address = "192.168.1.120"; - config->network.destination_port = "9999"; + config->network.destination_port = 9999; // Initialize config_fields_t config->sender_id = "1c920570-e0b4-4637-b02c-26c9d4275c71"; diff --git a/cpp/libs/gst_nmos_sender_plugin/utils.h b/cpp/libs/gst_nmos_sender_plugin/utils.h index b84a8a2..2d371ac 100644 --- a/cpp/libs/gst_nmos_sender_plugin/utils.h +++ b/cpp/libs/gst_nmos_sender_plugin/utils.h @@ -39,7 +39,7 @@ typedef struct network_fields_t std::string source_address; std::string interface_name; std::string destination_address; - std::string destination_port; + gint destination_port; } network_fields_t; typedef struct config_fields_t From 573beb89cfceebc513a584ff615be7145c1df2e9 Mon Sep 17 00:00:00 2001 From: LufeBisect Date: Mon, 20 Jan 2025 17:21:21 +0000 Subject: [PATCH 2/4] Audio receiver and RAII introduction --- cpp/libs/CMakeLists.txt | 3 +- .../CMakeLists.txt | 67 +++ .../gst_nmos_audio_receiver_plugin.cpp | 452 ++++++++++++++++++ .../gst_nmos_audio_receiver_plugin/utils.cpp | 189 ++++++++ .../gst_nmos_audio_receiver_plugin/utils.h | 116 +++++ cpp/libs/gst_nmos_receiver_plugin/utils.h | 49 -- .../gst_nmos_sender_plugin.cpp | 58 ++- cpp/libs/gst_nmos_sender_plugin/utils.h | 74 +++ .../CMakeLists.txt | 4 +- .../gst_nmos_video_receiver_plugin.cpp} | 184 +++---- .../utils.cpp | 0 .../gst_nmos_video_receiver_plugin/utils.h | 119 +++++ 12 files changed, 1152 insertions(+), 163 deletions(-) create mode 100644 cpp/libs/gst_nmos_audio_receiver_plugin/CMakeLists.txt create mode 100644 cpp/libs/gst_nmos_audio_receiver_plugin/gst_nmos_audio_receiver_plugin.cpp create mode 100644 cpp/libs/gst_nmos_audio_receiver_plugin/utils.cpp create mode 100644 cpp/libs/gst_nmos_audio_receiver_plugin/utils.h delete mode 100644 cpp/libs/gst_nmos_receiver_plugin/utils.h rename cpp/libs/{gst_nmos_receiver_plugin => gst_nmos_video_receiver_plugin}/CMakeLists.txt (94%) rename cpp/libs/{gst_nmos_receiver_plugin/gst_nmos_receiver_plugin.cpp => gst_nmos_video_receiver_plugin/gst_nmos_video_receiver_plugin.cpp} (70%) rename cpp/libs/{gst_nmos_receiver_plugin => gst_nmos_video_receiver_plugin}/utils.cpp (100%) create mode 100644 cpp/libs/gst_nmos_video_receiver_plugin/utils.h diff --git a/cpp/libs/CMakeLists.txt b/cpp/libs/CMakeLists.txt index 515ef7c..8c8a6c5 100644 --- a/cpp/libs/CMakeLists.txt +++ b/cpp/libs/CMakeLists.txt @@ -3,7 +3,8 @@ add_subdirectory(bisect_nmoscpp) add_subdirectory(bisect_sdp) add_subdirectory(bisect_gst) add_subdirectory(gst_nmos_sender_plugin) -add_subdirectory(gst_nmos_receiver_plugin) +add_subdirectory(gst_nmos_video_receiver_plugin) +add_subdirectory(gst_nmos_audio_receiver_plugin) add_subdirectory(ossrf_nmos_api) add_subdirectory(ossrf_gstreamer_api) add_subdirectory(bisect_json) diff --git a/cpp/libs/gst_nmos_audio_receiver_plugin/CMakeLists.txt b/cpp/libs/gst_nmos_audio_receiver_plugin/CMakeLists.txt new file mode 100644 index 0000000..8865116 --- /dev/null +++ b/cpp/libs/gst_nmos_audio_receiver_plugin/CMakeLists.txt @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 3.16) +project(gst_nmos_audio_receiver_plugin LANGUAGES CXX) + +find_package(nlohmann_json REQUIRED) +find_package(PkgConfig REQUIRED) + +# Locate GLib package +pkg_check_modules(GLIB REQUIRED glib-2.0) + +# Locate GStreamer packages +pkg_search_module(GSTREAMER REQUIRED gstreamer-1.0>=1.4) +pkg_search_module(GSTREAMER_APP REQUIRED gstreamer-app-1.0>=1.4) +pkg_search_module(GSTREAMER_AUDIO REQUIRED gstreamer-audio-1.0>=1.4) +pkg_search_module(GSTREAMER_VIDEO REQUIRED gstreamer-video-1.0>=1.4) + +# Include GLib directories and link libraries +include_directories(${GLIB_INCLUDE_DIRS}) +link_directories(${GLIB_LIBRARY_DIRS}) +add_definitions(${GLIB_CFLAGS_OTHER}) + +# Include source files for gst_nmos_audio_receiver_plugin +file(GLOB_RECURSE PLUGIN_SOURCE_FILES *.cpp *.h) + +# Include source files for utils +file(GLOB_RECURSE UTILS_SOURCE_FILES utils/*.cpp utils/*.h) + +# Combine utils and plugin sources +set(SOURCE_FILES ${PLUGIN_SOURCE_FILES} ${UTILS_SOURCE_FILES}) + +# Define the plugin as a shared library +add_library(${PROJECT_NAME} MODULE ${SOURCE_FILES}) + +# Include directories +target_include_directories(${PROJECT_NAME} + PRIVATE ${GSTREAMER_INCLUDE_DIRS} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/utils/include +) + +# Link GStreamer libraries +target_compile_options(${PROJECT_NAME} PRIVATE ${GSTREAMER_CFLAGS_OTHER}) +target_link_libraries(${PROJECT_NAME} + PRIVATE + ${GSTREAMER_LIBRARIES} + ${GSTREAMER_APP_LIBRARIES} + ${GSTREAMER_AUDIO_LIBRARIES} + ${GSTREAMER_VIDEO_LIBRARIES} + PUBLIC + bisect::project_warnings + bisect::expected + bisect::bisect_nmoscpp + bisect::bisect_json + nlohmann_json::nlohmann_json + ossrf::ossrf_nmos_api + ${GLIB_LIBRARIES} +) + +# Specify the output directory and the library name +set_target_properties(${PROJECT_NAME} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins + OUTPUT_NAME "gstnmosaudioreceiver" +) + +add_library(ossrf::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) + +# Install the plugin to the system's GStreamer plugin directory +install(TARGETS ${PROJECT_NAME} + LIBRARY DESTINATION ~/.local/lib/gstreamer-1.0) diff --git a/cpp/libs/gst_nmos_audio_receiver_plugin/gst_nmos_audio_receiver_plugin.cpp b/cpp/libs/gst_nmos_audio_receiver_plugin/gst_nmos_audio_receiver_plugin.cpp new file mode 100644 index 0000000..bf73ea6 --- /dev/null +++ b/cpp/libs/gst_nmos_audio_receiver_plugin/gst_nmos_audio_receiver_plugin.cpp @@ -0,0 +1,452 @@ + +/* GStreamer + * + * Copyright (C) <2024> Bisect Lda + * @author: Luís Ferreira + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Suite 500, + * Boston, MA 02110-1335, USA. + */ + +#include "bisect/expected/macros.h" +#include "bisect/json.h" +#include "bisect/sdp.h" +#include "bisect/sdp/reader.h" +#include "bisect/nmoscpp/configuration.h" +#include "utils.h" +#include "ossrf/nmos/api/nmos_client.h" +#include +#include + +using namespace bisect; +using namespace bisect::sdp; + +GST_DEBUG_CATEGORY_STATIC(gst_nmosaudioreceiver_debug_category); +#define GST_CAT_DEFAULT gst_nmosaudioreceiver_debug_category +#define GST_TYPE_NMOSAUDIORECEIVER (gst_nmosaudioreceiver_get_type()) +#define GST_NMOSAUDIORECEIVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_NMOSAUDIORECEIVER, GstNmosaudioreceiver)) +#define GST_NMOSAUDIORECEIVER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_NMOSAUDIORECEIVER, GstNmosaudioreceiverClass)) +#define GST_IS_NMOSAUDIORECEIVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_NMOSAUDIORECEIVER)) +#define GST_IS_NMOSAUDIORECEIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_NMOSAUDIORECEIVER)) + +typedef struct _GstNmosaudioreceiver +{ + GstBin parent; + GstPad* element_pad; + GstPad* bin_pad; + GstClock* clock; + GstElementHandle<_GstElement> bin; + GstElementHandle<_GstElement> udp_src; + GstElementHandle<_GstElement> rtp_audio_depay; + GstElementHandle<_GstElement> rtp_jitter_buffer; + GstElementHandle<_GstElement> queue; + ossrf::nmos_client_uptr client; + config_fields_t config; + std::string sdp_string; + sdp_settings_t sdp_settings; + bool nmos_active; +} GstNmosaudioreceiver; + +typedef struct _GstNmosaudioreceiverClass +{ + GstBinClass parent_class; +} GstNmosaudioreceiverClass; + +G_DEFINE_TYPE_WITH_CODE(GstNmosaudioreceiver, gst_nmosaudioreceiver, GST_TYPE_BIN, + GST_DEBUG_CATEGORY_INIT(gst_nmosaudioreceiver_debug_category, "nmosaudioreceiver", 0, + "NMOS Receiver Plugin")) +// Pad template +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-raw, " + "format=(string){ S16LE, S24BE }; " + "application/x-rtp")); + +enum class PropertyId : uint32_t +{ + NodeId = 1, + NodeConfigFileLocation = 2, + DeviceId = 3, + DeviceLabel = 4, + DeviceDescription = 5, + ReceiverId = 6, + ReceiverLabel = 7, + ReceiverDescription = 8, + DstAddress = 9 +}; + +// Set properties so element variables can change depending on them +static void gst_nmosaudioreceiver_set_property(GObject* object, guint property_id, const GValue* value, + GParamSpec* pspec) +{ + GstNmosaudioreceiver* self = GST_NMOSAUDIORECEIVER(object); + + switch(static_cast(property_id)) + { + case PropertyId::NodeId: self->config.node.id = g_value_dup_string(value); break; + + case PropertyId::NodeConfigFileLocation: + self->config.node.configuration_location = g_value_dup_string(value); + break; + + case PropertyId::DeviceId: self->config.device.id = g_value_dup_string(value); break; + + case PropertyId::DeviceLabel: self->config.device.label = g_value_dup_string(value); break; + + case PropertyId::DeviceDescription: self->config.device.description = g_value_dup_string(value); break; + + case PropertyId::ReceiverId: self->config.id = g_value_dup_string(value); break; + + case PropertyId::ReceiverLabel: self->config.label = g_value_dup_string(value); break; + + case PropertyId::ReceiverDescription: self->config.description = g_value_dup_string(value); break; + + case PropertyId::DstAddress: self->config.address = g_value_dup_string(value); break; + + default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; + } +} + +static void gst_nmosaudioreceiver_get_property(GObject* object, guint property_id, GValue* value, GParamSpec* pspec) +{ + GstNmosaudioreceiver* self = GST_NMOSAUDIORECEIVER(object); + + switch(static_cast(property_id)) + { + + case PropertyId::NodeId: g_value_set_string(value, self->config.node.id.c_str()); break; + case PropertyId::NodeConfigFileLocation: + g_value_set_string(value, self->config.node.configuration_location.c_str()); + break; + case PropertyId::DeviceId: g_value_set_string(value, self->config.device.id.c_str()); break; + case PropertyId::DeviceLabel: g_value_set_string(value, self->config.device.label.c_str()); break; + case PropertyId::DeviceDescription: g_value_set_string(value, self->config.device.description.c_str()); break; + case PropertyId::ReceiverId: g_value_set_string(value, self->config.id.c_str()); break; + case PropertyId::ReceiverLabel: g_value_set_string(value, self->config.label.c_str()); break; + case PropertyId::ReceiverDescription: g_value_set_string(value, self->config.description.c_str()); break; + case PropertyId::DstAddress: g_value_set_string(value, self->config.address.c_str()); break; + + default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; + } +} + +static GstPadProbeReturn block_pad_probe_cb(GstPad* pad, GstPadProbeInfo* info, gpointer user_data) +{ + return GST_PAD_PROBE_DROP; +} + +void remove_old_bin(GstNmosaudioreceiver* self) +{ + if(self->element_pad != nullptr) + { + gulong block_id = + gst_pad_add_probe(self->element_pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, block_pad_probe_cb, NULL, NULL); + + gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_start()); + gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_stop(FALSE)); + + self->clock = gst_element_get_clock(GST_ELEMENT(self)); + + gst_element_set_state(self->bin.get(), GST_STATE_NULL); + + gst_bin_remove(GST_BIN(self), self->udp_src.get()); + gst_bin_remove(GST_BIN(self), self->rtp_jitter_buffer.get()); + gst_bin_remove(GST_BIN(self), self->rtp_audio_depay.get()); + gst_bin_remove(GST_BIN(self), self->queue.get()); + + gst_bin_remove(GST_BIN(self), self->bin.get()); + + self->udp_src.reset(); + self->queue.reset(); + self->rtp_jitter_buffer.reset(); + self->rtp_audio_depay.reset(); + self->bin.reset(); + + if(block_id != 0) + { + gst_pad_remove_probe(self->element_pad, block_id); + } + } +} + +void construct_pipeline(GstNmosaudioreceiver* self) +{ + auto format = self->sdp_settings.format; + try + { + bisect::nmoscpp::audio_sender_info_t audio_info = std::get(format); + fmt::print("\n===== SDP Settings =====\n" + "Audio (from variant):\n" + " - bits per sample: {}\n" + " - number of channels: {}\n" + " - packet time: {}\n" + " - sampling rate: {}\n" + "Primary leg:\n" + " - destination_ip: {}\n" + " - destination_port: {}\n" + "\n", + audio_info.bits_per_sample, audio_info.number_of_channels, audio_info.packet_time, + audio_info.sampling_rate, self->sdp_settings.primary.destination_ip.value().c_str(), + self->sdp_settings.primary.destination_port.value()); + + auto maybeBin = GstElementHandle::create_bin("dynamic-bin"); + if(std::holds_alternative(maybeBin)) + { + GST_ERROR_OBJECT(self, "Failed to create bin"); + return; + } + + GstElementHandle bin = std::move(std::get>(maybeBin)); + + self->bin = std::move(bin); + + auto maybeUdpsrc = GstElementHandle::create_element("udpsrc", nullptr); + auto maybeQueue = GstElementHandle::create_element("queue", nullptr); + auto maybeJitter = GstElementHandle::create_element("rtpjitterbuffer", nullptr); + auto maybeDepay = GstElementHandle::create_element("rtpL24depay", nullptr); + + if(std::holds_alternative(maybeUdpsrc) || std::holds_alternative(maybeQueue) || + std::holds_alternative(maybeJitter) || std::holds_alternative(maybeDepay)) + { + GST_ERROR_OBJECT(self, "Failed to create pipeline elements."); + return; + } + + GstElementHandle udpsrc = std::move(std::get>(maybeUdpsrc)); + GstElementHandle queue = std::move(std::get>(maybeQueue)); + GstElementHandle jitter = std::move(std::get>(maybeJitter)); + GstElementHandle depay = std::move(std::get>(maybeDepay)); + + self->udp_src = std::move(udpsrc); + self->queue = std::move(queue); + self->rtp_jitter_buffer = std::move(jitter); + self->rtp_audio_depay = std::move(depay); + + g_object_set( + G_OBJECT(self->udp_src.get()), "address", self->sdp_settings.primary.destination_ip.value().c_str(), "port", + self->sdp_settings.primary.destination_port.value(), "do-timestamp", true, "buffer-size", 212992, nullptr); + + GstCaps* caps = gst_caps_new_simple("application/x-rtp", "clock-rate", G_TYPE_INT, audio_info.sampling_rate, + "channels", G_TYPE_INT, audio_info.number_of_channels, nullptr); + + g_object_set(G_OBJECT(self->udp_src.get()), "caps", caps, nullptr); + + g_object_set(G_OBJECT(self->rtp_jitter_buffer.get()), "do-retransmission", true, "do-lost", true, "mode", 0, + "latency", 0, nullptr); + + g_object_set(G_OBJECT(self->queue.get()), "max-size-buffers", 3, nullptr); + + gst_bin_add_many(GST_BIN(self->bin.get()), self->udp_src.get(), self->rtp_jitter_buffer.get(), + self->queue.get(), self->rtp_audio_depay.get(), nullptr); + + gst_element_sync_state_with_parent(self->udp_src.get()); + gst_element_sync_state_with_parent(self->rtp_jitter_buffer.get()); + gst_element_sync_state_with_parent(self->queue.get()); + gst_element_sync_state_with_parent(self->rtp_audio_depay.get()); + + if(gst_element_link_many(self->udp_src.get(), self->rtp_jitter_buffer.get(), self->queue.get(), + self->rtp_audio_depay.get(), nullptr) == false) + { + GST_ERROR_OBJECT(self, "Failed to link elements inside the bin."); + return; + } + + // Create a ghost pad from the depay's src + GstPad* bin_src_pad = gst_element_get_static_pad(self->rtp_audio_depay.get(), "src"); + if(bin_src_pad == nullptr) + { + GST_ERROR_OBJECT(self, "Failed to get src pad from rtp_audio_depay."); + return; + } + + GstPad* bin_ghost_pad = gst_ghost_pad_new("src", bin_src_pad); + gst_object_unref(bin_src_pad); + self->bin_pad = bin_ghost_pad; + + if(bin_ghost_pad == nullptr || gst_element_add_pad(self->bin.get(), bin_ghost_pad) == false) + { + GST_ERROR_OBJECT(self, "Failed to create or add ghost pad to bin."); + return; + } + + gst_bin_add(GST_BIN(self), self->bin.get()); + + GstPad* plugin_pad = gst_element_get_static_pad(GST_ELEMENT(self), "src"); + if(plugin_pad == nullptr) + { + GST_ERROR_OBJECT(self, "Failed to get plugin ghost pad."); + return; + } + + self->element_pad = plugin_pad; + + if(gst_ghost_pad_set_target(GST_GHOST_PAD(self->element_pad), bin_ghost_pad) == false) + { + GST_ERROR_OBJECT(self, "Failed to link plugin ghost pad to bin's ghost pad."); + return; + } + + gst_object_unref(plugin_pad); + } + catch(std::bad_variant_access const& ex) + { + g_print("Invalid format sent for current receiver.\n"); + } +} + +void create_nmos(GstNmosaudioreceiver* self) +{ + const auto node_config_json = create_node_config(self->config); + const auto device_config_json = create_device_config(self->config); + nlohmann::json_abi_v3_11_3::json receiver_config_json = nullptr; + + receiver_config_json = create_receiver_config(self->config); + + // g_print("Node Configuration: %s\n", node_config_json.dump().c_str()); + // g_print("Device Configuration: %s\n", device_config_json.dump().c_str()); + // g_print("Receiver Configuration: %s\n", receiver_config_json.dump().c_str()); + + auto receiver_callback = [self](const std::optional& sdp, bool master_enabled) { + // Debug print + fmt::print("Receiver Activation Callback: SDP={}, Master Enabled={}\n", sdp.has_value() ? sdp.value() : "None", + master_enabled); + + if(sdp) + { + fmt::print("Received SDP: {}\n", sdp.value()); + auto sdp_settings = parse_sdp(sdp.value()); + if(sdp_settings.has_value() && sdp.value() != self->sdp_string) + { + self->sdp_settings = sdp_settings.value(); + self->sdp_string = sdp.value(); + remove_old_bin(self); + construct_pipeline(self); + gst_element_set_state(GST_ELEMENT(self), GST_STATE_PLAYING); + } + } + else + { + fmt::print("No SDP provided.\n"); + } + fmt::print("Master enabled: {}\n", master_enabled); + }; + + auto result = ossrf::nmos_client_t::create(self->config.node.id, node_config_json.dump()); + if(result.has_value()) + { + self->client = std::move(result.value()); + self->client->add_device(device_config_json.dump()); + self->client->add_receiver(self->config.device.id, receiver_config_json.dump(), receiver_callback); + GST_INFO_OBJECT(self, "NMOS client initialized successfully."); + } + else + { + GST_ERROR_OBJECT(self, "Failed to initialize NMOS client."); + } +} + +static GstStateChangeReturn gst_nmosaudioreceiver_change_state(GstElement* element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstNmosaudioreceiver* self = GST_NMOSAUDIORECEIVER(element); + + switch(transition) + { + case GST_STATE_CHANGE_NULL_TO_READY: { + if(self->nmos_active != true) + { + create_nmos(self); + self->nmos_active = true; + } + } + break; + + default: break; + } + + ret = GST_ELEMENT_CLASS(gst_nmosaudioreceiver_parent_class)->change_state(element, transition); + + return ret; +} + +static void add_property(GObjectClass* object_class, guint id, const gchar* name, const gchar* nick, const gchar* blurb, + const gchar* default_value) +{ + g_object_class_install_property(object_class, id, + g_param_spec_string(name, nick, blurb, default_value, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); +} + +// Class initialization +static void gst_nmosaudioreceiver_class_init(GstNmosaudioreceiverClass* klass) +{ + GObjectClass* object_class = G_OBJECT_CLASS(klass); + GstElementClass* element_class = GST_ELEMENT_CLASS(klass); + + gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&src_template)); + + object_class->set_property = gst_nmosaudioreceiver_set_property; + object_class->get_property = gst_nmosaudioreceiver_get_property; + + add_property(object_class, 1, "node-id", "Node ID", "The ID of the node", "d49c85db-1c33-4f21-b160-58edd2af1810"); + add_property( + object_class, 2, "node-config-file-location", "Node Config File Location", + "The location of the configuration file for the node", + "/home/nmos/repos/nmos-sender-receiver-framework/cpp/demos/config/nmos_plugin_node_config_receiver.json"); + add_property(object_class, 3, "device-id", "Device ID", "The ID of the device", + "1ad20d7c-8c58-4c84-8f14-3cf3e3af164c"); + add_property(object_class, 4, "device-label", "Device Label", "The label to identify the device", "OSSRF Device"); + add_property(object_class, 5, "device-description", "Device Description", "Description of the device", + "OSSRF Device Description"); + add_property(object_class, 6, "receiver-id", "Receiver ID", "UUID of the receiver", + "db9f46cf-2414-4e25-b6c6-2078159857f9"); + add_property(object_class, 7, "receiver-label", "Label of the receiver", "Label of the receiver", + "BISECT OSSRF Audio Receiver"); + add_property(object_class, 8, "receiver-description", "Description of the receiver", "Description of the receiver", + "BISECT OSSRF Audio Receiver"); + add_property(object_class, 9, "destination-address", "Destination Address", "Address of the destination", + "127.0.0.1"); + + gst_element_class_set_static_metadata(element_class, "NMOS Audio Receiver", "Source/Network", + "Receives raw audio from NMOS", "Luis Ferreira "); + + element_class->change_state = gst_nmosaudioreceiver_change_state; +} + +// Object initialization +static void gst_nmosaudioreceiver_init(GstNmosaudioreceiver* self) +{ + GstPadTemplate* src_tmpl = gst_static_pad_template_get(&src_template); + GstPad* ghost_src = gst_ghost_pad_new_no_target_from_template("src", src_tmpl); + gst_object_unref(src_tmpl); + gst_element_add_pad(GST_ELEMENT(self), ghost_src); + + create_default_config_fields(&self->config); +} + +static gboolean plugin_init(GstPlugin* plugin) +{ + return gst_element_register(plugin, "nmosaudioreceiver", GST_RANK_NONE, GST_TYPE_NMOSAUDIORECEIVER); +} + +#define VERSION "0.1" +#define PACKAGE "gst-nmos-audio-receiver-plugin" +#define PACKAGE_NAME "AMWA NMOS Sender and Receiver Framework Plugins" +#define GST_PACKAGE_ORIGIN "https://www.amwa.tv/" +#define GST_PACKAGE_LICENSE "Apache-2.0" + +GST_PLUGIN_DEFINE(GST_VERSION_MAJOR, GST_VERSION_MINOR, nmosaudioreceiver, + "Plugin to receive audio stream from NMOS application", plugin_init, VERSION, GST_PACKAGE_LICENSE, + PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/cpp/libs/gst_nmos_audio_receiver_plugin/utils.cpp b/cpp/libs/gst_nmos_audio_receiver_plugin/utils.cpp new file mode 100644 index 0000000..05b2687 --- /dev/null +++ b/cpp/libs/gst_nmos_audio_receiver_plugin/utils.cpp @@ -0,0 +1,189 @@ +#include "utils.h" +#include "ossrf/nmos/api/nmos_client.h" +#include "bisect/nmoscpp/configuration.h" +#include "bisect/expected/macros.h" +#include "bisect/expected.h" +#include "bisect/json.h" +#include + +using namespace bisect; +using json = nlohmann::json; + +void create_default_config_fields(config_fields_t* config) +{ + if(!config) + { + g_critical("Config pointer is NULL"); + return; + } + + // Initialize node_fields_t + config->node.id = "d1073007-4099-452f-b8e8-837b8987d845"; + config->node.configuration_location = + "/home/nmos/repos/nmos-sender-receiver-framework/cpp/demos/config/nmos_plugin_node_config_receiver.json"; + + // Initialize device_fields_t + config->device.id = "093a1c56-4033-49f7-9e17-fbcb21a19383"; + config->device.label = "OSSRF Device2"; + config->device.description = "OSSRF Device2"; + + // Initialize config_fields_t + config->id = "af600517-a452-4c2c-8b60-2caa770d6435"; + config->label = "BISECT OSSRF Audio Receiver"; + config->description = "BISECT OSSRF Audio Receiver"; + config->interface_name = "wlp1s0"; + config->address = "192.168.1.36"; +} + +json create_node_config(config_fields_t& config) +{ + const std::string node_config_str = get_node_config(config.node.configuration_location); + json node_config = json::parse(node_config_str); + node_config["id"] = config.node.id; + + return node_config; +} + +json create_device_config(config_fields_t& config) +{ + json device = { + {"id", config.device.id}, {"label", config.device.label}, {"description", config.device.description}}; + return device; +} + +json create_receiver_config(config_fields_t& config) +{ + json sender = { + {"id", config.id}, + {"label", config.label}, + {"description", config.description}, + {"network", {{"primary", {{"interface_address", config.address}, {"interface_name", config.interface_name}}}}}, + {"capabilities", {"audio/L24"}}}; + return sender; +} + +std::string translate_video_format(const std::string& gst_format) +{ + static const std::unordered_map video_format_map = {{"UYVP", "YCbCr-4:2:2"}, + {"RGBA", "RGBA-8:8:8:8"}}; + + auto it = video_format_map.find(gst_format); + if(it != video_format_map.end()) + { + return it->second; + } + return gst_format; +} + +std::string translate_audio_format(const std::string& gst_format) +{ + static const std::unordered_map audio_format_map = {{"S24BE", "L24"}, {"S16LE", "L16"}}; + + auto it = audio_format_map.find(gst_format); + if(it != audio_format_map.end()) + { + return it->second; + } + return gst_format; +} + +expected load_configuration_from_file(std::string_view config_file) +{ + std::ifstream ifs(config_file.data()); + BST_ENFORCE(ifs.is_open(), "Failed opening file {}", config_file); + std::ostringstream buffer; + buffer << ifs.rdbuf(); + return parse_json(buffer.str()); +} + +std::string get_node_id(char* node_configuration_location) +{ + const auto configuration_result = load_configuration_from_file(node_configuration_location); + + const json& configuration = configuration_result.value(); + + auto node_result = find(configuration, "node"); + + const json& node = node_result.value(); + + auto node_id_result = find(node, "id"); + auto node_config_result = find(node, "configuration"); + + if(node_id_result.has_value() && node_config_result.has_value()) + { + const std::string node_id = node_id_result.value(); + const std::string node_configuration = node_config_result.value().dump(); + + return node_id; + } + + return ""; +} + +std::string get_node_config(std::string node_configuration_location) +{ + const auto configuration_result = load_configuration_from_file(node_configuration_location.c_str()); + + const json& configuration = configuration_result.value(); + + auto node_result = find(configuration, "node"); + + const json& node = node_result.value(); + + auto node_id_result = find(node, "id"); + auto node_config_result = find(node, "configuration"); + + if(node_id_result.has_value() && node_config_result.has_value()) + { + const std::string node_id = node_id_result.value(); + const std::string node_configuration = node_config_result.value().dump(); + + return node_configuration; + } + + return ""; +} + +std::string get_device_id(char* device_configuration_location) +{ + const auto configuration_result = load_configuration_from_file(device_configuration_location); + + const json& configuration = configuration_result.value(); + + auto device_result = find(configuration, "device"); + + const json& device = device_result.value(); + + auto device_id_result = find(device, "id"); + + const std::string device_id = device_id_result.value(); + const std::string device_config = device.dump(); + + return device_id; +} + +std::string get_device_config(char* device_configuration_location) +{ + const auto configuration_result = load_configuration_from_file(device_configuration_location); + + const json& configuration = configuration_result.value(); + + auto device_result = find(configuration, "device"); + + const json& device = device_result.value(); + + auto device_id_result = find(device, "id"); + + const std::string device_id = device_id_result.value(); + const std::string device_config = device.dump(); + + return device_config; +} + +std::string get_sender_config(char* sender_configuration_location) +{ + // FIX ME: Gonna be hardcoded for now + const auto sender_config = load_configuration_from_file(sender_configuration_location); + + return sender_config.value().dump(); +} diff --git a/cpp/libs/gst_nmos_audio_receiver_plugin/utils.h b/cpp/libs/gst_nmos_audio_receiver_plugin/utils.h new file mode 100644 index 0000000..b64513b --- /dev/null +++ b/cpp/libs/gst_nmos_audio_receiver_plugin/utils.h @@ -0,0 +1,116 @@ +#pragma once +#include +#include +#include +#include +#include + +typedef struct node_fields_t +{ + std::string id; + std::string configuration_location; +} node_fields_t; + +typedef struct device_fields_t +{ + std::string id; + std::string label; + std::string description; +} device_fields_t; + +typedef struct config_fields_t +{ + node_fields_t node; + device_fields_t device; + std::string id; + std::string label; + std::string description; + std::string address; + gint port; + std::string interface_name; +} config_fields_t; + +template class GstElementHandle +{ + public: + static std::variant create_element(const char* factory_name, + const char* element_name = nullptr) + { + T* elem = reinterpret_cast(gst_element_factory_make(factory_name, element_name)); + if(!elem) + { + return nullptr; + } + return GstElementHandle(elem); + } + + static std::variant create_bin(const char* bin_name) + { + T* bin = reinterpret_cast(gst_bin_new(bin_name)); + if(!bin) + { + return nullptr; + } + return GstElementHandle(bin); + } + + GstElementHandle(const GstElementHandle&) = delete; + GstElementHandle& operator=(const GstElementHandle&) = delete; + + // Move constructor + GstElementHandle(GstElementHandle&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; } + + // Move assignment + GstElementHandle& operator=(GstElementHandle&& other) noexcept + { + if(this != &other) + { + if(handle_ != nullptr) + { + gst_object_unref(handle_); + } + handle_ = other.handle_; + other.handle_ = nullptr; + } + return *this; + } + + ~GstElementHandle() + { + if(handle_) + { + gst_object_unref(handle_); + } + } + + void reset(T* new_ptr = nullptr) { handle_ = new_ptr; } + + T* get() const { return handle_; } + + explicit operator bool() const { return (handle_ != nullptr); } + + private: + explicit GstElementHandle(T* handle) : handle_(handle) {} + + T* handle_ = nullptr; +}; + +void create_default_config_fields(config_fields_t* config); + +nlohmann::json create_node_config(config_fields_t& config); +nlohmann::json create_device_config(config_fields_t& config); +nlohmann::json create_receiver_config(config_fields_t& config); + +std::string translate_video_format(const std::string& gst_format); + +std::string translate_audio_format(const std::string& gst_format); + +std::string get_node_id(char* node_configuration_location); + +std::string get_node_config(std::string node_configuration_location); + +std::string get_device_id(char* device_configuration_location); + +std::string get_device_config(char* device_configuration_location); + +std::string get_sender_config(char* sender_configuration_location); diff --git a/cpp/libs/gst_nmos_receiver_plugin/utils.h b/cpp/libs/gst_nmos_receiver_plugin/utils.h deleted file mode 100644 index 8a0de55..0000000 --- a/cpp/libs/gst_nmos_receiver_plugin/utils.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once -#include -#include -#include - -typedef struct node_fields_t -{ - std::string id; - std::string configuration_location; -} node_fields_t; - -typedef struct device_fields_t -{ - std::string id; - std::string label; - std::string description; -} device_fields_t; - -typedef struct config_fields_t -{ - node_fields_t node; - device_fields_t device; - std::string id; - std::string label; - std::string description; - std::string address; - gint port; - std::string interface_name; -} config_fields_t; - -void create_default_config_fields(config_fields_t* config); - -nlohmann::json create_node_config(config_fields_t& config); -nlohmann::json create_device_config(config_fields_t& config); -nlohmann::json create_receiver_config(config_fields_t& config); - -std::string translate_video_format(const std::string& gst_format); - -std::string translate_audio_format(const std::string& gst_format); - -std::string get_node_id(char* node_configuration_location); - -std::string get_node_config(std::string node_configuration_location); - -std::string get_device_id(char* device_configuration_location); - -std::string get_device_config(char* device_configuration_location); - -std::string get_sender_config(char* sender_configuration_location); diff --git a/cpp/libs/gst_nmos_sender_plugin/gst_nmos_sender_plugin.cpp b/cpp/libs/gst_nmos_sender_plugin/gst_nmos_sender_plugin.cpp index b3884e9..093ef77 100644 --- a/cpp/libs/gst_nmos_sender_plugin/gst_nmos_sender_plugin.cpp +++ b/cpp/libs/gst_nmos_sender_plugin/gst_nmos_sender_plugin.cpp @@ -37,10 +37,10 @@ GST_DEBUG_CATEGORY_STATIC(gst_nmossender_debug_category); typedef struct _GstNmossender { GstBin parent; - GstElement* queue; - GstElement* video_payloader; - GstElement* audio_payloader; - GstElement* udpsink; + GstElementHandle<_GstElement> queue; + GstElementHandle<_GstElement> video_payloader; + GstElementHandle<_GstElement> audio_payloader; + GstElementHandle<_GstElement> udpsink; ossrf::nmos_client_uptr client; GstCaps* caps; config_fields_t config; @@ -109,17 +109,18 @@ static void gst_nmossender_set_property(GObject* object, guint property_id, cons case PropertyId::InterfaceName: self->config.network.interface_name = g_value_dup_string(value); - g_object_set(G_OBJECT(self->udpsink), "bind_address", self->config.network.interface_name.c_str(), nullptr); + g_object_set(G_OBJECT(self->udpsink.get()), "bind_address", self->config.network.interface_name.c_str(), + nullptr); break; case PropertyId::DestinationAddress: self->config.network.destination_address = g_value_dup_string(value); - g_object_set(G_OBJECT(self->udpsink), "host", self->config.network.destination_address.c_str(), nullptr); + g_object_set(G_OBJECT(self->udpsink.get()), "host", self->config.network.destination_address.c_str(), nullptr); break; case PropertyId::DestinationPort: self->config.network.destination_port = atoi(g_value_get_string(value)); - g_object_set(G_OBJECT(self->udpsink), "port", self->config.network.destination_port, nullptr); + g_object_set(G_OBJECT(self->udpsink.get()), "port", self->config.network.destination_port, nullptr); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; @@ -147,7 +148,7 @@ static void gst_nmossender_get_property(GObject* object, guint property_id, GVal case PropertyId::DestinationAddress: g_value_set_string(value, self->config.network.destination_address.c_str()); break; - case PropertyId::DestinationPort: g_value_set_string(value, self->config.network.destination_port.c_str()); break; + case PropertyId::DestinationPort: g_value_set_int(value, self->config.network.destination_port); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } @@ -180,12 +181,12 @@ static gboolean gst_nmossender_sink_event(GstPad* pad, GstObject* parent, GstEve // g_print("Audio caps detected\n"); // GST_INFO_OBJECT(self, "Audio caps detected"); // - if(gst_element_link(self->queue, self->audio_payloader) == false) + if(gst_element_link(self->queue.get(), self->audio_payloader.get()) == false) { GST_ERROR_OBJECT(self, "Failed to link queue to audio_payloader"); return false; } - if(gst_element_link(self->audio_payloader, self->udpsink) == false) + if(gst_element_link(self->audio_payloader.get(), self->udpsink.get()) == false) { GST_ERROR_OBJECT(self, "Failed to link audio_payloader to udpsink"); return false; @@ -222,12 +223,12 @@ static gboolean gst_nmossender_sink_event(GstPad* pad, GstObject* parent, GstEve // g_print("Video caps detected\n"); // GST_INFO_OBJECT(self, "Video caps detected"); // - if(gst_element_link(self->queue, self->video_payloader) == false) + if(gst_element_link(self->queue.get(), self->video_payloader.get()) == false) { GST_ERROR_OBJECT(self, "Failed to link queue to video_payloader"); return false; } - if(gst_element_link(self->video_payloader, self->udpsink) == false) + if(gst_element_link(self->video_payloader.get(), self->udpsink.get()) == false) { GST_ERROR_OBJECT(self, "Failed to link video_payloader to udpsink"); return false; @@ -393,27 +394,38 @@ static void gst_nmossender_class_init(GstNmossenderClass* klass) /* Object initialization */ static void gst_nmossender_init(GstNmossender* self) { - self->queue = gst_element_factory_make("queue", nullptr); - self->video_payloader = gst_element_factory_make("rtpvrawpay", nullptr); - self->audio_payloader = gst_element_factory_make("rtpL24pay", nullptr); - self->udpsink = gst_element_factory_make("udpsink", nullptr); + auto maybeQueue = GstElementHandle::create_element("queue", nullptr); + auto maybeVideoPay = GstElementHandle::create_element("rtpvrawpay", nullptr); + auto maybeAudioPay = GstElementHandle::create_element("rtpL24pay", nullptr); + auto maybeUdpSink = GstElementHandle::create_element("udpsink", nullptr); - if(!self->queue || !self->video_payloader || !self->audio_payloader || !self->udpsink) + if(std::holds_alternative(maybeQueue) || std::holds_alternative(maybeVideoPay) || + std::holds_alternative(maybeAudioPay) || std::holds_alternative(maybeUdpSink)) { - GST_ERROR_OBJECT(self, - "Failed to create internal elements: queue or video_payloader or audio_playloader or udpsink"); + GST_ERROR_OBJECT(self, "Failed to create pipeline elements."); return; } + GstElementHandle queue = std::move(std::get>(maybeQueue)); + GstElementHandle rtpvrawpay = std::move(std::get>(maybeVideoPay)); + GstElementHandle rtpL24pay = std::move(std::get>(maybeAudioPay)); + GstElementHandle udpsink = std::move(std::get>(maybeUdpSink)); + + self->queue = std::move(queue); + self->video_payloader = std::move(rtpvrawpay); + self->audio_payloader = std::move(rtpL24pay); + self->udpsink = std::move(udpsink); + // set properties - g_object_set(G_OBJECT(self->queue), "max-size-buffers", 1, NULL); - g_object_set(G_OBJECT(self->udpsink), "host", "127.0.0.1", "port", 9999, NULL); + g_object_set(G_OBJECT(self->queue.get()), "max-size-buffers", 1, nullptr); + g_object_set(G_OBJECT(self->udpsink.get()), "host", "127.0.0.1", "port", 9999, nullptr); create_default_config_fields(&self->config); - gst_bin_add_many(GST_BIN(self), self->queue, self->video_payloader, self->audio_payloader, self->udpsink, nullptr); + gst_bin_add_many(GST_BIN(self), self->queue.get(), self->video_payloader.get(), self->audio_payloader.get(), + self->udpsink.get(), nullptr); // create and configure the sink pad - GstPad* queue_sinkpad = gst_element_get_static_pad(self->queue, "sink"); + GstPad* queue_sinkpad = gst_element_get_static_pad(self->queue.get(), "sink"); if(queue_sinkpad == nullptr) { GST_ERROR_OBJECT(self, "Failed to get static pad 'sink' from queue"); diff --git a/cpp/libs/gst_nmos_sender_plugin/utils.h b/cpp/libs/gst_nmos_sender_plugin/utils.h index 2d371ac..e3817b5 100644 --- a/cpp/libs/gst_nmos_sender_plugin/utils.h +++ b/cpp/libs/gst_nmos_sender_plugin/utils.h @@ -1,6 +1,8 @@ #pragma once #include #include +#include +#include #include typedef struct node_fields_t @@ -55,6 +57,78 @@ typedef struct config_fields_t network_fields_t network; } config_fields_t; +template class GstElementHandle +{ + public: + static std::variant create_element(const char* factory_name, + const char* element_name = nullptr) + { + T* elem = reinterpret_cast(gst_element_factory_make(factory_name, element_name)); + if(!elem) + { + return nullptr; + } + return GstElementHandle(elem); + } + + static std::variant create_bin(const char* bin_name) + { + T* bin = reinterpret_cast(gst_bin_new(bin_name)); + if(!bin) + { + return nullptr; + } + return GstElementHandle(bin); + } + + GstElementHandle(const GstElementHandle&) = delete; + GstElementHandle& operator=(const GstElementHandle&) = delete; + + // Move constructor + GstElementHandle(GstElementHandle&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; } + + // Move assignment + GstElementHandle& operator=(GstElementHandle&& other) noexcept + { + if(this != &other) + { + if(handle_ != nullptr) + { + gst_object_unref(handle_); + } + handle_ = other.handle_; + other.handle_ = nullptr; + } + return *this; + } + + ~GstElementHandle() + { + if(handle_) + { + gst_object_unref(handle_); + } + } + + void reset(T* new_ptr = nullptr) + { + if(handle_) + { + gst_object_unref(handle_); + } + handle_ = new_ptr; + } + + T* get() const { return handle_; } + + explicit operator bool() const { return (handle_ != nullptr); } + + private: + explicit GstElementHandle(T* handle) : handle_(handle) {} + + T* handle_ = nullptr; +}; + void create_default_config_fields(config_fields_t* config); nlohmann::json create_node_config(config_fields_t& config); diff --git a/cpp/libs/gst_nmos_receiver_plugin/CMakeLists.txt b/cpp/libs/gst_nmos_video_receiver_plugin/CMakeLists.txt similarity index 94% rename from cpp/libs/gst_nmos_receiver_plugin/CMakeLists.txt rename to cpp/libs/gst_nmos_video_receiver_plugin/CMakeLists.txt index fb91789..ab5d707 100644 --- a/cpp/libs/gst_nmos_receiver_plugin/CMakeLists.txt +++ b/cpp/libs/gst_nmos_video_receiver_plugin/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(gst_nmos_receiver_plugin LANGUAGES CXX) +project(gst_nmos_video_receiver_plugin LANGUAGES CXX) find_package(nlohmann_json REQUIRED) find_package(PkgConfig REQUIRED) @@ -18,7 +18,7 @@ include_directories(${GLIB_INCLUDE_DIRS}) link_directories(${GLIB_LIBRARY_DIRS}) add_definitions(${GLIB_CFLAGS_OTHER}) -# Include source files for gst_nmos_receiver_plugin +# Include source files for gst_nmos_video_receiver_plugin file(GLOB_RECURSE PLUGIN_SOURCE_FILES *.cpp *.h) # Include source files for utils diff --git a/cpp/libs/gst_nmos_receiver_plugin/gst_nmos_receiver_plugin.cpp b/cpp/libs/gst_nmos_video_receiver_plugin/gst_nmos_video_receiver_plugin.cpp similarity index 70% rename from cpp/libs/gst_nmos_receiver_plugin/gst_nmos_receiver_plugin.cpp rename to cpp/libs/gst_nmos_video_receiver_plugin/gst_nmos_video_receiver_plugin.cpp index f192aac..4a27a92 100644 --- a/cpp/libs/gst_nmos_receiver_plugin/gst_nmos_receiver_plugin.cpp +++ b/cpp/libs/gst_nmos_video_receiver_plugin/gst_nmos_video_receiver_plugin.cpp @@ -33,40 +33,40 @@ using namespace bisect; using namespace bisect::sdp; -GST_DEBUG_CATEGORY_STATIC(gst_nmosreceiver_debug_category); -#define GST_CAT_DEFAULT gst_nmosreceiver_debug_category -#define GST_TYPE_NMOSRECEIVER (gst_nmosreceiver_get_type()) -#define GST_NMOSRECEIVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_NMOSRECEIVER, GstNmosreceiver)) -#define GST_NMOSRECEIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_NMOSRECEIVER, GstNmosreceiverClass)) -#define GST_IS_NMOSRECEIVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_NMOSRECEIVER)) -#define GST_IS_NMOSRECEIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_NMOSRECEIVER)) - -typedef struct _GstNmosreceiver +GST_DEBUG_CATEGORY_STATIC(gst_nmosvideoreceiver_debug_category); +#define GST_CAT_DEFAULT gst_nmosvideoreceiver_debug_category +#define GST_TYPE_NMOSVIDEORECEIVER (gst_nmosvideoreceiver_get_type()) +#define GST_NMOSVIDEORECEIVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_NMOSVIDEORECEIVER, GstNmosvideoreceiver)) +#define GST_NMOSVIDEORECEIVER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_NMOSVIDEORECEIVER, GstNmosvideoreceiverClass)) +#define GST_IS_NMOSVIDEORECEIVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_NMOSVIDEORECEIVER)) +#define GST_IS_NMOSVIDEORECEIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_NMOSVIDEORECEIVER)) + +typedef struct _GstNmosvideoreceiver { GstBin parent; GstPad* element_pad; GstPad* bin_pad; GstClock* clock; - GstElement* bin; - GstElement* udp_src; - GstElement* rtp_video_depay; - GstElement* rtp_jitter_buffer; - GstElement* queue1; - GstElement* video_rate; - GstElement* identity; + GstElementHandle<_GstElement> bin; + GstElementHandle<_GstElement> udp_src; + GstElementHandle<_GstElement> rtp_video_depay; + GstElementHandle<_GstElement> rtp_jitter_buffer; + GstElementHandle<_GstElement> queue; ossrf::nmos_client_uptr client; config_fields_t config; + std::string sdp_string; sdp_settings_t sdp_settings; bool nmos_active; -} GstNmosreceiver; +} GstNmosvideoreceiver; -typedef struct _GstNmosreceiverClass +typedef struct _GstNmosvideoreceiverClass { GstBinClass parent_class; -} GstNmosreceiverClass; +} GstNmosvideoreceiverClass; -G_DEFINE_TYPE_WITH_CODE(GstNmosreceiver, gst_nmosreceiver, GST_TYPE_BIN, - GST_DEBUG_CATEGORY_INIT(gst_nmosreceiver_debug_category, "nmosvideoreceiver", 0, +G_DEFINE_TYPE_WITH_CODE(GstNmosvideoreceiver, gst_nmosvideoreceiver, GST_TYPE_BIN, + GST_DEBUG_CATEGORY_INIT(gst_nmosvideoreceiver_debug_category, "nmosvideoreceiver", 0, "NMOS Receiver Plugin")) // Pad template static GstStaticPadTemplate src_template = @@ -91,9 +91,10 @@ enum class PropertyId : uint32_t }; // Set properties so element variables can change depending on them -static void gst_nmosreceiver_set_property(GObject* object, guint property_id, const GValue* value, GParamSpec* pspec) +static void gst_nmosvideoreceiver_set_property(GObject* object, guint property_id, const GValue* value, + GParamSpec* pspec) { - GstNmosreceiver* self = GST_NMOSRECEIVER(object); + GstNmosvideoreceiver* self = GST_NMOSVIDEORECEIVER(object); switch(static_cast(property_id)) { @@ -121,9 +122,9 @@ static void gst_nmosreceiver_set_property(GObject* object, guint property_id, co } } -static void gst_nmosreceiver_get_property(GObject* object, guint property_id, GValue* value, GParamSpec* pspec) +static void gst_nmosvideoreceiver_get_property(GObject* object, guint property_id, GValue* value, GParamSpec* pspec) { - GstNmosreceiver* self = GST_NMOSRECEIVER(object); + GstNmosvideoreceiver* self = GST_NMOSVIDEORECEIVER(object); switch(static_cast(property_id)) { @@ -149,31 +150,41 @@ static GstPadProbeReturn block_pad_probe_cb(GstPad* pad, GstPadProbeInfo* info, return GST_PAD_PROBE_DROP; } -void remove_old_bin(GstNmosreceiver* self) +void remove_old_bin(GstNmosvideoreceiver* self) { if(self->element_pad != nullptr) { - gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_start()); - gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_stop(FALSE)); - gulong block_id = gst_pad_add_probe(self->element_pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, block_pad_probe_cb, NULL, NULL); + gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_start()); + gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_stop(FALSE)); + self->clock = gst_element_get_clock(GST_ELEMENT(self)); - gst_element_set_state(self->bin, GST_STATE_NULL); + gst_element_set_state(self->bin.get(), GST_STATE_NULL); + + gst_bin_remove(GST_BIN(self), self->udp_src.get()); + gst_bin_remove(GST_BIN(self), self->rtp_jitter_buffer.get()); + gst_bin_remove(GST_BIN(self), self->rtp_video_depay.get()); + gst_bin_remove(GST_BIN(self), self->queue.get()); + + gst_bin_remove(GST_BIN(self), self->bin.get()); - gst_bin_remove(GST_BIN(self), self->bin); + self->udp_src.reset(); + self->queue.reset(); + self->rtp_jitter_buffer.reset(); + self->rtp_video_depay.reset(); + self->bin.reset(); if(block_id != 0) { gst_pad_remove_probe(self->element_pad, block_id); } - gst_object_unref(self->bin); } } -void construct_pipeline(GstNmosreceiver* self) +void construct_pipeline(GstNmosvideoreceiver* self) { auto format = self->sdp_settings.format; try @@ -197,37 +208,42 @@ void construct_pipeline(GstNmosreceiver* self) self->sdp_settings.primary.destination_ip.value().c_str(), self->sdp_settings.primary.destination_port.value()); - self->bin = nullptr; - - self->queue1 = nullptr; - self->video_rate = nullptr; - self->udp_src = nullptr; - self->rtp_jitter_buffer = nullptr; - self->rtp_video_depay = nullptr; - self->identity = nullptr; - - GstElement* bin = gst_bin_new("dynamic-bin"); - if(bin == nullptr) + auto maybeBin = GstElementHandle::create_bin("dynamic-bin"); + if(std::holds_alternative(maybeBin)) { GST_ERROR_OBJECT(self, "Failed to create bin"); return; } - self->queue1 = gst_element_factory_make("queue", nullptr); - self->video_rate = gst_element_factory_make("videorate", nullptr); - self->udp_src = gst_element_factory_make("udpsrc", nullptr); - self->rtp_jitter_buffer = gst_element_factory_make("rtpjitterbuffer", nullptr); - self->rtp_video_depay = gst_element_factory_make("rtpvrawdepay", nullptr); - self->identity = gst_element_factory_make("identity", nullptr); + GstElementHandle bin = std::move(std::get>(maybeBin)); + + self->bin = std::move(bin); + + auto maybeUdpsrc = GstElementHandle::create_element("udpsrc", nullptr); + auto maybeQueue = GstElementHandle::create_element("queue", nullptr); + auto maybeJitter = GstElementHandle::create_element("rtpjitterbuffer", nullptr); + auto maybeDepay = GstElementHandle::create_element("rtpvrawdepay", nullptr); - if(!self->udp_src || !self->rtp_jitter_buffer || !self->queue1 || !self->rtp_video_depay) + if(std::holds_alternative(maybeUdpsrc) || std::holds_alternative(maybeQueue) || + std::holds_alternative(maybeJitter) || std::holds_alternative(maybeDepay)) { GST_ERROR_OBJECT(self, "Failed to create pipeline elements."); return; } - g_object_set(G_OBJECT(self->udp_src), "address", self->sdp_settings.primary.destination_ip.value().c_str(), - "port", self->sdp_settings.primary.destination_port.value(), "buffer-size", 67108864, nullptr); + GstElementHandle udpsrc = std::move(std::get>(maybeUdpsrc)); + GstElementHandle queue = std::move(std::get>(maybeQueue)); + GstElementHandle jitter = std::move(std::get>(maybeJitter)); + GstElementHandle depay = std::move(std::get>(maybeDepay)); + + self->udp_src = std::move(udpsrc); + self->queue = std::move(queue); + self->rtp_jitter_buffer = std::move(jitter); + self->rtp_video_depay = std::move(depay); + + g_object_set(G_OBJECT(self->udp_src.get()), "address", + self->sdp_settings.primary.destination_ip.value().c_str(), "port", + self->sdp_settings.primary.destination_port.value(), "buffer-size", 67108864, nullptr); // g_signal_connect(self->identity, "handoff", G_CALLBACK(identity_handoff_callback), NULL); @@ -239,38 +255,33 @@ void construct_pipeline(GstNmosreceiver* self) video_info.chroma_sub_sampling.c_str(), "depth", G_TYPE_STRING, std::to_string(video_info.depth).c_str(), "framerate", GST_TYPE_FRACTION, fr.numerator(), fr.denominator(), nullptr); - g_object_set(G_OBJECT(self->udp_src), "caps", caps, nullptr); + g_object_set(G_OBJECT(self->udp_src.get()), "caps", caps, nullptr); - g_object_set(G_OBJECT(self->rtp_jitter_buffer), "do-retransmission", true, "do-lost", true, "mode", 2, + g_object_set(G_OBJECT(self->rtp_jitter_buffer.get()), "do-retransmission", true, "do-lost", true, "mode", 2, "latency", 0, nullptr); - g_object_set(G_OBJECT(self->queue1), "max-size-buffers", 3, nullptr); + g_object_set(G_OBJECT(self->queue.get()), "max-size-buffers", 3, nullptr); - gst_bin_add_many(GST_BIN(bin), self->udp_src, self->rtp_jitter_buffer, self->video_rate, self->queue1, - self->rtp_video_depay, self->identity, nullptr); + gst_bin_add_many(GST_BIN(self->bin.get()), self->udp_src.get(), self->rtp_jitter_buffer.get(), + self->queue.get(), self->rtp_video_depay.get(), nullptr); - gst_element_sync_state_with_parent(self->udp_src); - gst_element_sync_state_with_parent(self->rtp_jitter_buffer); - gst_element_sync_state_with_parent(self->queue1); - gst_element_sync_state_with_parent(self->rtp_video_depay); - gst_element_sync_state_with_parent(self->identity); + gst_element_sync_state_with_parent(self->udp_src.get()); + gst_element_sync_state_with_parent(self->rtp_jitter_buffer.get()); + gst_element_sync_state_with_parent(self->queue.get()); + gst_element_sync_state_with_parent(self->rtp_video_depay.get()); - self->bin = bin; - - if(gst_element_link_many(self->udp_src, self->rtp_jitter_buffer, self->queue1, self->rtp_video_depay, - nullptr) == false) + if(gst_element_link_many(self->udp_src.get(), self->rtp_jitter_buffer.get(), self->queue.get(), + self->rtp_video_depay.get(), nullptr) == false) { GST_ERROR_OBJECT(self, "Failed to link elements inside the bin."); - gst_object_unref(self->bin); return; } // Create a ghost pad from the depay's src - GstPad* bin_src_pad = gst_element_get_static_pad(self->rtp_video_depay, "src"); + GstPad* bin_src_pad = gst_element_get_static_pad(self->rtp_video_depay.get(), "src"); if(bin_src_pad == nullptr) { GST_ERROR_OBJECT(self, "Failed to get src pad from rtp_video_depay."); - gst_object_unref(bin); return; } @@ -278,20 +289,18 @@ void construct_pipeline(GstNmosreceiver* self) gst_object_unref(bin_src_pad); self->bin_pad = bin_ghost_pad; - if(bin_ghost_pad == nullptr || gst_element_add_pad(bin, bin_ghost_pad) == false) + if(bin_ghost_pad == nullptr || gst_element_add_pad(self->bin.get(), bin_ghost_pad) == false) { GST_ERROR_OBJECT(self, "Failed to create or add ghost pad to bin."); - gst_object_unref(bin); return; } - gst_bin_add(GST_BIN(self), bin); + gst_bin_add(GST_BIN(self), self->bin.get()); GstPad* plugin_pad = gst_element_get_static_pad(GST_ELEMENT(self), "src"); if(plugin_pad == nullptr) { GST_ERROR_OBJECT(self, "Failed to get plugin ghost pad."); - gst_object_unref(bin); return; } @@ -300,7 +309,6 @@ void construct_pipeline(GstNmosreceiver* self) if(gst_ghost_pad_set_target(GST_GHOST_PAD(self->element_pad), bin_ghost_pad) == false) { GST_ERROR_OBJECT(self, "Failed to link plugin ghost pad to bin's ghost pad."); - gst_object_unref(bin); return; } @@ -312,9 +320,8 @@ void construct_pipeline(GstNmosreceiver* self) } } -void create_nmos(GstNmosreceiver* self) +void create_nmos(GstNmosvideoreceiver* self) { - const auto node_config_json = create_node_config(self->config); const auto device_config_json = create_device_config(self->config); nlohmann::json_abi_v3_11_3::json receiver_config_json = nullptr; @@ -334,9 +341,10 @@ void create_nmos(GstNmosreceiver* self) { fmt::print("Received SDP: {}\n", sdp.value()); auto sdp_settings = parse_sdp(sdp.value()); - if(sdp_settings.has_value()) + if(sdp_settings.has_value() && sdp.value() != self->sdp_string) { self->sdp_settings = sdp_settings.value(); + self->sdp_string = sdp.value(); remove_old_bin(self); construct_pipeline(self); auto time = gst_element_get_base_time(GST_ELEMENT(self)); @@ -365,10 +373,10 @@ void create_nmos(GstNmosreceiver* self) } } -static GstStateChangeReturn gst_nmosreceiver_change_state(GstElement* element, GstStateChange transition) +static GstStateChangeReturn gst_nmosvideoreceiver_change_state(GstElement* element, GstStateChange transition) { GstStateChangeReturn ret; - GstNmosreceiver* self = GST_NMOSRECEIVER(element); + GstNmosvideoreceiver* self = GST_NMOSVIDEORECEIVER(element); switch(transition) { @@ -384,7 +392,7 @@ static GstStateChangeReturn gst_nmosreceiver_change_state(GstElement* element, G default: break; } - ret = GST_ELEMENT_CLASS(gst_nmosreceiver_parent_class)->change_state(element, transition); + ret = GST_ELEMENT_CLASS(gst_nmosvideoreceiver_parent_class)->change_state(element, transition); return ret; } @@ -398,15 +406,15 @@ static void add_property(GObjectClass* object_class, guint id, const gchar* name } // Class initialization -static void gst_nmosreceiver_class_init(GstNmosreceiverClass* klass) +static void gst_nmosvideoreceiver_class_init(GstNmosvideoreceiverClass* klass) { GObjectClass* object_class = G_OBJECT_CLASS(klass); GstElementClass* element_class = GST_ELEMENT_CLASS(klass); gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&src_template)); - object_class->set_property = gst_nmosreceiver_set_property; - object_class->get_property = gst_nmosreceiver_get_property; + object_class->set_property = gst_nmosvideoreceiver_set_property; + object_class->get_property = gst_nmosvideoreceiver_get_property; add_property(object_class, 1, "node-id", "Node ID", "The ID of the node", "d49c85db-1c33-4f21-b160-58edd2af1810"); add_property( @@ -430,11 +438,11 @@ static void gst_nmosreceiver_class_init(GstNmosreceiverClass* klass) gst_element_class_set_static_metadata(element_class, "NMOS Video Receiver", "Source/Network", "Receives raw video from NMOS", "Luis Ferreira "); - element_class->change_state = gst_nmosreceiver_change_state; + element_class->change_state = gst_nmosvideoreceiver_change_state; } // Object initialization -static void gst_nmosreceiver_init(GstNmosreceiver* self) +static void gst_nmosvideoreceiver_init(GstNmosvideoreceiver* self) { GstPadTemplate* src_tmpl = gst_static_pad_template_get(&src_template); GstPad* ghost_src = gst_ghost_pad_new_no_target_from_template("src", src_tmpl); @@ -446,11 +454,11 @@ static void gst_nmosreceiver_init(GstNmosreceiver* self) static gboolean plugin_init(GstPlugin* plugin) { - return gst_element_register(plugin, "nmosvideoreceiver", GST_RANK_NONE, GST_TYPE_NMOSRECEIVER); + return gst_element_register(plugin, "nmosvideoreceiver", GST_RANK_NONE, GST_TYPE_NMOSVIDEORECEIVER); } #define VERSION "0.1" -#define PACKAGE "gst-nmos-receiver-plugin" +#define PACKAGE "gst-nmos-video-receiver-plugin" #define PACKAGE_NAME "AMWA NMOS Sender and Receiver Framework Plugins" #define GST_PACKAGE_ORIGIN "https://www.amwa.tv/" #define GST_PACKAGE_LICENSE "Apache-2.0" diff --git a/cpp/libs/gst_nmos_receiver_plugin/utils.cpp b/cpp/libs/gst_nmos_video_receiver_plugin/utils.cpp similarity index 100% rename from cpp/libs/gst_nmos_receiver_plugin/utils.cpp rename to cpp/libs/gst_nmos_video_receiver_plugin/utils.cpp diff --git a/cpp/libs/gst_nmos_video_receiver_plugin/utils.h b/cpp/libs/gst_nmos_video_receiver_plugin/utils.h new file mode 100644 index 0000000..f3cb2a1 --- /dev/null +++ b/cpp/libs/gst_nmos_video_receiver_plugin/utils.h @@ -0,0 +1,119 @@ +#pragma once +#include +#include +#include +#include +#include + +typedef struct node_fields_t +{ + std::string id; + std::string configuration_location; +} node_fields_t; + +typedef struct device_fields_t +{ + std::string id; + std::string label; + std::string description; +} device_fields_t; + +typedef struct config_fields_t +{ + node_fields_t node; + device_fields_t device; + std::string id; + std::string label; + std::string description; + std::string address; + gint port; + std::string interface_name; +} config_fields_t; + +template class GstElementHandle +{ + public: + static std::variant create_element(const char* factory_name, + const char* element_name = nullptr) + { + T* elem = reinterpret_cast(gst_element_factory_make(factory_name, element_name)); + if(!elem) + { + return nullptr; + } + return GstElementHandle(elem); + } + + static std::variant create_bin(const char* bin_name) + { + T* bin = reinterpret_cast(gst_bin_new(bin_name)); + if(!bin) + { + return nullptr; + } + return GstElementHandle(bin); + } + + GstElementHandle(const GstElementHandle&) = delete; + GstElementHandle& operator=(const GstElementHandle&) = delete; + + // Move constructor + GstElementHandle(GstElementHandle&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; } + + // Move assignment + GstElementHandle& operator=(GstElementHandle&& other) noexcept + { + if(this != &other) + { + if(handle_ != nullptr) + { + gst_object_unref(handle_); + } + handle_ = other.handle_; + other.handle_ = nullptr; + } + return *this; + } + + ~GstElementHandle() + { + if(handle_) + { + gst_object_unref(handle_); + } + } + + void reset(T* new_ptr = nullptr) + { + handle_ = new_ptr; + } + + T* get() const { return handle_; } + + explicit operator bool() const { return (handle_ != nullptr); } + + private: + explicit GstElementHandle(T* handle) : handle_(handle) {} + + T* handle_ = nullptr; +}; + +void create_default_config_fields(config_fields_t* config); + +nlohmann::json create_node_config(config_fields_t& config); +nlohmann::json create_device_config(config_fields_t& config); +nlohmann::json create_receiver_config(config_fields_t& config); + +std::string translate_video_format(const std::string& gst_format); + +std::string translate_audio_format(const std::string& gst_format); + +std::string get_node_id(char* node_configuration_location); + +std::string get_node_config(std::string node_configuration_location); + +std::string get_device_id(char* device_configuration_location); + +std::string get_device_config(char* device_configuration_location); + +std::string get_sender_config(char* sender_configuration_location); From 8b6366e85484db22bc01ceb654a84733c71e5620 Mon Sep 17 00:00:00 2001 From: LufeBisect Date: Tue, 21 Jan 2025 16:38:08 +0000 Subject: [PATCH 3/4] Added L16 audio --- .../gst_nmos_audio_receiver_plugin.cpp | 96 +++++++++++++------ .../gst_nmos_audio_receiver_plugin/utils.cpp | 37 ++++--- .../gst_nmos_sender_plugin.cpp | 90 ++++++++++------- cpp/libs/gst_nmos_sender_plugin/utils.cpp | 50 ++++++---- .../gst_nmos_video_receiver_plugin.cpp | 7 +- .../gst_nmos_video_receiver_plugin/utils.cpp | 37 ++++--- .../lib/src/serialization/media_types.h | 1 + .../lib/src/serialization/receiver.cpp | 15 ++- .../lib/src/serialization/sender.cpp | 17 ++++ 9 files changed, 240 insertions(+), 110 deletions(-) diff --git a/cpp/libs/gst_nmos_audio_receiver_plugin/gst_nmos_audio_receiver_plugin.cpp b/cpp/libs/gst_nmos_audio_receiver_plugin/gst_nmos_audio_receiver_plugin.cpp index bf73ea6..d821fbc 100644 --- a/cpp/libs/gst_nmos_audio_receiver_plugin/gst_nmos_audio_receiver_plugin.cpp +++ b/cpp/libs/gst_nmos_audio_receiver_plugin/gst_nmos_audio_receiver_plugin.cpp @@ -50,7 +50,8 @@ typedef struct _GstNmosaudioreceiver GstClock* clock; GstElementHandle<_GstElement> bin; GstElementHandle<_GstElement> udp_src; - GstElementHandle<_GstElement> rtp_audio_depay; + GstElementHandle<_GstElement> rtp_audio_depay_16; + GstElementHandle<_GstElement> rtp_audio_depay_24; GstElementHandle<_GstElement> rtp_jitter_buffer; GstElementHandle<_GstElement> queue; ossrf::nmos_client_uptr client; @@ -71,7 +72,7 @@ G_DEFINE_TYPE_WITH_CODE(GstNmosaudioreceiver, gst_nmosaudioreceiver, GST_TYPE_BI // Pad template static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS("audio/x-raw, " - "format=(string){ S16LE, S24BE }; " + "format=(string){ S16BE, S24BE }; " "application/x-rtp")); enum class PropertyId : uint32_t @@ -163,7 +164,8 @@ void remove_old_bin(GstNmosaudioreceiver* self) gst_bin_remove(GST_BIN(self), self->udp_src.get()); gst_bin_remove(GST_BIN(self), self->rtp_jitter_buffer.get()); - gst_bin_remove(GST_BIN(self), self->rtp_audio_depay.get()); + gst_bin_remove(GST_BIN(self), self->rtp_audio_depay_16.get()); + gst_bin_remove(GST_BIN(self), self->rtp_audio_depay_24.get()); gst_bin_remove(GST_BIN(self), self->queue.get()); gst_bin_remove(GST_BIN(self), self->bin.get()); @@ -171,7 +173,8 @@ void remove_old_bin(GstNmosaudioreceiver* self) self->udp_src.reset(); self->queue.reset(); self->rtp_jitter_buffer.reset(); - self->rtp_audio_depay.reset(); + self->rtp_audio_depay_16.reset(); + self->rtp_audio_depay_24.reset(); self->bin.reset(); if(block_id != 0) @@ -212,27 +215,31 @@ void construct_pipeline(GstNmosaudioreceiver* self) self->bin = std::move(bin); - auto maybeUdpsrc = GstElementHandle::create_element("udpsrc", nullptr); - auto maybeQueue = GstElementHandle::create_element("queue", nullptr); - auto maybeJitter = GstElementHandle::create_element("rtpjitterbuffer", nullptr); - auto maybeDepay = GstElementHandle::create_element("rtpL24depay", nullptr); + auto maybeUdpsrc = GstElementHandle::create_element("udpsrc", nullptr); + auto maybeQueue = GstElementHandle::create_element("queue", nullptr); + auto maybeJitter = GstElementHandle::create_element("rtpjitterbuffer", nullptr); + auto maybeDepay16 = GstElementHandle::create_element("rtpL16depay", nullptr); + auto maybeDepay24 = GstElementHandle::create_element("rtpL24depay", nullptr); if(std::holds_alternative(maybeUdpsrc) || std::holds_alternative(maybeQueue) || - std::holds_alternative(maybeJitter) || std::holds_alternative(maybeDepay)) + std::holds_alternative(maybeJitter) || + std::holds_alternative(maybeDepay16) || std::holds_alternative(maybeDepay24)) { GST_ERROR_OBJECT(self, "Failed to create pipeline elements."); return; } - GstElementHandle udpsrc = std::move(std::get>(maybeUdpsrc)); - GstElementHandle queue = std::move(std::get>(maybeQueue)); - GstElementHandle jitter = std::move(std::get>(maybeJitter)); - GstElementHandle depay = std::move(std::get>(maybeDepay)); + GstElementHandle udpsrc = std::move(std::get>(maybeUdpsrc)); + GstElementHandle queue = std::move(std::get>(maybeQueue)); + GstElementHandle jitter = std::move(std::get>(maybeJitter)); + GstElementHandle depay16 = std::move(std::get>(maybeDepay16)); + GstElementHandle depay24 = std::move(std::get>(maybeDepay24)); - self->udp_src = std::move(udpsrc); - self->queue = std::move(queue); - self->rtp_jitter_buffer = std::move(jitter); - self->rtp_audio_depay = std::move(depay); + self->udp_src = std::move(udpsrc); + self->queue = std::move(queue); + self->rtp_jitter_buffer = std::move(jitter); + self->rtp_audio_depay_16 = std::move(depay16); + self->rtp_audio_depay_24 = std::move(depay24); g_object_set( G_OBJECT(self->udp_src.get()), "address", self->sdp_settings.primary.destination_ip.value().c_str(), "port", @@ -249,26 +256,50 @@ void construct_pipeline(GstNmosaudioreceiver* self) g_object_set(G_OBJECT(self->queue.get()), "max-size-buffers", 3, nullptr); gst_bin_add_many(GST_BIN(self->bin.get()), self->udp_src.get(), self->rtp_jitter_buffer.get(), - self->queue.get(), self->rtp_audio_depay.get(), nullptr); + self->queue.get(), self->rtp_audio_depay_16.get(), self->rtp_audio_depay_24.get(), nullptr); gst_element_sync_state_with_parent(self->udp_src.get()); gst_element_sync_state_with_parent(self->rtp_jitter_buffer.get()); gst_element_sync_state_with_parent(self->queue.get()); - gst_element_sync_state_with_parent(self->rtp_audio_depay.get()); + gst_element_sync_state_with_parent(self->rtp_audio_depay_16.get()); + gst_element_sync_state_with_parent(self->rtp_audio_depay_24.get()); - if(gst_element_link_many(self->udp_src.get(), self->rtp_jitter_buffer.get(), self->queue.get(), - self->rtp_audio_depay.get(), nullptr) == false) + GstPad* bin_src_pad = nullptr; + + if(audio_info.bits_per_sample == 16) { - GST_ERROR_OBJECT(self, "Failed to link elements inside the bin."); - return; - } - // Create a ghost pad from the depay's src - GstPad* bin_src_pad = gst_element_get_static_pad(self->rtp_audio_depay.get(), "src"); - if(bin_src_pad == nullptr) + if(gst_element_link_many(self->udp_src.get(), self->rtp_jitter_buffer.get(), self->queue.get(), + self->rtp_audio_depay_16.get(), nullptr) == false) + { + GST_ERROR_OBJECT(self, "Failed to link elements inside the bin."); + return; + } + + // Create a ghost pad from the depay's src + bin_src_pad = gst_element_get_static_pad(self->rtp_audio_depay_16.get(), "src"); + if(bin_src_pad == nullptr) + { + GST_ERROR_OBJECT(self, "Failed to get src pad from rtp_audio_depay."); + return; + } + } + else { - GST_ERROR_OBJECT(self, "Failed to get src pad from rtp_audio_depay."); - return; + if(gst_element_link_many(self->udp_src.get(), self->rtp_jitter_buffer.get(), self->queue.get(), + self->rtp_audio_depay_24.get(), nullptr) == false) + { + GST_ERROR_OBJECT(self, "Failed to link elements inside the bin."); + return; + } + + // Create a ghost pad from the depay's src + bin_src_pad = gst_element_get_static_pad(self->rtp_audio_depay_24.get(), "src"); + if(bin_src_pad == nullptr) + { + GST_ERROR_OBJECT(self, "Failed to get src pad from rtp_audio_depay."); + return; + } } GstPad* bin_ghost_pad = gst_ghost_pad_new("src", bin_src_pad); @@ -308,7 +339,12 @@ void construct_pipeline(GstNmosaudioreceiver* self) void create_nmos(GstNmosaudioreceiver* self) { - const auto node_config_json = create_node_config(self->config); + const auto node_config_json = create_node_config(self->config); + if(node_config_json == nullptr) + { + GST_ERROR_OBJECT(self, "Failed to initialize NMOS client. No valid node JSON location given."); + return; + } const auto device_config_json = create_device_config(self->config); nlohmann::json_abi_v3_11_3::json receiver_config_json = nullptr; diff --git a/cpp/libs/gst_nmos_audio_receiver_plugin/utils.cpp b/cpp/libs/gst_nmos_audio_receiver_plugin/utils.cpp index 05b2687..aac35f2 100644 --- a/cpp/libs/gst_nmos_audio_receiver_plugin/utils.cpp +++ b/cpp/libs/gst_nmos_audio_receiver_plugin/utils.cpp @@ -38,10 +38,17 @@ void create_default_config_fields(config_fields_t* config) json create_node_config(config_fields_t& config) { const std::string node_config_str = get_node_config(config.node.configuration_location); - json node_config = json::parse(node_config_str); - node_config["id"] = config.node.id; + if(node_config_str != "") + { + json node_config = json::parse(node_config_str); + node_config["id"] = config.node.id; - return node_config; + return node_config; + } + else + { + return nullptr; + } } json create_device_config(config_fields_t& config) @@ -124,23 +131,25 @@ std::string get_node_config(std::string node_configuration_location) { const auto configuration_result = load_configuration_from_file(node_configuration_location.c_str()); - const json& configuration = configuration_result.value(); + if(configuration_result.has_value()) + { + const json& configuration = configuration_result.value(); - auto node_result = find(configuration, "node"); + auto node_result = find(configuration, "node"); - const json& node = node_result.value(); + const json& node = node_result.value(); - auto node_id_result = find(node, "id"); - auto node_config_result = find(node, "configuration"); + auto node_id_result = find(node, "id"); + auto node_config_result = find(node, "configuration"); - if(node_id_result.has_value() && node_config_result.has_value()) - { - const std::string node_id = node_id_result.value(); - const std::string node_configuration = node_config_result.value().dump(); + if(node_id_result.has_value() && node_config_result.has_value()) + { + const std::string node_id = node_id_result.value(); + const std::string node_configuration = node_config_result.value().dump(); - return node_configuration; + return node_configuration; + } } - return ""; } diff --git a/cpp/libs/gst_nmos_sender_plugin/gst_nmos_sender_plugin.cpp b/cpp/libs/gst_nmos_sender_plugin/gst_nmos_sender_plugin.cpp index 093ef77..956a2d1 100644 --- a/cpp/libs/gst_nmos_sender_plugin/gst_nmos_sender_plugin.cpp +++ b/cpp/libs/gst_nmos_sender_plugin/gst_nmos_sender_plugin.cpp @@ -39,7 +39,8 @@ typedef struct _GstNmossender GstBin parent; GstElementHandle<_GstElement> queue; GstElementHandle<_GstElement> video_payloader; - GstElementHandle<_GstElement> audio_payloader; + GstElementHandle<_GstElement> audio_payloader_16; + GstElementHandle<_GstElement> audio_payloader_24; GstElementHandle<_GstElement> udpsink; ossrf::nmos_client_uptr client; GstCaps* caps; @@ -75,7 +76,7 @@ static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE("sink", GST_ GST_STATIC_CAPS("video/x-raw, " "format=(string){ UYVP }; " "audio/x-raw, " - "format=(string)S24BE, " + "format=(string){ S24BE, S16BE }, " "layout=(string)interleaved, " "rate=(int)[ 1, 2147483647 ], " "channels=(int)[ 1, 2147483647 ]")); @@ -176,21 +177,7 @@ static gboolean gst_nmossender_sink_event(GstPad* pad, GstObject* parent, GstEve if(media_type == "audio/x-raw") { - g_print("\n\n\n\n\n\n\n\n\n\n\n"); - // DEBUG - // g_print("Audio caps detected\n"); - // GST_INFO_OBJECT(self, "Audio caps detected"); - // - if(gst_element_link(self->queue.get(), self->audio_payloader.get()) == false) - { - GST_ERROR_OBJECT(self, "Failed to link queue to audio_payloader"); - return false; - } - if(gst_element_link(self->audio_payloader.get(), self->udpsink.get()) == false) - { - GST_ERROR_OBJECT(self, "Failed to link audio_payloader to udpsink"); - return false; - } + self->caps = gst_caps_ref(caps); self->config.is_audio = true; for(guint i = 0; i < gst_caps_get_size(caps); i++) @@ -207,7 +194,37 @@ static gboolean gst_nmossender_sink_event(GstPad* pad, GstObject* parent, GstEve } if((format = gst_structure_get_string(structure, "format"))) { - self->config.audio_sender_fields.format = translate_audio_format(format); + self->config.audio_sender_fields.format = format; + } + } + // DEBUG + // g_print("Audio caps detected\n"); + // GST_INFO_OBJECT(self, "Audio caps detected"); + // + if(self->config.audio_sender_fields.format == "S24BE") + { + if(gst_element_link(self->queue.get(), self->audio_payloader_24.get()) == false) + { + GST_ERROR_OBJECT(self, "Failed to link queue to audio_payloader"); + return false; + } + if(gst_element_link(self->audio_payloader_24.get(), self->udpsink.get()) == false) + { + GST_ERROR_OBJECT(self, "Failed to link audio_payloader_24 to udpsink"); + return false; + } + } + else + { + if(gst_element_link(self->queue.get(), self->audio_payloader_16.get()) == false) + { + GST_ERROR_OBJECT(self, "Failed to link queue to audio_payloader"); + return false; + } + if(gst_element_link(self->audio_payloader_16.get(), self->udpsink.get()) == false) + { + GST_ERROR_OBJECT(self, "Failed to link audio_payloader_16 to udpsink"); + return false; } } // DEBUG @@ -217,8 +234,6 @@ static gboolean gst_nmossender_sink_event(GstPad* pad, GstObject* parent, GstEve } else if(media_type == "video/x-raw") { - - g_print("\n\n\n\n\n\n\n\n\n\n\n"); // DEBUG // g_print("Video caps detected\n"); // GST_INFO_OBJECT(self, "Video caps detected"); @@ -292,7 +307,12 @@ static GstStateChangeReturn gst_nmossender_change_state(GstElement* element, Gst { case GST_STATE_CHANGE_PAUSED_TO_PLAYING: { - const auto node_config_json = create_node_config(self->config); + const auto node_config_json = create_node_config(self->config); + if(node_config_json == nullptr) + { + GST_ERROR_OBJECT(self, "Failed to initialize NMOS client. No valid node JSON location given."); + return GST_STATE_CHANGE_FAILURE; + } const auto device_config_json = create_device_config(self->config); nlohmann::json_abi_v3_11_3::json sender_config_json = nullptr; @@ -394,13 +414,15 @@ static void gst_nmossender_class_init(GstNmossenderClass* klass) /* Object initialization */ static void gst_nmossender_init(GstNmossender* self) { - auto maybeQueue = GstElementHandle::create_element("queue", nullptr); - auto maybeVideoPay = GstElementHandle::create_element("rtpvrawpay", nullptr); - auto maybeAudioPay = GstElementHandle::create_element("rtpL24pay", nullptr); - auto maybeUdpSink = GstElementHandle::create_element("udpsink", nullptr); + auto maybeQueue = GstElementHandle::create_element("queue", nullptr); + auto maybeVideoPay = GstElementHandle::create_element("rtpvrawpay", nullptr); + auto maybeAudioPay16 = GstElementHandle::create_element("rtpL16pay", nullptr); + auto maybeAudioPay24 = GstElementHandle::create_element("rtpL24pay", nullptr); + auto maybeUdpSink = GstElementHandle::create_element("udpsink", nullptr); if(std::holds_alternative(maybeQueue) || std::holds_alternative(maybeVideoPay) || - std::holds_alternative(maybeAudioPay) || std::holds_alternative(maybeUdpSink)) + std::holds_alternative(maybeAudioPay16) || + std::holds_alternative(maybeAudioPay24) || std::holds_alternative(maybeUdpSink)) { GST_ERROR_OBJECT(self, "Failed to create pipeline elements."); return; @@ -408,21 +430,23 @@ static void gst_nmossender_init(GstNmossender* self) GstElementHandle queue = std::move(std::get>(maybeQueue)); GstElementHandle rtpvrawpay = std::move(std::get>(maybeVideoPay)); - GstElementHandle rtpL24pay = std::move(std::get>(maybeAudioPay)); + GstElementHandle rtpL16pay = std::move(std::get>(maybeAudioPay16)); + GstElementHandle rtpL24pay = std::move(std::get>(maybeAudioPay24)); GstElementHandle udpsink = std::move(std::get>(maybeUdpSink)); - self->queue = std::move(queue); - self->video_payloader = std::move(rtpvrawpay); - self->audio_payloader = std::move(rtpL24pay); - self->udpsink = std::move(udpsink); + self->queue = std::move(queue); + self->video_payloader = std::move(rtpvrawpay); + self->audio_payloader_16 = std::move(rtpL16pay); + self->audio_payloader_24 = std::move(rtpL24pay); + self->udpsink = std::move(udpsink); // set properties g_object_set(G_OBJECT(self->queue.get()), "max-size-buffers", 1, nullptr); g_object_set(G_OBJECT(self->udpsink.get()), "host", "127.0.0.1", "port", 9999, nullptr); create_default_config_fields(&self->config); - gst_bin_add_many(GST_BIN(self), self->queue.get(), self->video_payloader.get(), self->audio_payloader.get(), - self->udpsink.get(), nullptr); + gst_bin_add_many(GST_BIN(self), self->queue.get(), self->video_payloader.get(), self->audio_payloader_24.get(), + self->audio_payloader_16.get(), self->udpsink.get(), nullptr); // create and configure the sink pad GstPad* queue_sinkpad = gst_element_get_static_pad(self->queue.get(), "sink"); diff --git a/cpp/libs/gst_nmos_sender_plugin/utils.cpp b/cpp/libs/gst_nmos_sender_plugin/utils.cpp index a197f82..430769a 100644 --- a/cpp/libs/gst_nmos_sender_plugin/utils.cpp +++ b/cpp/libs/gst_nmos_sender_plugin/utils.cpp @@ -57,13 +57,18 @@ json create_node_config(config_fields_t& config) { const std::string node_config_str = get_node_config(config.node.configuration_location.data()); - // Parse the configuration into a JSON object - json node_config = json::parse(node_config_str); + if(node_config_str != "") + { + json node_config = json::parse(node_config_str); - // Override the "id" field with the one from the config_fields_t structure - node_config["id"] = config.node.id; + node_config["id"] = config.node.id; - return node_config; + return node_config; + } + else + { + return nullptr; + } } json create_device_config(config_fields_t& config) @@ -99,6 +104,15 @@ json create_video_sender_config(config_fields_t& config) json create_audio_sender_config(config_fields_t& config) { + std::string media_type = "audio/L16"; + if(config.audio_sender_fields.format == "S24BE") + { + media_type = "audio/L24"; + } + else if(config.audio_sender_fields.format == "S16BE") + { + media_type = "audio/L16"; + } json sender = {{"id", config.sender_id}, {"label", config.sender_label}, {"description", config.sender_description}, @@ -109,7 +123,7 @@ json create_audio_sender_config(config_fields_t& config) {"destination_address", config.network.destination_address}, {"destination_port", config.network.destination_port}}}}}, {"payload_type", 97}, - {"media_type", "audio/L24"}, + {"media_type", media_type}, {"media", {{"number_of_channels", config.audio_sender_fields.number_of_channels}, {"sampling_rate", config.audio_sender_fields.sampling_rate}, @@ -179,23 +193,25 @@ std::string get_node_config(char* node_configuration_location) { const auto configuration_result = load_configuration_from_file(node_configuration_location); - const json& configuration = configuration_result.value(); + if(configuration_result.has_value()) + { + const json& configuration = configuration_result.value(); - auto node_result = find(configuration, "node"); + auto node_result = find(configuration, "node"); - const json& node = node_result.value(); + const json& node = node_result.value(); - auto node_id_result = find(node, "id"); - auto node_config_result = find(node, "configuration"); + auto node_id_result = find(node, "id"); + auto node_config_result = find(node, "configuration"); - if(node_id_result.has_value() && node_config_result.has_value()) - { - const std::string node_id = node_id_result.value(); - const std::string node_configuration = node_config_result.value().dump(); + if(node_id_result.has_value() && node_config_result.has_value()) + { + const std::string node_id = node_id_result.value(); + const std::string node_configuration = node_config_result.value().dump(); - return node_configuration; + return node_configuration; + } } - return ""; } diff --git a/cpp/libs/gst_nmos_video_receiver_plugin/gst_nmos_video_receiver_plugin.cpp b/cpp/libs/gst_nmos_video_receiver_plugin/gst_nmos_video_receiver_plugin.cpp index 4a27a92..0f77142 100644 --- a/cpp/libs/gst_nmos_video_receiver_plugin/gst_nmos_video_receiver_plugin.cpp +++ b/cpp/libs/gst_nmos_video_receiver_plugin/gst_nmos_video_receiver_plugin.cpp @@ -322,7 +322,12 @@ void construct_pipeline(GstNmosvideoreceiver* self) void create_nmos(GstNmosvideoreceiver* self) { - const auto node_config_json = create_node_config(self->config); + const auto node_config_json = create_node_config(self->config); + if(node_config_json == nullptr) + { + GST_ERROR_OBJECT(self, "Failed to initialize NMOS client. No valid node JSON location given."); + return; + } const auto device_config_json = create_device_config(self->config); nlohmann::json_abi_v3_11_3::json receiver_config_json = nullptr; diff --git a/cpp/libs/gst_nmos_video_receiver_plugin/utils.cpp b/cpp/libs/gst_nmos_video_receiver_plugin/utils.cpp index 044bddb..76d5bb9 100644 --- a/cpp/libs/gst_nmos_video_receiver_plugin/utils.cpp +++ b/cpp/libs/gst_nmos_video_receiver_plugin/utils.cpp @@ -38,10 +38,17 @@ void create_default_config_fields(config_fields_t* config) json create_node_config(config_fields_t& config) { const std::string node_config_str = get_node_config(config.node.configuration_location); - json node_config = json::parse(node_config_str); - node_config["id"] = config.node.id; + if(node_config_str != "") + { + json node_config = json::parse(node_config_str); + node_config["id"] = config.node.id; - return node_config; + return node_config; + } + else + { + return nullptr; + } } json create_device_config(config_fields_t& config) @@ -124,23 +131,25 @@ std::string get_node_config(std::string node_configuration_location) { const auto configuration_result = load_configuration_from_file(node_configuration_location.c_str()); - const json& configuration = configuration_result.value(); + if(configuration_result.has_value()) + { + const json& configuration = configuration_result.value(); - auto node_result = find(configuration, "node"); + auto node_result = find(configuration, "node"); - const json& node = node_result.value(); + const json& node = node_result.value(); - auto node_id_result = find(node, "id"); - auto node_config_result = find(node, "configuration"); + auto node_id_result = find(node, "id"); + auto node_config_result = find(node, "configuration"); - if(node_id_result.has_value() && node_config_result.has_value()) - { - const std::string node_id = node_id_result.value(); - const std::string node_configuration = node_config_result.value().dump(); + if(node_id_result.has_value() && node_config_result.has_value()) + { + const std::string node_id = node_id_result.value(); + const std::string node_configuration = node_config_result.value().dump(); - return node_configuration; + return node_configuration; + } } - return ""; } diff --git a/cpp/libs/ossrf_nmos_api/lib/src/serialization/media_types.h b/cpp/libs/ossrf_nmos_api/lib/src/serialization/media_types.h index 62d0f3a..09ced69 100644 --- a/cpp/libs/ossrf_nmos_api/lib/src/serialization/media_types.h +++ b/cpp/libs/ossrf_nmos_api/lib/src/serialization/media_types.h @@ -20,4 +20,5 @@ namespace ossrf::media_types { constexpr std::string_view VIDEO_RAW = "video/raw"; constexpr std::string_view AUDIO_L24 = "audio/L24"; + constexpr std::string_view AUDIO_L16 = "audio/L16"; } // namespace ossrf::media_types diff --git a/cpp/libs/ossrf_nmos_api/lib/src/serialization/receiver.cpp b/cpp/libs/ossrf_nmos_api/lib/src/serialization/receiver.cpp index d6e9db3..cbbd00a 100644 --- a/cpp/libs/ossrf_nmos_api/lib/src/serialization/receiver.cpp +++ b/cpp/libs/ossrf_nmos_api/lib/src/serialization/receiver.cpp @@ -44,7 +44,20 @@ namespace else if(media_type == "audio/L24") { receiver.media_types = {nmos::media_types::audio_L24}; - receiver.format = nmos::formats::audio; + if(c[1] != "") + { + receiver.media_types.push_back(nmos::media_types::audio_L(16)); + } + receiver.format = nmos::formats::audio; + } + else if(media_type == "audio/L16") + { + receiver.media_types = {nmos::media_types::audio_L(16)}; + if(c[1] != "") + { + receiver.media_types.push_back(nmos::media_types::audio_L24); + } + receiver.format = nmos::formats::audio; } else { diff --git a/cpp/libs/ossrf_nmos_api/lib/src/serialization/sender.cpp b/cpp/libs/ossrf_nmos_api/lib/src/serialization/sender.cpp index 51942e7..8183ff5 100644 --- a/cpp/libs/ossrf_nmos_api/lib/src/serialization/sender.cpp +++ b/cpp/libs/ossrf_nmos_api/lib/src/serialization/sender.cpp @@ -65,6 +65,16 @@ namespace return info; } + expected audio_l16_sender_info_from_json(const json& media) + { + audio_sender_info_t info; + BST_CHECK_ASSIGN(info.number_of_channels, find(media, "number_of_channels")); + BST_CHECK_ASSIGN(info.sampling_rate, find(media, "sampling_rate")); + BST_CHECK_ASSIGN(info.packet_time, find(media, "packet_time")); + info.bits_per_sample = 16; + return info; + } + std::string synthetize_id(std::string sender_id, int delta) { const auto last_digit_value = std::stoi(sender_id.substr(sender_id.size() - 1), 0, 16); @@ -125,6 +135,13 @@ expected ossrf::nmos_sender_from_json(const json& config) sender.format = nmos::formats::audio; sender.grain_rate = info.sampling_rate; } + else if(sender.media_type == media_types::AUDIO_L16) + { + BST_ASSIGN(info, audio_l16_sender_info_from_json(media)); + sender.media = info; + sender.format = nmos::formats::audio; + sender.grain_rate = info.sampling_rate; + } else { BST_FAIL("invalid media type: {}", sender.media_type); From 04b289acf029b547a66a3a3634ab9ee7a8493fee Mon Sep 17 00:00:00 2001 From: LufeBisect Date: Wed, 22 Jan 2025 18:06:34 +0000 Subject: [PATCH 4/4] utils.cpp/h : Refactor for reduced redundancy --- cpp/libs/CMakeLists.txt | 4 +- .../CMakeLists.txt | 67 ------ .../gst_nmos_audio_receiver_plugin/utils.cpp | 198 ------------------ .../gst_nmos_audio_receiver_plugin/utils.h | 116 ---------- cpp/libs/gst_nmos_plugins/CMakeLists.txt | 99 +++++++++ .../include/element_class.h} | 56 +---- .../include/nmos_configuration.h | 57 +++++ .../src}/gst_nmos_audio_receiver_plugin.cpp | 92 ++++---- .../src}/gst_nmos_sender_plugin.cpp | 56 ++--- .../src}/gst_nmos_video_receiver_plugin.cpp | 119 +++++------ .../src}/utils.cpp | 191 +++++++++++------ cpp/libs/gst_nmos_plugins/src/utils.h | 33 +++ .../gst_nmos_sender_plugin/CMakeLists.txt | 67 ------ cpp/libs/gst_nmos_sender_plugin/utils.h | 151 ------------- .../CMakeLists.txt | 67 ------ .../gst_nmos_video_receiver_plugin/utils.cpp | 198 ------------------ 16 files changed, 432 insertions(+), 1139 deletions(-) delete mode 100644 cpp/libs/gst_nmos_audio_receiver_plugin/CMakeLists.txt delete mode 100644 cpp/libs/gst_nmos_audio_receiver_plugin/utils.cpp delete mode 100644 cpp/libs/gst_nmos_audio_receiver_plugin/utils.h create mode 100644 cpp/libs/gst_nmos_plugins/CMakeLists.txt rename cpp/libs/{gst_nmos_video_receiver_plugin/utils.h => gst_nmos_plugins/include/element_class.h} (55%) create mode 100644 cpp/libs/gst_nmos_plugins/include/nmos_configuration.h rename cpp/libs/{gst_nmos_audio_receiver_plugin => gst_nmos_plugins/src}/gst_nmos_audio_receiver_plugin.cpp (86%) rename cpp/libs/{gst_nmos_sender_plugin => gst_nmos_plugins/src}/gst_nmos_sender_plugin.cpp (88%) rename cpp/libs/{gst_nmos_video_receiver_plugin => gst_nmos_plugins/src}/gst_nmos_video_receiver_plugin.cpp (83%) rename cpp/libs/{gst_nmos_sender_plugin => gst_nmos_plugins/src}/utils.cpp (70%) create mode 100644 cpp/libs/gst_nmos_plugins/src/utils.h delete mode 100644 cpp/libs/gst_nmos_sender_plugin/CMakeLists.txt delete mode 100644 cpp/libs/gst_nmos_sender_plugin/utils.h delete mode 100644 cpp/libs/gst_nmos_video_receiver_plugin/CMakeLists.txt delete mode 100644 cpp/libs/gst_nmos_video_receiver_plugin/utils.cpp diff --git a/cpp/libs/CMakeLists.txt b/cpp/libs/CMakeLists.txt index 8c8a6c5..1bb41b6 100644 --- a/cpp/libs/CMakeLists.txt +++ b/cpp/libs/CMakeLists.txt @@ -2,9 +2,7 @@ add_subdirectory(bisect_expected) add_subdirectory(bisect_nmoscpp) add_subdirectory(bisect_sdp) add_subdirectory(bisect_gst) -add_subdirectory(gst_nmos_sender_plugin) -add_subdirectory(gst_nmos_video_receiver_plugin) -add_subdirectory(gst_nmos_audio_receiver_plugin) +add_subdirectory(gst_nmos_plugins) add_subdirectory(ossrf_nmos_api) add_subdirectory(ossrf_gstreamer_api) add_subdirectory(bisect_json) diff --git a/cpp/libs/gst_nmos_audio_receiver_plugin/CMakeLists.txt b/cpp/libs/gst_nmos_audio_receiver_plugin/CMakeLists.txt deleted file mode 100644 index 8865116..0000000 --- a/cpp/libs/gst_nmos_audio_receiver_plugin/CMakeLists.txt +++ /dev/null @@ -1,67 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(gst_nmos_audio_receiver_plugin LANGUAGES CXX) - -find_package(nlohmann_json REQUIRED) -find_package(PkgConfig REQUIRED) - -# Locate GLib package -pkg_check_modules(GLIB REQUIRED glib-2.0) - -# Locate GStreamer packages -pkg_search_module(GSTREAMER REQUIRED gstreamer-1.0>=1.4) -pkg_search_module(GSTREAMER_APP REQUIRED gstreamer-app-1.0>=1.4) -pkg_search_module(GSTREAMER_AUDIO REQUIRED gstreamer-audio-1.0>=1.4) -pkg_search_module(GSTREAMER_VIDEO REQUIRED gstreamer-video-1.0>=1.4) - -# Include GLib directories and link libraries -include_directories(${GLIB_INCLUDE_DIRS}) -link_directories(${GLIB_LIBRARY_DIRS}) -add_definitions(${GLIB_CFLAGS_OTHER}) - -# Include source files for gst_nmos_audio_receiver_plugin -file(GLOB_RECURSE PLUGIN_SOURCE_FILES *.cpp *.h) - -# Include source files for utils -file(GLOB_RECURSE UTILS_SOURCE_FILES utils/*.cpp utils/*.h) - -# Combine utils and plugin sources -set(SOURCE_FILES ${PLUGIN_SOURCE_FILES} ${UTILS_SOURCE_FILES}) - -# Define the plugin as a shared library -add_library(${PROJECT_NAME} MODULE ${SOURCE_FILES}) - -# Include directories -target_include_directories(${PROJECT_NAME} - PRIVATE ${GSTREAMER_INCLUDE_DIRS} - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/utils/include -) - -# Link GStreamer libraries -target_compile_options(${PROJECT_NAME} PRIVATE ${GSTREAMER_CFLAGS_OTHER}) -target_link_libraries(${PROJECT_NAME} - PRIVATE - ${GSTREAMER_LIBRARIES} - ${GSTREAMER_APP_LIBRARIES} - ${GSTREAMER_AUDIO_LIBRARIES} - ${GSTREAMER_VIDEO_LIBRARIES} - PUBLIC - bisect::project_warnings - bisect::expected - bisect::bisect_nmoscpp - bisect::bisect_json - nlohmann_json::nlohmann_json - ossrf::ossrf_nmos_api - ${GLIB_LIBRARIES} -) - -# Specify the output directory and the library name -set_target_properties(${PROJECT_NAME} PROPERTIES - LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins - OUTPUT_NAME "gstnmosaudioreceiver" -) - -add_library(ossrf::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) - -# Install the plugin to the system's GStreamer plugin directory -install(TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ~/.local/lib/gstreamer-1.0) diff --git a/cpp/libs/gst_nmos_audio_receiver_plugin/utils.cpp b/cpp/libs/gst_nmos_audio_receiver_plugin/utils.cpp deleted file mode 100644 index aac35f2..0000000 --- a/cpp/libs/gst_nmos_audio_receiver_plugin/utils.cpp +++ /dev/null @@ -1,198 +0,0 @@ -#include "utils.h" -#include "ossrf/nmos/api/nmos_client.h" -#include "bisect/nmoscpp/configuration.h" -#include "bisect/expected/macros.h" -#include "bisect/expected.h" -#include "bisect/json.h" -#include - -using namespace bisect; -using json = nlohmann::json; - -void create_default_config_fields(config_fields_t* config) -{ - if(!config) - { - g_critical("Config pointer is NULL"); - return; - } - - // Initialize node_fields_t - config->node.id = "d1073007-4099-452f-b8e8-837b8987d845"; - config->node.configuration_location = - "/home/nmos/repos/nmos-sender-receiver-framework/cpp/demos/config/nmos_plugin_node_config_receiver.json"; - - // Initialize device_fields_t - config->device.id = "093a1c56-4033-49f7-9e17-fbcb21a19383"; - config->device.label = "OSSRF Device2"; - config->device.description = "OSSRF Device2"; - - // Initialize config_fields_t - config->id = "af600517-a452-4c2c-8b60-2caa770d6435"; - config->label = "BISECT OSSRF Audio Receiver"; - config->description = "BISECT OSSRF Audio Receiver"; - config->interface_name = "wlp1s0"; - config->address = "192.168.1.36"; -} - -json create_node_config(config_fields_t& config) -{ - const std::string node_config_str = get_node_config(config.node.configuration_location); - if(node_config_str != "") - { - json node_config = json::parse(node_config_str); - node_config["id"] = config.node.id; - - return node_config; - } - else - { - return nullptr; - } -} - -json create_device_config(config_fields_t& config) -{ - json device = { - {"id", config.device.id}, {"label", config.device.label}, {"description", config.device.description}}; - return device; -} - -json create_receiver_config(config_fields_t& config) -{ - json sender = { - {"id", config.id}, - {"label", config.label}, - {"description", config.description}, - {"network", {{"primary", {{"interface_address", config.address}, {"interface_name", config.interface_name}}}}}, - {"capabilities", {"audio/L24"}}}; - return sender; -} - -std::string translate_video_format(const std::string& gst_format) -{ - static const std::unordered_map video_format_map = {{"UYVP", "YCbCr-4:2:2"}, - {"RGBA", "RGBA-8:8:8:8"}}; - - auto it = video_format_map.find(gst_format); - if(it != video_format_map.end()) - { - return it->second; - } - return gst_format; -} - -std::string translate_audio_format(const std::string& gst_format) -{ - static const std::unordered_map audio_format_map = {{"S24BE", "L24"}, {"S16LE", "L16"}}; - - auto it = audio_format_map.find(gst_format); - if(it != audio_format_map.end()) - { - return it->second; - } - return gst_format; -} - -expected load_configuration_from_file(std::string_view config_file) -{ - std::ifstream ifs(config_file.data()); - BST_ENFORCE(ifs.is_open(), "Failed opening file {}", config_file); - std::ostringstream buffer; - buffer << ifs.rdbuf(); - return parse_json(buffer.str()); -} - -std::string get_node_id(char* node_configuration_location) -{ - const auto configuration_result = load_configuration_from_file(node_configuration_location); - - const json& configuration = configuration_result.value(); - - auto node_result = find(configuration, "node"); - - const json& node = node_result.value(); - - auto node_id_result = find(node, "id"); - auto node_config_result = find(node, "configuration"); - - if(node_id_result.has_value() && node_config_result.has_value()) - { - const std::string node_id = node_id_result.value(); - const std::string node_configuration = node_config_result.value().dump(); - - return node_id; - } - - return ""; -} - -std::string get_node_config(std::string node_configuration_location) -{ - const auto configuration_result = load_configuration_from_file(node_configuration_location.c_str()); - - if(configuration_result.has_value()) - { - const json& configuration = configuration_result.value(); - - auto node_result = find(configuration, "node"); - - const json& node = node_result.value(); - - auto node_id_result = find(node, "id"); - auto node_config_result = find(node, "configuration"); - - if(node_id_result.has_value() && node_config_result.has_value()) - { - const std::string node_id = node_id_result.value(); - const std::string node_configuration = node_config_result.value().dump(); - - return node_configuration; - } - } - return ""; -} - -std::string get_device_id(char* device_configuration_location) -{ - const auto configuration_result = load_configuration_from_file(device_configuration_location); - - const json& configuration = configuration_result.value(); - - auto device_result = find(configuration, "device"); - - const json& device = device_result.value(); - - auto device_id_result = find(device, "id"); - - const std::string device_id = device_id_result.value(); - const std::string device_config = device.dump(); - - return device_id; -} - -std::string get_device_config(char* device_configuration_location) -{ - const auto configuration_result = load_configuration_from_file(device_configuration_location); - - const json& configuration = configuration_result.value(); - - auto device_result = find(configuration, "device"); - - const json& device = device_result.value(); - - auto device_id_result = find(device, "id"); - - const std::string device_id = device_id_result.value(); - const std::string device_config = device.dump(); - - return device_config; -} - -std::string get_sender_config(char* sender_configuration_location) -{ - // FIX ME: Gonna be hardcoded for now - const auto sender_config = load_configuration_from_file(sender_configuration_location); - - return sender_config.value().dump(); -} diff --git a/cpp/libs/gst_nmos_audio_receiver_plugin/utils.h b/cpp/libs/gst_nmos_audio_receiver_plugin/utils.h deleted file mode 100644 index b64513b..0000000 --- a/cpp/libs/gst_nmos_audio_receiver_plugin/utils.h +++ /dev/null @@ -1,116 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -typedef struct node_fields_t -{ - std::string id; - std::string configuration_location; -} node_fields_t; - -typedef struct device_fields_t -{ - std::string id; - std::string label; - std::string description; -} device_fields_t; - -typedef struct config_fields_t -{ - node_fields_t node; - device_fields_t device; - std::string id; - std::string label; - std::string description; - std::string address; - gint port; - std::string interface_name; -} config_fields_t; - -template class GstElementHandle -{ - public: - static std::variant create_element(const char* factory_name, - const char* element_name = nullptr) - { - T* elem = reinterpret_cast(gst_element_factory_make(factory_name, element_name)); - if(!elem) - { - return nullptr; - } - return GstElementHandle(elem); - } - - static std::variant create_bin(const char* bin_name) - { - T* bin = reinterpret_cast(gst_bin_new(bin_name)); - if(!bin) - { - return nullptr; - } - return GstElementHandle(bin); - } - - GstElementHandle(const GstElementHandle&) = delete; - GstElementHandle& operator=(const GstElementHandle&) = delete; - - // Move constructor - GstElementHandle(GstElementHandle&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; } - - // Move assignment - GstElementHandle& operator=(GstElementHandle&& other) noexcept - { - if(this != &other) - { - if(handle_ != nullptr) - { - gst_object_unref(handle_); - } - handle_ = other.handle_; - other.handle_ = nullptr; - } - return *this; - } - - ~GstElementHandle() - { - if(handle_) - { - gst_object_unref(handle_); - } - } - - void reset(T* new_ptr = nullptr) { handle_ = new_ptr; } - - T* get() const { return handle_; } - - explicit operator bool() const { return (handle_ != nullptr); } - - private: - explicit GstElementHandle(T* handle) : handle_(handle) {} - - T* handle_ = nullptr; -}; - -void create_default_config_fields(config_fields_t* config); - -nlohmann::json create_node_config(config_fields_t& config); -nlohmann::json create_device_config(config_fields_t& config); -nlohmann::json create_receiver_config(config_fields_t& config); - -std::string translate_video_format(const std::string& gst_format); - -std::string translate_audio_format(const std::string& gst_format); - -std::string get_node_id(char* node_configuration_location); - -std::string get_node_config(std::string node_configuration_location); - -std::string get_device_id(char* device_configuration_location); - -std::string get_device_config(char* device_configuration_location); - -std::string get_sender_config(char* sender_configuration_location); diff --git a/cpp/libs/gst_nmos_plugins/CMakeLists.txt b/cpp/libs/gst_nmos_plugins/CMakeLists.txt new file mode 100644 index 0000000..2c3304c --- /dev/null +++ b/cpp/libs/gst_nmos_plugins/CMakeLists.txt @@ -0,0 +1,99 @@ +cmake_minimum_required(VERSION 3.16) +project(gst_nmos_plugins LANGUAGES CXX) + +find_package(nlohmann_json REQUIRED) +find_package(PkgConfig REQUIRED) + +# Locate GLib package +pkg_check_modules(GLIB REQUIRED glib-2.0) + +# Locate GStreamer packages +pkg_search_module(GSTREAMER REQUIRED gstreamer-1.0>=1.4) +pkg_search_module(GSTREAMER_APP REQUIRED gstreamer-app-1.0>=1.4) +pkg_search_module(GSTREAMER_AUDIO REQUIRED gstreamer-audio-1.0>=1.4) +pkg_search_module(GSTREAMER_VIDEO REQUIRED gstreamer-video-1.0>=1.4) + +# Include the parent directory of gst_nmos_plugins +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +# Utils library (shared across plugins) +add_library(utils STATIC src/utils.cpp) + +# Enable -fPIC for utils +set_target_properties(utils PROPERTIES POSITION_INDEPENDENT_CODE ON) + +target_include_directories(utils + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/.. + PRIVATE ${GLIB_INCLUDE_DIRS} + PRIVATE ${GSTREAMER_INCLUDE_DIRS} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../bisect + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../ossrf +) +target_link_libraries(utils + PUBLIC nlohmann_json::nlohmann_json + PRIVATE ${GLIB_LIBRARIES} + PRIVATE ${GSTREAMER_LIBRARIES} + PRIVATE ${GSTREAMER_APP_LIBRARIES} + PRIVATE ${GSTREAMER_AUDIO_LIBRARIES} + PRIVATE ${GSTREAMER_VIDEO_LIBRARIES} + PRIVATE bisect::project_warnings + PRIVATE bisect::expected + PRIVATE bisect::bisect_nmoscpp + PRIVATE bisect::bisect_json + PRIVATE ossrf::ossrf_nmos_api +) + +# Function to create a plugin target +function(create_plugin plugin_name plugin_sources output_name) + add_library(${plugin_name} MODULE ${plugin_sources}) + target_include_directories(${plugin_name} + PRIVATE ${GSTREAMER_INCLUDE_DIRS} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/.. + PUBLIC $ + ) + target_link_libraries(${plugin_name} + PRIVATE + ${GSTREAMER_LIBRARIES} + ${GSTREAMER_APP_LIBRARIES} + ${GSTREAMER_AUDIO_LIBRARIES} + ${GSTREAMER_VIDEO_LIBRARIES} + utils + PUBLIC + bisect::project_warnings + bisect::expected + bisect::bisect_nmoscpp + bisect::bisect_json + nlohmann_json::nlohmann_json + ossrf::ossrf_nmos_api + ${GLIB_LIBRARIES} + ) + set_target_properties(${plugin_name} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins + OUTPUT_NAME ${output_name} + ) + add_library(ossrf::${plugin_name} ALIAS ${plugin_name}) + install(TARGETS ${plugin_name} + LIBRARY DESTINATION ~/.local/lib/gstreamer-1.0 + ) +endfunction() + +# Plugin: Video Receiver +create_plugin( + gst_nmos_video_receiver_plugin + src/gst_nmos_video_receiver_plugin.cpp + "gstnmosvideoreceiver" +) + +# Plugin: Audio Receiver +create_plugin( + gst_nmos_audio_receiver_plugin + src/gst_nmos_audio_receiver_plugin.cpp + "gstnmosaudioreceiver" +) + +# Plugin: Sender +create_plugin( + gst_nmos_sender_plugin + src/gst_nmos_sender_plugin.cpp + "gstnmossender" +) diff --git a/cpp/libs/gst_nmos_video_receiver_plugin/utils.h b/cpp/libs/gst_nmos_plugins/include/element_class.h similarity index 55% rename from cpp/libs/gst_nmos_video_receiver_plugin/utils.h rename to cpp/libs/gst_nmos_plugins/include/element_class.h index f3cb2a1..c7576a1 100644 --- a/cpp/libs/gst_nmos_video_receiver_plugin/utils.h +++ b/cpp/libs/gst_nmos_plugins/include/element_class.h @@ -5,31 +5,6 @@ #include #include -typedef struct node_fields_t -{ - std::string id; - std::string configuration_location; -} node_fields_t; - -typedef struct device_fields_t -{ - std::string id; - std::string label; - std::string description; -} device_fields_t; - -typedef struct config_fields_t -{ - node_fields_t node; - device_fields_t device; - std::string id; - std::string label; - std::string description; - std::string address; - gint port; - std::string interface_name; -} config_fields_t; - template class GstElementHandle { public: @@ -37,7 +12,7 @@ template class GstElementHandle const char* element_name = nullptr) { T* elem = reinterpret_cast(gst_element_factory_make(factory_name, element_name)); - if(!elem) + if(elem == nullptr) { return nullptr; } @@ -47,7 +22,7 @@ template class GstElementHandle static std::variant create_bin(const char* bin_name) { T* bin = reinterpret_cast(gst_bin_new(bin_name)); - if(!bin) + if(bin == nullptr) { return nullptr; } @@ -57,10 +32,8 @@ template class GstElementHandle GstElementHandle(const GstElementHandle&) = delete; GstElementHandle& operator=(const GstElementHandle&) = delete; - // Move constructor GstElementHandle(GstElementHandle&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; } - // Move assignment GstElementHandle& operator=(GstElementHandle&& other) noexcept { if(this != &other) @@ -83,37 +56,16 @@ template class GstElementHandle } } - void reset(T* new_ptr = nullptr) + /// Destruction of this object will no longer unref the owned element. + void forget(T* new_ptr = nullptr) { handle_ = new_ptr; } T* get() const { return handle_; } - explicit operator bool() const { return (handle_ != nullptr); } - private: explicit GstElementHandle(T* handle) : handle_(handle) {} T* handle_ = nullptr; }; - -void create_default_config_fields(config_fields_t* config); - -nlohmann::json create_node_config(config_fields_t& config); -nlohmann::json create_device_config(config_fields_t& config); -nlohmann::json create_receiver_config(config_fields_t& config); - -std::string translate_video_format(const std::string& gst_format); - -std::string translate_audio_format(const std::string& gst_format); - -std::string get_node_id(char* node_configuration_location); - -std::string get_node_config(std::string node_configuration_location); - -std::string get_device_id(char* device_configuration_location); - -std::string get_device_config(char* device_configuration_location); - -std::string get_sender_config(char* sender_configuration_location); diff --git a/cpp/libs/gst_nmos_plugins/include/nmos_configuration.h b/cpp/libs/gst_nmos_plugins/include/nmos_configuration.h new file mode 100644 index 0000000..d57e81a --- /dev/null +++ b/cpp/libs/gst_nmos_plugins/include/nmos_configuration.h @@ -0,0 +1,57 @@ +#pragma once +#include +#include + +struct node_fields_t +{ + std::string id; + std::string configuration_location; +}; + +struct device_fields_t +{ + std::string id; + std::string label; + std::string description; +}; + +struct video_media_fields_t +{ + gint width; + gint height; + gint frame_rate_num; + gint frame_rate_den; + std::string sampling; + std::string structure; +}; + +struct audio_media_fields_t +{ + std::string format; + gint number_of_channels; + gint sampling_rate; + gint packet_time; +}; + +struct network_fields_t +{ + std::string source_address; + std::string interface_name; + std::string destination_address; + gint destination_port; +}; + +struct config_fields_t +{ + node_fields_t node; + device_fields_t device; + gboolean is_audio; + video_media_fields_t video_media_fields; + audio_media_fields_t audio_sender_fields; + std::string id; + std::string label; + std::string description; + network_fields_t network; + std::string interface_name; + std::string address; +}; diff --git a/cpp/libs/gst_nmos_audio_receiver_plugin/gst_nmos_audio_receiver_plugin.cpp b/cpp/libs/gst_nmos_plugins/src/gst_nmos_audio_receiver_plugin.cpp similarity index 86% rename from cpp/libs/gst_nmos_audio_receiver_plugin/gst_nmos_audio_receiver_plugin.cpp rename to cpp/libs/gst_nmos_plugins/src/gst_nmos_audio_receiver_plugin.cpp index d821fbc..4dee7af 100644 --- a/cpp/libs/gst_nmos_audio_receiver_plugin/gst_nmos_audio_receiver_plugin.cpp +++ b/cpp/libs/gst_nmos_plugins/src/gst_nmos_audio_receiver_plugin.cpp @@ -27,6 +27,8 @@ #include "bisect/nmoscpp/configuration.h" #include "utils.h" #include "ossrf/nmos/api/nmos_client.h" +#include "../include/element_class.h" +#include "../include/nmos_configuration.h" #include #include @@ -150,37 +152,38 @@ static GstPadProbeReturn block_pad_probe_cb(GstPad* pad, GstPadProbeInfo* info, void remove_old_bin(GstNmosaudioreceiver* self) { - if(self->element_pad != nullptr) + if(self->element_pad == nullptr) { - gulong block_id = - gst_pad_add_probe(self->element_pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, block_pad_probe_cb, NULL, NULL); + return; + } + gulong block_id = + gst_pad_add_probe(self->element_pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, block_pad_probe_cb, NULL, NULL); - gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_start()); - gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_stop(FALSE)); + gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_start()); + gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_stop(FALSE)); - self->clock = gst_element_get_clock(GST_ELEMENT(self)); + self->clock = gst_element_get_clock(GST_ELEMENT(self)); - gst_element_set_state(self->bin.get(), GST_STATE_NULL); + gst_element_set_state(self->bin.get(), GST_STATE_NULL); - gst_bin_remove(GST_BIN(self), self->udp_src.get()); - gst_bin_remove(GST_BIN(self), self->rtp_jitter_buffer.get()); - gst_bin_remove(GST_BIN(self), self->rtp_audio_depay_16.get()); - gst_bin_remove(GST_BIN(self), self->rtp_audio_depay_24.get()); - gst_bin_remove(GST_BIN(self), self->queue.get()); + gst_bin_remove(GST_BIN(self), self->udp_src.get()); + gst_bin_remove(GST_BIN(self), self->rtp_jitter_buffer.get()); + gst_bin_remove(GST_BIN(self), self->rtp_audio_depay_16.get()); + gst_bin_remove(GST_BIN(self), self->rtp_audio_depay_24.get()); + gst_bin_remove(GST_BIN(self), self->queue.get()); - gst_bin_remove(GST_BIN(self), self->bin.get()); + gst_bin_remove(GST_BIN(self), self->bin.get()); - self->udp_src.reset(); - self->queue.reset(); - self->rtp_jitter_buffer.reset(); - self->rtp_audio_depay_16.reset(); - self->rtp_audio_depay_24.reset(); - self->bin.reset(); + self->udp_src.forget(); + self->queue.forget(); + self->rtp_jitter_buffer.forget(); + self->rtp_audio_depay_16.forget(); + self->rtp_audio_depay_24.forget(); + self->bin.forget(); - if(block_id != 0) - { - gst_pad_remove_probe(self->element_pad, block_id); - } + if(block_id != 0) + { + gst_pad_remove_probe(self->element_pad, block_id); } } @@ -211,9 +214,7 @@ void construct_pipeline(GstNmosaudioreceiver* self) return; } - GstElementHandle bin = std::move(std::get>(maybeBin)); - - self->bin = std::move(bin); + self->bin = std::move(std::get>(maybeBin)); auto maybeUdpsrc = GstElementHandle::create_element("udpsrc", nullptr); auto maybeQueue = GstElementHandle::create_element("queue", nullptr); @@ -229,17 +230,11 @@ void construct_pipeline(GstNmosaudioreceiver* self) return; } - GstElementHandle udpsrc = std::move(std::get>(maybeUdpsrc)); - GstElementHandle queue = std::move(std::get>(maybeQueue)); - GstElementHandle jitter = std::move(std::get>(maybeJitter)); - GstElementHandle depay16 = std::move(std::get>(maybeDepay16)); - GstElementHandle depay24 = std::move(std::get>(maybeDepay24)); - - self->udp_src = std::move(udpsrc); - self->queue = std::move(queue); - self->rtp_jitter_buffer = std::move(jitter); - self->rtp_audio_depay_16 = std::move(depay16); - self->rtp_audio_depay_24 = std::move(depay24); + self->udp_src = std::move(std::get>(maybeUdpsrc)); + self->queue = std::move(std::get>(maybeQueue)); + self->rtp_jitter_buffer = std::move(std::get>(maybeJitter)); + self->rtp_audio_depay_16 = std::move(std::get>(maybeDepay16)); + self->rtp_audio_depay_24 = std::move(std::get>(maybeDepay24)); g_object_set( G_OBJECT(self->udp_src.get()), "address", self->sdp_settings.primary.destination_ip.value().c_str(), "port", @@ -333,7 +328,7 @@ void construct_pipeline(GstNmosaudioreceiver* self) } catch(std::bad_variant_access const& ex) { - g_print("Invalid format sent for current receiver.\n"); + fmt::print("Invalid format sent for current receiver.\n"); } } @@ -350,12 +345,7 @@ void create_nmos(GstNmosaudioreceiver* self) receiver_config_json = create_receiver_config(self->config); - // g_print("Node Configuration: %s\n", node_config_json.dump().c_str()); - // g_print("Device Configuration: %s\n", device_config_json.dump().c_str()); - // g_print("Receiver Configuration: %s\n", receiver_config_json.dump().c_str()); - auto receiver_callback = [self](const std::optional& sdp, bool master_enabled) { - // Debug print fmt::print("Receiver Activation Callback: SDP={}, Master Enabled={}\n", sdp.has_value() ? sdp.value() : "None", master_enabled); @@ -380,17 +370,15 @@ void create_nmos(GstNmosaudioreceiver* self) }; auto result = ossrf::nmos_client_t::create(self->config.node.id, node_config_json.dump()); - if(result.has_value()) - { - self->client = std::move(result.value()); - self->client->add_device(device_config_json.dump()); - self->client->add_receiver(self->config.device.id, receiver_config_json.dump(), receiver_callback); - GST_INFO_OBJECT(self, "NMOS client initialized successfully."); - } - else + if(!result.has_value()) { GST_ERROR_OBJECT(self, "Failed to initialize NMOS client."); + return; } + self->client = std::move(result.value()); + self->client->add_device(device_config_json.dump()); + self->client->add_receiver(self->config.device.id, receiver_config_json.dump(), receiver_callback); + GST_INFO_OBJECT(self, "NMOS client initialized successfully."); } static GstStateChangeReturn gst_nmosaudioreceiver_change_state(GstElement* element, GstStateChange transition) @@ -469,7 +457,7 @@ static void gst_nmosaudioreceiver_init(GstNmosaudioreceiver* self) gst_object_unref(src_tmpl); gst_element_add_pad(GST_ELEMENT(self), ghost_src); - create_default_config_fields(&self->config); + create_default_config_fields_audio_receiver(&self->config); } static gboolean plugin_init(GstPlugin* plugin) diff --git a/cpp/libs/gst_nmos_sender_plugin/gst_nmos_sender_plugin.cpp b/cpp/libs/gst_nmos_plugins/src/gst_nmos_sender_plugin.cpp similarity index 88% rename from cpp/libs/gst_nmos_sender_plugin/gst_nmos_sender_plugin.cpp rename to cpp/libs/gst_nmos_plugins/src/gst_nmos_sender_plugin.cpp index 956a2d1..ee453d9 100644 --- a/cpp/libs/gst_nmos_sender_plugin/gst_nmos_sender_plugin.cpp +++ b/cpp/libs/gst_nmos_plugins/src/gst_nmos_sender_plugin.cpp @@ -23,6 +23,8 @@ #include "bisect/json.h" #include "utils.h" #include "ossrf/nmos/api/nmos_client.h" +#include "../include/element_class.h" +#include "../include/nmos_configuration.h" #include #include @@ -100,11 +102,11 @@ static void gst_nmossender_set_property(GObject* object, guint property_id, cons case PropertyId::DeviceDescription: self->config.device.description = g_value_dup_string(value); break; - case PropertyId::SenderId: self->config.sender_id = g_value_dup_string(value); break; + case PropertyId::SenderId: self->config.id = g_value_dup_string(value); break; - case PropertyId::SenderLabel: self->config.sender_label = g_value_dup_string(value); break; + case PropertyId::SenderLabel: self->config.label = g_value_dup_string(value); break; - case PropertyId::SenderDescription: self->config.sender_description = g_value_dup_string(value); break; + case PropertyId::SenderDescription: self->config.description = g_value_dup_string(value); break; case PropertyId::SourceAddress: self->config.network.source_address = g_value_dup_string(value); break; @@ -141,9 +143,9 @@ static void gst_nmossender_get_property(GObject* object, guint property_id, GVal case PropertyId::DeviceId: g_value_set_string(value, self->config.device.id.c_str()); break; case PropertyId::DeviceLabel: g_value_set_string(value, self->config.device.label.c_str()); break; case PropertyId::DeviceDescription: g_value_set_string(value, self->config.device.description.c_str()); break; - case PropertyId::SenderId: g_value_set_string(value, self->config.sender_id.c_str()); break; - case PropertyId::SenderLabel: g_value_set_string(value, self->config.sender_label.c_str()); break; - case PropertyId::SenderDescription: g_value_set_string(value, self->config.sender_description.c_str()); break; + case PropertyId::SenderId: g_value_set_string(value, self->config.id.c_str()); break; + case PropertyId::SenderLabel: g_value_set_string(value, self->config.label.c_str()); break; + case PropertyId::SenderDescription: g_value_set_string(value, self->config.description.c_str()); break; case PropertyId::SourceAddress: g_value_set_string(value, self->config.network.source_address.c_str()); break; case PropertyId::InterfaceName: g_value_set_string(value, self->config.network.interface_name.c_str()); break; case PropertyId::DestinationAddress: @@ -169,8 +171,6 @@ static gboolean gst_nmossender_sink_event(GstPad* pad, GstObject* parent, GstEve if(caps) { gchar* caps_str = gst_caps_to_string(caps); - // DEBUG - // g_print("\n\nReceived CAPS from upstream: %s\n", caps_str); const GstStructure* structure = gst_caps_get_structure(caps, 0); std::string media_type = gst_structure_get_name(structure); @@ -197,10 +197,6 @@ static gboolean gst_nmossender_sink_event(GstPad* pad, GstObject* parent, GstEve self->config.audio_sender_fields.format = format; } } - // DEBUG - // g_print("Audio caps detected\n"); - // GST_INFO_OBJECT(self, "Audio caps detected"); - // if(self->config.audio_sender_fields.format == "S24BE") { if(gst_element_link(self->queue.get(), self->audio_payloader_24.get()) == false) @@ -227,17 +223,9 @@ static gboolean gst_nmossender_sink_event(GstPad* pad, GstObject* parent, GstEve return false; } } - // DEBUG - // g_print("Rate: %d, Channels: %d, Format: %s\n", self->config.audio_sender_fields.sampling_rate, - // self->config.audio_sender_fields.number_of_channels, - // self->config.audio_sender_fields.format); } else if(media_type == "video/x-raw") { - // DEBUG - // g_print("Video caps detected\n"); - // GST_INFO_OBJECT(self, "Video caps detected"); - // if(gst_element_link(self->queue.get(), self->video_payloader.get()) == false) { GST_ERROR_OBJECT(self, "Failed to link queue to video_payloader"); @@ -267,10 +255,6 @@ static gboolean gst_nmossender_sink_event(GstPad* pad, GstObject* parent, GstEve self->config.video_media_fields.sampling = translate_video_format(format); } } - - // DEBUG - // g_print("Width: %d, Height: %d, Format: %s\n", self->config.video_media_fields.width, - // self->config.video_media_fields.height, self->config.video_media_fields.sampling); } else { @@ -325,11 +309,6 @@ static GstStateChangeReturn gst_nmossender_change_state(GstElement* element, Gst sender_config_json = create_video_sender_config(self->config); } - // DEBUG - // g_print("Node Configuration: %s\n", node_config_json.dump().c_str()); - // g_print("Device Configuration: %s\n", device_config_json.dump().c_str()); - // g_print("Sender Configuration: %s\n", sender_config_json.dump().c_str()); - auto result = ossrf::nmos_client_t::create(self->config.node.id, node_config_json.dump()); if(result.has_value() == false) { @@ -355,7 +334,6 @@ static GstStateChangeReturn gst_nmossender_change_state(GstElement* element, Gst default: break; } - // This is needed to work ¯\_(ツ)_/¯ ret = GST_ELEMENT_CLASS(gst_nmossender_parent_class)->change_state(element, transition); return ret; @@ -428,22 +406,16 @@ static void gst_nmossender_init(GstNmossender* self) return; } - GstElementHandle queue = std::move(std::get>(maybeQueue)); - GstElementHandle rtpvrawpay = std::move(std::get>(maybeVideoPay)); - GstElementHandle rtpL16pay = std::move(std::get>(maybeAudioPay16)); - GstElementHandle rtpL24pay = std::move(std::get>(maybeAudioPay24)); - GstElementHandle udpsink = std::move(std::get>(maybeUdpSink)); - - self->queue = std::move(queue); - self->video_payloader = std::move(rtpvrawpay); - self->audio_payloader_16 = std::move(rtpL16pay); - self->audio_payloader_24 = std::move(rtpL24pay); - self->udpsink = std::move(udpsink); + self->queue = std::move(std::get>(maybeQueue)); + self->video_payloader = std::move(std::get>(maybeVideoPay)); + self->audio_payloader_16 = std::move(std::get>(maybeAudioPay16)); + self->audio_payloader_24 = std::move(std::get>(maybeAudioPay24)); + self->udpsink = std::move(std::get>(maybeUdpSink)); // set properties g_object_set(G_OBJECT(self->queue.get()), "max-size-buffers", 1, nullptr); g_object_set(G_OBJECT(self->udpsink.get()), "host", "127.0.0.1", "port", 9999, nullptr); - create_default_config_fields(&self->config); + create_default_config_fields_sender(&self->config); gst_bin_add_many(GST_BIN(self), self->queue.get(), self->video_payloader.get(), self->audio_payloader_24.get(), self->audio_payloader_16.get(), self->udpsink.get(), nullptr); diff --git a/cpp/libs/gst_nmos_video_receiver_plugin/gst_nmos_video_receiver_plugin.cpp b/cpp/libs/gst_nmos_plugins/src/gst_nmos_video_receiver_plugin.cpp similarity index 83% rename from cpp/libs/gst_nmos_video_receiver_plugin/gst_nmos_video_receiver_plugin.cpp rename to cpp/libs/gst_nmos_plugins/src/gst_nmos_video_receiver_plugin.cpp index 0f77142..8b52ef2 100644 --- a/cpp/libs/gst_nmos_video_receiver_plugin/gst_nmos_video_receiver_plugin.cpp +++ b/cpp/libs/gst_nmos_plugins/src/gst_nmos_video_receiver_plugin.cpp @@ -27,6 +27,8 @@ #include "bisect/nmoscpp/configuration.h" #include "utils.h" #include "ossrf/nmos/api/nmos_client.h" +#include "gst_nmos_plugins/include/element_class.h" +#include "gst_nmos_plugins/include/nmos_configuration.h" #include #include @@ -152,35 +154,36 @@ static GstPadProbeReturn block_pad_probe_cb(GstPad* pad, GstPadProbeInfo* info, void remove_old_bin(GstNmosvideoreceiver* self) { - if(self->element_pad != nullptr) + if(self->element_pad == nullptr) { - gulong block_id = - gst_pad_add_probe(self->element_pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, block_pad_probe_cb, NULL, NULL); + return; + } + gulong block_id = + gst_pad_add_probe(self->element_pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, block_pad_probe_cb, NULL, NULL); - gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_start()); - gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_stop(FALSE)); + gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_start()); + gst_element_send_event(GST_ELEMENT(self), gst_event_new_flush_stop(FALSE)); - self->clock = gst_element_get_clock(GST_ELEMENT(self)); + self->clock = gst_element_get_clock(GST_ELEMENT(self)); - gst_element_set_state(self->bin.get(), GST_STATE_NULL); + gst_element_set_state(self->bin.get(), GST_STATE_NULL); - gst_bin_remove(GST_BIN(self), self->udp_src.get()); - gst_bin_remove(GST_BIN(self), self->rtp_jitter_buffer.get()); - gst_bin_remove(GST_BIN(self), self->rtp_video_depay.get()); - gst_bin_remove(GST_BIN(self), self->queue.get()); + gst_bin_remove(GST_BIN(self), self->udp_src.get()); + gst_bin_remove(GST_BIN(self), self->rtp_jitter_buffer.get()); + gst_bin_remove(GST_BIN(self), self->rtp_video_depay.get()); + gst_bin_remove(GST_BIN(self), self->queue.get()); - gst_bin_remove(GST_BIN(self), self->bin.get()); + gst_bin_remove(GST_BIN(self), self->bin.get()); - self->udp_src.reset(); - self->queue.reset(); - self->rtp_jitter_buffer.reset(); - self->rtp_video_depay.reset(); - self->bin.reset(); + self->udp_src.forget(); + self->queue.forget(); + self->rtp_jitter_buffer.forget(); + self->rtp_video_depay.forget(); + self->bin.forget(); - if(block_id != 0) - { - gst_pad_remove_probe(self->element_pad, block_id); - } + if(block_id != 0) + { + gst_pad_remove_probe(self->element_pad, block_id); } } @@ -191,22 +194,22 @@ void construct_pipeline(GstNmosvideoreceiver* self) { bisect::nmoscpp::video_sender_info_t video_info = std::get(format); auto& fr = video_info.exact_framerate; - g_print("\n===== SDP Settings =====\n" - "Video (from variant):\n" - " - height: %d\n" - " - width: %d\n" - " - framerate: %ld/%ld\n" - " - chroma_sub_sampling: %s\n" - " - structure (interlace mode): %s\n" - " - depth: %d\n" - "Primary leg:\n" - " - destination_ip: %s\n" - " - destination_port: %d\n" - "\n", - video_info.height, video_info.width, fr.numerator(), fr.denominator(), - video_info.chroma_sub_sampling.c_str(), video_info.structure.name.c_str(), video_info.depth, - self->sdp_settings.primary.destination_ip.value().c_str(), - self->sdp_settings.primary.destination_port.value()); + fmt::print("\n===== SDP Settings =====\n" + "Video (from variant):\n" + " - height: %d\n" + " - width: %d\n" + " - framerate: %ld/%ld\n" + " - chroma_sub_sampling: %s\n" + " - structure (interlace mode): %s\n" + " - depth: %d\n" + "Primary leg:\n" + " - destination_ip: %s\n" + " - destination_port: %d\n" + "\n", + video_info.height, video_info.width, fr.numerator(), fr.denominator(), + video_info.chroma_sub_sampling.c_str(), video_info.structure.name.c_str(), video_info.depth, + self->sdp_settings.primary.destination_ip.value().c_str(), + self->sdp_settings.primary.destination_port.value()); auto maybeBin = GstElementHandle::create_bin("dynamic-bin"); if(std::holds_alternative(maybeBin)) @@ -215,9 +218,7 @@ void construct_pipeline(GstNmosvideoreceiver* self) return; } - GstElementHandle bin = std::move(std::get>(maybeBin)); - - self->bin = std::move(bin); + self->bin = std::move(std::get>(maybeBin)); auto maybeUdpsrc = GstElementHandle::create_element("udpsrc", nullptr); auto maybeQueue = GstElementHandle::create_element("queue", nullptr); @@ -231,22 +232,15 @@ void construct_pipeline(GstNmosvideoreceiver* self) return; } - GstElementHandle udpsrc = std::move(std::get>(maybeUdpsrc)); - GstElementHandle queue = std::move(std::get>(maybeQueue)); - GstElementHandle jitter = std::move(std::get>(maybeJitter)); - GstElementHandle depay = std::move(std::get>(maybeDepay)); - - self->udp_src = std::move(udpsrc); - self->queue = std::move(queue); - self->rtp_jitter_buffer = std::move(jitter); - self->rtp_video_depay = std::move(depay); + self->udp_src = std::move(std::get>(maybeUdpsrc)); + self->queue = std::move(std::get>(maybeQueue)); + self->rtp_jitter_buffer = std::move(std::get>(maybeJitter)); + self->rtp_video_depay = std::move(std::get>(maybeDepay)); g_object_set(G_OBJECT(self->udp_src.get()), "address", self->sdp_settings.primary.destination_ip.value().c_str(), "port", self->sdp_settings.primary.destination_port.value(), "buffer-size", 67108864, nullptr); - // g_signal_connect(self->identity, "handoff", G_CALLBACK(identity_handoff_callback), NULL); - GstCaps* caps = gst_caps_new_simple( "application/x-rtp", "media", G_TYPE_STRING, "video", "clock-rate", G_TYPE_INT, 90000, "encoding-name", G_TYPE_STRING, "RAW", "payload", G_TYPE_INT, 96, "width", G_TYPE_STRING, @@ -316,7 +310,7 @@ void construct_pipeline(GstNmosvideoreceiver* self) } catch(std::bad_variant_access const& ex) { - g_print("Invalid format sent for current receiver.\n"); + fmt::print("Invalid format sent for current receiver.\n"); } } @@ -333,12 +327,7 @@ void create_nmos(GstNmosvideoreceiver* self) receiver_config_json = create_receiver_config(self->config); - // g_print("Node Configuration: %s\n", node_config_json.dump().c_str()); - // g_print("Device Configuration: %s\n", device_config_json.dump().c_str()); - // g_print("Receiver Configuration: %s\n", receiver_config_json.dump().c_str()); - auto receiver_callback = [self](const std::optional& sdp, bool master_enabled) { - // Debug print fmt::print("Receiver Activation Callback: SDP={}, Master Enabled={}\n", sdp.has_value() ? sdp.value() : "None", master_enabled); @@ -365,17 +354,15 @@ void create_nmos(GstNmosvideoreceiver* self) }; auto result = ossrf::nmos_client_t::create(self->config.node.id, node_config_json.dump()); - if(result.has_value()) - { - self->client = std::move(result.value()); - self->client->add_device(device_config_json.dump()); - self->client->add_receiver(self->config.device.id, receiver_config_json.dump(), receiver_callback); - GST_INFO_OBJECT(self, "NMOS client initialized successfully."); - } - else + if(!result.has_value()) { GST_ERROR_OBJECT(self, "Failed to initialize NMOS client."); + return; } + self->client = std::move(result.value()); + self->client->add_device(device_config_json.dump()); + self->client->add_receiver(self->config.device.id, receiver_config_json.dump(), receiver_callback); + GST_INFO_OBJECT(self, "NMOS client initialized successfully."); } static GstStateChangeReturn gst_nmosvideoreceiver_change_state(GstElement* element, GstStateChange transition) @@ -454,7 +441,7 @@ static void gst_nmosvideoreceiver_init(GstNmosvideoreceiver* self) gst_object_unref(src_tmpl); gst_element_add_pad(GST_ELEMENT(self), ghost_src); - create_default_config_fields(&self->config); + create_default_config_fields_video_receiver(&self->config); } static gboolean plugin_init(GstPlugin* plugin) diff --git a/cpp/libs/gst_nmos_sender_plugin/utils.cpp b/cpp/libs/gst_nmos_plugins/src/utils.cpp similarity index 70% rename from cpp/libs/gst_nmos_sender_plugin/utils.cpp rename to cpp/libs/gst_nmos_plugins/src/utils.cpp index 430769a..d8a9b0b 100644 --- a/cpp/libs/gst_nmos_sender_plugin/utils.cpp +++ b/cpp/libs/gst_nmos_plugins/src/utils.cpp @@ -4,12 +4,15 @@ #include "bisect/expected/macros.h" #include "bisect/expected.h" #include "bisect/json.h" +#include "gst_nmos_plugins/include/element_class.h" +#include "gst_nmos_plugins/include/nmos_configuration.h" #include using namespace bisect; using json = nlohmann::json; -void create_default_config_fields(config_fields_t* config) + +void create_default_config_fields_sender(config_fields_t* config) { if(!config) { @@ -48,19 +51,71 @@ void create_default_config_fields(config_fields_t* config) config->network.destination_port = 9999; // Initialize config_fields_t - config->sender_id = "1c920570-e0b4-4637-b02c-26c9d4275c71"; - config->sender_label = "BISECT OSSRF Media Sender"; - config->sender_description = "BISECT OSSRF Media Sender"; + config->id = "1c920570-e0b4-4637-b02c-26c9d4275c71"; + config->label = "BISECT OSSRF Media Sender"; + config->description = "BISECT OSSRF Media Sender"; } -json create_node_config(config_fields_t& config) +void create_default_config_fields_video_receiver(config_fields_t* config) { - const std::string node_config_str = get_node_config(config.node.configuration_location.data()); + if(!config) + { + g_critical("Config pointer is NULL"); + return; + } - if(node_config_str != "") + // Initialize node_fields_t + config->node.id = "d49c85db-1c33-4f21-b160-58edd2af1810"; + config->node.configuration_location = + "/home/nmos/repos/nmos-sender-receiver-framework/cpp/demos/config/nmos_plugin_node_config_receiver.json"; + + // Initialize device_fields_t + config->device.id = "1ad20d7c-8c58-4c84-8f14-3cf3e3af164c"; + config->device.label = "OSSRF Device2"; + config->device.description = "OSSRF Device2"; + + // Initialize config_fields_t + config->id = "db9f46cf-2414-4e25-b6c6-2078159857f9"; + config->label = "BISECT OSSRF Video Receiver"; + config->description = "BISECT OSSRF Video Receiver"; + config->interface_name = "wlp1s0"; + config->address = "192.168.1.36"; + config->is_audio = false; +} + +void create_default_config_fields_audio_receiver(config_fields_t* config) +{ + if(!config) { - json node_config = json::parse(node_config_str); + g_critical("Config pointer is NULL"); + return; + } + + // Initialize node_fields_t + config->node.id = "d1073007-4099-452f-b8e8-837b8987d845"; + config->node.configuration_location = + "/home/nmos/repos/nmos-sender-receiver-framework/cpp/demos/config/nmos_plugin_node_config_receiver.json"; + + // Initialize device_fields_t + config->device.id = "093a1c56-4033-49f7-9e17-fbcb21a19383"; + config->device.label = "OSSRF Device2"; + config->device.description = "OSSRF Device2"; + + // Initialize config_fields_t + config->id = "af600517-a452-4c2c-8b60-2caa770d6435"; + config->label = "BISECT OSSRF Audio Receiver"; + config->description = "BISECT OSSRF Audio Receiver"; + config->interface_name = "wlp1s0"; + config->address = "192.168.1.36"; + config->is_audio = true; +} +json create_node_config(config_fields_t& config) +{ + const std::string node_config_str = get_node_config(config.node.configuration_location); + if(node_config_str != "") + { + json node_config = json::parse(node_config_str); node_config["id"] = config.node.id; return node_config; @@ -78,63 +133,25 @@ json create_device_config(config_fields_t& config) return device; } -json create_video_sender_config(config_fields_t& config) -{ - json sender = { - {"id", config.sender_id}, - {"label", config.sender_label}, - {"description", config.sender_description}, - {"network", - {{"primary", - {{"source_address", config.network.source_address}, - {"interface_name", config.network.interface_name}, - {"destination_address", config.network.destination_address}, - {"destination_port", config.network.destination_port}}}}}, - {"payload_type", 97}, - {"media_type", "video/raw"}, - {"media", - {{"width", config.video_media_fields.width}, - {"height", config.video_media_fields.height}, - {"frame_rate", - {{"num", config.video_media_fields.frame_rate_num}, {"den", config.video_media_fields.frame_rate_den}}}, - {"sampling", config.video_media_fields.sampling}, - {"structure", config.video_media_fields.structure}}}}; - return sender; -} - -json create_audio_sender_config(config_fields_t& config) +json create_receiver_config(config_fields_t& config) { - std::string media_type = "audio/L16"; - if(config.audio_sender_fields.format == "S24BE") - { - media_type = "audio/L24"; + std::string caps = "video/raw"; + if(config.is_audio == true){ + caps = "audio/L24"; } - else if(config.audio_sender_fields.format == "S16BE") - { - media_type = "audio/L16"; - } - json sender = {{"id", config.sender_id}, - {"label", config.sender_label}, - {"description", config.sender_description}, - {"network", - {{"primary", - {{"source_address", config.network.source_address}, - {"interface_name", config.network.interface_name}, - {"destination_address", config.network.destination_address}, - {"destination_port", config.network.destination_port}}}}}, - {"payload_type", 97}, - {"media_type", media_type}, - {"media", - {{"number_of_channels", config.audio_sender_fields.number_of_channels}, - {"sampling_rate", config.audio_sender_fields.sampling_rate}, - {"packet_time", config.audio_sender_fields.packet_time}}}}; + json sender = { + {"id", config.id}, + {"label", config.label}, + {"description", config.description}, + {"network", {{"primary", {{"interface_address", config.address}, {"interface_name", config.interface_name}}}}}, + {"capabilities", {caps}}}; return sender; } std::string translate_video_format(const std::string& gst_format) { - static const std::unordered_map video_format_map = { - {"UYVP", "YCbCr-4:2:2"}, {"RGB", "RGB-8:8:8"}, {"RGBA", "RGBA-8:8:8:8"}}; + static const std::unordered_map video_format_map = {{"UYVP", "YCbCr-4:2:2"}, + {"RGBA", "RGBA-8:8:8:8"}}; auto it = video_format_map.find(gst_format); if(it != video_format_map.end()) @@ -189,9 +206,9 @@ std::string get_node_id(char* node_configuration_location) return ""; } -std::string get_node_config(char* node_configuration_location) +std::string get_node_config(std::string node_configuration_location) { - const auto configuration_result = load_configuration_from_file(node_configuration_location); + const auto configuration_result = load_configuration_from_file(node_configuration_location.c_str()); if(configuration_result.has_value()) { @@ -258,3 +275,57 @@ std::string get_sender_config(char* sender_configuration_location) return sender_config.value().dump(); } + + +json create_video_sender_config(config_fields_t& config) +{ + json sender = { + {"id", config.id}, + {"label", config.label}, + {"description", config.description}, + {"network", + {{"primary", + {{"source_address", config.network.source_address}, + {"interface_name", config.network.interface_name}, + {"destination_address", config.network.destination_address}, + {"destination_port", config.network.destination_port}}}}}, + {"payload_type", 97}, + {"media_type", "video/raw"}, + {"media", + {{"width", config.video_media_fields.width}, + {"height", config.video_media_fields.height}, + {"frame_rate", + {{"num", config.video_media_fields.frame_rate_num}, {"den", config.video_media_fields.frame_rate_den}}}, + {"sampling", config.video_media_fields.sampling}, + {"structure", config.video_media_fields.structure}}}}; + return sender; +} + +json create_audio_sender_config(config_fields_t& config) +{ + std::string media_type = "audio/L16"; + if(config.audio_sender_fields.format == "S24BE") + { + media_type = "audio/L24"; + } + else if(config.audio_sender_fields.format == "S16BE") + { + media_type = "audio/L16"; + } + json sender = {{"id", config.id}, + {"label", config.label}, + {"description", config.description}, + {"network", + {{"primary", + {{"source_address", config.network.source_address}, + {"interface_name", config.network.interface_name}, + {"destination_address", config.network.destination_address}, + {"destination_port", config.network.destination_port}}}}}, + {"payload_type", 97}, + {"media_type", media_type}, + {"media", + {{"number_of_channels", config.audio_sender_fields.number_of_channels}, + {"sampling_rate", config.audio_sender_fields.sampling_rate}, + {"packet_time", config.audio_sender_fields.packet_time}}}}; + return sender; +} diff --git a/cpp/libs/gst_nmos_plugins/src/utils.h b/cpp/libs/gst_nmos_plugins/src/utils.h new file mode 100644 index 0000000..10ef2f1 --- /dev/null +++ b/cpp/libs/gst_nmos_plugins/src/utils.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include +#include +#include +#include "../include/element_class.h" +#include "../include/nmos_configuration.h" + +void create_default_config_fields_sender(config_fields_t* config); +void create_default_config_fields_video_receiver(config_fields_t* config); +void create_default_config_fields_audio_receiver(config_fields_t* config); + +nlohmann::json create_node_config(config_fields_t& config); +nlohmann::json create_device_config(config_fields_t& config); +nlohmann::json create_receiver_config(config_fields_t& config); + +std::string translate_video_format(const std::string& gst_format); + +std::string translate_audio_format(const std::string& gst_format); + +std::string get_node_id(char* node_configuration_location); + +std::string get_node_config(std::string node_configuration_location); + +std::string get_device_id(char* device_configuration_location); + +std::string get_device_config(char* device_configuration_location); + +std::string get_sender_config(char* sender_configuration_location); + +nlohmann::json create_video_sender_config(config_fields_t& config); +nlohmann::json create_audio_sender_config(config_fields_t& config); diff --git a/cpp/libs/gst_nmos_sender_plugin/CMakeLists.txt b/cpp/libs/gst_nmos_sender_plugin/CMakeLists.txt deleted file mode 100644 index c51e312..0000000 --- a/cpp/libs/gst_nmos_sender_plugin/CMakeLists.txt +++ /dev/null @@ -1,67 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(gst_nmos_sender_plugin LANGUAGES CXX) - -find_package(nlohmann_json REQUIRED) -find_package(PkgConfig REQUIRED) - -# Locate GLib package -pkg_check_modules(GLIB REQUIRED glib-2.0) - -# Locate GStreamer packages -pkg_search_module(GSTREAMER REQUIRED gstreamer-1.0>=1.4) -pkg_search_module(GSTREAMER_APP REQUIRED gstreamer-app-1.0>=1.4) -pkg_search_module(GSTREAMER_AUDIO REQUIRED gstreamer-audio-1.0>=1.4) -pkg_search_module(GSTREAMER_VIDEO REQUIRED gstreamer-video-1.0>=1.4) - -# Include GLib directories and link libraries -include_directories(${GLIB_INCLUDE_DIRS}) -link_directories(${GLIB_LIBRARY_DIRS}) -add_definitions(${GLIB_CFLAGS_OTHER}) - -# Include source files for gst_nmos_sender_plugin -file(GLOB_RECURSE PLUGIN_SOURCE_FILES *.cpp *.h) - -# Include source files for utils -file(GLOB_RECURSE UTILS_SOURCE_FILES utils/*.cpp utils/*.h) - -# Combine utils and plugin sources -set(SOURCE_FILES ${PLUGIN_SOURCE_FILES} ${UTILS_SOURCE_FILES}) - -# Define the plugin as a shared library -add_library(${PROJECT_NAME} MODULE ${SOURCE_FILES}) - -# Include directories -target_include_directories(${PROJECT_NAME} - PRIVATE ${GSTREAMER_INCLUDE_DIRS} - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/utils/include -) - -# Link GStreamer libraries -target_compile_options(${PROJECT_NAME} PRIVATE ${GSTREAMER_CFLAGS_OTHER}) -target_link_libraries(${PROJECT_NAME} - PRIVATE - ${GSTREAMER_LIBRARIES} - ${GSTREAMER_APP_LIBRARIES} - ${GSTREAMER_AUDIO_LIBRARIES} - ${GSTREAMER_VIDEO_LIBRARIES} - PUBLIC - bisect::project_warnings - bisect::expected - bisect::bisect_nmoscpp - bisect::bisect_json - nlohmann_json::nlohmann_json - ossrf::ossrf_nmos_api - ${GLIB_LIBRARIES} -) - -# Specify the output directory and the library name -set_target_properties(${PROJECT_NAME} PROPERTIES - LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins - OUTPUT_NAME "gstnmossender" # Custom .so name -) - -add_library(ossrf::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) - -# Install the plugin to the system's GStreamer plugin directory -install(TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ~/.local/lib/gstreamer-1.0) diff --git a/cpp/libs/gst_nmos_sender_plugin/utils.h b/cpp/libs/gst_nmos_sender_plugin/utils.h deleted file mode 100644 index e3817b5..0000000 --- a/cpp/libs/gst_nmos_sender_plugin/utils.h +++ /dev/null @@ -1,151 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -typedef struct node_fields_t -{ - std::string id; - std::string configuration_location; -} node_fields_t; - -typedef struct device_fields_t -{ - std::string id; - std::string label; - std::string description; -} device_fields_t; - -typedef struct video_media_fields_t -{ - gint width; - gint height; - gint frame_rate_num; - gint frame_rate_den; - std::string sampling; - std::string structure; -} video_media_fields_t; - -typedef struct audio_media_fields_t -{ - std::string format; - gint number_of_channels; - gint sampling_rate; - gint packet_time; -} audio_media_fields_t; - -typedef struct network_fields_t -{ - std::string source_address; - std::string interface_name; - std::string destination_address; - gint destination_port; -} network_fields_t; - -typedef struct config_fields_t -{ - node_fields_t node; - device_fields_t device; - gboolean is_audio; - video_media_fields_t video_media_fields; - audio_media_fields_t audio_sender_fields; - std::string sender_id; - std::string sender_label; - std::string sender_description; - network_fields_t network; -} config_fields_t; - -template class GstElementHandle -{ - public: - static std::variant create_element(const char* factory_name, - const char* element_name = nullptr) - { - T* elem = reinterpret_cast(gst_element_factory_make(factory_name, element_name)); - if(!elem) - { - return nullptr; - } - return GstElementHandle(elem); - } - - static std::variant create_bin(const char* bin_name) - { - T* bin = reinterpret_cast(gst_bin_new(bin_name)); - if(!bin) - { - return nullptr; - } - return GstElementHandle(bin); - } - - GstElementHandle(const GstElementHandle&) = delete; - GstElementHandle& operator=(const GstElementHandle&) = delete; - - // Move constructor - GstElementHandle(GstElementHandle&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; } - - // Move assignment - GstElementHandle& operator=(GstElementHandle&& other) noexcept - { - if(this != &other) - { - if(handle_ != nullptr) - { - gst_object_unref(handle_); - } - handle_ = other.handle_; - other.handle_ = nullptr; - } - return *this; - } - - ~GstElementHandle() - { - if(handle_) - { - gst_object_unref(handle_); - } - } - - void reset(T* new_ptr = nullptr) - { - if(handle_) - { - gst_object_unref(handle_); - } - handle_ = new_ptr; - } - - T* get() const { return handle_; } - - explicit operator bool() const { return (handle_ != nullptr); } - - private: - explicit GstElementHandle(T* handle) : handle_(handle) {} - - T* handle_ = nullptr; -}; - -void create_default_config_fields(config_fields_t* config); - -nlohmann::json create_node_config(config_fields_t& config); -nlohmann::json create_device_config(config_fields_t& config); -nlohmann::json create_video_sender_config(config_fields_t& config); -nlohmann::json create_audio_sender_config(config_fields_t& config); - -std::string translate_video_format(const std::string& gst_format); - -std::string translate_audio_format(const std::string& gst_format); - -std::string get_node_id(char* node_configuration_location); - -std::string get_node_config(char* node_configuration_location); - -std::string get_device_id(char* device_configuration_location); - -std::string get_device_config(char* device_configuration_location); - -std::string get_sender_config(char* sender_configuration_location); diff --git a/cpp/libs/gst_nmos_video_receiver_plugin/CMakeLists.txt b/cpp/libs/gst_nmos_video_receiver_plugin/CMakeLists.txt deleted file mode 100644 index ab5d707..0000000 --- a/cpp/libs/gst_nmos_video_receiver_plugin/CMakeLists.txt +++ /dev/null @@ -1,67 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(gst_nmos_video_receiver_plugin LANGUAGES CXX) - -find_package(nlohmann_json REQUIRED) -find_package(PkgConfig REQUIRED) - -# Locate GLib package -pkg_check_modules(GLIB REQUIRED glib-2.0) - -# Locate GStreamer packages -pkg_search_module(GSTREAMER REQUIRED gstreamer-1.0>=1.4) -pkg_search_module(GSTREAMER_APP REQUIRED gstreamer-app-1.0>=1.4) -pkg_search_module(GSTREAMER_AUDIO REQUIRED gstreamer-audio-1.0>=1.4) -pkg_search_module(GSTREAMER_VIDEO REQUIRED gstreamer-video-1.0>=1.4) - -# Include GLib directories and link libraries -include_directories(${GLIB_INCLUDE_DIRS}) -link_directories(${GLIB_LIBRARY_DIRS}) -add_definitions(${GLIB_CFLAGS_OTHER}) - -# Include source files for gst_nmos_video_receiver_plugin -file(GLOB_RECURSE PLUGIN_SOURCE_FILES *.cpp *.h) - -# Include source files for utils -file(GLOB_RECURSE UTILS_SOURCE_FILES utils/*.cpp utils/*.h) - -# Combine utils and plugin sources -set(SOURCE_FILES ${PLUGIN_SOURCE_FILES} ${UTILS_SOURCE_FILES}) - -# Define the plugin as a shared library -add_library(${PROJECT_NAME} MODULE ${SOURCE_FILES}) - -# Include directories -target_include_directories(${PROJECT_NAME} - PRIVATE ${GSTREAMER_INCLUDE_DIRS} - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/utils/include -) - -# Link GStreamer libraries -target_compile_options(${PROJECT_NAME} PRIVATE ${GSTREAMER_CFLAGS_OTHER}) -target_link_libraries(${PROJECT_NAME} - PRIVATE - ${GSTREAMER_LIBRARIES} - ${GSTREAMER_APP_LIBRARIES} - ${GSTREAMER_AUDIO_LIBRARIES} - ${GSTREAMER_VIDEO_LIBRARIES} - PUBLIC - bisect::project_warnings - bisect::expected - bisect::bisect_nmoscpp - bisect::bisect_json - nlohmann_json::nlohmann_json - ossrf::ossrf_nmos_api - ${GLIB_LIBRARIES} -) - -# Specify the output directory and the library name -set_target_properties(${PROJECT_NAME} PROPERTIES - LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins - OUTPUT_NAME "gstnmosvideoreceiver" -) - -add_library(ossrf::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) - -# Install the plugin to the system's GStreamer plugin directory -install(TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ~/.local/lib/gstreamer-1.0) diff --git a/cpp/libs/gst_nmos_video_receiver_plugin/utils.cpp b/cpp/libs/gst_nmos_video_receiver_plugin/utils.cpp deleted file mode 100644 index 76d5bb9..0000000 --- a/cpp/libs/gst_nmos_video_receiver_plugin/utils.cpp +++ /dev/null @@ -1,198 +0,0 @@ -#include "utils.h" -#include "ossrf/nmos/api/nmos_client.h" -#include "bisect/nmoscpp/configuration.h" -#include "bisect/expected/macros.h" -#include "bisect/expected.h" -#include "bisect/json.h" -#include - -using namespace bisect; -using json = nlohmann::json; - -void create_default_config_fields(config_fields_t* config) -{ - if(!config) - { - g_critical("Config pointer is NULL"); - return; - } - - // Initialize node_fields_t - config->node.id = "d49c85db-1c33-4f21-b160-58edd2af1810"; - config->node.configuration_location = - "/home/nmos/repos/nmos-sender-receiver-framework/cpp/demos/config/nmos_plugin_node_config_receiver.json"; - - // Initialize device_fields_t - config->device.id = "1ad20d7c-8c58-4c84-8f14-3cf3e3af164c"; - config->device.label = "OSSRF Device2"; - config->device.description = "OSSRF Device2"; - - // Initialize config_fields_t - config->id = "db9f46cf-2414-4e25-b6c6-2078159857f9"; - config->label = "BISECT OSSRF Video Receiver"; - config->description = "BISECT OSSRF Video Receiver"; - config->interface_name = "wlp1s0"; - config->address = "192.168.1.36"; -} - -json create_node_config(config_fields_t& config) -{ - const std::string node_config_str = get_node_config(config.node.configuration_location); - if(node_config_str != "") - { - json node_config = json::parse(node_config_str); - node_config["id"] = config.node.id; - - return node_config; - } - else - { - return nullptr; - } -} - -json create_device_config(config_fields_t& config) -{ - json device = { - {"id", config.device.id}, {"label", config.device.label}, {"description", config.device.description}}; - return device; -} - -json create_receiver_config(config_fields_t& config) -{ - json sender = { - {"id", config.id}, - {"label", config.label}, - {"description", config.description}, - {"network", {{"primary", {{"interface_address", config.address}, {"interface_name", config.interface_name}}}}}, - {"capabilities", {"video/raw"}}}; - return sender; -} - -std::string translate_video_format(const std::string& gst_format) -{ - static const std::unordered_map video_format_map = {{"UYVP", "YCbCr-4:2:2"}, - {"RGBA", "RGBA-8:8:8:8"}}; - - auto it = video_format_map.find(gst_format); - if(it != video_format_map.end()) - { - return it->second; - } - return gst_format; -} - -std::string translate_audio_format(const std::string& gst_format) -{ - static const std::unordered_map audio_format_map = {{"S24BE", "L24"}, {"S16LE", "L16"}}; - - auto it = audio_format_map.find(gst_format); - if(it != audio_format_map.end()) - { - return it->second; - } - return gst_format; -} - -expected load_configuration_from_file(std::string_view config_file) -{ - std::ifstream ifs(config_file.data()); - BST_ENFORCE(ifs.is_open(), "Failed opening file {}", config_file); - std::ostringstream buffer; - buffer << ifs.rdbuf(); - return parse_json(buffer.str()); -} - -std::string get_node_id(char* node_configuration_location) -{ - const auto configuration_result = load_configuration_from_file(node_configuration_location); - - const json& configuration = configuration_result.value(); - - auto node_result = find(configuration, "node"); - - const json& node = node_result.value(); - - auto node_id_result = find(node, "id"); - auto node_config_result = find(node, "configuration"); - - if(node_id_result.has_value() && node_config_result.has_value()) - { - const std::string node_id = node_id_result.value(); - const std::string node_configuration = node_config_result.value().dump(); - - return node_id; - } - - return ""; -} - -std::string get_node_config(std::string node_configuration_location) -{ - const auto configuration_result = load_configuration_from_file(node_configuration_location.c_str()); - - if(configuration_result.has_value()) - { - const json& configuration = configuration_result.value(); - - auto node_result = find(configuration, "node"); - - const json& node = node_result.value(); - - auto node_id_result = find(node, "id"); - auto node_config_result = find(node, "configuration"); - - if(node_id_result.has_value() && node_config_result.has_value()) - { - const std::string node_id = node_id_result.value(); - const std::string node_configuration = node_config_result.value().dump(); - - return node_configuration; - } - } - return ""; -} - -std::string get_device_id(char* device_configuration_location) -{ - const auto configuration_result = load_configuration_from_file(device_configuration_location); - - const json& configuration = configuration_result.value(); - - auto device_result = find(configuration, "device"); - - const json& device = device_result.value(); - - auto device_id_result = find(device, "id"); - - const std::string device_id = device_id_result.value(); - const std::string device_config = device.dump(); - - return device_id; -} - -std::string get_device_config(char* device_configuration_location) -{ - const auto configuration_result = load_configuration_from_file(device_configuration_location); - - const json& configuration = configuration_result.value(); - - auto device_result = find(configuration, "device"); - - const json& device = device_result.value(); - - auto device_id_result = find(device, "id"); - - const std::string device_id = device_id_result.value(); - const std::string device_config = device.dump(); - - return device_config; -} - -std::string get_sender_config(char* sender_configuration_location) -{ - // FIX ME: Gonna be hardcoded for now - const auto sender_config = load_configuration_from_file(sender_configuration_location); - - return sender_config.value().dump(); -}