Partilhar via


Efeitos de vídeo personalizados

Este artigo descreve como criar um componente do Tempo de Execução do Windows que implementa a interface IBasicVideoEffect para criar efeitos personalizados para fluxos de vídeo. Os efeitos personalizados podem ser usados com várias APIs diferentes do Tempo de Execução do Windows, incluindo MediaCapture, que fornece acesso à câmera de um dispositivo, e MediaComposition, que permite criar composições complexas a partir de clipes de mídia.

Adicionar um efeito personalizado ao seu aplicativo

Um efeito de vídeo personalizado é definido em uma classe que implementa a interface IBasicVideoEffect . Essa classe não pode ser incluída diretamente no projeto do seu aplicativo. Em vez disso, você deve usar um componente do Tempo de Execução do Windows para hospedar sua classe de efeito de vídeo.

Adicionar um componente do Tempo de Execução do Windows ao efeito de vídeo

  1. No Microsoft Visual Studio, com sua solução aberta, vá para o menu Arquivo e selecione Adicionar> Novo Projeto.
  2. Selecione o tipo de projeto Componente do Windows Runtime (Universal Windows).
  3. Para este exemplo, nomeie o projeto VideoEffectComponent. Esse nome será referenciado no código posteriormente.
  4. Clique em OK.
  5. O modelo de projeto cria uma classe chamada Class1.cs. No Solution Explorer, clique com o botão direito do mouse no ícone de Class1.cs e escolha Renomear.
  6. Renomeie o arquivo para ExampleVideoEffect.cs. O Visual Studio mostrará um prompt perguntando se você deseja atualizar todas as referências para o novo nome. Clique em Sim.
  7. Abra ExampleVideoEffect.cs e atualize a definição de classe para implementar a interface IBasicVideoEffect .
public sealed class ExampleVideoEffect : IBasicVideoEffect

Você precisa incluir os namespaces a seguir em seu arquivo de classe effect para acessar todos os tipos usados nos exemplos deste artigo.

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

Implemente a interface IBasicVideoEffect usando o processamento de software

Seu efeito de vídeo deve implementar todos os métodos e propriedades da interface IBasicVideoEffect . Esta seção orienta você por uma implementação simples dessa interface que usa processamento de software.

Método Close

O sistema chamará o método Close em sua classe quando o efeito for desligado. Você deve usar esse método para descartar todos os recursos que criou. O argumento para o método é um MediaEffectClosedReason, que permite saber se o efeito foi fechado normalmente, se ocorreu um erro ou se o efeito não oferece suporte ao formato de codificação necessário.

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

Método DiscardQueuedFrames

O método DiscardQueuedFrames é chamado quando seu efeito deve ser redefinido. Um cenário típico para isso é se seu efeito armazena quadros processados anteriormente para usar no processamento do quadro atual. Quando esse método é chamado, você deve descartar o conjunto de quadros anteriores que você salvou. Este método pode ser usado para redefinir qualquer estado relacionado a quadros anteriores, não apenas quadros de vídeo acumulados.

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

Propriedade IsReadOnly

A propriedade IsReadOnly permite que o sistema saiba se o efeito gravará na saída do efeito. Se o aplicativo não modificar os quadros de vídeo (por exemplo, um efeito que executa apenas a análise dos quadros de vídeo), você deverá definir essa propriedade como true, o que fará com que o sistema copie com eficiência a entrada do quadro para a saída do quadro para você.

Dica

Quando a propriedade IsReadOnly é definida como true, o sistema copia o quadro de entrada para o quadro de saída antes que ProcessFrame seja chamado. Definir a propriedade IsReadOnly como true não restringe a gravação nos quadros de saída do efeito em ProcessFrame.

public bool IsReadOnly { get { return false; } }

MétodoSetEncodingProperties

O sistema chama SetEncodingProperties em seu efeito para permitir que você saiba as propriedades de codificação do fluxo de vídeo no qual o efeito está operando. Esse método também fornece uma referência ao dispositivo Direct3D usado para renderização de hardware. O uso desse dispositivo é mostrado no exemplo de processamento de hardware mais adiante neste artigo.

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

Propriedade SupportedEncodingProperties

O sistema verifica a propriedade SupportedEncodingProperties para determinar quais propriedades de codificação são suportadas pelo seu efeito. Observe que, se o consumidor do seu efeito não puder codificar o vídeo usando as propriedades especificadas, ele chamará Close no seu efeito e removerá seu efeito do pipeline de vídeo.

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

Observação

Se você retornar uma lista vazia de objetos VideoEncodingProperties de SupportedEncodingProperties, o sistema usará como padrão a codificação ARGB32.

 

Propriedade SupportedMemoryTypes

O sistema verifica a propriedade SupportedMemoryTypes para determinar se o efeito acessará quadros de vídeo na memória de software ou na memória de hardware (GPU). Se você retornar MediaMemoryTypes.Cpu, seu efeito receberá quadros de entrada e saída que contêm dados de imagem em objetos SoftwareBitmap. Se você retornar MediaMemoryTypes.Gpu, seu efeito receberá quadros de entrada e saída que contêm dados de imagem em objetos IDirect3DSurface.

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

Observação

Se você especificar MediaMemoryTypes.GpuAndCpu, o sistema usará a GPU ou a memória do sistema, o que for mais eficiente para o pipeline. Ao usar esse valor, você deve verificar no método ProcessFrame para ver se o SoftwareBitmap ou IDirect3DSurface passado para o método contém dados e, em seguida, processar o quadro adequadamente.

 

Propriedade TimeIndependent

A propriedade TimeIndependent permite que o sistema saiba se o efeito não requer tempo uniforme. Quando definido como verdadeiro, o sistema pode usar otimizações que melhoram o desempenho do efeito.

public bool TimeIndependent { get { return true; } }

Método SetProperties

O método SetProperties permite que o aplicativo que está usando seu efeito ajuste os parâmetros de efeito. As propriedades são passadas como um mapa IPropertySet de nomes e valores de propriedades.

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

Este exemplo simples escurecerá os pixels em cada quadro de vídeo de acordo com um valor especificado. Uma propriedade é declarada e TryGetValue é usado para obter o valor definido pelo aplicativo de chamada. Se nenhum valor foi definido, um valor padrão de .5 será usado.

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

Método ProcessFrame

O método ProcessFrame é onde o efeito modifica os dados de imagem do vídeo. O método é chamado uma vez por quadro e recebe um objeto ProcessVideoFrameContext. Esse objeto contém um objeto VideoFrame de entrada que contém o quadro de entrada a ser processado e um objeto VideoFrame de saída no qual você grava dados de imagem que serão passados para o restante do pipeline de vídeo. Cada um desses objetos VideoFrame tem uma propriedade SoftwareBitmap e uma propriedade Direct3DSurface, mas qual deles pode ser usado é determinado pelo valor retornado da propriedade SupportedMemoryTypes.

Este exemplo mostra uma implementação simples do método ProcessFrame usando processamento de software. Para obter mais informações sobre como trabalhar com objetos SoftwareBitmap, consulte Geração de imagens. Um exemplo de implementação de ProcessFrame usando processamento de hardware é mostrado posteriormente neste artigo.

Acessar o buffer de dados de um SoftwareBitmap requer interoperabilidade COM, portanto, você deve incluir o namespace System.Runtime.InteropServices em seu arquivo de classe de efeito.

using System.Runtime.InteropServices;

Adicione o código a seguir dentro do namespace para que o efeito importe a interface para acessar o buffer de imagem.

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

Observação

Como essa técnica acessa um buffer de imagem nativo e não gerenciado, você precisará configurar seu projeto para permitir código não seguro.

  1. No Gerenciador de Soluções, clique com o botão direito do mouse no projeto VideoEffectComponent e selecione Propriedades.
  2. Selecione a guia Compilar.
  3. Marque a caixa de seleção Permitir código não seguro.

 

Agora você pode adicionar a implementação do método ProcessFrame . Primeiro, esse método obtém um objeto BitmapBuffer dos bitmaps de software de entrada e saída. O quadro de saída é aberto para gravação e a entrada para leitura. Em seguida, um IMemoryBufferReference é obtido para cada buffer chamando CreateReference. Depois, o buffer de dados real é obtido convertendo os objetos IMemoryBufferReference como a interface de interoperabilidade COM definida acima, IMemoryByteAccess e, em seguida, chamando GetBuffer.

Agora que os buffers de dados foram obtidos, você pode ler a partir do buffer de entrada e gravar no buffer de saída. O layout do buffer é obtido chamando GetPlaneDescription, que fornece informações sobre a largura, o passo e o deslocamento inicial do buffer. Os bits por pixel são determinados pelas propriedades de codificação definidas anteriormente com o método SetEncodingProperties . As informações de formato do buffer são usadas para localizar o índice no buffer para cada pixel. O valor de pixel do buffer de origem é copiado para o buffer de destino, com os valores de cor sendo multiplicados pela propriedade FadeValue definida para esse efeito para esmaecê-los pela quantidade especificada.

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

Implementar a interface IBasicVideoEffect usando o processamento de hardware

Criar um efeito de vídeo personalizado usando o processamento de hardware (GPU) é quase idêntico ao uso do processamento de software, conforme descrito acima. Esta seção mostrará as poucas diferenças em um efeito que usa processamento de hardware. Este exemplo usa a API do Tempo de Execução do Windows Win2D. Para obter mais informações sobre como usar o Win2D, consulte a documentação do Win2D.

Use as etapas a seguir para adicionar o pacote NuGet do Win2D ao projeto que você criou, conforme descrito na seção Adicionar um efeito personalizado ao seu aplicativo no início deste artigo.

Para adicionar o pacote NuGet do Win2D ao seu projeto de efeito

  1. No Gerenciador de Soluções, clique com o botão direito do mouse no projeto VideoEffectComponent e selecione Gerenciar Pacotes NuGet.
  2. Na parte superior da janela, selecione a guia Procurar .
  3. Na caixa de pesquisa, insira Win2D.
  4. Selecione Win2D.uwp e, em seguida, selecione Instalar no painel direito.
  5. A caixa de diálogo Revisar alterações mostra o pacote a ser instalado. Clique em OK.
  6. Aceite a licença do pacote.

Além dos namespaces incluídos na configuração básica do projeto, você precisará incluir os seguintes namespaces fornecidos pelo Win2D.

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

Como esse efeito usará a memória da GPU para operar nos dados da imagem, você deve retornar MediaMemoryTypes.Gpu da propriedade SupportedMemoryTypes.

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

Defina as propriedades de codificação às quais seu efeito dará suporte com a propriedade SupportedEncodingProperties. Ao trabalhar com Win2D, você deve usar a codificação ARGB32.

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

Use o método SetEncodingProperties para criar um novo objeto Win2D CanvasDevice do IDirect3DDevice passado para o método.

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

A implementação SetProperties é idêntica ao exemplo de processamento de software anterior. Este exemplo usa uma propriedade BlurAmount para configurar um efeito de desfoque 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;
    }
}

A última etapa é implementar o método ProcessFrame que realmente processa os dados da imagem.

Usando APIs Win2D, um CanvasBitmap é criado a partir da propriedade Direct3DSurface do quadro de entrada. Um CanvasRenderTarget é criado a partir do Direct3DSurface do quadro de saída e um CanvasDrawingSession é criado a partir desse destino de renderização. Um novo Win2D GaussianBlurEffect é inicializado, usando a propriedade BlurAmount que nosso efeito expõe por meio de SetProperties. Por fim, o método CanvasDrawingSession.DrawImage é chamado para desenhar o bitmap de entrada para o destino de renderização usando o efeito de desfoque.

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

    }
}

Adicionando um efeito personalizado ao seu aplicativo

Para usar o efeito de vídeo do seu aplicativo, você deve adicionar uma referência ao projeto de efeito ao seu aplicativo.

  1. No Solution Explorer, no projeto do app, clique com o botão direito do mouse em Referências e selecione Adicionar Referência.
  2. Expanda a guia Projetos , selecione Solução e marque a caixa de seleção do nome do projeto de efeito. Para este exemplo, o nome é VideoEffectComponent.
  3. Clique em OK.

Adicionar seu efeito personalizado a um fluxo de vídeo da câmera

Você pode configurar um fluxo de visualização simples da câmera seguindo as etapas no artigo Acesso simples à visualização da câmera. Seguir essas etapas fornecerá um objeto MediaCapture inicializado que é usado para acessar o fluxo de vídeo da câmera.

Para adicionar seu efeito de vídeo personalizado a um fluxo de câmera, primeiro crie um novo objeto VideoEffectDefinition , passando o namespace e o nome da classe para o efeito. Em seguida, chame o método AddVideoEffect do objeto MediaCapture para adicionar seu efeito ao fluxo especificado. Este exemplo usa o valor MediaStreamType.VideoPreview para especificar que o efeito deve ser adicionado ao fluxo de visualização. Se o aplicativo der suporte à captura de vídeo, você também poderá usar MediaStreamType.VideoRecord para adicionar o efeito ao fluxo de captura. AddVideoEffect retorna um objeto IMediaExtension que representa seu efeito personalizado. Você pode usar o método SetProperties para definir a configuração do efeito.

Depois que o efeito for adicionado, StartPreviewAsync será chamado para iniciar o fluxo de visualização.

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

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

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

await mediaCapture.StartPreviewAsync();

Adicionar seu efeito personalizado a um clipe em um MediaComposition

Para obter orientações gerais sobre a criação de composições de mídia a partir de videoclipes, consulte Composições e edição de mídia. O snippet de código a seguir mostra a criação de uma composição de mídia simples que usa um efeito de vídeo personalizado. Um objeto MediaClip é criado chamando CreateFromFileAsync, passando um arquivo de vídeo que foi selecionado pelo usuário com um FileOpenPicker, e o clipe é adicionado a um novo MediaComposition. Em seguida, um novo objeto VideoEffectDefinition é criado, passando o namespace e o nome da classe do efeito para o construtor. Por fim, a definição de efeito é adicionada à coleção VideoEffectDefinitions do objeto 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);