4#include <mediacapture/qgstreamermediarecorder_p.h>
5#include <qgstreamerformatinfo_p.h>
6#include <common/qgstpipeline_p.h>
7#include <common/qgstreamermessage_p.h>
8#include <common/qgst_debug_p.h>
9#include <qgstreamerintegration_p.h>
11#include <QtMultimedia/private/qmediastoragelocation_p.h>
12#include <QtMultimedia/private/qplatformcamera_p.h>
13#include <QtMultimedia/qaudiodevice.h>
15#include <QtCore/qdebug.h>
16#include <QtCore/qeventloop.h>
17#include <QtCore/qstandardpaths.h>
18#include <QtCore/qloggingcategory.h>
20#include <gst/gsttagsetter.h>
21#include <gst/gstversion.h>
22#include <gst/video/video.h>
23#include <gst/pbutils/encoding-profile.h>
29QGstreamerMediaRecorder::QGstreamerMediaRecorder(QMediaRecorder *parent)
30 : QPlatformMediaRecorder(parent), audioPauseControl(*
this), videoPauseControl(*
this)
32 signalDurationChangedTimer.setInterval(100);
33 signalDurationChangedTimer.callOnTimeout(&signalDurationChangedTimer, [
this]() {
34 durationChanged(duration());
50 const QString &description)
52 updateError(code, description);
58 constexpr bool traceStateChange =
false;
59 constexpr bool traceAllEvents =
false;
61 if constexpr (traceAllEvents)
62 qCDebug(qLcMediaRecorder) <<
"received event:" << msg;
65 case GST_MESSAGE_ELEMENT: {
67 if (s.name() ==
"GstBinForwarded")
70 qCDebug(qLcMediaRecorder) <<
"received element message from" << msg.source().name()
75 case GST_MESSAGE_EOS: {
76 qCDebug(qLcMediaRecorder) <<
"received EOS from" << msg.source().name();
81 case GST_MESSAGE_ERROR: {
82 qCDebug(qLcMediaRecorder) <<
"received error:" << msg.source().name()
83 << QCompactGstMessageAdaptor(msg);
85 QUniqueGErrorHandle err;
87 gst_message_parse_error(msg.message(), &err, &debug);
88 updateError(QMediaRecorder::ResourceError, QString::fromUtf8(err.get()->message));
95 case GST_MESSAGE_STATE_CHANGED: {
96 if constexpr (traceStateChange)
97 qCDebug(qLcMediaRecorder) <<
"received state change" << QCompactGstMessageAdaptor(msg);
109 return std::max(audioPauseControl.duration, videoPauseControl.duration);
117 auto caps = formatInfo->formatCaps(settings.fileFormat());
119 GstEncodingContainerProfile *profile =
120 (GstEncodingContainerProfile *)gst_encoding_container_profile_new(
121 "container_profile", (gchar *)
"custom container profile",
122 const_cast<GstCaps *>(caps.caps()),
131 QGstCaps caps = formatInfo->videoCaps(settings.mediaFormat());
135 QSize videoResolution = settings.videoResolution();
136 if (videoResolution.isValid())
139 GstEncodingVideoProfile *profile =
140 gst_encoding_video_profile_new(
const_cast<GstCaps *>(caps.caps()),
nullptr,
144 gst_encoding_video_profile_set_pass(profile, 0);
145 gst_encoding_video_profile_set_variableframerate(profile, TRUE);
147 return (GstEncodingProfile *)profile;
154 auto caps = formatInfo->audioCaps(settings.mediaFormat());
158 GstEncodingProfile *profile =
159 (GstEncodingProfile *)gst_encoding_audio_profile_new(
const_cast<GstCaps *>(caps.caps()),
170 auto *containerProfile = createContainerProfile(settings);
171 if (!containerProfile) {
172 qWarning() <<
"QGstreamerMediaEncoder: failed to create container profile!";
176 GstEncodingProfile *audioProfile = createAudioProfile(settings);
177 GstEncodingProfile *videoProfile =
nullptr;
178 if (settings.videoCodec() != QMediaFormat::VideoCodec::Unspecified)
179 videoProfile = createVideoProfile(settings);
185 if (!gst_encoding_container_profile_add_profile(containerProfile, videoProfile)) {
186 qWarning() <<
"QGstreamerMediaEncoder: failed to add video profile!";
187 gst_encoding_profile_unref(videoProfile);
191 if (!gst_encoding_container_profile_add_profile(containerProfile, audioProfile)) {
192 qWarning() <<
"QGstreamerMediaEncoder: failed to add audio profile!";
193 gst_encoding_profile_unref(audioProfile);
197 return containerProfile;
203 pauseStartPts.reset();
205 firstBufferPts.reset();
210 pad.addProbe<&QGstreamerMediaRecorder::PauseControl::processBuffer>(
this,
211 GST_PAD_PROBE_TYPE_BUFFER);
215 GstPadProbeInfo *info)
217 auto buffer = GST_PAD_PROBE_INFO_BUFFER(info);
219 return GST_PAD_PROBE_OK;
221 buffer = gst_buffer_make_writable(buffer);
224 return GST_PAD_PROBE_OK;
226 GST_PAD_PROBE_INFO_DATA(info) = buffer;
228 if (!GST_BUFFER_PTS_IS_VALID(buffer))
229 return GST_PAD_PROBE_OK;
232 firstBufferPts = GST_BUFFER_PTS(buffer);
234 if (encoder.state() == QMediaRecorder::PausedState) {
236 pauseStartPts = GST_BUFFER_PTS(buffer);
238 return GST_PAD_PROBE_DROP;
242 pauseOffsetPts += GST_BUFFER_PTS(buffer) - *pauseStartPts;
243 pauseStartPts.reset();
245 GST_BUFFER_PTS(buffer) -= pauseOffsetPts;
247 duration = (GST_BUFFER_PTS(buffer) - *firstBufferPts) / GST_MSECOND;
249 return GST_PAD_PROBE_OK;
254 if (!m_session ||m_finalizing || state() != QMediaRecorder::StoppedState)
257 const auto hasVideo = m_session->camera() && m_session->camera()->isActive();
260 if (!hasVideo && !hasAudio) {
261 updateError(QMediaRecorder::ResourceError, QMediaRecorder::tr(
"No camera or audio input"));
265 const auto audioOnly = settings.videoCodec() == QMediaFormat::VideoCodec::Unspecified;
267 auto primaryLocation = audioOnly ? QStandardPaths::MusicLocation : QStandardPaths::MoviesLocation;
268 auto container = settings.preferredSuffix();
269 auto location = QMediaStorageLocation::generateFileName(outputLocation().toLocalFile(), primaryLocation, container);
271 QUrl actualSink = QUrl::fromLocalFile(QDir::currentPath()).resolved(location);
272 qCDebug(qLcMediaRecorder) <<
"recording new video to" << actualSink;
274 Q_ASSERT(!actualSink.isEmpty());
277 Q_ASSERT(gstEncodebin);
278 auto *encodingProfile = createEncodingProfile(settings);
279 g_object_set(gstEncodebin.object(),
"profile", encodingProfile,
nullptr);
280 gst_encoding_profile_unref(encodingProfile);
283 Q_ASSERT(gstFileSink);
284 gstFileSink.set(
"location", QFile::encodeName(actualSink.toLocalFile()).constData());
289 audioPauseControl.reset();
290 videoPauseControl.reset();
295 qWarning() <<
"Unsupported audio codec";
297 audioPauseControl.installOn(audioSink);
303 qWarning() <<
"Unsupported video codec";
305 videoPauseControl.installOn(videoSink);
309 std::move(gstEncodebin),
310 std::move(gstFileSink),
311 std::move(audioSink),
312 std::move(videoSink),
315 m_session->linkAndStartEncoder(std::move(recorder), m_metaData);
317 signalDurationChangedTimer.start();
322 actualLocationChanged(QUrl::fromLocalFile(location));
323 stateChanged(QMediaRecorder::RecordingState);
328 if (!m_session || m_finalizing || state() != QMediaRecorder::RecordingState)
330 signalDurationChangedTimer.stop();
331 durationChanged(duration());
333 stateChanged(QMediaRecorder::PausedState);
339 if (!m_session || m_finalizing || state() != QMediaRecorder::PausedState)
341 signalDurationChangedTimer.start();
342 stateChanged(QMediaRecorder::RecordingState);
347 if (!m_session || m_finalizing || state() == QMediaRecorder::StoppedState)
349 durationChanged(duration());
350 qCDebug(qLcMediaRecorder) <<
"stop";
353 signalDurationChangedTimer.stop();
358 if (!m_session || !m_finalizing)
361 qCDebug(qLcMediaRecorder) <<
"finalize";
364 m_finalizing =
false;
365 stateChanged(QMediaRecorder::StoppedState);
372 m_metaData = metaData;
382 QGstreamerMediaCaptureSession *captureSession =
383 static_cast<QGstreamerMediaCaptureSession *>(session);
384 if (m_session == captureSession)
391 QObject::connect(mediaRecorder(), &QMediaRecorder::recorderStateChanged, &loop,
397 m_session = captureSession;
void dumpGraph(const char *fileNamePrefix, bool includeTimestamp=true) const
static QGstBin createFromFactory(const char *factory, const char *name)
void setResolution(QSize)
QGstPad getRequestPad(const char *name) const
static QGstElement createFromFactory(const char *factory, const char *name=nullptr)
QGstreamerMessage getMessage()
const QGstreamerFormatInfo * gstFormatsInfo()
static QGstreamerIntegration * instance()
QGstStructureView structure() const
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")