共用方式為


自訂視訊效果

本文介紹如何建立實作 IBasicVideoEffect 介面的 Windows 執行階段元件,為視訊串流建立自訂效果。 自訂效果可與多種不同的 Windows 執行階段 API 一起使用,包括 MediaCapture (提供對裝置相機的存取) 和 MediaComposition (可讓您從媒體短片建立複雜的組合)。

在您的應用程式中新增自訂效果

自訂視訊效果定義於實作 IBasicVideoEffect 介面的類別中。 此類別無法直接包含在您應用程式的專案中。 相反地,您必須使用 Windows 執行階段元件來裝載視訊效果類別。

為您的視訊效果新增 Windows 執行階段元件

  1. 在 Microsoft Visual Studio 中,開啟解決方案後,請前往檔案功能表並選取新增->新專案
  2. 選取 Windows 執行階段元件 (通用 Windows) 專案類型。
  3. 在此範例中,將專案命名為 VideoEffectComponent。 稍後會在程式碼中參考此名稱。
  4. 按一下 [確定]
  5. 專案範本會建立名為 Class1.cs 的類別。 在方案總管中,以滑鼠右鍵按一下 Class1.cs 圖示,然後選取重新命名
  6. 將檔案重新命名為 ExampleVideoEffect.cs。 Visual Studio 會顯示提示,詢問您是否要更新新名稱的所有參考。 按一下
  7. 開啟 ExampleVideoEffect.cs 並更新類別定義以實作 IBasicVideoEffect 介面。
public sealed class ExampleVideoEffect : IBasicVideoEffect

您必須在效果類別檔案中包含下列命名空間,才能存取本文範例中使用的所有類型。

using Windows.Media.Effects;
using Windows.Media.MediaProperties;
using Windows.Foundation.Collections;
using Windows.Graphics.DirectX.Direct3D11;
using Windows.Graphics.Imaging;

使用軟體處理實作 IBasicVideoEffect 介面

您的視訊效果必須實作 IBasicVideoEffect 介面的所有方法和屬性。 本節將逐步引導您完成此介面的簡單實作,以使用軟體處理。

Close 方法

當效果應該關閉時,系統將呼叫類別上的 Close 方法。 您應該使用這個方法來處置您已建立的任何資源。 此方法的引數是 MediaEffectClosedReason,它可以讓您知道效果是否正常關閉、是否發生錯誤或效果是否不支援所需的編碼格式。

public void Close(MediaEffectClosedReason reason)
{
    // Dispose of effect resources
}

DiscardQueuedFrames 方法

當您的效果應重設時,將會呼叫 DiscardQueuedFrames 方法。 一個典型的案例是,您的效果會儲存先前處理的畫面,以用於處理目前的畫面。 呼叫此方法時,您應該處置先前儲存的畫面集。 這個方法可以用來重設與先前畫面相關的任何狀態,而不只是累積的視訊畫面。

private int frameCount;
public void DiscardQueuedFrames()
{
    frameCount = 0;
}

IsReadOnly 屬性

IsReadOnly 屬性可讓系統知道您的效果是否會寫入效果的輸出。 如果您的 app 未修改視訊畫面 (例如,只執行視訊畫面分析的效果),您應該將此屬性設定為 true,這會導致系統有效率地將畫面輸入複製到畫面輸出。

提示

IsReadOnly 屬性設為 true 時,系統會在呼叫 ProcessFrame 之前將輸入畫面複製到輸出畫面。 將 IsReadOnly 屬性設為 true 不會限制您寫入 ProcessFrame 中的效果輸出畫面。

public bool IsReadOnly { get { return false; } }

SetEncodingProperties 方法

系統會在效果上呼叫 SetEncodingProperties,讓您知道效果運作所在的視訊串流編碼屬性。 這個方法也會提供用於硬體轉譯之 Direct3D 裝置的參考。 本文稍後的硬體處理範例會顯示此裝置的使用方式。

private VideoEncodingProperties encodingProperties;
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
    this.encodingProperties = encodingProperties;
}

SupportedEncodingProperties 屬性

系統會檢查 SupportedEncodingProperties 屬性,以判斷您的效果支援哪些編碼屬性。 請注意,如果效果的取用者無法使用您指定的屬性來編碼音訊,系統將會在效果上呼叫 Close,並將您的效果從視訊管線中移除。

public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties
{            
    get
    {
        var encodingProperties = new VideoEncodingProperties();
        encodingProperties.Subtype = "ARGB32";
        return new List<VideoEncodingProperties>() { encodingProperties };

        // If the list is empty, the encoding type will be ARGB32.
        // return new List<VideoEncodingProperties>();
    }
}

注意

如果從 SupportedEncodingProperties 傳回空的 VideoEncodingProperties 物件清單,系統將預設使用 ARGB32 編碼。

 

SupportedMemoryTypes 屬性

系統會檢查 SupportedMemoryTypes 屬性,以判斷您的效果是否會存取軟體記憶體或硬體 (GPU) 記憶體中的視訊畫面。 如果您傳回 MediaMemoryTypes.Cpu,您的效果將會傳遞輸入和輸出畫面,其中包含 SoftwareBitmap 物件中的影像資料。 如果您傳回 MediaMemoryTypes.Gpu,您的效果將會傳遞輸入和輸出畫面,其中包含 IDirect3DSurface 物件中的影像資料。

public MediaMemoryTypes SupportedMemoryTypes { get { return MediaMemoryTypes.Cpu; } }

注意

如果您指定 MediaMemoryTypes.GpuAndCpu,系統將會使用 GPU 或系統記憶體,管線會更有效率。 使用此值時,必須在 ProcessFrame 方法中檢查傳入該方法的 SoftwareBitmapIDirect3DSurface 是否包含資料,然後相應地處理畫面。

 

TimeIndependent 屬性

TimeIndependent 屬性讓系統知道您的效果是否不需要統一計時。 當設定為 true 時,系統可以使用可增強效果效能的最佳化。

public bool TimeIndependent { get { return true; } }

SetProperties 方法

SetProperties 方法允許使用您的效果的應用程式調整效果參數。 屬性做為屬性名稱和值的 IPropertySet 對應進行傳遞。

private IPropertySet configuration;
public void SetProperties(IPropertySet configuration)
{
    this.configuration = configuration;
}

這個簡單的範例會根據指定的值,將每個視訊畫面中的像素變暗。 屬性會宣告,而 TryGetValue 是用來取得呼叫應用程式所設定的值。 如果未設定任何值,則會使用預設值 .5。

public double FadeValue
{
    get
    {
        object val;
        if (configuration != null && configuration.TryGetValue("FadeValue", out val))
        {
            return (double)val;
        }
        return .5;
    }
}

ProcessFrame 方法

ProcessFrame 方法是您的效果修改視訊影像資料的位置。 此方法每個畫面呼叫一次,並傳遞一個 ProcessVideoFrameContext 物件。 該物件包含一個輸入 VideoFrame 物件 (其中包含要處理的傳入畫面) 和一個輸出 VideoFrame 物件,您可以在其中寫入影像資料,這些資料將傳遞到視訊管線的其餘部分。 每個 VideoFrame 物件都有一個 SoftwareBitmap 屬性和一個 Direct3DSurface 屬性,但可以使用其中哪一個取決於從 SupportedMemoryTypes 屬性傳回的值。

此範例示範使用軟體處理之 ProcessFrame 方法的簡單實作。 有關使用 SoftwareBitmap 物件的更多資訊,請參閱映像處理。 本文稍後會顯示使用硬體處理的 ProcessFrame 實作範例。

存取 SoftwareBitmap 的資料緩衝區需要 COM Interop,因此您應該在效果類別檔案中包含 System.Runtime.InteropServices 命名空間。

using System.Runtime.InteropServices;

在命名空間內新增下列程式碼,以取得效果,以匯入介面來存取影像緩衝區。

[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
    void GetBuffer(out byte* buffer, out uint capacity);
}

注意

由於這項技術會存取原生非受控影像緩衝區,因此您必須設定專案以允許不安全的程式碼。

  1. 在方案總管中,以滑鼠右鍵按一下 VideoEffectComponent 專案並選取 屬性
  2. 選取組建索引標籤。
  3. 選取允許不安全程式碼核取方塊。

 

現在您可以新增 ProcessFrame 方法實作。 首先,此方法從輸入和輸出軟體點陣圖中取得 BitmapBuffer 物件。 請注意,輸出畫面已開啟以供寫入,以及讀取的輸入。 接下來,透過呼叫 CreateReference 為每個緩衝區取得 IMemoryBufferReference。 然後,透過將 IMemoryBufferReference 物件轉換為上面定義的 COM Interop 介面 IMemoryByteAccess,然後呼叫 GetBuffer 來取得實際的資料緩衝區。

現在已取得資料緩衝區,您可以從輸入緩衝區讀取並寫入輸出緩衝區。 緩衝區的配置是透過呼叫 GetPlaneDescription 來獲得的,它提供有關緩衝區的寬度、步距和初始位移的資訊。 每個像素的位元取決於先前使用 SetEncodingProperties 方法所設定的編碼屬性。 緩衝區格式資訊是用來尋找每個像素緩衝區中的索引。 來源緩衝區中的像素值會複製到目標緩衝區,其色彩值乘以為此效果定義的 FadeValue 屬性,以將它們減去指定的數量。

public unsafe void ProcessFrame(ProcessVideoFrameContext context)
{
    using (BitmapBuffer buffer = context.InputFrame.SoftwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
    using (BitmapBuffer targetBuffer = context.OutputFrame.SoftwareBitmap.LockBuffer(BitmapBufferAccessMode.Write))
    {
        using (var reference = buffer.CreateReference())
        using (var targetReference = targetBuffer.CreateReference())
        {
            byte* dataInBytes;
            uint capacity;
            ((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacity);

            byte* targetDataInBytes;
            uint targetCapacity;
            ((IMemoryBufferByteAccess)targetReference).GetBuffer(out targetDataInBytes, out targetCapacity);

            var fadeValue = FadeValue;

            // Fill-in the BGRA plane
            BitmapPlaneDescription bufferLayout = buffer.GetPlaneDescription(0);
            for (int i = 0; i < bufferLayout.Height; i++)
            {
                for (int j = 0; j < bufferLayout.Width; j++)
                {

                    byte value = (byte)((float)j / bufferLayout.Width * 255);

                    int bytesPerPixel = 4; 
                    if (encodingProperties.Subtype != "ARGB32")
                    {
                        // If you support other encodings, adjust index into the buffer accordingly
                    }
                    

                    int idx = bufferLayout.StartIndex + bufferLayout.Stride * i + bytesPerPixel * j;

                    targetDataInBytes[idx + 0] = (byte)(fadeValue * (float)dataInBytes[idx + 0]);
                    targetDataInBytes[idx + 1] = (byte)(fadeValue * (float)dataInBytes[idx + 1]);
                    targetDataInBytes[idx + 2] = (byte)(fadeValue * (float)dataInBytes[idx + 2]);
                    targetDataInBytes[idx + 3] = dataInBytes[idx + 3];
                }
            }
        }
    }
}

使用硬體處理實作 IBasicVideoEffect 介面

使用硬體 (GPU) 處理建立自訂視訊效果與使用軟體處理幾乎完全相同,如上所述。 本節會顯示使用硬體處理之效果的一些差異。 此範例使用 Win2D Windows 執行階段 API。 有關使用 Win2D 的詳細資訊,請參閱 Win2D 文件

使用下列步驟,將 Win2D NuGet 套件新增至您建立的專案,如本文開頭的將自訂效果新增至您的應用程式一節中所述。

將 Win2D NuGet 套件新增到效果專案

  1. 方案總管中,以滑鼠右鍵按一下 VideoEffectComponent 專案並選取管理 NuGet 套件
  2. 在視窗頂部,選取瀏覽索引標籤。
  3. 在搜尋方塊中輸入 Win2D
  4. 選取 Win2D.uwp,然後選取右窗格中的安裝
  5. 檢閱變更對話方塊會顯示要安裝的套件。 按一下 [確定]
  6. 接受套件授權。

除了基本專案設定中包含的命名空間之外,您還需要包含 Win2D 所提供的下列命名空間。

using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas;

由於此效果將使用 GPU 記憶體來操作影像資料,因此您應該從 SupportedMemoryTypes 屬性傳回 MediaMemoryTypes.Gpu

public MediaMemoryTypes SupportedMemoryTypes { get { return MediaMemoryTypes.Gpu; } }

使用 SupportedEncodingProperties 屬性設定效果將支援的編碼屬性。 使用 Win2D 時,您必須使用 ARGB32 編碼。

public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties {
    get
    {
        var encodingProperties = new VideoEncodingProperties();
        encodingProperties.Subtype = "ARGB32";
        return new List<VideoEncodingProperties>() { encodingProperties };
    }
}

使用 SetEncodingProperties 方法從傳遞到該方法的 IDirect3DDevice 建立新的 Win2D CanvasDevice 物件。

private CanvasDevice canvasDevice;
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
    canvasDevice = CanvasDevice.CreateFromDirect3D11Device(device);
}

SetProperties 實作與前面的軟體處理範例相同。 此範例會使用 BlurAmount 屬性來設定 Win2D 模糊效果。

private IPropertySet configuration;
public void SetProperties(IPropertySet configuration)
{
    this.configuration = configuration;
}
public double BlurAmount
{
    get
    {
        object val;
        if (configuration != null && configuration.TryGetValue("BlurAmount", out val))
        {
            return (double)val;
        }
        return 3;
    }
}

最後一步是實作實際處理影像資料的 ProcessFrame 方法。

使用 Win2D API,可以根據輸入畫面的 Direct3DSurface 屬性建立 CanvasBitmapCanvasRenderTarget 是從輸出畫面的 Direct3DSurface 建立的,並且 CanvasDrawingSession 是從該轉譯目標建立的。 使用我們的效果透過 SetProperties 公開的 BlurAmount 屬性來初始化新的 Win2D GaussianBlurEffect。 最後,呼叫 CanvasDrawingSession.DrawImage 方法,使用模糊效果將輸入點陣圖繪製到轉譯目標。

public void ProcessFrame(ProcessVideoFrameContext context)
{

    using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromDirect3D11Surface(canvasDevice, context.InputFrame.Direct3DSurface))
    using (CanvasRenderTarget renderTarget = CanvasRenderTarget.CreateFromDirect3D11Surface(canvasDevice, context.OutputFrame.Direct3DSurface))
    using (CanvasDrawingSession ds = renderTarget.CreateDrawingSession())
    {


        var gaussianBlurEffect = new GaussianBlurEffect
        {
            Source = inputBitmap,
            BlurAmount = (float)BlurAmount,
            Optimization = EffectOptimization.Speed
        };

        ds.DrawImage(gaussianBlurEffect);

    }
}

將自訂效果新增至您的應用程式

若要從您的應用程式使用視訊效果,您必須將效果項目的參考新增至您的應用程式。

  1. 在方案總管中,於您的應用程式專案下,以滑鼠右鍵按一下參考,然後選取新增參考
  2. 展開專案索引標籤,選取解決方案,然後選取效果項目名稱的核取方塊。 在這則範例中,名稱為 VideoEffectComponent
  3. 按一下 [確定]

將自訂效果新增至相機機視訊串流

您可以按照簡單相機預覽存取一文中的步驟從相機設定簡單的預覽串流。 按照這些步驟將為您提供一個初始化的 MediaCapture 物件,用於存取相機的視訊串流。

若要將自訂視訊效果新增至相機串流,請先建立新的 VideoEffectDefinition 物件,並傳入效果的命名空間和類別名稱。 接下來,呼叫 MediaCapture 物件的 AddVideoEffect 方法會將效果新增至指定的串流。 這個範例會使用 MediaStreamType.VideoPreview 值來指定效果應該新增至預覽串流。 如果您的應用程式支援視訊擷取,您也可以使用 MediaStreamType.VideoRecord 將效果新增至擷取串流。 AddVideoEffect 會傳回代表您自訂效果的 IMediaExtension 物件。 您可以使用 SetProperties 方法來設定效果的設定。

新增效果後,將呼叫 StartPreviewAsync 以啟動預覽串流。

var videoEffectDefinition = new VideoEffectDefinition("VideoEffectComponent.ExampleVideoEffect");

IMediaExtension videoEffect =
   await mediaCapture.AddVideoEffectAsync(videoEffectDefinition, MediaStreamType.VideoPreview);

videoEffect.SetProperties(new PropertySet() { { "FadeValue", .25 } });

await mediaCapture.StartPreviewAsync();

將自訂效果新增至 MediaComposition 中的剪輯

如需從影片剪輯建立媒體組合的一般指引,請參閱媒體組合和編輯。 下列程式碼片段示範如何建立使用自訂視訊效果的簡單媒體組合。 MediaClip 物件是透過呼叫 CreateFromFileAsync 建立的,傳入使用者使用 FileOpenPicker 選取的視訊檔案,並將剪輯新增至新的 MediaComposition。 接下來會建立新的 VideoEffectDefinition 物件,並將效果的命名空間和類別名稱傳遞至建構函式。 最後,效果定義會新增至 MediaClip 物件的 VideoEffectDefinitions 集合。

MediaComposition composition = new MediaComposition();
var clip = await MediaClip.CreateFromFileAsync(pickedFile);
composition.Clips.Add(clip);

var videoEffectDefinition = new VideoEffectDefinition("VideoEffectComponent.ExampleVideoEffect", new PropertySet() { { "FadeValue", .5 } });

clip.VideoEffectDefinitions.Add(videoEffectDefinition);