Мультимедиа

Обзор Qt Multimedia: архитектура модуля, QMediaPlayer, QAudioOutput

Введение

После циклов про сети, СУБД, DevOps и алгоритмы переходим к новому, ранее не затронутому направлению — обработке аудио и видео в Qt-приложениях через модуль Qt Multimedia. В этой обзорной статье разберём общую архитектуру модуля и базовые классы, на которых строится всё дальнейшее изложение цикла: воспроизведение, запись, обработку и потоковую передачу медиаданных.

Концепция

Qt Multimedia в современных версиях Qt (Qt6) построен на принципе разделения ответственности между несколькими специализированными классами, соединяемыми через QMediaCaptureSession (статья 471) для записи и напрямую через QMediaPlayer для воспроизведения — QMediaPlayer отвечает за загрузку и декодирование медиаконтента (из файла или сети), QAudioOutput отвечает именно за вывод звука на физическое аудиоустройство, а QVideoWidget/QML VideoOutput (статья 469) — за отображение видеокадров. Это разделение (в отличие от единого, монолитного класса «плеера» более старых версий API) даёт гибкость — один и тот же QMediaPlayer может быть подключен к разным аудио- и видеовыходам, или временно отключен от вывода вовсе (например, для анализа содержимого без реального воспроизведения).

Пример кода

// Минимальный пример воспроизведения аудиофайла — базовая связка классов модуля
#include <QMediaPlayer>
#include <QAudioOutput>

class SimpleAudioPlayer : public QObject
{
    Q_OBJECT
public:
    SimpleAudioPlayer(QObject *parent = nullptr) : QObject(parent)
    {
        m_audioOutput = new QAudioOutput(this);
        m_player = new QMediaPlayer(this);

        m_player->setAudioOutput(m_audioOutput); // явная связь плеера с конкретным аудиовыходом
        m_audioOutput->setVolume(0.5);            // громкость от 0.0 до 1.0
    }

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

private:
    QMediaPlayer *m_player;
    QAudioOutput *m_audioOutput;
};
// Перечень доступных аудиоустройств вывода — пользователь может выбрать конкретную колонку/наушники
#include <QMediaDevices>

void listAudioOutputDevices()
{
    const QList<QAudioDevice> devices = QMediaDevices::audioOutputs();
    for (const QAudioDevice &device : devices) {
        qDebug() << "Устройство:" << device.description()
                  << (device == QMediaDevices::defaultAudioOutput() ? "(по умолчанию)" : "");
    }
}

void useSpecificDevice(QAudioOutput *output, const QAudioDevice &chosenDevice)
{
    output->setDevice(chosenDevice); // переключение вывода на конкретное физическое устройство
}

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

SimpleAudioPlayer показывает минимальную, но полную связку трёх обязательных компонентов воспроизведения — QMediaPlayer (логика декодирования и управления воспроизведением), QAudioOutput (фактический вывод звука), и явную связь между ними через setAudioOutput(), без которой созданный плеер декодировал бы медиаданные, но не воспроизводил бы звук никуда. QMediaDevices::audioOutputs() демонстрирует динамическое перечисление реально доступных в системе устройств вывода звука — это особенно важно для приложений, дающих пользователю возможность выбора конкретного устройства (наушники против внешних колонок), а не полагающихся исключительно на устройство по умолчанию, выбранное операционной системой.

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

  • Забытая явная связь QMediaPlayer с QAudioOutput через setAudioOutput() — в Qt6 эта связь, в отличие от более старых версий API, не устанавливается автоматически, и плеер без явно подключенного аудиовыхода будет декодировать и «воспроизводить» медиафайл без какого-либо слышимого звука, что на первый взгляд выглядит как непонятная, необъяснимая ошибка («файл загрузился, статус plays, но звука нет»), хотя причина — простое упущение этой обязательной связи.
  • Игнорирование изменения доступных аудиоустройств во время работы приложения (подключение/отключение наушников, USB-гарнитуры) — список устройств, полученный однократно при старте приложения, может устареть, если пользователь физически подключает или отключает устройство уже во время работы приложения, и для надёжного поведения стоит подписаться на сигнал QMediaDevices::audioOutputsChanged() для реакции на такие изменения в реальном времени, а не полагаться на статичный, однократно полученный список.
  • Предположение о доступности всех модулей Qt Multimedia на всех платформах в равной степени — некоторые возможности (форматы кодеков, специфичные эффекты) могут зависеть от установленных в системе мультимедийных бэкендов (на Linux — GStreamer, на Windows — Media Foundation), и код, написанный и протестированный на одной платформе, не гарантированно ведёт себя идентично на другой без явной проверки конкретно используемого бэкенда и его реальных возможностей.
  • Создание QMediaPlayer/QAudioOutput без родителя (parent) в коде, где предполагается автоматическое управление их жизненным циклом через иерархию Qt-объектов — без указания родителя при создании эти объекты не будут автоматически удалены при удалении содержащего их виджета/окна, что требует либо ручного управления их временем жизни, либо (что предпочтительнее, как в примере SimpleAudioPlayer) обязательной передачи подходящего родителя при создании.