diff --git a/Changelog b/Changelog
index c1fd66b4bd..c5fb21d198 100644
--- a/Changelog
+++ b/Changelog
@@ -23,6 +23,7 @@ version <next>:
 - ffmpeg CLI -bsf option may now be used for input as well as output
 - ffmpeg CLI options may now be used as -/opt <path>, which is equivalent
   to -opt <contents of file <path>>
+- showinfo bitstream filter
 
 version 6.1:
 - libaribcaption decoder
diff --git a/doc/bitstream_filters.texi b/doc/bitstream_filters.texi
index dc4f85bac0..d5bac105ff 100644
--- a/doc/bitstream_filters.texi
+++ b/doc/bitstream_filters.texi
@@ -887,6 +887,10 @@ For example, to set PTS equal to DTS (not recommended if B-frames are involved):
 ffmpeg -i INPUT -c:a copy -bsf:a setts=pts=DTS out.mkv
 @end example
 
+@section showinfo
+Log basic packet information. Mainly useful for testing, debugging,
+and development.
+
 @anchor{text2movsub}
 @section text2movsub
 
diff --git a/libavcodec/bitstream_filters.c b/libavcodec/bitstream_filters.c
index 1e9a676a3d..1bae113d92 100644
--- a/libavcodec/bitstream_filters.c
+++ b/libavcodec/bitstream_filters.c
@@ -58,6 +58,7 @@ extern const FFBitStreamFilter ff_pgs_frame_merge_bsf;
 extern const FFBitStreamFilter ff_prores_metadata_bsf;
 extern const FFBitStreamFilter ff_remove_extradata_bsf;
 extern const FFBitStreamFilter ff_setts_bsf;
+extern const FFBitStreamFilter ff_showinfo_bsf;
 extern const FFBitStreamFilter ff_text2movsub_bsf;
 extern const FFBitStreamFilter ff_trace_headers_bsf;
 extern const FFBitStreamFilter ff_truehd_core_bsf;
diff --git a/libavcodec/bsf/Makefile b/libavcodec/bsf/Makefile
index 7831b0f2aa..62609eb24e 100644
--- a/libavcodec/bsf/Makefile
+++ b/libavcodec/bsf/Makefile
@@ -36,6 +36,7 @@ OBJS-$(CONFIG_PGS_FRAME_MERGE_BSF)        += bsf/pgs_frame_merge.o
 OBJS-$(CONFIG_PRORES_METADATA_BSF)        += bsf/prores_metadata.o
 OBJS-$(CONFIG_REMOVE_EXTRADATA_BSF)       += bsf/remove_extradata.o
 OBJS-$(CONFIG_SETTS_BSF)                  += bsf/setts.o
+OBJS-$(CONFIG_SHOWINFO_BSF)               += bsf/showinfo.o
 OBJS-$(CONFIG_TEXT2MOVSUB_BSF)            += bsf/movsub.o
 OBJS-$(CONFIG_TRACE_HEADERS_BSF)          += bsf/trace_headers.o
 OBJS-$(CONFIG_TRUEHD_CORE_BSF)            += bsf/truehd_core.o
diff --git a/libavcodec/bsf/showinfo.c b/libavcodec/bsf/showinfo.c
new file mode 100644
index 0000000000..dc1ac8ceb6
--- /dev/null
+++ b/libavcodec/bsf/showinfo.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2024 Anton Khirnov <anton@khirnov.net>
+ *
+ * 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 <stdint.h>
+
+#include "bsf.h"
+#include "bsf_internal.h"
+
+#include "libavutil/log.h"
+#include "libavutil/timestamp.h"
+
+typedef struct ShowinfoContext {
+    uint64_t nb_packets;
+} ShowinfoContext;
+
+static int showinfo_filter(AVBSFContext *ctx, AVPacket *pkt)
+{
+    ShowinfoContext *priv = ctx->priv_data;
+
+    while (1) {
+        int ret;
+
+        ret = ff_bsf_get_packet_ref(ctx, pkt);
+        if (ret < 0)
+            return ret;
+
+        av_log(ctx, AV_LOG_INFO,
+               "n:%7"PRIu64" "
+               "size:%7d "
+               "pts:%s pt:%s "
+               "dts:%s dt:%s "
+               "ds:%"PRId64" d:%s "
+               "\n",
+               priv->nb_packets, pkt->size,
+               av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ctx->time_base_in),
+               av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ctx->time_base_in),
+               pkt->duration, av_ts2timestr(pkt->duration, &ctx->time_base_in));
+
+        priv->nb_packets++;
+
+        return 0;
+    }
+}
+
+const FFBitStreamFilter ff_showinfo_bsf = {
+    .p.name         = "showinfo",
+    .filter         = showinfo_filter,
+    .priv_data_size = sizeof(ShowinfoContext),
+};
