[FFmpeg-devel] 回复: [PATCH] avfilter: add PCM dumping between filters for audio debugging

Andreas Rheinhardt andreas.rheinhardt at outlook.com
Wed May 28 14:27:59 EEST 2025


Fang Yibo:
> Re: [FFmpeg-devel,v2,0/2] avfilter: add PCM dumping between filters for audio debugging
> 
> From: Yibo Fang <blueybf777 at 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 at 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 <blueybf777 at 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 <blueybf777 at 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 <blueybf777 at 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 <blueybf777 at 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-bounces at ffmpeg.org> on behalf of Andreas Rheinhardt <andreas.rheinhardt at outlook.com>
> Sent: 26 April 2025 3:00
> To: ffmpeg-devel at ffmpeg.org <ffmpeg-devel at 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 <blueybf777 at 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 <blueybf777 at 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



More information about the ffmpeg-devel mailing list