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 : capturePipeline{
80 QGstPipeline::create("mediaCapturePipeline"),
81 },
82 gstAudioTee{
83 makeTee("audioTee"),
84 },
85 audioSrcPadForEncoder{ gstAudioTee.getRequestPad("src_%u") },
86 audioSrcPadForOutput{ gstAudioTee.getRequestPad("src_%u") },
87 gstVideoTee{
88 makeTee("videoTee"),
89 },
90 videoSrcPadForEncoder{ gstVideoTee.getRequestPad("src_%u") },
91 videoSrcPadForOutput{ gstVideoTee.getRequestPad("src_%u") },
92 videoSrcPadForImageCapture{ gstVideoTee.getRequestPad("src_%u") },
93 gstVideoOutput(videoOutput)
94{
95 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 gstVideoOutput->setVideoSink(m_gstVideoSink);
101
102 gstVideoOutput->setIsPreview();
103
104 capturePipeline.installMessageFilter(static_cast<QGstreamerBusMessageFilter *>(this));
105 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(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 capturePipeline.setState(GST_STATE_PLAYING);
121 gstVideoOutput->setActive(true);
122
123 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 gstVideoOutput ? gstVideoOutput->gstElement().staticPad("sink") : QGstPad{};
134}
135
136QGstPad QGstreamerMediaCaptureSession::audioOutputSink()
137{
138 return gstAudioOutput ? gstAudioOutput->gstElement().staticPad("sink") : QGstPad{};
139}
140
141QGstreamerMediaCaptureSession::~QGstreamerMediaCaptureSession()
142{
143 setMediaRecorder(nullptr);
144 setImageCapture(nullptr);
145 setCamera(nullptr);
146 capturePipeline.removeMessageFilter(static_cast<QGstreamerBusMessageFilter *>(this));
147 capturePipeline.setStateSync(GST_STATE_READY);
148 capturePipeline.setStateSync(GST_STATE_NULL);
149}
150
151QPlatformCamera *QGstreamerMediaCaptureSession::camera()
152{
153 return gstCamera;
154}
155
156void QGstreamerMediaCaptureSession::setCamera(QPlatformCamera *platformCamera)
157{
158 auto *camera = static_cast<QGstreamerCameraBase *>(platformCamera);
159 if (gstCamera == camera)
160 return;
161
162 if (gstCamera) {
163 QObject::disconnect(gstCameraActiveConnection);
164 if (gstVideoTee)
165 setCameraActive(false);
166 }
167
168 gstCamera = camera;
169
170 if (gstCamera) {
171 gstCameraActiveConnection =
172 QObject::connect(camera, &QPlatformCamera::activeChanged, this,
173 &QGstreamerMediaCaptureSession::setCameraActive);
174 if (gstCamera->isActive())
175 setCameraActive(true);
176 }
177
178 emit cameraChanged();
179}
180
181void QGstreamerMediaCaptureSession::setCameraActive(bool activate)
182{
183 std::array padsToSync = {
184 videoSrcPadForEncoder,
185 videoSrcPadForImageCapture,
186 videoSrcPadForOutput,
187 gstVideoTee.sink(),
188 };
189
190 QGstElement cameraElement = gstCamera->gstElement();
191 QGstElement videoOutputElement = gstVideoOutput->gstElement();
192
193 if (activate) {
194 gstCamera->setCaptureSession(this);
195 capturePipeline.add(gstVideoTee);
196
197 executeWhilePadsAreIdle(padsToSync, [&] {
198 capturePipeline.add(cameraElement);
199 if (videoOutputElement)
200 capturePipeline.add(videoOutputElement);
201
202 if (m_currentRecorderState && m_currentRecorderState->videoSink)
203 videoSrcPadForEncoder.link(m_currentRecorderState->videoSink);
204 if (videoOutputElement)
205 videoSrcPadForOutput.link(videoOutputSink());
206 if (m_imageCapture)
207 videoSrcPadForImageCapture.link(imageCaptureSink());
208
209 qLinkGstElements(cameraElement, gstVideoTee);
210
211 setStateOnElements({ gstVideoTee, cameraElement, videoOutputElement },
212 GST_STATE_PLAYING);
213 });
214
215 finishStateChangeOnElements({ gstVideoTee, cameraElement, videoOutputElement });
216
217 for (QGstElement addedElement : { gstVideoTee, cameraElement, videoOutputElement })
218 addedElement.finishStateChange();
219
220 } else {
221 executeWhilePadsAreIdle(padsToSync, [&] {
222 for (QGstPad &pad : padsToSync)
223 pad.unlinkPeer();
224 });
225 capturePipeline.stopAndRemoveElements(cameraElement, gstVideoTee, videoOutputElement);
226
227 gstCamera->setCaptureSession(nullptr);
228 }
229
230 capturePipeline.dumpGraph("camera");
231}
232
233QPlatformImageCapture *QGstreamerMediaCaptureSession::imageCapture()
234{
235 return m_imageCapture;
236}
237
238void QGstreamerMediaCaptureSession::setImageCapture(QPlatformImageCapture *imageCapture)
239{
240 QGstreamerImageCapture *control = static_cast<QGstreamerImageCapture *>(imageCapture);
241 if (m_imageCapture == control)
242 return;
243
244 videoSrcPadForEncoder.modifyPipelineInIdleProbe([&] {
245 if (m_imageCapture) {
246 qUnlinkGstElements(gstVideoTee, m_imageCapture->gstElement());
247 capturePipeline.stopAndRemoveElements(m_imageCapture->gstElement());
248 m_imageCapture->setCaptureSession(nullptr);
249 }
250
251 m_imageCapture = control;
252
253 if (m_imageCapture) {
254 capturePipeline.add(m_imageCapture->gstElement());
255 videoSrcPadForImageCapture.link(imageCaptureSink());
256 m_imageCapture->setCaptureSession(this);
257 m_imageCapture->gstElement().setState(GST_STATE_PLAYING);
258 }
259 });
260 if (m_imageCapture)
261 m_imageCapture->gstElement().finishStateChange();
262
263 capturePipeline.dumpGraph("imageCapture");
264
265 emit imageCaptureChanged();
266}
267
268void QGstreamerMediaCaptureSession::setMediaRecorder(QPlatformMediaRecorder *recorder)
269{
270 QGstreamerMediaRecorder *control = static_cast<QGstreamerMediaRecorder *>(recorder);
271 if (m_mediaRecorder == control)
272 return;
273
274 if (m_mediaRecorder)
275 m_mediaRecorder->setCaptureSession(nullptr);
276 m_mediaRecorder = control;
277 if (m_mediaRecorder)
278 m_mediaRecorder->setCaptureSession(this);
279
280 emit encoderChanged();
281 capturePipeline.dumpGraph("encoder");
282}
283
284QPlatformMediaRecorder *QGstreamerMediaCaptureSession::mediaRecorder()
285{
286 return m_mediaRecorder;
287}
288
289void QGstreamerMediaCaptureSession::linkAndStartEncoder(RecorderElements recorder,
290 const QMediaMetaData &metadata)
291{
292 Q_ASSERT(!m_currentRecorderState);
293
294 std::array padsToSync = {
295 audioSrcPadForEncoder,
296 videoSrcPadForEncoder,
297 };
298
299 executeWhilePadsAreIdle(padsToSync, [&] {
300 capturePipeline.add(recorder.encodeBin, recorder.fileSink);
301 qLinkGstElements(recorder.encodeBin, recorder.fileSink);
302
303 applyMetaDataToTagSetter(metadata, recorder.encodeBin);
304
305 if (recorder.videoSink) {
306 QGstCaps capsFromCamera = gstVideoTee.sink().currentCaps();
307
308 encoderVideoCapsFilter =
309 QGstElement::createFromFactory("capsfilter", "encoderVideoCapsFilter");
310 encoderVideoCapsFilter.set("caps", capsFromCamera);
311
312 capturePipeline.add(encoderVideoCapsFilter);
313 encoderVideoCapsFilter.src().link(recorder.videoSink);
314 videoSrcPadForEncoder.link(encoderVideoCapsFilter.sink());
315 }
316
317 if (recorder.audioSink) {
318 QGstCaps capsFromInput = gstAudioTee.sink().currentCaps();
319
320 encoderAudioCapsFilter =
321 QGstElement::createFromFactory("capsfilter", "encoderAudioCapsFilter");
322
323 encoderAudioCapsFilter.set("caps", capsFromInput);
324
325 capturePipeline.add(encoderAudioCapsFilter);
326
327 encoderAudioCapsFilter.src().link(recorder.audioSink);
328 audioSrcPadForEncoder.link(encoderAudioCapsFilter.sink());
329 }
330 setStateOnElements({ recorder.encodeBin, recorder.fileSink, encoderVideoCapsFilter,
331 encoderAudioCapsFilter },
332 GST_STATE_PLAYING);
333
334 GstEvent *event = gst_event_new_reconfigure();
335 gst_element_send_event(recorder.fileSink.element(), event);
336 });
337
338 finishStateChangeOnElements({ recorder.encodeBin, recorder.fileSink, encoderVideoCapsFilter,
339 encoderAudioCapsFilter });
340
341 m_currentRecorderState = std::move(recorder);
342}
343
344void QGstreamerMediaCaptureSession::unlinkRecorder()
345{
346 std::array padsToSync = {
347 audioSrcPadForEncoder,
348 videoSrcPadForEncoder,
349 };
350
351 executeWhilePadsAreIdle(padsToSync, [&] {
352 if (encoderVideoCapsFilter)
353 qUnlinkGstElements(gstVideoTee, encoderVideoCapsFilter);
354
355 if (encoderAudioCapsFilter)
356 qUnlinkGstElements(gstAudioTee, encoderAudioCapsFilter);
357 });
358
359 if (encoderVideoCapsFilter) {
360 capturePipeline.stopAndRemoveElements(encoderVideoCapsFilter);
361 encoderVideoCapsFilter = {};
362 }
363
364 if (encoderAudioCapsFilter) {
365 capturePipeline.stopAndRemoveElements(encoderAudioCapsFilter);
366 encoderAudioCapsFilter = {};
367 }
368
369 m_currentRecorderState->encodeBin.sendEos();
370}
371
372void QGstreamerMediaCaptureSession::finalizeRecorder()
373{
374 capturePipeline.stopAndRemoveElements(m_currentRecorderState->encodeBin,
375 m_currentRecorderState->fileSink);
376
377 m_currentRecorderState = std::nullopt;
378}
379
380const QGstPipeline &QGstreamerMediaCaptureSession::pipeline() const
381{
382 return capturePipeline;
383}
384
385void QGstreamerMediaCaptureSession::setAudioInput(QPlatformAudioInput *input)
386{
387 if (gstAudioInput == input)
388 return;
389
390 if (input && !gstAudioInput) {
391 // a new input is connected, we need to add/link the audio tee and audio tee
392
393 capturePipeline.add(gstAudioTee);
394
395 std::array padsToSync = {
396 audioSrcPadForEncoder,
397 audioSrcPadForOutput,
398 gstAudioTee.sink(),
399 };
400
401 executeWhilePadsAreIdle(padsToSync, [&] {
402 if (m_currentRecorderState && m_currentRecorderState->audioSink)
403 audioSrcPadForEncoder.link(m_currentRecorderState->audioSink);
404 if (gstAudioOutput) {
405 capturePipeline.add(gstAudioOutput->gstElement());
406 audioSrcPadForOutput.link(audioOutputSink());
407 }
408
409 gstAudioInput = static_cast<QGstreamerAudioInput *>(input);
410 capturePipeline.add(gstAudioInput->gstElement());
411
412 qLinkGstElements(gstAudioInput->gstElement(), gstAudioTee);
413
414 gstAudioTee.setState(GST_STATE_PLAYING);
415 if (gstAudioOutput)
416 gstAudioOutput->gstElement().setState(GST_STATE_PLAYING);
417 gstAudioInput->gstElement().setState(GST_STATE_PLAYING);
418 });
419
420 } else if (!input && gstAudioInput) {
421 // input has been removed, unlink and remove audio output and audio tee
422
423 std::array padsToSync = {
424 audioSrcPadForEncoder,
425 audioSrcPadForOutput,
426 gstAudioTee.sink(),
427 };
428
429 executeWhilePadsAreIdle(padsToSync, [&] {
430 for (QGstPad &pad : padsToSync)
431 pad.unlinkPeer();
432 });
433
434 capturePipeline.stopAndRemoveElements(gstAudioTee);
435 if (gstAudioOutput)
436 capturePipeline.stopAndRemoveElements(gstAudioOutput->gstElement());
437 capturePipeline.stopAndRemoveElements(gstAudioInput->gstElement());
438
439 gstAudioInput = nullptr;
440 } else {
441 QGstElement oldInputElement = gstAudioInput->gstElement();
442
443 gstAudioTee.sink().modifyPipelineInIdleProbe([&] {
444 oldInputElement.sink().unlinkPeer();
445 gstAudioInput = static_cast<QGstreamerAudioInput *>(input);
446 capturePipeline.add(gstAudioInput->gstElement());
447
448 qLinkGstElements(gstAudioInput->gstElement(), gstAudioTee);
449
450 gstAudioInput->gstElement().setState(GST_STATE_PLAYING);
451 });
452
453 gstAudioInput->gstElement().finishStateChange();
454
455 capturePipeline.stopAndRemoveElements(gstAudioInput->gstElement());
456 }
457}
458
459void QGstreamerMediaCaptureSession::setVideoPreview(QVideoSink *sink)
460{
461 // Disconnect previous sink
463
464 if (!sink)
465 return;
466
467 // Connect pluggable sink to native sink
468 auto pluggableSink = dynamic_cast<QGstreamerPluggableVideoSink *>(sink->platformVideoSink());
469 Q_ASSERT(pluggableSink);
470 m_gstVideoSink->connectPluggableVideoSink(pluggableSink);
471}
472
473void QGstreamerMediaCaptureSession::setAudioOutput(QPlatformAudioOutput *output)
474{
475 if (gstAudioOutput == output)
476 return;
477
478 auto *gstOutput = static_cast<QGstreamerAudioOutput *>(output);
479 if (gstOutput)
480 gstOutput->setAsync(false);
481
482 if (!gstAudioInput) {
483 // audio output is not active, since there is no audio input
484 gstAudioOutput = static_cast<QGstreamerAudioOutput *>(output);
485 } else {
486 QGstElement oldOutputElement =
487 gstAudioOutput ? gstAudioOutput->gstElement() : QGstElement{};
488 gstAudioOutput = static_cast<QGstreamerAudioOutput *>(output);
489
490 audioSrcPadForOutput.modifyPipelineInIdleProbe([&] {
491 if (oldOutputElement)
492 oldOutputElement.sink().unlinkPeer();
493
494 if (gstAudioOutput) {
495 capturePipeline.add(gstAudioOutput->gstElement());
496 audioSrcPadForOutput.link(gstAudioOutput->gstElement().staticPad("sink"));
497 gstAudioOutput->gstElement().setState(GST_STATE_PLAYING);
498 }
499 });
500
501 if (gstAudioOutput)
502 gstAudioOutput->gstElement().finishStateChange();
503
504 if (oldOutputElement)
505 capturePipeline.stopAndRemoveElements(oldOutputElement);
506 }
507}
508
509QGstreamerRelayVideoSink *QGstreamerMediaCaptureSession::gstreamerVideoSink() const
510{
511 return gstVideoOutput ? gstVideoOutput->gstreamerVideoSink() : nullptr;
512}
513
514bool QGstreamerMediaCaptureSession::processBusMessage(const QGstreamerMessage &msg)
515{
516 if (m_mediaRecorder)
517 m_mediaRecorder->processBusMessage(msg);
518
519 switch (msg.type()) {
520 case GST_MESSAGE_ERROR:
521 return processBusMessageError(msg);
522
523 case GST_MESSAGE_LATENCY:
524 return processBusMessageLatency(msg);
525
526 default:
527 break;
528 }
529
530 return false;
531}
532
533bool QGstreamerMediaCaptureSession::processBusMessageError(const QGstreamerMessage &msg)
534{
535 QUniqueGErrorHandle error;
536 QUniqueGStringHandle message;
537 gst_message_parse_error(msg.message(), &error, &message);
538
539 qWarning() << "QGstreamerMediaCapture: received error from gstreamer" << error << message;
540 capturePipeline.dumpGraph("captureError");
541
542 return false;
543}
544
545bool QGstreamerMediaCaptureSession::processBusMessageLatency(const QGstreamerMessage &)
546{
547 capturePipeline.recalculateLatency();
548 return false;
549}
550
551QT_END_NAMESPACE
QGstPad sink() const
Definition qgst.cpp:1040
QGstPad staticPad(const char *name) const
Definition qgst.cpp:1030
static QGstElement createFromFactory(const char *factory, const char *name=nullptr)
Definition qgst.cpp:944
bool link(const QGstPad &sink) const
Definition qgst.cpp:835
bool unlinkPeer() const
Definition qgst.cpp:845
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)