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>
14#include <QtCore/qloggingcategory.h>
15#include <QtCore/private/quniquehandle_p.h>
24 tee.set(
"allow-not-linked",
true);
28template <
typename Functor>
35 return executeWhilePadsAreIdle(pads.subspan(1), f);
38 pads.front().modifyPipelineInIdleProbe(f);
40 auto remain = pads.subspan(1);
41 pads.front().modifyPipelineInIdleProbe([&] {
42 executeWhilePadsAreIdle(remain, f);
49 for (QGstElement element : elements)
51 element.setState(state);
56 for (QGstElement element : elements)
58 element.finishStateChange();
63QMaybe<QPlatformMediaCaptureSession *> QGstreamerMediaCaptureSession::create()
65 auto videoOutput = QGstreamerVideoOutput::create();
67 return videoOutput.error();
69 static const auto error = qGstErrorMessageIfElementsNotAvailable(
"tee",
"capsfilter");
73 return new QGstreamerMediaCaptureSession(videoOutput.value());
78 QGstPipeline::create(
"mediaCapturePipeline"),
83 audioSrcPadForEncoder{ gstAudioTee.getRequestPad(
"src_%u") },
84 audioSrcPadForOutput{ gstAudioTee.getRequestPad(
"src_%u") },
88 videoSrcPadForEncoder{ gstVideoTee.getRequestPad(
"src_%u") },
89 videoSrcPadForOutput{ gstVideoTee.getRequestPad(
"src_%u") },
90 videoSrcPadForImageCapture{ gstVideoTee.getRequestPad(
"src_%u") },
91 gstVideoOutput(videoOutput)
93 gstVideoOutput->setParent(
this);
96 capturePipeline.installMessageFilter(
static_cast<QGstreamerBusMessageFilter *>(
this));
97 capturePipeline.set(
"message-forward",
true);
104 QGstClockHandle systemClock{
105 gst_system_clock_obtain(),
107 gst_pipeline_use_clock(capturePipeline.pipeline(), systemClock.get());
111 capturePipeline.setState(GST_STATE_PLAYING);
114 capturePipeline.dumpGraph(
"initial");
117QGstPad QGstreamerMediaCaptureSession::imageCaptureSink()
122QGstPad QGstreamerMediaCaptureSession::videoOutputSink()
127QGstPad QGstreamerMediaCaptureSession::audioOutputSink()
134 setMediaRecorder(
nullptr);
135 setImageCapture(
nullptr);
137 capturePipeline.removeMessageFilter(
static_cast<QGstreamerBusMessageFilter *>(
this));
138 capturePipeline.setStateSync(GST_STATE_READY);
139 capturePipeline.setStateSync(GST_STATE_NULL);
147void QGstreamerMediaCaptureSession::
setCamera(QPlatformCamera *platformCamera)
150 if (gstCamera == camera)
154 QObject::disconnect(gstCameraActiveConnection);
156 setCameraActive(
false);
162 gstCameraActiveConnection =
163 QObject::connect(camera, &QPlatformCamera::activeChanged,
this,
164 &QGstreamerMediaCaptureSession::setCameraActive);
165 if (gstCamera->isActive())
166 setCameraActive(
true);
169 emit cameraChanged();
172void QGstreamerMediaCaptureSession::setCameraActive(
bool activate)
174 std::array padsToSync = {
175 videoSrcPadForEncoder,
176 videoSrcPadForImageCapture,
177 videoSrcPadForOutput,
185 gstCamera->setCaptureSession(
this);
186 capturePipeline.add(gstVideoTee);
188 executeWhilePadsAreIdle(padsToSync, [&] {
189 capturePipeline.add(cameraElement);
190 if (videoOutputElement)
191 capturePipeline.add(videoOutputElement);
193 if (m_currentRecorderState && m_currentRecorderState->videoSink)
194 videoSrcPadForEncoder.link(m_currentRecorderState->videoSink);
195 if (videoOutputElement)
196 videoSrcPadForOutput
.link(videoOutputSink()
);
198 videoSrcPadForImageCapture
.link(imageCaptureSink()
);
200 qLinkGstElements(cameraElement, gstVideoTee);
202 setStateOnElements({ gstVideoTee, cameraElement, videoOutputElement },
206 finishStateChangeOnElements({ gstVideoTee, cameraElement, videoOutputElement });
208 for (QGstElement addedElement : { gstVideoTee, cameraElement, videoOutputElement })
209 addedElement.finishStateChange();
212 executeWhilePadsAreIdle(padsToSync, [&] {
213 for (QGstPad &pad : padsToSync)
216 capturePipeline.stopAndRemoveElements(cameraElement, gstVideoTee, videoOutputElement);
218 gstCamera->setCaptureSession(
nullptr);
221 capturePipeline.dumpGraph(
"camera");
226 return m_imageCapture;
229void QGstreamerMediaCaptureSession::
setImageCapture(QPlatformImageCapture *imageCapture)
232 if (m_imageCapture == control)
235 videoSrcPadForEncoder.modifyPipelineInIdleProbe([&] {
236 if (m_imageCapture) {
237 qUnlinkGstElements(gstVideoTee, m_imageCapture->gstElement());
238 capturePipeline.stopAndRemoveElements(m_imageCapture->gstElement());
239 m_imageCapture->setCaptureSession(
nullptr);
242 m_imageCapture = control;
244 if (m_imageCapture) {
245 capturePipeline.add(m_imageCapture->gstElement());
246 videoSrcPadForImageCapture
.link(imageCaptureSink()
);
247 m_imageCapture->setCaptureSession(
this);
248 m_imageCapture->gstElement().setState(GST_STATE_PLAYING);
254 capturePipeline.dumpGraph(
"imageCapture");
256 emit imageCaptureChanged();
262 if (m_mediaRecorder == control)
266 m_mediaRecorder->setCaptureSession(
nullptr);
267 m_mediaRecorder = control;
269 m_mediaRecorder->setCaptureSession(
this);
271 emit encoderChanged();
272 capturePipeline.dumpGraph(
"encoder");
277 return m_mediaRecorder;
281 const QMediaMetaData &metadata)
283 Q_ASSERT(!m_currentRecorderState);
285 std::array padsToSync = {
286 audioSrcPadForEncoder,
287 videoSrcPadForEncoder,
290 executeWhilePadsAreIdle(padsToSync, [&] {
291 capturePipeline.add(recorder.encodeBin, recorder.fileSink);
292 qLinkGstElements(recorder.encodeBin, recorder.fileSink);
294 applyMetaDataToTagSetter(metadata, recorder.encodeBin);
297 QGstCaps capsFromCamera = gstVideoTee.sink().currentCaps();
299 encoderVideoCapsFilter =
300 QGstElement::createFromFactory(
"capsfilter",
"encoderVideoCapsFilter");
301 encoderVideoCapsFilter.set(
"caps", capsFromCamera);
303 capturePipeline.add(encoderVideoCapsFilter);
304 encoderVideoCapsFilter.src().link(recorder.videoSink);
305 videoSrcPadForEncoder.link(encoderVideoCapsFilter.sink());
309 QGstCaps capsFromInput = gstAudioTee.sink().currentCaps();
311 encoderAudioCapsFilter =
312 QGstElement::createFromFactory(
"capsfilter",
"encoderAudioCapsFilter");
314 encoderAudioCapsFilter.set(
"caps", capsFromInput);
316 capturePipeline.add(encoderAudioCapsFilter);
318 encoderAudioCapsFilter.src().link(recorder.audioSink);
319 audioSrcPadForEncoder.link(encoderAudioCapsFilter.sink());
321 setStateOnElements({ recorder.encodeBin, recorder.fileSink, encoderVideoCapsFilter,
322 encoderAudioCapsFilter },
325 GstEvent *event = gst_event_new_reconfigure();
326 gst_element_send_event(recorder.fileSink.element(), event);
329 finishStateChangeOnElements({ recorder.encodeBin, recorder.fileSink, encoderVideoCapsFilter,
330 encoderAudioCapsFilter });
332 m_currentRecorderState = std::move(recorder);
337 std::array padsToSync = {
338 audioSrcPadForEncoder,
339 videoSrcPadForEncoder,
342 executeWhilePadsAreIdle(padsToSync, [&] {
343 if (encoderVideoCapsFilter)
344 qUnlinkGstElements(gstVideoTee, encoderVideoCapsFilter);
346 if (encoderAudioCapsFilter)
347 qUnlinkGstElements(gstAudioTee, encoderAudioCapsFilter);
350 if (encoderVideoCapsFilter) {
351 capturePipeline.stopAndRemoveElements(encoderVideoCapsFilter);
352 encoderVideoCapsFilter = {};
355 if (encoderAudioCapsFilter) {
356 capturePipeline.stopAndRemoveElements(encoderAudioCapsFilter);
357 encoderAudioCapsFilter = {};
360 m_currentRecorderState->encodeBin.sendEos();
365 capturePipeline.stopAndRemoveElements(m_currentRecorderState->encodeBin,
366 m_currentRecorderState->fileSink);
368 m_currentRecorderState = std::nullopt;
373 return capturePipeline;
376void QGstreamerMediaCaptureSession::
setAudioInput(QPlatformAudioInput *input)
378 if (gstAudioInput == input)
381 if (input && !gstAudioInput) {
384 capturePipeline.add(gstAudioTee);
386 std::array padsToSync = {
387 audioSrcPadForEncoder,
388 audioSrcPadForOutput,
392 executeWhilePadsAreIdle(padsToSync, [&] {
393 if (m_currentRecorderState && m_currentRecorderState->audioSink)
394 audioSrcPadForEncoder.link(m_currentRecorderState->audioSink);
395 if (gstAudioOutput) {
396 capturePipeline.add(gstAudioOutput->gstElement());
397 audioSrcPadForOutput
.link(audioOutputSink()
);
401 capturePipeline.add(gstAudioInput->gstElement());
403 qLinkGstElements(gstAudioInput->gstElement(), gstAudioTee);
405 gstAudioTee.setState(GST_STATE_PLAYING);
407 gstAudioOutput->gstElement().setState(GST_STATE_PLAYING);
408 gstAudioInput->gstElement().setState(GST_STATE_PLAYING);
411 }
else if (!input && gstAudioInput) {
414 std::array padsToSync = {
415 audioSrcPadForEncoder,
416 audioSrcPadForOutput,
420 executeWhilePadsAreIdle(padsToSync, [&] {
421 for (QGstPad &pad : padsToSync)
425 capturePipeline.stopAndRemoveElements(gstAudioTee);
427 capturePipeline.stopAndRemoveElements(gstAudioOutput->gstElement());
428 capturePipeline.stopAndRemoveElements(gstAudioInput->gstElement());
430 gstAudioInput =
nullptr;
434 gstAudioTee.sink().modifyPipelineInIdleProbe([&] {
435 oldInputElement.sink().unlinkPeer();
436 gstAudioInput =
static_cast<QGstreamerAudioInput *>(input);
437 capturePipeline.add(gstAudioInput->gstElement());
439 qLinkGstElements(gstAudioInput->gstElement(), gstAudioTee);
441 gstAudioInput->gstElement().setState(GST_STATE_PLAYING);
446 capturePipeline.stopAndRemoveElements(gstAudioInput->gstElement());
450void QGstreamerMediaCaptureSession::setVideoPreview(QVideoSink *sink)
452 auto *gstSink = sink ?
static_cast<
QGstreamerVideoSink *>(sink->platformVideoSink()) :
nullptr;
454 gstSink->setAsync(
false);
457 capturePipeline.dumpGraph(
"setVideoPreview");
462 if (gstAudioOutput == output)
467 gstOutput->setAsync(
false);
469 if (!gstAudioInput) {
477 audioSrcPadForOutput.modifyPipelineInIdleProbe([&] {
478 if (oldOutputElement)
481 if (gstAudioOutput) {
482 capturePipeline.add(gstAudioOutput->gstElement());
484 gstAudioOutput->gstElement().setState(GST_STATE_PLAYING);
491 if (oldOutputElement)
492 capturePipeline.stopAndRemoveElements(oldOutputElement);
506 switch (msg.type()) {
507 case GST_MESSAGE_ERROR:
508 return processBusMessageError(msg);
510 case GST_MESSAGE_LATENCY:
511 return processBusMessageLatency(msg);
520bool QGstreamerMediaCaptureSession::processBusMessageError(
const QGstreamerMessage &msg)
522 QUniqueGErrorHandle error;
523 QUniqueGStringHandle message;
524 gst_message_parse_error(msg.message(), &error, &message);
526 qWarning() <<
"QGstreamerMediaCapture: received error from gstreamer" << error << message;
527 capturePipeline.dumpGraph(
"captureError");
532bool QGstreamerMediaCaptureSession::processBusMessageLatency(
const QGstreamerMessage &)
534 capturePipeline.recalculateLatency();
QGstPad staticPad(const char *name) const
static QGstElement createFromFactory(const char *factory, const char *name=nullptr)
bool link(const QGstPad &sink) const
QGstElement gstElement() const
virtual QGstElement gstElement() const =0
QGstElement gstElement() const
QGstreamerVideoSink * gstreamerVideoSink() const
QGstElement gstElement() const
void setVideoSink(QVideoSink *sink)
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)