Qt 6.x
The Qt SDK
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
7#include "qgstpipeline_p.h"
9#include <private/qplatformcamera_p.h>
10#include "qaudiodevice.h"
11#include <private/qmediastoragelocation_p.h>
12
13#include <qdebug.h>
14#include <qeventloop.h>
15#include <qstandardpaths.h>
16#include <qmimetype.h>
17#include <qloggingcategory.h>
18
19#include <gst/gsttagsetter.h>
20#include <gst/gstversion.h>
21#include <gst/video/video.h>
22#include <gst/pbutils/encoding-profile.h>
23
24static Q_LOGGING_CATEGORY(qLcMediaEncoderGst, "qt.multimedia.encoder")
25
27
30 audioPauseControl(*this),
31 videoPauseControl(*this)
32{
33 signalDurationChangedTimer.setInterval(100);
34 signalDurationChangedTimer.callOnTimeout([this](){ durationChanged(duration()); });
35}
36
38{
39 if (!gstPipeline.isNull()) {
40 finalize();
41 gstPipeline.removeMessageFilter(this);
42 gstPipeline.setStateSync(GST_STATE_NULL);
43 }
44}
45
47{
48 return true;
49}
50
51void QGstreamerMediaEncoder::handleSessionError(QMediaRecorder::Error code, const QString &description)
52{
53 error(code, description);
54 stop();
55}
56
58{
59 if (message.isNull())
60 return false;
61 auto msg = message;
62
63// qCDebug(qLcMediaEncoderGst) << "received event from" << message.source().name() << Qt::hex << message.type();
64// if (message.type() == GST_MESSAGE_STATE_CHANGED) {
65// GstState oldState;
66// GstState newState;
67// GstState pending;
68// gst_message_parse_state_changed(gm, &oldState, &newState, &pending);
69// qCDebug(qLcMediaEncoderGst) << "received state change from" << message.source().name() << oldState << newState << pending;
70// }
71 if (msg.type() == GST_MESSAGE_ELEMENT) {
73 qCDebug(qLcMediaEncoderGst) << "received element message from" << msg.source().name() << s.name();
74 if (s.name() == "GstBinForwarded")
75 msg = QGstreamerMessage(s);
76 if (msg.isNull())
77 return false;
78 }
79
80 if (msg.type() == GST_MESSAGE_EOS) {
81 qCDebug(qLcMediaEncoderGst) << "received EOS from" << msg.source().name();
82 finalize();
83 return false;
84 }
85
86 if (msg.type() == GST_MESSAGE_ERROR) {
87 GError *err;
88 gchar *debug;
89 gst_message_parse_error(msg.rawMessage(), &err, &debug);
91 g_error_free(err);
92 g_free(debug);
93 if (!m_finalizing)
94 stop();
95 finalize();
96 }
97
98 return false;
99}
100
102{
103 return std::max(audioPauseControl.duration, videoPauseControl.duration);
104}
105
106
107static GstEncodingContainerProfile *createContainerProfile(const QMediaEncoderSettings &settings)
108{
110
111 auto caps = formatInfo->formatCaps(settings.fileFormat());
112
113 GstEncodingContainerProfile *profile = (GstEncodingContainerProfile *)gst_encoding_container_profile_new(
114 "container_profile",
115 (gchar *)"custom container profile",
116 const_cast<GstCaps *>(caps.get()),
117 nullptr); //preset
118 return profile;
119}
120
121static GstEncodingProfile *createVideoProfile(const QMediaEncoderSettings &settings)
122{
124
125 auto caps = formatInfo->videoCaps(settings.mediaFormat());
126 if (caps.isNull())
127 return nullptr;
128
129 GstEncodingVideoProfile *profile = gst_encoding_video_profile_new(
130 const_cast<GstCaps *>(caps.get()),
131 nullptr,
132 nullptr, //restriction
133 0); //presence
134
135 gst_encoding_video_profile_set_pass(profile, 0);
136 gst_encoding_video_profile_set_variableframerate(profile, TRUE);
137
138 return (GstEncodingProfile *)profile;
139}
140
141static GstEncodingProfile *createAudioProfile(const QMediaEncoderSettings &settings)
142{
144
145 auto caps = formatInfo->audioCaps(settings.mediaFormat());
146 if (caps.isNull())
147 return nullptr;
148
149 GstEncodingProfile *profile = (GstEncodingProfile *)gst_encoding_audio_profile_new(
150 const_cast<GstCaps *>(caps.get()),
151 nullptr, //preset
152 nullptr, //restriction
153 0); //presence
154
155 return profile;
156}
157
158
159static GstEncodingContainerProfile *createEncodingProfile(const QMediaEncoderSettings &settings)
160{
161 auto *containerProfile = createContainerProfile(settings);
162 if (!containerProfile) {
163 qWarning() << "QGstreamerMediaEncoder: failed to create container profile!";
164 return nullptr;
165 }
166
167 GstEncodingProfile *audioProfile = createAudioProfile(settings);
168 GstEncodingProfile *videoProfile = nullptr;
170 videoProfile = createVideoProfile(settings);
171// qDebug() << "audio profile" << (audioProfile ? gst_caps_to_string(gst_encoding_profile_get_format(audioProfile)) : "(null)");
172// qDebug() << "video profile" << (videoProfile ? gst_caps_to_string(gst_encoding_profile_get_format(videoProfile)) : "(null)");
173// qDebug() << "conta profile" << gst_caps_to_string(gst_encoding_profile_get_format((GstEncodingProfile *)containerProfile));
174
175 if (videoProfile) {
176 if (!gst_encoding_container_profile_add_profile(containerProfile, videoProfile)) {
177 qWarning() << "QGstreamerMediaEncoder: failed to add video profile!";
178 gst_encoding_profile_unref(videoProfile);
179 }
180 }
181 if (audioProfile) {
182 if (!gst_encoding_container_profile_add_profile(containerProfile, audioProfile)) {
183 qWarning() << "QGstreamerMediaEncoder: failed to add audio profile!";
184 gst_encoding_profile_unref(audioProfile);
185 }
186 }
187
188 return containerProfile;
189}
190
191void QGstreamerMediaEncoder::PauseControl::reset()
192{
193 pauseOffsetPts = 0;
194 pauseStartPts.reset();
195 duration = 0;
196 firstBufferPts.reset();
197}
198
199void QGstreamerMediaEncoder::PauseControl::installOn(QGstPad pad)
200{
201 pad.addProbe<&QGstreamerMediaEncoder::PauseControl::processBuffer>(this, GST_PAD_PROBE_TYPE_BUFFER);
202}
203
204GstPadProbeReturn QGstreamerMediaEncoder::PauseControl::processBuffer(QGstPad, GstPadProbeInfo *info)
205{
206 auto buffer = GST_PAD_PROBE_INFO_BUFFER(info);
207 if (!buffer)
208 return GST_PAD_PROBE_OK;
209
210 buffer = gst_buffer_make_writable(buffer);
211
212 if (!buffer)
213 return GST_PAD_PROBE_OK;
214
215 GST_PAD_PROBE_INFO_DATA(info) = buffer;
216
217 if (!GST_BUFFER_PTS_IS_VALID(buffer))
218 return GST_PAD_PROBE_OK;
219
220 if (!firstBufferPts)
221 firstBufferPts = GST_BUFFER_PTS(buffer);
222
223 if (encoder.state() == QMediaRecorder::PausedState) {
224 if (!pauseStartPts)
225 pauseStartPts = GST_BUFFER_PTS(buffer);
226
227 return GST_PAD_PROBE_DROP;
228 }
229
230 if (pauseStartPts) {
231 pauseOffsetPts += GST_BUFFER_PTS(buffer) - *pauseStartPts;
232 pauseStartPts.reset();
233 }
234 GST_BUFFER_PTS(buffer) -= pauseOffsetPts;
235
236 duration = (GST_BUFFER_PTS(buffer) - *firstBufferPts) / GST_MSECOND;
237
238 return GST_PAD_PROBE_OK;
239}
240
242{
243 if (!m_session ||m_finalizing || state() != QMediaRecorder::StoppedState)
244 return;
245
246 const auto hasVideo = m_session->camera() && m_session->camera()->isActive();
247 const auto hasAudio = m_session->audioInput() != nullptr;
248
249 if (!hasVideo && !hasAudio) {
250 error(QMediaRecorder::ResourceError, QMediaRecorder::tr("No camera or audio input"));
251 return;
252 }
253
254 const auto audioOnly = settings.videoCodec() == QMediaFormat::VideoCodec::Unspecified;
255
256 auto primaryLocation = audioOnly ? QStandardPaths::MusicLocation : QStandardPaths::MoviesLocation;
257 auto container = settings.mimeType().preferredSuffix();
258 auto location = QMediaStorageLocation::generateFileName(outputLocation().toLocalFile(), primaryLocation, container);
259
261 qCDebug(qLcMediaEncoderGst) << "recording new video to" << actualSink;
262
263 Q_ASSERT(!actualSink.isEmpty());
264
265 gstEncoder = QGstElement("encodebin", "encodebin");
266 Q_ASSERT(gstEncoder);
267 auto *encodingProfile = createEncodingProfile(settings);
268 g_object_set (gstEncoder.object(), "profile", encodingProfile, nullptr);
269 gst_encoding_profile_unref(encodingProfile);
270
271 gstFileSink = QGstElement("filesink", "filesink");
272 Q_ASSERT(gstFileSink);
273 gstFileSink.set("location", QFile::encodeName(actualSink.toLocalFile()).constData());
274 gstFileSink.set("async", false);
275
276 QGstPad audioSink = {};
277 QGstPad videoSink = {};
278
279 audioPauseControl.reset();
280 videoPauseControl.reset();
281
282 if (hasAudio) {
283 audioSink = gstEncoder.getRequestPad("audio_%u");
284 if (audioSink.isNull())
285 qWarning() << "Unsupported audio codec";
286 else
287 audioPauseControl.installOn(audioSink);
288 }
289
290 if (hasVideo) {
291 videoSink = gstEncoder.getRequestPad("video_%u");
292 if (videoSink.isNull())
293 qWarning() << "Unsupported video codec";
294 else
295 videoPauseControl.installOn(videoSink);
296 }
297
298 gstPipeline.add(gstEncoder, gstFileSink);
299 gstEncoder.link(gstFileSink);
300 m_metaData.setMetaData(gstEncoder.bin());
301
302 m_session->linkEncoder(audioSink, videoSink);
303
304 gstEncoder.syncStateWithParent();
305 gstFileSink.syncStateWithParent();
306
307 signalDurationChangedTimer.start();
308 gstPipeline.dumpGraph("recording");
309
313}
314
316{
317 if (!m_session || m_finalizing || state() != QMediaRecorder::RecordingState)
318 return;
319 signalDurationChangedTimer.stop();
320 gstPipeline.dumpGraph("before-pause");
322}
323
325{
326 gstPipeline.dumpGraph("before-resume");
327 if (!m_session || m_finalizing || state() != QMediaRecorder::PausedState)
328 return;
329 signalDurationChangedTimer.start();
331}
332
334{
335 if (!m_session || m_finalizing || state() == QMediaRecorder::StoppedState)
336 return;
337 qCDebug(qLcMediaEncoderGst) << "stop";
338 m_finalizing = true;
339 m_session->unlinkEncoder();
340 signalDurationChangedTimer.stop();
341
342 qCDebug(qLcMediaEncoderGst) << ">>>>>>>>>>>>> sending EOS";
343 gstEncoder.sendEos();
344}
345
346void QGstreamerMediaEncoder::finalize()
347{
348 if (!m_session || gstEncoder.isNull())
349 return;
350
351 qCDebug(qLcMediaEncoderGst) << "finalize";
352
353 gstPipeline.remove(gstEncoder);
354 gstPipeline.remove(gstFileSink);
355 gstEncoder.setStateSync(GST_STATE_NULL);
356 gstFileSink.setStateSync(GST_STATE_NULL);
357 gstFileSink = {};
358 gstEncoder = {};
359 m_finalizing = false;
361}
362
364{
365 if (!m_session)
366 return;
367 m_metaData = static_cast<const QGstreamerMetaData &>(metaData);
368}
369
371{
372 return m_metaData;
373}
374
376{
377 QGstreamerMediaCapture *captureSession = static_cast<QGstreamerMediaCapture *>(session);
378 if (m_session == captureSession)
379 return;
380
381 if (m_session) {
382 stop();
383 if (m_finalizing) {
384 QEventLoop loop;
385 loop.connect(mediaRecorder(), SIGNAL(recorderStateChanged(RecorderState)), SLOT(quit()));
386 loop.exec();
387 }
388
389 gstPipeline.removeMessageFilter(this);
390 gstPipeline = {};
391 }
392
393 m_session = captureSession;
394 if (!m_session)
395 return;
396
397 gstPipeline = captureSession->gstPipeline;
398 gstPipeline.set("message-forward", true);
399 gstPipeline.installMessageFilter(this);
400}
401
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
static QString currentPath()
Returns the absolute path of the application's current directory.
Definition qdir.cpp:2051
\inmodule QtCore
Definition qeventloop.h:16
int exec(ProcessEventsFlags flags=AllEvents)
Enters the main event loop and waits until exit() is called.
static QByteArray encodeName(const QString &fileName)
Converts fileName to an 8-bit encoding that you can use in native APIs.
Definition qfile.h:158
GstBin * bin() const
Definition qgst_p.h:564
void remove(const QGstElement &element)
Definition qgst_p.h:561
void add(const QGstElement &element)
Definition qgst_p.h:549
QGstPad getRequestPad(const char *name) const
Definition qgst_p.h:446
bool setStateSync(GstState state)
Definition qgst_p.h:463
bool syncStateWithParent()
Definition qgst_p.h:475
void sendEos() const
Definition qgst_p.h:490
bool link(const QGstElement &next)
Definition qgst_p.h:429
bool isNull() const
Definition qgst_p.h:288
void set(const char *property, const char *str)
Definition qgst_p.h:290
GstObject * object() const
Definition qgst_p.h:315
void addProbe(T *instance, GstPadProbeType type)
Definition qgst_p.h:350
void installMessageFilter(QGstreamerSyncMessageFilter *filter)
void dumpGraph(const char *fileName)
void removeMessageFilter(QGstreamerSyncMessageFilter *filter)
const GstStructure * structure
Definition qgst_p.h:135
const QGstreamerFormatInfo * gstFormatsInfo() const
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
void setMetaData(GstBin *bin) const
\inmodule QtMultimedia
\inmodule QtMultimedia
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:2823
void actualLocationChanged(const QUrl &location)
void stateChanged(QMediaRecorder::RecorderState state)
QMediaRecorder::Error error() const
void durationChanged(qint64 position)
virtual QMediaRecorder::RecorderState state() const
virtual bool isActive() const =0
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:208
void stop()
Stops the timer.
Definition qtimer.cpp:226
\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:3354
QUrl resolved(const QUrl &relative) const
Returns the result of the merge of this URL with relative.
Definition qurl.cpp:2722
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
Definition qurl.cpp:1888
QString toLocalFile() const
Returns the path of this URL formatted as a local file path.
Definition qurl.cpp:3411
#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.
static GstEncodingProfile * createAudioProfile(const QMediaEncoderSettings &settings)
static GstEncodingContainerProfile * createEncodingProfile(const QMediaEncoderSettings &settings)
static GstEncodingContainerProfile * createContainerProfile(const QMediaEncoderSettings &settings)
static GstEncodingProfile * createVideoProfile(const QMediaEncoderSettings &settings)
#define qWarning
Definition qlogging.h:162
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
static QPlatformMediaFormatInfo * formatInfo()
#define SLOT(a)
Definition qobjectdefs.h:51
#define SIGNAL(a)
Definition qobjectdefs.h:52
GLint location
GLenum GLuint buffer
GLuint GLsizei const GLchar * message
GLdouble s
[6]
Definition qopenglext.h:235
static QString toLocalFile(const QString &url)
Definition qqmlfile.cpp:611
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
long long qint64
Definition qtypes.h:55
QFileInfo info(fileName)
[8]
QSettings settings("MySoft", "Star Runner")
[0]
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent