Search code examples
c#screen-recordingwindows-graphics-capture

Recording a region with Windows Graphics Capture API


I am building a screen recording app in C# using Windows Graphics Capture API. I am using this script. I can select monitor and can record it to mp4 file. I can also select a Window and record it too. But how can we record a region with this? Ideally I need to give x,y coordinates along with width and height to record that specific region.

Here are the functions which return GraphicsCaptureItem for Window or Monitor hwnd, which can be used to record.

public static GraphicsCaptureItem CreateItemForWindow(IntPtr hwnd)
{
    var factory = WindowsRuntimeMarshal.GetActivationFactory(typeof(GraphicsCaptureItem));
    var interop = (IGraphicsCaptureItemInterop)factory;
    var temp = typeof(GraphicsCaptureItem);           
    var itemPointer = interop.CreateForWindow(hwnd, GraphicsCaptureItemGuid);
    var item = Marshal.GetObjectForIUnknown(itemPointer) as GraphicsCaptureItem;
    Marshal.Release(itemPointer);

    return item;
}

public static GraphicsCaptureItem CreateItemForMonitor(IntPtr hmon)
{
    var factory = WindowsRuntimeMarshal.GetActivationFactory(typeof(GraphicsCaptureItem));
    var interop = (IGraphicsCaptureItemInterop)factory;
    var temp = typeof(GraphicsCaptureItem);         
    var itemPointer = interop.CreateForMonitor(hmon, GraphicsCaptureItemGuid);
    var item = Marshal.GetObjectForIUnknown(itemPointer) as GraphicsCaptureItem;
    Marshal.Release(itemPointer);

    return item;
}

And this is Recording function

private async void RecordScreen(GraphicsCaptureItem item)
{
    _device = Direct3D11Helpers.CreateDevice();

    // Get our encoder properties
    uint frameRate = 30;
    uint bitrate = 3 * 1000000; 
    var width = (uint)item.Size.Width;
    var height = (uint)item.Size.Height;

    // Kick off the encoding
    try
    {
        newFile = GetTempFile();
        using (var stream = new FileStream(newFile, FileMode.CreateNew).AsRandomAccessStream())
        using (_encoder = new Encoder(_device, item))
        {
            await _encoder.EncodeAsync(
                stream,
                width, height, bitrate,
                frameRate);
        }
    }
    catch (Exception ex)
    {}
}

Solution

  • I achieved this by passing a custom region to CopySubresourceRegion in WaitForNewFrame method.

    public SurfaceWithInfo WaitForNewFrame()
    {
        .....
        using (var multithreadLock = new MultithreadLock(_multithread))
        using (var sourceTexture = Direct3D11Helpers.CreateSharpDXTexture2D(_currentFrame.Surface))
        {
            .....
    
            using (var copyTexture = new SharpDX.Direct3D11.Texture2D(_d3dDevice, description))
            {
                .....
                var region = new SharpDX.Direct3D11.ResourceRegion(
                    _region.Left,
                    _region.Top,
                    0,
                    _region.Left + _region.Width,
                    _region.Top + _region.Height,
                    1
                );
    
                _d3dDevice.ImmediateContext.CopyResource(_blankTexture, copyTexture);
                _d3dDevice.ImmediateContext.CopySubresourceRegion(sourceTexture, 0, region, copyTexture, 0);
                result.Surface = Direct3D11Helpers.CreateDirect3DSurfaceFromSharpDXTexture(copyTexture);
            }
        }
        ....
    }