Мультимедиа

Воспроизведение видео: QVideoWidget/QML VideoOutput, потоковое видео

8 просмотров

Введение

Аудио воспроизводится «невидимо» — для видео нужен дополнительный компонент, отображающий декодированные видеокадры на экране. В этой статье разберём QVideoWidget для классических Qt Widgets приложений и VideoOutput для Qt Quick/QML, а также базовые аспекты воспроизведения сетевого, потокового видео.

Концепция

QVideoWidget — готовый виджет, который достаточно поместить в layout приложения и подключить к QMediaPlayer через setVideoOutput(), после чего он автоматически отображает декодируемые плеером видеокадры без необходимости вручную обрабатывать отдельные кадры. В Qt Quick аналогичную роль играет VideoOutput — QML-элемент, подключаемый к плееру таким же образом, но интегрированный в декларативную сцену QML наравне с другими визуальными элементами, что даёт более простую анимацию и компоновку видео в составе более сложного, современного интерфейса. Воспроизведение сетевого видео (по HTTP/HTTPS) использует тот же QMediaPlayer, принимающий сетевой QUrl вместо локального файлового пути — внутренняя буферизация и постепенная загрузка обрабатываются плеером автоматически, без необходимости вручную управлять загрузкой потока.

Пример кода

// QVideoWidget в классическом Qt Widgets приложении
#include <QVideoWidget>
#include <QMediaPlayer>
#include <QAudioOutput>

class VideoPlayerWidget : public QWidget
{
    Q_OBJECT
public:
    VideoPlayerWidget(QWidget *parent = nullptr) : QWidget(parent)
    {
        m_videoWidget = new QVideoWidget(this);
        m_player = new QMediaPlayer(this);
        m_audioOutput = new QAudioOutput(this);

        m_player->setVideoOutput(m_videoWidget); // связь плеера с виджетом отображения видеокадров
        m_player->setAudioOutput(m_audioOutput);  // отдельная связь для звуковой составляющей видео

        auto *layout = new QVBoxLayout(this);
        layout->addWidget(m_videoWidget);
    }

    void playVideo(const QUrl &source)
    {
        m_player->setSource(source);
        m_player->play();
    }

private:
    QVideoWidget *m_videoWidget;
    QMediaPlayer *m_player;
    QAudioOutput *m_audioOutput;
};
// VideoOutput в Qt Quick — интеграция видео в декларативную QML-сцену
import QtQuick
import QtMultimedia

Rectangle {
    width: 800; height: 600

    MediaPlayer {
        id: mediaPlayer
        source: "https://example.com/sample_video.mp4" // сетевой источник
        audioOutput: AudioOutput {}
        videoOutput: videoOutput
    }

    VideoOutput {
        id: videoOutput
        anchors.fill: parent
        fillMode: VideoOutput.PreserveAspectFit // сохранение пропорций видео при масштабировании
    }

    MouseArea {
        anchors.fill: parent
        onClicked: mediaPlayer.playbackState === MediaPlayer.PlayingState
                   ? mediaPlayer.pause() : mediaPlayer.play()
    }
}

Пояснения к коду

VideoPlayerWidget показывает явное разделение видео- и аудиовыхода — даже для видеофайла со встроенным звуком требуется отдельная связь setAudioOutput(), поскольку QVideoWidget отвечает исключительно за визуальное отображение, не имея отношения к выводу звуковой составляющей того же файла. QML-пример показывает декларативный, в духе остальной QML-сцены, способ описания того же воспроизведения — MediaPlayer, AudioOutput и VideoOutput определяются как обычные QML-элементы, связанные через свойства (audioOutput, videoOutput), и fillMode: VideoOutput.PreserveAspectFit явно решает частую практическую задачу — сохранение исходных пропорций видео при отображении в области, чьё соотношение сторон отличается от соотношения сторон самого видео (без растягивания/искажения изображения).

Подводные камни

  • Игнорирование fillMode для VideoOutput (или эквивалентных настроек масштабирования для QVideoWidget) — без явной настройки видео может отображаться искажённым (растянутым не пропорционально) при несовпадении соотношения сторон видео и области отображения, и выбор подходящего режима (сохранение пропорций с возможными чёрными полосами, либо обрезка для заполнения всей области без искажения) должен быть осознанным решением, соответствующим ожидаемому пользовательскому опыту конкретного приложения.
  • Воспроизведение сетевого видео без обработки возможных сетевых ошибок (отсутствие соединения, недоступность сервера, прерывание потока на середине) — в отличие от локального файла, сетевой источник может стать недоступным в любой момент уже во время воспроизведения, и приложение должно подписаться на errorOccurred сигнал плеера, чтобы корректно сообщить пользователю о проблеме, а не оставлять интерфейс в неопределённом, «зависшем» состоянии без какой-либо обратной связи о причине прерывания.
  • Предположение о мгновенной готовности сетевого видео к воспроизведению без буферизации — в отличие от уже полностью доступного локального файла, сетевое видео требует первоначальной буферизации перед стартом плавного воспроизведения, и приложение должно явно отображать индикатор загрузки/буферизации (через отслеживание bufferProgress сигнала, статья 475), а не показывать пользователю пустой, неинформативный экран во время этой задержки.
  • Использование QVideoWidget внутри сложных, многослойных композиций виджетов (например, как дочерний элемент полупрозрачного оверлея) без понимания, что видеовиджет на некоторых платформах/бэкендах может использовать аппаратное ускорение через отдельный, не полностью интегрированный с обычным механизмом отрисовки Qt Widgets слой — это может приводить к неожиданным визуальным артефактам при попытке наложить другие виджеты поверх видео или применить к нему стандартные эффекты прозрачности/трансформации Qt Widgets, не предназначенные изначально для применения к аппаратно-ускоренному видеослою.