diff --git a/doc/dox/config/pipewire-client.conf.5.md b/doc/dox/config/pipewire-client.conf.5.md index db43839a0..321538a79 100644 --- a/doc/dox/config/pipewire-client.conf.5.md +++ b/doc/dox/config/pipewire-client.conf.5.md @@ -80,6 +80,9 @@ stream.properties = { #channelmix.fc-cutoff = 12000.0 #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 + #channelmix.center-level = 0.707106781 + #channelmix.surround-level = 0.707106781 + #channelmix.lfe-level = 0.5 #channelmix.hilbert-taps = 0 #dither.noise = 0 #dither.method = none # rectangular, triangular, triangular-hf, wannamaker3, shaped5 diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index a215c6f21..44b16f505 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -783,6 +783,15 @@ more to the center speaker and leaves the ambient sound in the stereo channels. This is only active when up-mix is enabled and a Front Center channel is mixed. \endparblock +@PAR@ node-prop channelmix.center-level = 0.707106781 +The level of the center channel when up/downmixing. + +@PAR@ node-prop channelmix.surround-level = 0.707106781 +The level of the surround channels when up/downmixing. + +@PAR@ node-prop channelmix.lfe-level = 0.5 +The level of the LFE channel when up/downmixing. + @PAR@ node-prop channelmix.hilbert-taps = 0 \parblock This option will apply a 90 degree phase shift to the rear channels to improve specialization. diff --git a/doc/dox/config/pipewire-pulse.conf.5.md b/doc/dox/config/pipewire-pulse.conf.5.md index ad1b213c7..9cc8d4c48 100644 --- a/doc/dox/config/pipewire-pulse.conf.5.md +++ b/doc/dox/config/pipewire-pulse.conf.5.md @@ -93,6 +93,9 @@ stream.properties = { #channelmix.fc-cutoff = 12000.0 #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 + #channelmix.center-level = 0.707106781 + #channelmix.surround-level = 0.707106781 + #channelmix.lfe-level = 0.5 #channelmix.hilbert-taps = 0 #dither.noise = 0 #dither.method = none # rectangular, triangular, triangular-hf, wannamaker3, shaped5 diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index dce1367f1..482200854 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -724,6 +724,34 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index, SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 19: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.center-level"), + SPA_PROP_INFO_description, SPA_POD_String("Center up/downmix level"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.center_level, 0.0, 10.0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 20: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.surround-level"), + SPA_PROP_INFO_description, SPA_POD_String("Surround up/downmix level"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.surround_level, 0.0, 10.0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 21: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.lfe-level"), + SPA_PROP_INFO_description, SPA_POD_String("LFE up/downmix level"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.lfe_level, 0.0, 10.0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + + case 22: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.hilbert-taps"), @@ -732,7 +760,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index, this->mix.hilbert_taps, 0, MAX_TAPS), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 20: + case 23: spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); spa_pod_builder_add(b, SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix-method"), @@ -751,14 +779,14 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index, spa_pod_builder_pop(b, &f[1]); *param = spa_pod_builder_pop(b, &f[0]); break; - case 21: + case 24: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_rate), SPA_PROP_INFO_description, SPA_POD_String("Rate scaler"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Double(p->rate, 0.0, 10.0)); break; - case 22: + case 25: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_quality), @@ -767,7 +795,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index, SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->resample_quality, 0, 14), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 23: + case 26: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("resample.disable"), @@ -775,7 +803,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index, SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->resample_disabled), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 24: + case 27: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("dither.noise"), @@ -783,7 +811,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index, SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(this->dir[1].conv.noise_bits, 0, 16), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 25: + case 28: spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); spa_pod_builder_add(b, SPA_PROP_INFO_name, SPA_POD_String("dither.method"), @@ -801,7 +829,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index, spa_pod_builder_pop(b, &f[1]); *param = spa_pod_builder_pop(b, &f[0]); break; - case 26: + case 29: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("debug.wav-path"), @@ -809,7 +837,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index, SPA_PROP_INFO_type, SPA_POD_String(p->wav_path), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 27: + case 30: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.lock-volumes"), @@ -817,7 +845,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index, SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->lock_volumes), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 28: + case 31: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph.disable"), @@ -825,7 +853,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index, SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->filter_graph_disabled), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 29: + case 32: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph.N"), @@ -836,7 +864,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index, default: if (this->filter_graph[0] && this->filter_graph[0]->graph) { return spa_filter_graph_enum_prop_info(this->filter_graph[0]->graph, - index - 30, b, param); + index - 33, b, param); } return 0; } @@ -903,6 +931,12 @@ static int node_param_props(struct impl *this, uint32_t id, uint32_t index, spa_pod_builder_float(b, this->mix.rear_delay); spa_pod_builder_string(b, "channelmix.stereo-widen"); spa_pod_builder_float(b, this->mix.widen); + spa_pod_builder_string(b, "channelmix.center-level"); + spa_pod_builder_float(b, this->mix.center_level); + spa_pod_builder_string(b, "channelmix.surround-level"); + spa_pod_builder_float(b, this->mix.surround_level); + spa_pod_builder_string(b, "channelmix.lfe-level"); + spa_pod_builder_float(b, this->mix.lfe_level); spa_pod_builder_string(b, "channelmix.hilbert-taps"); spa_pod_builder_int(b, this->mix.hilbert_taps); spa_pod_builder_string(b, "channelmix.upmix-method"); @@ -1490,6 +1524,12 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char * spa_atof(s, &this->mix.rear_delay); else if (spa_streq(k, "channelmix.stereo-widen")) spa_atof(s, &this->mix.widen); + else if (spa_streq(k, "channelmix.center-level")) + spa_atof(s, &this->mix.center_level); + else if (spa_streq(k, "channelmix.surround-level")) + spa_atof(s, &this->mix.surround_level); + else if (spa_streq(k, "channelmix.lfe-level")) + spa_atof(s, &this->mix.lfe_level); else if (spa_streq(k, "channelmix.hilbert-taps")) spa_atou32(s, &this->mix.hilbert_taps, 0); else if (spa_streq(k, "channelmix.upmix-method")) @@ -4320,13 +4360,7 @@ impl_init(const struct spa_handle_factory *factory, this->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; this->rate_limit.burst = 1; - this->mix.options = CHANNELMIX_OPTION_UPMIX | CHANNELMIX_OPTION_MIX_LFE; - this->mix.upmix = CHANNELMIX_UPMIX_NONE; - this->mix.log = this->log; - this->mix.lfe_cutoff = 0.0f; - this->mix.fc_cutoff = 0.0f; - this->mix.rear_delay = 0.0f; - this->mix.widen = 0.0f; + channelmix_reset(&this->mix); for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; diff --git a/spa/plugins/audioconvert/channelmix-ops.c b/spa/plugins/audioconvert/channelmix-ops.c index 921270450..574d0dd1b 100644 --- a/spa/plugins/audioconvert/channelmix-ops.c +++ b/spa/plugins/audioconvert/channelmix-ops.c @@ -198,9 +198,9 @@ static int make_matrix(struct channelmix *mix) uint32_t dst_chan = mix->dst_chan; uint64_t unassigned, keep; uint32_t i, j, ic, jc, matrix_encoding = MATRIX_NORMAL; - float clev = SQRT1_2; - float slev = SQRT1_2; - float llev = 0.5f; + float clev = mix->center_level; + float slev = mix->surround_level; + float llev = mix->lfe_level; float maxsum = 0.0f; bool filter_fc = false, filter_lfe = false, matched = false, normalize; #define _MATRIX(s,d) matrix[_CH(s)][_CH(d)] @@ -874,6 +874,21 @@ static void impl_channelmix_free(struct channelmix *mix) mix->process = NULL; } +void channelmix_reset(struct channelmix *mix) +{ + spa_zero(*mix); + mix->options = CHANNELMIX_DEFAULT_OPTIONS; + mix->upmix = CHANNELMIX_DEFAULT_UPMIX; + mix->lfe_cutoff = CHANNELMIX_DEFAULT_LFE_CUTOFF; + mix->fc_cutoff = CHANNELMIX_DEFAULT_FC_CUTOFF; + mix->rear_delay = CHANNELMIX_DEFAULT_REAR_DELAY; + mix->center_level = CHANNELMIX_DEFAULT_CENTER_LEVEL; + mix->surround_level = CHANNELMIX_DEFAULT_SURROUND_LEVEL; + mix->lfe_level = CHANNELMIX_DEFAULT_LFE_LEVEL; + mix->widen = CHANNELMIX_DEFAULT_WIDEN; + mix->hilbert_taps = CHANNELMIX_DEFAULT_HILBERT_TAPS; +} + int channelmix_init(struct channelmix *mix) { const struct channelmix_info *info; diff --git a/spa/plugins/audioconvert/channelmix-ops.h b/spa/plugins/audioconvert/channelmix-ops.h index 947cc6f34..c66eaddd3 100644 --- a/spa/plugins/audioconvert/channelmix-ops.h +++ b/spa/plugins/audioconvert/channelmix-ops.h @@ -28,6 +28,17 @@ #define CHANNELMIX_OPS_MAX_ALIGN 16 +#define CHANNELMIX_DEFAULT_OPTIONS (CHANNELMIX_OPTION_UPMIX | CHANNELMIX_OPTION_MIX_LFE) +#define CHANNELMIX_DEFAULT_UPMIX CHANNELMIX_UPMIX_NONE +#define CHANNELMIX_DEFAULT_LFE_CUTOFF 0.0f +#define CHANNELMIX_DEFAULT_FC_CUTOFF 0.0f +#define CHANNELMIX_DEFAULT_REAR_DELAY 0.0f +#define CHANNELMIX_DEFAULT_CENTER_LEVEL 0.707106781f +#define CHANNELMIX_DEFAULT_SURROUND_LEVEL 0.707106781f +#define CHANNELMIX_DEFAULT_LFE_LEVEL 0.5f +#define CHANNELMIX_DEFAULT_WIDEN 0.0f +#define CHANNELMIX_DEFAULT_HILBERT_TAPS 0 + struct channelmix { uint32_t src_chan; uint32_t dst_chan; @@ -60,6 +71,9 @@ struct channelmix { float fc_cutoff; /* in Hz, 0 is disabled */ float rear_delay; /* in ms, 0 is disabled */ float widen; /* stereo widen. 0 is disabled */ + float center_level; /* center down/upmix level, sqrt(1/2) */ + float lfe_level; /* lfe down/upmix level, 1/2 */ + float surround_level; /* surround down/upmix level, sqrt(1/2) */ uint32_t hilbert_taps; /* to phase shift, 0 disabled */ struct lr4 lr4[MAX_CHANNELS]; @@ -80,6 +94,7 @@ struct channelmix { void *data; }; +void channelmix_reset(struct channelmix *mix); int channelmix_init(struct channelmix *mix); static const struct channelmix_upmix_info { diff --git a/spa/plugins/audioconvert/test-audioconvert.c b/spa/plugins/audioconvert/test-audioconvert.c index de3ebb8b5..de18d524b 100644 --- a/spa/plugins/audioconvert/test-audioconvert.c +++ b/spa/plugins/audioconvert/test-audioconvert.c @@ -54,7 +54,7 @@ static int setup_context(struct context *ctx) size_t size; int res; struct spa_support support[1]; - struct spa_dict_item items[6]; + struct spa_dict_item items[9]; const struct spa_handle_factory *factory; void *iface; @@ -76,10 +76,13 @@ static int setup_context(struct context *ctx) items[3] = SPA_DICT_ITEM_INIT("channelmix.lfe-cutoff", "150"); items[4] = SPA_DICT_ITEM_INIT("channelmix.fc-cutoff", "12000"); items[5] = SPA_DICT_ITEM_INIT("channelmix.rear-delay", "12.0"); + items[6] = SPA_DICT_ITEM_INIT("channelmix.center-level", "0.707106781"); + items[7] = SPA_DICT_ITEM_INIT("channelmix.surround-level", "0.707106781"); + items[8] = SPA_DICT_ITEM_INIT("channelmix.lfe-level", "0.5"); res = spa_handle_factory_init(factory, ctx->convert_handle, - &SPA_DICT_INIT(items, 6), + &SPA_DICT_INIT(items, 9), support, 1); spa_assert_se(res >= 0); diff --git a/spa/plugins/audioconvert/test-channelmix.c b/spa/plugins/audioconvert/test-channelmix.c index b68c956bf..529db880f 100644 --- a/spa/plugins/audioconvert/test-channelmix.c +++ b/spa/plugins/audioconvert/test-channelmix.c @@ -45,7 +45,7 @@ static void test_mix(uint32_t src_chan, uint32_t src_mask, uint32_t dst_chan, ui spa_log_debug(&logger.log, "start %d->%d (%08x -> %08x)", src_chan, dst_chan, src_mask, dst_mask); - spa_zero(mix); + channelmix_reset(&mix); mix.options = options; mix.src_chan = src_chan; mix.dst_chan = dst_chan; @@ -340,7 +340,7 @@ static void test_n_m_impl(void) src[i] = src_data[i]; } - spa_zero(mix); + channelmix_reset(&mix); mix.src_chan = 16; mix.dst_chan = 12; mix.log = &logger.log; diff --git a/src/daemon/client.conf.in b/src/daemon/client.conf.in index 46874af93..e0baeda92 100644 --- a/src/daemon/client.conf.in +++ b/src/daemon/client.conf.in @@ -101,6 +101,9 @@ stream.properties = { #channelmix.lfe-cutoff = 150 #channelmix.fc-cutoff = 12000 #channelmix.rear-delay = 12.0 + #channelmix.center-level = 0.707106781 + #channelmix.surround-level = 0.707106781 + #channelmix.lfe-level = 0.5 #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 #dither.noise = 0 diff --git a/src/daemon/minimal.conf.in b/src/daemon/minimal.conf.in index 6def01bcf..7ab93e92b 100644 --- a/src/daemon/minimal.conf.in +++ b/src/daemon/minimal.conf.in @@ -332,6 +332,9 @@ context.objects = [ #channelmix.fc-cutoff = 12000 #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 + #channelmix.center-level = 0.707106781 + #channelmix.surround-level = 0.707106781 + #channelmix.lfe-level = 0.5 #channelmix.hilbert-taps = 0 #channelmix.disable = false #dither.noise = 0 diff --git a/src/daemon/pipewire-avb.conf.in b/src/daemon/pipewire-avb.conf.in index da158f212..e43015780 100644 --- a/src/daemon/pipewire-avb.conf.in +++ b/src/daemon/pipewire-avb.conf.in @@ -60,6 +60,9 @@ stream.properties = { #channelmix.fc-cutoff = 6000 #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.1 + #channelmix.center-level = 0.707106781 + #channelmix.surround-level = 0.707106781 + #channelmix.lfe-level = 0.5 #channelmix.hilbert-taps = 0 } diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in index 8c21e37df..db646db0b 100644 --- a/src/daemon/pipewire-pulse.conf.in +++ b/src/daemon/pipewire-pulse.conf.in @@ -88,6 +88,9 @@ stream.properties = { #channelmix.fc-cutoff = 12000 #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 + #channelmix.center-level = 0.707106781 + #channelmix.surround-level = 0.707106781 + #channelmix.lfe-level = 0.5 #channelmix.hilbert-taps = 0 #dither.noise = 0 }