diff --git a/src/internal_modules/roc_core/parse_duration.cpp b/src/internal_modules/roc_core/parse_units.cpp similarity index 56% rename from src/internal_modules/roc_core/parse_duration.cpp rename to src/internal_modules/roc_core/parse_units.cpp index de7e33990..e16581a40 100644 --- a/src/internal_modules/roc_core/parse_duration.cpp +++ b/src/internal_modules/roc_core/parse_units.cpp @@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "roc_core/parse_duration.h" +#include "roc_core/parse_units.h" #include "roc_core/log.h" #include "roc_core/stddefs.h" @@ -84,5 +84,67 @@ bool parse_duration(const char* str, nanoseconds_t& result) { return true; } +bool parse_size(const char* str, size_t& result) { + if (str == NULL) { + roc_log(LogError, "parse size: string is null"); + return false; + } + + const size_t kibibyte = 1024; + const size_t mebibyte = 1024 * kibibyte; + const size_t gibibyte = 1024 * mebibyte; + + size_t multiplier = 1; + + const size_t str_len = strlen(str); + const char* suffix; + + // suffix is optional. + if ((suffix = find_suffix(str, str_len, "G"))) { + multiplier = gibibyte; + } else if ((suffix = find_suffix(str, str_len, "M"))) { + multiplier = mebibyte; + } else if ((suffix = find_suffix(str, str_len, "K"))) { + multiplier = kibibyte; + } + + if (!isdigit(*str)) { + roc_log(LogError, + "parse size: invalid format, not a number, expected [], " + "where suffix="); + return false; + } + + char* number_end = NULL; + unsigned long long number = strtoull(str, &number_end, 10); + + if (number == ULLONG_MAX || (!suffix && *number_end != '\0') + || (suffix && number_end != suffix)) { + roc_log(LogError, + "parse size: invalid format, can't parse number, expected " + "[], where suffix="); + return false; + } + + size_t number_converted = number; + if (number_converted < number) { + roc_log(LogError, + "parse size: too large, can't parse number, expected " + "[], where suffix="); + return false; + } + + size_t output = number_converted * multiplier; + if (output / multiplier != number_converted) { + roc_log(LogError, + "parse size: too large, can't parse number, expected " + "[], where suffix="); + return false; + } + + result = output; + return true; +} + } // namespace core } // namespace roc diff --git a/src/internal_modules/roc_core/parse_duration.h b/src/internal_modules/roc_core/parse_units.h similarity index 60% rename from src/internal_modules/roc_core/parse_duration.h rename to src/internal_modules/roc_core/parse_units.h index 71b1488aa..9e7052e0d 100644 --- a/src/internal_modules/roc_core/parse_duration.h +++ b/src/internal_modules/roc_core/parse_units.h @@ -6,11 +6,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -//! @file roc_core/parse_duration.h -//! @brief Parse duration. +//! @file roc_core/parse_units.h +//! @brief Parse units like duration, size, etc. -#ifndef ROC_CORE_PARSE_DURATION_H_ -#define ROC_CORE_PARSE_DURATION_H_ +#ifndef ROC_CORE_PARSE_UNITS_H_ +#define ROC_CORE_PARSE_UNITS_H_ #include "roc_core/attributes.h" #include "roc_core/time.h" @@ -33,7 +33,20 @@ namespace core { //! false if string can't be parsed. ROC_ATTR_NODISCARD bool parse_duration(const char* string, nanoseconds_t& result); +//! Parse size from string. +//! +//! @remarks +//! The input string should be in one of the following forms: +//! - "" +//! - "K" +//! - "M" +//! - "G" +//! +//! @returns +//! false if string can't be parsed. +ROC_ATTR_NODISCARD bool parse_size(const char* string, size_t& result); + } // namespace core } // namespace roc -#endif // ROC_CORE_PARSE_DURATION_H_ +#endif // ROC_CORE_PARSE_UNITS_H_ diff --git a/src/tests/roc_core/test_parse_duration.cpp b/src/tests/roc_core/test_parse_duration.cpp deleted file mode 100644 index 0fe85320f..000000000 --- a/src/tests/roc_core/test_parse_duration.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2015 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_core/parse_duration.h" -#include "roc_core/time.h" - -namespace roc { -namespace core { - -TEST_GROUP(parse_duration) {}; - -TEST(parse_duration, error) { - nanoseconds_t result = 0; - - CHECK(!parse_duration(NULL, result)); - CHECK(!parse_duration("", result)); - CHECK(!parse_duration("1", result)); - CHECK(!parse_duration("s", result)); - CHECK(!parse_duration("1 s", result)); - CHECK(!parse_duration(" 1s", result)); - CHECK(!parse_duration("1s ", result)); - CHECK(!parse_duration("!s", result)); - CHECK(!parse_duration("s1", result)); - CHECK(!parse_duration("1x", result)); -} - -TEST(parse_duration, parse) { - nanoseconds_t result = 0; - - CHECK(parse_duration("123ns", result)); - CHECK(result == 123 * Nanosecond); - - CHECK(parse_duration("123us", result)); - CHECK(result == 123 * Microsecond); - - CHECK(parse_duration("123ms", result)); - CHECK(result == 123 * Millisecond); - - CHECK(parse_duration("123s", result)); - CHECK(result == 123 * Second); - - CHECK(parse_duration("123m", result)); - CHECK(result == 123 * Minute); - - CHECK(parse_duration("123h", result)); - CHECK(result == 123 * Hour); -} - -} // namespace core -} // namespace roc diff --git a/src/tests/roc_core/test_parse_units.cpp b/src/tests/roc_core/test_parse_units.cpp new file mode 100644 index 000000000..42bdc871e --- /dev/null +++ b/src/tests/roc_core/test_parse_units.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2015 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_core/parse_units.h" +#include "roc_core/time.h" + +namespace roc { +namespace core { + +TEST_GROUP(parse_units) {}; + +TEST(parse_units, parse_duration_error) { + nanoseconds_t result = 0; + + CHECK(!parse_duration(NULL, result)); + CHECK(!parse_duration("", result)); + CHECK(!parse_duration("1", result)); + CHECK(!parse_duration("s", result)); + CHECK(!parse_duration("1 s", result)); + CHECK(!parse_duration(" 1s", result)); + CHECK(!parse_duration("1s ", result)); + CHECK(!parse_duration("!s", result)); + CHECK(!parse_duration("s1", result)); + CHECK(!parse_duration("1x", result)); +} + +TEST(parse_units, parse_duration) { + nanoseconds_t result = 0; + + CHECK(parse_duration("123ns", result)); + CHECK(result == 123 * Nanosecond); + + CHECK(parse_duration("123us", result)); + CHECK(result == 123 * Microsecond); + + CHECK(parse_duration("123ms", result)); + CHECK(result == 123 * Millisecond); + + CHECK(parse_duration("123s", result)); + CHECK(result == 123 * Second); + + CHECK(parse_duration("123m", result)); + CHECK(result == 123 * Minute); + + CHECK(parse_duration("123h", result)); + CHECK(result == 123 * Hour); +} + +TEST(parse_units, parse_size_error) { + size_t result = 0; + + CHECK(!parse_size(NULL, result)); + CHECK(!parse_size("", result)); + CHECK(!parse_size("K", result)); + CHECK(!parse_size("1 K", result)); + CHECK(!parse_size(" 1K", result)); + CHECK(!parse_size("1K ", result)); + CHECK(!parse_size("!K", result)); + CHECK(!parse_size("K1", result)); + CHECK(!parse_size("1x", result)); +} + +TEST(parse_units, parse_size) { + size_t result = 0; + + const size_t kibibyte = 1024; + const size_t mebibyte = 1024 * kibibyte; + const size_t gibibyte = 1024 * mebibyte; + + CHECK(parse_size("123", result)); + CHECK(result == 123); + + CHECK(parse_size("123K", result)); + CHECK(result == 123 * kibibyte); + + CHECK(parse_size("123M", result)); + CHECK(result == 123 * mebibyte); + + CHECK(parse_size("1G", result)); + CHECK(result == 1 * gibibyte); +} + +TEST(parse_units, parse_size_overflows_due_to_sizeof_size_t) { + if (SIZE_MAX < ULLONG_MAX) { + char s[32]; + snprintf(s, 32, "%llu", ULLONG_MAX - 1); + size_t result = 0; + CHECK_FALSE(parse_size(s, result)); + } +} + +TEST(parse_units, parse_size_overflows_due_to_multiplier) { + char s[32]; + snprintf(s, 32, "%zuK", SIZE_MAX - 1); + size_t result = 0; + CHECK_FALSE(parse_size(s, result)); +} + +} // namespace core +} // namespace roc diff --git a/src/tools/roc_copy/main.cpp b/src/tools/roc_copy/main.cpp index 01e18e952..d97b9bfc2 100644 --- a/src/tools/roc_copy/main.cpp +++ b/src/tools/roc_copy/main.cpp @@ -11,7 +11,7 @@ #include "roc_core/crash_handler.h" #include "roc_core/heap_arena.h" #include "roc_core/log.h" -#include "roc_core/parse_duration.h" +#include "roc_core/parse_units.h" #include "roc_core/scoped_ptr.h" #include "roc_pipeline/transcoder_sink.h" #include "roc_sndio/backend_dispatcher.h" diff --git a/src/tools/roc_recv/cmdline.ggo b/src/tools/roc_recv/cmdline.ggo index 684db2eb4..851965074 100644 --- a/src/tools/roc_recv/cmdline.ggo +++ b/src/tools/roc_recv/cmdline.ggo @@ -44,14 +44,14 @@ section "Options" option "choppy-play-timeout" - "Choppy playback timeout, TIME units" string optional - option "packet-limit" - "Maximum packet size, in bytes" - int optional + option "frame-len" - "Duration of the internal frames, TIME units" + typestr="TIME" string optional - option "frame-limit" - "Maximum internal frame size, in bytes" - int optional + option "max-packet-size" - "Maximum packet size, in SIZE units" + typestr="SIZE" string optional - option "frame-length" - "Duration of the internal frames, TIME units" - typestr="TIME" string optional + option "max-frame-size" - "Maximum internal frame size, in SIZE units" + typestr="SIZE" string optional option "rate" - "Override output sample rate, Hz" int optional @@ -92,6 +92,9 @@ FILE_FORMAT is the output file format name, e.g.: TIME is an integer number with a suffix, e.g.: 123ns; 123us; 123ms; 123s; 123m; 123h; +SIZE is an integer number with an optional suffix, e.g.: + 123; 123K; 123M; 123G; + Use --list-supported option to print the list of the supported URI schemes and file formats. diff --git a/src/tools/roc_recv/main.cpp b/src/tools/roc_recv/main.cpp index 628649aed..3714111b9 100644 --- a/src/tools/roc_recv/main.cpp +++ b/src/tools/roc_recv/main.cpp @@ -16,7 +16,7 @@ #include "roc_core/crash_handler.h" #include "roc_core/heap_arena.h" #include "roc_core/log.h" -#include "roc_core/parse_duration.h" +#include "roc_core/parse_units.h" #include "roc_core/scoped_ptr.h" #include "roc_netio/network_loop.h" #include "roc_node/context.h" @@ -64,65 +64,40 @@ int main(int argc, char** argv) { break; } - node::ContextConfig context_config; - - if (args.packet_limit_given) { - if (args.packet_limit_arg <= 0) { - roc_log(LogError, "invalid --packet-limit: should be > 0"); - return 1; - } - context_config.max_packet_size = (size_t)args.packet_limit_arg; - } - - if (args.frame_limit_given) { - if (args.frame_limit_arg <= 0) { - roc_log(LogError, "invalid --frame-limit: should be > 0"); - return 1; - } - context_config.max_frame_size = (size_t)args.frame_limit_arg; - } - - core::HeapArena heap_arena; - - node::Context context(context_config, heap_arena); - if (!context.is_valid()) { - roc_log(LogError, "can't initialize node context"); - return 1; - } - - sndio::BackendDispatcher backend_dispatcher(context.arena()); - - if (args.list_supported_given) { - if (!address::print_supported(context.arena())) { - return 1; - } - - if (!sndio::print_supported(backend_dispatcher, context.arena())) { - return 1; - } - - return 0; - } - pipeline::ReceiverConfig receiver_config; sndio::Config io_config; io_config.sample_spec.set_channel_set( receiver_config.common.output_sample_spec.channel_set()); - if (args.frame_length_given) { - if (!core::parse_duration(args.frame_length_arg, io_config.frame_length)) { - roc_log(LogError, "invalid --frame-length: bad format"); + if (args.frame_len_given) { + if (!core::parse_duration(args.frame_len_arg, io_config.frame_length)) { + roc_log(LogError, "invalid --frame-len: bad format"); return 1; } if (receiver_config.common.output_sample_spec.ns_2_samples_overall( io_config.frame_length) <= 0) { - roc_log(LogError, "invalid --frame-length: should be > 0"); + roc_log(LogError, "invalid --frame-len: should be > 0"); return 1; } } + if (args.io_latency_given) { + if (!core::parse_duration(args.io_latency_arg, io_config.latency)) { + roc_log(LogError, "invalid --io-latency"); + return 1; + } + } + + if (args.rate_given) { + if (args.rate_arg <= 0) { + roc_log(LogError, "invalid --rate: should be > 0"); + return 1; + } + io_config.sample_spec.set_sample_rate((size_t)args.rate_arg); + } + sndio::BackendMap::instance().set_frame_size( io_config.frame_length, receiver_config.common.output_sample_spec); @@ -235,19 +210,62 @@ int main(int argc, char** argv) { receiver_config.common.enable_profiling = args.profiling_flag; receiver_config.common.enable_beeping = args.beep_flag; - if (args.io_latency_given) { - if (!core::parse_duration(args.io_latency_arg, io_config.latency)) { - roc_log(LogError, "invalid --io-latency"); + node::ContextConfig context_config; + + if (args.max_packet_size_given) { + if (!core::parse_size(args.max_packet_size_arg, context_config.max_packet_size)) { + roc_log(LogError, "invalid --max-packet-size"); + return 1; + } + if (context_config.max_packet_size == 0) { + roc_log(LogError, "invalid --max-packet-size: should be > 0"); return 1; } } - if (args.rate_given) { - if (args.rate_arg <= 0) { - roc_log(LogError, "invalid --rate: should be > 0"); + if (args.max_frame_size_given) { + if (!core::parse_size(args.max_frame_size_arg, context_config.max_frame_size)) { + roc_log(LogError, "invalid --max-frame-size"); return 1; } - io_config.sample_spec.set_sample_rate((size_t)args.rate_arg); + if (context_config.max_frame_size == 0) { + roc_log(LogError, "invalid --max-frame-size: should be > 0"); + return 1; + } + } else { + audio::SampleSpec spec = io_config.sample_spec; + if (spec.sample_rate() == 0) { + spec.set_sample_rate(48000); + } + if (spec.num_channels() == 0) { + spec.set_channel_set(audio::ChannelSet(audio::ChanLayout_Surround, + audio::ChanOrder_Smpte, + audio::ChanMask_Surround_7_1_4)); + } + context_config.max_frame_size = + spec.ns_2_samples_overall(io_config.frame_length) * sizeof(audio::sample_t); + } + + core::HeapArena heap_arena; + + node::Context context(context_config, heap_arena); + if (!context.is_valid()) { + roc_log(LogError, "can't initialize node context"); + return 1; + } + + sndio::BackendDispatcher backend_dispatcher(context.arena()); + + if (args.list_supported_given) { + if (!address::print_supported(context.arena())) { + return 1; + } + + if (!sndio::print_supported(backend_dispatcher, context.arena())) { + return 1; + } + + return 0; } address::IoUri output_uri(context.arena()); diff --git a/src/tools/roc_send/cmdline.ggo b/src/tools/roc_send/cmdline.ggo index bcbb3e31b..cf5487a58 100644 --- a/src/tools/roc_send/cmdline.ggo +++ b/src/tools/roc_send/cmdline.ggo @@ -29,17 +29,17 @@ section "Options" option "nbrpr" - "Number of repair packets in FEC block" int optional - option "packet-length" - "Outgoing packet length, TIME units" + option "packet-len" - "Outgoing packet length, TIME units" string optional - option "packet-limit" - "Maximum packet size, in bytes" - int optional + option "frame-len" - "Duration of the internal frames, TIME units" + typestr="TIME" string optional - option "frame-limit" - "Maximum internal frame size, in bytes" - int optional + option "max-packet-size" - "Maximum packet size, in SIZE units" + typestr="SIZE" string optional - option "frame-length" - "Duration of the internal frames, TIME units" - typestr="TIME" string optional + option "max-frame-size" - "Maximum internal frame size, in SIZE units" + typestr="SIZE" string optional option "rate" - "Override input sample rate, Hz" int optional @@ -71,6 +71,9 @@ FILE_FORMAT is the output file format name, e.g.: TIME is an integer number with a suffix, e.g.: 123ns; 123us; 123ms; 123s; 123m; 123h; +SIZE is an integer number with an optional suffix, e.g.: + 123; 123K; 123M; 123G; + Use --list-supported option to print the list of the supported URI schemes and file formats. diff --git a/src/tools/roc_send/main.cpp b/src/tools/roc_send/main.cpp index 0a7f3e195..bffb2ede6 100644 --- a/src/tools/roc_send/main.cpp +++ b/src/tools/roc_send/main.cpp @@ -15,7 +15,7 @@ #include "roc_core/crash_handler.h" #include "roc_core/heap_arena.h" #include "roc_core/log.h" -#include "roc_core/parse_duration.h" +#include "roc_core/parse_units.h" #include "roc_core/scoped_ptr.h" #include "roc_netio/network_loop.h" #include "roc_netio/udp_sender_port.h" @@ -63,22 +63,60 @@ int main(int argc, char** argv) { break; } + pipeline::SenderConfig sender_config; + + sndio::Config io_config; + io_config.sample_spec.set_channel_set(sender_config.input_sample_spec.channel_set()); + + if (args.packet_len_given) { + if (!core::parse_duration(args.packet_len_arg, sender_config.packet_length)) { + roc_log(LogError, "invalid --packet-len"); + return 1; + } + } + + if (args.frame_len_given) { + if (!core::parse_duration(args.frame_len_arg, io_config.frame_length)) { + roc_log(LogError, "invalid --frame-len: bad format"); + return 1; + } + if (sender_config.input_sample_spec.ns_2_samples_overall(io_config.frame_length) + <= 0) { + roc_log(LogError, "invalid --frame-len: should be > 0"); + return 1; + } + } + node::ContextConfig context_config; - if (args.packet_limit_given) { - if (args.packet_limit_arg <= 0) { - roc_log(LogError, "invalid --packet-limit: should be > 0"); + if (args.max_packet_size_given) { + if (!core::parse_size(args.max_packet_size_arg, context_config.max_packet_size)) { + roc_log(LogError, "invalid --max-packet-size"); return 1; } - context_config.max_packet_size = (size_t)args.packet_limit_arg; + if (context_config.max_packet_size == 0) { + roc_log(LogError, "invalid --max-packet-size: should be > 0"); + return 1; + } + } else { + size_t extra_space = 64; + // TODO(gh-608): take size from --packet-encoding instead of assuming 2 bytes per + // sample + context_config.max_packet_size = 2 + * sender_config.input_sample_spec.ns_2_samples_overall( + sender_config.packet_length) + + extra_space; } - if (args.frame_limit_given) { - if (args.frame_limit_arg <= 0) { - roc_log(LogError, "invalid --frame-limit: should be > 0"); + if (args.max_frame_size_given) { + if (!core::parse_size(args.max_frame_size_arg, context_config.max_frame_size)) { + roc_log(LogError, "invalid --max-frame-size"); + return 1; + } + if (context_config.max_frame_size == 0) { + roc_log(LogError, "invalid --max-frame-size: should be > 0"); return 1; } - context_config.max_frame_size = (size_t)args.frame_limit_arg; } core::HeapArena heap_arena; @@ -102,30 +140,6 @@ int main(int argc, char** argv) { return 0; } - pipeline::SenderConfig sender_config; - - sndio::Config io_config; - io_config.sample_spec.set_channel_set(sender_config.input_sample_spec.channel_set()); - - if (args.packet_length_given) { - if (!core::parse_duration(args.packet_length_arg, sender_config.packet_length)) { - roc_log(LogError, "invalid --packet-length"); - return 1; - } - } - - if (args.frame_length_given) { - if (!core::parse_duration(args.frame_length_arg, io_config.frame_length)) { - roc_log(LogError, "invalid --frame-length: bad format"); - return 1; - } - if (sender_config.input_sample_spec.ns_2_samples_overall(io_config.frame_length) - <= 0) { - roc_log(LogError, "invalid --frame-length: should be > 0"); - return 1; - } - } - sndio::BackendMap::instance().set_frame_size(io_config.frame_length, sender_config.input_sample_spec);