本文說明如何建立一個實作 IBasicVideoEffect 介面的 Windows 執行階段 元件,以為影片串流創造自訂特效。 你可以用 MediaCapture 和 MediaComposition 使用自訂效果。
Note
介面IBasicVideoEffect是命名空間中的 Windows.Media.Effects Windows 執行階段 API,介面成員也是你在 UWP 中實作的。 然而,WinUI 3 桌面應用程式沒有 UWP 專案所使用的 Windows 執行階段 元件專案範本。 相反地,你必須使用 C#/WinRT 類別函式庫來撰寫這個效果,並且必須明確註冊該元件以啟用 Windows 執行階段,正如本文所述。
為你的影片效果新增一個 Windows 執行階段 元件
WinUI 3 桌面應用程式使用 C#/WinRT 來撰寫 Windows 執行階段 元件,而非僅支援 UWP 的 Windows 執行階段 元件專案範本。
在 方案總管 中右鍵點擊你的解決方案,選擇新增>Project。
選擇 類別庫 專案範本。 專案名稱 為 VideoEffectComponent。
在 VideoEffectComponent.csproj 中,設定目標框架以符合你的 WinUI 3 應用程式,並將專案標記為 Windows 執行階段 元件:
<PropertyGroup> <TargetFramework>net8.0-windows10.0.19041.0</TargetFramework> <CsWinRTComponent>true</CsWinRTComponent> </PropertyGroup>在 VideoEffectComponent 專案中安裝最新的 Microsoft.Windows.CsWinRT NuGet 套件。
從你的主 WinUI 3 應用程式中,將專案參考加入這個元件專案。
將預設類別檔案重新命名為 ExampleVideoEffect.cs。
想了解更多關於以此方式撰寫元件的資訊,請參閱 「攻略—建立 C#/WinRT 元件」。
註冊效果元件以便啟用
VideoEffectDefinition透過其 Windows 執行階段 可啟用的類別 ID(你傳遞給typeof(...).FullName的完整型別名稱)來啟動你的效應。 除非你註冊了該類別 ID,否則啟動會在執行時失敗,並出現「類別未註冊」例外,即使程式碼會編譯。 如何註冊該類別,取決於你的應用程式是否已封裝。
已封裝的應用程式
將一個 <Extensions> 項目新增至 Package.appxmanifest 中,以宣告該效果是由 WinRT.Host.dll 裝載的同處理序可啟用類別;WinRT.Host.dll 也是 C#/WinRT 會加入至您的建置輸出的裝載組件:
<Extensions>
<Extension Category="windows.activatableClass.inProcessServer">
<InProcessServer>
<Path>WinRT.Host.dll</Path>
<ActivatableClass
ActivatableClassId="VideoEffectComponent.ExampleVideoEffect"
ThreadingModel="both" />
</InProcessServer>
</Extension>
</Extensions>
Note
ActivatableClassId 必須完全符合你傳給 VideoEffectDefinition的命名空間限定類別名稱。
未封裝應用程式
未封裝的應用程式沒有 Package.appxmanifest,所以你會把可啟用的類別註冊在應用程式清單檔案中。 新增一個名為 YourApp.exe.manifest 的新文字檔到你的應用程式專案中,將其 內容屬性設 為 True ,以便複製到輸出目錄,並以以下格式新增相同的類別註冊:
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="YourApp"/>
<file name="WinRT.Host.dll">
<activatableClass
name="VideoEffectComponent.ExampleVideoEffect"
threadingModel="both"
xmlns="urn:schemas-microsoft-com:winrt.v1" />
</file>
</assembly>
欲了解更多關於 C#/WinRT 元件的託管與註冊資訊,請參閱 C#/WinRT GitHub 倉庫中的受管理元件託管。
使用軟體處理實作 IBasicVideoEffect 介面
你的影片效果必須實作 IBasicVideoEffect 介面的所有方法與屬性。 本節展示軟體處理實作。
類別定義與命名空間
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Windows.Foundation.Collections;
using Windows.Graphics.Imaging;
using Windows.Media;
using Windows.Media.Effects;
using Windows.Media.MediaProperties;
namespace VideoEffectComponent
{
public sealed class ExampleVideoEffect : IBasicVideoEffect
{
private VideoEncodingProperties _encodingProperties;
private IPropertySet _configuration;
private double _fadeValue = 0.5;
// The following members implement the IBasicVideoEffect and
// IMediaExtension interfaces. Each member is explained in its own
// section later in this article.
public void SetEncodingProperties(
VideoEncodingProperties encodingProperties,
Windows.Graphics.DirectX.Direct3D11.IDirect3DDevice device)
{
_encodingProperties = encodingProperties;
}
public void SetProperties(IPropertySet configuration)
{
_configuration = configuration;
if (configuration != null &&
configuration.TryGetValue("FadeValue", out object value))
{
_fadeValue = (double)value;
}
}
public void ProcessFrame(ProcessVideoFrameContext context)
{
// See ProcessFrame method — software processing later in
// this article for the full implementation.
}
public void DiscardQueuedFrames()
{
// Reset any cached frame data
}
public void Close(MediaEffectClosedReason reason)
{
// Clean up resources
}
public bool IsReadOnly => false;
public bool TimeIndependent => true;
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties
{
get
{
var properties = new List<VideoEncodingProperties>();
properties.Add(new VideoEncodingProperties
{
Subtype = "ARGB32"
});
return properties;
}
}
public MediaMemoryTypes SupportedMemoryTypes => MediaMemoryTypes.Cpu;
}
}
Note
ExampleVideoEffect 類別必須在此處顯示的 VideoEffectComponent 命名空間內宣告,因為本文後續的 typeof(VideoEffectComponent.ExampleVideoEffect).FullName 呼叫以及資訊清單註冊中的 ActivatableClassId 值,都取決於這個完全限定的命名空間名稱。 以下各節將逐一詳細說明每個介面成員;此處顯示的 ProcessFrame 方法只是預留位置,會由 ProcessFrame 方法——軟體處理 中的完整像素處理實作取代。
Close 方法
系統在效果關閉時呼叫 關閉 。 用這個方法處理你創造的任何資源。
public void Close(MediaEffectClosedReason reason)
{
// Clean up resources
}
DiscardQueuedFrames 方法
系統會在您的效果需要重設時呼叫 DiscardQueuedFrames。 利用此方法處理先前快取的幀。
public void DiscardQueuedFrames()
{
// Reset any cached frame data
}
IsReadOnly 屬性
IsReadOnly 屬性告訴系統你的效果是否寫入輸出。 如果你的效果只分析影格,設定為 true 系統會將影格從輸入複製到輸出。
public bool IsReadOnly
{
get => false;
}
Tip
當 IsReadOnly 時 true,系統會在呼叫 ProcessFrame 前將輸入幀複製到輸出幀。 你仍然可以在 ProcessFrame 中寫入輸出影格。
SetEncodingProperties 方法
系統會呼叫 SetEncodingProperties,將影片串流的編碼屬性告知你的效果。 此方法同時也參考了硬體渲染的 Direct3D 裝置。
private Windows.Media.MediaProperties.VideoEncodingProperties _encodingProperties;
public void SetEncodingProperties(
VideoEncodingProperties encodingProperties,
Windows.Graphics.DirectX.Direct3D11.IDirect3DDevice device)
{
_encodingProperties = encodingProperties;
}
SupportedEncodingProperties 屬性
系統會檢查 SupportedEncodingProperties 以判斷你的效果支援哪些編碼屬性。
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties
{
get
{
var properties = new List<VideoEncodingProperties>();
properties.Add(new VideoEncodingProperties
{
Subtype = "ARGB32"
});
return properties;
}
}
Note
如果你回傳的物件清單 VideoEncodingProperties 是空的,系統會預設使用 ARGB32 編碼。
支援的記憶體類型屬性
SupportedMemoryTypes 屬性決定效果存取的是軟體記憶體中的影片影格,還是 GPU 記憶體中的影片影格。
public MediaMemoryTypes SupportedMemoryTypes
{
get => MediaMemoryTypes.Cpu;
}
如果你回傳 MediaMemoryTypes.Cpu,系統會將影格以 SoftwareBitmap 物件傳遞。 如果你返回 MediaMemoryTypes.Gpu,系統會將幀以 IDirect3DSurface 物件傳遞。
TimeIndependent 屬性
如果你的效果不需要均勻的時序,就將 TimeIndependent 設為 true 。 這讓系統能夠優化效能。
public bool TimeIndependent
{
get => true;
}
SetProperties 方法
SetProperties 方法允許呼叫應用程式將設定參數傳給你的效果器。
private double _fadeValue = 0.5;
private Windows.Foundation.Collections.IPropertySet _configuration;
public void SetProperties(IPropertySet configuration)
{
_configuration = configuration;
if (configuration != null &&
configuration.TryGetValue("FadeValue", out object value))
{
_fadeValue = (double)value;
}
}
ProcessFrame 方法 — 軟體處理
ProcessFrame 方法是您的效果修改影像資料的地方。 此方法每個影格呼叫一次,並接收一個 ProcessVideoFrameContext 物件,其中包含輸入和輸出 VideoFrame 物件。
若要存取 SoftwareBitmap 的原始像素資料,請使用 COM 互通操作。 在你的效果命名空間中加入以下介面定義:
[ComImport]
[System.Runtime.InteropServices.Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
void GetBuffer(out byte* buffer, out uint capacity);
}
Note
此技術存取原生且未受管理的影像緩衝區。 你必須設定專案允許不安全的程式碼。 在專案屬性中,選擇 建置 標籤並啟用 允許不安全的程式碼。
以下範例會依照設定的淡化值,將影格中的每個像素調暗:
public unsafe void ProcessFrame(ProcessVideoFrameContext context)
{
using (BitmapBuffer inputBuffer = context.InputFrame
.SoftwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
using (BitmapBuffer outputBuffer = context.OutputFrame
.SoftwareBitmap.LockBuffer(BitmapBufferAccessMode.Write))
{
using (var inputRef = inputBuffer.CreateReference())
using (var outputRef = outputBuffer.CreateReference())
{
byte* inputBytes;
uint inputCapacity;
((IMemoryBufferByteAccess)inputRef)
.GetBuffer(out inputBytes, out inputCapacity);
byte* outputBytes;
uint outputCapacity;
((IMemoryBufferByteAccess)outputRef)
.GetBuffer(out outputBytes, out outputCapacity);
var inputPlane =
inputBuffer.GetPlaneDescription(0);
for (int i = 0;
i < inputPlane.Height;
i++)
{
for (int j = 0;
j < inputPlane.Width;
j++)
{
int offset = inputPlane.StartIndex
+ inputPlane.Stride * i
+ 4 * j;
// Apply fade to B, G, R channels
// (skip alpha at offset+3)
outputBytes[offset + 0] = (byte)(
inputBytes[offset + 0] * _fadeValue);
outputBytes[offset + 1] = (byte)(
inputBytes[offset + 1] * _fadeValue);
outputBytes[offset + 2] = (byte)(
inputBytes[offset + 2] * _fadeValue);
outputBytes[offset + 3] =
inputBytes[offset + 3]; // alpha
}
}
}
}
}
使用Win2D進行硬體處理
基於 GPU 的處理,請使用 Win2D 代替軟體點陣圖操作。 使用硬體處理時:
- 將 Microsoft.Graphics.Win2D NuGet 套件新增至你的效果專案。
- 從
SupportedMemoryTypes傳回MediaMemoryTypes.Gpu。 - 從
SetEncodingProperties儲存 Direct3D 裝置參考。 - 在
ProcessFrame中,從 Direct3D 裝置建立 aCanvasDevice,並對輸出畫面Direct3DSurface進行 Win2D 繪圖操作。
Note
對於 WinUI 3 專案,請使用 Microsoft.Graphics.Win2D 套件,而不是較舊的 Win2D.uwp 套件。
將效果新增至影片串流
將影片效果加入 MediaCapture 影片串流:
var effectDefinition = new VideoEffectDefinition(
typeof(VideoEffectComponent.ExampleVideoEffect).FullName);
await _mediaCapture.AddVideoEffectAsync(
effectDefinition,
MediaStreamType.VideoPreview);
若要傳遞組態屬性:
var properties = new PropertySet();
properties["FadeValue"] = 0.7;
var effectDefinition = new VideoEffectDefinition(
typeof(VideoEffectComponent.ExampleVideoEffect).FullName,
properties);
將效果加入媒體構圖
在 MediaComposition 中將視訊效果新增至片段:
var effectDefinition = new VideoEffectDefinition(
typeof(VideoEffectComponent.ExampleVideoEffect).FullName);
mediaClip.VideoEffectDefinitions.Add(effectDefinition);