Skip to content

Commit 87f5ba1

Browse files
th0ma7claude
andcommitted
tvheadend: audio/video start alignment confirmed; 107 is no longer a demo
Five runs per encoder with the rewritten 108 close the problem outright. Offset between the first audio packet and the first picture, negative meaning the picture comes first: step SOFTWARE VAAPI QSV med / worst med / worst med / worst none 534 / 534 1391 / 1391 1368 / 1368 107 alone 130 / 226 356 / 1052 85 / 110 107 + 108 (flag) 280 / 295 304 / 385 53 / 93 107 + 108 (pts) -11 / -5 -9 / 0 -18 / -2 Fifteen measurements in the last row, every one at or below zero: no run left with audio ahead of the picture. The DEMONSTRATION label comes off 107 -- the remedy is settled and the numbers back each step of it. Adds 106-108-av-start-HOWTO.txt, recording how the three patches divide the problem and why none substitutes for another: 106 acts on a frame entering the encoder, 107 and 108 on packets leaving for the client. Without 106 the first picture lands one frame later and the other two discard 3003 ticks more audio than they need to. The flag-based row is kept deliberately. That version of 108 removed the VAAPI worst case and did nothing for software, because a flag set by the first picture lets through audio arriving behind it stamped earlier -- one software recording shows VIDEO at pts 572 ms written first, then six audio packets stamped 277 to 384 ms. The timestamp comparison is the point, and 107 already had it right. Chain applies with zero rejects from a pristine tarball. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QEH1b4ASNYSrZFQnEeroj8
1 parent ac66316 commit 87f5ba1

4 files changed

Lines changed: 100 additions & 8 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
=============================================================================
2+
Audio/video start alignment -- 106, 107 and 108 together
3+
=============================================================================
4+
5+
Symptom: a transcoded stream begins with sound over a blank or frozen picture,
6+
half a second to a second and a half of it, on every encoder path.
7+
8+
Cause, measured rather than assumed. The video decoder cannot produce anything
9+
until the source delivers an MPEG-2 sequence header, which is where width and
10+
height come from; timing the logs, only 23 to 87 ms separate the filter graph
11+
being configured from the first frame leaving it, so essentially the whole wait
12+
is that. The audio encoder meanwhile runs from the first moment. Two things
13+
then let its output reach the client ahead of any picture:
14+
15+
- globalheaders releases its hold queue as soon as headers are complete, and
16+
that queue holds several hundred ms of audio with no picture beside it;
17+
- after the release gh_passthru forwards everything unfiltered, and the audio
18+
encoder lags the video one on the delivery clock while stamping its output
19+
with the original stream times -- so audio arrives *after* the first picture
20+
carrying a pts *before* it.
21+
22+
Each patch closes one part, and none of them substitutes for another:
23+
24+
106 video.c recovers the first frame's timestamp from its dts so the
25+
monotonicity guard stops discarding it. Without it the
26+
first picture is one frame later, which is 3003 ticks
27+
more audio for 107 and 108 to discard.
28+
107 globalheaders drops audio stamped before the first picture when the
29+
hold queue is released.
30+
108 globalheaders keeps that same timestamp test running past the release,
31+
so late-arriving early-stamped audio is caught too.
32+
33+
Offset between the first audio packet and the first picture, measured by
34+
recording the stream with curl and reading the Matroska blocks. Negative means
35+
the picture comes first, which is what a viewer wants:
36+
37+
step SOFTWARE VAAPI QSV
38+
med / worst med / worst med / worst
39+
none 534 / 534 1391 / 1391 1368 / 1368
40+
107 alone 130 / 226 356 / 1052 85 / 110
41+
107 + 108 (flag) 280 / 295 304 / 385 53 / 93
42+
107 + 108 (pts) -11 / -5 -9 / 0 -18 / -2
43+
44+
Five runs per encoder in the final row, fifteen measurements, every one of them
45+
at or below zero -- no run left with audio ahead of the picture.
46+
47+
The middle row is kept because it is instructive. That version of 108 gated on
48+
"have we delivered a picture yet" instead of comparing timestamps. It removed
49+
the VAAPI worst case, 1052 down to 385 ms, and did nothing at all for software,
50+
because a flag is set by the first picture and then lets through the audio that
51+
arrives behind it stamped earlier. Reading one software recording in file write
52+
order shows it plainly:
53+
54+
VIDEO pts 572 ms <- delivered first
55+
audio pts 277 ms
56+
audio pts 298 ms ... six of them, all stamped ahead of that picture
57+
58+
The timestamp comparison is the whole point, and 107 already had it right.
59+
60+
Cost: the leading audio is discarded, up to about 1.4 s of sound the source did
61+
provide. Holding it instead does not work -- holding does not alter timestamps,
62+
so a later release still stamps it ahead of the first picture and the player
63+
still shows a blank frame. Making that work would mean shifting audio
64+
timestamps forward, which desynchronises sound from picture. Discarding is the
65+
only variant that closes the gap without touching synchronisation.
66+
67+
None of this is introduced by PR #2117: video.c's guard and globalheaders.c are
68+
both identical in master (45cbe4adb).

cross/tvheadend/patches/106-video-recover-first-frame-pts.patch

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ joins the stream. A VAAPI run that joined on a P frame produced no untimed
5555
frame at all -- the decoder had no reference and discarded everything until the
5656
next key frame, by which point timestamps were sound again.
5757

58+
Still worth carrying alongside 107 and 108, which close the audio/video
59+
start-up gap downstream in globalheaders.c. They do not overlap: this one acts
60+
on a frame entering the encoder, they act on packets leaving for the client.
61+
Without it the first picture lands one frame later, so 107 and 108 discard
62+
3003 ticks more audio than they need to. See 106-108-av-start-HOWTO.txt.
63+
5864
Not introduced by PR #2117: the guard is identical in master (45cbe4adb).
5965

6066
Left alone deliberately: self->pts is seeded to 0 by the calloc of TVHContext,

cross/tvheadend/patches/107-globalheaders-align-av-start.patch

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
Start the audio and video tracks together on a transcoded stream.
22

3-
DEMONSTRATION PATCH -- the mechanism is measured, the remedy is one of two
4-
possible ones and is offered for discussion rather than as a settled fix. See
5-
the trade-off at the end.
6-
73
Recording the output of three profiles with curl and reading the Matroska
84
blocks directly shows the audio track beginning well before the first picture:
95

@@ -54,7 +50,20 @@ the release and the first picture still slips out ahead of it. Patch 108
5450
extends the same filter past the release and closes that second window; the two
5551
are meant to be measured separately.
5652

57-
The trade-off, and why this is a demonstration. Discarding leading audio means
53+
Final measurement, five runs per encoder, offset between the first audio packet
54+
and the first picture (negative = picture first, which is what a viewer wants):
55+
56+
step SOFTWARE VAAPI QSV
57+
med / worst med / worst med / worst
58+
none 534 / 534 1391 / 1391 1368 / 1368
59+
107 alone 130 / 226 356 / 1052 85 / 110
60+
107 + 108 (flag) 280 / 295 304 / 385 53 / 93
61+
107 + 108 (pts) -11 / -5 -9 / 0 -18 / -2
62+
63+
Fifteen measurements in the final row, every one at or below zero. See
64+
106-108-av-start-HOWTO.txt for how the three patches divide the problem.
65+
66+
The trade-off. Discarding leading audio means
5867
the stream starts at the first picture and loses up to ~1.4 s of sound that the
5968
source did provide. The alternative is to hold the audio instead of dropping
6069
it, so nothing is lost but start-up latency grows by the same amount. Which is

cross/tvheadend/patches/108-globalheaders-align-av-passthru.patch

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,18 @@ gh_first_video_pts is seeded to PTS_UNSET at creation -- calloc would leave it
4141
at 0, a legitimate timestamp -- and reset in gh_flush(), which is the one place
4242
every stop and restart path goes through.
4343

44-
Untested at runtime. Same measurement as before: record with curl, compare the
45-
first audio and first video timestamps, several runs per profile since the
46-
spread between runs is wide.
44+
Final measurement, five runs per encoder, offset between the first audio packet
45+
and the first picture (negative = picture first, which is what a viewer wants):
46+
47+
step SOFTWARE VAAPI QSV
48+
med / worst med / worst med / worst
49+
none 534 / 534 1391 / 1391 1368 / 1368
50+
107 alone 130 / 226 356 / 1052 85 / 110
51+
107 + 108 (flag) 280 / 295 304 / 385 53 / 93
52+
107 + 108 (pts) -11 / -5 -9 / 0 -18 / -2
53+
54+
Fifteen measurements in the final row, every one at or below zero. See
55+
106-108-av-start-HOWTO.txt for how the three patches divide the problem.
4756

4857
Not introduced by PR #2117: globalheaders.c is untouched by it.
4958

0 commit comments

Comments
 (0)