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
qgstreamermediacapturesession.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
4#include <mediacapture/qgstreamermediacapturesession_p.h>
5#include <mediacapture/qgstreamermediarecorder_p.h>
6#include <mediacapture/qgstreamerimagecapture_p.h>
7#include <mediacapture/qgstreamercamera_p.h>
8#include <common/qgstpipeline_p.h>
9#include <common/qgstreameraudioinput_p.h>
10#include <common/qgstreameraudiooutput_p.h>
11#include <common/qgstreamervideooutput_p.h>
12#include <common/qgst_debug_p.h>
13
14#include <QtMultimedia/private/qthreadlocalrhi_p.h>
15
16#include <QtCore/qloggingcategory.h>
17#include <QtCore/private/quniquehandle_p.h>
18
20
21namespace {
22
23QGstElement makeTee(const char *name)
24{
26 tee.set("allow-not-linked", true);
27 return tee;
28}
29
30template <typename Functor>
31void executeWhilePadsAreIdle(QSpan<QGstPad> pads, Functor &&f)
32{
33 if (pads.isEmpty())
34 f();
35
36 if (!pads.front())
37 return executeWhilePadsAreIdle(pads.subspan(1), f);
38
39 if (pads.size() == 1)
40 pads.front().modifyPipelineInIdleProbe(f);
41 else {
42 auto remain = pads.subspan(1);
43 pads.front().modifyPipelineInIdleProbe([&] {
44 executeWhilePadsAreIdle(remain, f);
45 });
46 }
47}
48
49void setStateOnElements(QSpan<const QGstElement> elements, GstState state)
50{
51 for (QGstElement element : elements)
52 if (element)
53 element.setState(state);
54}
55
56void finishStateChangeOnElements(QSpan<const QGstElement> elements)
57{
58 for (QGstElement element : elements)
59 if (element)
60 element.finishStateChange();
61}
62
63} // namespace
64
65q23::expected<QPlatformMediaCaptureSession *, QString> QGstreamerMediaCaptureSession::create()
66{
67 auto videoOutput = QGstreamerVideoOutput::create();
68 if (!videoOutput)
69 return q23::unexpected{ videoOutput.error() };
70
71 static const auto error = qGstErrorMessageIfElementsNotAvailable("tee", "capsfilter");
72 if (error)
73 return q23::unexpected{ *error };
74
75 return new QGstreamerMediaCaptureSession(videoOutput.value());
76}
77
78QGstreamerMediaCaptureSession::QGstreamerMediaCaptureSession(QGstreamerVideoOutput *videoOutput)
79 : m_capturePipeline{
80 QGstPipeline::create("mediaCapturePipeline"),
81 },
82 m_gstAudioTee{
83 makeTee("audioTee"),
84 },
85 m_audioSrcPadForEncoder{ m_gstAudioTee.getRequestPad("src_%u") },
86 m_audioSrcPadForOutput{ m_gstAudioTee.getRequestPad("src_%u") },
87 m_gstVideoTee{
88 makeTee("videoTee"),
89 },
90 m_videoSrcPadForEncoder{ m_gstVideoTee.getRequestPad("src_%u") },
91 m_videoSrcPadForOutput{ m_gstVideoTee.getRequestPad("src_%u") },
92 m_videoSrcPadForImageCapture{ m_gstVideoTee.getRequestPad("src_%u") },
93 m_gstVideoOutput(videoOutput)
94{
95 m_gstVideoOutput->setParent(this);
96
97 // NOTE: Creating a GStreamer video sink to be owned by the capture session, any sink created by
98 // user would be a pluggable sink connected to this
99 m_gstVideoSink = new QGstreamerRelayVideoSink(this);
100 m_gstVideoOutput->setVideoSink(m_gstVideoSink);
101
102 m_gstVideoOutput->setIsPreview();
103
104 m_capturePipeline.installMessageFilter(static_cast<QGstreamerBusMessageFilter *>(this));
105 m_capturePipeline.set("message-forward", true);
106
107 // Use system clock to drive all elements in the pipeline. Otherwise,
108 // the clock is sourced from the elements (e.g. from an audio source).
109 // Since the elements are added and removed dynamically the clock would
110 // also change causing lost of synchronization in the pipeline.
111
112 QGstClockHandle systemClock{
113 gst_system_clock_obtain(),
114 QGstClockHandle::HasRef,
115 };
116 gst_pipeline_use_clock(m_capturePipeline.pipeline(), systemClock.get());
117
118 // This is the recording pipeline with only live sources, thus the pipeline
119 // will be always in the playing state.
120 m_capturePipeline.setState(GST_STATE_PLAYING);
121 m_gstVideoOutput->setActive(true);
122
123 m_capturePipeline.dumpGraph("initial");
124}
125
126QGstPad QGstreamerMediaCaptureSession::imageCaptureSink()
127{
128 return m_imageCapture ? m_imageCapture->gstElement().staticPad("sink") : QGstPad{};
129}
130
131QGstPad QGstreamerMediaCaptureSession::videoOutputSink()
132{
133 return m_gstVideoOutput ? m_gstVideoOutput->gstElement().staticPad("sink") : QGstPad{};
134}
135
136QGstPad QGstreamerMediaCaptureSession::audioOutputSink()
137{
138 return m_gstAudioOutput ? m_gstAudioOutput->gstElement().staticPad("sink") : QGstPad{};
139}
140
141QGstreamerMediaCaptureSession::~QGstreamerMediaCaptureSession()
142{
143 setMediaRecorder(nullptr);
144 setImageCapture(nullptr);
145 setCamera(nullptr);
146 m_capturePipeline.removeMessageFilter(static_cast<QGstreamerBusMessageFilter *>(this));
147 m_capturePipeline.setStateSync(GST_STATE_READY);
148 m_capturePipeline.setStateSync(GST_STATE_NULL);
149}
150
151QPlatformCamera *QGstreamerMediaCaptureSession::camera()
152{
153 return m_gstCamera;
154}
155
156void QGstreamerMediaCaptureSession::setCamera(QPlatformCamera *platformCamera)
157{
158 auto *camera = static_cast<QGstreamerCameraBase *>(platformCamera);
159 if (m_gstCamera == camera)
160 return;
161
162 if (m_gstCamera) {
163 QObject::disconnect(m_gstCameraActiveConnection);
164 if (m_gstVideoTee)
165 setCameraActive(false);
166 }
167
168 m_gstCamera = camera;
169
170 if (m_gstCamera) {
171 m_gstCameraActiveConnection =
172 QObject::connect(camera, &QPlatformCamera::activeChanged, this,
173 &QGstreamerMediaCaptureSession::setCameraActive);
174 if (m_gstCamera->isActive())
175 setCameraActive(true);
176 }
177
178 emit cameraChanged();
179}
180
181void QGstreamerMediaCaptureSession::setCameraActive(bool activate)
182{
183 std::array padsToSync = {
184 m_videoSrcPadForEncoder,
185 m_videoSrcPadForImageCapture,
186 m_videoSrcPadForOutput,
187 m_gstVideoTee.sink(),
188 };
189
190 QGstElement cameraElement = m_gstCamera->gstElement();
191 QGstElement videoOutputElement = m_gstVideoOutput->gstElement();
192
193 if (activate) {
194 m_capturePipeline.add(m_gstVideoTee);
195
196 executeWhilePadsAreIdle(padsToSync, [&] {
197 m_capturePipeline.add(cameraElement);
198 if (videoOutputElement)
199 m_capturePipeline.add(videoOutputElement);
200
201 if (m_currentRecorderState && m_currentRecorderState->videoSink)
202 m_videoSrcPadForEncoder.link(m_currentRecorderState->videoSink);
203 if (videoOutputElement)
204 m_videoSrcPadForOutput.link(videoOutputSink());
205 if (m_imageCapture)
206 m_videoSrcPadForImageCapture.link(imageCaptureSink());
207
208 qLinkGstElements(cameraElement, m_gstVideoTee);
209
210 setStateOnElements({ m_gstVideoTee, cameraElement, videoOutputElement },
211 GST_STATE_PLAYING);
212 });
213
214 finishStateChangeOnElements({ m_gstVideoTee, cameraElement, videoOutputElement });
215
216 for (QGstElement addedElement : { m_gstVideoTee, cameraElement, videoOutputElement })
217 addedElement.finishStateChange();
218
219 } else {
220 executeWhilePadsAreIdle(padsToSync, [&] {
221 for (QGstPad &pad : padsToSync)
222 pad.unlinkPeer();
223 });
224 m_capturePipeline.stopAndRemoveElements(cameraElement, m_gstVideoTee, videoOutputElement);
225 }
226
227 m_capturePipeline.dumpGraph("camera");
228}
229
230QPlatformImageCapture *QGstreamerMediaCaptureSession::imageCapture()
231{
232 return m_imageCapture;
233}
234
235void QGstreamerMediaCaptureSession::setImageCapture(QPlatformImageCapture *imageCapture)
236{
237 QGstreamerImageCapture *control = static_cast<QGstreamerImageCapture *>(imageCapture);
238 if (m_imageCapture == control)
239 return;
240
241 m_videoSrcPadForEncoder.modifyPipelineInIdleProbe([&] {
242 if (m_imageCapture) {
243 qUnlinkGstElements(m_gstVideoTee, m_imageCapture->gstElement());
244 m_capturePipeline.stopAndRemoveElements(m_imageCapture->gstElement());
245 m_imageCapture->setCaptureSession(nullptr);
246 }
247
248 m_imageCapture = control;
249
250 if (m_imageCapture) {
251 m_capturePipeline.add(m_imageCapture->gstElement());
252 m_videoSrcPadForImageCapture.link(imageCaptureSink());
253 m_imageCapture->setCaptureSession(this);
254 m_imageCapture->gstElement().setState(GST_STATE_PLAYING);
255 }
256 });
257 if (m_imageCapture)
258 m_imageCapture->gstElement().finishStateChange();
259
260 m_capturePipeline.dumpGraph("imageCapture");
261
262 emit imageCaptureChanged();
263}
264
265void QGstreamerMediaCaptureSession::setMediaRecorder(QPlatformMediaRecorder *recorder)
266{
267 QGstreamerMediaRecorder *control = static_cast<QGstreamerMediaRecorder *>(recorder);
268 if (m_mediaRecorder == control)
269 return;
270
271 if (m_mediaRecorder)
272 m_mediaRecorder->setCaptureSession(nullptr);
273 m_mediaRecorder = control;
274 if (m_mediaRecorder)
275 m_mediaRecorder->setCaptureSession(this);
276
277 emit encoderChanged();
278 m_capturePipeline.dumpGraph("encoder");
279}
280
281QPlatformMediaRecorder *QGstreamerMediaCaptureSession::mediaRecorder()
282{
283 return m_mediaRecorder;
284}
285
286void QGstreamerMediaCaptureSession::linkAndStartEncoder(RecorderElements recorder,
287 const QMediaMetaData &metadata)
288{
289 Q_ASSERT(!m_currentRecorderState);
290
291 std::array padsToSync = {
292 m_audioSrcPadForEncoder,
293 m_videoSrcPadForEncoder,
294 };
295
296 executeWhilePadsAreIdle(padsToSync, [&] {
297 m_capturePipeline.add(recorder.encodeBin, recorder.fileSink);
298 qLinkGstElements(recorder.encodeBin, recorder.fileSink);
299
300 applyMetaDataToTagSetter(metadata, recorder.encodeBin);
301
302 if (recorder.videoSink) {
303 QGstCaps capsFromCamera = m_gstVideoTee.sink().currentCaps();
304
305 m_encoderVideoCapsFilter =
306 QGstElement::createFromFactory("capsfilter", "encoderVideoCapsFilter");
307 m_encoderVideoCapsFilter.set("caps", capsFromCamera);
308
309 m_capturePipeline.add(m_encoderVideoCapsFilter);
310 m_encoderVideoCapsFilter.src().link(recorder.videoSink);
311 m_videoSrcPadForEncoder.link(m_encoderVideoCapsFilter.sink());
312 }
313
314 if (recorder.audioSink) {
315 QGstCaps capsFromInput = m_gstAudioTee.sink().currentCaps();
316
317 m_encoderAudioCapsFilter =
318 QGstElement::createFromFactory("capsfilter", "encoderAudioCapsFilter");
319
320 m_encoderAudioCapsFilter.set("caps", capsFromInput);
321
322 m_capturePipeline.add(m_encoderAudioCapsFilter);
323
324 m_encoderAudioCapsFilter.src().link(recorder.audioSink);
325 m_audioSrcPadForEncoder.link(m_encoderAudioCapsFilter.sink());
326 }
327 setStateOnElements({ recorder.encodeBin, recorder.fileSink, m_encoderVideoCapsFilter,
328 m_encoderAudioCapsFilter },
329 GST_STATE_PLAYING);
330
331 GstEvent *event = gst_event_new_reconfigure();
332 gst_element_send_event(recorder.fileSink.element(), event);
333 });
334
335 finishStateChangeOnElements({ recorder.encodeBin, recorder.fileSink, m_encoderVideoCapsFilter,
336 m_encoderAudioCapsFilter });
337
338 m_currentRecorderState = std::move(recorder);
339}
340
341void QGstreamerMediaCaptureSession::unlinkRecorder()
342{
343 std::array padsToSync = {
344 m_audioSrcPadForEncoder,
345 m_videoSrcPadForEncoder,
346 };
347
348 executeWhilePadsAreIdle(padsToSync, [&] {
349 if (m_encoderVideoCapsFilter)
350 qUnlinkGstElements(m_gstVideoTee, m_encoderVideoCapsFilter);
351
352 if (m_encoderAudioCapsFilter)
353 qUnlinkGstElements(m_gstAudioTee, m_encoderAudioCapsFilter);
354 });
355
356 if (m_encoderVideoCapsFilter) {
357 m_capturePipeline.stopAndRemoveElements(m_encoderVideoCapsFilter);
358 m_encoderVideoCapsFilter = {};
359 }
360
361 if (m_encoderAudioCapsFilter) {
362 m_capturePipeline.stopAndRemoveElements(m_encoderAudioCapsFilter);
363 m_encoderAudioCapsFilter = {};
364 }
365
366 m_currentRecorderState->encodeBin.sendEos();
367}
368
369void QGstreamerMediaCaptureSession::finalizeRecorder()
370{
371 m_capturePipeline.stopAndRemoveElements(m_currentRecorderState->encodeBin,
372 m_currentRecorderState->fileSink);
373
374 m_currentRecorderState = std::nullopt;
375}
376
377const QGstPipeline &QGstreamerMediaCaptureSession::pipeline() const
378{
379 return m_capturePipeline;
380}
381
382void QGstreamerMediaCaptureSession::setAudioInput(QPlatformAudioInput *input)
383{
384 if (m_gstAudioInput == input)
385 return;
386
387 if (input && !m_gstAudioInput) {
388 // a new input is connected, we need to add/link the audio tee and audio tee
389
390 m_capturePipeline.add(m_gstAudioTee);
391
392 std::array padsToSync = {
393 m_audioSrcPadForEncoder,
394 m_audioSrcPadForOutput,
395 m_gstAudioTee.sink(),
396 };
397
398 executeWhilePadsAreIdle(padsToSync, [&] {
399 if (m_currentRecorderState && m_currentRecorderState->audioSink)
400 m_audioSrcPadForEncoder.link(m_currentRecorderState->audioSink);
401 if (m_gstAudioOutput) {
402 m_capturePipeline.add(m_gstAudioOutput->gstElement());
403 m_audioSrcPadForOutput.link(audioOutputSink());
404 }
405
406 m_gstAudioInput = static_cast<QGstreamerAudioInput *>(input);
407 m_capturePipeline.add(m_gstAudioInput->gstElement());
408
409 qLinkGstElements(m_gstAudioInput->gstElement(), m_gstAudioTee);
410
411 m_gstAudioTee.setState(GST_STATE_PLAYING);
412 if (m_gstAudioOutput)
413 m_gstAudioOutput->gstElement().setState(GST_STATE_PLAYING);
414 m_gstAudioInput->gstElement().setState(GST_STATE_PLAYING);
415 });
416
417 } else if (!input && m_gstAudioInput) {
418 // input has been removed, unlink and remove audio output and audio tee
419
420 std::array padsToSync = {
421 m_audioSrcPadForEncoder,
422 m_audioSrcPadForOutput,
423 m_gstAudioTee.sink(),
424 };
425
426 executeWhilePadsAreIdle(padsToSync, [&] {
427 for (QGstPad &pad : padsToSync)
428 pad.unlinkPeer();
429 });
430
431 m_capturePipeline.stopAndRemoveElements(m_gstAudioTee);
432 if (m_gstAudioOutput)
433 m_capturePipeline.stopAndRemoveElements(m_gstAudioOutput->gstElement());
434 m_capturePipeline.stopAndRemoveElements(m_gstAudioInput->gstElement());
435
436 m_gstAudioInput = nullptr;
437 } else {
438 QGstElement oldInputElement = m_gstAudioInput->gstElement();
439
440 m_gstAudioTee.sink().modifyPipelineInIdleProbe([&] {
441 oldInputElement.sink().unlinkPeer();
442 m_gstAudioInput = static_cast<QGstreamerAudioInput *>(input);
443 m_capturePipeline.add(m_gstAudioInput->gstElement());
444
445 qLinkGstElements(m_gstAudioInput->gstElement(), m_gstAudioTee);
446
447 m_gstAudioInput->gstElement().setState(GST_STATE_PLAYING);
448 });
449
450 m_gstAudioInput->gstElement().finishStateChange();
451
452 m_capturePipeline.stopAndRemoveElements(m_gstAudioInput->gstElement());
453 }
454}
455
456void QGstreamerMediaCaptureSession::setVideoPreview(QVideoSink *sink)
457{
458 // Disconnect previous sink
460
461 if (!sink)
462 return;
463
464 // Connect pluggable sink to native sink
465 auto pluggableSink = dynamic_cast<QGstreamerPluggableVideoSink *>(sink->platformVideoSink());
466 Q_ASSERT(pluggableSink);
467 m_gstVideoSink->connectPluggableVideoSink(pluggableSink);
468}
469
470void QGstreamerMediaCaptureSession::setAudioOutput(QPlatformAudioOutput *output)
471{
472 if (m_gstAudioOutput == output)
473 return;
474
475 auto *gstOutput = static_cast<QGstreamerAudioOutput *>(output);
476 if (gstOutput)
477 gstOutput->setAsync(false);
478
479 if (!m_gstAudioInput) {
480 // audio output is not active, since there is no audio input
481 m_gstAudioOutput = static_cast<QGstreamerAudioOutput *>(output);
482 } else {
483 QGstElement oldOutputElement =
484 m_gstAudioOutput ? m_gstAudioOutput->gstElement() : QGstElement{};
485 m_gstAudioOutput = static_cast<QGstreamerAudioOutput *>(output);
486
487 m_audioSrcPadForOutput.modifyPipelineInIdleProbe([&] {
488 if (oldOutputElement)
489 oldOutputElement.sink().unlinkPeer();
490
491 if (m_gstAudioOutput) {
492 m_capturePipeline.add(m_gstAudioOutput->gstElement());
493 m_audioSrcPadForOutput.link(m_gstAudioOutput->gstElement().staticPad("sink"));
494 m_gstAudioOutput->gstElement().setState(GST_STATE_PLAYING);
495 }
496 });
497
498 if (m_gstAudioOutput)
499 m_gstAudioOutput->gstElement().finishStateChange();
500
501 if (oldOutputElement)
502 m_capturePipeline.stopAndRemoveElements(oldOutputElement);
503 }
504}
505
506QGstreamerRelayVideoSink *QGstreamerMediaCaptureSession::gstreamerVideoSink() const
507{
508 return m_gstVideoOutput ? m_gstVideoOutput->gstreamerVideoSink() : nullptr;
509}
510
511bool QGstreamerMediaCaptureSession::processBusMessage(const QGstreamerMessage &msg)
512{
513 if (m_mediaRecorder)
514 m_mediaRecorder->processBusMessage(msg);
515
516 switch (msg.type()) {
517 case GST_MESSAGE_ERROR:
518 return processBusMessageError(msg);
519
520 case GST_MESSAGE_LATENCY:
521 return processBusMessageLatency(msg);
522
523 default:
524 break;
525 }
526
527 return false;
528}
529
530bool QGstreamerMediaCaptureSession::processBusMessageError(const QGstreamerMessage &msg)
531{
532 QUniqueGErrorHandle error;
533 QUniqueGStringHandle message;
534 gst_message_parse_error(msg.message(), &error, &message);
535
536 qWarning() << "QGstreamerMediaCapture: received error from gstreamer" << error << message;
537 m_capturePipeline.dumpGraph("captureError");
538
539 return false;
540}
541
542bool QGstreamerMediaCaptureSession::processBusMessageLatency(const QGstreamerMessage &)
543{
544 m_capturePipeline.recalculateLatency();
545 return false;
546}
547
548QT_END_NAMESPACE
QGstPad sink() const
Definition qgst.cpp:1107
QGstPad staticPad(const char *name) const
Definition qgst.cpp:1097
static QGstElement createFromFactory(const char *factory, const char *name=nullptr)
Definition qgst.cpp:1011
bool link(const QGstPad &sink) const
Definition qgst.cpp:902
bool unlinkPeer() const
Definition qgst.cpp:912
QGstElement gstElement() const
QGstElement gstElement() const
virtual QGstElement gstElement() const =0
void setMediaRecorder(QPlatformMediaRecorder *recorder) override
QPlatformImageCapture * imageCapture() override
QPlatformMediaRecorder * mediaRecorder() override
void setAudioInput(QPlatformAudioInput *input) override
bool processBusMessage(const QGstreamerMessage &) override
void setAudioOutput(QPlatformAudioOutput *output) override
QGstreamerRelayVideoSink * gstreamerVideoSink() const
void setImageCapture(QPlatformImageCapture *imageCapture) override
void setCamera(QPlatformCamera *camera) override
void processBusMessage(const QGstreamerMessage &message)
void connectPluggableVideoSink(QGstreamerPluggableVideoSink *pluggableSink)
void setVideoSink(QGstreamerRelayVideoSink *sink)
QGstElement gstElement() const
QGstreamerRelayVideoSink * gstreamerVideoSink() const
Combined button and popup list for selecting options.
void executeWhilePadsAreIdle(QSpan< QGstPad > pads, Functor &&f)
void finishStateChangeOnElements(QSpan< const QGstElement > elements)
void setStateOnElements(QSpan< const QGstElement > elements, GstState state)
QGstElement makeTee(const char *name)