diff --git a/README.md b/README.md index 2ea4c46..9f8a2dd 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,7 @@ - [Build instructions](#build-instructions) - [Running receiver](#running-receiver) - [Running sender](#running-sender) -- [Custom audio encoding](#custom-audio-encoding) -- [Custom FEC encoding](#custom-fec-encoding) -- [Configuring latency](#configuring-latency) -- [Configuring source or sink name](#configuring-source-or-sink-name) +- [Advanced configuration](#advanced-configuration) - [Troubleshooting](#troubleshooting) - [Authors](#authors) - [License](#license) @@ -172,29 +169,32 @@ For the receiving side, use `module-roc-sink-input` PulseAudio module. It create Roc sink input supports several options: -| option | default | description | note | -|----------------------------|------------------------|--------------------------------------------------------------|---------------------| -| sink | \ | the name of the sink to connect the new sink input to | | -| sink\_input\_properties | empty | additional sink input properties | | -| local\_ip | 0.0.0.0 | local address to bind to | | -| local\_source\_port | 10001 | local port for source (RTP) packets | | -| local\_repair\_port | 10002 | local port for repair (FEC) packets | | -| local\_control\_port | 10003 | local port for control (RTCP) packets | | -| packet\_encoding_id | 10 | encoding id (any number, same on sender and receiver) | for custom encoding | -| packet\_encoding\_rate | 44100 | encoding sample rate | for custom encoding | -| packet\_encoding\_format | s16 | encoding sample format (s16) | for custom encoding | -| packet\_encoding\_chans | stereo | encoding channel layout (mono, stereo) | for custom encoding | -| fec\_encoding | rs8m | encoding for FEC packets (default, disable, rs8m, ldpc) | | -| resampler\_backend | selected automatically | resampler backend (default, builtin, speex, speexdec) | | -| resampler\_profile | medium | resampler profile (default, high, medium, low) | | -| latency\_backend | selected automatically | latency tuner backend (default, niq) | | -| latency\_profile | selected automatically | latency tuner profile (default, intact, responsive, gradual) | | -| target\_latency\_msec | 200 | target latency in milliseconds | | -| min\_latency\_msec | selected automatically | minimum latency in milliseconds | | -| max\_latency\_msec | selected automatically | maximum latency in milliseconds | | -| io\_latency\_msec | 40 | playback latency in milliseconds | | -| no\_play_timeout\_msec | selected automatically | no playback timeout in milliseconds | | -| choppy\_play_timeout\_msec | selected automatically | choppy playback timeout in milliseconds | | +| option | default | description | note | +|----------------------------|------------------------|-----------------------------------------------------------------------------|-----------------------------| +| local\_ip | 0.0.0.0 | local address to bind to | | +| local\_source\_port | 10001 | local port for source (RTP) packets | | +| local\_repair\_port | 10002 | local port for repair (FEC) packets | | +| local\_control\_port | 10003 | local port for control (RTCP) packets | | +| sink | \ | the name of the sink to connect the new sink input to | | +| sink\_input\_properties | empty | additional sink input properties | | +| sink\_input\_rate | 44100 | local sink input sample rate | | +| sink\_input\_format | f32 | local sink input sample format (f32) | | +| sink\_input\_chans | stereo | local sink input channel layout (mono, stereo) | | +| packet\_encoding_id | 10 | encoding id for audio packets (any number, but same on sender and receiver) | for custom network encoding | +| packet\_encoding\_rate | 44100 | sample rate for audio packets | for custom network encoding | +| packet\_encoding\_format | s16 | sample format for audio packets (s16) | for custom network encoding | +| packet\_encoding\_chans | stereo | channel layout for audio packets (mono, stereo) | for custom network encoding | +| fec\_encoding | rs8m | encoding for FEC packets (default, disable, rs8m, ldpc) | | +| resampler\_backend | selected automatically | resampler backend (default, builtin, speex, speexdec) | | +| resampler\_profile | medium | resampler profile (default, high, medium, low) | | +| latency\_backend | selected automatically | latency tuner backend (default, niq) | | +| latency\_profile | selected automatically | latency tuner profile (default, intact, responsive, gradual) | | +| target\_latency\_msec | 200 | target latency in milliseconds | | +| min\_latency\_msec | selected automatically | minimum latency in milliseconds | | +| max\_latency\_msec | selected automatically | maximum latency in milliseconds | | +| io\_latency\_msec | 40 | playback latency in milliseconds | | +| no\_play_timeout\_msec | selected automatically | no playback timeout in milliseconds | | +| choppy\_play_timeout\_msec | selected automatically | choppy playback timeout in milliseconds | | Here is how you can create a Roc sink input from command line: @@ -228,29 +228,32 @@ For the sending side, use `module-roc-sink` PulseAudio module. It creates a Puls Roc sink supports several options: -| option | default | description | note | -|--------------------------|------------------------|--------------------------------------------------------------|-------------------------------| -| sink\_name | roc\_sender | the name of the new sink | | -| sink\_properties | empty | additional sink properties | | -| remote\_ip | required parameter | remote receiver address | | -| remote\_source\_port | 10001 | remote receiver port for source (audio) packets | | -| remote\_repair\_port | 10002 | remote receiver port for repair (FEC) packets | | -| remote\_control\_port | 10003 | remote receiver port for control (RTCP) packets | | -| packet\_encoding_id | 10 | encoding id (any number, same on sender and receiver) | for custom encoding | -| packet\_encoding\_rate | 44100 | encoding sample rate | for custom encoding | -| packet\_encoding\_format | s16 | encoding sample format (s16) | for custom encoding | -| packet\_encoding\_chans | stereo | encoding channel layout (mono, stereo) | for custom encoding | -| packet\_length\_msec | 5 | audio packet length in milliseconds | | -| fec\_encoding | rs8m | encoding for FEC packets (default, disable, rs8m, ldpc) | | -| fec\_block\_nbsrc | 18 | number of source packets in FEC block | | -| fec\_block\_nbrpr | 10 | number of repair packets in FEC block | | -| resampler\_backend | selected automatically | resampler backend (default, builtin, speex, speexdec) | | -| resampler\_profile | medium | resampler profile (default, high, medium, low) | | -| latency\_backend | disabled | latency tuner backend (default, niq) | for sender-side latency tuner | -| latency\_profile | disabled | latency tuner profile (default, intact, responsive, gradual) | for sender-side latency tuner | -| target\_latency\_msec | disabled | target latency in milliseconds | for sender-side latency tuner | -| min\_latency\_msec | disabled | minimum latency in milliseconds | for sender-side latency tuner | -| max\_latency\_msec | disabled | maximum latency in milliseconds | for sender-side latency tuner | +| option | default | description | note | +|--------------------------|------------------------|-----------------------------------------------------------------------------|-------------------------------| +| remote\_ip | required parameter | remote receiver address | | +| remote\_source\_port | 10001 | remote receiver port for source (audio) packets | | +| remote\_repair\_port | 10002 | remote receiver port for repair (FEC) packets | | +| remote\_control\_port | 10003 | remote receiver port for control (RTCP) packets | | +| sink\_name | roc\_sender | the name of the new sink | | +| sink\_properties | empty | additional sink properties | | +| sink\_rate | 44100 | local sink sample rate | | +| sink\_format | f32 | local sink sample format (f32) | | +| sink\_chans | stereo | local sink channel layout (mono, stereo) | | +| packet\_encoding_id | 10 | encoding id for audio packets (any number, but same on sender and receiver) | for custom network encoding | +| packet\_encoding\_rate | 44100 | sample rate for audio packets | for custom network encoding | +| packet\_encoding\_format | s16 | sample format for audio packets (s16) | for custom network encoding | +| packet\_encoding\_chans | stereo | channel layout for audio packets (mono, stereo) | for custom network encoding | +| packet\_length\_msec | 5 | audio packet length in milliseconds | | +| fec\_encoding | rs8m | encoding for FEC packets (default, disable, rs8m, ldpc) | | +| fec\_block\_nbsrc | 18 | number of source packets in FEC block | | +| fec\_block\_nbrpr | 10 | number of repair packets in FEC block | | +| resampler\_backend | selected automatically | resampler backend (default, builtin, speex, speexdec) | | +| resampler\_profile | medium | resampler profile (default, high, medium, low) | | +| latency\_backend | disabled | latency tuner backend (default, niq) | for sender-side latency tuner | +| latency\_profile | disabled | latency tuner profile (default, intact, responsive, gradual) | for sender-side latency tuner | +| target\_latency\_msec | disabled | target latency in milliseconds | for sender-side latency tuner | +| min\_latency\_msec | disabled | minimum latency in milliseconds | for sender-side latency tuner | +| max\_latency\_msec | disabled | maximum latency in milliseconds | for sender-side latency tuner | Here is how you can create a Roc sink from command line: @@ -274,7 +277,22 @@ Or via the `pavucontrol` graphical tool: ![image](docs/roc_pulse_sender.png) -## Custom audio encoding +## Advanced configuration + +### Custom local encoding + +You can use these options to control how Roc sink or sink input presents itself to PulseAudio: + +* `sink_rate`, `sink_format`, `sink_channels` +* `sink_input_rate`, `sink_input_format`, `sink_input_channels` + +These options may be useful if you want to avoid automatic conversions (such as resampling and channel mapping) performed by PulseAudio when device and application encodings don't match. + +Note that if sink/sink input encoding doesn't match packet encoding (see below), Roc will perform conversion by itself, so you probably want to configure that too. + +Also note that Roc receiver usually performs resampling even when there is no sample rate mismatch, because it is used to adjust clock speed. Hence it makes sense to avoid resampling in PulseAudio and keep resampling only in Roc. + +### Custom packet encoding By default, `module-roc-sink-input` and `module-roc-sink` code audio as 44100Hz with 16-bit stereo. @@ -287,7 +305,7 @@ To employ alternative encoding, you need to provide the following options: All four parameters should be provided on **both sender and receiver** and have **exact same values**. -## Custom FEC encoding +### Custom FEC encoding By default, `module-roc-sink-input` and `module-roc-sink` use Reed-Solomon (`rs8m`) FEC encoding for loss repair. @@ -299,7 +317,7 @@ This can be configured via `fec_encoding` parameter. Available options are: This parameter should be provided on **both sender and receiver** and have **exact same value**. -## Configuring latency +### Configuring latency Essential receiver-side (`module-roc-sink-input`) parameters are: @@ -326,7 +344,7 @@ There are also sender-side (`module-roc-sink`) parameters that affect latency: For lower latency, you may need lower packet length and FEC block size. And vice versa, for higher latency and network jitter, you may need to increase both packet length (for less overhead) and FEC block size (for better repair). -## Configuring source or sink name +### Configuring source or sink name PulseAudio sinks and sink inputs have name and description. Name is usually used when the sink or sink input is referenced from command-line tools or configuration files, and description is shown in the GUI. diff --git a/src/module-roc-sink-input.c b/src/module-roc-sink-input.c index faab1bd..6f2a32d 100644 --- a/src/module-roc-sink-input.c +++ b/src/module-roc-sink-input.c @@ -32,12 +32,15 @@ PA_MODULE_AUTHOR("Roc Streaming authors"); PA_MODULE_DESCRIPTION("Read audio stream from Roc receiver"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(false); -PA_MODULE_USAGE("sink= " - "sink_input_properties= " - "local_ip= " +PA_MODULE_USAGE("local_ip= " "local_source_port= " "local_repair_port= " "local_control_port= " + "sink= " + "sink_input_properties= " + "sink_input_rate " + "sink_input_format=f32 " + "sink_input_chans=mono|stereo " "packet_encoding_id=<8-bit number> " "packet_encoding_rate= " "packet_encoding_format=s16 " @@ -66,30 +69,33 @@ struct roc_sink_input_userdata { roc_receiver* receiver; }; -static const char* const roc_sink_input_modargs[] = { // - "sink", // - "sink_input_name", // - "sink_input_properties", // - "local_ip", // - "local_source_port", // - "local_repair_port", // - "local_control_port", // - "packet_encoding_id", // - "packet_encoding_rate", // - "packet_encoding_format", // - "packet_encoding_chans", // - "fec_encoding", // - "resampler_backend", // - "resampler_profile", // - "latency_backend", // - "latency_profile", // - "target_latency_msec", // - "min_latency_msec", // - "max_latency_msec", // - "io_latency_msec", // - "no_play_timeout_msec", // - "choppy_play_timeout_msec", // - NULL +static const char* const roc_sink_input_modargs[] = { + "local_ip", + "local_source_port", + "local_repair_port", + "local_control_port", + "sink", + "sink_input_name", + "sink_input_properties", + "sink_rate", + "sink_format", + "sink_chans", + "packet_encoding_id", + "packet_encoding_rate", + "packet_encoding_format", + "packet_encoding_chans", + "fec_encoding", + "resampler_backend", + "resampler_profile", + "latency_backend", + "latency_profile", + "target_latency_msec", + "min_latency_msec", + "max_latency_msec", + "io_latency_msec", + "no_play_timeout_msec", + "choppy_play_timeout_msec", + NULL, }; static int process_message( @@ -218,9 +224,12 @@ int pa__init(pa_module* m) { roc_receiver_config receiver_config; memset(&receiver_config, 0, sizeof(receiver_config)); - receiver_config.frame_encoding.rate = 44100; - receiver_config.frame_encoding.channels = ROC_CHANNEL_LAYOUT_STEREO; - receiver_config.frame_encoding.format = ROC_FORMAT_PCM_FLOAT32; + if (rocpulse_parse_media_encoding(&receiver_config.frame_encoding, args, + "sink_input_rate", "sink_input_format", + "sink_input_chans") + < 0) { + goto error; + } roc_packet_encoding receiver_packet_encoding = 0; @@ -232,6 +241,7 @@ int pa__init(pa_module* m) { if (receiver_packet_encoding != 0) { roc_media_encoding encoding; + memset(&encoding, 0, sizeof(encoding)); if (rocpulse_parse_media_encoding(&encoding, args, "packet_encoding_rate", "packet_encoding_format", @@ -245,10 +255,6 @@ int pa__init(pa_module* m) { pa_log("can't register packet encoding"); goto error; } - - /* propagate packet encoding to sink input */ - receiver_config.frame_encoding.rate = encoding.rate; - receiver_config.frame_encoding.channels = encoding.channels; } roc_fec_encoding receiver_fec_encoding = ROC_FEC_ENCODING_DEFAULT; diff --git a/src/module-roc-sink.c b/src/module-roc-sink.c index ef4e41c..6221926 100644 --- a/src/module-roc-sink.c +++ b/src/module-roc-sink.c @@ -34,12 +34,15 @@ PA_MODULE_AUTHOR("Roc Streaming authors"); PA_MODULE_DESCRIPTION("Write audio stream to Roc sender"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(false); -PA_MODULE_USAGE("sink_name= " - "sink_properties= " - "remote_ip= " +PA_MODULE_USAGE("remote_ip= " "remote_source_port= " "remote_repair_port=" "remote_control_port= " + "sink_name= " + "sink_properties= " + "sink_rate " + "sink_format=f32 " + "sink_chans=mono|stereo " "packet_encoding_id=<8-bit number> " "packet_encoding_rate= " "packet_encoding_format=s16 " @@ -56,6 +59,34 @@ PA_MODULE_USAGE("sink_name= " "min_latency_msec= " "max_latency_msec="); +static const char* const roc_sink_modargs[] = { + "remote_ip", + "remote_source_port", + "remote_repair_port", + "remote_control_port", + "sink_name", + "sink_properties", + "sink_rate", + "sink_format", + "sink_chans", + "packet_encoding_id", + "packet_encoding_rate", + "packet_encoding_format", + "packet_encoding_chans", + "packet_length_msec", + "fec_encoding", + "fec_block_nbsrc", + "fec_block_nbrpr", + "resampler_backend", + "resampler_profile", + "latency_backend", + "latency_profile", + "target_latency_msec", + "min_latency_msec", + "max_latency_msec", + NULL, +}; + struct roc_sink_userdata { pa_module* module; pa_sink* sink; @@ -74,31 +105,6 @@ struct roc_sink_userdata { roc_sender* sender; }; -static const char* const roc_sink_modargs[] = { // - "sink_name", // - "sink_properties", // - "remote_ip", // - "remote_source_port", // - "remote_repair_port", // - "remote_control_port", // - "packet_encoding_id", // - "packet_encoding_rate", // - "packet_encoding_format", // - "packet_encoding_chans", // - "packet_length_msec", // - "fec_encoding", // - "fec_block_nbsrc", // - "fec_block_nbrpr", // - "resampler_backend", // - "resampler_profile", // - "latency_backend", // - "latency_profile", // - "target_latency_msec", // - "min_latency_msec", // - "max_latency_msec", // - NULL -}; - static int process_message( pa_msgobject* o, int code, void* data, int64_t offset, pa_memchunk* chunk) { switch (code) { @@ -267,9 +273,11 @@ int pa__init(pa_module* m) { roc_sender_config sender_config; memset(&sender_config, 0, sizeof(sender_config)); - sender_config.frame_encoding.rate = 44100; - sender_config.frame_encoding.channels = ROC_CHANNEL_LAYOUT_STEREO; - sender_config.frame_encoding.format = ROC_FORMAT_PCM_FLOAT32; + if (rocpulse_parse_media_encoding(&sender_config.frame_encoding, args, "sink_rate", + "sink_format", "sink_chans") + < 0) { + goto error; + } if (rocpulse_parse_packet_encoding(&sender_config.packet_encoding, args, "packet_encoding_id") @@ -279,6 +287,7 @@ int pa__init(pa_module* m) { if (sender_config.packet_encoding != 0) { roc_media_encoding encoding; + memset(&encoding, 0, sizeof(encoding)); if (rocpulse_parse_media_encoding(&encoding, args, "packet_encoding_rate", "packet_encoding_format", @@ -293,10 +302,6 @@ int pa__init(pa_module* m) { pa_log("can't register packet encoding"); goto error; } - - /* propagate packet encoding to sink */ - sender_config.frame_encoding.rate = encoding.rate; - sender_config.frame_encoding.channels = encoding.channels; } if (rocpulse_parse_duration_msec_ul(&sender_config.packet_length, 1, args, diff --git a/src/rocpulse_helpers.c b/src/rocpulse_helpers.c index 5a30cea..78e866a 100644 --- a/src/rocpulse_helpers.c +++ b/src/rocpulse_helpers.c @@ -234,13 +234,21 @@ int rocpulse_parse_media_encoding(roc_media_encoding* out, const char* rate_arg_name, const char* format_arg_name, const char* chans_arg_name) { - memset(out, 0, sizeof(*out)); - /* rate */ if (rocpulse_parse_uint(&out->rate, args, rate_arg_name, "44100") < 0) { return -1; } + /* format */ + const char* format = pa_modargs_get_value(args, format_arg_name, "s16"); + if (!format || !*format || strcmp(format, "s16") == 0 || strcmp(format, "f32") == 0) { + // TODO: properly handle format when roc-toolkit is fixed + out->format = ROC_FORMAT_PCM_FLOAT32; + } else { + pa_log("invalid %s: %s", format_arg_name, format); + return -1; + } + /* channels */ const char* chans = pa_modargs_get_value(args, chans_arg_name, "stereo"); if (!chans || !*chans || strcmp(chans, "stereo") == 0) { @@ -252,16 +260,6 @@ int rocpulse_parse_media_encoding(roc_media_encoding* out, return -1; } - /* format */ - const char* format = pa_modargs_get_value(args, format_arg_name, "s16"); - if (!format || !*format || strcmp(format, "s16") == 0) { - // TODO: use proper format when roc API is fixed - out->format = ROC_FORMAT_PCM_FLOAT32; - } else { - pa_log("invalid %s: %s", format_arg_name, format); - return -1; - } - return 0; } @@ -393,7 +391,12 @@ int rocpulse_extract_encoding(const roc_media_encoding* src_encoding, return -1; } - dst_sample_spec->format = PA_SAMPLE_FLOAT32LE; + switch (src_encoding->format) { + case ROC_FORMAT_PCM_FLOAT32: + dst_sample_spec->format = PA_SAMPLE_FLOAT32LE; + break; + } + dst_sample_spec->rate = src_encoding->rate; dst_sample_spec->channels = dst_channel_map->channels;