비디오로 화면 캡처Screen capture to video

이 문서에서는 Windows. Graphics. Capture Api를 사용 하 여 화면에서 캡처한 프레임을 비디오 파일로 인코딩하는 방법을 설명 합니다.This article describes how to encode frames captured from the screen with the Windows.Graphics.Capture APIs to a video file. 화면 캡처에 대 한 자세한 내용은 Screeen 캡처를 참조 하세요.For information on screen capturing still images, see Screeen capture. 이 문서에 나와 있는 개념 및 기술을 활용 하는 간단한 종단 간 샘플 앱은 SimpleRecorder를 참조 하세요.For a simple end-to-end sample app that utilizes the concepts and techniques shown in this article, see SimpleRecorder.

비디오 캡처 프로세스 개요Overview of the video capture process

이 문서에서는 비디오 파일에 창의 콘텐츠를 기록 하는 예제 앱의 연습을 제공 합니다.This article provides a walkthrough of an example app that records the contents of a window to a video file. 이 시나리오를 구현 하는 데 필요한 많은 코드가 있는 것 처럼 보일 수 있지만, 화면 레코더 앱의 상위 수준 구조는 매우 간단 합니다.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. 화면 캡처 프로세스에서는 세 가지 기본 UWP 기능을 사용 합니다.The screen capture process uses three primary UWP features:

  • GraphicsCapture api는 실제로 화면에서 픽셀을 가져오는 작업을 수행 합니다.The Windows.GraphicsCapture APIs do the work of actually grabbing the pixels from the screen. GraphicsCaptureItem 클래스는 캡처 중인 창이 나 표시를 나타냅니다.The GraphicsCaptureItem class represents the window or display being captured. GraphicsCaptureSession 는 캡처 작업을 시작 하 고 중지 하는 데 사용 됩니다.GraphicsCaptureSession is used to start and stop the capture operation. Direct3D11CaptureFramePool 클래스는 화면 내용이 복사 되는 프레임의 버퍼를 유지 관리 합니다.The Direct3D11CaptureFramePool class maintains a buffer of frames into which the screen contents are copied.
  • Mediastreamsource 클래스는 캡처된 프레임을 받고 비디오 스트림을 생성 합니다.The MediaStreamSource class receives the captured frames and generates a video stream.
  • MediaTranscoder 클래스는 mediastreamsource 에 의해 생성 된 스트림을 받아서 비디오 파일로 인코딩합니다.The MediaTranscoder class receives the stream produced by the MediaStreamSource and encodes it into a video file.

이 문서에 표시 된 예제 코드는 몇 가지 다른 작업으로 분류 될 수 있습니다.The example code shown in this article can be categorized into a few different tasks:

  • 초기화 -여기에는 위에 설명 된 UWP 클래스를 구성 하 고, 그래픽 장치 인터페이스를 초기화 하 고, 캡처할 창을 선택 하 고, 해상도 및 프레임 속도와 같은 인코딩 매개 변수를 설정 하는 작업이 포함 됩니다.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.
  • 이벤트 처리기 및 스레딩 -기본 캡처 루프의 기본 드라이버는 SampleRequested 이벤트를 통해 정기적으로 프레임을 요청 하는 mediastreamsource 입니다.Event handlers and threading - The primary driver of the main capture loop is the MediaStreamSource which requests frames periodically through the SampleRequested event. 이 예제에서는 이벤트를 사용 하 여 예제의 여러 구성 요소 간의 새 프레임에 대 한 요청을 조정 합니다.This example uses events to coordinate the requests for new frames between the different components of the example. 동기화는 프레임을 동시에 캡처하고 인코딩할 수 있도록 하는 데 중요 합니다.Synchronization is important to allow frames to be captured and encoded simultaneously.
  • 프레임 복사 는 캡처 프레임 버퍼에서 mediastreamsource 에 전달 될 수 있는 별도의 Direct3D 화면으로 복사 되어 인코딩할 때 리소스를 덮어쓰지 않도록 합니다.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 Api는이 복사 작업을 빠르게 수행 하는 데 사용 됩니다.Direct3D APIs are used to perform this copy operation quickly.

Direct3D Api 정보About the Direct3D APIs

위에서 설명한 것 처럼 캡처된 각 프레임의 복사는이 문서에 표시 된 구현의 가장 복잡 한 부분입니다.As stated above, the copying of each captured frame is probably the most complex part of the implementation shown in this article. 낮은 수준에서이 작업은 Direct3D를 사용 하 여 수행 됩니다.At a low level, this operation is done using Direct3D. 이 예제에서는 SharpDX 라이브러리를 사용 하 여 c #에서 Direct3D 작업을 수행 합니다.For this example, we are using the SharpDX library to perform the Direct3D operations from C#. 이 라이브러리는 더 이상 공식적으로 지원 되지 않지만, 낮은 수준의 복사 작업에서 성능이이 시나리오에 적합 하기 때문에 선택 되었습니다.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. 이러한 작업을 위해 고유한 코드나 기타 라이브러리를 쉽게 대체할 수 있도록 Direct3D 작업을 가능한 한 불연속으로 유지 하려고 했습니다.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.

프로젝트 설정Setting up your project

이 연습의 예제 코드는 Visual Studio 2019의 비어 있는 앱 (유니버설 Windows) c # 프로젝트 템플릿을 사용 하 여 만들었습니다.The example code in this walkthrough was created using the Blank App (Universal Windows) C# project template in Visual Studio 2019. 앱에서 Appxmanifest.xml api를 사용 하려면 프로젝트의 파일에 그래픽 캡처 기능을 포함 해야 합니다.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. 이 예제에서는 생성 된 비디오 파일을 장치의 비디오 라이브러리에 저장 합니다.This example saves generated video files to the Videos Library on the device. 이 폴더에 액세스 하려면 비디오 라이브러리 기능을 포함 해야 합니다.To access this folder you must include the Videos Library capability.

SharpDX Nuget 패키지를 설치 하려면 Visual Studio에서 Nuget 패키지 관리를 선택 합니다.To install the SharpDX Nuget package, in Visual Studio select Manage Nuget Packages. 찾아보기 탭에서 "SharpDX. Direct3D11" 패키지를 검색 하 고 설치를 클릭 합니다.In the Browse tab search for the "SharpDX.Direct3D11" package and click Install.

이 문서에 있는 코드 목록의 크기를 줄이기 위해 아래 연습의 코드에서는 명시적 네임 스페이스 참조와 선행 밑줄 ""로 명명 된 MainPage 클래스 멤버 변수의 선언이 생략 됩니다.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 for encoding

이 섹션에 설명 된 Setupencoding 메서드는 비디오 프레임을 캡처하고 인코딩하고 캡처된 비디오에 대 한 인코딩 매개 변수를 설정 하는 데 사용할 몇 가지 주요 개체를 초기화 합니다.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. 이 메서드는 프로그래밍 방식으로 또는 단추 클릭과 같은 사용자 상호 작용에 대 한 응답으로 호출할 수 있습니다.This method could be called programmatically or in response to a user interaction like a button click. Setupencoding 에 대 한 코드 목록은 초기화 단계에 대 한 설명 뒤에 표시 됩니다.The code listing for SetupEncoding is shown below after the descriptions of the initialization steps.

  • 캡처 지원 확인Check for capture support. 캡처 프로세스를 시작 하기 전에 GraphicsCaptureSession 를 호출 하 여 화면 캡처 기능이 현재 장치에서 지원 되는지 확인 해야 합니다.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.

  • Direct3D 인터페이스를 초기화 합니다.Initialize Direct3D interfaces. 이 샘플에서는 Direct3D를 사용 하 여 화면에서 캡처한 픽셀을 비디오 프레임으로 인코딩된 질감으로 복사 합니다.This sample uses Direct3D to copy the pixels captured from the screen into a texture that is encoded as a video frame. CreateD3DDeviceCreateSharpDXDeviceDirect3D 인터페이스를 초기화 하는 데 사용 되는 도우미 메서드는이 문서의 뒷부분에 나와 있습니다.The helper methods used to initialize the Direct3D interfaces, CreateD3DDevice and CreateSharpDXDevice, are shown later in this article.

  • GraphicsCaptureItem를 초기화 합니다.Initialize a GraphicsCaptureItem. GraphicsCaptureItem 은 창이 나 전체 화면 중에서 캡처할 화면에 있는 항목을 나타냅니다.A GraphicsCaptureItem represents an item on the screen that is going to be captured, either a window or the entire screen. 사용자가 GraphicsCapturePicker 을 만들고 PickSingleItemAsync를 호출 하 여 캡처할 항목을 선택할 수 있습니다.Allow the user to pick an item to capture by creating a GraphicsCapturePicker and calling PickSingleItemAsync.

  • 컴퍼지션 질감을 만듭니다.Create a composition texture. 각 비디오 프레임을 복사 하는 데 사용 되는 질감 리소스 및 연결 된 렌더링 대상 뷰를 만듭니다.Create a texture resource and an associated render target view that will be used to copy each video frame. GraphicsCaptureItem 을 만들고 해당 차원을 알고 있을 때까지이 질감을 만들 수 없습니다.This texture can't be created until the GraphicsCaptureItem has been created and we know its dimensions. 이 컴퍼지션 질감이 사용 되는 방법을 보려면 Waitfornewframe 의 설명을 참조 하세요.See the description of the WaitForNewFrame to see how this composition texture is used. 이 질감을 만들기 위한 도우미 메서드는이 문서의 뒷부분에도 표시 됩니다.The helper method for creating this texture is also shown later in this article.

  • MediaEncodingProfile 및 VideoStreamDescriptor를 만듭니다.Create a MediaEncodingProfile and VideoStreamDescriptor. Mediastreamsource 클래스의 인스턴스는 화면에서 캡처된 이미지를 가져와 비디오 스트림으로 인코딩합니다.An instance of the MediaStreamSource class will take images captured from the screen and encode them into a video stream. 그런 다음 MediaTranscoder 클래스에 의해 비디오 스트림이 비디오 파일로 트랜스 코딩 됩니다.Then, the video stream will be transcoded into a video file by the MediaTranscoder class. VideoStreamDecriptormediastreamsource에 대해 해상도 및 프레임 비율과 같은 인코딩 매개 변수를 제공 합니다.A VideoStreamDecriptor provides encoding parameters, such as resolution and frame rate, for the MediaStreamSource. MediaTranscoder 에 대 한 비디오 파일 인코딩 매개 변수는 MediaEncodingProfile로 지정 됩니다.The video file encoding parameters for the MediaTranscoder are specified with a MediaEncodingProfile. 비디오 인코딩에 사용 되는 크기는 캡처 중인 창의 크기와 동일할 필요는 없지만,이 예제를 단순하게 유지 하기 위해 인코딩 설정은 캡처 항목의 실제 차원을 사용 하도록 하드 코딩 됩니다.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.

  • MediaStreamSource 및 MediaTranscoder 개체를 만듭니다.Create the MediaStreamSource and MediaTranscoder objects. 위에서 설명한 것 처럼 Mediastreamsource 개체는 개별 프레임을 비디오 스트림으로 인코딩합니다.As mentioned above, the MediaStreamSource object encodes individual frames into a video stream. 이전 단계에서 만든 MediaEncodingProfile 를 전달 하 여이 클래스에 대 한 생성자를 호출 합니다.Call the constructor for this class, passing in the MediaEncodingProfile created in the previous step. 버퍼 시간을 0으로 설정 하 고 시작SampleRequested 이벤트에 대 한 처리기를 등록 합니다 .이 내용은이 문서의 뒷부분에 나와 있습니다.Set the buffer time to zero and register handlers for the Starting and SampleRequested events, which will be shown later in this article. 그런 다음 MediaTranscoder 클래스의 새 인스턴스를 생성 하 고 하드웨어 가속을 사용 하도록 설정 합니다.Next, construct a new instance of the MediaTranscoder class and enable hardware acceleration.

  • 출력 파일 만들기 이 메서드의 마지막 단계는 비디오를 트랜스 코딩 하는 파일을 만드는 것입니다.Create an output file The final step in this method is to create a file to which the video will be transcoded. 이 예제에서는 장치의 비디오 라이브러리 폴더에 고유 하 게 명명 된 파일을 만듭니다.For this example, we will just create a uniquely named file in the Videos Library folder on the device. 이 폴더에 액세스 하기 위해 앱은 앱 매니페스트에서 "비디오 라이브러리" 기능을 지정 해야 합니다.Note that in order to access this folder, your app must specify the "Videos Library" capability in the app manifest. 파일이 만들어진 후에는 읽기 및 쓰기를 위해 열고 결과 스트림을 다음에 표시 되는 EncodeAsync 메서드로 전달 합니다.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;
    }
}

인코딩 시작Start encoding

이제 주 개체가 초기화 되었으므로 캡처 작업을 시작 하도록 EncodeAsync 메서드가 구현 됩니다.Now that the main objects have been initialized the EncodeAsync method is implemented to kick off the capture operation. 이 메서드는 먼저 기록 되지 않았는지 확인 하 고, 그렇지 않은 경우 도우미 메서드 Startcapture 를 호출 하 여 화면에서 프레임 캡처를 시작 합니다.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. 이 방법은이 문서의 뒷부분에 나와 있습니다.This method is shown later in this article. 그런 다음, PrepareMediaStreamSourceTranscodeAsync 를 호출 하 여 이전 섹션에서 만든 인코딩 프로필을 사용 하 여 mediastreamsource 개체에 의해 생성 된 비디오 스트림을 출력 파일 스트림으로 트랜스 코딩 합니다.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. 코드 변환기 준비 되 면 TranscodeAsync 를 호출 하 여 트랜스 코딩을 시작 합니다.Once the transcoder has been prepared, call TranscodeAsync to start transcoding. MediaTranscoder사용에 대 한 자세한 내용은 미디어 파일 트랜스 코드를 참조 하세요.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();
    }
}

MediaStreamSource 이벤트를 처리 합니다.Handle MediaStreamSource events

Mediastreamsource 개체는 화면에서 캡처한 프레임을 가져와 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. 개체의 이벤트에 대 한 처리기를 통해 Mediastreamsource 에 프레임을 전달 합니다.We pass the frames to the MediaStreamSource via handlers for the object's events.

SampleRequested 이벤트는 mediastreamsource 가 새 비디오 프레임을 사용할 준비가 되었을 때 발생 합니다.The SampleRequested event is raised when the MediaStreamSource is ready for a new video frame. 현재 기록 하 고 있는지 확인 한 후 도우미 메서드 Waitfornewframe 을 호출 하 여 화면에서 캡처된 새 프레임을 가져옵니다.After making sure we are currently recording, the helper method WaitForNewFrame is called to get a new frame captured from the screen. 이 문서의 뒷부분에 나오는이 메서드는 캡처된 프레임을 포함 하는 ID3D11Surface 개체를 반환 합니다.This method, shown later in this article, returns a ID3D11Surface object containing the captured frame. 이 예제에서는 프레임을 캡처한 시스템 시간을 저장 하는 도우미 클래스에서 IDirect3DSurface 인터페이스를 래핑합니다.For this example, we wrap the IDirect3DSurface interface in a helper class that also stores the system time at which the frame was captured. 프레임과 시스템 시간은 모두 mediastreamsample. CreateFromDirect3D11Surface factory 메서드에 전달 되 고 결과 MediastreamsampleMediaStreamSourceSampleRequestedEventArgsMediaStreamSourceSampleRequest 속성으로 설정 됩니다.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. 이는 캡처된 프레임이 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();
    }
}

시작 이벤트에 대 한 처리기에서 waitfornewframe을 호출 하지만 프레임을 캡처한 시스템 시간만 전달 합니다 .이 메서드는 mediastreamsource 에서 후속 프레임의 타이밍을 제대로 인코딩하는 데 사용 하는 SetActualStartPosition MediaStreamSourceStartingRequest.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);
    }
}

캡처 시작Start capturing

이 단계에 표시 된 Startcapture 메서드는 이전 단계에 표시 된 EncodeAsync 도우미 메서드에서 호출 됩니다.The StartCapture method shown in this step is called from the EncodeAsync helper method shown in a previous step. 첫째,이 메서드는 캡처 작업의 흐름을 제어 하는 데 사용 되는 이벤트 개체 집합을 초기화 합니다.First, this method initializes up a set of event objects that are used to control the flow of the capture operation.

  • _multithread 은 복사 되는 동안 다른 스레드가 SharpDX 질감에 액세스 하지 않도록 하는 데 사용 되는 SharpDX 라이브러리의 다중 스레드 개체를 래핑하는 도우미 클래스입니다._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 는 새 프레임이 캡처되고 mediastreamsource 에 전달 될 수 있음을 알리는 데 사용 됩니다._frameEvent is used to signal that a new frame has been captured and can be passed to the MediaStreamSource
  • _closedEvent 기록이 중지 되었으며 새 프레임을 기다릴 필요가 없다는 신호를 보냅니다._closedEvent signals that recording has stopped and that we shouldn't wait for any new frames.

프레임 이벤트와 닫힌 이벤트는 배열에 추가 되므로 캡처 루프에서 둘 중 하나를 기다릴 수 있습니다.The frame event and closed event are added to an array so we can wait for either one of them in the capture loop.

Startcapture 메서드의 나머지 부분에서는 실제 화면 캡처를 수행 하는 Windows Graphics. 캡처 api를 설정 합니다.The rest of the StartCapture method sets up the Windows.Graphics.Capture APIs that will do the actual screen capturing. 먼저 이벤트가 CaptureItem 이벤트에 등록 됩니다.First, an event is registered for the CaptureItem.Closed event. 다음으로 한 번에 여러 개의 캡처된 프레임을 버퍼링 할 수 있는 Direct3D11CaptureFramePool 를 만듭니다.Next, a Direct3D11CaptureFramePool is created, which allows multiple captured frames to be buffered at a time. Createfreethreaded 메서드는 프레임 풀을 만드는 데 사용 되며, FrameArrived 이벤트는 응용 프로그램의 주 스레드가 아니라 풀의 자체 작업자 스레드에서 호출 됩니다.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. 그런 다음 FrameArrived 이벤트에 대 한 처리기가 등록 됩니다.Next, a handler is registered for the FrameArrived event. 마지막으로, 선택 된 CaptureItem 에 대 한 GraphicsCaptureSession 생성 되 고, 프레임 캡처는 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();
}

그래픽 캡처 이벤트 처리Handle graphics capture events

이전 단계에서는 그래픽 캡처 이벤트에 대해 두 개의 처리기를 등록 하 고 캡처 루프의 흐름을 관리 하는 데 도움이 되는 몇 가지 이벤트를 설정 했습니다.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.

FrameArrived 이벤트는 Direct3D11CaptureFramePool 에 사용할 수 있는 새 캡처된 프레임이 있을 때 발생 합니다.The FrameArrived event is raised when the Direct3D11CaptureFramePool has a new captured frame available. 이 이벤트에 대 한 처리기에서 보낸 사람에 대해 TryGetNextFrame 를 호출 하 여 캡처된 다음 프레임을 가져옵니다.In the handler for this event, call TryGetNextFrame on the sender to get the next captured frame. 프레임을 검색 한 후에는 캡처 루프에서 사용할 수 있는 새 프레임이 있음을 인식 하도록 _frameEvent 를 설정 합니다.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();
}

Closed 이벤트 처리기에서 캡처 루프가 중지할 시기를 알 수 있도록 _closedEvent 에 알립니다.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();
}

새 프레임 대기Wait for new frames

이 섹션에서 설명 하는 Waitfornewframe 도우미 메서드는 캡처 루프의 많은 부분을 발생 하는 위치입니다.The WaitForNewFrame helper method described in this section is where the heavy-lifting of the capture loop occurs. 이 메서드는 Mediastreamsource 가 비디오 스트림에 새 프레임을 추가할 준비가 될 때마다 OnMediaStreamSourceSampleRequested 이벤트 처리기에서 호출 됩니다.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. 개략적인 수준에서이 함수는 새 프레임을 캡처하는 동안 인코딩에 대해 Mediastreamsource 에 전달할 수 있도록 각 화면 캡처 비디오 프레임을 하나의 Direct3D 화면에서 다른 Direct3D 화면으로 복사 하기만 합니다.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. 이 예제에서는 SharpDX 라이브러리를 사용 하 여 실제 복사 작업을 수행 합니다.This example uses the SharpDX library to perform the actual copy operation.

새 프레임을 대기 하기 전에 메서드는 클래스 변수에 저장 된 이전 프레임을 삭제 하 고 _currentFrame하 고 _frameEvent를 다시 설정 합니다.Before waiting for a new frame, the method disposes of any previous frame stored in the class variable, _currentFrame, and resets the _frameEvent. 그런 다음 메서드는 _frameEvent 또는 _closedEvent 신호를 받을 때까지 대기 합니다.Then the method waits for either the _frameEvent or the _closedEvent to be signaled. Closed 이벤트가 설정 되 면 앱에서 도우미 메서드를 호출 하 여 캡처 리소스를 정리 합니다.If the closed event is set, then the app calls a helper method to cleanup the capture resources. 이 방법은이 문서의 뒷부분에 나와 있습니다.This method is shown later in this article.

Frame 이벤트가 설정 되 면 이전 단계에서 정의 된 FrameArrived 이벤트 처리기가 호출 되었음을 알 수 있으며, 캡처한 프레임 데이터를 mediastreamsource에 전달 되는 Direct3D 11 화면으로 복사 하는 프로세스를 시작 합니다.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.

이 예제에서는 도우미 클래스 SurfaceWithInfo를 사용 합니다 .이 클래스는 mediastreamsource 에 필요한 프레임의 비디오 프레임 및 시스템 시간을 단일 개체로 전달 하는 것만 허용 합니다.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. 프레임 복사 프로세스의 첫 번째 단계는이 클래스를 인스턴스화하고 시스템 시간을 설정 하는 것입니다.The first step of the frame copy process is to instantiate this class and set the system time.

다음 단계는 특히 SharpDX 라이브러리에 의존 하는이 예제의 일부입니다.The next steps are the part of this example that relies specifically on the SharpDX library. 여기에 사용 된 도우미 함수는이 문서의 끝에 정의 되어 있습니다.The helper functions used here are defined at the end of this article. 먼저 Multithreadlock 을 사용 하 여 복사를 수행 하는 동안 다른 스레드가 비디오 프레임 버퍼에 액세스 하지 않도록 합니다.First we use the MultiThreadLock to make sure no other threads access the video frame buffer while we are making the copy. 그런 다음 도우미 메서드 CreateSharpDXTexture2D 를 호출 하 여 비디오 프레임에서 SharpDX Texture2D 개체를 만듭니다.Next, we call the helper method CreateSharpDXTexture2D to create a SharpDX Texture2D object from the video frame. 복사 작업의 원본 질감이 됩니다.This will be the source texture for the copy operation.

다음으로 이전 단계에서 만든 Texture2D 개체에서 이전에 프로세스에서 만든 컴퍼지션 질감으로 복사 합니다.Next, we copy from the Texture2D object created in the previous step into the composition texture we created earlier in the process. 이 컴퍼지션 질감은 다음 프레임이 캡처될 때 인코딩 프로세스가 픽셀에 대해 작동할 수 있도록 스왑 버퍼 역할을 합니다.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. 복사를 수행 하기 위해 컴퍼지션 텍스처와 연결 된 렌더링 대상 뷰를 지운 다음,이 경우에는 복사 하려는 질감 내에서 영역을 정의 하 고 CopySubresourceRegion 를 호출 하 여 실제로 픽셀을 컴포지션 질감에 복사 합니다.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.

대상 텍스처를 만들 때 사용할 질감 설명의 복사본을 만들지만, 설명이 수정 되 면 새 질감에 쓰기 권한이 있도록 Bindflagsrendertarget 으로 설정 합니다.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. CpuAccessFlagsNone 으로 설정 하면 시스템에서 복사 작업을 최적화할 수 있습니다.Setting the CpuAccessFlags to None allows the system to optimize the copy operation. 질감 설명은 새 질감 리소스를 만드는 데 사용 되 고, 컴퍼지션 질감 리소스는 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. 마지막으로이 메서드에서 반환 되는 IDirect3DSurface 개체를 만들기 위해 CreateDirect3DSurfaceFromSharpDXTexture 가 호출 됩니다.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;
}

리소스 캡처 및 정리 중지Stop capture and clean up resources

Stop 메서드는 캡처 작업을 중지 하는 방법을 제공 합니다.The Stop method provides a way to stop the capture operation. 앱은 단추 클릭과 같은 사용자 상호 작용에 대 한 응답으로 또는이를 프로그래밍 방식으로 호출할 수 있습니다.Your app may call this programmatically or in response to a user interaction, like a button click. 이 메서드는 단순히 _closedEvent설정 합니다.This method simply sets the _closedEvent. 이전 단계에서 정의한 Waitfornewframe 메서드는이 이벤트를 검색 하 고 설정 된 경우 캡처 작업을 종료 합니다.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();
}

정리 메서드는 복사 작업 중에 만들어진 리소스를 적절 하 게 삭제 하는 데 사용 됩니다.The Cleanup method is used to properly dispose of the resources that were created during the copy operation. 다음 내용이 포함됩니다.This includes:

  • 캡처 세션에서 사용 하는 Direct3D11CaptureFramePool 개체입니다.The Direct3D11CaptureFramePool object used by the capture session
  • GraphicsCaptureSessionGraphicsCaptureItemThe GraphicsCaptureSession and GraphicsCaptureItem
  • Direct3D 및 SharpDX 장치The Direct3D and SharpDX devices
  • 복사 작업에 사용 되는 SharpDX 텍스처 및 렌더링 대상 뷰입니다.The SharpDX texture and render target view used in the copy operation.
  • 현재 프레임을 저장 하는 데 사용 되는 Direct3D11CaptureFrame 입니다.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();
}

도우미 래퍼 클래스Helper wrapper classes

다음 도우미 클래스는이 문서의 예제 코드에 도움이 되도록 정의 되었습니다.The following helper classes were defined to help with the example code in this article.

Multithreadlock 도우미 클래스는 다른 스레드가 복사 되는 동안 질감 리소스에 액세스 하지 않도록 하는 SharpDX 다중 스레드 클래스를 래핑합니다.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 은 캡처된 프레임 및 캡처된 시간을 나타내는 SystemRelativeTimeIDirect3DSurface 를 연결 하는 데 사용 됩니다.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 및 SharpDX helper ApiDirect3D and SharpDX helper APIs

다음 도우미 Api는 Direct3D 및 SharpDX 리소스 만들기를 추상화 하도록 정의 되어 있습니다.The following helper APIs are defined to abstract out the creation of Direct3D and SharpDX resources. 이러한 기술에 대 한 자세한 설명은이 문서의 범위를 벗어나지만 연습에 표시 된 예제 코드를 구현 하는 데 사용할 수 있는 코드를 여기에서 제공 합니다.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;
    }
}

참고 항목See also