Как стать автором
Обновить

Комментарии 32

Быстро аппаратно декодировать H.264 на GPU чтобы потом попиксельно конвертировать NV12 в RGB на хосте. Вместо того же GPU, который создан для этого. Ладно не привели CUDA или OpenCL кернел, хотя бы упомянули об этом, или о векторизации на хосте.
1. А зачем переводить NV12 в RGB? В статье я написал почему я этого не делаю, хотя такая возможность имеется.
2. Я пишу на ATOM Z8350, какое там CUDA?
3. На OpenCL можно перевести в RGB, но какой в этом смысл вместо 3 МБайт(NV12), нужно переписать 6 МБайт(RGB)?
1. Для вывода на экран.
2. CUDA или OpenCL, внимательно читаете?
3. Приведенный пример конвертации мягко говоря неудачный, вам было на это указано.
почему бы Вам просто не скопипастить сюда ffmpeg_hw из примеров ffmpega? А еще лучше ffplay, он хоть YUV, через SDLные шейдеры рисует, а не терзает старичка GDI
ffmpeg_hw и был взят за основу. Но у меня другая задача, мне нужно не просто отобразить кадры на экране, а держать в памяти до 4-х кадров для их обработки. Если бы у меня был 24-ядерный процессор, я бы держал 24 кадра в памяти, и для обработки одного кадра у меня было бы 40мс х 24 = 960 мс. А сейчас мне необходимо обработать 1 кадр за 160 мс.
Отображение кадра у меня это не основное, кстати, а чем плох GDI? Кроме того, что SDL кроссплатформенный, а GDI только под Windows.
тем, что SDL работает повер опенгл со всеми вытекающими(YUV->RGB, например конвертится в шейдере, бесплатный ресайз, кроп и т.д) и диффкцитные такты ЦП не расходуются, причем в текстурки льется по DMA. Проблем держать в памяти, хоть 44 кадра, тоже не вижу
А еще лучше ffplay

А как Вы побороли задержки по воспроизведению rtsp в ffplay? Я пробовал только вначале на Pentium'е было секунды 3, что на мой взгляд не приемлемо.
о каких задержках идет речь? Если речь о задержке в начале, то ясно, что ничего не будет, пока не придет очередной I-фрэйм, если Вы о low latency, то что-то вроде -nocache -probesize и -analyzeduration, точно не помню. И конечно на источнике настроить h264 профиль без B-фреймов и RTSP поверх UDP. У меня с хайвижина 1980p@25 на малине 0.2 сек, кодек mmal льет в текстурку через EGL_KHR_image, в шейдере уив->ргб. Использую, как дверной глазок
(YUV->RGB, например конвертится в шейдере, бесплатный ресайз, кроп и т.д)

Мне это не нужно. Цель не проигрыватель (их много, можно и VLC использовать, он еще меньше загружает ЦП, чем FFmpeg). Мне нужно получить кадр в памяти, запустить на одно из ядер для обработки алгоритмами компьютерного зрения, согласовать последовательность кадров (так как кадры могут обрабатываться за разное время) и результат отобразить на экране.
Какой «чудный» код. У вас память течет.
В коде, за спойлером (C++ перевод из NV12 в RGB) действительно не освобождался buff, чисто случайно вышло при удалении комментария. И я практически не применяю перевода NV12 в RGB. Этот пример демонстрирует работу с NV12 напрямую.
В кодах и dll утечек памяти нет, проверено на продолжительной работе.
uint8_t *buff = new uint8_t(1920*3*2);
...
pFile = fopen(szFilename, "wb");
	if (pFile == NULL)
		return; // вот тут она и потечет

Провререно. У вас просто временной интервал теста маловат.
Место на диске кончится, или сетевой диск отвалится, злой пользователь или антивирус права доступа поправит, и не откроется у вас это файл гарантированно.
Бонусом в коде еще слишком много всего прекрасного.

P.S. Скобки у new/delete точно правильные?
Суть не в том, что она течёт или не течёт, а в том, что код ужасен. Это уже не С, но ещё не С++. RAII, smart pointers, streams — вот это С++.

В состав ffmpeg входит библиотека swscale, которая позволяет конвертировать в разные цветовые пространства. Все функции там оптимизированы с использованием SSE

Про swscale я писал в предыдущей статье, там есть пример. Но чем плохо работать с NV12 напрямую? Я делаю так для минимизации загрузки процессора.
Да в общем-то ни чем не плохо. Я писал нечто похожее для проекта распознавания лиц и там нужен был BGR, так как использовался opencv и каскады хаара.
avformat_open_input(&input_ctx, filename, NULL, NULL

Стоит добавить, что все функции ffmpeg API стоит проверять на возвращаемое значение и обрабатывать ошибки, так как в противном случае может вылезти сегфолт в самом неожиданном месте и вы долго будете искать почему так.
Обработка ошибок в коде dll сделана.
if (pFile == NULL)
return;


Возможно, здесь стоит возвращать код ошибки:

if (pFile == NULL)
return S_ERROR;

Так как вы не узнаете иначе про ошибку
fwrite(buff, 1, 1920 * 3 * 2, pFile);

Обратите внимание, что функция fwrite возвращает некоторое число, которое тоже надо обрабатывать, так как не всегда может получится записать ровно столько байт сколько задумано
char szFilename[32];


Такие вещи обычно именуются константой, хотя бы так: const int MAX_BUFF_SIZE = 32
Ну и 32 явно может не хватить для файла с длинным названием (например путь до него)
void SaveFrame(uint8_t * f1, uint8_t * f2, int iFrame)

f1, f2 не меняются в коде? Можно сделать их const это позволит избежать некоторых ошибок, тем более вы с памятью работаете
uint8_t *buff = new uint8_t(1920*3*2);

Так ли необходимо каждый раз буфер выделять?
Возможно, с точки зрения скорости, стоит выделить один раз и сделать его членом класса.
Перезаписывать 75 Мбайт в секунду серьезная задача для любого процессора


Пиковая пропускная способность планки памяти DDR4-1600 составляет 12.8 Гб/с
Память, как правило, работает в 2х-канальном режиме. Итого 25.6 Гб/с
Мобильные процессоры ARM 3-4 года назад выдавали 15-16 Гб/с на некоторых алгоритмах обработки изображений с 2х-канальной DDR4.
Вы ошиблись на 2 порядка.
А зачем тогда нужен кеш 1,2 и 3 уровня???

Я надеюсь, вы сейчас не всерьёз это сказали. У кэшей совсем другое устройство на физическом и логическом уровне, а так же иные задержки доступа. Там другие политики согласованности данных (протоколы кэш-когерентности).

На самом деле, Н.264 — совсем несложный кодек, и даже одно ядро Atom в софтверном режиме спокойно понятет декодирование.

Извините за капитанский комментарий, но я бы на вашем месте начал со сборки последней версии ffmpeg из git master на свежем компиляторе, с оптимизациями. Затем проверил бы уровень загрузки ядер при (де)кодировании видео.

Вывод: программное декодирование потока RTSP Full HD H.264 занимает до двух ядер Intel ATOM Z8350 к тому же периодически происходит потеря пакетов, из-за чего часть кадров декодировано неправильно. Данный способ более применим для декодирования записанных видео файлов, т. к. не нужна работа в реальном масштабе времени.

Вот вывод из вашей прошлой статьи, но нет никакого анализа производительности. Для начала можно запустить ffmpeg под встроенным профилировщиком Visual Studio, посмотреть на hotspot'ы. Возможно, это не получится сделать на сборках zeranoe, если оттуда были вырезаны символы во время компиляции.

В целом подход, вы начинаете заниматься оптимизацией проекта, не зная, что именно тормозит вашу текущую имплементацию.
Я надеюсь, вы сейчас не всерьёз это сказали.

Нет, очень серьезно. Я занимаюсь обработкой в Реал-Тайм. Работа с основной памяти занимает 100-150 тактов машинного времени (я же привел ссылку https://habr.com/ru/company/otus/blog/343566/). А по вашему на тактовой частоте процессора могу считывать и записывать 1 байт информации в основную память? Можете привести пример кода перезаписи 100 МБайт за 31*2 = 62 мс?
А по вашему на тактовой частоте процессора могу считывать и записывать 1 байт информации в основную память? Можете привести пример кода перезаписи 100 МБайт за 31*2 = 62 мс?

Не понял вашу мысль. Сильно упрощённо, процесс чтения из памяти происходит так:
У процессора есть инструкция чтения из памяти. Грубо говоря, ReadWord. Принимает указатель, кладёт содержимое памяти в регистр.
Есть контроллер DMA, который:
  1. принимает череду таких команд ReadWord
  2. преобразует их в набор запросов на чтение согласно спеки интерфейса шины памяти

При этом используются оптимизационные техники, такие как address coalescing (множество мелких чтений преобразуются в один длинных запрос DMA) и прочие.

Этот процесс, как правило, асинхронный (пока приходят новые данные, процессор делает вычисления над тем, что уже прочёл). Такая техника (latency hiding) лежит в основе высокой производительности GPU, например. Если процесс асинхронный, то частота процессора влияет только на то, как много запросов в единицу времени он генерирует.

Таким образом, процесс чтения и записи в память можно рассматривать как схему «производитель — потребитель», где производителем является процессор (генерирует запросы ReadWord), а потребителем — контроллер памяти, который эти запросы читает, оптимизирует и выполняет.

В реальной жизни всё сложнее, контроллеры DMA могут выполнять много всякой разной работы — например, транспонировать маленькие матрицы на лету, чтобы процессор читал столбцы кусочков изображений в векторные регистры. Могут преобразовывать цветовые пространства и ещё много чего.

Можете привести пример кода перезаписи 100 МБайт за 31*2 = 62 мс?


#include <iostream>
#include <algorithm>
#include <chrono>
#include <vector>

using namespace std;
using namespace chrono;

// 1024 * 1024 elements;
static const size_t numElems = 1048576UL;

int main()
{
	vector<int> src(numElems), dst(numElems);
	vector<double> bandwidth;
	const size_t vectorSize = src.size() * sizeof(src.front());

	for (int numLaps = 0; numLaps < 1024; numLaps++) {
		void* const source = src.data();
		void* destination = dst.data();
		auto start = system_clock::now();
		memcpy(destination, source, vectorSize);
		auto end = system_clock::now();
		auto time = duration_cast<microseconds>(end - start).count();
		auto bw = double(vectorSize) / double(time);
		
		cout << bw << " megabytes / s" << endl;
		bandwidth.push_back(bw);
	}

	double acc = 0;
	for (auto bw : bandwidth) {
		acc += bw;
	}

	acc = acc / bandwidth.size();
	cout << "Avg bandwidth: " << acc << " megabytes / s" << endl;

	return 0;
}


Выдаёт чуть меньше 7 Гбайт/с на моей машине. Это примерно те 75Мб/с, умноженные на 100, о которых я говорил в первом комментарии. Машина вот такая: VS 2019, Core i7-7770, DDR4 (не знаю частоту, скорее всего 2133). У вас на Atom'е будет меньше. Но не на порядок.
Спасибо, за информацию по перезаписи из оперативной памяти в оперативную память.
Но декодированный кадр после DXVA2 я получаю в uncacheable speculative write combining (USWC). Память USWC очень медленно читается (раз в 20 медленней). ATOM с этой перезаписью плохо справляется (проверено на VLC). Для ускорения используются инструкции SSE4, как написано в статье.
Весь смысл в HW-acceleration'е декодирования видео — в том, чтобы не гонять декодированный кадр обратно в RAM, а держать в памяти GPU для последующего показа.

Как только вы пытаетесь скачать его обратно — начинаются проблемы. Задержка на получение кадра и его передачу обратно в RAM в принципе может убить весь выигрыш по скорости. Если вы делаете пост-обработку кадра после декодирования, делайте её на шейдерах / OpenCL.

На вашем месте, я бы начал с замера производительности декодирования Н.264 на одном ядре Atom'а на свежей сборке libx264 из исходников со всеми оптимизациями. Есть немалый шанс, что декодирование не будет bottleneck'ом.

ATOM с этой перезаписью плохо справляется (проверено на VLC). Для ускорения используются инструкции SSE4, как написано в статье.

У вас система с общей памятью. Никаких перезаписей вообще делать не нужно, только выделение на стороне GPU с последующим отображением в память CPU. Посмотрите в сторону habr.com/ru/post/215359
Все так, только не совсем понятно, как обеспечить передачу указателя памяти из vaapi или vdpau/dxva в opencl да еще и каким-то унифицированным способом для всех этих технологий. Плюс, допустим конвертация из NV12 в RGB при помощи opencl может и не дать прироста. Было бы интересно попробовать, кстати.
Посмотрите на vendor extension'ы для OpenCL от Intel.
Ещё можно покопаться в исходниках этого проекта: github.com/intel/libxcam.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории