From f852f26fc24a763fda5799202590f8a5bff88fe1 Mon Sep 17 00:00:00 2001 From: Martin Piatka Date: Wed, 13 Aug 2025 13:21:36 +0200 Subject: [PATCH 01/12] utils: Add SDP parser --- Makefile.in | 1 + src/utils/sdp_parser.cpp | 110 +++++++++++++++++++++++++++++++++++++++ src/utils/sdp_parser.hpp | 65 +++++++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 src/utils/sdp_parser.cpp create mode 100644 src/utils/sdp_parser.hpp diff --git a/Makefile.in b/Makefile.in index c92f77272..6723a3ded 100644 --- a/Makefile.in +++ b/Makefile.in @@ -107,6 +107,7 @@ COMMON_OBJS = \ src/audio/capture/none.o \ src/audio/capture/sdi.o \ src/audio/capture/testcard.o \ + src/utils/sdp_parser.o \ src/audio/codec.o \ src/audio/codec/dummy_pcm.o \ src/audio/export.o \ diff --git a/src/utils/sdp_parser.cpp b/src/utils/sdp_parser.cpp new file mode 100644 index 000000000..5143e5af9 --- /dev/null +++ b/src/utils/sdp_parser.cpp @@ -0,0 +1,110 @@ +#include "sdp_parser.hpp" +#include "utils/string_view_utils.hpp" + +Sap_packet_view Sap_packet_view::from_buffer(const void *buf, size_t size){ + Sap_packet_view ret; + + std::string_view sap(static_cast(buf), size); + + if(sap.empty()){ + return ret; + } + + ret.flags = sap[0]; + sap.remove_prefix(1); + + ret.version = ret.flags >> 6; + + uint8_t auth_len = 0; + if(sap.empty()) + return ret; + + auth_len = sap[0]; + sap.remove_prefix(1); + + //TODO error check length + ret.hash = sap[0] << 8 | sap[1]; + sap.remove_prefix(2); + + //TODO error check length + ret.source = sap.substr(0, 4); + sap.remove_prefix(4); + + //TODO error check length + sap.remove_prefix(auth_len); + + if(sv_is_prefix(sap, "v=0")){ + //RFC says that the payload type is optional if it's sdp and + //that it should be detected by the presence of v=0 + ret.payload_type = "application/sdp"; + ret.payload = sap; + } else { + auto null_idx = sap.find('\0'); + if(null_idx == sap.npos){ + return ret; + } + ret.payload_type = sap.substr(0, null_idx); + ret.payload = sap.substr(null_idx + 1); + } + + ret.valid = true; + return ret; +} + +Sdp_view Sdp_view::from_buffer(const void *buf, size_t size){ + Sdp_view ret; + + std::string_view sdp(static_cast(buf), size); + + if(sdp.empty()){ + return ret; + } + + while(!sdp.empty()){ + auto line = tokenize(sdp, '\n'); + if(line.empty()) + continue; + + if(line.back() == '\r') + line.remove_suffix(1); + + auto eq_idx = line.find('='); + if(eq_idx == line.npos){ + continue; + } + auto key = line.substr(0, eq_idx); + auto val = line.substr(eq_idx + 1); + + if(key == "c"){ + if(ret.media.empty()){ + ret.connection = val; + } else { + ret.media.back().connection = val; + } + } else if(key == "a"){ + auto attrib = tokenize(val, ':'); + auto attrib_val = tokenize(val, ':'); + + if(ret.media.empty()){ + ret.session_attributes.push_back({attrib, attrib_val}); + } else { + ret.media.back().attributes.push_back({attrib, attrib_val}); + } + + } else if(key =="s"){ + ret.session_name = val; + } else if(key == "o"){ + ret.origin = val; + } else if(key == "m"){ + ret.media.emplace_back(); + auto& medium = ret.media.back(); + + medium.media_desc = val; + } + + } + + ret.valid = true; + + return ret; +} diff --git a/src/utils/sdp_parser.hpp b/src/utils/sdp_parser.hpp new file mode 100644 index 000000000..b9498bb2a --- /dev/null +++ b/src/utils/sdp_parser.hpp @@ -0,0 +1,65 @@ +#ifndef SDP_PARSER_HPP_cccb52235120 +#define SDP_PARSER_HPP_cccb52235120 + +#include +#include +#include + +#define SAP_FLAG_COMPRESSED (1 << 0) +#define SAP_FLAG_ENCRYPTED (1 << 1) +#define SAP_FLAG_DELETION (1 << 2) +#define SAP_FLAG_IPV6 (1 << 4) + + +struct Sap_packet_view{ + uint8_t version; + uint8_t flags; + uint16_t hash; + + std::string_view source; + std::string_view payload_type; + std::string_view payload; + + static Sap_packet_view from_buffer(const void *buf, size_t size); + + bool isValid() const { return valid; } + bool isCompressed() const { return flags & SAP_FLAG_COMPRESSED; } + bool isEncrypted() const { return flags & SAP_FLAG_ENCRYPTED; } + bool isDeletion() const { return flags & SAP_FLAG_DELETION; } + bool isIpv6() const { return flags & SAP_FLAG_IPV6; } + + bool valid = false; + +}; + +struct Sdp_attribute{ + std::string_view key; + std::string_view val; +}; + +struct Sdp_media_desc{ + std::string_view media_desc; + std::string_view title; + std::string_view connection; + std::vector attributes; +}; + +struct Sdp_view{ + std::string_view origin; + std::string_view session_name; + std::string_view session_info; + std::string_view connection; + + std::string_view session_time; + std::vector session_attributes; + + std::vector media; + + static Sdp_view from_buffer(const void *buf, size_t size); + bool isValid() const { return valid; } + + bool valid = false; +}; + + +#endif From 747d4e527a5414e8c9af8d604f964faa63c7adc1 Mon Sep 17 00:00:00 2001 From: Martin Piatka Date: Tue, 29 Jul 2025 14:54:58 +0200 Subject: [PATCH 02/12] utils/sdp_parser: WIP RTP packet parsing --- src/utils/sdp_parser.cpp | 27 +++++++++++++++++++++++++++ src/utils/sdp_parser.hpp | 17 +++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/utils/sdp_parser.cpp b/src/utils/sdp_parser.cpp index 5143e5af9..90b250d6b 100644 --- a/src/utils/sdp_parser.cpp +++ b/src/utils/sdp_parser.cpp @@ -108,3 +108,30 @@ Sdp_view Sdp_view::from_buffer(const void *buf, size_t size){ return ret; } + +Rtp_pkt_view Rtp_pkt_view::from_buffer(void *buf, size_t size){ + Rtp_pkt_view ret{}; + + if(size < 12) + return ret; + + auto charbuf = static_cast(buf); + + uint8_t version = charbuf[0] >> 6; + bool padding = charbuf[0] & (1 << 5); + bool extension = charbuf[0] & (1 << 4); + ret.csrc_count = charbuf[0] & 0x0F; + ret.marker = charbuf[1] & 0x80; + ret.payload_type = charbuf[1] & 0x7F; + ret.seq = charbuf[2] << 8 | charbuf[3]; + ret.timestamp = charbuf[4] << 24 | charbuf[5] << 16 | charbuf[6] << 8 | charbuf[7]; + ret.ssrc = charbuf[8] << 24 | charbuf[9] << 16 | charbuf[10] << 8 | charbuf[11]; + + + size_t data_offset = 12 + ret.csrc_count * 4; + ret.data = &charbuf[data_offset]; + ret.data_len = size - data_offset; + + ret.valid = true; + return ret; +} diff --git a/src/utils/sdp_parser.hpp b/src/utils/sdp_parser.hpp index b9498bb2a..5d150ee2f 100644 --- a/src/utils/sdp_parser.hpp +++ b/src/utils/sdp_parser.hpp @@ -61,5 +61,22 @@ struct Sdp_view{ bool valid = false; }; +struct Rtp_pkt_view{ + bool marker; + uint8_t payload_type; + uint16_t seq; + uint32_t timestamp; + uint32_t ssrc; + //uint32_t *csrcs; + size_t csrc_count; + void *data; + size_t data_len; + + static Rtp_pkt_view from_buffer(void *buf, size_t size); + bool isValid() const { return valid; } + + bool valid = false; +}; + #endif From bf48a751d78c8c04109eb504a646fc6e4ba86fcd Mon Sep 17 00:00:00 2001 From: Martin Piatka Date: Wed, 6 Aug 2025 15:17:14 +0200 Subject: [PATCH 03/12] utils/sdp_parser: Check length --- src/utils/sdp_parser.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils/sdp_parser.cpp b/src/utils/sdp_parser.cpp index 90b250d6b..949fc231f 100644 --- a/src/utils/sdp_parser.cpp +++ b/src/utils/sdp_parser.cpp @@ -6,7 +6,7 @@ Sap_packet_view Sap_packet_view::from_buffer(const void *buf, size_t size){ std::string_view sap(static_cast(buf), size); - if(sap.empty()){ + if(sap.size() < 8){ return ret; } @@ -22,15 +22,15 @@ Sap_packet_view Sap_packet_view::from_buffer(const void *buf, size_t size){ auth_len = sap[0]; sap.remove_prefix(1); - //TODO error check length ret.hash = sap[0] << 8 | sap[1]; sap.remove_prefix(2); - //TODO error check length ret.source = sap.substr(0, 4); sap.remove_prefix(4); - //TODO error check length + if(sap.size() < auth_len) + return ret; + sap.remove_prefix(auth_len); if(sv_is_prefix(sap, "v=0")){ @@ -117,7 +117,7 @@ Rtp_pkt_view Rtp_pkt_view::from_buffer(void *buf, size_t size){ auto charbuf = static_cast(buf); - uint8_t version = charbuf[0] >> 6; + [[maybe_unused]] uint8_t version = charbuf[0] >> 6; bool padding = charbuf[0] & (1 << 5); bool extension = charbuf[0] & (1 << 4); ret.csrc_count = charbuf[0] & 0x0F; From b3a2e2b13e1f502007afd9d46851a6a6454517f8 Mon Sep 17 00:00:00 2001 From: Martin Piatka Date: Tue, 15 Jul 2025 16:43:30 +0200 Subject: [PATCH 04/12] tests: Some SAP parsing tests --- Makefile.in | 1 + test/run_tests.c | 2 + test/test_sdp_parser.cpp | 127 +++++++++++++++++++++++++++++++++++++++ test/test_sdp_parser.h | 1 + 4 files changed, 131 insertions(+) create mode 100644 test/test_sdp_parser.cpp create mode 100644 test/test_sdp_parser.h diff --git a/Makefile.in b/Makefile.in index 6723a3ded..74b3c3456 100644 --- a/Makefile.in +++ b/Makefile.in @@ -225,6 +225,7 @@ TEST_OBJS = $(COMMON_OBJS) \ test/test_tv.o \ test/test_net_udp.o \ test/test_rtp.o \ + test/test_sdp_parser.o \ test/run_tests.o DEP_FILES_2 = $(REFLECTOR_OBJS) $(TEST_OBJS) $(ULTRAGRID_OBJS) diff --git a/test/run_tests.c b/test/run_tests.c index 9ce3c715a..b0e36c177 100644 --- a/test/run_tests.c +++ b/test/run_tests.c @@ -58,6 +58,7 @@ #include "test_rtp.h" #include "test_video_capture.h" #include "test_video_display.h" +#include "test_sdp_parser.h" #define TEST_AV_HW 1 @@ -131,6 +132,7 @@ struct { DEFINE_TEST(misc_test_replace_all), DEFINE_TEST(misc_test_unit_evaluate), DEFINE_TEST(misc_test_video_desc_io_op_symmetry), + DEFINE_TEST(test_sdp_parser), }; static bool test_helper(const char *name, int (*func)(), bool quiet) { diff --git a/test/test_sdp_parser.cpp b/test/test_sdp_parser.cpp new file mode 100644 index 000000000..45940d18a --- /dev/null +++ b/test/test_sdp_parser.cpp @@ -0,0 +1,127 @@ +#include "config.h" +#include "config_unix.h" +#include "config_win32.h" +#include "debug.h" +#include "memory.h" +#include "utils/sdp_parser.hpp" +#include "unit_common.h" +extern "C" { +#include "test_sdp_parser.h" +} + + +unsigned char sap_packet_data[] = {0x20, 0x0, 0xc4, 0xf1, 0xa9, 0xfe, 0xbd, 0xb8, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x64, 0x70, 0x0, 0x76, 0x3d, 0x30, 0xd, 0xa, 0x6f, 0x3d, 0x2d, 0x20, 0x33, 0x36, 0x31, 0x38, 0x39, 0x37, 0x30, 0x20, 0x33, 0x36, 0x31, 0x38, 0x39, 0x37, 0x39, 0x20, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x31, 0x36, 0x39, 0x2e, 0x32, 0x35, 0x34, 0x2e, 0x31, 0x38, 0x39, 0x2e, 0x31, 0x38, 0x34, 0xd, 0xa, 0x73, 0x3d, 0x41, 0x56, 0x49, 0x4f, 0x55, 0x53, 0x42, 0x2d, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x20, 0x3a, 0x20, 0x32, 0xd, 0xa, 0x69, 0x3d, 0x31, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x3a, 0x20, 0x4c, 0x65, 0x66, 0x74, 0xd, 0xa, 0x63, 0x3d, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x32, 0x33, 0x39, 0x2e, 0x36, 0x39, 0x2e, 0x36, 0x39, 0x2e, 0x32, 0x36, 0x2f, 0x33, 0x32, 0xd, 0xa, 0x74, 0x3d, 0x30, 0x20, 0x30, 0xd, 0xa, 0x61, 0x3d, 0x6b, 0x65, 0x79, 0x77, 0x64, 0x73, 0x3a, 0x44, 0x61, 0x6e, 0x74, 0x65, 0xd, 0xa, 0x61, 0x3d, 0x72, 0x65, 0x63, 0x76, 0x6f, 0x6e, 0x6c, 0x79, 0xd, 0xa, 0x6d, 0x3d, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x20, 0x35, 0x30, 0x30, 0x34, 0x20, 0x52, 0x54, 0x50, 0x2f, 0x41, 0x56, 0x50, 0x20, 0x39, 0x36, 0xd, 0xa, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x39, 0x36, 0x20, 0x4c, 0x32, 0x34, 0x2f, 0x34, 0x38, 0x30, 0x30, 0x30, 0x2f, 0x31, 0xd, 0xa, 0x61, 0x3d, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x3a, 0x31, 0xd, 0xa, 0x61, 0x3d, 0x74, 0x73, 0x2d, 0x72, 0x65, 0x66, 0x63, 0x6c, 0x6b, 0x3a, 0x70, 0x74, 0x70, 0x3d, 0x49, 0x45, 0x45, 0x45, 0x31, 0x35, 0x38, 0x38, 0x2d, 0x32, 0x30, 0x30, 0x38, 0x3a, 0x30, 0x30, 0x2d, 0x31, 0x44, 0x2d, 0x43, 0x31, 0x2d, 0x46, 0x46, 0x2d, 0x46, 0x45, 0x2d, 0x41, 0x31, 0x2d, 0x42, 0x38, 0x2d, 0x42, 0x43, 0x3a, 0x30, 0xd, 0xa, 0x61, 0x3d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x63, 0x6c, 0x6b, 0x3a, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x30, 0xd, 0xa}; + +unsigned char sap_packet_with_auth_data[] = {0x20, 0x3, 0x1, 0x2, 0x3, 0xc4, 0xf1, 0xa9, 0xfe, 0xbd, 0xb8, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x64, 0x70, 0x0, 0x76, 0x3d, 0x30, 0xd, 0xa, 0x6f, 0x3d, 0x2d, 0x20, 0x33, 0x36, 0x31, 0x38, 0x39, 0x37, 0x30, 0x20, 0x33, 0x36, 0x31, 0x38, 0x39, 0x37, 0x39, 0x20, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x31, 0x36, 0x39, 0x2e, 0x32, 0x35, 0x34, 0x2e, 0x31, 0x38, 0x39, 0x2e, 0x31, 0x38, 0x34, 0xd, 0xa, 0x73, 0x3d, 0x41, 0x56, 0x49, 0x4f, 0x55, 0x53, 0x42, 0x2d, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x20, 0x3a, 0x20, 0x32, 0xd, 0xa, 0x69, 0x3d, 0x31, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x3a, 0x20, 0x4c, 0x65, 0x66, 0x74, 0xd, 0xa, 0x63, 0x3d, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x32, 0x33, 0x39, 0x2e, 0x36, 0x39, 0x2e, 0x36, 0x39, 0x2e, 0x32, 0x36, 0x2f, 0x33, 0x32, 0xd, 0xa, 0x74, 0x3d, 0x30, 0x20, 0x30, 0xd, 0xa, 0x61, 0x3d, 0x6b, 0x65, 0x79, 0x77, 0x64, 0x73, 0x3a, 0x44, 0x61, 0x6e, 0x74, 0x65, 0xd, 0xa, 0x61, 0x3d, 0x72, 0x65, 0x63, 0x76, 0x6f, 0x6e, 0x6c, 0x79, 0xd, 0xa, 0x6d, 0x3d, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x20, 0x35, 0x30, 0x30, 0x34, 0x20, 0x52, 0x54, 0x50, 0x2f, 0x41, 0x56, 0x50, 0x20, 0x39, 0x36, 0xd, 0xa, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x39, 0x36, 0x20, 0x4c, 0x32, 0x34, 0x2f, 0x34, 0x38, 0x30, 0x30, 0x30, 0x2f, 0x31, 0xd, 0xa, 0x61, 0x3d, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x3a, 0x31, 0xd, 0xa, 0x61, 0x3d, 0x74, 0x73, 0x2d, 0x72, 0x65, 0x66, 0x63, 0x6c, 0x6b, 0x3a, 0x70, 0x74, 0x70, 0x3d, 0x49, 0x45, 0x45, 0x45, 0x31, 0x35, 0x38, 0x38, 0x2d, 0x32, 0x30, 0x30, 0x38, 0x3a, 0x30, 0x30, 0x2d, 0x31, 0x44, 0x2d, 0x43, 0x31, 0x2d, 0x46, 0x46, 0x2d, 0x46, 0x45, 0x2d, 0x41, 0x31, 0x2d, 0x42, 0x38, 0x2d, 0x42, 0x43, 0x3a, 0x30, 0xd, 0xa, 0x61, 0x3d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x63, 0x6c, 0x6b, 0x3a, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x30, 0xd, 0xa}; + +unsigned char sap_packet_2channels_data[] = {0x20, 0x0, 0x17, 0xcd, 0xa9, 0xfe, 0xbd, 0xb8, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x64, 0x70, 0x0, 0x76, 0x3d, 0x30, 0xd, 0xa, 0x6f, 0x3d, 0x2d, 0x20, 0x35, 0x31, 0x37, 0x33, 0x32, 0x32, 0x20, 0x35, 0x31, 0x37, 0x33, 0x32, 0x32, 0x20, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x31, 0x36, 0x39, 0x2e, 0x32, 0x35, 0x34, 0x2e, 0x31, 0x38, 0x39, 0x2e, 0x31, 0x38, 0x34, 0xd, 0xa, 0x73, 0x3d, 0x41, 0x56, 0x49, 0x4f, 0x55, 0x53, 0x42, 0x2d, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x20, 0x3a, 0x20, 0x32, 0xd, 0xa, 0x69, 0x3d, 0x32, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x3a, 0x20, 0x4c, 0x65, 0x66, 0x74, 0x2c, 0x20, 0x52, 0x69, 0x67, 0x68, 0x74, 0xd, 0xa, 0x63, 0x3d, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x32, 0x33, 0x39, 0x2e, 0x36, 0x39, 0x2e, 0x32, 0x34, 0x37, 0x2e, 0x32, 0x35, 0x31, 0x2f, 0x33, 0x32, 0xd, 0xa, 0x74, 0x3d, 0x30, 0x20, 0x30, 0xd, 0xa, 0x61, 0x3d, 0x6b, 0x65, 0x79, 0x77, 0x64, 0x73, 0x3a, 0x44, 0x61, 0x6e, 0x74, 0x65, 0xd, 0xa, 0x61, 0x3d, 0x72, 0x65, 0x63, 0x76, 0x6f, 0x6e, 0x6c, 0x79, 0xd, 0xa, 0x6d, 0x3d, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x20, 0x35, 0x30, 0x30, 0x34, 0x20, 0x52, 0x54, 0x50, 0x2f, 0x41, 0x56, 0x50, 0x20, 0x39, 0x37, 0xd, 0xa, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x39, 0x37, 0x20, 0x4c, 0x32, 0x34, 0x2f, 0x34, 0x38, 0x30, 0x30, 0x30, 0x2f, 0x32, 0xd, 0xa, 0x61, 0x3d, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x3a, 0x31, 0xd, 0xa, 0x61, 0x3d, 0x74, 0x73, 0x2d, 0x72, 0x65, 0x66, 0x63, 0x6c, 0x6b, 0x3a, 0x70, 0x74, 0x70, 0x3d, 0x49, 0x45, 0x45, 0x45, 0x31, 0x35, 0x38, 0x38, 0x2d, 0x32, 0x30, 0x30, 0x38, 0x3a, 0x30, 0x30, 0x2d, 0x31, 0x44, 0x2d, 0x43, 0x31, 0x2d, 0x46, 0x46, 0x2d, 0x46, 0x45, 0x2d, 0x41, 0x31, 0x2d, 0x42, 0x38, 0x2d, 0x42, 0x43, 0x3a, 0x30, 0xd, 0xa, 0x61, 0x3d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x63, 0x6c, 0x6b, 0x3a, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x30, 0xd, 0xa}; + +unsigned char sap_packet_no_payload_type[] = {0x20, 0x0, 0x17, 0xcd, 0xa9, 0xfe, 0xbd, 0xb8, 0x76, 0x3d, 0x30, 0xd, 0xa, 0x6f, 0x3d, 0x2d, 0x20, 0x35, 0x31, 0x37, 0x33, 0x32, 0x32, 0x20, 0x35, 0x31, 0x37, 0x33, 0x32, 0x32, 0x20, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x31, 0x36, 0x39, 0x2e, 0x32, 0x35, 0x34, 0x2e, 0x31, 0x38, 0x39, 0x2e, 0x31, 0x38, 0x34, 0xd, 0xa, 0x73, 0x3d, 0x41, 0x56, 0x49, 0x4f, 0x55, 0x53, 0x42, 0x2d, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x20, 0x3a, 0x20, 0x32, 0xd, 0xa, 0x69, 0x3d, 0x32, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x3a, 0x20, 0x4c, 0x65, 0x66, 0x74, 0x2c, 0x20, 0x52, 0x69, 0x67, 0x68, 0x74, 0xd, 0xa, 0x63, 0x3d, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x32, 0x33, 0x39, 0x2e, 0x36, 0x39, 0x2e, 0x32, 0x34, 0x37, 0x2e, 0x32, 0x35, 0x31, 0x2f, 0x33, 0x32, 0xd, 0xa, 0x74, 0x3d, 0x30, 0x20, 0x30, 0xd, 0xa, 0x61, 0x3d, 0x6b, 0x65, 0x79, 0x77, 0x64, 0x73, 0x3a, 0x44, 0x61, 0x6e, 0x74, 0x65, 0xd, 0xa, 0x61, 0x3d, 0x72, 0x65, 0x63, 0x76, 0x6f, 0x6e, 0x6c, 0x79, 0xd, 0xa, 0x6d, 0x3d, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x20, 0x35, 0x30, 0x30, 0x34, 0x20, 0x52, 0x54, 0x50, 0x2f, 0x41, 0x56, 0x50, 0x20, 0x39, 0x37, 0xd, 0xa, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x39, 0x37, 0x20, 0x4c, 0x32, 0x34, 0x2f, 0x34, 0x38, 0x30, 0x30, 0x30, 0x2f, 0x32, 0xd, 0xa, 0x61, 0x3d, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x3a, 0x31, 0xd, 0xa, 0x61, 0x3d, 0x74, 0x73, 0x2d, 0x72, 0x65, 0x66, 0x63, 0x6c, 0x6b, 0x3a, 0x70, 0x74, 0x70, 0x3d, 0x49, 0x45, 0x45, 0x45, 0x31, 0x35, 0x38, 0x38, 0x2d, 0x32, 0x30, 0x30, 0x38, 0x3a, 0x30, 0x30, 0x2d, 0x31, 0x44, 0x2d, 0x43, 0x31, 0x2d, 0x46, 0x46, 0x2d, 0x46, 0x45, 0x2d, 0x41, 0x31, 0x2d, 0x42, 0x38, 0x2d, 0x42, 0x43, 0x3a, 0x30, 0xd, 0xa, 0x61, 0x3d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x63, 0x6c, 0x6b, 0x3a, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x30, 0xd, 0xa}; + +unsigned char sap_packet_2channels_deletion_data[] = {0x24, 0x0, 0x17, 0xcd, 0xa9, 0xfe, 0xbd, 0xb8, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x64, 0x70, 0x0, 0x76, 0x3d, 0x30, 0xd, 0xa, 0x6f, 0x3d, 0x2d, 0x20, 0x35, 0x31, 0x37, 0x33, 0x32, 0x32, 0x20, 0x35, 0x31, 0x37, 0x33, 0x32, 0x32, 0x20, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x31, 0x36, 0x39, 0x2e, 0x32, 0x35, 0x34, 0x2e, 0x31, 0x38, 0x39, 0x2e, 0x31, 0x38, 0x34, 0xd, 0xa, 0x73, 0x3d, 0x41, 0x56, 0x49, 0x4f, 0x55, 0x53, 0x42, 0x2d, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x20, 0x3a, 0x20, 0x32, 0xd, 0xa, 0x69, 0x3d, 0x32, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x3a, 0x20, 0x4c, 0x65, 0x66, 0x74, 0x2c, 0x20, 0x52, 0x69, 0x67, 0x68, 0x74, 0xd, 0xa, 0x63, 0x3d, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x32, 0x33, 0x39, 0x2e, 0x36, 0x39, 0x2e, 0x32, 0x34, 0x37, 0x2e, 0x32, 0x35, 0x31, 0x2f, 0x33, 0x32, 0xd, 0xa, 0x74, 0x3d, 0x30, 0x20, 0x30, 0xd, 0xa, 0x61, 0x3d, 0x6b, 0x65, 0x79, 0x77, 0x64, 0x73, 0x3a, 0x44, 0x61, 0x6e, 0x74, 0x65, 0xd, 0xa, 0x61, 0x3d, 0x72, 0x65, 0x63, 0x76, 0x6f, 0x6e, 0x6c, 0x79, 0xd, 0xa, 0x6d, 0x3d, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x20, 0x35, 0x30, 0x30, 0x34, 0x20, 0x52, 0x54, 0x50, 0x2f, 0x41, 0x56, 0x50, 0x20, 0x39, 0x37, 0xd, 0xa, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x39, 0x37, 0x20, 0x4c, 0x32, 0x34, 0x2f, 0x34, 0x38, 0x30, 0x30, 0x30, 0x2f, 0x32, 0xd, 0xa, 0x61, 0x3d, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x3a, 0x31, 0xd, 0xa, 0x61, 0x3d, 0x74, 0x73, 0x2d, 0x72, 0x65, 0x66, 0x63, 0x6c, 0x6b, 0x3a, 0x70, 0x74, 0x70, 0x3d, 0x49, 0x45, 0x45, 0x45, 0x31, 0x35, 0x38, 0x38, 0x2d, 0x32, 0x30, 0x30, 0x38, 0x3a, 0x30, 0x30, 0x2d, 0x31, 0x44, 0x2d, 0x43, 0x31, 0x2d, 0x46, 0x46, 0x2d, 0x46, 0x45, 0x2d, 0x41, 0x31, 0x2d, 0x42, 0x38, 0x2d, 0x42, 0x43, 0x3a, 0x30, 0xd, 0xa, 0x61, 0x3d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x63, 0x6c, 0x6b, 0x3a, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x30, 0xd, 0xa}; + +#define TEST_CASE(fun)\ + do { \ + int ret = fun(); \ + if(ret){ \ + printf(#fun " failed\n"); \ + return ret; \ + } \ + } while(0) + + +static int test_sap(){ + Sap_packet_view sap = Sap_packet_view::from_buffer(sap_packet_data, sizeof(sap_packet_data)); + + ASSERT(sap.isValid()); + ASSERT(!sap.isCompressed()); + ASSERT(!sap.isEncrypted()); + ASSERT(!sap.isIpv6()); + ASSERT(!sap.isDeletion()); + ASSERT_EQUAL("application/sdp", sap.payload_type); + ASSERT_EQUAL(291, sap.payload.size()); + + return 0; +} + +static int test_sap_with_auth_header(){ + Sap_packet_view sap = Sap_packet_view::from_buffer(sap_packet_with_auth_data, sizeof(sap_packet_with_auth_data)); + + ASSERT(sap.isValid()); + ASSERT(!sap.isCompressed()); + ASSERT(!sap.isEncrypted()); + ASSERT(!sap.isIpv6()); + ASSERT(!sap.isDeletion()); + ASSERT_EQUAL("application/sdp", sap.payload_type); + ASSERT_EQUAL(291, sap.payload.size()); + + return 0; +} + +static int test_sap_empty(){ + Sap_packet_view sap = Sap_packet_view::from_buffer("", 0); + + ASSERT(!sap.isValid()); + + return 0; +} + +static int test_sap_deletion_flag(){ + Sap_packet_view sap = Sap_packet_view::from_buffer(sap_packet_2channels_deletion_data, sizeof(sap_packet_2channels_deletion_data)); + + ASSERT(sap.isValid()); + ASSERT(sap.isDeletion()); + ASSERT_EQUAL(298, sap.payload.size()); + + return 0; +} + +static int test_sap_no_payload_type(){ + Sap_packet_view sap = Sap_packet_view::from_buffer(sap_packet_no_payload_type, sizeof(sap_packet_no_payload_type)); + + ASSERT(sap.isValid()); + ASSERT_EQUAL("application/sdp", sap.payload_type); + ASSERT_EQUAL(298, sap.payload.size()); + + return 0; +} + + +static int test_sdp_empty(){ + Sdp_view sdp = Sdp_view::from_buffer("", 0); + + ASSERT(!sdp.isValid()); + + return 0; +} + +static int test_sdp_basic(){ + Sap_packet_view sap = Sap_packet_view::from_buffer(sap_packet_no_payload_type, sizeof(sap_packet_no_payload_type)); + Sdp_view sdp = Sdp_view::from_buffer(sap.payload.data(), sap.payload.size()); + + ASSERT(sdp.isValid()); + ASSERT_EQUAL("- 517322 517322 IN IP4 169.254.189.184", sdp.origin); + ASSERT_EQUAL("AVIOUSB-xxxxxx : 2", sdp.session_name); + ASSERT_EQUAL(1, sdp.media.size()); + ASSERT_EQUAL("", sdp.media[0].connection); + ASSERT_EQUAL("IN IP4 239.69.247.251/32", sdp.connection); + ASSERT_EQUAL("audio 5004 RTP/AVP 97", sdp.media[0].media_desc); + ASSERT_EQUAL(4, sdp.media[0].attributes.size()); + ASSERT_EQUAL("rtpmap", sdp.media[0].attributes[0].key); + ASSERT_EQUAL("97 L24/48000/2", sdp.media[0].attributes[0].val); + + return 0; +} + +int test_sdp_parser(void){ + TEST_CASE(test_sap); + TEST_CASE(test_sap_empty); + TEST_CASE(test_sap_deletion_flag); + TEST_CASE(test_sap_with_auth_header); + TEST_CASE(test_sap_no_payload_type); + + TEST_CASE(test_sdp_empty); + TEST_CASE(test_sdp_basic); + + return 0; +} diff --git a/test/test_sdp_parser.h b/test/test_sdp_parser.h new file mode 100644 index 000000000..09d6b4fa6 --- /dev/null +++ b/test/test_sdp_parser.h @@ -0,0 +1 @@ +int test_sdp_parser(void); From f54e8ab699a6b69542d9205ae818365202a180b9 Mon Sep 17 00:00:00 2001 From: Martin Piatka Date: Wed, 6 Aug 2025 16:08:39 +0200 Subject: [PATCH 05/12] utils/sdp_parser: Support padding in rtp packets --- src/utils/sdp_parser.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/utils/sdp_parser.cpp b/src/utils/sdp_parser.cpp index 949fc231f..8496d56fe 100644 --- a/src/utils/sdp_parser.cpp +++ b/src/utils/sdp_parser.cpp @@ -132,6 +132,17 @@ Rtp_pkt_view Rtp_pkt_view::from_buffer(void *buf, size_t size){ ret.data = &charbuf[data_offset]; ret.data_len = size - data_offset; + if(padding){ + if(ret.data_len == 0) + return ret; + + uint8_t padding_bytes = ((uint8_t *)(ret.data))[ret.data_len - 1]; + if(padding_bytes < ret.data_len) + return ret; + + ret.data_len -= padding_bytes; + } + ret.valid = true; return ret; } From c5a98e0f024438dc1fb30eb5a25ecb8fc326f253 Mon Sep 17 00:00:00 2001 From: Martin Piatka Date: Wed, 13 Aug 2025 13:27:54 +0200 Subject: [PATCH 06/12] utils/sdp_parser: Parse the origin into fields --- src/utils/sdp_parser.cpp | 15 ++++++++++++++- src/utils/sdp_parser.hpp | 8 +++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/utils/sdp_parser.cpp b/src/utils/sdp_parser.cpp index 8496d56fe..edd2d49e3 100644 --- a/src/utils/sdp_parser.cpp +++ b/src/utils/sdp_parser.cpp @@ -94,7 +94,20 @@ Sdp_view Sdp_view::from_buffer(const void *buf, size_t size){ } else if(key =="s"){ ret.session_name = val; } else if(key == "o"){ - ret.origin = val; + auto origin_sv = val; + ret.username = tokenize(origin_sv, ' '); + auto sess_id_sv = tokenize(origin_sv, ' '); + auto sess_ver_sv = tokenize(origin_sv, ' '); + if(!parse_num(sess_id_sv, ret.sess_id)){ + return ret; + } + if(!parse_num(sess_ver_sv, ret.sess_version)){ + return ret; + } + + ret.nettype = tokenize(origin_sv, ' '); + ret.addrtype = tokenize(origin_sv, ' '); + ret.unicast_addr = tokenize(origin_sv, ' '); } else if(key == "m"){ ret.media.emplace_back(); auto& medium = ret.media.back(); diff --git a/src/utils/sdp_parser.hpp b/src/utils/sdp_parser.hpp index 5d150ee2f..3dac00a1c 100644 --- a/src/utils/sdp_parser.hpp +++ b/src/utils/sdp_parser.hpp @@ -45,7 +45,13 @@ struct Sdp_media_desc{ }; struct Sdp_view{ - std::string_view origin; + std::string_view username; + uint64_t sess_id; + uint64_t sess_version; + std::string_view nettype; + std::string_view addrtype; + std::string_view unicast_addr; + std::string_view session_name; std::string_view session_info; std::string_view connection; From 5165b16989efb11daf503160ab0dda3cab111e3c Mon Sep 17 00:00:00 2001 From: Martin Piatka Date: Wed, 13 Aug 2025 13:28:14 +0200 Subject: [PATCH 07/12] tests/sdp_parser: Test session origin fields --- test/test_sdp_parser.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/test_sdp_parser.cpp b/test/test_sdp_parser.cpp index 45940d18a..98345102d 100644 --- a/test/test_sdp_parser.cpp +++ b/test/test_sdp_parser.cpp @@ -100,7 +100,12 @@ static int test_sdp_basic(){ Sdp_view sdp = Sdp_view::from_buffer(sap.payload.data(), sap.payload.size()); ASSERT(sdp.isValid()); - ASSERT_EQUAL("- 517322 517322 IN IP4 169.254.189.184", sdp.origin); + ASSERT_EQUAL("-", sdp.username); + ASSERT_EQUAL(517322, sdp.sess_id); + ASSERT_EQUAL(517322, sdp.sess_version); + ASSERT_EQUAL("IN", sdp.nettype); + ASSERT_EQUAL("IP4", sdp.addrtype); + ASSERT_EQUAL("169.254.189.184", sdp.unicast_addr); ASSERT_EQUAL("AVIOUSB-xxxxxx : 2", sdp.session_name); ASSERT_EQUAL(1, sdp.media.size()); ASSERT_EQUAL("", sdp.media[0].connection); From a1d7a353073982d1113c8398f1f21f7b677c39fb Mon Sep 17 00:00:00 2001 From: Martin Piatka Date: Thu, 10 Jul 2025 16:30:36 +0200 Subject: [PATCH 08/12] Add AES67 capture --- Makefile.in | 1 + src/audio/capture/aes67.cpp | 581 ++++++++++++++++++++++++++++++++++++ 2 files changed, 582 insertions(+) create mode 100644 src/audio/capture/aes67.cpp diff --git a/Makefile.in b/Makefile.in index 74b3c3456..8c907f7b8 100644 --- a/Makefile.in +++ b/Makefile.in @@ -108,6 +108,7 @@ COMMON_OBJS = \ src/audio/capture/sdi.o \ src/audio/capture/testcard.o \ src/utils/sdp_parser.o \ + src/audio/capture/aes67.o \ src/audio/codec.o \ src/audio/codec/dummy_pcm.o \ src/audio/export.o \ diff --git a/src/audio/capture/aes67.cpp b/src/audio/capture/aes67.cpp new file mode 100644 index 000000000..27c51e1cd --- /dev/null +++ b/src/audio/capture/aes67.cpp @@ -0,0 +1,581 @@ +/** + * @file audio/capture/aes67.cpp + * @author Martin Piatka + * + */ +/* + * Copyright (c) 2025 CESNET + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include // for uint32_t +#include // for free, NULL, malloc +#include // for printf +#include // for strcmp + +#include +#include +#include +#include +#include +#include +#include + +#include "audio/audio_capture.h" // for AUDIO_CAPTURE_ABI_VERSION, audio_ca... +#include "audio/types.h" // for audio_frame +#include "host.h" // for INIT_NOERR +#include "lib_common.h" // for REGISTER_MODULE, library_class +#include "tv.h" // for get_time_in_ns, time_ns_t, NS_TO_US +#include "types.h" // for kHz48, device_info (ptr only) +#include "debug.h" + +#include "rtp/net_udp.h" + +#include "utils/color_out.h" +#include "utils/string_view_utils.hpp" +#include "utils/sdp_parser.hpp" +#include "crypto/crc.h" + +#define MOD_NAME "[aes67 acap] " + +#define MAX_PACKET_LEN 9000 + +namespace{ + +struct Rtp_stream{ + std::string name; + std::string description; + std::map fmts; + + std::string address; + int port; +}; + +using sess_id_t = uint32_t; + +struct Sap_session{ + sess_id_t unique_identifier; //Hash computed from sdp username, session id and unicast address + uint64_t sess_ver; + uint16_t sap_hash; //Hash of sap packet that contained sdp for this version of session + std::string name; + std::string description; + + std::vector streams; +}; + +struct Allocated_audio_frame{ + Allocated_audio_frame() = default; + Allocated_audio_frame(const audio_desc& desc){ + frame.ch_count = desc.ch_count; + frame.bps = desc.bps; + frame.sample_rate = desc.sample_rate; + + frame.max_size = desc.bps * desc.ch_count * desc.sample_rate; + data.reserve(frame.max_size); + frame.data = data.data(); + } + + bool is_desc_same(const audio_desc& desc){ + return frame.ch_count == desc.ch_count + && frame.bps == desc.bps + && frame.sample_rate == desc.sample_rate; + } + + audio_frame frame{}; + std::vector data; +}; + +} //anon namespace + +struct state_aes67_cap { + std::string network_interface_name; + std::string sap_address; + int sap_port; + std::string requested_sess_hash; + unsigned req_stream_idx = 0; + + std::atomic sdp_should_run = true; + std::thread sdp_thread; + socket_udp *sdp_sock = nullptr; + int curr_sap_hash = -1; + std::map sap_hash_to_sess_id_map; + std::map sap_sessions; + + std::atomic rtp_should_run = true; + std::thread rtp_thread; + + std::mutex frame_mut; + std::condition_variable frame_cond; + Allocated_audio_frame front_frame; + Allocated_audio_frame back_frame; + +}; + +static audio_desc sdp_fmt_to_audio_desc(std::string_view fmt_sv){ + auto codec_sv = tokenize(fmt_sv, '/'); + auto rate_sv = tokenize(fmt_sv, '/'); + auto ch_n_sv = tokenize(fmt_sv, '/'); + + audio_desc fmt{}; + if(codec_sv == "L16"){ + fmt.codec = AC_PCM; + fmt.bps = 2; + } else if(codec_sv == "L24"){ + fmt.codec = AC_PCM; + fmt.bps = 3; + } + parse_num(rate_sv, fmt.sample_rate); + if(!parse_num(ch_n_sv, fmt.ch_count)){ + //channel count is optional + fmt.ch_count = 1; + } + + return fmt; +} + +static std::string_view sdp_connection_get_address(std::string_view conn){ + [[maybe_unused]] auto nettype = tokenize(conn, ' '); + [[maybe_unused]] auto addrtype = tokenize(conn, ' '); + auto addr_with_tll = tokenize(conn, ' '); + + return tokenize(addr_with_tll, '/'); +} + +static void print_sap_session(const Sap_session& sess){ + color_printf(TBOLD("Session " TGREEN("%x")) " \"%s\" (%s):\n", sess.unique_identifier, + std::string(sess.name).c_str(), + std::string(sess.description).c_str()); + int i = 0; + for(const auto& stream : sess.streams){ + color_printf("\t" TBOLD("Stream %d") " \"%s\" (%s:%d)\n", i++, std::string(stream.name).c_str(), std::string(stream.address).c_str(), stream.port); + color_printf("\t\tFormats:\n"); + for(const auto& fmt : stream.fmts){ + color_printf("\t\t\t%d: %d bps, %d channels, %d rate\n", + fmt.first, + fmt.second.bps, + fmt.second.ch_count, + fmt.second.sample_rate); + } + } +} + +static void aes67_rtp_worker(state_aes67_cap *s, Rtp_stream stream){ + log_msg(LOG_LEVEL_NOTICE, MOD_NAME "RTP Worker starting (%s:%d)\n", stream.address.c_str(), stream.port); + + auto rtp_sock = udp_init_if(stream.address.c_str(), s->network_interface_name.c_str(), stream.port, 0, 255, 0, false); + + int last_payload_type = 0; + audio_desc desc{}; + + using namespace std::literals::chrono_literals; + while(s->rtp_should_run){ + int buflen = 0; + uint8_t buffer[MAX_PACKET_LEN]; + timeval timeout {1, 0}; + buflen = udp_recv_timeout(rtp_sock, (char *)buffer, MAX_PACKET_LEN, &timeout); + + if(!buflen){ + continue; + } + + + Rtp_pkt_view rtp_pkt = Rtp_pkt_view::from_buffer(buffer, buflen); + if(!rtp_pkt.isValid()){ + log_msg(LOG_LEVEL_WARNING, MOD_NAME "Invalid RTP packet\n"); + continue; + } + + log_msg(LOG_LEVEL_DEBUG, MOD_NAME "RTP Got packet len %ld, seq %u, timestamp %u, PT %u\n", + rtp_pkt.data_len, + rtp_pkt.seq, + rtp_pkt.timestamp, + rtp_pkt.payload_type); + + std::lock_guard lk(s->frame_mut); + + if(rtp_pkt.payload_type != last_payload_type){ + auto fmt_it = stream.fmts.find(rtp_pkt.payload_type); + if(fmt_it == stream.fmts.end()){ + log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unknown payload type\n"); + continue; + } + desc = fmt_it->second; + log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Reconfigured\n"); + last_payload_type = rtp_pkt.payload_type; + } + if(!s->back_frame.is_desc_same(desc)){ + s->back_frame = Allocated_audio_frame(desc); + } + + int sample_count = rtp_pkt.data_len / desc.bps; + + unsigned char *src = static_cast(rtp_pkt.data); + + const int bps = desc.bps; + const int swap_count = bps / 2; + for(int i = 0; i < sample_count; i++){ + for(int j = 0; j < swap_count; j++){ + unsigned char tmp = src[i * bps + j]; + src[i * bps + j] = src[i * bps + bps - j - 1]; + src[i * bps + bps - j - 1] = tmp;; + } + } + + size_t to_write = std::min(s->back_frame.frame.max_size - s->back_frame.data.size(), sample_count * desc.bps); + s->back_frame.data.insert(s->back_frame.data.end(), src, src + to_write); + s->back_frame.frame.data_len = s->back_frame.data.size(); + s->frame_cond.notify_one(); + } + + udp_exit(rtp_sock); + + log_msg(LOG_LEVEL_NOTICE, MOD_NAME "RTP Worker stopping\n"); +} + +static sess_id_t get_unique_sdp_identifier(const Sdp_view& sdp){ + auto hash = crc32buf(sdp.username.data(), sdp.username.size()); + hash = crc32buf_with_oldcrc(reinterpret_cast(&sdp.sess_id), sizeof(sdp.sess_id), hash); + hash = crc32buf_with_oldcrc(sdp.unicast_addr.data(), sdp.unicast_addr.size(), hash); + + return hash; +} + +static bool sess_hash_is_prefix(std::string_view req, sess_id_t sess_id){ + char buf[sizeof(sess_id_t) * 2 + 1] = ""; + snprintf(buf, sizeof(buf), "%x", sess_id); + + return sv_is_prefix(buf, req); +} + +static void stop_rtp_thread(state_aes67_cap *s){ + s->rtp_should_run = false; + log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Joining rtp thread\n"); + s->rtp_thread.join(); + log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Joined\n"); + s->curr_sap_hash = -1; +} + +static void start_rtp_thread(state_aes67_cap *s, const Sap_session& new_sess){ + if(s->curr_sap_hash != -1){ + log_msg(LOG_LEVEL_WARNING, MOD_NAME "RTP thread is already running\n"); + stop_rtp_thread(s); + } + + if(new_sess.streams.size() <= s->req_stream_idx){ + log_msg(LOG_LEVEL_ERROR, MOD_NAME "Requested stream index %d but session only has %ld stream(s)!\n", s->req_stream_idx, new_sess.streams.size()); + return; + } + + s->rtp_should_run = true; + s->curr_sap_hash = new_sess.sap_hash; + s->rtp_thread = std::thread(aes67_rtp_worker, s, new_sess.streams[s->req_stream_idx]); +} + +static void parse_sap(state_aes67_cap *s, std::string_view sap){ + Sap_packet_view pkt = Sap_packet_view::from_buffer(sap.data(), sap.size()); + + if(!pkt.isValid()){ + log_msg(LOG_LEVEL_WARNING, MOD_NAME "Invalid SDP packet\n"); + return; + } + + if(pkt.isCompressed() || pkt.isEncrypted()){ + log_msg(LOG_LEVEL_WARNING, MOD_NAME "Compressed or encrypted SAP packets are not supported\n"); + return; + } + if(pkt.isIpv6()){ + log_msg(LOG_LEVEL_WARNING, MOD_NAME "IPv6 SAP packets are not supported\n"); + return; + } + + if(s->sap_hash_to_sess_id_map.find(pkt.hash) != s->sap_hash_to_sess_id_map.end()){ + if(pkt.isDeletion()){ + uint64_t sess_id = s->sap_hash_to_sess_id_map[pkt.hash]; + auto& sess = s->sap_sessions[sess_id]; + log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Removing session %x\n", sess.unique_identifier); + if(s->curr_sap_hash == sess.sap_hash){ + stop_rtp_thread(s); + } + s->sap_sessions.erase(sess_id); + } else { + log_msg(LOG_LEVEL_INFO, MOD_NAME "SAP with hash %x already known\n", pkt.hash); + } + + return; + } + + log_msg(LOG_LEVEL_NOTICE, MOD_NAME "New SAP %x\n", pkt.hash); + + log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Source %u.%u.%u.%u\n", + (unsigned char) pkt.source[0], + (unsigned char) pkt.source[1], + (unsigned char) pkt.source[2], + (unsigned char) pkt.source[3]); + + if(pkt.payload_type != "application/sdp"){ + log_msg(LOG_LEVEL_WARNING, MOD_NAME "Unknown SAP payload type \"%s\"\n", std::string(pkt.payload_type).c_str()); + return; + } + + Sdp_view sdp = Sdp_view::from_buffer(pkt.payload.data(), pkt.payload.size()); + + if(!sdp.isValid()){ + log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to parse SDP\n"); + return; + } + + Sap_session new_sess{}; + new_sess.unique_identifier = get_unique_sdp_identifier(sdp); + new_sess.sess_ver = sdp.sess_version; + new_sess.sap_hash = pkt.hash; + new_sess.name = sdp.session_name; + new_sess.description = sdp.session_info; + + std::map session_fmts; + + for(const auto& sess_attrib : sdp.session_attributes){ + if(sess_attrib.key == "rtpmap"){ + auto val = sess_attrib.val; + auto id_sv = tokenize(val, ' '); + auto fmt_sv = tokenize(val, ' '); + uint8_t id = 0; + parse_num(id_sv, id); + + auto fmt = sdp_fmt_to_audio_desc(fmt_sv); + + session_fmts[id] = fmt; + } + } + + std::string sess_addr(sdp_connection_get_address(sdp.connection)); + + for(const auto& medium : sdp.media){ + Rtp_stream new_stream{}; + new_stream.fmts = session_fmts; + new_stream.address = sess_addr; + new_stream.name = medium.title; + new_stream.description = medium.media_desc; + + auto media_addr = sdp_connection_get_address(medium.connection); + if(!media_addr.empty()){ + new_stream.address = media_addr; + } + + for(const auto& m_attrib : medium.attributes){ + if(m_attrib.key == "rtpmap"){ + auto val = m_attrib.val; + auto id_sv = tokenize(val, ' '); + auto fmt_sv = tokenize(val, ' '); + uint8_t id = 0; + if(!parse_num(id_sv, id)){ + log_msg(LOG_LEVEL_DEBUG, "Failed to parse rtpmap attribute \"%s\"\n", std::string(m_attrib.val).c_str()); + continue; + } + + auto fmt = sdp_fmt_to_audio_desc(fmt_sv); + + new_stream.fmts[id] = fmt; + } + } + + auto m_desc = medium.media_desc; + [[maybe_unused]] auto m_type = tokenize(m_desc, ' '); + auto m_port = tokenize(m_desc, ' '); + if(!parse_num(m_port, new_stream.port)){ + log_msg(LOG_LEVEL_DEBUG, "Failed to parse stream port from media description \"%s\"\n", std::string(medium.media_desc).c_str()); + continue; + } + + new_sess.streams.push_back(std::move(new_stream)); + } + + s->sap_hash_to_sess_id_map[new_sess.sap_hash] = new_sess.unique_identifier; + + if(auto it = s->sap_sessions.find(new_sess.unique_identifier); it != s->sap_sessions.end()){ + if(new_sess.sess_ver > it->second.sess_ver){ + log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Got session update\n"); + + if(it->second.sap_hash == s->curr_sap_hash){ + stop_rtp_thread(s); + start_rtp_thread(s, new_sess); + } + + s->sap_sessions[new_sess.unique_identifier] = std::move(new_sess); + } else { + log_msg(LOG_LEVEL_WARNING, MOD_NAME "Got SAP with lower session version\n"); + } + + return; + } + + print_sap_session(new_sess); + if(s->curr_sap_hash == -1 && (sess_hash_is_prefix(s->requested_sess_hash, new_sess.unique_identifier) || s->requested_sess_hash == "any")){ + start_rtp_thread(s, new_sess); + } + s->sap_sessions[new_sess.unique_identifier] = std::move(new_sess); +} + +static void aes67_sdp_worker(state_aes67_cap *s){ + s->sdp_sock = udp_init_if(s->sap_address.c_str(), s->network_interface_name.c_str(), s->sap_port, 0, 255, 0, false); + + while(s->sdp_should_run){ + int buflen = 0; + uint8_t buffer[MAX_PACKET_LEN]; + timeval timeout {1, 0}; + buflen = udp_recv_timeout(s->sdp_sock, (char *)buffer, MAX_PACKET_LEN, &timeout); + + if(!buflen) + continue; + + parse_sap(s, std::string_view((const char *) buffer, buflen)); + + } + + udp_exit(s->sdp_sock); + + log_msg(LOG_LEVEL_NOTICE, MOD_NAME "SDP worker stopping\n"); +} + +static void audio_cap_aes67_probe(struct device_info **available_devices, + int *count, + void (**deleter)(void *)) +{ + *deleter = free; + *available_devices = nullptr; + *count = 0; +} + +static void audio_cap_aes67_help(state_aes67_cap *s){ + color_printf("AES67 audio capture.\n"); + color_printf("Usage\n"); + color_printf(TERM_BOLD TERM_FG_RED "\t-s aes67" TERM_FG_RESET ":if=[:sess=][:stream=][:sap_address=][:sap_port=]\n" TERM_RESET); + color_printf(TERM_BOLD "\t\tif=" TERM_RESET " network interface to listen on\n"); + color_printf(TERM_BOLD "\t\tsess=" TERM_RESET " hash of the session to receive. If not specified first seen session is received\n"); + color_printf(TERM_BOLD "\t\tstream=" TERM_RESET " index of stream in a session to receive. If not specified stream 0 is received\n"); + color_printf(TERM_BOLD "\t\tsap_address=" TERM_RESET " multicast IP for SAP (default 239.255.255.255)\n"); + color_printf(TERM_BOLD "\t\tsap_port=" TERM_RESET " port for SAP (default 9875)\n"); + + color_printf("\n"); + color_printf("Waiting for SAP:\n"); + + s->requested_sess_hash = "none"; + s->sdp_thread = std::thread(aes67_sdp_worker, s); + using namespace std::literals::chrono_literals; + std::this_thread::sleep_for(31s); //aes67 supposedly announces every 30 seconds + s->sdp_should_run = false; + s->sdp_thread.join(); +} + +static void *audio_cap_aes67_init(struct module *parent, const char *cfg) { + UNUSED(parent); + auto s = std::make_unique(); + s->sap_address = "239.255.255.255"; + s->sap_port = 9875; + + std::string_view cfg_sv(cfg); + while(!cfg_sv.empty()){ + auto tok = tokenize(cfg_sv, ':', '"'); + + auto key = tokenize(tok, '='); + auto val = tokenize(tok, '='); + + if(key == "if"){ + s->network_interface_name = val; + } else if (key == "sess"){ + s->requested_sess_hash = val; + } else if (key == "sap_ip"){ + s->sap_address = val; + } else if (key == "sap_port"){ + if(!parse_num(val, s->sap_port)){ + log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to parse value for option %s\n", std::string(key).c_str()); + return {}; + } + } else if (key == "stream"){ + if(!parse_num(val, s->req_stream_idx)){ + log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to parse value for option %s\n", std::string(key).c_str()); + return {}; + } + } else if(key == "help"){ + audio_cap_aes67_help(s.get()); + return INIT_NOERR; + } + } + + if(s->network_interface_name.empty()){ + log_msg(LOG_LEVEL_ERROR, MOD_NAME "Network interface must be specified\n"); + return {}; + } + + s->sdp_thread = std::thread(aes67_sdp_worker, s.get()); + + return s.release(); +} + +static struct audio_frame *audio_cap_aes67_read(void *state){ + auto s = static_cast(state); + + s->front_frame.data.clear(); + s->front_frame.frame.data_len = 0; + + using namespace std::literals::chrono_literals; + + std::unique_lock lk(s->frame_mut); + + if(!s->frame_cond.wait_for(lk, 500ms, [&]{ return s->back_frame.frame.data_len > 0; })){ + return nullptr; + } + + std::swap(s->back_frame, s->front_frame); + + return &s->front_frame.frame; +} + +static void audio_cap_aes67_done(void *state) +{ + auto s = std::unique_ptr(static_cast(state)); + s->sdp_should_run = false; + if(s->sdp_thread.joinable()) + s->sdp_thread.join(); + + s->rtp_should_run = false; + if(s->rtp_thread.joinable()) + s->rtp_thread.join(); + +} + +static const struct audio_capture_info acap_aes67_info = { + audio_cap_aes67_probe, + audio_cap_aes67_init, + audio_cap_aes67_read, + audio_cap_aes67_done +}; + +REGISTER_MODULE(aes67, &acap_aes67_info, LIBRARY_CLASS_AUDIO_CAPTURE, AUDIO_CAPTURE_ABI_VERSION); From 75ad082e6110844d9e3425f9ceedfedf6af22323 Mon Sep 17 00:00:00 2001 From: Martin Piatka Date: Tue, 12 Aug 2025 15:20:41 +0200 Subject: [PATCH 09/12] acap/aes67: Factor creating a Sap_session from SDP into own function --- src/audio/capture/aes67.cpp | 106 ++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 54 deletions(-) diff --git a/src/audio/capture/aes67.cpp b/src/audio/capture/aes67.cpp index 27c51e1cd..8ad8d2e71 100644 --- a/src/audio/capture/aes67.cpp +++ b/src/audio/capture/aes67.cpp @@ -299,63 +299,10 @@ static void start_rtp_thread(state_aes67_cap *s, const Sap_session& new_sess){ s->rtp_thread = std::thread(aes67_rtp_worker, s, new_sess.streams[s->req_stream_idx]); } -static void parse_sap(state_aes67_cap *s, std::string_view sap){ - Sap_packet_view pkt = Sap_packet_view::from_buffer(sap.data(), sap.size()); - - if(!pkt.isValid()){ - log_msg(LOG_LEVEL_WARNING, MOD_NAME "Invalid SDP packet\n"); - return; - } - - if(pkt.isCompressed() || pkt.isEncrypted()){ - log_msg(LOG_LEVEL_WARNING, MOD_NAME "Compressed or encrypted SAP packets are not supported\n"); - return; - } - if(pkt.isIpv6()){ - log_msg(LOG_LEVEL_WARNING, MOD_NAME "IPv6 SAP packets are not supported\n"); - return; - } - - if(s->sap_hash_to_sess_id_map.find(pkt.hash) != s->sap_hash_to_sess_id_map.end()){ - if(pkt.isDeletion()){ - uint64_t sess_id = s->sap_hash_to_sess_id_map[pkt.hash]; - auto& sess = s->sap_sessions[sess_id]; - log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Removing session %x\n", sess.unique_identifier); - if(s->curr_sap_hash == sess.sap_hash){ - stop_rtp_thread(s); - } - s->sap_sessions.erase(sess_id); - } else { - log_msg(LOG_LEVEL_INFO, MOD_NAME "SAP with hash %x already known\n", pkt.hash); - } - - return; - } - - log_msg(LOG_LEVEL_NOTICE, MOD_NAME "New SAP %x\n", pkt.hash); - - log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Source %u.%u.%u.%u\n", - (unsigned char) pkt.source[0], - (unsigned char) pkt.source[1], - (unsigned char) pkt.source[2], - (unsigned char) pkt.source[3]); - - if(pkt.payload_type != "application/sdp"){ - log_msg(LOG_LEVEL_WARNING, MOD_NAME "Unknown SAP payload type \"%s\"\n", std::string(pkt.payload_type).c_str()); - return; - } - - Sdp_view sdp = Sdp_view::from_buffer(pkt.payload.data(), pkt.payload.size()); - - if(!sdp.isValid()){ - log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to parse SDP\n"); - return; - } - +static Sap_session sap_session_from_sdp(const Sdp_view& sdp){ Sap_session new_sess{}; new_sess.unique_identifier = get_unique_sdp_identifier(sdp); new_sess.sess_ver = sdp.sess_version; - new_sess.sap_hash = pkt.hash; new_sess.name = sdp.session_name; new_sess.description = sdp.session_info; @@ -417,6 +364,57 @@ static void parse_sap(state_aes67_cap *s, std::string_view sap){ new_sess.streams.push_back(std::move(new_stream)); } + return new_sess; +} + +static void parse_sap(state_aes67_cap *s, std::string_view sap){ + Sap_packet_view pkt = Sap_packet_view::from_buffer(sap.data(), sap.size()); + + if(!pkt.isValid()){ + log_msg(LOG_LEVEL_WARNING, MOD_NAME "Invalid SDP packet\n"); + return; + } + + if(pkt.isCompressed() || pkt.isEncrypted()){ + log_msg(LOG_LEVEL_WARNING, MOD_NAME "Compressed or encrypted SAP packets are not supported\n"); + return; + } + if(pkt.isIpv6()){ + log_msg(LOG_LEVEL_WARNING, MOD_NAME "IPv6 SAP packets are not supported\n"); + return; + } + + if(s->sap_hash_to_sess_id_map.find(pkt.hash) != s->sap_hash_to_sess_id_map.end()){ + if(pkt.isDeletion()){ + uint64_t sess_id = s->sap_hash_to_sess_id_map[pkt.hash]; + auto& sess = s->sap_sessions[sess_id]; + log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Removing session %x\n", sess.unique_identifier); + if(s->curr_sap_hash == sess.sap_hash){ + stop_rtp_thread(s); + } + s->sap_sessions.erase(sess_id); + } else { + log_msg(LOG_LEVEL_INFO, MOD_NAME "SAP with hash %x already known\n", pkt.hash); + } + + return; + } + + if(pkt.payload_type != "application/sdp"){ + log_msg(LOG_LEVEL_WARNING, MOD_NAME "Unknown SAP payload type \"%s\"\n", std::string(pkt.payload_type).c_str()); + return; + } + + Sdp_view sdp = Sdp_view::from_buffer(pkt.payload.data(), pkt.payload.size()); + + if(!sdp.isValid()){ + log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to parse SDP\n"); + return; + } + + auto new_sess = sap_session_from_sdp(sdp); + new_sess.sap_hash = pkt.hash; + s->sap_hash_to_sess_id_map[new_sess.sap_hash] = new_sess.unique_identifier; if(auto it = s->sap_sessions.find(new_sess.unique_identifier); it != s->sap_sessions.end()){ From f3e0e996290dc32a8d9fc1062265420a880811a5 Mon Sep 17 00:00:00 2001 From: Martin Piatka Date: Tue, 12 Aug 2025 15:45:40 +0200 Subject: [PATCH 10/12] acap/aes67: Improve help text --- src/audio/capture/aes67.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/audio/capture/aes67.cpp b/src/audio/capture/aes67.cpp index 8ad8d2e71..6bcfd4c70 100644 --- a/src/audio/capture/aes67.cpp +++ b/src/audio/capture/aes67.cpp @@ -430,15 +430,15 @@ static void parse_sap(state_aes67_cap *s, std::string_view sap){ } else { log_msg(LOG_LEVEL_WARNING, MOD_NAME "Got SAP with lower session version\n"); } - - return; - } - - print_sap_session(new_sess); - if(s->curr_sap_hash == -1 && (sess_hash_is_prefix(s->requested_sess_hash, new_sess.unique_identifier) || s->requested_sess_hash == "any")){ - start_rtp_thread(s, new_sess); + } else { + print_sap_session(new_sess); + if(s->curr_sap_hash == -1 + && (sess_hash_is_prefix(s->requested_sess_hash, new_sess.unique_identifier) || s->requested_sess_hash == "any")) + { + start_rtp_thread(s, new_sess); + } + s->sap_sessions[new_sess.unique_identifier] = std::move(new_sess); } - s->sap_sessions[new_sess.unique_identifier] = std::move(new_sess); } static void aes67_sdp_worker(state_aes67_cap *s){ @@ -480,8 +480,13 @@ static void audio_cap_aes67_help(state_aes67_cap *s){ color_printf(TERM_BOLD "\t\tstream=" TERM_RESET " index of stream in a session to receive. If not specified stream 0 is received\n"); color_printf(TERM_BOLD "\t\tsap_address=" TERM_RESET " multicast IP for SAP (default 239.255.255.255)\n"); color_printf(TERM_BOLD "\t\tsap_port=" TERM_RESET " port for SAP (default 9875)\n"); - color_printf("\n"); + + if(s->network_interface_name.empty()){ + color_printf(TERM_BOLD TERM_FG_RED "To get available sessions run " TERM_FG_RESET "\"-s aes67:if=:help\"\n\n"); + return; + } + color_printf("Waiting for SAP:\n"); s->requested_sess_hash = "none"; From f5d9014f59d08315edc26de51332e00d8d102801 Mon Sep 17 00:00:00 2001 From: Martin Piatka Date: Wed, 13 Aug 2025 16:27:02 +0200 Subject: [PATCH 11/12] tests: Some tests for Rtp_view --- test/test_sdp_parser.cpp | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/test_sdp_parser.cpp b/test/test_sdp_parser.cpp index 98345102d..983bfdb59 100644 --- a/test/test_sdp_parser.cpp +++ b/test/test_sdp_parser.cpp @@ -20,6 +20,14 @@ unsigned char sap_packet_no_payload_type[] = {0x20, 0x0, 0x17, 0xcd, 0xa9, 0xfe, unsigned char sap_packet_2channels_deletion_data[] = {0x24, 0x0, 0x17, 0xcd, 0xa9, 0xfe, 0xbd, 0xb8, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x64, 0x70, 0x0, 0x76, 0x3d, 0x30, 0xd, 0xa, 0x6f, 0x3d, 0x2d, 0x20, 0x35, 0x31, 0x37, 0x33, 0x32, 0x32, 0x20, 0x35, 0x31, 0x37, 0x33, 0x32, 0x32, 0x20, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x31, 0x36, 0x39, 0x2e, 0x32, 0x35, 0x34, 0x2e, 0x31, 0x38, 0x39, 0x2e, 0x31, 0x38, 0x34, 0xd, 0xa, 0x73, 0x3d, 0x41, 0x56, 0x49, 0x4f, 0x55, 0x53, 0x42, 0x2d, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x20, 0x3a, 0x20, 0x32, 0xd, 0xa, 0x69, 0x3d, 0x32, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x3a, 0x20, 0x4c, 0x65, 0x66, 0x74, 0x2c, 0x20, 0x52, 0x69, 0x67, 0x68, 0x74, 0xd, 0xa, 0x63, 0x3d, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x32, 0x33, 0x39, 0x2e, 0x36, 0x39, 0x2e, 0x32, 0x34, 0x37, 0x2e, 0x32, 0x35, 0x31, 0x2f, 0x33, 0x32, 0xd, 0xa, 0x74, 0x3d, 0x30, 0x20, 0x30, 0xd, 0xa, 0x61, 0x3d, 0x6b, 0x65, 0x79, 0x77, 0x64, 0x73, 0x3a, 0x44, 0x61, 0x6e, 0x74, 0x65, 0xd, 0xa, 0x61, 0x3d, 0x72, 0x65, 0x63, 0x76, 0x6f, 0x6e, 0x6c, 0x79, 0xd, 0xa, 0x6d, 0x3d, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x20, 0x35, 0x30, 0x30, 0x34, 0x20, 0x52, 0x54, 0x50, 0x2f, 0x41, 0x56, 0x50, 0x20, 0x39, 0x37, 0xd, 0xa, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x39, 0x37, 0x20, 0x4c, 0x32, 0x34, 0x2f, 0x34, 0x38, 0x30, 0x30, 0x30, 0x2f, 0x32, 0xd, 0xa, 0x61, 0x3d, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x3a, 0x31, 0xd, 0xa, 0x61, 0x3d, 0x74, 0x73, 0x2d, 0x72, 0x65, 0x66, 0x63, 0x6c, 0x6b, 0x3a, 0x70, 0x74, 0x70, 0x3d, 0x49, 0x45, 0x45, 0x45, 0x31, 0x35, 0x38, 0x38, 0x2d, 0x32, 0x30, 0x30, 0x38, 0x3a, 0x30, 0x30, 0x2d, 0x31, 0x44, 0x2d, 0x43, 0x31, 0x2d, 0x46, 0x46, 0x2d, 0x46, 0x45, 0x2d, 0x41, 0x31, 0x2d, 0x42, 0x38, 0x2d, 0x42, 0x43, 0x3a, 0x30, 0xd, 0xa, 0x61, 0x3d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x63, 0x6c, 0x6b, 0x3a, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x30, 0xd, 0xa}; +unsigned char rtp_packet[] = {0x80, 0x60, 0xdc, 0xf2, 0x0, 0xd, 0x32, 0x93, 0xc8, 0x15, 0x45, 0x61, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; + +unsigned char rtp_packet_padding[] = {0xa0, 0x60, 0xdc, 0xf2, 0x0, 0xd, 0x32, 0x93, 0xc8, 0x15, 0x45, 0x61, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbb, 0x02}; + + +//padding size > payload size +unsigned char rtp_packet_padding_invalid[] = {0xa0, 0x60, 0xdc, 0xf2, 0x0, 0xd, 0x32, 0x93, 0xc8, 0x15, 0x45, 0x61, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbb, 0xff}; + #define TEST_CASE(fun)\ do { \ int ret = fun(); \ @@ -118,6 +126,44 @@ static int test_sdp_basic(){ return 0; } +static int test_rtp_basic(){ + Rtp_pkt_view rtp = Rtp_pkt_view::from_buffer(rtp_packet, sizeof(rtp_packet)); + + ASSERT(rtp.isValid()); + ASSERT(!rtp.marker); + ASSERT_EQUAL(96, rtp.payload_type); + ASSERT_EQUAL(56562, rtp.seq); + ASSERT_EQUAL(864915, rtp.timestamp); + ASSERT_EQUAL(0xc8154561, rtp.ssrc); + ASSERT_EQUAL(0, rtp.csrc_count); + ASSERT_EQUAL(144, rtp.data_len); + + return 0; +} + +static int test_rtp_padding(){ + Rtp_pkt_view rtp = Rtp_pkt_view::from_buffer(rtp_packet_padding, sizeof(rtp_packet_padding)); + + ASSERT(rtp.isValid()); + ASSERT(!rtp.marker); + ASSERT_EQUAL(96, rtp.payload_type); + ASSERT_EQUAL(56562, rtp.seq); + ASSERT_EQUAL(864915, rtp.timestamp); + ASSERT_EQUAL(0xc8154561, rtp.ssrc); + ASSERT_EQUAL(0, rtp.csrc_count); + ASSERT_EQUAL(144, rtp.data_len); + + return 0; +} + +static int test_rtp_padding_invalid(){ + Rtp_pkt_view rtp = Rtp_pkt_view::from_buffer(rtp_packet_padding_invalid, sizeof(rtp_packet_padding_invalid)); + + ASSERT(!rtp.isValid()); + + return 0; +} + int test_sdp_parser(void){ TEST_CASE(test_sap); TEST_CASE(test_sap_empty); @@ -128,5 +174,9 @@ int test_sdp_parser(void){ TEST_CASE(test_sdp_empty); TEST_CASE(test_sdp_basic); + TEST_CASE(test_rtp_basic); + TEST_CASE(test_rtp_padding); + TEST_CASE(test_rtp_padding_invalid); + return 0; } From 22983964dddac6c7fb96b66024fd6bc84631f193 Mon Sep 17 00:00:00 2001 From: Martin Piatka Date: Wed, 13 Aug 2025 16:27:46 +0200 Subject: [PATCH 12/12] utils/sdp_parser: Fix rtp padding handling --- src/utils/sdp_parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/sdp_parser.cpp b/src/utils/sdp_parser.cpp index edd2d49e3..b1a1e7af8 100644 --- a/src/utils/sdp_parser.cpp +++ b/src/utils/sdp_parser.cpp @@ -150,7 +150,7 @@ Rtp_pkt_view Rtp_pkt_view::from_buffer(void *buf, size_t size){ return ret; uint8_t padding_bytes = ((uint8_t *)(ret.data))[ret.data_len - 1]; - if(padding_bytes < ret.data_len) + if(padding_bytes > ret.data_len) return ret; ret.data_len -= padding_bytes;