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
22QFFmpegMediaRecorder::QFFmpegMediaRecorder(QMediaRecorder *parent) : QPlatformMediaRecorder(parent)
23{
24}
25
26QFFmpegMediaRecorder::~QFFmpegMediaRecorder() = default;
27
28bool QFFmpegMediaRecorder::isLocationWritable(const QUrl &) const
29{
30 return true;
31}
32
33void QFFmpegMediaRecorder::handleSessionError(QMediaRecorder::Error code, const QString &description)
34{
35 updateError(error: code, errorString: description);
36 stop();
37}
38
39void QFFmpegMediaRecorder::record(QMediaEncoderSettings &settings)
40{
41 if (!m_session || state() != QMediaRecorder::StoppedState)
42 return;
43
44 auto videoSources = m_session->activeVideoSources();
45 auto audioInputs = m_session->activeAudioInputs();
46 const auto hasVideo = !videoSources.empty();
47 const auto hasAudio = !audioInputs.empty();
48
49 if (!hasVideo && !hasAudio) {
50 updateError(error: QMediaRecorder::ResourceError, errorString: QMediaRecorder::tr(s: "No video or audio input"));
51 return;
52 }
53
54 if (outputDevice() && !outputLocation().isEmpty())
55 qCWarning(qLcMediaEncoder)
56 << "Both outputDevice and outputLocation has been set to QMediaRecorder";
57
58 if (outputDevice() && !outputDevice()->isWritable())
59 qCWarning(qLcMediaEncoder) << "Output device has been set but not it's not writable";
60
61 QString actualLocation;
62 auto formatContext = std::make_unique<QFFmpeg::EncodingFormatContext>(args: settings.fileFormat());
63
64 if (outputDevice() && outputDevice()->isWritable()) {
65 formatContext->openAVIO(device: outputDevice());
66 } else {
67 actualLocation = findActualLocation(settings);
68 qCDebug(qLcMediaEncoder) << "recording new media to" << actualLocation;
69 formatContext->openAVIO(filePath: actualLocation);
70 }
71
72 qCDebug(qLcMediaEncoder) << "requested format:" << settings.fileFormat()
73 << settings.audioCodec();
74
75 if (!formatContext->isAVIOOpen()) {
76 updateError(error: QMediaRecorder::LocationNotWritable,
77 errorString: QMediaRecorder::tr(s: "Cannot open the output location for writing"));
78 return;
79 }
80
81 m_recordingEngine.reset(p: new RecordingEngine(settings, std::move(formatContext)));
82 m_recordingEngine->setMetaData(m_metaData);
83
84 connect(sender: m_recordingEngine.get(), signal: &QFFmpeg::RecordingEngine::durationChanged, context: this,
85 slot: &QFFmpegMediaRecorder::newDuration);
86 connect(sender: m_recordingEngine.get(), signal: &QFFmpeg::RecordingEngine::finalizationDone, context: this,
87 slot: &QFFmpegMediaRecorder::finalizationDone);
88 connect(sender: m_recordingEngine.get(), signal: &QFFmpeg::RecordingEngine::sessionError, context: this,
89 slot: &QFFmpegMediaRecorder::handleSessionError);
90
91 updateAutoStop();
92
93 auto handleStreamInitializationError = [this](QMediaRecorder::Error code,
94 const QString &description) {
95 qCWarning(qLcMediaEncoder) << "Stream initialization error:" << description;
96 updateError(error: code, errorString: description);
97 };
98
99 connect(sender: m_recordingEngine.get(), signal: &QFFmpeg::RecordingEngine::streamInitializationError, context: this,
100 slot&: handleStreamInitializationError);
101
102 durationChanged(position: 0);
103 actualLocationChanged(location: QUrl::fromLocalFile(localfile: actualLocation));
104 stateChanged(state: QMediaRecorder::RecordingState);
105
106 m_recordingEngine->initialize(audioSources: audioInputs, videoSources);
107}
108
109void QFFmpegMediaRecorder::pause()
110{
111 if (!m_session || state() != QMediaRecorder::RecordingState)
112 return;
113
114 Q_ASSERT(m_recordingEngine);
115 m_recordingEngine->setPaused(true);
116
117 stateChanged(state: QMediaRecorder::PausedState);
118}
119
120void QFFmpegMediaRecorder::resume()
121{
122 if (!m_session || state() != QMediaRecorder::PausedState)
123 return;
124
125 Q_ASSERT(m_recordingEngine);
126 m_recordingEngine->setPaused(false);
127
128 stateChanged(state: QMediaRecorder::RecordingState);
129}
130
131void QFFmpegMediaRecorder::stop()
132{
133 if (!m_session || state() == QMediaRecorder::StoppedState)
134 return;
135 auto * input = m_session ? m_session->audioInput() : nullptr;
136 if (input)
137 static_cast<QFFmpegAudioInput *>(input)->setRunning(false);
138 qCDebug(qLcMediaEncoder) << "stop";
139
140 m_recordingEngine.reset();
141}
142
143void QFFmpegMediaRecorder::finalizationDone()
144{
145 stateChanged(state: QMediaRecorder::StoppedState);
146}
147
148void QFFmpegMediaRecorder::setMetaData(const QMediaMetaData &metaData)
149{
150 if (!m_session)
151 return;
152 m_metaData = metaData;
153}
154
155QMediaMetaData QFFmpegMediaRecorder::metaData() const
156{
157 return m_metaData;
158}
159
160void QFFmpegMediaRecorder::setCaptureSession(QFFmpegMediaCaptureSession *session)
161{
162 auto *captureSession = session;
163 if (m_session == captureSession)
164 return;
165
166 if (m_session)
167 stop();
168
169 m_session = captureSession;
170 if (!m_session)
171 return;
172}
173
174void QFFmpegMediaRecorder::updateAutoStop()
175{
176 const bool autoStop = mediaRecorder()->autoStop();
177 if (!m_recordingEngine || m_recordingEngine->autoStop() == autoStop)
178 return;
179
180 if (autoStop)
181 connect(sender: m_recordingEngine.get(), signal: &QFFmpeg::RecordingEngine::autoStopped, context: this,
182 slot: &QFFmpegMediaRecorder::stop);
183 else
184 disconnect(sender: m_recordingEngine.get(), signal: &QFFmpeg::RecordingEngine::autoStopped, receiver: this,
185 slot: &QFFmpegMediaRecorder::stop);
186
187 m_recordingEngine->setAutoStop(autoStop);
188}
189
190void QFFmpegMediaRecorder::RecordingEngineDeleter::operator()(
191 RecordingEngine *recordingEngine) const
192{
193 // ### all of the below should be done asynchronous. finalize() should do it's work in a thread
194 // to avoid blocking the UI in case of slow codecs
195 recordingEngine->finalize();
196}
197
198QT_END_NAMESPACE
199
200#include "moc_qffmpegmediarecorder_p.cpp"
201

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