Anpassade videoeffekter

I den här artikeln beskrivs hur du skapar en Windows Runtime komponent som implementerar gränssnittet IBasicVideoEffect för att skapa anpassade effekter för videoströmmar. Du kan använda anpassade effekter med MediaCapture och MediaComposition.

Note

Gränssnittet IBasicVideoEffect är ett Windows Runtime API i Windows.Media.Effects namnområdet och gränssnittsmedlemmarna är samma som du implementerade i UWP. WinUI 3-skrivbordsappar har dock inte den Windows Runtime komponentprojektmall som UWP-projekt använde. I stället skapar du effekten med hjälp av ett C#/WinRT-klassbibliotek, och du måste uttryckligen registrera komponenten för Windows Runtime aktivering enligt beskrivningen i den här artikeln.

Lägg till en Windows Runtime komponent för videoeffekten

WinUI 3-skrivbordsappar använder C#/WinRT för att skapa Windows Runtime komponenter i stället för projektmallen UWP-only Windows Runtime Component.

  1. Högerklicka på lösningen i Prieskumník riešení och välj Lägg till>ny Project.

  2. Välj projektmallen Klassbibliotek . Ge projektet namnet VideoEffectComponent.

  3. I VideoEffectComponent.csproj anger du målramverket så att det matchar WinUI 3-appen och markerar projektet som en Windows Runtime komponent:

    <PropertyGroup>
        <TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
        <CsWinRTComponent>true</CsWinRTComponent>
    </PropertyGroup>
    
  4. Installera det senaste Microsoft.Windows.CsWinRT-NuGet-paketet i projektet VideoEffectComponent.

  5. Lägg till en projektreferens från din huvudsakliga WinUI 3-app i det här komponentprojektet.

  6. Byt namn på standardklassfilen till ExampleVideoEffect.cs.

Mer information om hur du redigerar komponenter på det här sättet finns i Genomgång – Skapa en C#/WinRT-komponent.

Registrera effektkomponenten för aktivering

VideoEffectDefinition aktiverar din effekt med hjälp av dess aktiveringsbara klass-ID för Windows Runtime (det fullständiga typnamnet som du skickar till typeof(...).FullName). Om du inte registrerar klass-ID:t misslyckas aktiveringen under körning med ett undantag av typen ”klass inte registrerad”, även om koden kompileras. Hur du registrerar klassen beror på om din app är paketerad.

Paketerade appar

Lägg till en <Extensions> post i Package.appxmanifest som deklarerar effekten som en aktiverbar klass i processen som hanteras av WinRT.Host.dll, vilket är värdsammansättningen som C#/WinRT lägger till i byggresultatet:

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

Note

ActivatableClassId måste exakt matcha det namnområdeskvalificerade klassnamnet som du skickar till VideoEffectDefinition.

Opacketerade appar

Opaketerade appar har ingen Package.appxmanifest, så i stället registrerar du den aktiverbara klassen i en programmanifestfil. Lägg till en ny textfil med namnet YourApp.exe.manifest i appprojektet, ställ in dess innehållsegenskapTrue så att den kopieras till utdatakatalogen och lägg till samma klassregistrering i det här formatet:

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

Mer information om värdhantering och registrering av C#/WinRT-komponenter hittar du i Värdhantering för hanterade komponenter i C#/WinRT:s GitHub-repo.

Implementera gränssnittet IBasicVideoEffect med hjälp av programvarubearbetning

Videoeffekten måste implementera alla metoder och egenskaper för gränssnittet IBasicVideoEffect . Det här avsnittet visar en implementering av programvarubearbetning.

Klassdefinition och namnområden

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

Klassen ExampleVideoEffect måste deklareras i namnområdet VideoEffectComponent som visas här, eftersom anropen typeof(VideoEffectComponent.ExampleVideoEffect).FullName senare i den här artikeln och ActivatableClassId värdet i manifestregistreringen beror på det exakta namnområdeskvalificerade namnet. Avsnitten nedan går igenom varje gränssnittsmedlem i detalj; metoden ProcessFrame som visas här är en platshållare som ersätts med den fullständiga pixelbearbetningsimplementeringen i ProcessFrame-metoden – programvarubearbetning.

Stäng-metod

Systemet anropar Stäng när effekten stängs av. Använd den här metoden för att ta bort alla resurser som du har skapat.

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

DiscardQueuedFrames metod

Systemet anropar DiscardQueuedFrames när effekten ska återställas. Använd det här alternativet om du vill ta bort tidigare cachelagrade ramar.

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

IsReadOnly-egenskap

Egenskapen IsReadOnly talar om för systemet om din effekt skriver till utdata. Om din effekt bara analyserar bildrutor anger du detta till true så att systemet kopierar bildrutor från indata till utdata.

public bool IsReadOnly
{
    get => false;
}

Tip

När IsReadOnly är truekopierar systemet indataramen till utdataramen innan ProcessFrame anropas. Du kan fortfarande skriva till utdataramarna i ProcessFrame.

metoden SetEncodingProperties

Systemet anropar SetEncodingProperties för att tala om för din effekt vilka kodningsegenskaper videoströmmen har. Den här metoden innehåller också en referens till Direct3D-enheten för maskinvaruåtergivning.

private Windows.Media.MediaProperties.VideoEncodingProperties _encodingProperties;

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

SupportedEncodingProperties-egenskap

Systemet kontrollerar SupportedEncodingProperties för att avgöra vilka kodningsegenskaper som din effekt stöder.

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

Note

Om du returnerar en tom lista med VideoEncodingProperties-objekt använder systemet ARGB32-kodning som standard.

SupportedMemoryTypes-egenskap

Egenskapen SupportedMemoryTypes avgör om din effekt kommer åt videoramar i programvaruminnet eller GPU-minnet.

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

Om du returnerar MediaMemoryTypes.Cpuskickar systemet ramar som SoftwareBitmap-objekt . Om du returnerar MediaMemoryTypes.Gpuskickar systemet ramar som IDirect3DSurface-objekt .

Egenskapen TimeIndependent

Ange TimeIndependent till true om effekten inte kräver enhetlig tidsinställning. Detta gör att systemet kan optimera prestanda.

public bool TimeIndependent
{
    get => true;
}

SetProperties-metod

Med metoden SetProperties kan den anropande appen skicka konfigurationsparametrar till din effekt.

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-metod – programvarubearbetning

Metoden ProcessFrame är där effekten ändrar bilddata. Den här metoden anropas en gång per bildruta och tar emot ett ProcessVideoFrameContext-objekt med VideoFrame-objekt för indata och utdata.

Om du vill få åtkomst till de råa pixeldataene för SoftwareBitmap använder du COM-interoperabilitet. Lägg till följande gränssnittsdefinition i ditt effektnamnområde:

[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

Den här tekniken kommer åt en intern, ohanterad bildbuffert. Du måste konfigurera projektet för att tillåta osäker kod. I projektegenskaperna väljer du fliken Skapa och aktiverar Tillåt osäker kod.

I följande exempel dämpas varje bildpunkt i bildrutan med det konfigurerade toningsvärdet:

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

Maskinvarubearbetning med Win2D

För GPU-baserad bearbetning använder du Win2D i stället för programbitmappsmanipulering. När du använder maskinvarubearbetning:

  1. Lägg till Microsoft. NuGet-paketet Graphics.Win2D till ditt effektprojekt.
  2. Gå tillbaka MediaMemoryTypes.Gpu från SupportedMemoryTypes.
  3. Spara Direct3D-enhetsreferensen från SetEncodingProperties.
  4. I ProcessFrameskapar du en CanvasDevice från Direct3D-enheten och använder Win2D-ritningsåtgärder på utdataramens Direct3DSurface.

Note

För WinUI 3-projekt använder du paketet Microsoft.Graphics.Win2D snarare än det äldre paketet Win2D.uwp.

Lägg till effekten i en videoström

Lägg till videoeffekten i en MediaCapture-videoström :

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

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

Så här skickar du konfigurationsegenskaper:

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

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

Lägg till effekten i en mediekomposition

Lägg till videoeffekten i ett klipp i en MediaComposition:

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

mediaClip.VideoEffectDefinitions.Add(effectDefinition);