diff --git a/ffmpeg/decoder.h b/ffmpeg/decoder.h index 2c0d106689..70e8fbb51c 100755 --- a/ffmpeg/decoder.h +++ b/ffmpeg/decoder.h @@ -55,6 +55,9 @@ struct input_ctx { // In HW transcoding, demuxer is opened once and used, // so it is necessary to check whether the input pixel format does not change in the middle. enum AVPixelFormat last_format; + + // per-segment tracking (reset for each segment) + int64_t segment_last_pts; // best-effort pts of most recent decoded video frame }; // Exported methods diff --git a/ffmpeg/ffmpeg_test.go b/ffmpeg/ffmpeg_test.go index f39d1f56fb..699a0239c5 100644 --- a/ffmpeg/ffmpeg_test.go +++ b/ffmpeg/ffmpeg_test.go @@ -2504,8 +2504,8 @@ func TestTranscoder_LargeOutputs(t *testing.T) { close(closeCh) assert.Nil(err) assert.Equal(120, res.Decoded.Frames) - assert.Equal(116, res.Encoded[0].Frames) // ffmpeg probably drops missing timestamp frames - assert.Equal(56, res.Encoded[1].Frames) + assert.Equal(120, res.Encoded[0].Frames) + assert.Equal(60, res.Encoded[1].Frames) cmd := ` # check input properties to ensure they still have the weird timestamps ffprobe -of csv -hide_banner -show_entries frame=pts_time,pkt_dts_time,media_type,pict_type $1/../data/missing-dts.ts 2>&1 | grep video > input.out @@ -2642,25 +2642,26 @@ func TestTranscoder_LargeOutputs(t *testing.T) { cat <<- 'EOF2' > expected-output.out frame,video,25994.033333,25994.033333,I, frame,video,25994.066667,25994.066667,P - frame,video,25994.100000,25994.100000,B - frame,video,25994.133333,25994.133333,P + frame,video,25994.100000,25994.100000,P + frame,video,25994.133333,25994.133333,B frame,video,25994.166667,25994.166667,B frame,video,25994.200000,25994.200000,B - frame,video,25994.233333,25994.233333,B - frame,video,25994.266667,25994.266667,P + frame,video,25994.233333,25994.233333,P + frame,video,25994.266667,25994.266667,B frame,video,25994.300000,25994.300000,B - frame,video,25994.333333,25994.333333,P - frame,video,25994.366667,25994.366667,B + frame,video,25994.333333,25994.333333,B + frame,video,25994.366667,25994.366667,P frame,video,25994.400000,25994.400000,B frame,video,25994.433333,25994.433333,B - frame,video,25994.466667,25994.466667,P - frame,video,25994.500000,25994.500000,B + frame,video,25994.466667,25994.466667,B + frame,video,25994.500000,25994.500000,P frame,video,25994.533333,25994.533333,B frame,video,25994.566667,25994.566667,B - frame,video,25994.600000,25994.600000,P - frame,video,25994.666667,25994.666667,P, - frame,video,25994.700000,25994.700000,B, - frame,video,25994.733333,25994.733333,B, + frame,video,25994.600000,25994.600000,B + frame,video,25994.633333,25994.633333,P, + frame,video,25994.666667,25994.666667,B, + frame,video,25994.700000,25994.700000,P, + frame,video,25994.733333,25994.733333,P, frame,video,25994.766667,25994.766667,B, frame,video,25994.800000,25994.800000,P, frame,video,25994.833333,25994.833333,B, @@ -2669,32 +2670,35 @@ func TestTranscoder_LargeOutputs(t *testing.T) { frame,video,25994.933333,25994.933333,P, frame,video,25994.966667,25994.966667,B, frame,video,25995.000000,25995.000000,P, - frame,video,25995.033333,25995.033333,B, - frame,video,25995.066667,25995.066667,B, + frame,video,25995.033333,25995.033333,P, + frame,video,25995.066667,25995.066667,P, + frame,video,25995.100000,25995.100000,P, frame,video,25995.133333,25995.133333,B, frame,video,25995.166667,25995.166667,P, frame,video,25995.200000,25995.200000,B, + frame,video,25995.233333,25995.233333,B, frame,video,25995.266667,25995.266667,B, - frame,video,25995.300000,25995.300000,B, - frame,video,25995.333333,25995.333333,P, + frame,video,25995.300000,25995.300000,P, + frame,video,25995.333333,25995.333333,B, frame,video,25995.366667,25995.366667,B, frame,video,25995.400000,25995.400000,B, - frame,video,25995.433333,25995.433333,B, - frame,video,25995.466667,25995.466667,P, + frame,video,25995.433333,25995.433333,P, + frame,video,25995.466667,25995.466667,B, frame,video,25995.500000,25995.500000,B, frame,video,25995.533333,25995.533333,B, - frame,video,25995.566667,25995.566667,B, - frame,video,25995.600000,25995.600000,P, + frame,video,25995.566667,25995.566667,P, + frame,video,25995.600000,25995.600000,B, frame,video,25995.633333,25995.633333,B, frame,video,25995.666667,25995.666667,B, + frame,video,25995.700000,25995.700000,P, frame,video,25995.733333,25995.733333,B, - frame,video,25995.766667,25995.766667,P, + frame,video,25995.766667,25995.766667,B, frame,video,25995.800000,25995.800000,B, - frame,video,25995.833333,25995.833333,B, + frame,video,25995.833333,25995.833333,P, frame,video,25995.866667,25995.866667,B, - frame,video,25995.900000,25995.900000,P, + frame,video,25995.900000,25995.900000,B, frame,video,25995.933333,25995.933333,B, - frame,video,25995.966667,N/A,B, + frame,video,25995.966667,N/A,P, frame,video,25996.000000,N/A,P, EOF2 diff -u expected-output.out output.out diff --git a/ffmpeg/filter.c b/ffmpeg/filter.c index bb0cf93f4a..d9452c6db0 100644 --- a/ffmpeg/filter.c +++ b/ffmpeg/filter.c @@ -334,7 +334,28 @@ int filtergraph_write(AVFrame *inf, struct input_ctx *ictx, struct output_ctx *o // Timestamp handling code AVStream *vst = ictx->ic->streams[ictx->vi]; if (inf) { // Non-Flush Frame - inf->opaque = (void *) inf->pts; // Store original PTS for calc later + if (is_video) { + int64_t pts = inf->pts; + if (pts == AV_NOPTS_VALUE) { + av_log(NULL, AV_LOG_WARNING, "Filter frame pts is AV_NOPTS_VALUE. Falling back to best_effort_timestamp\n"); + pts = inf->best_effort_timestamp; + } + if (pts == AV_NOPTS_VALUE && ictx->segment_last_pts != AV_NOPTS_VALUE) { + av_log(NULL, AV_LOG_WARNING, "Filter frame pts is still AV_NOPTS_VALUE. Falling back to segment_last_pts + step\n"); + int64_t step = inf->duration; + if (!step && vst->r_frame_rate.den){ + step = av_rescale_q(1, av_inv_q(vst->r_frame_rate), vst->time_base); + } + if (step){ + pts = ictx->segment_last_pts + step; + } + } + inf->pts = pts; + } + inf->opaque = (void *) inf->pts; + if (inf->pts == AV_NOPTS_VALUE) { + av_log(NULL, AV_LOG_ERROR, "Filter frame pts remains AV_NOPTS_VALUE\n"); + } if (is_video && octx->fps.den) { // Custom PTS set when FPS filter is used int64_t ts_step = inf->pts - filter->prev_frame_pts; diff --git a/ffmpeg/transcoder.c b/ffmpeg/transcoder.c index 8942f093e6..7d0231863a 100755 --- a/ffmpeg/transcoder.c +++ b/ffmpeg/transcoder.c @@ -326,6 +326,8 @@ int transcode(struct transcode_thread *h, int nb_outputs = h->nb_outputs; int outputs_ready = 0, hit_eof = 0; + ictx->segment_last_pts = AV_NOPTS_VALUE; + ipkt = av_packet_alloc(); if (!ipkt) LPMS_ERR(transcode_cleanup, "Unable to allocated packet"); dframe = av_frame_alloc(); @@ -426,7 +428,17 @@ int transcode(struct transcode_thread *h, decoded_results->frames += dframe->width && dframe->height; decoded_results->pixels += dframe->width * dframe->height; has_frame = has_frame && dframe->width && dframe->height; - if (has_frame) last_frame = ictx->last_frame_v; + if (has_frame) { + last_frame = ictx->last_frame_v; + int64_t pts = dframe->pts; + // Try best effort timestamp if pts is not available + if (pts == AV_NOPTS_VALUE) pts = dframe->best_effort_timestamp; + // If best effort timestamp is not available, try to use segment last pts + duration + if (pts == AV_NOPTS_VALUE && ictx->segment_last_pts != AV_NOPTS_VALUE && dframe->duration) + pts = ictx->segment_last_pts + dframe->duration; + // Only update segment last pts if pts is valid + if (pts != AV_NOPTS_VALUE) ictx->segment_last_pts = pts; + } } else if (AVMEDIA_TYPE_AUDIO == ist->codecpar->codec_type) { has_frame = has_frame && dframe->nb_samples; if (has_frame) last_frame = ictx->last_frame_a;