Skip to content

Commit

Permalink
Implement decoder draining - fixes an issue where last frames were dr…
Browse files Browse the repository at this point in the history
…opped (closes #72)
  • Loading branch information
radek-k committed Apr 9, 2022
1 parent 5b3b847 commit bebe7d9
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 30 deletions.
14 changes: 14 additions & 0 deletions FFMediaToolkit/Common/Internal/MediaPacket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ public static MediaPacket AllocateEmpty()
return new MediaPacket(packet);
}

/// <summary>
/// Creates a flush packet.
/// </summary>
/// <param name="streamIndex">The stream index.</param>
/// <returns>The flush packet.</returns>
public static MediaPacket CreateFlushPacket(int streamIndex)
{
var packet = ffmpeg.av_packet_alloc();
packet->stream_index = streamIndex;
packet->data = null;
packet->size = 0;
return new MediaPacket(packet);
}

/// <summary>
/// Sets valid PTS/DTS values. Used only in encoding.
/// </summary>
Expand Down
40 changes: 25 additions & 15 deletions FFMediaToolkit/Decoding/Internal/Decoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{
using System;
using System.Collections.Generic;
using System.IO;
using FFMediaToolkit.Common;
using FFMediaToolkit.Common.Internal;
using FFMediaToolkit.Helpers;
Expand All @@ -15,6 +16,7 @@ internal unsafe class Decoder : Wrapper<AVCodecContext>
private readonly int bufferLimit;
private int bufferSize = 0;
private bool reuseLastPacket;
private bool flushing = false;
private MediaPacket packet;

/// <summary>
Expand Down Expand Up @@ -114,6 +116,8 @@ public void SkipFrames(long targetTs)
/// </summary>
public void DiscardBufferedData()
{
ffmpeg.avcodec_flush_buffers(Pointer);

foreach (var packet in BufferedPackets)
{
packet.Wipe();
Expand All @@ -122,18 +126,13 @@ public void DiscardBufferedData()

BufferedPackets.Clear();
bufferSize = 0;
flushing = false;
}

/// <summary>
/// Flushes the codec buffers.
/// </summary>
public void FlushUnmanagedBuffers() => ffmpeg.avcodec_flush_buffers(Pointer);

/// <inheritdoc/>
protected override void OnDisposing()
{
RecentlyDecodedFrame.Dispose();
FlushUnmanagedBuffers();
ffmpeg.avcodec_close(Pointer);
}

Expand All @@ -144,10 +143,20 @@ private void ReadNextFrame()

do
{
DecodePacket(); // Gets the next packet and sends it to the decoder
if (!flushing)
{
DecodePacket(); // Gets the next packet and sends it to the decoder
}

error = ffmpeg.avcodec_receive_frame(Pointer, RecentlyDecodedFrame.Pointer); // Tries to decode frame from the packets.
}
while (error == ffmpeg.AVERROR(ffmpeg.EAGAIN) || error == -35); // The EAGAIN code means that the frame decoding has not been completed and more packets are needed.

if (error == ffmpeg.AVERROR_EOF)
{
throw new EndOfStreamException("End of file.");
}

error.ThrowIfError("An error occurred while decoding the frame.");
}

Expand All @@ -156,23 +165,24 @@ private void DecodePacket()
if (!reuseLastPacket)
{
if (IsBufferEmpty)
OwnerFile.GetPacketFromStream(Info.Index);
{
flushing = !OwnerFile.GetPacketFromStream(Info.Index);
}

packet = BufferedPackets.Dequeue();
bufferSize -= packet.Pointer->size;
}

// Sends the packet to the decoder.
var result = ffmpeg.avcodec_send_packet(Pointer, packet);

if (result == ffmpeg.AVERROR(ffmpeg.EAGAIN))
{
reuseLastPacket = true;
}
else
reuseLastPacket = result == ffmpeg.AVERROR(ffmpeg.EAGAIN);

if (!reuseLastPacket)
{
reuseLastPacket = false;
result.ThrowIfError("Cannot send a packet to the decoder.");
packet.Wipe();
packet.Dispose();
result.ThrowIfError("Cannot send a packet to the decoder.");
}
}
}
Expand Down
41 changes: 27 additions & 14 deletions FFMediaToolkit/Decoding/Internal/InputContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,28 @@ public void SeekFile(long targetTs, int streamIndex)
{
ffmpeg.av_seek_frame(Pointer, streamIndex, targetTs, ffmpeg.AVSEEK_FLAG_BACKWARD).ThrowIfError($"Seek to {targetTs} failed.");

Decoders[streamIndex].FlushUnmanagedBuffers();
GetPacketFromStream(streamIndex);
foreach (var decoder in Decoders)
{
decoder?.DiscardBufferedData();
}
}

/// <summary>
/// Reads a packet from the specified stream index and buffers it in the respective codec.
/// </summary>
/// <param name="streamIndex">Index of the stream to read from.</param>
public void GetPacketFromStream(int streamIndex)
/// <returns>True if the requested packet was read, false if EOF ocurred and a flush packet was send to the buffer.</returns>
public bool GetPacketFromStream(int streamIndex)
{
MediaPacket packet;
do
{
packet = ReadPacket();
if (!TryReadNextPacket(out packet))
{
Decoders[streamIndex].BufferPacket(MediaPacket.CreateFlushPacket(streamIndex));
return false;
}

var stream = Decoders[packet.StreamIndex];
if (stream == null)
{
Expand All @@ -98,6 +106,7 @@ public void GetPacketFromStream(int streamIndex)
}
}
while (packet?.StreamIndex != streamIndex);
return true;
}

/// <inheritdoc/>
Expand Down Expand Up @@ -190,22 +199,26 @@ private void OpenStreams(MediaOptions options)
/// <summary>
/// Reads the next packet from this file.
/// </summary>
private MediaPacket ReadPacket()
private bool TryReadNextPacket(out MediaPacket packet)
{
var pkt = MediaPacket.AllocateEmpty();
var result = ffmpeg.av_read_frame(Pointer, pkt.Pointer); // Gets the next packet from the file.
packet = MediaPacket.AllocateEmpty();
var result = ffmpeg.av_read_frame(Pointer, packet.Pointer); // Gets the next packet from the file.

// Check if the end of file error occurred
if (result == ffmpeg.AVERROR_EOF)
if (result < 0)
{
throw new EndOfStreamException("End of the file.");
}
else
{
result.ThrowIfError("Cannot read next packet from the file");
packet.Dispose();
if (result == ffmpeg.AVERROR_EOF)
{
return false;
}
else
{
result.ThrowIfError("Cannot read next packet from the file");
}
}

return pkt;
return true;
}
}
}
3 changes: 2 additions & 1 deletion FFMediaToolkit/Decoding/MediaStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ internal MediaStream(Decoder stream, MediaOptions options)
/// <summary>
/// Discards all buffered frame data associated with this stream.
/// </summary>
[Obsolete("Do not call this method. Buffered data is automatically discarded when required")]
public void DiscardBufferedData() => Stream.DiscardBufferedData();

/// <inheritdoc/>
public virtual void Dispose()
{
DiscardBufferedData();
Stream.DiscardBufferedData();
Stream.Dispose();
}

Expand Down

0 comments on commit bebe7d9

Please sign in to comment.