QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QtMultimediaReceiver.cc
Go to the documentation of this file.
2
3#include <QtGui/QImage>
4#include <QtMultimedia/QMediaCaptureSession>
5#include <QtMultimedia/QMediaFormat>
6#include <QtMultimedia/QMediaMetaData>
7#include <QtMultimedia/QMediaPlayer>
8#include <QtMultimedia/QMediaRecorder>
9#include <QtMultimedia/QVideoFrame>
10#include <QtMultimedia/QVideoSink>
11#include <QtMultimediaQuick/private/qquickvideooutput_p.h>
12#include <QtQuick/QQuickItem>
13
14#include "QGCLoggingCategory.h"
15
16QGC_LOGGING_CATEGORY(QtMultimediaReceiverLog, "Video.QtMultimediaReceiver")
17
19 : VideoReceiver(parent),
20 _mediaPlayer(new QMediaPlayer(this)),
21 _captureSession(new QMediaCaptureSession(this)),
22 _mediaRecorder(new QMediaRecorder(this))
23{
24 // qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO << this;
25
26 _captureSession->setRecorder(_mediaRecorder);
27
28 (void) connect(_mediaPlayer, &QMediaPlayer::playingChanged, this, &QtMultimediaReceiver::streamingChanged);
29 (void) connect(_mediaPlayer, &QMediaPlayer::hasVideoChanged, this, &QtMultimediaReceiver::decodingChanged);
30 (void) connect(_mediaPlayer, &QMediaPlayer::playbackStateChanged, this,
31 [this](QMediaPlayer::PlaybackState newState) {
32 if (newState == QMediaPlayer::PlaybackState::PlayingState) {
33 _frameTimer.start();
34 } else if (newState == QMediaPlayer::PlaybackState::StoppedState) {
35 _frameTimer.stop();
36 }
37 });
38 (void) connect(_mediaPlayer, &QMediaPlayer::mediaStatusChanged, this, [this](QMediaPlayer::MediaStatus status) {
39 switch (status) {
40 case QMediaPlayer::MediaStatus::LoadingMedia:
41 _streamDevice = _mediaPlayer->sourceDevice();
42 break;
43 default:
44 break;
45 }
46 });
47 (void) connect(_mediaPlayer, &QMediaPlayer::metaDataChanged, this, []() {
48 /*const QMediaMetaData metaData = _mediaPlayer->metaData();
49 const QVariant resolution = metaData.value(QMediaMetaData::Key::Resolution);
50 const QSize videoSize = resolution.toSize();*/
51 });
52 (void) connect(_mediaPlayer, &QMediaPlayer::bufferProgressChanged, this, [](float filled) {
53 qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO << "Buffer Progress:" << filled;
54 });
55 (void) connect(_mediaPlayer, &QMediaPlayer::errorOccurred, this,
56 [](QMediaPlayer::Error error, const QString& errorString) {
57 switch (error) {
58 case QMediaPlayer::Error::NetworkError:
59 break;
60 default:
61 break;
62 }
63
64 qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO << errorString;
65 });
66
67 // _mediaRecorder->setEncodingMode(QMediaRecorder::EncodingMode::AverageBitRateEncoding);
68 // _mediaRecorder->setQuality(QMediaRecorder::Quality::HighQuality);
69 // _mediaRecorder->setVideoBitRate()
70 _mediaRecorder->setVideoFrameRate(0);
71 _mediaRecorder->setVideoResolution(QSize());
72 (void) connect(
73 _mediaRecorder, &QMediaRecorder::recorderStateChanged, this, [this](QMediaRecorder::RecorderState state) {
74 if (state == QMediaRecorder::RecorderState::RecordingState) {
75 emit recordingStarted(_mediaRecorder->actualLocation().toString());
76 }
77 emit recordingChanged(_mediaRecorder->recorderState() == QMediaRecorder::RecorderState::RecordingState);
78 });
79 (void) connect(_mediaRecorder, &QMediaRecorder::errorOccurred, this,
80 [](QMediaRecorder::Error error, const QString& errorString) {
81 switch (error) {
82 case QMediaRecorder::Error::OutOfSpaceError:
83 break;
84 default:
85 break;
86 }
87
88 qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO << errorString;
89 });
90
91 _frameTimer.setSingleShot(true);
92 _frameTimer.setTimerType(Qt::PreciseTimer);
93 (void) connect(&_frameTimer, &QTimer::timeout, this, &QtMultimediaReceiver::timeout);
94}
95
97{
98 qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO << this;
99}
100
101void* QtMultimediaReceiver::createVideoSink(QQuickItem* widget, QObject* parent)
102{
103 Q_UNUSED(parent);
104
105 QVideoSink* videoSink = nullptr;
106 if (widget) {
107 QQuickVideoOutput* const videoOutput = reinterpret_cast<QQuickVideoOutput*>(widget);
108 videoSink = videoOutput->videoSink();
109 }
110
111 return videoSink;
112}
113
115{
116 /*if (!sink) {
117 return;
118 }
119
120 QVideoSink* const videoSink = reinterpret_cast<QVideoSink*>(sink);
121 videoSink->deleteLater();*/
122}
123
125{
126 Q_UNUSED(parent);
127 return new QtMultimediaReceiver(nullptr);
128}
129
130void QtMultimediaReceiver::start(uint32_t timeout)
131{
132 qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO;
133
134 if (_mediaPlayer->isPlaying()) {
135 qCDebug(QtMultimediaReceiverLog) << "Already running!";
137 return;
138 }
139
140 if (_uri.isEmpty()) {
141 qCDebug(QtMultimediaReceiverLog) << "Failed because URI is not specified";
143 return;
144 }
145 _mediaPlayer->setSource(QUrl::fromUserInput(_uri));
146
147 _frameTimer.setInterval(timeout);
148
149 // QAbstractVideoBuffer *buffer = _videoSink->videoFrame()->videoBuffer();
150
151 /*if (!_mediaPlayer->hasVideo()) {
152 emit onStartComplete(STATUS_FAIL);
153 }*/
154
155 _mediaPlayer->play();
156
157 qCDebug(QtMultimediaReceiverLog) << "Starting";
158
160}
161
163{
164 qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO;
165
166 if (!_mediaPlayer->isPlaying()) {
167 qCDebug(QtMultimediaReceiverLog) << "Already stopped!";
169 return;
170 }
171
172 if (_mediaPlayer->source().isEmpty()) {
173 qCWarning(QtMultimediaReceiverLog) << "Stop called on empty URI";
175 return;
176 }
177
178 _mediaPlayer->stop();
179
180 qCDebug(QtMultimediaReceiverLog) << "Stopped";
181
183}
184
186{
187 qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO;
188
189 if (!sink) {
190 qCCritical(QtMultimediaReceiverLog) << "VideoSink is NULL";
192 return;
193 }
194
195 if (_videoSink) {
196 qCWarning(QtMultimediaReceiverLog) << "VideoSink is already set";
197 }
198
199 if (_videoSizeUpdater) {
200 qCWarning(QtMultimediaReceiverLog) << "VideoSizeConnection is already set";
201 }
202
203 _videoSink = reinterpret_cast<QVideoSink*>(sink);
204 _videoSizeUpdater = connect(_videoSink, &QVideoSink::videoSizeChanged, this,
205 [this]() { emit videoSizeChanged(_videoSink->videoSize()); });
206 _videoFrameUpdater = connect(_videoSink, &QVideoSink::videoFrameChanged, this, [this](const QVideoFrame& frame) {
207 if (frame.isValid()) {
208 _frameTimer.start();
209 }
210 });
211 _rhi = _videoSink->rhi();
212 _videoSink->setSubtitleText("");
213
214 _mediaPlayer->setVideoSink(_videoSink);
215
216 qCDebug(QtMultimediaReceiverLog) << "Decoding";
217
219}
220
222{
223 qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO;
224
225 if (!_videoSink) {
226 qCWarning(QtMultimediaReceiverLog) << "VideoSink is NULL";
228 return;
229 }
230
231 (void) disconnect(_videoSizeUpdater);
232 _mediaPlayer->setVideoSink(nullptr);
233 _videoSink = nullptr;
234
235 qCDebug(QtMultimediaReceiverLog) << "Stopped Decoding";
236
238}
239
240void QtMultimediaReceiver::startRecording(const QString& videoFile, FILE_FORMAT format)
241{
242 qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO;
243
244 if (!_mediaRecorder->isAvailable()) {
245 qCWarning(QtMultimediaReceiverLog) << "Recording Unavailable";
247 return;
248 }
249
250 switch (format) {
251 case FILE_FORMAT_MKV:
252 _mediaRecorder->setMediaFormat(QMediaFormat::FileFormat::Matroska);
253 break;
254 case FILE_FORMAT_MOV:
255 _mediaRecorder->setMediaFormat(QMediaFormat::FileFormat::QuickTime);
256 break;
257 case FILE_FORMAT_MP4:
258 _mediaRecorder->setMediaFormat(QMediaFormat::FileFormat::MPEG4);
259 break;
260 default:
261 // QMediaFormat::AVI, WMV, Ogg, WebM
262 _mediaRecorder->setMediaFormat(QMediaFormat::FileFormat::UnspecifiedFormat);
263 break;
264 }
265
266 _mediaRecorder->setOutputLocation(QUrl::fromLocalFile(videoFile));
267 _recordingOutput = _mediaRecorder->outputLocation().toLocalFile();
268 _mediaRecorder->record();
269
270 qCDebug(QtMultimediaReceiverLog) << "Recording";
271
273}
274
276{
277 qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO;
278
279 _mediaRecorder->stop();
280
281 qCDebug(QtMultimediaReceiverLog) << "Stopped Recording";
282
284}
285
286void QtMultimediaReceiver::takeScreenshot(const QString& imageFile)
287{
288 qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO;
289
290 if (!_videoSink) {
291 qCWarning(QtMultimediaReceiverLog) << "Video Sink is NULL";
293 return;
294 }
295
296 const QVideoFrame frame = _videoSink->videoFrame();
297 if (!frame.isValid() || !frame.isReadable()) {
298 qCWarning(QtMultimediaReceiverLog) << "Screenshot Frame is Invalid";
300 return;
301 }
302
303 // toImage() maps RhiTextureHandle frames GPU->CPU internally, yielding the actual decoded
304 // frame rather than a grab of the Quick item (which was the prior broken behavior).
305 const QImage image = frame.toImage();
306 if (!image.isNull()) {
307 if (!image.save(imageFile)) {
308 qCWarning(QtMultimediaReceiverLog) << "Screenshot save failed:" << imageFile;
310 return;
311 }
312 qCDebug(QtMultimediaReceiverLog) << "Screenshot saved:" << imageFile;
314 return;
315 }
316
317 // Fallback only matters for GPU-backed frames; a manual RHI readback here needs the frame's
318 // native texture via private QtMultimedia APIs on the render thread, which is fragile -- fail
319 // loudly instead of hand-rolling it.
320 if (frame.handleType() == QVideoFrame::RhiTextureHandle) {
321 qCWarning(QtMultimediaReceiverLog) << "Screenshot: GPU frame readback unavailable (toImage returned null)";
322 } else {
323 qCWarning(QtMultimediaReceiverLog) << "Screenshot: frame.toImage() returned null";
324 }
326}
QString errorString
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static VideoReceiver * createVideoReceiver(QObject *parent)
QMetaObject::Connection _videoSizeUpdater
QMetaObject::Connection _videoFrameUpdater
void takeScreenshot(const QString &imageFile) override
void startDecoding(void *sink) override
void start(uint32_t timeout) override
static void releaseVideoSink(void *sink)
void startRecording(const QString &videoFile, VideoReceiver::FILE_FORMAT format) override
QMediaRecorder * _mediaRecorder
static void * createVideoSink(QQuickItem *widget, QObject *parent=nullptr)
void videoSizeChanged(QSize size)
void streamingChanged(bool active)
QQuickItem * widget()
QString _recordingOutput
void onStartRecordingComplete(STATUS status)
VideoSinkHandle sink() const
void onTakeScreenshotComplete(STATUS status)
void decodingChanged(bool active)
void onStartComplete(STATUS status)
void onStopDecodingComplete(STATUS status)
void onStopRecordingComplete(STATUS status)
void onStartDecodingComplete(STATUS status)
void onStopComplete(STATUS status)