屏幕捕获到视频

本文介绍如何将使用 Windows.Graphics.Capture API 从屏幕捕获的帧编码为视频文件。 有关屏幕捕获静态图像的信息,请参阅屏幕捕获。 有关利用本文中所述的概念和技术的简单端到端示例应用,请参阅 SimpleRecorder

视频捕获过程概述

本文提供了一个示例应用的演练,该应用将窗口内容记录到视频文件中。 虽然实现此方案可能需要大量代码,但屏幕录制器应用程序的高级结构非常简单。 屏幕捕获过程使用三个主要 UWP 功能:

本文中所示的示例代码可以分类为几个不同的任务:

  • 初始化 - 这包括配置上述 UWP 类、初始化图形设备接口、选取要捕获的窗口以及设置编码参数(如分辨率和帧速率)。
  • 事件处理程序和线程处理 - 主捕获循环的主要驱动因素是 MediaStreamSource,它通过 SampleRequested 事件定期请求帧。 此示例使用事件在示例的不同组件之间协调对新帧的请求。 同步对于允许同时捕获和编码帧十分重要。
  • 复制帧 - 帧会从捕获帧缓冲区复制到可传递给 MediaStreamSource 的单独 Direct3D 图面,以便在编码时不覆盖资源。 Direct3D API 用于快速执行此复制操作。

关于 Direct3D API

如上所述,每个捕获的帧的复制可能是本文中所示实现的最复杂部分。 在较低级别,此操作使用 Direct3D 完成。 在此示例中,我们使用 SharpDX 库从 C# 执行 Direct3D 操作。 不再正式支持此库,但选择此库是因为它在执行低级复制操作时的性能非常适合于此方案。 我们尝试使 Direct3D 操作尽可能离散,以便你可更轻松地替代你自己的代码或其他库来执行这些任务。

设置项目

本演练中的示例代码是在 Visual Studio 2019 中使用“空白应用(通用 Windows)”C# 项目模板创建的。 若要在应用中使用 Windows.Graphics.Capture API,必须在项目的 Package.appxmanifest 文件中包含“图形捕获”功能。 此示例会将生成的视频文件保存到设备上的视频库中。 若要访问此文件夹,必须包含“视频库”功能。

若要安装 SharpDX Nuget 程序包,请在 Visual Studio 选择“管理 Nuget 程序包”。 在“浏览”选项卡中搜索“SharpDX.Direct3D11”程序包,然后单击“安装”。

请注意,为了减小本文中代码列表的大小,以下演练中的代码省略了显式命名空间引用以及使用前导下划线“_”命名的 MainPage 类成员变量的声明。

编码设置

此部分中所述的 SetupEncoding 方法会初始化一些将用于捕获和编码视频帧的主对象,并为捕获的视频设置编码参数。 此方法可通过编程方式进行调用,或是在响应用户交互(如按钮单击)时进行调用。 下面在初始化步骤说明之后显示了 SetupEncoding 的代码列表。

  • 检查捕获支持。 在开始捕获过程之前,需要调用 GraphicsCaptureSession.IsSupported 以确保当前设备支持屏幕捕获功能。

  • 初始化 Direct3D 接口。 此示例使用 Direct3D 将从屏幕捕获的像素复制到编码为视频帧的纹理中。 本文后面部分演示了用于初始化 Direct3D 接口 CreateD3DDevice 和 CreateSharpDXDevice 的帮助程序方法。

  • 初始化 GraphicsCaptureItem。 GraphicsCaptureItem 表示屏幕上将捕获的项(窗口或整个屏幕)。 允许用户通过创建 GraphicsCapturePicker 并调用 PickSingleItemAsync 来选取要捕获的项。

  • 创建组合纹理。 创建将用于复制每个视频帧的纹理资源和关联呈现目标视图。 只有在创建 GraphicsCaptureItem 并知道其尺寸后,才能创建此纹理。 请参阅 WaitForNewFrame 的说明,了解如何使用此组合纹理。 本文后面部分也演示了用于创建此纹理的帮助程序方法。

  • 创建 MediaEncodingProfile 和 VideoStreamDescriptor。 MediaStreamSource 类的实例会获取从屏幕捕获的图像,并将其编码到视频流中。 MediaTranscoder 类随后将视频流转码为视频文件。 VideoStreamDecriptor 为 MediaStreamSource 提供编码参数(如分辨率和帧速率)。 MediaTranscoder 的视频文件编码参数使用 MediaEncodingProfile 进行指定。 请注意,用于视频编码的大小不必与所捕获的窗口大小相同,不过为了使此示例简单,编码设置进行硬编码,以使用捕获项的实际尺寸。

  • 创建 MediaStreamSource 和 MediaTranscoder 对象。 如上所述,MediaStreamSource 对象将单个帧编码到视频流中。 调用此类的构造函数,并传入在上一步中创建的 MediaEncodingProfile。 将缓冲时间设置为零,并注册 StartingSampleRequested 事件的处理程序(将在本文后面部分进行演示)。 接下来,构造 MediaTranscoder 类的新实例,并启用硬件加速。

  • 创建输出文件 此方法中的最后一步是创建作为视频转码目标的文件。 在此示例中,我们只是在设备上的视频库文件夹中创建唯一命名的文件。 请注意,若要访问此文件夹,应用必须在应用部件清单 (manifest) 中指定“视频库”功能。 创建文件后,打开它以进行读取和写入,并将生成的流传递到 EncodeAsync 方法中(接下来将进行演示)。

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

开始编码

现在已初始化了主对象,便实现 EncodeAsync 方法以启动捕获操作。 此方法会首先检查以确保尚未进行录制,如果不是这样,则会调用帮助程序方法 StartCapture 开始从屏幕捕获帧。 此方法将在本文后面部分进行演示。 接下来,调用 PrepareMediaStreamSourceTranscodeAsync,以使 MediaTranscoder 准备好使用我们在上一部分中创建的编码配置文件将 MediaStreamSource 对象生成的视频流转码为输出文件流。 准备好转码器后,调用 TranscodeAsync 以开始转码。 有关使用 MediaTranscoder 的详细信息,请参阅转码媒体文件


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

        StartCapture();

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

        await transcode.TranscodeAsync();
    }
}

处理 MediaStreamSource 事件

MediaStreamSource 对象会获取从屏幕捕获的帧,并将其转换为可使用 MediaTranscoder 保存到文件的视频流。 我们通过对象事件的处理程序将帧传递到 MediaStreamSource。

为新的视频帧准备好 MediaStreamSource 后,会引发 SampleRequested 事件。 确保当前正在进行录制,会调用帮助程序方法 WaitForNewFrame 以获取从屏幕捕获的新帧。 如本文后面部分所示,此方法会返回包含捕获帧的 ID3D11Surface 对象。 对于此示例,我们将 IDirect3DSurface 接口包装在还会存储捕获帧时的系统时间的帮助程序类中。 帧和系统时间都传递到 MediaStreamSample.CreateFromDirect3D11Surface 工厂方法中,生成的 MediaStreamSample 会设置为 MediaStreamSourceSampleRequestedEventArgsMediaStreamSourceSampleRequest.Sample 属性。 这是将捕获帧提供给 MediaStreamSource 的方式。

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

                var timeStamp = frame.SystemRelativeTime;

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

Starting 事件的处理程序中,我们调用 WaitForNewFrame,但只将捕获帧时的系统时间传递到 MediaStreamSourceStartingRequest.SetActualStartPosition 方法,MediaStreamSource 会使用该方法对后续帧的计时正确编码。

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

开始捕获

此步骤中所示的 StartCapture 方法是从上一步中所示的 EncodeAsync 帮助程序方法进行调用。 首先,此方法初始化一组用于控制捕获操作流的事件对象。

  • _multithread 是一种帮助程序类,用于包装 SharpDX 库的 Multithread 对象,该对象将用于确保在复制时没有其他对象访问 SharpDX 纹理。
  • _frameEvent 用于指示新帧已捕获并且可传递到 MediaStreamSource
  • _closedEvent 指示录制已停止,我们不应等待任何新帧。

帧事件和关闭事件会添加到一个数组中,以便我们可以在捕获循环中等待其中任一个事件。

StartCapture 方法的其余部分会设置将执行实际屏幕捕获操作的 Windows.Graphics.Capture API。 首先,为 CaptureItem.Closed 事件注册事件。 接下来,创建 Direct3D11CaptureFramePool,它允许一次缓冲多个捕获帧。 CreateFreeThreaded 方法用于创建帧池,以便对该池自己的工作线程而不是应用主线程调用 FrameArrived 事件。 接下来,为 FrameArrived 事件注册处理程序。 最后,为所选 CaptureItem 创建 GraphicsCaptureSession,并通过调用 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();
}

处理图形捕获事件

在上一步中,我们为图形捕获事件注册了两个处理程序,并设置了一些事件以帮助管理捕获循环流。

当 Direct3D11CaptureFramePool 有可用的新捕获帧时,会引发 FrameArrived 事件。 在此事件的处理程序中,在发送方上调用 TryGetNextFrame 以获取下一个捕获帧。 检索帧后,我们会设置 _frameEvent,以便捕获循环知道有可用的新帧。

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

在 Closed 事件处理程序中,我们向 _closedEvent 发送现信号,以便捕获循环知道何时停止。

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

等待新帧

此部分中所述的 WaitForNewFrame 帮助程序方法是进行捕获循环中的繁重任务的位置。 请记住,只要 MediaStreamSource 准备好将新帧添加到视频流,便会从 OnMediaStreamSourceSampleRequested 事件处理程序调用此方法。 从较高级别,此函数只需将每个屏幕捕获的视频帧从一个 Direct3D 图面复制到另一个图面,以便可以在捕获新帧时传递到 MediaStreamSource 进行编码。 此示例使用 SharpDX 库执行实际复制操作。

在等待新帧之前,方法会释放类 _currentFrame 变量中存储的任何以前帧,并重置 _frameEvent。 随后方法会等待向 _frameEvent 或 _closedEvent 发送信号。 如果设置了关闭事件,则应用会调用帮助程序方法以清理捕获资源。 此方法将在本文后面部分进行演示。

如果设置了帧事件,则我们知道调用了上一步中定义的 FrameArrived 事件处理程序,并开始将捕获帧数据复制到将传递给 MediaStreamSource 的 Direct3D 11 图面的过程。

此示例使用一个帮助程序类 SurfaceWithInfo,它仅允许将视频帧和帧的系统时间(MediaStreamSource 需要这两者)作为单个对象进行传递。 帧复制过程的第一步是实例化此类并设置系统时间。

后续步骤是此示例中专门依赖于 SharpDX 库的部分。 本文末尾定义了此处使用的帮助程序函数。 首先,我们使用 MultiThreadLock 确保在进行复制时没有其他线程访问视频帧缓冲区。 接下来,调用帮助程序方法 CreateSharpDXTexture2D,从视频帧创建 SharpDX Texture2D 对象。 这会是复制操作的源纹理。

接下来,我们将上一步骤中创建的 Texture2D 对象复制到之前在过程中创建的组合纹理中。 此组合纹理充当交换缓冲区,以便编码过程可以在捕获下一帧时对像素进行操作。 为了执行复制,我们会清除与组合纹理关联的呈现目标视图,然后在纹理中定义要复制的区域(本例中为整个纹理),接下来调用 CopySubresourceRegion 以将像素实际复制到组合纹理。

我们会创建纹理说明的副本以在创建目标纹理时使用,但修改了该说明,将 BindFlags 设置为 RenderTarget,以便新纹理具有写入访问权限。 将 CpuAccessFlags 设置为 None 会允许系统优化复制操作。 纹理说明用于创建新纹理资源,并且组合纹理资源会通过 CopyResource 调用复制到此新资源中。 最后,调用 CreateDirect3DSurfaceFromSharpDXTexture 以创建从此方法返回的 IDirect3DSurface 对象。

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 方法提供了一种停止捕获操作的方式。 应用可通过编程方式调用此方法,或是在响应用户交互(如按钮单击)时进行调用。 此方法只需设置 _closedEvent。 前面步骤中定义的 WaitForNewFrame 方法会查找此事件,如果已设置,则关闭捕获操作。

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

Cleanup 方法用于正确释放复制操作期间创建的资源。 这包括:

  • 捕获会话使用的 Direct3D11CaptureFramePool 对象
  • GraphicsCaptureSession 和 GraphicsCaptureItem
  • Direct3D 和 SharpDX 设备
  • 复制操作中使用的 SharpDX 纹理和呈现目标视图。
  • 用于存储当前帧的 Direct3D11CaptureFrame。
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();
}

帮助程序包装器类

定义了以下帮助程序类以帮助处理本文中的示例代码。

MultithreadLock 帮助程序类会包装 SharpDX Multithread 类,该类可确保在复制时没有其他线程访问纹理资源。

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 用于将 IDirect3DSurface 与 SystemRelativeTime 关联,它们分别表示捕获帧和捕获时间。

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 帮助程序 API

定义了以下帮助程序 API 以抽象化 Direct3D 和 SharpDX 资源的创建。 这些技术的详细说明不在本文涵盖范围内,但此处提供了代码,使你可以实现演练中所示的示例代码。

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

另请参阅