mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-06-18 14:32:58 -04:00
mixer-dsp: add support for fade-in and fade-out
When the port IO_Buffers is set, do a fade-in of the next buffer data into the final mixed output. When the port IO Buffers in cleared, do a fade-out of that last sample. Add a zeroramp.duration property that controls the length in seconds of the fade-in/out curve. By default, set this to 5ms. The fade-in and fade-outs avoid popping noise from sudden DC voltage changes when ports a linked and unlinked. It will also trigger when the upstream peer is paused, because that will also remove the IO Buffers from the mixer ports.
This commit is contained in:
parent
9a19091ac7
commit
214b9f6daa
2 changed files with 136 additions and 45 deletions
|
|
@ -54,7 +54,7 @@ static int setup_context(struct context *ctx)
|
||||||
size_t size;
|
size_t size;
|
||||||
int res;
|
int res;
|
||||||
struct spa_support support[1];
|
struct spa_support support[1];
|
||||||
struct spa_dict_item items[9];
|
struct spa_dict_item items[11];
|
||||||
const struct spa_handle_factory *factory;
|
const struct spa_handle_factory *factory;
|
||||||
void *iface;
|
void *iface;
|
||||||
|
|
||||||
|
|
@ -79,10 +79,12 @@ static int setup_context(struct context *ctx)
|
||||||
items[6] = SPA_DICT_ITEM_INIT("channelmix.center-level", "0.707106781");
|
items[6] = SPA_DICT_ITEM_INIT("channelmix.center-level", "0.707106781");
|
||||||
items[7] = SPA_DICT_ITEM_INIT("channelmix.surround-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");
|
items[8] = SPA_DICT_ITEM_INIT("channelmix.lfe-level", "0.5");
|
||||||
|
items[9] = SPA_DICT_ITEM_INIT("zeroramp.gap", "0");
|
||||||
|
items[10] = SPA_DICT_ITEM_INIT("zeroramp.duration", "0.0");
|
||||||
|
|
||||||
res = spa_handle_factory_init(factory,
|
res = spa_handle_factory_init(factory,
|
||||||
ctx->convert_handle,
|
ctx->convert_handle,
|
||||||
&SPA_DICT_INIT(items, 9),
|
&SPA_DICT_INIT(items, 11),
|
||||||
support, 1);
|
support, 1);
|
||||||
spa_assert_se(res >= 0);
|
spa_assert_se(res >= 0);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
|
||||||
#include <spa/support/plugin.h>
|
#include <spa/support/plugin.h>
|
||||||
|
|
@ -32,6 +33,7 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.mixer-dsp");
|
||||||
#define MAX_DATAS SPA_AUDIO_MAX_CHANNELS
|
#define MAX_DATAS SPA_AUDIO_MAX_CHANNELS
|
||||||
#define MAX_PORTS 512
|
#define MAX_PORTS 512
|
||||||
#define MAX_ALIGN MIX_OPS_MAX_ALIGN
|
#define MAX_ALIGN MIX_OPS_MAX_ALIGN
|
||||||
|
#define MAX_CURVE 4096u
|
||||||
|
|
||||||
#define PORT_DEFAULT_VOLUME 1.0
|
#define PORT_DEFAULT_VOLUME 1.0
|
||||||
#define PORT_DEFAULT_MUTE false
|
#define PORT_DEFAULT_MUTE false
|
||||||
|
|
@ -85,6 +87,14 @@ struct port {
|
||||||
|
|
||||||
struct spa_list mix_link;
|
struct spa_list mix_link;
|
||||||
bool active:1;
|
bool active:1;
|
||||||
|
uint32_t ramp_pos;
|
||||||
|
float history[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ramp_info {
|
||||||
|
const float *data;
|
||||||
|
struct port *port;
|
||||||
|
int ramp_dir;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct impl {
|
struct impl {
|
||||||
|
|
@ -117,11 +127,16 @@ struct impl {
|
||||||
|
|
||||||
struct buffer *mix_buffers[MAX_PORTS];
|
struct buffer *mix_buffers[MAX_PORTS];
|
||||||
const void *mix_datas[MAX_PORTS];
|
const void *mix_datas[MAX_PORTS];
|
||||||
|
struct ramp_info ramp_info[MAX_PORTS];
|
||||||
|
|
||||||
int n_formats;
|
int n_formats;
|
||||||
struct spa_audio_info format;
|
struct spa_audio_info format;
|
||||||
uint32_t stride;
|
uint32_t stride;
|
||||||
|
|
||||||
|
float curve[MAX_CURVE];
|
||||||
|
uint32_t n_curve;
|
||||||
|
float zeroramp_duration;
|
||||||
|
|
||||||
unsigned int have_format:1;
|
unsigned int have_format:1;
|
||||||
unsigned int started:1;
|
unsigned int started:1;
|
||||||
|
|
||||||
|
|
@ -246,13 +261,15 @@ impl_node_set_callbacks(void *object,
|
||||||
|
|
||||||
static struct port *get_free_port(struct impl *this)
|
static struct port *get_free_port(struct impl *this)
|
||||||
{
|
{
|
||||||
struct port *port;
|
struct port *port, *tmp;
|
||||||
if (!spa_list_is_empty(&this->free_list)) {
|
spa_list_for_each_safe(port, tmp, &this->free_list, link) {
|
||||||
port = spa_list_first(&this->free_list, struct port, link);
|
if (!port->active) {
|
||||||
spa_list_remove(&port->link);
|
spa_list_remove(&port->link);
|
||||||
} else {
|
spa_memzero(port, sizeof(struct port));
|
||||||
port = calloc(1, sizeof(struct port));
|
return port;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
port = calloc(1, sizeof(struct port));
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -316,7 +333,6 @@ impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_
|
||||||
this->have_format = false;
|
this->have_format = false;
|
||||||
}
|
}
|
||||||
clear_buffers(this, port);
|
clear_buffers(this, port);
|
||||||
spa_memzero(port, sizeof(struct port));
|
|
||||||
spa_list_append(&this->free_list, &port->link);
|
spa_list_append(&this->free_list, &port->link);
|
||||||
|
|
||||||
spa_log_debug(this->log, "%p: remove port %d:%d", this,
|
spa_log_debug(this->log, "%p: remove port %d:%d", this,
|
||||||
|
|
@ -729,14 +745,12 @@ static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq,
|
||||||
{
|
{
|
||||||
struct io_info *info = user_data;
|
struct io_info *info = user_data;
|
||||||
struct port *port = info->port;
|
struct port *port = info->port;
|
||||||
|
struct impl *impl = info->impl;
|
||||||
|
|
||||||
if (info->data == NULL || info->size < sizeof(struct spa_io_buffers)) {
|
if (info->data == NULL || info->size < sizeof(struct spa_io_buffers)) {
|
||||||
port->io[0] = NULL;
|
port->io[0] = NULL;
|
||||||
port->io[1] = NULL;
|
port->io[1] = NULL;
|
||||||
if (port->active) {
|
port->ramp_pos = impl->n_curve;
|
||||||
spa_list_remove(&port->mix_link);
|
|
||||||
port->active = false;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (info->size >= sizeof(struct spa_io_async_buffers)) {
|
if (info->size >= sizeof(struct spa_io_async_buffers)) {
|
||||||
struct spa_io_async_buffers *ab = info->data;
|
struct spa_io_async_buffers *ab = info->data;
|
||||||
|
|
@ -746,6 +760,7 @@ static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq,
|
||||||
port->io[0] = info->data;
|
port->io[0] = info->data;
|
||||||
port->io[1] = info->data;
|
port->io[1] = info->data;
|
||||||
}
|
}
|
||||||
|
port->ramp_pos = 0;
|
||||||
if (port->direction == SPA_DIRECTION_INPUT && !port->active) {
|
if (port->direction == SPA_DIRECTION_INPUT && !port->active) {
|
||||||
spa_list_append(&info->impl->mix_list, &port->mix_link);
|
spa_list_append(&info->impl->mix_list, &port->mix_link);
|
||||||
port->active = true;
|
port->active = true;
|
||||||
|
|
@ -803,14 +818,46 @@ static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t
|
||||||
return queue_buffer(this, port, &port->buffers[buffer_id]);
|
return queue_buffer(this, port, &port->buffers[buffer_id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ramp_up(struct impl *this, float *dst, uint32_t size, struct ramp_info *ri)
|
||||||
|
{
|
||||||
|
uint32_t i, c;
|
||||||
|
struct port *port = ri->port;
|
||||||
|
|
||||||
|
spa_log_info(this->log, "fade-in %u/%u", port->ramp_pos, this->n_curve);
|
||||||
|
|
||||||
|
for (c = port->ramp_pos, i = 0; i < size && c < this->n_curve; i++, c++)
|
||||||
|
dst[i] += ri->data[i] * this->curve[c];
|
||||||
|
for (; i < size; i++)
|
||||||
|
dst[i] += ri->data[i];
|
||||||
|
port->ramp_pos = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ramp_down(struct impl *this, float *dst, uint32_t size, struct ramp_info *ri)
|
||||||
|
{
|
||||||
|
uint32_t i, c;
|
||||||
|
struct port *port = ri->port;
|
||||||
|
float last_sample = port->history[0];
|
||||||
|
|
||||||
|
spa_log_info(this->log, "fade-out %u %f", port->ramp_pos, last_sample);
|
||||||
|
|
||||||
|
for (c = port->ramp_pos, i = 0; i < size && c > 0; i++, c--)
|
||||||
|
dst[i] += last_sample * this->curve[c-1];
|
||||||
|
port->ramp_pos = c;
|
||||||
|
if (c == 0 && port->active) {
|
||||||
|
spa_list_remove(&port->mix_link);
|
||||||
|
port->active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int impl_node_process(void *object)
|
static int impl_node_process(void *object)
|
||||||
{
|
{
|
||||||
struct impl *this = object;
|
struct impl *this = object;
|
||||||
struct port *outport, *inport;
|
struct port *outport, *inport;
|
||||||
struct spa_io_buffers *outio;
|
struct spa_io_buffers *outio;
|
||||||
uint32_t n_buffers, maxsize;
|
uint32_t n_buffers, maxsize, n_ramps, i;
|
||||||
struct buffer **buffers;
|
struct buffer *last_buffer = NULL;
|
||||||
struct buffer *outb;
|
struct buffer *outb;
|
||||||
|
struct ramp_info *ramps;
|
||||||
const void **datas;
|
const void **datas;
|
||||||
uint32_t cycle = this->position->clock.cycle & 1;
|
uint32_t cycle = this->position->clock.cycle & 1;
|
||||||
struct spa_data *d;
|
struct spa_data *d;
|
||||||
|
|
@ -833,43 +880,65 @@ static int impl_node_process(void *object)
|
||||||
outio->buffer_id = SPA_ID_INVALID;
|
outio->buffer_id = SPA_ID_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffers = this->mix_buffers;
|
|
||||||
datas = this->mix_datas;
|
datas = this->mix_datas;
|
||||||
n_buffers = 0;
|
ramps = this->ramp_info;
|
||||||
|
n_buffers = n_ramps = 0;
|
||||||
|
|
||||||
maxsize = UINT32_MAX;
|
maxsize = UINT32_MAX;
|
||||||
|
|
||||||
spa_list_for_each(inport, &this->mix_list, mix_link) {
|
spa_list_for_each(inport, &this->mix_list, mix_link) {
|
||||||
struct spa_io_buffers *inio = inport->io[cycle];
|
struct spa_io_buffers *inio;
|
||||||
struct buffer *inb;
|
struct buffer *inb = NULL;
|
||||||
struct spa_data *bd;
|
struct spa_data *bd;
|
||||||
uint32_t size, offs;
|
uint32_t size, offs;
|
||||||
|
float *s;
|
||||||
|
|
||||||
if (inio->buffer_id >= inport->n_buffers ||
|
if (SPA_UNLIKELY((inio = inport->io[cycle]) == NULL)) {
|
||||||
inio->status != SPA_STATUS_HAVE_DATA) {
|
spa_log_trace_fp(this->log, "%p: skip input id:%d io:%p/%p/%d ramp:%d",
|
||||||
spa_log_trace_fp(this->log, "%p: skip input idx:%d "
|
this, inport->id, inport->io[0], inport->io[1], cycle,
|
||||||
"io:%p status:%d buf_id:%d n_buffers:%d", this,
|
inport->ramp_pos);
|
||||||
inport->id, inio, inio->status, inio->buffer_id, inport->n_buffers);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
else if (inio->buffer_id >= inport->n_buffers ||
|
||||||
|
inio->status != SPA_STATUS_HAVE_DATA) {
|
||||||
|
spa_log_trace_fp(this->log, "%p: skip input id:%d "
|
||||||
|
"io:%p status:%d buf_id:%d n_buffers:%d ramp:%d", this,
|
||||||
|
inport->id, inio, inio->status, inio->buffer_id, inport->n_buffers,
|
||||||
|
inport->ramp_pos);
|
||||||
|
} else {
|
||||||
inb = &inport->buffers[inio->buffer_id];
|
inb = &inport->buffers[inio->buffer_id];
|
||||||
|
}
|
||||||
|
if (inb != NULL) {
|
||||||
bd = &inb->buffer->datas[0];
|
bd = &inb->buffer->datas[0];
|
||||||
|
|
||||||
offs = SPA_MIN(bd->chunk->offset, bd->maxsize);
|
offs = SPA_MIN(bd->chunk->offset, bd->maxsize);
|
||||||
size = SPA_MIN(bd->maxsize - offs, bd->chunk->size);
|
size = SPA_MIN(bd->maxsize - offs, bd->chunk->size);
|
||||||
maxsize = SPA_MIN(maxsize, size);
|
maxsize = SPA_MIN(maxsize, size);
|
||||||
|
s = SPA_PTROFF(bd->data, offs, float);
|
||||||
|
|
||||||
spa_log_trace_fp(this->log, "%p: mix input %d %p->%p %d %d/%d %d:%d/%d %u", this,
|
spa_log_trace_fp(this->log, "%p: mix input %d %p->%p %d %d/%d %d:%d/%d %u", this,
|
||||||
inport->id, inio, outio, inio->status, inio->buffer_id, inport->n_buffers,
|
inport->id, inio, outio, inio->status, inio->buffer_id, inport->n_buffers,
|
||||||
offs, size, (int)sizeof(float),
|
offs, size, (int)sizeof(float),
|
||||||
bd->chunk->flags);
|
bd->chunk->flags);
|
||||||
|
|
||||||
if (!SPA_FLAG_IS_SET(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY)) {
|
if (SPA_UNLIKELY(inport->ramp_pos < this->n_curve)) {
|
||||||
datas[n_buffers] = SPA_PTROFF(bd->data, offs, void);
|
/* new port */
|
||||||
buffers[n_buffers++] = inb;
|
struct ramp_info *ri = &ramps[n_ramps++];
|
||||||
|
ri->port = inport;
|
||||||
|
ri->ramp_dir = 1;
|
||||||
|
ri->data = s;
|
||||||
|
} else {
|
||||||
|
datas[n_buffers++] = s;
|
||||||
|
last_buffer = inb;
|
||||||
}
|
}
|
||||||
|
if (size >= sizeof(float))
|
||||||
|
inport->history[0] = s[size/sizeof(float)-1];
|
||||||
inio->status = SPA_STATUS_NEED_DATA;
|
inio->status = SPA_STATUS_NEED_DATA;
|
||||||
|
} else if (inport->ramp_pos > 0) {
|
||||||
|
/* removed port, ramp down */
|
||||||
|
struct ramp_info *ri = &ramps[n_ramps++];
|
||||||
|
ri->port = inport;
|
||||||
|
ri->ramp_dir = -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outb = dequeue_buffer(this, outport);
|
outb = dequeue_buffer(this, outport);
|
||||||
|
|
@ -882,26 +951,37 @@ static int impl_node_process(void *object)
|
||||||
suppressed, outport->n_buffers);
|
suppressed, outport->n_buffers);
|
||||||
return -EPIPE;
|
return -EPIPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
d = outb->buf.datas;
|
d = outb->buf.datas;
|
||||||
|
|
||||||
if (n_buffers == 1 && SPA_FLAG_IS_SET(d[0].flags, SPA_DATA_FLAG_DYNAMIC)) {
|
if (n_buffers == 1 && SPA_FLAG_IS_SET(d[0].flags, SPA_DATA_FLAG_DYNAMIC) && n_ramps == 0) {
|
||||||
spa_log_trace_fp(this->log, "%p: %d passthrough", this, n_buffers);
|
spa_log_trace_fp(this->log, "%p: %d passthrough", this, n_buffers);
|
||||||
*outb->buffer = *buffers[0]->buffer;
|
*outb->buffer = *last_buffer->buffer;
|
||||||
} else {
|
} else {
|
||||||
|
bool empty = n_buffers == 0 && n_ramps == 0;
|
||||||
|
float *dst = d[0].data;
|
||||||
|
uint32_t dst_samples;
|
||||||
|
|
||||||
*outb->buffer = outb->buf;
|
*outb->buffer = outb->buf;
|
||||||
|
|
||||||
maxsize = SPA_MIN(maxsize, d[0].maxsize);
|
maxsize = SPA_MIN(maxsize, d[0].maxsize);
|
||||||
|
dst_samples = maxsize / sizeof(float);
|
||||||
|
|
||||||
d[0].chunk->offset = 0;
|
d[0].chunk->offset = 0;
|
||||||
d[0].chunk->size = maxsize;
|
d[0].chunk->size = maxsize;
|
||||||
d[0].chunk->stride = sizeof(float);
|
d[0].chunk->stride = sizeof(float);
|
||||||
SPA_FLAG_UPDATE(d[0].chunk->flags, SPA_CHUNK_FLAG_EMPTY, n_buffers == 0);
|
SPA_FLAG_UPDATE(d[0].chunk->flags, SPA_CHUNK_FLAG_EMPTY, empty);
|
||||||
|
|
||||||
spa_log_trace_fp(this->log, "%p: %d mix %d", this, n_buffers, maxsize);
|
spa_log_trace_fp(this->log, "%p: %d mix %d %u", this, n_buffers, maxsize, n_ramps);
|
||||||
|
|
||||||
mix_ops_process(&this->ops, d[0].data,
|
mix_ops_process(&this->ops, dst, datas, n_buffers, dst_samples);
|
||||||
datas, n_buffers, maxsize / sizeof(float));
|
|
||||||
|
for (i = 0; i < n_ramps; i++) {
|
||||||
|
struct ramp_info *ri = &ramps[i];
|
||||||
|
if (ri->ramp_dir < 0)
|
||||||
|
ramp_down(this, dst, dst_samples, ri);
|
||||||
|
else
|
||||||
|
ramp_up(this, dst, dst_samples, ri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outio->buffer_id = outb->id;
|
outio->buffer_id = outb->id;
|
||||||
|
|
@ -1005,13 +1085,22 @@ impl_init(const struct spa_handle_factory *factory,
|
||||||
this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu));
|
this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->zeroramp_duration = 0.005f;
|
||||||
|
|
||||||
for (i = 0; info && i < info->n_items; i++) {
|
for (i = 0; info && i < info->n_items; i++) {
|
||||||
const char *k = info->items[i].key;
|
const char *k = info->items[i].key;
|
||||||
const char *s = info->items[i].value;
|
const char *s = info->items[i].value;
|
||||||
if (spa_streq(k, "clock.quantum-limit"))
|
if (spa_streq(k, "clock.quantum-limit"))
|
||||||
spa_atou32(s, &this->quantum_limit, 0);
|
spa_atou32(s, &this->quantum_limit, 0);
|
||||||
|
if (spa_streq(k, "zeroramp.duration"))
|
||||||
|
spa_atof(s, &this->zeroramp_duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->n_curve = SPA_MIN((uint32_t)(this->zeroramp_duration * 48000), MAX_CURVE);
|
||||||
|
/* S-shaped curve */
|
||||||
|
for (i = 0; i < this->n_curve; i++)
|
||||||
|
this->curve[i] = (float)(0.5 + 0.5 * cos(M_PI + M_PI * i / this->n_curve));
|
||||||
|
|
||||||
spa_hook_list_init(&this->hooks);
|
spa_hook_list_init(&this->hooks);
|
||||||
spa_list_init(&this->port_list);
|
spa_list_init(&this->port_list);
|
||||||
spa_list_init(&this->free_list);
|
spa_list_init(&this->free_list);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue