Skip to content

Commit

Permalink
roc-streaminggh-608 Implement rtp::parse_encoding()
Browse files Browse the repository at this point in the history
  • Loading branch information
gavv committed Jan 30, 2024
1 parent 26af7d0 commit cf74174
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 17 deletions.
40 changes: 39 additions & 1 deletion src/internal_modules/roc_audio/sample_spec.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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:
//! - "<format>/<rate>/<channels>"
//!
//! Where:
//! - "<format>" is string name of sample format (e.g. "s16")
//! - "<rate>" is a positive integer
//! - "<channels>" can be: "<surround preset>", "<surround channel list>",
//! "<multitrack mask>", "<multitrack channel list>"
//!
//! - "<surround preset>" is a string name of predefined surround channel
//! mask, e.g. "stereo", "surround4.1", etc.
//! - "<surround channel list>" is comma-separated list of surround channel names,
//! e.g. "FL,FC,FR"
//!
//! - "<multitrack mask>" is a 1024-bit hex mask defining which tracks are
//! enabled, e.g. "0xAA00BB00"
//! - "<multitrack channel list>" is a comma-separated list of track numbers
//! or ranges, e.g. "1,2,5-8"
//!
//! Each of the three components ("<format>", "<rate>", "<channels>") may be set
//! to "-", which means "keep unset".
//!
//! All four forms of "<channels>" 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);
Expand Down
38 changes: 22 additions & 16 deletions src/internal_modules/roc_core/parse_units.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
" <float><suffix>, where suffix=<ns|us|ms|s|m|h>");
return false;
return false;
}

if (str == suffix) {
roc_log(LogError,
"parse duration: invalid format, missing number, expected "
"<float><suffix>");
"parse duration: invalid format: missing number, expected"
" <float><suffix>, where suffix=<ns|us|ms|s|m|h>");
return false;
}

if (!isdigit(*str) && *str != '-') {
roc_log(LogError,
"parse duration: invalid format, not a number, expected "
"<float><suffix>");
"parse duration: invalid format: not a number, expected"
" <float><suffix>, where suffix=<ns|us|ms|s|m|h>");
return false;
}

Expand All @@ -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 "
"<float><suffix>");
"parse duration: invalid format: not a number, expected"
" <float><suffix>, where suffix=<ns|us|ms|s|m|h>");
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 "
"<float><suffix>");
"parse duration: number out of range:"
" value=%f maximum=%f",
number_multiplied, (double)INT64_MAX);
return false;
}

Expand Down Expand Up @@ -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 "
"<float>[<suffix>], where suffix=<K|M|G>");
"parse size: invalid format: not a number, expected"
" <float>[<suffix>], where suffix=<K|M|G>");
return false;
}

Expand All @@ -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 "
"<float>[<suffix>], where suffix=<K|M|G>");
"parse size: invalid format: not a number, expected"
" <float>[<suffix>], where suffix=<K|M|G>");
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 "
"<float>[<suffix>], where suffix=<K|M|G>");
"parse size: number out of range:"
" value=%f maximum=%f",
number_multiplied, (double)SIZE_MAX);
return false;
}

result = (size_t)number_multiplied;
return true;
}
Expand Down
70 changes: 70 additions & 0 deletions src/internal_modules/roc_rtp/encoding.cpp
Original file line number Diff line number Diff line change
@@ -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"
" <id>:<format>/<rate>/<channels>");
return false;
}

if (!isdigit(*str)) {
roc_log(LogError,
"parse encoding: invalid format: not a number, expected"
" <id>:<format>/<rate>/<channels>");
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"
" <id>:<format>/<rate>/<channels>");
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"
" <id>:<format>/<rate>/<channels>");
return false;
}

result.payload_type = (unsigned int)number;
result.packet_flags = packet::Packet::FlagAudio;

return true;
}

} // namespace rtp
} // namespace roc
21 changes: 21 additions & 0 deletions src/internal_modules/roc_rtp/encoding.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -51,6 +52,26 @@ struct Encoding {
}
};

//! Parse RTP encoding from string.
//!
//! @remarks
//! The input string should have the form:
//! - "<id>:<spec>"
//!
//! Where:
//! - "<id>" is payload id, a positive integer
//! - "<spec>" is sample spec, in form "<format>/<rate>/<channel>"
//!
//! See audio::parse_sample_spec() for details on "<spec>" 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

Expand Down
55 changes: 55 additions & 0 deletions src/tests/roc_rtp/test_encoding.cpp
Original file line number Diff line number Diff line change
@@ -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 <CppUTest/TestHarness.h>

#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

0 comments on commit cf74174

Please sign in to comment.