Реализация собственного плеера посредством Qt + FFmpeg
В общем, с классами QMediaPlayer, QVideoWidget и QVideoProbe реализовать нужный мне функционал не получается. Недавно появившийся в Qt 5.15 метод QVideoWidget::videoSurface(), который должен вернуть указатель на видеоповерхность QAbstractVideoSurface, ломает работу проигрывателя с ошибкой:
DirectShowPlayerService::doRender: Unknown error 0x80040266
Которую я, естественно, гуглил, и проблема тут не в кодеках, т.к. K-Lite Codec Pack Basic был установлен, и видеофайлы проигрываются до момента вызова вышеупомянутого метода.
Мне необходимо отслеживать оригинальный размер кадра и масштабированный под размер виджета, в который осуществляется вывод, а также координаты начала этого кадра на виджете (т.к. масштабирование должно быть пропорциональным).
Допустим я создал класс-обёртку для кода FFmpeg. И я получаю кадры из него (QImage или QPixmap), и рисую их QPainter'ом на QWidget. Т.к. приложением управляет QApplication, а рисовать нужно постоянно, видимо нужно будет запускать этот проигрыватель в отдельном потоке.
Подскажите, как лучше это реализовать? Возможно я где-то ошибаюсь, и хорошо бы это узнать сейчас, потому что я себе это смутно представляю. Передача и приём сигналов при этом будут работать с другим потоком?
Ответы (1 шт):
В ffmpeg из потока (AVStream) вы получаете AVPacket, которые надо разжать/декодировать в AVFrame, в котором и находятся оригинальные размеры изображения.
Общая схема работы такая:
- Поток подключения к источнику данных и получения сжатых пакетов AVPacket
- Поток декодирования AVPacket в AVFrame (Здесь же конвертор AVFrame из внутреннего формата в привычный RGB + масштабирование, по необходимости) 2* + потоки, по необходимости, для декодирования аудио, текста и так далее
- Главный поток приложения (с отображением кадра на экране). AVFrame к этому моменту должен содержать размеры, формат данных и указатель на начало данных, чтобы передать все это в QImage - это "прозрачная" операция, то есть AVFrame будет выступать в качестве данных, а QImage - как оболочка/посредник к этим данным.
Кроме того, вам необходимо хранить полученные пакеты AVPacket в циклической очереди. Поток 1. будет класть эти пакеты в очередь, а Поток 2. считывать
Еще одна циклическая очередь нужна, чтобы хранить готовые кадры между потоками 2 и 3, из которого по таймеру в главном потоке приложения (3), эти кадры будут забираться и отображаться на экране с частой fps (задается в таймере).
Конечно, если вы собираетесь все это реализовать на Qt, то необходимо ознакомиться с механизмом сигналов и слотов, что существенно облегчит вашу задачу.