Аппаратное кодирование видео с 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) не показывается.