diff --git a/FlyleafLib/Controls/WPF/FlyleafHost.cs b/FlyleafLib/Controls/WPF/FlyleafHost.cs index 230554a..7bce2da 100644 --- a/FlyleafLib/Controls/WPF/FlyleafHost.cs +++ b/FlyleafLib/Controls/WPF/FlyleafHost.cs @@ -7,7 +7,6 @@ using Brushes = System.Windows.Media.Brushes; -using static FlyleafLib.Utils; using static FlyleafLib.Utils.NativeMethods; using FlyleafLib.MediaPlayer; @@ -1104,7 +1103,7 @@ private void Host_LayoutUpdated(object sender, EventArgs e) catch (Exception ex) { // It has been noticed with NavigationService (The visual tree changes, visual root IsVisible is false but FlyleafHost is still visible) - if (Logger.CanDebug) Log.Debug($"Host_LayoutUpdated: {ex.Message}"); + if (CanDebug) Log.Debug($"Host_LayoutUpdated: {ex.Message}"); // TBR: (Currently handle on each time Visible=true) It's possible that the owner/parent has been changed (for some reason Host_Loaded will not be called) *probably when the Owner stays the same but the actual Handle changes //if (ex.Message == "The specified Visual is not an ancestor of this Visual.") @@ -1769,16 +1768,16 @@ public FlyleafHost() return; MarginTarget= this; - Log = new LogHandler(("[#" + UniqueId + "]").PadRight(8, ' ') + $" [FlyleafHost NP] "); + Log = new(("[#" + UniqueId + "]").PadRight(8, ' ') + $" [FlyleafHost NP] "); Loaded += Host_Loaded; } public FlyleafHost(Window standAloneOverlay) { - UniqueId = idGenerator++; - Log = new LogHandler(("[#" + UniqueId + "]").PadRight(8, ' ') + $" [FlyleafHost NP] "); + UniqueId = idGenerator++; + Log = new(("[#" + UniqueId + "]").PadRight(8, ' ') + $" [FlyleafHost NP] "); - IsStandAlone = true; - IsAttached = false; + IsStandAlone= true; + IsAttached = false; this.standAloneOverlay = standAloneOverlay; standAloneOverlay.Loaded += OverlayStandAlone_Loaded; diff --git a/FlyleafLib/Controls/WPF/PlayerDebug.xaml b/FlyleafLib/Controls/WPF/PlayerDebug.xaml index 2e27d17..f413a1f 100644 --- a/FlyleafLib/Controls/WPF/PlayerDebug.xaml +++ b/FlyleafLib/Controls/WPF/PlayerDebug.xaml @@ -50,6 +50,7 @@ + @@ -62,14 +63,9 @@ - + - - - - - @@ -143,8 +139,8 @@ - - + + @@ -211,7 +207,7 @@ - + @@ -315,7 +311,7 @@ - + diff --git a/FlyleafLib/Controls/WPF/RelayCommand.cs b/FlyleafLib/Controls/WPF/RelayCommand.cs index 901e8a5..4d94966 100644 --- a/FlyleafLib/Controls/WPF/RelayCommand.cs +++ b/FlyleafLib/Controls/WPF/RelayCommand.cs @@ -1,5 +1,4 @@ -using System; -using System.Windows.Input; +using System.Windows.Input; namespace FlyleafLib.Controls.WPF; diff --git a/FlyleafLib/Controls/WPF/RelayCommandSimple.cs b/FlyleafLib/Controls/WPF/RelayCommandSimple.cs index 55f75b3..7a827ea 100644 --- a/FlyleafLib/Controls/WPF/RelayCommandSimple.cs +++ b/FlyleafLib/Controls/WPF/RelayCommandSimple.cs @@ -1,5 +1,4 @@ -using System; -using System.Windows.Input; +using System.Windows.Input; namespace FlyleafLib.Controls.WPF; diff --git a/FlyleafLib/Engine/Config.cs b/FlyleafLib/Engine/Config.cs index 0ec8877..041c1fa 100644 --- a/FlyleafLib/Engine/Config.cs +++ b/FlyleafLib/Engine/Config.cs @@ -1,10 +1,6 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; +using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; using System.Windows.Data; using FlyleafLib.Controls.WPF; @@ -15,7 +11,6 @@ using FlyleafLib.MediaPlayer.Translation; using FlyleafLib.MediaPlayer.Translation.Services; using FlyleafLib.Plugins; -using static FlyleafLib.Utils; namespace FlyleafLib; @@ -776,7 +771,7 @@ public System.Windows.Media.Color /// * D3D11 possible performs better with color conversion and filters, FLVP supports only brightness/contrast filters
/// * D3D11 supports deinterlace (bob) /// - public VideoProcessors VideoProcessor { get => _VideoProcessor; set { if (Set(ref _VideoProcessor, value)) player?.renderer?.UpdateVideoProcessor(); } } + public VideoProcessors VideoProcessor { get => _VideoProcessor; set { if (Set(ref _VideoProcessor, value)) player?.renderer?.UpdateVideoProcessor(); } } VideoProcessors _VideoProcessor = VideoProcessors.Auto; /// @@ -791,20 +786,20 @@ public System.Windows.Media.Color public Vortice.DXGI.PresentFlags PresentFlags { get; set; } = Vortice.DXGI.PresentFlags.DoNotWait; - public DeInterlace DeInterlace { get => _DeInterlace; set { if (Set(ref _DeInterlace, value)) player?.renderer?.UpdateDeinterlace(); } } + public DeInterlace DeInterlace { get => _DeInterlace; set { if (Set(ref _DeInterlace, value)) player?.renderer?.UpdateDeinterlace(); } } DeInterlace _DeInterlace = DeInterlace.Auto; /// /// The HDR to SDR method that will be used by the pixel shader /// public unsafe HDRtoSDRMethod - HDRtoSDRMethod { get => _HDRtoSDRMethod; set { if (Set(ref _HDRtoSDRMethod, value) && player != null && player.VideoDecoder.VideoStream != null && player.VideoDecoder.VideoStream.ColorSpace == ColorSpace.BT2020) player.renderer.UpdateHDRtoSDR(); }} + HDRtoSDRMethod { get => _HDRtoSDRMethod; set { if (Set(ref _HDRtoSDRMethod, value)) player?.renderer?.UpdateHDRtoSDR(); }} HDRtoSDRMethod _HDRtoSDRMethod = HDRtoSDRMethod.Hable; /// /// SDR Display Peak Luminance (will be used for HDR to SDR conversion) /// - public unsafe float SDRDisplayNits { get => _SDRDisplayNits; set { if (Set(ref _SDRDisplayNits, value) && player != null && player.VideoDecoder.VideoStream != null && player.VideoDecoder.VideoStream.ColorSpace == ColorSpace.BT2020) player.renderer.UpdateHDRtoSDR(); } } + public unsafe float SDRDisplayNits { get => _SDRDisplayNits; set { if (Set(ref _SDRDisplayNits, value)) player?.renderer?.UpdateHDRtoSDR(); } } float _SDRDisplayNits = Engine.Video != null ? Engine.Video.RecommendedLuminance : 200; /// @@ -1378,7 +1373,7 @@ public Flyleaf.FFmpeg.LogLevel /// :console -> System.Console /// <path> -> Absolute or relative file path /// - public string LogOutput { get => _LogOutput; set { _LogOutput = value; if (Engine.IsLoaded) Logger.SetOutput(); } } + public string LogOutput { get => _LogOutput; set { _LogOutput = value; if (Engine.IsLoaded) SetOutput(); } } string _LogOutput = ""; /// diff --git a/FlyleafLib/Engine/Engine.Audio.cs b/FlyleafLib/Engine/Engine.Audio.cs index 12a20c9..d4f5cbb 100644 --- a/FlyleafLib/Engine/Engine.Audio.cs +++ b/FlyleafLib/Engine/Engine.Audio.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Threading.Tasks; +using System.ComponentModel; using System.Windows.Data; using SharpGen.Runtime; @@ -96,7 +93,7 @@ private void EnumerateDevices() CurrentDevice.Id = defaultDevice.Id; CurrentDevice.Name = defaultDevice.FriendlyName; - if (Logger.CanInfo) + if (CanInfo) { string dump = ""; foreach (var device in deviceEnum.EnumAudioEndpoints(DataFlow.Render, DeviceStates.Active)) @@ -117,7 +114,7 @@ private void EnumerateDevices() } private void RefreshDevices() { - Utils.UIInvokeIfRequired(() => // UI Required? + UIInvokeIfRequired(() => // UI Required? { lock (locker) { diff --git a/FlyleafLib/Engine/Engine.FFmpeg.cs b/FlyleafLib/Engine/Engine.FFmpeg.cs index 30ec7a9..adc760a 100644 --- a/FlyleafLib/Engine/Engine.FFmpeg.cs +++ b/FlyleafLib/Engine/Engine.FFmpeg.cs @@ -13,7 +13,7 @@ internal FFmpegEngine() try { Engine.Log.Info($"Loading FFmpeg libraries from '{Engine.Config.FFmpegPath}'"); - Folder = Utils.GetFolderPath(Engine.Config.FFmpegPath); + Folder = GetFolderPath(Engine.Config.FFmpegPath); LoadLibraries(Folder, Engine.Config.FFmpegLoadProfile); uint ver = avformat_version(); @@ -51,15 +51,15 @@ internal static void SetLogLevel() byte* buffer = stackalloc byte[AV_LOG_BUFFER_SIZE]; int printPrefix = 1; av_log_format_line2(p0, level, format, vl, buffer, AV_LOG_BUFFER_SIZE, &printPrefix); - string line = Utils.BytePtrToStringUTF8(buffer); + string line = BytePtrToStringUTF8(buffer); - Logger.Output($"FFmpeg|{level,-7}|{line.Trim()}"); + Output($"FFmpeg|{level,-7}|{line.Trim()}"); }; internal unsafe static string ErrorCodeToMsg(int error) { byte* buffer = stackalloc byte[AV_LOG_BUFFER_SIZE]; av_strerror(error, buffer, AV_LOG_BUFFER_SIZE); - return Utils.BytePtrToStringUTF8(buffer); + return BytePtrToStringUTF8(buffer); } } diff --git a/FlyleafLib/Engine/Engine.Plugins.cs b/FlyleafLib/Engine/Engine.Plugins.cs index 9a9f76b..4fb80ae 100644 --- a/FlyleafLib/Engine/Engine.Plugins.cs +++ b/FlyleafLib/Engine/Engine.Plugins.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.IO; -using System.Reflection; +using System.Reflection; using FlyleafLib.Plugins; @@ -17,7 +15,7 @@ public Dictionary internal PluginsEngine() { - Folder = string.IsNullOrEmpty(Engine.Config.PluginsPath) ? null : Utils.GetFolderPath(Engine.Config.PluginsPath); + Folder = string.IsNullOrEmpty(Engine.Config.PluginsPath) ? null : GetFolderPath(Engine.Config.PluginsPath); LoadAssemblies(); } @@ -69,6 +67,6 @@ public void LoadPlugin(Assembly assembly) } } } - catch (Exception e) { Engine.Log.Error($"[PluginHandler] [Error] Failed to load assembly ({e.Message} {Utils.GetRecInnerException(e)})"); } + catch (Exception e) { Engine.Log.Error($"[PluginHandler] [Error] Failed to load assembly ({e.Message} {GetRecInnerException(e)})"); } } } diff --git a/FlyleafLib/Engine/Engine.Video.cs b/FlyleafLib/Engine/Engine.Video.cs index 326e600..de6bcc5 100644 --- a/FlyleafLib/Engine/Engine.Video.cs +++ b/FlyleafLib/Engine/Engine.Video.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Windows.Data; +using System.Windows.Data; using Vortice.DXGI; using Vortice.MediaFoundation; diff --git a/FlyleafLib/Engine/Engine.cs b/FlyleafLib/Engine/Engine.cs index 5bbff41..25f4848 100644 --- a/FlyleafLib/Engine/Engine.cs +++ b/FlyleafLib/Engine/Engine.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Threading; -using System.Windows; +using System.Windows; using FlyleafLib.MediaPlayer; @@ -82,7 +79,7 @@ public static void TimeBeginPeriod1() if (timePeriod == 1) { Log.Trace("timeBeginPeriod(1)"); - Utils.NativeMethods.TimeBeginPeriod(1); + NativeMethods.TimeBeginPeriod(1); } } } @@ -99,7 +96,7 @@ public static void TimeEndPeriod1() if (timePeriod == 0) { Log.Trace("timeEndPeriod(1)"); - Utils.NativeMethods.TimeEndPeriod(1); + NativeMethods.TimeEndPeriod(1); } } } @@ -116,7 +113,7 @@ private static void StartInternal(EngineConfig config = null, bool async = false Config = config ?? new EngineConfig(); if (Application.Current == null) - new Application(); + _ = new Application(); StartInternalUI(); @@ -138,9 +135,9 @@ private static void StartInternalUI() Players[0].Dispose(); }; - Logger.SetOutput(); - Log = new LogHandler("[FlyleafEngine] "); - Audio = new AudioEngine(); + SetOutput(); + Log = new("[FlyleafEngine] "); + Audio = new(); } private static void StartInternalNonUI() @@ -148,9 +145,9 @@ private static void StartInternalNonUI() var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; Log.Info($"FlyleafLib {version.Major }.{version.Minor}.{version.Build}"); - FFmpeg = new FFmpegEngine(); - Video = new VideoEngine(); - Plugins = new PluginsEngine(); + FFmpeg = new(); + Video = new(); + Plugins = new(); Players = []; IsLoaded= true; @@ -325,7 +322,7 @@ void UIAction() catch { } } - Utils.UI(UIAction); + UI(UIAction); Thread.Sleep(Config.UIRefreshInterval); } catch { curLoop = 0; } diff --git a/FlyleafLib/Engine/Globals.cs b/FlyleafLib/Engine/Globals.cs index 99ee427..bbb1611 100644 --- a/FlyleafLib/Engine/Globals.cs +++ b/FlyleafLib/Engine/Globals.cs @@ -1,8 +1,18 @@ global using System; +global using System.Collections.Concurrent; +global using System.Collections.Generic; +global using System.Collections.ObjectModel; +global using System.IO; +global using System.Text; +global using System.Threading; +global using System.Threading.Tasks; + global using Flyleaf.FFmpeg; + global using static Flyleaf.FFmpeg.Raw; +global using static FlyleafLib.Logger; +global using static FlyleafLib.Utils; -using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Runtime.CompilerServices; @@ -61,9 +71,9 @@ public enum ZeroCopy : int public enum ColorSpace : int { None = 0, - BT601 = 1, - BT709 = 2, - BT2020 = 3 + Bt601 = 1, + Bt709 = 2, + Bt2020 = 3 } public enum ColorRange : int { @@ -71,6 +81,12 @@ public enum ColorRange : int Full = 1, Limited = 2 } +public enum ColorType +{ + YUV, + RGB, + Gray +} public enum HDRFormat : int { None = 0, @@ -113,7 +129,7 @@ public class GPUOutput public override string ToString() { - int gcd = Utils.GCD(Width, Height); + int gcd = GCD(Width, Height); return $"{DeviceName,-20} [Id: {Id,-4}\t, Top: {Top,-4}, Left: {Left,-4}, Width: {Width,-4}, Height: {Height,-4}, Ratio: [" + (gcd > 0 ? $"{Width/gcd}:{Height/gcd}]" : "]"); } } @@ -135,7 +151,7 @@ public List Outputs { get; internal set; } public override string ToString() - => (Vendor + " " + Description).PadRight(40) + $"[ID: {Id,-6}, LUID: {Luid,-6}, DVM: {Utils.GetBytesReadable(VideoMemory),-8}, DSM: {Utils.GetBytesReadable(SystemMemory),-8}, SSM: {Utils.GetBytesReadable(SharedMemory)}]"; + => (Vendor + " " + Description).PadRight(40) + $"[ID: {Id,-6}, LUID: {Luid,-6}, DVM: {GetBytesReadable(VideoMemory),-8}, DSM: {GetBytesReadable(SystemMemory),-8}, SSM: {GetBytesReadable(SharedMemory)}]"; } public enum VideoFilters { @@ -246,7 +262,7 @@ public class NotifyPropertyChanged : INotifyPropertyChanged protected bool Set(ref T field, T value, bool check = true, [CallerMemberName] string propertyName = "") { - //Utils.Log($"[===| {propertyName} |===] | Set | {IsUI()}"); + //Log($"[===| {propertyName} |===] | Set | {IsUI()}"); if (!check || !EqualityComparer.Default.Equals(field, value)) { @@ -263,14 +279,14 @@ protected bool Set(ref T field, T value, bool check = true, [CallerMemberName protected bool SetUI(ref T field, T value, bool check = true, [CallerMemberName] string propertyName = "") { - //Utils.Log($"[===| {propertyName} |===] | SetUI | {IsUI()}"); + //Log($"[===| {propertyName} |===] | SetUI | {IsUI()}"); if (!check || !EqualityComparer.Default.Equals(field, value)) { field = value; //if (!DisableNotifications) - Utils.UI(() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName))); + UI(() => PropertyChanged?.Invoke(this, new(propertyName))); return true; } @@ -279,18 +295,18 @@ protected bool SetUI(ref T field, T value, bool check = true, [CallerMemberNa } protected void Raise([CallerMemberName] string propertyName = "") { - //Utils.Log($"[===| {propertyName} |===] | Raise | {IsUI()}"); + //Log($"[===| {propertyName} |===] | Raise | {IsUI()}"); //if (!DisableNotifications) - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + PropertyChanged?.Invoke(this, new(propertyName)); } protected void RaiseUI([CallerMemberName] string propertyName = "") { - //Utils.Log($"[===| {propertyName} |===] | RaiseUI | {IsUI()}"); + //Log($"[===| {propertyName} |===] | RaiseUI | {IsUI()}"); //if (!DisableNotifications) - Utils.UI(() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName))); + UI(() => PropertyChanged?.Invoke(this, new(propertyName))); } } diff --git a/FlyleafLib/Engine/Language.cs b/FlyleafLib/Engine/Language.cs index e9df916..547156c 100644 --- a/FlyleafLib/Engine/Language.cs +++ b/FlyleafLib/Engine/Language.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Globalization; +using System.Globalization; using System.Linq; using System.Text.Json.Serialization; diff --git a/FlyleafLib/FlyleafLib.csproj b/FlyleafLib/FlyleafLib.csproj index d9d4ce4..b5f47a5 100644 --- a/FlyleafLib/FlyleafLib.csproj +++ b/FlyleafLib/FlyleafLib.csproj @@ -7,7 +7,7 @@ true Media Player .NET Library for WinUI 3/WPF/WinForms (based on FFmpeg/DirectX) - 3.8.8 + 3.8.9 SuRGeoNix SuRGeoNix © 2025 GPL-3.0-or-later diff --git a/FlyleafLib/MediaFramework/MediaContext/DecoderContext.Open.cs b/FlyleafLib/MediaFramework/MediaContext/DecoderContext.Open.cs index 9690a10..2480cec 100644 --- a/FlyleafLib/MediaFramework/MediaContext/DecoderContext.Open.cs +++ b/FlyleafLib/MediaFramework/MediaContext/DecoderContext.Open.cs @@ -1,15 +1,9 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; +using System.Linq; using FlyleafLib.MediaFramework.MediaDemuxer; using FlyleafLib.MediaFramework.MediaPlaylist; using FlyleafLib.MediaFramework.MediaStream; using FlyleafLib.MediaPlayer; -using static FlyleafLib.Logger; -using static FlyleafLib.Utils; namespace FlyleafLib.MediaFramework.MediaContext; diff --git a/FlyleafLib/MediaFramework/MediaContext/DecoderContext.cs b/FlyleafLib/MediaFramework/MediaContext/DecoderContext.cs index 8a349a1..d27e399 100644 --- a/FlyleafLib/MediaFramework/MediaContext/DecoderContext.cs +++ b/FlyleafLib/MediaFramework/MediaContext/DecoderContext.cs @@ -5,9 +5,6 @@ using FlyleafLib.MediaPlayer; using FlyleafLib.Plugins; -using static FlyleafLib.Logger; -using static FlyleafLib.Utils; - namespace FlyleafLib.MediaFramework.MediaContext; public unsafe partial class DecoderContext : PluginHandler @@ -143,34 +140,34 @@ public SubtitlesStream[] SubtitlesStreams public DecoderContext(Config config = null, int uniqueId = -1, bool enableDecoding = true) : base(config, uniqueId) { - Log = new LogHandler(("[#" + UniqueId + "]").PadRight(8, ' ') + " [DecoderContext] "); + Log = new(("[#" + UniqueId + "]").PadRight(8, ' ') + " [DecoderContext] "); Playlist.decoder = this; EnableDecoding = enableDecoding; - AudioDemuxer = new Demuxer(Config.Demuxer, MediaType.Audio, UniqueId, EnableDecoding); - VideoDemuxer = new Demuxer(Config.Demuxer, MediaType.Video, UniqueId, EnableDecoding); + AudioDemuxer = new(Config.Demuxer, MediaType.Audio, UniqueId, EnableDecoding); + VideoDemuxer = new(Config.Demuxer, MediaType.Video, UniqueId, EnableDecoding); SubtitlesDemuxers = new Demuxer[subNum]; for (int i = 0; i < subNum; i++) { - SubtitlesDemuxers[i] = new Demuxer(Config.Demuxer, MediaType.Subs, UniqueId, EnableDecoding); + SubtitlesDemuxers[i] = new(Config.Demuxer, MediaType.Subs, UniqueId, EnableDecoding); } - DataDemuxer = new Demuxer(Config.Demuxer, MediaType.Data, UniqueId, EnableDecoding); + DataDemuxer = new(Config.Demuxer, MediaType.Data, UniqueId, EnableDecoding); - SubtitlesManager = new SubtitlesManager(Config, subNum); - SubtitlesOCR = new SubtitlesOCR(Config.Subtitles, subNum); - SubtitlesASR = new SubtitlesASR(SubtitlesManager, Config); + SubtitlesManager = new(Config, subNum); + SubtitlesOCR = new(Config.Subtitles, subNum); + SubtitlesASR = new(SubtitlesManager, Config); - Recorder = new Remuxer(UniqueId); + Recorder = new(UniqueId); - VideoDecoder = new VideoDecoder(Config, UniqueId); - AudioDecoder = new AudioDecoder(Config, UniqueId, VideoDecoder); + VideoDecoder = new(Config, UniqueId); + AudioDecoder = new(Config, UniqueId, VideoDecoder); SubtitlesDecoders = new SubtitlesDecoder[subNum]; for (int i = 0; i < subNum; i++) { - SubtitlesDecoders[i] = new SubtitlesDecoder(Config, UniqueId, i); + SubtitlesDecoders[i] = new(Config, UniqueId, i); } - DataDecoder = new DataDecoder(Config, UniqueId); + DataDecoder = new(Config, UniqueId); if (EnableDecoding && config.Player.Usage != MediaPlayer.Usage.Audio) VideoDecoder.CreateRenderer(); @@ -412,7 +409,7 @@ private long CalcSeekTimestamp(Demuxer demuxer, long ms, ref bool forward) ticks = startTime; forward = true; } - else if (ticks > startTime + (!VideoDemuxer.Disposed ? VideoDemuxer.Duration : AudioDemuxer.Duration) - (50 * 10000)) + else if (ticks > startTime + (!VideoDemuxer.Disposed ? VideoDemuxer.Duration : AudioDemuxer.Duration) - (50 * 10000) && demuxer.Duration > 0) // demuxer.Duration > 0 (allow blindly when duration 0) { ticks = Math.Max(startTime, startTime + demuxer.Duration - (50 * 10000)); forward = false; @@ -674,7 +671,7 @@ public long GetVideoFrame(long timestamp = -1) if (codecType == AVMediaType.Video && VideoDecoder.keyPacketRequired) { - if (packet->flags.HasFlag(PktFlags.Key)) + if (packet->flags.HasFlag(PktFlags.Key) || packet->pts == VideoDecoder.startPts) VideoDecoder.keyPacketRequired = false; else { @@ -788,7 +785,7 @@ public long GetVideoFrame(long timestamp = -1) continue; } - //if (CanInfo) Info($"Asked for {Utils.TicksToTime(timestamp)} and got {Utils.TicksToTime((long)(frame->pts * VideoStream.Timebase) - VideoDemuxer.StartTime)} | Diff {Utils.TicksToTime(timestamp - ((long)(frame->pts * VideoStream.Timebase) - VideoDemuxer.StartTime))}"); + //if (CanInfo) Info($"Asked for {TicksToTime(timestamp)} and got {TicksToTime((long)(frame->pts * VideoStream.Timebase) - VideoDemuxer.StartTime)} | Diff {TicksToTime(timestamp - ((long)(frame->pts * VideoStream.Timebase) - VideoDemuxer.StartTime))}"); VideoDecoder.StartTime = (long)(frame->pts * VideoStream.Timebase) - VideoDemuxer.StartTime; var mFrame = VideoDecoder.Renderer.FillPlanes(frame); diff --git a/FlyleafLib/MediaFramework/MediaContext/Downloader.cs b/FlyleafLib/MediaFramework/MediaContext/Downloader.cs index 1395648..576683d 100644 --- a/FlyleafLib/MediaFramework/MediaContext/Downloader.cs +++ b/FlyleafLib/MediaFramework/MediaContext/Downloader.cs @@ -1,12 +1,6 @@ -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -using FlyleafLib.MediaFramework.MediaDemuxer; +using FlyleafLib.MediaFramework.MediaDemuxer; using FlyleafLib.MediaFramework.MediaRemuxer; -using static FlyleafLib.Logger; - /* TODO * Don't let audio go further than video (because of large duration without video)? * @@ -279,7 +273,7 @@ protected override void RunInternal() isAudioDemuxer = false; } - //Log($"[{isAudioDemuxer}] {Utils.TicksToTime(ts1)} | {Utils.TicksToTime(ts2)}"); + //Log($"[{isAudioDemuxer}] {TicksToTime(ts1)} | {TicksToTime(ts2)}"); } } else diff --git a/FlyleafLib/MediaFramework/MediaDecoder/AudioDecoder.Filters.cs b/FlyleafLib/MediaFramework/MediaDecoder/AudioDecoder.Filters.cs index 8defa18..ecdf2ce 100644 --- a/FlyleafLib/MediaFramework/MediaDecoder/AudioDecoder.Filters.cs +++ b/FlyleafLib/MediaFramework/MediaDecoder/AudioDecoder.Filters.cs @@ -1,11 +1,8 @@ using System.Runtime.InteropServices; -using System.Threading; using FlyleafLib.MediaFramework.MediaStream; using FlyleafLib.MediaFramework.MediaFrame; -using static FlyleafLib.Logger; - namespace FlyleafLib.MediaFramework.MediaDecoder; public unsafe partial class AudioDecoder @@ -171,7 +168,7 @@ protected override void OnSpeedChanged(double value) internal void FixSample(AudioFrame frame, double oldSpeed, double speed) { var oldDataLen = frame.dataLen; - frame.dataLen = Utils.Align((int) (oldDataLen * oldSpeed / speed), ASampleBytes); + frame.dataLen = Align((int) (oldDataLen * oldSpeed / speed), ASampleBytes); fixed (byte* cBufStartPosPtr = &cBuf[0]) { var curOffset = (long)frame.dataPtr - (long)cBufStartPosPtr; @@ -256,7 +253,7 @@ private void ProcessFilters() else if (Math.Abs(frame->pts - nextPts) > 10 * 10000) // 10ms distance should resync filters (TBR: it should be 0ms however we might get 0 pkt_duration for unknown?) { DrainFilters(); - Log.Warn($"Resync filters! ({Utils.TicksToTime((long)((frame->pts - nextPts) * AudioStream.Timebase))} distance)"); + Log.Warn($"Resync filters! ({TicksToTime((long)((frame->pts - nextPts) * AudioStream.Timebase))} distance)"); //resyncWithVideoRequired = !VideoDecoder.Disposed; DisposeFrames(); avcodec_flush_buffers(codecCtx); @@ -361,12 +358,12 @@ private void ProcessFilter() timestamp = (long)((newPts * AudioStream.Timebase) - demuxer.StartTime + Config.Audio.Delay) }; - if (CanTrace) Log.Trace($"Processes {Utils.TicksToTime(mFrame.timestamp)}"); + if (CanTrace) Log.Trace($"Processes {TicksToTime(mFrame.timestamp)}"); fixed (byte* circularBufferPosPtr = &cBuf[cBufPos]) mFrame.dataPtr = (IntPtr)circularBufferPosPtr; - Marshal.Copy((IntPtr) filtframe->data[0], cBuf, cBufPos, mFrame.dataLen); + Marshal.Copy(filtframe->data[0], cBuf, cBufPos, mFrame.dataLen); cBufPos += curLen; Frames.Enqueue(mFrame); diff --git a/FlyleafLib/MediaFramework/MediaDecoder/AudioDecoder.cs b/FlyleafLib/MediaFramework/MediaDecoder/AudioDecoder.cs index 31a01c9..335074f 100644 --- a/FlyleafLib/MediaFramework/MediaDecoder/AudioDecoder.cs +++ b/FlyleafLib/MediaFramework/MediaDecoder/AudioDecoder.cs @@ -1,12 +1,7 @@ -using System.Collections.Concurrent; -using System.Threading; - -using FlyleafLib.MediaFramework.MediaStream; +using FlyleafLib.MediaFramework.MediaStream; using FlyleafLib.MediaFramework.MediaFrame; using FlyleafLib.MediaFramework.MediaRemuxer; -using static FlyleafLib.Logger; - namespace FlyleafLib.MediaFramework.MediaDecoder; /* TODO @@ -130,9 +125,9 @@ public void Flush() protected override void RunInternal() { - int ret = 0; - int allowedErrors = Config.Decoder.MaxErrors; - int sleepMs = Config.Decoder.MaxAudioFrames > 5 && Config.Player.MaxLatency == 0 ? 10 : 4; + int allowedErrors = Config.Decoder.MaxErrors; + int sleepMs = Config.Decoder.MaxAudioFrames > 5 && Config.Player.MaxLatency == 0 ? 10 : 4; + int ret; AVPacket *packet; do @@ -296,7 +291,7 @@ protected override void RunInternal() if (frame->duration <= 0) frame->duration = av_rescale_q((long)(frame->nb_samples * sampleRateTimebase), Engine.FFmpeg.AV_TIMEBASE_Q, Stream.AVStream->time_base); - bool codecChanged = AudioStream.SampleFormat != codecCtx->sample_fmt || AudioStream.SampleRate != codecCtx->sample_rate || AudioStream.ChannelLayout != codecCtx->ch_layout.u.mask; + codecChanged = AudioStream.SampleFormat != codecCtx->sample_fmt || AudioStream.SampleRate != codecCtx->sample_rate || AudioStream.ChannelLayout != codecCtx->ch_layout.u.mask; if (!filledFromCodec || codecChanged) { @@ -305,20 +300,18 @@ protected override void RunInternal() byte[] buf = new byte[50]; fixed (byte* bufPtr = buf) { - av_channel_layout_describe(&codecCtx->ch_layout, bufPtr, (nuint)buf.Length); - Log.Warn($"Codec changed {AudioStream.CodecIDOrig} {AudioStream.SampleFormat} {AudioStream.SampleRate} {AudioStream.ChannelLayoutStr} => {codecCtx->codec_id} {codecCtx->sample_fmt} {codecCtx->sample_rate} {Utils.BytePtrToStringUTF8(bufPtr)}"); + _ = av_channel_layout_describe(&codecCtx->ch_layout, bufPtr, (nuint)buf.Length); + Log.Warn($"Codec changed {AudioStream.CodecIDOrig} {AudioStream.SampleFormat} {AudioStream.SampleRate} {AudioStream.ChannelLayoutStr} => {codecCtx->codec_id} {codecCtx->sample_fmt} {codecCtx->sample_rate} {BytePtrToStringUTF8(bufPtr)}"); } } DisposeInternal(); - filledFromCodec = true; - - avcodec_parameters_from_context(Stream.AVStream->codecpar, codecCtx); - AudioStream.AVStream->time_base = codecCtx->pkt_timebase; - AudioStream.Refresh(); + filledFromCodec = true; + AudioStream.Refresh(this, frame); + codecChanged = false; resyncWithVideoRequired = !VideoDecoder.Disposed; - sampleRateTimebase = 1000 * 1000.0 / codecCtx->sample_rate; - nextPts = AudioStream.StartTimePts; + sampleRateTimebase = 1000 * 1000.0 / codecCtx->sample_rate; + nextPts = AudioStream.StartTimePts; if (frame->pts == AV_NOPTS_VALUE) frame->pts = nextPts; @@ -351,7 +344,7 @@ protected override void RunInternal() if (ts < VideoDecoder.StartTime) { - if (CanTrace) Log.Trace($"Drops {Utils.TicksToTime(ts)} (< V: {Utils.TicksToTime(VideoDecoder.StartTime)})"); + if (CanTrace) Log.Trace($"Drops {TicksToTime(ts)} (< V: {TicksToTime(VideoDecoder.StartTime)})"); av_frame_unref(frame); continue; } @@ -386,14 +379,14 @@ private void Process() nextPts = frame->pts + frame->duration; var dataLen = frame->nb_samples * ASampleBytes; - var speedDataLen= Utils.Align((int)(dataLen / speed), ASampleBytes); + var speedDataLen= Align((int)(dataLen / speed), ASampleBytes); AudioFrame mFrame = new() { timestamp = (long)(frame->pts * AudioStream.Timebase) - demuxer.StartTime + Config.Audio.Delay, dataLen = speedDataLen }; - if (CanTrace) Log.Trace($"Processes {Utils.TicksToTime(mFrame.timestamp)}"); + if (CanTrace) Log.Trace($"Processes {TicksToTime(mFrame.timestamp)}"); if (frame->nb_samples > cBufSamples) AllocateCircularBuffer(frame->nb_samples); diff --git a/FlyleafLib/MediaFramework/MediaDecoder/DataDecoder.cs b/FlyleafLib/MediaFramework/MediaDecoder/DataDecoder.cs index b80c78f..cdcb830 100644 --- a/FlyleafLib/MediaFramework/MediaDecoder/DataDecoder.cs +++ b/FlyleafLib/MediaFramework/MediaDecoder/DataDecoder.cs @@ -1,6 +1,4 @@ -using System.Threading; -using System.Collections.Concurrent; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; using FlyleafLib.MediaFramework.MediaFrame; using FlyleafLib.MediaFramework.MediaStream; @@ -11,14 +9,14 @@ public unsafe class DataDecoder : DecoderBase public DataStream DataStream => (DataStream)Stream; public ConcurrentQueue - Frames { get; protected set; } = new ConcurrentQueue(); + Frames { get; protected set; } = []; public DataDecoder(Config config, int uniqueId = -1) : base(config, uniqueId) { } protected override unsafe int Setup(AVCodec* codec) => 0; protected override void DisposeInternal() - => Frames = new ConcurrentQueue(); + => Frames = []; public void Flush() { @@ -142,7 +140,7 @@ protected override void RunInternal() private DataFrame ProcessDataFrame(AVPacket* packet) { - IntPtr ptr = new IntPtr(packet->data); + IntPtr ptr = new(packet->data); byte[] dataFrame = new byte[packet->size]; Marshal.Copy(ptr, dataFrame, 0, packet->size); // for performance/in case of large data we could just keep the avpacket data directly (DataFrame instead of byte[] Data just use AVPacket* and move ref?) @@ -150,12 +148,12 @@ private DataFrame ProcessDataFrame(AVPacket* packet) { timestamp = (long)(packet->pts * DataStream.Timebase) - demuxer.StartTime, DataCodecId = DataStream.CodecID, - Data = dataFrame + Data = dataFrame }; return mFrame; } public void DisposeFrames() - => Frames = new ConcurrentQueue(); + => Frames = []; } diff --git a/FlyleafLib/MediaFramework/MediaDecoder/DecoderBase.cs b/FlyleafLib/MediaFramework/MediaDecoder/DecoderBase.cs index d48c089..f084825 100644 --- a/FlyleafLib/MediaFramework/MediaDecoder/DecoderBase.cs +++ b/FlyleafLib/MediaFramework/MediaDecoder/DecoderBase.cs @@ -20,6 +20,7 @@ public abstract unsafe class DecoderBase : RunThreadBase protected double speed = 1, oldSpeed = 1; protected virtual void OnSpeedChanged(double value) { } + internal bool codecChanged; internal bool filledFromCodec; protected AVFrame* frame; protected AVCodecContext* codecCtx; @@ -115,7 +116,7 @@ protected string Open2(StreamBase stream, StreamBase prevStream, bool openStream AVDictionaryEntry *t = null; while ((t = av_dict_get(avopt, "", t, DictReadFlags.IgnoreSuffix)) != null) - Log.Debug($"Ignoring codec option {Utils.BytePtrToStringUTF8(t->key)}"); + Log.Debug($"Ignoring codec option {BytePtrToStringUTF8(t->key)}"); } av_dict_free(&avopt); diff --git a/FlyleafLib/MediaFramework/MediaDecoder/SubtitlesDecoder.cs b/FlyleafLib/MediaFramework/MediaDecoder/SubtitlesDecoder.cs index d80fa20..db7265b 100644 --- a/FlyleafLib/MediaFramework/MediaDecoder/SubtitlesDecoder.cs +++ b/FlyleafLib/MediaFramework/MediaDecoder/SubtitlesDecoder.cs @@ -1,11 +1,9 @@ -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Threading; +using System.Diagnostics; + using FlyleafLib.MediaFramework.MediaDemuxer; using FlyleafLib.MediaFramework.MediaStream; using FlyleafLib.MediaFramework.MediaFrame; using FlyleafLib.MediaFramework.MediaRenderer; -using static FlyleafLib.Logger; namespace FlyleafLib.MediaFramework.MediaDecoder; @@ -14,7 +12,7 @@ public unsafe class SubtitlesDecoder : DecoderBase public SubtitlesStream SubtitlesStream => (SubtitlesStream) Stream; public ConcurrentQueue - Frames { get; protected set; } = new ConcurrentQueue(); + Frames { get; protected set; } = []; public PacketQueue SubtitlesPackets; @@ -172,9 +170,7 @@ protected override void RunInternal() if (!filledFromCodec) // TODO: CodecChanged? And when findstreaminfo is disabled as it is an external demuxer will not know the main demuxer's start time { filledFromCodec = true; - avcodec_parameters_from_context(Stream.AVStream->codecpar, codecCtx); - SubtitlesStream.Refresh(); - + SubtitlesStream.Refresh(this); CodecChanged?.Invoke(this); } @@ -199,7 +195,7 @@ protected override void RunInternal() if (subFrame.sub.rects[0]->type == AVSubtitleType.Ass) { - subFrame.text = Utils.BytePtrToStringUTF8(subFrame.sub.rects[0]->ass).Trim(); + subFrame.text = BytePtrToStringUTF8(subFrame.sub.rects[0]->ass).Trim(); Config.Subtitles.Parser(subFrame); fixed(AVSubtitle* subPtr = &subFrame.sub) @@ -210,7 +206,7 @@ protected override void RunInternal() } else if (subFrame.sub.rects[0]->type == AVSubtitleType.Text) { - subFrame.text = Utils.BytePtrToStringUTF8(subFrame.sub.rects[0]->text).Trim(); + subFrame.text = BytePtrToStringUTF8(subFrame.sub.rects[0]->text).Trim(); fixed(AVSubtitle* subPtr = &subFrame.sub) avsubtitle_free(subPtr); @@ -234,7 +230,7 @@ protected override void RunInternal() }; } - if (CanTrace) Log.Trace($"Processes {Utils.TicksToTime(subFrame.timestamp)}"); + if (CanTrace) Log.Trace($"Processes {TicksToTime(subFrame.timestamp)}"); Frames.Enqueue(subFrame); } diff --git a/FlyleafLib/MediaFramework/MediaDecoder/VideoDecoder.cs b/FlyleafLib/MediaFramework/MediaDecoder/VideoDecoder.cs index e11f0b9..1716c25 100644 --- a/FlyleafLib/MediaFramework/MediaDecoder/VideoDecoder.cs +++ b/FlyleafLib/MediaFramework/MediaDecoder/VideoDecoder.cs @@ -1,7 +1,4 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Threading; +using System.Runtime.InteropServices; using Vortice.DXGI; using Vortice.Direct3D11; @@ -13,8 +10,6 @@ using FlyleafLib.MediaFramework.MediaRenderer; using FlyleafLib.MediaFramework.MediaRemuxer; -using static FlyleafLib.Logger; - namespace FlyleafLib.MediaFramework.MediaDecoder; public unsafe class VideoDecoder : DecoderBase @@ -41,6 +36,8 @@ public ConcurrentQueue internal bool swFallback; internal bool keyPacketRequired; + internal bool isIntraOnly; + internal long startPts; internal long lastFixedPts; bool checkExtraFrames; // DecodeFrameNext @@ -211,8 +208,10 @@ private AVPixelFormat get_format(AVCodecContext* avctx, AVPixelFormat* pix_fmts) // TBR: Catch codec changed on live streams (check codec/profiles and check even on sw frames) if (ret == 2) { - Log.Warn($"Codec changed {VideoStream.CodecID} {VideoStream.Width}x{VideoStream.Height} => {codecCtx->codec_id} {codecCtx->width}x{codecCtx->height}"); + // NOTE: It seems that codecCtx changes but upcoming frame still has previous configuration (this will fire FillFromCodec twice, could cause issues?) filledFromCodec = false; + codecChanged = true; + Log.Warn($"Codec changed {VideoStream.CodecID} {VideoStream.Width}x{VideoStream.Height} => {codecCtx->codec_id} {codecCtx->width}x{codecCtx->height}"); } return PIX_FMT_HWACCEL; @@ -270,7 +269,11 @@ private int AllocateHWFrames() lock (Renderer.lockDevice) { textureFFmpeg = new ID3D11Texture2D((nint) va_frames_ctx->texture); - ZeroCopy = Config.Decoder.ZeroCopy == FlyleafLib.ZeroCopy.Enabled || (Config.Decoder.ZeroCopy == FlyleafLib.ZeroCopy.Auto && codecCtx->width == textureFFmpeg.Description.Width && codecCtx->height == textureFFmpeg.Description.Height); + ZeroCopy = + Config.Decoder.ZeroCopy == FlyleafLib.ZeroCopy.Enabled || + (Config.Decoder.ZeroCopy == FlyleafLib.ZeroCopy.Auto && + codecCtx->width == textureFFmpeg.Description.Width && + codecCtx->height == textureFFmpeg.Description.Height); filledFromCodec = false; } } @@ -282,7 +285,11 @@ internal void RecalculateZeroCopy() lock (Renderer.lockDevice) { bool save = ZeroCopy; - ZeroCopy = VideoAccelerated && (Config.Decoder.ZeroCopy == FlyleafLib.ZeroCopy.Enabled || (Config.Decoder.ZeroCopy == FlyleafLib.ZeroCopy.Auto && codecCtx->width == textureFFmpeg.Description.Width && codecCtx->height == textureFFmpeg.Description.Height)); + ZeroCopy = VideoAccelerated && + (Config.Decoder.ZeroCopy == FlyleafLib.ZeroCopy.Enabled || + (Config.Decoder.ZeroCopy == FlyleafLib.ZeroCopy.Auto && + codecCtx->width == textureFFmpeg.Description.Width && + codecCtx->height == textureFFmpeg.Description.Height)); if (save != ZeroCopy) { Renderer?.ConfigPlanes(); @@ -321,9 +328,9 @@ protected override int Setup(AVCodec* codec) //var t2 = av_stream_get_side_data(VideoStream.AVStream, AVPacketSideDataType.AV_PKT_DATA_CONTENT_LIGHT_LEVEL, null); // TBR: during swFallback (keyFrameRequiredPacket should not reset, currenlty saved in SWFallback) - keyPacketRequired = false; // allow no key packet after open (lot of videos missing this) - ZeroCopy = false; - filledFromCodec = false; + keyPacketRequired = false; // allow no key packet after open (lot of videos missing this) + ZeroCopy = false; + filledFromCodec = false; lastFixedPts = 0; // TBR: might need to set this to first known pts/dts @@ -340,6 +347,11 @@ protected override int Setup(AVCodec* codec) else codecCtx->thread_count = Math.Min(Config.Decoder.VideoThreads, codecCtx->codec_id == AVCodecID.Hevc ? 32 : 16); + if (codecCtx->codec_descriptor != null) + isIntraOnly = codecCtx->codec_descriptor->props.HasFlag(CodecPropFlags.IntraOnly); + + startPts = VideoStream.StartTimePts; + return 0; } internal bool SetupSws() @@ -351,7 +363,7 @@ internal bool SetupSws() int outBufferSize = av_image_get_buffer_size(fmt, codecCtx->width, codecCtx->height, 1); swsBufferPtr = Marshal.AllocHGlobal(outBufferSize); - av_image_fill_arrays(ref swsData, ref swsLineSize, (byte*) swsBufferPtr, fmt, codecCtx->width, codecCtx->height, 1); + _ = av_image_fill_arrays(ref swsData, ref swsLineSize, (byte*) swsBufferPtr, fmt, codecCtx->width, codecCtx->height, 1); swsCtx = sws_getContext(codecCtx->coded_width, codecCtx->coded_height, codecCtx->pix_fmt, codecCtx->width, codecCtx->height, fmt, Config.Video.SwsHighQuality ? SCALING_HQ : SCALING_LQ, null, null, null); if (swsCtx == null) @@ -377,10 +389,9 @@ internal void Flush() DisposeFrames(); avcodec_flush_buffers(codecCtx); - keyPacketRequired - = true; - StartTime = AV_NOPTS_VALUE; - curSpeedFrame = 9999; + keyPacketRequired = !isIntraOnly; + StartTime = AV_NOPTS_VALUE; + curSpeedFrame = 9999; } } @@ -392,9 +403,9 @@ protected override void RunInternal() return; } - int ret = 0; - int allowedErrors = Config.Decoder.MaxErrors; - int sleepMs = Config.Decoder.MaxVideoFrames > 2 && Config.Player.MaxLatency == 0 ? 10 : 2; + int allowedErrors = Config.Decoder.MaxErrors; + int sleepMs = Config.Decoder.MaxVideoFrames > 2 && Config.Player.MaxLatency == 0 ? 10 : 2; + int ret; AVPacket *packet; do @@ -502,7 +513,7 @@ protected override void RunInternal() if (keyPacketRequired) { - if (packet->flags.HasFlag(PktFlags.Key)) + if (packet->flags.HasFlag(PktFlags.Key) || packet->pts == startPts) keyPacketRequired = false; else { @@ -550,12 +561,15 @@ protected override void RunInternal() // GetFormat checks already for this but only for hardware accelerated (should also check for codec/fps* and possible reset sws if required) // Might use AVERROR_INPUT_CHANGED to let ffmpeg check for those (requires a flag to be set*) - if (frame->height != VideoStream.Height || frame->width != VideoStream.Width) + if ((frame->height != VideoStream.Height || frame->width != VideoStream.Width) && !codecChanged) // could be already changed on getformat { // THIS IS Wrong and can cause filledFromCodec all the time. comparing frame<->videostream dimensions but we update the videostream from codecparam dimensions (which we pass from codecCtx w/h) // Related with display dimensions / coded dimensions / frame-crop dimensions (and apply_cropping) - it could happen when frame->crop... are not 0 - Log.Warn($"Codec changed {VideoStream.CodecID} {VideoStream.Width}x{VideoStream.Height} => {codecCtx->codec_id} {frame->width}x{frame->height}"); + + // TBR: codecCtx w/h changes earlier than frame w/h (still receiving previous config frames?)* can cause issues? + codecChanged = true; filledFromCodec = false; + Log.Warn($"Codec changed {VideoStream.CodecID} {VideoStream.Width}x{VideoStream.Height} => {codecCtx->codec_id} {frame->width}x{frame->height}"); } if (frame->best_effort_timestamp != AV_NOPTS_VALUE) @@ -634,36 +648,14 @@ internal int FillFromCodec(AVFrame* frame) filledFromCodec = true; curFixSeekDelta = 0; - avcodec_parameters_from_context(Stream.AVStream->codecpar, codecCtx); - VideoStream.AVStream->time_base = codecCtx->pkt_timebase; - VideoStream.Refresh(VideoAccelerated && codecCtx->sw_pix_fmt != AVPixelFormat.None ? codecCtx->sw_pix_fmt : codecCtx->pix_fmt, frame); - - if (!(VideoStream.FPS > 0)) // NaN - { - VideoStream.FPS = av_q2d(codecCtx->framerate) > 0 ? av_q2d(codecCtx->framerate) : 0; - VideoStream.FrameDuration = VideoStream.FPS > 0 ? (long) (10000000 / VideoStream.FPS) : 0; - if (VideoStream.FrameDuration > 0) - VideoStream.Demuxer.VideoPackets.frameDuration = VideoStream.FrameDuration; - } - - if (VideoStream.FieldOrder != DeInterlace.Progressive) - { - VideoStream.FPS2 = VideoStream.FPS; - VideoStream.FrameDuration2 = VideoStream.FrameDuration; - VideoStream.FPS /= 2; - VideoStream.FrameDuration *= 2; - } - else - { - VideoStream.FPS2 = VideoStream.FPS * 2; - VideoStream.FrameDuration2 = VideoStream.FrameDuration / 2; - } - + VideoStream.Refresh(this, frame); + codecChanged = false; + startPts = VideoStream.StartTimePts; skipSpeedFrames = speed * VideoStream.FPS / Config.Video.MaxOutputFps; CodecChanged?.Invoke(this); DisposeFrame(Renderer.LastFrame); - if (VideoStream.PixelFormat == AVPixelFormat.None || !Renderer.ConfigPlanes()) + if (VideoStream.PixelFormat == AVPixelFormat.None || !Renderer.ConfigPlanes(true)) { Log.Error("[Pixel Format] Unknown"); return -1234; @@ -690,7 +682,7 @@ internal void HandleDeviceReset() } Renderer.Flush(); Open2(Stream, null, false); - keyPacketRequired = true; + keyPacketRequired = !isIntraOnly; } internal string SWFallback() @@ -924,6 +916,9 @@ public void RefreshMaxVideoFrames() } } + // TBR (GetFrameNumberX): Still issues mainly with Prev, e.g. jumps from 279 to 281 frame | VFR / Timebase / FrameDuration / FPS inaccuracy + // Should use just GetFramePrev/Next and work with pts (but we currenlty work with Player.CurTime) + /// /// Gets the frame number of a VideoFrame timestamp /// @@ -953,8 +948,9 @@ public long GetFrameTimestamp(int frameNumber) /// /// Zero based frame index /// The requested VideoFrame or null on failure - public VideoFrame GetFrame(int frameNumber) + public VideoFrame GetFrame(int frameNumber, bool backwards = false) { + frameNumber = Math.Max(0, frameNumber); long requiredTimestamp = GetFrameTimestamp(frameNumber); long curSeekMcs = requiredTimestamp / 10; int curFrameNumber; @@ -966,6 +962,9 @@ public VideoFrame GetFrame(int frameNumber) demuxer.Interrupter.SeekRequest(); int ret = av_seek_frame(demuxer.FormatContext, -1, curSeekMcs - curFixSeekDelta, SeekFlags.Frame | SeekFlags.Backward); + if (ret < 0) + ret = av_seek_frame(demuxer.FormatContext, -1, Math.Max((curSeekMcs - (long)TimeSpan.FromSeconds(1).TotalMicroseconds) - curFixSeekDelta, demuxer.StartTime / 10), SeekFlags.Frame); + demuxer.DisposePackets(); if (demuxer.Status == Status.Ended) @@ -980,9 +979,9 @@ public VideoFrame GetFrame(int frameNumber) if (DecodeFrameNext() != 0) return null; - long seekedTimestamp = (long)(frame->pts * VideoStream.Timebase); + curFrameNumber = GetFrameNumber2((long)(frame->pts * VideoStream.Timebase)); - if (GetFrameNumber2(seekedTimestamp) > frameNumber) + if (curFrameNumber > frameNumber) { curFixSeekDelta += FIX_SEEK_DELTA_MCS; continue; @@ -990,9 +989,13 @@ public VideoFrame GetFrame(int frameNumber) do { - curFrameNumber = GetFrameNumber2((long)(frame->pts * VideoStream.Timebase)); - if (curFrameNumber >= frameNumber) + if (curFrameNumber >= frameNumber || + (backwards && curFrameNumber + 2 >= frameNumber && GetFrameNumber2((long)(frame->pts * VideoStream.Timebase) + VideoStream.FrameDuration + (VideoStream.FrameDuration / 2)) - curFrameNumber > 1)) + // At least return a previous frame in case of Tb inaccuracy and don't stuck at the same frame { + if (backwards && curFrameNumber + 2 >= frameNumber && GetFrameNumber2((long)(frame->pts * VideoStream.Timebase) + VideoStream.FrameDuration + (VideoStream.FrameDuration / 2)) - curFrameNumber > 1) + Log.Debug(""); + var mFrame = Renderer.FillPlanes(frame); if (mFrame != null) return mFrame; @@ -1004,8 +1007,12 @@ public VideoFrame GetFrame(int frameNumber) } av_frame_unref(frame); + if (DecodeFrameNext() != 0) + break; - } while (DecodeFrameNext() == 0); + curFrameNumber = GetFrameNumber2((long)(frame->pts * VideoStream.Timebase)); + + } while (true); return null; } while (true); @@ -1077,7 +1084,7 @@ public int DecodeFrameNext() if (keyPacketRequired) { - if (demuxer.packet->flags.HasFlag(PktFlags.Key)) + if (demuxer.packet->flags.HasFlag(PktFlags.Key) || demuxer.packet->pts == startPts) keyPacketRequired = false; else { @@ -1257,239 +1264,4 @@ internal void StartRecording(Remuxer remuxer) } internal void StopRecording() => isRecording = false; #endregion - - #region TODO Decoder Profiles - - /* Use the same as FFmpeg - * https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/dxva2.c - * https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/avcodec.h - */ - - //internal enum DecoderProfiles - //{ - // DXVA_ModeMPEG2and1_VLD, - // DXVA_ModeMPEG1_VLD, - // DXVA2_ModeMPEG2_VLD, - // DXVA2_ModeMPEG2_IDCT, - // DXVA2_ModeMPEG2_MoComp, - // DXVA_ModeH264_A, - // DXVA_ModeH264_B, - // DXVA_ModeH264_C, - // DXVA_ModeH264_D, - // DXVA_ModeH264_E, - // DXVA_ModeH264_F, - // DXVA_ModeH264_VLD_Stereo_Progressive_NoFGT, - // DXVA_ModeH264_VLD_Stereo_NoFGT, - // DXVA_ModeH264_VLD_Multiview_NoFGT, - // DXVA_ModeWMV8_A, - // DXVA_ModeWMV8_B, - // DXVA_ModeWMV9_A, - // DXVA_ModeWMV9_B, - // DXVA_ModeWMV9_C, - // DXVA_ModeVC1_A, - // DXVA_ModeVC1_B, - // DXVA_ModeVC1_C, - // DXVA_ModeVC1_D, - // DXVA_ModeVC1_D2010, - // DXVA_ModeMPEG4pt2_VLD_Simple, - // DXVA_ModeMPEG4pt2_VLD_AdvSimple_NoGMC, - // DXVA_ModeMPEG4pt2_VLD_AdvSimple_GMC, - // DXVA_ModeHEVC_VLD_Main, - // DXVA_ModeHEVC_VLD_Main10, - // DXVA_ModeVP8_VLD, - // DXVA_ModeVP9_VLD_Profile0, - // DXVA_ModeVP9_VLD_10bit_Profile2, - // DXVA_ModeMPEG1_A, - // DXVA_ModeMPEG2_A, - // DXVA_ModeMPEG2_B, - // DXVA_ModeMPEG2_C, - // DXVA_ModeMPEG2_D, - // DXVA_ModeH261_A, - // DXVA_ModeH261_B, - // DXVA_ModeH263_A, - // DXVA_ModeH263_B, - // DXVA_ModeH263_C, - // DXVA_ModeH263_D, - // DXVA_ModeH263_E, - // DXVA_ModeH263_F, - // DXVA_ModeH264_VLD_WithFMOASO_NoFGT, - // DXVA_ModeH264_VLD_Multiview, - // DXVADDI_Intel_ModeH264_A, - // DXVADDI_Intel_ModeH264_C, - // DXVA_Intel_H264_NoFGT_ClearVideo, - // DXVA_ModeH264_VLD_NoFGT_Flash, - // DXVA_Intel_VC1_ClearVideo, - // DXVA_Intel_VC1_ClearVideo_2, - // DXVA_nVidia_MPEG4_ASP, - // DXVA_ModeMPEG4pt2_VLD_AdvSimple_Avivo, - // DXVA_ModeHEVC_VLD_Main_Intel, - // DXVA_ModeHEVC_VLD_Main10_Intel, - // DXVA_ModeHEVC_VLD_Main12_Intel, - // DXVA_ModeHEVC_VLD_Main422_10_Intel, - // DXVA_ModeHEVC_VLD_Main422_12_Intel, - // DXVA_ModeHEVC_VLD_Main444_Intel, - // DXVA_ModeHEVC_VLD_Main444_10_Intel, - // DXVA_ModeHEVC_VLD_Main444_12_Intel, - // DXVA_ModeH264_VLD_SVC_Scalable_Baseline, - // DXVA_ModeH264_VLD_SVC_Restricted_Scalable_Baseline, - // DXVA_ModeH264_VLD_SVC_Scalable_High, - // DXVA_ModeH264_VLD_SVC_Restricted_Scalable_High_Progressive, - // DXVA_ModeVP9_VLD_Intel, - // DXVA_ModeAV1_VLD_Profile0, - // DXVA_ModeAV1_VLD_Profile1, - // DXVA_ModeAV1_VLD_Profile2, - // DXVA_ModeAV1_VLD_12bit_Profile2, - // DXVA_ModeAV1_VLD_12bit_Profile2_420 - //} - //internal static Dictionary DXVADecoderProfiles = new() - //{ - // { new(0x86695f12, 0x340e, 0x4f04, 0x9f, 0xd3, 0x92, 0x53, 0xdd, 0x32, 0x74, 0x60), DecoderProfiles.DXVA_ModeMPEG2and1_VLD }, - // { new(0x6f3ec719, 0x3735, 0x42cc, 0x80, 0x63, 0x65, 0xcc, 0x3c, 0xb3, 0x66, 0x16), DecoderProfiles.DXVA_ModeMPEG1_VLD }, - // { new(0xee27417f, 0x5e28,0x4e65, 0xbe, 0xea, 0x1d, 0x26, 0xb5, 0x08, 0xad, 0xc9), DecoderProfiles.DXVA2_ModeMPEG2_VLD }, - // { new(0xbf22ad00, 0x03ea,0x4690, 0x80, 0x77, 0x47, 0x33, 0x46, 0x20, 0x9b, 0x7e), DecoderProfiles.DXVA2_ModeMPEG2_IDCT }, - // { new(0xe6a9f44b, 0x61b0,0x4563, 0x9e, 0xa4, 0x63, 0xd2, 0xa3, 0xc6, 0xfe, 0x66), DecoderProfiles.DXVA2_ModeMPEG2_MoComp }, - // { new(0x1b81be64, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeH264_A }, - // { new(0x1b81be65, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeH264_B }, - // { new(0x1b81be66, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeH264_C }, - // { new(0x1b81be67, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeH264_D }, - // { new(0x1b81be68, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeH264_E }, - // { new(0x1b81be69, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeH264_F }, - // { new(0xd79be8da, 0x0cf1,0x4c81, 0xb8, 0x2a, 0x69, 0xa4, 0xe2, 0x36, 0xf4, 0x3d), DecoderProfiles.DXVA_ModeH264_VLD_Stereo_Progressive_NoFGT }, - // { new(0xf9aaccbb, 0xc2b6,0x4cfc, 0x87, 0x79, 0x57, 0x07, 0xb1, 0x76, 0x05, 0x52), DecoderProfiles.DXVA_ModeH264_VLD_Stereo_NoFGT }, - // { new(0x705b9d82, 0x76cf,0x49d6, 0xb7, 0xe6, 0xac, 0x88, 0x72, 0xdb, 0x01, 0x3c), DecoderProfiles.DXVA_ModeH264_VLD_Multiview_NoFGT }, - // { new(0x1b81be80, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeWMV8_A }, - // { new(0x1b81be81, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeWMV8_B }, - // { new(0x1b81be90, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeWMV9_A }, - // { new(0x1b81be91, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeWMV9_B }, - // { new(0x1b81be94, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeWMV9_C }, - // { new(0x1b81beA0, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeVC1_A }, - // { new(0x1b81beA1, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeVC1_B }, - // { new(0x1b81beA2, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeVC1_C }, - // { new(0x1b81beA3, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeVC1_D }, - // { new(0x1b81bea4, 0xa0c7,0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeVC1_D2010 }, - // { new(0xefd64d74, 0xc9e8,0x41d7, 0xa5, 0xe9, 0xe9, 0xb0, 0xe3, 0x9f, 0xa3, 0x19), DecoderProfiles.DXVA_ModeMPEG4pt2_VLD_Simple }, - // { new(0xed418a9f, 0x010d,0x4eda, 0x9a, 0xe3, 0x9a, 0x65, 0x35, 0x8d, 0x8d, 0x2e), DecoderProfiles.DXVA_ModeMPEG4pt2_VLD_AdvSimple_NoGMC }, - // { new(0xab998b5b, 0x4258,0x44a9, 0x9f, 0xeb, 0x94, 0xe5, 0x97, 0xa6, 0xba, 0xae), DecoderProfiles.DXVA_ModeMPEG4pt2_VLD_AdvSimple_GMC }, - // { new(0x5b11d51b, 0x2f4c,0x4452, 0xbc, 0xc3, 0x09, 0xf2, 0xa1, 0x16, 0x0c, 0xc0), DecoderProfiles.DXVA_ModeHEVC_VLD_Main }, - // { new(0x107af0e0, 0xef1a,0x4d19, 0xab, 0xa8, 0x67, 0xa1, 0x63, 0x07, 0x3d, 0x13), DecoderProfiles.DXVA_ModeHEVC_VLD_Main10 }, - // { new(0x90b899ea, 0x3a62,0x4705, 0x88, 0xb3, 0x8d, 0xf0, 0x4b, 0x27, 0x44, 0xe7), DecoderProfiles.DXVA_ModeVP8_VLD }, - // { new(0x463707f8, 0xa1d0,0x4585, 0x87, 0x6d, 0x83, 0xaa, 0x6d, 0x60, 0xb8, 0x9e), DecoderProfiles.DXVA_ModeVP9_VLD_Profile0 }, - // { new(0xa4c749ef, 0x6ecf,0x48aa, 0x84, 0x48, 0x50, 0xa7, 0xa1, 0x16, 0x5f, 0xf7), DecoderProfiles.DXVA_ModeVP9_VLD_10bit_Profile2 }, - // { new(0x1b81be09, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeMPEG1_A }, - // { new(0x1b81be0A, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeMPEG2_A }, - // { new(0x1b81be0B, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeMPEG2_B }, - // { new(0x1b81be0C, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeMPEG2_C }, - // { new(0x1b81be0D, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeMPEG2_D }, - // { new(0x1b81be01, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeH261_A }, - // { new(0x1b81be02, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeH261_B }, - // { new(0x1b81be03, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeH263_A }, - // { new(0x1b81be04, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeH263_B }, - // { new(0x1b81be05, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeH263_C }, - // { new(0x1b81be06, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeH263_D }, - // { new(0x1b81be07, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeH263_E }, - // { new(0x1b81be08, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5), DecoderProfiles.DXVA_ModeH263_F }, - // { new(0xd5f04ff9, 0x3418, 0x45d8, 0x95, 0x61, 0x32, 0xa7, 0x6a, 0xae, 0x2d, 0xdd), DecoderProfiles.DXVA_ModeH264_VLD_WithFMOASO_NoFGT }, - // { new(0x9901CCD3, 0xca12, 0x4b7e, 0x86, 0x7a, 0xe2, 0x22, 0x3d, 0x92, 0x55, 0xc3), DecoderProfiles.DXVA_ModeH264_VLD_Multiview }, - // { new(0x604F8E64, 0x4951, 0x4c54, 0x88, 0xFE, 0xAB, 0xD2, 0x5C, 0x15, 0xB3, 0xD6), DecoderProfiles.DXVADDI_Intel_ModeH264_A }, - // { new(0x604F8E66, 0x4951, 0x4c54, 0x88, 0xFE, 0xAB, 0xD2, 0x5C, 0x15, 0xB3, 0xD6), DecoderProfiles.DXVADDI_Intel_ModeH264_C }, - // { new(0x604F8E68, 0x4951, 0x4c54, 0x88, 0xFE, 0xAB, 0xD2, 0x5C, 0x15, 0xB3, 0xD6), DecoderProfiles.DXVA_Intel_H264_NoFGT_ClearVideo }, - // { new(0x4245F676, 0x2BBC, 0x4166, 0xa0, 0xBB, 0x54, 0xE7, 0xB8, 0x49, 0xC3, 0x80), DecoderProfiles.DXVA_ModeH264_VLD_NoFGT_Flash }, - // { new(0xBCC5DB6D, 0xA2B6, 0x4AF0, 0xAC, 0xE4, 0xAD, 0xB1, 0xF7, 0x87, 0xBC, 0x89), DecoderProfiles.DXVA_Intel_VC1_ClearVideo }, - // { new(0xE07EC519, 0xE651, 0x4CD6, 0xAC, 0x84, 0x13, 0x70, 0xCC, 0xEE, 0xC8, 0x51), DecoderProfiles.DXVA_Intel_VC1_ClearVideo_2 }, - // { new(0x9947EC6F, 0x689B, 0x11DC, 0xA3, 0x20, 0x00, 0x19, 0xDB, 0xBC, 0x41, 0x84), DecoderProfiles.DXVA_nVidia_MPEG4_ASP }, - // { new(0x7C74ADC6, 0xe2ba, 0x4ade, 0x86, 0xde, 0x30, 0xbe, 0xab, 0xb4, 0x0c, 0xc1), DecoderProfiles.DXVA_ModeMPEG4pt2_VLD_AdvSimple_Avivo }, - // { new(0x8c56eb1e, 0x2b47, 0x466f, 0x8d, 0x33, 0x7d, 0xbc, 0xd6, 0x3f, 0x3d, 0xf2), DecoderProfiles.DXVA_ModeHEVC_VLD_Main_Intel }, - // { new(0x75fc75f7, 0xc589, 0x4a07, 0xa2, 0x5b, 0x72, 0xe0, 0x3b, 0x03, 0x83, 0xb3), DecoderProfiles.DXVA_ModeHEVC_VLD_Main10_Intel }, - // { new(0x8ff8a3aa, 0xc456, 0x4132, 0xb6, 0xef, 0x69, 0xd9, 0xdd, 0x72, 0x57, 0x1d), DecoderProfiles.DXVA_ModeHEVC_VLD_Main12_Intel }, - // { new(0xe484dcb8, 0xcac9, 0x4859, 0x99, 0xf5, 0x5c, 0x0d, 0x45, 0x06, 0x90, 0x89), DecoderProfiles.DXVA_ModeHEVC_VLD_Main422_10_Intel }, - // { new(0xc23dd857, 0x874b, 0x423c, 0xb6, 0xe0, 0x82, 0xce, 0xaa, 0x9b, 0x11, 0x8a), DecoderProfiles.DXVA_ModeHEVC_VLD_Main422_12_Intel }, - // { new(0x41a5af96, 0xe415, 0x4b0c, 0x9d, 0x03, 0x90, 0x78, 0x58, 0xe2, 0x3e, 0x78), DecoderProfiles.DXVA_ModeHEVC_VLD_Main444_Intel }, - // { new(0x6a6a81ba, 0x912a, 0x485d, 0xb5, 0x7f, 0xcc, 0xd2, 0xd3, 0x7b, 0x8d, 0x94), DecoderProfiles.DXVA_ModeHEVC_VLD_Main444_10_Intel }, - // { new(0x5b08e35d, 0x0c66, 0x4c51, 0xa6, 0xf1, 0x89, 0xd0, 0x0c, 0xb2, 0xc1, 0x97), DecoderProfiles.DXVA_ModeHEVC_VLD_Main444_12_Intel }, - // { new(0xc30700c4, 0xe384, 0x43e0, 0xb9, 0x82, 0x2d, 0x89, 0xee, 0x7f, 0x77, 0xc4), DecoderProfiles.DXVA_ModeH264_VLD_SVC_Scalable_Baseline }, - // { new(0x9b8175d4, 0xd670, 0x4cf2, 0xa9, 0xf0, 0xfa, 0x56, 0xdf, 0x71, 0xa1, 0xae), DecoderProfiles.DXVA_ModeH264_VLD_SVC_Restricted_Scalable_Baseline }, - // { new(0x728012c9, 0x66a8, 0x422f, 0x97, 0xe9, 0xb5, 0xe3, 0x9b, 0x51, 0xc0, 0x53), DecoderProfiles.DXVA_ModeH264_VLD_SVC_Scalable_High }, - // { new(0x8efa5926, 0xbd9e, 0x4b04, 0x8b, 0x72, 0x8f, 0x97, 0x7d, 0xc4, 0x4c, 0x36), DecoderProfiles.DXVA_ModeH264_VLD_SVC_Restricted_Scalable_High_Progressive }, - // { new(0x76988a52, 0xdf13, 0x419a, 0x8e, 0x64, 0xff, 0xcf, 0x4a, 0x33, 0x6c, 0xf5), DecoderProfiles.DXVA_ModeVP9_VLD_Intel }, - // { new(0xb8be4ccb, 0xcf53, 0x46ba, 0x8d, 0x59, 0xd6, 0xb8, 0xa6, 0xda, 0x5d, 0x2a), DecoderProfiles.DXVA_ModeAV1_VLD_Profile0 }, - // { new(0x6936ff0f, 0x45b1, 0x4163, 0x9c, 0xc1, 0x64, 0x6e, 0xf6, 0x94, 0x61, 0x08), DecoderProfiles.DXVA_ModeAV1_VLD_Profile1 }, - // { new(0x0c5f2aa1, 0xe541, 0x4089, 0xbb, 0x7b, 0x98, 0x11, 0x0a, 0x19, 0xd7, 0xc8), DecoderProfiles.DXVA_ModeAV1_VLD_Profile2 }, - // { new(0x17127009, 0xa00f, 0x4ce1, 0x99, 0x4e, 0xbf, 0x40, 0x81, 0xf6, 0xf3, 0xf0), DecoderProfiles.DXVA_ModeAV1_VLD_12bit_Profile2 }, - // { new(0x2d80bed6, 0x9cac, 0x4835, 0x9e, 0x91, 0x32, 0x7b, 0xbc, 0x4f, 0x9e, 0xe8), DecoderProfiles.DXVA_ModeAV1_VLD_12bit_Profile2_420 }, - - - //}; - //internal static Dictionary DXVADecoderProfilesDesc = new() - //{ - // { DecoderProfiles.DXVA_ModeMPEG1_A, "MPEG-1 decoder, restricted profile A" }, - // { DecoderProfiles.DXVA_ModeMPEG2_A, "MPEG-2 decoder, restricted profile A" }, - // { DecoderProfiles.DXVA_ModeMPEG2_B, "MPEG-2 decoder, restricted profile B" }, - // { DecoderProfiles.DXVA_ModeMPEG2_C, "MPEG-2 decoder, restricted profile C" }, - // { DecoderProfiles.DXVA_ModeMPEG2_D, "MPEG-2 decoder, restricted profile D" }, - // { DecoderProfiles.DXVA2_ModeMPEG2_VLD, "MPEG-2 variable-length decoder" }, - // { DecoderProfiles.DXVA_ModeMPEG2and1_VLD, "MPEG-2 & MPEG-1 variable-length decoder" }, - // { DecoderProfiles.DXVA2_ModeMPEG2_MoComp, "MPEG-2 motion compensation" }, - // { DecoderProfiles.DXVA2_ModeMPEG2_IDCT, "MPEG-2 inverse discrete cosine transform" }, - // { DecoderProfiles.DXVA_ModeMPEG1_VLD, "MPEG-1 variable-length decoder, no D pictures" }, - // { DecoderProfiles.DXVA_ModeH264_F, "H.264 variable-length decoder, film grain technology" }, - // { DecoderProfiles.DXVA_ModeH264_E, "H.264 variable-length decoder, no film grain technology" }, - // { DecoderProfiles.DXVA_Intel_H264_NoFGT_ClearVideo, "H.264 variable-length decoder, no film grain technology (Intel ClearVideo)" }, - // { DecoderProfiles.DXVA_ModeH264_VLD_WithFMOASO_NoFGT, "H.264 variable-length decoder, no film grain technology, FMO/ASO" }, - // { DecoderProfiles.DXVA_ModeH264_VLD_NoFGT_Flash, "H.264 variable-length decoder, no film grain technology, Flash" }, - // { DecoderProfiles.DXVA_ModeH264_D, "H.264 inverse discrete cosine transform, film grain technology" }, - // { DecoderProfiles.DXVA_ModeH264_C, "H.264 inverse discrete cosine transform, no film grain technology" }, - // { DecoderProfiles.DXVADDI_Intel_ModeH264_C, "H.264 inverse discrete cosine transform, no film grain technology (Intel)" }, - // { DecoderProfiles.DXVA_ModeH264_B, "H.264 motion compensation, film grain technology" }, - // { DecoderProfiles.DXVA_ModeH264_A, "H.264 motion compensation, no film grain technology" }, - // { DecoderProfiles.DXVADDI_Intel_ModeH264_A, "H.264 motion compensation, no film grain technology (Intel)" }, - // { DecoderProfiles.DXVA_ModeH264_VLD_Stereo_Progressive_NoFGT, "H.264 stereo high profile, mbs flag set" }, - // { DecoderProfiles.DXVA_ModeH264_VLD_Stereo_NoFGT, "H.264 stereo high profile" }, - // { DecoderProfiles.DXVA_ModeH264_VLD_Multiview_NoFGT, "H.264 multiview high profile" }, - // { DecoderProfiles.DXVA_ModeH264_VLD_SVC_Scalable_Baseline, "H.264 scalable video coding, Scalable Baseline Profile" }, - // { DecoderProfiles.DXVA_ModeH264_VLD_SVC_Restricted_Scalable_Baseline, "H.264 scalable video coding, Scalable Constrained Baseline Profile" }, - // { DecoderProfiles.DXVA_ModeH264_VLD_SVC_Scalable_High, "H.264 scalable video coding, Scalable High Profile" }, - // { DecoderProfiles.DXVA_ModeH264_VLD_SVC_Restricted_Scalable_High_Progressive, "H.264 scalable video coding, Scalable Constrained High Profile" }, - // { DecoderProfiles.DXVA_ModeWMV8_B, "Windows Media Video 8 motion compensation" }, - // { DecoderProfiles.DXVA_ModeWMV8_A, "Windows Media Video 8 post processing" }, - // { DecoderProfiles.DXVA_ModeWMV9_C, "Windows Media Video 9 IDCT" }, - // { DecoderProfiles.DXVA_ModeWMV9_B, "Windows Media Video 9 motion compensation" }, - // { DecoderProfiles.DXVA_ModeWMV9_A, "Windows Media Video 9 post processing" }, - // { DecoderProfiles.DXVA_ModeVC1_D, "VC-1 variable-length decoder" }, - // { DecoderProfiles.DXVA_ModeVC1_D2010, "VC-1 variable-length decoder" }, - // { DecoderProfiles.DXVA_Intel_VC1_ClearVideo_2, "VC-1 variable-length decoder 2 (Intel)" }, - // { DecoderProfiles.DXVA_Intel_VC1_ClearVideo, "VC-1 variable-length decoder (Intel)" }, - // { DecoderProfiles.DXVA_ModeVC1_C, "VC-1 inverse discrete cosine transform" }, - // { DecoderProfiles.DXVA_ModeVC1_B, "VC-1 motion compensation" }, - // { DecoderProfiles.DXVA_ModeVC1_A, "VC-1 post processing" }, - // { DecoderProfiles.DXVA_nVidia_MPEG4_ASP, "MPEG-4 Part 2 nVidia bitstream decoder" }, - // { DecoderProfiles.DXVA_ModeMPEG4pt2_VLD_Simple, "MPEG-4 Part 2 variable-length decoder, Simple Profile" }, - // { DecoderProfiles.DXVA_ModeMPEG4pt2_VLD_AdvSimple_NoGMC, "MPEG-4 Part 2 variable-length decoder, Simple&Advanced Profile, no GMC" }, - // { DecoderProfiles.DXVA_ModeMPEG4pt2_VLD_AdvSimple_GMC, "MPEG-4 Part 2 variable-length decoder, Simple&Advanced Profile, GMC" }, - // { DecoderProfiles.DXVA_ModeMPEG4pt2_VLD_AdvSimple_Avivo, "MPEG-4 Part 2 variable-length decoder, Simple&Advanced Profile, Avivo" }, - // { DecoderProfiles.DXVA_ModeHEVC_VLD_Main_Intel, "HEVC Main profile (Intel)" }, - // { DecoderProfiles.DXVA_ModeHEVC_VLD_Main10_Intel, "HEVC Main 10 profile (Intel)" }, - // { DecoderProfiles.DXVA_ModeHEVC_VLD_Main12_Intel, "HEVC Main profile 4:2:2 Range Extension (Intel)" }, - // { DecoderProfiles.DXVA_ModeHEVC_VLD_Main422_10_Intel, "HEVC Main 10 profile 4:2:2 Range Extension (Intel)" }, - // { DecoderProfiles.DXVA_ModeHEVC_VLD_Main422_12_Intel, "HEVC Main 12 profile 4:2:2 Range Extension (Intel)" }, - // { DecoderProfiles.DXVA_ModeHEVC_VLD_Main444_Intel, "HEVC Main profile 4:4:4 Range Extension (Intel)" }, - // { DecoderProfiles.DXVA_ModeHEVC_VLD_Main444_10_Intel, "HEVC Main 10 profile 4:4:4 Range Extension (Intel)" }, - // { DecoderProfiles.DXVA_ModeHEVC_VLD_Main444_12_Intel, "HEVC Main 12 profile 4:4:4 Range Extension (Intel)" }, - // { DecoderProfiles.DXVA_ModeHEVC_VLD_Main, "HEVC Main profile" }, - // { DecoderProfiles.DXVA_ModeHEVC_VLD_Main10, "HEVC Main 10 profile" }, - // { DecoderProfiles.DXVA_ModeH261_A, "H.261 decoder, restricted profile A" }, - // { DecoderProfiles.DXVA_ModeH261_B, "H.261 decoder, restricted profile B" }, - // { DecoderProfiles.DXVA_ModeH263_A, "H.263 decoder, restricted profile A" }, - // { DecoderProfiles.DXVA_ModeH263_B, "H.263 decoder, restricted profile B" }, - // { DecoderProfiles.DXVA_ModeH263_C, "H.263 decoder, restricted profile C" }, - // { DecoderProfiles.DXVA_ModeH263_D, "H.263 decoder, restricted profile D" }, - // { DecoderProfiles.DXVA_ModeH263_E, "H.263 decoder, restricted profile E" }, - // { DecoderProfiles.DXVA_ModeH263_F, "H.263 decoder, restricted profile F" }, - // { DecoderProfiles.DXVA_ModeVP8_VLD, "VP8" }, - // { DecoderProfiles.DXVA_ModeVP9_VLD_Profile0, "VP9 profile 0" }, - // { DecoderProfiles.DXVA_ModeVP9_VLD_10bit_Profile2, "VP9 profile" }, - // { DecoderProfiles.DXVA_ModeVP9_VLD_Intel, "VP9 profile Intel" }, - // { DecoderProfiles.DXVA_ModeAV1_VLD_Profile0, "AV1 Main profile" }, - // { DecoderProfiles.DXVA_ModeAV1_VLD_Profile1, "AV1 High profile" }, - //}; - #endregion } diff --git a/FlyleafLib/MediaFramework/MediaDemuxer/CustomIOContext.cs b/FlyleafLib/MediaFramework/MediaDemuxer/CustomIOContext.cs index 23843be..67f0fae 100644 --- a/FlyleafLib/MediaFramework/MediaDemuxer/CustomIOContext.cs +++ b/FlyleafLib/MediaFramework/MediaDemuxer/CustomIOContext.cs @@ -1,6 +1,4 @@ -using System.IO; - -namespace FlyleafLib.MediaFramework.MediaDemuxer; +namespace FlyleafLib.MediaFramework.MediaDemuxer; public unsafe class CustomIOContext { @@ -32,7 +30,7 @@ public void Dispose() av_free(avioCtx->buffer); fixed (AVIOContext** ptr = &avioCtx) avio_context_free(ptr); } - avioCtx = null; + avioCtx= null; stream = null; ioread = null; ioseek = null; diff --git a/FlyleafLib/MediaFramework/MediaDemuxer/Demuxer.cs b/FlyleafLib/MediaFramework/MediaDemuxer/Demuxer.cs index 13e5a74..efe2fdd 100644 --- a/FlyleafLib/MediaFramework/MediaDemuxer/Demuxer.cs +++ b/FlyleafLib/MediaFramework/MediaDemuxer/Demuxer.cs @@ -1,5 +1,4 @@ using System.Collections.Concurrent; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Runtime.InteropServices; @@ -8,11 +7,12 @@ using System.Threading.Tasks; using System.Windows.Data; +using static FlyleafLib.Config; +using static FlyleafLib.Logger; + using FlyleafLib.MediaFramework.MediaProgram; using FlyleafLib.MediaFramework.MediaStream; using FlyleafLib.MediaPlayer; -using static FlyleafLib.Config; -using static FlyleafLib.Logger; namespace FlyleafLib.MediaFramework.MediaDemuxer; @@ -35,12 +35,12 @@ public unsafe class Demuxer : RunThreadBase public string Extensions { get; private set; } public string Extension { get; private set; } public long StartTime { get; private set; } - public long StartRealTime { get; private set; } + public DateTime StartRealTime { get; private set; } public long Duration { get; internal set; } public void ForceDuration(long duration) { Duration = duration; IsLive = duration != 0; } public Dictionary - Metadata { get; internal set; } = new Dictionary(); + Metadata { get; internal set; } = []; /// /// The time of first packet in the queue (zero based, substracts start time) @@ -60,21 +60,21 @@ public Dictionary // Media Programs public ObservableCollection - Programs { get; private set; } = new ObservableCollection(); + Programs { get; private set; } = []; // Media Streams public ObservableCollection - AudioStreams { get; private set; } = new ObservableCollection(); + AudioStreams { get; private set; } = []; public ObservableCollection - VideoStreams { get; private set; } = new ObservableCollection(); + VideoStreams { get; private set; } = []; public ObservableCollection SubtitlesStreamsAll - { get; private set; } = new ObservableCollection(); + { get; private set; } = []; public ObservableCollection - DataStreams { get; private set; } = new ObservableCollection(); + DataStreams { get; private set; } = []; readonly object lockStreams = new(); - public List EnabledStreams { get; private set; } = new List(); + public List EnabledStreams { get; private set; } = []; public Dictionary AVStreamToStream{ get; private set; } @@ -121,7 +121,7 @@ public PacketQueue GetPacketsPtr(StreamBase stream) public ConcurrentQueue>> VideoPacketsReverse - { get; private set; } = new ConcurrentQueue>>(); + { get; private set; } = []; public bool IsReversePlayback { get; private set; } @@ -132,7 +132,7 @@ public bool IsReversePlayback public Interrupter Interrupter { get; private set; } public ObservableCollection - Chapters { get; private set; } = new ObservableCollection(); + Chapters { get; private set; } = []; public class Chapter { public long StartTime { get; set; } @@ -154,7 +154,7 @@ internal void OnTimedOut() public AVPacket* packet; public AVFormatContext* fmtCtx; internal HLSContext* hlsCtx; - + bool analyzed; long hlsPrevSeqNo = AV_NOPTS_VALUE; // Identifies the change of the m3u8 playlist (wraped) internal long hlsStartTime = AV_NOPTS_VALUE; // Calculation of first timestamp (lastPacketTs - hlsCurDuration) long hlsCurDuration; // Duration until the start of the current segment @@ -162,6 +162,7 @@ internal void OnTimedOut() public object lockFmtCtx = new(); internal bool allowReadInterrupts; + static readonly DateTime EPOCH = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); // To calculate StartRealTime /* Reverse Playback * @@ -218,7 +219,7 @@ public Demuxer(DemuxerConfig config, MediaType type = MediaType.Video, int uniqu string typeStr = Type == MediaType.Video ? "Main" : Type.ToString(); threadName = $"Demuxer: {typeStr,5}"; - Utils.UIInvokeIfRequired(() => + UIInvokeIfRequired(() => { BindingOperations.EnableCollectionSynchronization(Programs, lockStreams); BindingOperations.EnableCollectionSynchronization(AudioStreams, lockStreams); @@ -294,9 +295,9 @@ public void Dispose() Stop(); - Url = null; - hlsCtx = null; - + Url = null; + hlsCtx = null; + analyzed = false; IsReversePlayback = false; curReverseStopPts = AV_NOPTS_VALUE; curReverseStartPts = AV_NOPTS_VALUE; @@ -447,7 +448,7 @@ public string Open(string url, Stream stream) urlFromUrl = query[..inputEnds]; query = query[(inputEnds + 1)..]; - fmtOptExtra = Utils.ParseQueryString(query); + fmtOptExtra = ParseQueryString(query); } } @@ -464,7 +465,7 @@ public string Open(string url, Stream stream) if (queryPos != -1) { - fmtOptExtra = Utils.ParseQueryString(urlSpan.Slice(queryPos + 1)); + fmtOptExtra = ParseQueryString(urlSpan.Slice(queryPos + 1)); url = urlSpan[..queryPos].ToString(); } } @@ -477,7 +478,7 @@ public string Open(string url, Stream stream) int queryStarts = url.IndexOf('?'); if (queryStarts != -1) { - var qp = Utils.ParseQueryString(url.AsSpan()[(queryStarts + 1)..]); + var qp = ParseQueryString(url.AsSpan()[(queryStarts + 1)..]); foreach (var kv in qp) queryParams[kv.Key] = kv.Value; } @@ -500,7 +501,7 @@ public string Open(string url, Stream stream) } if (Type == MediaType.Subs && - Utils.ExtensionsSubtitlesText.Contains(Utils.GetUrlExtention(url)) && + ExtensionsSubtitlesText.Contains(GetUrlExtention(url)) && File.Exists(url)) { // If the files can be read with text subtitles, load them all into memory and convert them to UTF8. @@ -571,12 +572,12 @@ public string Open(string url, Stream stream) if (isDevice) { - string fmtName = Utils.BytePtrToStringUTF8(inFmt->name); + string fmtName = BytePtrToStringUTF8(inFmt->name); if (fmtName == "decklink") // avoid using UI thread for decklink (STA should be enough for CoInitialize/CoCreateInstance) - Utils.STAInvoke(() => OpenFormat(url, inFmt, fmtOptExtra, out ret)); + STAInvoke(() => OpenFormat(url, inFmt, fmtOptExtra, out ret)); else - Utils.UIInvoke(() => OpenFormat(url, inFmt, fmtOptExtra, out ret)); + UIInvoke(() => OpenFormat(url, inFmt, fmtOptExtra, out ret)); } else OpenFormat(url, inFmt, fmtOptExtra, out ret); @@ -584,6 +585,8 @@ public string Open(string url, Stream stream) if ((ret == AVERROR_EXIT && !Interrupter.Timedout) || Status != Status.Opening || Interrupter.ForceInterrupt == 1) { if (ret < 0) fmtCtx = null; return error = "Cancelled"; } if (ret < 0) { fmtCtx = null; return error = Interrupter.Timedout ? "[avformat_open_input] Timeout" : $"[avformat_open_input] {FFmpegEngine.ErrorCodeToMsg(ret)} ({ret})"; } + Name = BytePtrToStringUTF8(fmtCtx->iformat->name); + // Find Streams Info if (Config.AllowFindStreamInfo) { @@ -599,7 +602,7 @@ public string Open(string url, Stream stream) * https://github.com/SuRGeoNix/Flyleaf/issues/502 */ - if (Utils.BytePtrToStringUTF8(fmtCtx->iformat->name) == "mpegts") + if (Name == "mpegts") { bool requiresMoreAnalyse = false; @@ -619,15 +622,16 @@ public string Open(string url, Stream stream) ret = avformat_find_stream_info(fmtCtx, null); if (ret == AVERROR_EXIT || Status != Status.Opening || Interrupter.ForceInterrupt == 1) return error = "Cancelled"; if (ret < 0) return error = $"[avformat_find_stream_info] {FFmpegEngine.ErrorCodeToMsg(ret)} ({ret})"; + analyzed = true; } // Prevent Multiple Immediate exit requested on eof (maybe should not use avio_feof() to test for the end) if (fmtCtx->pb != null) fmtCtx->pb->eof_reached = 0; - bool hasVideo = FillInfo(); + FillInfo(); - if (Type == MediaType.Video && !hasVideo && AudioStreams.Count == 0) + if (Type == MediaType.Video && VideoStreams.Count == 0 && AudioStreams.Count == 0) return error = $"No audio / video stream found"; else if (Type == MediaType.Audio && AudioStreams.Count == 0) return error = $"No audio stream found"; @@ -662,7 +666,7 @@ int IOOpen(AVFormatContext* s, AVIOContext** pb, byte* urlb, IOFlags flags, AVDi if (avoptCopy != null) { while ((t = av_dict_get(avoptCopy, "", t, DictReadFlags.IgnoreSuffix)) != null) - _ = av_dict_set(avFmtOpts, Utils.BytePtrToStringUTF8(t->key), Utils.BytePtrToStringUTF8(t->value), 0); + _ = av_dict_set(avFmtOpts, BytePtrToStringUTF8(t->key), BytePtrToStringUTF8(t->value), 0); } if (queryParams == null) @@ -697,7 +701,7 @@ int IOOpen(AVFormatContext* s, AVIOContext** pb, byte* urlb, IOFlags flags, AVDi { ReadOnlySpan urlNoQuery = new(urlb, queryPos); ReadOnlySpan urlQuery = new(urlb + queryPos + 1, urlLength - queryPos - 1); - var qps = Utils.ParseQueryString(Encoding.UTF8.GetString(urlQuery)); + var qps = ParseQueryString(Encoding.UTF8.GetString(urlQuery)); foreach (var kv in queryParams) if (!qps.ContainsKey(kv.Key)) @@ -727,57 +731,41 @@ private void OpenFormat(string url, AVInputFormat* inFmt, Dictionary= 0) + if (ret >= 0 && CanTrace) { AVDictionaryEntry *t = null; - while ((t = av_dict_get(avopt, "", t, DictReadFlags.IgnoreSuffix)) != null) - Log.Debug($"Ignoring format option {Utils.BytePtrToStringUTF8(t->key)}"); + Log.Trace($"Ignoring format option {BytePtrToStringUTF8(t->key)}"); } av_dict_free(&avopt); } } - private bool FillInfo() + private void FillInfo() { - Name = Utils.BytePtrToStringUTF8(fmtCtx->iformat->name); - LongName = Utils.BytePtrToStringUTF8(fmtCtx->iformat->long_name); - Extensions = Utils.BytePtrToStringUTF8(fmtCtx->iformat->extensions); - - StartTime = fmtCtx->start_time != AV_NOPTS_VALUE ? fmtCtx->start_time * 10 : 0; - StartRealTime = fmtCtx->start_time_realtime != AV_NOPTS_VALUE ? fmtCtx->start_time_realtime * 10 : 0; - Duration = fmtCtx->duration > 0 ? fmtCtx->duration * 10 : 0; - - // TBR: Possible we can get Apple HTTP Live Streaming/hls with HLSPlaylist->finished with Duration != 0 - if (Engine.Config.FFmpegHLSLiveSeek && Duration == 0 && Name == "hls" && Environment.Is64BitProcess) // HLSContext cast is not safe - { - hlsCtx = (HLSContext*) fmtCtx->priv_data; - StartTime = 0; - //UpdateHLSTime(); Maybe with default 0 playlist - } - - if (fmtCtx->nb_streams == 1 && fmtCtx->streams[0]->codecpar->codec_type == AVMediaType.Subtitle) // External Streams (mainly for .sub will have as start time the first subs timestamp) - StartTime = 0; - - IsLive = Duration == 0 || hlsCtx != null; + LongName = BytePtrToStringUTF8(fmtCtx->iformat->long_name); + Extensions = BytePtrToStringUTF8(fmtCtx->iformat->extensions); + Extension = GetValidExtension(); - bool hasVideo = false; - AVStreamToStream = new Dictionary(); + // External Streams (mainly for .sub will have as start time the first subs timestamp) + StartTime = fmtCtx->start_time == NoTs || (fmtCtx->nb_streams == 1 && fmtCtx->streams[0]->codecpar->codec_type == AVMediaType.Subtitle) ? 0 : fmtCtx->start_time * 10; + if (fmtCtx->start_time_realtime != NoTs) + StartRealTime = EPOCH.AddMicroseconds(fmtCtx->start_time_realtime); Metadata.Clear(); AVDictionaryEntry* b = null; @@ -785,116 +773,235 @@ private bool FillInfo() { b = av_dict_get(fmtCtx->metadata, "", b, DictReadFlags.IgnoreSuffix); if (b == null) break; - Metadata.Add(Utils.BytePtrToStringUTF8(b->key), Utils.BytePtrToStringUTF8(b->value)); + Metadata.Add(BytePtrToStringUTF8(b->key), BytePtrToStringUTF8(b->value)); } - Chapters.Clear(); - string dump = ""; - for (int i=0; inb_chapters; i++) - { - var chp = fmtCtx->chapters[i]; - double tb = av_q2d(chp->time_base) * 10000.0 * 1000.0; - string title = ""; - - b = null; - while (true) - { - b = av_dict_get(chp->metadata, "", b, DictReadFlags.IgnoreSuffix); - if (b == null) break; - if (Utils.BytePtrToStringUTF8(b->key).ToLower() == "title") - title = Utils.BytePtrToStringUTF8(b->value); - } - - if (CanDebug) - dump += $"[Chapter {i+1,-2}] {Utils.TicksToTime((long)(chp->start * tb) - StartTime)} - {Utils.TicksToTime((long)(chp->end * tb) - StartTime)} | {title}\r\n"; - - Chapters.Add(new Chapter() - { - StartTime = (long)((chp->start * tb) - StartTime), - EndTime = (long)((chp->end * tb) - StartTime), - Title = title - }); - } - - if (CanDebug && dump != "") Log.Debug($"Chapters\r\n\r\n{dump}"); - - bool audioHasEng = false; + bool audioHasEng= false; bool subsHasEng = false; + AVStreamToStream= []; lock (lockStreams) { - for (int i=0; inb_streams; i++) + for (int i = 0; i < fmtCtx->nb_streams; i++) { - fmtCtx->streams[i]->discard = AVDiscard.All; + var stream = fmtCtx->streams[i]; + stream->discard = AVDiscard.All; + if (stream->codecpar->codec_id == AVCodecID.None) + { + AVStreamToStream.Add(stream->index, new MiscStream(this, stream)); + Log.Info($"#[Invalid #{i}] No codec"); + continue; + } - switch (fmtCtx->streams[i]->codecpar->codec_type) + switch (stream->codecpar->codec_type) { case AVMediaType.Audio: - AudioStreams.Add(new AudioStream(this, fmtCtx->streams[i])); - AVStreamToStream.Add(fmtCtx->streams[i]->index, AudioStreams[^1]); + AudioStreams.Add(new(this, stream)); + AVStreamToStream.Add(stream->index, AudioStreams[^1]); audioHasEng = AudioStreams[^1].Language == Language.English; break; case AVMediaType.Video: - if ((fmtCtx->streams[i]->disposition & DispositionFlags.AttachedPic) != 0) - { Log.Info($"Excluding image stream #{i}"); continue; } + if ((stream->disposition & DispositionFlags.AttachedPic) != 0) + { + AVStreamToStream.Add(stream->index, new MiscStream(this, stream)); + Log.Info($"Excluding image stream #{i}"); + } // TBR: When AllowFindStreamInfo = false we can get valid pixel format during decoding (in case of remuxing only this might crash, possible check if usedecoders?) - if (((AVPixelFormat)fmtCtx->streams[i]->codecpar->format) == AVPixelFormat.None && Config.AllowFindStreamInfo) + else if (((AVPixelFormat)stream->codecpar->format) == AVPixelFormat.None && Config.AllowFindStreamInfo) { + AVStreamToStream.Add(stream->index, new MiscStream(this, stream)); Log.Info($"Excluding invalid video stream #{i}"); - continue; } - VideoStreams.Add(new VideoStream(this, fmtCtx->streams[i])); - AVStreamToStream.Add(fmtCtx->streams[i]->index, VideoStreams[^1]); - hasVideo |= !Config.AllowFindStreamInfo || VideoStreams[^1].PixelFormat != AVPixelFormat.None; + else + { + VideoStreams.Add(new(this, stream)); + AVStreamToStream.Add(stream->index, VideoStreams[^1]); + } break; case AVMediaType.Subtitle: - SubtitlesStreamsAll.Add(new SubtitlesStream(this, fmtCtx->streams[i])); - AVStreamToStream.Add(fmtCtx->streams[i]->index, SubtitlesStreamsAll[^1]); + SubtitlesStreamsAll.Add(new(this, fmtCtx->streams[i])); + AVStreamToStream.Add(stream->index, SubtitlesStreamsAll[^1]); subsHasEng = SubtitlesStreamsAll[^1].Language == Language.English; break; case AVMediaType.Data: - DataStreams.Add(new DataStream(this, fmtCtx->streams[i])); - AVStreamToStream.Add(fmtCtx->streams[i]->index, DataStreams[^1]); + DataStreams.Add(new(this, stream)); + AVStreamToStream.Add(stream->index, DataStreams[^1]); break; default: - Log.Info($"#[Unknown #{i}] {fmtCtx->streams[i]->codecpar->codec_type}"); + AVStreamToStream.Add(stream->index, new MiscStream(this, stream)); + Log.Info($"#[Unknown #{i}] {stream->codecpar->codec_type}"); break; } } + } - if (!audioHasEng) - for (int i=0; inb_programs > 0) + if (fmtCtx->nb_programs > 0) + { + for (int i = 0; i < fmtCtx->nb_programs; i++) { - for (int i = 0; i < fmtCtx->nb_programs; i++) + fmtCtx->programs[i]->discard = AVDiscard.All; + Program program = new(fmtCtx->programs[i], this); + Programs.Add(program); + } + } + + Duration = fmtCtx->duration > 0 ? fmtCtx->duration * 10 : 0; + + // Try to fill duration when missing (not analyzed mainly) | Considers CFR + if (Duration == 0 && !analyzed) + { + foreach(var videoStream in VideoStreams) + if (videoStream.TotalFrames > 0 && videoStream.FrameDuration > 0) { - fmtCtx->programs[i]->discard = AVDiscard.All; - Program program = new(fmtCtx->programs[i], this); - Programs.Add(program); + Duration = videoStream.TotalFrames * videoStream.FrameDuration; + for (int i = 0; i < fmtCtx->nb_streams; i++) + AVStreamToStream[i].UpdateDuration(); + break; + } + + long fileSize = 0; + if (Duration == 0 && fmtCtx->pb != null && (fileSize = avio_size(fmtCtx->pb)) > 0) + { + double bitrate = fmtCtx->bit_rate; + if (bitrate <= 0 && fmtCtx->nb_streams == 1) + { + if (VideoStreams.Count > 0) + bitrate = VideoStreams[0].BitRate; + + if (bitrate <= 0 && AudioStreams.Count > 0) + bitrate = AudioStreams[0].BitRate; + } + + if (bitrate > 0) + { + Duration = (long)(((fileSize * 8.0) / bitrate) * 10_000_000); + for (int i = 0; i < fmtCtx->nb_streams; i++) + AVStreamToStream[i].UpdateDuration(); } } + } + else + { + for (int i = 0; i < fmtCtx->nb_streams; i++) + AVStreamToStream[i].UpdateDuration(); + } + + // TBR: Possible we can get Apple HTTP Live Streaming/hls with HLSPlaylist->finished with Duration != 0 + if (Engine.Config.FFmpegHLSLiveSeek && Duration == 0 && Name == "hls" && Environment.Is64BitProcess) // HLSContext cast is not safe + { + hlsCtx = (HLSContext*)fmtCtx->priv_data; + StartTime = 0; + //UpdateHLSTime(); Maybe with default 0 playlist + } + + IsLive = Duration == 0 || hlsCtx != null; + + string dumpChapters = ""; + + if (fmtCtx->nb_chapters > 0) + dumpChapters = GetChapters(); + + if (CanInfo) + { + string dump = $"Format Info\r\n{GetDump("")}"; + + if (fmtCtx->metadata != null) + dump += "\r\n" + GetDumpMetadata(Metadata) + "\r\n"; + + if (fmtCtx->nb_programs > 0) + dump += $"\r\n[Programs]\r\n{GetDumpPrograms()}"; + + foreach(var stream in AVStreamToStream.Values) + dump += $"\r\n{stream.GetDump()}\r\n"; + + if (dumpChapters != "") + dump += $"\r\n[Chapters]\r\n{dumpChapters}"; + + Log.Info(dump); + } + } + string GetChapters() + { + AVDictionaryEntry* b; + string dump = ""; + for (int i = 0; i < fmtCtx->nb_chapters; i++) + { + var chp = fmtCtx->chapters[i]; + double tb = av_q2d(chp->time_base) * 10000.0 * 1000.0; + string title = ""; + + b = null; + while (true) + { + b = av_dict_get(chp->metadata, "", b, DictReadFlags.IgnoreSuffix); + if (b == null) + break; + + if (BytePtrToStringUTF8(b->key).Equals("title", StringComparison.OrdinalIgnoreCase)) + title = BytePtrToStringUTF8(b->value); + } + + if (CanDebug) + dump += $"\t#{i+1:D2}: {TicksToTime2((long)(chp->start * tb) - StartTime)} - {TicksToTime2((long)(chp->end * tb) - StartTime)} | {title}\r\n"; - Extension = GetValidExtension(); + Chapters.Add(new Chapter() + { + StartTime = (long)((chp->start * tb) - StartTime), + EndTime = (long)((chp->end * tb) - StartTime), + Title = title + }); } - PrintDump(); - return hasVideo; + return dump; + } + string GetDump(string chapters) => + $""" + [Time ] {TicksToTime(StartTime)} / {TicksToTime(Duration)}{(fmtCtx->duration != NoTs ? $" (based on {fmtCtx->duration_estimation_method})" : "")}{(fmtCtx->start_time_realtime != NoTs ? $" [RealTime: {StartRealTime.ToLocalTime()}]" : "")}{(fmtCtx->bit_rate > 0 ? $", {fmtCtx->bit_rate/1000} kb/s" : "")} + [Format ] {LongName} ({Name}){(fmtCtx->iformat->flags != FmtFlags.None ? $" [Flags: {fmtCtx->iformat->flags}]" : "")}{(fmtCtx->ctx_flags != FmtCtxFlags.None ? $" [CtxFlags: {fmtCtx->ctx_flags}]" : "")}{(fmtCtx->iformat->mime_type != null ? $" [Mime: {BytePtrToStringUTF8(fmtCtx->iformat->mime_type)}]" : "")}{(Extensions != null ? $" [Ext(s): {Extensions}]" : "")} + """; + string GetDumpPrograms() + { + string dump = ""; + for (int i = 0; i < fmtCtx->nb_programs; i++) + { + dump += $"\t#{i:D2}: "; + + for (int l = 0; l < fmtCtx->programs[i]->nb_stream_indexes; l++) + dump += $"{fmtCtx->programs[i]->stream_index[l]}, "; + + if (fmtCtx->programs[i]->nb_stream_indexes > 0) + dump = dump[..^2]; + + dump += "\r\n"; + } + + return dump; + } + string GetDumpStreams() + { + string dump = ""; + foreach(var stream in AVStreamToStream.Values) + dump += stream.GetDump() + "\r\n"; + + return dump; } public int SeekInQueue(long ticks, bool forward = false) @@ -1027,7 +1134,7 @@ public int Seek(long ticks, bool forward = false) fmtCtx->pb->error = 0; // AVERROR_EXIT will stay forever and will cause the demuxer to go in Status Stopped instead of Ended (after interrupted seeks) fmtCtx->pb->eof_reached = 0; } - avformat_flush(fmtCtx); + _ = avformat_flush(fmtCtx); // Forces seekable HLS if (hlsCtx != null) @@ -1080,7 +1187,7 @@ public int Seek(long ticks, bool forward = false) fmtCtx->pb->eof_reached = 0; avio_seek(fmtCtx->pb, savedPbPos, 0); } - avformat_flush(fmtCtx); + _ = avformat_flush(fmtCtx); } else lastSeekTime = ticks - StartTime - (hlsCtx != null ? hlsStartTime : 0); @@ -1187,7 +1294,7 @@ protected override void RunInternal() var stream = AVStreamToStream[packet->stream_index]; long dts = packet->dts == AV_NOPTS_VALUE ? -1 : (long)(packet->dts * stream.Timebase); long pts = packet->pts == AV_NOPTS_VALUE ? -1 : (long)(packet->pts * stream.Timebase); - Log.Trace($"[{stream.Type}] DTS: {(dts == -1 ? "-" : Utils.TicksToTime(dts))} PTS: {(pts == -1 ? "-" : Utils.TicksToTime(pts))} | FLPTS: {(pts == -1 ? "-" : Utils.TicksToTime(pts - StartTime))} | CurTime: {Utils.TicksToTime(CurTime)} | Buffered: {Utils.TicksToTime(BufferedDuration)}"); + Log.Trace($"[{stream.Type}] DTS: {(dts == -1 ? "-" : TicksToTime(dts))} PTS: {(pts == -1 ? "-" : TicksToTime(pts))} | FLPTS: {(pts == -1 ? "-" : TicksToTime(pts - StartTime))} | CurTime: {TicksToTime(CurTime)} | Buffered: {TicksToTime(BufferedDuration)}"); } // Enqueue Packet (AVS Queue or Single Queue) @@ -1196,7 +1303,7 @@ protected override void RunInternal() switch (fmtCtx->streams[packet->stream_index]->codecpar->codec_type) { case AVMediaType.Audio: - //Log($"Audio => {Utils.TicksToTime((long)(packet->pts * AudioStream.Timebase))} | {Utils.TicksToTime(CurTime)}"); + //Log($"Audio => {TicksToTime((long)(packet->pts * AudioStream.Timebase))} | {TicksToTime(CurTime)}"); // Handles A/V de-sync and ffmpeg bug with 2^33 timestamps wrapping if (Config.MaxAudioPackets != 0 && AudioPackets.Count > Config.MaxAudioPackets) @@ -1219,7 +1326,7 @@ protected override void RunInternal() break; case AVMediaType.Video: - //Log($"Video => {Utils.TicksToTime((long)(packet->pts * VideoStream.Timebase))} | {Utils.TicksToTime(CurTime)}"); + //Log($"Video => {TicksToTime((long)(packet->pts * VideoStream.Timebase))} | {TicksToTime(CurTime)}"); lastVideoPacketPts = packet->pts; VideoPackets.Enqueue(packet); packet = av_packet_alloc(); @@ -1338,7 +1445,7 @@ private void RunInternalReverse() break; } - //Log($"[][][SEEK END] {curReverseStartPts} | {Utils.TicksToTime((long) (curReverseStartPts * VideoStream.Timebase))}"); + //Log($"[][][SEEK END] {curReverseStartPts} | {TicksToTime((long) (curReverseStartPts * VideoStream.Timebase))}"); Interrupter.SeekRequest(); ret = av_seek_frame(fmtCtx, VideoStream.StreamIndex, Math.Max(curReverseStartPts - curReverseSeekOffset, VideoStream.StartTimePts), SeekFlags.Backward); @@ -1419,7 +1526,7 @@ private void RunInternalReverse() break; } - //Log($"[][][SEEK] {curReverseStartPts} | {Utils.TicksToTime((long) (curReverseStartPts * VideoStream.Timebase))}"); + //Log($"[][][SEEK] {curReverseStartPts} | {TicksToTime((long) (curReverseStartPts * VideoStream.Timebase))}"); Interrupter.SeekRequest(); ret = av_seek_frame(fmtCtx, VideoStream.StreamIndex, Math.Max(curReverseStartPts - curReverseSeekOffset, 0), SeekFlags.Backward); @@ -1770,33 +1877,6 @@ private string GetValidExtension() return defaultExtenstion; } - private void PrintDump() - { - string dump = $"\r\n[Format ] {LongName}/{Name} | {Extensions} | {Utils.TicksToTime(fmtCtx->start_time * 10)}/{Utils.TicksToTime(fmtCtx->duration * 10)} | {Utils.TicksToTime(StartTime)}/{Utils.TicksToTime(Duration)}"; - - foreach(var stream in VideoStreams) dump += "\r\n" + stream.GetDump(); - foreach(var stream in AudioStreams) dump += "\r\n" + stream.GetDump(); - foreach(var stream in SubtitlesStreamsAll) dump += "\r\n" + stream.GetDump(); - - if (fmtCtx->nb_programs > 0) - dump += $"\r\n[Programs] {fmtCtx->nb_programs}"; - - for (int i=0; inb_programs; i++) - { - dump += $"\r\n\tProgram #{i}"; - - if (fmtCtx->programs[i]->nb_stream_indexes > 0) - dump += $"\r\n\t\tStreams [{fmtCtx->programs[i]->nb_stream_indexes}]: "; - - for (int l=0; lprograms[i]->nb_stream_indexes; l++) - dump += $"{fmtCtx->programs[i]->stream_index[l]},"; - - if (fmtCtx->programs[i]->nb_stream_indexes > 0) - dump = dump[..^1]; - } - - if (CanInfo) Log.Info($"Format Context Info {dump}\r\n"); - } /// /// Gets next VideoPacket from the existing queue or demuxes it if required (Demuxer must not be running) diff --git a/FlyleafLib/MediaFramework/MediaDemuxer/DemuxerInput.cs b/FlyleafLib/MediaFramework/MediaDemuxer/DemuxerInput.cs index f8721a0..5bc5cfe 100644 --- a/FlyleafLib/MediaFramework/MediaDemuxer/DemuxerInput.cs +++ b/FlyleafLib/MediaFramework/MediaDemuxer/DemuxerInput.cs @@ -1,20 +1,17 @@ -using System.Collections.Generic; -using System.IO; - -namespace FlyleafLib.MediaFramework.MediaDemuxer; +namespace FlyleafLib.MediaFramework.MediaDemuxer; public class DemuxerInput : NotifyPropertyChanged { /// /// Url provided as a demuxer input /// - public string Url { get => _Url; set => _Url = Utils.FixFileUrl(value); } + public string Url { get => _Url; set => _Url = FixFileUrl(value); } string _Url; /// /// Fallback url provided as a demuxer input /// - public string UrlFallback { get => _UrlFallback; set => _UrlFallback = Utils.FixFileUrl(value); } + public string UrlFallback { get => _UrlFallback; set => _UrlFallback = FixFileUrl(value); } string _UrlFallback; /// diff --git a/FlyleafLib/MediaFramework/MediaDemuxer/Interrupter.cs b/FlyleafLib/MediaFramework/MediaDemuxer/Interrupter.cs index 9b38908..429f321 100644 --- a/FlyleafLib/MediaFramework/MediaDemuxer/Interrupter.cs +++ b/FlyleafLib/MediaFramework/MediaDemuxer/Interrupter.cs @@ -1,7 +1,5 @@ using System.Diagnostics; -using static FlyleafLib.Logger; - namespace FlyleafLib.MediaFramework.MediaDemuxer; public unsafe class Interrupter diff --git a/FlyleafLib/MediaFramework/MediaDevice/DeviceBase.cs b/FlyleafLib/MediaFramework/MediaDevice/DeviceBase.cs index d735350..919d2bc 100644 --- a/FlyleafLib/MediaFramework/MediaDevice/DeviceBase.cs +++ b/FlyleafLib/MediaFramework/MediaDevice/DeviceBase.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; namespace FlyleafLib.MediaFramework.MediaDevice; diff --git a/FlyleafLib/MediaFramework/MediaDevice/VideoDeviceStream.cs b/FlyleafLib/MediaFramework/MediaDevice/VideoDeviceStream.cs index 2d74cc6..c9a7e14 100644 --- a/FlyleafLib/MediaFramework/MediaDevice/VideoDeviceStream.cs +++ b/FlyleafLib/MediaFramework/MediaDevice/VideoDeviceStream.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; using System.Reflection; using Vortice.MediaFoundation; @@ -46,7 +45,7 @@ private static unsafe string GetFFmpegFormat(string subType) { case "MJPG": var descriptorPtr = avcodec_descriptor_get(AVCodecID.Mjpeg); - return $"vcodec={Utils.BytePtrToStringUTF8(descriptorPtr->name)}"; + return $"vcodec={BytePtrToStringUTF8(descriptorPtr->name)}"; case "YUY2": return $"pixel_format={av_get_pix_fmt_name(AVPixelFormat.Yuyv422)}"; case "NV12": diff --git a/FlyleafLib/MediaFramework/MediaFrame/AudioFrame.cs b/FlyleafLib/MediaFramework/MediaFrame/AudioFrame.cs index 0408b14..190231c 100644 --- a/FlyleafLib/MediaFramework/MediaFrame/AudioFrame.cs +++ b/FlyleafLib/MediaFramework/MediaFrame/AudioFrame.cs @@ -1,6 +1,4 @@ -using System; - -namespace FlyleafLib.MediaFramework.MediaFrame; +namespace FlyleafLib.MediaFramework.MediaFrame; public class AudioFrame : FrameBase { diff --git a/FlyleafLib/MediaFramework/MediaFrame/SubtitlesFrame.cs b/FlyleafLib/MediaFramework/MediaFrame/SubtitlesFrame.cs index 139b9a3..d560bcb 100644 --- a/FlyleafLib/MediaFramework/MediaFrame/SubtitlesFrame.cs +++ b/FlyleafLib/MediaFramework/MediaFrame/SubtitlesFrame.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Drawing; +using System.Drawing; using System.Globalization; namespace FlyleafLib.MediaFramework.MediaFrame; diff --git a/FlyleafLib/MediaFramework/MediaPlaylist/M3UPlaylist.cs b/FlyleafLib/MediaFramework/MediaPlaylist/M3UPlaylist.cs index 55c835f..7e640e2 100644 --- a/FlyleafLib/MediaFramework/MediaPlaylist/M3UPlaylist.cs +++ b/FlyleafLib/MediaFramework/MediaPlaylist/M3UPlaylist.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.IO; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; namespace FlyleafLib.MediaFramework.MediaPlaylist; @@ -17,16 +15,16 @@ public string OriginalTitle public bool Not_24_7 { get; set; } public int Height { get; set; } - public Dictionary Tags { get; set; } = new Dictionary(); + public Dictionary Tags { get; set; } = []; } public class M3UPlaylist { public static List ParseFromHttp(string url, int timeoutMs = 30000) { - string downStr = Utils.DownloadToString(url, timeoutMs); + string downStr = DownloadToString(url, timeoutMs); if (downStr == null) - return new(); + return []; using StringReader reader = new(downStr); return Parse(reader); diff --git a/FlyleafLib/MediaFramework/MediaPlaylist/PLSPlaylist.cs b/FlyleafLib/MediaFramework/MediaPlaylist/PLSPlaylist.cs index 6c65447..78e9d66 100644 --- a/FlyleafLib/MediaFramework/MediaPlaylist/PLSPlaylist.cs +++ b/FlyleafLib/MediaFramework/MediaPlaylist/PLSPlaylist.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Text; +using System.Runtime.InteropServices; namespace FlyleafLib.MediaFramework.MediaPlaylist; diff --git a/FlyleafLib/MediaFramework/MediaPlaylist/Playlist.cs b/FlyleafLib/MediaFramework/MediaPlaylist/Playlist.cs index 9ceee00..e0d2df7 100644 --- a/FlyleafLib/MediaFramework/MediaPlaylist/Playlist.cs +++ b/FlyleafLib/MediaFramework/MediaPlaylist/Playlist.cs @@ -1,10 +1,7 @@ -using System.Collections.ObjectModel; -using System.IO; +using System.Windows.Data; using FlyleafLib.MediaFramework.MediaContext; -using static FlyleafLib.Utils; - namespace FlyleafLib.MediaFramework.MediaPlaylist; public class Playlist : NotifyPropertyChanged @@ -76,7 +73,7 @@ internal void UpdatePrevNextItem() // TODO: MediaType (Music/MusicClip/Movie/TVShow/etc.) probably should go per Playlist Item public ObservableCollection - Items { get; set; } = new ObservableCollection(); + Items { get; set; } = []; object lockItems = new(); long openCounter; @@ -86,8 +83,8 @@ public ObservableCollection public Playlist(int uniqueId) { - Log = new LogHandler(("[#" + uniqueId + "]").PadRight(8, ' ') + " [Playlist] "); - UIInvokeIfRequired(() => System.Windows.Data.BindingOperations.EnableCollectionSynchronization(Items, lockItems)); + Log = new(("[#" + uniqueId + "]").PadRight(8, ' ') + " [Playlist] "); + UIInvokeIfRequired(() => BindingOperations.EnableCollectionSynchronization(Items, lockItems)); } public void Reset() @@ -143,9 +140,9 @@ public void AddItem(PlaylistItem item, string pluginName, object tag = null) UIInvokeIfRequired(() => { - System.Windows.Data.BindingOperations.EnableCollectionSynchronization(item.ExternalAudioStreams, item.lockExternalStreams); - System.Windows.Data.BindingOperations.EnableCollectionSynchronization(item.ExternalVideoStreams, item.lockExternalStreams); - System.Windows.Data.BindingOperations.EnableCollectionSynchronization(item.ExternalSubtitlesStreamsAll, item.lockExternalStreams); + BindingOperations.EnableCollectionSynchronization(item.ExternalAudioStreams, item.lockExternalStreams); + BindingOperations.EnableCollectionSynchronization(item.ExternalVideoStreams, item.lockExternalStreams); + BindingOperations.EnableCollectionSynchronization(item.ExternalSubtitlesStreamsAll, item.lockExternalStreams); }); } } diff --git a/FlyleafLib/MediaFramework/MediaPlaylist/PlaylistItem.cs b/FlyleafLib/MediaFramework/MediaPlaylist/PlaylistItem.cs index 034f855..79db08a 100644 --- a/FlyleafLib/MediaFramework/MediaPlaylist/PlaylistItem.cs +++ b/FlyleafLib/MediaFramework/MediaPlaylist/PlaylistItem.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; - -using FlyleafLib.MediaFramework.MediaDemuxer; +using FlyleafLib.MediaFramework.MediaDemuxer; using FlyleafLib.MediaFramework.MediaStream; namespace FlyleafLib.MediaFramework.MediaPlaylist; @@ -93,7 +90,7 @@ public void FillMediaParts() // Called during OpenScrape (if file) & Open/Search return; filled = true; - var mp = Utils.GetMediaParts(OriginalTitle); + var mp = GetMediaParts(OriginalTitle); Year = mp.Year; Season = mp.Season; Episode = mp.Episode; diff --git a/FlyleafLib/MediaFramework/MediaProgram/Program.cs b/FlyleafLib/MediaFramework/MediaProgram/Program.cs index 46fb081..43d8be9 100644 --- a/FlyleafLib/MediaFramework/MediaProgram/Program.cs +++ b/FlyleafLib/MediaFramework/MediaProgram/Program.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; using FlyleafLib.MediaFramework.MediaDemuxer; using FlyleafLib.MediaFramework.MediaStream; @@ -52,7 +51,7 @@ public unsafe Program(AVProgram* program, Demuxer demuxer) { b = av_dict_get(program->metadata, "", b, DictReadFlags.IgnoreSuffix); if (b == null) break; - metadata.Add(Utils.BytePtrToStringUTF8(b->key), Utils.BytePtrToStringUTF8(b->value)); + metadata.Add(BytePtrToStringUTF8(b->key), BytePtrToStringUTF8(b->value)); } Metadata = metadata; } diff --git a/FlyleafLib/MediaFramework/MediaRemuxer/Remuxer.cs b/FlyleafLib/MediaFramework/MediaRemuxer/Remuxer.cs index 10c19de..9963f97 100644 --- a/FlyleafLib/MediaFramework/MediaRemuxer/Remuxer.cs +++ b/FlyleafLib/MediaFramework/MediaRemuxer/Remuxer.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics; namespace FlyleafLib.MediaFramework.MediaRemuxer; @@ -11,18 +10,18 @@ public unsafe class Remuxer public bool HasStreams => mapInOutStreams2.Count > 0 || mapInOutStreams.Count > 0; public bool HeaderWritten { get; private set; } - Dictionary mapInOutStreams = new(); - Dictionary mapInInStream = new(); - Dictionary mapInStreamToDts = new(); - Dictionary mapInOutStreams2 = new(); - Dictionary mapInInStream2 = new(); - Dictionary mapInStreamToDts2 = new(); + Dictionary mapInOutStreams = []; + Dictionary mapInInStream = []; + Dictionary mapInStreamToDts = []; + Dictionary mapInOutStreams2 = []; + Dictionary mapInInStream2 = []; + Dictionary mapInStreamToDts2 = []; AVFormatContext* fmtCtx; AVOutputFormat* fmt; public Remuxer(int uniqueId = -1) - => UniqueId = uniqueId == -1 ? Utils.GetUniqueId() : uniqueId; + => UniqueId = uniqueId == -1 ? GetUniqueId() : uniqueId; public int Open(string filename) { @@ -35,7 +34,7 @@ public int Open(string filename) if (ret < 0) return ret; fmt = fmtCtx->oformat; - mapInStreamToDts = new Dictionary(); + mapInStreamToDts = []; Disposed = false; return 0; @@ -63,8 +62,8 @@ public int AddStream(AVStream* in_stream, bool isAudioDemuxer = false) b = av_dict_get(in_stream->metadata, "", b, DictReadFlags.IgnoreSuffix); if (b == null) break; - if (Utils.BytePtrToStringUTF8(b->key).ToLower() == "language" || Utils.BytePtrToStringUTF8(b->key).ToLower() == "lang") - av_dict_set(&out_stream->metadata, Utils.BytePtrToStringUTF8(b->key), Utils.BytePtrToStringUTF8(b->value), 0); + if (BytePtrToStringUTF8(b->key).ToLower() == "language" || BytePtrToStringUTF8(b->key).ToLower() == "lang") + _ = av_dict_set(&out_stream->metadata, BytePtrToStringUTF8(b->key), BytePtrToStringUTF8(b->value), 0); } out_stream->codecpar->codec_tag = 0; diff --git a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.Device.cs b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.Device.cs index 1124f10..91c3a4d 100644 --- a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.Device.cs +++ b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.Device.cs @@ -1,6 +1,7 @@ using System.Numerics; using System.Runtime.InteropServices; using System.Text.RegularExpressions; + using Vortice; using Vortice.Direct3D; using Vortice.Direct3D11; @@ -465,7 +466,7 @@ void HandleDeviceReset() VideoDecoder.Dispose(); Flush(); VideoDecoder.Open(stream); // Should Re-ConfigPlanes() - VideoDecoder.keyPacketRequired = true; + VideoDecoder.keyPacketRequired = !VideoDecoder.isIntraOnly; if (running) VideoDecoder.Start(); } diff --git a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.Filters.cs b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.Filters.cs index 95d910b..7aab4f4 100644 --- a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.Filters.cs +++ b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.Filters.cs @@ -1,13 +1,10 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; using System.Text.Json.Serialization; using Vortice.Direct3D11; using FlyleafLib.Controls.WPF; -using static FlyleafLib.Utils; - namespace FlyleafLib.MediaFramework.MediaRenderer; unsafe public partial class Renderer @@ -31,7 +28,6 @@ void SetupFilters() } alreadySetup = true; - UpdateHDRtoSDR(false); // TODO: Separate the only first time setup (generic) var d3Filters = curVPCC.Filters; @@ -42,7 +38,8 @@ void SetupFilters() if (cfgFLFilters.TryGetValue(filter, out var cfgFLFilter)) { cfgFLFilter.SetFilter(this, flFilter); - HasFLFilters = cfgFLFilter.Value != cfgFLFilter.Default; + if (cfgFLFilter.Value != cfgFLFilter.Default) + HasFLFilters = true; if (cfgFLFilter.Value != cfgFLFilter.Default) UpdateFLFilterValue(cfgFLFilter, false); } diff --git a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.PixelShader.cs b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.PixelShader.cs index 2bd9009..0975a5f 100644 --- a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.PixelShader.cs +++ b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.PixelShader.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Threading; - -using SharpGen.Runtime; +using SharpGen.Runtime; using Vortice; using Vortice.Direct3D; using Vortice.Direct3D11; @@ -11,8 +8,6 @@ using FlyleafLib.MediaFramework.MediaDecoder; using FlyleafLib.MediaFramework.MediaFrame; -using static FlyleafLib.Logger; - using ID3D11Texture2D = Vortice.Direct3D11.ID3D11Texture2D; namespace FlyleafLib.MediaFramework.MediaRenderer; @@ -37,6 +32,7 @@ enum PSCase : int HW, HWZeroCopy, + Gray, RGBPacked, RGBPacked2, RGBPlanar, @@ -64,7 +60,7 @@ void InitPS() { textDesc[i].Usage = ResourceUsage.Default; textDesc[i].BindFlags = BindFlags.ShaderResource;// | BindFlags.RenderTarget; - textDesc[i].SampleDescription = new SampleDescription(1, 0); + textDesc[i].SampleDescription = new(1, 0); textDesc[i].ArraySize = 1; textDesc[i].MipLevels = 1; } @@ -72,11 +68,11 @@ void InitPS() for (int i=0; iflags & PixFmtFlags.Be) == 0) // We currently force SwsScale for BE (RGBA64/BGRA64 BE noted that could work as is?*) + if (CanTrace) + Log.Trace($"Preparing planes for {VideoStream.PixelFormatStr} with {videoProcessor}"); + + bool supported = VideoDecoder.VideoAccelerated || (!VideoStream.PixelFormatDesc->flags.HasFlag(PixFmtFlags.Pal) && (!VideoStream.PixelFormatDesc->flags.HasFlag(PixFmtFlags.Be) || VideoStream.PixelComp0Depth <= 8)); + if (supported) // We currently force SwsScale for BE (RGBA64/BGRA64 BE noted that could work as is?*) { if (videoProcessor == VideoProcessors.D3D11) { @@ -130,7 +133,7 @@ internal bool ConfigPlanes() { Usage = 0u, RGB_Range = VideoStream.ColorRange == ColorRange.Full ? 0u : 1u, - YCbCr_Matrix = VideoStream.ColorSpace != ColorSpace.BT601 ? 1u : 0u, + YCbCr_Matrix = VideoStream.ColorSpace != ColorSpace.Bt601 ? 1u : 0u, YCbCr_xvYCC = 0u, Nominal_Range = VideoStream.ColorRange == ColorRange.Full ? 2u : 1u }; @@ -186,7 +189,7 @@ internal bool ConfigPlanes() defines.Add(dTone); } - else if (VideoStream.ColorSpace == ColorSpace.BT2020) + else if (VideoStream.ColorSpace == ColorSpace.Bt2020) { defines.Add(dBT2020); curPSUniqueId += "b"; @@ -208,9 +211,9 @@ internal bool ConfigPlanes() defines.Add(dYUVFull); } - if (VideoStream.ColorSpace == ColorSpace.BT709) + if (VideoStream.ColorSpace == ColorSpace.Bt709) psBufferData.coefsIndex = 1; - else if (VideoStream.ColorSpace == ColorSpace.BT2020) + else if (VideoStream.ColorSpace == ColorSpace.Bt2020) psBufferData.coefsIndex = 0; else psBufferData.coefsIndex = 2; @@ -229,7 +232,6 @@ internal bool ConfigPlanes() if (VideoDecoder.ZeroCopy) { curPSCase = PSCase.HWZeroCopy; - curPSUniqueId += ((int)curPSCase).ToString(); for (int i=0; i 8) - { - curPSUniqueId += "x"; - textDesc[0].Format = srvDesc[0].Format = Format.R16G16B16A16_UNorm; - } - else if (VideoStream.PixelComp0Depth > 4) - textDesc[0].Format = srvDesc[0].Format = Format.R8G8B8A8_UNorm; // B8G8R8X8_UNorm for 0[rgb]? - - string offsets = ""; - for (int i = 0; i < VideoStream.PixelComps.Length; i++) - offsets += pixelOffsets[(int) (VideoStream.PixelComps[i].offset / Math.Ceiling(VideoStream.PixelComp0Depth / 8.0))]; - - curPSUniqueId += offsets; - - if (VideoStream.PixelComps.Length > 3) - SetPS(curPSUniqueId, $"color = Texture1.Sample(Sampler, input.Texture).{offsets};"); - else - SetPS(curPSUniqueId, $"color = float4(Texture1.Sample(Sampler, input.Texture).{offsets}, 1.0);"); - } - - // [BGR/RGB]16 - else if (VideoStream.PixelPlanes == 1 && ( - VideoStream.PixelFormat == AVPixelFormat.Rgb444le|| - VideoStream.PixelFormat == AVPixelFormat.Bgr444le)) - { - curPSCase = PSCase.RGBPacked2; - curPSUniqueId += ((int)curPSCase).ToString(); - - textDesc[0].Width = VideoStream.Width; - textDesc[0].Height = VideoStream.Height; - - textDesc[0].Format = srvDesc[0].Format = Format.B4G4R4A4_UNorm; - - if (VideoStream.PixelFormat == AVPixelFormat.Rgb444le) - { - curPSUniqueId += "a"; - SetPS(curPSUniqueId, $"color = float4(Texture1.Sample(Sampler, input.Texture).rgb, 1.0);"); - } - else - { - curPSUniqueId += "b"; - SetPS(curPSUniqueId, $"color = float4(Texture1.Sample(Sampler, input.Texture).bgr, 1.0);"); - } - } - - // GBR(A) <=16 - else if (VideoStream.PixelPlanes > 2 && VideoStream.PixelComp0Depth <= 16) - { - curPSCase = PSCase.RGBPlanar; - curPSUniqueId += ((int)curPSCase).ToString(); - - for (int i=0; i 8) - { - curPSUniqueId += VideoStream.PixelComp0Depth; - - for (int i=0; ilog2_chroma_h > 0 ? (VideoStream.Height + 1) >> VideoStream.PixelFormatDesc->log2_chroma_h : VideoStream.Height >> VideoStream.PixelFormatDesc->log2_chroma_h; string offsets = VideoStream.PixelComps[1].offset > VideoStream.PixelComps[2].offset ? "gr" : "rg"; + curPSUniqueId += offsets; if (VideoStream.PixelComp0Depth > 8) { @@ -500,7 +384,7 @@ internal bool ConfigPlanes() } // Y_U_V - else if (VideoStream.PixelPlanes > 2) + else if (VideoStream.PixelPlanes > 2) // Possible Alpha { curPSCase = PSCase.YUVPlanar; curPSUniqueId += ((int)curPSCase).ToString(); @@ -511,44 +395,256 @@ internal bool ConfigPlanes() textDesc[1].Height = textDesc[2].Height= VideoStream.PixelFormatDesc->log2_chroma_h > 0 ? (VideoStream.Height + 1) >> VideoStream.PixelFormatDesc->log2_chroma_h : VideoStream.Height >> VideoStream.PixelFormatDesc->log2_chroma_h; string shader = @" - color.r = Texture1.Sample(Sampler, input.Texture).r; - color.g = Texture2.Sample(Sampler, input.Texture).r; - color.b = Texture3.Sample(Sampler, input.Texture).r; - "; + color.r = Texture1.Sample(Sampler, input.Texture).r; + color.g = Texture2.Sample(Sampler, input.Texture).r; + color.b = Texture3.Sample(Sampler, input.Texture).r; +"; + // TODO: eg. Gamma28 => color.r = pow(color.r, 2.8); and then it needs back after yuv->rgb with c = pow(c, 1.0 / 2.8); if (VideoStream.PixelPlanes == 4) { curPSUniqueId += "x"; shader += @" - color.a = Texture4.Sample(Sampler, input.Texture).r; - "; + color.a = Texture4.Sample(Sampler, input.Texture).r; +"; } - + else + shader += @" + color.a = 1.0f; +"; + Format curFormat = Format.R8_UNorm; + int maxBits = 8; if (VideoStream.PixelComp0Depth > 8) + { + curPSUniqueId += "a"; + curFormat = Format.R16_UNorm; + maxBits = 16; + } + + for (int i = 0; i < VideoStream.PixelPlanes; i++) + textDesc[i].Format = srvDesc[i].Format = curFormat; + + // TBR: This is an estimation from N-bits to eg 16-bits + if (maxBits - VideoStream.PixelComp0Depth != 0) { curPSUniqueId += VideoStream.PixelComp0Depth; + shader += @" + color.rgb *= pow(2, " + (maxBits - VideoStream.PixelComp0Depth) + @"); +"; + } + + SetPS(curPSUniqueId, shader, defines); + } + } + + else if (VideoStream.ColorType == ColorType.RGB) + { + // [RGB0]32 | [RGBA]32 | [RGBA]64 + if (VideoStream.PixelPlanes == 1 && ( + VideoStream.PixelFormat == AVPixelFormat._0RGB || + VideoStream.PixelFormat == AVPixelFormat.Rgb0 || + VideoStream.PixelFormat == AVPixelFormat._0BGR || + VideoStream.PixelFormat == AVPixelFormat.Bgr0 || + + VideoStream.PixelFormat == AVPixelFormat.Argb || + VideoStream.PixelFormat == AVPixelFormat.Rgba || + VideoStream.PixelFormat == AVPixelFormat.Abgr || + VideoStream.PixelFormat == AVPixelFormat.Bgra || - for (int i=0; i 8) + { + curPSUniqueId += "1"; + textDesc[0].Format = srvDesc[0].Format = Format.R16G16B16A16_UNorm; + } + else + textDesc[0].Format = srvDesc[0].Format = Format.R8G8B8A8_UNorm; // B8G8R8X8_UNorm for 0[rgb]? + + string offsets = ""; + for (int i = 0; i < VideoStream.PixelComps.Length; i++) + offsets += pixelOffsets[(int) (VideoStream.PixelComps[i].offset / Math.Ceiling(VideoStream.PixelComp0Depth / 8.0))]; + + curPSUniqueId += offsets; + + string shader; + if (VideoStream.PixelComps.Length > 3) + shader = @$" + color = Texture1.Sample(Sampler, input.Texture).{offsets}; +"; + else + shader = @$" + color = float4(Texture1.Sample(Sampler, input.Texture).{offsets}, 1.0f); +"; + // TODO: Should transfer it to pixel shader + if (VideoStream.ColorRange == ColorRange.Limited) + { // RGBLimitedToFull + curPSUniqueId += "k"; shader += @" - color = color * pow(2, " + (16 - VideoStream.PixelComp0Depth) + @"); - "; + color.rgb = (color.rgb - rgbOffset) * rgbScale; +"; + } + + SetPS(curPSUniqueId, shader, defines); + } + + // [BGR/RGB]16 + else if (VideoStream.PixelPlanes == 1 && ( + VideoStream.PixelFormat == AVPixelFormat.Rgb444le|| + VideoStream.PixelFormat == AVPixelFormat.Bgr444le)) + { + curPSCase = PSCase.RGBPacked2; + curPSUniqueId += ((int)curPSCase).ToString(); + + textDesc[0].Width = VideoStream.Width; + textDesc[0].Height = VideoStream.Height; + + textDesc[0].Format = srvDesc[0].Format = Format.B4G4R4A4_UNorm; + + string shader; + if (VideoStream.PixelFormat == AVPixelFormat.Rgb444le) + { + curPSUniqueId += "a"; + shader = @" + color = float4(Texture1.Sample(Sampler, input.Texture).rgb, 1.0f); +"; + } + else + shader = @" + color = float4(Texture1.Sample(Sampler, input.Texture).bgr, 1.0f); +"; + // TODO: Should transfer it to pixel shader + if (VideoStream.ColorRange == ColorRange.Limited) + { // RGBLimitedToFull + curPSUniqueId += "k"; + shader += @" + color.rgb = (color.rgb - rgbOffset) * rgbScale; +"; + } + + SetPS(curPSUniqueId, shader, defines); + } + + // GBR(A) + else if (VideoStream.PixelPlanes > 2) // TBR: Usually transfer func 'Linear' for > 8-bit which requires pow (*?) + { + curPSCase = PSCase.RGBPlanar; + curPSUniqueId += ((int)curPSCase).ToString(); + + for (int i = 0; i < VideoStream.PixelPlanes; i++) + { + textDesc[i].Width = VideoStream.Width; + textDesc[i].Height = VideoStream.Height; + } + + string shader = @" + color.g = Texture1.Sample(Sampler, input.Texture).r; + color.b = Texture2.Sample(Sampler, input.Texture).r; + color.r = Texture3.Sample(Sampler, input.Texture).r; +"; + if (VideoStream.PixelPlanes == 4) + { + curPSUniqueId += "x"; + + shader += @" + color.a = Texture4.Sample(Sampler, input.Texture).r; +"; } else + shader += @" + color.a = 1.0f; +"; + /* TODO: + * Using pow for scale/normalize is not accurate (when maxBits != VideoStream.PixelComp0Depth) + * Mainly affects gbrp10 (should prefer Texture2D for more accurate and better performance) + */ + + Format curFormat = Format.R8_UNorm; + int maxBits = 8; + if (VideoStream.PixelComp0Depth > 16) + { + curPSUniqueId += "a"; + curFormat = Format.R32_Float; + maxBits = 32; + } + else if (VideoStream.PixelComp0Depth > 8) { curPSUniqueId += "b"; + curFormat = Format.R16_UNorm; + maxBits = 16; + } - for (int i=0; i 8) + { + curPSUniqueId += "x"; + maxBits = 16; + textDesc[0].Format = srvDesc[0].Format = Format.R16_UNorm; + } + else + textDesc[0].Format = srvDesc[0].Format = Format.R8_UNorm; + + if (maxBits - VideoStream.PixelComp0Depth != 0) + { + curPSUniqueId += VideoStream.PixelComp0Depth; + shader += @" + color.rgb *= pow(2, " + (maxBits - VideoStream.PixelComp0Depth) + @"); +"; + } + + // TODO: Should transfer it to pixel shader + if (VideoStream.ColorRange == ColorRange.Limited) + { // RGBLimitedToFull + curPSUniqueId += "k"; + shader += @" + color.rgb = (color.rgb - rgbOffset) * rgbScale; +"; + } + + SetPS(curPSUniqueId, shader, defines); + } } } @@ -576,9 +672,8 @@ internal bool ConfigPlanes() textDesc[0].Format = srvDesc[0].Format = Format.R8G8B8A8_UNorm; srvDesc[0].ViewDimension = ShaderResourceViewDimension.Texture2D; - // TODO: should add HDR? SetPS(curPSUniqueId, @" -color = float4(Texture1.Sample(Sampler, input.Texture).rgb, 1.0); + color = float4(Texture1.Sample(Sampler, input.Texture).rgb, 1.0); "); } @@ -622,13 +717,14 @@ internal bool ConfigPlanes() Monitor.Exit(VideoDecoder.lockCodecCtx); } } + internal VideoFrame FillPlanes(AVFrame* frame) { try { VideoFrame mFrame = new(); mFrame.timestamp = (long)(frame->pts * VideoStream.Timebase) - VideoDecoder.Demuxer.StartTime; - if (CanTrace) Log.Trace($"Processes {Utils.TicksToTime(mFrame.timestamp)}"); + if (CanTrace) Log.Trace($"Processes {TicksToTime(mFrame.timestamp)}"); if (curPSCase == PSCase.HWZeroCopy) { @@ -757,7 +853,7 @@ internal VideoFrame FillPlanes(AVFrame* frame) } } - void SetPS(string uniqueId, string sampleHLSL, List defines = null) + void SetPS(string uniqueId, ReadOnlySpan sampleHLSL, List defines = null) { if (curPSUniqueId == prevPSUniqueId) return; diff --git a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.Present.cs b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.Present.cs index 9050e10..1c9ec15 100644 --- a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.Present.cs +++ b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.Present.cs @@ -1,12 +1,7 @@ -using System.Threading; -using System.Threading.Tasks; - -using SharpGen.Runtime; +using SharpGen.Runtime; using Vortice.Direct3D11; using Vortice.DXGI; -using static FlyleafLib.Logger; - using FlyleafLib.MediaFramework.MediaDecoder; using FlyleafLib.MediaFramework.MediaFrame; diff --git a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.PresentOffline.cs b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.PresentOffline.cs index c94a23e..c3d36c6 100644 --- a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.PresentOffline.cs +++ b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.PresentOffline.cs @@ -1,6 +1,5 @@ using System.Drawing.Imaging; using System.Drawing; -using System.Threading; using System.Windows.Media.Imaging; using SharpGen.Runtime; @@ -20,27 +19,27 @@ namespace FlyleafLib.MediaFramework.MediaRenderer; public partial class Renderer { // subs - Texture2DDescription overlayTextureDesc; - ID3D11Texture2D overlayTexture; - ID3D11ShaderResourceView overlayTextureSrv; - int overlayTextureOriginalWidth; - int overlayTextureOriginalHeight; - int overlayTextureOriginalPosX; - int overlayTextureOriginalPosY; + Texture2DDescription overlayTextureDesc; + ID3D11Texture2D overlayTexture; + ID3D11ShaderResourceView overlayTextureSrv; + int overlayTextureOriginalWidth; + int overlayTextureOriginalHeight; + int overlayTextureOriginalPosX; + int overlayTextureOriginalPosY; - ID3D11ShaderResourceView[] overlayTextureSRVs = new ID3D11ShaderResourceView[1]; + ID3D11ShaderResourceView[] overlayTextureSRVs = new ID3D11ShaderResourceView[1]; // Used for off screen rendering - Texture2DDescription singleStageDesc, singleGpuDesc; - ID3D11Texture2D singleStage; - ID3D11Texture2D singleGpu; - ID3D11RenderTargetView singleGpuRtv; - Viewport singleViewport; + Texture2DDescription singleStageDesc, singleGpuDesc; + ID3D11Texture2D singleStage; + ID3D11Texture2D singleGpu; + ID3D11RenderTargetView singleGpuRtv; + Viewport singleViewport; // Used for parallel off screen rendering - ID3D11RenderTargetView[] rtv2; - ID3D11Texture2D[] backBuffer2; - bool[] backBuffer2busy; + ID3D11RenderTargetView[] rtv2; + ID3D11Texture2D[] backBuffer2; + bool[] backBuffer2busy; unsafe internal void PresentOffline(VideoFrame frame, ID3D11RenderTargetView rtv, Viewport viewport) { diff --git a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.SwapChain.cs b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.SwapChain.cs index 4a786cf..5b4b3fb 100644 --- a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.SwapChain.cs +++ b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.SwapChain.cs @@ -7,18 +7,19 @@ using Vortice.DXGI; using static FlyleafLib.Utils.NativeMethods; + using ID3D11Texture2D = Vortice.Direct3D11.ID3D11Texture2D; namespace FlyleafLib.MediaFramework.MediaRenderer; public partial class Renderer { - ID3D11Texture2D backBuffer; - ID3D11RenderTargetView backBufferRtv; - IDXGISwapChain1 swapChain; - IDCompositionDevice dCompDevice; - IDCompositionVisual dCompVisual; - IDCompositionTarget dCompTarget; + ID3D11Texture2D backBuffer; + ID3D11RenderTargetView backBufferRtv; + IDXGISwapChain1 swapChain; + IDCompositionDevice dCompDevice; + IDCompositionVisual dCompVisual; + IDCompositionTarget dCompTarget; const Int32 WM_NCDESTROY= 0x0082; const Int32 WM_SIZE = 0x0005; @@ -129,10 +130,10 @@ internal void InitializeSwapChain(nint handle) if (!isFlushing) // avoid calling UI thread during Player.Stop { // SetWindowSubclass seems to require UI thread when RemoveWindowSubclass does not (docs are not mentioning this?) - if (System.Threading.Thread.CurrentThread.ManagedThreadId == System.Windows.Application.Current.Dispatcher.Thread.ManagedThreadId) + if (Thread.CurrentThread.ManagedThreadId == Application.Current.Dispatcher.Thread.ManagedThreadId) SetWindowSubclass(ControlHandle, wndProcDelegatePtr, UIntPtr.Zero, UIntPtr.Zero); else - Utils.UI(() => SetWindowSubclass(ControlHandle, wndProcDelegatePtr, UIntPtr.Zero, UIntPtr.Zero)); + UI(() => SetWindowSubclass(ControlHandle, wndProcDelegatePtr, UIntPtr.Zero, UIntPtr.Zero)); } Engine.Video.Factory.MakeWindowAssociation(ControlHandle, WindowAssociationFlags.IgnoreAll); diff --git a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.VideoProcessor.cs b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.VideoProcessor.cs index 51e9323..7de1e2c 100644 --- a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.VideoProcessor.cs +++ b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.VideoProcessor.cs @@ -1,12 +1,8 @@ -using System.Collections.Generic; -using System.Numerics; -using System.Threading; +using System.Numerics; using Vortice.Direct3D11; using Vortice.DXGI; -using static FlyleafLib.Logger; -using static FlyleafLib.Utils; using ID3D11VideoContext = Vortice.Direct3D11.ID3D11VideoContext; namespace FlyleafLib.MediaFramework.MediaRenderer; @@ -289,19 +285,14 @@ internal void UpdateDeinterlace() FieldType = Config.Video.DeInterlace == DeInterlace.Auto ? (VideoStream != null ? VideoStream.FieldOrder : DeInterlace.Progressive) : Config.Video.DeInterlace; vc?.VideoProcessorSetStreamFrameFormat(vp, 0, FieldType == DeInterlace.Progressive ? VideoFrameFormat.Progressive : (FieldType == DeInterlace.BottomField ? VideoFrameFormat.InterlacedBottomFieldFirst : VideoFrameFormat.InterlacedTopFieldFirst)); - psBufferData.fieldType = FieldType; var fieldType = Config.Video.DeInterlace == DeInterlace.Auto ? VideoStream.FieldOrder : Config.Video.DeInterlace; var newVp = !D3D11VPFailed && VideoDecoder.VideoAccelerated && - (Config.Video.VideoProcessor == VideoProcessors.D3D11 || (fieldType != DeInterlace.Progressive && Config.Video.VideoProcessor != VideoProcessors.Flyleaf)) ? + (Config.Video.VideoProcessor == VideoProcessors.D3D11 || fieldType != DeInterlace.Progressive) ? VideoProcessors.D3D11 : VideoProcessors.Flyleaf; if (newVp != VideoProcessor) ConfigPlanes(); - else - context.UpdateSubresource(psBufferData, psBuffer); - - Present(); } } internal void SetFieldType(DeInterlace fieldType) diff --git a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.cs b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.cs index ad2a01c..bdab461 100644 --- a/FlyleafLib/MediaFramework/MediaRenderer/Renderer.cs +++ b/FlyleafLib/MediaFramework/MediaRenderer/Renderer.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; using System.Windows; using Vortice; @@ -189,10 +188,10 @@ public void SetPanAll(int panX, int panY, uint rotation, double zoom, Point p, b public Renderer(VideoDecoder videoDecoder, nint handle = 0, int uniqueId = -1) { - UniqueId = uniqueId == -1 ? Utils.GetUniqueId() : uniqueId; + UniqueId = uniqueId == -1 ? GetUniqueId() : uniqueId; VideoDecoder= videoDecoder; Config = videoDecoder.Config; - Log = new LogHandler(("[#" + UniqueId + "]").PadRight(8, ' ') + " [Renderer ] "); + Log = new(("[#" + UniqueId + "]").PadRight(8, ' ') + " [Renderer ] "); use2d = Config.Video.Use2DGraphics; overlayTextureDesc = new() @@ -204,10 +203,10 @@ public Renderer(VideoDecoder videoDecoder, nint handle = 0, int uniqueId = -1) ArraySize = 1, MipLevels = 1, BindFlags = BindFlags.ShaderResource, - SampleDescription = new SampleDescription(1, 0) + SampleDescription = new(1, 0) }; - singleStageDesc = new Texture2DDescription() + singleStageDesc = new() { Usage = ResourceUsage.Staging, Format = Format.B8G8R8A8_UNorm, @@ -215,20 +214,20 @@ public Renderer(VideoDecoder videoDecoder, nint handle = 0, int uniqueId = -1) MipLevels = 1, BindFlags = BindFlags.None, CPUAccessFlags = CpuAccessFlags.Read, - SampleDescription = new SampleDescription(1, 0), + SampleDescription = new(1, 0), Width = 0, Height = 0 }; - singleGpuDesc = new Texture2DDescription() + singleGpuDesc = new() { Usage = ResourceUsage.Default, Format = Format.B8G8R8A8_UNorm, ArraySize = 1, MipLevels = 1, BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource, - SampleDescription = new SampleDescription(1, 0) + SampleDescription = new(1, 0) }; wndProcDelegate = new(WndProc); @@ -242,8 +241,8 @@ public Renderer(VideoDecoder videoDecoder, nint handle = 0, int uniqueId = -1) Renderer parent; public Renderer(Renderer renderer, nint handle, int uniqueId = -1) { - UniqueId = uniqueId == -1 ? Utils.GetUniqueId() : uniqueId; - Log = new LogHandler(("[#" + UniqueId + "]").PadRight(8, ' ') + " [Renderer Repl] "); + UniqueId = uniqueId == -1 ? GetUniqueId() : uniqueId; + Log = new(("[#" + UniqueId + "]").PadRight(8, ' ') + " [Renderer Repl] "); renderer.child = this; parent = renderer; diff --git a/FlyleafLib/MediaFramework/MediaRenderer/ShaderCompiler.PixelShader.cs b/FlyleafLib/MediaFramework/MediaRenderer/ShaderCompiler.PixelShader.cs new file mode 100644 index 0000000..b797af4 --- /dev/null +++ b/FlyleafLib/MediaFramework/MediaRenderer/ShaderCompiler.PixelShader.cs @@ -0,0 +1,377 @@ +/* +LinearToSRGB | SRGBToLinear: + Simplified version (slightly better performance and on dark colors, but not accurate enough) | possible expose to config + c = pow(c, 1.0 / 2.2); | c = pow(c, 2.2); + +HABLE_DEFAULT + TBR: whitepoint, if should be 4.8 and possible expose to config + +Chroma Location / Sampling + Small improvement but not performance penalty + +Up/Down Scaling + High performance penalty and difficult implementation for good quality + Use D3D11VA proprietary and add shader support +*/ + +namespace FlyleafLib.MediaFramework.MediaRenderer; + +internal static partial class ShaderCompiler +{ + static ReadOnlySpan PS_HEADER => @" +#pragma warning( disable: 3571 ) + +Texture2D Texture1 : register(t0); +Texture2D Texture2 : register(t1); +Texture2D Texture3 : register(t2); +Texture2D Texture4 : register(t3); + +struct ConfigData +{ + int coefsIndex; + float brightness; + float contrast; + float hue; + float saturation; + float uvOffset; + float yoffset; + int tonemap; + float hdrtone; + int fieldType; + + float2 padding; +}; + +cbuffer Config : register(b0) +{ + ConfigData Config; +}; + +SamplerState Sampler : IMMUTABLE +{ + Filter = MIN_MAG_MIP_LINEAR; + AddressU = CLAMP; + AddressV = CLAMP; + AddressW = CLAMP; + ComparisonFunc = NEVER; + MinLOD = 0; +}; + +inline float3 LinearToSRGB(float3 c) +{ + float3 srgbLo = c * 12.92; + float3 srgbHi = 1.055 * pow(c, 1.0 / 2.4) - 0.055; + float3 threshold = step(0.0031308, c); + + return lerp(srgbLo, srgbHi, threshold); +} + +inline float3 SRGBToLinear(float3 c) +{ + float3 linearLo = c / 12.92; + float3 linearHi = pow((c + 0.055) / 1.055, 2.4); + float3 threshold = step(0.04045, c); + + return lerp(linearLo, linearHi, threshold); +} + +inline float3 Gamut2020To709(float3 c) +{ + static const float3x3 mat = + { + 1.6605, -0.5876, -0.0728, + -0.1246, 1.1329, -0.0083, + -0.0182, -0.1006, 1.1187 + }; + return mul(mat, c); +} + +#if defined(dYUVLimited) +static const float3x3 coefs[3] = +{ + // 0: BT.2020 (Limited) + { + 1.16438356, 0.00000000, 1.67867410, + 1.16438356, -0.18732601, -0.65042418, + 1.16438356, 2.14177196, 0.00000000 + }, + // 1: BT.709 (Limited) + { + 1.16438356, 0.00000000, 1.79274107, + 1.16438356, -0.21324861, -0.53290933, + 1.16438356, 2.11240179, 0.00000000 + }, + // 2: BT.601 (Limited) + { + 1.16438356, 0.00000000, 1.59602678, + 1.16438356, -0.39176160, -0.81296823, + 1.16438356, 2.01723214, 0.00000000 + } +}; + +inline float3 YUVToRGBLimited(float3 yuv) +{ + yuv.x -= 0.0625; + yuv.yz -= 0.5; + return mul(coefs[Config.coefsIndex], yuv); +} +#elif defined(dYUVFull) +static const float3x3 coefs[3] = +{ + // 0: BT.2020 (Full) + { + 1.00000000, 0.00000000, 1.47460000, + 1.00000000, -0.16455313, -0.57135313, + 1.00000000, 1.88140000, 0.00000000 + }, + // 1: BT.709 (Full) + { + 1.00000000, 0.00000000, 1.57480000, + 1.00000000, -0.18732600, -0.46812400, + 1.00000000, 1.85560000, 0.00000000 + }, + // 2: BT.601 (Full) + { + 1.00000000, 0.00000000, 1.40200000, + 1.00000000, -0.34413600, -0.71413600, + 1.00000000, 1.77200000, 0.00000000 + } +}; + +inline float3 YUVToRGBFull(float3 yuv) +{ + yuv.yz -= 0.5; + return mul(coefs[Config.coefsIndex], yuv); +} +#else +// TODO: RGBLimitedToFull + Linears (transfer from .cs) +static const float rgbOffset = 16.0 / 255.0; +static const float rgbScale = 255.0 / 219.0; +#endif + +#if defined(dPQToLinear) || defined(dHLGToLinear) +static const float ST2084_m1 = 0.1593017578125; +static const float ST2084_m2 = 78.84375; +static const float ST2084_c1 = 0.8359375; +static const float ST2084_c2 = 18.8515625; +static const float ST2084_c3 = 18.6875; + +inline float3 PQToLinear(float3 rgb, float factor) +{ + rgb = pow(rgb, 1.0 / ST2084_m2); + rgb = max(rgb - ST2084_c1, 0.0) / (ST2084_c2 - ST2084_c3 * rgb); + rgb = pow(rgb, 1.0 / ST2084_m1); + rgb *= factor; + return rgb; +} + +inline float3 LinearToPQ(float3 rgb, float divider) +{ + rgb /= divider; + rgb = pow(rgb, ST2084_m1); + rgb = (ST2084_c1 + ST2084_c2 * rgb) / (1.0f + ST2084_c3 * rgb); + rgb = pow(rgb, ST2084_m2); + return rgb; +} +#endif + +#if defined(dHLGToLinear) +inline float3 HLGInverse(float3 rgb) +{ + const float a = 0.17883277; + const float b = 0.28466892; + const float c = 0.55991073; + + rgb = (rgb <= 0.5) + ? rgb * rgb * 4.0 + : (exp((rgb - c) / a) + b); + + // This will require different factor-nits for HLG (*19.5) + //rgb = (rgb <= 0.5) + // ? (rgb * rgb) / 3.0 + // : (exp((rgb - c) / a) + b) / 12.0; + return rgb; +} + +inline float3 HLGToLinear(float3 rgb) +{ + static const float3 ootf_2020 = float3(0.2627, 0.6780, 0.0593); + + rgb = HLGInverse(rgb); + float ootf_ys = 2000.0f * dot(ootf_2020, rgb); + rgb *= pow(ootf_ys, 0.2f); + return rgb; +} +#endif + +#if defined(dTone) +inline float3 ToneAces(float3 x) +{ + const float a = 2.51; + const float b = 0.03; + const float c = 2.43; + const float d = 0.59; + const float e = 0.14; + return (x * (a * x + b)) / (x * (c * x + d) + e); +} + +inline float3 ToneHable(float3 x) +{ + const float A = 0.15f; + const float B = 0.5f; + const float C = 0.1f; + const float D = 0.2f; + const float E = 0.02f; + const float F = 0.3f; + + // some use those + //const float A = 0.22f; + //const float B = 0.3f; + //const float C = 0.1f; + //const float D = 0.2f; + //const float E = 0.01f; + //const float F = 0.3f; + + return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F; +} +static const float3 HABLE_DEFAULT = ToneHable(11.2); // whitepoint (TBR: if should be 4.8 and possible expose to config) + +inline float3 ToneReinhard(float3 x) +{ + return x * (1.0 + x / 4.84) / (x + 1.0); +} +#endif + +#pragma warning( disable: 4000 ) +inline float3 Hue(float3 rgb, float angle) +{ + // Compiler optimization will ignore it + //[branch] + //if (angle == 0) + // return rgb; + + static const float3x3 hueBase = float3x3( + 0.299, 0.587, 0.114, + 0.299, 0.587, 0.114, + 0.299, 0.587, 0.114 + ); + + static const float3x3 hueCos = float3x3( + 0.701, -0.587, -0.114, + -0.299, 0.413, -0.114, + -0.300, -0.588, 0.886 + ); + + static const float3x3 hueSin = float3x3( + 0.168, 0.330, -0.497, + -0.328, 0.035, 0.292, + 1.250, -1.050, -0.203 + ); + + float c = cos(angle); + float s = -sin(angle); + + return mul(hueBase + c * hueCos + s * hueSin, rgb); +} + +inline float3 Saturation(float3 rgb, float saturation) +{ + // Compiler optimization will ignore it + //[branch] + //if (saturation == 1.0) + // return rgb; + + static const float3 kBT709 = float3(0.2126, 0.7152, 0.0722); + + float luminance = dot(rgb, kBT709); + return lerp(luminance.rrr, rgb, saturation); +} +#pragma warning( enable: 4000 ) + +// hdrmethod enum +static const int Aces = 1; +static const int Hable = 2; +static const int Reinhard = 3; + +struct PSInput +{ + float4 Position : SV_POSITION; + float2 Texture : TEXCOORD; +}; + +float4 main(PSInput input) : SV_TARGET +{ + float4 color; +"u8; + + static ReadOnlySpan PS_FOOTER => @" + + float3 c = color.rgb; + +#if defined(dYUVLimited) + c = YUVToRGBLimited(c); +#elif defined(dYUVFull) + c = YUVToRGBFull(c); +#endif + +#if defined(dBT2020) + c = SRGBToLinear(c); // TODO: transferfunc gamma* + c = Gamut2020To709(c); + c = saturate(c); + c = LinearToSRGB(c); +#else + +#if defined(dPQToLinear) + c = PQToLinear(c, Config.hdrtone); +#elif defined(dHLGToLinear) + c = HLGToLinear(c); + c = LinearToPQ(c, 1000.0); + c = PQToLinear(c, Config.hdrtone); +#endif + +#if defined(dTone) + [branch] + if (Config.tonemap == Hable) + { + c = ToneHable(c) / HABLE_DEFAULT; + c = saturate(c); + c = Gamut2020To709(c); + c = saturate(c); + c = LinearToSRGB(c); + } + else if (Config.tonemap == Reinhard) + { + c = ToneReinhard(c); + c = saturate(c); + c = Gamut2020To709(c); + c = saturate(c); + c = LinearToSRGB(c); + } + else if (Config.tonemap == Aces) + { + c = ToneAces(c); + c = saturate(c); + c = Gamut2020To709(c); + c = saturate(c); + c = pow(c, 0.27); + } + else + { + c = LinearToSRGB(c); + } +#endif + +#endif + +#if defined(dFilters) + // Contrast / Brightness / Hue / Saturation + c *= Config.contrast; + c += Config.brightness; + c = Hue(c, Config.hue); + c = Saturation(c, Config.saturation); +#endif + + return saturate(float4(c, color.a)); +} +"u8; +} diff --git a/FlyleafLib/MediaFramework/MediaRenderer/ShaderCompiler.VertexShader.cs b/FlyleafLib/MediaFramework/MediaRenderer/ShaderCompiler.VertexShader.cs new file mode 100644 index 0000000..b1b20f0 --- /dev/null +++ b/FlyleafLib/MediaFramework/MediaRenderer/ShaderCompiler.VertexShader.cs @@ -0,0 +1,33 @@ +namespace FlyleafLib.MediaFramework.MediaRenderer; + +internal static partial class ShaderCompiler +{ + static ReadOnlySpan VS => @" +cbuffer cBuf : register(b0) +{ + matrix mat; +} + +struct VSInput +{ + float4 Position : POSITION; + float2 Texture : TEXCOORD; +}; + +struct PSInput +{ + float4 Position : SV_POSITION; + float2 Texture : TEXCOORD; +}; + +PSInput main(VSInput vsi) +{ + PSInput psi; + + psi.Position = mul(vsi.Position, mat); + psi.Texture = vsi.Texture; + + return psi; +} +"u8; +} diff --git a/FlyleafLib/MediaFramework/MediaRenderer/ShaderCompiler.cs b/FlyleafLib/MediaFramework/MediaRenderer/ShaderCompiler.cs index c56b7d0..e6c993f 100644 --- a/FlyleafLib/MediaFramework/MediaRenderer/ShaderCompiler.cs +++ b/FlyleafLib/MediaFramework/MediaRenderer/ShaderCompiler.cs @@ -1,51 +1,47 @@ -using System.Collections.Generic; -using System.Text; -using System.Threading; +using System.Buffers; +using System.Diagnostics; using Vortice.D3DCompiler; using Vortice.Direct3D; using Vortice.Direct3D11; using ID3D11Device = Vortice.Direct3D11.ID3D11Device; -using static FlyleafLib.Utils; namespace FlyleafLib.MediaFramework.MediaRenderer; -internal class BlobWrapper +internal static partial class ShaderCompiler { - public Blob blob; - public BlobWrapper(Blob blob) => this.blob = blob; - public BlobWrapper() { } -} - -internal static class ShaderCompiler -{ - static readonly string SHADERVER = Environment.OSVersion.Version.Major >= 10 ? "_5_0" : "_4_0_level_9_3"; - internal static Blob VSBlob = Compile(VS, false); + const int MAX_CACHE_SIZE = 64; + const string MAIN = "main"; + const string LOG_PREFIX = "[Shader] "; + static readonly string SHADERVER = Environment.OSVersion.Version.Major >= 10 ? "_5_0" : "_4_0_level_9_3"; + static readonly string PSVER = $"ps{SHADERVER}"; + static readonly string VSVER = $"vs{SHADERVER}"; + internal static Blob VSBlob = Compile(VS, false); - const int MAXSIZE = 64; - static Dictionary cache = new(); + static Dictionary cache = []; - internal static ID3D11PixelShader CompilePS(ID3D11Device device, string uniqueId, string hlslSample, List defines = null) + internal static ID3D11PixelShader CompilePS(ID3D11Device device, string uniqueId, ReadOnlySpan hlslSample, List defines = null) { BlobWrapper bw; lock (cache) { - if (cache.Count > MAXSIZE) + if (cache.Count > MAX_CACHE_SIZE) { - Engine.Log.Trace($"[ShaderCompiler] Clears cache"); + LogInfo("Clearing Cache"); + foreach (var bw1 in cache.Values) bw1.blob.Dispose(); cache.Clear(); } - - cache.TryGetValue(uniqueId, out var bw2); - if (bw2 != null) + else if (cache.TryGetValue(uniqueId, out var bw2)) { - Engine.Log.Trace($"[ShaderCompiler] Found in cache {uniqueId}"); - lock(bw2) + if (CanDebug) + LogDebug($"Using from Cache '{uniqueId}'"); + + lock (bw2) return device.CreatePixelShader(bw2.blob); } @@ -54,494 +50,65 @@ internal static ID3D11PixelShader CompilePS(ID3D11Device device, string uniqueId cache.Add(uniqueId, bw); } - if (Engine.Config.LogLevel >= LogLevel.Trace) - Engine.Log.Trace($"[ShaderCompiler] Compiling {uniqueId} ...\r\n{PS_HEADER}{hlslSample}{PS_FOOTER}"); + if (CanDebug) + LogDebug($"Compiling '{uniqueId}'"); + + // PS_HEADER + hlslSample + PS_FOOTER (Max 10KB) + Debug.Assert(PS_HEADER.Length + PS_FOOTER.Length + Encoding.UTF8.GetMaxByteCount(hlslSample.Length) < 12_000); + byte[] bufferPool = ArrayPool.Shared.Rent(12 * 1024); + Span buffer = bufferPool; + PS_HEADER.CopyTo(buffer); + int offset = PS_HEADER.Length; + offset += Encoding.UTF8.GetBytes(hlslSample, buffer[offset..]); + PS_FOOTER.CopyTo(buffer[offset..]); + offset += PS_FOOTER.Length; + bw.blob = Compile(buffer[..offset], true, defines); + ArrayPool.Shared.Return(bufferPool); - var blob = Compile(PS_HEADER + hlslSample + PS_FOOTER, true, defines); - bw.blob = blob; var ps = device.CreatePixelShader(bw.blob); Monitor.Exit(bw); - Engine.Log.Trace($"[ShaderCompiler] Compiled {uniqueId}"); return ps; } - internal static Blob Compile(string hlsl, bool isPS = true, List defines = null) + + internal static unsafe Blob Compile(ReadOnlySpan bytes, bool isPS = true, List defines = null) { ShaderMacro[] definesMacro = null; if (defines != null) { + // NOTE: requires NULL termination (+1) definesMacro = new ShaderMacro[defines.Count + 1]; - for(int i=0; i GetUrlExtention(x) == "hlsl").ToArray(); - - // foreach (string shader in shaders) - // using (Stream stream = assembly.GetManifestResourceStream(shader)) - // { - // string shaderName = shader.Substring(0, shader.Length - 5); - // shaderName = shaderName.Substring(shaderName.LastIndexOf('.') + 1); - - // byte[] bytes = new byte[stream.Length]; - // stream.Read(bytes, 0, bytes.Length); - - // CompileShader(bytes, shaderName); - // } - //} - - //private unsafe static void CompileFileShaders() - //{ - // List shaders = Directory.EnumerateFiles(EmbeddedShadersFolder, "*.hlsl").ToList(); - // foreach (string shader in shaders) - // { - // string shaderName = shader.Substring(0, shader.Length - 5); - // shaderName = shaderName.Substring(shaderName.LastIndexOf('\\') + 1); - - // CompileShader(File.ReadAllBytes(shader), shaderName); - // } - //} - - // Loads compiled blob shaders - //private static void LoadShaders() - //{ - // Assembly assembly = Assembly.GetExecutingAssembly(); - // string[] shaders = assembly.GetManifestResourceNames().Where(x => GetUrlExtention(x) == "blob").ToArray(); - // string tempFile = Path.GetTempFileName(); - - // foreach (string shader in shaders) - // { - // using (Stream stream = assembly.GetManifestResourceStream(shader)) - // { - // var shaderName = shader.Substring(0, shader.Length - 5); - // shaderName = shaderName.Substring(shaderName.LastIndexOf('.') + 1); - - // byte[] bytes = new byte[stream.Length]; - // stream.Read(bytes, 0, bytes.Length); - - // Dictionary curShaders = shaderName.Substring(0, 2).ToLower() == "vs" ? VSShaderBlobs : PSShaderBlobs; - - // File.WriteAllBytes(tempFile, bytes); - // curShaders.Add(shaderName, Compiler.ReadFileToBlob(tempFile)); - // } - // } - //} - - // Should work at least from main Samples => FlyleafPlayer (WPF Control) (WPF) - //static string EmbeddedShadersFolder = @"..\..\..\..\..\..\FlyleafLib\MediaFramework\MediaRenderer\Shaders"; - //static Assembly ASSEMBLY = Assembly.GetExecutingAssembly(); - //static string SHADERS_NS = typeof(Renderer).Namespace + ".Shaders."; - - //static byte[] GetEmbeddedShaderResource(string shaderName) - //{ - // using (Stream stream = ASSEMBLY.GetManifestResourceStream(SHADERS_NS + shaderName + ".hlsl")) - // { - // byte[] bytes = new byte[stream.Length]; - // stream.Read(bytes, 0, bytes.Length); - - // return bytes; - // } - //} - - const string PS_HEADER = @" -//#pragma warning( disable: 3571 ) -Texture2D Texture1 : register(t0); -Texture2D Texture2 : register(t1); -Texture2D Texture3 : register(t2); -Texture2D Texture4 : register(t3); - -struct ConfigData -{ - int coefsIndex; - float brightness; - float contrast; - float hue; - float saturation; - float uvOffset; - float yoffset; - int tonemap; - float hdrtone; - int fieldType; - - float2 padding; -}; - -cbuffer Config : register(b0) -{ - ConfigData Config; -}; - - -SamplerState Sampler : IMMUTABLE -{ - Filter = MIN_MAG_MIP_LINEAR; - AddressU = CLAMP; - AddressV = CLAMP; - AddressW = CLAMP; - ComparisonFunc = NEVER; - MinLOD = 0; -}; - -inline float3 Gamut2020To709(float3 c) -{ - static const float3x3 mat = - { - 1.6605, -0.5876, -0.0728, - -0.1246, 1.1329, -0.0083, - -0.0182, -0.1006, 1.1187 - }; - return mul(mat, c); -} - -#if defined(dYUVLimited) -static const float3x3 coefs[3] = -{ - // 0: BT.2020 (Limited) - { - 1.16438356, 0.00000000, 1.67867410, - 1.16438356, -0.18732601, -0.65042418, - 1.16438356, 2.14177196, 0.00000000 - }, - // 1: BT.709 (Limited) - { - 1.16438356, 0.00000000, 1.79274107, - 1.16438356, -0.21324861, -0.53290933, - 1.16438356, 2.11240179, 0.00000000 - }, - // 2: BT.601 (Limited) - { - 1.16438356, 0.00000000, 1.59602678, - 1.16438356, -0.39176160, -0.81296823, - 1.16438356, 2.01723214, 0.00000000 - } -}; - -inline float3 YUVToRGBLimited(float3 yuv) -{ - yuv.x -= 0.0625; - yuv.yz -= 0.5; - return mul(coefs[Config.coefsIndex], yuv); -} -#elif defined(dYUVFull) -static const float3x3 coefs[3] = -{ - // 0: BT.2020 (Full) - { - 1.00000000, 0.00000000, 1.47460000, - 1.00000000, -0.16455313, -0.57135313, - 1.00000000, 1.88140000, 0.00000000 - }, - // 1: BT.709 (Full) - { - 1.00000000, 0.00000000, 1.57480000, - 1.00000000, -0.18732600, -0.46812400, - 1.00000000, 1.85560000, 0.00000000 - }, - // 2: BT.601 (Full) - { - 1.00000000, 0.00000000, 1.40200000, - 1.00000000, -0.34413600, -0.71413600, - 1.00000000, 1.77200000, 0.00000000 - } -}; - -inline float3 YUVToRGBFull(float3 yuv) -{ - yuv.x = (yuv.x - 0.0625) * 1.16438356; - yuv.yz -= 0.5; - return mul(coefs[Config.coefsIndex], yuv); -} -#endif - -#if defined(dPQToLinear) || defined(dHLGToLinear) -static const float ST2084_m1 = 0.1593017578125; -static const float ST2084_m2 = 78.84375; -static const float ST2084_c1 = 0.8359375; -static const float ST2084_c2 = 18.8515625; -static const float ST2084_c3 = 18.6875; - -inline float3 PQToLinear(float3 rgb, float factor) -{ - rgb = pow(rgb, 1.0 / ST2084_m2); - rgb = max(rgb - ST2084_c1, 0.0) / (ST2084_c2 - ST2084_c3 * rgb); - rgb = pow(rgb, 1.0 / ST2084_m1); - rgb *= factor; - return rgb; -} - -inline float3 LinearToPQ(float3 rgb, float divider) -{ - rgb /= divider; - rgb = pow(rgb, ST2084_m1); - rgb = (ST2084_c1 + ST2084_c2 * rgb) / (1.0f + ST2084_c3 * rgb); - rgb = pow(rgb, ST2084_m2); - return rgb; -} -#endif - -#if defined(dHLGToLinear) -inline float3 HLGInverse(float3 rgb) -{ - const float a = 0.17883277; - const float b = 0.28466892; - const float c = 0.55991073; - - rgb = (rgb <= 0.5) - ? rgb * rgb * 4.0 - : (exp((rgb - c) / a) + b); - - // This will require different factor-nits for HLG (*19.5) - //rgb = (rgb <= 0.5) - // ? (rgb * rgb) / 3.0 - // : (exp((rgb - c) / a) + b) / 12.0; - return rgb; -} - -inline float3 HLGToLinear(float3 rgb) -{ - static const float3 ootf_2020 = float3(0.2627, 0.6780, 0.0593); - - rgb = HLGInverse(rgb); - float ootf_ys = 2000.0f * dot(ootf_2020, rgb); - rgb *= pow(ootf_ys, 0.2f); - return rgb; -} -#endif - -#if defined(dTone) -inline float3 ToneAces(float3 x) -{ - const float a = 2.51; - const float b = 0.03; - const float c = 2.43; - const float d = 0.59; - const float e = 0.14; - return saturate((x * (a * x + b)) / (x * (c * x + d) + e)); -} - -inline float3 ToneHable(float3 x) -{ - const float A = 0.15f; - const float B = 0.5f; - const float C = 0.1f; - const float D = 0.2f; - const float E = 0.02f; - const float F = 0.3f; - - // some use those - //const float A = 0.22f; - //const float B = 0.3f; - //const float C = 0.1f; - //const float D = 0.2f; - //const float E = 0.01f; - //const float F = 0.3f; - - return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F; -} -static const float3 HABLE48 = ToneHable(4.8); - -inline float3 ToneReinhard(float3 x) //, float whitepoint=2.2) // or gamma?* -{ - return x * (1.0 + x / 4.84) / (x + 1.0); -} -#endif - -#pragma warning( disable: 4000 ) -inline float3 Hue(float3 rgb, float angle) -{ - // Compiler optimization will ignore it - //[branch] - //if (angle == 0) - // return rgb; - - static const float3x3 hueBase = float3x3( - 0.299, 0.587, 0.114, - 0.299, 0.587, 0.114, - 0.299, 0.587, 0.114 - ); - - static const float3x3 hueCos = float3x3( - 0.701, -0.587, -0.114, - -0.299, 0.413, -0.114, - -0.300, -0.588, 0.886 - ); - - static const float3x3 hueSin = float3x3( - 0.168, 0.330, -0.497, - -0.328, 0.035, 0.292, - 1.250, -1.050, -0.203 - ); - - float c = cos(angle); - float s = -sin(angle); - - return mul(hueBase + c * hueCos + s * hueSin, rgb); -} - -inline float3 Saturation(float3 rgb, float saturation) -{ - // Compiler optimization will ignore it - //[branch] - //if (saturation == 1.0) - // return rgb; - - static const float3 kBT709 = float3(0.2126, 0.7152, 0.0722); - - float luminance = dot(rgb, kBT709); - return lerp(luminance.rrr, rgb, saturation); -} -#pragma warning( enable: 4000 ) - -// hdrmethod enum -static const int Aces = 1; -static const int Hable = 2; -static const int Reinhard = 3; - -struct PSInput -{ - float4 Position : SV_POSITION; - float2 Texture : TEXCOORD; -}; - -float4 main(PSInput input) : SV_TARGET -{ - float4 color; - - // Dynamic Sampling -"; - - const string PS_FOOTER = @" - - float3 c = color.rgb; - -#if defined(dYUVLimited) || defined(dYUVFull) - [branch] - if (Config.fieldType != -1 && int(input.Position.y) % 2 != Config.fieldType) - { - float yAbove = Texture1.Sample(Sampler, float2(input.Texture.x, input.Texture.y - Config.yoffset)).r; - float yBelow = Texture1.Sample(Sampler, float2(input.Texture.x, input.Texture.y + Config.yoffset)).r; - c.r = (yAbove + yBelow) * 0.5f; - } -#endif - -#if defined(dYUVLimited) - c = YUVToRGBLimited(c); -#elif defined(dYUVFull) - c = YUVToRGBFull(c); -#endif - -#if defined(dBT2020) - c = pow(c, 2.2); // TODO: transferfunc gamma* - c = Gamut2020To709(c); - c = saturate(c); - c = pow(c, 1.0 / 2.2); -#else - -#if defined(dPQToLinear) - c = PQToLinear(c, Config.hdrtone); -#elif defined(dHLGToLinear) - c = HLGToLinear(c); - c = LinearToPQ(c, 1000.0); - c = PQToLinear(c, Config.hdrtone); -#endif - -#if defined(dTone) - if (Config.tonemap == Hable) - { - c = ToneHable(c) / HABLE48; - c = Gamut2020To709(c); - c = saturate(c); - c = pow(c, 1.0 / 2.2); - } - else if (Config.tonemap == Reinhard) - { - c = ToneReinhard(c); - c = Gamut2020To709(c); - c = saturate(c); - c = pow(c, 1.0 / 2.2); - } - else if (Config.tonemap == Aces) - { - c = ToneAces(c); - c = Gamut2020To709(c); - c = saturate(c); - c = pow(c, 0.27); - } - else - { - c = pow(c, 1.0 / 2.2); - } -#endif - -#endif - -#if defined(dFilters) - // Contrast / Brightness / Hue / Saturation - c *= Config.contrast; - c += Config.brightness; - c = Hue(c, Config.hue); - c = Saturation(c, Config.saturation); -#endif - - return saturate(float4(c, 1.0)); -} -"; - - const string VS = @" -cbuffer cBuf : register(b0) -{ - matrix mat; + static void LogError(string msg) => Engine.Log.Error($"{LOG_PREFIX}{msg}"); + static void LogInfo (string msg) => Engine.Log.Info ($"{LOG_PREFIX}{msg}"); + static void LogDebug(string msg) => Engine.Log.Debug($"{LOG_PREFIX}{msg}"); + static void LogTrace(string msg) => Engine.Log.Trace($"{LOG_PREFIX}{msg}"); } -struct VSInput -{ - float4 Position : POSITION; - float2 Texture : TEXCOORD; -}; - -struct PSInput -{ - float4 Position : SV_POSITION; - float2 Texture : TEXCOORD; -}; - -PSInput main(VSInput vsi) -{ - PSInput psi; - - psi.Position = mul(vsi.Position, mat); - psi.Texture = vsi.Texture; - - return psi; -} -"; -} +class BlobWrapper { public Blob blob; } // For locking per Blob (before creation) diff --git a/FlyleafLib/MediaFramework/MediaStream/AudioStream.cs b/FlyleafLib/MediaFramework/MediaStream/AudioStream.cs index aca189e..c5adec1 100644 --- a/FlyleafLib/MediaFramework/MediaStream/AudioStream.cs +++ b/FlyleafLib/MediaFramework/MediaStream/AudioStream.cs @@ -1,4 +1,5 @@ -using FlyleafLib.MediaFramework.MediaDemuxer; +using FlyleafLib.MediaFramework.MediaDecoder; +using FlyleafLib.MediaFramework.MediaDemuxer; namespace FlyleafLib.MediaFramework.MediaStream; @@ -13,37 +14,87 @@ public unsafe class AudioStream : StreamBase public int SampleRate { get; set; } public AVCodecID CodecIDOrig { get; set; } - public override string GetDump() - => $"[{Type} #{StreamIndex}-{Language.IdSubLanguage}{(Title != null ? "(" + Title + ")" : "")}] {Codec} {SampleFormatStr}@{Bits} {SampleRate / 1000}KHz {ChannelLayoutStr} | [BR: {BitRate}] | {Utils.TicksToTime((long)(AVStream->start_time * Timebase))}/{Utils.TicksToTime((long)(AVStream->duration * Timebase))} | {Utils.TicksToTime(StartTime)}/{Utils.TicksToTime(Duration)}"; + public AudioStream(Demuxer demuxer, AVStream* st) : base(demuxer, st) + => Type = MediaType.Audio; - public AudioStream() { } - public AudioStream(Demuxer demuxer, AVStream* st) : base(demuxer, st) => Refresh(); - - public override void Refresh() + public override void Initialize() { - base.Refresh(); - - SampleFormat = (AVSampleFormat) Enum.ToObject(typeof(AVSampleFormat), AVStream->codecpar->format); - SampleFormatStr = av_get_sample_fmt_name(SampleFormat); - SampleRate = AVStream->codecpar->sample_rate; - - if (AVStream->codecpar->ch_layout.order == AVChannelOrder.Unspec) - av_channel_layout_default(&AVStream->codecpar->ch_layout, AVStream->codecpar->ch_layout.nb_channels); - - ChannelLayout = AVStream->codecpar->ch_layout.u.mask; - Channels = AVStream->codecpar->ch_layout.nb_channels; - Bits = AVStream->codecpar->bits_per_coded_sample; - // https://trac.ffmpeg.org/ticket/7321 CodecIDOrig = CodecID; if (CodecID == AVCodecID.Mp2 && (SampleFormat == AVSampleFormat.Fltp || SampleFormat == AVSampleFormat.Flt)) CodecID = AVCodecID.Mp3; // OR? st->codecpar->format = (int) AVSampleFormat.AV_SAMPLE_FMT_S16P; + Bits = cp->bits_per_coded_sample; + SampleFormat = (AVSampleFormat)cp->format; + SampleFormatStr = LowerCaseFirstChar(SampleFormat.ToString()); + SampleRate = cp->sample_rate; + + if (cp->ch_layout.order == AVChannelOrder.Unspec && cp->ch_layout.nb_channels > 0) + av_channel_layout_default(&cp->ch_layout, cp->ch_layout.nb_channels); + + ChannelLayout = cp->ch_layout.u.mask; + Channels = cp->ch_layout.nb_channels; byte[] buf = new byte[50]; fixed (byte* bufPtr = buf) { - av_channel_layout_describe(&AVStream->codecpar->ch_layout, bufPtr, (nuint)buf.Length); - ChannelLayoutStr = Utils.BytePtrToStringUTF8(bufPtr); + _ = av_channel_layout_describe(&cp->ch_layout, bufPtr, (nuint)buf.Length); + ChannelLayoutStr = BytePtrToStringUTF8(bufPtr); + } + } + + public void Refresh(AudioDecoder decoder, AVFrame* frame) + { + var codecCtx = decoder.CodecCtx; + + ReUpdate(); + + if (codecCtx->bits_per_coded_sample > 0) + Bits = codecCtx->bits_per_coded_sample; + + if (codecCtx->bit_rate > 0) + BitRate = codecCtx->bit_rate; // for logging only + + if (frame->format != (int)AVSampleFormat.None) + { + SampleFormat = (AVSampleFormat)frame->format; + SampleFormatStr = LowerCaseFirstChar(SampleFormat.ToString()); + } + + if (frame->sample_rate > 0) + SampleRate = codecCtx->sample_rate; + else if (codecCtx->sample_rate > 0) + SampleRate = codecCtx->sample_rate; + + if (frame->ch_layout.nb_channels > 0) + { + if (frame->ch_layout.order == AVChannelOrder.Unspec) + av_channel_layout_default(&frame->ch_layout, frame->ch_layout.nb_channels); + + ChannelLayout = frame->ch_layout.u.mask; + Channels = frame->ch_layout.nb_channels; + byte[] buf = new byte[50]; + fixed (byte* bufPtr = buf) + { + _ = av_channel_layout_describe(&frame->ch_layout, bufPtr, (nuint)buf.Length); + ChannelLayoutStr = BytePtrToStringUTF8(bufPtr); + } + } + else if (codecCtx->ch_layout.nb_channels > 0) + { + if (codecCtx->ch_layout.order == AVChannelOrder.Unspec) + av_channel_layout_default(&codecCtx->ch_layout, codecCtx->ch_layout.nb_channels); + + ChannelLayout = codecCtx->ch_layout.u.mask; + Channels = codecCtx->ch_layout.nb_channels; + byte[] buf = new byte[50]; + fixed (byte* bufPtr = buf) + { + _ = av_channel_layout_describe(&codecCtx->ch_layout, bufPtr, (nuint)buf.Length); + ChannelLayoutStr = BytePtrToStringUTF8(bufPtr); + } } + + if (CanDebug) + Demuxer.Log.Debug($"Stream Info (Filled)\r\n{GetDump()}"); } } diff --git a/FlyleafLib/MediaFramework/MediaStream/DataStream.cs b/FlyleafLib/MediaFramework/MediaStream/DataStream.cs index c7fc01c..ba4b2e7 100644 --- a/FlyleafLib/MediaFramework/MediaStream/DataStream.cs +++ b/FlyleafLib/MediaFramework/MediaStream/DataStream.cs @@ -4,20 +4,8 @@ namespace FlyleafLib.MediaFramework.MediaStream; public unsafe class DataStream : StreamBase { - - public DataStream() { } public DataStream(Demuxer demuxer, AVStream* st) : base(demuxer, st) - { - Demuxer = demuxer; - AVStream = st; - Refresh(); - } - - public override void Refresh() - { - base.Refresh(); - } + => Type = MediaType.Data; - public override string GetDump() - => $"[{Type} #{StreamIndex}] {CodecID}"; + public override void Initialize() { } } diff --git a/FlyleafLib/MediaFramework/MediaStream/ExternalStream.cs b/FlyleafLib/MediaFramework/MediaStream/ExternalStream.cs index 938669e..b31146f 100644 --- a/FlyleafLib/MediaFramework/MediaStream/ExternalStream.cs +++ b/FlyleafLib/MediaFramework/MediaStream/ExternalStream.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -using FlyleafLib.MediaFramework.MediaDemuxer; +using FlyleafLib.MediaFramework.MediaDemuxer; using FlyleafLib.MediaFramework.MediaPlaylist; using FlyleafLib.MediaPlayer; diff --git a/FlyleafLib/MediaFramework/MediaStream/StreamBase.cs b/FlyleafLib/MediaFramework/MediaStream/StreamBase.cs index b32e76c..0933495 100644 --- a/FlyleafLib/MediaFramework/MediaStream/StreamBase.cs +++ b/FlyleafLib/MediaFramework/MediaStream/StreamBase.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -using FlyleafLib.MediaFramework.MediaDemuxer; +using FlyleafLib.MediaFramework.MediaDemuxer; using FlyleafLib.MediaPlayer; namespace FlyleafLib.MediaFramework.MediaStream; @@ -43,50 +41,85 @@ internal set public long StartTime { get; internal set; } public long StartTimePts { get; internal set; } public long Duration { get; internal set; } - public Dictionary Metadata { get; internal set; } = new Dictionary(); + public Dictionary Metadata { get; internal set; } = []; public MediaType Type { get; internal set; } + protected AVCodecParameters* cp; + #region Subtitles // TODO: L: Used for subtitle streams only, but defined in the base class public bool EnabledPrimarySubtitle => Enabled && this.GetSubEnabled(0); public bool EnabledSecondarySubtitle => Enabled && this.GetSubEnabled(1); #endregion - public abstract string GetDump(); - public StreamBase() { } public StreamBase(Demuxer demuxer, AVStream* st) { Demuxer = demuxer; AVStream = st; + cp = st->codecpar; + BitRate = cp->bit_rate; + CodecID = cp->codec_id; + Codec = avcodec_get_name(cp->codec_id); + StreamIndex = AVStream->index; + Timebase = av_q2d(AVStream->time_base) * 10000.0 * 1000.0; + + if (AVStream->start_time != NoTs) + { + StartTimePts= AVStream->start_time; + StartTime = Demuxer.hlsCtx == null ? (long)(AVStream->start_time * Timebase) : Demuxer.StartTime; + } + else + { + StartTime = Demuxer.StartTime; + StartTimePts= av_rescale_q(StartTime/10, Engine.FFmpeg.AV_TIMEBASE_Q, AVStream->time_base); + } + + UpdateHLS(); + UpdateMetadata(); + Initialize(); } - public virtual void Refresh() + public abstract void Initialize(); + + // Possible Fields Updated by FFmpeg (after open/decode frame) + protected void ReUpdate() { - BitRate = AVStream->codecpar->bit_rate; - CodecID = AVStream->codecpar->codec_id; - Codec = avcodec_get_name(AVStream->codecpar->codec_id); - StreamIndex = AVStream->index; - Timebase = av_q2d(AVStream->time_base) * 10000.0 * 1000.0; - StartTime = AVStream->start_time != AV_NOPTS_VALUE && Demuxer.hlsCtx == null ? (long)(AVStream->start_time * Timebase) : Demuxer.StartTime; - StartTimePts= AVStream->start_time != AV_NOPTS_VALUE ? AVStream->start_time : av_rescale_q(StartTime/10, Engine.FFmpeg.AV_TIMEBASE_Q, AVStream->time_base); - Duration = AVStream->duration != AV_NOPTS_VALUE ? (long)(AVStream->duration * Timebase) : Demuxer.Duration; - Type = this is VideoStream ? MediaType.Video : (this is AudioStream ? MediaType.Audio : (this is SubtitlesStream ? MediaType.Subs : MediaType.Data)); + if (AVStream->start_time != NoTs && Demuxer.hlsCtx == null) + { + StartTimePts= AVStream->start_time; + StartTime = (long)(StartTimePts * Timebase); + } + + if (AVStream->duration != NoTs) + Duration = (long)(AVStream->duration * Timebase); + + UpdateHLS(); + } + + // Demuxer Callback + internal void UpdateDuration() + => Duration = AVStream->duration != NoTs ? (long)(AVStream->duration * Timebase) : Demuxer.Duration; + + protected void UpdateHLS() + { + if (Demuxer.hlsCtx == null || HLSPlaylist != null) + return; - if (Demuxer.hlsCtx != null) + for (int i = 0; i < Demuxer.hlsCtx->n_playlists; i++) { - for (int i=0; in_playlists; i++) - { - playlist** playlists = Demuxer.hlsCtx->playlists; - for (int l=0; ln_main_streams; l++) - if (playlists[i]->main_streams[l]->index == StreamIndex) - { - Demuxer.Log.Debug($"Stream #{StreamIndex} Found in playlist {i}"); - HLSPlaylist = playlists[i]; - break; - } - } + playlist** playlists = Demuxer.hlsCtx->playlists; + for (int l=0; ln_main_streams; l++) + if (playlists[i]->main_streams[l]->index == StreamIndex) + { + Demuxer.Log.Debug($"Stream #{StreamIndex} Found in playlist {i}"); + HLSPlaylist = playlists[i]; + break; + } } + } + protected void UpdateMetadata() + { Metadata.Clear(); AVDictionaryEntry* b = null; @@ -94,7 +127,7 @@ public virtual void Refresh() { b = av_dict_get(AVStream->metadata, "", b, DictReadFlags.IgnoreSuffix); if (b == null) break; - Metadata.Add(Utils.BytePtrToStringUTF8(b->key), Utils.BytePtrToStringUTF8(b->value)); + Metadata.Add(BytePtrToStringUTF8(b->key), BytePtrToStringUTF8(b->value)); } foreach (var kv in Metadata) @@ -110,4 +143,54 @@ public virtual void Refresh() if (Language == null) Language = Language.Unknown; } + + public virtual string GetDump() + { + string dump = $"[{Type,-5} #{StreamIndex:D2}]"; + if (Language.OriginalInput != null) + dump += $" ({Language.OriginalInput})"; + + if (StartTime != NoTs || Duration != NoTs) + { + dump += "\r\n\t[Time ] "; + dump += StartTimePts != NoTs ? $"{TicksToTime2(StartTime)} ({StartTimePts})" : "-"; + dump += " / "; + dump += AVStream->duration != NoTs ? $"{TicksToTime2(Duration)} ({AVStream->duration})": "-"; + dump += $" | tb: {AVStream->time_base}"; + } + + string profile = null; + var codecDescriptor = avcodec_descriptor_get(CodecID); + if (codecDescriptor != null) + profile = avcodec_profile_name(CodecID, cp->profile); + dump += $"\r\n\t[Codec ] {Codec}{(profile != null ? " | " + avcodec_profile_name(CodecID, cp->profile) : "")}"; + + if (cp->codec_tag != 0) + dump += $" ({GetFourCCString(cp->codec_tag)} / 0x{cp->codec_tag:X4})"; + + if (BitRate > 0) + dump += $", {(int)(BitRate / 1000)} kb/s"; + + if (AVStream->disposition != DispositionFlags.None) + dump += $" - ({GetFlagsAsString(AVStream->disposition)})"; + + if (this is AudioStream audio) + dump += $"\r\n\t[Format ] {audio.SampleRate} Hz, {audio.ChannelLayoutStr}, {audio.SampleFormatStr}"; + else if (this is VideoStream video) + dump += $"\r\n\t[Format ] {video.PixelFormat} ({cp->color_primaries}, {video.ColorSpace}, {video.ColorTransfer}, {cp->chroma_location}, {video.ColorRange}, {video.FieldOrder}), {video.Width}x{video.Height} @ {DoubleToTimeMini(video.FPS)} fps [SAR1: {video.SAR} DAR: {video.AspectRatio}]"; + + if (Metadata.Count > 0) + dump += $"\r\n{GetDumpMetadata(Metadata, "language")}"; + + return dump; + } +} + +// Use to avoid nulls on broken streams (AVStreamToStream) +public unsafe class MiscStream : StreamBase +{ + public MiscStream(Demuxer demuxer, AVStream* st) : base(demuxer, st) + => Type = MediaType.Data; + + public override void Initialize() { } } diff --git a/FlyleafLib/MediaFramework/MediaStream/SubtitlesStream.cs b/FlyleafLib/MediaFramework/MediaStream/SubtitlesStream.cs index e0e35f7..118bc0b 100644 --- a/FlyleafLib/MediaFramework/MediaStream/SubtitlesStream.cs +++ b/FlyleafLib/MediaFramework/MediaStream/SubtitlesStream.cs @@ -1,5 +1,6 @@ -using System.IO; -using System.Linq; +using System.Linq; + +using FlyleafLib.MediaFramework.MediaDecoder; using FlyleafLib.MediaFramework.MediaDemuxer; using FlyleafLib.MediaPlayer; @@ -39,7 +40,7 @@ public SelectedSubMethod(ISubtitlesStream stream, SelectSubMethod method) public unsafe class SubtitlesStream : StreamBase, ISubtitlesStream { - public bool IsBitmap { get; set; } + public bool IsBitmap { get; private set; } public SelectedSubMethod[] SelectedSubMethods { get @@ -58,36 +59,39 @@ public SelectedSubMethod[] SelectedSubMethods { public string DisplayMember => $"[#{StreamIndex}] {Language} ({(IsBitmap ? "BMP" : "TXT")})"; - public override string GetDump() - => $"[{Type} #{StreamIndex}-{Language.IdSubLanguage}{(Title != null ? "(" + Title + ")" : "")}] {Codec} | [BR: {BitRate}] | {Utils.TicksToTime((long)(AVStream->start_time * Timebase))}/{Utils.TicksToTime((long)(AVStream->duration * Timebase))} | {Utils.TicksToTime(StartTime)}/{Utils.TicksToTime(Duration)}"; - - public SubtitlesStream() { } - public SubtitlesStream(Demuxer demuxer, AVStream* st) : base(demuxer, st) => Refresh(); + public SubtitlesStream(Demuxer demuxer, AVStream* st) : base(demuxer, st) + => Type = MediaType.Subs; - public override void Refresh() + public override void Initialize() { - base.Refresh(); - var codecDescr = avcodec_descriptor_get(CodecID); IsBitmap = codecDescr != null && (codecDescr->props & CodecPropFlags.BitmapSub) != 0; - if (Demuxer.FormatContext->nb_streams == 1) // External Streams (mainly for .sub will have as start time the first subs timestamp) StartTime = 0; } + public void Refresh(SubtitlesDecoder decoder) + { + ReUpdate(); + + if (CanDebug) + Demuxer.Log.Debug($"Stream Info (Filled)\r\n{GetDump()}"); + } + + public void ExternalStreamAdded() { // VobSub (parse .idx data to extradata - based on .sub url) if (CodecID == AVCodecID.DvdSubtitle && ExternalStream != null && ExternalStream.Url.EndsWith(".sub", StringComparison.OrdinalIgnoreCase)) { - var idxFile = ExternalStream.Url.Substring(0, ExternalStream.Url.Length - 3) + "idx"; + var idxFile = string.Concat(ExternalStream.Url.AsSpan(0, ExternalStream.Url.Length - 3), "idx"); if (File.Exists(idxFile)) { var bytes = File.ReadAllBytes(idxFile); - AVStream->codecpar->extradata = (byte*)av_malloc((nuint)bytes.Length); - AVStream->codecpar->extradata_size = bytes.Length; + cp->extradata = (byte*)av_malloc((nuint)bytes.Length); + cp->extradata_size = bytes.Length; Span src = new(bytes); - Span dst = new(AVStream->codecpar->extradata, bytes.Length); + Span dst = new(cp->extradata, bytes.Length); src.CopyTo(dst); } } diff --git a/FlyleafLib/MediaFramework/MediaStream/VideoStream.cs b/FlyleafLib/MediaFramework/MediaStream/VideoStream.cs index f0242a8..4e47022 100644 --- a/FlyleafLib/MediaFramework/MediaStream/VideoStream.cs +++ b/FlyleafLib/MediaFramework/MediaStream/VideoStream.cs @@ -1,12 +1,20 @@ -using FlyleafLib.MediaFramework.MediaDemuxer; +using FlyleafLib.MediaFramework.MediaDecoder; +using FlyleafLib.MediaFramework.MediaDemuxer; namespace FlyleafLib.MediaFramework.MediaStream; public unsafe class VideoStream : StreamBase { + /* TODO + * Color Primaries (not really required?) + * Chroma Location (when we add support in renderer) + */ + public AspectRatio AspectRatio { get; set; } + public AVRational SAR { get; set; } public ColorRange ColorRange { get; set; } public ColorSpace ColorSpace { get; set; } + public ColorType ColorType { get; set; } public AVColorTransferCharacteristic ColorTransfer { get; set; } public DeInterlace FieldOrder { get; set; } @@ -16,7 +24,6 @@ public AVColorTransferCharacteristic public double FPS2 { get; set; } // interlace public long FrameDuration2 { get ;set; } // interlace public uint Height { get; set; } - public bool IsRGB { get; set; } public HDRFormat HDRFormat { get; set; } public AVComponentDescriptor[] PixelComps { get; set; } @@ -25,196 +32,231 @@ public AVColorTransferCharacteristic public AVPixFmtDescriptor* PixelFormatDesc { get; set; } public string PixelFormatStr { get; set; } public int PixelPlanes { get; set; } - public bool PixelSameDepth { get; set; } public bool PixelInterleaved { get; set; } public int TotalFrames { get; set; } public uint Width { get; set; } - public bool FixTimestamps { get; set; } // TBR: For formats such as h264/hevc that have no or invalid pts values - - public override string GetDump() - => $"[{Type} #{StreamIndex}] {Codec} {PixelFormatStr} {Width}x{Height} @ {FPS:#.###} | [Color: {ColorSpace}] [BR: {BitRate}] | {Utils.TicksToTime((long)(AVStream->start_time * Timebase))}/{Utils.TicksToTime((long)(AVStream->duration * Timebase))} | {Utils.TicksToTime(StartTime)}/{Utils.TicksToTime(Duration)}"; + public bool FixTimestamps { get; set; } - public VideoStream() { } public VideoStream(Demuxer demuxer, AVStream* st) : base(demuxer, st) + => Type = MediaType.Video; + + /* NOTES + * Initialize() during Demuxer.FillInfo (Analysed or Basic) + * Refresh() during Decoder.FillFromCodec (First valid frame + Format Changed) + * + * Some fields might know only during Refresh, should make sure we fill them (especially if we didn't analyse the input) + * Don't default (eg Color Range) during Initialize, wait for Refresh + * Priorities: AVFrame => AVCodecContext => AVStream (AVCodecParameters) *Try to keep Color config from stream instead + */ + + // First time fill from AVStream's Codec Parameters | Info to help choosing stream quality mainly (not in use yet) + public override void Initialize() { - Demuxer = demuxer; - AVStream = st; - Refresh(); - } - - public void Refresh(AVPixelFormat format = AVPixelFormat.None, AVFrame* frame = null) - { - base.Refresh(); - FieldOrder = AVStream->codecpar->field_order == AVFieldOrder.Tt ? DeInterlace.TopField : (AVStream->codecpar->field_order == AVFieldOrder.Bb ? DeInterlace.BottomField : DeInterlace.Progressive); - PixelFormat = format == AVPixelFormat.None ? (AVPixelFormat)AVStream->codecpar->format : format; - PixelFormatStr = PixelFormat.ToString().Replace("AV_PIX_FMT_","").ToLower(); - Width = (uint)AVStream->codecpar->width; - Height = (uint)AVStream->codecpar->height; + PixelFormat = (AVPixelFormat)cp->format; + if (PixelFormat != AVPixelFormat.None) + AnalysePixelFormat(); + + Width = (uint)cp->width; + Height = (uint)cp->height; + SAR = av_guess_sample_aspect_ratio(null, AVStream, null); + if (SAR.Num != 0) + { + int x, y; + _ = av_reduce(&x, &y, Width * SAR.Num, Height * SAR.Den, 1024 * 1024); + AspectRatio = new(x, y); + } if (Demuxer.FormatContext->iformat->flags.HasFlag(FmtFlags.Notimestamps)) - { FixTimestamps = true; - if (Demuxer.Config.ForceFPS > 0) - FPS = Demuxer.Config.ForceFPS; - else - FPS = av_q2d(av_guess_frame_rate(Demuxer.FormatContext, AVStream, frame)); - - if (FPS == 0) - FPS = 25; - } + if (Demuxer.Config.ForceFPS > 0) + FPS = Demuxer.Config.ForceFPS; else { - FixTimestamps = false; - FPS = av_q2d(av_guess_frame_rate(Demuxer.FormatContext, AVStream, frame)); + FPS = av_q2d(av_guess_frame_rate(Demuxer.FormatContext, AVStream, null)); + if (double.IsNaN(FPS) || double.IsInfinity(FPS) || FPS < 0.0) + FPS = 0.0; + } + + if (FPS > 0) + { + FrameDuration = (long)(10_000_000 / FPS); + TotalFrames = (int)(Duration / FrameDuration); } - FrameDuration = FPS > 0 ? (long) (10_000_000 / FPS) : 0; - TotalFrames = AVStream->duration > 0 && FrameDuration > 0 ? (int) (AVStream->duration * Timebase / FrameDuration) : (FrameDuration > 0 ? (int) (Demuxer.Duration / FrameDuration) : 0); + FieldOrder = cp->field_order == AVFieldOrder.Tt ? DeInterlace.TopField : (cp->field_order == AVFieldOrder.Bb ? DeInterlace.BottomField : DeInterlace.Progressive); + ColorTransfer = cp->color_trc; - int x, y; - AVRational sar = av_guess_sample_aspect_ratio(null, AVStream, null); - if (av_cmp_q(sar, av_make_q(0, 1)) <= 0) - sar = av_make_q(1, 1); + if (cp->color_range == AVColorRange.Mpeg) + ColorRange = ColorRange.Limited; + else if (cp->color_range == AVColorRange.Jpeg) + ColorRange = ColorRange.Full; - av_reduce(&x, &y, Width * sar.Num, Height * sar.Den, 1024 * 1024); - AspectRatio = new AspectRatio(x, y); + if (cp->color_space == AVColorSpace.Bt709) + ColorSpace = ColorSpace.Bt709; + else if (cp->color_space == AVColorSpace.Bt470bg) + ColorSpace = ColorSpace.Bt601; + else if (cp->color_space == AVColorSpace.Bt2020Ncl || cp->color_space == AVColorSpace.Bt2020Cl) + ColorSpace = ColorSpace.Bt2020; AVPacketSideData* pktSideData; - if ((pktSideData = av_packet_side_data_get(AVStream->codecpar->coded_side_data, AVStream->codecpar->nb_coded_side_data, AVPacketSideDataType.Displaymatrix)) != null && pktSideData->data != null) + if ((pktSideData = av_packet_side_data_get(cp->coded_side_data, cp->nb_coded_side_data, AVPacketSideDataType.Displaymatrix)) != null && pktSideData->data != null) { double rotation = -Math.Round(av_display_rotation_get((int*)pktSideData->data)); Rotation = rotation - (360*Math.Floor(rotation/360 + 0.9/360)); } + } + + // >= Second time fill from Decoder / Frame | TBR: We could avoid re-filling it when re-enabling a stream ... when same PixelFormat (VideoAcceleration) + public void Refresh(VideoDecoder decoder, AVFrame* frame) + { + var codecCtx= decoder.CodecCtx; + var format = decoder.VideoAccelerated && codecCtx->sw_pix_fmt != AVPixelFormat.None ? codecCtx->sw_pix_fmt : codecCtx->pix_fmt; - ColorRange = AVStream->codecpar->color_range == AVColorRange.Jpeg ? ColorRange.Full : ColorRange.Limited; - - var colorSpace = AVStream->codecpar->color_space; - if (colorSpace == AVColorSpace.Bt709) - ColorSpace = ColorSpace.BT709; - else if (colorSpace == AVColorSpace.Bt470bg) - ColorSpace = ColorSpace.BT601; - else if (colorSpace == AVColorSpace.Bt2020Ncl || colorSpace == AVColorSpace.Bt2020Cl) - ColorSpace = ColorSpace.BT2020; - - ColorTransfer = AVStream->codecpar->color_trc; - - // Avoid early check for HDR - //if (ColorTransfer == AVColorTransferCharacteristic.AribStdB67) - // HDRFormat = HDRFormat.HLG; - //else if (ColorTransfer == AVColorTransferCharacteristic.Smpte2084) - //{ - // for (int i = 0; i < AVStream->codecpar->nb_coded_side_data; i++) - // { - // var csdata = AVStream->codecpar->coded_side_data[i]; - // switch (csdata.type) - // { - // case AVPacketSideDataType.DoviConf: - // HDRFormat = HDRFormat.DolbyVision; - // break; - // case AVPacketSideDataType.DynamicHdr10Plus: - // HDRFormat = HDRFormat.HDRPlus; - // break; - // case AVPacketSideDataType.ContentLightLevel: - // //AVContentLightMetadata t2 = *((AVContentLightMetadata*)csdata.data); - // break; - // case AVPacketSideDataType.MasteringDisplayMetadata: - // //AVMasteringDisplayMetadata t1 = *((AVMasteringDisplayMetadata*)csdata.data); - // HDRFormat = HDRFormat.HDR; - // break; - // } - // } - //} - - if (frame != null) + if (PixelFormat != format) { - AVFrameSideData* frameSideData; - if ((frameSideData = av_frame_get_side_data(frame, AVFrameSideDataType.Displaymatrix)) != null && frameSideData->data != null) - { - var rotation = -Math.Round(av_display_rotation_get((int*)frameSideData->data)); - Rotation = rotation - (360*Math.Floor(rotation/360 + 0.9/360)); - } + if (format == AVPixelFormat.None) + return; - if (frame->flags.HasFlag(FrameFlags.Interlaced)) - FieldOrder = frame->flags.HasFlag(FrameFlags.TopFieldFirst) ? DeInterlace.TopField : DeInterlace.BottomField; + PixelFormat = format; + AnalysePixelFormat(); + } + else if (format == AVPixelFormat.None) // Both None (Should be removed from Demuxer's streams?*) + return; + + ReUpdate(); - ColorRange = frame->color_range == AVColorRange.Jpeg ? ColorRange.Full : ColorRange.Limited; + if (codecCtx->bit_rate > 0) + BitRate = codecCtx->bit_rate; // for logging only + if (SAR.Num == 0 || decoder.codecChanged) + { + Width = (uint)frame->width; + Height = (uint)frame->height; + + if (frame->sample_aspect_ratio.Num != 0) + SAR = frame->sample_aspect_ratio; + else if (codecCtx->sample_aspect_ratio.Num != 0) + SAR = codecCtx->sample_aspect_ratio; + else if (SAR.Num == 0) + SAR = new(1, 1); + + int x, y; + _ = av_reduce(&x, &y, Width * SAR.Num, Height * SAR.Den, 1024 * 1024); + AspectRatio = new(x, y); + } + + if (frame->flags.HasFlag(FrameFlags.Interlaced)) + FieldOrder = frame->flags.HasFlag(FrameFlags.TopFieldFirst) ? DeInterlace.TopField : DeInterlace.BottomField; + else + FieldOrder = codecCtx->field_order == AVFieldOrder.Tt ? DeInterlace.TopField : (codecCtx->field_order == AVFieldOrder.Bb ? DeInterlace.BottomField : DeInterlace.Progressive); + + if (ColorTransfer == AVColorTransferCharacteristic.Unspecified) // TBR: AVStream has AribStdB67 and Frame/CodecCtx has Bt2020_10 (priority to stream?)* + { if (frame->color_trc != AVColorTransferCharacteristic.Unspecified) ColorTransfer = frame->color_trc; + else if (codecCtx->color_trc != AVColorTransferCharacteristic.Unspecified) + ColorTransfer = codecCtx->color_trc; + } + if (ColorRange == ColorRange.None) + { + if (frame->color_range == AVColorRange.Mpeg) + ColorRange = ColorRange.Limited; + else if (frame->color_range == AVColorRange.Jpeg) + ColorRange = ColorRange.Full; + else if (codecCtx->color_range == AVColorRange.Mpeg) + ColorRange = ColorRange.Limited; + else if (codecCtx->color_range == AVColorRange.Jpeg) + ColorRange = ColorRange.Full; + else if (ColorRange == ColorRange.None) + ColorRange = ColorType == ColorType.YUV && !PixelFormatStr.Contains('j') ? ColorRange.Limited : ColorRange.Full; // yuvj family defaults to full + } + + if (ColorTransfer == AVColorTransferCharacteristic.AribStdB67) + HDRFormat = HDRFormat.HLG; + else if (ColorTransfer == AVColorTransferCharacteristic.Smpte2084) + { + if (av_frame_get_side_data(frame, AVFrameSideDataType.DoviMetadata) != null) + HDRFormat = HDRFormat.DolbyVision; + else if (av_frame_get_side_data(frame, AVFrameSideDataType.DynamicHdrPlus) != null) + HDRFormat = HDRFormat.HDRPlus; + else + HDRFormat = HDRFormat.HDR; + } + + if (HDRFormat != HDRFormat.None) // Forcing BT.2020 with PQ/HLG transfer? + ColorSpace = ColorSpace.Bt2020; + + if (ColorSpace == ColorSpace.None) + { if (frame->colorspace == AVColorSpace.Bt709) - ColorSpace = ColorSpace.BT709; + ColorSpace = ColorSpace.Bt709; else if (frame->colorspace == AVColorSpace.Bt470bg) - ColorSpace = ColorSpace.BT601; + ColorSpace = ColorSpace.Bt601; else if (frame->colorspace == AVColorSpace.Bt2020Ncl || frame->colorspace == AVColorSpace.Bt2020Cl) - ColorSpace = ColorSpace.BT2020; - - if (ColorTransfer == AVColorTransferCharacteristic.AribStdB67) - HDRFormat = HDRFormat.HLG; - else if (ColorTransfer == AVColorTransferCharacteristic.Smpte2084) - { - var dolbyData = av_frame_get_side_data(frame, AVFrameSideDataType.DoviMetadata); - if (dolbyData != null) - HDRFormat = HDRFormat.DolbyVision; - else - { - var hdrPlusData = av_frame_get_side_data(frame, AVFrameSideDataType.DynamicHdrPlus); - if (hdrPlusData != null) - { - //AVDynamicHDRPlus* x1 = (AVDynamicHDRPlus*)hdrPlusData->data; - HDRFormat = HDRFormat.HDRPlus; - } - else - { - //AVMasteringDisplayMetadata t1; - //AVContentLightMetadata t2; - - //var masterData = av_frame_get_side_data(frame, AVFrameSideDataType.MasteringDisplayMetadata); - //if (masterData != null) - // t1 = *((AVMasteringDisplayMetadata*)masterData->data); - //var lightData = av_frame_get_side_data(frame, AVFrameSideDataType.ContentLightLevel); - //if (lightData != null) - // t2 = *((AVContentLightMetadata*) lightData->data); - - HDRFormat = HDRFormat.HDR; - } - } - } - - if (HDRFormat != HDRFormat.None) // Forcing BT.2020 with PQ/HLG transfer? - ColorSpace = ColorSpace.BT2020; + ColorSpace = ColorSpace.Bt2020; + else if (codecCtx->colorspace == AVColorSpace.Bt709) + ColorSpace = ColorSpace.Bt709; + else if (codecCtx->colorspace == AVColorSpace.Bt470bg) + ColorSpace = ColorSpace.Bt601; + else if (codecCtx->colorspace == AVColorSpace.Bt2020Ncl || codecCtx->colorspace == AVColorSpace.Bt2020Cl) + ColorSpace = ColorSpace.Bt2020; else if (ColorSpace == ColorSpace.None) - ColorSpace = Height > 576 ? ColorSpace.BT709 : ColorSpace.BT601; + ColorSpace = Height > 576 ? ColorSpace.Bt709 : ColorSpace.Bt601; + } + + // We consider that FPS can't change (only if it was missing we fill it) + if (FPS == 0.0) + { + var newFps = av_q2d(codecCtx->framerate); + FPS = double.IsNaN(newFps) || double.IsInfinity(newFps) || newFps <= 0.0 ? 25 : newFps; // Force default to 25 fps + FrameDuration = (long)(10_000_000 / FPS); + TotalFrames = (int)(Duration / FrameDuration); + Demuxer.VideoPackets.frameDuration = FrameDuration; } - if (PixelFormat == AVPixelFormat.None || PixelPlanes > 0) // Should re-analyze? (possible to get different pixel format on 2nd... call?) - return; + // FPS2 / FrameDuration2 (DeInterlace) + if (FieldOrder != DeInterlace.Progressive) + { + FPS2 = FPS; + FrameDuration2 = FrameDuration; + FPS /= 2; + FrameDuration *= 2; + } + else + { + FPS2 = FPS * 2; + FrameDuration2 = FrameDuration / 2; + } - PixelFormatDesc = av_pix_fmt_desc_get(PixelFormat); - var comps = PixelFormatDesc->comp.ToArray(); - PixelComps = new AVComponentDescriptor[PixelFormatDesc->nb_components]; - for (int i=0; idata != null) + { + var rotation = -Math.Round(av_display_rotation_get((int*)frameSideData->data)); + Rotation = rotation - (360*Math.Floor(rotation/360 + 0.9/360)); + } - PixelInterleaved= PixelFormatDesc->log2_chroma_w != PixelFormatDesc->log2_chroma_h; - IsRGB = (PixelFormatDesc->flags & PixFmtFlags.Rgb) != 0; + if (CanDebug) + Demuxer.Log.Debug($"Stream Info (Filled)\r\n{GetDump()}"); + } - PixelSameDepth = true; + void AnalysePixelFormat() + { + PixelFormatStr = LowerCaseFirstChar(PixelFormat.ToString()); + PixelFormatDesc = av_pix_fmt_desc_get(PixelFormat); + PixelComps = PixelFormatDesc->comp.ToArray(); + PixelInterleaved= PixelFormatDesc->log2_chroma_w != PixelFormatDesc->log2_chroma_h; + ColorType = PixelComps.Length == 1 ? ColorType.Gray : ((PixelFormatDesc->flags & PixFmtFlags.Rgb) != 0 ? ColorType.RGB : ColorType.YUV); PixelPlanes = 0; + if (PixelComps.Length > 0) { PixelComp0Depth = PixelComps[0].depth; - int prevBit = PixelComp0Depth; - for (int i=0; i PixelPlanes) PixelPlanes = PixelComps[i].plane; - if (prevBit != PixelComps[i].depth) - PixelSameDepth = false; - } - PixelPlanes++; } } diff --git a/FlyleafLib/MediaFramework/RunThreadBase.cs b/FlyleafLib/MediaFramework/RunThreadBase.cs index cceade1..e6435f8 100644 --- a/FlyleafLib/MediaFramework/RunThreadBase.cs +++ b/FlyleafLib/MediaFramework/RunThreadBase.cs @@ -1,8 +1,4 @@ -using System.Threading; - -using static FlyleafLib.Logger; - -namespace FlyleafLib.MediaFramework; +namespace FlyleafLib.MediaFramework; public abstract class RunThreadBase : NotifyPropertyChanged { @@ -13,8 +9,8 @@ public Status Status { { lock (lockStatus) { - if (CanDebug && _Status != Status.QueueFull && value != Status.QueueFull && _Status != Status.QueueEmpty && value != Status.QueueEmpty) - Log.Debug($"{_Status} -> {value}"); + if (CanTrace && _Status != Status.QueueFull && value != Status.QueueFull && _Status != Status.QueueEmpty && value != Status.QueueEmpty) + Log.Trace($"{_Status} -> {value}"); _Status = value; } @@ -41,7 +37,7 @@ protected string threadName { set { _threadName = value; - Log = new LogHandler(("[#" + UniqueId + "]").PadRight(8, ' ') + $" [{threadName}] "); + Log = new(("[#" + UniqueId + "]").PadRight(8, ' ') + $" [{threadName}] "); } } string _threadName; @@ -51,7 +47,7 @@ protected string threadName { internal object lockStatus = new(); public RunThreadBase(int uniqueId = -1) - => UniqueId = uniqueId == -1 ? Utils.GetUniqueId() : uniqueId; + => UniqueId = uniqueId == -1 ? GetUniqueId() : uniqueId; public void Pause() { diff --git a/FlyleafLib/MediaPlayer/Activity.cs b/FlyleafLib/MediaPlayer/Activity.cs index caf6f5d..6094e01 100644 --- a/FlyleafLib/MediaPlayer/Activity.cs +++ b/FlyleafLib/MediaPlayer/Activity.cs @@ -30,7 +30,7 @@ public ActivityMode Mode else swMouse.Restart(); - Utils.UI(SetMode); + UI(SetMode); } } internal ActivityMode _Mode = ActivityMode.FullActive, mode = ActivityMode.FullActive; @@ -94,7 +94,7 @@ internal void SetMode() lock (cursorLocker) { // Windows 11 Bug: When pressing winkey (ShowCursor return negative but still visible, SetCursor(null) fails too) - while (Utils.NativeMethods.ShowCursor(false) >= 0); + while (NativeMethods.ShowCursor(false) >= 0); isCursorHidden = true; } @@ -103,7 +103,7 @@ internal void SetMode() { lock (cursorLocker) { - while (Utils.NativeMethods.ShowCursor(true) < 0); + while (NativeMethods.ShowCursor(true) < 0); isCursorHidden = false; } } @@ -161,7 +161,7 @@ public bool PreFilterMessage(ref Message m) { lock (cursorLocker) { - while (Utils.NativeMethods.ShowCursor(true) < 0); + while (NativeMethods.ShowCursor(true) < 0); isCursorHidden = false; foreach(var player in Engine.Players) player.Activity.RefreshFullActive(); diff --git a/FlyleafLib/MediaPlayer/Audio.cs b/FlyleafLib/MediaPlayer/Audio.cs index b9e97ed..dffde0e 100644 --- a/FlyleafLib/MediaPlayer/Audio.cs +++ b/FlyleafLib/MediaPlayer/Audio.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.ObjectModel; - -using Vortice.Multimedia; +using Vortice.Multimedia; using Vortice.XAudio2; using static Vortice.XAudio2.XAudio2; @@ -10,8 +7,6 @@ using FlyleafLib.MediaFramework.MediaFrame; using FlyleafLib.MediaFramework.MediaStream; -using static FlyleafLib.Logger; - namespace FlyleafLib.MediaPlayer; public class Audio : NotifyPropertyChanged @@ -261,7 +256,7 @@ internal void AddSamples(AudioFrame aFrame) try { if (CanTrace) - player.Log.Trace($"[A] Presenting {Utils.TicksToTime(player.aFrame.timestamp)}"); + player.Log.Trace($"[A] Presenting {TicksToTime(player.aFrame.timestamp)}"); framesDisplayed++; diff --git a/FlyleafLib/MediaPlayer/Commands.cs b/FlyleafLib/MediaPlayer/Commands.cs index a390dda..6f03562 100644 --- a/FlyleafLib/MediaPlayer/Commands.cs +++ b/FlyleafLib/MediaPlayer/Commands.cs @@ -1,5 +1,4 @@ -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using FlyleafLib.Controls.WPF; using FlyleafLib.MediaFramework.MediaPlaylist; diff --git a/FlyleafLib/MediaPlayer/Data.cs b/FlyleafLib/MediaPlayer/Data.cs index 5e699cf..7bed4cd 100644 --- a/FlyleafLib/MediaPlayer/Data.cs +++ b/FlyleafLib/MediaPlayer/Data.cs @@ -1,9 +1,8 @@ using FlyleafLib.MediaFramework.MediaContext; using FlyleafLib.MediaFramework.MediaStream; -using System; -using System.Collections.ObjectModel; namespace FlyleafLib.MediaPlayer; + public class Data : NotifyPropertyChanged { /// diff --git a/FlyleafLib/MediaPlayer/Player.Extra.cs b/FlyleafLib/MediaPlayer/Player.Extra.cs index d7c920f..606983a 100644 --- a/FlyleafLib/MediaPlayer/Player.Extra.cs +++ b/FlyleafLib/MediaPlayer/Player.Extra.cs @@ -1,6 +1,4 @@ -using System; -using System.Drawing.Imaging; -using System.IO; +using System.Drawing.Imaging; using System.Windows; using FlyleafLib.MediaFramework.MediaDecoder; @@ -8,9 +6,6 @@ using FlyleafLib.MediaFramework.MediaFrame; using FlyleafLib.MediaFramework.MediaRenderer; -using static FlyleafLib.Utils; -using static FlyleafLib.Logger; - namespace FlyleafLib.MediaPlayer; unsafe partial class Player @@ -55,6 +50,9 @@ public void SeekForward_(long offset, bool accurate) Seek((int)(seekTs / 10000), true); } + public void SeekToStart() => Seek(0); + public void SeekToEnd() => Seek((int)((Duration / 10_000) - TimeSpan.FromSeconds(5).TotalMilliseconds)); + public void SeekToChapter(Demuxer.Chapter chapter) => /* TODO * Accurate pts required (backward/forward check) @@ -247,7 +245,7 @@ public void ShowFramePrev() if (VideoDecoder.Frames.IsEmpty) { reversePlaybackResync = true; // Temp fix for previous timestamps until we seperate GetFrame for Extractor and the Player - vFrame = VideoDecoder.GetFrame(VideoDecoder.GetFrameNumber(CurTime) - 1); + vFrame = VideoDecoder.GetFrame(VideoDecoder.GetFrameNumber(CurTime) - 1, true); } else VideoDecoder.Frames.TryDequeue(out vFrame); diff --git a/FlyleafLib/MediaPlayer/Player.Keys.cs b/FlyleafLib/MediaPlayer/Player.Keys.cs index ab254ed..382699a 100644 --- a/FlyleafLib/MediaPlayer/Player.Keys.cs +++ b/FlyleafLib/MediaPlayer/Player.Keys.cs @@ -1,10 +1,7 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.InteropServices; using System.Text.Json.Serialization; using System.Windows.Input; -using static FlyleafLib.Logger; namespace FlyleafLib.MediaPlayer; @@ -477,6 +474,12 @@ public Action GetKeyBindingAction(KeyBindingAction action) case KeyBindingAction.SeekForward4: return player.SeekForward4; + case KeyBindingAction.SeekToStart: + return player.SeekToStart; + + case KeyBindingAction.SeekToEnd: + return player.SeekToEnd; + case KeyBindingAction.SubsCurSeek: return player.Subtitles.CurSeek; case KeyBindingAction.SubsPrevSeek: @@ -569,6 +572,8 @@ public Action GetKeyBindingAction(KeyBindingAction action) { KeyBindingAction.Stop }, { KeyBindingAction.Flush }, { KeyBindingAction.ToggleSeekAccurate }, + { KeyBindingAction.SeekToStart }, + { KeyBindingAction.SeekToEnd }, { KeyBindingAction.SpeedAdd }, { KeyBindingAction.SpeedAdd2 }, { KeyBindingAction.SpeedRemove }, @@ -615,6 +620,7 @@ public void SetAction(Action action, bool isKeyUp) public Action ActionInternal { get; internal set; } } +// NOTE: To be able to support compatibility with previous config versions add new to end? public enum KeyBindingAction { [Description(nameof(Custom))] @@ -731,6 +737,10 @@ public enum KeyBindingAction SeekForward4, [Description("Seek backwards (4)")] SeekBackward4, + [Description("Seek to start position")] + SeekToStart, + [Description("Seek to end position")] + SeekToEnd, [Description("Seek to the previous subtitle")] SubsPrevSeek, diff --git a/FlyleafLib/MediaPlayer/Player.Open.cs b/FlyleafLib/MediaPlayer/Player.Open.cs index f79a421..cfe795f 100644 --- a/FlyleafLib/MediaPlayer/Player.Open.cs +++ b/FlyleafLib/MediaPlayer/Player.Open.cs @@ -1,15 +1,8 @@ -using System; -using System.Collections.Concurrent; -using System.IO; -using System.Threading.Tasks; - -using FlyleafLib.MediaFramework.MediaDecoder; +using FlyleafLib.MediaFramework.MediaDecoder; using FlyleafLib.MediaFramework.MediaPlaylist; using FlyleafLib.MediaFramework.MediaStream; -using static FlyleafLib.Logger; using static FlyleafLib.MediaFramework.MediaContext.DecoderContext; -using static FlyleafLib.Utils; namespace FlyleafLib.MediaPlayer; diff --git a/FlyleafLib/MediaPlayer/Player.Playback.cs b/FlyleafLib/MediaPlayer/Player.Playback.cs index e9b18c1..6d3d7c3 100644 --- a/FlyleafLib/MediaPlayer/Player.Playback.cs +++ b/FlyleafLib/MediaPlayer/Player.Playback.cs @@ -1,14 +1,9 @@ -using System; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; +using System.Runtime.InteropServices; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; using FlyleafLib.MediaFramework.MediaDecoder; using FlyleafLib.MediaFramework.MediaFrame; -using static FlyleafLib.Utils; -using static FlyleafLib.Logger; namespace FlyleafLib.MediaPlayer; diff --git a/FlyleafLib/MediaPlayer/Player.Screamers.cs b/FlyleafLib/MediaPlayer/Player.Screamers.cs index f705103..56c83ca 100644 --- a/FlyleafLib/MediaPlayer/Player.Screamers.cs +++ b/FlyleafLib/MediaPlayer/Player.Screamers.cs @@ -1,11 +1,8 @@ using System.Diagnostics; -using System.Threading; using FlyleafLib.MediaFramework.MediaDecoder; using FlyleafLib.MediaFramework.MediaFrame; using FlyleafLib.MediaFramework.MediaStream; -using static FlyleafLib.Utils; -using static FlyleafLib.Logger; namespace FlyleafLib.MediaPlayer; diff --git a/FlyleafLib/MediaPlayer/Player.cs b/FlyleafLib/MediaPlayer/Player.cs index 1c240f6..6e39522 100644 --- a/FlyleafLib/MediaPlayer/Player.cs +++ b/FlyleafLib/MediaPlayer/Player.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Threading; - -using FlyleafLib.Controls; +using FlyleafLib.Controls; using FlyleafLib.MediaFramework.MediaContext; using FlyleafLib.MediaFramework.MediaDecoder; using FlyleafLib.MediaFramework.MediaFrame; @@ -12,7 +6,6 @@ using FlyleafLib.MediaFramework.MediaPlaylist; using FlyleafLib.MediaFramework.MediaDemuxer; -using static FlyleafLib.Utils; using static FlyleafLib.Logger; namespace FlyleafLib.MediaPlayer; @@ -485,23 +478,23 @@ public Player(Config config = null) if (config != null) { if (config.Player.player != null) - throw new Exception("Player's configuration is already assigned to another player"); + throw new("Player's configuration is already assigned to another player"); Config = config; } else Config = new Config(); - PlayerId = GetUniqueId(); - Log = new LogHandler(("[#" + PlayerId + "]").PadRight(8, ' ') + " [Player ] "); + PlayerId = GetUniqueId(); + Log = new(("[#" + PlayerId + "]").PadRight(8, ' ') + " [Player ] "); Log.Debug($"Creating Player (Usage = {Config.Player.Usage})"); - Activity = new Activity(this); - Audio = new Audio(this); - Video = new Video(this); - Subtitles = new Subtitles(this); - Data = new Data(this); - Commands = new Commands(this); + Activity = new(this); + Audio = new(this); + Video = new(this); + Subtitles = new(this); + Data = new(this); + Commands = new(this); Config.SetPlayer(this); @@ -511,7 +504,7 @@ public Player(Config config = null) Config.Subtitles.Enabled = false; } - decoder = new DecoderContext(Config, PlayerId) { Tag = this }; + decoder = new(Config, PlayerId) { Tag = this }; Engine.AddPlayer(this); if (decoder.VideoDecoder.Renderer != null) diff --git a/FlyleafLib/MediaPlayer/Subtitles.cs b/FlyleafLib/MediaPlayer/Subtitles.cs index 6ac244d..798a1ae 100644 --- a/FlyleafLib/MediaPlayer/Subtitles.cs +++ b/FlyleafLib/MediaPlayer/Subtitles.cs @@ -1,5 +1,4 @@ -using System.Collections.ObjectModel; -using FlyleafLib.MediaFramework.MediaContext; +using FlyleafLib.MediaFramework.MediaContext; using FlyleafLib.MediaFramework.MediaStream; using System.ComponentModel; using System.Diagnostics; diff --git a/FlyleafLib/MediaPlayer/Video.cs b/FlyleafLib/MediaPlayer/Video.cs index 91eb65d..c11616d 100644 --- a/FlyleafLib/MediaPlayer/Video.cs +++ b/FlyleafLib/MediaPlayer/Video.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.ObjectModel; - -using FlyleafLib.MediaFramework.MediaContext; +using FlyleafLib.MediaFramework.MediaContext; using FlyleafLib.MediaFramework.MediaStream; namespace FlyleafLib.MediaPlayer; diff --git a/FlyleafLib/Plugins/OpenDefault.cs b/FlyleafLib/Plugins/OpenDefault.cs index ac3868f..72b5a8b 100644 --- a/FlyleafLib/Plugins/OpenDefault.cs +++ b/FlyleafLib/Plugins/OpenDefault.cs @@ -1,7 +1,4 @@ -using System; -using System.IO; - -using FlyleafLib.MediaFramework.MediaPlaylist; +using FlyleafLib.MediaFramework.MediaPlaylist; namespace FlyleafLib.Plugins; @@ -39,7 +36,7 @@ public OpenResults Open() // Proper Url Format bool isWeb = false; - string ext = Utils.GetUrlExtention(Playlist.Url); + string ext = GetUrlExtention(Playlist.Url); Uri uri = null; string localPath= null; @@ -84,7 +81,7 @@ public OpenResults Open() foreach(var mitem in items) { - AddPlaylistItem(new PlaylistItem() + AddPlaylistItem(new() { Title = mitem.Title, Url = mitem.Url, diff --git a/FlyleafLib/Plugins/OpenSubtitles.cs b/FlyleafLib/Plugins/OpenSubtitles.cs index 368a446..3e503f7 100644 --- a/FlyleafLib/Plugins/OpenSubtitles.cs +++ b/FlyleafLib/Plugins/OpenSubtitles.cs @@ -1,8 +1,6 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; +using System.Linq; using FlyleafLib.MediaFramework.MediaStream; +using FlyleafLib.MediaFramework.MediaPlaylist; using Lingua; namespace FlyleafLib.Plugins; @@ -20,7 +18,7 @@ public class OpenSubtitles : PluginBase, IOpenSubtitles, ISearchLocalSubtitles return detector; }, true); - private static readonly HashSet ExtSet = new(Utils.ExtensionsSubtitles, StringComparer.OrdinalIgnoreCase); + private static readonly HashSet ExtSet = new(ExtensionsSubtitles, StringComparer.OrdinalIgnoreCase); public OpenSubtitlesResults Open(string url) { @@ -243,7 +241,7 @@ private static bool IsSubtitleBitmap(string path) { FileInfo fi = new(path); - return Utils.ExtensionsSubtitlesBitmap.Contains(fi.Extension.TrimStart('.').ToLower()); + return ExtensionsSubtitlesBitmap.Contains(fi.Extension.TrimStart('.').ToLower()); } catch { diff --git a/FlyleafLib/Plugins/PluginBase.cs b/FlyleafLib/Plugins/PluginBase.cs index bb7eaf6..d26240b 100644 --- a/FlyleafLib/Plugins/PluginBase.cs +++ b/FlyleafLib/Plugins/PluginBase.cs @@ -1,13 +1,7 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; - -using FlyleafLib.MediaFramework.MediaContext; +using FlyleafLib.MediaFramework.MediaContext; using FlyleafLib.MediaFramework.MediaPlaylist; using FlyleafLib.MediaFramework.MediaStream; -using static FlyleafLib.Utils; - namespace FlyleafLib.Plugins; public abstract class PluginBase : PluginType, IPlugin diff --git a/FlyleafLib/Plugins/PluginHandler.cs b/FlyleafLib/Plugins/PluginHandler.cs index 52e739e..ef6b214 100644 --- a/FlyleafLib/Plugins/PluginHandler.cs +++ b/FlyleafLib/Plugins/PluginHandler.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; +using System.Linq; using FlyleafLib.MediaFramework.MediaPlaylist; using FlyleafLib.MediaFramework.MediaStream; @@ -62,10 +60,10 @@ public Dictionary LogHandler Log; public PluginHandler(Config config, int uniqueId = -1) { - Config = config; - UniqueId= uniqueId == -1 ? Utils.GetUniqueId() : uniqueId; - Playlist = new Playlist(UniqueId); - Log = new LogHandler(("[#" + UniqueId + "]").PadRight(8, ' ') + " [PluginHandler ] "); + Config = config; + UniqueId = uniqueId == -1 ? GetUniqueId() : uniqueId; + Playlist = new(UniqueId); + Log = new(("[#" + UniqueId + "]").PadRight(8, ' ') + " [PluginHandler ] "); LoadPlugins(); } @@ -91,9 +89,9 @@ private void LoadPlugins() try { var plugin = CreatePluginInstance(type, this); - plugin.Log = new LogHandler(("[#" + UniqueId + "]").PadRight(8, ' ') + $" [{plugin.Name,-14}] "); + plugin.Log = new(("[#" + UniqueId + "]").PadRight(8, ' ') + $" [{plugin.Name,-14}] "); Plugins.Add(plugin.Name, plugin); - } catch (Exception e) { Log.Error($"[Plugins] [Error] Failed to load plugin ... ({e.Message} {Utils.GetRecInnerException(e)}"); } + } catch (Exception e) { Log.Error($"[Plugins] [Error] Failed to load plugin ... ({e.Message} {GetRecInnerException(e)}"); } } PluginsOpen = []; diff --git a/FlyleafLib/Plugins/StreamSuggester.cs b/FlyleafLib/Plugins/StreamSuggester.cs index ad7fa85..ff78971 100644 --- a/FlyleafLib/Plugins/StreamSuggester.cs +++ b/FlyleafLib/Plugins/StreamSuggester.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; +using System.Linq; using FlyleafLib.MediaFramework.MediaPlaylist; using FlyleafLib.MediaFramework.MediaStream; diff --git a/FlyleafLib/Utils/Logger.cs b/FlyleafLib/Utils/Logger.cs index ea68c7a..3ce7d6e 100644 --- a/FlyleafLib/Utils/Logger.cs +++ b/FlyleafLib/Utils/Logger.cs @@ -1,10 +1,4 @@ -using System.Collections.Generic; -using System.Collections.Concurrent; -using System.IO; -using System.Text; -using System.Threading.Tasks; - -namespace FlyleafLib; +namespace FlyleafLib; public static class Logger { @@ -22,12 +16,12 @@ internal static Action static string lastOutput = ""; static ConcurrentQueue - fileData = new(); + fileData = []; static bool fileTaskRunning; static FileStream fileStream; static object lockFileStream = new(); static Dictionary - logLevels = new(); + logLevels = []; static Logger() { @@ -166,11 +160,11 @@ public class LogHandler public LogHandler(string prefix = "") => Prefix = prefix; - public void Error(string msg) => Logger.Log($"{Prefix}{msg}", LogLevel.Error); - public void Info(string msg) => Logger.Log($"{Prefix}{msg}", LogLevel.Info); - public void Warn(string msg) => Logger.Log($"{Prefix}{msg}", LogLevel.Warn); - public void Debug(string msg) => Logger.Log($"{Prefix}{msg}", LogLevel.Debug); - public void Trace(string msg) => Logger.Log($"{Prefix}{msg}", LogLevel.Trace); + public void Error(string msg) => Log($"{Prefix}{msg}", LogLevel.Error); + public void Info(string msg) => Log($"{Prefix}{msg}", LogLevel.Info); + public void Warn(string msg) => Log($"{Prefix}{msg}", LogLevel.Warn); + public void Debug(string msg) => Log($"{Prefix}{msg}", LogLevel.Debug); + public void Trace(string msg) => Log($"{Prefix}{msg}", LogLevel.Trace); } public enum LogLevel diff --git a/FlyleafLib/Utils/ObservableDictionary.cs b/FlyleafLib/Utils/ObservableDictionary.cs index 25ab656..a1b8bfa 100644 --- a/FlyleafLib/Utils/ObservableDictionary.cs +++ b/FlyleafLib/Utils/ObservableDictionary.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Collections.Specialized; +using System.Collections.Specialized; using System.ComponentModel; using System.Linq; diff --git a/FlyleafLib/Utils/Utils.cs b/FlyleafLib/Utils/Utils.cs index 091536b..7f1935b 100644 --- a/FlyleafLib/Utils/Utils.cs +++ b/FlyleafLib/Utils/Utils.cs @@ -1,15 +1,10 @@ -using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; -using System.IO; using System.IO.Compression; using System.Linq; using System.Net.Http; using System.Runtime.InteropServices; -using System.Text; using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; using System.Windows; using CliWrap; using Microsoft.Win32; @@ -43,7 +38,7 @@ public static partial class Utils public static readonly List ExtensionsSubtitlesText = [ - "ass", "ssa", "srt", "txt", "text", "vtt" + "ass", "ssa", "srt", "text", "vtt" ]; public static readonly List ExtensionsSubtitlesBitmap = @@ -479,6 +474,14 @@ public static string FixFileUrl(string url) return url; } + public static string LowerCaseFirstChar(string input) + { // check null manually + Span buffer = stackalloc char[input.Length]; + input.AsSpan().CopyTo(buffer); + buffer[0] = char.ToLowerInvariant(buffer[0]); + + return new string(buffer); + } /// /// Convert Windows lnk file path to target path @@ -684,6 +687,115 @@ public static string ToHexadecimal(byte[] bytes) [GeneratedRegex(@"[^a-z0-9]$", RegexOptions.IgnoreCase)] private static partial Regex RxNonAlphaNumeric(); + #region Temp Transfer (v4) +#nullable enable + static string metaSpaces = new(' ',"[Metadata] ".Length); + public static string GetDumpMetadata(Dictionary? metadata, string? exclude = null) + { + if (metadata == null || metadata.Count == 0) + return ""; + + int maxLen = 0; + foreach(var item in metadata) + if (item.Key.Length > maxLen && item.Key != exclude) + maxLen = item.Key.Length; + + string dump = ""; + int i = 1; + foreach(var item in metadata) + { + if (item.Key == exclude) + { + i++; + continue; + } + + if (i == metadata.Count) + dump += $"{item.Key.PadRight(maxLen)}: {item.Value}"; + else + dump += $"{item.Key.PadRight(maxLen)}: {item.Value}\r\n\t{metaSpaces}"; + + i++; + } + + if (dump == "") + return ""; + + return $"\t[Metadata] {dump}"; + } + public static string TicksToTime2(long ticks) + { + if (ticks == NoTs) + return "-"; + + if (ticks == 0) + return "00:00:00.000"; + + return TsToTime(TimeSpan.FromTicks(ticks)); // TimeSpan.FromTicks(ticks).ToString("g"); + } + public static string McsToTime(long micro) + { + if (micro == NoTs) + return "-"; + + if (micro == 0) + return "00:00:00.000"; + + return TsToTime(TimeSpan.FromMicroseconds(micro)); + } + public static string TsToTime(TimeSpan ts) + { + if (ts.Ticks > 0) + { + if (ts.TotalDays < 1) + return ts.ToString(@"hh\:mm\:ss\.fff"); + else + return ts.ToString(@"d\-hh\:mm\:ss\.fff"); + } + + if (ts.TotalDays > -1) + return ts.ToString(@"\-hh\:mm\:ss\.fff"); + else + return ts.ToString(@"\-d\-hh\:mm\:ss\.fff"); + } + public static string DoubleToTimeMini(double d) => d.ToString("#.000", CultureInfo.InvariantCulture); + public static List GetFlagsAsList(T value) where T : Enum + { + List values = []; + + var enumValues = Enum.GetValuesAsUnderlyingType(typeof(T)); + //var enumValues = Enum.GetValues(typeof(T)); // breaks AOT? + + foreach(T flag in enumValues) + if (value.HasFlag(flag) && flag.ToString() != "None") + values.Add(flag); + + return values; + } + public static string? GetFlagsAsString(T value, string separator = " | ") where T : Enum + { + string? ret = null; + List values = GetFlagsAsList(value); + + if (values.Count == 0) + return ret; + + for (int i = 0; i < values.Count - 1; i++) + ret += values[i] + separator; + + return ret + values[^1]; + } + public unsafe static string GetFourCCString(uint fourcc) + { + byte* t1 = (byte*)av_mallocz(AV_FOURCC_MAX_STRING_SIZE); + av_fourcc_make_string(t1, fourcc); + string ret = BytePtrToStringUTF8(t1)!; + av_free(t1); + return ret; + } +#nullable restore + #endregion + public static string TruncateString(string str, int maxLength, string suffix = "...") { if (string.IsNullOrEmpty(str)) diff --git a/FlyleafLib/Utils/ZOrderHandler.cs b/FlyleafLib/Utils/ZOrderHandler.cs index 4589cad..3e191ec 100644 --- a/FlyleafLib/Utils/ZOrderHandler.cs +++ b/FlyleafLib/Utils/ZOrderHandler.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; -using System.Threading.Tasks; using System.Windows.Interop; using System.Windows; diff --git a/LLPlayer/Views/FlyleafOverlay.xaml b/LLPlayer/Views/FlyleafOverlay.xaml index 9e500b5..2f7bb75 100644 --- a/LLPlayer/Views/FlyleafOverlay.xaml +++ b/LLPlayer/Views/FlyleafOverlay.xaml @@ -26,6 +26,21 @@ + + + + + + + + @@ -82,21 +97,6 @@ - - - - - - - -