Мультимедиа

Итоговый мини-проект: медиаплеер с записью экрана и аудио-визуализацией

Введение

В заключительной статье цикла Qt Multimedia собираем итоговый проект — приложение, объединяющее воспроизведение медиафайлов (статьи 467-469), запись экрана (расширение концепций захвата видео статьи 471 на запись содержимого экрана вместо камеры), и визуализацию аудиоспектра (статья 472) в едином, практическом инструменте.

Концепция

Архитектура итогового проекта: основной плеер на базе QMediaPlayer/QAudioOutput/QVideoWidget для воспроизведения медиафайлов с отображением метаданных (статья 476); параллельный модуль записи экрана, использующий QScreenCapture (специализированный источник захвата для QMediaCaptureSession, концептуально аналогичный QCamera, статья 471, но захватывающий содержимое экрана вместо физической камеры) в сочетании с QMediaRecorder (статья 473); и модуль визуализации спектра, анализирующий поток воспроизводимого аудио через FFT (статья 472) для отображения анимированного индикатора частотного спектра в реальном времени.

Пример кода

// Запись экрана через QScreenCapture — аналогично записи с камеры, но другой источник
#include <QScreenCapture>
#include <QMediaCaptureSession>
#include <QMediaRecorder>
#include <QGuiApplication>

class ScreenRecorder : public QObject
{
    Q_OBJECT
public:
    ScreenRecorder(QObject *parent = nullptr) : QObject(parent)
    {
        m_screenCapture = new QScreenCapture(this);
        m_screenCapture->setScreen(QGuiApplication::primaryScreen());

        m_recorder = new QMediaRecorder(this);
        m_captureSession.setScreenCapture(m_screenCapture);
        m_captureSession.setRecorder(m_recorder);
    }

    void startRecording(const QString &outputPath)
    {
        m_screenCapture->start();
        m_recorder->setOutputLocation(QUrl::fromLocalFile(outputPath));
        m_recorder->record();
    }

    void stopRecording()
    {
        m_recorder->stop();
        m_screenCapture->stop();
    }

private:
    QScreenCapture *m_screenCapture;
    QMediaRecorder *m_recorder;
    QMediaCaptureSession m_captureSession;
};
// Связывание плеера с визуализацией спектра через QAudioBufferOutput (промежуточный перехват аудиоданных)
#include <QAudioBufferOutput>

class VisualizingPlayer : public QObject
{
    Q_OBJECT
public:
    VisualizingPlayer(QObject *parent = nullptr) : QObject(parent)
    {
        m_player = new QMediaPlayer(this);
        m_audioOutput = new QAudioOutput(this);
        m_bufferOutput = new QAudioBufferOutput(this); // даёт доступ к "сырым" буферам ВО ВРЕМЯ воспроизведения

        m_player->setAudioOutput(m_audioOutput);
        m_player->setAudioBufferOutput(m_bufferOutput);

        connect(m_bufferOutput, &QAudioBufferOutput::audioBufferReceived,
                this, &VisualizingPlayer::analyzeForVisualization);
    }

private slots:
    void analyzeForVisualization(const QAudioBuffer &buffer)
    {
        const qint16 *samples = buffer.constData<qint16>();
        int sampleCount = buffer.sampleCount();
        // computeSpectrum() из статьи 472 — применяется к данным, поступающим в реальном
        // времени параллельно с обычным, нормальным воспроизведением через QAudioOutput
        QVector<float> spectrum = computeSpectrum(samples, sampleCount);
        emit spectrumUpdated(spectrum);
    }

signals:
    void spectrumUpdated(const QVector<float> &spectrum);

private:
    QMediaPlayer *m_player;
    QAudioOutput *m_audioOutput;
    QAudioBufferOutput *m_bufferOutput;
};

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

ScreenRecorder показывает прямую параллель с записью видео с камеры (статья 471) — QScreenCapture заменяет QCamera как источник в той же архитектуре QMediaCaptureSession, демонстрируя последовательность и переиспользуемость общей архитектуры Qt Multimedia для принципиально разных источников видео (физическая камера против содержимого экрана). VisualizingPlayer показывает ключевую техническую деталь итогового проекта — QAudioBufferOutput, подключённый параллельно с обычным QAudioOutput, даёт доступ к тем же самым PCM-данным, что реально воспроизводятся через колонки, для независимого анализа (вычисление спектра через computeSpectrum статьи 472) без необходимости как-либо мешать обычному, нормальному процессу воспроизведения звука — оба пути (воспроизведение и анализ для визуализации) работают параллельно от одного и того же источника декодированных аудиоданных.

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

  • Запись экрана без запроса/проверки необходимых системных разрешений, аналогично подводным камням статей 470-471 про микрофон и камеру — современные операционные системы (особенно macOS) требуют явного, согласованного пользователем разрешения на запись содержимого экрана, отдельного от разрешений на доступ к камере/микрофону, и приложение должно явно обрабатывать оба возможных исхода этого отдельного запроса разрешения.
  • Вычисление спектра (FFT, статья 472) синхронно в том же потоке, что обрабатывает сигнал audioBufferReceived, без учёта вычислительной стоимости этой операции — если вычисление спектра занимает заметное время, выполнение его непосредственно в обработчике сигнала может задержать обработку последующих аудиобуферов, потенциально влияя на плавность самого воспроизведения звука (если оба пути неудачно используют общий, единственный поток); для надёжной реализации стоит рассмотреть перенос вычисления спектра в отдельный поток, не блокирующий основной поток обработки аудиоданных.
  • Запись экрана с одновременным воспроизведением звука через колонки того же компьютера без явного учёта возможного попадания этого воспроизводимого звука в саму запись (через микрофон, если запись экрана сопровождается также записью с микрофона) — для записи «чистого» звука системы (без паразитного попадания через микрофон того, что одновременно воспроизводится через колонки) обычно нужен явный захват системного звука как отдельного источника, а не звука, перехваченного физическим микрофоном из помещения.
  • Объединение в одном приложении нескольких ресурсоёмких задач одновременно (декодирование видео для воспроизведения, запись экрана, вычисление FFT для визуализации) без оценки совокупной вычислительной нагрузки на типичном, не самом мощном оборудовании пользователя — каждая из этих задач по отдельности может работать приемлемо, но их одновременное выполнение в рамках единого приложения может создать суммарную нагрузку, заметно превышающую возможности менее производительного оборудования, и для надёжного, предсказуемого поведения на разнообразном оборудовании пользователей стоит явно протестировать и, при необходимости, оптимизировать именно сценарий одновременного использования всех функций приложения, а не только каждой из них в отдельности.

Цикл «Аудио/видео обработка (Qt Multimedia)» (статьи 467-478) завершён. Общий план теперь включает 478 тем, дополняя десктоп/embedded/сетевые/СУБД/DevOps/алгоритмические циклы практическим направлением мультимедийной разработки на Qt.