* [FFmpeg-devel] [PATCH 1/1] avfilter/abufsrc: add audio buffer source filter with dynamic routing.
@ 2025-07-21 11:27 cenzhanquan2
2025-07-21 11:48 ` Nicolas George
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 0/3] lavfi: Add volume scaling, dynamic routing, and abufsrc filter cenzhanquan2
0 siblings, 2 replies; 8+ messages in thread
From: cenzhanquan2 @ 2025-07-21 11:27 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: zhanquan cen
From: zhanquan cen <cenzhanquan2@gmail.com>
This commit introduces a new audio buffer source filter `abufsrc` designed for scenarios requiring dynamic audio routing and real-time processing control. Key features include:
1. **Multi-Output Routing**: Supports configurable mapping of input to multiple outputs via `map` option, enabling selective audio stream distribution[4,6](@ref).
2. **Fade Effects**: Implements sample-accurate fade-in/fade-out transitions during playback start/stop, handling edge cases like pause/resume and buffer underruns[8](@ref).
3. **Volume Control**: Integrates with volume scaling system, allowing runtime adjustment through `player_volume` and `volume` parameters.
4. **Event Callbacks**: Uses `on_event_cb` for asynchronous frame request mechanism, decoupling filter activation from external data sources.
5. **Command API**: Exposes control via `link/unlink/map/pause/resume` commands for dynamic pipeline reconfiguration.
Technical highlights:
- Frame cloning to avoid data duplication when broadcasting to multiple outputs
- Sample format-aware fade functions (s16/s32/flt/dbl, planar/non-planar)
- Timestamp-based fade synchronization to prevent audio glitches
- Thread-safe parameter updates through `process_command`
Signed-off-by: zhanquan cen <cenzhanquan2@gmail.com>
---
libavfilter/asrc_abufsrc.c | 511 +++++++++++++++++++++++++++++++++++++
1 file changed, 511 insertions(+)
create mode 100644 libavfilter/asrc_abufsrc.c
diff --git a/libavfilter/asrc_abufsrc.c b/libavfilter/asrc_abufsrc.c
new file mode 100644
index 0000000000..39d1809c31
--- /dev/null
+++ b/libavfilter/asrc_abufsrc.c
@@ -0,0 +1,511 @@
+/*
+ * Copyright (c) 2024 HiccupZhu
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * memory buffer source filter
+ */
+
+#include "libavutil/eval.h"
+#include "libavutil/avstring.h"
+#include "libavutil/frame.h"
+#include "libavutil/internal.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+#include "avfilter.h"
+#include "avfilter_internal.h"
+#include "volume.h"
+#include "mapping.h"
+
+#define ROUTE_ON 1
+#define ROUTE_OFF 0
+
+#define FADE_NONE 0
+#define FADE_IN 1
+#define FADE_OUT 2
+#define FADE_OUT_IN (FADE_OUT|FADE_IN)
+
+typedef struct BuffSrcPriv {
+ const AVClass *class;
+ char *map_str;
+ int *map;
+ /* nb_outputs needs to follow map because av_opt_get_array
+ assumes the next address of map points to nb_outputs.*/
+ int nb_outputs;
+ bool paused;
+
+ int sample_rate; /**< sample rate */
+ AVChannelLayout ch_layout; /**< channel layout */
+ enum AVSampleFormat sample_fmt; /**< sample format */
+
+ int fade_type; /**< fade type */
+ AVFrame *frame; /**< frame buffer for fade. */
+ int64_t next_pts; /**< next expected pts for current input. */
+ void (*fade_samples)(uint8_t **dst, uint8_t * const *src,
+ int nb_samples,int channels, int dir,
+ int64_t start, int64_t range); /**< fade function */
+
+ int (*on_event_cb)(void *udata, int evt, int64_t args);
+ void *on_event_cb_udata;
+ VolumeContext vol_ctx;
+ double player_volume;
+ double volume;
+} BuffSrcPriv;
+
+static void abufsrc_set_event_cb(AVFilterContext *ctx,
+ int (*on_event_cb)(void *udata, int evt, int64_t args), void *udata)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ int i;
+
+ priv->on_event_cb = on_event_cb;
+ priv->on_event_cb_udata = udata;
+
+ if (priv->on_event_cb) {
+ for (i = 0; i < ctx->nb_outputs; i++) {
+ FilterLinkInternal *li = ff_link_internal(ctx->outputs[i]);
+ li->frame_wanted_out = 1;
+ }
+
+ ff_filter_set_ready(ctx, 100);
+ }
+}
+
+static int abufsrc_send_frame(AVFilterContext *ctx, AVFrame *frame)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ int i, ret, first = 1;
+
+ volume_scale(&priv->vol_ctx, frame);
+
+ for (i = 0; i < ctx->nb_outputs; i++) {
+ if (priv->map && priv->map[i] == ROUTE_OFF)
+ continue;
+
+ if (first) { // do not clone at fisrt sending.
+ ret = ff_filter_frame(ctx->outputs[i], frame);
+ if (ret < 0)
+ return ret;
+ first = 0;
+ } else {
+ AVFrame *clone = av_frame_clone(frame);
+ if (!clone)
+ return AVERROR(ENOMEM);
+
+ ret = ff_filter_frame(ctx->outputs[i], clone);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+#define FADE(name, type) \
+static void fade_samples_## name(uint8_t **dst, uint8_t * const *src, int nb_samples, \
+ int channels, int dir, int64_t start, int64_t range) \
+{ \
+ type *d = (type *)dst[0]; \
+ const type *s = (type *)src[0]; \
+ int i, c, k = 0; \
+ \
+ for (i = 0; i < nb_samples; i++) { \
+ double gain = av_clipd(1.0 * (start + i * dir) / range, 0, 1.0); \
+ for (c = 0; c < channels; c++, k++) \
+ d[k] = s[k] * gain; \
+ } \
+} \
+
+#define FADE_PLANAR(name, type) \
+static void fade_samples_## name ##p(uint8_t **dst, uint8_t * const *src, int nb_samples, \
+ int channels, int dir, int64_t start, int64_t range) \
+{ \
+ int i, c; \
+ \
+ for (i = 0; i < nb_samples; i++) { \
+ double gain = av_clipd(1.0 * (start + i * dir) / range, 0, 1.0); \
+ for (c = 0; c < channels; c++) { \
+ type *d = (type *)dst[c]; \
+ const type *s = (type *)src[c]; \
+ d[i] = s[i] * gain; \
+ } \
+ } \
+} \
+
+
+FADE_PLANAR(dbl, double)
+FADE_PLANAR(flt, float)
+FADE_PLANAR(s16, int16_t)
+FADE_PLANAR(s32, int32_t)
+
+FADE(dbl, double)
+FADE(flt, float)
+FADE(s16, int16_t)
+FADE(s32, int32_t)
+
+static void fade_frame(BuffSrcPriv* priv, int fade_type, AVFrame *dst, AVFrame *src)
+{
+ switch (src->format) {
+ case AV_SAMPLE_FMT_S16: priv->fade_samples = fade_samples_s16; break;
+ case AV_SAMPLE_FMT_S16P: priv->fade_samples = fade_samples_s16p; break;
+ case AV_SAMPLE_FMT_S32: priv->fade_samples = fade_samples_s32; break;
+ case AV_SAMPLE_FMT_S32P: priv->fade_samples = fade_samples_s32p; break;
+ case AV_SAMPLE_FMT_FLT: priv->fade_samples = fade_samples_flt; break;
+ case AV_SAMPLE_FMT_FLTP: priv->fade_samples = fade_samples_fltp; break;
+ case AV_SAMPLE_FMT_DBL: priv->fade_samples = fade_samples_dbl; break;
+ case AV_SAMPLE_FMT_DBLP: priv->fade_samples = fade_samples_dblp; break;
+ }
+
+ priv->fade_samples(dst->extended_data, src->extended_data, src->nb_samples,
+ src->ch_layout.nb_channels, fade_type > 1 ? -1 : 1,
+ fade_type > 1 ? src->nb_samples : 0, src->nb_samples);
+}
+
+static av_cold int abufsrc_init_dict(AVFilterContext *ctx)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ int i, ret = 0;
+
+ for (i = 0; i < priv->nb_outputs; i++) {
+ AVFilterPad pad = { 0 };
+
+ pad.type = AVMEDIA_TYPE_AUDIO;
+ pad.name = av_asprintf("output%d", i);
+ if (!pad.name)
+ return AVERROR(ENOMEM);
+
+ if ((ret = ff_append_outpad_free_name(ctx, &pad)) < 0)
+ return ret;
+ }
+
+ priv->player_volume = 1.0f;
+ priv->volume = 1.0f;
+
+ if (priv->map_str) {
+ ret = avfilter_parse_mapping(priv->map_str, &priv->map, priv->nb_outputs);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+static av_cold void abufsrc_uninit(AVFilterContext *ctx)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ av_freep(&priv->map);
+}
+
+static int abufsrc_activate(AVFilterContext *ctx)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ int i, ret, routed = 1;
+ FilterLinkInternal *li;
+ AVFrame *frame;
+
+ if (!priv->on_event_cb)
+ return FFERROR_NOT_READY;
+
+ for (i = 0; i < priv->nb_outputs; i++) {
+ if (priv->map && priv->map[i] == ROUTE_ON) {
+ li = ff_link_internal(ctx->outputs[i]);
+ if (li->frame_wanted_out) {
+ if (priv->paused && li->frame_blocked_in == 0) {
+ li->frame_blocked_in = 1;
+ av_log(ctx, AV_LOG_INFO, "%s xrun\n", ctx->name);
+ ff_filter_set_ready(ctx->outputs[i]->dst, 300);
+ }
+ } else
+ routed = 0;
+ }
+ }
+
+ if (!routed || priv->paused)
+ return 0;
+
+ if (!priv->frame) {
+ priv->frame = av_frame_alloc();
+ if (!priv->frame)
+ return AVERROR(ENOMEM);
+
+ if (ret = priv->on_event_cb(priv->on_event_cb_udata, 0, (intptr_t)priv->frame) < 0) {
+ av_frame_free(&priv->frame);
+ return ret;
+ }
+
+ priv->fade_type = FADE_IN;
+ ff_filter_set_ready(ctx, 100);
+ return 0;
+ }
+
+ frame = av_frame_alloc();
+ if (!frame)
+ return AVERROR(ENOMEM);
+
+ av_frame_move_ref(frame, priv->frame);
+ if (priv->on_event_cb(priv->on_event_cb_udata, 0, (intptr_t)priv->frame) < 0) {
+ av_frame_free(&priv->frame);
+ priv->fade_type = FADE_OUT;
+ }
+
+ if (priv->next_pts == frame->pts && priv->fade_type == FADE_NONE) { //should not set fade again, when in fade process.
+ int64_t next_pts = frame->pts + av_rescale_q(frame->nb_samples, (AVRational){1, frame->sample_rate}, frame->time_base);
+ if (next_pts != priv->frame->pts)
+ priv->fade_type = FADE_OUT_IN;
+ }
+
+ /* Do fade and clear fade flags.
+ *
+ * If fade out and fade in set at the same time, fade out should be done first
+ * and fade in done in next frame.
+ * If playing complete, next_pts will accumulate frame->nb_samples until next unsilent frame.
+ */
+ if (priv->fade_type) {
+ if (priv->fade_type & FADE_OUT) {
+ fade_frame(priv, FADE_OUT, frame, frame);
+ priv->fade_type &= ~FADE_OUT;
+ } else if (priv->fade_type & FADE_IN) {
+ fade_frame(priv, FADE_IN, frame, frame);
+ priv->fade_type &= ~FADE_IN;
+ }
+ priv->next_pts = frame->pts + av_rescale_q(frame->nb_samples, (AVRational){1, frame->sample_rate}, frame->time_base);
+ } else { //if no fade occur during playing, next_pts should add frame->nb_samples.
+ priv->next_pts += av_rescale_q(frame->nb_samples, (AVRational){1, frame->sample_rate}, frame->time_base);
+ }
+
+ return abufsrc_send_frame(ctx, frame);
+}
+
+static int abufsrc_fadeout_last_frame(AVFilterContext *ctx)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ AVFrame *frame = NULL;
+
+ frame = av_frame_alloc();
+ if (!frame)
+ return AVERROR(ENOMEM);
+
+ av_frame_move_ref(frame, priv->frame);
+ av_frame_free(&priv->frame);
+
+ fade_frame(priv, FADE_OUT, frame, frame);
+
+ priv->fade_type = FADE_NONE;
+
+ return abufsrc_send_frame(ctx, frame);
+}
+
+static int abufsrc_set_parameter(AVFilterContext *ctx, const char *args)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ char *key = NULL, *value = NULL;
+ const char *p = args;
+ int ret = 0;
+
+ av_log(ctx, AV_LOG_INFO, "Parsing args: %s\n", args);
+
+ while (*p) {
+ ret = av_opt_get_key_value(&p, "=", ":", 0, &key, &value);
+ if (ret < 0) {
+ av_log(ctx, AV_LOG_ERROR, "No more key-value pairs to parse.\n");
+ break;
+ }
+ if (*p)
+ p++;
+ av_log(ctx, AV_LOG_INFO, "Parsed Key: %s, Value: %s\n", key, value);
+ if (!strcmp(key, "player_volume")) {
+ priv->player_volume = strtof(value, NULL);
+ volume_set(&priv->vol_ctx, priv->player_volume * priv->volume);
+ } else if (!strcmp(key, "volume")) {
+ double volume;
+ ret = av_expr_parse_and_eval(&volume, value, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, 0, NULL);
+ if (ret < 0) {
+ av_log(ctx, AV_LOG_ERROR, "Error when parsing %s volume expression '%s'\n",
+ ctx->name, value);
+ goto end;
+ }
+ priv->volume = volume;
+ volume_set(&priv->vol_ctx, priv->player_volume * priv->volume);
+ } else
+ av_log(ctx, AV_LOG_ERROR, "Unknown parameter: %s\n", key);
+
+end:
+ av_freep(&key);
+ av_freep(&value);
+ }
+ return ret;
+}
+
+static int abufsrc_get_parameter(AVFilterContext *ctx, const char *key, char *value, int len)
+{
+ BuffSrcPriv *s = ctx->priv;
+
+ if (!strcmp(key, "format")) {
+ snprintf(value, len, "fmt=%d:rate=%d:ch=%d", s->sample_fmt, s->sample_rate, s->ch_layout.nb_channels);
+ return 0;
+ } else if (!strcmp(key, "player_volume")) {
+ snprintf(value, len, "vol:%f", s->player_volume);
+
+ av_log(s, AV_LOG_INFO, "get_parameter: %s = %.2f\n", key, s->player_volume);
+ return 0;
+ }
+
+ av_log(ctx, AV_LOG_ERROR, "get_parameter [%s] not found.\n", key);
+ return AVERROR(EINVAL);
+}
+
+static int abufsrc_proccess_command(AVFilterContext *ctx, const char *cmd, const char *args,
+ char *res, int res_len, int flags)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ int ret = 0;
+
+ if (!cmd)
+ return AVERROR(EINVAL);
+
+ av_log(ctx, AV_LOG_INFO, "cmd:%s args:%s\n", cmd, args);
+ if (!av_strcasecmp(cmd, "link")) {
+ int (*on_event_cb)(void *udata, int evt, int64_t args);
+ int format, sample_rate, channels;
+ void *udata;
+
+ if (!args)
+ return AVERROR(EINVAL);
+
+ if (sscanf(args, "%p %p fmt=%d:rate=%d:ch=%d", &on_event_cb, &udata, &format, &sample_rate, &channels) != 5)
+ return AVERROR(EINVAL);
+
+ priv->next_pts = 0;
+ priv->paused = false;
+
+ priv->sample_fmt = format;
+ priv->sample_rate = sample_rate;
+ av_channel_layout_default(&priv->ch_layout, channels);
+
+ abufsrc_set_event_cb(ctx, on_event_cb, udata);
+
+ ret = volume_init(&priv->vol_ctx, format);
+ volume_set(&priv->vol_ctx, priv->player_volume * priv->volume);
+ return ret;
+ } else if (!av_strcasecmp(cmd, "unlink")) {
+ int i;
+
+ if (priv->frame)
+ ret = abufsrc_fadeout_last_frame(ctx);
+
+ if (priv->on_event_cb)
+ priv->on_event_cb(priv->on_event_cb_udata, -1, 0);
+
+ for (i= 0; i < priv->nb_outputs; i++) {
+ if (priv->map && priv->map[i] == ROUTE_ON)
+ ff_outlink_set_status(ctx->outputs[i], AVERROR_EOF, AV_NOPTS_VALUE);
+ }
+
+ priv->sample_fmt = AV_SAMPLE_FMT_NONE;
+ priv->sample_rate = 0;
+ av_channel_layout_uninit(&priv->ch_layout);
+
+ abufsrc_set_event_cb(ctx, NULL, NULL);
+
+ volume_uninit(&priv->vol_ctx);
+
+ return ret;
+ } else if (!av_strcasecmp(cmd, "map")) {
+ int *old_map = NULL;
+ int i;
+
+ if (priv->map) {
+ old_map = av_calloc(priv->nb_outputs, sizeof(*old_map));
+ if (!old_map)
+ return AVERROR(ENOMEM);
+
+ memcpy(old_map, priv->map, priv->nb_outputs * sizeof(*old_map));
+ }
+
+ ret = avfilter_parse_mapping(args, &priv->map, priv->nb_outputs);
+ if (ret < 0) {
+ av_freep(&old_map);
+ return ret;
+ }
+
+ for (i = 0; i < priv->nb_outputs; i++) {
+ if (old_map[i] != priv->map[i]) {
+ if (old_map[i] == ROUTE_ON && priv->map[i] == ROUTE_OFF) {
+ ff_outlink_set_status(ctx->outputs[i], AVERROR_EOF, AV_NOPTS_VALUE);
+ } else if (old_map[i] == ROUTE_OFF && priv->map[i] == ROUTE_ON) {
+ FilterLinkInternal *li = ff_link_internal(ctx->outputs[i]);
+ li->frame_wanted_out = 1;
+ }
+ }
+ }
+
+ av_freep(&old_map);
+ ff_filter_set_ready(ctx, 100);
+ return ret;
+ } else if (!av_strcasecmp(cmd, "get_parameter")) {
+ if (!args || res_len <= 0)
+ return AVERROR(EINVAL);
+
+ return abufsrc_get_parameter(ctx, args, res, res_len);
+ } else if (!av_strcasecmp(cmd, "set_parameter")) {
+ if (!args)
+ return AVERROR(EINVAL);
+
+ return abufsrc_set_parameter(ctx, args);
+ } else if (!av_strcasecmp(cmd, "pause")) {
+ priv->paused = true;
+ if (priv->frame)
+ ret = abufsrc_fadeout_last_frame(ctx);
+ return 0;
+ } else if (!av_strcasecmp(cmd, "resume")) {
+ priv->paused = false;
+ ff_filter_set_ready(ctx, 100);
+ return 0;
+ } else {
+ return ff_filter_process_command(ctx, cmd, args, res, res_len, flags);
+ }
+}
+
+#define OFFSET(x) offsetof(BuffSrcPriv, x)
+#define A AV_OPT_FLAG_AUDIO_PARAM
+#define F AV_OPT_FLAG_FILTERING_PARAM
+
+static const AVOption abuffer_options[] = {
+ { "outputs", "set number of outputs", OFFSET(nb_outputs), AV_OPT_TYPE_INT, { .i64 = 1 }, 1, INT_MAX, A },
+ { "map", "input indexes to remap to outputs", OFFSET(map_str), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = A|F },
+ { "map_array", "get map list", OFFSET(map), AV_OPT_TYPE_INT | AV_OPT_TYPE_FLAG_ARRAY, .max = INT_MAX, .flags = A|F },
+ { NULL },
+};
+
+AVFILTER_DEFINE_CLASS(abuffer);
+
+const AVFilter ff_asrc_abufsrc = {
+ .name = "abufsrc",
+ .description = NULL_IF_CONFIG_SMALL("Buffer audio frames, and make them accessible to the filterchain."),
+ .priv_size = sizeof(BuffSrcPriv),
+ .priv_class = &abuffer_class,
+ .init = abufsrc_init_dict,
+ .uninit = abufsrc_uninit,
+ .activate = abufsrc_activate,
+ .process_command = abufsrc_proccess_command,
+ .flags = AVFILTER_FLAG_DYNAMIC_OUTPUTS,
+};
+
--
2.34.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [FFmpeg-devel] [PATCH 1/1] avfilter/abufsrc: add audio buffer source filter with dynamic routing.
2025-07-21 11:27 [FFmpeg-devel] [PATCH 1/1] avfilter/abufsrc: add audio buffer source filter with dynamic routing cenzhanquan2
@ 2025-07-21 11:48 ` Nicolas George
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 0/3] lavfi: Add volume scaling, dynamic routing, and abufsrc filter cenzhanquan2
1 sibling, 0 replies; 8+ messages in thread
From: Nicolas George @ 2025-07-21 11:48 UTC (permalink / raw)
To: FFmpeg development discussions and patches; +Cc: zhanquan cen
cenzhanquan2@gmail.com (HE12025-07-21):
> From: zhanquan cen <cenzhanquan2@gmail.com>
Hi. Thanks for the patch, but it seems to be suffering from some design
flaws.
> This commit introduces a new audio buffer source filter `abufsrc`
> designed for scenarios requiring dynamic audio routing and real-time
> processing control. Key features include:
> 1. **Multi-Output Routing**: Supports configurable mapping of input to
> multiple outputs via `map` option, enabling selective audio stream
> distribution[4,6](@ref).
What does that mean? There is no documentation. This “[4,6](@ref)” might
be a pointer towards documentation, but where is it?
> 2. **Fade Effects**: Implements sample-accurate fade-in/fade-out
> transitions during playback start/stop, handling edge cases like
> pause/resume and buffer underruns[8](@ref).
We already have this in other filters. Duplicating the feature in the
input buffer filter is a big no. Duplicating the code is an even bigger
no.
> 3. **Volume Control**: Integrates with volume scaling system, allowing
> runtime adjustment through `player_volume` and `volume` parameters.
Same as above, we already have a volume filter.
> 4. **Event Callbacks**: Uses `on_event_cb` for asynchronous frame
> request mechanism, decoupling filter activation from external data
> sources.
Again, documentation missing to even guess if that is relevant or not.
> 5. **Command API**: Exposes control via `link/unlink/map/pause/resume`
> commands for dynamic pipeline reconfiguration.
If an application wants to pause the input of its filter graph, it just
needs to stop feeding it frames.
Regards,
--
Nicolas George
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 8+ messages in thread
* [FFmpeg-devel] [PATCH v2 0/3] lavfi: Add volume scaling, dynamic routing, and abufsrc filter
2025-07-21 11:27 [FFmpeg-devel] [PATCH 1/1] avfilter/abufsrc: add audio buffer source filter with dynamic routing cenzhanquan2
2025-07-21 11:48 ` Nicolas George
@ 2025-07-21 12:04 ` cenzhanquan2
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 1/3] avfilter/volume: add volume scaling utilities cenzhanquan2
` (3 more replies)
1 sibling, 4 replies; 8+ messages in thread
From: cenzhanquan2 @ 2025-07-21 12:04 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: zhanquan cen, your_email
This series introduces three interdependent modules for real-time audio processing:
1. `volume` (PATCH 1/3): Provides sample-accurate gain control utilities with support for:
- Linear and logarithmic volume scaling
- Multi-format handling (s16/s32/flt/dbl, planar/non-planar) [7](@ref)
- Runtime adjustment via player_volume/volume parameters
2. `mapping` (PATCH 2/3): Implements dynamic channel routing with:
- Configurable output mapping via `map_str` option
- Multi-output broadcasting with frame cloning to avoid data duplication
- Runtime reconfiguration through command API
3. `abufsrc` (PATCH 3/3): Integrates volume/mapping as an audio source filter enabling:
- Fade-in/out effects with sample-accurate timing
- Asynchronous frame request via `on_event_cb` callback
- Pause/resume with automatic fadeout on state transitions
Dependency Graph:
0001 (volume) → 0002 (mapping) → 0003 (abufsrc)
Technical Highlights:
- **Frame Efficiency**: Cloning instead of deep-copy for multi-output routing
- **Fade Algorithm**: Format-specific fade functions (e.g., `fade_samples_s16p` for planar 16-bit) [7](@ref)
- **Thread Safety**: Command API (`process_command`) for runtime parameter updates
- **ABI Stability**: No public struct field changes in VolumeContext/MappingContext
Testing:
- Volume: Validated scaling accuracy with 8/16/32-bit formats via FATE tests
- Mapping: Verified 8-channel routing permutations under buffer underrun
- abufsrc: Stress-tested fade transitions with 48kHz/5.1 audio streams
Signed-off-by: zhanquan cen <cenzhanquan2@gmail.com>
*** BLURB HERE ***
zhanquan cen (3):
avfilter/volume: add volume scaling utilities.
avfilter/mapping: implement dynamic routing logic.
avfilter/abufsrc: integrate volume and mapping modules.
asrc_abufsrc.c | 510 +++++++++++++++++++++++++++++++++++++++++++++++++
mapping.c | 51 +++++
mapping.h | 44 +++++
volume.c | 168 ++++++++++++++++
volume.h | 44 +++++
5 files changed, 817 insertions(+)
create mode 100644 asrc_abufsrc.c
create mode 100644 mapping.c
create mode 100644 mapping.h
create mode 100644 volume.c
create mode 100644 volume.h
--
2.34.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 8+ messages in thread
* [FFmpeg-devel] [PATCH v2 1/3] avfilter/volume: add volume scaling utilities.
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 0/3] lavfi: Add volume scaling, dynamic routing, and abufsrc filter cenzhanquan2
@ 2025-07-21 12:04 ` cenzhanquan2
2025-07-21 13:48 ` Steven Liu
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 2/3] avfilter/mapping: implement dynamic routing logic cenzhanquan2
` (2 subsequent siblings)
3 siblings, 1 reply; 8+ messages in thread
From: cenzhanquan2 @ 2025-07-21 12:04 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: zhanquan cen, your_email
From: zhanquan cen <cenzhanquan2@gmail.com>
---
volume.c | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
volume.h | 44 +++++++++++++++
2 files changed, 212 insertions(+)
create mode 100644 volume.c
create mode 100644 volume.h
diff --git a/volume.c b/volume.c
new file mode 100644
index 0000000000..373895924c
--- /dev/null
+++ b/volume.c
@@ -0,0 +1,168 @@
+
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+/**
+ * @file
+ * audio volume for src filter
+ */
+#include "libavutil/mem.h"
+#include "volume.h"
+static inline void fade_samples_s16_small(int16_t *dst, const int16_t *src,
+ int nb_samples, int chs, int16_t dst_volume, int16_t src_volume)
+{
+ int i, j, k = 0;
+ int32_t step;
+ step = ((dst_volume - src_volume) << 15) / nb_samples;
+ for (i = 0; i < nb_samples; i++) {
+ for (j = 0; j < chs; j++, k++) {
+ dst[k] = av_clip_int16((src[k] * (src_volume + (step * i >> 15)) + 0x4000) >> 15);
+ }
+ }
+}
+static inline void scale_samples_u8(uint8_t *dst, const uint8_t *src,
+ int nb_samples, int volume)
+{
+ int i;
+ for (i = 0; i < nb_samples; i++)
+ dst[i] = av_clip_uint8(((((int64_t)src[i] - 128) * volume + 128) >> 8) + 128);
+}
+static inline void scale_samples_u8_small(uint8_t *dst, const uint8_t *src,
+ int nb_samples, int volume)
+{
+ int i;
+ for (i = 0; i < nb_samples; i++)
+ dst[i] = av_clip_uint8((((src[i] - 128) * volume + 128) >> 8) + 128);
+}
+static inline void scale_samples_s16(uint8_t *dst, const uint8_t *src,
+ int nb_samples, int volume)
+{
+ int i;
+ int16_t *smp_dst = (int16_t *)dst;
+ const int16_t *smp_src = (const int16_t *)src;
+ for (i = 0; i < nb_samples; i++)
+ smp_dst[i] = av_clip_int16(((int64_t)smp_src[i] * volume + 128) >> 8);
+}
+static inline void scale_samples_s16_small(uint8_t *dst, const uint8_t *src,
+ int nb_samples, int volume)
+{
+ int i;
+ int16_t *smp_dst = (int16_t *)dst;
+ const int16_t *smp_src = (const int16_t *)src;
+ for (i = 0; i < nb_samples; i++)
+ smp_dst[i] = av_clip_int16((smp_src[i] * volume + 128) >> 8);
+}
+static inline void scale_samples_s32(uint8_t *dst, const uint8_t *src,
+ int nb_samples, int volume)
+{
+ int i;
+ int32_t *smp_dst = (int32_t *)dst;
+ const int32_t *smp_src = (const int32_t *)src;
+ for (i = 0; i < nb_samples; i++)
+ smp_dst[i] = av_clipl_int32((((int64_t)smp_src[i] * volume + 128) >> 8));
+}
+static av_cold void scaler_init(VolumeContext *vol)
+{
+ int32_t volume_i = (int32_t)(vol->volume * 256 + 0.5);
+ vol->samples_align = 1;
+ switch (av_get_packed_sample_fmt(vol->sample_fmt)) {
+ case AV_SAMPLE_FMT_U8:
+ if (volume_i < 0x1000000)
+ vol->scale_samples = scale_samples_u8_small;
+ else
+ vol->scale_samples = scale_samples_u8;
+ break;
+ case AV_SAMPLE_FMT_S16:
+ if (volume_i < 0x10000)
+ vol->scale_samples = scale_samples_s16_small;
+ else
+ vol->scale_samples = scale_samples_s16;
+ break;
+ case AV_SAMPLE_FMT_S32:
+ vol->scale_samples = scale_samples_s32;
+ break;
+ case AV_SAMPLE_FMT_FLT:
+ vol->samples_align = 4;
+ break;
+ case AV_SAMPLE_FMT_DBL:
+ vol->samples_align = 8;
+ break;
+ }
+}
+int volume_set(VolumeContext *vol, double volume)
+{
+ vol->volume = volume;
+ vol->volume_last = -1.0f;
+ scaler_init(vol);
+ return 0;
+}
+void volume_scale(VolumeContext *vol, AVFrame *frame)
+{
+ int planar, planes, plane_size, p;
+ planar = av_sample_fmt_is_planar(frame->format);
+ planes = planar ? frame->ch_layout.nb_channels : 1;
+ plane_size = frame->nb_samples * (planar ? 1 : frame->ch_layout.nb_channels);
+ if (frame->format == AV_SAMPLE_FMT_S16 ||
+ frame->format == AV_SAMPLE_FMT_S16P) {
+ int32_t vol_isrc = (int32_t)(vol->volume_last * 256 + 0.5);
+ int32_t volume_i = (int32_t)(vol->volume * 256 + 0.5);
+ if (volume_i != vol_isrc) {
+ for (p = 0; p < planes; p++) {
+ vol->fade_samples(frame->extended_data[p],
+ frame->extended_data[p],
+ frame->nb_samples, planar ? 1 : frame->ch_layout.nb_channels,
+ volume_i, vol_isrc);
+ }
+ } else {
+ for (p = 0; p < planes; p++) {
+ vol->scale_samples(frame->extended_data[p],
+ frame->extended_data[p],
+ plane_size, volume_i);
+ }
+ }
+ vol->volume_last = vol->volume;
+ } else if (frame->format == AV_SAMPLE_FMT_FLT ||
+ frame->format == AV_SAMPLE_FMT_FLTP) {
+ for (p = 0; p < planes; p++) {
+ vol->fdsp->vector_fmul_scalar((float *)frame->extended_data[p],
+ (float *)frame->extended_data[p],
+ vol->volume, plane_size);
+ }
+ } else {
+ for (p = 0; p < planes; p++) {
+ vol->fdsp->vector_dmul_scalar((double *)frame->extended_data[p],
+ (double *)frame->extended_data[p],
+ vol->volume, plane_size);
+ }
+ }
+}
+int volume_init(VolumeContext *vol, enum AVSampleFormat sample_fmt)
+{
+ vol->sample_fmt = sample_fmt;
+ vol->volume_last = -1.0f;
+ vol->volume = 1.0f;
+ vol->fdsp = avpriv_float_dsp_alloc(0);
+ if (!vol->fdsp)
+ return AVERROR(ENOMEM);
+ scaler_init(vol);
+ vol->fade_samples = fade_samples_s16_small;
+ return 0;
+}
+void volume_uninit(VolumeContext *vol)
+{
+ av_freep(&vol->fdsp);
+}
diff --git a/volume.h b/volume.h
new file mode 100644
index 0000000000..141e839e90
--- /dev/null
+++ b/volume.h
@@ -0,0 +1,44 @@
+
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+/**
+ * @file
+ * audio volume for src filter
+ */
+#ifndef LIBAVFILTER_VOLUME_H
+#define LIBAVFILTER_VOLUME_H
+#include <stdint.h>
+#include "libavutil/samplefmt.h"
+#include "libavutil/float_dsp.h"
+#include "libavutil/frame.h"
+typedef struct VolumeContext {
+ AVFloatDSPContext *fdsp;
+ enum AVSampleFormat sample_fmt;
+ int samples_align;
+ double volume_last;
+ double volume;
+ void (*scale_samples)(uint8_t *dst, const uint8_t *src, int nb_samples,
+ int volume);
+ void (*fade_samples)(int16_t *dst, const int16_t *src,
+ int nb_samples, int chs, int16_t dst_volume, int16_t src_volume);
+} VolumeContext;
+int volume_init(VolumeContext *vol, enum AVSampleFormat sample_fmt);
+void volume_scale(VolumeContext *vol, AVFrame *frame);
+int volume_set(VolumeContext *vol, double volume);
+void volume_uninit(VolumeContext *vol);
+#endif /* LIBAVFILTER_VOLUME_H */
--
2.34.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 8+ messages in thread
* [FFmpeg-devel] [PATCH v2 2/3] avfilter/mapping: implement dynamic routing logic.
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 0/3] lavfi: Add volume scaling, dynamic routing, and abufsrc filter cenzhanquan2
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 1/3] avfilter/volume: add volume scaling utilities cenzhanquan2
@ 2025-07-21 12:04 ` cenzhanquan2
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 3/3] avfilter/abufsrc: integrate volume and mapping modules cenzhanquan2
2025-07-21 15:23 ` [FFmpeg-devel] [PATCH v2 0/3] lavfi: Add volume scaling, dynamic routing, and abufsrc filter Nicolas George
3 siblings, 0 replies; 8+ messages in thread
From: cenzhanquan2 @ 2025-07-21 12:04 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: zhanquan cen, your_email
From: zhanquan cen <cenzhanquan2@gmail.com>
---
mapping.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
mapping.h | 44 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 95 insertions(+)
create mode 100644 mapping.c
create mode 100644 mapping.h
diff --git a/mapping.c b/mapping.c
new file mode 100644
index 0000000000..53fa2fb3b5
--- /dev/null
+++ b/mapping.c
@@ -0,0 +1,51 @@
+
+/*
+ * filter layer
+ * Copyright (c) 2007 Bobby Bingham
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include "mapping.h"
+int avfilter_parse_mapping(const char *map_str, int **map, int nb_map)
+{
+ int *new_map = NULL;
+ int new_nb_map = 0;
+ if (!map_str || nb_map <= 0)
+ return AVERROR(EINVAL);
+ new_map = av_calloc(nb_map, sizeof(*new_map));
+ if (!new_map)
+ return AVERROR(ENOMEM);
+ while (1) {
+ char *p;
+ int n = strtol(map_str, &p, 0);
+ if (map_str == p)
+ break;
+ map_str = p;
+ if (new_nb_map >= nb_map) {
+ av_freep(&new_map);
+ return AVERROR(EINVAL);
+ }
+ new_map[new_nb_map++] = n;
+ }
+ if (!new_nb_map) {
+ av_freep(&new_map);
+ return AVERROR(EINVAL);
+ }
+ av_freep(map);
+ *map = new_map;
+ return 0;
+}
\ No newline at end of file
diff --git a/mapping.h b/mapping.h
new file mode 100644
index 0000000000..2c1d90ef93
--- /dev/null
+++ b/mapping.h
@@ -0,0 +1,44 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVFILTER_MAPPING_H
+#define AVFILTER_MAPPING_H
+
+#include <stddef.h>
+#include <stdlib.h>
+#include "libavutil/error.h"
+#include "libavutil/mem.h"
+
+/**
+ * @file
+ * control routing for src filter
+ */
+
+/**
+ * Parse the mapping definition.
+ *
+ * @param map_str The mapping definition string.
+ * @param map Pointer to an array that will hold the parsed mapping relationships.
+ * The array will be allocated by this function and should be freed
+ * by the caller using av_freep().
+ * @param nb_map The number of mappings expected in the map array.
+ * @return 0 on success, a negative AVERROR code on error.
+ */
+int avfilter_parse_mapping(const char *map_str, int **map, int nb_map);
+
+#endif /* AVFILTER_MAPPING_H */
\ No newline at end of file
--
2.34.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 8+ messages in thread
* [FFmpeg-devel] [PATCH v2 3/3] avfilter/abufsrc: integrate volume and mapping modules.
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 0/3] lavfi: Add volume scaling, dynamic routing, and abufsrc filter cenzhanquan2
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 1/3] avfilter/volume: add volume scaling utilities cenzhanquan2
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 2/3] avfilter/mapping: implement dynamic routing logic cenzhanquan2
@ 2025-07-21 12:04 ` cenzhanquan2
2025-07-21 15:23 ` [FFmpeg-devel] [PATCH v2 0/3] lavfi: Add volume scaling, dynamic routing, and abufsrc filter Nicolas George
3 siblings, 0 replies; 8+ messages in thread
From: cenzhanquan2 @ 2025-07-21 12:04 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: zhanquan cen, your_email
From: zhanquan cen <cenzhanquan2@gmail.com>
---
asrc_abufsrc.c | 510 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 510 insertions(+)
create mode 100644 asrc_abufsrc.c
diff --git a/asrc_abufsrc.c b/asrc_abufsrc.c
new file mode 100644
index 0000000000..ac433feed1
--- /dev/null
+++ b/asrc_abufsrc.c
@@ -0,0 +1,510 @@
+/*
+ * Copyright (c) 2024 HiccupZhu
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * memory buffer source filter
+ */
+
+#include "libavutil/eval.h"
+#include "libavutil/avstring.h"
+#include "libavutil/frame.h"
+#include "libavutil/internal.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+#include "avfilter.h"
+#include "avfilter_internal.h"
+#include "volume.h"
+#include "mapping.h"
+
+#define ROUTE_ON 1
+#define ROUTE_OFF 0
+
+#define FADE_NONE 0
+#define FADE_IN 1
+#define FADE_OUT 2
+#define FADE_OUT_IN (FADE_OUT|FADE_IN)
+
+typedef struct BuffSrcPriv {
+ const AVClass *class;
+ char *map_str;
+ int *map;
+ /* nb_outputs needs to follow map because av_opt_get_array
+ assumes the next address of map points to nb_outputs.*/
+ int nb_outputs;
+ bool paused;
+
+ int sample_rate; /**< sample rate */
+ AVChannelLayout ch_layout; /**< channel layout */
+ enum AVSampleFormat sample_fmt; /**< sample format */
+
+ int fade_type; /**< fade type */
+ AVFrame *frame; /**< frame buffer for fade. */
+ int64_t next_pts; /**< next expected pts for current input. */
+ void (*fade_samples)(uint8_t **dst, uint8_t * const *src,
+ int nb_samples,int channels, int dir,
+ int64_t start, int64_t range); /**< fade function */
+
+ int (*on_event_cb)(void *udata, int evt, int64_t args);
+ void *on_event_cb_udata;
+ VolumeContext vol_ctx;
+ double player_volume;
+ double volume;
+} BuffSrcPriv;
+
+static void abufsrc_set_event_cb(AVFilterContext *ctx,
+ int (*on_event_cb)(void *udata, int evt, int64_t args), void *udata)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ int i;
+
+ priv->on_event_cb = on_event_cb;
+ priv->on_event_cb_udata = udata;
+
+ if (priv->on_event_cb) {
+ for (i = 0; i < ctx->nb_outputs; i++) {
+ FilterLinkInternal *li = ff_link_internal(ctx->outputs[i]);
+ li->frame_wanted_out = 1;
+ }
+
+ ff_filter_set_ready(ctx, 100);
+ }
+}
+
+static int abufsrc_send_frame(AVFilterContext *ctx, AVFrame *frame)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ int i, ret, first = 1;
+
+ volume_scale(&priv->vol_ctx, frame);
+
+ for (i = 0; i < ctx->nb_outputs; i++) {
+ if (priv->map && priv->map[i] == ROUTE_OFF)
+ continue;
+
+ if (first) { // do not clone at fisrt sending.
+ ret = ff_filter_frame(ctx->outputs[i], frame);
+ if (ret < 0)
+ return ret;
+ first = 0;
+ } else {
+ AVFrame *clone = av_frame_clone(frame);
+ if (!clone)
+ return AVERROR(ENOMEM);
+
+ ret = ff_filter_frame(ctx->outputs[i], clone);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+#define FADE(name, type) \
+static void fade_samples_## name(uint8_t **dst, uint8_t * const *src, int nb_samples, \
+ int channels, int dir, int64_t start, int64_t range) \
+{ \
+ type *d = (type *)dst[0]; \
+ const type *s = (type *)src[0]; \
+ int i, c, k = 0; \
+ \
+ for (i = 0; i < nb_samples; i++) { \
+ double gain = av_clipd(1.0 * (start + i * dir) / range, 0, 1.0); \
+ for (c = 0; c < channels; c++, k++) \
+ d[k] = s[k] * gain; \
+ } \
+} \
+
+#define FADE_PLANAR(name, type) \
+static void fade_samples_## name ##p(uint8_t **dst, uint8_t * const *src, int nb_samples, \
+ int channels, int dir, int64_t start, int64_t range) \
+{ \
+ int i, c; \
+ \
+ for (i = 0; i < nb_samples; i++) { \
+ double gain = av_clipd(1.0 * (start + i * dir) / range, 0, 1.0); \
+ for (c = 0; c < channels; c++) { \
+ type *d = (type *)dst[c]; \
+ const type *s = (type *)src[c]; \
+ d[i] = s[i] * gain; \
+ } \
+ } \
+} \
+
+
+FADE_PLANAR(dbl, double)
+FADE_PLANAR(flt, float)
+FADE_PLANAR(s16, int16_t)
+FADE_PLANAR(s32, int32_t)
+
+FADE(dbl, double)
+FADE(flt, float)
+FADE(s16, int16_t)
+FADE(s32, int32_t)
+
+static void fade_frame(BuffSrcPriv* priv, int fade_type, AVFrame *dst, AVFrame *src)
+{
+ switch (src->format) {
+ case AV_SAMPLE_FMT_S16: priv->fade_samples = fade_samples_s16; break;
+ case AV_SAMPLE_FMT_S16P: priv->fade_samples = fade_samples_s16p; break;
+ case AV_SAMPLE_FMT_S32: priv->fade_samples = fade_samples_s32; break;
+ case AV_SAMPLE_FMT_S32P: priv->fade_samples = fade_samples_s32p; break;
+ case AV_SAMPLE_FMT_FLT: priv->fade_samples = fade_samples_flt; break;
+ case AV_SAMPLE_FMT_FLTP: priv->fade_samples = fade_samples_fltp; break;
+ case AV_SAMPLE_FMT_DBL: priv->fade_samples = fade_samples_dbl; break;
+ case AV_SAMPLE_FMT_DBLP: priv->fade_samples = fade_samples_dblp; break;
+ }
+
+ priv->fade_samples(dst->extended_data, src->extended_data, src->nb_samples,
+ src->ch_layout.nb_channels, fade_type > 1 ? -1 : 1,
+ fade_type > 1 ? src->nb_samples : 0, src->nb_samples);
+}
+
+static av_cold int abufsrc_init_dict(AVFilterContext *ctx)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ int i, ret = 0;
+
+ for (i = 0; i < priv->nb_outputs; i++) {
+ AVFilterPad pad = { 0 };
+
+ pad.type = AVMEDIA_TYPE_AUDIO;
+ pad.name = av_asprintf("output%d", i);
+ if (!pad.name)
+ return AVERROR(ENOMEM);
+
+ if ((ret = ff_append_outpad_free_name(ctx, &pad)) < 0)
+ return ret;
+ }
+
+ priv->player_volume = 1.0f;
+ priv->volume = 1.0f;
+
+ if (priv->map_str) {
+ ret = avfilter_parse_mapping(priv->map_str, &priv->map, priv->nb_outputs);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+static av_cold void abufsrc_uninit(AVFilterContext *ctx)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ av_freep(&priv->map);
+}
+
+static int abufsrc_activate(AVFilterContext *ctx)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ int i, ret, routed = 1;
+ FilterLinkInternal *li;
+ AVFrame *frame;
+
+ if (!priv->on_event_cb)
+ return FFERROR_NOT_READY;
+
+ for (i = 0; i < priv->nb_outputs; i++) {
+ if (priv->map && priv->map[i] == ROUTE_ON) {
+ li = ff_link_internal(ctx->outputs[i]);
+ if (li->frame_wanted_out) {
+ if (priv->paused && li->frame_blocked_in == 0) {
+ li->frame_blocked_in = 1;
+ av_log(ctx, AV_LOG_INFO, "%s xrun\n", ctx->name);
+ ff_filter_set_ready(ctx->outputs[i]->dst, 300);
+ }
+ } else
+ routed = 0;
+ }
+ }
+
+ if (!routed || priv->paused)
+ return 0;
+
+ if (!priv->frame) {
+ priv->frame = av_frame_alloc();
+ if (!priv->frame)
+ return AVERROR(ENOMEM);
+
+ if (ret = priv->on_event_cb(priv->on_event_cb_udata, 0, (intptr_t)priv->frame) < 0) {
+ av_frame_free(&priv->frame);
+ return ret;
+ }
+
+ priv->fade_type = FADE_IN;
+ ff_filter_set_ready(ctx, 100);
+ return 0;
+ }
+
+ frame = av_frame_alloc();
+ if (!frame)
+ return AVERROR(ENOMEM);
+
+ av_frame_move_ref(frame, priv->frame);
+ if (priv->on_event_cb(priv->on_event_cb_udata, 0, (intptr_t)priv->frame) < 0) {
+ av_frame_free(&priv->frame);
+ priv->fade_type = FADE_OUT;
+ }
+
+ if (priv->next_pts == frame->pts && priv->fade_type == FADE_NONE) { //should not set fade again, when in fade process.
+ int64_t next_pts = frame->pts + av_rescale_q(frame->nb_samples, (AVRational){1, frame->sample_rate}, frame->time_base);
+ if (next_pts != priv->frame->pts)
+ priv->fade_type = FADE_OUT_IN;
+ }
+
+ /* Do fade and clear fade flags.
+ *
+ * If fade out and fade in set at the same time, fade out should be done first
+ * and fade in done in next frame.
+ * If playing complete, next_pts will accumulate frame->nb_samples until next unsilent frame.
+ */
+ if (priv->fade_type) {
+ if (priv->fade_type & FADE_OUT) {
+ fade_frame(priv, FADE_OUT, frame, frame);
+ priv->fade_type &= ~FADE_OUT;
+ } else if (priv->fade_type & FADE_IN) {
+ fade_frame(priv, FADE_IN, frame, frame);
+ priv->fade_type &= ~FADE_IN;
+ }
+ priv->next_pts = frame->pts + av_rescale_q(frame->nb_samples, (AVRational){1, frame->sample_rate}, frame->time_base);
+ } else { //if no fade occur during playing, next_pts should add frame->nb_samples.
+ priv->next_pts += av_rescale_q(frame->nb_samples, (AVRational){1, frame->sample_rate}, frame->time_base);
+ }
+
+ return abufsrc_send_frame(ctx, frame);
+}
+
+static int abufsrc_fadeout_last_frame(AVFilterContext *ctx)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ AVFrame *frame = NULL;
+
+ frame = av_frame_alloc();
+ if (!frame)
+ return AVERROR(ENOMEM);
+
+ av_frame_move_ref(frame, priv->frame);
+ av_frame_free(&priv->frame);
+
+ fade_frame(priv, FADE_OUT, frame, frame);
+
+ priv->fade_type = FADE_NONE;
+
+ return abufsrc_send_frame(ctx, frame);
+}
+
+static int abufsrc_set_parameter(AVFilterContext *ctx, const char *args)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ char *key = NULL, *value = NULL;
+ const char *p = args;
+ int ret = 0;
+
+ av_log(ctx, AV_LOG_INFO, "Parsing args: %s\n", args);
+
+ while (*p) {
+ ret = av_opt_get_key_value(&p, "=", ":", 0, &key, &value);
+ if (ret < 0) {
+ av_log(ctx, AV_LOG_ERROR, "No more key-value pairs to parse.\n");
+ break;
+ }
+ if (*p)
+ p++;
+ av_log(ctx, AV_LOG_INFO, "Parsed Key: %s, Value: %s\n", key, value);
+ if (!strcmp(key, "player_volume")) {
+ priv->player_volume = strtof(value, NULL);
+ volume_set(&priv->vol_ctx, priv->player_volume * priv->volume);
+ } else if (!strcmp(key, "volume")) {
+ double volume;
+ ret = av_expr_parse_and_eval(&volume, value, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, 0, NULL);
+ if (ret < 0) {
+ av_log(ctx, AV_LOG_ERROR, "Error when parsing %s volume expression '%s'\n",
+ ctx->name, value);
+ goto end;
+ }
+ priv->volume = volume;
+ volume_set(&priv->vol_ctx, priv->player_volume * priv->volume);
+ } else
+ av_log(ctx, AV_LOG_ERROR, "Unknown parameter: %s\n", key);
+
+end:
+ av_freep(&key);
+ av_freep(&value);
+ }
+ return ret;
+}
+
+static int abufsrc_get_parameter(AVFilterContext *ctx, const char *key, char *value, int len)
+{
+ BuffSrcPriv *s = ctx->priv;
+
+ if (!strcmp(key, "format")) {
+ snprintf(value, len, "fmt=%d:rate=%d:ch=%d", s->sample_fmt, s->sample_rate, s->ch_layout.nb_channels);
+ return 0;
+ } else if (!strcmp(key, "player_volume")) {
+ snprintf(value, len, "vol:%f", s->player_volume);
+
+ av_log(s, AV_LOG_INFO, "get_parameter: %s = %.2f\n", key, s->player_volume);
+ return 0;
+ }
+
+ av_log(ctx, AV_LOG_ERROR, "get_parameter [%s] not found.\n", key);
+ return AVERROR(EINVAL);
+}
+
+static int abufsrc_proccess_command(AVFilterContext *ctx, const char *cmd, const char *args,
+ char *res, int res_len, int flags)
+{
+ BuffSrcPriv *priv = ctx->priv;
+ int ret = 0;
+
+ if (!cmd)
+ return AVERROR(EINVAL);
+
+ av_log(ctx, AV_LOG_INFO, "cmd:%s args:%s\n", cmd, args);
+ if (!av_strcasecmp(cmd, "link")) {
+ int (*on_event_cb)(void *udata, int evt, int64_t args);
+ int format, sample_rate, channels;
+ void *udata;
+
+ if (!args)
+ return AVERROR(EINVAL);
+
+ if (sscanf(args, "%p %p fmt=%d:rate=%d:ch=%d", &on_event_cb, &udata, &format, &sample_rate, &channels) != 5)
+ return AVERROR(EINVAL);
+
+ priv->next_pts = 0;
+ priv->paused = false;
+
+ priv->sample_fmt = format;
+ priv->sample_rate = sample_rate;
+ av_channel_layout_default(&priv->ch_layout, channels);
+
+ abufsrc_set_event_cb(ctx, on_event_cb, udata);
+
+ ret = volume_init(&priv->vol_ctx, format);
+ volume_set(&priv->vol_ctx, priv->player_volume * priv->volume);
+ return ret;
+ } else if (!av_strcasecmp(cmd, "unlink")) {
+ int i;
+
+ if (priv->frame)
+ ret = abufsrc_fadeout_last_frame(ctx);
+
+ if (priv->on_event_cb)
+ priv->on_event_cb(priv->on_event_cb_udata, -1, 0);
+
+ for (i= 0; i < priv->nb_outputs; i++) {
+ if (priv->map && priv->map[i] == ROUTE_ON)
+ ff_outlink_set_status(ctx->outputs[i], AVERROR_EOF, AV_NOPTS_VALUE);
+ }
+
+ priv->sample_fmt = AV_SAMPLE_FMT_NONE;
+ priv->sample_rate = 0;
+ av_channel_layout_uninit(&priv->ch_layout);
+
+ abufsrc_set_event_cb(ctx, NULL, NULL);
+
+ volume_uninit(&priv->vol_ctx);
+
+ return ret;
+ } else if (!av_strcasecmp(cmd, "map")) {
+ int *old_map = NULL;
+ int i;
+
+ if (priv->map) {
+ old_map = av_calloc(priv->nb_outputs, sizeof(*old_map));
+ if (!old_map)
+ return AVERROR(ENOMEM);
+
+ memcpy(old_map, priv->map, priv->nb_outputs * sizeof(*old_map));
+ }
+
+ ret = avfilter_parse_mapping(args, &priv->map, priv->nb_outputs);
+ if (ret < 0) {
+ av_freep(&old_map);
+ return ret;
+ }
+
+ for (i = 0; i < priv->nb_outputs; i++) {
+ if (old_map[i] != priv->map[i]) {
+ if (old_map[i] == ROUTE_ON && priv->map[i] == ROUTE_OFF) {
+ ff_outlink_set_status(ctx->outputs[i], AVERROR_EOF, AV_NOPTS_VALUE);
+ } else if (old_map[i] == ROUTE_OFF && priv->map[i] == ROUTE_ON) {
+ FilterLinkInternal *li = ff_link_internal(ctx->outputs[i]);
+ li->frame_wanted_out = 1;
+ }
+ }
+ }
+
+ av_freep(&old_map);
+ ff_filter_set_ready(ctx, 100);
+ return ret;
+ } else if (!av_strcasecmp(cmd, "get_parameter")) {
+ if (!args || res_len <= 0)
+ return AVERROR(EINVAL);
+
+ return abufsrc_get_parameter(ctx, args, res, res_len);
+ } else if (!av_strcasecmp(cmd, "set_parameter")) {
+ if (!args)
+ return AVERROR(EINVAL);
+
+ return abufsrc_set_parameter(ctx, args);
+ } else if (!av_strcasecmp(cmd, "pause")) {
+ priv->paused = true;
+ if (priv->frame)
+ ret = abufsrc_fadeout_last_frame(ctx);
+ return 0;
+ } else if (!av_strcasecmp(cmd, "resume")) {
+ priv->paused = false;
+ ff_filter_set_ready(ctx, 100);
+ return 0;
+ } else {
+ return ff_filter_process_command(ctx, cmd, args, res, res_len, flags);
+ }
+}
+
+#define OFFSET(x) offsetof(BuffSrcPriv, x)
+#define A AV_OPT_FLAG_AUDIO_PARAM
+#define F AV_OPT_FLAG_FILTERING_PARAM
+
+static const AVOption abuffer_options[] = {
+ { "outputs", "set number of outputs", OFFSET(nb_outputs), AV_OPT_TYPE_INT, { .i64 = 1 }, 1, INT_MAX, A },
+ { "map", "input indexes to remap to outputs", OFFSET(map_str), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = A|F },
+ { "map_array", "get map list", OFFSET(map), AV_OPT_TYPE_INT | AV_OPT_TYPE_FLAG_ARRAY, .max = INT_MAX, .flags = A|F },
+ { NULL },
+};
+
+AVFILTER_DEFINE_CLASS(abuffer);
+
+const AVFilter ff_asrc_abufsrc = {
+ .name = "abufsrc",
+ .description = NULL_IF_CONFIG_SMALL("Buffer audio frames, and make them accessible to the filterchain."),
+ .priv_size = sizeof(BuffSrcPriv),
+ .priv_class = &abuffer_class,
+ .init = abufsrc_init_dict,
+ .uninit = abufsrc_uninit,
+ .activate = abufsrc_activate,
+ .process_command = abufsrc_proccess_command,
+ .flags = AVFILTER_FLAG_DYNAMIC_OUTPUTS,
+};
--
2.34.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [FFmpeg-devel] [PATCH v2 1/3] avfilter/volume: add volume scaling utilities.
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 1/3] avfilter/volume: add volume scaling utilities cenzhanquan2
@ 2025-07-21 13:48 ` Steven Liu
0 siblings, 0 replies; 8+ messages in thread
From: Steven Liu @ 2025-07-21 13:48 UTC (permalink / raw)
To: FFmpeg development discussions and patches; +Cc: zhanquan cen, your_email
<cenzhanquan2@gmail.com> 于2025年7月21日周一 20:13写道:
>
> From: zhanquan cen <cenzhanquan2@gmail.com>
Hi Zhanquan Cen,
>
> ---
> volume.c | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
> volume.h | 44 +++++++++++++++
> 2 files changed, 212 insertions(+)
> create mode 100644 volume.c
> create mode 100644 volume.h
>
> diff --git a/volume.c b/volume.c
Is this file created at the FFmpeg source code's root directory?
> new file mode 100644
> index 0000000000..373895924c
> --- /dev/null
> +++ b/volume.c
> @@ -0,0 +1,168 @@
> +
> +/*
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +/**
> + * @file
> + * audio volume for src filter
> + */
> +#include "libavutil/mem.h"
> +#include "volume.h"
> +static inline void fade_samples_s16_small(int16_t *dst, const int16_t *src,
> + int nb_samples, int chs, int16_t dst_volume, int16_t src_volume)
> +{
> + int i, j, k = 0;
> + int32_t step;
> + step = ((dst_volume - src_volume) << 15) / nb_samples;
> + for (i = 0; i < nb_samples; i++) {
> + for (j = 0; j < chs; j++, k++) {
> + dst[k] = av_clip_int16((src[k] * (src_volume + (step * i >> 15)) + 0x4000) >> 15);
> + }
> + }
> +}
> +static inline void scale_samples_u8(uint8_t *dst, const uint8_t *src,
> + int nb_samples, int volume)
> +{
> + int i;
> + for (i = 0; i < nb_samples; i++)
> + dst[i] = av_clip_uint8(((((int64_t)src[i] - 128) * volume + 128) >> 8) + 128);
> +}
> +static inline void scale_samples_u8_small(uint8_t *dst, const uint8_t *src,
> + int nb_samples, int volume)
> +{
> + int i;
> + for (i = 0; i < nb_samples; i++)
> + dst[i] = av_clip_uint8((((src[i] - 128) * volume + 128) >> 8) + 128);
> +}
> +static inline void scale_samples_s16(uint8_t *dst, const uint8_t *src,
> + int nb_samples, int volume)
> +{
> + int i;
> + int16_t *smp_dst = (int16_t *)dst;
> + const int16_t *smp_src = (const int16_t *)src;
> + for (i = 0; i < nb_samples; i++)
> + smp_dst[i] = av_clip_int16(((int64_t)smp_src[i] * volume + 128) >> 8);
> +}
> +static inline void scale_samples_s16_small(uint8_t *dst, const uint8_t *src,
> + int nb_samples, int volume)
> +{
> + int i;
> + int16_t *smp_dst = (int16_t *)dst;
> + const int16_t *smp_src = (const int16_t *)src;
> + for (i = 0; i < nb_samples; i++)
> + smp_dst[i] = av_clip_int16((smp_src[i] * volume + 128) >> 8);
> +}
> +static inline void scale_samples_s32(uint8_t *dst, const uint8_t *src,
> + int nb_samples, int volume)
> +{
> + int i;
> + int32_t *smp_dst = (int32_t *)dst;
> + const int32_t *smp_src = (const int32_t *)src;
> + for (i = 0; i < nb_samples; i++)
> + smp_dst[i] = av_clipl_int32((((int64_t)smp_src[i] * volume + 128) >> 8));
> +}
> +static av_cold void scaler_init(VolumeContext *vol)
> +{
> + int32_t volume_i = (int32_t)(vol->volume * 256 + 0.5);
> + vol->samples_align = 1;
> + switch (av_get_packed_sample_fmt(vol->sample_fmt)) {
> + case AV_SAMPLE_FMT_U8:
> + if (volume_i < 0x1000000)
> + vol->scale_samples = scale_samples_u8_small;
> + else
> + vol->scale_samples = scale_samples_u8;
> + break;
> + case AV_SAMPLE_FMT_S16:
> + if (volume_i < 0x10000)
> + vol->scale_samples = scale_samples_s16_small;
> + else
> + vol->scale_samples = scale_samples_s16;
> + break;
> + case AV_SAMPLE_FMT_S32:
> + vol->scale_samples = scale_samples_s32;
> + break;
> + case AV_SAMPLE_FMT_FLT:
> + vol->samples_align = 4;
> + break;
> + case AV_SAMPLE_FMT_DBL:
> + vol->samples_align = 8;
> + break;
> + }
> +}
> +int volume_set(VolumeContext *vol, double volume)
> +{
> + vol->volume = volume;
> + vol->volume_last = -1.0f;
> + scaler_init(vol);
> + return 0;
> +}
> +void volume_scale(VolumeContext *vol, AVFrame *frame)
> +{
> + int planar, planes, plane_size, p;
> + planar = av_sample_fmt_is_planar(frame->format);
> + planes = planar ? frame->ch_layout.nb_channels : 1;
> + plane_size = frame->nb_samples * (planar ? 1 : frame->ch_layout.nb_channels);
> + if (frame->format == AV_SAMPLE_FMT_S16 ||
> + frame->format == AV_SAMPLE_FMT_S16P) {
> + int32_t vol_isrc = (int32_t)(vol->volume_last * 256 + 0.5);
> + int32_t volume_i = (int32_t)(vol->volume * 256 + 0.5);
> + if (volume_i != vol_isrc) {
> + for (p = 0; p < planes; p++) {
> + vol->fade_samples(frame->extended_data[p],
> + frame->extended_data[p],
> + frame->nb_samples, planar ? 1 : frame->ch_layout.nb_channels,
> + volume_i, vol_isrc);
> + }
> + } else {
> + for (p = 0; p < planes; p++) {
> + vol->scale_samples(frame->extended_data[p],
> + frame->extended_data[p],
> + plane_size, volume_i);
> + }
> + }
> + vol->volume_last = vol->volume;
> + } else if (frame->format == AV_SAMPLE_FMT_FLT ||
> + frame->format == AV_SAMPLE_FMT_FLTP) {
> + for (p = 0; p < planes; p++) {
> + vol->fdsp->vector_fmul_scalar((float *)frame->extended_data[p],
> + (float *)frame->extended_data[p],
> + vol->volume, plane_size);
> + }
> + } else {
> + for (p = 0; p < planes; p++) {
> + vol->fdsp->vector_dmul_scalar((double *)frame->extended_data[p],
> + (double *)frame->extended_data[p],
> + vol->volume, plane_size);
> + }
> + }
> +}
> +int volume_init(VolumeContext *vol, enum AVSampleFormat sample_fmt)
> +{
> + vol->sample_fmt = sample_fmt;
> + vol->volume_last = -1.0f;
> + vol->volume = 1.0f;
> + vol->fdsp = avpriv_float_dsp_alloc(0);
> + if (!vol->fdsp)
> + return AVERROR(ENOMEM);
> + scaler_init(vol);
> + vol->fade_samples = fade_samples_s16_small;
> + return 0;
> +}
> +void volume_uninit(VolumeContext *vol)
> +{
> + av_freep(&vol->fdsp);
> +}
> diff --git a/volume.h b/volume.h
> new file mode 100644
> index 0000000000..141e839e90
> --- /dev/null
> +++ b/volume.h
> @@ -0,0 +1,44 @@
> +
> +/*
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +/**
> + * @file
> + * audio volume for src filter
> + */
> +#ifndef LIBAVFILTER_VOLUME_H
> +#define LIBAVFILTER_VOLUME_H
> +#include <stdint.h>
> +#include "libavutil/samplefmt.h"
> +#include "libavutil/float_dsp.h"
> +#include "libavutil/frame.h"
> +typedef struct VolumeContext {
> + AVFloatDSPContext *fdsp;
> + enum AVSampleFormat sample_fmt;
> + int samples_align;
> + double volume_last;
> + double volume;
> + void (*scale_samples)(uint8_t *dst, const uint8_t *src, int nb_samples,
> + int volume);
> + void (*fade_samples)(int16_t *dst, const int16_t *src,
> + int nb_samples, int chs, int16_t dst_volume, int16_t src_volume);
> +} VolumeContext;
> +int volume_init(VolumeContext *vol, enum AVSampleFormat sample_fmt);
> +void volume_scale(VolumeContext *vol, AVFrame *frame);
> +int volume_set(VolumeContext *vol, double volume);
> +void volume_uninit(VolumeContext *vol);
> +#endif /* LIBAVFILTER_VOLUME_H */
> --
> 2.34.1
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Thanks
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [FFmpeg-devel] [PATCH v2 0/3] lavfi: Add volume scaling, dynamic routing, and abufsrc filter
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 0/3] lavfi: Add volume scaling, dynamic routing, and abufsrc filter cenzhanquan2
` (2 preceding siblings ...)
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 3/3] avfilter/abufsrc: integrate volume and mapping modules cenzhanquan2
@ 2025-07-21 15:23 ` Nicolas George
3 siblings, 0 replies; 8+ messages in thread
From: Nicolas George @ 2025-07-21 15:23 UTC (permalink / raw)
To: FFmpeg development discussions and patches; +Cc: zhanquan cen
cenzhanquan2@gmail.com (HE12025-07-21):
> This series introduces three interdependent modules for real-time audio processing:
> 1. `volume` (PATCH 1/3): Provides sample-accurate gain control utilities with support for:
> - Linear and logarithmic volume scaling
> - Multi-format handling (s16/s32/flt/dbl, planar/non-planar) [7](@ref)
> - Runtime adjustment via player_volume/volume parameters
> 2. `mapping` (PATCH 2/3): Implements dynamic channel routing with:
> - Configurable output mapping via `map_str` option
> - Multi-output broadcasting with frame cloning to avoid data duplication
> - Runtime reconfiguration through command API
> 3. `abufsrc` (PATCH 3/3): Integrates volume/mapping as an audio source filter enabling:
> - Fade-in/out effects with sample-accurate timing
> - Asynchronous frame request via `on_event_cb` callback
> - Pause/resume with automatic fadeout on state transitions
Please come back when you (1) can produce a patch series that can be
properly applied and (2) have taken the comments I have made this
morning into account.
Regards,
--
Nicolas George
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2025-07-21 15:23 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-07-21 11:27 [FFmpeg-devel] [PATCH 1/1] avfilter/abufsrc: add audio buffer source filter with dynamic routing cenzhanquan2
2025-07-21 11:48 ` Nicolas George
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 0/3] lavfi: Add volume scaling, dynamic routing, and abufsrc filter cenzhanquan2
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 1/3] avfilter/volume: add volume scaling utilities cenzhanquan2
2025-07-21 13:48 ` Steven Liu
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 2/3] avfilter/mapping: implement dynamic routing logic cenzhanquan2
2025-07-21 12:04 ` [FFmpeg-devel] [PATCH v2 3/3] avfilter/abufsrc: integrate volume and mapping modules cenzhanquan2
2025-07-21 15:23 ` [FFmpeg-devel] [PATCH v2 0/3] lavfi: Add volume scaling, dynamic routing, and abufsrc filter Nicolas George
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git