FFmpeg практика аппаратного декодирования DXVA2

Привет! Эта статья продолжение моей статьи FFmpeg начало работы Visual Studio. Здесь мы приступим к аппаратному декодированию RTSP-потока FULL HD. Заранее скажу, что с данной задачей легко справится даже Intel ATOM Z8350.

Задача: аппаратное декодирование и запись до 4-х кадров в оперативную память для последующей параллельной обработки (четырьмя ядрами процессора) с IP-камеры RTSP h.264. Обработанные кадры отображаю с помощью функций WinAPI. Как итог мы получим быстродействующую систему для компьютерной обработки RTSP-потока в параллельном режиме. Далее можно подключать алгоритмы Компьютерного зрения для обработки кадров Real Time.

image


Вступление


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

DirectX Video Acceleration (DXVA) — это API для использования аппаратного ускорения для ускорения обработки видео силами графических процессоров (GPU). DXVA 2.0 позволяет перенаправлять на GPU большее количество операций, включая захват видео и операции обработки видео.
После написания предыдущей статьи мне было задано не мало вопросов: «почему использован именно FFmpeg?» Начну с проблематики. Основная сложность аппаратного декодирования состоит в записи раскодированного кадра в ОЗУ. Для Full HD это 1920×1080 х 3 = 6 220 800 байт. Даже с учетом того что кадр храниться в формате NV12 — это тоже немало 1920×1080 x 1.5 = 3 110 400 байт. Перезаписывать 75 Мбайт в секунду серьезная задача для любого процессора. Для решения этой задачи Intel добавила команды SSE 4, которые позволяют переписывать данные без участия процессора. К сожалению, не во всех библиотеках это реализовано. Мной были опробованы следующие библиотеки:

  1. FFmpeg
  2. VLC
  3. OpenCV


VLC — работает с IP-камерами через аппаратное декодирование (очень низкая загрузка процессора), примитивный проигрыватель потока RTSP можно построить буквально в 10 строк кода, но получение декодированных кадров в оперативную память занимает слишком много процессорного времени.

OpenCV — для работы с потоком RTSP использует FFmpeg, поэтому решено работать без посредников, т.е. использовать библиотеку FFmpeg. К тому же FFmpeg, который установлен по умолчанию, в OpenCV собран без аппаратного декодирования.

FFmpeg — показала хорошие, на мой взгляд результаты, работает стабильно. Единственный недостаток не реализована работа с WEB-камерами для версии X86 (X64 вроде позволяет работать) в Windows.

Аппаратное декодирование видео — это просто


На самом деле аппаратное декодирование с помощью библиотеки FFmpeg — не сложнее программного. Настройки проекта такие же, как и для программной реализации, блок-схема осталась без изменений.

Вывести на экран список поддерживаемых FFmpeg методов аппаратного декодирования можно

fprintf(stderr, " %s", av_hwdevice_get_type_name(type));


Первое, что нам нужно сделать — это сообщить FFmpeg с помощью какого аппаратного декодера Вы хотите декодировать видео. В моем случае Windows10 + Intel Atom Z8350 оставляют только DXVA2:

type = av_hwdevice_find_type_by_name("dxva2");


Вы же в качестве аппаратного декодера можете выбрать CUDA, D3D11VA, QSV или VAAPI (только Linux). Соответственно у вас должно быть данное аппаратное решение и FFmpeg должен быть собран с его поддержкой.

Открываем видеопоток:

avformat_open_input(&input_ctx, filename, NULL, NULL;


Получаем информацию о видеопотоке:

av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);


Выделяем память:


frame = av_frame_alloc(); // здесь храниться декодированный файл вне ОЗУ
sw_frame = av_frame_alloc(); // Сюда мы перепишем декодированный файл в ОЗУ


Данная функция переписывает декодированный файл в ОЗУ:

av_hwframe_transfer_data(sw_frame, frame, 0);


Немного о формате NV12


Итак, мы получили кадр в структуру sw_frame. Полученный кадр храниться в формате NV12. Данный формат был придуман Microsoft. Он позволяет хранить информацию о пикселе в 12 бит. Где 8 бит интенсивность, а 4 битами описывается цветность (вернее цветность сразу описывается для 4-х рядом стоящих пикселей 2×2). Причем, sw_frame.data[0] — храниться интенсивность, а в sw_frame.data[1] — храниться цветность. Для перевода из NV-12 в RGB можете воспользоваться следующей функцией:

C++ перевод из NV12 в RGB
void SaveFrame(uint8_t * f1, uint8_t * f2, int iFrame) {
	FILE *pFile;
	char szFilename[32];
	int  x, i, j;
//	char buff[1920 * 1080 * 3];
	uint8_t *buff = new uint8_t(1920*3*2);
	int u=0, v=0, y=0;

		// Open file

	sprintf(szFilename, "frame%d.ppm", iFrame);
	pFile = fopen(szFilename, "wb");
	if (pFile == NULL)
		return;

	// Записуем заголовок файла
	fprintf(pFile, "P6\n%d %d\n255\n", 1920, 1080);

	for (j = 0; j < 1080 / 2; j++) {
		for (i = 0; i < 1920; i +=2) {
			
			// 1 точка rgb
			y = *(f1 + j * 1920 * 2 + i);
			v = *(f2 + j * 1920 + i) - 128;
			u = *(f2 + j * 1920 + i + 1) - 128;

			x = round(y + 1.370705 * v);
			if (x < 0) x = 0;
			if (x > 255) x = 255;
//			if (j > 34) printf("%i, ",(j * 1920 * 2 + i) * 3);
			buff[i * 3 + 2] = x;
			x = round(y - 0.698001 * v - 0.337633 * u);
			if (x < 0) x = 0;
			if (x > 255) x = 255;
			buff[i * 3 + 1] = x;
			x = round(y + 1.732446 * u);
			if (x < 0) x = 0;
			if (x > 255) x = 255;
			buff[i * 3] = x;

			// 2 точка rgb
			y = *(f1 + j * 1920 * 2 + i + 1);
			x = y + 1.370705 * v;
			if (x < 0) x = 0;
			if (x > 255) x = 255;
			buff[i * 3 + 5] = x;
			x = y - 0.698001 * v - 0.337633 * u;
			if (x < 0) x = 0;
			if (x > 255) x = 255;
			buff[i * 3 + 4] = x;
			x = y + 1.732446 * u;
			if (x < 0) x = 0;
			if (x > 255) x = 255;
			buff[i * 3 + 3] = x;
			
			// 3 точка rgb
			y = *(f1 + j * 1920 * 2 + 1920 + i);
			x = y + 1.370705 * v;
			if (x < 0) x = 0;
			if (x > 255) x = 255;
			buff[(1920 + i) * 3 + 2] = x;
			x = y - 0.698001 * v - 0.337633 * u;
			if (x < 0) x = 0;
			if (x > 255) x = 255;
			buff[(1920 + i) * 3 + 1] = x;
			x = y + 1.732446 * u;
			if (x < 0) x = 0;
			if (x > 255) x = 255;
			buff[(1920 + i) * 3 + 0] = x;
			// 4 точка rgb
			y = *(f1 + j * 1920 * 2 + 1920 + i + 1);
			x = y + 1.370705 * v;
			if (x < 0) x = 0;
			if (x > 255) x = 255;
			buff[(1920 + i) * 3 + 5] = x;
			x = y - 0.698001 * v - 0.337633 * u;
			if (x < 0) x = 0;
			if (x > 255) x = 255;
			buff[(1920 + i) * 3 + 4] = x;
			x = y + 1.732446 * u;
			if (x < 0) x = 0;
			if (x > 255) x = 255;
			buff[(1920 + i) * 3 + 3] = x;
//			printf("%i, ", i);
			
		} // for i
		fwrite(buff, 1, 1920 * 3 * 2, pFile);
		printf("\n   %i\n", j);
	} // for j
//	printf("Save4\n");
	// Write pixel data
//	fwrite(buff, 1, 1920*1080*3, pFile);
	// Close file
	printf("close\n");
	fclose(pFile);
	printf("exit\n");
//	delete buff;
//	return;
}


Хотя работа с NV12 позволяет ускорить выполнение таких процедур, как размывка, Retinex и получение изображения в оттенках серого (просто отбросив цветность). В моих задачах я не перевожу формат NV12 в RGB, так как это занимает дополнительное время.

И так мы научились аппаратно декодировать видеофайлы и выводить их в окно. Познакомились в форматом NV12 и как его преобразовывать в привычный RGB.

Dll аппаратного декодирования


Кадры FFmpeg выдает через 40 мс (при 25 кадрах в секунду). Как правило, обработка кадра Full HD занимает значительно больше времени. Для этого требуется организовать много поточность, для максимальной загрузки всех 4-х ядер процессора. Я на практике один раз запускаю 6 потоков и больше их не снимаю, что значительно упрощает работу и увеличивает надежность работы программы. Схема работы приведена на рис. 1

image
рис. 1 Схема построения многопоточной программы с FFmpeg

Я написал свой декодер в виде *.dll (FFmpegD.DLL) для включения в свои проекты. Это позволяет сократить код-проекта, что повышает понимание кода и включать в любые языки программирования, вплоть до Ассемблера (проверено:)). С помощью нее мы напишем свой проигрыватель RTSP-потока с IP-камеры.

Для начала работы с DLL нужно передать указатель массив int[13], HANDLE события поступления нового кадра, HANDLE начала обработки нового пакета данных с камеры и массив char адрес камеры.

Структура массива дана в таблице 1.

image

Перед вызовом необходимо обнулить номера кадров 1–4.

DLL выполнит все необходимые действия по инициализации FFmpeg и будет записывать указатели и номера кадров. После установит событие «Поступление нового кадра». Нужно только обрабатывать поступающие кадры и вместо номера кадра записывать 0 (это значит кадр обработан и больше не используется).

Ниже Вы найдете пример проигрывателя с исходным кодом. За основу взят пример ShowDib3 Charles Petzold.

→ Архив с проектом
→ Архив FFmpegD.dll

ИТОГ: аппаратный детектор движения FFmpeg даже на Intel Atom Z8350 декодирует h264 Full HD в реальном времени с загрузкой процессора до 20% с подключенный детектором движения.


Пример работы детектора движения на Intel ATOM Z8350. Первые 30 сек идет подсчет фона. После работает детектор движения по методу вычитания фона.

P.S. Так же можете декодировать и видеофайлы (сжатые h.264)!!!

Ссылки:

  1. Разная полезная информация по FFmpeg
  2. Информация по использованию различных библиотек, предоставляемых FFmpeg
  3. Информация о форматах и переводе в RGB

© Habrahabr.ru