Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qgstreamermediaencoder.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
10
11#include <QtMultimedia/private/qmediastoragelocation_p.h>
12#include <QtMultimedia/private/qplatformcamera_p.h>
13#include <QtMultimedia/qaudiodevice.h>
14
15#include <QtCore/qdebug.h>
16#include <QtCore/qeventloop.h>
17#include <QtCore/qstandardpaths.h>
18#include <QtCore/qmimetype.h>
19#include <QtCore/qloggingcategory.h>
20
21#include <gst/gsttagsetter.h>
22#include <gst/gstversion.h>
23#include <gst/video/video.h>
24#include <gst/pbutils/encoding-profile.h>
25
26static Q_LOGGING_CATEGORY(qLcMediaEncoderGst, "qt.multimedia.encoder")
27
29
31 : QPlatformMediaRecorder(parent),
32 audioPauseControl(*this),
33 videoPauseControl(*this)
34{
35 signalDurationChangedTimer.setInterval(100);
36 signalDurationChangedTimer.callOnTimeout(&signalDurationChangedTimer, [this]() {
37 durationChanged(duration());
38 });
39}
40
42{
43 if (!capturePipeline.isNull()) {
44 finalize();
45 capturePipeline.removeMessageFilter(this);
46 capturePipeline.setStateSync(GST_STATE_NULL);
47 }
48}
49
51{
52 return true;
53}
54
55void QGstreamerMediaEncoder::handleSessionError(QMediaRecorder::Error code, const QString &description)
56{
57 updateError(code, description);
58 stop();
59}
60
62{
63 constexpr bool traceStateChange = false;
64 constexpr bool traceAllEvents = false;
65
66 if constexpr (traceAllEvents)
67 qCDebug(qLcMediaEncoderGst) << "received event:" << msg;
68
69 switch (msg.type()) {
70 case GST_MESSAGE_ELEMENT: {
72 if (s.name() == "GstBinForwarded")
73 return processBusMessage(s.getMessage());
74
75 qCDebug(qLcMediaEncoderGst)
76 << "received element message from" << msg.source().name() << s.name();
77 return false;
78 }
79
80 case GST_MESSAGE_EOS: {
81 qCDebug(qLcMediaEncoderGst) << "received EOS from" << msg.source().name();
82 finalize();
83 return false;
84 }
85
86 case GST_MESSAGE_ERROR: {
87 qCDebug(qLcMediaEncoderGst)
88 << "received error:" << msg.source().name() << QCompactGstMessageAdaptor(msg);
89
92 gst_message_parse_error(msg.message(), &err, &debug);
94 if (!m_finalizing)
95 stop();
96 finalize();
97 return false;
98 }
99
100 case GST_MESSAGE_STATE_CHANGED: {
101 if constexpr (traceStateChange)
102 qCDebug(qLcMediaEncoderGst)
103 << "received state change" << QCompactGstMessageAdaptor(msg);
104
105 return false;
106 }
107
108 default:
109 return false;
110 };
111}
112
114{
115 return std::max(audioPauseControl.duration, videoPauseControl.duration);
116}
117
118
119static GstEncodingContainerProfile *createContainerProfile(const QMediaEncoderSettings &settings)
120{
121 auto *formatInfo = QGstreamerIntegration::instance()->gstFormatsInfo();
122
123 auto caps = formatInfo->formatCaps(settings.fileFormat());
124
125 GstEncodingContainerProfile *profile =
126 (GstEncodingContainerProfile *)gst_encoding_container_profile_new(
127 "container_profile", (gchar *)"custom container profile",
128 const_cast<GstCaps *>(caps.caps()),
129 nullptr); // preset
130 return profile;
131}
132
133static GstEncodingProfile *createVideoProfile(const QMediaEncoderSettings &settings)
134{
135 auto *formatInfo = QGstreamerIntegration::instance()->gstFormatsInfo();
136
137 QGstCaps caps = formatInfo->videoCaps(settings.mediaFormat());
138 if (caps.isNull())
139 return nullptr;
140
141 QSize videoResolution = settings.videoResolution();
142 if (videoResolution.isValid())
143 caps.setResolution(videoResolution);
144
145 GstEncodingVideoProfile *profile =
146 gst_encoding_video_profile_new(const_cast<GstCaps *>(caps.caps()), nullptr,
147 nullptr, // restriction
148 0); // presence
149
150 gst_encoding_video_profile_set_pass(profile, 0);
151 gst_encoding_video_profile_set_variableframerate(profile, TRUE);
152
153 return (GstEncodingProfile *)profile;
154}
155
156static GstEncodingProfile *createAudioProfile(const QMediaEncoderSettings &settings)
157{
158 auto *formatInfo = QGstreamerIntegration::instance()->gstFormatsInfo();
159
160 auto caps = formatInfo->audioCaps(settings.mediaFormat());
161 if (caps.isNull())
162 return nullptr;
163
164 GstEncodingProfile *profile =
165 (GstEncodingProfile *)gst_encoding_audio_profile_new(const_cast<GstCaps *>(caps.caps()),
166 nullptr, // preset
167 nullptr, // restriction
168 0); // presence
169
170 return profile;
171}
172
173
174static GstEncodingContainerProfile *createEncodingProfile(const QMediaEncoderSettings &settings)
175{
176 auto *containerProfile = createContainerProfile(settings);
177 if (!containerProfile) {
178 qWarning() << "QGstreamerMediaEncoder: failed to create container profile!";
179 return nullptr;
180 }
181
182 GstEncodingProfile *audioProfile = createAudioProfile(settings);
183 GstEncodingProfile *videoProfile = nullptr;
185 videoProfile = createVideoProfile(settings);
186// qDebug() << "audio profile" << (audioProfile ? gst_caps_to_string(gst_encoding_profile_get_format(audioProfile)) : "(null)");
187// qDebug() << "video profile" << (videoProfile ? gst_caps_to_string(gst_encoding_profile_get_format(videoProfile)) : "(null)");
188// qDebug() << "conta profile" << gst_caps_to_string(gst_encoding_profile_get_format((GstEncodingProfile *)containerProfile));
189
190 if (videoProfile) {
191 if (!gst_encoding_container_profile_add_profile(containerProfile, videoProfile)) {
192 qWarning() << "QGstreamerMediaEncoder: failed to add video profile!";
193 gst_encoding_profile_unref(videoProfile);
194 }
195 }
196 if (audioProfile) {
197 if (!gst_encoding_container_profile_add_profile(containerProfile, audioProfile)) {
198 qWarning() << "QGstreamerMediaEncoder: failed to add audio profile!";
199 gst_encoding_profile_unref(audioProfile);
200 }
201 }
202
203 return containerProfile;
204}
205
206void QGstreamerMediaEncoder::PauseControl::reset()
207{
208 pauseOffsetPts = 0;
209 pauseStartPts.reset();
210 duration = 0;
211 firstBufferPts.reset();
212}
213
214void QGstreamerMediaEncoder::PauseControl::installOn(QGstPad pad)
215{
216 pad.addProbe<&QGstreamerMediaEncoder::PauseControl::processBuffer>(this, GST_PAD_PROBE_TYPE_BUFFER);
217}
218
219GstPadProbeReturn QGstreamerMediaEncoder::PauseControl::processBuffer(QGstPad, GstPadProbeInfo *info)
220{
221 auto buffer = GST_PAD_PROBE_INFO_BUFFER(info);
222 if (!buffer)
223 return GST_PAD_PROBE_OK;
224
225 buffer = gst_buffer_make_writable(buffer);
226
227 if (!buffer)
228 return GST_PAD_PROBE_OK;
229
230 GST_PAD_PROBE_INFO_DATA(info) = buffer;
231
232 if (!GST_BUFFER_PTS_IS_VALID(buffer))
233 return GST_PAD_PROBE_OK;
234
235 if (!firstBufferPts)
236 firstBufferPts = GST_BUFFER_PTS(buffer);
237
238 if (encoder.state() == QMediaRecorder::PausedState) {
239 if (!pauseStartPts)
240 pauseStartPts = GST_BUFFER_PTS(buffer);
241
242 return GST_PAD_PROBE_DROP;
243 }
244
245 if (pauseStartPts) {
246 pauseOffsetPts += GST_BUFFER_PTS(buffer) - *pauseStartPts;
247 pauseStartPts.reset();
248 }
249 GST_BUFFER_PTS(buffer) -= pauseOffsetPts;
250
251 duration = (GST_BUFFER_PTS(buffer) - *firstBufferPts) / GST_MSECOND;
252
253 return GST_PAD_PROBE_OK;
254}
255
257{
258 if (!m_session ||m_finalizing || state() != QMediaRecorder::StoppedState)
259 return;
260
261 const auto hasVideo = m_session->camera() && m_session->camera()->isActive();
262 const auto hasAudio = m_session->audioInput() != nullptr;
263
264 if (!hasVideo && !hasAudio) {
265 updateError(QMediaRecorder::ResourceError, QMediaRecorder::tr("No camera or audio input"));
266 return;
267 }
268
269 const auto audioOnly = settings.videoCodec() == QMediaFormat::VideoCodec::Unspecified;
270
271 auto primaryLocation = audioOnly ? QStandardPaths::MusicLocation : QStandardPaths::MoviesLocation;
272 auto container = settings.mimeType().preferredSuffix();
273 auto location = QMediaStorageLocation::generateFileName(outputLocation().toLocalFile(), primaryLocation, container);
274
275 QUrl actualSink = QUrl::fromLocalFile(QDir::currentPath()).resolved(location);
276 qCDebug(qLcMediaEncoderGst) << "recording new video to" << actualSink;
277
278 Q_ASSERT(!actualSink.isEmpty());
279
280 gstEncoder = QGstBin::createFromFactory("encodebin", "encodebin");
281 Q_ASSERT(gstEncoder);
282 auto *encodingProfile = createEncodingProfile(settings);
283 g_object_set (gstEncoder.object(), "profile", encodingProfile, nullptr);
284 gst_encoding_profile_unref(encodingProfile);
285
286 gstFileSink = QGstElement::createFromFactory("filesink", "filesink");
287 Q_ASSERT(gstFileSink);
288 gstFileSink.set("location", QFile::encodeName(actualSink.toLocalFile()).constData());
289 gstFileSink.set("async", false);
290
291 QGstPad audioSink = {};
292 QGstPad videoSink = {};
293
294 audioPauseControl.reset();
295 videoPauseControl.reset();
296
297 if (hasAudio) {
298 audioSink = gstEncoder.getRequestPad("audio_%u");
299 if (audioSink.isNull())
300 qWarning() << "Unsupported audio codec";
301 else
302 audioPauseControl.installOn(audioSink);
303 }
304
305 if (hasVideo) {
306 videoSink = gstEncoder.getRequestPad("video_%u");
307 if (videoSink.isNull())
308 qWarning() << "Unsupported video codec";
309 else
310 videoPauseControl.installOn(videoSink);
311 }
312
313 capturePipeline.modifyPipelineWhileNotRunning([&] {
314 capturePipeline.add(gstEncoder, gstFileSink);
315 qLinkGstElements(gstEncoder, gstFileSink);
316 applyMetaDataToTagSetter(m_metaData, gstEncoder);
317
318 m_session->linkEncoder(audioSink, videoSink);
319
320 gstEncoder.syncStateWithParent();
321 gstFileSink.syncStateWithParent();
322 });
323
324 signalDurationChangedTimer.start();
325 capturePipeline.dumpGraph("recording");
326
330}
331
333{
334 if (!m_session || m_finalizing || state() != QMediaRecorder::RecordingState)
335 return;
336 signalDurationChangedTimer.stop();
338 capturePipeline.dumpGraph("before-pause");
340}
341
343{
344 capturePipeline.dumpGraph("before-resume");
345 if (!m_session || m_finalizing || state() != QMediaRecorder::PausedState)
346 return;
347 signalDurationChangedTimer.start();
349}
350
352{
353 if (!m_session || m_finalizing || state() == QMediaRecorder::StoppedState)
354 return;
356 qCDebug(qLcMediaEncoderGst) << "stop";
357 m_finalizing = true;
358 m_session->unlinkEncoder();
359 signalDurationChangedTimer.stop();
360
361 qCDebug(qLcMediaEncoderGst) << ">>>>>>>>>>>>> sending EOS";
362 gstEncoder.sendEos();
363}
364
365void QGstreamerMediaEncoder::finalize()
366{
367 if (!m_session || gstEncoder.isNull())
368 return;
369
370 qCDebug(qLcMediaEncoderGst) << "finalize";
371
372 capturePipeline.stopAndRemoveElements(gstEncoder, gstFileSink);
373 gstFileSink = {};
374 gstEncoder = {};
375 m_finalizing = false;
377}
378
380{
381 if (!m_session)
382 return;
383 m_metaData = metaData;
384}
385
387{
388 return m_metaData;
389}
390
392{
393 QGstreamerMediaCapture *captureSession = static_cast<QGstreamerMediaCapture *>(session);
394 if (m_session == captureSession)
395 return;
396
397 if (m_session) {
398 stop();
399 if (m_finalizing) {
400 QEventLoop loop;
403 loop.exec();
404 }
405
406 capturePipeline.removeMessageFilter(this);
407 capturePipeline = {};
408 }
409
410 m_session = captureSession;
411 if (!m_session)
412 return;
413
414 capturePipeline = captureSession->capturePipeline;
415 capturePipeline.set("message-forward", true);
416 capturePipeline.installMessageFilter(this);
417}
418
static QString currentPath()
Returns the absolute path of the application's current directory.
Definition qdir.cpp:2054
\inmodule QtCore
Definition qeventloop.h:16
int exec(ProcessEventsFlags flags=AllEvents)
Enters the main event loop and waits until exit() is called.
void quit()
Tells the event loop to exit normally.
static QByteArray encodeName(const QString &fileName)
Converts fileName to an 8-bit encoding that you can use in native APIs.
Definition qfile.h:158
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void add)(const Ts &...ts)
Definition qgst_p.h:691
static QGstBin createFromFactory(const char *factory, const char *name)
Definition qgst.cpp:1065
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void stopAndRemoveElements)(Ts... ts)
Definition qgst_p.h:710
GstCaps * caps() const
Definition qgst.cpp:526
void setResolution(QSize)
Definition qgst.cpp:463
QGstPad getRequestPad(const char *name) const
Definition qgst.cpp:913
bool setStateSync(GstState state, std::chrono::nanoseconds timeout=std::chrono::seconds(1))
Definition qgst.cpp:947
bool syncStateWithParent()
Definition qgst.cpp:969
void sendEos() const
Definition qgst.cpp:1003
static QGstElement createFromFactory(const char *factory, const char *name=nullptr)
Definition qgst.cpp:835
const char * name() const
Definition qgst.cpp:682
void set(const char *property, const char *str)
Definition qgst.cpp:538
GstObject * object() const
Definition qgst.cpp:677
void addProbe(T *instance, GstPadProbeType type)
Definition qgst_p.h:494
void installMessageFilter(QGstreamerSyncMessageFilter *filter)
void dumpGraph(const char *fileName)
void removeMessageFilter(QGstreamerSyncMessageFilter *filter)
void modifyPipelineWhileNotRunning(Functor &&fn)
static QGstreamerIntegration * instance()
QPlatformCamera * camera() override
QGstreamerAudioInput * audioInput()
void linkEncoder(QGstPad audioSink, QGstPad videoSink)
void setCaptureSession(QPlatformMediaCaptureSession *session)
qint64 duration() const override
void record(QMediaEncoderSettings &settings) override
QMediaMetaData metaData() const override
bool isLocationWritable(const QUrl &sink) const override
void setMetaData(const QMediaMetaData &) override
bool processBusMessage(const QGstreamerMessage &message) override
QGstStructure structure() const
QGstObject source() const
GstMessageType type() const
GstMessage * message() const
\inmodule QtMultimedia
\inmodule QtMultimedia
void recorderStateChanged(RecorderState state)
\qmlsignal QtMultimedia::MediaRecorder::recorderStateChanged(RecorderState state)
Error
\qmlproperty enumeration QtMultimedia::MediaRecorder::error
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
void actualLocationChanged(const QUrl &location)
void updateError(QMediaRecorder::Error error, const QString &errorString)
void stateChanged(QMediaRecorder::RecorderState state)
void durationChanged(qint64 position)
virtual QMediaRecorder::RecorderState state() const
virtual bool isActive() const =0
\inmodule QtCore
Definition qsize.h:25
constexpr bool isValid() const noexcept
Returns true if both the width and height is equal to or greater than 0; otherwise returns false.
Definition qsize.h:127
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
void stop()
Stops the timer.
Definition qtimer.cpp:267
Type get() const noexcept
\inmodule QtCore
Definition qurl.h:94
static QUrl fromLocalFile(const QString &localfile)
Returns a QUrl representation of localFile, interpreted as a local file.
Definition qurl.cpp:3368
#define this
Definition dialogs.cpp:9
Q_MULTIMEDIA_EXPORT QString generateFileName(const QString &requestedName, QStandardPaths::StandardLocation type, const QString &extension)
Combined button and popup list for selecting options.
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void qLinkGstElements)(const Ts &...ts)
Definition qgst_p.h:644
static GstEncodingProfile * createAudioProfile(const QMediaEncoderSettings &settings)
static GstEncodingContainerProfile * createEncodingProfile(const QMediaEncoderSettings &settings)
static GstEncodingContainerProfile * createContainerProfile(const QMediaEncoderSettings &settings)
static GstEncodingProfile * createVideoProfile(const QMediaEncoderSettings &settings)
static void applyMetaDataToTagSetter(const QMediaMetaData &metadata, GstTagSetter *element)
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
GLint location
GLenum GLuint buffer
GLdouble s
[6]
Definition qopenglext.h:235
static QString toLocalFile(const QString &url)
Definition qqmlfile.cpp:708
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
long long qint64
Definition qtypes.h:60
QSettings settings("MySoft", "Star Runner")
[0]
QHostInfo info
[0]