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 m_audioSrcPadForEncoder{ m_gstAudioTee.getRequestPad(
"src_%u") },
86 m_audioSrcPadForOutput{ m_gstAudioTee.getRequestPad(
"src_%u") },
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)
95 m_gstVideoOutput->setParent(
this);
99 m_gstVideoSink =
new QGstreamerRelayVideoSink(
this);
104 m_capturePipeline.installMessageFilter(
static_cast<QGstreamerBusMessageFilter *>(
this));
105 m_capturePipeline.set(
"message-forward",
true);
112 QGstClockHandle systemClock{
113 gst_system_clock_obtain(),
114 QGstClockHandle::HasRef,
116 gst_pipeline_use_clock(m_capturePipeline.pipeline(), systemClock.get());
120 m_capturePipeline.setState(GST_STATE_PLAYING);
123 m_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 m_capturePipeline.removeMessageFilter(
static_cast<QGstreamerBusMessageFilter *>(
this));
147 m_capturePipeline.setStateSync(GST_STATE_READY);
148 m_capturePipeline.setStateSync(GST_STATE_NULL);
156void QGstreamerMediaCaptureSession::
setCamera(QPlatformCamera *platformCamera)
159 if (m_gstCamera == camera)
163 QObject::disconnect(m_gstCameraActiveConnection);
165 setCameraActive(
false);
168 m_gstCamera = camera;
171 m_gstCameraActiveConnection =
172 QObject::connect(camera, &QPlatformCamera::activeChanged,
this,
173 &QGstreamerMediaCaptureSession::setCameraActive);
174 if (m_gstCamera->isActive())
175 setCameraActive(
true);
178 emit cameraChanged();
181void QGstreamerMediaCaptureSession::setCameraActive(
bool activate)
183 std::array padsToSync = {
184 m_videoSrcPadForEncoder,
185 m_videoSrcPadForImageCapture,
186 m_videoSrcPadForOutput,
187 m_gstVideoTee.sink(),
194 m_capturePipeline.add(m_gstVideoTee);
196 executeWhilePadsAreIdle(padsToSync, [&] {
197 m_capturePipeline.add(cameraElement);
198 if (videoOutputElement)
199 m_capturePipeline.add(videoOutputElement);
201 if (m_currentRecorderState && m_currentRecorderState->videoSink)
202 m_videoSrcPadForEncoder
.link(m_currentRecorderState->videoSink);
203 if (videoOutputElement)
204 m_videoSrcPadForOutput
.link(videoOutputSink()
);
206 m_videoSrcPadForImageCapture
.link(imageCaptureSink()
);
208 qLinkGstElements(cameraElement, m_gstVideoTee);
210 setStateOnElements({ m_gstVideoTee, cameraElement, videoOutputElement },
214 finishStateChangeOnElements({ m_gstVideoTee, cameraElement, videoOutputElement });
216 for (QGstElement addedElement : { m_gstVideoTee, cameraElement, videoOutputElement })
217 addedElement.finishStateChange();
220 executeWhilePadsAreIdle(padsToSync, [&] {
221 for (QGstPad &pad : padsToSync)
224 m_capturePipeline.stopAndRemoveElements(cameraElement, m_gstVideoTee, videoOutputElement);
227 m_capturePipeline.dumpGraph(
"camera");
232 return m_imageCapture;
235void QGstreamerMediaCaptureSession::
setImageCapture(QPlatformImageCapture *imageCapture)
237 QGstreamerImageCapture *control =
static_cast<QGstreamerImageCapture *>(imageCapture);
238 if (m_imageCapture == control)
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);
248 m_imageCapture = control;
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);
258 m_imageCapture->gstElement().finishStateChange();
260 m_capturePipeline.dumpGraph(
"imageCapture");
262 emit imageCaptureChanged();
268 if (m_mediaRecorder == control)
272 m_mediaRecorder->setCaptureSession(
nullptr);
273 m_mediaRecorder = control;
275 m_mediaRecorder->setCaptureSession(
this);
277 emit encoderChanged();
278 m_capturePipeline.dumpGraph(
"encoder");
283 return m_mediaRecorder;
287 const QMediaMetaData &metadata)
289 Q_ASSERT(!m_currentRecorderState);
291 std::array padsToSync = {
292 m_audioSrcPadForEncoder,
293 m_videoSrcPadForEncoder,
296 executeWhilePadsAreIdle(padsToSync, [&] {
297 m_capturePipeline.add(recorder.encodeBin, recorder.fileSink);
298 qLinkGstElements(recorder.encodeBin, recorder.fileSink);
300 applyMetaDataToTagSetter(metadata, recorder.encodeBin);
303 QGstCaps capsFromCamera = m_gstVideoTee.sink().currentCaps();
305 m_encoderVideoCapsFilter =
306 QGstElement::createFromFactory(
"capsfilter",
"encoderVideoCapsFilter");
307 m_encoderVideoCapsFilter.set(
"caps", capsFromCamera);
309 m_capturePipeline.add(m_encoderVideoCapsFilter);
310 m_encoderVideoCapsFilter.src().link(recorder.videoSink);
311 m_videoSrcPadForEncoder
.link(m_encoderVideoCapsFilter.sink());
315 QGstCaps capsFromInput = m_gstAudioTee.sink().currentCaps();
317 m_encoderAudioCapsFilter =
318 QGstElement::createFromFactory(
"capsfilter",
"encoderAudioCapsFilter");
320 m_encoderAudioCapsFilter.set(
"caps", capsFromInput);
322 m_capturePipeline.add(m_encoderAudioCapsFilter);
324 m_encoderAudioCapsFilter.src().link(recorder.audioSink);
325 m_audioSrcPadForEncoder
.link(m_encoderAudioCapsFilter.sink());
327 setStateOnElements({ recorder.encodeBin, recorder.fileSink, m_encoderVideoCapsFilter,
328 m_encoderAudioCapsFilter },
331 GstEvent *event = gst_event_new_reconfigure();
332 gst_element_send_event(recorder.fileSink.element(), event);
335 finishStateChangeOnElements({ recorder.encodeBin, recorder.fileSink, m_encoderVideoCapsFilter,
336 m_encoderAudioCapsFilter });
338 m_currentRecorderState = std::move(recorder);
343 std::array padsToSync = {
344 m_audioSrcPadForEncoder,
345 m_videoSrcPadForEncoder,
348 executeWhilePadsAreIdle(padsToSync, [&] {
349 if (m_encoderVideoCapsFilter)
350 qUnlinkGstElements(m_gstVideoTee, m_encoderVideoCapsFilter);
352 if (m_encoderAudioCapsFilter)
353 qUnlinkGstElements(m_gstAudioTee, m_encoderAudioCapsFilter);
356 if (m_encoderVideoCapsFilter) {
357 m_capturePipeline.stopAndRemoveElements(m_encoderVideoCapsFilter);
358 m_encoderVideoCapsFilter = {};
361 if (m_encoderAudioCapsFilter) {
362 m_capturePipeline.stopAndRemoveElements(m_encoderAudioCapsFilter);
363 m_encoderAudioCapsFilter = {};
366 m_currentRecorderState->encodeBin.sendEos();
371 m_capturePipeline.stopAndRemoveElements(m_currentRecorderState->encodeBin,
372 m_currentRecorderState->fileSink);
374 m_currentRecorderState = std::nullopt;
379 return m_capturePipeline;
382void QGstreamerMediaCaptureSession::
setAudioInput(QPlatformAudioInput *input)
384 if (m_gstAudioInput == input)
387 if (input && !m_gstAudioInput) {
390 m_capturePipeline.add(m_gstAudioTee);
392 std::array padsToSync = {
393 m_audioSrcPadForEncoder,
394 m_audioSrcPadForOutput,
395 m_gstAudioTee.sink(),
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()
);
407 m_capturePipeline.add(m_gstAudioInput->gstElement());
411 m_gstAudioTee.setState(GST_STATE_PLAYING);
412 if (m_gstAudioOutput)
417 }
else if (!input && m_gstAudioInput) {
420 std::array padsToSync = {
421 m_audioSrcPadForEncoder,
422 m_audioSrcPadForOutput,
423 m_gstAudioTee.sink(),
426 executeWhilePadsAreIdle(padsToSync, [&] {
427 for (QGstPad &pad : padsToSync)
431 m_capturePipeline.stopAndRemoveElements(m_gstAudioTee);
432 if (m_gstAudioOutput)
433 m_capturePipeline.stopAndRemoveElements(m_gstAudioOutput->gstElement());
434 m_capturePipeline.stopAndRemoveElements(m_gstAudioInput->gstElement());
436 m_gstAudioInput =
nullptr;
440 m_gstAudioTee.sink().modifyPipelineInIdleProbe([&] {
441 oldInputElement.sink().unlinkPeer();
442 m_gstAudioInput =
static_cast<QGstreamerAudioInput *>(input);
443 m_capturePipeline.add(m_gstAudioInput->gstElement());
445 qLinkGstElements(m_gstAudioInput->gstElement(), m_gstAudioTee);
447 m_gstAudioInput->gstElement().setState(GST_STATE_PLAYING);
452 m_capturePipeline.stopAndRemoveElements(m_gstAudioInput->gstElement());
456void QGstreamerMediaCaptureSession::setVideoPreview(QVideoSink *sink)
466 Q_ASSERT(pluggableSink);
472 if (m_gstAudioOutput == output)
477 gstOutput->setAsync(
false);
479 if (!m_gstAudioInput) {
484 m_gstAudioOutput ? m_gstAudioOutput
->gstElement() : QGstElement{};
487 m_audioSrcPadForOutput.modifyPipelineInIdleProbe([&] {
488 if (oldOutputElement)
491 if (m_gstAudioOutput) {
492 m_capturePipeline.add(m_gstAudioOutput->gstElement());
498 if (m_gstAudioOutput)
501 if (oldOutputElement)
502 m_capturePipeline.stopAndRemoveElements(oldOutputElement);
516 switch (msg.type()) {
517 case GST_MESSAGE_ERROR:
518 return processBusMessageError(msg);
520 case GST_MESSAGE_LATENCY:
521 return processBusMessageLatency(msg);
530bool QGstreamerMediaCaptureSession::processBusMessageError(
const QGstreamerMessage &msg)
532 QUniqueGErrorHandle error;
533 QUniqueGStringHandle message;
534 gst_message_parse_error(msg.message(), &error, &message);
536 qWarning() <<
"QGstreamerMediaCapture: received error from gstreamer" << error << message;
537 m_capturePipeline.dumpGraph(
"captureError");
542bool QGstreamerMediaCaptureSession::processBusMessageLatency(
const QGstreamerMessage &)
544 m_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)