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. Los efectos personalizados se pueden usar con varias API de Windows Runtime diferentes, como MediaCapture, que proporciona acceso a la cámara de un dispositivo y MediaComposition, que permite crear composiciones complejas fuera de clips multimedia.
Adición de un efecto personalizado a la aplicación
Un efecto de vídeo personalizado se define en una clase que implementa la interfaz IBasicVideoEffect. Esta clase no se puede incluir directamente en el proyecto de la aplicación. En su lugar, debes usar un componente de Windows Runtime para hospedar la clase de efectos de vídeo.
Agregar un componente de Windows Runtime para el efecto de vídeo
- En Microsoft Visual Studio, con la solución abierta, vaya al menú Archivo y seleccione Agregar->Nuevo proyecto.
- Seleccione el tipo de proyecto Componente de Windows Runtime (Universal Windows).
- En este ejemplo, asigne al proyecto el nombre VideoEffectComponent. Se hará referencia a este nombre en el código más adelante.
- Haga clic en OK.
- La plantilla de proyecto crea una clase denominada Class1.cs. En el Explorador de soluciones, haga clic con el botón derecho en el icono de Class1.cs y seleccione Cambiar nombre.
- Cambie el nombre del archivo a ExampleVideoEffect.cs. Visual Studio mostrará un mensaje que le preguntará si desea actualizar todas las referencias al nuevo nombre. Haga clic en Sí.
- Abra ExampleVideoEffect.cs y actualice la definición de clase para implementar la interfaz IBasicVideoEffect.
public sealed class ExampleVideoEffect : IBasicVideoEffect
Debe incluir los siguientes espacios de nombres en el archivo de clase del efecto para tener acceso a todos los tipos usados en los ejemplos de este artículo.
using Windows.Media.Effects;
using Windows.Media.MediaProperties;
using Windows.Foundation.Collections;
using Windows.Graphics.DirectX.Direct3D11;
using Windows.Graphics.Imaging;
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. Esta sección le guiará a través de una implementación sencilla de esta interfaz que usa el procesamiento de software.
Método Close
El sistema llamará al método Close en la clase cuando se cierre el efecto. Debe usar este método para eliminar los recursos que haya creado. El argumento del método es un MediaEffectClosedReason que le permite saber si el efecto se cerró normalmente, si se produjo un error o si el efecto no admite el formato de codificación necesario.
public void Close(MediaEffectClosedReason reason)
{
// Dispose of effect resources
}
Método DiscardQueuedFrames
Se llama al método DiscardQueuedFrames cuando se debe restablecer el efecto. Un escenario típico para esto es si el efecto almacena fotogramas procesados previamente para usarlos en el procesamiento del fotograma actual. Cuando se llama a este método, debe eliminar el conjunto de fotogramas anteriores que guardó. Este método se puede usar para restablecer cualquier estado relacionado con fotogramas anteriores, no solo fotogramas de vídeo acumulados.
private int frameCount;
public void DiscardQueuedFrames()
{
frameCount = 0;
}
IsReadOnly, propiedad
La propiedad IsReadOnly permite al sistema saber si el efecto escribirá en la salida del efecto. Si la aplicación no modifica los fotogramas de vídeo (por ejemplo, un efecto que solo realiza el análisis de los fotogramas de vídeo), debes establecer esta propiedad en true, lo que hará que el sistema copie eficazmente la entrada de fotograma en la salida del fotograma.
Sugerencia
Cuando la propiedad IsReadOnly se establece en true, el sistema copia el marco de entrada en el marco de salida antes de llamar a ProcessFrame. Establecer la propiedad IsReadOnly en true no le impide escribir en los marcos de salida del efecto en ProcessFrame.
public bool IsReadOnly { get { return false; } }
Método SetEncodingProperties
El sistema llama a SetEncodingProperties en el efecto para informarle de las propiedades de codificación de la secuencia de vídeo en la que funciona el efecto. Este método también proporciona una referencia al dispositivo Direct3D que se usa para la representación de hardware. El uso de este dispositivo se muestra en el ejemplo de procesamiento de hardware más adelante en este artículo.
private VideoEncodingProperties encodingProperties;
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
this.encodingProperties = encodingProperties;
}
Propiedad SupportedEncodingProperties
El sistema comprueba la propiedad SupportedEncodingProperties para determinar qué propiedades de codificación son compatibles con el efecto. Tenga en cuenta que si el consumidor del efecto no puede codificar vídeo con las propiedades que especifique, llamará a Close en el efecto y quitará el efecto de la canalización 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>();
}
}
Nota:
Si devuelve una lista vacía de objetos VideoEncodingProperties de SupportedEncodingProperties, el sistema tendrá como valor predeterminado la codificación ARGB32.
Propiedad SupportedMemoryTypes
El sistema comprueba la propiedad SupportedMemoryTypes para determinar si el efecto tendrá acceso a fotogramas de vídeo en memoria de software o en memoria de hardware (GPU). Si devuelve MediaMemoryTypes.Cpu, se pasarán los fotogramas de entrada y salida que contienen datos de imagen en objetos SoftwareBitmap. Si devuelve MediaMemoryTypes.Gpu, se pasarán los fotogramas de entrada y salida que contienen datos de imagen en objetos IDirect3DSurface.
public MediaMemoryTypes SupportedMemoryTypes { get { return MediaMemoryTypes.Cpu; } }
Nota:
Si especifica MediaMemoryTypes.GpuAndCpu, el sistema usará gpu o memoria del sistema, lo que sea más eficaz para la canalización. Al usar este valor, debe proteger el método ProcessFrame para ver si SoftwareBitmap o IDirect3DSurface pasado al método contiene datos y, a continuación, procesar el marco en consecuencia.
Propiedad TimeIndependent
La propiedad TimeIndependent permite al sistema saber si el efecto no requiere un tiempo uniforme. Cuando se establece en true, el sistema puede usar optimizaciones que mejoran el rendimiento del efecto.
public bool TimeIndependent { get { return true; } }
Método SetProperties
El método SetProperties permite que la aplicación que usa el efecto ajuste los parámetros de efecto. Las propiedades se pasan como un mapa IPropertySet de nombres y valores de propiedad.
private IPropertySet configuration;
public void SetProperties(IPropertySet configuration)
{
this.configuration = configuration;
}
En este ejemplo sencillo se atenuarán los píxeles de cada fotograma de vídeo según un valor especificado. Se declara una propiedad y TryGetValue se usa para obtener el valor establecido por la aplicación que realiza la llamada. Si no se ha establecido ningún valor, se utiliza un valor predeterminado de .5.
public double FadeValue
{
get
{
object val;
if (configuration != null && configuration.TryGetValue("FadeValue", out val))
{
return (double)val;
}
return .5;
}
}
Método ProcessFrame
El método ProcessFrame es donde el efecto modifica los datos de imagen del vídeo. El método se llama una vez por fotograma y se pasa un objeto ProcessVideoFrameContext. Este objeto contiene un objeto VideoFrame de entrada que contiene el marco entrante que se va a procesar y un objeto VideoFrame de salida en el que se escriben datos de imagen que se pasarán al resto de la canalización de vídeo. Cada uno de estos objetos VideoFrame tiene una propiedad SoftwareBitmap y una propiedad Direct3DSurface, pero cuál de estos objetos se puede usar viene determinado por el valor devuelto por la propiedad SupportedMemoryTypes.
En este ejemplo se muestra una implementación sencilla del método ProcessFrame mediante el procesamiento de software. Para obtener más información sobre cómo trabajar con objetos SoftwareBitmap, vea Imaging. Una implementación de ProcessFrame de ejemplo mediante el procesamiento de hardware se muestra más adelante en este artículo.
El acceso al búfer de datos de un SoftwareBitmap requiere interoperabilidad COM, por lo que debe incluir el espacio de nombres System.Runtime.InteropServices en el archivo de clase de efecto.
using System.Runtime.InteropServices;
Agregue el código siguiente dentro del espacio de nombres para que su efecto importe la interfaz para acceder al búfer de imágenes.
[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
void GetBuffer(out byte* buffer, out uint capacity);
}
Nota:
Dado que esta técnica tiene acceso a un búfer de imágenes nativo y no administrado, deberá configurar el proyecto para permitir código no seguro.
- En Explorador de soluciones, haga clic con el botón derecho en el proyecto VideoEffectComponent y seleccione Propiedades.
- Seleccione la pestaña Crear.
- Active la casilla Permitir código no seguro.
Ahora puede agregar la implementación del método ProcessFrame . En primer lugar, este método obtiene un objeto BitmapBuffer de los mapas de bits de software de entrada y salida. Tenga en cuenta que el marco de salida se abre para escribir y la entrada para leer. A continuación, se obtiene una IMemoryBufferReference para cada búfer mediante una llamada a CreateReference. A continuación, el búfer de datos real se obtiene mediante la conversión de los objetos IMemoryBufferReference como la interfaz de interoperabilidad COM definida anteriormente, IMemoryByteAccess y, a continuación, llamando a GetBuffer.
Ahora que se han obtenido los búferes de datos, puede leer del búfer de entrada y escribir en el búfer de salida. El diseño del búfer se obtiene llamando a GetPlaneDescription, que proporciona información sobre el ancho, el paso y el desplazamiento inicial del búfer. Las propiedades de codificación establecidas anteriormente con el método SetEncodingProperties determinan los bits por píxel. La información de formato del búfer se usa para buscar el índice en el búfer para cada píxel. El valor de píxel del búfer de origen se copia en el búfer de destino, con los valores de color multiplicados por la propiedad FadeValue definida para este efecto para atenuarlos por la cantidad 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];
}
}
}
}
}
Implementación de la interfaz IBasicVideoEffect mediante el procesamiento de hardware
La creación de un efecto de vídeo personalizado mediante el procesamiento de hardware (GPU) es casi idéntico al uso del procesamiento de software, como se ha descrito anteriormente. En esta sección se muestran las pocas diferencias en un efecto que usa el procesamiento de hardware. En este ejemplo se usa la API de Windows Runtime de Win2D. Para obtener más información sobre el uso de Win2D, consulte la documentación de Win2D.
Siga estos pasos para agregar el paquete NuGet Win2D al proyecto que creó tal y como se describe en la sección Agregar un efecto personalizado a la aplicación al principio de este artículo.
Para agregar el paquete NuGet Win2D al proyecto de efecto
- En Explorador de soluciones, haga clic con el botón derecho en el proyecto VideoEffectComponent y seleccione Administrar paquetes NuGet.
- En la parte superior de la ventana, seleccione la pestaña Examinar .
- En el cuadro de búsqueda, escriba Win2D.
- Seleccione Win2D.uwp y, a continuación, seleccione Instalar en el panel derecho.
- El cuadro de diálogo Revisar cambios muestra el paquete que se va a instalar. Haga clic en OK.
- Acepte la licencia del paquete.
Además de los espacios de nombres incluidos en la configuración básica del proyecto, deberá incluir los siguientes espacios de nombres proporcionados por Win2D.
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas;
Dado que este efecto usará la memoria de GPU para funcionar en los datos de la imagen, debe devolver MediaMemoryTypes.Gpu desde la propiedad SupportedMemoryTypes.
public MediaMemoryTypes SupportedMemoryTypes { get { return MediaMemoryTypes.Gpu; } }
Establezca las propiedades de codificación que el efecto admitirá con la propiedad SupportedEncodingProperties. Al trabajar con Win2D, debes usar la codificación ARGB32.
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties {
get
{
var encodingProperties = new VideoEncodingProperties();
encodingProperties.Subtype = "ARGB32";
return new List<VideoEncodingProperties>() { encodingProperties };
}
}
Use el método SetEncodingProperties para crear un nuevo objeto CanvasDevice win2D a partir del IDirect3DDevice pasado al método .
private CanvasDevice canvasDevice;
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
canvasDevice = CanvasDevice.CreateFromDirect3D11Device(device);
}
La implementación de SetProperties es idéntica al ejemplo de procesamiento de software anterior. En este ejemplo se usa una propiedad BlurAmount para configurar un efecto de desenfoque 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;
}
}
El último paso es implementar el método ProcessFrame que procesa realmente los datos de imagen.
Con las API de Win2D, se crea un CanvasBitmap a partir de la propiedad Direct3DSurface del marco de entrada. CanvasRenderTarget se crea a partir de Direct3DSurface del marco de salida y se crea un CanvasDrawingSession a partir de este destino de representación. Se inicializa un nuevo Win2D GaussianBlurEffect, utilizando la propiedad BlurAmount que expone nuestro efecto a través de SetProperties. Por último, se llama al método CanvasDrawingSession.DrawImage para dibujar el mapa de bits de entrada en el destino de representación mediante el efecto de desenfoque.
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);
}
}
Adición del efecto personalizado a la aplicación
Para usar el efecto de vídeo de la aplicación, debes agregar una referencia al proyecto de efecto a la aplicación.
- En el Explorador de soluciones, en su proyecto de aplicación, haga clic con el botón derecho en Referencias y seleccione Agregar referencia.
- Expanda la pestaña Proyectos, seleccione Solución y, a continuación, active la casilla del nombre del proyecto de efecto. En este ejemplo, el nombre es VideoEffectComponent.
- Haga clic en OK.
Adición del efecto personalizado a una secuencia de vídeo de cámara
Puede configurar una secuencia de vista previa simple desde la cámara siguiendo los pasos del artículo Acceso a la vista previa de la cámara simple. Siguiendo estos pasos le proporcionará un objeto MediaCapture inicializado que se usa para acceder a la secuencia de vídeo de la cámara.
Para agregar el efecto de vídeo personalizado a una secuencia de cámara, cree primero un nuevo objeto VideoEffectDefinition , pasando el espacio de nombres y el nombre de clase del efecto. A continuación, llame al método AddVideoEffect del objeto MediaCapture para agregar el efecto a la secuencia especificada. En este ejemplo se usa el valor MediaStreamType.VideoPreview para especificar que el efecto se debe agregar a la secuencia de vista previa. Si la aplicación admite la captura de vídeo, también puede usar MediaStreamType.VideoRecord para agregar el efecto a la secuencia de captura. AddVideoEffect devuelve un objeto IMediaExtension que representa el efecto personalizado. Puede usar el método SetProperties para establecer la configuración del efecto.
Una vez agregado el efecto, se llama a StartPreviewAsync para iniciar la secuencia de vista previa.
var videoEffectDefinition = new VideoEffectDefinition("VideoEffectComponent.ExampleVideoEffect");
IMediaExtension videoEffect =
await mediaCapture.AddVideoEffectAsync(videoEffectDefinition, MediaStreamType.VideoPreview);
videoEffect.SetProperties(new PropertySet() { { "FadeValue", .25 } });
await mediaCapture.StartPreviewAsync();
Agregar el efecto personalizado a un clip en MediaComposition
Para obtener instrucciones generales para crear composiciones multimedia a partir de clips de vídeo, consulte Composiciones multimedia y edición. El siguiente fragmento de código muestra la creación de una composición multimedia sencilla que usa un efecto de vídeo personalizado. Un objeto MediaClip se crea llamando a CreateFromFileAsync, pasando un archivo de vídeo seleccionado por el usuario con un FileOpenPicker y el clip se agrega a una nueva MediaComposition. A continuación, se crea un nuevo objeto VideoEffectDefinition , pasando el espacio de nombres y el nombre de clase del efecto al constructor. Por último, la definición del efecto se agrega a la colección VideoEffectDefinitions del 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);