video: drain libavcodec's reorder buffer at end-of-stream
Closes H1 from the pre-ship review (the known-limit doc note added inc0d55ba). The previous workaround was "first play-through truncates the last ~16 frames; replay is fine because flush_buffers clears libavcodec." That trade-off was OK for shipping but the proper fix is to drain the reorder buffer before propagating EOS to ExoPlayer. Media3's SimpleDecoder short-circuits the end-of-stream input buffer and never invokes the subclass's decode(), so there's no hook to send avcodec_send_packet(NULL). Every override worth overriding (decode loop, queue methods, flush) is final on SimpleDecoder. So we vendor a copy as FfmpegSimpleDecoder (Apache 2.0 attribution at the top of the file) with one structural change: an EOS-drain state. On EOS input, signalEndOfInput() flushes libavcodec's reorder queue, then drainAtEndOfStream() is called on successive output buffers until it reports DRAIN_DONE — at which point the loop attaches BUFFER_FLAG_END_OF_STREAM and resumes normal teardown. Everything else mirrors SimpleDecoder verbatim so upstream improvements are cheap to pull forward. - FfmpegSimpleDecoder.java: vendored base class. - ffmpegVideoSignalEos JNI: sends avcodec_send_packet(NULL). - FfmpegVideoDecoder: extends the new base; signalEndOfInput forwards to the JNI; drainAtEndOfStream re-uses the existing ffmpegVideoReceiveFrame so per-frame PTS recovery and the pending_frame path fromc0d55bacontinue to work during drain.
This commit is contained in:
@@ -444,6 +444,26 @@ VIDEO_DECODER_FUNC(jint, ffmpegVideoReceiveFrame, jlong handle,
|
||||
return VIDEO_DECODER_SUCCESS;
|
||||
}
|
||||
|
||||
// Signals libavcodec to flush its reorder buffer so the Java side can
|
||||
// pull the remaining frames out via successive ffmpegVideoReceiveFrame
|
||||
// calls. Used during end-of-stream drain: SimpleDecoder's EOS short-
|
||||
// circuit hides the EOS input from our decode() override, so without
|
||||
// this hook libavcodec's buffered tail (~16 frames for iOS H.264
|
||||
// High@3.1) would be lost on first play-through.
|
||||
VIDEO_DECODER_FUNC(jint, ffmpegVideoSignalEos, jlong handle) {
|
||||
if (!handle) {
|
||||
LOGE("ffmpegVideoSignalEos: null handle");
|
||||
return VIDEO_DECODER_ERROR_OTHER;
|
||||
}
|
||||
UxFfmpegVideoContext* ctx = (UxFfmpegVideoContext*)handle;
|
||||
int result = avcodec_send_packet(ctx->codec_ctx, nullptr);
|
||||
if (result < 0 && result != AVERROR_EOF) {
|
||||
logError("avcodec_send_packet(NULL)", result);
|
||||
return transformError(result);
|
||||
}
|
||||
return VIDEO_DECODER_SUCCESS;
|
||||
}
|
||||
|
||||
VIDEO_DECODER_FUNC(void, ffmpegVideoFlush, jlong handle) {
|
||||
if (!handle) return;
|
||||
UxFfmpegVideoContext* ctx = (UxFfmpegVideoContext*)handle;
|
||||
|
||||
Reference in New Issue
Block a user