diff --git a/.gitmodules b/.gitmodules index e323a49..932cd36 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "lib/Theorafile"] path = lib/Theorafile url = https://github.com/FNA-XNA/Theorafile.git +[submodule "lib/dav1dfile"] + path = lib/dav1dfile + url = git@github.com:MoonsideGames/dav1dfile.git diff --git a/MoonWorks.csproj b/MoonWorks.csproj index 9346bc8..52b7977 100644 --- a/MoonWorks.csproj +++ b/MoonWorks.csproj @@ -17,6 +17,7 @@ + diff --git a/MoonWorks.dll.config b/MoonWorks.dll.config index c076485..cc9a116 100644 --- a/MoonWorks.dll.config +++ b/MoonWorks.dll.config @@ -19,4 +19,8 @@ + + + + diff --git a/lib/RefreshCS b/lib/RefreshCS index ebf5111..9edb2d1 160000 --- a/lib/RefreshCS +++ b/lib/RefreshCS @@ -1 +1 @@ -Subproject commit ebf511133aa6f567c004d687acac474e1649bbde +Subproject commit 9edb2d1c97f4a38dd3d6d87298d6c604defa4fc8 diff --git a/lib/dav1dfile b/lib/dav1dfile new file mode 160000 index 0000000..8ba817f --- /dev/null +++ b/lib/dav1dfile @@ -0,0 +1 @@ +Subproject commit 8ba817ff9bf65ed2fa7b9c12d70112b29c450cf4 diff --git a/src/Graphics/CommandBuffer.cs b/src/Graphics/CommandBuffer.cs index 8992819..a664791 100644 --- a/src/Graphics/CommandBuffer.cs +++ b/src/Graphics/CommandBuffer.cs @@ -1946,7 +1946,17 @@ namespace MoonWorks.Graphics /// /// Asynchronously copies YUV data into three textures. Use with compressed video. /// - public void SetTextureDataYUV(Texture yTexture, Texture uTexture, Texture vTexture, IntPtr dataPtr, uint dataLengthInBytes) + public void SetTextureDataYUV( + Texture yTexture, + Texture uTexture, + Texture vTexture, + IntPtr yDataPtr, + IntPtr uDataPtr, + IntPtr vDataPtr, + uint yDataLengthInBytes, + uint uvDataLengthInBytes, + uint yStride, + uint uvStride) { #if DEBUG AssertRenderPassInactive("Cannot copy during render pass!"); @@ -1962,8 +1972,13 @@ namespace MoonWorks.Graphics yTexture.Height, uTexture.Width, uTexture.Height, - dataPtr, - dataLengthInBytes + yDataPtr, + uDataPtr, + vDataPtr, + yDataLengthInBytes, + uvDataLengthInBytes, + yStride, + uvStride ); } diff --git a/src/Video/VideoAV1.cs b/src/Video/VideoAV1.cs new file mode 100644 index 0000000..e423284 --- /dev/null +++ b/src/Video/VideoAV1.cs @@ -0,0 +1,88 @@ +using System; +using System.IO; + +namespace MoonWorks.Video +{ + public unsafe class VideoAV1 : IDisposable + { + public IntPtr Handle => handle; + IntPtr handle; + + public int Width => width; + public int Height => height; + public double FramesPerSecond => 28.97; + public Dav1dfile.PixelLayout PixelLayout => pixelLayout; + public int UVWidth { get; } + public int UVHeight { get; } + + private int width; + private int height; + private double fps; + private Dav1dfile.PixelLayout pixelLayout; + + bool IsDisposed; + + public VideoAV1(string filename) + { + if (!File.Exists(filename)) + { + throw new ArgumentException("Video file not found!"); + } + + if (Dav1dfile.df_fopen(filename, out handle) == 0) + { + throw new Exception("Failed to open video file!"); + } + + Dav1dfile.df_videoinfo(Handle, out width, out height, out pixelLayout); + + if (pixelLayout == Dav1dfile.PixelLayout.I420) + { + UVWidth = Width / 2; + UVHeight = Height / 2; + } + else if (pixelLayout == Dav1dfile.PixelLayout.I422) + { + UVWidth = Width / 2; + UVHeight = Height; + } + else if (pixelLayout == Dav1dfile.PixelLayout.I444) + { + UVWidth = width; + UVHeight = height; + } + else + { + throw new NotSupportedException("Unrecognized YUV format!"); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + // dispose managed state (managed objects) + } + + // free unmanaged resources (unmanaged objects) + Dav1dfile.df_close(Handle); + + IsDisposed = true; + } + } + + ~VideoAV1() + { + Dispose(disposing: false); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Video/VideoPlayer.cs b/src/Video/VideoPlayer.cs index d4c52ad..5372768 100644 --- a/src/Video/VideoPlayer.cs +++ b/src/Video/VideoPlayer.cs @@ -11,20 +11,9 @@ namespace MoonWorks.Video public Texture RenderTexture { get; private set; } = null; public VideoState State { get; private set; } = VideoState.Stopped; public bool Loop { get; set; } - public float Volume { - get => volume; - set - { - volume = value; - if (audioStream != null) - { - audioStream.Volume = value; - } - } - } public float PlaybackSpeed { get; set; } = 1; - private Video Video = null; + private VideoAV1 Video = null; private GraphicsDevice GraphicsDevice; private Texture yTexture = null; @@ -32,19 +21,14 @@ namespace MoonWorks.Video private Texture vTexture = null; private Sampler LinearSampler; - private void* yuvData = null; - private int yuvDataLength = 0; - private int currentFrame; - private AudioDevice AudioDevice; - private StreamingSoundTheora audioStream = null; - private float volume = 1.0f; - private Stopwatch timer; private double lastTimestamp; private double timeElapsed; + private Stopwatch profilingTimer = new Stopwatch(); + private bool disposed; public VideoPlayer(GraphicsDevice graphicsDevice, AudioDevice audioDevice) @@ -55,13 +39,12 @@ namespace MoonWorks.Video throw new InvalidOperationException("Missing video shaders!"); } - AudioDevice = audioDevice; LinearSampler = new Sampler(graphicsDevice, SamplerCreateInfo.LinearClamp); timer = new Stopwatch(); } - public void Load(Video video) + public void Load(VideoAV1 video) { if (Video != video) { @@ -111,20 +94,9 @@ namespace MoonWorks.Video vTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight); } - var newDataLength = ( - (video.Width * video.Height) + - (video.UVWidth * video.UVHeight * 2) - ); - - if (newDataLength != yuvDataLength) - { - yuvData = NativeMemory.Realloc(yuvData, (nuint) newDataLength); - yuvDataLength = newDataLength; - } - Video = video; - InitializeTheoraStream(); + InitializeDav1dStream(); } } @@ -139,11 +111,6 @@ namespace MoonWorks.Video timer.Start(); - if (audioStream != null) - { - audioStream.Play(); - } - State = VideoState.Playing; } @@ -158,11 +125,6 @@ namespace MoonWorks.Video timer.Stop(); - if (audioStream != null) - { - audioStream.Pause(); - } - State = VideoState.Paused; } @@ -181,9 +143,7 @@ namespace MoonWorks.Video lastTimestamp = 0; timeElapsed = 0; - DestroyAudioStream(); - - Theorafile.tf_reset(Video.Handle); + Dav1dfile.df_reset(Video.Handle); State = VideoState.Stopped; } @@ -197,11 +157,6 @@ namespace MoonWorks.Video public void Update() { if (Video == null) { return; } - - if (audioStream != null) - { - audioStream.Update(); - } } public void Render() @@ -217,32 +172,41 @@ namespace MoonWorks.Video int thisFrame = ((int) (timeElapsed / (1000.0 / Video.FramesPerSecond))); if (thisFrame > currentFrame) { - if (Theorafile.tf_readvideo( + profilingTimer.Restart(); + int readResult = Dav1dfile.df_readvideo( Video.Handle, - (IntPtr) yuvData, - thisFrame - currentFrame - ) == 1 || currentFrame == -1) + thisFrame - currentFrame, + out var yData, + out var uData, + out var vData, + out var yDataLength, + out var uvDataLength, + out var yStride, + out var uvStride + ); + profilingTimer.Stop(); + System.Console.WriteLine($"Frame decoded in: {profilingTimer.Elapsed.TotalMilliseconds}"); + + if (readResult == 1 || currentFrame == -1) { - UpdateRenderTexture(); + UpdateRenderTexture(yData, uData, vData, yDataLength, uvDataLength, yStride, uvStride); } currentFrame = thisFrame; } - bool ended = Theorafile.tf_eos(Video.Handle) == 1; + bool ended = Dav1dfile.df_eos(Video.Handle) == 1; if (ended) { timer.Stop(); timer.Reset(); - DestroyAudioStream(); - - Theorafile.tf_reset(Video.Handle); + Dav1dfile.df_reset(Video.Handle); if (Loop) { // Start over! - InitializeTheoraStream(); + InitializeDav1dStream(); timer.Start(); } @@ -253,7 +217,7 @@ namespace MoonWorks.Video } } - private void UpdateRenderTexture() + private void UpdateRenderTexture(IntPtr yData, IntPtr uData, IntPtr vData, uint yDataLength, uint uvDataLength, uint yStride, uint uvStride) { var commandBuffer = GraphicsDevice.AcquireCommandBuffer(); @@ -261,8 +225,13 @@ namespace MoonWorks.Video yTexture, uTexture, vTexture, - (IntPtr) yuvData, - (uint) yuvDataLength + yData, + uData, + vData, + yDataLength, + uvDataLength, + yStride, + uvStride ); commandBuffer.BeginRenderPass( @@ -305,35 +274,22 @@ namespace MoonWorks.Video ); } - private void InitializeTheoraStream() + private void InitializeDav1dStream() { // Grab the first video frame ASAP. - while (Theorafile.tf_readvideo(Video.Handle, (IntPtr) yuvData, 1) == 0); - - // Grab the first bit of audio. We're trying to start the decoding ASAP. - if (AudioDevice != null && Theorafile.tf_hasaudio(Video.Handle) == 1) - { - DestroyAudioStream(); - - int channels, sampleRate; - Theorafile.tf_audioinfo(Video.Handle, out channels, out sampleRate); - - audioStream = new StreamingSoundTheora(AudioDevice, Video.Handle, channels, (uint) sampleRate); - } - + Dav1dfile.df_readvideo( + Video.Handle, + 1, + out var _, + out var _, + out var _, + out var _, + out var _, + out var _, + out var _); currentFrame = -1; } - private void DestroyAudioStream() - { - if (audioStream != null) - { - audioStream.StopImmediate(); - audioStream.Dispose(); - audioStream = null; - } - } - protected virtual void Dispose(bool disposing) { if (!disposed) @@ -347,9 +303,6 @@ namespace MoonWorks.Video vTexture.Dispose(); } - // free unmanaged resources (unmanaged objects) and override finalizer - NativeMemory.Free(yuvData); - disposed = true; } }