From fa204652714c1a62ab3deab3ab740d6aeaf8188b Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Wed, 29 Dec 2021 22:55:31 -0800 Subject: [PATCH] FFmpeg Decoder: Fix seeking in files with preroll that happens to make the decoder return EAGAIN error, so they don't inadvertently skip actual audio data unnecessarily. Fixes seeking to the start of USAC files with preroll packets. --- Plugins/FFMPEG/FFMPEGDecoder.m | 43 +++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/Plugins/FFMPEG/FFMPEGDecoder.m b/Plugins/FFMPEG/FFMPEGDecoder.m index 88a15c37f..168da73be 100644 --- a/Plugins/FFMPEG/FFMPEGDecoder.m +++ b/Plugins/FFMPEG/FFMPEGDecoder.m @@ -251,6 +251,7 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op) totalFrames = av_rescale_q(stream->duration, stream->time_base, tb); bitrate = (int)((codecCtx->bit_rate) / 1000); framesRead = 0; + seekFrame = 0; // Skip preroll if necessary endOfStream = NO; endOfAudio = NO; @@ -348,24 +349,6 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op) } readNextPacket = NO; // we probably won't need to consume another chunk - - // FFmpeg seeking by packet is usually inexact, so skip up to - // target sample using packet timestamp - if (seekFrame >= 0 && errcode >= 0) { - DLog(@"Seeking to frame %lld", seekFrame); - AVRational tb = {.num = 1, .den = codecCtx->sample_rate}; - int64_t packetBeginFrame = av_rescale_q( - lastReadPacket->dts, - formatCtx->streams[streamIndex]->time_base, - tb - ); - - if (packetBeginFrame < seekFrame) { - seekBytesSkip += (int)((seekFrame - packetBeginFrame) * frameSize); - } - - seekFrame = -1; - } } if (dataSize <= bytesConsumedFromDecodedFrame) @@ -385,6 +368,7 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op) else if (errcode == AVERROR(EAGAIN)) { // Read another packet + DLog(@"Decoded samples: %d", lastDecodedFrame->nb_samples); readNextPacket = YES; continue; } @@ -405,6 +389,27 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op) if ( dataSize < 0 ) dataSize = 0; + // FFmpeg seeking by packet is usually inexact, so skip up to + // target sample using packet timestamp + // New: Moved here, because sometimes preroll packets also + // trigger EAGAIN above, so ask for the next packet's timestamp + // instead + if (seekFrame >= 0 && errcode >= 0) { + DLog(@"Seeking to frame %lld", seekFrame); + AVRational tb = {.num = 1, .den = codecCtx->sample_rate}; + int64_t packetBeginFrame = av_rescale_q( + lastReadPacket->dts, + formatCtx->streams[streamIndex]->time_base, + tb + ); + + if (packetBeginFrame < seekFrame) { + seekBytesSkip += (int)((seekFrame - packetBeginFrame) * frameSize); + } + + seekFrame = -1; + } + int minSkipped = FFMIN(dataSize, seekBytesSkip); bytesConsumedFromDecodedFrame += minSkipped; seekBytesSkip -= minSkipped; @@ -456,7 +461,7 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op) } AVRational tb = {.num = 1, .den = codecCtx->sample_rate}; int64_t ts = av_rescale_q(frame, tb, formatCtx->streams[streamIndex]->time_base); - avformat_seek_file(formatCtx, streamIndex, ts - 1000, ts, ts, AVSEEK_FLAG_ANY); + avformat_seek_file(formatCtx, streamIndex, ts - 1000, ts, ts, 0); avcodec_flush_buffers(codecCtx); readNextPacket = YES; // so we immediately read next packet bytesConsumedFromDecodedFrame = INT_MAX; // so we immediately begin decoding next frame