Captura de pantalla a vídeo

En este artículo se describe cómo codificar fotogramas capturados desde la pantalla con las API Windows.Graphics.Capture en un archivo de vídeo. Para obtener información sobre la captura de pantalla de imágenes fijas, consulte Captura de pantalla. Para ver una sencilla aplicación de ejemplo de extremo a extremo que utiliza los conceptos y técnicas que se muestran en este artículo, consulte SimpleRecorder.

Información general del proceso de captura de vídeos

En este artículo se proporciona un tutorial de una aplicación de ejemplo que graba el contenido de una ventana en un archivo de vídeo. Aunque pueda parecer que se necesita mucho código para implementar este escenario, la estructura general de una aplicación de grabación de pantalla es bastante sencilla. El proceso de captura de pantalla utiliza tres funciones principales de UWP:

El código de ejemplo que se muestra en este artículo se puede clasificar en varias tareas diferentes:

  • Inicialización : esto incluye configurar las clases de UWP descritas anteriormente, inicializar las interfaces de dispositivo gráfico, seleccionar una ventana para capturar y configurar los parámetros de codificación, como la resolución y la velocidad de fotogramas.
  • Controladores de eventos y subprocesos: el controlador principal del bucle de captura principal es MediaStreamSource, que solicita fotogramas periódicamente a través del evento SampleRequested. En este ejemplo se usan eventos para coordinar las solicitudes de nuevos fotogramas entre los distintos componentes del ejemplo. La sincronización es importante para que los fotogramas se capturen y codifiquen simultáneamente.
  • Copia de fotogramas: los fotogramas se copian del búfer de fotogramas de captura en una superficie de Direct3D independiente que se puede transferir a MediaStreamSource para que el recurso no se sobrescriba al codificarlo. Las API de Direct3D se usan para realizar esta operación de copia rápidamente.

Acerca de las API de Direct3D

Como ya se ha dicho, la copia de cada fotograma capturado es probablemente la parte más compleja de la implementación que se muestra en este artículo. Concretamente, esta operación se realiza mediante Direct3D. En este ejemplo, vamos a utilizar la biblioteca SharpDX para realizar las operaciones de Direct3D desde C#. Aunque esta biblioteca ya no cuenta con soporte oficial, la hemos elegido porque su rendimiento en operaciones detalladas de copia es muy adecuado para este escenario. Hemos intentado mantener las operaciones Direct3D lo más discretas posible para que le resulte más fácil sustituir su propio código u otras bibliotecas para estas tareas.

Configurar tu proyecto

El código de ejemplo de este tutorial se creó con la plantilla de proyecto Aplicación vacía (Windows universal) de C# en Visual Studio 2019. Para usar las API de Windows.Graphics.Capture en la aplicación, debe incluir la funcionalidad de Captura de gráficos en el archivo Package.appxmanifest del proyecto. En este ejemplo, los archivos de vídeo generados se guardan en la biblioteca de vídeos del dispositivo. Para acceder a esta carpeta, debe incluir la funcionalidad Biblioteca de vídeos.

Para instalar el paquete Nuget de SharpDX, en Visual Studio, seleccione Administrar paquetes de Nuget. En la pestaña Examinar, busque el paquete "SharpDX.Direct3D11" y haga clic en Instalar.

Tenga en cuenta que, para reducir el tamaño de las listas de código de este artículo, en el código siguiente del tutorial se han omitido las referencias explícitas al espacio de nombres y la declaración de variables de miembro de la clase MainPage, que se nombran con un guion bajo inicial, "_".

Configuración de la codificación

El método SetupEncoding descrito en esta sección inicializa algunos de los objetos principales que se usarán para capturar y codificar fotogramas de vídeo y configura los parámetros de codificación para el vídeo capturado. Se podría llamar a este método mediante programación o en respuesta a una interacción del usuario, como un clic de botón. La lista de código de SetupEncoding se muestra después de las descripciones de los pasos de inicialización.

  • Comprobar si la función de captura es compatible. Antes de comenzar el proceso de captura, debe llamar a GraphicsCaptureSession.IsSupported para asegurarse de que la función de captura de pantalla es compatible con el dispositivo actual.

  • Inicializar las interfaces de Direct3D. En este ejemplo se usa Direct3D para copiar los píxeles capturados de la pantalla en una textura codificada como fotograma de vídeo. Los métodos auxiliares usados para inicializar las interfaces de Direct3D, CreateD3DDevice y CreateSharpDXDevice, se muestran más adelante en este artículo.

  • Inicializar un GraphicsCaptureItem. GraphicsCaptureItem representa un elemento en la pantalla que se va a capturar, ya sea una ventana o toda la pantalla. Permite al usuario elegir un elemento para capturar creando un GraphicsCapturePicker y llamando a PickSingleItemAsync.

  • Crear una textura de composición. Cree un recurso de textura y una vista de destino de representación asociada que se usará para copiar cada fotograma de vídeo. Esta textura no se puede crear hasta que se haya creado un GraphicsCaptureItem y se conozcan las dimensiones. Consulte la descripción de WaitForNewFrame para ver cómo se usa esta textura de composición. El método auxiliar para crear esta textura también se muestra más adelante en este artículo.

  • Crear un mediaEncodingProfile y VideoStreamDescriptor. Una instancia de la clase MediaStreamSource tomará imágenes capturadas desde la pantalla y las codificará en una secuencia de vídeo. A continuación, la secuencia de vídeo se transcodificará en un archivo de vídeo mediante la clase MediaTranscoder. Un VideoStreamDecriptor proporciona parámetros de codificación, como resolución y velocidad de fotogramas, para MediaStreamSource. Los parámetros de codificación del archivo de vídeo para MediaTranscoder se especifican con un MediaEncodingProfile. Tenga en cuenta que el tamaño usado para la codificación de vídeo no tiene que ser el mismo que el tamaño de la ventana que se captura, pero para simplificar este ejemplo, la configuración de codificación está codificada de forma fija para usar las dimensiones reales del elemento de captura.

  • Crear los objetos MediaStreamSource y MediaTranscoder. Como se ha mencionado antes, el objeto MediaStreamSource codifica fotogramas individuales en una secuencia de vídeo. Llame al constructor para esta clase y transfiera el MediaEncodingProfile creado en el paso anterior. Establezca el tiempo del búfer en cero y registre controladores para los eventos Starting y SampleRequested, que se mostrarán más adelante en este artículo. A continuación, cree una nueva instancia de la clase MediaTranscoder y habilite la aceleración de hardware.

  • Crear un archivo de salida El paso final de este método es crear un archivo al que se transcodificará el vídeo. En este ejemplo, simplemente crearemos un archivo con nombre único en la carpeta biblioteca de vídeos del dispositivo. Tenga en cuenta que para acceder a esta carpeta, la aplicación debe especificar la funcionalidad "Biblioteca de vídeos" en el manifiesto de la aplicación. Una vez creado el archivo, ábralo para lectura y escritura y transfiera la secuencia resultante al método EncodeAsync que se mostrará a continuación.

private async Task SetupEncoding()
{
    if (!GraphicsCaptureSession.IsSupported())
    {
        // Show message to user that screen capture is unsupported
        return;
    }

    // Create the D3D device and SharpDX device
    if (_device == null)
    {
        _device = Direct3D11Helpers.CreateD3DDevice();
    }
    if (_sharpDxD3dDevice == null)
    {
        _sharpDxD3dDevice = Direct3D11Helpers.CreateSharpDXDevice(_device);
    }
    


    try
    {
        // Let the user pick an item to capture
        var picker = new GraphicsCapturePicker();
        _captureItem = await picker.PickSingleItemAsync();
        if (_captureItem == null)
        {
            return;
        }

        // Initialize a blank texture and render target view for copying frames, using the same size as the capture item
        _composeTexture = Direct3D11Helpers.InitializeComposeTexture(_sharpDxD3dDevice, _captureItem.Size);
        _composeRenderTargetView = new SharpDX.Direct3D11.RenderTargetView(_sharpDxD3dDevice, _composeTexture);

        // This example encodes video using the item's actual size.
        var width = (uint)_captureItem.Size.Width; 
        var height = (uint)_captureItem.Size.Height;

        // Make sure the dimensions are are even. Required by some encoders.
        width = (width % 2 == 0) ? width : width + 1;
        height = (height % 2 == 0) ? height : height + 1;


        var temp = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p);
        var bitrate = temp.Video.Bitrate;
        uint framerate = 30;

        _encodingProfile = new MediaEncodingProfile();
        _encodingProfile.Container.Subtype = "MPEG4";
        _encodingProfile.Video.Subtype = "H264";
        _encodingProfile.Video.Width = width;
        _encodingProfile.Video.Height = height;
        _encodingProfile.Video.Bitrate = bitrate;
        _encodingProfile.Video.FrameRate.Numerator = framerate;
        _encodingProfile.Video.FrameRate.Denominator = 1;
        _encodingProfile.Video.PixelAspectRatio.Numerator = 1;
        _encodingProfile.Video.PixelAspectRatio.Denominator = 1;

        var videoProperties = VideoEncodingProperties.CreateUncompressed(MediaEncodingSubtypes.Bgra8, width, height);
        _videoDescriptor = new VideoStreamDescriptor(videoProperties);

        // Create our MediaStreamSource
        _mediaStreamSource = new MediaStreamSource(_videoDescriptor);
        _mediaStreamSource.BufferTime = TimeSpan.FromSeconds(0);
        _mediaStreamSource.Starting += OnMediaStreamSourceStarting;
        _mediaStreamSource.SampleRequested += OnMediaStreamSourceSampleRequested;

        // Create our transcoder
        _transcoder = new MediaTranscoder();
        _transcoder.HardwareAccelerationEnabled = true;


        // Create a destination file - Access to the VideosLibrary requires the "Videos Library" capability
        var folder = KnownFolders.VideosLibrary;
        var name = DateTime.Now.ToString("yyyyMMdd-HHmm-ss");
        var file = await folder.CreateFileAsync($"{name}.mp4");
        
        using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))

        await EncodeAsync(stream);
        
    }
    catch (Exception ex)
    {
        
        return;
    }
}

Inicio de la codificación

Ahora que se han inicializado los objetos principales, se implementa el método EncodeAsync para iniciar la operación de captura. Este método primero comprueba que no estamos grabando y, si no, llama al método auxiliar StartCapture para empezar a capturar fotogramas desde la pantalla. Este método se muestra más adelante en este artículo. A continuación, se llama a PrepareMediaStreamSourceTranscodeAsync para que MediaTranscoder esté listo para transcodificar la secuencia de vídeo generada por el objeto MediaStreamSource a la secuencia de archivo de salida, utilizando el perfil de codificación que creamos en la sección anterior. Una vez preparado el transcodificador, llame a TranscodeAsync para iniciar la transcodificación. Para obtener más información sobre el uso de MediaTranscoder, consulte Transcodificación de archivos multimedia.


private async Task EncodeAsync(IRandomAccessStream stream)
{
    if (!_isRecording)
    {
        _isRecording = true;

        StartCapture();

        var transcode = await _transcoder.PrepareMediaStreamSourceTranscodeAsync(_mediaStreamSource, stream, _encodingProfile);

        await transcode.TranscodeAsync();
    }
}

Control de los eventos MediaStreamSource

El objeto MediaStreamSource toma los fotogramas que capturamos desde la pantalla y los transforma en una secuencia de vídeo que se puede guardar en un archivo mediante MediaTranscoder. Transferimos los fotogramas a MediaStreamSource a través de los controladores de eventos del objeto.

El evento SampleRequested se genera cuando MediaStreamSource está listo para un nuevo fotograma de vídeo. Después de asegurarnos de que estamos grabando, se llama al método auxiliar WaitForNewFrame para obtener un nuevo fotograma capturado desde la pantalla. Este método, que se muestra más adelante en este artículo, devuelve un objeto ID3D11Surface que contiene el fotograma capturado. En este ejemplo, encapsulamos la interfaz IDirect3DSurface en una clase auxiliar que también almacena la hora del sistema a la que se capturó el fotograma. Tanto la hora del fotograma como la del sistema se transfieren al Factory Method MediaStreamSample.CreateFromDirect3D11Surface y el MediaStreamSample resultante se establece con la propiedad MediaStreamSourceSampleRequest.Sample de MediaStreamSourceSampleRequestedEventArgs. Así es como se proporciona el fotograma capturado a MediaStreamSource.

private void OnMediaStreamSourceSampleRequested(MediaStreamSource sender, MediaStreamSourceSampleRequestedEventArgs args)
{
    if (_isRecording && !_closed)
    {
        try
        {
            using (var frame = WaitForNewFrame())
            {
                if (frame == null)
                {
                    args.Request.Sample = null;
                    Stop();
                    Cleanup();
                    return;
                }

                var timeStamp = frame.SystemRelativeTime;

                var sample = MediaStreamSample.CreateFromDirect3D11Surface(frame.Surface, timeStamp);
                args.Request.Sample = sample;
            }
        }
        catch (Exception e)
        {
            Debug.WriteLine(e.Message);
            Debug.WriteLine(e.StackTrace);
            Debug.WriteLine(e);
            args.Request.Sample = null;
            Stop();
            Cleanup();
        }
    }
    else
    {
        args.Request.Sample = null;
        Stop();
        Cleanup();
    }
}

En el controlador del evento Starting, llamamos a WaitForNewFrame, pero solo transferimos la hora del sistema en que se capturó el fotograma al método MediaStreamSourceStartingRequest.SetActualStartPosition, que MediaStreamSource usa para codificar correctamente el tiempo de los fotogramas posteriores.

private void OnMediaStreamSourceStarting(MediaStreamSource sender, MediaStreamSourceStartingEventArgs args)
{
    using (var frame = WaitForNewFrame())
    {
        args.Request.SetActualStartPosition(frame.SystemRelativeTime);
    }
}

Inicio de la captura

Se llama al método StartCapture que se muestra en este paso desde el método auxiliar EncodeAsync mostrado en un paso anterior. En primer lugar, este método inicializa un conjunto de objetos de evento que se usan para controlar el flujo de la operación de captura.

  • _multithread es una clase auxiliar que encapsula el objeto Multithread de la biblioteca SharpDX que se usará para asegurarse de que ningún otro subproceso tenga acceso a la textura sharpDX mientras se copia.
  • _frameEvent se usa para indicar que se ha capturado un nuevo fotograma y que se puede transferir a MediaStreamSource.
  • _closedEvent indica que la grabación se ha detenido y que no deberíamos esperar a que se generen nuevos fotogramas.

Los eventos "frame" y "closed" se agregan a una matriz para que podamos esperar a cualquiera de ellos en el bucle de captura.

El resto del método StartCapture configura las API Windows.Graphics.Capture que realizarán la captura de pantalla real. En primer lugar, se registra un evento para el evento CaptureItem.Closed. A continuación, se crea Direct3D11CaptureFramePool, que permite almacenar en búfer varios fotogramas capturados a la vez. El método CreateFreeThreaded se usa para crear el grupo de fotogramas para que se llame al evento FrameArrived en el propio subproceso de trabajo del grupo en lugar de hacerlo en el subproceso principal de la aplicación. A continuación, se registra un controlador para el evento FrameArrived. Por último, se crea GraphicsCaptureSession para el CaptureItem seleccionado y la captura de fotogramas se inicia llamando a StartCapture.

public void StartCapture()
{

    _multithread = _sharpDxD3dDevice.QueryInterface<SharpDX.Direct3D11.Multithread>();
    _multithread.SetMultithreadProtected(true);
    _frameEvent = new ManualResetEvent(false);
    _closedEvent = new ManualResetEvent(false);
    _events = new[] { _closedEvent, _frameEvent };

    _captureItem.Closed += OnClosed;
    _framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(
        _device,
        DirectXPixelFormat.B8G8R8A8UIntNormalized,
        1,
        _captureItem.Size);
    _framePool.FrameArrived += OnFrameArrived;
    _session = _framePool.CreateCaptureSession(_captureItem);
    _session.StartCapture();
}

Control de los eventos de captura de gráficos

En el paso anterior registramos dos controladores para los eventos de captura de gráficos y configuramos algunos eventos para ayudar a administrar el flujo del bucle de captura.

El evento FrameArrived se genera cuando Direct3D11CaptureFramePool tiene disponible un nuevo fotograma capturado. En el controlador de este evento, llame a TryGetNextFrame en el remitente para obtener el siguiente fotograma capturado. Una vez recuperado el fotograma, establecemos _frameEvent para que nuestro bucle de captura sepa que hay un nuevo fotograma disponible.

private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)
{
    _currentFrame = sender.TryGetNextFrame();
    _frameEvent.Set();
}

En el controlador de eventos Closed, indicamos _closedEvent para que el bucle de captura sepa cuándo debe detenerse.

private void OnClosed(GraphicsCaptureItem sender, object args)
{
    _closedEvent.Set();
}

Espera de nuevos fotogramas

El método auxiliar WaitForNewFrame descrito en esta sección es donde ocurre el trabajo pesado del bucle de captura. Recuerde que se llama a este método desde el controlador de eventos OnMediaStreamSourceSampleRequested siempre que MediaStreamSource esté listo para que se agregue un nuevo fotograma a la secuencia de vídeo. A nivel general, esta función simplemente copia cada fotograma de vídeo capturado en la pantalla de una superficie Direct3D a otra para que se pueda transferir a MediaStreamSource para codificar mientras se captura un nuevo fotograma. En este ejemplo se usa la biblioteca SharpDX para realizar la operación de copia real.

Antes de esperar un nuevo fotograma, el método elimina cualquier fotograma anterior almacenado en la variable de clase, _currentFrame y restablece _frameEvent. A continuación, el método espera a que se indique _frameEvent o _closedEvent. Si se establece el evento "closed", la aplicación llama a un método auxiliar para limpiar los recursos de captura. Este método se muestra más adelante en este artículo.

Si se establece el evento "frame", sabemos que se ha llamado al controlador de eventos FrameArrived definido en el paso anterior y empezaremos el proceso de copiar los datos de fotograma capturados en una superficie de Direct3D 11 que se transferirá a MediaStreamSource.

En este ejemplo se usa una clase auxiliar, SurfaceWithInfo, que simplemente nos permite transferir el fotograma de vídeo y la hora del sistema del fotograma, ambos necesarios en MediaStreamSource, como un único objeto. El primer paso del proceso de copia de fotogramas consiste en instanciar esta clase y establecer la hora del sistema.

Los pasos siguientes son la parte de este ejemplo, que se basa específicamente en la biblioteca SharpDX. Las funciones auxiliares que se usan aquí se definen al final de este artículo. En primer lugar, usamos MultiThreadLock para asegurarnos de que ningún otro subproceso acceda al búfer de fotogramas de vídeo mientras realizamos la copia. A continuación, llamamos al método auxiliar CreateSharpDXTexture2D para crear un objeto Texture2D de SharpDX a partir del fotograma de vídeo. Esta será la textura de origen de la operación de copia.

A continuación, copiamos desde el objeto Texture2D creado en el paso anterior en la textura de composición que creamos anteriormente en el proceso. Esta textura de composición actúa como un búfer de intercambio para que el proceso de codificación pueda funcionar en los píxeles mientras se captura el siguiente fotograma. Para realizar la copia, borramos la vista de destino de representación asociada a la textura de composición y, a continuación, definimos la región dentro de la textura que queremos copiar, toda la textura en este caso, y llamamos a CopySubresourceRegion para copiar realmente los píxeles en la textura de composición.

Creamos una copia de la descripción de la textura que se usará al crear la textura de destino, pero la descripción se modifica, estableciendo BindFlags en RenderTarget para que la nueva textura tenga acceso de escritura. Establecer CpuAccessFlags en None permite al sistema optimizar la operación de copia. La descripción de la textura se usa para crear un nuevo recurso de textura y el recurso de textura de composición se copia en este nuevo recurso con una llamada a CopyResource. Por último, se llama a CreateDirect3DSurfaceFromSharpDXTexture para crear el objeto IDirect3DSurface que se devuelve desde este método.

public SurfaceWithInfo WaitForNewFrame()
{
    // Let's get a fresh one.
    _currentFrame?.Dispose();
    _frameEvent.Reset();

    var signaledEvent = _events[WaitHandle.WaitAny(_events)];
    if (signaledEvent == _closedEvent)
    {
        Cleanup();
        return null;
    }

    var result = new SurfaceWithInfo();
    result.SystemRelativeTime = _currentFrame.SystemRelativeTime;
    using (var multithreadLock = new MultithreadLock(_multithread))
    using (var sourceTexture = Direct3D11Helpers.CreateSharpDXTexture2D(_currentFrame.Surface))
    {

        _sharpDxD3dDevice.ImmediateContext.ClearRenderTargetView(_composeRenderTargetView, new SharpDX.Mathematics.Interop.RawColor4(0, 0, 0, 1));

        var width = Math.Clamp(_currentFrame.ContentSize.Width, 0, _currentFrame.Surface.Description.Width);
        var height = Math.Clamp(_currentFrame.ContentSize.Height, 0, _currentFrame.Surface.Description.Height);
        var region = new SharpDX.Direct3D11.ResourceRegion(0, 0, 0, width, height, 1);
        _sharpDxD3dDevice.ImmediateContext.CopySubresourceRegion(sourceTexture, 0, region, _composeTexture, 0);

        var description = sourceTexture.Description;
        description.Usage = SharpDX.Direct3D11.ResourceUsage.Default;
        description.BindFlags = SharpDX.Direct3D11.BindFlags.ShaderResource | SharpDX.Direct3D11.BindFlags.RenderTarget;
        description.CpuAccessFlags = SharpDX.Direct3D11.CpuAccessFlags.None;
        description.OptionFlags = SharpDX.Direct3D11.ResourceOptionFlags.None;

        using (var copyTexture = new SharpDX.Direct3D11.Texture2D(_sharpDxD3dDevice, description))
        {
            _sharpDxD3dDevice.ImmediateContext.CopyResource(_composeTexture, copyTexture);
            result.Surface = Direct3D11Helpers.CreateDirect3DSurfaceFromSharpDXTexture(copyTexture);
        }
    }

    return result;
}

Detención de la captura y limpieza de los recursos

El método Stop permite detener la operación de captura. La aplicación puede llamar a este método mediante programación o en respuesta a una interacción del usuario, como un clic de botón. Este método simplemente establece _closedEvent. El método WaitForNewFrame definido en los pasos anteriores busca este evento y, si está establecido, apaga la operación de captura.

private void Stop()
{
    _closedEvent.Set();
}

El método Cleanup se usa para eliminar correctamente los recursos que se crearon durante la operación de copia. Esto incluye:

  • El objeto Direct3D11CaptureFramePool usado en la sesión de captura.
  • GraphicsCaptureSession y GraphicsCaptureItem.
  • Los dispositivos Direct3D y SharpDX.
  • La textura de SharpDX y la vista de destino de representación que se han utilizado en la operación de copia.
  • Direct3D11CaptureFrame que se ha utilizado para almacenar el fotograma actual.
private void Cleanup()
{
    _framePool?.Dispose();
    _session?.Dispose();
    if (_captureItem != null)
    {
        _captureItem.Closed -= OnClosed;
    }
    _captureItem = null;
    _device = null;
    _sharpDxD3dDevice = null;
    _composeTexture?.Dispose();
    _composeTexture = null;
    _composeRenderTargetView?.Dispose();
    _composeRenderTargetView = null;
    _currentFrame?.Dispose();
}

Clases contenedoras auxiliares

Se han definido las siguientes clases auxiliares para ayudar con el código de ejemplo en este artículo.

La clase auxiliar MultithreadLock encapsula la clase Multithread de SharpDX que garantiza que otros subprocesos no tengan acceso a los recursos de textura mientras se copian.

class MultithreadLock : IDisposable
{
    public MultithreadLock(SharpDX.Direct3D11.Multithread multithread)
    {
        _multithread = multithread;
        _multithread?.Enter();
    }

    public void Dispose()
    {
        _multithread?.Leave();
        _multithread = null;
    }

    private SharpDX.Direct3D11.Multithread _multithread;
}

SurfaceWithInfo se usa para asociar IDirect3DSurface con SystemRelativeTime, que representa el fotograma capturado y la hora a la que se capturó, respectivamente.

public sealed class SurfaceWithInfo : IDisposable
{
    public IDirect3DSurface Surface { get; internal set; }
    public TimeSpan SystemRelativeTime { get; internal set; }

    public void Dispose()
    {
        Surface?.Dispose();
        Surface = null;
    }
}

API auxiliares de Direct3D y SharpDX

Se han definido las siguientes API auxiliares para abstraer la creación de recursos Direct3D y SharpDX. Aunque una explicación detallada de estas tecnologías está fuera del alcance de este artículo, aquí proporcionamos el código para que pueda implementar el código de ejemplo que se muestra en el tutorial.

[ComImport]
[Guid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComVisible(true)]
interface IDirect3DDxgiInterfaceAccess
{
    IntPtr GetInterface([In] ref Guid iid);
};

public static class Direct3D11Helpers
{
    internal static Guid IInspectable = new Guid("AF86E2E0-B12D-4c6a-9C5A-D7AA65101E90");
    internal static Guid ID3D11Resource = new Guid("dc8e63f3-d12b-4952-b47b-5e45026a862d");
    internal static Guid IDXGIAdapter3 = new Guid("645967A4-1392-4310-A798-8053CE3E93FD");
    internal static Guid ID3D11Device = new Guid("db6f6ddb-ac77-4e88-8253-819df9bbf140");
    internal static Guid ID3D11Texture2D = new Guid("6f15aaf2-d208-4e89-9ab4-489535d34f9c");

    [DllImport(
        "d3d11.dll",
        EntryPoint = "CreateDirect3D11DeviceFromDXGIDevice",
        SetLastError = true,
        CharSet = CharSet.Unicode,
        ExactSpelling = true,
        CallingConvention = CallingConvention.StdCall
        )]
    internal static extern UInt32 CreateDirect3D11DeviceFromDXGIDevice(IntPtr dxgiDevice, out IntPtr graphicsDevice);

    [DllImport(
        "d3d11.dll",
        EntryPoint = "CreateDirect3D11SurfaceFromDXGISurface",
        SetLastError = true,
        CharSet = CharSet.Unicode,
        ExactSpelling = true,
        CallingConvention = CallingConvention.StdCall
        )]
    internal static extern UInt32 CreateDirect3D11SurfaceFromDXGISurface(IntPtr dxgiSurface, out IntPtr graphicsSurface);

    public static IDirect3DDevice CreateD3DDevice()
    {
        return CreateD3DDevice(false);
    }

    public static IDirect3DDevice CreateD3DDevice(bool useWARP)
    {
        var d3dDevice = new SharpDX.Direct3D11.Device(
            useWARP ? SharpDX.Direct3D.DriverType.Software : SharpDX.Direct3D.DriverType.Hardware,
            SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport);
        IDirect3DDevice device = null;

        // Acquire the DXGI interface for the Direct3D device.
        using (var dxgiDevice = d3dDevice.QueryInterface<SharpDX.DXGI.Device3>())
        {
            // Wrap the native device using a WinRT interop object.
            uint hr = CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.NativePointer, out IntPtr pUnknown);

            if (hr == 0)
            {
                device = Marshal.GetObjectForIUnknown(pUnknown) as IDirect3DDevice;
                Marshal.Release(pUnknown);
            }
        }

        return device;
    }


    internal static IDirect3DSurface CreateDirect3DSurfaceFromSharpDXTexture(SharpDX.Direct3D11.Texture2D texture)
    {
        IDirect3DSurface surface = null;

        // Acquire the DXGI interface for the Direct3D surface.
        using (var dxgiSurface = texture.QueryInterface<SharpDX.DXGI.Surface>())
        {
            // Wrap the native device using a WinRT interop object.
            uint hr = CreateDirect3D11SurfaceFromDXGISurface(dxgiSurface.NativePointer, out IntPtr pUnknown);

            if (hr == 0)
            {
                surface = Marshal.GetObjectForIUnknown(pUnknown) as IDirect3DSurface;
                Marshal.Release(pUnknown);
            }
        }

        return surface;
    }



    internal static SharpDX.Direct3D11.Device CreateSharpDXDevice(IDirect3DDevice device)
    {
        var access = (IDirect3DDxgiInterfaceAccess)device;
        var d3dPointer = access.GetInterface(ID3D11Device);
        var d3dDevice = new SharpDX.Direct3D11.Device(d3dPointer);
        return d3dDevice;
    }

    internal static SharpDX.Direct3D11.Texture2D CreateSharpDXTexture2D(IDirect3DSurface surface)
    {
        var access = (IDirect3DDxgiInterfaceAccess)surface;
        var d3dPointer = access.GetInterface(ID3D11Texture2D);
        var d3dSurface = new SharpDX.Direct3D11.Texture2D(d3dPointer);
        return d3dSurface;
    }


    public static SharpDX.Direct3D11.Texture2D InitializeComposeTexture(
        SharpDX.Direct3D11.Device sharpDxD3dDevice,
        SizeInt32 size)
    {
        var description = new SharpDX.Direct3D11.Texture2DDescription
        {
            Width = size.Width,
            Height = size.Height,
            MipLevels = 1,
            ArraySize = 1,
            Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm,
            SampleDescription = new SharpDX.DXGI.SampleDescription()
            {
                Count = 1,
                Quality = 0
            },
            Usage = SharpDX.Direct3D11.ResourceUsage.Default,
            BindFlags = SharpDX.Direct3D11.BindFlags.ShaderResource | SharpDX.Direct3D11.BindFlags.RenderTarget,
            CpuAccessFlags = SharpDX.Direct3D11.CpuAccessFlags.None,
            OptionFlags = SharpDX.Direct3D11.ResourceOptionFlags.None
        };
        var composeTexture = new SharpDX.Direct3D11.Texture2D(sharpDxD3dDevice, description);
       

        using (var renderTargetView = new SharpDX.Direct3D11.RenderTargetView(sharpDxD3dDevice, composeTexture))
        {
            sharpDxD3dDevice.ImmediateContext.ClearRenderTargetView(renderTargetView, new SharpDX.Mathematics.Interop.RawColor4(0, 0, 0, 1));
        }

        return composeTexture;
    }
}

Consulte también