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)}");