OpenCV: установка таймауту на очікування кадру в класі VideoCapture

Всім доброго часу доби! З'явилася тут якось завдання: відтворити RTSP-відеопотік з камери. Т. к. я в достатній мірі знайомий з API OpenCV, було прийнято рішення використовувати саме його. Для захоплення відеопотоку в OpenCV використовується клас VideoCapture. На жаль, мережа досить часто у нас обривається, і ця проблема на моєму рівні не вирішується, тому необхідною умовою для комфортної роботи стала досить швидка реакція на падіння відеопотоку — стандартний таймаут на підключення і очікування наступного кадру становить 30 секунд, причому всередині VideoCapture виклики open() і read() блокують, що змушує писати навколо простого насправді коду різні обгортки зразок виклику їх в окремому потоці і очікування отримання результату в асинхронному режимі. Природно, ніякої радості з цього приводу я не відчував — все це ресурси, які в програмі повинні були йти на інші цілі, не кажучи вже про ускладнення коду. Було прийнято рішення: змінити стандартний таймаут, або додати можливість його зовнішньої установки. Вийшов досить брудний хак, який, втім, може комусь стати в нагоді. Можливо, якщо є спосіб краще — якщо такий є — дуже б хотілося дізнатися його, так що прошу коментарів. В ідеалі — може бути, серед читачів Хабра знайдуться розробники OpenCV, які таки звернуть увагу на цю проблему. Метою було змусити код «працювати, як треба, під Windows x64».

Кому цікаво — прошу під кат.

Дослідження

За допомогою Гугла було з'ясовано, що дана проблема корінням впирається в ffmpeg — там є callback'і, які дають інформацію про розрив з'єднання. Таймаут в 30 секунд встановили в пулл-реквесте #6053. Проблема додалася в наступному вигляді: на поточний момент cmake-складальник завантажує файл opencv_ffmpeg.dll замість його складання на місці, причому інструкція по збірці з ffmpeg зникла. Код з константами таймауту (який, принаймні, в Windows ніяким чином не компілюється) знаходиться в файлі modules/videoio/src/cap_ffmpeg_impl.hpp:

#define LIBAVFORMAT_INTERRUPT_OPEN_TIMEOUT_MS 30000
#define LIBAVFORMAT_INTERRUPT_READ_TIMEOUT_MS 30000

Завдання, таким чином, сформувалася наступним чином:

  1. Змусити даний файл збиратися під Windows;
  2. Змінити в ньому константи;
  3. Переконатися у відсутності проблем при роботі.
Недовго думаючи, розповім про те, яким чином вона була вирішена. Для початку потрібно завантажити останню девелоперську версію ffmpeg з заголовочными файлами, dll, і бібліотека, і покласти все це по потрібних місцях в source/3rdparty — заради зручності, насправді можна скласти куди завгодно. Далі, потрібно внести наступні зміни в вихідні файли OpenCV:

modules/videoio/src/cap_ffmpeg.cpp:

Закоментувати в инклудах рядки, що стосуються потенційного включення cap_ffmpeg_api.hpp

//#if defined HAVE_FFMPEG && !defined WIN32
#include "cap_ffmpeg_impl.hpp"
//#else
//#include "cap_ffmpeg_api.hpp"
//#endif

У функції icvInitFFMPEG()

...
#ifdef WIN32...
//всі закоментувати, включаючи директиви препроцесора
#elif defined HAVE_FFMPEG
//залишити тільки те, що є всередині
#endif
...

Що тут сталося — ми відмовилися від використання підвантаження функціональності ffmpeg з opencv_ffmpeg.dll за допомогою дзвінки на loadlibrary, і переключилися на внутрішню реалізацію, яка знаходиться всередині файлу cap_ffmpeg_impl.hpp.

modules/videoio/src/cap_ffmpeg_impl.hpp:

Знаходимо вищевказані константи, змінюємо їх на бажані. У моєму випадку —

#define LIBAVFORMAT_INTERRUPT_OPEN_TIMEOUT_MS 2000
#define LIBAVFORMAT_INTERRUPT_READ_TIMEOUT_MS 1000

Даний файл в загальному-то не створений для складання під Windows, тому, деяка функціональність, необхідна для його компіляції, в ній — мова йде про snprintf. Код був стягнутий звідки-то з StackOverflow. Вставити в будь-яке зручне місце.

#if defined(_MSC_VER) && _MSC_VER < 1900
#define snprintf c99_snprintf
#define vsnprintf c99_vsnprintf
__inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap)
{
int count = -1;
if (size != 0)
count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap);
if (count == -1)
count = _vscprintf(format, ap);
return count;
}

__inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...)
{
int count;
va_list ap;
va_start(ap, format);
count = c99_vsnprintf(outBuf, size, format, ap);
va_end(ap);
return count;
}
#endif

Після внесення змін, потрібно змусити все це добро компілюватися. Для початку, потрібно згенерувати *.sln для OpenCV за допомогою CMake. Спроба складання opencv_videoio на поточний момент не спрацює. Поправимо. Відкриваємо Visual Studio-проект для модуля videoio: build/modules/videoio/opencv_videoio.sln. В include directories додаємо відмінності файли ffmpeg, його *.lib додаємо до лінкування.

Як тільки opencv_videoio буде збиратися, цю dll можна спокійно підкладати замість «стандартної». Пам'ятаємо, що тепер для роботи також будуть потрібні всі dll з постачання ffmpeg — без них програма працювати не буде.

Важливий момент: тепер, для того, щоб це працювало, як бекенда VideoCapture необхідно вказувати cv::CAP_FFMPEG.

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

Дякую за увагу.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.