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