1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qffmpegmediarecorder_p.h"
5#include "qaudiodevice.h"
6#include <private/qmediastoragelocation_p.h>
7#include <private/qplatformcamera_p.h>
8#include <private/qplatformsurfacecapture_p.h>
9#include "qaudiosource.h"
10#include "qffmpegaudioinput_p.h"
11#include "qaudiobuffer.h"
12#include "recordingengine/qffmpegrecordingengine_p.h"
13#include "qffmpegmediacapturesession_p.h"
14
15#include <qdebug.h>
16#include <qloggingcategory.h>
17
18static Q_LOGGING_CATEGORY(qLcMediaEncoder, "qt.multimedia.ffmpeg.encoder");
19
20QT_BEGIN_NAMESPACE
21
22using namespace Qt::StringLiterals;
23
24QFFmpegMediaRecorder::QFFmpegMediaRecorder(QMediaRecorder *parent) : QPlatformMediaRecorder(parent)
25{
26}
27
28QFFmpegMediaRecorder::~QFFmpegMediaRecorder() = default;
29
30bool QFFmpegMediaRecorder::isLocationWritable(const QUrl &) const
31{
32 return true;
33}
34
35void QFFmpegMediaRecorder::handleSessionError(QMediaRecorder::Error code, const QString &description)
36{
37 updateError(error: code, errorString: description);
38 stop();
39}
40
41void QFFmpegMediaRecorder::record(QMediaEncoderSettings &settings)
42{
43 if (!m_session || state() != QMediaRecorder::StoppedState)
44 return;
45
46 auto videoSources = m_session->activeVideoSources();
47 auto audioInputs = m_session->activeAudioInputs();
48 const auto hasVideo = !videoSources.empty();
49 const auto hasAudio = !audioInputs.empty();
50
51 if (!hasVideo && !hasAudio) {
52 updateError(error: QMediaRecorder::ResourceError, errorString: QMediaRecorder::tr(s: "No video or audio input"));
53 return;
54 }
55
56 if (outputDevice() && !outputLocation().isEmpty())
57 qCWarning(qLcMediaEncoder)
58 << "Both outputDevice and outputLocation has been set to QMediaRecorder";
59
60 if (outputDevice() && !outputDevice()->isWritable())
61 qCWarning(qLcMediaEncoder) << "Output device has been set but not it's not writable";
62
63 QString actualLocation;
64 auto formatContext = std::make_unique<QFFmpeg::EncodingFormatContext>(args: settings.fileFormat());
65
66 if (outputDevice() && outputDevice()->isWritable()) {
67 formatContext->openAVIO(device: outputDevice());
68 } else {
69 actualLocation = findActualLocation(settings);
70 formatContext->openAVIO(filePath: actualLocation);
71 }
72
73 qCInfo(qLcMediaEncoder).nospace()
74 << "Recording new media with muxer "
75 << formatContext->avFormatContext()->oformat->long_name << " to "
76 << (actualLocation.isNull() ? u"IO device"_s : actualLocation)
77 << " with format: " << settings.fileFormat() << ", " << settings.audioCodec() << ", "
78 << settings.videoCodec();
79
80 if (!formatContext->isAVIOOpen()) {
81 updateError(error: QMediaRecorder::LocationNotWritable,
82 errorString: QMediaRecorder::tr(s: "Cannot open the output location for writing"));
83 return;
84 }
85
86 m_recordingEngine.reset(p: new RecordingEngine(settings, std::move(formatContext)));
87 m_recordingEngine->setMetaData(m_metaData);
88
89 connect(sender: m_recordingEngine.get(), signal: &QFFmpeg::RecordingEngine::durationChanged, context: this,
90 slot: &QFFmpegMediaRecorder::newDuration);
91 connect(sender: m_recordingEngine.get(), signal: &QFFmpeg::RecordingEngine::finalizationDone, context: this,
92 slot: &QFFmpegMediaRecorder::finalizationDone);
93 connect(sender: m_recordingEngine.get(), signal: &QFFmpeg::RecordingEngine::sessionError, context: this,
94 slot: &QFFmpegMediaRecorder::handleSessionError);
95
96 updateAutoStop();
97
98 auto handleStreamInitializationError = [this](QMediaRecorder::Error code,
99 const QString &description) {
100 qCWarning(qLcMediaEncoder) << "Stream initialization error:" << description;
101 updateError(error: code, errorString: description);
102 };
103
104 connect(sender: m_recordingEngine.get(), signal: &QFFmpeg::RecordingEngine::streamInitializationError, context: this,
105 slot&: handleStreamInitializationError);
106
107 durationChanged(position: 0);
108 actualLocationChanged(location: QUrl::fromLocalFile(localfile: actualLocation));
109
110 qCDebug(qLcMediaEncoder) << "Starting recording engine";
111 if (m_recordingEngine->initialize(audioSources: audioInputs, videoSources)) {
112 stateChanged(state: QMediaRecorder::RecordingState);
113 qCDebug(qLcMediaEncoder) << "Recording engine started";
114 } else {
115 // else an error has been already emitted
116 qCWarning(qLcMediaEncoder) << "Failed to start recording engine";
117 }
118}
119
120void QFFmpegMediaRecorder::pause()
121{
122 if (!m_session || state() != QMediaRecorder::RecordingState)
123 return;
124
125 Q_ASSERT(m_recordingEngine);
126 m_recordingEngine->setPaused(true);
127
128 stateChanged(state: QMediaRecorder::PausedState);
129}
130
131void QFFmpegMediaRecorder::resume()
132{
133 if (!m_session || state() != QMediaRecorder::PausedState)
134 return;
135
136 Q_ASSERT(m_recordingEngine);
137 m_recordingEngine->setPaused(false);
138
139 stateChanged(state: QMediaRecorder::RecordingState);
140}
141
142void QFFmpegMediaRecorder::stop()
143{
144 if (!m_session || state() == QMediaRecorder::StoppedState)
145 return;
146 auto * input = m_session ? m_session->audioInput() : nullptr;
147 if (input)
148 static_cast<QFFmpegAudioInput *>(input)->setRunning(false);
149 qCDebug(qLcMediaEncoder) << "Stopping media recorder";
150
151 m_recordingEngine.reset();
152}
153
154void QFFmpegMediaRecorder::finalizationDone()
155{
156 stateChanged(state: QMediaRecorder::StoppedState);
157}
158
159void QFFmpegMediaRecorder::setMetaData(const QMediaMetaData &metaData)
160{
161 if (!m_session)
162 return;
163 m_metaData = metaData;
164}
165
166QMediaMetaData QFFmpegMediaRecorder::metaData() const
167{
168 return m_metaData;
169}
170
171void QFFmpegMediaRecorder::setCaptureSession(QFFmpegMediaCaptureSession *session)
172{
173 auto *captureSession = session;
174 if (m_session == captureSession)
175 return;
176
177 if (m_session)
178 stop();
179
180 m_session = captureSession;
181 if (!m_session)
182 return;
183}
184
185void QFFmpegMediaRecorder::updateAutoStop()
186{
187 const bool autoStop = mediaRecorder()->autoStop();
188 if (!m_recordingEngine || m_recordingEngine->autoStop() == autoStop)
189 return;
190
191 if (autoStop)
192 connect(sender: m_recordingEngine.get(), signal: &QFFmpeg::RecordingEngine::autoStopped, context: this,
193 slot: &QFFmpegMediaRecorder::stop);
194 else
195 disconnect(sender: m_recordingEngine.get(), signal: &QFFmpeg::RecordingEngine::autoStopped, receiver: this,
196 slot: &QFFmpegMediaRecorder::stop);
197
198 m_recordingEngine->setAutoStop(autoStop);
199}
200
201void QFFmpegMediaRecorder::RecordingEngineDeleter::operator()(
202 RecordingEngine *recordingEngine) const
203{
204 // ### all of the below should be done asynchronous. finalize() should do it's work in a thread
205 // to avoid blocking the UI in case of slow codecs
206 recordingEngine->finalize();
207}
208
209QT_END_NAMESPACE
210
211#include "moc_qffmpegmediarecorder_p.cpp"
212

source code of qtmultimedia/src/plugins/multimedia/ffmpeg/qffmpegmediarecorder.cpp