Поделиться через


Видеозапись экрана

В этой статье описывается, как кодировать кадры, захваченные с экрана, с помощью API Windows.Graphics.Capture в видеофайл. Сведения о захвате по-прежнему изображений см. в разделе "Захват экрана". Простое комплексное приложение, использующее основные понятия и методы, показанные в этой статье, см. в разделе SimpleRecorder.

Обзор процесса записи видео

В этой статье представлено пошаговое руководство по примеру приложения, которое записывает содержимое окна в видеофайл. Хотя это может показаться, что для реализации этого сценария требуется много кода, высокоуровневая структура приложения средства записи экрана довольно проста. В процессе захвата экрана используются три основных компонента UWP:

  • API Windows.GraphicsCapture выполняют фактически захват пикселей с экрана. Класс GraphicsCaptureItem представляет окно или отображаемое отображение. GraphicsCaptureSession используется для запуска и остановки операции записи. Класс Direct3D11CaptureFramePool поддерживает буфер кадров, в которые копируются содержимое экрана.
  • Класс MediaStreamSource получает захваченные кадры и создает видеопоток.
  • Класс MediaTranscoder получает поток, созданный MediaStreamSource , и кодирует его в видеофайл.

Пример кода, показанного в этой статье, можно разделить на несколько различных задач:

  • Инициализация . Это включает настройку классов UWP, описанных выше, инициализацию интерфейсов графических устройств, выбор окна для записи и настройку параметров кодирования, таких как разрешение и частота кадров.
  • Обработчики событий и потоки — основной драйвер основного цикла записи — MediaStreamSource , который периодически запрашивает кадры через событие SampleRequested . В этом примере используются события для координации запросов на новые кадры между различными компонентами примера. Синхронизация важна для одновременного отслеживания и кодирования кадров.
  • Копирование кадров — кадры копируются из буфера кадра записи в отдельную поверхность Direct3D, которую можно передать в MediaStreamSource , чтобы ресурс не перезаписывался при кодировании. API Direct3D используются для быстрого выполнения этой операции копирования.

Сведения об API Direct3D

Как упоминалось выше, копирование каждого захваченного кадра, вероятно, является самой сложной частью реализации, показанной в этой статье. На низком уровне эта операция выполняется с помощью Direct3D. В этом примере мы используем библиотеку SharpDX для выполнения операций Direct3D из C#. Эта библиотека больше не поддерживается официально, но она была выбрана, так как она работает на низкоуровневых операциях копирования, хорошо подходит для этого сценария. Мы пытались сохранить операции Direct3D как можно более дискретными, чтобы упростить замену собственного кода или других библиотек для этих задач.

Настройка проекта

Пример кода в этом пошаговом руководстве был создан с помощью шаблона проекта C# пустого приложения (универсального приложения Windows) в Visual Studio 2019. Чтобы использовать API Windows.Graphics.Capture в приложении, необходимо включить функцию записи графики в файл Package.appxmanifest для проекта. В этом примере сохраняются созданные видеофайлы в библиотеку видео на устройстве. Чтобы получить доступ к этой папке, необходимо включить возможность библиотеки видео.

Чтобы установить пакет Nuget SharpDX, в Visual Studio выберите " Управление пакетами Nuget". На вкладке "Обзор" найдите пакет SharpDX.Direct3D11 и нажмите кнопку "Установить".

Обратите внимание, что чтобы уменьшить размер перечисления кода в этой статье, код в пошаговом руководстве ниже не содержит явных ссылок на пространство имен и объявление переменных члена класса MainPage, которые именуются с ведущим подчеркиванием "_".

Настройка для кодирования

Метод SetupEncoding , описанный в этом разделе, инициализирует некоторые из основных объектов, которые будут использоваться для записи и кодирования видеокадров и настройки параметров кодирования для захваченного видео. Этот метод можно вызвать программным способом или в ответ на взаимодействие пользователя, например нажатие кнопки. Список кода для SetupEncoding показан ниже после описания шагов инициализации.

  • Проверьте поддержку записи. Перед началом процесса захвата необходимо вызвать GraphicsCaptureSession.IsSupported , чтобы убедиться, что функция захвата экрана поддерживается на текущем устройстве.

  • Инициализация интерфейсов Direct3D. В этом примере используется Direct3D для копирования пикселей, захваченных с экрана, в текстуру, закодированную как видеокадр. Вспомогательные методы, используемые для инициализации интерфейсов Direct3DDevice, CreateD3Device и CreateSharpDXDevice, показаны далее в этой статье.

  • Инициализация GraphicsCaptureItem. GraphicsCaptureItem представляет элемент на экране, который собирается записать, окно или весь экран. Разрешить пользователю выбрать элемент для захвата, создав GraphicsCapturePicker и вызвав PickSingleItemAsync.

  • Создание текстуры композиции. Создайте ресурс текстуры и связанное целевое представление отрисовки, которое будет использоваться для копирования каждого кадра видео. Эта текстура не может быть создана до создания GraphicsCaptureItem , и мы знаем ее измерения. Ознакомьтесь с описанием WaitForNewFrame , чтобы узнать, как используется эта текстура композиции. Вспомогательный метод для создания этой текстуры также показан далее в этой статье.

  • Создайте MediaEncodingProfile и VideoStreamDescriptor. Экземпляр класса MediaStreamSource будет принимать изображения, захваченные с экрана, и кодировать их в видеопоток. Затем видеопоток будет перекодирован в видеофайл класса MediaTranscoder . VideoStreamDecriptor предоставляет параметры кодирования, такие как разрешение и частота кадров для MediaStreamSource. Параметры кодирования видеофайлов для MediaTranscoder задаются с помощью MediaEncodingProfile. Обратите внимание, что размер, используемый для кодирования видео, не должен совпадать с размером захваченного окна, но чтобы сохранить этот пример простым, параметры кодирования жестко закодируются для использования фактических измерений элемента записи.

  • Создайте объекты MediaStreamSource и MediaTranscoder. Как упоминалось выше, объект MediaStreamSource кодирует отдельные кадры в видеопоток. Вызовите конструктор для этого класса, передавая MediaEncodingProfile , созданный на предыдущем шаге. Задайте для буфера значение нуля и регистрировать обработчики для событий запуска и sampleRequested , которые будут показаны далее в этой статье. Затем создайте новый экземпляр класса MediaTranscoder и включите аппаратное ускорение.

  • Создайте выходной файл , последний шаг в этом методе — создать файл, в который будет перекодировано видео. В этом примере мы просто создадим уникально именованный файл в папке "Библиотека видео" на устройстве. Обратите внимание, что для доступа к этой папке приложение должно указать возможность "Библиотека видео" в манифесте приложения. После создания файла откройте его для чтения и записи и передайте полученный поток в метод 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 см. в разделе "Файлы мультимедиа Transcode".


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 через обработчики событий объекта.

Событие SampleRequested возникает, когда MediaStreamSource готов к новому видеокадру. Убедившись, что сейчас мы записываем вспомогательный метод WaitForNewFrame , вызывается для получения нового кадра, захваченного с экрана. Этот метод, показанный далее в этой статье, возвращает объект ID3D11Surface , содержащий захваченный кадр. В этом примере мы упаковываем интерфейс IDirect3DSurface в вспомогательный класс, который также сохраняет системное время, в котором был записан кадр. Кадр и системное время передаются в метод фабрики MediaStreamSample.CreateFromDirect3D11Surface и результирующем модуле MediaStreamSampleRequest.Sample свойства MediaStreamSourceSampleRequestleRequestedEventArgs. Таким образом, захваченный кадр предоставляется в 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();
    }
}

В обработчике события "Запуск " мы вызываем WaitForNewFrame, но только передайте системное время, когда кадр был записан в метод MediaStreamSourceStartingRequest.SetActualStartPosition , который MediaStreamSource использует для правильного кодирования времени последующих кадров.

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

Начало записи

Метод StartCapture , показанный на этом шаге, вызывается из вспомогательного метода EncodeAsync , показанного на предыдущем шаге. Во-первых, этот метод инициализирует набор объектов событий, используемых для управления потоком операции захвата.

  • _multithread — это вспомогательный класс, который упаковывает объект Multithread библиотеки SharpDX, который будет использоваться для проверки того, что другие потоки не обращаются к текстуре SharpDX во время копирования.
  • _frameEvent используется для сигнала о том, что новый кадр был захвачен и может передаваться в MediaStreamSource
  • _closedEvent сигналов, что запись остановлена, и что мы не должны ждать новых кадров.

Событие кадра и закрытое событие добавляются в массив, чтобы ждать одного из них в цикле записи.

Остальная часть метода StartCapture настраивает API Windows.Graphics.Capture, которые будут выполнять фактическое захват экрана. Во-первых, событие регистрируется для события 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();
}

Обработка событий захвата графики

На предыдущем шаге мы зарегистрировали два обработчика для событий записи графики и настроили некоторые события для управления потоком цикла захвата.

Событие FrameArrived возникает, когда Direct3D11CaptureFramePool имеет новый захваченный кадр. В обработчике этого события вызовите TryGetNextFrame отправителю, чтобы получить следующий захваченный кадр. После получения кадра мы зададим _frameEvent , чтобы наш цикл записи знал, что доступен новый кадр.

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

В обработчике закрытых событий мы сигналим _closedEvent , чтобы цикл записи знал, когда следует остановиться.

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

Дождитесь новых кадров

Вспомогательный метод WaitForNewFrame , описанный в этом разделе, заключается в том, что происходит тяжелый подъем цикла захвата. Помните, что этот метод вызывается из обработчика событий OnMediaStreamSourceSampleRequested , когда MediaStreamSource готов к добавлению нового кадра в видеопоток. На высоком уровне эта функция просто копирует каждый захваченный с экрана видеокадр из одной поверхности Direct3D в другую, чтобы его можно было передать в MediaStreamSource для кодирования во время записи нового кадра. В этом примере для выполнения фактической операции копирования используется библиотека SharpDX.

Перед ожиданием нового кадра метод удаляет любой предыдущий кадр, хранящийся в переменной класса, _currentFrame и сбрасывает _frameEvent. Затем метод ожидает сигнала _frameEvent или _closedEvent . Если задано закрытое событие, приложение вызывает вспомогательный метод для очистки ресурсов записи. Этот метод показан далее в этой статье.

Если задано событие кадра, то мы знаем, что обработчик событий FrameArrived , определенный на предыдущем шаге, был вызван, и мы начинаем процесс копирования захваченных данных кадра в поверхность Direct3D 11, которая будет передана в MediaStreamSource.

В этом примере используется вспомогательный класс 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();
}

Метод очистки используется для правильного удаления ресурсов, созданных во время операции копирования. В том числе:

  • Объект 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 упаковывает класс MultithreadX 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;
    }
}

Вспомогательные API Direct3D и SharpDX

Следующие вспомогательные 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;
    }
}

См. также