diff --git a/Util/FFmpeg/FFmpegNativeDecoder.cs b/Util/FFmpeg/FFmpegNativeDecoder.cs index 0e525bb..95b0886 100644 --- a/Util/FFmpeg/FFmpegNativeDecoder.cs +++ b/Util/FFmpeg/FFmpegNativeDecoder.cs @@ -191,6 +191,13 @@ internal sealed unsafe class FFmpegNativeDecoder : IDisposable private bool _isOpened; private bool _disposed; private bool _isDraining; + + /// Number of video packets skipped because the decoder rejected them as invalid data. + private int _skippedPacketCount; + + /// Number of skipped packets for which a warning is logged. + private const int MaxLoggedSkippedPackets = 5; + private readonly object _lock = new(); // Fields to detect SwsContext updates @@ -371,6 +378,16 @@ internal sealed unsafe class FFmpegNativeDecoder : IDisposable if (_videoStreamIndex == -1) throw new Exception("Failed to find video stream."); + // Only the video stream is used. The demuxer does not need to return the packets of the other streams + // (audio, subtitles, ...) once they are marked as discarded. + // Reference: AVStream.discard - "Selects which packets can be discarded at will and do not need to be demuxed." + // https://ffmpeg.org/doxygen/trunk/structAVStream.html + for (int i = 0; i < _pFormatContext->nb_streams; i++) + { + if (i != _videoStreamIndex) + _pFormatContext->streams[i]->discard = AVDiscard.AVDISCARD_ALL; + } + var stream = _pFormatContext->streams[_videoStreamIndex]; _timeBase = stream->time_base; @@ -441,9 +458,13 @@ internal sealed unsafe class FFmpegNativeDecoder : IDisposable TotalFrames = 0; } - Duration = _pFormatContext->duration > 0 - ? _pFormatContext->duration / (double)ffmpeg.AV_TIME_BASE - : (stream->duration > 0 ? stream->duration * ffmpeg.av_q2d(stream->time_base) : 0); + // Duration of the video stream (AVStream.duration, in the stream time base). + // When the stream does not carry a duration, the container duration (AVFormatContext.duration, + // in AV_TIME_BASE units, the longest of all streams) is used. + // Reference: https://ffmpeg.org/doxygen/trunk/structAVStream.html, https://ffmpeg.org/doxygen/trunk/structAVFormatContext.html + Duration = stream->duration > 0 + ? stream->duration * ffmpeg.av_q2d(stream->time_base) + : _pFormatContext->duration > 0 ? _pFormatContext->duration / (double)ffmpeg.AV_TIME_BASE : 0; // 9. Allocate frame and packet buffers // Reference: https://ffmpeg.org/doxygen/trunk/group__lavu__frame.html @@ -655,6 +676,8 @@ internal sealed unsafe class FFmpegNativeDecoder : IDisposable /// /// Reads packets from the container until a packet belonging to the video stream is found, /// then sends it to the decoder via avcodec_send_packet. + /// A packet that the decoder rejects as invalid data (AVERROR_INVALIDDATA, e.g. a damaged packet) + /// is skipped and the next packet is read. Any other decoder error is thrown. /// Reference: https://ffmpeg.org/doxygen/trunk/group__lavc__encdec.html /// /// True if a video packet was sent; false on EOF. @@ -670,6 +693,18 @@ internal sealed unsafe class FFmpegNativeDecoder : IDisposable { ret = ffmpeg.avcodec_send_packet(_pCodecContext, _pPacket); ffmpeg.av_packet_unref(_pPacket); + + if (ret == ffmpeg.AVERROR_INVALIDDATA) + { + // The warning is logged for the first few packets only (a damaged file can have many). + _skippedPacketCount++; + if (_skippedPacketCount <= MaxLoggedSkippedPackets) + { + Melon.Logger.Warning($"[FFmpegNativeDecoder] Skipped an invalid video packet ({_skippedPacketCount}): {GetFFmpegErrorString(ret)}"); + } + continue; + } + if (ret < 0) { throw new Exception($"Error sending packet to decoder: {GetFFmpegErrorString(ret)}");