Самый быстрый способ захвата экрана в Windows


159

Я хочу написать программу для экрана для платформы Windows, но не знаю, как сделать снимок экрана. Единственный известный мне метод - это использовать GDI, но мне любопытно, есть ли другие способы сделать это, и, если есть, то какие издержки наименьшие? Скорость является приоритетом.

Программа для создания скринкастинга будет предназначена для записи игровых материалов, хотя, если это сузит варианты, я по-прежнему открыт для любых других предложений, которые выпадают из этой области. Знание не плохо, в конце концов.

Редактировать : я наткнулся на эту статью: Различные методы захвата экрана . Он познакомил меня с методикой Windows Media API и DirectX. В заключении упоминается, что отключение аппаратного ускорения может значительно повысить производительность приложения захвата. Мне любопытно, почему это так. Может ли кто-нибудь заполнить пропущенные для меня пробелы?

Редактировать : я читал, что такие программы, как Camtasia, используют собственный драйвер захвата. Может ли кто-нибудь дать мне подробное объяснение того, как это работает, и почему это быстрее? Мне также может понадобиться руководство по реализации чего-то подобного, но я уверен, что в любом случае существует документация.

Кроме того, теперь я знаю, как FRAPS записывает экран. Он перехватывает базовый графический API для чтения из заднего буфера. Из того, что я понимаю, это быстрее, чем чтение из переднего буфера, потому что вы читаете из системной памяти, а не видео-памяти. Вы можете прочитать статью здесь .


Рассматривали ли вы вместо графической записи содержимого экрана использование системы воспроизведения ?
Бенджамин Линдли

2
Вам не нужно ничего зацеплять. Вам просто нужно написать свои входные события, чтобы они не управляли игрой напрямую, а вызывали другие функции. Например, если игрок нажимает левую клавишу, вы просто не уменьшаете положение игроков на x. Вместо этого вы вызываете функцию, например MovePlayerLeft(). И вы также записываете время и продолжительность нажатия клавиш и другой ввод. Затем, когда вы находитесь в режиме воспроизведения, вы просто игнорируете ввод и вместо этого читаете записанные данные. Если в данных вы видите нажатие левой клавиши, вы звоните MovePlayerLeft().
Бенджамин Линдли

1
@PigBen Это будет общее приложение для записи игрового материала. Это не для конкретной игры. Кто-нибудь, кто нажимает левую клавишу, может означать движение вправо, насколько я знаю. Кроме того, вы не рассматривали события, на которые пользователь не влиял. «А как насчет рендеринга?
Someguy

1
@ someguy Хорошо, я полагаю, вы делаете что-то гораздо более интенсивное, я добавил подпрограмму, используя вышеописанные методы, чтобы сэкономить на воспроизведении AVI в игре со скоростью около 30 кадров в секунду без помех. Я сделал многоэкранный видеомагнитофон, используя Windows API для «оптимизации рабочей силы», но он работал плохо даже при целевых 4 кадрах в секунду.
AJG85

1
Здесь есть зеркальный драйвер с открытым исходным кодом для Windows на сайте репозитория UltraVNC. Ultravnc.svn.sourceforge.net/viewvc/ultravnc/…
Beached

Ответы:


62

Это то, что я использую, чтобы собирать отдельные кадры, но если вы измените это и будете постоянно держать две цели открытыми, то вы можете «передать» их на диск, используя статический счетчик для имени файла. - Я не могу вспомнить, где я нашел это, но это было изменено, спасибо кому бы то ни было!

void dump_buffer()
{
   IDirect3DSurface9* pRenderTarget=NULL;
   IDirect3DSurface9* pDestTarget=NULL;
     const char file[] = "Pickture.bmp";
   // sanity checks.
   if (Device == NULL)
      return;

   // get the render target surface.
   HRESULT hr = Device->GetRenderTarget(0, &pRenderTarget);
   // get the current adapter display mode.
   //hr = pDirect3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,&d3ddisplaymode);

   // create a destination surface.
   hr = Device->CreateOffscreenPlainSurface(DisplayMde.Width,
                         DisplayMde.Height,
                         DisplayMde.Format,
                         D3DPOOL_SYSTEMMEM,
                         &pDestTarget,
                         NULL);
   //copy the render target to the destination surface.
   hr = Device->GetRenderTargetData(pRenderTarget, pDestTarget);
   //save its contents to a bitmap file.
   hr = D3DXSaveSurfaceToFile(file,
                              D3DXIFF_BMP,
                              pDestTarget,
                              NULL,
                              NULL);

   // clean up.
   pRenderTarget->Release();
   pDestTarget->Release();
}

Спасибо. Я слышал об этом методе некоторое время назад, который, как говорили, быстрее, чем чтение из переднего буфера. Вы честно делаете это таким образом, и это работает правильно?
Someguy

Проблема с передним буфером связана с доступом, то есть с попыткой скопировать обычный файл, который в данный момент обрабатывается, «прерывает» копию. Это работает достаточно хорошо для меня и ест мой жесткий диск!
Brandrew

@bobobobo Я не знаю, как именно это сработает, но я думал об использовании чего-то вроде Huffyuv. Изменить: Или, возможно, позволить пользователю выбирать из доступных фильтров DirectShow.
Someguy

1
Я просто не могу заставить это работать ... DirectX выплевывает некоторые недопустимые вызов на части GetRenderTargetData. Очевидно, что способ создания устройства должен иметь большое значение.
LightStriker

12
downvote, он работает только для вашего собственного приложения, поэтому не может быть использован для записи универсальных программ
user3125280 27.12.13

32

РЕДАКТИРОВАТЬ: я вижу, что это указано в вашей первой ссылке на редактирование как «путь GDI». Это по-прежнему достойный способ, даже если учесть рекомендации по производительности на этом сайте, я думаю, вы можете легко получить 30fps.

Из этого комментария (у меня нет опыта в этом, я просто ссылаюсь на кого-то, кто делает):

HDC hdc = GetDC(NULL); // get the desktop device context
HDC hDest = CreateCompatibleDC(hdc); // create a device context to use yourself

// get the height and width of the screen
int height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int width = GetSystemMetrics(SM_CXVIRTUALSCREEN);

// create a bitmap
HBITMAP hbDesktop = CreateCompatibleBitmap( hdc, width, height);

// use the previously created device context with the bitmap
SelectObject(hDest, hbDesktop);

// copy from the desktop device context to the bitmap device context
// call this once per 'frame'
BitBlt(hDest, 0,0, width, height, hdc, 0, 0, SRCCOPY);

// after the recording is done, release the desktop context you got..
ReleaseDC(NULL, hdc);

// ..delete the bitmap you were using to capture frames..
DeleteObject(hbDesktop);

// ..and delete the context you created
DeleteDC(hDest);

Я не говорю, что это самый быстрый, но BitBltоперация обычно очень быстрая, если вы копируете между контекстами совместимого устройства.

Для справки, Open Broadcaster Software реализует что-то вроде этого как часть своего метода "dc_capture" , хотя вместо создания контекста назначения hDestс использованием CreateCompatibleDCони используют IDXGISurface1, который работает с DirectX 10+. Если нет поддержки для этого, они отступают CreateCompatibleDC.

Чтобы изменить его на использование определенного приложения, вам нужно изменить в первой строке, GetDC(game)где gameнаходится дескриптор окна игры, а затем установить право heightи widthокно игры тоже.

Если у вас есть пиксели в hDest / hbDesktop, вам все равно нужно сохранить их в файл, но если вы делаете снимок экрана, то я подумаю, что вы захотите сохранить определенное количество их в памяти и сохранить в видеофайл в кусках, поэтому я не буду указывать на код для сохранения статического образа на диск.


3
msdn.microsoft.com/en-us/library/dd183370%28VS.85%29.aspx Отрывок: если форматы цветов контекста устройства источника и назначения не совпадают, функция BitBlt преобразует формат исходного цвета в соответствии с назначением формат.

2
Некоторые доказательства, подтверждающие это, были бы хорошими. Вы когда-нибудь публиковали сравнение производительности или видели точное?

2
Попробуйте его профилировать. Как я сказал в посте, я ссылаюсь на кого-то, кто имеет опыт работы с GDI. Если это приводит к утечке памяти, и вы знаете, как это исправить, отредактируйте сообщение, чтобы устранить утечку.

1
Я получил около 5 кадров в секунду с этим методом. Это было на ноутбуке с интегрированной графикой, так что я бы ожидал лучшего от настольного компьютера с настоящей видеокартой, но все же, он очень медленный.
Тимммм

1
@Timmmm Я добавил ссылку на то, как OBS реализует это. Надеюсь, это может немного ускорить процесс для вас.

19

Я написал программное обеспечение для захвата видео, похожее на FRAPS для приложений DirectX. Исходный код доступен, и моя статья объясняет общую технику. Посмотрите на http://blog.nektra.com/main/2013/07/23/instrumenting-direct3d-applications-to-capture-video-and-calculate-frames-per-second/

Уважение к вашим вопросам, связанным с производительностью,

  • DirectX должен быть быстрее, чем GDI, за исключением случаев, когда вы читаете из фронтбуфера, который очень медленный. Мой подход похож на FRAPS (чтение из буфера). Я перехватываю множество методов из интерфейсов Direct3D.

  • Для записи видео в реальном времени (с минимальным воздействием на приложение) необходим быстрый кодек. FRAPS использует собственный видеокодек без потерь. Lagarith и HUFFYUV - это универсальные видеокодеки без потерь, разработанные для приложений реального времени. Вы должны посмотреть на них, если вы хотите выводить видео файлы.

  • Другим подходом к записи скринкастов может быть написание Зеркального драйвера. Согласно Википедии: Когда видео зеркалирование активно, каждый раз, когда система обращается к основному видеоустройству в месте внутри зеркальной области, копия операции рисования выполняется на зеркальном видеоустройстве в режиме реального времени. См. Зеркальные драйверы на веб-сайте MSDN: http://msdn.microsoft.com/en-us/library/windows/hardware/ff568315(v=vs.85).aspx .


16

Я использую d3d9 для получения буфера и сохраняю его в png-файл, используя библиотеку d3dx:

    IDirect3DSurface9 * поверхность;

    // GetBackBuffer
    idirect3ddevice9-> GetBackBuffer (0, 0, D3DBACKBUFFER_TYPE_MONO, & surface);

    // сохранить поверхность
    D3DXSaveSurfaceToFileA ("filename.png", D3DXIFF_PNG, surface, NULL, NULL);

    SAFE_RELEASE (поверхность);

Для этого вы должны создать свой swapbuffer с

d3dpps.SwapEffect = D3DSWAPEFFECT_COPY ; // for screenshots.

(Таким образом, вы гарантируете, что задний буфер не будет поврежден до того, как вы сделаете скриншот).


Имеет смысл, спасибо. Знаете, в чем разница между этим и GetRenderTargetесть?
Someguy

Это просто получает текущую цель рендеринга (может быть другой внеэкранной поверхностью, если кто-то рендерится в текстуру в тот момент, когда вы вызываете).
бобобо

13

По моему мнению, подход GDI и подход DX различны по своей природе. рисование с использованием GDI применяет метод FLUSH, подход FLUSH рисует кадр, затем очищает его и перерисовывает другой кадр в том же буфере, это приведет к мерцанию в играх, требующих высокой частоты кадров.

  1. ПОЧЕМУ DX быстрее? в DX (или в мире графики) применяется более зрелый метод, называемый рендерингом с двойным буфером, где присутствуют два буфера, при представлении переднего буфера аппаратному обеспечению вы можете также выполнить рендеринг в другой буфер, после того как кадр 1 После завершения рендеринга система переходит в другой буфер (блокируя его для представления на аппаратном уровне и освобождая предыдущий буфер), таким образом, эффективность неэффективности рендеринга значительно улучшается.
  2. ПОЧЕМУ отказ от аппаратного ускорения? хотя при рендеринге с двойным буфером FPS улучшается, но время рендеринга все еще ограничено. Современное графическое оборудование обычно требует значительных оптимизаций при рендеринге, как правило, таких как сглаживание, это требует значительных вычислений, если вам не нужна высококачественная графика, конечно, вы можете просто отключить эту опцию. и это сэкономит вам время.

Я думаю, что вам действительно нужна система воспроизведения, я полностью согласен с тем, что обсуждали люди.


1
Смотрите обсуждение того, почему система воспроизведения неосуществима. Программа скринкастинга не для какой-либо конкретной игры.
Someguy

10

Я написал класс, который реализовал метод GDI для захвата экрана. Я тоже хотел получить дополнительную скорость, поэтому, после обнаружения метода DirectX (через GetFrontBuffer), я попробовал это, ожидая, что он будет быстрее.

Я был встревожен, обнаружив, что GDI работает примерно в 2,5 раза быстрее. После 100 испытаний захвата моего дисплея с двумя мониторами реализация GDI в среднем составляла 0,65 с на каждый снимок экрана, а метод DirectX - 1,72 с. Так что, согласно моим тестам, GDI определенно быстрее, чем GetFrontBuffer.

Мне не удалось заставить код Brandrew работать для тестирования DirectX через GetRenderTargetData. Экранная копия вышла чисто черной. Тем не менее, он может скопировать этот пустой экран очень быстро! Я продолжу возиться с этим и надеюсь получить рабочую версию, чтобы увидеть реальные результаты.


Спасибо за информацию. Я не проверял код Брандрю, но я знаю, что такой GetRenderTargetDataподход работает. Может быть, я напишу свой собственный ответ, когда я закончу свое заявление. Или вы можете обновить свой, как только у вас все заработало.
Someguy

0,65 за скриншот? Хорошая реализация GDI (хранение устройств и т. Д.) Должна легко выполнять 30 кадров в секунду в разрешении 1920x1200 на современном компьютере.
Кристофер Оезбек

Я полагаю, что качество изображения, отображаемого с помощью GDI, определенно хуже, чем у DX
zinking

Я провел этот тест в C # с SlimDX и, как ни странно, нашел те же результаты. Возможно, это может быть связано с тем, что при использовании SlimDX необходимо создавать новый поток и новое растровое изображение для каждого обновления кадра, а не создавать его один раз, перематывать и перезаписывать одно и то же местоположение.
Цезарь

Просто пояснение: на самом деле, «те же результаты» относились к тому, что GDI был быстрее - как упомянул @Christopher, 30fps + было очень выполнимым и все еще оставляло много свободного процессора.
Сезар

8

Несколько вещей, которые я смог найти: очевидно, использование «зеркального драйвера» быстро, хотя я не знаю об OSS.

Почему RDP такой быстрый по сравнению с другими программами дистанционного управления?

Также очевидно, что использование некоторых сверток StretchRect быстрее, чем BitBlt

http://betterlogic.com/roger/2010/07/fast-screen-capture/comment-page-1/#comment-5193

И тот, который вы упомянули (подключение Fraps к D3D DLL), вероятно, является единственным способом для приложений D3D, но не будет работать с захватом рабочего стола Windows XP. Так что теперь я просто хотел бы, чтобы по обычным окнам рабочего стола был эквивалентный по скорости Fraps ... кто-нибудь?

(Я думаю, что с aero вы можете использовать хрипы, похожие на fraps, но пользователям XP не повезет).

Также очевидно изменение битовой глубины экрана и / или отключение аппаратного ускорения. может помочь (и / или отключить аэро).

https://github.com/rdp/screen-capture-recorder-program включает в себя достаточно быструю утилиту захвата на основе BitBlt и тестовый модуль в составе его установки, который позволяет вам измерять скорости BitBlt для их оптимизации.

VirtualDub также имеет модуль захвата экрана «opengl», который, как говорят, быстр и делает такие вещи, как обнаружение изменений http://www.virtualdub.org/blog/pivot/entry.php?id=290


Интересно, будет ли быстрее использовать ваш «захват экрана» или сам использовать BitBlt? Есть ли какая-то оптимизация в вашем проекте?
Блез

Если вы используете Bitblt самостоятельно, вы можете избежать дополнительного memcpy (memcpy, как правило, не является самым большим узким местом, хотя и добавляет некоторое время - я упомянул его здесь только для его утилиты для тестирования производительности, но если вам нужно что-то или directshow, то его приятно )
rogerdpack

8

Для C ++ вы можете использовать: http://www.pinvoke.net/default.aspx/gdi32/BitBlt.html
Это может не работать на всех типах 3D-приложений / видео приложений. Тогда эта ссылка может быть более полезной, поскольку она описывает 3 различных метода, которые вы можете использовать.

Старый ответ (C #):
Вы можете использовать System.Drawing.Graphics.Copy , но это не очень быстро.

Пример проекта, который я написал, делает именно это: http://blog.tedd.no/index.php/2010/08/16/c-image-analysis-auto-gaming-with-source/

Я планирую обновить этот образец, используя более быстрый метод, такой как Direct3D: http://spazzarama.com/2009/02/07/screencapture-with-direct3d/

А вот ссылка для захвата видео: Как сделать снимок экрана для видео с помощью C # .Net?


2
Ах, я забыл упомянуть, что я программирую на C (возможно, C ++) и не планирую использовать .NET. Мне очень жаль :/.
Someguy

Я уже знал о BitBlt (GDI). Я посмотрю в Direct3D, хотя. Спасибо!
Someguy

Я изучал это несколько недель назад, но пока не успел его реализовать. Direct3D - это путь !! быстрее, чем встроенный метод C #, который использует GDI +.
Тедд Хансен

Я обновил свой оригинальный пост. Согласно ссылке, DirectX работает медленно, когда приходится вызывать GetFrontBufferData (). Это нужно учитывать при записи игрового материала? Не могли бы вы контекстуализировать это для меня?
Someguy

1
GDI медленный, поэтому он не подходит для проблемной области, DirectX или OpenGL были бы единственной разумной рекомендацией.

6

В Windows 8 Microsoft представила API дублирования рабочего стола Windows. Это официально рекомендуемый способ сделать это. Одна из приятных особенностей, которую он имеет для экранного вещания, заключается в том, что он обнаруживает движение окна, поэтому вы можете передавать дельты блоков при перемещении окон вместо необработанных пикселей. Кроме того, он сообщает вам, какие прямоугольники изменились, от одного кадра к другому.

Пример кода Microsoft довольно сложный, но API на самом деле прост и удобен в использовании. Я собрал пример проекта, который намного проще официального примера:

https://github.com/bmharper/WindowsDesktopDuplicationSample

Документы: https://docs.microsoft.com/en-gb/windows/desktop/direct3ddxgi/desktop-dup-api.

Официальный пример кода Microsoft: https://code.msdn.microsoft.com/windowsdesktop/Desktop-Duplication-Sample-da4c696a


Проект Github отлично работает на Windows 10, протестирован с веб-видео и Resident Evil 7. Лучшая вещь - это масштабирование загрузки процессора с частотой обновления графики.
jw_

как только вы запускаете GPU-Z, эта программа перестает захватывать экран.
Марино Шимич

5

Вы можете попробовать проект с открытым исходным кодом c ++ WinRobot @git , мощный скриншот

CComPtr<IWinRobotService> pService;
hr = pService.CoCreateInstance(__uuidof(ServiceHost) );

//get active console session
CComPtr<IUnknown> pUnk;
hr = pService->GetActiveConsoleSession(&pUnk);
CComQIPtr<IWinRobotSession> pSession = pUnk;

// capture screen
pUnk = 0;
hr = pSession->CreateScreenCapture(0,0,1280,800,&pUnk);

// get screen image data(with file mapping)
CComQIPtr<IScreenBufferStream> pBuffer = pUnk;

Служба поддержки :

  • Окно UAC
  • Winlogon
  • DirectShowOverlay

1
Это ... много крючков низкого уровня. Скорость захвата невероятна, если у вас есть права администратора.
toster-cx

Я нашел winrobot очень мощным и гладким.
ashishgupta_mca

Я изучил код WinRobot и не увидел ничего революционного в отношении захвата экрана: он использует тот же CreateCompatibleDC..BitBlt. Разве есть какое-то волшебство, когда это выполняется в контексте сервиса?
Шех

@shekh: Ваше исследование было слишком поверхностным. Код использует IDirectDrawSurface7-> BltFast () для копирования экрана с поверхности рисования экрана на поверхность копирования DD, затем он использует Filemapping для копирования изображения. Это довольно сложно, потому что код работает в службе, где вы не можете легко получить доступ к рабочим столам.
Эльмуе

2

Я сам делаю это с Directx и думаю, что это так быстро, как вы бы этого хотели. у меня нет быстрого примера кода, но я нашел это, что должно быть полезно. версия для DirectX11 не должна сильно отличаться, возможно, DirectX9 немного больше, но это путь


2

Я понимаю, что следующее предложение не отвечает на ваш вопрос, но самый простой метод, который я нашел для захвата быстро меняющегося представления DirectX, это подключить видеокамеру к порту S-video видеокарты и записать изображения как фильм. Затем перенесите видео с камеры обратно в файл MPG, WMV, AVI и т. Д. На компьютере.


2

Запись экрана может быть сделана в C # с использованием VLC API . Я сделал пример программы, чтобы продемонстрировать это. Он использует библиотеки LibVLCSharp и VideoLAN.LibVLC.Windows . Вы можете получить гораздо больше функций, связанных с рендерингом видео, используя этот кроссплатформенный API.

Документацию по API см .: LibVLCSharp API Github.

using System;
using System.IO;
using System.Reflection;
using System.Threading;
using LibVLCSharp.Shared;

namespace ScreenRecorderNetApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Core.Initialize();

            using (var libVlc = new LibVLC())
            using (var mediaPlayer = new MediaPlayer(libVlc))
            {
                var media = new Media(libVlc, "screen://", FromType.FromLocation);
                media.AddOption(":screen-fps=24");
                media.AddOption(":sout=#transcode{vcodec=h264,vb=0,scale=0,acodec=mp4a,ab=128,channels=2,samplerate=44100}:file{dst=testvlc.mp4}");
                media.AddOption(":sout-keep");

                mediaPlayer.Play(media);
                Thread.Sleep(10*1000);
                mediaPlayer.Stop();
            }
        }
    }
}

1
Для кого это может касаться: Обратите внимание, что libvlc выпущен под лицензией GPL. Если вы не собираетесь выпускать свой код под лицензией GPL, не используйте libvlc.
Себастьян Кэбот

Это не совсем точно, LibVLC - это LGPL - оно было лицензировано в 2011 году. Само приложение VLC остается GPL: videolan.org/press/lgpl-libvlc.html
Эндрю

0

DXGI Desktop Capture

Проект, который захватывает изображение рабочего стола с дублированием DXGI. Сохраняет захваченное изображение в файл в различных графических форматах (* .bmp; * .jpg; * .tif).

Этот образец написан на C ++. Вам также нужен некоторый опыт работы с DirectX (D3D11, D2D1).

Что может сделать приложение

  • Если у вас более одного настольного монитора, вы можете выбрать.
  • Изменение размера захваченного изображения рабочего стола.
  • Выберите разные режимы масштабирования.
  • Вы можете показать или скрыть значок мыши в выходном изображении.
  • Вы можете повернуть изображение для выходного изображения или оставить его по умолчанию.
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.