Skip to content

Commit

Permalink
Updating to 3.1.13 (fixes network stream issues)
Browse files Browse the repository at this point in the history
- Adds demuxer timeouts (open/read/seek/close) to the configuration
- Resets MaxQueueSize to 100
- Fixes hanging issues on av_read_frame/av_seek_frame by changing the interrupt implementation (possible fix also for #46)


Former-commit-id: daded0f
  • Loading branch information
SuRGeoNix committed Jul 27, 2021
1 parent f056a28 commit bf417ed
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 92 deletions.
4 changes: 0 additions & 4 deletions FlyleafLib.sln
Original file line number Diff line number Diff line change
Expand Up @@ -56,28 +56,24 @@ Global
{077262AB-BBF7-4914-B464-0E2D3BA1C921}.Release|x64.ActiveCfg = Release|x64
{077262AB-BBF7-4914-B464-0E2D3BA1C921}.Release|x64.Build.0 = Release|x64
{DCD91C61-1243-4A9D-8567-DDDEFDD4CEF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DCD91C61-1243-4A9D-8567-DDDEFDD4CEF1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DCD91C61-1243-4A9D-8567-DDDEFDD4CEF1}.Debug|x64.ActiveCfg = Debug|Any CPU
{DCD91C61-1243-4A9D-8567-DDDEFDD4CEF1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DCD91C61-1243-4A9D-8567-DDDEFDD4CEF1}.Release|Any CPU.Build.0 = Release|Any CPU
{DCD91C61-1243-4A9D-8567-DDDEFDD4CEF1}.Release|x64.ActiveCfg = Release|Any CPU
{DCD91C61-1243-4A9D-8567-DDDEFDD4CEF1}.Release|x64.Build.0 = Release|Any CPU
{02230750-8033-4A82-AD07-2588264F5A52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{02230750-8033-4A82-AD07-2588264F5A52}.Debug|Any CPU.Build.0 = Debug|Any CPU
{02230750-8033-4A82-AD07-2588264F5A52}.Debug|x64.ActiveCfg = Debug|Any CPU
{02230750-8033-4A82-AD07-2588264F5A52}.Release|Any CPU.ActiveCfg = Release|Any CPU
{02230750-8033-4A82-AD07-2588264F5A52}.Release|Any CPU.Build.0 = Release|Any CPU
{02230750-8033-4A82-AD07-2588264F5A52}.Release|x64.ActiveCfg = Release|Any CPU
{02230750-8033-4A82-AD07-2588264F5A52}.Release|x64.Build.0 = Release|Any CPU
{BCE7C6B3-8EC7-44A1-84BA-89FE95A2B005}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BCE7C6B3-8EC7-44A1-84BA-89FE95A2B005}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BCE7C6B3-8EC7-44A1-84BA-89FE95A2B005}.Debug|x64.ActiveCfg = Debug|Any CPU
{BCE7C6B3-8EC7-44A1-84BA-89FE95A2B005}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BCE7C6B3-8EC7-44A1-84BA-89FE95A2B005}.Release|Any CPU.Build.0 = Release|Any CPU
{BCE7C6B3-8EC7-44A1-84BA-89FE95A2B005}.Release|x64.ActiveCfg = Release|Any CPU
{BCE7C6B3-8EC7-44A1-84BA-89FE95A2B005}.Release|x64.Build.0 = Release|Any CPU
{66BD691C-5084-4B8A-B28B-06ADC198F63B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{66BD691C-5084-4B8A-B28B-06ADC198F63B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66BD691C-5084-4B8A-B28B-06ADC198F63B}.Debug|x64.ActiveCfg = Debug|Any CPU
{66BD691C-5084-4B8A-B28B-06ADC198F63B}.Debug|x64.Build.0 = Debug|Any CPU
{66BD691C-5084-4B8A-B28B-06ADC198F63B}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down
24 changes: 22 additions & 2 deletions FlyleafLib/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,34 @@ public Demuxer Clone()
/// <summary>
/// Maximum allowed packets for buffering
/// </summary>
public int MaxQueueSize { get; set; } = 25;
public int MaxQueueSize { get; set; } = 100;

/// <summary>
/// Maximum allowed errors before stopping
/// </summary>
public int MaxErrors { get; set; } = 30;


/// <summary>
/// av_read_frame timeout (ticks) for protocols that support interrupts
/// </summary>
public long CloseTimeout { get; set; } = 1 * 1000 * 10000;

/// <summary>
/// av_read_frame timeout (ticks) for protocols that support interrupts
/// </summary>
public long OpenTimeout { get; set; } = 30 * 1000 * 10000;

/// <summary>
/// av_read_frame timeout (ticks) for protocols that support interrupts
/// </summary>
public long ReadTimeout { get; set; } = 2 * 1000 * 10000;

/// <summary>
/// av_read_frame timeout (ticks) for protocols that support interrupts
/// </summary>
public long SeekTimeout { get; set; } = 8 * 1000 * 10000;

/// <summary>
/// FFmpeg's format options for audio demuxer
/// </summary>
Expand Down Expand Up @@ -122,7 +143,6 @@ public static SerializableDictionary<string, string> DefaultVideoFormatOpt()
defaults.Add("reconnect_streamed", "1"); // auto reconnect streamed / non seekable streams
defaults.Add("reconnect_delay_max", "5"); // max reconnect delay in seconds after which to give up
defaults.Add("rtsp_transport", "tcp"); // Seems UDP causing issues (use this by default?)
defaults.Add("stimeout", (20 * 1000 * 1000).ToString()); // RTSP microseconds timeout
return defaults;
}

Expand Down
16 changes: 7 additions & 9 deletions FlyleafLib/FlyleafLib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,20 @@
<PackageIconUrl />
<RepositoryUrl></RepositoryUrl>
<Description>Video Player .NET Library for WPF/WinForms (based on FFmpeg/SharpDX)</Description>
<Version>3.1.12</Version>
<Version>3.1.13</Version>
<Authors>SuRGeoNix</Authors>
<Copyright>SuRGeoNix © 2021</Copyright>
<PackageLicenseExpression>LGPL-3.0-or-later</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/SuRGeoNix/Flyleaf</PackageProjectUrl>
<PackageTags>flyleaf flyleaflib video media player engine framework ffmpeg sharpdx directx</PackageTags>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PackageReleaseNotes>Adds FFmpeg's Programs which include streams relationships (reduces bandwidth)
Changes default demuxer's MaxQueueSize from 200 to 25 as it seems that it causes delay issues to av_read_frame
Changes default video decoder's MinVideoFrames from 3 to 10
Fixes dead lock during video stream switch
Fixes rare decoder's race condition (was stoping even the demuxer was actually running)
Fixes .NET 5 subtitles convert issue to default system encoding</PackageReleaseNotes>
<AssemblyVersion>3.1.12.0</AssemblyVersion>
<FileVersion>3.1.12.0</FileVersion>
<PackageReleaseNotes>Adds demuxer timeouts (open/read/seek/close) to the configuration
Resets MaxQueueSize to 100
Fixes network stream issues
Fixes hanging issues on av_read_frame/av_seek_frame by changing the interrupt implementation</PackageReleaseNotes>
<AssemblyVersion>3.1.13.0</AssemblyVersion>
<FileVersion>3.1.13.0</FileVersion>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
Expand Down
16 changes: 10 additions & 6 deletions FlyleafLib/MediaFramework/MediaDecoder/AudioDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,7 @@ public override void Stop()
lock (lockCodecCtx)
{
base.Stop();
while (Frames.Count > 0)
{
Frames.TryDequeue(out AudioFrame aFrame);
if (aFrame != null) aFrame.audioData = new byte[0];
}
Frames = new ConcurrentQueue<AudioFrame>();
DisposeFrames();
if (swrCtx != null) { swr_close(swrCtx); fixed(SwrContext** ptr = &swrCtx) swr_free(ptr); swrCtx = null; }
if (m_dst_data != null) { av_freep(&m_dst_data[0]); fixed (byte*** ptr = &m_dst_data) av_freep(ptr); m_dst_data = null; }
}
Expand Down Expand Up @@ -224,5 +219,14 @@ private AudioFrame ProcessAudioFrame(AVFrame* frame)
return mFrame;
}

public void DisposeFrames()
{
while (Frames.Count > 0)
{
Frames.TryDequeue(out AudioFrame aFrame);
if (aFrame != null) aFrame.audioData = new byte[0];
}
Frames = new ConcurrentQueue<AudioFrame>();
}
}
}
1 change: 1 addition & 0 deletions FlyleafLib/MediaFramework/MediaDecoder/VideoDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ protected override void DecodeInternal()
{
if (ret == AVERROR_EOF)
{
if (demuxer.VideoPackets.Count > 0) { avcodec_flush_buffers(codecCtx); continue; } // TBR: Happens on HLS while switching video streams
Status = Status.Ended;
return;
}
Expand Down
166 changes: 105 additions & 61 deletions FlyleafLib/MediaFramework/MediaDemuxer/DemuxerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ public ConcurrentQueue<IntPtr> GetPacketsPtr(MediaType type)
public long VideoBytes { get; private set; } = 0;
public long AudioBytes { get; private set; } = 0;

internal bool allowProperClose;
internal object lockFmtCtx = new object();
internal GCHandle handle;

Expand All @@ -66,19 +65,50 @@ public ConcurrentQueue<IntPtr> GetPacketsPtr(MediaType type)
AVFormatContext* fmtCtx;
AVPacket* packet;
Config cfg;

long interruptRequestedAt;
InterruptRequester interruptRequester;

enum InterruptRequester
{
Close,
Open,
Read,
Seek
}

AVIOInterruptCB_callback_func interruptClbk = new AVIOInterruptCB_callback_func();
AVIOInterruptCB_callback InterruptClbk = (opaque) =>
{
GCHandle demuxerHandle = (GCHandle)((IntPtr)opaque);
DemuxerBase demuxer = (DemuxerBase)demuxerHandle.Target;

if (demuxer.allowProperClose) return 0;
long curTimeout = 0;
switch (demuxer.interruptRequester)
{
case InterruptRequester.Close:
curTimeout = demuxer.cfg.demuxer.CloseTimeout;
break;

case InterruptRequester.Open:
curTimeout = demuxer.cfg.demuxer.OpenTimeout;
break;

int interrupt = demuxer.DemuxInterrupt != 0 || demuxer.Status == Status.Stopping || demuxer.Status == Status.Stopped ? 1 : 0;
case InterruptRequester.Read:
curTimeout = demuxer.cfg.demuxer.ReadTimeout;
break;

case InterruptRequester.Seek:
curTimeout = demuxer.cfg.demuxer.SeekTimeout;
break;
}

return interrupt;
if (DateTime.UtcNow.Ticks - demuxer.interruptRequestedAt > curTimeout)
{
//demuxer.Log($"{demuxer.interruptRequester} Timeout !!!! {(DateTime.UtcNow.Ticks - demuxer.interruptRequestedAt) / 10000} ms");
return 1;
}

return demuxer.interruptRequester != InterruptRequester.Close && (demuxer.DemuxInterrupt != 0 || demuxer.Status == Status.Stopping || demuxer.Status == Status.Stopped) ? 1 : 0;
};

public DemuxerBase(Config config, int uniqueId)
Expand All @@ -105,66 +135,77 @@ public int Open(string url, Stream stream, Dictionary<string, string> opt, int f
{
int ret = -1;
Url = url;
Status = Status.Opening;

try
lock (lockFmtCtx)
{
if (!handle.IsAllocated) handle = GCHandle.Alloc(this);

// Parse Options to AV Dictionary Format Options
AVDictionary *avopt = null;
foreach (var optKV in opt)
av_dict_set(&avopt, optKV.Key, optKV.Value, 0);

// Allocate / Prepare Format Context
fmtCtx = avformat_alloc_context();
fmtCtx->interrupt_callback.callback = interruptClbk;
fmtCtx->interrupt_callback.opaque = (void*) GCHandle.ToIntPtr(handle);
fmtCtx->flags = flags;

if (stream != null)
CustomIOContext.Initialize(stream);

// Open Format Context
AVFormatContext* fmtCtxPtr = fmtCtx;
lock (lockFmtCtx)
{
try
{
Status = Status.Opening;
if (!handle.IsAllocated) handle = GCHandle.Alloc(this);

// Parse Options to AV Dictionary Format Options
AVDictionary *avopt = null;
foreach (var optKV in opt)
av_dict_set(&avopt, optKV.Key, optKV.Value, 0);

// Allocate / Prepare Format Context
fmtCtx = avformat_alloc_context();
fmtCtx->interrupt_callback.callback = interruptClbk;
fmtCtx->interrupt_callback.opaque = (void*) GCHandle.ToIntPtr(handle);
fmtCtx->flags = flags;

if (stream != null)
CustomIOContext.Initialize(stream);

// Open Format Context
AVFormatContext* fmtCtxPtr = fmtCtx;
interruptRequestedAt = DateTime.UtcNow.Ticks;
interruptRequester = InterruptRequester.Open;
ret = avformat_open_input(&fmtCtxPtr, stream == null ? url : null, null, &avopt);
if (ret == AVERROR_EXIT || Status != Status.Opening || DemuxInterrupt == 1) { Log("[Format] [ERROR-10] Cancelled"); fmtCtx = null; return ret = -10; }
if (ret < 0) { Log($"[Format] [ERROR-1] {Utils.FFmpeg.ErrorCodeToMsg(ret)} ({ret})"); fmtCtx = null; return ret; }
}
if (Status != Status.Opening) return -1;

// Find Streams Info
lock (lockFmtCtx)
{
if (Status != Status.Opening) return -1;
// Find Streams Info
ret = avformat_find_stream_info(fmtCtx, null);
if (ret < 0) { Log($"[Format] [ERROR-2] {Utils.FFmpeg.ErrorCodeToMsg(ret)} ({ret})"); allowProperClose = true; avformat_close_input(&fmtCtxPtr); allowProperClose = false; fmtCtx = null; return ret; }
}
if (Status != Status.Opening) return -1;
if (ret == AVERROR_EXIT || Status != Status.Opening || DemuxInterrupt == 1) { Log("[Format] [ERROR-10] Cancelled"); return ret = -10; }
if (ret < 0) { Log($"[Format] [ERROR-2] {Utils.FFmpeg.ErrorCodeToMsg(ret)} ({ret})"); return ret; }

lock (lockFmtCtx)
{
if (Status != Status.Opening) return -1;
bool hasVideo = FillInfo();

if (Type == MediaType.Video && !hasVideo)
{ Log($"[Format] [ERROR-3] No video stream found"); allowProperClose = true; avformat_close_input(&fmtCtxPtr); allowProperClose = false; fmtCtx = null; return -3; }
{ Log($"[Format] [ERROR-3] No video stream found"); return ret = -3; }
else if (Type == MediaType.Audio && AudioStreams.Count == 0)
{ Log($"[Format] [ERROR-4] No audio stream found"); allowProperClose = true; avformat_close_input(&fmtCtxPtr); allowProperClose = false; fmtCtx = null; return -4; }
{ Log($"[Format] [ERROR-4] No audio stream found"); return ret = -4; }
else if (Type == MediaType.Subs && SubtitlesStreams.Count == 0)
{ Log($"[Format] [ERROR-5] No subtitles stream found"); allowProperClose = true; avformat_close_input(&fmtCtxPtr); allowProperClose = false; fmtCtx = null; return -5; }
{ Log($"[Format] [ERROR-5] No subtitles stream found"); return ret = -5; }

StopThread();
StartThread();

if (ret > 0) ret = 0;
packet = av_packet_alloc();
return ret = 0; // 0 for success
}
}
finally { if (Status == Status.Opening) Status = Status.Stopped; }
finally
{
if (ret != 0)
{
Url = null;

packet = av_packet_alloc();
if (fmtCtx != null)
{
interruptRequestedAt = DateTime.UtcNow.Ticks;
interruptRequester = InterruptRequester.Close;
fixed (AVFormatContext** ptr = &fmtCtx) { avformat_close_input(ptr); fmtCtx = null; }
}

if (stream != null)
CustomIOContext.Dispose();

return ret; // 0 for success
if (Status == Status.Opening)
Status = Status.Stopped;
}
}
}
}

public bool FillInfo()
Expand Down Expand Up @@ -327,9 +368,9 @@ public void Stop()
// Close Format / Custom Contexts
if (fmtCtx != null)
{
allowProperClose = true;
interruptRequestedAt = DateTime.UtcNow.Ticks;
interruptRequester = InterruptRequester.Close;
fixed (AVFormatContext** ptr = &fmtCtx) { avformat_close_input(ptr); fmtCtx = null; }
allowProperClose = false;
}

if (packet != null) fixed (AVPacket** ptr = &packet) av_packet_free(ptr);
Expand All @@ -356,22 +397,24 @@ public int Seek(long ticks, bool foreward = false)

if (Status == Status.Stopped) return -1;

// TBR...
// TBR... (that was happening with the previous interrupt login, might still required tho, related to reconnect_at_eof)
//if (status == Status.Ended) { if (fmtCtx->pb == null) Open(url, decCtx.cfg.audio.Enabled, false); else status = Status.Paused; } //Open(url, ...); // Usefull for HTTP

int ret;

// Interrupt av_read_frame (will cause AVERROR_EXITs)
DemuxInterrupt = 1;
// Interrupt av_read_frame (will cause AVERROR_EXITs, will also cause av_read_frame/av_seek_frame to hang with not reason)
//DemuxInterrupt = 1;
lock (lockFmtCtx)
{
DemuxInterrupt = 0;
//DemuxInterrupt = 0;

// Free Packets
DisposePackets(AudioPackets);
DisposePackets(VideoPackets);
DisposePackets(SubtitlesPackets);

interruptRequestedAt = DateTime.UtcNow.Ticks;
interruptRequester = InterruptRequester.Seek;
if (Type == MediaType.Video)
{
ret = av_seek_frame(fmtCtx, -1, ticks / 10, foreward ? AVSEEK_FLAG_FRAME : AVSEEK_FLAG_BACKWARD); // AVSEEK_FLAG_BACKWARD will not work on .dav even if it returns 0 (it will work after it fills the index table)
Expand Down Expand Up @@ -434,25 +477,26 @@ private void Demux()
gotAVERROR_EXIT = false;
Thread.Sleep(5); // Possible come from seek or Player's stopping...
}

lock (lockFmtCtx)
{
// Demux Packet
interruptRequestedAt = DateTime.UtcNow.Ticks;
interruptRequester = InterruptRequester.Read;
ret = av_read_frame(fmtCtx, packet);
// Check for Errors / End
if (ret != 0)
{
av_packet_unref(packet);
if (ret == AVERROR_EOF) { Status = Status.Ended; break; }

if (ret == AVERROR_EXIT && DemuxInterrupt != 0)
{
gotAVERROR_EXIT = true;
continue;
}
allowedErrors--;
Log($"[ERROR-2] {Utils.FFmpeg.ErrorCodeToMsg(ret)} ({ret})");

Status = Status.Ended;
if (allowedErrors == 0) { Log("[ERROR-0] Too many errors!"); break; }

break;
gotAVERROR_EXIT = true;
continue;
}

TotalBytes += packet->size;
Expand Down
Loading

0 comments on commit bf417ed

Please sign in to comment.