Revision control
Copy as Markdown
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qwindowsmediadevicesession_p.h"
#include <mediacapture/qwindowsmediadevicereader_p.h>
#include <QtMultimedia/qaudioinput.h>
#include <QtMultimedia/qaudiooutput.h>
#include <QtMultimedia/qvideosink.h>
#include <QtMultimedia/private/qplatformvideosink_p.h>
#include <QtMultimedia/private/qwindowsmultimediautils_p.h>
#include <QtCore/qdebug.h>
#include <cguid.h>
QT_BEGIN_NAMESPACE
QWindowsMediaDeviceSession::QWindowsMediaDeviceSession(QObject *parent)
: QObject(parent)
{
m_mediaDeviceReader = new QWindowsMediaDeviceReader(this);
connect(m_mediaDeviceReader, &QWindowsMediaDeviceReader::streamingStarted,
this, &QWindowsMediaDeviceSession::handleStreamingStarted);
connect(m_mediaDeviceReader, &QWindowsMediaDeviceReader::streamingStopped,
this, &QWindowsMediaDeviceSession::handleStreamingStopped);
connect(m_mediaDeviceReader, &QWindowsMediaDeviceReader::streamingError,
this, &QWindowsMediaDeviceSession::handleStreamingError);
connect(m_mediaDeviceReader, &QWindowsMediaDeviceReader::videoFrameChanged,
this, &QWindowsMediaDeviceSession::handleVideoFrameChanged);
connect(m_mediaDeviceReader, &QWindowsMediaDeviceReader::recordingStarted,
this, &QWindowsMediaDeviceSession::recordingStarted);
connect(m_mediaDeviceReader, &QWindowsMediaDeviceReader::recordingStopped,
this, &QWindowsMediaDeviceSession::recordingStopped);
connect(m_mediaDeviceReader, &QWindowsMediaDeviceReader::recordingError,
this, &QWindowsMediaDeviceSession::recordingError);
connect(m_mediaDeviceReader, &QWindowsMediaDeviceReader::durationChanged,
this, &QWindowsMediaDeviceSession::durationChanged);
}
QWindowsMediaDeviceSession::~QWindowsMediaDeviceSession()
{
delete m_mediaDeviceReader;
}
bool QWindowsMediaDeviceSession::isActive() const
{
return m_active;
}
bool QWindowsMediaDeviceSession::isActivating() const
{
return m_activating;
}
void QWindowsMediaDeviceSession::setActive(bool active)
{
if ((active && (m_active || m_activating)) || (!active && !m_active && !m_activating))
return;
if (active) {
auto camId = QString::fromUtf8(m_activeCameraDevice.id());
auto micId = m_audioInput ? QString::fromUtf8(m_audioInput->device().id()) : QString();
if (!camId.isEmpty() || !micId.isEmpty()) {
if (m_mediaDeviceReader->activate(camId, m_cameraFormat, micId)) {
m_activating = true;
} else {
Q_EMIT streamingError(MF_E_NOT_AVAILABLE);
}
} else {
qWarning() << Q_FUNC_INFO << "Camera ID and Microphone ID both undefined.";
}
} else {
m_mediaDeviceReader->deactivate();
m_active = false;
m_activating = false;
Q_EMIT activeChanged(m_active);
Q_EMIT readyForCaptureChanged(m_active);
}
}
void QWindowsMediaDeviceSession::reactivate()
{
if (m_active || m_activating) {
pauseRecording();
setActive(false);
setActive(true);
resumeRecording();
}
}
void QWindowsMediaDeviceSession::setActiveCamera(const QCameraDevice &camera)
{
m_activeCameraDevice = camera;
reactivate();
}
QCameraDevice QWindowsMediaDeviceSession::activeCamera() const
{
return m_activeCameraDevice;
}
void QWindowsMediaDeviceSession::setCameraFormat(const QCameraFormat &cameraFormat)
{
m_cameraFormat = cameraFormat;
}
void QWindowsMediaDeviceSession::setVideoSink(QVideoSink *surface)
{
m_surface = surface;
}
void QWindowsMediaDeviceSession::handleStreamingStarted()
{
if (m_activating) {
m_active = true;
m_activating = false;
Q_EMIT activeChanged(m_active);
Q_EMIT readyForCaptureChanged(m_active);
}
}
void QWindowsMediaDeviceSession::handleStreamingStopped()
{
m_active = false;
Q_EMIT activeChanged(m_active);
Q_EMIT readyForCaptureChanged(m_active);
}
void QWindowsMediaDeviceSession::handleStreamingError(HRESULT errorCode)
{
if (m_surface)
m_surface->platformVideoSink()->setVideoFrame(QVideoFrame());
Q_EMIT streamingError(errorCode);
}
void QWindowsMediaDeviceSession::handleVideoFrameChanged(const QVideoFrame &frame)
{
if (m_surface)
m_surface->platformVideoSink()->setVideoFrame(frame);
Q_EMIT videoFrameChanged(frame);
}
void QWindowsMediaDeviceSession::setAudioInputMuted(bool muted)
{
m_mediaDeviceReader->setInputMuted(muted);
}
void QWindowsMediaDeviceSession::setAudioInputVolume(float volume)
{
m_mediaDeviceReader->setInputVolume(volume);
}
void QWindowsMediaDeviceSession::audioInputDeviceChanged()
{
reactivate();
}
void QWindowsMediaDeviceSession::setAudioOutputMuted(bool muted)
{
m_mediaDeviceReader->setOutputMuted(muted);
}
void QWindowsMediaDeviceSession::setAudioOutputVolume(float volume)
{
m_mediaDeviceReader->setOutputVolume(volume);
}
void QWindowsMediaDeviceSession::audioOutputDeviceChanged()
{
if (m_active || m_activating)
m_mediaDeviceReader->setAudioOutput(QString::fromUtf8(m_audioOutput->device().id()));
}
void QWindowsMediaDeviceSession::setAudioInput(QAudioInput *input)
{
if (m_audioInput == input)
return;
if (m_audioInput)
m_audioInput->disconnect(this);
m_audioInput = input;
audioInputDeviceChanged();
if (!m_audioInput)
return;
connect(m_audioInput, &QAudioInput::mutedChanged, this, &QWindowsMediaDeviceSession::setAudioInputMuted);
connect(m_audioInput, &QAudioInput::volumeChanged, this, &QWindowsMediaDeviceSession::setAudioInputVolume);
connect(m_audioInput, &QAudioInput::deviceChanged, this, &QWindowsMediaDeviceSession::audioInputDeviceChanged);
}
void QWindowsMediaDeviceSession::setAudioOutput(QAudioOutput *output)
{
if (m_audioOutput == output)
return;
if (m_audioOutput)
m_audioOutput->disconnect(this);
m_audioOutput = output;
if (!m_audioOutput) {
m_mediaDeviceReader->setAudioOutput({});
return;
}
m_mediaDeviceReader->setAudioOutput(QString::fromUtf8(m_audioOutput->device().id()));
connect(m_audioOutput, &QAudioOutput::mutedChanged, this, &QWindowsMediaDeviceSession::setAudioOutputMuted);
connect(m_audioOutput, &QAudioOutput::volumeChanged, this, &QWindowsMediaDeviceSession::setAudioOutputVolume);
connect(m_audioOutput, &QAudioOutput::deviceChanged, this, &QWindowsMediaDeviceSession::audioOutputDeviceChanged);
}
QMediaRecorder::Error QWindowsMediaDeviceSession::startRecording(QMediaEncoderSettings &settings, const QString &fileName, bool audioOnly)
{
std::optional<GUID> container = audioOnly
? QWMF::containerForAudioFileFormat(settings.mediaFormat().fileFormat())
: QWMF::containerForVideoFileFormat(settings.mediaFormat().fileFormat());
if (!container)
return QMediaRecorder::FormatError;
GUID videoFormat = QWMF::videoFormatForCodec(settings.videoCodec());
GUID audioFormat = QWMF::audioFormatForCodec(settings.audioCodec());
QSize res = settings.videoResolution();
UINT32 width, height;
if (res.width() > 0 && res.height() > 0) {
width = UINT32(res.width());
height = UINT32(res.height());
} else {
width = m_mediaDeviceReader->frameWidth();
height = m_mediaDeviceReader->frameHeight();
settings.setVideoResolution(QSize(int(width), int(height)));
}
qreal frameRate = settings.videoFrameRate();
if (frameRate <= 0) {
frameRate = m_mediaDeviceReader->frameRate();
settings.setVideoFrameRate(frameRate);
}
auto quality = settings.quality();
UINT32 videoBitRate = 0;
if (settings.videoBitRate() > 0) {
videoBitRate = UINT32(settings.videoBitRate());
} else {
videoBitRate = estimateVideoBitRate(videoFormat, width, height, frameRate, quality);
settings.setVideoBitRate(int(videoBitRate));
}
UINT32 audioBitRate = 0;
if (settings.audioBitRate() > 0) {
audioBitRate = UINT32(settings.audioBitRate());
} else {
audioBitRate = estimateAudioBitRate(audioFormat, quality);
settings.setAudioBitRate(int(audioBitRate));
}
return m_mediaDeviceReader->startRecording(fileName, *container, audioOnly ? GUID_NULL : videoFormat,
videoBitRate, width, height, frameRate,
audioFormat, audioBitRate);
}
void QWindowsMediaDeviceSession::stopRecording()
{
m_mediaDeviceReader->stopRecording();
}
bool QWindowsMediaDeviceSession::pauseRecording()
{
return m_mediaDeviceReader->pauseRecording();
}
bool QWindowsMediaDeviceSession::resumeRecording()
{
return m_mediaDeviceReader->resumeRecording();
}
// empirical estimate of the required video bitrate (for H.264)
quint32 QWindowsMediaDeviceSession::estimateVideoBitRate(const GUID &videoFormat, quint32 width, quint32 height,
qreal frameRate, QMediaRecorder::Quality quality)
{
Q_UNUSED(videoFormat);
qreal bitsPerPixel;
switch (quality) {
case QMediaRecorder::Quality::VeryLowQuality:
bitsPerPixel = 0.08;
break;
case QMediaRecorder::Quality::LowQuality:
bitsPerPixel = 0.2;
break;
case QMediaRecorder::Quality::NormalQuality:
bitsPerPixel = 0.3;
break;
case QMediaRecorder::Quality::HighQuality:
bitsPerPixel = 0.5;
break;
case QMediaRecorder::Quality::VeryHighQuality:
bitsPerPixel = 0.8;
break;
default:
bitsPerPixel = 0.3;
}
// Required bitrate is not linear on the number of pixels; small resolutions
// require more BPP, thus the minimum values, to try to compensate it.
quint32 pixelsPerSec = quint32(qMax(width, 320u) * qMax(height, 240u) * qMax(frameRate, 6.0));
return pixelsPerSec * bitsPerPixel;
}
quint32 QWindowsMediaDeviceSession::estimateAudioBitRate(const GUID &audioFormat, QMediaRecorder::Quality quality)
{
if (audioFormat == MFAudioFormat_AAC) {
// Bitrates supported by the AAC encoder are 96K, 128K, 160K, 192K.
switch (quality) {
case QMediaRecorder::Quality::VeryLowQuality:
return 96000;
case QMediaRecorder::Quality::LowQuality:
return 96000;
case QMediaRecorder::Quality::NormalQuality:
return 128000;
case QMediaRecorder::Quality::HighQuality:
return 160000;
case QMediaRecorder::Quality::VeryHighQuality:
return 192000;
default:
return 128000;
}
} else if (audioFormat == MFAudioFormat_MP3) {
// Bitrates supported by the MP3 encoder are
// 32K, 40K, 48K, 56K, 64K, 80K, 96K, 112K, 128K, 160K, 192K, 224K, 256K, 320K.
switch (quality) {
case QMediaRecorder::Quality::VeryLowQuality:
return 48000;
case QMediaRecorder::Quality::LowQuality:
return 96000;
case QMediaRecorder::Quality::NormalQuality:
return 128000;
case QMediaRecorder::Quality::HighQuality:
return 224000;
case QMediaRecorder::Quality::VeryHighQuality:
return 320000;
default:
return 128000;
}
} else if (audioFormat == MFAudioFormat_WMAudioV8) {
// Bitrates supported by the Windows Media Audio 8 encoder
switch (quality) {
case QMediaRecorder::Quality::VeryLowQuality:
return 32000;
case QMediaRecorder::Quality::LowQuality:
return 96000;
case QMediaRecorder::Quality::NormalQuality:
return 192000;
case QMediaRecorder::Quality::HighQuality:
return 256016;
case QMediaRecorder::Quality::VeryHighQuality:
return 320032;
default:
return 192000;
}
} else if (audioFormat == MFAudioFormat_WMAudioV9) {
// Bitrates supported by the Windows Media Audio 9 encoder
switch (quality) {
case QMediaRecorder::Quality::VeryLowQuality:
return 32000;
case QMediaRecorder::Quality::LowQuality:
return 96000;
case QMediaRecorder::Quality::NormalQuality:
return 192000;
case QMediaRecorder::Quality::HighQuality:
return 256016;
case QMediaRecorder::Quality::VeryHighQuality:
return 384000;
default:
return 192000;
}
}
return 0; // Use default for format
}
QT_END_NAMESPACE
#include "moc_qwindowsmediadevicesession_p.cpp"