diff --git a/src/internal_modules/roc_pipeline/config.cpp b/src/internal_modules/roc_pipeline/config.cpp index da37856d7..e93b25e16 100644 --- a/src/internal_modules/roc_pipeline/config.cpp +++ b/src/internal_modules/roc_pipeline/config.cpp @@ -16,6 +16,7 @@ SenderSinkConfig::SenderSinkConfig() : input_sample_spec(DefaultSampleSpec) , payload_type(rtp::PayloadType_L16_Stereo) , packet_length(DefaultPacketLength) + , packet_mtu(0) , enable_timing(false) , enable_auto_duration(false) , enable_auto_cts(false) diff --git a/src/internal_modules/roc_pipeline/config.h b/src/internal_modules/roc_pipeline/config.h index d5b8f77af..ed40b7f7d 100644 --- a/src/internal_modules/roc_pipeline/config.h +++ b/src/internal_modules/roc_pipeline/config.h @@ -66,8 +66,14 @@ struct SenderSinkConfig { unsigned payload_type; //! Packet length, in nanoseconds. + //! If zero and packet_mtu is set, derived automatically from packet_mtu. core::nanoseconds_t packet_length; + //! Maximum packet size (MTU), in bytes. + //! When non-zero and packet_length is zero, packet_length is auto-derived + //! so that the resulting packet fits within this MTU. + size_t packet_mtu; + //! FEC writer parameters. fec::WriterConfig fec_writer; diff --git a/src/internal_modules/roc_pipeline/sender_session.cpp b/src/internal_modules/roc_pipeline/sender_session.cpp index c32401bcd..e27df4701 100644 --- a/src/internal_modules/roc_pipeline/sender_session.cpp +++ b/src/internal_modules/roc_pipeline/sender_session.cpp @@ -52,6 +52,60 @@ bool SenderSession::create_transport_pipeline(SenderEndpoint* source_endpoint, return false; } + // Derive packet_length from packet_mtu if packet_length was not explicitly set. + // We use the encoding's sample spec to compute how many samples fit in the MTU. + core::nanoseconds_t effective_packet_length = sink_config_.packet_length; + if (sink_config_.packet_mtu > 0 && sink_config_.packet_length == 0) { + const size_t rtp_header_size = 64; // conservative RTP+FEC header estimate + if (sink_config_.packet_mtu > rtp_header_size) { + const size_t payload_bytes = sink_config_.packet_mtu - rtp_header_size; + // bytes -> samples per channel (PCM s16 = 2 bytes per sample per channel) + const size_t bytes_per_sample = + pkt_encoding->sample_spec.stream_timestamp_2_bytes(1); + const size_t samples_per_chan = + bytes_per_sample > 0 ? payload_bytes / bytes_per_sample : 0; + if (samples_per_chan > 0) { + effective_packet_length = + pkt_encoding->sample_spec.samples_per_chan_2_ns(samples_per_chan); + } + } + if (effective_packet_length <= 0) { + roc_log(LogError, + "sender session: can't derive packet length from mtu=%lu," + " mtu is too small", + (unsigned long)sink_config_.packet_mtu); + return false; + } + roc_log(LogDebug, + "sender session: derived packet_length=%.3fms from packet_mtu=%lu", + (double)effective_packet_length / core::Millisecond, + (unsigned long)sink_config_.packet_mtu); + } else if (sink_config_.packet_length == 0) { + effective_packet_length = DefaultPacketLength; + } + // If both are set, warn and pick the smaller one. + if (sink_config_.packet_mtu > 0 && sink_config_.packet_length > 0) { + const size_t rtp_header_size = 64; + if (sink_config_.packet_mtu > rtp_header_size) { + const size_t payload_bytes = sink_config_.packet_mtu - rtp_header_size; + const size_t bytes_per_sample = + pkt_encoding->sample_spec.stream_timestamp_2_bytes(1); + const size_t samples_per_chan = + bytes_per_sample > 0 ? payload_bytes / bytes_per_sample : 0; + if (samples_per_chan > 0) { + const core::nanoseconds_t mtu_length = + pkt_encoding->sample_spec.samples_per_chan_2_ns(samples_per_chan); + if (mtu_length < effective_packet_length) { + roc_log(LogWarn, + "sender session: both --packet-len and --packet-mtu set;" + " using smaller value derived from mtu=%.3fms", + (double)mtu_length / core::Millisecond); + effective_packet_length = mtu_length; + } + } + } + } + // First part of pipeline: chained packet writers from packetizer to endpoint. // Packetizer writes packet to this pipeline, and it the end it writes // packets into endpoint outbound writers. @@ -133,7 +187,7 @@ bool SenderSession::create_transport_pipeline(SenderEndpoint* source_endpoint, packetizer_.reset(new (packetizer_) audio::Packetizer( *pkt_writer, source_endpoint->outbound_composer(), *sequencer_, - *payload_encoder_, packet_factory_, sink_config_.packet_length, in_spec)); + *payload_encoder_, packet_factory_, effective_packet_length, in_spec)); if (!packetizer_ || !packetizer_->is_valid()) { return false; } diff --git a/src/tools/roc_send/cmdline.ggo b/src/tools/roc_send/cmdline.ggo index 90e48d170..01f3f812d 100644 --- a/src/tools/roc_send/cmdline.ggo +++ b/src/tools/roc_send/cmdline.ggo @@ -38,6 +38,12 @@ section "Options" option "packet-len" - "Outgoing packet length, TIME units" string optional + option "packet-mtu" - "Maximum packet size (MTU), SIZE units; auto-derives packet length when --packet-len is not set" + typestr="SIZE" string optional + + option "packet-encoding" - "RTP payload type and encoding for outgoing packets, in form PT:FORMAT/RATE/CHANNELS, e.g. 96:s16/48000/stereo" + typestr="PT_SPEC" string optional + option "frame-len" - "Duration of the internal frames, TIME units" typestr="TIME" string optional diff --git a/src/tools/roc_send/main.cpp b/src/tools/roc_send/main.cpp index ca3af5dd0..d3bd5e1c3 100644 --- a/src/tools/roc_send/main.cpp +++ b/src/tools/roc_send/main.cpp @@ -20,6 +20,7 @@ #include "roc_node/context.h" #include "roc_node/sender.h" #include "roc_pipeline/sender_sink.h" +#include "roc_rtp/encoding.h" #include "roc_sndio/backend_dispatcher.h" #include "roc_sndio/backend_map.h" #include "roc_sndio/print_supported.h" @@ -113,6 +114,31 @@ int main(int argc, char** argv) { } } + if (args.packet_mtu_given) { + if (!core::parse_size(args.packet_mtu_arg, sender_config.packet_mtu)) { + roc_log(LogError, "invalid --packet-mtu: bad format"); + return 1; + } + if (sender_config.packet_mtu == 0) { + roc_log(LogError, "invalid --packet-mtu: should be > 0"); + return 1; + } + // Signal to the pipeline to derive packet_length from MTU. + if (!args.packet_len_given) { + sender_config.packet_length = 0; + } + } + + if (args.packet_encoding_given) { + rtp::Encoding enc; + if (!rtp::parse_encoding(args.packet_encoding_arg, enc)) { + roc_log(LogError, "invalid --packet-encoding: bad format," + " expected PT:FORMAT/RATE/CHANNELS, e.g. 96:s16/48000/stereo"); + return 1; + } + sender_config.payload_type = enc.payload_type; + } + if (args.source_given) { address::EndpointUri source_endpoint(heap_arena); if (!address::parse_endpoint_uri( @@ -268,6 +294,23 @@ int main(int argc, char** argv) { return 1; } + if (args.packet_encoding_given) { + rtp::Encoding enc; + if (!rtp::parse_encoding(args.packet_encoding_arg, enc)) { + roc_log(LogError, "invalid --packet-encoding"); + return 1; + } + // Only register if it's not a built-in payload type. + if (!context.encoding_map().find_by_pt(enc.payload_type)) { + if (!context.encoding_map().add_encoding(enc)) { + roc_log(LogError, + "can't register --packet-encoding: payload type %u already exists", + enc.payload_type); + return 1; + } + } + } + sndio::BackendDispatcher backend_dispatcher(context.arena()); if (args.list_supported_given) { if (!address::print_supported(context.arena())) {