Revision control

Copy as Markdown

// Copyright (C) 2016 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 "qffmpegmediarecorder_p.h"
#include <QtCore/qdebug.h>
#include <QtCore/qloggingcategory.h>
#include <QtMultimedia/qaudiobuffer.h>
#include <QtMultimedia/qaudiodevice.h>
#include <QtMultimedia/qaudiosource.h>
#include <QtMultimedia/private/qmediastoragelocation_p.h>
#include <QtMultimedia/private/qplatformcamera_p.h>
#include <QtMultimedia/private/qplatformsurfacecapture_p.h>
#include "recordingengine/qffmpegrecordingengine_p.h"
#include "qffmpegmediacapturesession_p.h"
Q_STATIC_LOGGING_CATEGORY(qLcMediaEncoder, "qt.multimedia.ffmpeg.encoder");
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
QFFmpegMediaRecorder::QFFmpegMediaRecorder(QMediaRecorder *parent) : QPlatformMediaRecorder(parent)
{
}
QFFmpegMediaRecorder::~QFFmpegMediaRecorder() = default;
bool QFFmpegMediaRecorder::isLocationWritable(const QUrl &) const
{
return true;
}
void QFFmpegMediaRecorder::handleSessionError(QMediaRecorder::Error code, const QString &description)
{
updateError(code, description);
stop();
}
void QFFmpegMediaRecorder::record(QMediaEncoderSettings &settings)
{
if (!m_session || state() != QMediaRecorder::StoppedState)
return;
auto videoSources = m_session->activeVideoSources();
auto audioInputs = m_session->activeAudioInputs();
const auto hasVideo = !videoSources.empty();
const auto hasAudio = !audioInputs.empty();
if (!hasVideo && !hasAudio) {
updateError(QMediaRecorder::ResourceError, QMediaRecorder::tr("No video or audio input"));
return;
}
if (outputDevice() && !outputLocation().isEmpty())
qCWarning(qLcMediaEncoder)
<< "Both outputDevice and outputLocation has been set to QMediaRecorder";
if (outputDevice() && !outputDevice()->isWritable())
qCWarning(qLcMediaEncoder) << "Output device has been set but not it's not writable";
QString actualLocation;
auto formatContext = std::make_unique<QFFmpeg::EncodingFormatContext>(settings.fileFormat());
bool isAVIOOpen = false;
if (outputDevice() && outputDevice()->isWritable()) {
isAVIOOpen = formatContext->openAVIO(outputDevice());
} else {
actualLocation = findActualLocation(settings);
isAVIOOpen = formatContext->openAVIO(actualLocation);
}
qCInfo(qLcMediaEncoder).nospace()
<< "Recording new media with muxer "
<< formatContext->avFormatContext()->oformat->long_name << " to "
<< (actualLocation.isNull() ? u"IO device"_s : actualLocation)
<< " with format: " << settings.fileFormat() << ", " << settings.audioCodec() << ", "
<< settings.videoCodec();
if (!isAVIOOpen) {
updateError(QMediaRecorder::LocationNotWritable,
QMediaRecorder::tr("Cannot open the output location for writing"));
return;
}
m_recordingEngine.reset(new RecordingEngine(settings, std::move(formatContext)));
m_recordingEngine->setMetaData(m_metaData);
connect(m_recordingEngine.get(), &QFFmpeg::RecordingEngine::durationChanged, this,
&QFFmpegMediaRecorder::newDuration);
connect(m_recordingEngine.get(), &QFFmpeg::RecordingEngine::finalizationDone, this,
&QFFmpegMediaRecorder::finalizationDone);
connect(m_recordingEngine.get(), &QFFmpeg::RecordingEngine::sessionError, this,
&QFFmpegMediaRecorder::handleSessionError);
updateAutoStop();
auto handleStreamInitializationError = [this](QMediaRecorder::Error code,
const QString &description) {
qCWarning(qLcMediaEncoder) << "Stream initialization error:" << description;
updateError(code, description);
};
connect(m_recordingEngine.get(), &QFFmpeg::RecordingEngine::streamInitializationError, this,
handleStreamInitializationError);
durationChanged(0);
actualLocationChanged(QUrl::fromLocalFile(actualLocation));
qCDebug(qLcMediaEncoder) << "Starting recording engine";
if (m_recordingEngine->initialize(audioInputs, videoSources)) {
stateChanged(QMediaRecorder::RecordingState);
qCDebug(qLcMediaEncoder) << "Recording engine started";
} else {
// else an error has been already emitted
qCWarning(qLcMediaEncoder) << "Failed to start recording engine";
}
}
void QFFmpegMediaRecorder::pause()
{
if (!m_session || state() != QMediaRecorder::RecordingState)
return;
Q_ASSERT(m_recordingEngine);
m_recordingEngine->setPaused(true);
stateChanged(QMediaRecorder::PausedState);
}
void QFFmpegMediaRecorder::resume()
{
if (!m_session || state() != QMediaRecorder::PausedState)
return;
Q_ASSERT(m_recordingEngine);
m_recordingEngine->setPaused(false);
stateChanged(QMediaRecorder::RecordingState);
}
void QFFmpegMediaRecorder::stop()
{
if (!m_session || state() == QMediaRecorder::StoppedState)
return;
qCDebug(qLcMediaEncoder) << "Stopping media recorder";
m_recordingEngine.reset();
}
void QFFmpegMediaRecorder::finalizationDone()
{
stateChanged(QMediaRecorder::StoppedState);
}
void QFFmpegMediaRecorder::setMetaData(const QMediaMetaData &metaData)
{
if (!m_session)
return;
m_metaData = metaData;
}
QMediaMetaData QFFmpegMediaRecorder::metaData() const
{
return m_metaData;
}
void QFFmpegMediaRecorder::setCaptureSession(QFFmpegMediaCaptureSession *session)
{
auto *captureSession = session;
if (m_session == captureSession)
return;
if (m_session)
stop();
m_session = captureSession;
if (!m_session)
return;
}
void QFFmpegMediaRecorder::updateAutoStop()
{
const bool autoStop = mediaRecorder()->autoStop();
if (!m_recordingEngine || m_recordingEngine->autoStop() == autoStop)
return;
if (autoStop)
connect(m_recordingEngine.get(), &QFFmpeg::RecordingEngine::autoStopped, this,
&QFFmpegMediaRecorder::stop);
else
disconnect(m_recordingEngine.get(), &QFFmpeg::RecordingEngine::autoStopped, this,
&QFFmpegMediaRecorder::stop);
m_recordingEngine->setAutoStop(autoStop);
}
void QFFmpegMediaRecorder::RecordingEngineDeleter::operator()(
RecordingEngine *recordingEngine) const
{
// ### all of the below should be done asynchronous. finalize() should do it's work in a thread
// to avoid blocking the UI in case of slow codecs
recordingEngine->finalize();
}
QT_END_NAMESPACE
#include "moc_qffmpegmediarecorder_p.cpp"