Fang Yibo: > Re: [FFmpeg-devel,v2,0/2] avfilter: add PCM dumping between filters for audio > debugging > > From: Yibo Fang <blueybf...@outlook.com> > Subject: [FFmpeg-devel,v2,0/2] avfilter: add PCM dumping between filters for > audio debugging > Date: Wed, 23 Apr 2025 10:57:00 +0800 > Message-Id: <cover.0.2@ffmpeg> > > This series adds a debugging feature that lets developers dump raw > PCM audio between filters. The data are written to files in a user- > selected directory via two new commands: > > dump_raw_start file=/path/to/dir/ > dump_raw_stop > > Planar audio is automatically interleaved; only audio links are > affected. Patch 1/2 implements the feature, patch 2/2 documents it. > > Regards, > Yibo Fang > > --- > > From 2df56e74273d477ae27003e0981220749a6b58cf Mon Sep 17 00:00:00 2001 > From: Yibo Fang <blueybf...@outlook.com> > Date: Wed, 23 Apr 2025 10:58:55 +0800 > Subject: [PATCH 1/2] avfilter: add PCM dumping between filters for audio > debugging > > This patch introduces PCM dumping support between AVFilter links, > intended for audio debugging and filter-graph inspection. > > Two filter commands are added: > > dump_raw_start file=/path/to/output/dir/ > dump_raw_stop > > When dumping is enabled, audio leaving the selected filter output is > written to an automatically named file: > > <src_filter>-<dst_filter>.pcm > > Planar formats are converted to packed. File descriptors are opened > on demand and closed automatically. Works only on audio links. > > Example (C API): > avfilter_process_command(filter, "dump_raw_start", > "file=/tmp/", NULL, 0, 0); > > Signed-off-by: Yibo Fang <blueybf...@outlook.com> > --- > libavfilter/avfilter.c | 144 +++++++++++++++++++++++++++++++++++++++++ > libavfilter/avfilter.h | 5 ++ > 2 files changed, 149 insertions(+) > > diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c > index 64c1075c40..b7034569db 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" > @@ > return 0; > } > > +/* ---------- PCM dump helpers ---------- */ > + > +static av_cold void link_uninit_dump_pcm(AVFilterLink *link) > +{ > + if (link->dump_pcm_fds) { > + for (unsigned i = 0; i < link->nb_dump_pcm_fds; i++) > + if (link->dump_pcm_fds[i] >= 0) > + close(link->dump_pcm_fds[i]); > + av_freep(&link->dump_pcm_fds); > + link->nb_dump_pcm_fds = 0; > + } > + av_freep(&link->dump_pcm_path); > + link->dump_pcm = 0; > +} > + > +static av_cold int link_init_dump_pcm(AVFilterLink *link) > +{ > + /* build full path "<dir>/<src>-<dst>.pcm" */ > + const char *dir = link->dump_pcm_path; > + const char *sep = dir[strlen(dir) - 1] == '/' ? "" : "/"; > + char path[4096]; > + snprintf(path, sizeof(path), "%s%s%.16s-%.8s.pcm", > + dir, sep, link->src->name, link->dst->name); > + > + link->nb_dump_pcm_fds = 1; > + link->dump_pcm_fds = av_malloc(sizeof(int)); > + if (!link->dump_pcm_fds) > + return AVERROR(ENOMEM); > + > + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0644); > + if (fd < 0) { > + link_uninit_dump_pcm(link); > + return AVERROR(errno); > + } > + link->dump_pcm_fds[0] = fd; > + av_log(link->dst, AV_LOG_INFO, "PCM dump file: %s\n", path); > + return 0; > +} > + > +static int filter_set_dump_pcm(AVFilterContext *f, const char *arg, int set) > +{ > + const char *prefix = "file="; > + if (!arg || av_strncasecmp(arg, prefix, strlen(prefix))) { > + av_log(f, AV_LOG_ERROR, > + "dump_raw_* expects argument file=/dir/\n"); > + return AVERROR(EINVAL); > + } > + const char *dir = arg + strlen(prefix); > + > + for (int i = 0; i < f->nb_outputs; i++) { > + AVFilterLink *l = f->outputs[i]; > + if (l->type != AVMEDIA_TYPE_AUDIO) > + continue; > + if (set) { > + av_freep(&l->dump_pcm_path); > + l->dump_pcm_path = av_strdup(dir); > + if (!l->dump_pcm_path) > + return AVERROR(ENOMEM); > + l->dump_pcm = 1; > + } else > + link_uninit_dump_pcm(l); > + } > + return 0; > +} > + > +/* write one frame */ > +static int link_dump_frame(AVFilterLink *link, AVFrame *frame) > +{ > + int ret; > + if (!link->dump_pcm_fds && > + (ret = link_init_dump_pcm(link)) < 0) > + return ret; > + > + const int ch = frame->ch_layout.nb_channels; > + const int bps = av_get_bytes_per_sample(frame->format); > + const int ns = frame->nb_samples; > + const int totlen = ch * ns * bps; > + > + /* interleave planar to packed if necessary */ > + if (av_sample_fmt_is_planar(frame->format)) { > + uint8_t *buf = av_malloc(totlen); > + if (!buf) > + return AVERROR(ENOMEM); > + for (int s = 0; s < ns; s++) > + for (int c = 0; c < ch; c++) > + memcpy(buf + (s*ch + c)*bps, > + (uint8_t*)frame->extended_data[c] + s*bps, > + bps); > + ret = write(link->dump_pcm_fds[0], buf, totlen); > + av_free(buf); > + } else > + ret = write(link->dump_pcm_fds[0], frame->data[0], totlen); > + > + if (ret < 0) { > + av_log(link->dst, AV_LOG_ERROR, "PCM dump write failed: %s\n", > + av_err2str(ret)); > + link_uninit_dump_pcm(link); > + return ret; > + } > + return 0; > +} > + > static void link_free(AVFilterLink **link) > { > @@ > > int avfilter_process_command(AVFilterContext *filter, const char *cmd, > const char *arg, char *res, int res_len, int > flags) > { > @@ > - if (res == local_res) > - av_log(filter, AV_LOG_INFO, "%s", res); > - return 0; > + 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); > @@ > /* ... existing code ... */ > > + 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, > + "PCM dump failed with %s\n", av_err2str(ret)); > + } > + > li->frame_blocked_in = li->frame_wanted_out = 0; > @@ > diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h > index a89d3cf658..0d4c91bb6b 100644 > --- a/libavfilter/avfilter.h > +++ b/libavfilter/avfilter.h > @@ > AVChannelLayout ch_layout; ///< channel layout of current buffer > > + int dump_pcm; ///< enable dumping on this link > + int *dump_pcm_fds; ///< one FD per link (currently 1) > + unsigned nb_dump_pcm_fds; ///< number of FDs > + char *dump_pcm_path; ///< user-supplied directory > + > /** > * Define the time base used by the PTS of the frames/samples > * which will pass through this link. > -- > 2.34.1 > > --- > > From 8cf7bfa52a10be87e0f17b9783159b0e83f960f7 Mon Sep 17 00:00:00 2001 > From: Yibo Fang <blueybf...@outlook.com> > Date: Wed, 23 Apr 2025 12:38:00 +0800 > Subject: [PATCH 2/2] doc/filters: document dump_raw_start and dump_raw_stop > commands > > Signed-off-by: Yibo Fang <blueybf...@outlook.com> > --- > doc/filters.texi | 45 +++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 45 insertions(+) > > diff --git a/doc/filters.texi b/doc/filters.texi > index 45b3e03e5e..6a2d23df3f 100644 > --- a/doc/filters.texi > +++ b/doc/filters.texi > @@ > @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 > -- > 2.34.1 > > > > ________________________________ > From: ffmpeg-devel <ffmpeg-devel-boun...@ffmpeg.org> on behalf of Andreas > Rheinhardt <andreas.rheinha...@outlook.com> > Sent: 26 April 2025 3:00 > To: ffmpeg-devel@ffmpeg.org <ffmpeg-devel@ffmpeg.org> > Subject: Re: [FFmpeg-devel] [PATCH] avfilter: add PCM dumping between filters > for audio debugging > > 一只大 肥猫: >> This patch introduces PCM dumping support between AVFilter links, intended >> for audio debugging. >> >> It adds a configure-time option `--dumpdir=PATH` to specify the output >> directory for raw PCM data (default: /tmp/). >> >> Two commands are exposed to control dumping: >> - dump_raw_start <dst_filter_name> >> - dump_raw_stop <dst_filter_name> >> >> Dump files are written as: srcname-dstname-channel.pcm >> >> Supports packed and planar formats. File descriptors are managed >> automatically and work only on audio links. >> >> Example usage: >> avfilter_process_command(filter, "dump_raw_start", "volume", NULL, 0, 0); >> >> This feature helps developers debug filter behavior by inspecting >> intermediate audio data. >> --- >> From 6a31c85afd2800c09076c1e2b7c734c7b719f73d Mon Sep 17 00:00:00 2001 >> From: Yibo Fang <blueybf...@outlook.com> >> Date: Wed, 23 Apr 2025 09:17:51 +0800 >> Subject: [PATCH 1/1] avfilter: add PCM dumping between filters for audio >> debugging >> >> 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: Yibo Fang <blueybf...@outlook.com> >> --- >> configure | 7 +++ >> libavfilter/avfilter.c | 110 +++++++++++++++++++++++++++++++++++++++++ >> libavfilter/avfilter.h | 4 ++ >> 3 files changed, 121 insertions(+) >> >> diff --git a/configure b/configure >> index c94b8eac43..381633749d 100755 >> --- a/configure >> +++ b/configure >> @@ -524,6 +524,7 @@ Developer options (useful when working on FFmpeg itself): >> --disable-large-tests disable tests that use a large amount of memory >> --disable-ptx-compression don't compress CUDA PTX code 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 >> @@ -2690,6 +2691,7 @@ PATHS_LIST=" >> prefix >> shlibdir >> install_name_dir >> + dumpdir >> " >> >> CMDLINE_SET=" >> @@ -4123,6 +4125,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" >> @@ -8118,6 +8123,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 >> @@ -8267,6 +8273,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/libavfilter/avfilter.c b/libavfilter/avfilter.c >> index 64c1075c40..9fc6308544 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); >> @@ -617,6 +682,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) { >> @@ -1050,6 +1119,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); >> @@ -1087,6 +1191,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 a89d3cf658..6d04b9da77 100644 >> --- a/libavfilter/avfilter.h >> +++ b/libavfilter/avfilter.h >> @@ -404,6 +404,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.34.1 >> > > Can't you use the asplit filter to duplicate the audio? > > - Andreas
Ping for that question. - Andreas _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".