С приходом тяжеловесных видеоформатов, таких как 4K (Ultra HD), проблема эффективности декодирования видеопотока стала достаточно актуальной. На среднем компьютере приходится принимать специальные меры для того, чтобы можно было обработать такой видеопоток в реальном масштабе времени. В статье рассказывается о возможных способах увеличения скорости декодирования видеопотоков в решениях, основанных на FFmpeg, и приводятся результаты экспериментов по измерению скорости декодирования для 4K видеопотоков, закодированных в H264 и HEVC(H265).




Оглавление


  1. Три способа повышения скорости декодирования видеопотока
    1.1. Подключение дополнительных рабочих потоков в стандартных декодерах
    1.2. Подключение аппаратного ускорения в стандартных декодерах
    1.3. Использование специальных декодеров, реализующих декодирование на графических процессорах
  2. Измерение скорости декодирования
  3. Замечания о QSV декодерах
  Ресурсы




1. Три способа повышения скорости декодирования видеопотока


Мы будем рассматривать три способа повышения скорости декодирования видеопотока.


  1. Подключение дополнительных рабочих потоков (threads) в стандартных декодерах.
  2. Подключение аппаратного ускорения (HW Acceleration) в стандартных декодерах.
  3. Использование специальных декодеров, реализующих декодирование на графических процессорах.

Первый из них использует исключительно возможности CPU, а два других задействуют возможности графических процессоров.


Доступные способы повышения скорости декодирования видеопотока достаточно сильно зависят от используемой операционной системы, аппаратной конфигурации компьютера и конфигурации FFmpeg. Все приведенные в статье результаты проверялись на следующей программно-аппаратной конфигурации: операционная система — Windows 10, ЦП — Intel i5 8400 2.80 ГГц (6 ядер без hyper-threading), встроенный графический процессор — Intel UHD Graphics 630, память — 16 ГБ, сборка FFmpeg 4.2.1, с zeranoe.


Для лучшего понимания архитектуры кодеков в FFmpeg можно посмотреть предыдущую статью автора, которая находится здесь.



1.1. Подключение дополнительных рабочих потоков в стандартных декодерах


Многие декодеры (но, конечно, не все) позволяют установить количество рабочих потоков, используемых для декодирования. Для этого перед вызовом avcodec_open2() член thread_count структуры AVCodecContext надо установить в требуемое значение. Другой способ — добавить опцию threads в словарь опций, передаваемый в качестве третьего аргумента в avcodec_open2().


Наиболее популярные декодеры, используемые для тяжелых форматов, (h264, hevc, vp9) поддерживают эту возможность, а вот theora нет.


Для подключения дополнительных потоков в командной строке надо использовать ключ -threads.



1.2. Подключение аппаратного ускорения в стандартных декодерах


FFmpeg позволяет для некоторых декодеров подключить аппаратное ускорение. При программировании с использованием FFmpeg API все необходимое для подключения к декодерам аппаратного ускорения находится в заголовочном файле libavutil/hwcontext.h. В этом файле определено перечисление enum AVHWDeviceType, каждый элемент которого и соответствует некоторому типу аппаратного ускорения. Какие типы аппаратного ускорения доступны в текущей сборке FFmpeg можно узнать с помощью следующего кода:


void print_hwtypes_all()
{
    AVHWDeviceType hwtype = AV_HWDEVICE_TYPE_NONE;
    while ((hwtype = av_hwdevice_iterate_types(hwtype)) !=
                            AV_HWDEVICE_TYPE_NONE)
    {
        printf("%s\n", av_hwdevice_get_type_name(hwtype));
    }
}

Для описанной выше программно-аппаратной конфигурации мы получим:


    cuda
    dxva2
    qsv
    d3d11va

Понятно, что cuda требует установки платы Nvidia и соответствущего ПО, qsv использует технологию Intel Quick Sync Video (QSV), реализованную на встроенных графических процессорах Intel (см. [1]), dxva2 и d3d11va используют технологию DirectX Video Acceleration (см. [1]), доступную только в Windows.


Конкретные декодеры не обязаны поддерживать все эти типы аппаратного ускорения (или хотя бы один из них). Для определения типов, поддерживаемых конкретным декодером, можно воспользоваться следующим кодом:


void print_hwtypes(const AVCodec *decoder)
{
    for (int i = 0; ; ++i) { 
        const AVCodecHWConfig *config =
               avcodec_get_hw_config(decoder, i);
        if (config) {
            if (config->methods &
                    AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) {
                printf("%s\n", 
                  av_hwdevice_get_type_name(config->device_type));
            }
        }
        else {
            break;
        }
    }
}

Для описанной выше программно-аппаратной конфигурации декодеры h264, hevc, vp9, vc1 поддерживают следующие типы аппаратного ускорения:


    dxva2
    d3d11va
    cuda

А вот theora вообще не поддерживает аппаратного ускорения.


Теперь совсем кратко рассмотрим процедуру подключения к декодеру аппаратного ускорения, за дополнительными деталями надо обратится к примеру hw_decode.c. Также можно посмотреть статью [3], написанную 2expres.


void init_hwdevice(AVHWDeviceType hwtype, AVCodecContext *codec_ctx)
{
    AVBufferRef *dev_ctx = NULL;
    int ret = av_hwdevice_ctx_create(&dev_ctx, hwtype, NULL, NULL, 0);
    if (ret >= 0) {
        codec_ctx->get_format = get_hw_format; // см. hw_decode.c
        codec_ctx->hw_device_ctx = av_buffer_ref(dev_ctx);
// сохранить  dev_ctx, чтобы освободить после декодирования
// с помощью av_buffer_unref()
    }
}

После декодирования данные кадра находится в некотором специальном формате, который определяется типом устройства, поэтому его надо конвертировать в один из обычных пиксельных форматов с помощью функции av_hwframe_transfer_data(). Для dxva2 и d3d11va этот формат будет NV12.


Для подключения аппаратного ускорения в командной строке надо использовать ключ -hwaccel.


1.3. Использование специальных декодеров, реализующих декодирование на графических процессорах


В состав FFmpeg входят два семейства кодеков, реализующих кодирование и декодирование на графических процессорах.



Одно семейство использует технологию Intel Quick Sync Video (QSV), реализованную на видеопроцессорах, интегрированных в процессоры Intel семейств i3, i5, i7, i9. Подробнее см. [1]. Эти кодеки имеют суффикс _qsv. В рассматриваемой сборке FFmpeg есть следующие декодеры: h264_qsv, hevc_qsv, vp8_qsv, mpeg2_qsv, vc1_qsv.


Другое семейство использует технологии NVDEC, NVENC реализованные на платах Nvidia. Декодеры имеют суффикс _cuvid. В рассматриваемой сборке FFmpeg есть следующие декодеры: h264_cuvid, hevc_cuvid, mpeg2_cuvid, vc1_cuvid, vp8_cuvid, vp9_cuvid, mjpeg_cuvid, mpeg4_cuvid.


После открытия входного потока доступ к декодеру обычно реализуется следующим образом:


AVStream *strm;
// ...
AVCodecID cid = strm->codecpar->codec_id;
const AVCodec *decoder = avcodec_find_decoder(cid);

Но таким образом можно получить только декодер по умолчанию для данного идентификатора кодека. Для альтернативных декодеров нужно использовать имя декодера примерно таким образом:


const AVCodec *decoder = (cid == AV_CODEC_ID_H264)
    ? avcodec_find_decoder_by_name("h264_qsv")
    : avcodec_find_decoder(cid);

Для использования альтернативных декодеров в командной строке надо использовать ключ -c:v расположив его перед ключом -i примерно таким образом


ffmpeg -c:v h264_qsv -i INPUT ...


2. Измерение скорости декодирования


Для экспериментов по измерению скорости декодирования были выбраны два видеоролика, один закодирован в H264, другой в HEVC(H265). Размер кадра — 3840х2160 (Ultra HD), скорость — 30 к/с. Тестировались стандартные декодеры — h264, hevc и соответствующие QSV декодеры — h264_qsv, hevc_qsv. Стандартные декодеры настраивались в 4х вариантах: по умолчанию, два рабочих потока, четыре рабочих потока, аппаратное ускорение dxva2. В наших экспериментах dxva2 показал лучшие результаты, чем d3d11va, поэтому последний не участвовал в измерениях скорости декодирования. Для проведения тестов была написана программа которая извлекала пакеты из файла и декодировала их с максимально возможной скоростью, игнорируя метки времени и не выполняя рендеринг или иную обработку. Было два режима этой программы: в первом выполнялось только декодирование, в втором еще производилось конвертирование декодированного кадра в 32-битный формат BGRA с использованием библиотеки libswscale. (На выходе декодера кадр обычно имеет 12-битный планарный формат YUV420P или NV12.) Проводилось измерение времени, затраченного программой, и фиксировалось относительное время по отношению к номинальной длительности видеопотока (в процентах). Таким образом, если результат меньше 100%, то у нас есть шанс обработать видеопоток в реальном масштабе времени, если больше, то таких шансов нет. Также с помощью Диспетчера задач фиксировалась примерная загрузка ЦП и графического процессора. Использовалась 64-битная сборка FFmpeg.


Таблица. Измерение скорости декодирования
h264 hevc
Config # Время CPU GPU Время CPU GPU
default 1 75 26 0 125 25 0
2 132 28 0 180 27 0
threads=2 1 47 42 0 74 42 0
2 79 48 0 104 46 0
threads=4 1 35 60 0 46 64 0
2 60 54 0 71 70 0
dxva2 1 45 14 72 34 28 70
2 107 28 35 99 30 36
xxxx_qsv 1 25 34 80 25 34 72
2 70 39 54 70 40 50


Большого обсуждения результаты, наверное, не требуют. Единственно на что стоит обратить внимание — это заметные затраты на преобразование в BGRA. И главное, что эти затраты сильно меняются в зависимости от тестовой конфигурации, хотя работа во всех случаях выполнялась очень близкая.


Эксперименты проводились также для 32-битной сборки FFmpeg. Результаты довольно близкие, кроме одного случая: декодер hevc в конфигурациях без аппаратного ускорения показал падение производительности в 2-3 раза. Весьма неожиданный результат.


Описанные тесты можно выполнить в командной строке. Надо использовать глобальный ключ -benchmark и установить нулевой выход. Вот несколько примеров:


ffmpeg -benchmark -i INPUT -f null -
ffmpeg -benchmark -threads N -i INPUT -f null -
ffmpeg -benchmark -c:v h264_qsv -i INPUT -f null -
ffmpeg -benchmark -hwaccel dxva2 -i INPUT -f null -
ffmpeg -benchmark -i INPUT -pix_fmt bgra -f null -

На выходе будет показан фактический fps, а параметр speed покажет во сколько раз он выше номинального.



3. Замечания о QSV декодерах


В рассматриваемой сборке FFmpeg есть следующие QSV декодеры: h264_qsv, hevc_qsv, vp8_qsv, mpeg2_qsv, vc1_qsv. Два последних оказались неработоспособными. Декодер mpeg2_qsv выдавал искаженную картинку, а vc1_qsv выдавал ошибку при передаче пакета на декодирование. Правда, эти декодеры не особо актуальны, но, все-таки, зачем выкладывать неработоспособные компоненты, не вполне понятно.


К оставшимся декодерам тоже есть претензии. В целом они работают, за исключением одного момента — они некорректно отрабатывают вызов avcodec_flush_buffers(). Ошибки нет, но после этого вызова позиционирование работает некорректно.



Ресурсы


[1] Intel Quick Sync Video.
[2] DirectX Video Acceleration.
[3] FFmpeg практика аппаратного декодирования DXVA2.