Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [FFmpeg-devel] [PATCH] avfilter: add PCM dumping between filters for audio debugging
@ 2025-04-23  6:02 一只大 肥猫
  2025-04-25 12:26 ` Nicolas George
  2025-04-25 19:00 ` Andreas Rheinhardt
  0 siblings, 2 replies; 3+ messages in thread
From: 一只大 肥猫 @ 2025-04-23  6:02 UTC (permalink / raw)
  To: ffmpeg-devel

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@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@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

_______________________________________________
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] 3+ messages in thread

* Re: [FFmpeg-devel] [PATCH] avfilter: add PCM dumping between filters for audio debugging
  2025-04-23  6:02 [FFmpeg-devel] [PATCH] avfilter: add PCM dumping between filters for audio debugging 一只大 肥猫
@ 2025-04-25 12:26 ` Nicolas George
  2025-04-25 19:00 ` Andreas Rheinhardt
  1 sibling, 0 replies; 3+ messages in thread
From: Nicolas George @ 2025-04-25 12:26 UTC (permalink / raw)
  To: FFmpeg development discussions and patches

一只大 肥猫 (HE12025-04-23):
> This patch introduces PCM dumping support between AVFilter links, intended for audio debugging.

Thanks for the patch. See a few remarks below.

> 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

I do not like the fact that it relies on a hard-coded output path. I
think it would be better to take the output file name from the command
argument, even if it means splitting it.

> 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);


You need to add that into the documentation. And please add an example
for the command-line tool.

> 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@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@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)

You never call it this function with stop != 1.

> +{
> +    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]);

0 is a valid fd, -1 is usually used for invalid.

> +        }

> +        av_free(link->dump_pcm_fds);
> +        link->dump_pcm_fds    = NULL;

av_freep()

> +        link->nb_dump_pcm_fds = 0;
> +    }
> +

> +    if (stop)
> +        link->dump_pcm = 0;

Seems redundant with dump_pcm_fds being NULL or not. The fact that stop
is always 1 makes it confusing.

> +}
> +
> +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));

I think turning planar into packet when dumping would be much more
convenient and simpler to implement.

> +    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);

I would be more comfortable with O_EXCL there too.

> +        if (fd < 0) {
> +            link_uninit_dump_pcm(link, 1);
> +            return AVERROR(errno);
> +        }
> +        link->dump_pcm_fds[i] = fd;

Logging the file name would be a good idea.

> +    }
> +
> +    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;

Why not init it immediately?

> +            } else
> +                link_uninit_dump_pcm(link, 1);

I do not remember what we decided, but mixing braces and no braces is
not it. Same below.

> +
> +            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);

This is not how extended_data works.

You need to loop in case of short writes. Or maybe, simpler: use stdio.
Same below.

> +
> +            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.

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] 3+ messages in thread

* Re: [FFmpeg-devel] [PATCH] avfilter: add PCM dumping between filters for audio debugging
  2025-04-23  6:02 [FFmpeg-devel] [PATCH] avfilter: add PCM dumping between filters for audio debugging 一只大 肥猫
  2025-04-25 12:26 ` Nicolas George
@ 2025-04-25 19:00 ` Andreas Rheinhardt
  1 sibling, 0 replies; 3+ messages in thread
From: Andreas Rheinhardt @ 2025-04-25 19:00 UTC (permalink / raw)
  To: ffmpeg-devel

一只大 肥猫:
> 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@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@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

_______________________________________________
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] 3+ messages in thread

end of thread, other threads:[~2025-04-25 19:00 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-04-23  6:02 [FFmpeg-devel] [PATCH] avfilter: add PCM dumping between filters for audio debugging 一只大 肥猫
2025-04-25 12:26 ` Nicolas George
2025-04-25 19:00 ` Andreas Rheinhardt

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