Split the enable-lfe-remixing setting into two

remixing-produce-lfe controls upmixing, and remixing-consume-lfe
controls downmixing. The motivation is that a user might want to
synthesize LFE while playing stereo audio on his/her 5.1 speakers,
but at the same time follow the industry recommendation to omit
the LFE channel when producting a stereo downmix (e.g. for headphones)
from 5.1 content. Or the other way round.

Fixes: #753.
This commit is contained in:
Alexander Patrakov 2019-12-10 06:50:59 +00:00 committed by Georg Chini
parent b94dba9daf
commit 464828faf2
12 changed files with 101 additions and 26 deletions

View file

@ -147,13 +147,28 @@ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
</option> </option>
<option> <option>
<p><opt>enable-lfe-remixing=</opt> If disabled when upmixing or <p><opt>enable-lfe-remixing=</opt> This is a way to set
downmixing ignore LFE channels. When this option is disabled the <opt>remixing-produce-lfe</opt> and <opt>remixing-consume-lfe</opt>
output LFE channel will only get a signal when an input LFE to the same value at once. This option only exists for backward
channel is available as well. If no input LFE channel is compatibility and may be removed in a future version of PulseAudio.</p>
available the output LFE channel will always be 0. If no output </option>
LFE channel is available the signal on the input LFE channel
will be ignored. Defaults to <opt>no</opt>.</p> <option>
<p><opt>remixing-produce-lfe=</opt> If enabled, and the sink input
does not have the LFE channel, synthesize the output LFE channel
as a (lowpass-filtered, if <opt>lfe-crossover-freq</opt> is not 0)
average of all input channels. Also, when <opt>lfe-crossover-freq</opt>
is not 0, filter out low frequencies from other channels while
producing a synthetic LFE output. If disabled, the output LFE channel
will only get a signal when an input LFE channel is available as well.
Defaults to <opt>no</opt>.</p>
</option>
<option>
<p><opt>remixing-consume-lfe=</opt> If enabled, and the sink does not
have an LFE channel, redirect the input LFE channel (if any) to other
channels. If disabled, the input LFE channel will remain unused unless
the sink has the LFE channel as well. Defaults to <opt>no</opt>.</p>
</option> </option>
<option> <option>

View file

@ -84,7 +84,8 @@ static const pa_daemon_conf default_conf = {
.avoid_resampling = false, .avoid_resampling = false,
.disable_remixing = false, .disable_remixing = false,
.remixing_use_all_sink_channels = true, .remixing_use_all_sink_channels = true,
.disable_lfe_remixing = true, .remixing_produce_lfe = false,
.remixing_consume_lfe = false,
.lfe_crossover_freq = 0, .lfe_crossover_freq = 0,
.config_file = NULL, .config_file = NULL,
.use_pid_file = true, .use_pid_file = true,
@ -496,6 +497,48 @@ static int parse_rtprio(pa_config_parser_state *state) {
return 0; return 0;
} }
static int parse_disable_lfe_remix(pa_config_parser_state *state) {
pa_daemon_conf *c;
int k;
pa_assert(state);
c = state->data;
if ((k = pa_parse_boolean(state->rvalue)) < 0) {
pa_log("[%s:%u] Failed to parse boolean value: %s", state->filename, state->lineno, state->rvalue);
return -1;
}
c->remixing_produce_lfe = c->remixing_consume_lfe = !k;
pa_log("[%s:%u] Deprecated option 'disable-lfe-remixing' found.", state->filename, state->lineno);
pa_log("[%s:%u] Please migrate to 'remixing-produce-lfe' and 'remixing-consume-lfe', set both to '%s'.",
state->filename, state->lineno, pa_yes_no(c->remixing_produce_lfe));
return 0;
}
static int parse_enable_lfe_remix(pa_config_parser_state *state) {
pa_daemon_conf *c;
int k;
pa_assert(state);
c = state->data;
if ((k = pa_parse_boolean(state->rvalue)) < 0) {
pa_log("[%s:%u] Failed to parse boolean value: %s", state->filename, state->lineno, state->rvalue);
return -1;
}
c->remixing_produce_lfe = c->remixing_consume_lfe = k;
pa_log("[%s:%u] Deprecated option 'enable-lfe-remixing' found.", state->filename, state->lineno);
pa_log("[%s:%u] Please migrate to 'remixing-produce-lfe' and 'remixing-consume-lfe', set both to '%s'.",
state->filename, state->lineno, pa_yes_no(c->remixing_produce_lfe));
return 0;
}
#ifdef HAVE_DBUS #ifdef HAVE_DBUS
static int parse_server_type(pa_config_parser_state *state) { static int parse_server_type(pa_config_parser_state *state) {
pa_daemon_conf *c; pa_daemon_conf *c;
@ -565,8 +608,10 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
{ "enable-remixing", pa_config_parse_not_bool, &c->disable_remixing, NULL }, { "enable-remixing", pa_config_parse_not_bool, &c->disable_remixing, NULL },
{ "remixing-use-all-sink-channels", { "remixing-use-all-sink-channels",
pa_config_parse_bool, &c->remixing_use_all_sink_channels, NULL }, pa_config_parse_bool, &c->remixing_use_all_sink_channels, NULL },
{ "disable-lfe-remixing", pa_config_parse_bool, &c->disable_lfe_remixing, NULL }, { "disable-lfe-remixing", parse_disable_lfe_remix, c, NULL },
{ "enable-lfe-remixing", pa_config_parse_not_bool, &c->disable_lfe_remixing, NULL }, { "enable-lfe-remixing", parse_enable_lfe_remix, c, NULL },
{ "remixing-produce-lfe", pa_config_parse_bool, &c->remixing_produce_lfe, NULL },
{ "remixing-consume-lfe", pa_config_parse_bool, &c->remixing_consume_lfe, NULL },
{ "lfe-crossover-freq", pa_config_parse_unsigned, &c->lfe_crossover_freq, NULL }, { "lfe-crossover-freq", pa_config_parse_unsigned, &c->lfe_crossover_freq, NULL },
{ "load-default-script-file", pa_config_parse_bool, &c->load_default_script_file, NULL }, { "load-default-script-file", pa_config_parse_bool, &c->load_default_script_file, NULL },
{ "shm-size-bytes", pa_config_parse_size, &c->shm_size, NULL }, { "shm-size-bytes", pa_config_parse_size, &c->shm_size, NULL },
@ -761,7 +806,8 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) {
pa_strbuf_printf(s, "avoid-resampling = %s\n", pa_yes_no(c->avoid_resampling)); pa_strbuf_printf(s, "avoid-resampling = %s\n", pa_yes_no(c->avoid_resampling));
pa_strbuf_printf(s, "enable-remixing = %s\n", pa_yes_no(!c->disable_remixing)); pa_strbuf_printf(s, "enable-remixing = %s\n", pa_yes_no(!c->disable_remixing));
pa_strbuf_printf(s, "remixing-use-all-sink-channels = %s\n", pa_yes_no(c->remixing_use_all_sink_channels)); pa_strbuf_printf(s, "remixing-use-all-sink-channels = %s\n", pa_yes_no(c->remixing_use_all_sink_channels));
pa_strbuf_printf(s, "enable-lfe-remixing = %s\n", pa_yes_no(!c->disable_lfe_remixing)); pa_strbuf_printf(s, "remixing-produce-lfe = %s\n", pa_yes_no(c->remixing_produce_lfe));
pa_strbuf_printf(s, "remixing-consume-lfe = %s\n", pa_yes_no(c->remixing_consume_lfe));
pa_strbuf_printf(s, "lfe-crossover-freq = %u\n", c->lfe_crossover_freq); pa_strbuf_printf(s, "lfe-crossover-freq = %u\n", c->lfe_crossover_freq);
pa_strbuf_printf(s, "default-sample-format = %s\n", pa_sample_format_to_string(c->default_sample_spec.format)); pa_strbuf_printf(s, "default-sample-format = %s\n", pa_sample_format_to_string(c->default_sample_spec.format));
pa_strbuf_printf(s, "default-sample-rate = %u\n", c->default_sample_spec.rate); pa_strbuf_printf(s, "default-sample-rate = %u\n", c->default_sample_spec.rate);

View file

@ -70,7 +70,8 @@ typedef struct pa_daemon_conf {
avoid_resampling, avoid_resampling,
disable_remixing, disable_remixing,
remixing_use_all_sink_channels, remixing_use_all_sink_channels,
disable_lfe_remixing, remixing_produce_lfe,
remixing_consume_lfe,
load_default_script_file, load_default_script_file,
disallow_exit, disallow_exit,
log_meta, log_meta,

View file

@ -57,7 +57,8 @@ ifelse(@HAVE_DBUS@, 1, [dnl
; avoid-resampling = false ; avoid-resampling = false
; enable-remixing = yes ; enable-remixing = yes
; remixing-use-all-sink-channels = yes ; remixing-use-all-sink-channels = yes
; enable-lfe-remixing = no ; remixing-produce-lfe = no
; remixing-consume-lfe = no
; lfe-crossover-freq = 0 ; lfe-crossover-freq = 0
; flat-volumes = no ; flat-volumes = no

View file

@ -1056,7 +1056,8 @@ int main(int argc, char *argv[]) {
c->avoid_resampling = conf->avoid_resampling; c->avoid_resampling = conf->avoid_resampling;
c->disable_remixing = conf->disable_remixing; c->disable_remixing = conf->disable_remixing;
c->remixing_use_all_sink_channels = conf->remixing_use_all_sink_channels; c->remixing_use_all_sink_channels = conf->remixing_use_all_sink_channels;
c->disable_lfe_remixing = conf->disable_lfe_remixing; c->remixing_produce_lfe = conf->remixing_produce_lfe;
c->remixing_consume_lfe = conf->remixing_consume_lfe;
c->deferred_volume = conf->deferred_volume; c->deferred_volume = conf->deferred_volume;
c->running_as_daemon = conf->daemonize; c->running_as_daemon = conf->daemonize;
c->disallow_exit = conf->disallow_exit; c->disallow_exit = conf->disallow_exit;

View file

@ -144,7 +144,8 @@ pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t
c->realtime_priority = 5; c->realtime_priority = 5;
c->disable_remixing = false; c->disable_remixing = false;
c->remixing_use_all_sink_channels = true; c->remixing_use_all_sink_channels = true;
c->disable_lfe_remixing = true; c->remixing_produce_lfe = false;
c->remixing_consume_lfe = false;
c->lfe_crossover_freq = 0; c->lfe_crossover_freq = 0;
c->deferred_volume = true; c->deferred_volume = true;
c->resample_method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 1; c->resample_method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 1;

View file

@ -220,7 +220,8 @@ struct pa_core {
bool avoid_resampling:1; bool avoid_resampling:1;
bool disable_remixing:1; bool disable_remixing:1;
bool remixing_use_all_sink_channels:1; bool remixing_use_all_sink_channels:1;
bool disable_lfe_remixing:1; bool remixing_produce_lfe:1;
bool remixing_consume_lfe:1;
bool deferred_volume:1; bool deferred_volume:1;
pa_resample_method_t resample_method; pa_resample_method_t resample_method;

View file

@ -1099,7 +1099,7 @@ static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_remixed)
* right input channel. Something is really wrong in this * right input channel. Something is really wrong in this
* case anyway. */ * case anyway. */
} else if (on_lfe(b) && !(r->flags & PA_RESAMPLER_NO_LFE)) { } else if (on_lfe(b) && (r->flags & PA_RESAMPLER_PRODUCE_LFE)) {
/* We are not connected and an LFE. Let's average all /* We are not connected and an LFE. Let's average all
* channels for LFE. */ * channels for LFE. */
@ -1150,7 +1150,7 @@ static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_remixed)
m->map_table_f[oc][ic] = (1.f/9.f) / (float) ic_unconnected_center; m->map_table_f[oc][ic] = (1.f/9.f) / (float) ic_unconnected_center;
ic_unconnected_center_mixed_in = true; ic_unconnected_center_mixed_in = true;
} else if (on_lfe(a) && !(r->flags & PA_RESAMPLER_NO_LFE)) } else if (on_lfe(a) && (r->flags & PA_RESAMPLER_CONSUME_LFE))
m->map_table_f[oc][ic] = .375f / (float) ic_unconnected_lfe; m->map_table_f[oc][ic] = .375f / (float) ic_unconnected_lfe;
} }
} }

View file

@ -68,8 +68,9 @@ typedef enum pa_resample_flags {
PA_RESAMPLER_VARIABLE_RATE = 0x0001U, PA_RESAMPLER_VARIABLE_RATE = 0x0001U,
PA_RESAMPLER_NO_REMAP = 0x0002U, /* implies NO_REMIX */ PA_RESAMPLER_NO_REMAP = 0x0002U, /* implies NO_REMIX */
PA_RESAMPLER_NO_REMIX = 0x0004U, PA_RESAMPLER_NO_REMIX = 0x0004U,
PA_RESAMPLER_NO_LFE = 0x0008U,
PA_RESAMPLER_NO_FILL_SINK = 0x0010U, PA_RESAMPLER_NO_FILL_SINK = 0x0010U,
PA_RESAMPLER_PRODUCE_LFE = 0x0020U,
PA_RESAMPLER_CONSUME_LFE = 0x0040U,
} pa_resample_flags_t; } pa_resample_flags_t;
struct pa_resampler { struct pa_resampler {

View file

@ -476,7 +476,8 @@ int pa_sink_input_new(
((data->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | ((data->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
(core->disable_remixing || (data->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | (core->disable_remixing || (data->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
(core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) | (core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) |
(core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) { (core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) |
(core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0)))) {
pa_log_warn("Unsupported resampling operation."); pa_log_warn("Unsupported resampling operation.");
return -PA_ERR_NOTSUPPORTED; return -PA_ERR_NOTSUPPORTED;
} }
@ -2313,7 +2314,8 @@ int pa_sink_input_update_resampler(pa_sink_input *i) {
((i->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | ((i->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
(i->core->disable_remixing || (i->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | (i->core->disable_remixing || (i->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
(i->core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) | (i->core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) |
(i->core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)); (i->core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) |
(i->core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0));
if (!new_resampler) { if (!new_resampler) {
pa_log_warn("Unsupported resampling operation."); pa_log_warn("Unsupported resampling operation.");

View file

@ -414,7 +414,8 @@ int pa_source_output_new(
((data->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | ((data->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
(core->disable_remixing || (data->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | (core->disable_remixing || (data->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
(core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) | (core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) |
(core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) { (core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) |
(core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0)))) {
pa_log_warn("Unsupported resampling operation."); pa_log_warn("Unsupported resampling operation.");
return -PA_ERR_NOTSUPPORTED; return -PA_ERR_NOTSUPPORTED;
} }
@ -1755,7 +1756,8 @@ int pa_source_output_update_resampler(pa_source_output *o) {
((o->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | ((o->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
(o->core->disable_remixing || (o->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | (o->core->disable_remixing || (o->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
(o->core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) | (o->core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) |
(o->core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)); (o->core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) |
(o->core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0));
if (!new_resampler) { if (!new_resampler) {
pa_log_warn("Unsupported resampling operation."); pa_log_warn("Unsupported resampling operation.");

View file

@ -33,7 +33,7 @@ struct resample_flags {
}; };
/* Call like this to get an initializer for struct resample_flags: /* Call like this to get an initializer for struct resample_flags:
* RESAMPLE_FLAGS(PA_RESAMPLER_NO_LFE) * RESAMPLE_FLAGS(PA_RESAMPLER_PRODUCE_LFE)
*/ */
#define RESAMPLE_FLAGS(flags) { .str = #flags, .value = (flags) } #define RESAMPLE_FLAGS(flags) { .str = #flags, .value = (flags) }
@ -60,9 +60,13 @@ int main(int argc, char *argv[]) {
RESAMPLE_FLAGS(0), RESAMPLE_FLAGS(0),
RESAMPLE_FLAGS(PA_RESAMPLER_NO_REMAP), RESAMPLE_FLAGS(PA_RESAMPLER_NO_REMAP),
RESAMPLE_FLAGS(PA_RESAMPLER_NO_REMIX), RESAMPLE_FLAGS(PA_RESAMPLER_NO_REMIX),
RESAMPLE_FLAGS(PA_RESAMPLER_NO_LFE), RESAMPLE_FLAGS(PA_RESAMPLER_PRODUCE_LFE),
RESAMPLE_FLAGS(PA_RESAMPLER_NO_FILL_SINK), RESAMPLE_FLAGS(PA_RESAMPLER_NO_FILL_SINK),
RESAMPLE_FLAGS(PA_RESAMPLER_NO_LFE | PA_RESAMPLER_NO_FILL_SINK), RESAMPLE_FLAGS(PA_RESAMPLER_PRODUCE_LFE | PA_RESAMPLER_NO_FILL_SINK),
RESAMPLE_FLAGS(PA_RESAMPLER_CONSUME_LFE),
RESAMPLE_FLAGS(PA_RESAMPLER_CONSUME_LFE | PA_RESAMPLER_NO_FILL_SINK),
RESAMPLE_FLAGS(PA_RESAMPLER_PRODUCE_LFE | PA_RESAMPLER_CONSUME_LFE),
RESAMPLE_FLAGS(PA_RESAMPLER_PRODUCE_LFE | PA_RESAMPLER_CONSUME_LFE | PA_RESAMPLER_NO_FILL_SINK),
{ .str = NULL, .value = 0 }, { .str = NULL, .value = 0 },
}; };