2022-08-02 21:04:12 +00:00
|
|
|
/* Heavily based on https://github.com/FNA-XNA/FNA/blob/master/src/Media/Xiph/VideoPlayer.cs */
|
|
|
|
using System;
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
|
|
namespace MoonWorks.Video
|
|
|
|
{
|
|
|
|
public enum VideoState
|
|
|
|
{
|
|
|
|
Playing,
|
|
|
|
Paused,
|
|
|
|
Stopped
|
|
|
|
}
|
|
|
|
|
|
|
|
public unsafe class Video : IDisposable
|
|
|
|
{
|
|
|
|
internal IntPtr Handle;
|
2022-08-18 20:45:34 +00:00
|
|
|
private IntPtr rwData;
|
|
|
|
private void* videoData;
|
2022-08-02 21:04:12 +00:00
|
|
|
|
|
|
|
public double FramesPerSecond => fps;
|
2022-08-18 20:45:34 +00:00
|
|
|
public int Width => yWidth;
|
|
|
|
public int Height => yHeight;
|
|
|
|
public int UVWidth { get; }
|
|
|
|
public int UVHeight { get; }
|
2022-08-02 21:04:12 +00:00
|
|
|
|
|
|
|
private double fps;
|
|
|
|
private int yWidth;
|
|
|
|
private int yHeight;
|
|
|
|
|
|
|
|
private bool disposed;
|
|
|
|
|
2022-08-18 20:45:34 +00:00
|
|
|
public Video(string filename)
|
2022-08-02 21:04:12 +00:00
|
|
|
{
|
|
|
|
if (!System.IO.File.Exists(filename))
|
|
|
|
{
|
|
|
|
throw new ArgumentException("Video file not found!");
|
|
|
|
}
|
|
|
|
|
2022-08-18 20:45:34 +00:00
|
|
|
var bytes = System.IO.File.ReadAllBytes(filename);
|
|
|
|
videoData = NativeMemory.Alloc((nuint) bytes.Length);
|
|
|
|
Marshal.Copy(bytes, 0, (IntPtr) videoData, bytes.Length);
|
|
|
|
rwData = SDL2.SDL.SDL_RWFromMem((IntPtr) videoData, bytes.Length);
|
|
|
|
|
|
|
|
if (Theorafile.tf_open_callbacks(rwData, out Handle, callbacks) < 0)
|
2022-08-02 21:04:12 +00:00
|
|
|
{
|
|
|
|
throw new ArgumentException("Invalid video file!");
|
|
|
|
}
|
|
|
|
|
|
|
|
Theorafile.th_pixel_fmt format;
|
|
|
|
Theorafile.tf_videoinfo(
|
|
|
|
Handle,
|
|
|
|
out yWidth,
|
|
|
|
out yHeight,
|
|
|
|
out fps,
|
|
|
|
out format
|
|
|
|
);
|
|
|
|
|
|
|
|
if (format == Theorafile.th_pixel_fmt.TH_PF_420)
|
|
|
|
{
|
2022-08-18 20:45:34 +00:00
|
|
|
UVWidth = Width / 2;
|
|
|
|
UVHeight = Height / 2;
|
2022-08-02 21:04:12 +00:00
|
|
|
}
|
|
|
|
else if (format == Theorafile.th_pixel_fmt.TH_PF_422)
|
|
|
|
{
|
2022-08-18 20:45:34 +00:00
|
|
|
UVWidth = Width / 2;
|
|
|
|
UVHeight = Height;
|
2022-08-02 21:04:12 +00:00
|
|
|
}
|
|
|
|
else if (format == Theorafile.th_pixel_fmt.TH_PF_444)
|
|
|
|
{
|
2022-08-18 20:45:34 +00:00
|
|
|
UVWidth = Width;
|
|
|
|
UVHeight = Height;
|
2022-08-02 21:04:12 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
throw new NotSupportedException("Unrecognized YUV format!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-18 20:45:34 +00:00
|
|
|
private static IntPtr Read(IntPtr ptr, IntPtr size, IntPtr nmemb, IntPtr datasource) => (IntPtr) SDL2.SDL.SDL_RWread(datasource, ptr, size, nmemb);
|
|
|
|
private static int Seek(IntPtr datasource, long offset, Theorafile.SeekWhence whence) => (int) SDL2.SDL.SDL_RWseek(datasource, offset, (int) whence);
|
|
|
|
private static int Close(IntPtr datasource) => (int) SDL2.SDL.SDL_RWclose(datasource);
|
2022-08-02 21:04:12 +00:00
|
|
|
|
2022-08-18 20:45:34 +00:00
|
|
|
private static Theorafile.tf_callbacks callbacks = new Theorafile.tf_callbacks
|
2022-08-02 21:04:12 +00:00
|
|
|
{
|
2022-08-18 20:45:34 +00:00
|
|
|
read_func = Read,
|
|
|
|
seek_func = Seek,
|
|
|
|
close_func = Close
|
|
|
|
};
|
2022-08-02 21:04:12 +00:00
|
|
|
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
|
{
|
|
|
|
if (!disposed)
|
|
|
|
{
|
|
|
|
if (disposing)
|
|
|
|
{
|
|
|
|
// dispose managed state (managed objects)
|
|
|
|
}
|
|
|
|
|
|
|
|
// free unmanaged resources (unmanaged objects)
|
|
|
|
Theorafile.tf_close(ref Handle);
|
2022-08-18 20:45:34 +00:00
|
|
|
NativeMemory.Free(videoData);
|
2022-08-02 21:04:12 +00:00
|
|
|
|
|
|
|
disposed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
~Video()
|
|
|
|
{
|
|
|
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|