Files
maui-linux/Services/HardwareVideoService.cs
Dave Friedel 1f096c38dc Update with recovered code from VM binaries (Jan 1)
Recovered from decompiled OpenMaui.Controls.Linux.dll:
- SkiaShell.cs: FlyoutHeader, FlyoutFooter, scroll support (918 -> 1325 lines)
- X11Window.cs: Cursor support (XCreateFontCursor, XDefineCursor)
- All handlers with dark mode support
- All services with latest implementations
- LinuxApplication with theme change handling
2026-01-01 06:22:48 -05:00

563 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using SkiaSharp;
namespace Microsoft.Maui.Platform.Linux.Services;
public class HardwareVideoService : IDisposable
{
private struct VaImage
{
public uint ImageId;
public uint Format;
public uint FormatFourCC;
public int Width;
public int Height;
public uint DataSize;
public uint NumPlanes;
public uint PitchesPlane0;
public uint PitchesPlane1;
public uint PitchesPlane2;
public uint PitchesPlane3;
public uint OffsetsPlane0;
public uint OffsetsPlane1;
public uint OffsetsPlane2;
public uint OffsetsPlane3;
public uint BufferId;
}
private const string LibVa = "libva.so.2";
private const string LibVaDrm = "libva-drm.so.2";
private const string LibVaX11 = "libva-x11.so.2";
private const int VA_STATUS_SUCCESS = 0;
private const int VAProfileH264Baseline = 5;
private const int VAProfileH264Main = 6;
private const int VAProfileH264High = 7;
private const int VAProfileHEVCMain = 12;
private const int VAProfileHEVCMain10 = 13;
private const int VAProfileVP8Version0_3 = 14;
private const int VAProfileVP9Profile0 = 15;
private const int VAProfileVP9Profile2 = 17;
private const int VAProfileAV1Profile0 = 20;
private const int VAEntrypointVLD = 1;
private const uint VA_RT_FORMAT_YUV420 = 1u;
private const uint VA_RT_FORMAT_YUV420_10 = 256u;
private const string LibVdpau = "libvdpau.so.1";
private const int O_RDWR = 2;
private IntPtr _vaDisplay;
private uint _vaConfigId;
private uint _vaContextId;
private uint[] _vaSurfaces = Array.Empty<uint>();
private int _drmFd = -1;
private bool _initialized;
private bool _disposed;
private VideoAccelerationApi _currentApi = VideoAccelerationApi.Software;
private int _width;
private int _height;
private VideoProfile _profile;
private readonly HashSet<VideoProfile> _supportedProfiles = new HashSet<VideoProfile>();
private readonly object _lock = new object();
public VideoAccelerationApi CurrentApi => _currentApi;
public bool IsHardwareAccelerated
{
get
{
if (_currentApi != VideoAccelerationApi.Software)
{
return _initialized;
}
return false;
}
}
public IReadOnlySet<VideoProfile> SupportedProfiles => _supportedProfiles;
[DllImport("libva.so.2")]
private static extern IntPtr vaGetDisplayDRM(int fd);
[DllImport("libva-x11.so.2")]
private static extern IntPtr vaGetDisplay(IntPtr x11Display);
[DllImport("libva.so.2")]
private static extern int vaInitialize(IntPtr display, out int majorVersion, out int minorVersion);
[DllImport("libva.so.2")]
private static extern int vaTerminate(IntPtr display);
[DllImport("libva.so.2")]
private static extern IntPtr vaErrorStr(int errorCode);
[DllImport("libva.so.2")]
private static extern int vaQueryConfigProfiles(IntPtr display, [Out] int[] profileList, out int numProfiles);
[DllImport("libva.so.2")]
private static extern int vaQueryConfigEntrypoints(IntPtr display, int profile, [Out] int[] entrypoints, out int numEntrypoints);
[DllImport("libva.so.2")]
private static extern int vaCreateConfig(IntPtr display, int profile, int entrypoint, IntPtr attribList, int numAttribs, out uint configId);
[DllImport("libva.so.2")]
private static extern int vaDestroyConfig(IntPtr display, uint configId);
[DllImport("libva.so.2")]
private static extern int vaCreateContext(IntPtr display, uint configId, int pictureWidth, int pictureHeight, int flag, IntPtr renderTargets, int numRenderTargets, out uint contextId);
[DllImport("libva.so.2")]
private static extern int vaDestroyContext(IntPtr display, uint contextId);
[DllImport("libva.so.2")]
private static extern int vaCreateSurfaces(IntPtr display, uint format, uint width, uint height, [Out] uint[] surfaces, uint numSurfaces, IntPtr attribList, uint numAttribs);
[DllImport("libva.so.2")]
private static extern int vaDestroySurfaces(IntPtr display, [In] uint[] surfaces, int numSurfaces);
[DllImport("libva.so.2")]
private static extern int vaSyncSurface(IntPtr display, uint surfaceId);
[DllImport("libva.so.2")]
private static extern int vaMapBuffer(IntPtr display, uint bufferId, out IntPtr data);
[DllImport("libva.so.2")]
private static extern int vaUnmapBuffer(IntPtr display, uint bufferId);
[DllImport("libva.so.2")]
private static extern int vaDeriveImage(IntPtr display, uint surfaceId, out VaImage image);
[DllImport("libva.so.2")]
private static extern int vaDestroyImage(IntPtr display, uint imageId);
[DllImport("libvdpau.so.1")]
private static extern int vdp_device_create_x11(IntPtr display, int screen, out IntPtr device, out IntPtr getProcAddress);
[DllImport("libc")]
private static extern int open([MarshalAs(UnmanagedType.LPStr)] string path, int flags);
[DllImport("libc")]
private static extern int close(int fd);
public bool Initialize(VideoAccelerationApi api = VideoAccelerationApi.Auto, IntPtr x11Display = 0)
{
if (_initialized)
{
return true;
}
lock (_lock)
{
if (_initialized)
{
return true;
}
if ((api == VideoAccelerationApi.Auto || api == VideoAccelerationApi.VaApi) && TryInitializeVaApi(x11Display))
{
_currentApi = VideoAccelerationApi.VaApi;
_initialized = true;
Console.WriteLine($"[HardwareVideo] Initialized VA-API with {_supportedProfiles.Count} supported profiles");
return true;
}
if ((api == VideoAccelerationApi.Auto || api == VideoAccelerationApi.Vdpau) && TryInitializeVdpau(x11Display))
{
_currentApi = VideoAccelerationApi.Vdpau;
_initialized = true;
Console.WriteLine("[HardwareVideo] Initialized VDPAU");
return true;
}
Console.WriteLine("[HardwareVideo] No hardware acceleration available, using software");
_currentApi = VideoAccelerationApi.Software;
return false;
}
}
private bool TryInitializeVaApi(IntPtr x11Display)
{
try
{
string[] array = new string[3] { "/dev/dri/renderD128", "/dev/dri/renderD129", "/dev/dri/card0" };
foreach (string path in array)
{
_drmFd = open(path, 2);
if (_drmFd >= 0)
{
_vaDisplay = vaGetDisplayDRM(_drmFd);
if (_vaDisplay != IntPtr.Zero && InitializeVaDisplay())
{
return true;
}
close(_drmFd);
_drmFd = -1;
}
}
if (x11Display != IntPtr.Zero)
{
_vaDisplay = vaGetDisplay(x11Display);
if (_vaDisplay != IntPtr.Zero && InitializeVaDisplay())
{
return true;
}
}
return false;
}
catch (DllNotFoundException)
{
Console.WriteLine("[HardwareVideo] VA-API libraries not found");
return false;
}
catch (Exception ex2)
{
Console.WriteLine("[HardwareVideo] VA-API initialization failed: " + ex2.Message);
return false;
}
}
private bool InitializeVaDisplay()
{
int majorVersion;
int minorVersion;
int num = vaInitialize(_vaDisplay, out majorVersion, out minorVersion);
if (num != 0)
{
Console.WriteLine("[HardwareVideo] vaInitialize failed: " + GetVaError(num));
return false;
}
Console.WriteLine($"[HardwareVideo] VA-API {majorVersion}.{minorVersion} initialized");
int[] array = new int[32];
if (vaQueryConfigProfiles(_vaDisplay, array, out var numProfiles) == 0)
{
for (int i = 0; i < numProfiles; i++)
{
if (!TryMapVaProfile(array[i], out var profile))
{
continue;
}
int[] array2 = new int[8];
if (vaQueryConfigEntrypoints(_vaDisplay, array[i], array2, out var numEntrypoints) != 0)
{
continue;
}
for (int j = 0; j < numEntrypoints; j++)
{
if (array2[j] == 1)
{
_supportedProfiles.Add(profile);
break;
}
}
}
}
return true;
}
private bool TryInitializeVdpau(IntPtr x11Display)
{
if (x11Display == IntPtr.Zero)
{
return false;
}
try
{
if (vdp_device_create_x11(x11Display, 0, out var device, out var _) == 0 && device != IntPtr.Zero)
{
_supportedProfiles.Add(VideoProfile.H264Baseline);
_supportedProfiles.Add(VideoProfile.H264Main);
_supportedProfiles.Add(VideoProfile.H264High);
return true;
}
}
catch (DllNotFoundException)
{
Console.WriteLine("[HardwareVideo] VDPAU libraries not found");
}
catch (Exception ex2)
{
Console.WriteLine("[HardwareVideo] VDPAU initialization failed: " + ex2.Message);
}
return false;
}
public bool CreateDecoder(VideoProfile profile, int width, int height)
{
if (!_initialized || _currentApi == VideoAccelerationApi.Software)
{
return false;
}
if (!_supportedProfiles.Contains(profile))
{
Console.WriteLine($"[HardwareVideo] Profile {profile} not supported");
return false;
}
lock (_lock)
{
DestroyDecoder();
_width = width;
_height = height;
_profile = profile;
if (_currentApi == VideoAccelerationApi.VaApi)
{
return CreateVaApiDecoder(profile, width, height);
}
return false;
}
}
private bool CreateVaApiDecoder(VideoProfile profile, int width, int height)
{
int profile2 = MapToVaProfile(profile);
int num = vaCreateConfig(_vaDisplay, profile2, 1, IntPtr.Zero, 0, out _vaConfigId);
if (num != 0)
{
Console.WriteLine("[HardwareVideo] vaCreateConfig failed: " + GetVaError(num));
return false;
}
uint format = ((profile != VideoProfile.H265Main10 && profile != VideoProfile.Vp9Profile2) ? 1u : 256u);
_vaSurfaces = new uint[8];
num = vaCreateSurfaces(_vaDisplay, format, (uint)width, (uint)height, _vaSurfaces, 8u, IntPtr.Zero, 0u);
if (num != 0)
{
Console.WriteLine("[HardwareVideo] vaCreateSurfaces failed: " + GetVaError(num));
vaDestroyConfig(_vaDisplay, _vaConfigId);
return false;
}
num = vaCreateContext(_vaDisplay, _vaConfigId, width, height, 0, IntPtr.Zero, 0, out _vaContextId);
if (num != 0)
{
Console.WriteLine("[HardwareVideo] vaCreateContext failed: " + GetVaError(num));
vaDestroySurfaces(_vaDisplay, _vaSurfaces, _vaSurfaces.Length);
vaDestroyConfig(_vaDisplay, _vaConfigId);
return false;
}
Console.WriteLine($"[HardwareVideo] Created decoder: {profile} {width}x{height}");
return true;
}
public void DestroyDecoder()
{
lock (_lock)
{
if (_currentApi == VideoAccelerationApi.VaApi && _vaDisplay != IntPtr.Zero)
{
if (_vaContextId != 0)
{
vaDestroyContext(_vaDisplay, _vaContextId);
_vaContextId = 0u;
}
if (_vaSurfaces.Length != 0)
{
vaDestroySurfaces(_vaDisplay, _vaSurfaces, _vaSurfaces.Length);
_vaSurfaces = Array.Empty<uint>();
}
if (_vaConfigId != 0)
{
vaDestroyConfig(_vaDisplay, _vaConfigId);
_vaConfigId = 0u;
}
}
}
}
public VideoFrame? GetDecodedFrame(int surfaceIndex, long timestamp, bool isKeyFrame)
{
if (!_initialized || _currentApi != VideoAccelerationApi.VaApi)
{
return null;
}
if (surfaceIndex < 0 || surfaceIndex >= _vaSurfaces.Length)
{
return null;
}
uint surfaceId = _vaSurfaces[surfaceIndex];
if (vaSyncSurface(_vaDisplay, surfaceId) != 0)
{
return null;
}
if (vaDeriveImage(_vaDisplay, surfaceId, out var image) != 0)
{
return null;
}
if (vaMapBuffer(_vaDisplay, image.BufferId, out nint data) != 0)
{
vaDestroyImage(_vaDisplay, image.ImageId);
return null;
}
VideoFrame obj = new VideoFrame
{
Width = image.Width,
Height = image.Height,
DataY = data + (int)image.OffsetsPlane0,
DataU = data + (int)image.OffsetsPlane1,
DataV = data + (int)image.OffsetsPlane2,
StrideY = (int)image.PitchesPlane0,
StrideU = (int)image.PitchesPlane1,
StrideV = (int)image.PitchesPlane2,
Timestamp = timestamp,
IsKeyFrame = isKeyFrame
};
obj.SetReleaseCallback(delegate
{
vaUnmapBuffer(_vaDisplay, image.BufferId);
vaDestroyImage(_vaDisplay, image.ImageId);
});
return obj;
}
public unsafe SKBitmap? ConvertFrameToSkia(VideoFrame frame)
{
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_0019: Expected O, but got Unknown
if (frame == null)
{
return null;
}
SKBitmap val = new SKBitmap(frame.Width, frame.Height, (SKColorType)6, (SKAlphaType)1);
byte* dataY = (byte*)frame.DataY;
byte* dataU = (byte*)frame.DataU;
byte* dataV = (byte*)frame.DataV;
byte* pixels = (byte*)val.GetPixels();
for (int i = 0; i < frame.Height; i++)
{
for (int j = 0; j < frame.Width; j++)
{
int num = i * frame.StrideY + j;
int num2 = i / 2 * frame.StrideU + j / 2;
byte num3 = dataY[num];
int num4 = dataU[num2] - 128;
int num5 = dataV[num2] - 128;
int value = (int)((double)(int)num3 + 1.402 * (double)num5);
int value2 = (int)((double)(int)num3 - 0.344 * (double)num4 - 0.714 * (double)num5);
int value3 = (int)((double)(int)num3 + 1.772 * (double)num4);
value = Math.Clamp(value, 0, 255);
value2 = Math.Clamp(value2, 0, 255);
value3 = Math.Clamp(value3, 0, 255);
int num6 = (i * frame.Width + j) * 4;
pixels[num6] = (byte)value3;
pixels[num6 + 1] = (byte)value2;
pixels[num6 + 2] = (byte)value;
pixels[num6 + 3] = byte.MaxValue;
}
}
return val;
}
private static bool TryMapVaProfile(int vaProfile, out VideoProfile profile)
{
profile = vaProfile switch
{
5 => VideoProfile.H264Baseline,
6 => VideoProfile.H264Main,
7 => VideoProfile.H264High,
12 => VideoProfile.H265Main,
13 => VideoProfile.H265Main10,
14 => VideoProfile.Vp8,
15 => VideoProfile.Vp9Profile0,
17 => VideoProfile.Vp9Profile2,
20 => VideoProfile.Av1Main,
_ => VideoProfile.H264Main,
};
if (vaProfile >= 5)
{
return vaProfile <= 20;
}
return false;
}
private static int MapToVaProfile(VideoProfile profile)
{
return profile switch
{
VideoProfile.H264Baseline => 5,
VideoProfile.H264Main => 6,
VideoProfile.H264High => 7,
VideoProfile.H265Main => 12,
VideoProfile.H265Main10 => 13,
VideoProfile.Vp8 => 14,
VideoProfile.Vp9Profile0 => 15,
VideoProfile.Vp9Profile2 => 17,
VideoProfile.Av1Main => 20,
_ => 6,
};
}
private static string GetVaError(int status)
{
try
{
return Marshal.PtrToStringAnsi(vaErrorStr(status)) ?? $"Unknown error {status}";
}
catch
{
return $"Error code {status}";
}
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
DestroyDecoder();
if (_currentApi == VideoAccelerationApi.VaApi && _vaDisplay != IntPtr.Zero)
{
vaTerminate(_vaDisplay);
_vaDisplay = IntPtr.Zero;
}
if (_drmFd >= 0)
{
close(_drmFd);
_drmFd = -1;
}
GC.SuppressFinalize(this);
}
}
~HardwareVideoService()
{
Dispose();
}
}