Files
video-orchestra/godot-project/scripts/Platform/Windows/WindowsVP9Decoder.cs

422 lines
16 KiB
C#
Raw Normal View History

2025-09-13 03:22:54 +09:00
using Godot;
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace VideoOrchestra.Platform
{
/// <summary>
/// Windows VP9 decoder implementation using Media Foundation
/// </summary>
public class WindowsVP9Decoder : IVP9PlatformDecoder
{
private const int MAX_STREAMS = 3;
private const uint MFSTARTUP_NOSOCKET = 0x1;
private const uint MFSTARTUP_LITE = 0x1;
private const uint MFSTARTUP_FULL = 0x0;
// Media Foundation interfaces and structures
private IntPtr[] _mediaFoundationDecoders = new IntPtr[MAX_STREAMS];
private IntPtr[] _d3d11Textures = new IntPtr[MAX_STREAMS];
private ImageTexture[] _godotTextures = new ImageTexture[MAX_STREAMS];
private bool _initialized = false;
private bool _hardwareEnabled = true;
private int _width = 0;
private int _height = 0;
private VP9DecoderStatus _status = VP9DecoderStatus.Uninitialized;
public string PlatformName => "Windows";
public bool IsHardwareDecodingSupported => CheckHardwareSupport();
#region Media Foundation P/Invoke Declarations
[DllImport("mfplat.dll", CallingConvention = CallingConvention.StdCall)]
private static extern int MFStartup(uint version, uint flags);
[DllImport("mfplat.dll", CallingConvention = CallingConvention.StdCall)]
private static extern int MFShutdown();
[DllImport("mfplat.dll", CallingConvention = CallingConvention.StdCall)]
private static extern int MFCreateMediaType(out IntPtr mediaType);
[DllImport("mf.dll", CallingConvention = CallingConvention.StdCall)]
private static extern int MFCreateSourceResolver(out IntPtr sourceResolver);
[DllImport("mf.dll", CallingConvention = CallingConvention.StdCall)]
private static extern int MFCreateTopology(out IntPtr topology);
[DllImport("d3d11.dll", CallingConvention = CallingConvention.StdCall)]
private static extern int D3D11CreateDevice(
IntPtr adapter, uint driverType, IntPtr software, uint flags,
IntPtr featureLevels, uint featureLevelCount, uint sdkVersion,
out IntPtr device, out uint featureLevel, out IntPtr context);
// GUIDs for Media Foundation
private static readonly Guid MF_MT_MAJOR_TYPE = new("48eba18e-f8c9-4687-bf11-0a74c9f96a8f");
private static readonly Guid MF_MT_SUBTYPE = new("f7e34c9a-42e8-4714-b74b-cb29d72c35e5");
private static readonly Guid MFMediaType_Video = new("73646976-0000-0010-8000-00aa00389b71");
private static readonly Guid MFVideoFormat_VP90 = new("30395056-0000-0010-8000-00aa00389b71");
private static readonly Guid MFVideoFormat_NV12 = new("3231564e-0000-0010-8000-00aa00389b71");
#endregion
public WindowsVP9Decoder()
{
for (int i = 0; i < MAX_STREAMS; i++)
{
_mediaFoundationDecoders[i] = IntPtr.Zero;
_d3d11Textures[i] = IntPtr.Zero;
_godotTextures[i] = null;
}
}
public bool Initialize(int width, int height, bool enableHardware = true)
{
try
{
_width = width;
_height = height;
_hardwareEnabled = enableHardware && IsHardwareDecodingSupported;
bool mediaFoundationAvailable = false;
// Initialize Media Foundation
try
{
// Use version 2.0 (0x00020070) and LITE startup mode for basic functionality
int hr = MFStartup(0x00020070, MFSTARTUP_LITE);
if (hr != 0)
{
// Try full mode as fallback
hr = MFStartup(0x00020070, MFSTARTUP_FULL);
if (hr != 0)
{
// Common error codes:
// 0xC00D36E3 = MF_E_PLATFORM_NOT_INITIALIZED
// 0xC00D36E4 = MF_E_ALREADY_INITIALIZED
string errorMsg = hr switch
{
unchecked((int)0xC00D36E3) => "Media Foundation platform not available",
unchecked((int)0xC00D36E4) => "Media Foundation already initialized (continuing)",
unchecked((int)0x80004005) => "Media Foundation access denied",
_ => $"Media Foundation startup failed with HRESULT 0x{hr:X8}"
};
if (hr == unchecked((int)0xC00D36E4))
{
// Already initialized is OK, we can continue
GD.Print("Media Foundation already initialized, continuing...");
mediaFoundationAvailable = true;
}
else
{
GD.PrintErr($"Windows Media Foundation initialization failed: {errorMsg}");
GD.Print("Falling back to software-only simulation mode...");
mediaFoundationAvailable = false;
_hardwareEnabled = false;
}
}
else
{
mediaFoundationAvailable = true;
}
}
else
{
mediaFoundationAvailable = true;
}
}
catch (DllNotFoundException)
{
GD.Print("Media Foundation DLLs not found, using software simulation mode");
mediaFoundationAvailable = false;
_hardwareEnabled = false;
}
catch (Exception ex)
{
GD.PrintErr($"Media Foundation initialization exception: {ex.Message}");
mediaFoundationAvailable = false;
_hardwareEnabled = false;
}
// Initialize D3D11 device for hardware decoding if enabled
if (_hardwareEnabled && mediaFoundationAvailable)
{
if (!InitializeD3D11())
{
GD.Print("Warning: Failed to initialize D3D11, falling back to software decoding");
_hardwareEnabled = false;
}
}
// Initialize decoders for each stream
for (int i = 0; i < MAX_STREAMS; i++)
{
if (!InitializeStreamDecoder(i))
{
GD.PrintErr($"Failed to initialize decoder for stream {i}");
// Don't fail completely - continue with simulation mode
GD.Print($"Using simulation mode for stream {i}");
_mediaFoundationDecoders[i] = new IntPtr(i + 100); // Simulation placeholder
}
_godotTextures[i] = new ImageTexture();
}
_initialized = true;
_status = VP9DecoderStatus.Initialized;
string mode = mediaFoundationAvailable ?
(_hardwareEnabled ? "Hardware (Media Foundation)" : "Software (Media Foundation)") :
"Simulation";
GD.Print($"Windows VP9 decoder initialized: {width}x{height}, Mode: {mode}");
return true;
}
catch (Exception ex)
{
GD.PrintErr($"Error initializing Windows VP9 decoder: {ex.Message}");
_status = VP9DecoderStatus.Error;
Release();
return false;
}
}
private bool CheckHardwareSupport()
{
try
{
// Check for D3D11 and VP9 hardware decoder support
IntPtr device, context;
uint featureLevel;
int hr = D3D11CreateDevice(
IntPtr.Zero, 1, IntPtr.Zero, 0,
IntPtr.Zero, 0, 7, out device, out featureLevel, out context);
if (hr == 0 && device != IntPtr.Zero)
{
Marshal.Release(device);
Marshal.Release(context);
return true;
}
return false;
}
catch
{
return false;
}
}
private bool InitializeD3D11()
{
try
{
// Initialize D3D11 device for hardware-accelerated decoding
// This would create the D3D11 device and context needed for Media Foundation
// For now, we'll simulate this initialization
return true;
}
catch (Exception ex)
{
GD.PrintErr($"Failed to initialize D3D11: {ex.Message}");
return false;
}
}
private bool InitializeStreamDecoder(int streamId)
{
try
{
// Create Media Foundation decoder for this stream
// This would involve:
// 1. Creating IMFTransform for VP9 decoder
// 2. Setting input/output media types
// 3. Configuring for hardware acceleration if enabled
// For now, we'll simulate successful initialization
_mediaFoundationDecoders[streamId] = new IntPtr(streamId + 1); // Non-null placeholder
return true;
}
catch (Exception ex)
{
GD.PrintErr($"Failed to initialize decoder for stream {streamId}: {ex.Message}");
return false;
}
}
public bool DecodeFrame(byte[] frameData, int streamId)
{
if (!_initialized || streamId < 0 || streamId >= MAX_STREAMS)
{
return false;
}
if (frameData == null || frameData.Length == 0)
{
return false;
}
try
{
_status = VP9DecoderStatus.Decoding;
// Process VP9 frame through Media Foundation
bool success = ProcessFrameWithMediaFoundation(frameData, streamId);
if (success)
{
// Update Godot texture with decoded frame
UpdateGodotTexture(streamId);
}
else
{
_status = VP9DecoderStatus.Error;
}
return success;
}
catch (Exception ex)
{
GD.PrintErr($"Error decoding frame for stream {streamId}: {ex.Message}");
_status = VP9DecoderStatus.Error;
return false;
}
}
private bool ProcessFrameWithMediaFoundation(byte[] frameData, int streamId)
{
try
{
// This would involve:
// 1. Creating IMFSample from input data
// 2. Calling IMFTransform.ProcessInput()
// 3. Calling IMFTransform.ProcessOutput() to get decoded frame
// 4. Converting output to texture format
// For demonstration, simulate successful decode
GD.Print($"[Windows MF] Decoding frame for stream {streamId}: {frameData.Length} bytes");
// Simulate some processing time
Task.Delay(1).Wait();
return true;
}
catch (Exception ex)
{
throw new VP9DecoderException(PlatformName, streamId, $"Media Foundation decode failed: {ex.Message}");
}
}
private void UpdateGodotTexture(int streamId)
{
try
{
// Create dummy texture data for demonstration
// In real implementation, this would convert the decoded frame from Media Foundation
// to a format Godot can use (RGBA8 or similar)
var image = Image.CreateEmpty(_width, _height, false, Image.Format.Rgba8);
// Fill with a pattern to show it's working (different color per stream)
var color = streamId switch
{
0 => Colors.Red,
1 => Colors.Green,
2 => Colors.Blue,
_ => Colors.White
};
image.Fill(color);
_godotTextures[streamId].SetImage(image);
GD.Print($"Updated texture for stream {streamId}");
}
catch (Exception ex)
{
GD.PrintErr($"Failed to update texture for stream {streamId}: {ex.Message}");
}
}
public ImageTexture GetDecodedTexture(int streamId)
{
if (!_initialized || streamId < 0 || streamId >= MAX_STREAMS)
{
return null;
}
return _godotTextures[streamId];
}
public uint GetNativeTextureId(int streamId)
{
if (!_initialized || streamId < 0 || streamId >= MAX_STREAMS)
{
return 0;
}
// Return D3D11 texture handle if available
return (uint)_d3d11Textures[streamId].ToInt64();
}
public VP9DecoderStatus GetStatus()
{
return _status;
}
public void Release()
{
try
{
_status = VP9DecoderStatus.Released;
// Release Media Foundation resources
for (int i = 0; i < MAX_STREAMS; i++)
{
if (_mediaFoundationDecoders[i] != IntPtr.Zero)
{
// Release IMFTransform
Marshal.Release(_mediaFoundationDecoders[i]);
_mediaFoundationDecoders[i] = IntPtr.Zero;
}
if (_d3d11Textures[i] != IntPtr.Zero)
{
// Release D3D11 texture
Marshal.Release(_d3d11Textures[i]);
_d3d11Textures[i] = IntPtr.Zero;
}
_godotTextures[i]?.Dispose();
_godotTextures[i] = null;
}
// Shutdown Media Foundation if it was initialized
if (_initialized)
{
try
{
MFShutdown();
}
catch (Exception ex)
{
GD.Print($"Warning: Error shutting down Media Foundation: {ex.Message}");
}
}
_initialized = false;
GD.Print("Windows VP9 decoder released");
}
catch (Exception ex)
{
GD.PrintErr($"Error releasing Windows VP9 decoder: {ex.Message}");
}
}
public void Dispose()
{
Release();
}
}
}