filter-graph: add multiple in/out support for avfilter

Make one buffersrc for each input and configure it to mono. Try to guess
the channel position from the port name or use the config option and
fall back to FC (MONO) if unspecified.

Make one buffersink for each output and place a format converter in
front of it. Configure the converter to produce 1 channel with a layout
guessed from the port name or from the config.

With this we can use channelsplit and amerge to create multichannel
streams for avfilter plugins.
This commit is contained in:
Wim Taymans 2025-06-09 17:03:20 +02:00
parent 94116901ce
commit 2f51b9a5d9

View file

@ -7,11 +7,13 @@
#include <dlfcn.h>
#include <math.h>
#include <limits.h>
#include <string.h>
#include <spa/utils/result.h>
#include <spa/utils/defs.h>
#include <spa/utils/list.h>
#include <spa/utils/string.h>
#include <spa/utils/json.h>
#include <spa/support/log.h>
#include <libavutil/opt.h>
@ -21,6 +23,9 @@
#include "audio-plugin.h"
#define MAX_PORTS 256
#define MAX_CTX 64
struct plugin {
struct spa_handle handle;
struct spa_fga_plugin plugin;
@ -31,6 +36,7 @@ struct plugin {
struct descriptor {
struct spa_fga_descriptor desc;
struct plugin *p;
AVFilterGraph *filter_graph;
const AVFilter *format;
const AVFilter *buffersrc;
@ -41,16 +47,17 @@ struct instance {
struct descriptor *desc;
AVFilterGraph *filter_graph;
AVFilterInOut *in;
AVFilterInOut *out;
uint32_t rate;
AVFilterContext *ctx[128];
uint32_t n_ctx;
float *data[128];
AVFrame *frame;
AVFilterContext *ctx[MAX_CTX];
AVChannelLayout layout[MAX_CTX];
uint32_t n_ctx;
uint32_t n_src;
uint32_t n_sink;
float *data[MAX_PORTS];
};
static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc,
@ -59,10 +66,13 @@ static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struc
struct descriptor *d = (struct descriptor *)desc;
struct plugin *p = d->p;
struct instance *i;
AVFilterInOut *fp;
AVFilterInOut *fp, *in, *out;
AVFilterContext *cnv, *ctx;
int res;
char channel[512];
char options_str[1024];
uint32_t n_fp;
const char *chan;
i = calloc(1, sizeof(*i));
if (i == NULL)
@ -77,38 +87,74 @@ static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struc
return NULL;
}
res = avfilter_graph_parse2(i->filter_graph, d->desc.name, &i->in, &i->out);
res = avfilter_graph_parse2(i->filter_graph, d->desc.name, &in, &out);
if (res < 0) {
spa_log_error(p->log, "can parse filter graph %s", d->desc.name);
errno = EINVAL;
return NULL;
}
for (fp = i->in; fp != NULL; fp = fp->next) {
for (n_fp = 0, fp = in; fp != NULL; fp = fp->next, n_fp++) {
ctx = avfilter_graph_alloc_filter(i->filter_graph, d->buffersrc, "src");
if (ctx == NULL) {
spa_log_error(p->log, "can't alloc buffersrc");
return NULL;
}
if ((chan = strrchr(fp->name, '_')) != NULL)
chan++;
if (config == NULL ||
spa_json_str_object_find(config, strlen(config),
fp->name, channel, sizeof(channel)) < 0)
snprintf(channel, sizeof(channel), "%s", chan ? chan : "FC");
if (av_channel_layout_from_string(&i->layout[n_fp], channel) == AV_CHAN_NONE ||
i->layout[n_fp].nb_channels != 1) {
spa_log_warn(p->log, "invalid channels %s, using MONO", channel);
av_channel_layout_from_string(&i->layout[n_fp], "FC");
}
av_channel_layout_describe(&i->layout[n_fp], channel, sizeof(channel));
snprintf(options_str, sizeof(options_str),
"sample_fmt=%s:sample_rate=%ld:channel_layout=mono",
av_get_sample_fmt_name(AV_SAMPLE_FMT_FLTP), SampleRate);
"sample_fmt=%s:sample_rate=%ld:channel_layout=%s",
av_get_sample_fmt_name(AV_SAMPLE_FMT_FLTP), SampleRate, channel);
spa_log_info(p->log, "%d buffersrc %s", n_fp, options_str);
avfilter_init_str(ctx, options_str);
avfilter_link(ctx, 0, fp->filter_ctx, fp->pad_idx);
i->ctx[i->n_ctx++] = ctx;
}
for (fp = i->out; fp != NULL; fp = fp->next) {
i->ctx[n_fp] = ctx;
}
i->n_src = n_fp;
for (fp = out; fp != NULL; fp = fp->next, n_fp++) {
cnv = avfilter_graph_alloc_filter(i->filter_graph, d->format, "format");
if (cnv == NULL) {
spa_log_error(p->log, "can't alloc format");
return NULL;
}
snprintf(options_str, sizeof(options_str),
"sample_fmts=%s:sample_rates=%ld:channel_layouts=mono",
av_get_sample_fmt_name(AV_SAMPLE_FMT_FLTP), SampleRate);
if ((chan = strrchr(fp->name, '_')) != NULL)
chan++;
if (config == NULL ||
spa_json_str_object_find(config, strlen(config),
fp->name, channel, sizeof(channel)) < 0)
snprintf(channel, sizeof(channel), "%s", chan ? chan : "FC");
if (av_channel_layout_from_string(&i->layout[n_fp], channel) == AV_CHAN_NONE ||
i->layout[n_fp].nb_channels != 1) {
spa_log_warn(p->log, "invalid channels %s, using MONO", channel);
av_channel_layout_from_string(&i->layout[n_fp], "FC");
}
av_channel_layout_describe(&i->layout[n_fp], channel, sizeof(channel));
snprintf(options_str, sizeof(options_str),
"sample_fmts=%s:sample_rates=%ld:channel_layouts=%s",
av_get_sample_fmt_name(AV_SAMPLE_FMT_FLTP), SampleRate, channel);
spa_log_info(p->log, "%d format %s", n_fp, options_str);
avfilter_init_str(cnv, options_str);
avfilter_link(fp->filter_ctx, fp->pad_idx, cnv, 0);
@ -119,15 +165,17 @@ static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struc
}
avfilter_init_str(ctx, NULL);
avfilter_link(cnv, 0, ctx, 0);
i->ctx[i->n_ctx++] = ctx;
i->ctx[n_fp] = ctx;
}
i->n_sink = n_fp;
avfilter_graph_config(i->filter_graph, NULL);
i->frame = av_frame_alloc();
#if 1
#if 0
char *dump = avfilter_graph_dump(i->filter_graph, NULL);
fprintf(stderr, "%s\n", dump);
spa_log_debug(p->log, "%s", dump);
free(dump);
#endif
return i;
@ -136,6 +184,8 @@ static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struc
static void ffmpeg_cleanup(void *instance)
{
struct instance *i = instance;
avfilter_graph_free(&i->filter_graph);
av_frame_free(&i->frame);
free(i);
}
@ -156,31 +206,43 @@ static void ffmpeg_run(void *instance, unsigned long SampleCount)
{
struct instance *i = instance;
char buf[1024];
int err;
int err, j;
uint32_t c, d = 0;
spa_log_debug(i->desc->p->log, "run %ld", SampleCount);
i->frame->nb_samples = SampleCount;
i->frame->sample_rate = i->rate;
i->frame->format = AV_SAMPLE_FMT_FLTP;
av_channel_layout_copy(&i->frame->ch_layout, &(AVChannelLayout)AV_CHANNEL_LAYOUT_MONO);
spa_log_trace(i->desc->p->log, "run %ld", SampleCount);
i->frame->data[0] = (uint8_t*)i->data[0];
for (c = 0; c < i->n_src; c++) {
i->frame->nb_samples = SampleCount;
i->frame->sample_rate = i->rate;
i->frame->format = AV_SAMPLE_FMT_FLTP;
av_channel_layout_copy(&i->frame->ch_layout, &i->layout[c]);
if ((err = av_buffersrc_add_frame_flags(i->ctx[0], i->frame,
AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT)) < 0) {
av_strerror(err, buf, sizeof(buf));
spa_log_error(i->desc->p->log, "can't add frame %s", buf);
av_frame_unref(i->frame);
return;
for (j = 0; j < i->layout[c].nb_channels; j++)
i->frame->data[j] = (uint8_t*)i->data[d++];
if ((err = av_buffersrc_add_frame_flags(i->ctx[c], i->frame,
AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT)) < 0) {
av_strerror(err, buf, sizeof(buf));
spa_log_error(i->desc->p->log, "can't add frame: %s", buf);
av_frame_unref(i->frame);
continue;
}
}
if ((err = av_buffersink_get_samples(i->ctx[1], i->frame, SampleCount)) >= 0) {
for (; c < i->n_sink; c++) {
if ((err = av_buffersink_get_samples(i->ctx[c], i->frame, SampleCount)) < 0) {
av_strerror(err, buf, sizeof(buf));
spa_log_error(i->desc->p->log, "can't get frame: %s", buf);
continue;
}
spa_log_trace(i->desc->p->log, "got frame %d %d %d %s",
i->frame->nb_samples,
i->frame->ch_layout.nb_channels,
i->frame->sample_rate,
av_get_sample_fmt_name(i->frame->format));
memcpy(i->data[1], i->frame->data[0], SampleCount * sizeof(float));
for (j = 0; j < i->layout[c].nb_channels; j++)
memcpy(i->data[d++], i->frame->data[j], SampleCount * sizeof(float));
av_frame_unref(i->frame);
}
@ -190,7 +252,7 @@ static const struct spa_fga_descriptor *ffmpeg_plugin_make_desc(void *plugin, co
{
struct plugin *p = (struct plugin *)plugin;
struct descriptor *desc;
uint32_t i;
uint32_t n_fp;
AVFilterInOut *in = NULL, *out = NULL, *fp;
int res;
@ -215,15 +277,20 @@ static const struct spa_fga_descriptor *ffmpeg_plugin_make_desc(void *plugin, co
}
desc->desc.n_ports = 0;
for (fp = in; fp != NULL; fp = fp->next) {
for (n_fp = 0, fp = in; fp != NULL; fp = fp->next, n_fp++) {
spa_log_info(p->log, "%p: in %s %p:%d", fp, fp->name, fp->filter_ctx, fp->pad_idx);
desc->desc.n_ports++;
}
for (fp = out; fp != NULL; fp = fp->next) {
for (fp = out; fp != NULL; fp = fp->next, n_fp++) {
spa_log_info(p->log, "%p: out %s %p:%d", fp, fp->name, fp->filter_ctx, fp->pad_idx);
desc->desc.n_ports++;
}
if (desc->desc.n_ports > MAX_CTX) {
spa_log_error(p->log, "%p: too many ports %d > %d", desc,
desc->desc.n_ports, MAX_CTX);
errno = ENOSPC;
return NULL;
}
desc->desc.instantiate = ffmpeg_instantiate;
desc->desc.cleanup = ffmpeg_cleanup;
@ -236,15 +303,15 @@ static const struct spa_fga_descriptor *ffmpeg_plugin_make_desc(void *plugin, co
desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct spa_fga_port));
for (i = 0, fp = in; fp != NULL; i++, fp = fp->next) {
desc->desc.ports[i].index = i;
desc->desc.ports[i].name = fp->name;
desc->desc.ports[i].flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO;
for (n_fp = 0, fp = in; fp != NULL; fp = fp->next, n_fp++) {
desc->desc.ports[n_fp].index = n_fp;
desc->desc.ports[n_fp].name = spa_aprintf("%s", fp->name);
desc->desc.ports[n_fp].flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO;
}
for (fp = out; fp != NULL; i++, fp = fp->next) {
desc->desc.ports[i].index = i;
desc->desc.ports[i].name = fp->name;
desc->desc.ports[i].flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO;
for (fp = out; fp != NULL; fp = fp->next, n_fp++) {
desc->desc.ports[n_fp].index = n_fp;
desc->desc.ports[n_fp].name = spa_aprintf("%s", fp->name);
desc->desc.ports[n_fp].flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO;
}
desc->buffersrc = avfilter_get_by_name("abuffer");
desc->buffersink = avfilter_get_by_name("abuffersink");
@ -277,7 +344,6 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void
static int impl_clear(struct spa_handle *handle)
{
struct plugin *impl = (struct plugin *)handle;
return 0;
}
@ -297,7 +363,6 @@ impl_init(const struct spa_handle_factory *factory,
{
struct plugin *impl;
uint32_t i;
int res;
const char *path = NULL;
handle->get_interface = impl_get_interface;