Efek video kustom

Artikel ini menjelaskan cara membuat komponen Windows Runtime yang mengimplementasikan antarmuka IBasicVideoEffect untuk membuat efek kustom untuk aliran video. Anda dapat menggunakan efek kustom dengan MediaCapture dan MediaComposition.

Note

Antarmuka IBasicVideoEffect adalah API Windows Runtime dalam ruang nama Windows.Media.Effects, dan anggota antarmukanya sama dengan yang Anda implementasikan di UWP. Namun, aplikasi desktop WinUI 3 tidak memiliki templat proyek Komponen Windows Runtime yang digunakan proyek UWP. Sebagai gantinya, Anda menulis efek menggunakan pustaka kelas C#/WinRT, dan Anda harus secara eksplisit mendaftarkan komponen untuk aktivasi Windows Runtime, seperti yang dijelaskan dalam artikel ini.

Menambahkan komponen Windows Runtime untuk efek video Anda

Aplikasi desktop WinUI 3 menggunakan C#/WinRT untuk menulis komponen Windows Runtime, bukan templat proyek Komponen Windows Runtime khusus UWP.

  1. Klik kanan solusi Anda di Penjelajah Solusi dan pilih Tambahkan>Project Baru.

  2. Pilih templat proyek Pustaka Kelas . Beri nama proyek VideoEffectComponent.

  3. Di VideoEffectComponent.csproj, atur kerangka kerja target agar sesuai dengan aplikasi WinUI 3 Anda dan tandai proyek sebagai komponen Windows Runtime:

    <PropertyGroup>
        <TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
        <CsWinRTComponent>true</CsWinRTComponent>
    </PropertyGroup>
    
  4. Instal paket NuGet Microsoft.Windows.CsWinRT terbaru di proyek VideoEffectComponent.

  5. Tambahkan referensi proyek dari aplikasi WinUI 3 utama Anda ke proyek komponen ini.

  6. Ganti nama file kelas default menjadi ExampleVideoEffect.cs.

Untuk informasi selengkapnya tentang penulisan komponen dengan cara ini, lihat Panduan—Membuat komponen C#/WinRT.

Mendaftarkan komponen efek untuk aktivasi

VideoEffectDefinition mengaktifkan efek Anda menggunakan ID kelas yang dapat diaktifkan Windows Runtime (nama tipe lengkap yang Anda teruskan ke typeof(...).FullName). Kecuali Anda mendaftarkan ID kelas tersebut, aktivasi gagal pada waktu proses dengan pengecualian "kelas tidak terdaftar", meskipun kode dikompilasi. Cara Anda mendaftarkan kelas tergantung pada apakah aplikasi Anda dipaketkan.

Aplikasi kemasan

Tambahkan entri <Extensions> ke Package.appxmanifest yang mendeklarasikan efek sebagai kelas yang dapat diaktifkan dalam proses yang dihosting oleh WinRT.Host.dll, yang merupakan assembly host yang ditambahkan C#/WinRT ke output build Anda:

<Extensions>
    <Extension Category="windows.activatableClass.inProcessServer">
        <InProcessServer>
            <Path>WinRT.Host.dll</Path>
            <ActivatableClass
                ActivatableClassId="VideoEffectComponent.ExampleVideoEffect"
                ThreadingModel="both" />
        </InProcessServer>
    </Extension>
</Extensions>

Note

ActivatableClassId harus sama persis dengan nama kelas yang memenuhi syarat namespace yang Anda berikan ke VideoEffectDefinition.

Aplikasi yang tidak dikemas

Aplikasi yang tidak dikemas tidak memiliki Package.appxmanifest, sehingga Anda mendaftarkan kelas yang dapat diaktifkan dalam file manifes aplikasi sebagai gantinya. Tambahkan file teks baru bernama YourApp.exe.manifest ke proyek aplikasi Anda, atur properti Kontennya ke True sehingga disalin ke direktori output, dan tambahkan pendaftaran kelas yang sama dalam format ini:

<?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>

Untuk informasi selengkapnya tentang menghosting dan mendaftarkan komponen C#/WinRT, lihat Hosting komponen terkelola di repositori C#/WinRT GitHub.

Menerapkan antarmuka IBasicVideoEffect menggunakan pemrosesan perangkat lunak

Efek video Anda harus menerapkan semua metode dan properti antarmuka IBasicVideoEffect . Bagian ini menunjukkan implementasi pemrosesan perangkat lunak.

Definisi kelas dan namespace

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

Kelas ExampleVideoEffect harus dideklarasikan di dalam namespace VideoEffectComponent yang ditampilkan di sini, karena pemanggilan typeof(VideoEffectComponent.ExampleVideoEffect).FullName nanti dalam artikel ini, dan nilai ActivatableClassId dalam pendaftaran manifes, bergantung pada nama yang memenuhi syarat namespace yang persis ini. Bagian-bagian di bawah ini menjelaskan setiap anggota antarmuka secara rinci; metode ProcessFrame yang ditampilkan di sini adalah placeholder yang digantikan oleh implementasi pemrosesan piksel lengkap di dalam metode ProcessFrame — pemrosesan perangkat lunak.

Metode Penutupan

Sistem memanggil Tutup ketika efek dimatikan. Gunakan metode ini untuk membuang sumber daya apa pun yang Anda buat.

public void Close(MediaEffectClosedReason reason)
{
    // Clean up resources
}

Metode DiscardQueuedFrames

Sistem memanggil DiscardQueuedFrames ketika efek Anda harus direset. Gunakan ini untuk menghapus frame yang sebelumnya disimpan dalam cache.

public void DiscardQueuedFrames()
{
    // Reset any cached frame data
}

Properti IsReadOnly

Properti IsReadOnly memberi tahu sistem apakah efek Anda menulis ke output. Jika efek Anda hanya menganalisis bingkai, atur ini ke true sehingga sistem menyalin bingkai dari input ke output.

public bool IsReadOnly
{
    get => false;
}

Tip

Ketika IsReadOnly adalah true, sistem menyalin bingkai input ke bingkai output sebelum ProcessFrame dipanggil. Anda masih dapat menulis ke frame output di ProcessFrame.

Metode SetEncodingProperties

Sistem memanggil SetEncodingProperties untuk memberi tahu efek Anda tentang properti pengodean untuk aliran video. Metode ini juga menyediakan referensi ke perangkat Direct3D untuk penyajian perangkat keras.

private Windows.Media.MediaProperties.VideoEncodingProperties _encodingProperties;

public void SetEncodingProperties(
    VideoEncodingProperties encodingProperties,
    Windows.Graphics.DirectX.Direct3D11.IDirect3DDevice device)
{
    _encodingProperties = encodingProperties;
}

properti SupportedEncodingProperties

Sistem memeriksa SupportedEncodingProperties untuk menentukan properti pengodean mana yang didukung efek Anda.

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

Note

Jika Anda mengembalikan daftar VideoEncodingProperties objek kosong, sistem default ke pengodean ARGB32.

properti SupportedMemoryTypes

Properti SupportedMemoryTypes menentukan apakah efek Anda mengakses bingkai video dalam memori perangkat lunak atau memori GPU.

public MediaMemoryTypes SupportedMemoryTypes
{
    get => MediaMemoryTypes.Cpu;
}

Jika Anda mengembalikan MediaMemoryTypes.Cpu, sistem meneruskan bingkai sebagai objek SoftwareBitmap . Jika Anda mengembalikan MediaMemoryTypes.Gpu, sistem meneruskan bingkai sebagai objek IDirect3DSurface .

Properti TimeIndependent

Atur TimeIndependent ke true jika efek Anda tidak memerlukan waktu yang seragam. Ini memungkinkan sistem untuk mengoptimalkan performa.

public bool TimeIndependent
{
    get => true;
}

Metode SetProperties

Metode SetProperties memungkinkan aplikasi panggilan meneruskan parameter konfigurasi ke efek Anda.

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;
    }
}

Metode ProcessFrame — pemrosesan perangkat lunak

Metode ProcessFrame adalah tempat efek Anda memodifikasi data gambar. Metode ini dipanggil sekali per bingkai dan menerima objek ProcessVideoFrameContext dengan objek VideoFrame input dan output.

Untuk mengakses data piksel mentah dari SoftwareBitmap, gunakan interop COM. Tambahkan definisi antarmuka berikut di namespace layanan efek Anda:

[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

Teknik ini mengakses buffer gambar asli yang tidak dikelola. Anda harus mengonfigurasi proyek Anda untuk mengizinkan kode yang tidak aman. Di properti proyek, pilih tab Build dan aktifkan Izinkan kode yang tidak aman.

Contoh berikut meredupkan setiap piksel dalam frame dengan nilai peredupan yang dikonfigurasi:

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
                }
            }
        }
    }
}

Pemrosesan perangkat keras dengan Win2D

Untuk pemrosesan berbasis GPU, gunakan Win2D alih-alih manipulasi bitmap perangkat lunak. Saat menggunakan pemrosesan perangkat keras:

  1. Tambahkan paket NuGet Microsoft.Graphics.Win2D ke proyek efek Anda.
  2. Kembali ke MediaMemoryTypes.Gpu dari SupportedMemoryTypes.
  3. Simpan referensi perangkat Direct3D dari SetEncodingProperties.
  4. Dalam ProcessFrame, buatlah CanvasDevice dari perangkat Direct3D dan gunakan operasi menggambar Win2D pada Direct3DSurface frame output.

Note

Untuk proyek WinUI 3, gunakan paket Microsoft.Graphics.Win2D alih-alih paket Win2D.uwp yang lebih lama.

Menambahkan efek ke aliran video

Tambahkan efek video ke aliran video MediaCapture :

var effectDefinition = new VideoEffectDefinition(
    typeof(VideoEffectComponent.ExampleVideoEffect).FullName);

await _mediaCapture.AddVideoEffectAsync(
    effectDefinition,
    MediaStreamType.VideoPreview);

Untuk meneruskan properti konfigurasi:

var properties = new PropertySet();
properties["FadeValue"] = 0.7;

var effectDefinition = new VideoEffectDefinition(
    typeof(VideoEffectComponent.ExampleVideoEffect).FullName,
    properties);

Menambahkan efek ke komposisi media

Tambahkan efek video ke klip di MediaComposition:

var effectDefinition = new VideoEffectDefinition(
    typeof(VideoEffectComponent.ExampleVideoEffect).FullName);

mediaClip.VideoEffectDefinitions.Add(effectDefinition);