Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qffmpegmediacapturesession.cpp
Go to the documentation of this file.
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
5
6#include <QtCore/qloggingcategory.h>
7
8#include <QtFFmpegMediaPluginImpl/private/qffmpegaudioinput_p.h>
9#include <QtFFmpegMediaPluginImpl/private/qffmpegimagecapture_p.h>
10#include <QtFFmpegMediaPluginImpl/private/qffmpegmediarecorder_p.h>
11
12#include <QtMultimedia/private/qmultimedia_ranges_p.h>
13#include <QtMultimedia/private/qplatformaudioinput_p.h>
14#include <QtMultimedia/private/qplatformaudiobufferinput_p.h>
15#include <QtMultimedia/private/qplatformaudiooutput_p.h>
16#include <QtMultimedia/private/qplatformcamera_p.h>
17#include <QtMultimedia/private/qplatformsurfacecapture_p.h>
18#include <QtMultimedia/private/qplatformvideoframeinput_p.h>
19#include <QtMultimedia/qaudiobuffer.h>
20#include <QtMultimedia/qaudiooutput.h>
21#include <QtMultimedia/qaudiosink.h>
22#include <QtMultimedia/qvideosink.h>
23
24#include <array>
25
26QT_BEGIN_NAMESPACE
27
28using namespace Qt::StringLiterals;
29namespace ranges = QtMultimediaPrivate::ranges;
30
31Q_STATIC_LOGGING_CATEGORY(qLcFFmpegMediaCaptureSession, "qt.multimedia.ffmpeg.mediacapturesession")
32
34{
35 // Heuristic params to avoid jittering
36 // TODO: investigate the reason of jittering and probably reduce the factor
37 constexpr int BufferSizeFactor = 2;
38 constexpr int BufferSizeExceeding = 4096;
39
40 return input.bufferSize() * BufferSizeFactor + BufferSizeExceeding;
41}
42
44{
45 connect(
46 this,
47 &QFFmpegMediaCaptureSession::primaryActiveVideoSourceChanged,
48 this,
49 [this] {
50 updateVideoFrameConnection(nullptr);
51 });
52}
53
55
57{
58 return m_camera;
59}
60
61void QFFmpegMediaCaptureSession::setCamera(QPlatformCamera *camera)
62{
63 if (setVideoSource(m_camera, camera))
64 emit cameraChanged();
65}
66
71
72void QFFmpegMediaCaptureSession::setScreenCapture(QPlatformSurfaceCapture *screenCapture)
73{
74 if (setVideoSource(m_screenCapture, screenCapture))
75 emit screenCaptureChanged();
76}
77
82
83void QFFmpegMediaCaptureSession::setWindowCapture(QPlatformSurfaceCapture *windowCapture)
84{
85 if (setVideoSource(m_windowCapture, windowCapture))
86 emit windowCaptureChanged();
87}
88
93
94void QFFmpegMediaCaptureSession::setVideoFrameInput(QPlatformVideoFrameInput *input)
95{
96 if (setVideoSource(m_videoFrameInput, input))
97 emit videoFrameInputChanged();
98}
99
101{
102 return m_imageCapture;
103}
104
105void QFFmpegMediaCaptureSession::setImageCapture(QPlatformImageCapture *imageCapture)
106{
107 if (m_imageCapture == imageCapture)
108 return;
109
110 if (m_imageCapture)
111 m_imageCapture->setCaptureSession(nullptr);
112
113 m_imageCapture = static_cast<QFFmpegImageCapture *>(imageCapture);
114
115 if (m_imageCapture)
116 m_imageCapture->setCaptureSession(this);
117
118 emit imageCaptureChanged();
119}
120
121void QFFmpegMediaCaptureSession::setMediaRecorder(QPlatformMediaRecorder *recorder)
122{
123 auto *r = static_cast<QFFmpegMediaRecorder *>(recorder);
124 if (m_mediaRecorder == r)
125 return;
126
127 if (m_mediaRecorder)
128 m_mediaRecorder->setCaptureSession(nullptr);
129 m_mediaRecorder = r;
130 if (m_mediaRecorder)
131 m_mediaRecorder->setCaptureSession(this);
132
133 emit encoderChanged();
134}
135
137{
138 return m_mediaRecorder;
139}
140
141void QFFmpegMediaCaptureSession::setAudioInput(QPlatformAudioInput *input)
142{
143 qCDebug(qLcFFmpegMediaCaptureSession)
144 << "set audio input:" << (input ? input->device.description() : u"null"_s);
145
146 auto ffmpegAudioInput = dynamic_cast<QFFmpegAudioInput *>(input);
147 Q_ASSERT(!!input == !!ffmpegAudioInput);
148
149 if (m_audioInput == ffmpegAudioInput)
150 return;
151
152 if (m_audioInput)
153 m_audioInput->q->disconnect(this);
154
155 m_audioInput = ffmpegAudioInput;
156 if (m_audioInput)
157 // TODO: implement the signal in QPlatformAudioInput and connect to it, QTBUG-112294
158 connect(m_audioInput->q, &QAudioInput::deviceChanged, this,
159 &QFFmpegMediaCaptureSession::updateAudioSink);
160
161 updateAudioSink();
162}
163
164void QFFmpegMediaCaptureSession::setAudioBufferInput(QPlatformAudioBufferInput *input)
165{
166 // TODO: implement binding to audio sink like setAudioInput does
167 m_audioBufferInput = input;
168}
169
170void QFFmpegMediaCaptureSession::updateAudioSink()
171{
172 if (m_audioSink) {
173 m_audioSink->reset();
174 m_audioSink.reset();
175 }
176
177 if (!m_audioInput || !m_audioOutput)
178 return;
179
180 auto format = m_audioInput->device.preferredFormat();
181
182 if (!m_audioOutput->device.isFormatSupported(format))
183 qWarning() << "Audio source format" << format << "is not compatible with the audio output";
184
185 m_audioSink = std::make_unique<QAudioSink>(m_audioOutput->device, format);
186
187 m_audioBufferSize = preferredAudioSinkBufferSize(*m_audioInput);
188 m_audioSink->setBufferSize(m_audioBufferSize);
189
190 qCDebug(qLcFFmpegMediaCaptureSession)
191 << "Create audiosink, format:" << format << "bufferSize:" << m_audioSink->bufferSize()
192 << "output device:" << m_audioOutput->device.description();
193
194 m_audioIODevice = m_audioSink->start();
195 if (m_audioIODevice) {
196 auto writeToDevice = [this](const QAudioBuffer &buffer) {
197 if (m_audioBufferSize < preferredAudioSinkBufferSize(*m_audioInput)) {
198 qCDebug(qLcFFmpegMediaCaptureSession)
199 << "Recreate audiosink due to small buffer size:" << m_audioBufferSize;
200
201 updateAudioSink();
202 }
203
204 const auto written =
205 m_audioIODevice->write(buffer.data<const char>(), buffer.byteCount());
206
207 if (written < buffer.byteCount())
208 qCWarning(qLcFFmpegMediaCaptureSession)
209 << "Not all bytes written:" << written << "vs" << buffer.byteCount();
210 };
211 connect(m_audioInput, &QFFmpegAudioInput::newAudioBuffer, m_audioSink.get(), writeToDevice);
212 } else {
213 qWarning() << "Failed to start audiosink push mode";
214 }
215
216 updateVolume();
217}
218
219void QFFmpegMediaCaptureSession::updateVolume()
220{
221 if (m_audioSink)
222 m_audioSink->setVolume(m_audioOutput->muted ? 0.f : m_audioOutput->volume);
223}
224
226{
227 return m_audioInput;
228}
229
231{
232 if (std::exchange(m_videoSink, sink) == sink)
233 return;
234
235 updateVideoFrameConnection(nullptr);
236}
237
238void QFFmpegMediaCaptureSession::setAudioOutput(QPlatformAudioOutput *output)
239{
240 qCDebug(qLcFFmpegMediaCaptureSession)
241 << "set audio output:" << (output ? output->device.description() : u"null"_s);
242
243 if (m_audioOutput == output)
244 return;
245
246 if (m_audioOutput)
247 m_audioOutput->q->disconnect(this);
248
249 m_audioOutput = output;
250 if (m_audioOutput) {
251 // TODO: implement the signals in QPlatformAudioOutput and connect to them, QTBUG-112294
252 connect(m_audioOutput->q, &QAudioOutput::deviceChanged, this,
253 &QFFmpegMediaCaptureSession::updateAudioSink);
254 connect(m_audioOutput->q, &QAudioOutput::volumeChanged, this,
255 &QFFmpegMediaCaptureSession::updateVolume);
256 connect(m_audioOutput->q, &QAudioOutput::mutedChanged, this,
257 &QFFmpegMediaCaptureSession::updateVolume);
258 }
259
260 updateAudioSink();
261}
262
263void QFFmpegMediaCaptureSession::onSourceActivating(QPlatformVideoSource &source)
264{
265 // The source is about to go active and may emit frames from a
266 // background thread before setActive() is done executing.
267 // We set up the connections ahead of time to make sure
268 // always catch the first frame(s).
269 updateVideoFrameConnection(&source);
270}
271
273{
274 // The source failed to activate, so it will not become active. Revert to the
275 // connection implied by the currently active sources.
276 updateVideoFrameConnection(nullptr);
277}
278
279// Returns the highest-priority active video source. If pendingActiveSource
280// is set, it is treated as active even if it does not report isActive() yet.
281QPlatformVideoSource *
282QFFmpegMediaCaptureSession::primaryVideoSource(QPlatformVideoSource *pendingActiveSource)
283{
284 // Priority order must match QPlatformMediaCaptureSession::activeVideoSources().
285 const std::array<QPlatformVideoSource *, 4> sourcesByPriority{
286 videoFrameInput(), camera(), screenCapture(), windowCapture()
287 };
288
289 auto it = ranges::find_if(
290 sourcesByPriority,
291 [&](QPlatformVideoSource *item) {
292 return item && (item == pendingActiveSource || item->isActive());
293 });
294 if (it != sourcesByPriority.end())
295 return *it;
296
297 return nullptr;
298}
299
300void QFFmpegMediaCaptureSession::updateVideoFrameConnection(QPlatformVideoSource *pendingActiveSource)
301{
302 QPlatformVideoSource *source = primaryVideoSource(pendingActiveSource);
303
304 if (m_videoSinkConnection.source == source && m_videoSinkConnection.sink == m_videoSink)
305 return;
306
307 disconnect(m_videoSinkConnection.connection);
308 m_videoSinkConnection = {};
309
310 if (source && m_videoSink) {
311 // AutoConnection type might be a pessimization due to an extra queuing
312 // TODO: investigate and integrate direct connection
313 m_videoSinkConnection.source = source;
314 m_videoSinkConnection.sink = m_videoSink;
315 m_videoSinkConnection.connection = connect(
316 source,
317 &QPlatformVideoSource::newVideoFrame,
318 m_videoSink,
319 &QVideoSink::setVideoFrame);
320 }
321}
322
323void QFFmpegMediaCaptureSession::updatePrimaryActiveVideoSource()
324{
325 QPlatformVideoSource *source = primaryVideoSource(nullptr);
326 if (std::exchange(m_primaryActiveVideoSource, source) != source)
327 emit primaryActiveVideoSourceChanged();
328}
329
330template<typename VideoSource>
331bool QFFmpegMediaCaptureSession::setVideoSource(QPointer<VideoSource> &source,
332 VideoSource *newSource)
333{
334 if (source == newSource)
335 return false;
336
337 if (auto prevSource = std::exchange(source, newSource)) {
338 prevSource->setCaptureSession(nullptr);
339 prevSource->disconnect(this);
340 }
341
342 if (source) {
343 source->setCaptureSession(this);
344 connect(source, &QPlatformVideoSource::activeChanged, this,
345 &QFFmpegMediaCaptureSession::updatePrimaryActiveVideoSource);
346 connect(source, &QObject::destroyed, this,
347 &QFFmpegMediaCaptureSession::updatePrimaryActiveVideoSource, Qt::QueuedConnection);
348 }
349
350 updatePrimaryActiveVideoSource();
351
352 return true;
353}
354
356{
357 return m_primaryActiveVideoSource;
358}
359
361{
362 std::vector<QAudioBufferSource *> result;
363 if (m_audioInput)
364 result.push_back(m_audioInput);
365
366 if (m_audioBufferInput)
367 result.push_back(m_audioBufferInput);
368
369 return result;
370}
371
372QT_END_NAMESPACE
373
374#include "moc_qffmpegmediacapturesession_p.cpp"
void setMediaRecorder(QPlatformMediaRecorder *recorder) override
void setVideoPreview(QVideoSink *sink) override
QPlatformSurfaceCapture * windowCapture() override
void setWindowCapture(QPlatformSurfaceCapture *) override
void setVideoFrameInput(QPlatformVideoFrameInput *) override
void setScreenCapture(QPlatformSurfaceCapture *) override
void setAudioInput(QPlatformAudioInput *input) override
QPlatformVideoFrameInput * videoFrameInput() override
std::vector< QAudioBufferSource * > activeAudioInputs() const
void setImageCapture(QPlatformImageCapture *imageCapture) override
QPlatformSurfaceCapture * screenCapture() override
QPlatformImageCapture * imageCapture() override
QPlatformAudioInput * audioInput() const
void onSourceActivating(QPlatformVideoSource &)
QPlatformVideoSource * primaryActiveVideoSource()
void setCamera(QPlatformCamera *camera) override
void onSourceActivationFailure(QPlatformVideoSource &)
QPlatformMediaRecorder * mediaRecorder() override
QPlatformCamera * camera() override
~QFFmpegMediaCaptureSession() override
void setAudioBufferInput(QPlatformAudioBufferInput *input) override
void setAudioOutput(QPlatformAudioOutput *output) override
static int preferredAudioSinkBufferSize(const QFFmpegAudioInput &input)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)