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.