From 8a2c33365c8b60a7121fc861916538fe00e25739 Mon Sep 17 00:00:00 2001 From: Victor Gaydov Date: Wed, 29 Nov 2023 13:56:52 +0400 Subject: [PATCH] gh-608 Initial implementation of sample spec parser --- .../roc_audio/channel_defs.cpp | 20 -- src/internal_modules/roc_audio/channel_defs.h | 6 - .../roc_audio/channel_set.cpp | 8 + src/internal_modules/roc_audio/channel_set.h | 6 + .../roc_audio/channel_set_format.cpp | 11 +- .../roc_audio/sample_spec.cpp | 5 + src/internal_modules/roc_audio/sample_spec.h | 10 + .../roc_audio/sample_spec_format.cpp | 23 ++ .../roc_audio/sample_spec_parse.rl | 271 +++++++++++++++++ .../roc_audio/sample_spec_to_str.cpp | 7 +- .../roc_core/string_builder.cpp | 2 +- src/tests/roc_audio/test_channel_set.cpp | 50 +++- src/tests/roc_audio/test_sample_spec.cpp | 283 ++++++++++++++++++ src/tests/roc_core/test_string_builder.cpp | 2 +- 14 files changed, 648 insertions(+), 56 deletions(-) create mode 100644 src/internal_modules/roc_audio/sample_spec_format.cpp create mode 100644 src/internal_modules/roc_audio/sample_spec_parse.rl diff --git a/src/internal_modules/roc_audio/channel_defs.cpp b/src/internal_modules/roc_audio/channel_defs.cpp index 98ebf7530..2d57d256f 100644 --- a/src/internal_modules/roc_audio/channel_defs.cpp +++ b/src/internal_modules/roc_audio/channel_defs.cpp @@ -56,16 +56,6 @@ const char* channel_pos_to_str(ChannelPosition pos) { return NULL; } -ChannelPosition channel_pos_from_str(const char* name) { - for (size_t i = 0; i < ROC_ARRAY_SIZE(ChanPositionNames); i++) { - if (strcmp(ChanPositionNames[i].name, name) == 0) { - return ChanPositionNames[i].pos; - } - } - - return ChanPos_Max; -} - const char* channel_mask_to_str(ChannelMask mask) { for (size_t i = 0; i < ROC_ARRAY_SIZE(ChanMaskNames); i++) { if (ChanMaskNames[i].mask == mask) { @@ -76,15 +66,5 @@ const char* channel_mask_to_str(ChannelMask mask) { return NULL; } -ChannelMask channel_mask_from_str(const char* name) { - for (size_t i = 0; i < ROC_ARRAY_SIZE(ChanMaskNames); i++) { - if (strcmp(ChanMaskNames[i].name, name) == 0) { - return ChanMaskNames[i].mask; - } - } - - return 0; -} - } // namespace audio } // namespace roc diff --git a/src/internal_modules/roc_audio/channel_defs.h b/src/internal_modules/roc_audio/channel_defs.h index 0a7c18f81..8c6feaa9c 100644 --- a/src/internal_modules/roc_audio/channel_defs.h +++ b/src/internal_modules/roc_audio/channel_defs.h @@ -388,15 +388,9 @@ const char* channel_order_to_str(ChannelOrder); //! Get string name from channel position. const char* channel_pos_to_str(ChannelPosition); -//! Get channel position from string name. -ChannelPosition channel_pos_from_str(const char*); - //! Get string name from channel mask. const char* channel_mask_to_str(ChannelMask); -//! Get channel mask from string name. -ChannelMask channel_mask_from_str(const char*); - } // namespace audio } // namespace roc diff --git a/src/internal_modules/roc_audio/channel_set.cpp b/src/internal_modules/roc_audio/channel_set.cpp index 28aa67661..719c948b7 100644 --- a/src/internal_modules/roc_audio/channel_set.cpp +++ b/src/internal_modules/roc_audio/channel_set.cpp @@ -153,6 +153,14 @@ size_t ChannelSet::last_channel() const { return last_chan_; } +bool ChannelSet::is_equal(ChannelMask mask) const { + if (last_chan_ >= WordBits) { + return false; + } + + return (words_[0] == mask); +} + bool ChannelSet::is_subset(ChannelMask mask) const { if (last_chan_ >= WordBits) { return false; diff --git a/src/internal_modules/roc_audio/channel_set.h b/src/internal_modules/roc_audio/channel_set.h index 3529b5f6d..5d14bd3ad 100644 --- a/src/internal_modules/roc_audio/channel_set.h +++ b/src/internal_modules/roc_audio/channel_set.h @@ -83,6 +83,12 @@ class ChannelSet { //! Panics if there are no enabled channels. size_t last_channel() const; + //! Check if channel set is equal to given mask. + //! @remarks + //! The mask defines only first 32 channels. If any channels outside of 0-31 + //! range are enabled in channel set, the method will fail. + bool is_equal(ChannelMask mask) const; + //! Check if channel set is sub-set of given mask, or equal to it. //! @remarks //! The mask defines only first 32 channels. If any channels outside of 0-31 diff --git a/src/internal_modules/roc_audio/channel_set_format.cpp b/src/internal_modules/roc_audio/channel_set_format.cpp index 3082f6722..02b2e9ec2 100644 --- a/src/internal_modules/roc_audio/channel_set_format.cpp +++ b/src/internal_modules/roc_audio/channel_set_format.cpp @@ -39,27 +39,26 @@ void format_channel_set(const ChannelSet& ch_set, core::StringBuilder& bld) { } else { bld.append_str(" ch=0x"); - // Find last non-zero byte. size_t last_byte = 0; + for (size_t n = 0; n < ch_set.num_bytes(); n++) { if (ch_set.byte_at(n) != 0) { last_byte = n; } } - // Format bitmask from LSB to MSB in hex. - for (size_t n = 0; n <= last_byte; n++) { + size_t n = last_byte; + do { const uint8_t byte = ch_set.byte_at(n); const uint8_t lo = (byte & 0xf); const uint8_t hi = ((byte >> 4) & 0xf); - bld.append_uint(lo, 16); - if (hi != 0 || n != last_byte) { bld.append_uint(hi, 16); } - } + bld.append_uint(lo, 16); + } while (n-- != 0); } } diff --git a/src/internal_modules/roc_audio/sample_spec.cpp b/src/internal_modules/roc_audio/sample_spec.cpp index 7950f60b0..78799bf76 100644 --- a/src/internal_modules/roc_audio/sample_spec.cpp +++ b/src/internal_modules/roc_audio/sample_spec.cpp @@ -97,6 +97,11 @@ bool SampleSpec::is_valid() const { return sample_rate_ != 0 && channel_set_.is_valid(); } +void SampleSpec::clear() { + sample_rate_ = 0; + channel_set_.clear(); +} + size_t SampleSpec::sample_rate() const { return sample_rate_; } diff --git a/src/internal_modules/roc_audio/sample_spec.h b/src/internal_modules/roc_audio/sample_spec.h index 97f29fe8c..e962c32d6 100644 --- a/src/internal_modules/roc_audio/sample_spec.h +++ b/src/internal_modules/roc_audio/sample_spec.h @@ -15,6 +15,7 @@ #include "roc_audio/channel_set.h" #include "roc_audio/sample.h" #include "roc_core/stddefs.h" +#include "roc_core/string_builder.h" #include "roc_core/time.h" #include "roc_packet/units.h" @@ -57,6 +58,9 @@ class SampleSpec { //! Check if sample spec has non-zero rate and valid channel set. bool is_valid() const; + //! Unset all fields. + void clear(); + //! Get sample rate. //! @remarks //! Defines sample frequency (number of samples per second). @@ -148,6 +152,12 @@ class SampleSpec { ChannelSet channel_set_; }; +//! Parse sample spec from string. +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); + } // namespace audio } // namespace roc diff --git a/src/internal_modules/roc_audio/sample_spec_format.cpp b/src/internal_modules/roc_audio/sample_spec_format.cpp new file mode 100644 index 000000000..a08271faf --- /dev/null +++ b/src/internal_modules/roc_audio/sample_spec_format.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 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_audio/sample_spec.h" + +namespace roc { +namespace audio { + +void format_sample_spec(const SampleSpec& sample_spec, core::StringBuilder& bld) { + bld.append_str(""); +} + +} // namespace audio +} // namespace roc diff --git a/src/internal_modules/roc_audio/sample_spec_parse.rl b/src/internal_modules/roc_audio/sample_spec_parse.rl new file mode 100644 index 000000000..ce7ddfc70 --- /dev/null +++ b/src/internal_modules/roc_audio/sample_spec_parse.rl @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2023 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_audio/channel_tables.h" +#include "roc_audio/sample_spec.h" +#include "roc_core/log.h" +#include "roc_core/macro_helpers.h" +#include "roc_core/panic.h" + +namespace roc { +namespace audio { + +%%{ + machine parse_sample_spec; + write data; +}%% + +namespace { + +bool parse_size_t(const char* str, size_t str_len, size_t& result) { + char* str_end = NULL; + long num = strtol(str, &str_end, 10); + + if (num == LONG_MAX || num == LONG_MIN || str_end != str + str_len) { + return false; + } + if (num < 0 || (uint64_t)num > (uint64_t)ROC_MAX_OF(size_t)) { + return false; + } + + result = (size_t)num; + return true; +} + +bool parse_surround_channel(const char* str, size_t str_len, ChannelPosition& result) { + for (size_t i = 0; i < ROC_ARRAY_SIZE(ChanPositionNames); i++) { + if (strlen(ChanPositionNames[i].name) == str_len && + memcmp(ChanPositionNames[i].name, str, str_len) == 0) { + result = ChanPositionNames[i].pos; + return true; + } + } + + return false; +} + +bool parse_surround_mask(const char* str, size_t str_len, ChannelMask& result) { + for (size_t i = 0; i < ROC_ARRAY_SIZE(ChanMaskNames); i++) { + if (strlen(ChanMaskNames[i].name) == str_len && + memcmp(ChanMaskNames[i].name, str, str_len) == 0) { + result = ChanMaskNames[i].mask; + return true; + } + } + + return false; +} + +bool parse_multitrack_channel(const char* str, size_t str_len, size_t& result) { + if (!parse_size_t(str, str_len, result)) { + return false; + } + + if (result >= ChannelSet::max_channels()) { + return false; + } + + return true; +} + +bool parse_multitrack_mask(const char* str, size_t str_len, ChannelSet& result) { + size_t str_pos = str_len - 1; + size_t ch_pos = 0; + + do { + char digit_str[2] = { str[str_pos], '\0' }; + char* digit_end = NULL; + + const long num = (unsigned)strtol(digit_str, &digit_end, 16); + if (num == LONG_MAX || num == LONG_MIN || digit_end != digit_str + 1) { + return false; + } + + for (size_t bit = 0; bit < 4; bit++) { + if (num & (1 << bit)) { + result.set_channel(ch_pos, true); + } + ch_pos++; + } + } while (str_pos-- != 0); + + return true; +} + +bool parse_sample_rate(const char* str, size_t str_len, size_t& result) { + if (!parse_size_t(str, str_len, result)) { + return false; + } + + if (result == 0) { + return false; + } + + return true; +} + +bool parse_sample_spec_imp(const char* str, SampleSpec& sample_spec) { + if (!str) { + roc_log(LogError, "parse sample spec: input string is null"); + return false; + } + + sample_spec.clear(); + + // for ragel + const char* p = str; + const char *pe = str + strlen(str); + + const char *eof = pe; + int cs = 0; + + // for start_token + const char* start_p = NULL; + + // for mtr range + size_t mtr_range_begin = 0, + mtr_range_end = 0; + + // parse result + bool success = false; + + %%{ + action start_token { + start_p = p; + } + + action set_surround_mask { + ChannelMask ch_mask = 0; + if (!parse_surround_mask(start_p, p - start_p, ch_mask)) { + roc_log(LogError, "parse sample spec: invalid channel mask name"); + return false; + } + sample_spec.channel_set().set_channel_mask(ch_mask); + } + + action set_surround_channel { + ChannelPosition ch_pos = ChanPos_Max; + if (!parse_surround_channel(start_p, p - start_p, ch_pos)) { + roc_log(LogError, "parse sample spec: invalid channel name"); + return false; + } + sample_spec.channel_set().set_channel(ch_pos, true); + } + + action set_surround { + sample_spec.channel_set().set_layout(ChanLayout_Surround); + sample_spec.channel_set().set_order(ChanOrder_Smpte); + } + + action set_mtr_number { + size_t ch_pos = 0; + if (!parse_multitrack_channel(start_p, p - start_p, ch_pos)) { + roc_log(LogError, "parse sample spec: invalid channel number," + " should be integer in range [0; %d]", + (int)ChannelSet::max_channels() - 1); + return false; + } + sample_spec.channel_set().set_channel(ch_pos, true); + } + + action set_mtr_range_begin { + if (!parse_multitrack_channel(start_p, p - start_p, mtr_range_begin)) { + roc_log(LogError, "parse sample spec: invalid channel number," + " should be integer in range [0; %d]", + (int)ChannelSet::max_channels() - 1); + return false; + } + } + + action set_mtr_range_end { + if (!parse_multitrack_channel(start_p, p - start_p, mtr_range_end)) { + roc_log(LogError, "parse sample spec: invalid channel number," + " should be integer in range [0; %d]", + (int)ChannelSet::max_channels() - 1); + return false; + } + } + + action set_mtr_range { + sample_spec.channel_set().set_channel_range( + mtr_range_begin, mtr_range_end, true); + } + + action set_mtr_mask { + if (!parse_multitrack_mask(start_p, p - start_p, sample_spec.channel_set())) { + roc_log(LogError, "parse sample spec: invalid channel mask"); + return false; + } + } + + action set_mtr { + sample_spec.channel_set().set_layout(ChanLayout_Multitrack); + sample_spec.channel_set().set_order(ChanOrder_None); + } + + action set_rate { + size_t rate = 0; + if (!parse_sample_rate(start_p, p - start_p, rate)) { + roc_log(LogError, "parse sample spec: invalid sample rate"); + return false; + } + sample_spec.set_sample_rate(rate); + } + + surround_mask = ([a-z] [a-z0-9.]+) >start_token %set_surround_mask; + surround_channel = [A-Z]+ >start_token %set_surround_channel; + surround_list = surround_channel (',' surround_channel)*; + + surround = (surround_mask | surround_list) %set_surround; + + mtr_mask_hex = [0-9a-fA-F]+ >start_token %set_mtr_mask; + mtr_mask = '0x' mtr_mask_hex; + + mtr_number = [0-9]+ >start_token %set_mtr_number; + mtr_range_begin = [0-9]+ >start_token %set_mtr_range_begin; + mtr_range_end = [0-9]+ >start_token %set_mtr_range_end; + mtr_range = (mtr_range_begin '-' mtr_range_end) >start_token %set_mtr_range; + mtr_channel = mtr_number | mtr_range; + mtr_list = mtr_channel (',' mtr_channel)*; + + mtr = (mtr_mask | mtr_list) %set_mtr; + + format = [a-z0-9_]+; + rate = [0-9]+ >start_token %set_rate; + channels = surround | mtr; + + main := ( ('-' | format) '/' ('-' | rate) '/' ('-' | channels) ) + %{ success = true; } + ; + + write init; + write exec; + }%% + + if (!success) { + roc_log(LogError, + "parse sample spec: expected 'FORMAT/RATE/CHANNELS', got '%s'", + str); + return false; + } + + return true; +} + +} // namespace + +bool parse_sample_spec(const char* str, SampleSpec& result) { + if (!parse_sample_spec_imp(str, result)) { + result.clear(); + return false; + } + return true; +} + +} // namespace audio +} // namespace roc diff --git a/src/internal_modules/roc_audio/sample_spec_to_str.cpp b/src/internal_modules/roc_audio/sample_spec_to_str.cpp index 38de238f1..21dc7be0a 100644 --- a/src/internal_modules/roc_audio/sample_spec_to_str.cpp +++ b/src/internal_modules/roc_audio/sample_spec_to_str.cpp @@ -7,7 +7,6 @@ */ #include "roc_audio/sample_spec_to_str.h" -#include "roc_core/string_builder.h" namespace roc { namespace audio { @@ -15,11 +14,7 @@ namespace audio { sample_spec_to_str::sample_spec_to_str(const SampleSpec& sample_spec) { core::StringBuilder bld(buf_, sizeof(buf_)); - bld.append_str(""); + format_sample_spec(sample_spec, bld); } } // namespace audio diff --git a/src/internal_modules/roc_core/string_builder.cpp b/src/internal_modules/roc_core/string_builder.cpp index 4841b1338..e44448136 100644 --- a/src/internal_modules/roc_core/string_builder.cpp +++ b/src/internal_modules/roc_core/string_builder.cpp @@ -175,7 +175,7 @@ bool StringBuilder::append_uint(uint64_t number, unsigned int base) { char tmp[128]; // 128 is enough for any base in case of 64-bit ints size_t tmp_pos = sizeof(tmp) - 1; do { - tmp[tmp_pos] = "0123456789abcdef"[number % base]; + tmp[tmp_pos] = "0123456789ABCDEF"[number % base]; tmp_pos--; number = number / base; } while (number > 0); diff --git a/src/tests/roc_audio/test_channel_set.cpp b/src/tests/roc_audio/test_channel_set.cpp index 5a3810252..9a73e3f9a 100644 --- a/src/tests/roc_audio/test_channel_set.cpp +++ b/src/tests/roc_audio/test_channel_set.cpp @@ -8,6 +8,7 @@ #include +#include "roc_audio/channel_defs.h" #include "roc_audio/channel_set.h" #include "roc_audio/channel_set_to_str.h" @@ -355,15 +356,17 @@ TEST(channel_set, clear) { } } -TEST(channel_set, subset_superset) { +TEST(channel_set, equal_subset_superset) { { // empty ChannelSet ch_set; ch_set.set_layout(ChanLayout_Surround); + CHECK(ch_set.is_equal(0x0)); CHECK(ch_set.is_subset(0x0)); CHECK(ch_set.is_superset(0x0)); + CHECK(!ch_set.is_equal(0xffffffff)); CHECK(ch_set.is_subset(0xffffffff)); CHECK(!ch_set.is_superset(0xffffffff)); } @@ -373,18 +376,23 @@ TEST(channel_set, subset_superset) { ch_set.set_layout(ChanLayout_Surround); ch_set.set_channel_mask(0x5); + CHECK(ch_set.is_equal(0x5)); CHECK(ch_set.is_subset(0x5)); CHECK(ch_set.is_superset(0x5)); + CHECK(!ch_set.is_equal(0x7)); CHECK(ch_set.is_subset(0x7)); CHECK(!ch_set.is_superset(0x7)); + CHECK(!ch_set.is_equal(0x4)); CHECK(!ch_set.is_subset(0x4)); CHECK(ch_set.is_superset(0x4)); + CHECK(!ch_set.is_equal(0x0)); CHECK(!ch_set.is_subset(0x0)); CHECK(ch_set.is_superset(0x0)); + CHECK(!ch_set.is_equal(0xffffffff)); CHECK(ch_set.is_subset(0xffffffff)); CHECK(!ch_set.is_superset(0xffffffff)); } @@ -394,12 +402,15 @@ TEST(channel_set, subset_superset) { ch_set.set_channel(2, true); ch_set.set_channel(101, true); + CHECK(!ch_set.is_equal(0x2)); CHECK(!ch_set.is_subset(0x2)); CHECK(ch_set.is_superset(0x2)); + CHECK(!ch_set.is_equal(0x0)); CHECK(!ch_set.is_subset(0x0)); CHECK(ch_set.is_superset(0x0)); + CHECK(!ch_set.is_equal(0xffffffff)); CHECK(!ch_set.is_subset(0xffffffff)); CHECK(ch_set.is_superset(0xffffffff)); } @@ -498,6 +509,13 @@ TEST(channel_set, to_string) { STRCMP_EQUAL("", channel_set_to_str(ch_set).c_str()); } + { + ChannelSet ch_set; + ch_set.set_layout(ChanLayout_Surround); + ch_set.set_order(ChanOrder_Smpte); + + STRCMP_EQUAL("", channel_set_to_str(ch_set).c_str()); + } { ChannelSet ch_set(ChanLayout_Surround, ChanOrder_Smpte, ChanMask_Surround_Mono); @@ -517,36 +535,36 @@ TEST(channel_set, to_string) { } { ChannelSet ch_set; + ch_set.set_layout(ChanLayout_Multitrack); + STRCMP_EQUAL("", channel_set_to_str(ch_set).c_str()); + } + { + ChannelSet ch_set; ch_set.set_layout(ChanLayout_Multitrack); ch_set.set_channel_range(0, 7, true); - STRCMP_EQUAL("", channel_set_to_str(ch_set).c_str()); + STRCMP_EQUAL("", channel_set_to_str(ch_set).c_str()); } { ChannelSet ch_set; - ch_set.set_layout(ChanLayout_Multitrack); - ch_set.set_channel(4, true); + ch_set.set_channel(2, true); + ch_set.set_channel(3, true); + ch_set.set_channel(5, true); ch_set.set_channel(7, true); - ch_set.set_channel_range(8, 11, true); - // 256-bit mask is formatted from LSB to MSB in hex - // trailing zeros are truncated - - // channel: 0-3 4-7 8-11 - // bitmask: 0000 1001 1111 - // hex: 0x0 0x9 0xf - - STRCMP_EQUAL("", channel_set_to_str(ch_set).c_str()); + STRCMP_EQUAL("", channel_set_to_str(ch_set).c_str()); } { ChannelSet ch_set; - ch_set.set_layout(ChanLayout_Multitrack); - ch_set.set_channel_range(68, 70, true); + ch_set.set_channel(2, true); + ch_set.set_channel(3, true); + ch_set.set_channel(85, true); + ch_set.set_channel(87, true); - STRCMP_EQUAL("", + STRCMP_EQUAL("", channel_set_to_str(ch_set).c_str()); } } diff --git a/src/tests/roc_audio/test_sample_spec.cpp b/src/tests/roc_audio/test_sample_spec.cpp index 8b77c6645..47fdb4812 100644 --- a/src/tests/roc_audio/test_sample_spec.cpp +++ b/src/tests/roc_audio/test_sample_spec.cpp @@ -7,7 +7,9 @@ */ #include +#include +#include "roc_audio/channel_defs.h" #include "roc_audio/sample_spec.cpp" #include "roc_core/cpu_traits.h" #include "roc_core/macro_helpers.h" @@ -170,5 +172,286 @@ TEST(sample_spec, saturation) { } } +TEST(sample_spec, clear) { + SampleSpec sample_spec; + + // sample spec is invalid + CHECK(!sample_spec.is_valid()); + CHECK_EQUAL(0, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_None, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_None, sample_spec.channel_set().order()); + CHECK_EQUAL(0, sample_spec.channel_set().num_channels()); + + // set all fields + sample_spec.set_sample_rate(44100); + sample_spec.channel_set().set_layout(ChanLayout_Surround); + sample_spec.channel_set().set_order(ChanOrder_Smpte); + sample_spec.channel_set().set_channel_mask(ChanMask_Surround_Stereo); + + // sample spec is valid + CHECK(sample_spec.is_valid()); + CHECK_EQUAL(44100, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_Surround, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_Smpte, sample_spec.channel_set().order()); + CHECK(sample_spec.channel_set().is_equal(ChanMask_Surround_Stereo)); + + // clear all fields + sample_spec.clear(); + + // sample spec is invalid + CHECK(!sample_spec.is_valid()); + CHECK_EQUAL(0, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_None, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_None, sample_spec.channel_set().order()); + CHECK_EQUAL(0, sample_spec.channel_set().num_channels()); +} + +TEST(sample_spec, parse_rate) { + { // 44.1Khz + SampleSpec sample_spec; + CHECK(parse_sample_spec("s16/44100/stereo", sample_spec)); + + CHECK(sample_spec.is_valid()); + + CHECK_EQUAL(44100, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_Surround, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_Smpte, sample_spec.channel_set().order()); + CHECK(sample_spec.channel_set().is_equal(ChanMask_Surround_Stereo)); + } + { // 48Khz + SampleSpec sample_spec; + CHECK(parse_sample_spec("s16/48000/stereo", sample_spec)); + + CHECK(sample_spec.is_valid()); + + CHECK_EQUAL(48000, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_Surround, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_Smpte, sample_spec.channel_set().order()); + CHECK(sample_spec.channel_set().is_equal(ChanMask_Surround_Stereo)); + } +} + +TEST(sample_spec, parse_channels) { + { // surround stereo + SampleSpec sample_spec; + CHECK(parse_sample_spec("s16/48000/stereo", sample_spec)); + + CHECK(sample_spec.is_valid()); + + CHECK_EQUAL(48000, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_Surround, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_Smpte, sample_spec.channel_set().order()); + CHECK(sample_spec.channel_set().is_equal(ChanMask_Surround_Stereo)); + } + { // surround 5.1.2 + SampleSpec sample_spec; + CHECK(parse_sample_spec("s16/48000/surround5.1.2", sample_spec)); + + CHECK(sample_spec.is_valid()); + + CHECK_EQUAL(48000, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_Surround, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_Smpte, sample_spec.channel_set().order()); + CHECK(sample_spec.channel_set().is_equal(ChanMask_Surround_5_1_2)); + } + { // surround channel list + SampleSpec sample_spec; + CHECK(parse_sample_spec("s16/48000/FL,FC,FR", sample_spec)); + + CHECK(sample_spec.is_valid()); + + CHECK_EQUAL(48000, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_Surround, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_Smpte, sample_spec.channel_set().order()); + + CHECK_EQUAL(3, sample_spec.num_channels()); + CHECK(sample_spec.channel_set().has_channel(ChanPos_FrontLeft)); + CHECK(sample_spec.channel_set().has_channel(ChanPos_FrontCenter)); + CHECK(sample_spec.channel_set().has_channel(ChanPos_FrontRight)); + } + { // multitrack channel list + SampleSpec sample_spec; + CHECK(parse_sample_spec("s16/48000/1,2,3", sample_spec)); + + CHECK(sample_spec.is_valid()); + + CHECK_EQUAL(48000, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_Multitrack, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_None, sample_spec.channel_set().order()); + + CHECK_EQUAL(3, sample_spec.num_channels()); + CHECK(sample_spec.channel_set().has_channel(1)); + CHECK(sample_spec.channel_set().has_channel(2)); + CHECK(sample_spec.channel_set().has_channel(3)); + } + { // multitrack channel range + SampleSpec sample_spec; + CHECK(parse_sample_spec("s16/48000/1-3", sample_spec)); + + CHECK(sample_spec.is_valid()); + + CHECK_EQUAL(48000, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_Multitrack, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_None, sample_spec.channel_set().order()); + + CHECK_EQUAL(3, sample_spec.num_channels()); + CHECK(sample_spec.channel_set().has_channel(1)); + CHECK(sample_spec.channel_set().has_channel(2)); + CHECK(sample_spec.channel_set().has_channel(3)); + } + { // multitrack channel list and range + SampleSpec sample_spec; + CHECK(parse_sample_spec("s16/48000/1,3-5,7", sample_spec)); + + CHECK(sample_spec.is_valid()); + + CHECK_EQUAL(48000, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_Multitrack, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_None, sample_spec.channel_set().order()); + + CHECK_EQUAL(5, sample_spec.num_channels()); + CHECK(sample_spec.channel_set().has_channel(1)); + CHECK(sample_spec.channel_set().has_channel(3)); + CHECK(sample_spec.channel_set().has_channel(4)); + CHECK(sample_spec.channel_set().has_channel(5)); + CHECK(sample_spec.channel_set().has_channel(7)); + } + { // multitrack mask (zero) + SampleSpec sample_spec; + CHECK(parse_sample_spec("s16/48000/0x00", sample_spec)); + + CHECK(!sample_spec.is_valid()); + + CHECK_EQUAL(48000, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_Multitrack, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_None, sample_spec.channel_set().order()); + CHECK_EQUAL(0, sample_spec.num_channels()); + } + { // multitrack mask (short) + SampleSpec sample_spec; + // 0xAC = 10101100 + CHECK(parse_sample_spec("s16/48000/0xAC", sample_spec)); + + CHECK(sample_spec.is_valid()); + + CHECK_EQUAL(48000, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_Multitrack, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_None, sample_spec.channel_set().order()); + + CHECK_EQUAL(4, sample_spec.num_channels()); + CHECK(sample_spec.channel_set().has_channel(2)); + CHECK(sample_spec.channel_set().has_channel(3)); + CHECK(sample_spec.channel_set().has_channel(5)); + CHECK(sample_spec.channel_set().has_channel(7)); + } + { // multitrack mask (long) + SampleSpec sample_spec; + // 1010, 80 zero bits, 1100 + CHECK(parse_sample_spec("s16/48000/0xA00000000000000000000C", sample_spec)); + + CHECK(sample_spec.is_valid()); + + CHECK_EQUAL(48000, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_Multitrack, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_None, sample_spec.channel_set().order()); + + CHECK_EQUAL(4, sample_spec.num_channels()); + CHECK(sample_spec.channel_set().has_channel(2)); + CHECK(sample_spec.channel_set().has_channel(3)); + CHECK(sample_spec.channel_set().has_channel(85)); + CHECK(sample_spec.channel_set().has_channel(87)); + } +} + +TEST(sample_spec, parse_defaults) { + { // no format + SampleSpec sample_spec; + CHECK(parse_sample_spec("-/44100/stereo", sample_spec)); + + // TODO(gh-547): should be invalid + CHECK(sample_spec.is_valid()); + + CHECK_EQUAL(44100, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_Surround, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_Smpte, sample_spec.channel_set().order()); + CHECK(sample_spec.channel_set().is_equal(ChanMask_Surround_Stereo)); + } + { // no rate + SampleSpec sample_spec; + CHECK(parse_sample_spec("s16/-/stereo", sample_spec)); + + CHECK(!sample_spec.is_valid()); + + CHECK_EQUAL(0, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_Surround, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_Smpte, sample_spec.channel_set().order()); + CHECK(sample_spec.channel_set().is_equal(ChanMask_Surround_Stereo)); + } + { // no channels + SampleSpec sample_spec; + CHECK(parse_sample_spec("s16/48000/-", sample_spec)); + + CHECK(!sample_spec.is_valid()); + + CHECK_EQUAL(48000, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_None, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_None, sample_spec.channel_set().order()); + CHECK_EQUAL(0, sample_spec.channel_set().num_channels()); + } + { // no nothing + SampleSpec sample_spec; + CHECK(parse_sample_spec("-/-/-", sample_spec)); + + CHECK(!sample_spec.is_valid()); + + CHECK_EQUAL(0, sample_spec.sample_rate()); + CHECK_EQUAL(ChanLayout_None, sample_spec.channel_set().layout()); + CHECK_EQUAL(ChanOrder_None, sample_spec.channel_set().order()); + CHECK_EQUAL(0, sample_spec.channel_set().num_channels()); + } +} + +TEST(sample_spec, parse_errors) { + SampleSpec sample_spec; + + { // bad syntax + CHECK(!parse_sample_spec("", sample_spec)); + CHECK(!parse_sample_spec("/", sample_spec)); + CHECK(!parse_sample_spec("//", sample_spec)); + CHECK(!parse_sample_spec("///", sample_spec)); + CHECK(!parse_sample_spec("/48000/stereo", sample_spec)); + CHECK(!parse_sample_spec("s16//stereo", sample_spec)); + CHECK(!parse_sample_spec("s16/48000/", sample_spec)); + CHECK(!parse_sample_spec("/s16/48000/stereo", sample_spec)); + CHECK(!parse_sample_spec("s16/48000/stereo/", sample_spec)); + } + { // bad rate + CHECK(!parse_sample_spec("s16/0/stereo", sample_spec)); + CHECK(!parse_sample_spec("s16/-1/stereo", sample_spec)); + CHECK(!parse_sample_spec("s16/bad/stereo", sample_spec)); + } + { // bad surround + CHECK(!parse_sample_spec("s16/44100/bad", sample_spec)); + CHECK(!parse_sample_spec("s16/44100/BAD,BAD", sample_spec)); + CHECK(!parse_sample_spec("s16/44100/stereo,", sample_spec)); + CHECK(!parse_sample_spec("s16/44100/FL,FR,", sample_spec)); + CHECK(!parse_sample_spec("s16/44100/,FL,FR", sample_spec)); + CHECK(!parse_sample_spec("s16/44100/FL,,FR", sample_spec)); + } + { // bad multitrack + CHECK(!parse_sample_spec("s16/44100/1,2,", sample_spec)); + CHECK(!parse_sample_spec("s16/44100/,1,2", sample_spec)); + CHECK(!parse_sample_spec("s16/44100/1,,2", sample_spec)); + CHECK(!parse_sample_spec("s16/44100/1-", sample_spec)); + CHECK(!parse_sample_spec("s16/44100/-2", sample_spec)); + CHECK(!parse_sample_spec("s16/44100/1--2", sample_spec)); + CHECK(!parse_sample_spec("s16/44100/10000", sample_spec)); + CHECK(!parse_sample_spec("s16/44100/10000-20000", sample_spec)); + CHECK(!parse_sample_spec("s16/44100/0x", sample_spec)); + CHECK(!parse_sample_spec("s16/44100/0XF", sample_spec)); + CHECK(!parse_sample_spec("s16/44100/0xZZ", sample_spec)); + } +} + } // namespace audio } // namespace roc diff --git a/src/tests/roc_core/test_string_builder.cpp b/src/tests/roc_core/test_string_builder.cpp index 3dcffa8bd..aaff1ccfa 100644 --- a/src/tests/roc_core/test_string_builder.cpp +++ b/src/tests/roc_core/test_string_builder.cpp @@ -343,7 +343,7 @@ TEST(string_builder, append_uint) { enum { Size = 9 }; char dst[] = "xxxxxxxx"; - char res[] = "----dead"; + char res[] = "----DEAD"; StringBuilder b(dst, Size);