Skip to content

Commit

Permalink
ffmpeg: Rescale DTS better during FPS passthrough
Browse files Browse the repository at this point in the history
This mostly ensures that non-B frames have the same dts/pts.

The PTS/DTS from the encoder can be "squashed" a bit during rescaling
back to the source timebase if it is used directly, due to the lower
resolution of the encoder timebase. We avoid this problem with the
PTS in in FPS passthrough mode by reusing the source pts, but only
rescale the encoder-provided DTS back to the source timebase for some
semblance of timestamp consistency. Because the DTS values are
squashed, they can differ from the PTS even with non-B frames.

The DTS values are still monotonic, so the exact numbers are not really
important. However, some tools use `dts == pts` as a heuristic to check
for B-frames ... so help them out to avoid spurious B-frame detections.

To fix the DTS/PTS mismatch, take the difference between the
encoder-provided dts/pts, rescale that difference back to the source
time base, and re-calculate the dts using the source pts.

Also see #405
  • Loading branch information
j0sh committed Aug 10, 2024
1 parent 409f6e0 commit b2fedd6
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 1 deletion.
3 changes: 2 additions & 1 deletion ffmpeg/encoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -373,8 +373,9 @@ static int encode(AVCodecContext* encoder, AVFrame *frame, struct output_ctx* oc
if (AVMEDIA_TYPE_VIDEO == ost->codecpar->codec_type && !octx->fps.den && octx->vf.active) {
// try to preserve source timestamps for fps passthrough.
time_base = octx->vf.time_base;
int64_t pts_dts_diff = pkt->pts - pkt->dts;
pkt->pts = (int64_t)pkt->opaque; // already in filter timebase
pkt->dts = av_rescale_q(pkt->dts, encoder->time_base, time_base);
pkt->dts = pkt->pts - av_rescale_q(pts_dts_diff, encoder->time_base, time_base);
}
ret = mux(pkt, time_base, octx, ost);
if (ret < 0) goto encode_cleanup;
Expand Down
69 changes: 69 additions & 0 deletions ffmpeg/ffmpeg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1516,6 +1516,75 @@ func TestTranscoder_PassthroughFPS(t *testing.T) {
run(cmd)
}

func TestTranscoder_PassthroughFPS_AdjustTimestamps(t *testing.T) {
// check timestamp adjustments for fps passthrough

run, dir := setupTest(t)
defer os.RemoveAll(dir)

cmd := `
ffmpeg -i "$1/../transcoder/test.ts" -an -c:v copy -t 0.5 test-short.ts
ffprobe -loglevel warning -show_entries frame=pts,duration -of csv=p=0 test-short.ts | grep -v '^$' > expected-frame-pts.out
wc -l expected-frame-pts.out | grep "32 expected-frame-pts.out"
cat << EXPECTED_TS_EOF > expected-pkt-ts.out
pts,dts,duration
128970,125970,1500
134910,127410,1500
131940,128940,1500
130500,130500,1500
133380,131880,1500
137970,133470,1500
136440,134940,1500
143910,136410,1500
140940,137940,1500
139410,139410,1500
142470,140970,1500
149940,142440,1500
146970,143970,1500
145440,145440,1500
148500,147000,1500
155970,148470,1500
152910,149910,1500
151380,151380,1500
154440,152940,1500
161910,154410,1500
158940,155940,1500
157410,157410,1500
160470,158970,1500
167940,160440,1500
164970,161970,1500
163440,163440,1500
166500,165000,1500
173970,166470,1500
170910,167910,1500
169380,169380,1500
172440,170940,1500
176940,172440,1500
EXPECTED_TS_EOF
`
run(cmd)

in := &TranscodeOptionsIn{Fname: dir + "/test-short.ts"}
out := []TranscodeOptions{{Profile: P144p30fps16x9}}
out[0].Profile.Framerate = 0 // Passthrough!
out[0].Profile.Profile = ProfileH264High
out[0].Oname = dir + "/out-0.ts"
_, err := Transcode3(in, out)
require.Nil(t, err)
cmd = `
echo "pts,dts,duration" > received-pkt-ts.out
ffprobe -loglevel warning -show_entries packet=pts,dts,duration,pict_type -of csv=p=0 out-0.ts | grep -v '^$' | sed 's/,*$//g' >> received-pkt-ts.out
ffprobe -loglevel warning -show_entries frame=pts,duration -of csv=p=0 test-short.ts | grep -v '^$' > received-frame-pts.out
# ensure packet pts+dts matches what is expected
diff -u expected-pkt-ts.out received-pkt-ts.out
# ensure all pts are accounted for from original
diff -u expected-frame-pts.out received-frame-pts.out
`
run(cmd)
}

func TestTranscoder_FormatOptions(t *testing.T) {
// Test combinations of VideoProfile.Format and TranscodeOptions.Muxer
// The former takes precedence over the latter if set
Expand Down

0 comments on commit b2fedd6

Please sign in to comment.