Skip to content

Commit bfd5d7c

Browse files
committed
Use native ISurfaceImageSourceNativeWithD2D
Now we are able to perform direct frame drawing without dependency on CanvasBitmap or any SoftwareBitmap which requires back-to-back GPU->CPU->GPU frame copy. The GPU usage difference is negligible, as close as mainstream video player GPU usage.
1 parent d9b43ee commit bfd5d7c

File tree

5 files changed

+304
-349
lines changed

5 files changed

+304
-349
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
using Hi3Helper.Win32.WinRT.SwapChainPanelHelper;
2+
using Microsoft.Graphics.Canvas;
3+
using Microsoft.UI.Xaml;
4+
using Microsoft.UI.Xaml.Controls;
5+
using System;
6+
using System.Linq;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
9+
using System.Threading;
10+
using Windows.Media.Playback;
11+
using WinRT;
12+
using WinRT.Interop;
13+
14+
namespace BackgroundTest.CustomControl.LayeredBackgroundImage;
15+
16+
public partial class LayeredBackgroundImage
17+
{
18+
#region Properties
19+
20+
private static ref readonly Guid MediaPlayer_IID
21+
{
22+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
23+
get
24+
{
25+
ReadOnlySpan<byte> span = [
26+
253, 55, 229, 207, 106, 248, 70, 68, 191, 77,
27+
200, 231, 146, 183, 180, 179
28+
];
29+
return ref Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(span));
30+
}
31+
}
32+
33+
#endregion
34+
35+
#region Fields
36+
37+
private volatile int _isBlockVideoFrameDraw = 1;
38+
private int _isVideoFrameDrawInProgress;
39+
40+
#endregion
41+
42+
#region Video Frame Drawing
43+
44+
private unsafe void VideoPlayer_VideoFrameAvailable(MediaPlayer sender, object args)
45+
{
46+
if (_canvasSurfaceImageSourceNative == null)
47+
{
48+
return;
49+
}
50+
51+
if (_isBlockVideoFrameDraw == 1 ||
52+
Interlocked.Exchange(ref _isVideoFrameDrawInProgress, 1) == 1)
53+
{
54+
return;
55+
}
56+
57+
try
58+
{
59+
DispatcherQueue.TryEnqueue(() =>
60+
{
61+
SwapChainPanelHelper.BeginDrawNativeSurfaceImageSource(
62+
_canvasSurfaceImageSourceNative,
63+
_canvasRenderArea,
64+
out nint surfacePpv);
65+
66+
SwapChainPanelHelper.MediaPlayer_CopyFrameToVideoSurfaceUnsafe(_videoPlayerPtr, surfacePpv);
67+
_canvasSurfaceImageSourceNative.EndDraw();
68+
Marshal.Release(surfacePpv);
69+
});
70+
// sender.CopyFrameToVideoSurface(_canvasRenderTarget);
71+
// DrawVideoFrame(_canvasImageSource);
72+
}
73+
catch (Exception ex)
74+
{
75+
Console.WriteLine(ex);
76+
}
77+
finally
78+
{
79+
Volatile.Write(ref _isVideoFrameDrawInProgress, 0);
80+
}
81+
}
82+
83+
#endregion
84+
85+
#region Video Frame Initialization / Disposal
86+
87+
private void InitializeVideoPlayer()
88+
{
89+
if (_videoPlayer == null)
90+
{
91+
_videoPlayer = new MediaPlayer
92+
{
93+
AutoPlay = false,
94+
IsLoopingEnabled = true,
95+
IsVideoFrameServerEnabled = true,
96+
Volume = AudioVolume.GetClampedVolume(),
97+
IsMuted = !IsAudioEnabled
98+
};
99+
_videoPlayerPtr = ((IWinRTObject)_videoPlayer).NativeObject.As<IUnknownVftbl>(MediaPlayer_IID).ThisPtr;
100+
_videoPlayer.MediaOpened += InitializeVideoFrameOnMediaOpened;
101+
}
102+
}
103+
104+
private void DisposeVideoPlayer()
105+
{
106+
if (_videoPlayer != null!)
107+
{
108+
_videoPlayer.Dispose();
109+
_videoPlayer.VideoFrameAvailable -= VideoPlayer_VideoFrameAvailable;
110+
_videoPlayer.MediaOpened -= InitializeVideoFrameOnMediaOpened;
111+
Interlocked.Exchange(ref _videoPlayer!, null);
112+
_videoPlayerPtr = nint.Zero;
113+
}
114+
}
115+
116+
private void InitializeRenderTargetSize(MediaPlaybackSession playbackSession)
117+
{
118+
double currentCanvasWidth = playbackSession.NaturalVideoWidth;
119+
double currentCanvasHeight = playbackSession.NaturalVideoHeight;
120+
121+
// Scale by 1.5x size is required for XAML (well, actually 2x, but to reduce BitmapSize as well. Might look a bit blurry.)
122+
_canvasWidth = (int)(currentCanvasWidth * XamlRoot.RasterizationScale * 1.5d);
123+
_canvasHeight = (int)(currentCanvasHeight * XamlRoot.RasterizationScale * 1.5d);
124+
_canvasRenderArea = new(0, 0, _canvasWidth, _canvasHeight);
125+
}
126+
127+
private void InitializeRenderTarget()
128+
{
129+
DisposeRenderTarget(); // Always ensure the previous render target has been disposed
130+
131+
_canvasDevice = CanvasDevice.GetSharedDevice();
132+
_canvasSurfaceImageSource = new(_canvasWidth, _canvasHeight, true);
133+
SwapChainPanelHelper.GetNativeSurfaceImageSource(_canvasSurfaceImageSource, out _canvasSurfaceImageSourceNative);
134+
135+
if (FindRenderImage() is Image image)
136+
{
137+
image.Source = _canvasSurfaceImageSource;
138+
}
139+
}
140+
141+
private void DisposeRenderTarget()
142+
{
143+
_canvasDevice?.Dispose();
144+
if (FindRenderImage() is Image image)
145+
{
146+
image.Source = null;
147+
}
148+
149+
Interlocked.Exchange(ref _canvasDevice, null);
150+
}
151+
152+
private Image? FindRenderImage() => _backgroundGrid.Children
153+
.OfType<Image>()
154+
.Where(x => x.Name == "VideoRenderFrame")
155+
.LastOrDefault();
156+
157+
#endregion
158+
159+
#region Video Player Events
160+
161+
private static void IsAudioEnabled_OnChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
162+
{
163+
LayeredBackgroundImage instance = (LayeredBackgroundImage)d;
164+
if (instance._videoPlayer is not { } videoPlayer)
165+
{
166+
return;
167+
}
168+
169+
videoPlayer.IsMuted = !(bool)e.NewValue;
170+
}
171+
172+
private static void AudioVolume_OnChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
173+
{
174+
LayeredBackgroundImage instance = (LayeredBackgroundImage)d;
175+
if (instance._videoPlayer is not { } videoPlayer)
176+
{
177+
return;
178+
}
179+
180+
double volume = e.NewValue.TryGetDouble();
181+
videoPlayer.Volume = volume.GetClampedVolume();
182+
}
183+
184+
public void Play()
185+
{
186+
try
187+
{
188+
if (_videoPlayer != null!)
189+
{
190+
InitializeRenderTarget();
191+
_videoPlayer.VideoFrameAvailable += VideoPlayer_VideoFrameAvailable;
192+
_videoPlayer.Play();
193+
_isBlockVideoFrameDraw = 0;
194+
}
195+
}
196+
catch (Exception e)
197+
{
198+
Console.WriteLine(e);
199+
}
200+
}
201+
202+
public void Pause()
203+
{
204+
try
205+
{
206+
if (_videoPlayer != null!)
207+
{
208+
_isBlockVideoFrameDraw = 1;
209+
_videoPlayer.Pause();
210+
_videoPlayer.VideoFrameAvailable -= VideoPlayer_VideoFrameAvailable;
211+
DisposeRenderTarget();
212+
213+
GC.Collect();
214+
GC.WaitForPendingFinalizers();
215+
}
216+
}
217+
catch (Exception e)
218+
{
219+
Console.WriteLine(e);
220+
}
221+
}
222+
223+
#endregion
224+
}

0 commit comments

Comments
 (0)