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

17 июня 2019

Аппаратное кодирование видео с ffmpeg и nvidia

По мере того, как телефоны становятся мощнее компьютеров, внутри компьютеров периферийные процессоры становятся мощнее центральных.

Аппаратные видеоакселераторы являются тому ярким примером.

Первоначально разработанные для развлечения избалованных геймеров и распространившиеся ради обогащения майнеров, они всё чаще находят и общественно-полезное применение.

Таковым, например, является перекодирование видео в CDN — поставщики содержимого размещают видео, не задумываясь о технических деталях, а CDN берёт на себя обязанность перекодировать его в поддерживаемый клиентом формат, с оптимальными для клиента качеством и разрешением.

Кодирование является веcьма ресурсоёмкой задачей, поэтому появление аппаратного ускорения в популярных инструментах было вопросом времени:

https://trac.ffmpeg.org/wiki/HWAccelIntro#NVENCNVDEC

Ниже мы перечисляем детали, оказавшиеся для нас неочевидными.

Good bye, platform independent static binary

До сих пор мы использовали «почти официальные» статические сборки ffmpeg от https://www.johnvansickle.com/ffmpeg/ и они нас полностью устраивали — как набором включённых опций и библиотек, так и оперативностью выпусков.

Естественным желанием было либо добавить в него модуль nvenc, либо найти кого-то, кто уже сделал это до нас. Однако быстро выяснилось, что такой вариант неосуществим, т.к. модулю для работы требуются библиотеки NVidia, но эти библиотеки распространяются только в динамическом виде, т.е. собранное с ними приложение перестанет быть статическим:

“CUDA is only supplied as shared libraries.”

https://devtalk.nvidia.com/default/topic/418749/linking-frustration-lcuda-fails/

Из-за того, что библиотеки являются платформенно-зависимыми — ffmpeg с nvenc тоже получается платформенно-зависимым, т.е. собирать его приходится на той же платформе, на которой планируется запускать.

Многие приложения (например, Apache и Nginx) научились поддерживать модульность не только на стадии сборки, но и на стадии выполнения. К сожалению, ffmpeg пока не из их числа. В противном случае можно было бы иметь статический ffmpeg и собранный динамически дополнительный модуль nvenc, который подгружался бы только при наличии соответствующих ключей запуска и установленных библиотек.

Список поддерживаемых дистрибутивов и версий в https://developer.download.nvidia.com/compute/cuda/repos/ включает в себя RHEL, SLES, Fedora, Ubuntu и OpenSUSE — Debian и прочие любительские оси в нём отсутствуют.

Наш сценарий сборки проверен на CentOS 7, Ubuntu 16.04 и 18.04, а инсталляционный пакет мы подготовили только для CentOS.

Good bye, kernel independent userland

Библиотеки взаимодействуют с видеокартой не напрямую, а через драйвер, который собирается и устанавливается через DKMS.

Для сборки драйвера необходим пакет kernel-devel, но автоматической зависимости на него нет, поэтому будьте готовы поставить вручную:

yum install -y epel-release kernel-devel git wget
reboot   #..если yum установил новое ядро
yum install -y https://developer.download.nvidia.com/compute/cuda/repos/rhel7/x86_64/cuda-repo-rhel7-10.0.130-1.x86_64.rpm
yum install -y cuda-drivers cuda-runtime-10-0

Теперь можем устанавливать и запускать собранный нами ffmpeg:

yum install -y http://repo.docker.ru/pub/linux/centos/7/noarch/docker-release-1-1.el7.noarch.rpm
yum install -y ffmpeg-cuda
ffmpeg-cuda -hide_banner -h encoder=hevc_nvenc

Определённую сложность может создать отслеживание версий CUDA API:

  • сначала необходимо понять, с какой из них собран ffmpeg-cuda (например, через ldd /bin/ffmpeg-cuda | grep npp),
  • затем найти и установить соответствующий cuda-repo-rhel7,
  • и из него установить cuda-runtime-X-X (10-0 на момент написания заметки).

Мы попытались автоматизировать эти шаги, но не отлаживали наш сценарий достаточно тщательно, поэтому используйте его на свой страх\риск:

curl https://gist.githubusercontent.com/ilyaevseev/bbe85d1bf231e3f443498a8c6e3d3020/raw//ffmpeg-cuda-install.sh | sh -

Build it yourself

В сети оказалось много описаний по сборке ffmpeg с nvenc и без него, разной степени свежести и подробности.

Наш вариант стал симбиозом инструкций из стандартной документации (для Ubuntu и CentOS) с инструкцией по сборке nvenc от Brainiarc7.

Закончилось тем, что сам Brainiarc7 сделал форк нашего репозитария и перенёс наши наработки в свой проект.

Damn runtime limits

По непонятным причинам Nvidia решила ограничить максимальное количество одновременно запущенных процессов кодирования (т.н. “concurrent sessions”) на бюджетных GPU двумя штуками:

https://developer.nvidia.com/video-encode-decode-gpu-support-matrix

По ещё менее понятным причинам ограничение реализовано не аппаратно, а программно — внутри одной из библиотек, без защиты от отладки и изменения.

Разумеется, коллективный разум нашёл способ его обойти:

git clone https://github.com/keylase/nvidia-patch
sh nvidia-patch/patch.sh

Feel the force, Luke

Перенос кодирования с центрального процессора на видеокарту оказался немедленно замечен всеми сотрудниками тестовой лаборатории, потому что шумевший до этого как пылесос сервер начал работать беззвучно.

Разница в быстродействии также оказалась ощутимой: если Xeon E5-2620 v2 (12 ядер, 2.10GHz) сконвертировал тестовое видео за 175 секунд со скоростью 150 fps, то видеокарта справилась за 52 секунды и 540fps, т.е. в 3,5 раза быстрее.

Что и как проверять?

Для проверки состояния видеокарты рекомендуется использовать утилиту nvidia-smi. Вот пример её вывода:

# nvidia-smi
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 410.48                 Driver Version: 410.48                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Quadro P400         Off  | 00000000:04:00.0 Off |                  N/A |
| 34%   47C    P0    N/A /  N/A |    257MiB /  2000MiB |    100%      Default |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0      2055      C   ffmpeg-nvenc                                 123MiB |
|    0      2056      C   ffmpeg-nvenc                                 123MiB |
+-----------------------------------------------------------------------------+

Ключевыми параметрами для нас являются Volatile GPU-Util и Memory usage:

  • если GPU-Util увеличивается до 100%, запуск новых процессов перестаёт приводить к росту суммарного быстродействия,
  • если Memory usage вырастает до 100%, запуск процессов становится невозможным.

Обратите внимание, что в отличие от центрального процессора, загрузка по ядрам CUDA внутри GPU (которых, если верить спецификации, в нём 256) не показывается.



← Назад в Блог

Подпишитесь на новые статьи: