Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Development/nmos-cpp-node/node_implementation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2105,6 +2105,13 @@ nmos::connection_resource_auto_resolver make_node_implementation_auto_resolver(c
const bool smpte2022_7 = 1 < transport_params.size();
nmos::details::resolve_auto(transport_params[0], nmos::fields::interface_ip, [&] { return web::json::front(nmos::fields::constraint_enum(constraints.at(0).at(nmos::fields::interface_ip))); });
if (smpte2022_7) nmos::details::resolve_auto(transport_params[1], nmos::fields::interface_ip, [&] { return web::json::back(nmos::fields::constraint_enum(constraints.at(1).at(nmos::fields::interface_ip))); });
for (auto& params : transport_params.as_array())
{
// The IS-05 receiver schema defines "auto" as the highest available number of dimensions.
nmos::details::resolve_auto(params, nmos::fields::fec_mode, [&] {
return value::string(!nmos::fields::fec2D_destination_port(params).is_null() ? U("2D") : U("1D"));
});
}
// lastly, apply the specification defaults for any properties not handled above
nmos::resolve_rtp_auto(id_type.second, transport_params);
}
Expand Down
1 change: 1 addition & 0 deletions Development/nmos/json_fields.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ namespace nmos
const web::json::field_as_value_or destination_ip{ U("destination_ip"), {} }; // string
const web::json::field_as_value_or source_port{ U("source_port"), {} }; // string or integer
const web::json::field_as_bool_or rtp_enabled{ U("rtp_enabled"), false };
const web::json::field_as_bool_or fec_enabled{ U("fec_enabled"), false };
const web::json::field_as_value_or fec_destination_ip{ U("fec_destination_ip"), {} }; // string
const web::json::field_as_value_or fec1D_destination_port{ U("fec1D_destination_port"), {} }; // string or integer
const web::json::field_as_value_or fec2D_destination_port{ U("fec2D_destination_port"), {} }; // string or integer
Expand Down
349 changes: 332 additions & 17 deletions Development/nmos/sdp_utils.cpp

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions Development/nmos/sdp_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,61 @@ namespace nmos
group_t(const sdp::group_semantics_type& semantics, const std::vector<utility::string_t>& media_stream_ids) : semantics(semantics), media_stream_ids(media_stream_ids) {}
} group;

// Forward Error Correction (FEC) Framework parameters for the IS-05-compatible
// topology of one source flow and one or two repair flows per RTP leg.
// RFC-only values are explicit because they cannot be derived from IS-05 transport parameters.
// See https://tools.ietf.org/html/rfc6364
struct fec_t
{
typedef std::vector<std::pair<utility::string_t, utility::string_t>> scheme_specific_t;

struct repair_window_t
{
uint32_t size;
sdp::repair_window_unit unit;

repair_window_t() : size() {}
repair_window_t(uint32_t size, const sdp::repair_window_unit& unit) : size(size), unit(unit) {}
};

struct repair_flow_t
{
uint64_t encoding_id;
bst::optional<uint64_t> preference_level;
scheme_specific_t sender_side_scheme_specific;
scheme_specific_t scheme_specific;
bst::optional<repair_window_t> repair_window;
utility::string_t media_stream_id;

repair_flow_t() : encoding_id() {}
repair_flow_t(uint64_t encoding_id, const utility::string_t& media_stream_id,
const scheme_specific_t& sender_side_scheme_specific = {}, const scheme_specific_t& scheme_specific = {},
bst::optional<uint64_t> preference_level = bst::nullopt, bst::optional<repair_window_t> repair_window = bst::nullopt)
: encoding_id(encoding_id)
, preference_level(preference_level)
, sender_side_scheme_specific(sender_side_scheme_specific)
, scheme_specific(scheme_specific)
, repair_window(repair_window)
, media_stream_id(media_stream_id)
{}
};

uint32_t source_id;
bst::optional<uint64_t> tag_length;
utility::string_t media_stream_id;
std::vector<repair_flow_t> repair_flows;

fec_t() : source_id() {}
fec_t(uint32_t source_id, const utility::string_t& media_stream_id, const std::vector<repair_flow_t>& repair_flows,
bst::optional<uint64_t> tag_length = bst::nullopt)
: source_id(source_id)
, tag_length(tag_length)
, media_stream_id(media_stream_id)
, repair_flows(repair_flows)
{}
};
std::vector<fec_t> fec;

// Media ("m=")
// See https://tools.ietf.org/html/rfc4566#section-5.14
sdp::media_type media_type;
Expand Down
150 changes: 150 additions & 0 deletions Development/nmos/test/sdp_utils_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,156 @@ a=rtpmap:103 raw/90000
BST_REQUIRE(test_params == transport_params);
}

////////////////////////////////////////////////////////////////////////////////////////////
BST_TEST_CASE(testInterpretationOfSdpFilesFec)
{
using web::json::value;
using web::json::value_of;

// See https://specs.amwa.tv/is-05/releases/v1.1.1/docs/4.1._Behaviour_-_RTP_Transport_Type.html#operation-with-smpte-2022-5
const std::string test_sdp = R"(v=0
o=ali 1122334455 1122334466 IN IP4 172.29.26.24
s=FEC Framework Examples
t=0 0
a=group:FEC-FR S1 R1
m=video 30000 RTP/AVP 100
c=IN IP4 233.252.0.1/127
a=rtpmap:100 MP2T/90000
a=fec-source-flow: id=0
a=mid:S1
m=application 30000 UDP/FEC
c=IN IP4 233.252.0.2/127
a=fec-repair-flow: encoding-id=10; ss-fssi=n:7,k:5
a=mid:R1
)";

const auto expected_transport_params = value_of({
value_of({
{ nmos::fields::source_ip, value::null() },
{ nmos::fields::multicast_ip, U("233.252.0.1") },
{ nmos::fields::interface_ip, U("auto") },
{ nmos::fields::destination_port, 30000 },
{ nmos::fields::rtp_enabled, true },
{ nmos::fields::fec_enabled, true },
{ nmos::fields::fec_mode, U("auto") },
{ nmos::fields::fec_destination_ip, U("233.252.0.2") },
{ nmos::fields::fec1D_destination_port, 30000 },
{ nmos::fields::fec2D_destination_port, value::null() }
})
});

const auto session_description = sdp::parse_session_description(test_sdp);
auto parsed = nmos::parse_session_description(session_description);
BST_REQUIRE(expected_transport_params == parsed.second);

BST_REQUIRE_EQUAL(1, parsed.first.fec.size());
const auto& fec = parsed.first.fec.at(0);
BST_CHECK_EQUAL(0, fec.source_id);
BST_CHECK_EQUAL(U("S1"), fec.media_stream_id);
BST_REQUIRE_EQUAL(1, fec.repair_flows.size());
BST_CHECK_EQUAL(10, fec.repair_flows.at(0).encoding_id);
BST_CHECK_EQUAL(U("R1"), fec.repair_flows.at(0).media_stream_id);
BST_REQUIRE_EQUAL(2, fec.repair_flows.at(0).sender_side_scheme_specific.size());
BST_CHECK_EQUAL(U("n"), fec.repair_flows.at(0).sender_side_scheme_specific.at(0).first);
BST_CHECK_EQUAL(U("7"), fec.repair_flows.at(0).sender_side_scheme_specific.at(0).second);

// A Receiver must resolve interface_ip before using the parsed parameters to create SDP.
parsed.second[0][nmos::fields::interface_ip] = value::string(U("172.29.26.24"));
const auto made_sdp = nmos::make_session_description(parsed.first, parsed.second);
const auto roundtripped = nmos::parse_session_description(sdp::parse_session_description(sdp::make_session_description(made_sdp)));
BST_REQUIRE(expected_transport_params == roundtripped.second);
BST_REQUIRE_EQUAL(1, roundtripped.first.fec.size());
BST_CHECK_EQUAL(U("R1"), roundtripped.first.fec.at(0).repair_flows.at(0).media_stream_id);
}

////////////////////////////////////////////////////////////////////////////////////////////
BST_TEST_CASE(testSdpTransportParamsFec2D)
{
using web::json::value_of;

nmos::sdp_parameters sdp_params{
U("FEC 2D"),
sdp::media_types::video,
{ 100, U("MP2T"), 90000 }
};
sdp_params.connection_data.ttl = 127;
nmos::sdp_parameters::fec_t::repair_flow_t first_repair{ 10, U("R1"), { { U("n"), U("7") }, { U("k"), U("5") } } };
first_repair.repair_window = nmos::sdp_parameters::fec_t::repair_window_t{ 200, sdp::repair_window_units::milliseconds };
nmos::sdp_parameters::fec_t::repair_flow_t second_repair{ 11, U("R2"), { { U("t"), U("3") } }, {}, 1 };
second_repair.repair_window = nmos::sdp_parameters::fec_t::repair_window_t{ 150500, sdp::repair_window_units::microseconds };
sdp_params.fec.push_back({ 0, U("S1"), { first_repair, second_repair } });

const auto sender_transport_params = value_of({
value_of({
{ nmos::fields::source_ip, U("192.0.2.1") },
{ nmos::fields::destination_ip, U("233.252.0.1") },
{ nmos::fields::source_port, 5004 },
{ nmos::fields::destination_port, 30000 },
{ nmos::fields::rtp_enabled, true },
{ nmos::fields::fec_enabled, true },
{ nmos::fields::fec_mode, U("2D") },
{ nmos::fields::fec_destination_ip, U("233.252.0.2") },
{ nmos::fields::fec1D_destination_port, 30002 },
{ nmos::fields::fec2D_destination_port, 30004 }
})
});

const auto session_description = sdp::parse_session_description(sdp::make_session_description(nmos::make_session_description(sdp_params, sender_transport_params)));
const auto& media_descriptions = sdp::fields::media_descriptions(session_description);
BST_REQUIRE_EQUAL(3, media_descriptions.size());

const auto parsed = nmos::parse_session_description(session_description);
BST_REQUIRE_EQUAL(1, parsed.second.size());
BST_CHECK(nmos::fields::fec_enabled(parsed.second.at(0)));
BST_CHECK_EQUAL(U("233.252.0.2"), nmos::fields::fec_destination_ip(parsed.second.at(0)).as_string());
BST_CHECK_EQUAL(30002, nmos::fields::fec1D_destination_port(parsed.second.at(0)).as_integer());
BST_CHECK_EQUAL(30004, nmos::fields::fec2D_destination_port(parsed.second.at(0)).as_integer());
BST_REQUIRE_EQUAL(2, parsed.first.fec.at(0).repair_flows.size());
BST_REQUIRE((bool)parsed.first.fec.at(0).repair_flows.at(1).repair_window);
BST_CHECK_EQUAL(150500, parsed.first.fec.at(0).repair_flows.at(1).repair_window->size);
}

////////////////////////////////////////////////////////////////////////////////////////////
BST_TEST_CASE(testSdpTransportParamsFecUnsupportedAndMalformedGroups)
{
const std::string before = R"(v=0
o=- 0 0 IN IP4 192.0.2.1
s=FEC
t=0 0
)";
const std::string source = R"(m=video 30000 RTP/AVP 100
c=IN IP4 233.252.0.1/127
a=rtpmap:100 MP2T/90000
a=fec-source-flow: id=0
a=mid:S1
)";
const std::string repair1 = R"(m=application 30002 UDP/FEC
c=IN IP4 233.252.0.2/127
a=fec-repair-flow: encoding-id=10
a=mid:R1
)";
const std::string repair2_different_address = R"(m=application 30004 UDP/FEC
c=IN IP4 233.252.0.3/127
a=fec-repair-flow: encoding-id=11
a=mid:R2
)";

// Multiple-source RFC 6364 groups are valid at the sdp/ layer but cannot be represented by IS-05 transport parameters.
const auto unsupported = sdp::parse_session_description(before + "a=group:FEC-FR S1 S2 R1\n" + source + repair1);
const auto unsupported_transport_params = nmos::get_session_description_transport_params(unsupported);
BST_REQUIRE_EQUAL(1, unsupported_transport_params.size());
BST_CHECK(!unsupported_transport_params.at(0).has_field(nmos::fields::fec_enabled));

const auto missing_media = sdp::parse_session_description(before + "a=group:FEC-FR S1 MISSING\n" + source);
BST_REQUIRE_THROW(nmos::get_session_description_transport_params(missing_media), std::runtime_error);

const auto different_addresses = sdp::parse_session_description(before
+ "a=group:FEC-FR S1 R1\n"
+ "a=group:FEC-FR S1 R2\n"
+ source + repair1 + repair2_different_address);
BST_REQUIRE_THROW(nmos::get_session_description_transport_params(different_addresses), std::runtime_error);
}

////////////////////////////////////////////////////////////////////////////////////////////
BST_TEST_CASE(testInterpretationOfSdpFilesSeparateSourceAddresses)
{
Expand Down
36 changes: 36 additions & 0 deletions Development/sdp/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ namespace sdp
const utility::string_t group{ U("group") };
const utility::string_t mid{ U("mid") };

// See https://tools.ietf.org/html/rfc6364
const utility::string_t fec_source_flow{ U("fec-source-flow") };
const utility::string_t fec_repair_flow{ U("fec-repair-flow") };
const utility::string_t repair_window{ U("repair-window") };

// See https://tools.ietf.org/html/rfc7273
const utility::string_t ts_refclk{ U("ts-refclk") };
const utility::string_t mediaclk{ U("mediaclk") };
Expand Down Expand Up @@ -173,6 +178,19 @@ namespace sdp
const web::json::field_as_string semantics{ U("semantics") }; // see sdp::group_semantics
const web::json::field_as_array mids{ U("mids") };

// a=fec-source-flow: id=<source id>[; tag-len=<tag length>]
// a=fec-repair-flow: encoding-id=<encoding id>[; preference-lvl=<preference>][; ss-fssi=<elements>][; fssi=<elements>]
// a=repair-window:<window size><unit>
// See https://tools.ietf.org/html/rfc6364
const web::json::field<uint64_t> source_id{ U("source_id") };
const web::json::field<uint64_t> tag_length{ U("tag_length") };
const web::json::field<uint64_t> encoding_id{ U("encoding_id") };
const web::json::field<uint64_t> preference_level{ U("preference_level") };
const web::json::field_as_array sender_side_scheme_specific{ U("sender_side_scheme_specific") };
const web::json::field_as_array scheme_specific{ U("scheme_specific") };
const web::json::field<uint64_t> window_size{ U("window_size") };
const web::json::field_as_string window_unit{ U("window_unit") }; // see sdp::repair_window_units

// a=source-filter: <filter-mode> <nettype> <address-types> <dest-address> <src-list>
// See https://tools.ietf.org/html/rfc4570
const web::json::field_as_string filter_mode{ U("filter_mode") }; // see sdp::filter_modes
Expand Down Expand Up @@ -283,6 +301,10 @@ namespace sdp
const protocol RTP_SAVP{ U("RTP/SAVP") };
// unspecified protocol running over UDP
const protocol udp{ U("udp") };
// FEC Framework input and repair packet formats
// See https://tools.ietf.org/html/rfc6364#section-4.1
const protocol FEC_UDP{ U("FEC/UDP") };
const protocol UDP_FEC{ U("UDP/FEC") };
}

// Bandwidth Specifiers
Expand Down Expand Up @@ -364,6 +386,20 @@ namespace sdp
{
// See https://tools.ietf.org/html/rfc7104
const group_semantics_type duplication{ U("DUP") };
// See https://tools.ietf.org/html/rfc5956 and https://tools.ietf.org/html/rfc6364
const group_semantics_type fec_fr{ U("FEC-FR") };
}
}

// Forward Error Correction (FEC) Framework Repair Window Units
// See https://tools.ietf.org/html/rfc6364#section-4.6
namespace sdp
{
DEFINE_STRING_ENUM(repair_window_unit)
namespace repair_window_units
{
const repair_window_unit milliseconds{ U("ms") };
const repair_window_unit microseconds{ U("us") };
}
}

Expand Down
Loading