diff --git a/src/main.c b/src/main.c index cee3000..2dd0dd5 100644 --- a/src/main.c +++ b/src/main.c @@ -22,17 +22,38 @@ #include "nvnmos.h" // example video format +#ifdef VIDEO_JXSV +// ST 2110-22 JPEG XS +#ifndef VIDEO_DESCRIPTION +#define VIDEO_DESCRIPTION "JPEG XS, YCbCr-4:2:2, 10 bit, 1280 x 720, progressive, 59.94 Hz" +#endif +#ifndef VIDEO_ENCODING_PARAMETERS +#define VIDEO_ENCODING_PARAMETERS "jxsv/90000" +#endif +#ifndef VIDEO_BANDWIDTH +#define VIDEO_BANDWIDTH "b=AS:116000\r\n" // transport bit rate (kb/s), approx. 1280 * 720 * 60000/1001 * 2 * 1.05 / 1e3 +#endif +#ifndef VIDEO_FORMAT_SPECIFIC_PARAMETERS +#define VIDEO_FORMAT_SPECIFIC_PARAMETERS "packetmode=0; profile=High444.12; level=1k-1; sublevel=Sublev3bpp; sampling=YCbCr-4:2:2; width=1280; height=720; exactframerate=60000/1001; depth=10; colorimetry=BT709; TCS=SDR; RANGE=FULL; SSN=ST2110-22:2019; TP=2110TPN" +#endif +#else +// ST 2110-20 #ifndef VIDEO_DESCRIPTION #define VIDEO_DESCRIPTION "YCbCr-4:2:2, 10 bit, 1920 x 1080, progressive, 50 Hz" #endif #ifndef VIDEO_ENCODING_PARAMETERS #define VIDEO_ENCODING_PARAMETERS "raw/90000" #endif +#ifndef VIDEO_BANDWIDTH +#define VIDEO_BANDWIDTH "" +#endif #ifndef VIDEO_FORMAT_SPECIFIC_PARAMETERS #define VIDEO_FORMAT_SPECIFIC_PARAMETERS "sampling=YCbCr-4:2:2; width=1920; height=1080; exactframerate=50; depth=10; TCS=SDR; colorimetry=BT709; PM=2110GPM; SSN=ST2110-20:2017; TP=2110TPN; " #endif +#endif // example audio format +// ST 2110-30 #ifndef AUDIO_DESCRIPTION #define AUDIO_DESCRIPTION "2 ch, 48 kHz, 24 bit" #endif @@ -67,10 +88,11 @@ static bool handle_rtp_connection_activated( } // construct example SDP for video sender or receiver -static bool init_video_sdp(char* sdp, size_t sdp_size, bool sender, const char* id, const char* interface_ip, const char* label, const char* group_hint, bool ptp) +static bool init_video_sdp(char* sdp, size_t sdp_size, bool sender, const char* id, const char* interface_ip, const char* label, const char* group_hint) { const char* description = VIDEO_DESCRIPTION; const char* encoding = VIDEO_ENCODING_PARAMETERS; + const char* bandwidth = VIDEO_BANDWIDTH; const char* format_specific_parameters = VIDEO_FORMAT_SPECIFIC_PARAMETERS; const char* multicast_ip = "233.252.0.0"; // MCAST-TEST-NET @@ -78,7 +100,7 @@ static bool init_video_sdp(char* sdp, size_t sdp_size, bool sender, const char* int destination_port = 5020; int source_port = 5004; int payload_type = 96; // conventional - const char* ts_refclk = ptp + const char* ts_refclk = CLK_PTP ? "a=ts-refclk:ptp=IEEE1588-2008:AC-DE-48-23-45-67-01-9F:42\r\n" "a=ts-refclk:ptp=IEEE1588-2008:traceable\r\n" // use both to include all parameters required for NMOS : "a=ts-refclk:localmac=CA-FE-01-CA-FE-02\r\n"; @@ -96,6 +118,7 @@ static bool init_video_sdp(char* sdp, size_t sdp_size, bool sender, const char* "a=x-nvnmos-group-hint:%s\r\n" // optional "m=video %d RTP/AVP %d\r\n" "c=IN IP4 %s/64\r\n" + "%s" "a=source-filter: incl IN IP4 %s %s\r\n" // omit for any-source multicast for receiver "a=x-nvnmos-iface-ip:%s\r\n" "a=x-nvnmos-src-port:%d\r\n" // not applicable for receiver @@ -113,6 +136,7 @@ static bool init_video_sdp(char* sdp, size_t sdp_size, bool sender, const char* destination_port, payload_type, multicast_ip, + bandwidth, multicast_ip, sender ? interface_ip : source_ip, interface_ip, @@ -128,7 +152,7 @@ static bool init_video_sdp(char* sdp, size_t sdp_size, bool sender, const char* } // construct example SDP for audio sender or receiver -static bool init_audio_sdp(char* sdp, size_t sdp_size, bool sender, const char* id, const char* interface_ip, const char* label, const char* group_hint, bool ptp) +static bool init_audio_sdp(char* sdp, size_t sdp_size, bool sender, const char* id, const char* interface_ip, const char* label, const char* group_hint) { const char* description = AUDIO_DESCRIPTION; const char* encoding = AUDIO_ENCODING_PARAMETERS; @@ -140,7 +164,7 @@ static bool init_audio_sdp(char* sdp, size_t sdp_size, bool sender, const char* int source_port = 5004; int payload_type = 97; // conventional const char* ptime = "a=ptime:1\r\n"; - const char* ts_refclk = ptp + const char* ts_refclk = CLK_PTP ? "a=ts-refclk:ptp=IEEE1588-2008:AC-DE-48-23-45-67-01-9F:42\r\n" "a=ts-refclk:ptp=IEEE1588-2008:traceable\r\n" // use both to include all parameters required for NMOS : "a=ts-refclk:localmac=CA-FE-01-CA-FE-02\r\n"; @@ -236,15 +260,14 @@ int main(int argc, char *argv[]) // receivers and senders representing the GStreamer sources and sinks const char* interface_ip = argv[3]; - bool ptp = CLK_PTP; char source_sdp[2][2048] = { 0 }; - if (!init_video_sdp(source_sdp[0], sizeof source_sdp[0], false, "source-0", interface_ip, "NvNmos Video Receiver", "rx-0:video", ptp)) return 1; - if (!init_audio_sdp(source_sdp[1], sizeof source_sdp[1], false, "source-1", interface_ip, "NvNmos Audio Receiver", "rx-0:audio", ptp)) return 1; + if (!init_video_sdp(source_sdp[0], sizeof source_sdp[0], false, "source-0", interface_ip, "NvNmos Video Receiver", "rx-0:video")) return 1; + if (!init_audio_sdp(source_sdp[1], sizeof source_sdp[1], false, "source-1", interface_ip, "NvNmos Audio Receiver", "rx-0:audio")) return 1; char sink_sdp[2][2048] = { 0 }; - if (!init_video_sdp(sink_sdp[0], sizeof sink_sdp[0], true, "sink-0", interface_ip, "NvNmos Video Sender", "tx-0:video", ptp)) return 1; - if (!init_audio_sdp(sink_sdp[1], sizeof sink_sdp[1], true, "sink-1", interface_ip, "NvNmos Audio Sender", "tx-0:audio", ptp)) return 1; + if (!init_video_sdp(sink_sdp[0], sizeof sink_sdp[0], true, "sink-0", interface_ip, "NvNmos Video Sender", "tx-0:video")) return 1; + if (!init_audio_sdp(sink_sdp[1], sizeof sink_sdp[1], true, "sink-1", interface_ip, "NvNmos Audio Sender", "tx-0:audio")) return 1; NvNmosReceiverConfig source_config[2] = { 0 }; diff --git a/src/nvnmos.h b/src/nvnmos.h index 8bfb499..4eedc01 100644 --- a/src/nvnmos.h +++ b/src/nvnmos.h @@ -49,7 +49,8 @@ * - AMWA BCP-002-01 Natural Grouping of NMOS Resources v1.0 * - AMWA BCP-002-02 NMOS Asset Distinguishing Information v1.0 * - AMWA BCP-004-01 NMOS Receiver Capabilities v1.0 - * - Session Description Protocol conforming to SMPTE ST 2110-20 and -30 + * - AMWA BCP-006-01 NMOS With JPEG XS v1.0 + * - Session Description Protocol conforming to SMPTE ST 2110-20, -22, -30, -40, and ST 2022-7 * * @ingroup NvNmosApi * @{ diff --git a/src/nvnmos_impl.cpp b/src/nvnmos_impl.cpp index 0888f36..a7fc61b 100644 --- a/src/nvnmos_impl.cpp +++ b/src/nvnmos_impl.cpp @@ -26,6 +26,7 @@ #include "nvnmos_impl.h" #include +#include #include #include #include @@ -41,6 +42,7 @@ #include "nmos/clock_ref_type.h" #include "nmos/colorspace.h" #include "nmos/connection_resources.h" +#include "nmos/format.h" #include "nmos/group_hint.h" #include "nmos/interlace_mode.h" #include "nmos/media_type.h" @@ -51,9 +53,11 @@ #include "nmos/node_server.h" #include "nmos/sdp_utils.h" #include "nmos/slog.h" +#include "nmos/st2110_21_sender_type.h" #include "nmos/system_resources.h" #include "nmos/transfer_characteristic.h" #include "nmos/transport.h" +#include "nmos/video_jxsv.h" #include "sdp/sdp.h" namespace nvnmos @@ -69,7 +73,7 @@ namespace nvnmos // supported formats enum class format { - // video/raw + // video/raw or video/jxsv video, // audio/L24 or audio/L16 audio, @@ -103,6 +107,14 @@ namespace nvnmos // get the optional session information utility::string_t get_session_description_session_info(const web::json::value& session_description); + // get the optional capabilities from the custom attribute + bool has_session_description_caps(const web::json::value& session_description); + + // get the format bit rate from the custom attribute if present or calculate an approximate value + uint64_t get_format_bit_rate(const nmos::sdp_parameters& sdp_params); + // get the transport bit rate from the custom attribute if present or calculate an approximate value + uint64_t get_transport_bit_rate(const nmos::sdp_parameters& sdp_params); + // find interface with the specified address std::vector::const_iterator find_interface(const std::vector& interfaces, const utility::string_t& address); @@ -198,7 +210,10 @@ namespace nvnmos // for now, only manage a single clock const auto clock = nmos::clock_names::clk0; - const auto format = impl::get_format(nmos::get_media_type(sdp_params)); + + const auto media_type = nmos::get_media_type(sdp_params); + const auto format = impl::get_format(media_type); + const auto interface_names = boost::copy_range>( transport_params.as_array() | boost::adaptors::transformed([&](const value& transport_param) { @@ -218,16 +233,36 @@ namespace nvnmos if (impl::format::video == format) { - const auto video = nmos::get_video_raw_parameters(sdp_params); - - source = nmos::make_video_source(source_id, device_id, clock, video.exactframerate, settings); - flow = nmos::make_raw_video_flow( - flow_id, source_id, device_id, - video.exactframerate, - video.width, video.height, video.interlace ? nmos::interlace_modes::interlaced_tff : nmos::interlace_modes::progressive, - nmos::colorspace{ video.colorimetry.name }, nmos::transfer_characteristic{ video.tcs.name }, video.sampling, video.depth, - settings - ); + if (nmos::media_types::video_raw == media_type) + { + const auto video = nmos::get_video_raw_parameters(sdp_params); + + source = nmos::make_video_source(source_id, device_id, clock, video.exactframerate, settings); + flow = nmos::make_raw_video_flow( + flow_id, source_id, device_id, + video.exactframerate, + video.width, video.height, video.interlace ? nmos::interlace_modes::interlaced_tff : nmos::interlace_modes::progressive, + nmos::colorspace{ video.colorimetry.name }, nmos::transfer_characteristic{ video.tcs.name }, video.sampling, video.depth, + settings + ); + } + else if (nmos::media_types::video_jxsv == media_type) + { + const auto video = nmos::get_video_jxsv_parameters(sdp_params); + const auto format_bit_rate = impl::get_format_bit_rate(sdp_params); + + source = nmos::make_video_source(source_id, device_id, clock, video.exactframerate, settings); + // nmos::make_video_jxsv_flow currently takes bits_per_pixel not bit_rate + flow = nmos::make_video_jxsv_flow( + flow_id, source_id, device_id, + video.exactframerate, + video.width, video.height, video.interlace ? nmos::interlace_modes::interlaced_tff : nmos::interlace_modes::progressive, + nmos::colorspace{ video.colorimetry.name }, nmos::transfer_characteristic{ video.tcs.name }, video.sampling, video.depth, + nmos::profile{ video.profile.name }, nmos::level{ video.level.name }, nmos::sublevel{ video.sublevel.name }, 0, + settings + ); + flow.data[nmos::fields::bit_rate] = value(format_bit_rate); + } } else if (impl::format::audio == format) { @@ -272,6 +307,31 @@ namespace nvnmos const auto manifest_href = nmos::experimental::make_manifest_api_manifest(sender_id, settings); auto sender = nmos::make_sender(sender_id, flow_id, nmos::transports::rtp, device_id, manifest_href.to_string(), interface_names, settings); + if (impl::format::video == format) + { + if (nmos::media_types::video_jxsv == media_type) + { + const auto video = nmos::get_video_jxsv_parameters(sdp_params); + + // additional attributes required by BCP-006-01 + // see https://specs.amwa.tv/bcp-006-01/releases/v1.0.0/docs/NMOS_With_JPEG_XS.html#senders + + const auto transport_bit_rate = impl::get_transport_bit_rate(sdp_params); + if (0 != transport_bit_rate) + { + sender.data[nmos::fields::bit_rate] = value(transport_bit_rate); + } + const auto packet_transmission_mode = nmos::parse_packet_transmission_mode(video.packetmode, video.transmode); + if (nmos::packet_transmission_modes::codestream != packet_transmission_mode) + { + sender.data[nmos::fields::packet_transmission_mode] = value(packet_transmission_mode.name); + } + if (!video.tp.empty()) + { + sender.data[nmos::fields::st2110_21_sender_type] = value(video.tp.name); + } + } + } auto connection_sender = nmos::make_connection_rtp_sender(sender_id, transport_params.size() > 1); // add some constraints; these should be completed fully! @@ -343,7 +403,11 @@ namespace nvnmos const auto node_id = impl::make_id(seed_id, nmos::types::node); const auto device_id = impl::make_id(seed_id, nmos::types::device); const auto receiver_id = impl::make_id(seed_id, nmos::types::receiver, internal_id); - const auto format = impl::get_format(nmos::get_media_type(sdp_params)); + + const auto media_type = nmos::get_media_type(sdp_params); + const auto format = impl::get_format(media_type); + const auto want_caps = !impl::has_session_description_caps(sdp); + const auto interface_names = boost::copy_range>( transport_params.as_array() | boost::adaptors::transformed([&](const value& transport_param) { @@ -362,55 +426,87 @@ namespace nvnmos if (impl::format::video == format) { - const auto video = nmos::get_video_raw_parameters(sdp_params); - - receiver = nmos::make_video_receiver(receiver_id, device_id, nmos::transports::rtp, interface_names, settings); - // add a constraint set; these should be completed fully! - const auto interlace_modes = video.interlace - ? std::vector{ nmos::interlace_modes::interlaced_bff.name, nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name } - : std::vector{ nmos::interlace_modes::progressive.name }; - receiver.data[nmos::fields::caps][nmos::fields::constraint_sets] = value_of({ - value_of({ - { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ video.exactframerate }) }, - { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ video.width }) }, - { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ video.height }) }, - { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint(interlace_modes) }, - { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ video.sampling.name }) } - }) - }); - receiver.data[nmos::fields::version] = receiver.data[nmos::fields::caps][nmos::fields::version] = value(nmos::make_version()); + receiver = nmos::make_receiver(receiver_id, device_id, nmos::transports::rtp, interface_names, nmos::formats::video, { media_type }, settings); + + if (want_caps) + { + if (nmos::media_types::video_raw == media_type) + { + const auto video = nmos::get_video_raw_parameters(sdp_params); + + const auto interlace_modes = video.interlace + ? std::vector{ nmos::interlace_modes::interlaced_bff.name, nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name } + : std::vector{ nmos::interlace_modes::progressive.name }; + receiver.data[nmos::fields::caps][nmos::fields::constraint_sets] = value_of({ + value_of({ + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ video.exactframerate }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ video.width }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ video.height }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint(interlace_modes) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ video.sampling.name }) } + }) + }); + } + else if (nmos::media_types::video_jxsv == media_type) + { + const auto video = nmos::get_video_jxsv_parameters(sdp_params); + + // some of the parameter constraints recommended by BCP-006-01 + // could also include common video ones (grain_rate, frame_width, frame_height, etc.) + // see https://specs.amwa.tv/bcp-006-01/releases/v1.0.0/docs/NMOS_With_JPEG_XS.html#receivers + const auto format_bit_rate = impl::get_format_bit_rate(sdp_params); + const auto transport_bit_rate = impl::get_transport_bit_rate(sdp_params); + const auto packet_transmission_mode = nmos::parse_packet_transmission_mode(video.packetmode, video.transmode); + receiver.data[nmos::fields::caps][nmos::fields::constraint_sets] = value_of({ + value_of({ + // hm, could enumerate lower profiles, levels or sublevels? + { !video.profile.empty() ? nmos::caps::format::profile.key : U(""), nmos::make_caps_string_constraint({ video.profile.name }) }, + { !video.level.empty() ? nmos::caps::format::level.key : U(""), nmos::make_caps_string_constraint({ video.level.name }) }, + { !video.sublevel.empty() ? nmos::caps::format::sublevel.key : U(""), nmos::make_caps_string_constraint({ video.sublevel.name }) }, + { 0 != format_bit_rate ? nmos::caps::format::bit_rate.key : U(""), nmos::make_caps_integer_constraint({}, nmos::no_minimum(), (int64_t)format_bit_rate) }, + { 0 != transport_bit_rate ? nmos::caps::transport::bit_rate.key : U(""), nmos::make_caps_integer_constraint({}, nmos::no_minimum(), (int64_t)transport_bit_rate) }, + { nmos::caps::transport::packet_transmission_mode, nmos::make_caps_string_constraint({ packet_transmission_mode.name }) } + }) + }); + } + receiver.data[nmos::fields::version] = receiver.data[nmos::fields::caps][nmos::fields::version] = value(nmos::make_version()); + } } else if (impl::format::audio == format) { const auto audio = nmos::get_audio_L_parameters(sdp_params); receiver = nmos::make_audio_receiver(receiver_id, device_id, nmos::transports::rtp, interface_names, audio.bit_depth, settings); - // add a constraint set; these should be completed fully! - receiver.data[nmos::fields::caps][nmos::fields::constraint_sets] = value_of({ - value_of({ - { nmos::caps::format::channel_count, nmos::make_caps_integer_constraint({ audio.channel_count }) }, - { nmos::caps::format::sample_rate, nmos::make_caps_rational_constraint({ audio.sample_rate }) }, - { nmos::caps::format::sample_depth, nmos::make_caps_integer_constraint({ audio.bit_depth }) }, - { 0 != sdp_params.packet_time ? nmos::caps::transport::packet_time.key : U(""), nmos::make_caps_number_constraint({ sdp_params.packet_time }) }, - { 0 != sdp_params.max_packet_time ? nmos::caps::transport::max_packet_time.key : U(""), nmos::make_caps_number_constraint({ sdp_params.max_packet_time }) } - }) - }); - receiver.data[nmos::fields::version] = receiver.data[nmos::fields::caps][nmos::fields::version] = value(nmos::make_version()); + if (want_caps) + { + receiver.data[nmos::fields::caps][nmos::fields::constraint_sets] = value_of({ + value_of({ + { nmos::caps::format::channel_count, nmos::make_caps_integer_constraint({ audio.channel_count }) }, + { nmos::caps::format::sample_rate, nmos::make_caps_rational_constraint({ audio.sample_rate }) }, + { nmos::caps::format::sample_depth, nmos::make_caps_integer_constraint({ audio.bit_depth }) }, + { 0 != sdp_params.packet_time ? nmos::caps::transport::packet_time.key : U(""), nmos::make_caps_number_constraint({ sdp_params.packet_time }) }, + { 0 != sdp_params.max_packet_time ? nmos::caps::transport::max_packet_time.key : U(""), nmos::make_caps_number_constraint({ sdp_params.max_packet_time }) } + }) + }); + receiver.data[nmos::fields::version] = receiver.data[nmos::fields::caps][nmos::fields::version] = value(nmos::make_version()); + } } else if (impl::format::data == format) { const auto data = nmos::get_video_smpte291_parameters(sdp_params); receiver = nmos::make_sdianc_data_receiver(receiver_id, device_id, nmos::transports::rtp, interface_names, settings); - // add a constraint set; these should be completed fully! - if (data.exactframerate) + if (want_caps) { - receiver.data[nmos::fields::caps][nmos::fields::constraint_sets] = value_of({ - value_of({ - { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ data.exactframerate }) } - }) - }); - receiver.data[nmos::fields::version] = receiver.data[nmos::fields::caps][nmos::fields::version] = value(nmos::make_version()); + if (data.exactframerate) + { + receiver.data[nmos::fields::caps][nmos::fields::constraint_sets] = value_of({ + value_of({ + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ data.exactframerate }) } + }) + }); + receiver.data[nmos::fields::version] = receiver.data[nmos::fields::caps][nmos::fields::version] = value(nmos::make_version()); + } } } else if (impl::format::mux == format) @@ -418,7 +514,10 @@ namespace nvnmos const auto mux = nmos::get_video_SMPTE2022_6_parameters(sdp_params); receiver = nmos::make_mux_receiver(receiver_id, device_id, nmos::transports::rtp, interface_names, settings); - // hmm, add a constraint set, e.g. taking account of sdp_params.framerate + if (want_caps) + { + // hmm, add a constraint set, e.g. taking account of sdp_params.framerate + } } auto connection_receiver = nmos::make_connection_rtp_receiver(receiver_id, transport_params.size() > 1); @@ -646,9 +745,25 @@ namespace nvnmos // Connection API callback to parse "transport_file" during a PATCH /staged request nmos::transport_file_parser make_node_implementation_transport_file_parser() { - // this uses the default transport file parser explicitly + // this uses a custom transport file parser to handle video/jxsv in addition to the core media types + // otherwise, it could simply return &nmos::parse_rtp_transport_file // (if this callback is specified, an 'empty' std::function is not allowed) - return &nmos::parse_rtp_transport_file; + return [](const nmos::resource& receiver, const nmos::resource& connection_receiver, const utility::string_t& transport_file_type, const utility::string_t& transport_file_data, slog::base_gate& gate) + { + const auto validate_sdp_parameters = [](const web::json::value& receiver, const nmos::sdp_parameters& sdp_params) + { + if (nmos::media_types::video_jxsv == nmos::get_media_type(sdp_params)) + { + nmos::validate_video_jxsv_sdp_parameters(receiver, sdp_params); + } + else + { + // validate core media types, i.e., "video/raw", "audio/L", "video/smpte291" and "video/SMPTE2022-6" + nmos::validate_sdp_parameters(receiver, sdp_params); + } + }; + return nmos::details::parse_rtp_transport_file(validate_sdp_parameters, receiver, connection_receiver, transport_file_type, transport_file_data, gate); + }; } // Connection API callback to perform application-specific validation of the merged /staged endpoint during a PATCH /staged request @@ -717,6 +832,12 @@ namespace nvnmos auto sdp_params = nmos::get_session_description_sdp_parameters(parsed_sdp); + // remove custom nvnmos parameters + sdp_params.fmtp.erase(std::remove_if(sdp_params.fmtp.begin(), sdp_params.fmtp.end(), [](const nmos::sdp_parameters::fmtp_t::value_type& param) + { + return boost::algorithm::starts_with(param.first, U("x-nvnmos-")); + }), sdp_params.fmtp.end()); + // update ts-refclk based on current clock { const auto seed_id = nmos::experimental::fields::seed_id(settings); @@ -1153,10 +1274,64 @@ namespace nvnmos return sdp::fields::information(session_description); } + // get the optional capabilities from the custom attribute + // a=x-nvnmos-caps: + // where is the RTP payload type and + // is zero or more parameters to be constrained, overriding the "a=fmtp:" line + // for now, just using this to indicate whether constraint sets are wanted at all + bool has_session_description_caps(const web::json::value& session_description) + { + using web::json::value; + + const auto& media_descriptions = sdp::fields::media_descriptions(session_description); + // hm, for simplicity, read caps only from the first media description + if (0 == media_descriptions.size()) return false; + const auto& media_description = media_descriptions.at(0); + const auto& media_attributes = sdp::fields::attributes(media_description); + { + const auto& ma = media_attributes.as_array(); + + auto caps = sdp::find_name(ma, nvnmos::attributes::caps); + return ma.end() != caps; + } + } + + // approximate IP/UDP/RTP overhead + const auto transport_bit_rate_factor = 1.05; + + // get the format bit rate from the custom attribute if present or calculate an approximate value + uint64_t get_format_bit_rate(const nmos::sdp_parameters& sdp_params) + { + // use custom format bit rate parameter if present + const auto format_bit_rate = nmos::details::find_fmtp(sdp_params.fmtp, nvnmos::fields::format_bit_rate); + if (sdp_params.fmtp.end() != format_bit_rate) return utility::istringstreamed(format_bit_rate->second); + // otherwise, calculate an approximate value based on custom transport bit rate parameter or bandwidth line + const auto transport_bit_rate = nmos::details::find_fmtp(sdp_params.fmtp, nvnmos::fields::transport_bit_rate); + if (sdp_params.fmtp.end() != transport_bit_rate) return uint64_t(utility::istringstreamed(transport_bit_rate->second) / impl::transport_bit_rate_factor); + if (sdp::bandwidth_types::application_specific == sdp_params.bandwidth.bandwidth_type) return uint64_t(sdp_params.bandwidth.bandwidth / impl::transport_bit_rate_factor); + return 0; + } + + // get the transport bit rate from the custom attribute if present or calculate an approximate value + uint64_t get_transport_bit_rate(const nmos::sdp_parameters& sdp_params) + { + // use custom transport bit rate parameter if present + const auto transport_bit_rate = nmos::details::find_fmtp(sdp_params.fmtp, nvnmos::fields::transport_bit_rate); + if (sdp_params.fmtp.end() != transport_bit_rate) return utility::istringstreamed(transport_bit_rate->second); + // otherwise, calculate an approximate value based on custom format bit rate parameter if present + const auto format_bit_rate = nmos::details::find_fmtp(sdp_params.fmtp, nvnmos::fields::format_bit_rate); + // round to nearest Megabit/second per examples in VSF TR-08:2022 + if (sdp_params.fmtp.end() != format_bit_rate) return uint64_t(utility::istringstreamed(format_bit_rate->second) * impl::transport_bit_rate_factor / 1e3 + 0.5) * 1000; + // or fall back to bandwidth line + if (sdp::bandwidth_types::application_specific == sdp_params.bandwidth.bandwidth_type) return sdp_params.bandwidth.bandwidth; + return 0; + } + // identify supported format from media type format get_format(const nmos::media_type& media_type) { if (nmos::media_types::video_raw == media_type) return format::video; + if (nmos::media_types::video_jxsv == media_type) return format::video; if (nmos::media_types::audio_L(24) == media_type) return format::audio; if (nmos::media_types::audio_L(16) == media_type) return format::audio; if (nmos::media_types::video_smpte291 == media_type) return format::data; diff --git a/src/nvnmos_impl.h b/src/nvnmos_impl.h index 17cf816..8776341 100644 --- a/src/nvnmos_impl.h +++ b/src/nvnmos_impl.h @@ -69,12 +69,20 @@ namespace nvnmos // for senders and receivers const utility::string_t internal_id{ U("x-nvnmos-id") }; const utility::string_t group_hint{ U("x-nvnmos-group-hint") }; + const utility::string_t caps{ U("x-nvnmos-caps") }; // for receivers const utility::string_t interface_ip{ U("x-nvnmos-iface-ip") }; // for senders const utility::string_t source_port{ U("x-nvnmos-src-port") }; } + // custom SDP format-specific parameters + namespace fields + { + const web::json::field transport_bit_rate{ U("x-nvnmos-transport-bit-rate") }; // transport bit rate including IP/UDP/RTP overhead + const web::json::field format_bit_rate{ U("x-nvnmos-format-bit-rate") }; // format bit rate excluding IP/UDP/RTP overhead + } + struct node_implementation_exception {}; // This constructs and inserts a node resource and a device resource into the model, based on the model settings.