PR #20715 opened by yibofang URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20715 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20715.patch
This patch adds the ability to dump raw PCM audio data between AVFilter links. It introduces a configure-time option `--dumpdir=PATH` to control the output directory of dump files (default: /tmp/). This feature is helpful for debugging filter behavior and verifying audio processing. Two filter commands are added: - dump_raw_start <dst_filter_name> - dump_raw_stop <dst_filter_name> The PCM files are written in the format: srcname-dstname-<channel>.pcm. Supports both packed and planar formats. File descriptors are automatically managed. Works only on audio links. Example usage: avfilter_process_command(filter, "dump_raw_start", "volume", NULL, 0, 0); Signed-off-by: YiboFang <[email protected]> >From 5fe14bede287bd3a056a953b27d41719c71b86a0 Mon Sep 17 00:00:00 2001 From: YiboFang <[email protected]> Date: Wed, 27 Aug 2025 09:37:24 +0800 Subject: [PATCH] avfilter: add raw PCM dump between AVFilter links This patch adds the ability to dump raw PCM audio data between AVFilter links. It introduces a configure-time option `--dumpdir=PATH` to control the output directory of dump files (default: /tmp/). This feature is helpful for debugging filter behavior and verifying audio processing. Two filter commands are added: - dump_raw_start <dst_filter_name> - dump_raw_stop <dst_filter_name> The PCM files are written in the format: srcname-dstname-<channel>.pcm. Supports both packed and planar formats. File descriptors are automatically managed. Works only on audio links. Example usage: avfilter_process_command(filter, "dump_raw_start", "volume", NULL, 0, 0); Signed-off-by: YiboFang <[email protected]> --- configure | 7 +++ doc/filters.texi | 36 ++++++++++++++ libavfilter/avfilter.c | 110 +++++++++++++++++++++++++++++++++++++++++ libavfilter/avfilter.h | 4 ++ 4 files changed, 157 insertions(+) diff --git a/configure b/configure index 7828381b5d..79ca73b303 100755 --- a/configure +++ b/configure @@ -531,6 +531,7 @@ Developer options (useful when working on FFmpeg itself): --disable-ptx-compression don't compress CUDA PTX code even when possible --disable-resource-compression don't compress resources even when possible --disable-version-tracking don't include the git/release version in the build + --dumpdir=PATH location of pcm dump files to save. NOTE: Object files are built at the place where configure is launched. EOF @@ -2738,6 +2739,7 @@ PATHS_LIST=" prefix shlibdir install_name_dir + dumpdir " CMDLINE_SET=" @@ -4223,6 +4225,9 @@ incdir_default='${prefix}/include' libdir_default='${prefix}/lib' mandir_default='${prefix}/share/man' +# runtime path +dumpdir_default='/tmp/' + # toolchain ar_default="ar" cc_default="gcc" @@ -8327,6 +8332,7 @@ DOCDIR=\$(DESTDIR)$docdir MANDIR=\$(DESTDIR)$mandir PKGCONFIGDIR=\$(DESTDIR)$pkgconfigdir INSTALL_NAME_DIR=$install_name_dir +DUMPDIR=$dumpdir SRC_PATH=$source_path SRC_LINK=$source_link ifndef MAIN_MAKEFILE @@ -8479,6 +8485,7 @@ cat > $TMPH <<EOF #define CONFIG_THIS_YEAR 2025 #define FFMPEG_DATADIR "$(eval c_escape $datadir)" #define AVCONV_DATADIR "$(eval c_escape $datadir)" +#define FFMPEG_DUMPDIR "$(eval c_escape $dumpdir)" #define CC_IDENT "$(c_escape ${cc_ident:-Unknown compiler})" #define OS_NAME $target_os #define EXTERN_PREFIX "${extern_prefix}" diff --git a/doc/filters.texi b/doc/filters.texi index 259162f6b7..f84f6fc10b 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -30915,6 +30915,42 @@ Draw a graph using input audio metadata. See @ref{drawgraph} +@section adumppcm + +Dump raw PCM audio data for debugging between filters. + +This filter allows writing raw audio data to disk from any audio link +in the filtergraph for inspection. It supports planar and packed formats, +automatically interleaving planar data for writing. + +@subsection Syntax + +@example +dump_raw_start=file=/your/dump/directory/ +@end example + +@subsection Usage + +To enable dumping, use the @code{dump_raw_start} command: + +@example +ffmpeg -i input.wav -af "volume,asendcmd='0.0 dump_raw_start file=/tmp/pcm/'" -f null - +@end example + +To stop dumping: + +@example +asendcmd='5.0 dump_raw_stop' +@end example + +@subsection Options + +@table @option +@item file +Specify a directory path (not a filename) where PCM data will be dumped. +The dumped file is named @code{<src>-<dst>.pcm} and will be overwritten if it already exists. +@end table + @section agraphmonitor See @ref{graphmonitor}. diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c index 169c2baa42..5eaff93b87 100644 --- a/libavfilter/avfilter.c +++ b/libavfilter/avfilter.c @@ -19,6 +19,9 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <fcntl.h> +#include <unistd.h> + #include "libavutil/avassert.h" #include "libavutil/avstring.h" #include "libavutil/bprint.h" @@ -195,6 +198,66 @@ int avfilter_link(AVFilterContext *src, unsigned srcpad, return 0; } +static av_cold void link_uninit_dump_pcm(AVFilterLink *link, int stop) +{ + if (link->dump_pcm_fds) { + int i; + for (i = 0; i < link->nb_dump_pcm_fds; i++) { + if (link->dump_pcm_fds[i]) + close(link->dump_pcm_fds[i]); + } + av_free(link->dump_pcm_fds); + link->dump_pcm_fds = NULL; + link->nb_dump_pcm_fds = 0; + } + + if (stop) + link->dump_pcm = 0; +} + +static av_cold int link_init_dump_pcm(AVFilterLink *link) +{ + char path[4096]; + int fd, i; + + link->nb_dump_pcm_fds = av_sample_fmt_is_planar(link->format)? link->ch_layout.nb_channels : 1; + link->dump_pcm_fds = av_malloc_array(link->nb_dump_pcm_fds, sizeof(int)); + if (!link->dump_pcm_fds) + return AVERROR(ENOMEM); + + for (i = 0; i < link->nb_dump_pcm_fds; i++) { + snprintf(path, sizeof(path), FFMPEG_DUMPDIR"%.16s-%.8s-%d.pcm", link->src->name, link->dst->name, i); + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + link_uninit_dump_pcm(link, 1); + return AVERROR(errno); + } + link->dump_pcm_fds[i] = fd; + } + + return 0; +} + +static int filter_set_dump_pcm(AVFilterContext *filter, const char *target, int set) +{ + int i; + + for (i = 0; i < filter->nb_outputs; i++) { + AVFilterLink *link = filter->outputs[i]; + if (!target || !strcmp(link->dst->name, target)) { + if (set) { + link->dump_pcm = 1; + } else + link_uninit_dump_pcm(link, 1); + + if (target) + return 0; + } + } + + return target ? AVERROR(EINVAL) : 0; +} + static void link_free(AVFilterLink **link) { FilterLinkInternal *li; @@ -208,6 +271,8 @@ static void link_free(AVFilterLink **link) av_channel_layout_uninit(&(*link)->ch_layout); av_frame_side_data_free(&(*link)->side_data, &(*link)->nb_side_data); + link_uninit_dump_pcm(*link, 1); + av_buffer_unref(&li->l.hw_frames_ctx); av_freep(link); @@ -620,6 +685,10 @@ int avfilter_process_command(AVFilterContext *filter, const char *cmd, const cha if (res == local_res) av_log(filter, AV_LOG_INFO, "%s", res); return 0; + }else if(!strcmp(cmd, "dump_raw_start")) { + return filter_set_dump_pcm(filter, arg, 1); + }else if(!strcmp(cmd, "dump_raw_stop")) { + return filter_set_dump_pcm(filter, arg, 0); }else if(!strcmp(cmd, "enable")) { return set_enable_expr(fffilterctx(filter), arg); }else if (fffilter(filter->filter)->process_command) { @@ -1064,6 +1133,41 @@ fail: return ret; } +static int link_dump_frame(AVFilterLink *link, AVFrame *frame) +{ + int samples_size, ret; + + if (!link->dump_pcm_fds) { + ret = link_init_dump_pcm(link); + if (ret < 0) + return ret; + } + + samples_size = av_get_bytes_per_sample(frame->format) * frame->nb_samples; + if (av_sample_fmt_is_planar(frame->format)) { + int i; + for (i = 0; i < link->nb_dump_pcm_fds && i < frame->ch_layout.nb_channels; i++) { + if (i < AV_NUM_DATA_POINTERS) { + ret = write(link->dump_pcm_fds[i], frame->data[i], samples_size); + } else + ret = write(link->dump_pcm_fds[i], frame->extended_data[i - AV_NUM_DATA_POINTERS], samples_size); + + if (ret < 0) + goto err; + } + } else { + ret = write(link->dump_pcm_fds[0], frame->data[0], samples_size * frame->ch_layout.nb_channels); + if (ret < 0) + goto err; + + } + + return 0; +err: + link_uninit_dump_pcm(link, 1); + return AVERROR(errno); +} + int ff_filter_frame(AVFilterLink *link, AVFrame *frame) { FilterLinkInternal * const li = ff_link_internal(link); @@ -1104,6 +1208,12 @@ int ff_filter_frame(AVFilterLink *link, AVFrame *frame) link->time_base); } + if (link->dump_pcm && link->type == AVMEDIA_TYPE_AUDIO) { + ret = link_dump_frame(link, frame); + if (ret < 0) + av_log(link->dst, AV_LOG_ERROR, "Dump pcm files failed with %d\n", ret); + } + li->frame_blocked_in = li->frame_wanted_out = 0; li->l.frame_count_in++; li->l.sample_count_in += frame->nb_samples; diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h index 02b58c42c2..5d86874b53 100644 --- a/libavfilter/avfilter.h +++ b/libavfilter/avfilter.h @@ -421,6 +421,10 @@ struct AVFilterLink { int sample_rate; ///< samples per second AVChannelLayout ch_layout; ///< channel layout of current buffer (see libavutil/channel_layout.h) + int dump_pcm; ///< flag to dump pcm + int *dump_pcm_fds; ///< dump files + unsigned nb_dump_pcm_fds; ///< number of dump file + /** * Define the time base used by the PTS of the frames/samples * which will pass through this link. -- 2.49.1 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
