Bagikan melalui


Efek video kustom

Artikel ini menjelaskan cara membuat komponen Windows Runtime yang mengimplementasikan antarmuka IBasicVideoEffect untuk membuat efek kustom untuk aliran video. Efek kustom dapat digunakan dengan beberapa API Windows Runtime yang berbeda termasuk MediaCapture, yang menyediakan akses ke kamera perangkat, dan MediaComposition, yang memungkinkan Anda membuat komposisi kompleks dari klip media.

Menambahkan efek kustom ke aplikasi Anda

Efek video kustom didefinisikan dalam kelas yang mengimplementasikan antarmuka IBasicVideoEffect . Kelas ini tidak dapat disertakan langsung dalam proyek aplikasi Anda. Sebagai gantinya, Anda harus menggunakan komponen Windows Runtime untuk menghosting kelas efek video Anda.

Menambahkan komponen Windows Runtime untuk efek video Anda

  1. Di Microsoft Visual Studio, dengan solusi Anda terbuka, buka menu File dan pilih Tambahkan> Proyek Baru.
  2. Pilih jenis proyek Windows Runtime Component (Universal Windows).
  3. Untuk contoh ini, beri nama proyek VideoEffectComponent. Nama ini akan dirujuk dalam kode nanti.
  4. Klik OK.
  5. Templat proyek membuat kelas yang disebut Class1.cs. Di Penjelajah Solusi, klik kanan ikon untuk Class1.cs dan pilih Ganti Nama.
  6. Ganti nama file menjadi ExampleVideoEffect.cs. Visual Studio akan menampilkan permintaan yang menanyakan apakah Anda ingin memperbarui semua referensi ke nama baru. Klik Ya.
  7. Buka ExampleVideoEffect.cs dan perbarui definisi kelas untuk mengimplementasikan antarmuka IBasicVideoEffect.
public sealed class ExampleVideoEffect : IBasicVideoEffect

Anda perlu menyertakan namespace berikut dalam file kelas efek Anda untuk mengakses semua jenis yang digunakan dalam contoh dalam artikel ini.

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

Menerapkan antarmuka IBasicVideoEffect menggunakan pemrosesan perangkat lunak

Efek video Anda harus menerapkan semua metode dan properti antarmuka IBasicVideoEffect. Bagian ini memandikan Anda melalui implementasi sederhana antarmuka ini yang menggunakan pemrosesan perangkat lunak.

Metode tutup

Sistem akan memanggil metode Tutup pada kelas Anda ketika efek harus dimatikan. Anda harus menggunakan metode ini untuk membuang sumber daya apa pun yang telah Anda buat. Argumen untuk metode ini adalah MediaEffectClosedReason yang memungkinkan Anda mengetahui apakah efek ditutup secara normal, jika terjadi kesalahan, atau jika efek tidak mendukung format pengodean yang diperlukan.

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

Metode DiscardQueuedFrames

Metode DiscardQueuedFrames dipanggil ketika efek Anda harus direset. Skenario umum untuk ini adalah jika efek Anda menyimpan bingkai yang diproses sebelumnya untuk digunakan dalam memproses bingkai saat ini. Ketika metode ini dipanggil, Anda harus membuang set bingkai sebelumnya yang Anda simpan. Metode ini dapat digunakan untuk mengatur ulang status apa pun yang terkait dengan bingkai sebelumnya, tidak hanya akumulasi bingkai video.

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

Properti IsReadOnly

Properti IsReadOnly memungkinkan sistem mengetahui apakah efek Anda akan menulis ke output efek. Jika aplikasi Anda tidak memodifikasi bingkai video (misalnya, efek yang hanya melakukan analisis bingkai video), Anda harus mengatur properti ini ke true, yang akan menyebabkan sistem menyalin input bingkai secara efisien ke output bingkai untuk Anda.

Tip

Ketika properti IsReadOnly diatur ke true, sistem menyalin bingkai input ke bingkai output sebelum ProcessFrame dipanggil. Mengatur properti IsReadOnly ke true tidak membatasi Anda menulis ke bingkai output efek di ProcessFrame.

public bool IsReadOnly { get { return false; } }

Metode SetEncodingProperties

Sistem memanggil SetEncodingProperties pada efek Anda untuk memberi tahu Anda properti pengodean untuk aliran video tempat efek beroperasi. Metode ini juga menyediakan referensi ke perangkat Direct3D yang digunakan untuk penyajian perangkat keras. Penggunaan perangkat ini ditampilkan dalam contoh pemrosesan perangkat keras nanti di artikel ini.

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

Properti SupportedEncodingProperties

Sistem memeriksa properti SupportedEncodingProperties untuk menentukan properti pengodean mana yang didukung oleh efek Anda. Perhatikan bahwa jika konsumen efek Anda tidak dapat mengodekan video menggunakan properti yang Anda tentukan, itu akan memanggil Tutup pada efek Anda dan akan menghapus efek Anda dari alur video.

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

Catatan

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

 

Properti SupportedMemoryTypes

Sistem memeriksa properti SupportedMemoryTypes untuk menentukan apakah efek Anda akan mengakses bingkai video dalam memori perangkat lunak atau dalam memori perangkat keras (GPU). Jika Anda mengembalikan MediaMemoryTypes.Cpu, efek Anda akan diteruskan bingkai input dan output yang berisi data gambar di objek SoftwareBitmap. Jika Anda mengembalikan MediaMemoryTypes.Gpu, efek Anda akan diteruskan bingkai input dan output yang berisi data gambar dalam objek IDirect3DSurface .

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

Catatan

Jika Anda menentukan MediaMemoryTypes.GpuAndCpu, sistem akan menggunakan GPU atau memori sistem, mana yang lebih efisien untuk alur. Saat menggunakan nilai ini, Anda harus memeriksa metode ProcessFrame untuk melihat apakah SoftwareBitmap atau IDirect3DSurface yang diteruskan ke metode berisi data, lalu memproses bingkai yang sesuai.

 

Properti TimeIndependent

Properti TimeIndependent memberi tahu sistem jika efek Anda tidak memerlukan waktu yang seragam. Ketika diatur ke true, sistem dapat menggunakan pengoptimalan yang meningkatkan performa efek.

public bool TimeIndependent { get { return true; } }

Metode SetProperties

Metode SetProperties memungkinkan aplikasi yang menggunakan efek Anda untuk menyesuaikan parameter efek. Properti diteruskan sebagai peta IPropertySet dari nama dan nilai properti.

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

Contoh sederhana ini akan meringankan piksel di setiap bingkai video sesuai dengan nilai yang ditentukan. Properti dideklarasikan dan TryGetValue digunakan untuk mendapatkan nilai yang ditetapkan oleh aplikasi panggilan. Jika tidak ada nilai yang ditetapkan, nilai default .5 akan digunakan.

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

Metode ProcessFrame

Metode ProcessFrame adalah tempat efek Anda memodifikasi data gambar video. Metode ini dipanggil sekali per bingkai dan diteruskan objek ProcessVideoFrameContext. Objek ini berisi objek VideoFrame input yang berisi bingkai masuk yang akan diproses dan objek VideoFrame output tempat Anda menulis data gambar yang akan diteruskan ke sisa alur video. Masing-masing objek VideoFrame ini memiliki properti SoftwareBitmap dan properti Direct3DSurface, tetapi mana dari objek ini yang dapat digunakan ditentukan oleh nilai yang Anda kembalikan dari properti SupportedMemoryTypes.

Contoh ini menunjukkan implementasi sederhana metode ProcessFrame menggunakan pemrosesan perangkat lunak. Untuk informasi selengkapnya tentang bekerja dengan objek SoftwareBitmap , lihat Pencitraan. Contoh implementasi ProcessFrame menggunakan pemrosesan perangkat keras ditampilkan nanti di artikel ini.

Mengakses buffer data SoftwareBitmap memerlukan interop COM, jadi Anda harus menyertakan namespace Layanan System.Runtime.InteropServices dalam file kelas efek Anda.

using System.Runtime.InteropServices;

Tambahkan kode berikut di dalam namespace layanan agar efek Anda mengimpor antarmuka untuk mengakses buffer gambar.

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

Catatan

Karena teknik ini mengakses buffer gambar asli yang tidak dikelola, Anda harus mengonfigurasi proyek Anda untuk mengizinkan kode yang tidak aman.

  1. Di Penjelajah Solusi, klik kanan proyek VideoEffectComponent dan pilih Properti.
  2. Pilih tab Build .
  3. Pilih kotak centang Perbolehkan kode tidak aman.

 

Sekarang Anda dapat menambahkan implementasi metode ProcessFrame . Pertama, metode ini mendapatkan objek BitmapBuffer dari bitmap perangkat lunak input dan output. Perhatikan bahwa bingkai output dibuka untuk menulis dan input untuk dibaca. Selanjutnya, IMemoryBufferReference diperoleh untuk setiap buffer dengan memanggil CreateReference. Kemudian, buffer data aktual diperoleh dengan mentransmisikan objek IMemoryBufferReference sebagai antarmuka interop COM yang ditentukan di atas, IMemoryByteAccess, lalu memanggil GetBuffer.

Sekarang setelah buffer data diperoleh, Anda dapat membaca dari buffer input dan menulis ke buffer output. Tata letak buffer diperoleh dengan memanggil GetPlaneDescription, yang menyediakan informasi tentang lebar, langkah, dan offset awal buffer. Bit per piksel ditentukan oleh properti pengodean yang diatur sebelumnya dengan metode SetEncodingProperties. Informasi format buffer digunakan untuk menemukan indeks ke dalam buffer untuk setiap piksel. Nilai piksel dari buffer sumber disalin ke dalam buffer target, dengan nilai warna dikalikan dengan properti FadeValue yang ditentukan untuk efek ini untuk meredupkannya dengan jumlah yang ditentukan.

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

Menerapkan antarmuka IBasicVideoEffect menggunakan pemrosesan perangkat keras

Membuat efek video kustom dengan menggunakan pemrosesan perangkat keras (GPU) hampir identik dengan menggunakan pemrosesan perangkat lunak seperti yang dijelaskan di atas. Bagian ini akan menunjukkan beberapa perbedaan dalam efek yang menggunakan pemrosesan perangkat keras. Contoh ini menggunakan WIN2D Windows Runtime API. Untuk informasi selengkapnya tentang menggunakan Win2D, lihat dokumentasi Win2D.

Gunakan langkah-langkah berikut untuk menambahkan paket Win2D NuGet ke proyek yang Anda buat seperti yang dijelaskan di bagian Tambahkan efek kustom ke aplikasi Anda di awal artikel ini.

Untuk menambahkan paket Win2D NuGet ke proyek efek Anda

  1. Di Penjelajah Solusi, klik kanan proyek VideoEffectComponent dan pilih Kelola Paket NuGet.
  2. Di bagian atas jendela, pilih tab Telusuri .
  3. Di kotak pencarian, masukkan Win2D.
  4. Pilih Win2D.uwp, lalu pilih Instal di panel kanan.
  5. Dialog Tinjau Perubahan memperlihatkan paket yang akan diinstal. Klik OK.
  6. Terima lisensi paket.

Selain namespace layanan yang disertakan dalam penyiapan proyek dasar, Anda harus menyertakan namespace berikut yang disediakan oleh Win2D.

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

Karena efek ini akan menggunakan memori GPU untuk beroperasi pada data gambar, Anda harus mengembalikan MediaMemoryTypes.Gpu dari properti SupportedMemoryTypes.

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

Atur properti pengodean yang akan didukung efek Anda dengan properti SupportedEncodingProperties. Saat bekerja dengan Win2D, Anda harus menggunakan pengodean ARGB32.

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

Gunakan metode SetEncodingProperties untuk membuat objek Win2D CanvasDevice baru dari IDirect3DDevice yang diteruskan ke metode .

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

Implementasi SetProperties identik dengan contoh pemrosesan perangkat lunak sebelumnya. Contoh ini menggunakan properti BlurAmount untuk mengonfigurasi efek blur 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;
    }
}

Langkah terakhir adalah menerapkan metode ProcessFrame yang benar-benar memproses data gambar.

Menggunakan API Win2D, CanvasBitmap dibuat dari properti Direct3DSurface bingkai input. CanvasRenderTarget dibuat dari Direct3DSurface bingkai output dan CanvasDrawingSession dibuat dari target render ini. Win2D GaussianBlurEffect baru diinisialisasi, menggunakan properti BlurAmount efek kami terekspos melalui SetProperties. Terakhir, metode CanvasDrawingSession.DrawImage dipanggil untuk menggambar bitmap input ke target render menggunakan efek kabur.

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

    }
}

Menambahkan efek kustom ke aplikasi Anda

Untuk menggunakan efek video dari aplikasi, Anda harus menambahkan referensi ke proyek efek ke aplikasi Anda.

  1. Di Penjelajah Solusi, di bawah proyek aplikasi Anda, klik kanan Referensi dan pilih Tambahkan referensi.
  2. Perluas tab Proyek , pilih Solusi, lalu pilih kotak centang untuk nama proyek efek Anda. Untuk contoh ini, namanya adalah VideoEffectComponent.
  3. Klik OK.

Menambahkan efek kustom Anda ke aliran video kamera

Anda dapat menyiapkan streaming pratinjau sederhana dari kamera dengan mengikuti langkah-langkah dalam artikel Akses pratinjau kamera sederhana. Mengikuti langkah-langkah tersebut akan memberi Anda objek MediaCapture yang diinisialisasi yang digunakan untuk mengakses aliran video kamera.

Untuk menambahkan efek video kustom Anda ke aliran kamera, pertama-tama buat objek VideoEffectDefinition baru, meneruskan namespace layanan dan nama kelas untuk efek Anda. Selanjutnya, panggil metode AddVideoEffect objek MediaCapture untuk menambahkan efek Anda ke aliran yang ditentukan. Contoh ini menggunakan nilai MediaStreamType.VideoPreview untuk menentukan bahwa efek harus ditambahkan ke aliran pratinjau. Jika aplikasi Mendukung pengambilan video, Anda juga dapat menggunakan MediaStreamType.VideoRecord untuk menambahkan efek ke aliran pengambilan. AddVideoEffect mengembalikan objek IMediaExtension yang mewakili efek kustom Anda. Anda dapat menggunakan metode SetProperties untuk mengatur konfigurasi untuk efek Anda.

Setelah efek ditambahkan, StartPreviewAsync dipanggil untuk memulai aliran pratinjau.

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

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

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

await mediaCapture.StartPreviewAsync();

Menambahkan efek kustom Anda ke klip di MediaComposition

Untuk panduan umum untuk membuat komposisi media dari klip video, lihat Komposisi dan pengeditan media. Cuplikan kode berikut menunjukkan pembuatan komposisi media sederhana yang menggunakan efek video kustom. Objek MediaClip dibuat dengan memanggil CreateFromFileAsync, meneruskan file video yang dipilih oleh pengguna dengan FileOpenPicker, dan klip ditambahkan ke MediaComposition baru. Selanjutnya objek VideoEffectDefinition baru dibuat, meneruskan namespace layanan dan nama kelas untuk efek Anda ke konstruktor. Terakhir, definisi efek ditambahkan ke koleksi VideoEffectDefinitions dari objek MediaClip.

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