Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,17 @@ jobs:
cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_COMPILER=clang \
-DCMAKE_C_FLAGS="-fsanitize=thread -g"
cmake --build build -j"$(nproc)"
- name: Decode every stream on 4 threads
- name: Decode every stream on 2 and 4 threads
env:
TSAN_OPTIONS: halt_on_error=1
run: |
for s in tests/streams/*.svac3; do
build/svac3dec -t 2 "$s" /dev/null
build/svac3dec -t 4 "$s" /dev/null
done
build/test_api tests/streams/cv608_synth_1080p_cbr2m.svac3
build/test_api -lost tests/streams/cv608_patch4_1080p.svac3
build/test_api -lost tests/streams/cv608_smartp_1080p.svac3

fuzz:
runs-on: ubuntu-24.04
Expand All @@ -64,6 +67,11 @@ jobs:
source/svac3/svac3_inter.c source/svac3/svac3_dec.c source/svac3/svac3_dsp.c \
source/svac3/svac3_pool.c -lpthread \
-o fuzz_patch
clang $F -DFUZZ_THREADS=3 tests/fuzz/fuzz_patch.c source/svac3/svac3_hls.c \
source/svac3/svac3_aec.c source/svac3/svac3_patch.c source/svac3/svac3_recon.c \
source/svac3/svac3_loopfilter.c source/svac3/svac3_inter.c source/svac3/svac3_dec.c \
source/svac3/svac3_dsp.c source/svac3/svac3_pool.c -lpthread \
-o fuzz_frames
- name: Fuzz the header parser for 60 s
run: |
mkdir -p corpus
Expand All @@ -76,6 +84,11 @@ jobs:
head -c 60000 tests/streams/cv608_smartp_1080p.svac3 > corpus_patch/library.svac3
head -c 60000 tests/streams/cv608_patch4_1080p.svac3 > corpus_patch/patches.svac3
./fuzz_patch -max_total_time=60 -max_len=65536 corpus_patch
- name: Fuzz the picture threads for 60 s
run: |
mkdir -p corpus_frames
cp corpus_patch/*.svac3 corpus_frames/
./fuzz_frames -max_total_time=60 -max_len=65536 corpus_frames

ffmpeg:
runs-on: ubuntu-24.04
Expand Down
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ add_test(NAME test_api
COMMAND test_api ${CMAKE_SOURCE_DIR}/tests/streams/cv608_synth_1080p_cbr2m.svac3)
add_test(NAME test_api_lost_patch
COMMAND test_api -lost ${CMAKE_SOURCE_DIR}/tests/streams/cv608_patch4_1080p.svac3)
add_test(NAME test_api_lost_library
COMMAND test_api -lost ${CMAKE_SOURCE_DIR}/tests/streams/cv608_smartp_1080p.svac3)
add_executable(test_dsp tests/test_dsp.c)
target_include_directories(test_dsp PRIVATE source/svac3)
target_link_libraries(test_dsp svac3d)
Expand Down
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,25 @@ decoded pictures. On the 1080p CBR corpus stream, one core decodes about
200 pictures a second on a Cortex-X925 and about 110 on a Core i9-13900;
at high bit rates the entropy decoder, which is serial, sets the pace.

`svac3d_set_threads()` (`svac3dec -t N`, FFmpeg's `-threads`) decodes on
several threads: one parses while another reconstructs, and the loop
filters run in bands on all of them. Pictures and their order are the same
whatever the number; on 2 threads a picture takes about 0.65 of the time.
`svac3d_set_threads()` (`svac3dec -t N`, FFmpeg's `-threads`) decodes N
pictures at a time. A P picture waits only for the rows of its reference
that its motion reaches, as the reference's thread finishes them, and
each picture has its reconstruction and loop filters follow its parser on
two threads of its own, so up to three threads per picture are busy.
Pictures come out in the same order and with the same bits whatever the
number, up to N pictures later than with one thread; `svac3d_flush()`
returns the last of them at the end of the stream.

On a Core Ultra 5 245K (six P-cores):

| stream | 1 thread | 2 threads | 4 threads |
|---|---|---|---|
| 1080p CBR, 90 pictures | 0.45 s | 0.13 s | 0.13 s |
| 2560x1440, 295 pictures | 3.25 s | 0.89 s | 0.79 s |
| 1080p QP 22, 120 pictures, an I picture in six | 1.58 s | 0.76 s | 0.68 s |

Threads cost 10-30% more CPU time than one thread does. Streams with many I
pictures gain least: nothing overlaps an I picture's entropy decoding.

## API

Expand All @@ -65,10 +80,14 @@ whatever the number; on 2 threads a picture takes about 0.65 of the time.
svac3d_t *d = svac3d_open();
svac3d_set_threads(d, 4); /* optional */
svac3d_decode(d, buf, len, on_picture, opaque); /* Annex-B, any whole NAL units */
svac3d_flush(d, on_picture, opaque); /* at the end, with threads */
svac3d_close(d);
```

`on_picture` receives each decoded picture, cropped to the display size.
`svac3d_decode_tag()` gives each picture a value from the buffer its first
patch came in, which is how the FFmpeg wrapper keeps packet timestamps on
pictures that come out later.

## FFmpeg

Expand Down
79 changes: 62 additions & 17 deletions ffmpeg/0001-avcodec-add-SVAC-3.0-decoding-via-libsvac3d.patch
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From 97083a1788ba49dcda36f32b05c9bfeb922a5ca5 Mon Sep 17 00:00:00 2001
From: Dmitry Ilyin <d.ilyin@openipc.org>
Date: Wed, 28 Oct 2026 23:38:39 +0300
Subject: [PATCH] avcodec: add SVAC 3.0 decoding via libsvac3d
Expand All @@ -13,21 +13,21 @@ tool set of the GB/T SVAC 3.0 draft.
libavcodec/allcodecs.c | 1 +
libavcodec/codec_desc.c | 7 ++
libavcodec/codec_id.h | 1 +
libavcodec/libsvac3d.c | 206 ++++++++++++++++++++++++++++++++++++++
libavcodec/libsvac3d.c | 251 ++++++++++++++++++++++++++++++++++++++
libavcodec/parsers.c | 1 +
libavcodec/svac3.h | 56 +++++++++++
libavcodec/svac3_parser.c | 121 ++++++++++++++++++++++
libavcodec/svac3.h | 56 +++++++++
libavcodec/svac3_parser.c | 121 ++++++++++++++++++
libavformat/Makefile | 1 +
libavformat/allformats.c | 1 +
libavformat/svac3dec.c | 64 ++++++++++++
12 files changed, 466 insertions(+)
libavformat/svac3dec.c | 64 ++++++++++
12 files changed, 511 insertions(+)
create mode 100644 libavcodec/libsvac3d.c
create mode 100644 libavcodec/svac3.h
create mode 100644 libavcodec/svac3_parser.c
create mode 100644 libavformat/svac3dec.c

diff --git a/configure b/configure
index 374bd5c..6fd5725 100755
index 374bd5c..61c7b77 100755
--- a/configure
+++ b/configure
@@ -290,6 +290,7 @@ External library support:
Expand Down Expand Up @@ -59,7 +59,7 @@ index 374bd5c..6fd5725 100755
enabled libtwolame && require libtwolame twolame.h twolame_init -ltwolame &&
{ check_lib libtwolame twolame.h twolame_encode_buffer_float32_interleaved -ltwolame ||
die "ERROR: libtwolame must be installed and version must be >= 0.3.10"; }
+enabled libsvac3d && require_pkg_config libsvac3d "libsvac3d >= 0.2.0" svac3d.h svac3d_set_threads
+enabled libsvac3d && require_pkg_config libsvac3d "libsvac3d >= 0.3.0" svac3d.h svac3d_flush
enabled libuavs3d && require_pkg_config libuavs3d "uavs3d >= 1.1.41" uavs3d.h uavs3d_decode
enabled libv4l2 && require_pkg_config libv4l2 libv4l2 libv4l2.h v4l2_ioctl
enabled libvidstab && require_pkg_config libvidstab "vidstab >= 0.98" vid.stab/libvidstab.h vsMotionDetectInit
Expand Down Expand Up @@ -127,10 +127,10 @@ index 668a068..a089876 100644
AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs
diff --git a/libavcodec/libsvac3d.c b/libavcodec/libsvac3d.c
new file mode 100644
index 0000000..e69d957
index 0000000..94f44cb
--- /dev/null
+++ b/libavcodec/libsvac3d.c
@@ -0,0 +1,206 @@
@@ -0,0 +1,251 @@
+/*
+ * This file is part of FFmpeg.
+ *
Expand Down Expand Up @@ -163,11 +163,25 @@ index 0000000..e69d957
+#include "codec_internal.h"
+#include "decode.h"
+
+/* The timing of a packet, found again by the tag its pictures carry. A
+ * picture is out at most a few pictures after its packet, so one this many
+ * packets back (after a run of packets with no picture, or with pictures
+ * in error) is not needed any more. */
+#define MAX_PROPS 256
+
+typedef struct PacketProps {
+ int64_t tag;
+ int64_t pts, dts, duration;
+} PacketProps;
+
+typedef struct LibSVAC3DContext {
+ svac3d_t *dec;
+ AVCodecContext *avctx;
+ AVPacket *pkt;
+ AVFifo *frames; ///< decoded pictures not yet returned, AVFrame *
+ AVFifo *props; ///< PacketProps of the packets pictures may still come from
+ int64_t tag; ///< of the last packet decoded
+ int drained; ///< the pictures still in flight at EOF are out
+ int err; ///< error raised inside the output callback
+ int pending_err; ///< error to return once the queued pictures are out
+} LibSVAC3DContext;
Expand Down Expand Up @@ -221,6 +235,18 @@ index 0000000..e69d957
+ av_image_copy_plane(frame->data[i], frame->linesize[i],
+ pic->data[i], pic->linesize[i], w, h);
+ }
+ /* with threads a picture comes out some packets after its own: its
+ * timing is that of the packet its tag names, not the last one */
+ for (PacketProps p; av_fifo_peek(s->props, &p, 1, 0) >= 0; av_fifo_drain2(s->props, 1)) {
+ if (p.tag < pic->tag)
+ continue;
+ if (p.tag == pic->tag) {
+ frame->pts = p.pts;
+ frame->pkt_dts = p.dts;
+ frame->duration = p.duration;
+ }
+ break;
+ }
+ frame->pict_type = pic->pict_type == 1 ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_P;
+ if (pic->pict_type == 1)
+ frame->flags |= AV_FRAME_FLAG_KEY;
Expand Down Expand Up @@ -248,8 +274,9 @@ index 0000000..e69d957
+ s->avctx = avctx;
+ s->pkt = av_packet_alloc();
+ s->frames = av_fifo_alloc2(2, sizeof(AVFrame *), AV_FIFO_FLAG_AUTO_GROW);
+ s->props = av_fifo_alloc2(8, sizeof(PacketProps), AV_FIFO_FLAG_AUTO_GROW);
+ s->dec = svac3d_open();
+ if (!s->pkt || !s->frames || !s->dec)
+ if (!s->pkt || !s->frames || !s->props || !s->dec)
+ return AVERROR(ENOMEM);
+ avctx->pix_fmt = AV_PIX_FMT_YUV420P;
+ /* libsvac3d runs its own threads: 0 asks for one per CPU */
Expand All @@ -273,6 +300,7 @@ index 0000000..e69d957
+
+ drop_frames(s);
+ av_fifo_freep2(&s->frames);
+ av_fifo_freep2(&s->props);
+ av_packet_free(&s->pkt);
+ svac3d_close(s->dec);
+ s->dec = NULL;
Expand All @@ -284,7 +312,9 @@ index 0000000..e69d957
+ LibSVAC3DContext *s = avctx->priv_data;
+
+ drop_frames(s);
+ av_fifo_reset2(s->props);
+ s->pending_err = 0;
+ s->drained = 0;
+ svac3d_reset(s->dec);
+}
+
Expand All @@ -308,11 +338,26 @@ index 0000000..e69d957
+ }
+
+ ret = ff_decode_get_packet(avctx, s->pkt);
+ if (ret < 0)
+ return ret; /* EAGAIN, or EOF: no pictures are held back */
+ s->err = 0;
+ ret = svac3d_decode(s->dec, s->pkt->data, s->pkt->size, svac3d_output, s);
+ av_packet_unref(s->pkt);
+ if (ret == AVERROR_EOF && !s->drained) {
+ /* the pictures other threads are still decoding */
+ s->drained = 1;
+ s->err = 0;
+ ret = svac3d_flush(s->dec, svac3d_output, s);
+ } else if (ret < 0) {
+ return ret;
+ } else {
+ const PacketProps p = {
+ .tag = ++s->tag, .pts = s->pkt->pts, .dts = s->pkt->dts,
+ .duration = s->pkt->duration,
+ };
+ if (av_fifo_can_read(s->props) >= MAX_PROPS)
+ av_fifo_drain2(s->props, 1);
+ s->err = av_fifo_write(s->props, &p, 1);
+ if (s->err >= 0)
+ ret = svac3d_decode_tag(s->dec, s->pkt->data, s->pkt->size, p.tag,
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
Resolved
+ svac3d_output, s);
+ av_packet_unref(s->pkt);
+ }
+ if (s->err < 0) {
+ s->pending_err = s->err;
+ } else if (ret < 0) {
Expand All @@ -333,7 +378,7 @@ index 0000000..e69d957
+ .close = libsvac3d_close,
+ .flush = libsvac3d_flush,
+ FF_CODEC_RECEIVE_FRAME_CB(libsvac3d_receive_frame),
+ .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_OTHER_THREADS,
+ .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY | AV_CODEC_CAP_OTHER_THREADS,
+ .caps_internal = FF_CODEC_CAP_INIT_CLEANUP | FF_CODEC_CAP_AUTO_THREADS,
+ .p.wrapper_name = "libsvac3d",
+};
Expand Down
Loading
Loading