Effetti video personalizzati

Questo articolo descrive come creare un componente Windows Runtime che implementa l'interfaccia IBasicVideoEffect per consentire la creazione di effetti personalizzati per i flussi video. Gli effetti personalizzati possono essere usati con diverse API di Windows Runtime, tra cui MediaCapture, che consente l'accesso alla fotocamera di un dispositivo e MediaComposition, che consente di creare composizioni complesse da clip multimediali.

Aggiungere un effetto personalizzato all'app

Un effetto video personalizzato viene definito in una classe che implementa l'interfaccia IBasicVideoEffect. Questa classe non può essere inclusa direttamente nel progetto dell'app. Si deve invece usare un componente Windows Runtime per ospitare la classe di effetti video.

Aggiungere un componente Windows Runtime per l'effetto video

  1. In Microsoft Visual Studio, con la soluzione aperta, passare al menu File e selezionare Aggiungi>Nuovo progetto.
  2. Selezionare il tipo di progetto Componente Windows Runtime (Windows universale).
  3. Per questo esempio assegnare al progetto il nome VideoEffectComponent. A questo nome verrà fatto riferimento nel codice in un secondo momento.
  4. Fare clic su OK.
  5. Il modello di progetto crea una classe denominata Class1.cs. In Esplora soluzioni fare clic con il tasto destro del mouse sull'icona Class1.cs e selezionare Rinomina.
  6. Assegnare il nome ExampleVideoEffect.cs. Visual Studio visualizzerà una richiesta che chiede se si desidera aggiornare tutti i riferimenti al nuovo nome. Fare clic su .
  7. Aprire ExampleVideoEffect.cs e aggiornare la definizione della classe per implementare l'interfaccia IBasicVideoEffect.
public sealed class ExampleVideoEffect : IBasicVideoEffect

È necessario includere gli spazi dei nomi seguenti nel file di classe dell'effetto per accedere a tutti i tipi usati negli esempi in questo articolo.

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

Implementare l'interfaccia IBasicVideoEffect usando l'elaborazione software

L'effetto video deve implementare tutti i metodi e le proprietà dell'interfaccia IBasicVideoEffect. Questa sezione illustra una semplice implementazione di questa interfaccia che usa l'elaborazione software.

Metodo Close

Il sistema chiamerà il metodo Close nella classe quando l'effetto deve essere arrestato. È consigliabile usare questo metodo per eliminare le risorse create. L'argomento del metodo è un oggetto MediaEffectClosedReason che consente di sapere se l'effetto è stato chiuso normalmente, se si è verificato un errore o se l'effetto non supporta il formato di codifica richiesto.

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

Metodo DiscardQueuedFrames

Il metodo DiscardQueuedFrames viene chiamato quando l'effetto deve essere reimpostato. Uno scenario tipico è se l'effetto archivia fotogrammi elaborati in precedenza da usare nell'elaborazione del frame corrente. Quando viene chiamato questo metodo, è necessario eliminare il set di fotogrammi precedenti salvati. Questo metodo può essere usato per reimpostare qualsiasi stato correlato ai fotogrammi precedenti, non solo ai fotogrammi video accumulati.

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

proprietà IsReadOnly

La proprietà psReadOnly consente al sistema di sapere se l'effetto scriverà nell'output dell'effetto. Se l'app non modifica i fotogrammi video (ad esempio, un effetto che esegue solo l'analisi dei fotogrammi video), si deve impostare questa proprietà su true, in modo che il sistema possa copiare in modo efficiente l'input del fotogramma nell'output del fotogramma.

Suggerimento

Quando IsReadOnly è impostata su true, il sistema copia il frame di input nel frame di output prima che venga chiamato ProcessFrame. L'impostazione della proprietà psReadOnly su true non impedisce la scrittura nei fotogrammi di output dell'effetto in ProcessFrame.

public bool IsReadOnly { get { return false; } }

Metodo SetEncodingProperties

Il sistema chiama SetEncodingProperties sull'effetto per informare le proprietà di codifica per il flusso video su cui opera l'effetto. Questo metodo fornisce anche un riferimento al dispositivo Direct3D usato per il rendering hardware. L'utilizzo di questo dispositivo è illustrato nell'esempio di elaborazione hardware più avanti in questo articolo.

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

SupportedEncodingProperties - proprietà

Il sistema controlla la proprietà SupportedEncodingProperties per determinare quali proprietà di codifica sono supportate dall'effetto. Si noti che se il consumer dell'effetto non può codificare il video usando le proprietà specificate, chiamerà Close sull'effetto e rimuoverà l'effetto dalla pipeline 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>();
    }
}

Nota

Se si restituisce un elenco vuoto di oggetti VideoEncodingProperties da SupportedEncodingProperties, per impostazione predefinita il sistema verrà impostato sulla codifica ARGB32.

 

SupportedMemoryTypes - proprietà

Il sistema controlla la proprietà SupportedMemoryTypes per determinare se l'effetto accederà ai fotogrammi video nella memoria software o nella memoria hardware (GPU). Se si restituisce MediaMemoryTypes.Cpu, l'effetto verrà passato ai fotogrammi di input e di output che contengono dati di immagine negli oggetti SoftwareBitmap. Se si restituisce MediaMemoryTypes.Gpu,l'effetto verrà passato ai fotogrammi di input e di output che contengono dati di immagine negli oggetti IDirect3DSurface.

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

Nota

Se si specifica MediaMemoryTypes.GpuAndCpu, il sistema userà GPU o memoria di sistema, a qualsiasi livello di efficienza per la pipeline. Quando si usa questo valore, è necessario controllare il metodo ProcessFrame per verificare se SoftwareBitmap or IDirect3DSurface viene passato nel metodo contiene dati e quindi elaborare il frame di conseguenza.

 

TimeIndependent - proprietà

La proprietà TimeIndependent consente al sistema di sapere se l'effetto non richiede tempi uniformi. Se impostato su true, il sistema può usare ottimizzazioni che migliorano le prestazioni dell'effetto.

public bool TimeIndependent { get { return true; } }

Metodo SetProperties

Il metodo SetProperties consente all'app che usa l'effetto di regolare i parametri dell'effetto. Le proprietà vengono passate come mappa IPropertySet di nomi e valori delle proprietà.

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

Questo semplice esempio indebolirà i pixel in ogni fotogramma video in base a un valore specificato. Una proprietà viene dichiarata e TryGetValue viene usata per ottenere il valore impostato dall'app chiamante. Se non è stato impostato alcun valore, viene usato un valore predefinito pari a .5.

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

Metodo ProcessFrame

Il metodo ProcessFrame è il percorso in cui l'effetto modifica i dati dell'immagine del video. Il metodo viene chiamato una volta per fotogramma e viene passato un oggetto ProcessVideoFrameContext. Questo oggetto contiene un VideoFrame di input che contiene il frame in ingresso da elaborare e un oggetto VideoFrame di output in cui si scrivono dati immagine che verranno passati al resto della pipeline video. Ciascuno di questi oggetti VideoFrame ha una proprietà SoftwareBitmap e una proprietà Direct3DSurface, ma quale di queste proprietà può essere utilizzata è determinata dal valore restituito dalla proprietà SupportedMemoryTypes.

Questo esempio illustra una semplice implementazione del metodo ProcessFrame usando l'elaborazione software. Per altre informazioni sull'uso degli oggetti SoftwareBitmap, vedere Imaging. Un esempio di implementazione di ProcessFrame che usa l'elaborazione hardware è illustrato più avanti in questo articolo.

Per accedere al buffer di dati di un SoftwareBitmap è necessaria l'interoperabilità COM, pertanto è necessario includere lo spazio dei nomi System.Runtime.InteropServices nel file della classe degli effetti.

using System.Runtime.InteropServices;

Aggiungere il codice seguente all'interno dello spazio dei nomi per l'effetto per importare l'interfaccia per l'accesso al buffer di immagini.

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

Nota

Poiché questa tecnica accede a un buffer di immagini non gestito nativo, è necessario configurare il progetto per consentire il codice non sicuro.

  1. In Esplora soluzioni fare clic con il tasto destro del mouse sul progetto VideoEffectComponent, quindi selezionare Proprietà.
  2. Seleziona la scheda Crea.
  3. Selezionare la casella di spunta Consenti codice non sicuro.

 

È ora possibile aggiungere l'implementazione del metodo ProcessFrame. In primo luogo, questo metodo ottiene un oggetto BitmapBuffer dalle bitmap software di input e output. Si noti che il frame di output viene aperto per la scrittura e l'input per la lettura. Successivamente, viene ottenuto un oggetto IMemoryBufferReference per ogni buffer chiamando CreateReference. Il buffer di dati effettivo viene quindi ottenuto eseguendo il cast degli oggetti IMemoryBufferReference come interfaccia di interoperabilità COM definita in precedenza, IMemoryByteAccess e chiamare GetBuffer.

Dopo aver ottenuto i buffer di dati, è possibile leggere dal buffer di input e scrivere nel buffer di output. Il layout del buffer viene ottenuto chiamando GetPlaneDescription, che fornisce informazioni sulla larghezza, lo stride e l'offset iniziale del buffer. I bit per pixel sono determinati dalle proprietà di codifica impostate in precedenza con il metodo SetEncodingProperties. Le informazioni sul formato del buffer vengono usate per trovare l'indice nel buffer per ogni pixel. Il valore pixel del buffer di origine viene copiato nel buffer di destinazione, con i valori di colore moltiplicati per la proprietà FadeValue definita per questo effetto per attenuarli in base alla quantità specificata.

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

Implementare l'interfaccia IBasicVideoEffect usando l'elaborazione hardware

La creazione di un effetto video personalizzato usando l'elaborazione hardware (GPU) è quasi identica all'uso dell'elaborazione software come descritto in precedenza. Questa sezione mostrerà le poche differenze in un effetto che usa l'elaborazione hardware. questo esempio usa Win2D Windows Runtime API. Per altre informazioni sull'uso di Win2D, vedere la documentazione di Win2D.

Usare la procedura seguente per aggiungere il pacchetto NuGet Win2D al progetto creato come descritto nella sezione Aggiungere un effetto personalizzato alla'app all'inizio di questo articolo.

Per aggiungere il pacchetto NuGet Win2D al progetto dell'effetto

  1. In Esplora soluzioni fare clic con il tasto destro del mouse sul progetto VideoEffectComponent e selezionare Gestisci pacchetti NuGet.
  2. Nella parte superiore della finestra, selezionare la scheda Browse.
  3. Nella casella di ricerca immettere Win2D.
  4. Selezionare Win2D.uwp e quindi selezionare Installa nel riquadro a destra.
  5. La finestra di dialogo Rivedi modifiche mostra il pacchetto da installare. Fare clic su OK.
  6. Accettare la licenza del pacchetto.

Oltre agli spazi dei nomi inclusi nella configurazione di base del progetto, si dovranno includere gli spazi dei nomi seguenti forniti da Win2D.

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

Poiché questo effetto userà la memoria GPU per operare sui dati dell'immagine, è necessario restituire MediaMemoryTypes.Gpu dalla proprietà SupportedMemoryTypes.

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

Impostare le proprietà di codifica supportate dall'effetto con la proprietà SupportedEncodingProperties. Quando si usa Win2D, è necessario usare la codifica ARGB32.

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

Usare il metodo SetEncodingProperties per creare un nuovo oggetto Win2D CanvasDevice da IDirect3DDevice passato nel metodo.

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

L'implementazione di SetProperties è identica all'esempio di elaborazione software precedente. Questo esempio usa una proprietà BlurAmount per configurare un effetto sfocatura 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;
    }
}

L'ultimo passaggio consiste nell'implementare il metodo ProcessFrame che elabora effettivamente i dati dell'immagine.

Usando le API Win2D, viene creato un oggetto CanvasBitmap dalla proprietà Direct3DSurface del frame di input. Un oggetto CanvasRenderTarget viene creato da Direct3DSurface del frame di output e viene creato un oggetto CanvasDrawingSession da questa destinazione di rendering. Viene inizializzata una nuova proprietà GaussianBlurEffect Win2D usando la proprietà BlurAmount esposta tramite SetProperties. Infine, viene chiamato il metodo CanvasDrawingSession.DrawImage per disegnare la bitmap di input nella destinazione di rendering usando l'effetto sfocatura.

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

    }
}

Aggiunta dell'effetto personalizzato all'app

Per usare l'effetto video dall'app, si deve aggiungere un riferimento al progetto di effetto all'app.

  1. In Esplora soluzioni fare clic con il tasto destro del mouse su Riferimenti e selezionare Aggiungi riferimento.
  2. Espandere la scheda Progetti, selezionare Soluzione e quindi selezionare la casella di controllo relativa al nome del progetto effetto. Nell'esempio, il nome file dello script è VideoEffectComponent.
  3. Fare clic su OK.

Aggiungere l'effetto personalizzato a un flusso video della fotocamera

È possibile configurare un semplice flusso di anteprima dalla fotocamera seguendo la procedura descritta nell'articolo Accesso in anteprima della fotocamera semplice. Seguendo questi passaggi verrà fornito un oggetto MediaCapture inizializzato che viene usato per accedere al flusso video della fotocamera.

Per aggiungere l'effetto video personalizzato a un flusso della fotocamera, creare prima di tutto un nuovo oggetto VideoEffectDefinition, passando lo spazio dei nomi e il nome della classe per l'effetto. Chiamare quindi il metodo AddVideoEffect dell'oggetto MediaCapture per aggiungere l'effetto al flusso specificato. In questo esempio viene usato il valore MediaStreamType.VideoPreview per specificare che l'effetto deve essere aggiunto al flusso di anteprima. Se l'app supporta l'acquisizione video, puoi anche usare MediaStreamType.VideoRecord per aggiungere l'effetto al flusso di acquisizione. AddVideoEffect restituisce un oggetto IMediaExtension che rappresenta l'effetto personalizzato. È possibile utilizzare il metodo SetProperties per impostare la configurazione per l'effetto.

Dopo aver aggiunto l'effetto, StartPreviewAsync viene chiamato per avviare il flusso di anteprima.

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

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

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

await mediaCapture.StartPreviewAsync();

Aggiungere l'effetto personalizzato a un clip in MediaComposition

Per indicazioni generali per la creazione di composizioni multimediali da clip video, vedere Composizioni multimediali e modifica. Il frammento di codice seguente mostra la creazione di una composizione multimediale semplice che usa un effetto video personalizzato. Un oggetto MediaClip viene creato chiamando CreateFromFileAsync, passando un file video selezionato dall'utente con un FileOpenPicker e la clip viene aggiunta a una nuova MediaComposition. Viene quindi creato un nuovooggetto VideoEffectDefinition, passando lo spazio dei nomi e il nome della classe per l'effetto al costruttore. Infine, la definizione dell'effetto viene aggiunta all'insieme VideoEffectDefinitions dell'oggetto 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);