От Qt5Multimedia до QtAVPlayer

от автора

Qt 6.5: FFmpeg became enabled by default

Qt 6.5: FFmpeg became enabled by default

Во времена, когда Nokia владела Qt, инженеры были отважнее. Они разработали и релизнули QMediaPlayer в Qt5Multimedia для проигрывания медиа файлов на основе подгружаемых плагинов. Архитектура позволяла сначала выпустить API, а реализацию конкретного бекенда добавить позже. Поэтому API уже существовал даже там, где полноценного воспроизведения видео ещё не было.

С учётом того, что публичный API нельзя изменить или удалить в мажорной версии, кто готов проделать такое еще раз?

Этот подход породил интерфейсы, часть функционала которых не так работала или полностью отсутствовала на некоторых платформах.

GStreamer

Один из первых бекендов был на основе GStreamer и только под Linux.

Идея выбрать GStreamer была не самая плохая, на то время, и долгое время после, основными заказчиками плеера были девайсы под Embedded Linux, где были доступны необходимые gst-plugins. Никакие Windows или macOS не были в приоритете. Что, спустя время, накопило критическую разницу в поведении плеера под разные платформы: плеер просто по-разному работал или не работал вообще.

В поздних версиях Qt5Multimedia была попытка сделать мульти-плагины и использовать GStreamer под Windows или другие платформы. И выяснилось, что GStreamer не такой уж кросс-платформенный: если нет реализации под платформу, то нет функционала.

QtAV

Wang Bin на это посмотрел и придумал свой QtAV, кросс-платформенный фреймворк на основе FFmpeg. Альтернатива тяжелому, негибкому Qt5Multimedia, так еще и для Qt4. Нам казалось, что у QtAV и Qt5Multimedia разные ниши и для разных задач, поэтому все успехи QtAV игнорировались. И не смотря на чудовищную нестабильность QtAV, многие пользователи все больше предпочитали его.

QtAVPlayer

Время пришло предложить FFmpeg в качестве альтернативного бекенда в Qt5Multimedia. Что воспринималось крайне негативно. По правде сказать, любые идеи не находили одобрения.

В это время кипела работа над Qt6.0, где было решено не релизить Qt6Multimedia вообще, так что про него старались не вспоминать лишний раз.

Так появился QtAVPlayer, теперь уж точно полноценный кросс-платформенный медиа плеер на основе FFmpeg.

Идея была в том, чтобы полностью отделить плеер от рендеринга. Предоставить интерфейс, который будет работать одинаково под все платформы. С полным контролем и предсказуемым поведением. С поддержкой килер фич от FFmpeg, например hardware-accelerated кодеков, фильтров и многих других.

Основная фишка была в том, что видео можно было рендерить в QML/QWidget отдельно от плеера используя Qt5Multimedia. Для этого приходилось переходить черту добра и зла, и использовать приватные интерфейсы из Qt5Multimedia/private.

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

Бывало в сети писали, что QtAVPlayer это тоже самое что QtAV, просто с поддержкой новых версий FFmpeg, т.к. QtAV самоликвидировался, из-за возможной мести благодарных юзерей**.

QtAVPlayer — это в основном наследие ffmpeg + ffplay.

Qt6Multimedia

В какой-то момент разверзлись тучи безнадежности, и был выпущен Qt6Multimedia с дефолтным FFmpeg бекендом, под авторством самого главного шефа*, где были предоставленны QVideoSink и QAudioSink.

Это еще больше облегчило работу с QtAVPlayer, теперь стало возможным использовать QRhi для hardware-accelerated рендеринга без крови в глазах и мучения совести:

player->setSource(rtsp);QObject::connect(player, &QAVPlayer::videoFrame, player,    [&](const QAVVideoFrame &frame) {        videoSink->setVideoFrame(frame);    }, Qt::DirectConnection);player->play();

Если используется hwaccel, то декодирование происходит на GPU и фрейм содержит только handle в зависимости от платформы, например под Linux или Android — это OpenGL, под Windows — D3D11Texture2D, под macOS Metal.

QAVVideoFrame можно сконвертировать в QVideoFrame из QtMultimedia без копирования данных.

QRhi умеет понимать и рисовать такие фреймы так же без дополнительного копирования (zero-copy).

Зачем QtAVPlayer если есть Qt6Multimedia?

Так зачем нужен QtAVPlayer если Qt6Multimedia и так уже с FFmpeg?

Qt6Multimedia использует FFmpeg как внутренний бекенд.

QtAVPlayer делает FFmpeg частью публичной модели программирования. Поэтому функционал FFmpeg доступен by design: доступ к AVPacket, AVFrame, AVFormatContext, AVCodecContext и т.д.

Интерфейсы адаптированны под FFmpeg, значит можно контролировать весь процесс от декодирования до кодирования и сохранения в файл.

Основная фича плеера — это поддержка filter_complex, божественных фильтров, в том числе hw-accelerated. Где можно накладывать множественные аудио или видео фильтры параллельно или последовательно.

Например, если доступен CUDA, то два фильтра вернут два фрейма: один с оригинальным размером, другой поменьше, причем фрейм не будет покидать GPU, потому что используется scale_cuda:

// Requires to force cuda based codecplayer->setInputVideoCodec("h264_cuvid");// Applying the parallel filters returns the one original frame and the scaled one using `scale_cuda`player->setFilter("[0:v]split=2[orig][toscale];[toscale]scale_cuda=160:120[scaled]");

Не знаю насколько это полезно, но мне нравится возможность запустить несколько плееров и сохранить видео из одного плеера и аудио из другого в один файл.

QAVPlayer p1, p2;QAVMuxerFrames muxer;QObject::connect(&p1, &QAVPlayer::videoFrame, &p1,    [&](const QAVVideoFrame &f) { muxer.enqueue(f); }, Qt::DirectConnection);QObject::connect(&p2, &QAVPlayer::audioFrame, &p2,    [&](const QAVAudioFrame &f) { muxer.enqueue(f); }, Qt::DirectConnection);auto streams = p1.availableStreams() + p2.availableStreams();muxer.load(streams, "output.mkv");p1.play();p2.play();

Если доступен OpenGL, то можно обойтись без QtMultimedia вообще, используя QAVWidget_OpenGL для рендеринга в QOpenGLWidget:

auto w = new QAVWidget_OpenGL(mainWidget);QObject::connect(player, &QAVPlayer::videoFrame, w,    [w](const QAVVideoFrame &frame) {        w->setVideoFrame(frame);    }, Qt::DirectConnection);

QAVWidget_OpenGL доступен и под Windows, где реализована конвертация D3D11Texture2D в OpenGL через NV_DX_interop. Это позволяет рендерить фреймы под Windows в OpenGL без копирования.

Для проигрывания аудио, реализован QAVAudioOutput, с поддержкой выбора устройства:

auto audioOutput = new QAVAudioOutput(&mainWidget);auto audioDevices = QMediaDevices::audioOutputs();if (!audioDevices.isEmpty())    audioOutput->setAudioDevice(audioDevices.first());QObject::connect(player, &QAVPlayer::audioFrame, audioOutput,    [&](const QAVAudioFrame &frame) {        audioOutput->play(frame);    }, Qt::DirectConnection);

С полным списоком можно ознакомиться тут: https://github.com/valbok/QtAVPlayer

QtAVPlayer полностью совместим с Qt5.6 и Qt6.X.

PS. QtAVPlayer изначально был Qt Module, и получил нейминг как у всех модулей, что бывает сбивает с толку. Например почему не QAVPlayer? Потому что это имя класса, а QtAVPlayer имя модуля.

*- Chief Technology Officer

**- Благодаря невозможности продолжения использования QtAV, появились фильтры в QtAVPlayer.

ссылка на оригинал статьи https://habr.com/ru/articles/1080420/