Captura de pantalla a vídeoScreen capture to video

En este artículo se describe cómo codificar fotogramas capturados desde la pantalla con las API de Windows. Graphics. Capture en un archivo de vídeo.This article describes how to encode frames captured from the screen with the Windows.Graphics.Capture APIs to a video file. Para obtener información sobre la captura de pantalla de imágenes fijas, consulte captura de Screeen.For information on screen capturing still images, see Screeen capture. Para obtener una aplicación de ejemplo de un extremo a otro simple que use los conceptos y las técnicas que se muestran en este artículo, vea SimpleRecorder.For a simple end-to-end sample app that utilizes the concepts and techniques shown in this article, see SimpleRecorder.

Información general del proceso de captura de vídeoOverview of the video capture process

En este artículo se proporciona un tutorial de una aplicación de ejemplo que registra el contenido de una ventana en un archivo de vídeo.This article provides a walkthrough of an example app that records the contents of a window to a video file. Aunque puede parecer que hay una gran cantidad de código necesaria para implementar este escenario, la estructura de alto nivel de una aplicación de grabadora de pantalla es bastante simple.While it may seem like there is a lot of code required to implement this scenario, the high-level structure of a screen recorder app is fairly simple. El proceso de captura de pantalla usa tres características principales de UWP:The screen capture process uses three primary UWP features:

El código de ejemplo que se muestra en este artículo se puede clasificar en varias tareas diferentes:The example code shown in this article can be categorized into a few different tasks:

  • Inicialización : Esto incluye la configuración de las clases de UWP descritas anteriormente, la inicialización de las interfaces de dispositivo gráfico, la selección de una ventana para capturar y la configuración de los parámetros de codificación, como la velocidad de fotogramas y la resolución.Initialization - This includes configuring the UWP classes described above, initializing the graphics device interfaces, picking a window to capture, and setting up the encoding parameters such as resolution and frame rate.
  • Controladores de eventos y subprocesos : el controlador principal del bucle de captura principal es el MediaStreamSource que solicita tramas periódicamente a través del evento SampleRequested .Event handlers and threading - The primary driver of the main capture loop is the MediaStreamSource which requests frames periodically through the SampleRequested event. En este ejemplo se usan eventos para coordinar las solicitudes de nuevos fotogramas entre los diferentes componentes del ejemplo.This example uses events to coordinate the requests for new frames between the different components of the example. La sincronización es importante para permitir que los fotogramas se capturen y codifiquen simultáneamente.Synchronization is important to allow frames to be captured and encoded simultaneously.
  • La copia de Marcos -fotogramas se copia desde el búfer de fotogramas de captura en una superficie de Direct3D independiente que se puede pasar a MediaStreamSource para que el recurso no se sobrescriba mientras se codifica.Copying frames - Frames are copied from the capture frame buffer into a separate Direct3D surface that can be passed to the MediaStreamSource so that the resource isn't overwritten while being encoded. Las API de Direct3D se usan para realizar esta operación de copia rápidamente.Direct3D APIs are used to perform this copy operation quickly.

Acerca de las API de Direct3DAbout the Direct3D APIs

Como se indicó anteriormente, la copia de cada fotograma capturado es probablemente la parte más compleja de la implementación que se muestra en este artículo.As stated above, the copying of each captured frame is probably the most complex part of the implementation shown in this article. En un nivel bajo, esta operación se realiza mediante Direct3D.At a low level, this operation is done using Direct3D. En este ejemplo, se usa la biblioteca SharpDX para realizar las operaciones de Direct3D desde C#.For this example, we are using the SharpDX library to perform the Direct3D operations from C#. Esta biblioteca ya no es compatible oficialmente, pero se eligió porque su rendimiento en operaciones de copia de bajo nivel es adecuado para este escenario.This library is no longer officially supported, but it was chosen because it's performance at low-level copy operations is well-suited for this scenario. Hemos intentado mantener las operaciones de Direct3D lo más discretas posible para que sea más fácil sustituir su propio código u otras bibliotecas para estas tareas.We have tried to keep the Direct3D operations as discrete as possible to make it easier for you to substitute your own code or other libraries for these tasks.

Configurar tu proyectoSetting up your project

El código de ejemplo de este tutorial se creó con la plantilla de proyecto de C# aplicación vacía (Windows universal) en Visual Studio 2019.The example code in this walkthrough was created using the Blank App (Universal Windows) C# project template in Visual Studio 2019. Para poder 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.In order to use the Windows.Graphics.Capture APIs in your app, you must include the Graphics Capture capability in the Package.appxmanifest file for your project. En este ejemplo se guardan los archivos de vídeo generados en la biblioteca de vídeos del dispositivo.This example saves generated video files to the Videos Library on the device. Para tener acceso a esta carpeta, debe incluir la capacidad de la biblioteca de vídeos .To access this folder you must include the Videos Library capability.

Para instalar el paquete Nuget de SharpDX, en Visual Studio, seleccione administrar paquetes Nuget.To install the SharpDX Nuget package, in Visual Studio select Manage Nuget Packages. En la pestaña examinar, busque el paquete "SharpDX. Direct3D 11" y haga clic en instalar.In the Browse tab search for the "SharpDX.Direct3D11" package and click Install.

Tenga en cuenta que para reducir el tamaño de los listados de código de este artículo, el código del tutorial siguiente omite las referencias de espacio de nombres explícitas y la declaración de las variables de miembro de clase MainPage que se denominan con un carácter de subrayado inicial, "".Note that in order to reduce the size of the code listings in this article, the code in the walkthrough below omits explicit namespace references and the declaration of MainPage class member variables which are named with a leading underscore, "".

Configuración de la codificaciónSetup for encoding

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.The SetupEncoding method described in this section initializes some of the main objects that will be used to capture and encode video frames and sets up the encoding parameters for captured video. Se puede llamar a este método mediante programación o en respuesta a una interacción del usuario como un clic de botón.This method could be called programmatically or in response to a user interaction like a button click. La lista de código para SetupEncoding se muestra a continuación después de las descripciones de los pasos de inicialización.The code listing for SetupEncoding is shown below after the descriptions of the initialization steps.

  • Compruebe la compatibilidad con la captura.Check for capture support. Antes de comenzar el proceso de captura, debe llamar a GraphicsCaptureSession. IsSupported para asegurarse de que se admite la característica de captura de pantalla en el dispositivo actual.Before beginning the capture process, you need to call GraphicsCaptureSession.IsSupported to make sure that the screen capture feature is supported on the current device.

  • Inicializar interfaces de Direct3D.Initialize Direct3D interfaces. En este ejemplo se usa Direct3D para copiar los píxeles capturados de la pantalla en una textura que está codificada como un fotograma de vídeo.This sample uses Direct3D to copy the pixels captured from the screen into a texture that is encoded as a video frame. Los métodos auxiliares que se usan para inicializar las interfaces de Direct3D, CreateD3DDevice y CreateSharpDXDevice, se muestran más adelante en este artículo.The helper methods used to initialize the Direct3D interfaces, CreateD3DDevice and CreateSharpDXDevice, are shown later in this article.

  • Inicializa un GraphicsCaptureItem.Initialize a GraphicsCaptureItem. Un GraphicsCaptureItem representa un elemento de la pantalla que se va a capturar, ya sea una ventana o toda la pantalla.A GraphicsCaptureItem represents an item on the screen that is going to be captured, either a window or the entire screen. Permite al usuario seleccionar un elemento para capturar creando un GraphicsCapturePicker y llamando a PickSingleItemAsync.Allow the user to pick an item to capture by creating a GraphicsCapturePicker and calling PickSingleItemAsync.

  • Cree una textura de composición.Create a composition texture. Cree un recurso de textura y una vista de destino de representación asociada que se usará para copiar cada fotograma de vídeo.Create a texture resource and an associated render target view that will be used to copy each video frame. Esta textura no se puede crear hasta que se haya creado el GraphicsCaptureItem y se conozcan sus dimensiones.This texture can't be created until the GraphicsCaptureItem has been created and we know its dimensions. Vea la descripción de WaitForNewFrame para ver cómo se usa esta textura de composición.See the description of the WaitForNewFrame to see how this composition texture is used. El método auxiliar para crear esta textura también se muestra más adelante en este artículo.The helper method for creating this texture is also shown later in this article.

  • Cree un MediaEncodingProfile y un VideoStreamDescriptor.Create a MediaEncodingProfile and VideoStreamDescriptor. Una instancia de la clase MediaStreamSource tomará las imágenes capturadas de la pantalla y las codificará en una secuencia de vídeo.An instance of the MediaStreamSource class will take images captured from the screen and encode them into a video stream. A continuación, la secuencia de vídeo se transcodificará en un archivo de vídeo por la clase MediaTranscoder .Then, the video stream will be transcoded into a video file by the MediaTranscoder class. Un VideoStreamDecriptor proporciona parámetros de codificación, como resolución y velocidad de fotogramas, para el MediaStreamSource.A VideoStreamDecriptor provides encoding parameters, such as resolution and frame rate, for the MediaStreamSource. Los parámetros de codificación del archivo de vídeo para MediaTranscoder se especifican con un MediaEncodingProfile.The video file encoding parameters for the MediaTranscoder are specified with a MediaEncodingProfile. Tenga en cuenta que el tamaño utilizado para la codificación de vídeo no tiene que ser el mismo que el tamaño de la ventana que se va a capturar, pero para simplificar este ejemplo, la configuración de codificación está codificada de forma rígida para usar las dimensiones reales del elemento de captura.Note that the size used for video encoding doesn't have to be the same as the size of the window being captured, but to keep this example simple, the encoding settings are hard-coded to use the capture item's actual dimensions.

  • Cree los objetos MediaStreamSource y MediaTranscoder.Create the MediaStreamSource and MediaTranscoder objects. Como se mencionó anteriormente, el objeto MediaStreamSource codifica fotogramas individuales en una secuencia de vídeo.As mentioned above, the MediaStreamSource object encodes individual frames into a video stream. Llame al constructor para esta clase, pasando el MediaEncodingProfile creado en el paso anterior.Call the constructor for this class, passing in the MediaEncodingProfile created in the previous step. Establezca el tiempo de búfer en cero y los controladores de registro para los eventos de Inicio y SampleRequested , que se mostrarán más adelante en este artículo.Set the buffer time to zero and register handlers for the Starting and SampleRequested events, which will be shown later in this article. A continuación, cree una nueva instancia de la clase MediaTranscoder y habilite la aceleración de hardware.Next, construct a new instance of the MediaTranscoder class and enable hardware acceleration.

  • Crear un archivo de salida El último paso de este método es crear un archivo en el que se transcodificará el vídeo.Create an output file The final step in this method is to create a file to which the video will be transcoded. En este ejemplo, solo se creará un archivo con un nombre único en la carpeta videos Library del dispositivo.For this example, we will just create a uniquely named file in the Videos Library folder on the device. Tenga en cuenta que para tener acceso a esta carpeta, la aplicación debe especificar la funcionalidad "biblioteca de vídeos" en el manifiesto de la aplicación.Note that in order to access this folder, your app must specify the "Videos Library" capability in the app manifest. Una vez creado el archivo, ábralo para lectura y escritura, y pase el flujo resultante en el método EncodeAsync que se mostrará a continuación.Once the file has been created, open it for read and write, and pass the resulting stream into the EncodeAsync method which will be shown next.

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

Iniciar codificaciónStart encoding

Ahora que se han inicializado los objetos principales, se implementa el método EncodeAsync para iniciar la operación de captura.Now that the main objects have been initialized the EncodeAsync method is implemented to kick off the capture operation. Este método comprueba primero para asegurarse de que aún no se está grabando y, si no es así, llama al método auxiliar StartCapture para empezar a capturar fotogramas de la pantalla.This method first checks to make sure we aren't already recording, and if not, it calls the helper method StartCapture to begin capturing frames from the screen. Este método se muestra más adelante en este artículo.This method is shown later in this article. A continuación, se llama a PrepareMediaStreamSourceTranscodeAsync para obtener la MediaTranscoder lista para transcodificar la secuencia de vídeo generada por el objeto MediaStreamSource en el flujo de archivos de salida, mediante el perfil de codificación que hemos creado en la sección anterior.Next, PrepareMediaStreamSourceTranscodeAsync is called to get the MediaTranscoder ready to transcode the video stream produced by the MediaStreamSource object to the output file stream, using the encoding profile we created in the previous section. Una vez preparado el transcodificador, llame a TranscodeAsync para iniciar la transcodificación.Once the transcoder has been prepared, call TranscodeAsync to start transcoding. Para obtener más información sobre el uso de MediaTranscoder, vea transcodificar archivos multimedia.For more information on using the MediaTranscoder, see Transcode media files.


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

        StartCapture();

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

        await transcode.TranscodeAsync();
    }
}

Controlar eventos MediaStreamSourceHandle MediaStreamSource events

El objeto MediaStreamSource toma fotogramas que se capturan de la pantalla y los transforma en una secuencia de vídeo que se puede guardar en un archivo mediante MediaTranscoder.The MediaStreamSource object takes frames that we capture from the screen and transforms them into a video stream that can be saved to a file using the MediaTranscoder. Pasamos los fotogramas a MediaStreamSource a través de los controladores de los eventos del objeto.We pass the frames to the MediaStreamSource via handlers for the object's events.

El evento SampleRequested se genera cuando el MediaStreamSource está listo para un nuevo fotograma de vídeo.The SampleRequested event is raised when the MediaStreamSource is ready for a new video frame. Después de asegurarse de que estamos grabando actualmente, se llama al método auxiliar WaitForNewFrame para obtener un nuevo fotograma capturado de la pantalla.After making sure we are currently recording, the helper method WaitForNewFrame is called to get a new frame captured from the screen. Este método, que se muestra más adelante en este artículo, devuelve un objeto ID3D11Surface que contiene el fotograma capturado.This method, shown later in this article, returns a ID3D11Surface object containing the captured frame. En este ejemplo, se ajusta la interfaz IDirect3DSurface en una clase auxiliar que también almacena la hora del sistema en la que se capturó el fotograma.For this example, we wrap the IDirect3DSurface interface in a helper class that also stores the system time at which the frame was captured. Tanto el marco como la hora del sistema se pasan en el Factory Method MediaStreamSample. CreateFromDirect3D11Surface y el MediaStreamSample resultante se establece en la propiedad MediaStreamSourceSampleRequest. Sample del MediaStreamSourceSampleRequestedEventArgs.Both the frame and the system time are passed into the MediaStreamSample.CreateFromDirect3D11Surface factory method and the resulting MediaStreamSample is set to the MediaStreamSourceSampleRequest.Sample property of the MediaStreamSourceSampleRequestedEventArgs. Así es como se proporciona el fotograma capturado al MediaStreamSource.This is how the captured frame is provided to the MediaStreamSource.

private void OnMediaStreamSourceSampleRequested(MediaStreamSource sender, MediaStreamSourceSampleRequestedEventArgs args)
{
    if (_isRecording && !_closed)
    {
        try
        {
            using (var frame = WaitForNewFrame())
            {
                if (frame == null)
                {
                    args.Request.Sample = null;
                    Dispose();
                    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 de Inicio , se llama a WaitForNewFrame, pero solo se pasa la hora del sistema que el fotograma se capturó al método MediaStreamSourceStartingRequest. SetActualStartPosition , que el MediaStreamSource usa para codificar correctamente el tiempo de los fotogramas posteriores.In the handler for the Starting event, we call WaitForNewFrame, but only pass the system time the frame was captured to the MediaStreamSourceStartingRequest.SetActualStartPosition method, which the MediaStreamSource uses to properly encode the timing of the subsequent frames.

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

Iniciar capturaStart capturing

El método StartCapture que se muestra en este paso se llama desde el método auxiliar EncodeAsync mostrado en un paso anterior.The StartCapture method shown in this step is called from the EncodeAsync helper method shown in a previous step. 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.First, this method initializes up a set of event objects that are used to control the flow of the capture operation.

  • _multithread es una clase auxiliar que ajusta el objeto multiproceso de la biblioteca SharpDX que se usará para asegurarse de que ningún otro subproceso tenga acceso a la textura de SharpDX mientras se copia._multithread is a helper class wrapping the SharpDX library's Multithread object that will be used to make sure that no other threads access the SharpDX texture while it's being copied.
  • _frameEvent se usa para indicar que se ha capturado un nuevo fotograma y se puede pasar a MediaStreamSource_frameEvent is used to signal that a new frame has been captured and can be passed to the MediaStreamSource
  • _closedEvent indica que se ha detenido la grabación y que no se debe esperar ningún fotograma nuevo._closedEvent signals that recording has stopped and that we shouldn't wait for any new frames.

El evento de marco y el evento cerrado se agregan a una matriz para que podamos esperar a uno de ellos en el bucle de captura.The frame event and closed event are added to an array so we can wait for either one of them in the capture loop.

El resto del método StartCapture configura las API de Windows. Graphics. Capture que realizarán la captura de pantalla real.The rest of the StartCapture method sets up the Windows.Graphics.Capture APIs that will do the actual screen capturing. En primer lugar, se registra un evento para el evento CaptureItem. Closed .First, an event is registered for the CaptureItem.Closed event. A continuación, se crea un Direct3D11CaptureFramePool , que permite almacenar en búfer varios fotogramas capturados a la vez.Next, a Direct3D11CaptureFramePool is created, which allows multiple captured frames to be buffered at a time. El método CreateFreeThreaded se usa para crear el grupo de marcos de modo que se llame al evento FrameArrived en el subproceso de trabajo del grupo en lugar de en el subproceso principal de la aplicación.The CreateFreeThreaded method is used to create the frame pool so that the FrameArrived event is called on the pool's own worker thread rather than on the app's main thread. A continuación, se registra un controlador para el evento FrameArrived .Next, a handler is registered for the FrameArrived event. Por último, se crea un GraphicsCaptureSession para el CaptureItem seleccionado y la captura de fotogramas se inicia mediante una llamada a StartCapture.Finally, a GraphicsCaptureSession is created for the selected CaptureItem and the capture of frames is initiated by calling 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 eventos de captura de gráficosHandle graphics capture events

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.In the previous step we registered two handlers for graphics capture events and set up some events to help manage the flow of the capture loop.

El evento FrameArrived se genera cuando Direct3D11CaptureFramePool tiene disponible un nuevo fotograma capturado.The FrameArrived event is raised when the Direct3D11CaptureFramePool has a new captured frame available. En el controlador de este evento, llame a TryGetNextFrame en el remitente para obtener el siguiente fotograma capturado.In the handler for this event, call TryGetNextFrame on the sender to get the next captured frame. Una vez recuperado el fotograma, se establece el _frameEvent para que el bucle de captura sepa que hay un nuevo marco disponible.After the frame is retrieved, we set the _frameEvent so that our capture loop knows there is a new frame available.

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

En el controlador de eventos Closed , se indica el _closedEvent para que el bucle de captura sepa cuándo detenerse.In the Closed event handler, we signal the _closedEvent so that the capture loop will know when to stop.

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

Esperar nuevos marcosWait for new frames

El método auxiliar WaitForNewFrame descrito en esta sección es donde se produce el pesado trabajo del bucle de captura.The WaitForNewFrame helper method described in this section is where the heavy-lifting of the capture loop occurs. Recuerde que se llama a este método desde el controlador de eventos OnMediaStreamSourceSampleRequested cada vez que el MediaStreamSource está listo para que se agregue un nuevo marco a la secuencia de vídeo.Remember, this method is called from the OnMediaStreamSourceSampleRequested event handler whenever the MediaStreamSource is ready for a new frame to be added to the video stream. En un nivel alto, esta función simplemente copia cada fotograma de vídeo capturado en pantalla de una superficie de Direct3D a otra, de modo que se pueda pasar a MediaStreamSource para la codificación mientras se captura un nuevo fotograma.At a high-level, this function simply copies each screen-captured video frame from one Direct3D surface to another so that it can be passed into the MediaStreamSource for encoding while a new frame is being captured. En este ejemplo se usa la biblioteca SharpDX para realizar la operación de copia real.This example uses the SharpDX library to perform the actual copy operation.

Antes de esperar a un nuevo fotograma, el método desecha cualquier fotograma anterior almacenado en la variable de clase, _currentFramey restablece el _frameEvent.Before waiting for a new frame, the method disposes of any previous frame stored in the class variable, _currentFrame, and resets the _frameEvent. A continuación, el método espera a que se Señalice el _frameEvent o el _closedEvent .Then the method waits for either the _frameEvent or the _closedEvent to be signaled. Si se establece el evento closed, la aplicación llama a un método auxiliar para limpiar los recursos de captura.If the closed event is set, then the app calls a helper method to cleanup the capture resources. Este método se muestra más adelante en este artículo.This method is shown later in this article.

Si se establece el evento Frame, sabemos que se ha llamado al controlador de eventos FrameArrived definido en el paso anterior y que comenzamos el proceso de copia de los datos de fotogramas capturados en una superficie de Direct3D 11 que se pasará a MediaStreamSource.If the frame event is set, then we know that the FrameArrived event handler defined in the previous step has been called, and we begin the process of copying the captured frame data into a Direct3D 11 surface that will be passed to the MediaStreamSource.

En este ejemplo se usa una clase auxiliar, SurfaceWithInfo, que simplemente nos permite pasar el fotograma de vídeo y la hora del sistema del fotograma, ambos requeridos por MediaStreamSource , como un solo objeto.This example uses a helper class, SurfaceWithInfo, which simply allows us to pass the video frame and the system time of the frame - both required by the MediaStreamSource - as a single object. El primer paso del proceso de copia de fotogramas consiste en crear una instancia de esta clase y establecer la hora del sistema.The first step of the frame copy process is to instantiate this class and set the system time.

Los pasos siguientes son la parte de este ejemplo que se basa específicamente en la biblioteca SharpDX.The next steps are the part of this example that relies specifically on the SharpDX library. Las funciones auxiliares que se usan aquí se definen al final de este artículo.The helper functions used here are defined at the end of this article. En primer lugar, usamos MultiThreadLock para asegurarse de que ningún otro subproceso tiene acceso al búfer de fotogramas de vídeo mientras hacemos la copia.First we use the MultiThreadLock to make sure no other threads access the video frame buffer while we are making the copy. A continuación, llamamos al método auxiliar CreateSharpDXTexture2D para crear un objeto SharpDX Texture2D desde el fotograma de vídeo.Next, we call the helper method CreateSharpDXTexture2D to create a SharpDX Texture2D object from the video frame. Será la textura de origen para la operación de copia.This will be the source texture for the copy operation.

A continuación, copiaremos del objeto Texture2D creado en el paso anterior en la textura de composición que hemos creado anteriormente en el proceso.Next, we copy from the Texture2D object created in the previous step into the composition texture we created earlier in the process. Esta textura de composición actúa como búfer de intercambio para que el proceso de codificación pueda funcionar en los píxeles mientras se captura el siguiente fotograma.This composition texture acts as a swap buffer so that the encoding process can operate on the pixels while the next frame is being captured. 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, a continuación, llamamos a CopySubresourceRegion para copiar realmente los píxeles en la textura de composición.To perform the copy, we clear the render target view associated with the composition texture, then we define the region within the texture we want to copy - the entire texture in this case, and then we call CopySubresourceRegion to actually copy the pixels to the composition texture.

Creamos una copia de la descripción de textura que se va a usar cuando se crea la textura de destino, pero se modifica la descripción y se establece BindFlags en RenderTarget para que la nueva textura tenga acceso de escritura.We create a copy of the texture description to use when we create our target texture, but the description is modified, setting the BindFlags to RenderTarget so that the new texture has write access. Establecer CpuAccessFlags en None permite que el sistema optimice la operación de copia.Setting the CpuAccessFlags to None allows the system to optimize the copy operation. 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.The texture description is used to create a new texture resource and the composition texture resource is copied into this new resource with a call to CopyResource. Por último, se llama a CreateDirect3DSurfaceFromSharpDXTexture para crear el objeto IDirect3DSurface que se devuelve desde este método.Finally, CreateDirect3DSurfaceFromSharpDXTexture is called to create the IDirect3DSurface object that is returned from this method.

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

Detener la captura y limpiar los recursosStop capture and clean up resources

El método Stop proporciona una manera de detener la operación de captura.The Stop method provides a way to stop the capture operation. 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.Your app may call this programmatically or in response to a user interaction, like a button click. Este método simplemente establece el _closedEvent.This method simply sets the _closedEvent. El método WaitForNewFrame definido en los pasos anteriores busca este evento y, si se establece, cierra la operación de captura.The WaitForNewFrame method defined in the previous steps looks for this event and, if set, shuts down the capture operation.

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

El método Cleanup se usa para eliminar correctamente los recursos creados durante la operación de copia.The Cleanup method is used to properly dispose of the resources that were created during the copy operation. Esto incluye:This includes:

  • El objeto Direct3D11CaptureFramePool usado por la sesión de capturaThe Direct3D11CaptureFramePool object used by the capture session
  • GraphicsCaptureSession y GraphicsCaptureItemThe GraphicsCaptureSession and GraphicsCaptureItem
  • Dispositivos Direct3D y SharpDXThe Direct3D and SharpDX devices
  • La vista de destino de representación y textura SharpDX utilizada en la operación de copia.The SharpDX texture and render target view used in the copy operation.
  • Direct3D11CaptureFrame que se usa para almacenar el marco actual.The Direct3D11CaptureFrame used for storing the current frame.
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 auxiliaresHelper wrapper classes

Las siguientes clases auxiliares se definieron para ayudar con el código de ejemplo de este artículo.The following helper classes were defined to help with the example code in this article.

La clase auxiliar MultithreadLock incluye la clase multiproceso SharpDX que garantiza que otros subprocesos no tengan acceso a los recursos de textura mientras se copian.The MultithreadLock helper class wraps the SharpDX Multithread class that makes sure that other threads don't access the texture resources while being copied.

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 un IDirect3DSurface con un SystemRelativeTime que representa la trama capturada y la hora en que se capturó, respectivamente.SurfaceWithInfo is used to associate an IDirect3DSurface with a SystemRelativeTime representing the a captured frame and the time it was captured, respectively.

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 SharpDXDirect3D and SharpDX helper APIs

Las siguientes API auxiliares están definidas para abstraer la creación de recursos de Direct3D y SharpDX.The following helper APIs are defined to abstract out the creation of Direct3D and SharpDX resources. Una explicación detallada de estas tecnologías está fuera del ámbito de este artículo, pero aquí se proporciona el código para que pueda implementar el código de ejemplo que se muestra en el tutorial.A detailed explanation of these technologies is outside the scope of this article but the code is provided here to allow you to implement the example code shown in the walkthrough.

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

Vea tambiénSee also