Мультимедиа

Запись видео с камеры: QCamera, QMediaCaptureSession

Введение

Статья 470 разобрала захват аудио — для видео с камеры используется другой, хотя концептуально похожий набор классов, организованный вокруг QMediaCaptureSession — центрального координатора, объединяющего источники захвата (камеру, микрофон) с приёмниками (видоискатель для предпросмотра, рекордер для записи в файл).

Концепция

QCamera представляет конкретное устройство захвата видео и управляет его параметрами (разрешение, частота кадров, фокус) — сам по себе он не выводит и не записывает видео, а лишь является источником видеопотока. QMediaCaptureSession связывает этот источник с одним или несколькими приёмниками одновременно — QVideoWidget/VideoOutput для живого предпросмотра («видоискатель», то, что пользователь видит на экране во время записи) и QMediaRecorder (статья 473) для собственно сохранения видео в файл — оба приёмника могут работать одновременно от одного и того же источника камеры.

Пример кода

// Связка камеры, предпросмотра и рекордера через QMediaCaptureSession
#include <QCamera>
#include <QMediaCaptureSession>
#include <QMediaRecorder>
#include <QVideoWidget>
#include <QMediaDevices>

class CameraRecorder : public QWidget
{
    Q_OBJECT
public:
    CameraRecorder(QWidget *parent = nullptr) : QWidget(parent)
    {
        QCameraDevice defaultCamera = QMediaDevices::defaultVideoInput();
        if (defaultCamera.isNull()) {
            qWarning() << "Камера не найдена в системе";
            return;
        }

        m_camera = new QCamera(defaultCamera, this);
        m_recorder = new QMediaRecorder(this);
        m_videoWidget = new QVideoWidget(this);

        m_captureSession.setCamera(m_camera);
        m_captureSession.setVideoOutput(m_videoWidget); // живой предпросмотр на экране
        m_captureSession.setRecorder(m_recorder);        // и одновременно — путь для записи в файл

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

        m_camera->start(); // предпросмотр начинает работать немедленно, ДО начала реальной записи
    }

    void startRecording(const QString &outputPath)
    {
        m_recorder->setOutputLocation(QUrl::fromLocalFile(outputPath));
        m_recorder->record(); // запись начинается ИМЕННО сейчас, предпросмотр уже работал ранее
    }

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

private:
    QCamera *m_camera;
    QMediaRecorder *m_recorder;
    QVideoWidget *m_videoWidget;
    QMediaCaptureSession m_captureSession;
};
// Перечисление доступных камер — полезно, когда подключено несколько устройств
void listAvailableCameras()
{
    const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
    for (const QCameraDevice &camera : cameras) {
        qDebug() << "Камера:" << camera.description();
        for (const QCameraFormat &format : camera.videoFormats()) {
            qDebug() << "  Поддерживаемый формат:" << format.resolution()
                      << "при" << format.maxFrameRate() << "кадр/с";
        }
    }
}

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

QMediaCaptureSession явно связывает три отдельных компонента (setCamera, setVideoOutput, setRecorder) — это архитектурное разделение позволяет независимо включать/выключать предпросмотр и запись (как показано — предпросмотр начинается сразу с m_camera->start(), а реальная запись — отдельным, более поздним вызовом m_recorder->record()), что соответствует типичному пользовательскому опыту приложения камеры, где видоискатель работает постоянно, а запись включается только по явному действию пользователя. listAvailableCameras() показывает, что у каждой камеры может быть несколько поддерживаемых комбинаций разрешения и частоты кадров, и явный выбор подходящей комбинации (через QCamera::setCameraFormat(), не показанный в первом примере для краткости) важен для приложений, которым нужен контроль над конкретным качеством записи, а не использование произвольного формата по умолчанию.

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

  • Запуск записи через QMediaRecorder без предварительно запущенной камеры (m_camera->start()) — попытка начать запись от неактивного источника видео не даёт ожидаемого результата (запись может либо не начаться вовсе, либо начаться с задержкой, соответствующей времени инициализации камеры, неожиданно происходящей уже после вызова record()), и явный, заблаговременный запуск камеры (обычно сразу при инициализации компонента предпросмотра, как в примере) обеспечивает мгновенный старт реальной записи без скрытой задержки инициализации.
  • Игнорирование запроса разрешения на доступ к камере, аналогично подводному камню статьи 470 про микрофон — современные операционные системы требуют явного, согласованного пользователем разрешения на доступ к камере, и приложение должно корректно обработать оба возможных исхода запроса разрешения, а не предполагать безусловную доступность камеры сразу после попытки её использования.
  • Выбор неподходящего разрешения/частоты кадров без проверки реальной поддержки конкретной камерой — в отличие от аудиоформатов (статья 470, с явной проверкой isFormatSupported), попытка установить нереалистичную для конкретной камеры комбинацию параметров (QCameraFormat) может привести либо к ошибке, либо к неявному использованию ближайшего поддерживаемого варианта самой системой, и для предсказуемого поведения стоит явно перебрать camera.videoFormats() и выбрать реально поддерживаемую комбинацию, а не задавать произвольные значения параметров наугад.
  • Одновременное использование нескольких QMediaCaptureSession, пытающихся захватить видео с одной и той же физической камеры одновременно — большинство камер (особенно встроенные веб-камеры ноутбуков) физически поддерживают единственного активного потребителя одновременно, и попытка второй, конкурирующей сессии захвата той же камеры обычно завершается ошибкой или неопределённым поведением, требуя явной координации между разными частями приложения (или разными приложениями), претендующими на использование одного и того же физического устройства камеры.