Benutzerdefinierte Videoeffekte

In diesem Artikel wird beschrieben, wie Sie eine Windows-Runtime Komponente erstellen, die die IBasicVideoEffect-Schnittstelle implementiert, um benutzerdefinierte Effekte für Videostreams zu erstellen. Sie können benutzerdefinierte Effekte mit "MediaCapture" und "MediaComposition" verwenden.

Note

Die IBasicVideoEffect-Schnittstelle ist eine Windows-Runtime-API im Namespace Windows.Media.Effects, und die Schnittstellenmitglieder sind dieselben, die Sie in UWP implementiert haben. WinUI 3-Desktop-Apps verfügen jedoch nicht über die Windows-Runtime Komponentenprojektvorlage, die UWP-Projekte verwendet haben. Stattdessen erstellen Sie den Effekt mithilfe einer C#/WinRT-Klassenbibliothek, und Sie müssen die Komponente explizit für Windows-Runtime Aktivierung registrieren, wie in diesem Artikel beschrieben.

Fügen Sie eine Windows-Runtime-Komponente für Ihren Videoeffekt hinzu.

WinUI 3-Desktop-Apps verwenden C#/WinRT, um Windows-Runtime Komponenten zu erstellen, anstelle der Nur-UWP-Windows-Runtime Komponentenprojektvorlage.

  1. Klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf Ihre Lösung, und wählen Sie"Neue Project> aus.

  2. Wählen Sie die Projektvorlage "Klassenbibliothek " aus. Nennen Sie das Projekt "VideoEffectComponent".

  3. Legen Sie in VideoEffectComponent.csproj das Zielframework so fest, dass sie Ihrer WinUI 3-App entspricht, und markieren Sie das Projekt als Windows-Runtime Komponente:

    <PropertyGroup>
        <TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
        <CsWinRTComponent>true</CsWinRTComponent>
    </PropertyGroup>
    
  4. Installieren Sie das neueste Microsoft.Windows.CsWinRT-NuGet-Paket im VideoEffectComponent-Projekt.

  5. Fügen Sie diesem Komponentenprojekt einen Projektverweis aus Ihrer WinUI 3-Haupt-App hinzu.

  6. Benennen Sie die Standardklassendatei in ExampleVideoEffect.cs um.

Weitere Informationen zum Erstellen von Komponenten auf diese Weise finden Sie unter Walkthrough – Create a C#/WinRT component.

Registrieren der Effektkomponente für die Aktivierung

VideoEffectDefinition aktiviert Ihren Effekt anhand seiner aktivierbaren Windows-Runtime-Klassen-ID (der vollständige Typname, den Sie an typeof(...).FullName übergeben). Sofern Sie diese Klassen-ID nicht registrieren, schlägt die Aktivierung zur Laufzeit mit einer Ausnahme "Klasse nicht registriert" fehl, auch wenn der Code kompiliert wird. Wie Sie die Klasse registrieren, hängt davon ab, ob Ihre App verpackt ist.

Verpackte Apps

Fügen Sie Package.appxmanifest einen <Extensions>-Eintrag hinzu, der den Effekt als prozessinterne aktivierbare Klasse deklariert, die von WinRT.Host.dll gehostet wird. Dies ist die Hostassembly, die C#/WinRT Ihrer Buildausgabe hinzufügt:

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

Note

ActivatableClassId muss genau mit dem Namespace-qualifizierten Klassennamen übereinstimmen, den Sie an VideoEffectDefinition übergeben.

Entpackte Apps

Nicht paketierte Apps haben kein Package.appxmanifest, daher registrieren Sie die aktivierbare Klasse stattdessen in einer Anwendungsmanifestdatei. Fügen Sie ihrem App-Projekt eine neue Textdatei namens YourApp.exe.manifest hinzu, legen Sie die Inhaltseigenschaft auf "True" fest, sodass sie in das Ausgabeverzeichnis kopiert wird, und fügen Sie die gleiche Klassenregistrierung in diesem Format hinzu:

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

Weitere Informationen zum Hosten und Registrieren von C#/WinRT-Komponenten finden Sie unter Verwaltete Komponentenhosting im C#/WinRT-GitHub-Repository.

Implementieren der IBasicVideoEffect-Schnittstelle mithilfe der Softwareverarbeitung

Ihr Videoeffekt muss alle Methoden und Eigenschaften der IBasicVideoEffect-Schnittstelle implementieren. In diesem Abschnitt wird eine Softwareverarbeitungsimplementierung gezeigt.

Klassendefinition und Namespaces

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

Die ExampleVideoEffect Klasse muss innerhalb des VideoEffectComponent hier gezeigten Namespace deklariert werden, da die typeof(VideoEffectComponent.ExampleVideoEffect).FullName Aufrufe weiter unten in diesem Artikel und der ActivatableClassId Wert in der Manifestregistrierung von diesem genauen namespacequalifizierten Namen abhängen. In den folgenden Abschnitten werden die einzelnen Schnittstellenelemente im Detail erläutert; die hier gezeigte ProcessFrame-Methode ist ein Platzhalter, der in Methode „ProcessFrame“ – Softwareverarbeitung durch die vollständige Pixelverarbeitungsimplementierung ersetzt wird.

Close-Methode

Das System ruft Close auf, wenn der Effekt beendet wird. Verwenden Sie diese Methode, um alle von Ihnen erstellten Ressourcen zu löschen.

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

DiscardQueuedFrames-Methode

Das System ruft DiscardQueuedFrames auf, wenn der Effekt zurückgesetzt werden soll. Verwenden Sie dies, um zuvor zwischengespeicherte Frames zu löschen.

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

IsReadOnly-Eigenschaft

Die IsReadOnly-Eigenschaft teilt dem System mit, ob Ihr Effekt in die Ausgabe schreibt. Wenn Ihr Effekt nur Frames analysiert, legen Sie dies so true fest, dass das System Frames von der Eingabe in die Ausgabe kopiert.

public bool IsReadOnly
{
    get => false;
}

Tip

Wenn IsReadOnlytrue ist, kopiert das System den Eingabe-Frame in den Ausgabe-Frame, bevor ProcessFrame aufgerufen wird. Sie können in ProcessFrame weiterhin in die Ausgabe-Frames schreiben.

SetEncodingProperties-Methode

Das System ruft SetEncodingProperties auf, um Ihren Effekt über die Kodierungseigenschaften des Videostreams zu informieren. Diese Methode stellt auch einen Verweis auf das Direct3D-Gerät für das Hardwarerendering bereit.

private Windows.Media.MediaProperties.VideoEncodingProperties _encodingProperties;

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

SupportedEncodingProperties-Eigenschaft

Das System überprüft SupportedEncodingProperties , um zu bestimmen, welche Codierungseigenschaften ihr Effekt unterstützt.

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

Note

Wenn Sie eine leere Liste von VideoEncodingProperties Objekten zurückgeben, wird standardmäßig die ARGB32-Codierung vom System verwendet.

SupportedMemoryTypes-Eigenschaft

Die SupportedMemoryTypes-Eigenschaft bestimmt, ob Der Effekt auf Videoframes im Softwarespeicher oder GPU-Speicher zugreift.

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

Wenn Sie MediaMemoryTypes.Cpu zurückgeben, übergibt das System Frames als SoftwareBitmap-Objekte. Wenn Sie MediaMemoryTypes.Gpu zurückgeben, übergibt das System Frames als IDirect3DSurface-Objekte.

TimeIndependent-Eigenschaft

Setzen Sie TimeIndependent auf true, wenn Ihr Effekt keine einheitliche Zeitsteuerung erfordert. Auf diese Weise kann das System die Leistung optimieren.

public bool TimeIndependent
{
    get => true;
}

SetProperties-Methode

Mit der SetProperties-Methode kann die aufrufende App Konfigurationsparameter an Ihren Effekt übergeben.

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-Methode – Softwareverarbeitung

Die ProcessFrame-Methode ist der Ort, an dem der Effekt die Bilddaten ändert. Diese Methode wird einmal pro Frame aufgerufen und empfängt ein ProcessVideoFrameContext-Objekt mit Eingabe- und Ausgabe-VideoFrame-Objekten .

Verwenden Sie COM-Interop, um auf die rohen Pixeldaten eines SoftwareBitmap zuzugreifen. Fügen Sie die folgende Schnittstellendefinition in Ihrem Effektnamespace hinzu:

[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

Diese Technik greift auf einen systemeigenen, nicht verwalteten Bildpuffer zu. Sie müssen Ihr Projekt so konfigurieren, dass unsicherer Code zulässig ist. Wählen Sie in den Projekteigenschaften die Registerkarte " Build " aus, und aktivieren Sie "Unsicheren Code zulassen".

Im folgenden Beispiel wird jedes Pixel im Bild um den konfigurierten Dimmwert gedimmt:

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

Hardwareverarbeitung mit Win2D

Verwenden Sie für die GPU-basierte Verarbeitung Win2D anstelle der Softwarebitmap-Manipulation. Bei Verwendung der Hardwareverarbeitung:

  1. Fügen Sie das Microsoft.Graphics.Win2D-NuGet-Paket zu Ihrem Effektprojekt hinzu.
  2. MediaMemoryTypes.Gpu aus SupportedMemoryTypes zurückgeben.
  3. Speichern Sie den Direct3D-Geräteverweis von SetEncodingProperties.
  4. Erstellen Sie in ProcessFrame ein CanvasDevice aus dem Direct3D-Gerät, und verwenden Sie Win2D-Zeichenvorgänge für Direct3DSurface des Ausgabe-Frames.

Note

Verwenden Sie für WinUI 3-Projekte die Microsoft. Graphics.Win2D-Paket anstelle des älteren Win2D.uwp-Pakets.

Hinzufügen des Effekts zu einem Videostream

Hinzufügen des Videoeffekts zu einem MediaCapture-Videostream :

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

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

So übergeben Sie Konfigurationseigenschaften:

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

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

Hinzufügen des Effekts zu einer Medienkomposition

Fügen Sie den Videoeffekt zu einem Clip in einer MediaComposition hinzu:

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

mediaClip.VideoEffectDefinitions.Add(effectDefinition);