module-roc: Add support for multi-channel audio

This commit is contained in:
Bernhard Sulzer 2025-11-03 15:36:34 +00:00 committed by Wim Taymans
parent 23f0acfd1d
commit 5bca3731a3
3 changed files with 70 additions and 14 deletions

View file

@ -12,6 +12,7 @@
#include <spa/utils/hook.h>
#include <spa/utils/result.h>
#include <spa/param/audio/format-utils.h>
#include <spa/param/audio/raw-json.h>
#include <roc/config.h>
#include <roc/log.h>
@ -52,7 +53,10 @@
*
* - \ref PW_KEY_NODE_NAME
* - \ref PW_KEY_NODE_DESCRIPTION
* - \ref PW_KEY_MEDIA_NAME
* - \ref PW_KEY_NODE_VIRTUAL
* - \ref PW_KEY_MEDIA_CLASS
* - \ref SPA_KEY_AUDIO_POSITION
*
* ## Example configuration
*\code{.unparsed}
@ -70,6 +74,7 @@
* sink.props = {
* node.name = "roc-sink"
* }
* audio.position = [ FL FR ]
* }
* }
*]
@ -264,18 +269,34 @@ static int roc_sink_setup(struct module_roc_sink_data *data)
spa_zero(sender_config);
sender_config.frame_encoding.rate = data->rate;
sender_config.frame_encoding.channels = ROC_CHANNEL_LAYOUT_STEREO;
sender_config.frame_encoding.format = ROC_FORMAT_PCM_FLOAT32;
sender_config.packet_encoding = ROC_PACKET_ENCODING_AVP_L16_STEREO;
sender_config.fec_encoding = data->fec_code;
info.rate = data->rate;
/* Fixed to be the same as ROC sender config above */
info.channels = 2;
info.rate = data->rate;
info.format = SPA_AUDIO_FORMAT_F32;
info.position[0] = SPA_AUDIO_CHANNEL_FL;
info.position[1] = SPA_AUDIO_CHANNEL_FR;
const char* positions = pw_properties_get(data->capture_props, SPA_KEY_AUDIO_POSITION);
int channels = spa_audio_parse_position_n(positions, strlen(positions), info.position, SPA_N_ELEMENTS(info.position), &info.channels);
if(channels == 2) {
sender_config.frame_encoding.channels = ROC_CHANNEL_LAYOUT_STEREO;
sender_config.packet_encoding = ROC_PACKET_ENCODING_AVP_L16_STEREO;
} else {
sender_config.frame_encoding.channels = ROC_CHANNEL_LAYOUT_MULTITRACK;
sender_config.frame_encoding.tracks = channels;
res = roc_context_register_encoding(data->context, PW_ROC_MULTITRACK_ENCODING_ID, &sender_config.frame_encoding);
if(res) {
pw_log_error("failed to register encoding: %d", res);
return -EINVAL;
}
sender_config.packet_encoding = PW_ROC_MULTITRACK_ENCODING_ID;
// As of v0.4.0, roc generates packets bigger than it can handle if many channels are used
// (see github.com/roc-streaming/roc-toolkit/issues/821)
sender_config.packet_length = PW_ROC_DEFAULT_PACKET_LENGTH * 2 / channels;
}
pw_properties_setf(data->capture_props, PW_KEY_NODE_RATE, "1/%d", info.rate);
@ -360,6 +381,7 @@ static const struct spa_dict_item module_roc_sink_info[] = {
"( remote.source.port=<remote receiver port for source packets> ) "
"( remote.repair.port=<remote receiver port for repair packets> ) "
"( remote.control.port=<remote receiver port for control packets> ) "
"( audio.position=<channel map, default:"PW_ROC_STEREO_POSITIONS"> ) "
"( sink.props= { key=val ... } ) " },
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
};
@ -418,6 +440,12 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
if ((str = pw_properties_get(capture_props, PW_KEY_MEDIA_CLASS)) == NULL)
pw_properties_set(capture_props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) {
pw_properties_set(capture_props, SPA_KEY_AUDIO_POSITION, str);
} else {
pw_properties_set(capture_props, SPA_KEY_AUDIO_POSITION, PW_ROC_STEREO_POSITIONS);
}
data->rate = pw_properties_get_uint32(capture_props, PW_KEY_AUDIO_RATE, 0);
if (data->rate == 0)
data->rate = PW_ROC_DEFAULT_RATE;

View file

@ -12,6 +12,7 @@
#include <spa/utils/hook.h>
#include <spa/utils/result.h>
#include <spa/param/audio/format-utils.h>
#include <spa/param/audio/raw-json.h>
#include <roc/config.h>
#include <roc/log.h>
@ -62,7 +63,9 @@
*
* - \ref PW_KEY_NODE_NAME
* - \ref PW_KEY_NODE_DESCRIPTION
* - \ref PW_KEY_MEDIA_NAME
* - \ref PW_KEY_NODE_VIRTUAL
* - \ref PW_KEY_MEDIA_CLASS
* - \ref SPA_KEY_AUDIO_POSITION
*
* ## Example configuration
*\code{.unparsed}
@ -85,6 +88,7 @@
* source.props = {
* node.name = "roc-source"
* }
* audio.position = [ FL FR ]
* }
* }
*]
@ -285,20 +289,33 @@ static int roc_source_setup(struct module_roc_source_data *data)
spa_zero(receiver_config);
receiver_config.frame_encoding.rate = data->rate;
receiver_config.frame_encoding.channels = ROC_CHANNEL_LAYOUT_STEREO;
receiver_config.frame_encoding.format = ROC_FORMAT_PCM_FLOAT32;
receiver_config.resampler_profile = data->resampler_profile;
receiver_config.resampler_backend = data->resampler_backend;
receiver_config.latency_tuner_backend = data->latency_tuner_backend;
receiver_config.latency_tuner_profile = data->latency_tuner_profile;
info.rate = data->rate;
/* Fixed to be the same as ROC receiver config above */
info.channels = 2;
info.rate = data->rate;
info.format = SPA_AUDIO_FORMAT_F32;
info.position[0] = SPA_AUDIO_CHANNEL_FL;
info.position[1] = SPA_AUDIO_CHANNEL_FR;
const char* positions = pw_properties_get(data->playback_props, SPA_KEY_AUDIO_POSITION);
int channels = spa_audio_parse_position_n(positions, strlen(positions), info.position, SPA_N_ELEMENTS(info.position), &info.channels);
if(channels == 2) {
receiver_config.frame_encoding.channels = ROC_CHANNEL_LAYOUT_STEREO;
} else {
receiver_config.frame_encoding.channels = ROC_CHANNEL_LAYOUT_MULTITRACK;
receiver_config.frame_encoding.tracks = channels;
res = roc_context_register_encoding(data->context, PW_ROC_MULTITRACK_ENCODING_ID, &receiver_config.frame_encoding);
if(res) {
pw_log_error("failed to register encoding: %d", res);
return -EINVAL;
}
}
data->stride = info.channels * sizeof(float);
pw_properties_setf(data->playback_props, PW_KEY_NODE_RATE, "1/%d", info.rate);
@ -403,6 +420,7 @@ static const struct spa_dict_item module_roc_source_info[] = {
"( local.source.port=<local receiver port for source packets> ) "
"( local.repair.port=<local receiver port for repair packets> ) "
"( local.control.port=<local receiver port for control packets> ) "
"( audio.position=<channel map, default:"PW_ROC_STEREO_POSITIONS"> ) "
"( source.props= { key=value ... } ) " },
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
};
@ -459,6 +477,12 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
if (pw_properties_get(playback_props, PW_KEY_NODE_NETWORK) == NULL)
pw_properties_set(playback_props, PW_KEY_NODE_NETWORK, "true");
if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) {
pw_properties_set(playback_props, SPA_KEY_AUDIO_POSITION, str);
} else {
pw_properties_set(playback_props, SPA_KEY_AUDIO_POSITION, PW_ROC_STEREO_POSITIONS);
}
data->rate = pw_properties_get_uint32(playback_props, PW_KEY_AUDIO_RATE, 0);
if (data->rate == 0)
data->rate = PW_ROC_DEFAULT_RATE;

View file

@ -13,6 +13,10 @@
#define PW_ROC_DEFAULT_SESS_LATENCY 200
#define PW_ROC_DEFAULT_RATE 44100
#define PW_ROC_DEFAULT_CONTROL_PROTO ROC_PROTO_RTCP
#define PW_ROC_DEFAULT_PACKET_LENGTH 5000000 // 5ms in ns
#define PW_ROC_MULTITRACK_ENCODING_ID 100
#define PW_ROC_STEREO_POSITIONS "[ FL FR ]"
static inline int pw_roc_parse_fec_encoding(roc_fec_encoding *out, const char *str)
{