Найшвидший спосіб захоплення екрана в 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
Існує з відкритим вихідним кодом драйвер дзеркала для вікон на сховище сайту 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

Проблема з фронтальним буфером полягає в доступі, тобто спробі скопіювати звичайну, яка в даний час надається, "перериває" копію. Це працює досить добре для мене і їсть мій жорсткий диск!
Брандрю

@bobobobo Я не знаю, як би це вийшло точно, але я думав використовувати щось на кшталт Huffyuv. Редагувати: Або, можливо, дозвольте користувачеві вибрати з доступних фільтрів прямого шоу.
someguy

1
Мені просто не вдається зробити цю роботу ... DirectX плює недійсний виклик у частині GetRenderTargetData. Очевидно, спосіб створення вашого пристрою повинен мати велике значення.
LightStriker

12
downvote, він працює лише для вашої власної програми, тому не можна використовувати для запису загальних програм
user3125280

32

EDIT: Я можу побачити, що це вказано під вашим першим посиланням на редагування як "спосіб GDI". Це все-таки гідний спосіб досягти навіть рекомендацій щодо продуктивності на цьому сайті, ви можете легко дістатися до 30 кадрів в секунду.

З цього коментаря (у мене немає досвіду цього робити, я просто посилаюся на когось, хто це робить):

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 реалізує щось подібне як частину їхнього методу "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 кадрів в секунду за допомогою цього методу. Це було на ноутбуці з інтегрованою графікою, тому я б очікував кращого від робочого столу з справжньою відеокартою, але все-таки це дуже повільно.
Timmmm

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 та поверхня);

    // зберегти поверхню
    D3DXSaveSurfaceToFileA ("filename.png", D3DXIFF_PNG, поверхня, NULL, NULL);

    SAFE_RELEASE (поверхня);

Для цього вам слід створити свопбуфер

d3dpps.SwapEffect = D3DSWAPEFFECT_COPY ; // for screenshots.

(Таким чином, ви гарантуєте, що зворотний буфер не замішаний, перш ніж зробити знімок екрана).


Має сенс, дякую. Чи знаєте ви, в чому різниця між цим і GetRenderTargetполягає?
someguy

Це просто отримує поточну ціль візуалізації (може бути ще однією поверхнею екрана, якщо хтось перетворює текстуру в момент, коли ви телефонуєте).
bobobobo

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 кадрів в секунду в 1920х1200 на сучасному комп'ютері.
Крістофер Йозбек

Я припускаю, що якість зображення, надана GDI, безумовно, гірша за DX
цинкування

Я провів цей тест на C # з SlimDX і, на диво, знайшов ті самі результати. Можливо, це може бути пов'язане з тим, що, використовуючи SlimDX, потрібно створити новий потік і нову растрову карту для кожного оновлення кадру, а не створювати його один раз, перемотувати назад і продовжувати перезапис того самого місця.
Сезар

Тільки уточнення: насправді "ті самі результати" посилалися на те, що GDI швидше - як зазначає @Christopher, 30 кадрів в секунду + було дуже доступно і все ще залишало безліч запасних процесорів.
Сезар

8

Кілька речей, які мені вдалося зібрати: очевидно, що використання "драйвера дзеркала" швидко, хоча я не знаю про ОСЗ.

Чому RDP настільки швидкий порівняно з іншими програмами дистанційного керування?

Також очевидно, що використання деяких згортків StretchRect швидше, ніж BitBlt

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

І той, про який ви згадали (fraps підключення до D3D DLL), мабуть, є єдиним способом для D3D-додатків, але не буде працювати з захопленням робочого столу Windows XP. Тож тепер я просто хочу, щоб у звичайних вікнах робочого столу були фрапи, еквівалентні швидкості ... хто-небудь?

(Я думаю, що за допомогою aero ви могли б використовувати гачки, що нагадують фрапу, але користувачам XP не пощастить).

Також, очевидно, змінюється глибина бітів екрана та / або вимикається апаратне прискорення. може допомогти (та / або відключити аерозоль).

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

У VirtualDub також є модуль зйомки екрана "opengl", який, як кажуть, виявляється швидким і робить такі дії, як виявлення змін http://www.virtualdub.org/blog/pivot/entry.php?id=290


Цікаво, чи буде швидше скористатися вашим "записувачем екрана-рекордером" або використовувати сам BitBlt? Чи є якась оптимізація у вашому проекті?
blez

Якщо ви самі використовуєте Bitblt, ви можете уникнути зайвої memcpy (memcpy, як правило, не є найбільшим вузьким місцем, хоч це і додає деякий час - я тут згадував лише про його корисну програму, але якщо вам потрібно щось або напрямне шоу, то його приємно )
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 Desktop для копіювання. Це офіційно рекомендований спосіб зробити це. Однією з приємних особливостей екранізації є те, що він виявляє рух вікон, тому ви можете передавати дельти блоків, коли вікна пересуваються замість необроблених пікселів. Крім того, він говорить вам, які прямокутники змінилися, від одного кадру до іншого.

Код прикладу 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
  • Вінлогон
  • DirectShowOverlay

1
Це ... багато гачків низького рівня. Швидкість захоплення дивовижна, якщо у вас є права адміністратора.
toster-cx

Я виявив winrobot дуже потужним і рівним.
ashishgupta_mca

Я вивчав код WinRobot і не бачив нічого кращого захоплення екрану wrt: він використовує той же CreateCompatibleDC..BitBlt. Якщо не існує якоїсь магії, коли це виконується в контексті обслуговування?
шех

@shekh: Ваше дослідження було надто поверховим. Код використовує IDirectDrawSurface7-> BltFast () для копіювання екрана з поверхні малюнка екрана на поверхню DD копії, а потім використовує Filemapping для копіювання зображення. Він досить складний, оскільки код працює в службі, де ви не можете легко отримати доступ до настільних ПК.
Елмуе

2

Я сам це роблю з directx і думаю, що це так швидко, як ви хотіли б. у мене немає швидкого зразка коду, але я знайшов це, що повинно бути корисним. версія directx11 не повинна сильно відрізнятися, directx9, можливо, трохи більше, але це шлях


2

Я усвідомлюю, що наступна пропозиція не відповідає на ваше запитання, але найпростіший метод, який я знайшов, щоб зафіксувати швидко мінливий вигляд DirectX, це підключити відеокамеру до S-відеопорту відеокарти та записати зображення як фільм. Потім перенесіть відео з камери назад у файл MPG, WMV, AVI тощо на комп'ютер.


2

Запис на екрані можна здійснити на C # за допомогою API VLC . Я зробив зразок програми, щоб продемонструвати це. Він використовує бібліотеки 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

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

Цей зразок написаний на C ++. Вам також потрібен досвід роботи з DirectX (D3D11, D2D1).

Що може зробити додаток

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