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 <QtMultimedia/private/qthreadlocalrhi_p.h>
16#include <QtCore/qloggingcategory.h>
17#include <QtCore/private/quniquehandle_p.h>
26 tee.set(
"allow-not-linked",
true);
30template <
typename Functor>
37 return executeWhilePadsAreIdle(pads.subspan(1), f);
40 pads.front().modifyPipelineInIdleProbe(f);
42 auto remain = pads.subspan(1);
43 pads.front().modifyPipelineInIdleProbe([&] {
44 executeWhilePadsAreIdle(remain, f);
51 for (QGstElement element : elements)
53 element.setState(state);
58 for (QGstElement element : elements)
60 element.finishStateChange();
65q23::expected<QPlatformMediaCaptureSession *, QString> QGstreamerMediaCaptureSession::create()
67 auto videoOutput = QGstreamerVideoOutput::create();
69 return q23::unexpected{ videoOutput.error() };
71 static const auto error = qGstErrorMessageIfElementsNotAvailable(
"tee",
"capsfilter");
73 return q23::unexpected{ *error };
75 return new QGstreamerMediaCaptureSession(videoOutput.value());
80 QGstPipeline::create(
"mediaCapturePipeline"),
85 audioSrcPadForEncoder{ gstAudioTee.getRequestPad(
"src_%u") },
86 audioSrcPadForOutput{ gstAudioTee.getRequestPad(
"src_%u") },
90 videoSrcPadForEncoder{ gstVideoTee.getRequestPad(
"src_%u") },
91 videoSrcPadForOutput{ gstVideoTee.getRequestPad(
"src_%u") },
92 videoSrcPadForImageCapture{ gstVideoTee.getRequestPad(
"src_%u") },
93 gstVideoOutput(videoOutput)
95 gstVideoOutput->setParent(
this);
99 m_gstVideoSink =
new QGstreamerRelayVideoSink(
this);
104 capturePipeline.installMessageFilter(
static_cast<QGstreamerBusMessageFilter *>(
this));
105 capturePipeline.set(
"message-forward",
true);
112 QGstClockHandle systemClock{
113 gst_system_clock_obtain(),
114 QGstClockHandle::HasRef,
116 gst_pipeline_use_clock(capturePipeline.pipeline(), systemClock.get());
120 capturePipeline.setState(GST_STATE_PLAYING);
123 capturePipeline.dumpGraph(
"initial");
126QGstPad QGstreamerMediaCaptureSession::imageCaptureSink()
128 return m_imageCapture ? m_imageCapture->gstElement()
.staticPad("sink") : QGstPad{};
131QGstPad QGstreamerMediaCaptureSession::videoOutputSink()
136QGstPad QGstreamerMediaCaptureSession::audioOutputSink()
143 setMediaRecorder(
nullptr);
144 setImageCapture(
nullptr);
146 capturePipeline.removeMessageFilter(
static_cast<QGstreamerBusMessageFilter *>(
this));
147 capturePipeline.setStateSync(GST_STATE_READY);
148 capturePipeline.setStateSync(GST_STATE_NULL);
156void QGstreamerMediaCaptureSession::
setCamera(QPlatformCamera *platformCamera)
159 if (gstCamera == camera)
163 QObject::disconnect(gstCameraActiveConnection);
165 setCameraActive(
false);
171 gstCameraActiveConnection =
172 QObject::connect(camera, &QPlatformCamera::activeChanged,
this,
173 &QGstreamerMediaCaptureSession::setCameraActive);
174 if (gstCamera->isActive())
175 setCameraActive(
true);
178 emit cameraChanged();
181void QGstreamerMediaCaptureSession::setCameraActive(
bool activate)
183 std::array padsToSync = {
184 videoSrcPadForEncoder,
185 videoSrcPadForImageCapture,
186 videoSrcPadForOutput,
194 gstCamera->setCaptureSession(
this);
195 capturePipeline.add(gstVideoTee);
197 executeWhilePadsAreIdle(padsToSync, [&] {
198 capturePipeline.add(cameraElement);
199 if (videoOutputElement)
200 capturePipeline.add(videoOutputElement);
202 if (m_currentRecorderState && m_currentRecorderState->videoSink)
203 videoSrcPadForEncoder
.link(m_currentRecorderState->videoSink);
204 if (videoOutputElement)
205 videoSrcPadForOutput
.link(videoOutputSink()
);
207 videoSrcPadForImageCapture
.link(imageCaptureSink()
);
209 qLinkGstElements(cameraElement, gstVideoTee);
211 setStateOnElements({ gstVideoTee, cameraElement, videoOutputElement },
215 finishStateChangeOnElements({ gstVideoTee, cameraElement, videoOutputElement });
217 for (QGstElement addedElement : { gstVideoTee, cameraElement, videoOutputElement })
218 addedElement.finishStateChange();
221 executeWhilePadsAreIdle(padsToSync, [&] {
222 for (QGstPad &pad : padsToSync)
225 capturePipeline.stopAndRemoveElements(cameraElement, gstVideoTee, videoOutputElement);
227 gstCamera->setCaptureSession(
nullptr);
230 capturePipeline.dumpGraph(
"camera");
235 return m_imageCapture;
238void QGstreamerMediaCaptureSession::
setImageCapture(QPlatformImageCapture *imageCapture)
240 QGstreamerImageCapture *control =
static_cast<QGstreamerImageCapture *>(imageCapture);
241 if (m_imageCapture == control)
244 videoSrcPadForEncoder.modifyPipelineInIdleProbe([&] {
245 if (m_imageCapture) {
246 qUnlinkGstElements(gstVideoTee, m_imageCapture->gstElement());
247 capturePipeline.stopAndRemoveElements(m_imageCapture->gstElement());
248 m_imageCapture->setCaptureSession(
nullptr);
251 m_imageCapture = control;
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);
261 m_imageCapture->gstElement().finishStateChange();
263 capturePipeline.dumpGraph(
"imageCapture");
265 emit imageCaptureChanged();
271 if (m_mediaRecorder == control)
275 m_mediaRecorder->setCaptureSession(
nullptr);
276 m_mediaRecorder = control;
278 m_mediaRecorder->setCaptureSession(
this);
280 emit encoderChanged();
281 capturePipeline.dumpGraph(
"encoder");
286 return m_mediaRecorder;
290 const QMediaMetaData &metadata)
292 Q_ASSERT(!m_currentRecorderState);
294 std::array padsToSync = {
295 audioSrcPadForEncoder,
296 videoSrcPadForEncoder,
299 executeWhilePadsAreIdle(padsToSync, [&] {
300 capturePipeline.add(recorder.encodeBin, recorder.fileSink);
301 qLinkGstElements(recorder.encodeBin, recorder.fileSink);
303 applyMetaDataToTagSetter(metadata, recorder.encodeBin);
306 QGstCaps capsFromCamera = gstVideoTee.sink().currentCaps();
308 encoderVideoCapsFilter =
309 QGstElement::createFromFactory(
"capsfilter",
"encoderVideoCapsFilter");
310 encoderVideoCapsFilter.set(
"caps", capsFromCamera);
312 capturePipeline.add(encoderVideoCapsFilter);
313 encoderVideoCapsFilter.src().link(recorder.videoSink);
314 videoSrcPadForEncoder
.link(encoderVideoCapsFilter.sink());
318 QGstCaps capsFromInput = gstAudioTee.sink().currentCaps();
320 encoderAudioCapsFilter =
321 QGstElement::createFromFactory(
"capsfilter",
"encoderAudioCapsFilter");
323 encoderAudioCapsFilter.set(
"caps", capsFromInput);
325 capturePipeline.add(encoderAudioCapsFilter);
327 encoderAudioCapsFilter.src().link(recorder.audioSink);
328 audioSrcPadForEncoder
.link(encoderAudioCapsFilter.sink());
330 setStateOnElements({ recorder.encodeBin, recorder.fileSink, encoderVideoCapsFilter,
331 encoderAudioCapsFilter },
334 GstEvent *event = gst_event_new_reconfigure();
335 gst_element_send_event(recorder.fileSink.element(), event);
338 finishStateChangeOnElements({ recorder.encodeBin, recorder.fileSink, encoderVideoCapsFilter,
339 encoderAudioCapsFilter });
341 m_currentRecorderState = std::move(recorder);
346 std::array padsToSync = {
347 audioSrcPadForEncoder,
348 videoSrcPadForEncoder,
351 executeWhilePadsAreIdle(padsToSync, [&] {
352 if (encoderVideoCapsFilter)
353 qUnlinkGstElements(gstVideoTee, encoderVideoCapsFilter);
355 if (encoderAudioCapsFilter)
356 qUnlinkGstElements(gstAudioTee, encoderAudioCapsFilter);
359 if (encoderVideoCapsFilter) {
360 capturePipeline.stopAndRemoveElements(encoderVideoCapsFilter);
361 encoderVideoCapsFilter = {};
364 if (encoderAudioCapsFilter) {
365 capturePipeline.stopAndRemoveElements(encoderAudioCapsFilter);
366 encoderAudioCapsFilter = {};
369 m_currentRecorderState->encodeBin.sendEos();
374 capturePipeline.stopAndRemoveElements(m_currentRecorderState->encodeBin,
375 m_currentRecorderState->fileSink);
377 m_currentRecorderState = std::nullopt;
382 return capturePipeline;
385void QGstreamerMediaCaptureSession::
setAudioInput(QPlatformAudioInput *input)
387 if (gstAudioInput == input)
390 if (input && !gstAudioInput) {
393 capturePipeline.add(gstAudioTee);
395 std::array padsToSync = {
396 audioSrcPadForEncoder,
397 audioSrcPadForOutput,
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()
);
410 capturePipeline.add(gstAudioInput->gstElement());
414 gstAudioTee.setState(GST_STATE_PLAYING);
420 }
else if (!input && gstAudioInput) {
423 std::array padsToSync = {
424 audioSrcPadForEncoder,
425 audioSrcPadForOutput,
429 executeWhilePadsAreIdle(padsToSync, [&] {
430 for (QGstPad &pad : padsToSync)
434 capturePipeline.stopAndRemoveElements(gstAudioTee);
436 capturePipeline.stopAndRemoveElements(gstAudioOutput->gstElement());
437 capturePipeline.stopAndRemoveElements(gstAudioInput->gstElement());
439 gstAudioInput =
nullptr;
443 gstAudioTee.sink().modifyPipelineInIdleProbe([&] {
444 oldInputElement.sink().unlinkPeer();
445 gstAudioInput =
static_cast<QGstreamerAudioInput *>(input);
446 capturePipeline.add(gstAudioInput->gstElement());
448 qLinkGstElements(gstAudioInput->gstElement(), gstAudioTee);
450 gstAudioInput->gstElement().setState(GST_STATE_PLAYING);
455 capturePipeline.stopAndRemoveElements(gstAudioInput->gstElement());
459void QGstreamerMediaCaptureSession::setVideoPreview(QVideoSink *sink)
469 Q_ASSERT(pluggableSink);
475 if (gstAudioOutput == output)
480 gstOutput->setAsync(
false);
482 if (!gstAudioInput) {
490 audioSrcPadForOutput.modifyPipelineInIdleProbe([&] {
491 if (oldOutputElement)
494 if (gstAudioOutput) {
495 capturePipeline.add(gstAudioOutput->gstElement());
504 if (oldOutputElement)
505 capturePipeline.stopAndRemoveElements(oldOutputElement);
519 switch (msg.type()) {
520 case GST_MESSAGE_ERROR:
521 return processBusMessageError(msg);
523 case GST_MESSAGE_LATENCY:
524 return processBusMessageLatency(msg);
533bool QGstreamerMediaCaptureSession::processBusMessageError(
const QGstreamerMessage &msg)
535 QUniqueGErrorHandle error;
536 QUniqueGStringHandle message;
537 gst_message_parse_error(msg.message(), &error, &message);
539 qWarning() <<
"QGstreamerMediaCapture: received error from gstreamer" << error << message;
540 capturePipeline.dumpGraph(
"captureError");
545bool QGstreamerMediaCaptureSession::processBusMessageLatency(
const QGstreamerMessage &)
547 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
void connectPluggableVideoSink(QGstreamerPluggableVideoSink *pluggableSink)
void disconnectPluggableVideoSink()
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)