Bildschirmaufnahme in VideoScreen capture to video

In diesem Artikel wird beschrieben, wie Sie Frames, die auf dem Bildschirm aufgezeichnet wurden, mit den Windows. Graphics. Capture-APIs in eine Videodatei codieren.This article describes how to encode frames captured from the screen with the Windows.Graphics.Capture APIs to a video file. Weitere Informationen zum Aufzeichnen von Bildschirmen finden Sie unter screeen Capture.For information on screen capturing still images, see Screeen capture. Eine einfache End-to-End-Beispiel-APP, die die in diesem Artikel gezeigten Konzepte und Techniken verwendet, finden Sie unter simplerecorder.For a simple end-to-end sample app that utilizes the concepts and techniques shown in this article, see SimpleRecorder.

Übersicht über den Video ErfassungsprozessOverview of the video capture process

Dieser Artikel enthält eine exemplarische Vorgehensweise für eine Beispiel-APP, die den Inhalt eines Fensters in einer Videodatei aufzeichnet.This article provides a walkthrough of an example app that records the contents of a window to a video file. Obwohl es möglicherweise so aussieht, als ob ein Großteil des Codes erforderlich ist, um dieses Szenario zu implementieren, ist die Struktur auf hoher Ebene einer screenrecorder-APP ziemlich einfach.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. Der Bildschirm Aufzeichnungsprozess verwendet drei primäre UWP-Features:The screen capture process uses three primary UWP features:

Der in diesem Artikel gezeigte Beispielcode kann in verschiedene Aufgaben eingeteilt werden:The example code shown in this article can be categorized into a few different tasks:

  • Initialisierung : Dies umfasst das Konfigurieren der oben beschriebenen UWP-Klassen, das Initialisieren der Grafikgeräte Schnittstellen, das Auswählen eines zu erfassenden Fensters und das Einrichten der Codierungs Parameter wie Auflösung und Framerate.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.
  • Ereignishandler und Threading : der primäre Treiber der Haupt Erfassungs Schleife ist MediaStreamSource , das Frames in regelmäßigen Abständen über das samplerequessierte Ereignis anfordert.Event handlers and threading - The primary driver of the main capture loop is the MediaStreamSource which requests frames periodically through the SampleRequested event. In diesem Beispiel werden Ereignisse verwendet, um die Anforderungen für neue Frames zwischen den verschiedenen Komponenten des Beispiels zu koordinieren.This example uses events to coordinate the requests for new frames between the different components of the example. Die Synchronisierung ist wichtig, um das gleichzeitige erfassen und Codieren von Frames zuzulassen.Synchronization is important to allow frames to be captured and encoded simultaneously.
  • Das Kopieren von Frames -Frames wird aus dem Aufzeichnungs Frame Puffer in eine separate Direct3D-Oberfläche kopiert, die an MediaStreamSource übertragen werden kann, damit die Ressource nicht während der Codierung überschrieben wird.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. Direct3D-APIs werden verwendet, um diesen Kopiervorgang schnell auszuführen.Direct3D APIs are used to perform this copy operation quickly.

Informationen zu den Direct3D-APIsAbout the Direct3D APIs

Wie bereits erwähnt, ist das Kopieren der einzelnen aufgezeichneten Frames wahrscheinlich der komplexeste Teil der in diesem Artikel gezeigten Implementierung.As stated above, the copying of each captured frame is probably the most complex part of the implementation shown in this article. Auf niedriger Ebene wird dieser Vorgang mithilfe von Direct3D durchgeführt.At a low level, this operation is done using Direct3D. In diesem Beispiel verwenden wir die sharpdx -Bibliothek, um die Direct3D-Vorgänge aus c# auszuführen.For this example, we are using the SharpDX library to perform the Direct3D operations from C#. Diese Bibliothek wird nicht mehr offiziell unterstützt, aber Sie wurde ausgewählt, da die Leistung bei Kopier Vorgängen auf niedriger Ebene für dieses Szenario gut geeignet ist.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. Wir haben versucht, die Direct3D-Vorgänge so diskret wie möglich zu halten, damit Sie Ihren eigenen Code oder andere Bibliotheken für diese Aufgaben leichter ersetzen können.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.

Einrichten Ihres ProjektsSetting up your project

Der Beispielcode in dieser exemplarischen Vorgehensweise wurde mithilfe der c#-Projektvorlage leere app (universelle Windows-APP) in Visual Studio 2019 erstellt.The example code in this walkthrough was created using the Blank App (Universal Windows) C# project template in Visual Studio 2019. Um die Windows. Graphics. Capture -APIs in Ihrer APP zu verwenden, müssen Sie die Grafik Aufzeichnungs Funktion in die Datei "Package. appxmanifest" für Ihr Projekt einschließen.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. In diesem Beispiel werden generierte Videodateien in der Video Bibliothek auf dem Gerät gespeichert.This example saves generated video files to the Videos Library on the device. Um auf diesen Ordner zuzugreifen, müssen Sie die Funktion für die Video Bibliothek einschließen.To access this folder you must include the Videos Library capability.

Wählen Sie zum Installieren des sharpdx-nuget-Pakets in Visual Studio nuget-Pakete verwaltenaus.To install the SharpDX Nuget package, in Visual Studio select Manage Nuget Packages. Suchen Sie auf der Registerkarte Durchsuchen nach dem Paket "sharpdx. Direct3D11", und klicken Sie auf Installieren.In the Browse tab search for the "SharpDX.Direct3D11" package and click Install.

Beachten Sie, dass der Code in der exemplarischen Vorgehensweise, um die Größe der Code Auflistungen in diesem Artikel zu reduzieren, explizite Namespace Verweise und die Deklaration von MainPage-Klassenmember-Variablen, die mit dem führenden Unterstrich "" benannt sind, unterbricht.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, "".

Setup für CodierungSetup for encoding

Mit der in diesem Abschnitt beschriebenen setupcoding -Methode werden einige der Hauptobjekte initialisiert, die zum Erfassen und Codieren von Video Frames und zum Einrichten der Codierungs Parameter für erfasste Videos verwendet werden.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. Diese Methode kann Programm gesteuert oder als Reaktion auf eine Benutzerinteraktion aufgerufen werden, z. b. ein Klick auf die Schaltfläche.This method could be called programmatically or in response to a user interaction like a button click. Das Codelisting für setupcoding wird unten nach den Beschreibungen der Initialisierungs Schritte angezeigt.The code listing for SetupEncoding is shown below after the descriptions of the initialization steps.

  • Überprüfen Sie die Erfassungs Unterstützung.Check for capture support. Bevor Sie mit dem Erfassungsprozess beginnen, müssen Sie graphicscapturesession. IsSupported aufrufen, um sicherzustellen, dass die Bildschirmaufnahme Funktion auf dem aktuellen Gerät unterstützt wird.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.

  • Initialisieren Sie Direct3D-Schnittstellen.Initialize Direct3D interfaces. In diesem Beispiel wird Direct3D verwendet, um die vom Bildschirm aufgezeichneten Pixel in eine Textur zu kopieren, die als Videoframe codiert ist.This sample uses Direct3D to copy the pixels captured from the screen into a texture that is encoded as a video frame. Weiter unten in diesem Artikel werden die Hilfsmethoden verwendet, die zum Initialisieren der Direct3D-Schnittstellen, CreateD3DDevice und " kreatesharpdxdevice" verwendet werden.The helper methods used to initialize the Direct3D interfaces, CreateD3DDevice and CreateSharpDXDevice, are shown later in this article.

  • Initialisieren Sie ein graphicscaptureitem-Element.Initialize a GraphicsCaptureItem. Ein graphicscaptureitem -Element stellt ein Element auf dem Bildschirm dar, das aufgezeichnet wird, entweder ein Fenster oder den gesamten Bildschirm.A GraphicsCaptureItem represents an item on the screen that is going to be captured, either a window or the entire screen. Ermöglicht es dem Benutzer, ein Element auszuwählen, das erfasst werden soll, indem ein graphicscapturepicker erstellt und picksingleitemasyncaufgerufen wird.Allow the user to pick an item to capture by creating a GraphicsCapturePicker and calling PickSingleItemAsync.

  • Erstellen Sie eine Kompositions Textur.Create a composition texture. Erstellen Sie eine Textur Ressource und eine zugeordnete renderzielansicht, die verwendet wird, um die einzelnen Video Frames zu kopieren.Create a texture resource and an associated render target view that will be used to copy each video frame. Diese Textur kann erst erstellt werden, nachdem das graphicscaptureitem -Element erstellt und die zugehörigen Dimensionen bekannt sind.This texture can't be created until the GraphicsCaptureItem has been created and we know its dimensions. Weitere Informationen zur Verwendung dieser Kompositions Textur finden Sie in der Beschreibung von waitfornewframe .See the description of the WaitForNewFrame to see how this composition texture is used. Die Hilfsmethode zum Erstellen dieser Textur wird auch weiter unten in diesem Artikel gezeigt.The helper method for creating this texture is also shown later in this article.

  • Erstellen Sie ein mediaencodingprofile-und videostreamdescriptor-Element.Create a MediaEncodingProfile and VideoStreamDescriptor. Eine Instanz der MediaStreamSource -Klasse übernimmt Bilder, die auf dem Bildschirm aufgezeichnet werden, und codiert Sie in einen Videostream.An instance of the MediaStreamSource class will take images captured from the screen and encode them into a video stream. Anschließend wird der Videostream von der mediatranscoder -Klasse in eine Videodatei transcodiert.Then, the video stream will be transcoded into a video file by the MediaTranscoder class. Ein videostreamdecriptor stellt Codierungs Parameter (z. b. Auflösung und Framerate) für MediaStreamSourcebereit.A VideoStreamDecriptor provides encoding parameters, such as resolution and frame rate, for the MediaStreamSource. Die Videodatei Codierungs Parameter für mediatranscoder werden mit einem mediaencodingprofileangegeben.The video file encoding parameters for the MediaTranscoder are specified with a MediaEncodingProfile. Beachten Sie, dass die für die Videocodierung verwendete Größe nicht mit der Größe des erfassten Fensters identisch sein muss. um dieses Beispiel einfach zu halten, sind die Codierungs Einstellungen jedoch hart codiert, um die tatsächlichen Dimensionen des Erfassungs Elements zu verwenden.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.

  • Erstellen Sie die Objekte MediaStreamSource und mediatranscoder.Create the MediaStreamSource and MediaTranscoder objects. Wie bereits erwähnt, codiert das MediaStreamSource -Objekt einzelne Frames in einen Videostream.As mentioned above, the MediaStreamSource object encodes individual frames into a video stream. Nennen Sie den Konstruktor für diese Klasse, und übergeben Sie das im vorherigen Schritt erstellte mediaencodingprofile .Call the constructor for this class, passing in the MediaEncodingProfile created in the previous step. Legen Sie die Pufferzeit auf NULL fest, und registrieren Sie die Handler für das Start -und das samplerequessierte -Ereignis, das weiter unten in diesem Artikel dargestellt wird.Set the buffer time to zero and register handlers for the Starting and SampleRequested events, which will be shown later in this article. Erstellen Sie als nächstes eine neue Instanz der mediatranscoder -Klasse, und aktivieren Sie die Hardwarebeschleunigung.Next, construct a new instance of the MediaTranscoder class and enable hardware acceleration.

  • Erstellen einer Ausgabedatei Der letzte Schritt in dieser Methode besteht darin, eine Datei zu erstellen, in die das Video transcodiert wird.Create an output file The final step in this method is to create a file to which the video will be transcoded. In diesem Beispiel erstellen wir einfach eine eindeutig benannte Datei im Ordner "Videos Library" auf dem Gerät.For this example, we will just create a uniquely named file in the Videos Library folder on the device. Beachten Sie, dass Ihre APP für den Zugriff auf diesen Ordner die "Videos Library"-Funktion im App-Manifest angeben muss.Note that in order to access this folder, your app must specify the "Videos Library" capability in the app manifest. Nachdem die Datei erstellt wurde, öffnen Sie Sie für Lese-und Schreibvorgänge, und übergeben Sie den resultierenden Stream an die encodeasync -Methode, die als nächstes angezeigt wird.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;
    }
}

Codierung startenStart encoding

Nachdem die Hauptobjekte initialisiert wurden, wird die encodeasync -Methode implementiert, um den Aufzeichnungs Vorgang zu starten.Now that the main objects have been initialized the EncodeAsync method is implemented to kick off the capture operation. Diese Methode prüft zunächst, ob Sie nicht bereits aufzeichnen, und wenn nicht, ruft Sie die Hilfsmethode startcapture auf, um mit dem Erfassen von Frames auf dem Bildschirm zu beginnen.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. Diese Methode wird weiter unten in diesem Artikel gezeigt.This method is shown later in this article. Als nächstes wird preparemediastreamsourcetranscodeasync aufgerufen, um den mediatranscoder zum transcodieren des Videodaten Stroms bereitzustellen, der vom MediaStreamSource -Objekt in den ausgabedateistream erstellt wurde. dabei wird das Codierungs Profil verwendet, das im vorherigen Abschnitt erstellt wurde.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. Nachdem der Transcoder vorbereitet wurde, können Sie transcodeasync aufrufen, um die Transcodierung zu starten.Once the transcoder has been prepared, call TranscodeAsync to start transcoding. Weitere Informationen zur Verwendung von mediatranscoderfinden Sie unter transcodieren von Mediendateien.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();
    }
}

Verarbeiten von MediaStreamSource-EreignissenHandle MediaStreamSource events

Das MediaStreamSource -Objekt übernimmt Frames, die auf dem Bildschirm aufgezeichnet werden, und transformiert Sie in einen Videostream, der mithilfe von mediatranscoderin einer Datei gespeichert werden kann.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. Die Frames werden mithilfe von Handlern für die Ereignisse des Objekts an die MediaStreamSource übergeben.We pass the frames to the MediaStreamSource via handlers for the object's events.

Das samplerequraised -Ereignis wird ausgelöst, wenn MediaStreamSource für einen neuen Videoframe bereit ist.The SampleRequested event is raised when the MediaStreamSource is ready for a new video frame. Nachdem Sie sichergestellt haben, dass wir gerade aufzeichnen, wird die Hilfsmethode waitfornewframe aufgerufen, um einen neuen Frame, der vom Bildschirm aufgezeichnet wird, zu erhalten.After making sure we are currently recording, the helper method WaitForNewFrame is called to get a new frame captured from the screen. Diese Methode, die weiter unten in diesem Artikel gezeigt wird, gibt ein ID3D11Surface -Objekt zurück, das den aufgezeichneten Frame enthält.This method, shown later in this article, returns a ID3D11Surface object containing the captured frame. In diesem Beispiel wrappen wir die IDirect3DSurface -Schnittstelle in einer Hilfsklasse, die auch die Systemzeit speichert, zu der der Frame aufgezeichnet wurde.For this example, we wrap the IDirect3DSurface interface in a helper class that also stores the system time at which the frame was captured. Sowohl die Frame-als auch die Systemzeit werden an die MediaStreamSample. CreateFromDirect3D11Surface Factory-Methode und das resultierende MediaStreamSample -Objekt auf die mediastreamsourcesamplerequest. Sample -Eigenschaft von mediastreamsourcesamplerequestedeventargsfestgelegt.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. Auf diese Weise wird der erfasste Frame für MediaStreamSourcebereitgestellt.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();
    }
}

Im-Handler für das Start Ereignis rufen wir waitfornewframeauf, übergeben jedoch nur die Systemzeit, in der der Frame aufgezeichnet wurde, an die mediastreamsourcestartingrequest. setactualstartposition -Methode, die von MediaStreamSource verwendet wird, um die zeitliche Steuerung der nachfolgenden Frames ordnungsgemäß zu codieren.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);
    }
}

Erfassung startenStart capturing

Die in diesem Schritt gezeigte startcapture -Methode wird von der in einem vorherigen Schritt gezeigten Hilfsmethode encodeasync aufgerufen.The StartCapture method shown in this step is called from the EncodeAsync helper method shown in a previous step. Zuerst initialisiert diese Methode eine Reihe von Ereignis Objekten, die zum Steuern des Ablaufs des Aufzeichnungs Vorgangs verwendet werden.First, this method initializes up a set of event objects that are used to control the flow of the capture operation.

  • _multithread ist eine Hilfsklasse, die das multithreadobjekt der sharpdx-Bibliothek umwickelt, das verwendet wird, um sicherzustellen, dass keine anderen Threads auf die sharpdx-Textur zugreifen, während Sie kopiert werden._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 wird verwendet, um zu signalisieren, dass ein neuer Frame aufgezeichnet wurde und an MediaStreamSource übermittelt werden kann._frameEvent is used to signal that a new frame has been captured and can be passed to the MediaStreamSource
  • _closedEvent signalisiert, dass die Aufzeichnung beendet wurde und dass wir nicht auf neue Frames warten sollten._closedEvent signals that recording has stopped and that we shouldn't wait for any new frames.

Das Frame Ereignis und das geschlossene Ereignis werden einem Array hinzugefügt, sodass wir auf eines dieser Elemente in der Erfassungs Schleife warten können.The frame event and closed event are added to an array so we can wait for either one of them in the capture loop.

Der Rest der startcapture -Methode richtet die Windows. Graphics. Capture-APIs ein, die die tatsächliche Bildschirm Erfassung durchführen.The rest of the StartCapture method sets up the Windows.Graphics.Capture APIs that will do the actual screen capturing. Zuerst wird ein Ereignis für das captureitem. Closed -Ereignis registriert.First, an event is registered for the CaptureItem.Closed event. Als nächstes wird ein Direct3D11CaptureFramePool erstellt, mit dem mehrere erfasste Frames gleichzeitig gepuffert werden können.Next, a Direct3D11CaptureFramePool is created, which allows multiple captured frames to be buffered at a time. Die Methode " foratefreethread" wird zum Erstellen des Frame Pools verwendet, sodass das framekam -Ereignis für den eigenen Workerthread des Pools aufgerufen wird und nicht für den Haupt Thread der app.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. Als nächstes wird ein Handler für das framekam -Ereignis registriert.Next, a handler is registered for the FrameArrived event. Zum Schluss wird eine graphicscapturesession für das ausgewählte captureitem erstellt, und die Erfassung von Frames wird durch Aufrufen von " startcapture" initiiert.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();
}

Behandeln von Grafik Aufzeichnungs EreignissenHandle graphics capture events

Im vorherigen Schritt haben wir zwei Handler für Grafik Aufzeichnungs Ereignisse registriert und einige Ereignisse eingerichtet, um die Verwaltung des Ablaufs der Erfassungs Schleife zu unterstützen.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.

Das framekam -Ereignis wird ausgelöst, wenn für Direct3D11CaptureFramePool ein neuer erfasster Frame verfügbar ist.The FrameArrived event is raised when the Direct3D11CaptureFramePool has a new captured frame available. Im Handler für dieses Ereignis wird trygetnextframe für den Absender aufgerufen, um den nächsten aufgezeichneten Frame abzurufen.In the handler for this event, call TryGetNextFrame on the sender to get the next captured frame. Nachdem der Frame abgerufen wurde, legen wir die _frameEvent so fest, dass die Erfassungs Schleife weiß, dass ein neuer Frame verfügbar ist.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();
}

Im Closed -Ereignishandler signalisieren wir die _closedEvent , damit die Aufzeichnungs Schleife weiß, wann Sie beendet werden soll.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();
}

Auf neue Frames wartenWait for new frames

Die in diesem Abschnitt beschriebene Hilfsmethode " waitfornewframe " ist der Ort, an dem die Erfassungs Schleife stark ausgelastet ist.The WaitForNewFrame helper method described in this section is where the heavy-lifting of the capture loop occurs. Beachten Sie, dass diese Methode vom onmediastreamsourcesamplerequtry- Ereignishandler aufgerufen wird, wenn MediaStreamSource bereit ist, um dem Videostream einen neuen Frame hinzuzufügen.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. Auf hoher Ebene kopiert diese Funktion einfach jeden Bildschirm, auf den der Bildschirm aufgezeichnet wird, von einer Direct3D-Oberfläche in eine andere, sodass Sie an die MediaStreamSource für die Codierung geleitet werden kann, während ein neuer Frame aufgezeichnet wird.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. In diesem Beispiel wird die sharpdx-Bibliothek verwendet, um den eigentlichen Kopiervorgang auszuführen.This example uses the SharpDX library to perform the actual copy operation.

Bevor auf einen neuen Frame gewartet wird, gibt die Methode einen beliebigen vorherigen Frame aus, der in der Klassen Variablen gespeichert ist, _currentFrameund setzt die _frameEventzurück.Before waiting for a new frame, the method disposes of any previous frame stored in the class variable, _currentFrame, and resets the _frameEvent. Dann wartet die Methode darauf, dass entweder die _frameEvent oder die _closedEvent signalisiert werden.Then the method waits for either the _frameEvent or the _closedEvent to be signaled. Wenn das Closed-Ereignis festgelegt ist, ruft die APP eine Hilfsmethode auf, um die Erfassungs Ressourcen zu bereinigen.If the closed event is set, then the app calls a helper method to cleanup the capture resources. Diese Methode wird weiter unten in diesem Artikel gezeigt.This method is shown later in this article.

Wenn das Frame-Ereignis festgelegt ist, wissen wir, dass der im vorherigen Schritt definierte framekam -Ereignishandler aufgerufen wurde, und wir beginnen mit dem Kopieren der aufgezeichneten Frame Daten in eine Direct3D 11-Oberfläche, die an die MediaStreamSource-Schnittstelle weitergeleitet wird.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.

In diesem Beispiel wird die Hilfsklasse " surfacewithinfo" verwendet, die es uns einfach ermöglicht, den Videoframe und die Systemzeit des Frames, beides von MediaStreamSource , als einzelnes Objekt zu übergeben.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. Der erste Schritt des Frame Kopiervorgangs besteht darin, diese Klasse zu instanziieren und die Systemzeit festzulegen.The first step of the frame copy process is to instantiate this class and set the system time.

Die nächsten Schritte sind der Teil dieses Beispiels, der speziell auf die sharpdx-Bibliothek basiert.The next steps are the part of this example that relies specifically on the SharpDX library. Die hier verwendeten Hilfsfunktionen werden am Ende dieses Artikels definiert.The helper functions used here are defined at the end of this article. Zuerst verwenden wir das multithreadlock , um sicherzustellen, dass keine anderen Threads auf den Video Frame Puffer zugreifen, während wir die Kopie erstellen.First we use the MultiThreadLock to make sure no other threads access the video frame buffer while we are making the copy. Als Nächstes rufen wir die Hilfsmethode CreateSharpDXTexture2D auf, um ein sharpdx Texture2D -Objekt aus dem Videoframe zu erstellen.Next, we call the helper method CreateSharpDXTexture2D to create a SharpDX Texture2D object from the video frame. Dies ist die Quell Textur für den Kopiervorgang.This will be the source texture for the copy operation.

Im nächsten Schritt kopieren wir aus dem im vorherigen Schritt erstellten Texture2D -Objekt in die Kompositionsstruktur, die wir zuvor in diesem Prozess erstellt haben.Next, we copy from the Texture2D object created in the previous step into the composition texture we created earlier in the process. Diese Kompositions Textur fungiert als Auslagerungs Puffer, sodass der Codierungsprozess in den Pixeln ausgeführt werden kann, während der nächste Frame aufgezeichnet wird.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. Um die Kopie auszuführen, löschen wir die renderzielansicht, die der Kompositions Textur zugeordnet ist, und definieren dann den Bereich innerhalb der Textur, den wir kopieren möchten (in diesem Fall die gesamte Textur). Anschließend wird copysubresourceregion aufgerufen, um die Pixel tatsächlich in die Kompositions Textur zu kopieren.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.

Wir erstellen eine Kopie der Textur Beschreibung, die beim Erstellen der Ziel Textur verwendet werden soll. die Beschreibung wird jedoch geändert, wobei " bindflags " auf renderTarget festgelegt wird, sodass die neue Textur Schreibzugriff hat.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. Wenn Sie " cpuaccessflags " auf " None " festlegen, kann das System den Kopiervorgang optimieren.Setting the CpuAccessFlags to None allows the system to optimize the copy operation. Die Textur Beschreibung wird zum Erstellen einer neuen Textur Ressource verwendet, und die Kompositions Textur Ressource wird mithilfe eines Aufrufes copyresourcein diese neue Ressource kopiert.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. Schließlich wird CreateDirect3DSurfaceFromSharpDXTexture aufgerufen, um das IDirect3DSurface -Objekt zu erstellen, das von dieser Methode zurückgegeben wird.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;
}

Erfassung und Bereinigung von Ressourcen AbbrechenStop capture and clean up resources

Die Methode zum Abbrechen bietet eine Möglichkeit, den Aufzeichnungs Vorgang zu unterbinden.The Stop method provides a way to stop the capture operation. Ihre APP kann diese Programm gesteuert oder als Reaktion auf eine Benutzerinteraktion (z. b. einen Klick auf die Schaltfläche) abrufen.Your app may call this programmatically or in response to a user interaction, like a button click. Diese Methode legt einfach den _closedEventfest.This method simply sets the _closedEvent. Die in den vorherigen Schritten definierte waitfornewframe -Methode sucht nach diesem Ereignis und fährt, falls festgelegt, den Aufzeichnungs Vorgang herunter.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();
}

Die Bereinigungs Methode wird verwendet, um die Ressourcen, die während des Kopiervorgangs erstellt wurden, ordnungsgemäß zu verwerfen.The Cleanup method is used to properly dispose of the resources that were created during the copy operation. Dies schließt Folgendes ein:This includes:

  • Das von der Erfassungs Sitzung verwendete Direct3D11CaptureFramePool -Objekt.The Direct3D11CaptureFramePool object used by the capture session
  • Graphicscapturesession und graphicscaptureitemThe GraphicsCaptureSession and GraphicsCaptureItem
  • Die Direct3D-und sharpdx-GeräteThe Direct3D and SharpDX devices
  • Die im Kopiervorgang verwendete sharpdx-Textur-und renderzielansicht.The SharpDX texture and render target view used in the copy operation.
  • Die Direct3D11CaptureFrame , die zum Speichern des aktuellen Frames verwendet werden.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();
}

Wrapper Klassen für HelperHelper wrapper classes

Die folgenden Hilfsklassen wurden definiert, um den Beispielcode in diesem Artikel zu unterstützen.The following helper classes were defined to help with the example code in this article.

Die multithreadlock -Hilfsklasse umschließt die sharpdx- multithreadklasse , mit der sichergestellt wird, dass andere Threads beim Kopieren nicht auf die Textur Ressourcen zugreifen.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 " wird verwendet, um eine IDirect3DSurface mit einer systemrelativetime zuzuordnen, die den aufgezeichneten Frame und die erfasste Zeit darstellt.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;
    }
}

Direct3D-und sharpdx-HilfsobjekteDirect3D and SharpDX helper APIs

Die folgenden hilfsapis werden definiert, um die Erstellung von Direct3D-und sharpdx-Ressourcen zu abstrahieren.The following helper APIs are defined to abstract out the creation of Direct3D and SharpDX resources. Eine ausführliche Erläuterung dieser Technologien ist nicht Gegenstand dieses Artikels, aber der Code wird hier bereitgestellt, damit Sie den in der exemplarischen Vorgehensweise gezeigten Beispielcode implementieren können.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;
    }
}

Weitere InformationenSee also