PR #24937 opened by FeiYe
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24937
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24937.patch

# Summary of changes

`rm_read_seek()` resets `rm->audio_pkt_cnt` but not the per-stream
`ast->sub_packet_cnt` / `ast->audiotimestamp`. For the interleaved audio
deinterleavers (GENR / INT4 / SIPR) this makes the first block assembled after a
seek complete early, so it keeps pre-seek payload in the remaining descramble
slots and loses its container timestamp. The audio decodes cleanly but plays
content from the old position.

This completes 519ebb5ee5, which added `rm_read_seek()` for the same class of
problem (trac ticket 1605, https://trac.ffmpeg.org/ticket/1605) but only reset
the output counter, not the block assembly progress.

## Why the existing recovery path does not catch this

`ff_rm_parse_packet()` already has

```c
if (flags & 2)
    y = ast->sub_packet_cnt = 0;
```

so a seek destination whose first audio packet carries the keyframe flag heals
itself. In practice the audio packet at a seek destination usually does not
carry that flag, since in RealMedia the audio keyframe flag is not set on every
interleave block boundary.

## When a stale count can survive at all

Assembling one block normally happens inside a single `av_read_frame()` call,
because an incomplete block returns -1 and the caller just reads the next
packet. The count can therefore only be observed non zero if packets of another
stream are interleaved at a finer granularity than one full block. Dumping the
packet order of three COOK files (A = audio rm packet, V = video, sampled after
a seek) shows both layouts:

```
90009885H.rm         (cook + rv30, h=16)
  AAAAAAAAAAAAAAAA VVVVVVVVVVVVVVVVVVVVVVVV
Ah_My_Goddess_01.rm  (cook + rv20, h=20)
  AAAAAAAAAAAAAAAAAAAA VVVVVVVVVVVVVVVVVVVV
affected file        (cook + rv40, h=16)
  A VVVVVV A VVVVVVVV A VVVVVVVVVVVVVVVVV AA VVVVV
```

For the first two, the 16 resp. 20 audio packets of a block are stored
contiguously, so `sub_packet_cnt` is always 0 at an `av_read_frame()` boundary
and those files are not affected at all. For the third, every audio packet is
followed by video packets, so the count stops at 1, 2, 3 ... and a seek at that
moment leaves it stale. This is also why none of the existing COOK samples
reproduce it.

## Reproduction

No special sequence is needed. `avformat_find_stream_info()` already leaves
`sub_packet_cnt` at 8 on the affected file, so plain open, find_stream_info,
seek is enough. Hashing the payload of the first audio packets returned after
the seek (first 8 hex digits of the MD5, 4 packets shown):

```
            before                                after
 5000 ms    b6a4039c c57f5eab c9a491b5 067c394f    29647d16 66cda631 99cead78 
ca6bb030
10000 ms    b6a4039c c57f5eab c9a491b5 067c394f    047078ea 14747fe3 1a92099b 
e71e3522
15000 ms    bf8f9a87 215c5877 c3885c71 9cf1c7c7    bf8f9a87 215c5877 c3885c71 
9cf1c7c7
20000 ms    8687f1ca 2de866d2 69cbd2c6 63fc6852    8687f1ca 2de866d2 69cbd2c6 
63fc6852
25000 ms    cf77ea88 6812acd9 af70a123 56a8f731    cf77ea88 6812acd9 af70a123 
56a8f731
30000 ms    b6a4039c c57f5eab c9a491b5 067c394f    09e320af 74aaac7b bf7c1948 
0c5e616a
```

Before the change, three unrelated destinations return the very same payload:
the leftovers of the block that `avformat_find_stream_info()` had partially
assembled near the start of the file. After the change each of them returns its
own data.

The other three rows are control cases. Their first audio packet does carry the
keyframe flag, so they take the existing recovery path and are byte for byte
unchanged, which shows that only the broken case is touched.

The stale count can equally come from a previous seek instead of from stream
probing, and then the fix makes the output independent of that history:

```
plain seek to 20000 ms, after             8687f1ca 2de866d2 69cbd2c6 63fc6852
seek to 5000 ms, read a few packets,
then seek to 20000 ms, after              8687f1ca 2de866d2 69cbd2c6 63fc6852
same sequence, before                     b6a4039c c57f5eab c9a491b5 067c394f
```

So after the patch a seek to a given position returns the same data no matter
what was read before it, which is not the case today.

## No change on the existing samples

`ra_cook.rm` from the FATE suite and the COOK samples under `real/` on
samples.ffmpeg.org (`90009885H.rm`, `Ah_My_Goddess_01.rm`,
`Vetenskap_extramaterial_2005-10-31_142936.rm`,
`Vetenskap_mosbricka_2005-03-02_105820.rm`, `FUN_RM_96.rm`) store each block
contiguously and are byte for byte unchanged by this patch at every seek
destination I tried.

## Sample

Attached as `cook_rv40_seek.rm`, 8388608 bytes,
md5 f43a85f23fc04a4c2f80ce39fafe6f0c. It is the first 8 MB of a longer file,
cut at a byte boundary, which keeps the fine grained interleaving that is
needed to reproduce this.

I am also happy to add a FATE seek test for the interleaved audio path as a
follow up if that is wanted, since the existing RM seek tests do not cover it:
two of them have no audio stream and the third uses AC3, which does not go
through the interleave matrix.


>From 666f976645ffe893256c7b05c849aed060db4eee Mon Sep 17 00:00:00 2001
From: yefei5 <[email protected]>
Date: Thu, 8 Oct 2026 11:27:29 +0800
Subject: [PATCH] avformat/rmdec: reset audio deinterleave state on seek

rm_read_seek() clears rm->audio_pkt_cnt but leaves the per-stream
ast->sub_packet_cnt and ast->audiotimestamp untouched. For the
interleaved deinterleavers (GENR / INT4 / SIPR) a stale sub_packet_cnt
makes the next block complete after only (h - sub_packet_cnt) new rm
packets, so it keeps pre-seek payload in the remaining descramble slots
and loses its container timestamp.

519ebb5ee5 added rm_read_seek() for the same problem (ticket #1605) but
only reset the output counter, not the assembly progress.

Signed-off-by: yefei5 <[email protected]>
---
 libavformat/rmdec.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/libavformat/rmdec.c b/libavformat/rmdec.c
index 4a8e2f08df..4009048981 100644
--- a/libavformat/rmdec.c
+++ b/libavformat/rmdec.c
@@ -1160,10 +1160,25 @@ static int rm_read_seek(AVFormatContext *s, int 
stream_index,
                         int64_t pts, int flags)
 {
     RMDemuxContext *rm = s->priv_data;
+    unsigned int i;
 
     if (ff_seek_frame_binary(s, stream_index, pts, flags) < 0)
         return -1;
     rm->audio_pkt_cnt = 0;
+
+    /* A stale sub_packet_cnt makes the first interleave block after the
+     * seek reuse pre-seek payload in the remaining descramble slots and
+     * lose its container timestamp, which also shifts all following
+     * block boundaries. */
+    for (i = 0; i < s->nb_streams; i++) {
+        AVStream *st  = s->streams[i];
+        RMStream *ast = st->priv_data;
+
+        if (ast && st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
+            ast->sub_packet_cnt = 0;
+            ast->audiotimestamp = AV_NOPTS_VALUE;
+        }
+    }
     return 0;
 }
 
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to