From cf74174dbc0db601474079903110e4461d2bae10 Mon Sep 17 00:00:00 2001 From: Victor Gaydov Date: Tue, 30 Jan 2024 17:53:48 +0400 Subject: [PATCH] gh-608 Implement rtp::parse_encoding() --- src/internal_modules/roc_audio/sample_spec.h | 40 ++++++++++- src/internal_modules/roc_core/parse_units.cpp | 38 +++++----- src/internal_modules/roc_rtp/encoding.cpp | 70 +++++++++++++++++++ src/internal_modules/roc_rtp/encoding.h | 21 ++++++ src/tests/roc_rtp/test_encoding.cpp | 55 +++++++++++++++ 5 files changed, 207 insertions(+), 17 deletions(-) create mode 100644 src/internal_modules/roc_rtp/encoding.cpp create mode 100644 src/tests/roc_rtp/test_encoding.cpp diff --git a/src/internal_modules/roc_audio/sample_spec.h b/src/internal_modules/roc_audio/sample_spec.h index 977220ace..bd09b23f9 100644 --- a/src/internal_modules/roc_audio/sample_spec.h +++ b/src/internal_modules/roc_audio/sample_spec.h @@ -16,6 +16,7 @@ #include "roc_audio/pcm_format.h" #include "roc_audio/sample.h" #include "roc_audio/sample_format.h" +#include "roc_core/attributes.h" #include "roc_core/stddefs.h" #include "roc_core/string_builder.h" #include "roc_core/time.h" @@ -187,7 +188,44 @@ class SampleSpec { }; //! Parse sample spec from string. -bool parse_sample_spec(const char* str, SampleSpec& result); +//! +//! @remarks +//! The input string should have the form: +//! - "//" +//! +//! Where: +//! - "" is string name of sample format (e.g. "s16") +//! - "" is a positive integer +//! - "" can be: "", "", +//! "", "" +//! +//! - "" is a string name of predefined surround channel +//! mask, e.g. "stereo", "surround4.1", etc. +//! - "" is comma-separated list of surround channel names, +//! e.g. "FL,FC,FR" +//! +//! - "" is a 1024-bit hex mask defining which tracks are +//! enabled, e.g. "0xAA00BB00" +//! - "" is a comma-separated list of track numbers +//! or ranges, e.g. "1,2,5-8" +//! +//! Each of the three components ("", "", "") may be set +//! to "-", which means "keep unset". +//! +//! All four forms of "" component are alternative ways to represent a +//! bitmask of enabled channels or tracks. The order of channels does no matter. +//! +//! Examples: +//! - "s16/44100/stereo" +//! - "s18_4le/48000/FL,FC,FR" +//! - "f32/96000/1,2,10-20,31" +//! - "f32/96000/0xA0000000FFFF0000000C" +//! - "-/44100/-" +//! - "-/-/-" +//! +//! @returns +//! false if string can't be parsed. +ROC_ATTR_NODISCARD bool parse_sample_spec(const char* str, SampleSpec& result); //! Format sample spec to string. void format_sample_spec(const SampleSpec& sample_spec, core::StringBuilder& bld); diff --git a/src/internal_modules/roc_core/parse_units.cpp b/src/internal_modules/roc_core/parse_units.cpp index 73b29ba62..cc6c1279f 100644 --- a/src/internal_modules/roc_core/parse_units.cpp +++ b/src/internal_modules/roc_core/parse_units.cpp @@ -52,21 +52,24 @@ bool parse_duration(const char* str, nanoseconds_t& result) { } else if ((suffix = find_suffix(str, str_len, "h"))) { multiplier = Hour; } else { - roc_log(LogError, "parse duration: no known suffix (ns, us, ms, s, m, h)"); + roc_log(LogError, + "parse duration: invalid format: missing suffix, expected" + " , where suffix="); + return false; return false; } if (str == suffix) { roc_log(LogError, - "parse duration: invalid format, missing number, expected " - ""); + "parse duration: invalid format: missing number, expected" + " , where suffix="); return false; } if (!isdigit(*str) && *str != '-') { roc_log(LogError, - "parse duration: invalid format, not a number, expected " - ""); + "parse duration: invalid format: not a number, expected" + " , where suffix="); return false; } @@ -75,16 +78,17 @@ bool parse_duration(const char* str, nanoseconds_t& result) { if ((number == 0. && str == number_end) || !number_end || number_end != suffix) { roc_log(LogError, - "parse duration: invalid format, can't parse number, expected " - ""); + "parse duration: invalid format: not a number, expected" + " , where suffix="); return false; } const double number_multiplied = round(number * (double)multiplier); if (number_multiplied > (double)INT64_MAX) { roc_log(LogError, - "parse duration: too large, can't parse number, expected " - ""); + "parse duration: number out of range:" + " value=%f maximum=%f", + number_multiplied, (double)INT64_MAX); return false; } @@ -116,10 +120,10 @@ bool parse_size(const char* str, size_t& result) { multiplier = kibibyte; } - if (!isdigit(*str) && *str != '-') { + if (!isdigit(*str)) { roc_log(LogError, - "parse size: invalid format, not a number, expected " - "[], where suffix="); + "parse size: invalid format: not a number, expected" + " [], where suffix="); return false; } @@ -129,18 +133,20 @@ bool parse_size(const char* str, size_t& result) { if ((number == 0. && str == number_end) || (!suffix && *number_end != '\0') || (suffix && number_end != suffix)) { roc_log(LogError, - "parse size: invalid format, can't parse number, expected " - "[], where suffix="); + "parse size: invalid format: not a number, expected" + " [], where suffix="); return false; } const double number_multiplied = round(number * (double)multiplier); if (number_multiplied > (double)SIZE_MAX) { roc_log(LogError, - "parse size: too large, can't parse number, expected " - "[], where suffix="); + "parse size: number out of range:" + " value=%f maximum=%f", + number_multiplied, (double)SIZE_MAX); return false; } + result = (size_t)number_multiplied; return true; } diff --git a/src/internal_modules/roc_rtp/encoding.cpp b/src/internal_modules/roc_rtp/encoding.cpp new file mode 100644 index 000000000..1f84f7e1b --- /dev/null +++ b/src/internal_modules/roc_rtp/encoding.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 Roc Streaming authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "roc_rtp/encoding.h" +#include "roc_audio/sample_spec.h" + +namespace roc { +namespace rtp { + +bool parse_encoding(const char* str, Encoding& result) { + result = Encoding(); + + if (str == NULL) { + roc_log(LogError, "parse encoding: string is null"); + return false; + } + + const char* sep = strchr(str, ':'); + if (sep == NULL) { + roc_log(LogError, + "parse encoding: invalid format: missing separator, expected" + " ://"); + return false; + } + + if (!isdigit(*str)) { + roc_log(LogError, + "parse encoding: invalid format: not a number, expected" + " ://"); + return false; + } + + char* number_end = NULL; + const unsigned long number = strtoul(str, &number_end, 10); + + if (number == ULONG_MAX || !number_end || number_end != sep) { + roc_log(LogError, + "parse encoding: invalid format: not a number, expected" + " ://"); + return false; + } + + if (number > UINT_MAX) { + roc_log(LogError, + "parse encoding: number out of range:" + " value=%lu maximum=%u", + number, UINT_MAX); + return false; + } + + if (!audio::parse_sample_spec(sep + 1, result.sample_spec)) { + roc_log(LogError, + "parse encoding: invalid format: invalid spec, expected" + " ://"); + return false; + } + + result.payload_type = (unsigned int)number; + result.packet_flags = packet::Packet::FlagAudio; + + return true; +} + +} // namespace rtp +} // namespace roc diff --git a/src/internal_modules/roc_rtp/encoding.h b/src/internal_modules/roc_rtp/encoding.h index 658dd134a..51575cbef 100644 --- a/src/internal_modules/roc_rtp/encoding.h +++ b/src/internal_modules/roc_rtp/encoding.h @@ -16,6 +16,7 @@ #include "roc_audio/iframe_encoder.h" #include "roc_audio/pcm_format.h" #include "roc_audio/sample_spec.h" +#include "roc_core/attributes.h" #include "roc_core/iarena.h" #include "roc_rtp/headers.h" @@ -51,6 +52,26 @@ struct Encoding { } }; +//! Parse RTP encoding from string. +//! +//! @remarks +//! The input string should have the form: +//! - ":" +//! +//! Where: +//! - "" is payload id, a positive integer +//! - "" is sample spec, in form "//" +//! +//! See audio::parse_sample_spec() for details on "" format. +//! +//! Examples: +//! - "55:s16/44100/stereo" +//! - "77:f32/96000/20-30" +//! +//! @returns +//! false if string can't be parsed. +ROC_ATTR_NODISCARD bool parse_encoding(const char* str, Encoding& result); + } // namespace rtp } // namespace roc diff --git a/src/tests/roc_rtp/test_encoding.cpp b/src/tests/roc_rtp/test_encoding.cpp new file mode 100644 index 000000000..9633b6c11 --- /dev/null +++ b/src/tests/roc_rtp/test_encoding.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 Roc Streaming authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include + +#include "roc_rtp/encoding.h" + +namespace roc { +namespace rtp { + +TEST_GROUP(encoding) {}; + +TEST(encoding, parse) { + Encoding enc; + CHECK(parse_encoding("101:s18/48000/surround4.1", enc)); + + CHECK_EQUAL(101, enc.payload_type); + + CHECK(enc.sample_spec.is_valid()); + CHECK_EQUAL(audio::SampleFormat_Pcm, enc.sample_spec.sample_format()); + CHECK_EQUAL(audio::PcmFormat_SInt18, enc.sample_spec.pcm_format()); + CHECK_EQUAL(48000, enc.sample_spec.sample_rate()); + CHECK_EQUAL(5, enc.sample_spec.num_channels()); + + CHECK_EQUAL(packet::Packet::FlagAudio, enc.packet_flags); + CHECK(enc.new_encoder == NULL); + CHECK(enc.new_decoder == NULL); +} + +TEST(encoding, parse_errors) { + Encoding enc; + + CHECK(!parse_encoding(":s16/44100/stereo", enc)); + CHECK(!parse_encoding("101,s16/44100/stereo", enc)); + CHECK(!parse_encoding("101:", enc)); + CHECK(!parse_encoding("101:s16/44100/bad", enc)); + CHECK(!parse_encoding(":", enc)); + CHECK(!parse_encoding("", enc)); + CHECK(!parse_encoding("::", enc)); + CHECK(!parse_encoding("101::s16/44100/stereo", enc)); + CHECK(!parse_encoding("xxx:s16/44100/stereo", enc)); + CHECK(!parse_encoding("-101:s16/44100/stereo", enc)); + CHECK(!parse_encoding("+101:s16/44100/stereo", enc)); + CHECK(!parse_encoding("101.2:s16/44100/stereo", enc)); + + CHECK(parse_encoding("101:s16/44100/stereo", enc)); +} + +} // namespace rtp +} // namespace roc