Efectos de vídeo personalizados

En este artículo se describe cómo crear un componente de Windows Runtime que implemente la interfaz IBasicVideoEffect para crear efectos personalizados para secuencias de vídeo. Puede usar efectos personalizados con MediaCapture y MediaComposition.

Note

La interfaz IBasicVideoEffect es una API de Windows Runtime del espacio de nombres Windows.Media.Effects, y los miembros de la interfaz son los mismos que ha implementado en UWP. Sin embargo, las aplicaciones de escritorio de WinUI 3 no tienen la plantilla de proyecto componente de Windows Runtime que usan los proyectos de UWP. En su lugar, debe crear el efecto con una biblioteca de clases de C#/WinRT, y debe registrar explícitamente el componente para la activación de Windows Runtime, como se describe en este artículo.

Agregar un componente de Windows Runtime para el efecto de vídeo

Las aplicaciones de escritorio de WinUI 3 usan C#/WinRT para crear componentes de Windows Runtime, en lugar de la plantilla de proyecto componente de Windows Runtime solo para UWP.

  1. Haga clic con el botón derecho en la solución en Explorador de soluciones y seleccione Agregar>nuevo Project.

  2. Seleccione la plantilla de proyecto Biblioteca de clases. Asigne al proyecto el nombre VideoEffectComponent.

  3. En VideoEffectComponent.csproj, establezca la plataforma de destino para que coincida con la aplicación WinUI 3 y marque el proyecto como un componente de Windows Runtime:

    <PropertyGroup>
        <TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
        <CsWinRTComponent>true</CsWinRTComponent>
    </PropertyGroup>
    
  4. Instale el paquete NuGet más reciente Microsoft.Windows.CsWinRT en el proyecto VideoEffectComponent.

  5. Agregue una referencia de proyecto desde la aplicación winUI 3 principal a este proyecto de componente.

  6. Cambie el nombre del archivo de clase predeterminado a ExampleVideoEffect.cs.

Para obtener más información sobre la creación de componentes de esta manera, vea Tutorial: Creación de un componente de C#/WinRT.

Registro del componente de efecto para la activación

VideoEffectDefinition activa tu efecto mediante su ID de clase activable de Windows Runtime (el nombre completo del tipo que pasas a typeof(...).FullName). A menos que registre ese identificador de clase, la activación produce un error en tiempo de ejecución con una excepción de "clase no registrada", aunque el código se compile. La forma de registrar la clase depende de si la aplicación está empaquetada.

Aplicaciones empaquetadas

Agregue una <Extensions> entrada a Package.appxmanifest que declare el efecto como una clase activable en proceso hospedada por WinRT.Host.dll, que es el ensamblado de hospedaje que C#/WinRT agrega a la salida de compilación:

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

Note

ActivatableClassId debe coincidir exactamente con el nombre de clase calificado por el espacio de nombres que se pasa a VideoEffectDefinition.

Aplicaciones sin empaquetar

Las aplicaciones sin empaquetar no tienen un Package.appxmanifest, por lo que la clase activable se registra en un archivo de manifiesto de la aplicación en su lugar. Agregue un nuevo archivo de texto denominado YourApp.exe.manifest al proyecto de aplicación, establezca su propiedad Content en True para que se copie en el directorio de salida y agregue el mismo registro de clase en este formato:

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

Para obtener más información sobre el hospedaje y el registro de componentes de C#/WinRT, consulte Hospedaje de componentes administrados en el repositorio de GitHub de C#/WinRT.

Implementación de la interfaz IBasicVideoEffect mediante el procesamiento de software

El efecto de vídeo debe implementar todos los métodos y propiedades de la interfaz IBasicVideoEffect . En esta sección se muestra una implementación de procesamiento de software.

Definición de clases y espacios de nombres

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

La clase ExampleVideoEffect debe declararse dentro del espacio de nombres VideoEffectComponent que se muestra aquí, ya que las llamadas a typeof(VideoEffectComponent.ExampleVideoEffect).FullName que se realizan más adelante en este artículo y el valor ActivatableClassId en el registro del manifiesto dependen de este nombre calificado por el espacio de nombres exacto. Las secciones siguientes describen en detalle cada miembro de la interfaz; el método ProcessFrame que se muestra aquí es un marcador de posición que se sustituye por la implementación completa del procesamiento de píxeles en método ProcessFrame — procesamiento por software.

Método Cerrar

El sistema llama a Close cuando el efecto se apaga. Use este método para eliminar los recursos que ha creado.

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

Método DiscardQueuedFrames

El sistema llama a DiscardQueuedFrames cuando se debe restablecer el efecto. Úselo para eliminar fotogramas previamente almacenados en caché.

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

Propiedad IsReadOnly

La propiedad IsReadOnly indica al sistema si tu efecto escribe en la salida. Si el efecto solo analiza los fotogramas, establézcalo en true para que el sistema copie los fotogramas de la entrada a la salida.

public bool IsReadOnly
{
    get => false;
}

Tip

Cuando IsReadOnly es true, el sistema copia el marco de entrada en el marco de salida antes de llamar a ProcessFrame . Aún así, puedes escribir en los fotogramas de salida en ProcessFrame.

Método SetEncodingProperties

El sistema llama a SetEncodingProperties para indicar a tu efecto las propiedades de codificación del flujo de vídeo. Este método también proporciona una referencia al dispositivo Direct3D para la representación de hardware.

private Windows.Media.MediaProperties.VideoEncodingProperties _encodingProperties;

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

Propiedad SupportedEncodingProperties

El sistema comprueba SupportedEncodingProperties para determinar qué propiedades de codificación admite el efecto.

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

Note

Si devuelve una lista vacía de VideoEncodingProperties objetos, el sistema tiene como valor predeterminado la codificación ARGB32.

Propiedad SupportedMemoryTypes

La propiedad SupportedMemoryTypes determina si el efecto accede a fotogramas de vídeo en memoria de software o memoria gpu.

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

Si devuelve MediaMemoryTypes.Cpu, el sistema pasa fotogramas como objetos SoftwareBitmap . Si devuelve MediaMemoryTypes.Gpu, el sistema pasa fotogramas como objetos IDirect3DSurface .

Propiedad TimeIndependent

Establezca TimeIndependent en true si el efecto no requiere una temporización uniforme. Esto permite al sistema optimizar el rendimiento.

public bool TimeIndependent
{
    get => true;
}

Método SetProperties

El método SetProperties permite a la aplicación que realiza la llamada pasar parámetros de configuración a tu efecto.

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

Método ProcessFrame: procesamiento de software

El método ProcessFrame es donde el efecto modifica los datos de la imagen. Este método se llama una vez por fotograma y recibe un objeto ProcessVideoFrameContext con objetos VideoFrame de entrada y salida.

Para acceder a los datos de píxeles sin procesar de un SoftwareBitmap, utiliza la interoperabilidad COM. Agregue la siguiente definición de interfaz en el espacio de nombres del efecto:

[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

Esta técnica tiene acceso a un búfer de imágenes nativo y no administrado. Debe configurar el proyecto para permitir código no seguro. En las propiedades del proyecto, seleccione la pestaña Compilar y habilite Permitir código no seguro.

En el ejemplo siguiente se atenua cada píxel del marco mediante el valor de atenuación configurado:

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

Procesamiento de hardware con Win2D

Para el procesamiento con GPU, utilice Win2D en lugar de la manipulación de mapas de bits por software. Al usar el procesamiento de hardware:

  1. Agregue el paquete NuGet Microsoft.Graphics.Win2D a su proyecto de efectos.
  2. Devuelve MediaMemoryTypes.Gpu de SupportedMemoryTypes.
  3. Guarde la referencia del dispositivo Direct3D de SetEncodingProperties.
  4. En ProcessFrame, crea un CanvasDevice a partir del dispositivo Direct3D y utiliza operaciones de dibujo de Win2D en el Direct3DSurface del fotograma de salida.

Note

Para los proyectos de WinUI 3, use el Microsoft. Paquete Graphics.Win2D en lugar del paquete win2D.uwp anterior.

Adición del efecto a una secuencia de vídeo

Agregue el efecto de vídeo a una secuencia de vídeo mediaCapture :

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

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

Para pasar las propiedades de configuración:

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

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

Agregar el efecto a una composición multimedia

Agregue el efecto de vídeo a un clip en mediaComposition:

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

mediaClip.VideoEffectDefinitions.Add(effectDefinition);