1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qgstreamerrecordercontrol.h" |
41 | #include "qgstreameraudioencode.h" |
42 | #include "qgstreamervideoencode.h" |
43 | #include "qgstreamermediacontainercontrol.h" |
44 | #include <QtCore/QDebug> |
45 | #include <QtGui/qdesktopservices.h> |
46 | |
47 | QGstreamerRecorderControl::QGstreamerRecorderControl(QGstreamerCaptureSession *session) |
48 | :QMediaRecorderControl(session), |
49 | m_session(session), |
50 | m_state(QMediaRecorder::StoppedState), |
51 | m_status(QMediaRecorder::UnloadedStatus) |
52 | { |
53 | connect(asender: m_session, SIGNAL(stateChanged(QGstreamerCaptureSession::State)), SLOT(updateStatus())); |
54 | connect(asender: m_session, SIGNAL(error(int,QString)), SLOT(handleSessionError(int,QString))); |
55 | connect(asender: m_session, SIGNAL(durationChanged(qint64)), SIGNAL(durationChanged(qint64))); |
56 | connect(asender: m_session, SIGNAL(mutedChanged(bool)), SIGNAL(mutedChanged(bool))); |
57 | connect(asender: m_session, SIGNAL(volumeChanged(qreal)), SIGNAL(volumeChanged(qreal))); |
58 | m_hasPreviewState = m_session->captureMode() != QGstreamerCaptureSession::Audio; |
59 | } |
60 | |
61 | QGstreamerRecorderControl::~QGstreamerRecorderControl() |
62 | { |
63 | } |
64 | |
65 | QUrl QGstreamerRecorderControl::outputLocation() const |
66 | { |
67 | return m_session->outputLocation(); |
68 | } |
69 | |
70 | bool QGstreamerRecorderControl::setOutputLocation(const QUrl &sink) |
71 | { |
72 | m_outputLocation = sink; |
73 | m_session->setOutputLocation(sink); |
74 | return true; |
75 | } |
76 | |
77 | |
78 | QMediaRecorder::State QGstreamerRecorderControl::state() const |
79 | { |
80 | return m_state; |
81 | } |
82 | |
83 | QMediaRecorder::Status QGstreamerRecorderControl::status() const |
84 | { |
85 | static QMediaRecorder::Status statusTable[3][3] = { |
86 | //Stopped recorder state: |
87 | { QMediaRecorder::LoadedStatus, QMediaRecorder::FinalizingStatus, QMediaRecorder::FinalizingStatus }, |
88 | //Recording recorder state: |
89 | { QMediaRecorder::StartingStatus, QMediaRecorder::RecordingStatus, QMediaRecorder::PausedStatus }, |
90 | //Paused recorder state: |
91 | { QMediaRecorder::StartingStatus, QMediaRecorder::RecordingStatus, QMediaRecorder::PausedStatus } |
92 | }; |
93 | |
94 | QMediaRecorder::State sessionState = QMediaRecorder::StoppedState; |
95 | |
96 | switch ( m_session->state() ) { |
97 | case QGstreamerCaptureSession::RecordingState: |
98 | sessionState = QMediaRecorder::RecordingState; |
99 | break; |
100 | case QGstreamerCaptureSession::PausedState: |
101 | sessionState = QMediaRecorder::PausedState; |
102 | break; |
103 | case QGstreamerCaptureSession::PreviewState: |
104 | case QGstreamerCaptureSession::StoppedState: |
105 | sessionState = QMediaRecorder::StoppedState; |
106 | break; |
107 | } |
108 | |
109 | return statusTable[m_state][sessionState]; |
110 | } |
111 | |
112 | void QGstreamerRecorderControl::updateStatus() |
113 | { |
114 | QMediaRecorder::Status newStatus = status(); |
115 | if (m_status != newStatus) { |
116 | m_status = newStatus; |
117 | emit statusChanged(status: m_status); |
118 | // If stop has been called and session state became stopped. |
119 | if (m_status == QMediaRecorder::LoadedStatus) |
120 | emit stateChanged(state: m_state); |
121 | } |
122 | } |
123 | |
124 | void QGstreamerRecorderControl::handleSessionError(int code, const QString &description) |
125 | { |
126 | emit error(error: code, errorString: description); |
127 | stop(); |
128 | } |
129 | |
130 | qint64 QGstreamerRecorderControl::duration() const |
131 | { |
132 | return m_session->duration(); |
133 | } |
134 | |
135 | void QGstreamerRecorderControl::setState(QMediaRecorder::State state) |
136 | { |
137 | switch (state) { |
138 | case QMediaRecorder::StoppedState: |
139 | stop(); |
140 | break; |
141 | case QMediaRecorder::PausedState: |
142 | pause(); |
143 | break; |
144 | case QMediaRecorder::RecordingState: |
145 | record(); |
146 | break; |
147 | } |
148 | } |
149 | |
150 | void QGstreamerRecorderControl::record() |
151 | { |
152 | if (m_state == QMediaRecorder::RecordingState) |
153 | return; |
154 | |
155 | m_state = QMediaRecorder::RecordingState; |
156 | |
157 | if (m_outputLocation.isEmpty()) { |
158 | QString container = m_session->mediaContainerControl()->containerExtension(); |
159 | if (container.isEmpty()) |
160 | container = "raw" ; |
161 | |
162 | m_session->setOutputLocation(QUrl(generateFileName(dir: defaultDir(), ext: container))); |
163 | } |
164 | |
165 | m_session->dumpGraph(fileName: "before-record" ); |
166 | if (!m_hasPreviewState || m_session->state() != QGstreamerCaptureSession::StoppedState) { |
167 | m_session->setState(QGstreamerCaptureSession::RecordingState); |
168 | } else |
169 | emit error(error: QMediaRecorder::ResourceError, errorString: tr(s: "Service has not been started" )); |
170 | |
171 | m_session->dumpGraph(fileName: "after-record" ); |
172 | |
173 | emit stateChanged(state: m_state); |
174 | updateStatus(); |
175 | |
176 | emit actualLocationChanged(location: m_session->outputLocation()); |
177 | } |
178 | |
179 | void QGstreamerRecorderControl::pause() |
180 | { |
181 | if (m_state == QMediaRecorder::PausedState) |
182 | return; |
183 | |
184 | m_state = QMediaRecorder::PausedState; |
185 | |
186 | m_session->dumpGraph(fileName: "before-pause" ); |
187 | if (!m_hasPreviewState || m_session->state() != QGstreamerCaptureSession::StoppedState) { |
188 | m_session->setState(QGstreamerCaptureSession::PausedState); |
189 | } else |
190 | emit error(error: QMediaRecorder::ResourceError, errorString: tr(s: "Service has not been started" )); |
191 | |
192 | emit stateChanged(state: m_state); |
193 | updateStatus(); |
194 | } |
195 | |
196 | void QGstreamerRecorderControl::stop() |
197 | { |
198 | if (m_state == QMediaRecorder::StoppedState) |
199 | return; |
200 | |
201 | m_state = QMediaRecorder::StoppedState; |
202 | |
203 | if (!m_hasPreviewState) { |
204 | m_session->setState(QGstreamerCaptureSession::StoppedState); |
205 | } else { |
206 | if (m_session->state() != QGstreamerCaptureSession::StoppedState) |
207 | m_session->setState(QGstreamerCaptureSession::PreviewState); |
208 | } |
209 | |
210 | updateStatus(); |
211 | } |
212 | |
213 | void QGstreamerRecorderControl::applySettings() |
214 | { |
215 | //Check the codecs are compatible with container, |
216 | //and choose the compatible codecs/container if omitted |
217 | QGstreamerAudioEncode *audioEncodeControl = m_session->audioEncodeControl(); |
218 | QGstreamerVideoEncode *videoEncodeControl = m_session->videoEncodeControl(); |
219 | QGstreamerMediaContainerControl *mediaContainerControl = m_session->mediaContainerControl(); |
220 | |
221 | bool needAudio = m_session->captureMode() & QGstreamerCaptureSession::Audio; |
222 | bool needVideo = m_session->captureMode() & QGstreamerCaptureSession::Video; |
223 | |
224 | QStringList containerCandidates; |
225 | if (mediaContainerControl->containerFormat().isEmpty()) |
226 | containerCandidates = mediaContainerControl->supportedContainers(); |
227 | else |
228 | containerCandidates << mediaContainerControl->containerFormat(); |
229 | |
230 | |
231 | QStringList audioCandidates; |
232 | if (needAudio) { |
233 | QAudioEncoderSettings audioSettings = audioEncodeControl->audioSettings(); |
234 | if (audioSettings.codec().isEmpty()) |
235 | audioCandidates = audioEncodeControl->supportedAudioCodecs(); |
236 | else |
237 | audioCandidates << audioSettings.codec(); |
238 | } |
239 | |
240 | QStringList videoCandidates; |
241 | if (needVideo) { |
242 | QVideoEncoderSettings videoSettings = videoEncodeControl->videoSettings(); |
243 | if (videoSettings.codec().isEmpty()) |
244 | videoCandidates = videoEncodeControl->supportedVideoCodecs(); |
245 | else |
246 | videoCandidates << videoSettings.codec(); |
247 | } |
248 | |
249 | QString container; |
250 | QString audioCodec; |
251 | QString videoCodec; |
252 | |
253 | for (const QString &containerCandidate : qAsConst(t&: containerCandidates)) { |
254 | QSet<QString> supportedTypes = mediaContainerControl->supportedStreamTypes(container: containerCandidate); |
255 | |
256 | audioCodec.clear(); |
257 | videoCodec.clear(); |
258 | |
259 | if (needAudio) { |
260 | bool found = false; |
261 | for (const QString &audioCandidate : qAsConst(t&: audioCandidates)) { |
262 | QSet<QString> audioTypes = audioEncodeControl->supportedStreamTypes(codecName: audioCandidate); |
263 | if (audioTypes.intersects(other: supportedTypes)) { |
264 | found = true; |
265 | audioCodec = audioCandidate; |
266 | break; |
267 | } |
268 | } |
269 | if (!found) |
270 | continue; |
271 | } |
272 | |
273 | if (needVideo) { |
274 | bool found = false; |
275 | for (const QString &videoCandidate : qAsConst(t&: videoCandidates)) { |
276 | QSet<QString> videoTypes = videoEncodeControl->supportedStreamTypes(codecName: videoCandidate); |
277 | if (videoTypes.intersects(other: supportedTypes)) { |
278 | found = true; |
279 | videoCodec = videoCandidate; |
280 | break; |
281 | } |
282 | } |
283 | if (!found) |
284 | continue; |
285 | } |
286 | |
287 | container = containerCandidate; |
288 | break; |
289 | } |
290 | |
291 | if (container.isEmpty()) { |
292 | emit error(error: QMediaRecorder::FormatError, errorString: tr(s: "Not compatible codecs and container format." )); |
293 | } else { |
294 | mediaContainerControl->setContainerFormat(container); |
295 | |
296 | if (needAudio) { |
297 | QAudioEncoderSettings audioSettings = audioEncodeControl->audioSettings(); |
298 | audioSettings.setCodec(audioCodec); |
299 | audioEncodeControl->setAudioSettings(audioSettings); |
300 | } |
301 | |
302 | if (needVideo) { |
303 | QVideoEncoderSettings videoSettings = videoEncodeControl->videoSettings(); |
304 | videoSettings.setCodec(videoCodec); |
305 | videoEncodeControl->setVideoSettings(videoSettings); |
306 | } |
307 | } |
308 | } |
309 | |
310 | |
311 | bool QGstreamerRecorderControl::isMuted() const |
312 | { |
313 | return m_session->isMuted(); |
314 | } |
315 | |
316 | qreal QGstreamerRecorderControl::volume() const |
317 | { |
318 | return m_session->volume(); |
319 | } |
320 | |
321 | void QGstreamerRecorderControl::setMuted(bool muted) |
322 | { |
323 | m_session->setMuted(muted); |
324 | } |
325 | |
326 | void QGstreamerRecorderControl::setVolume(qreal volume) |
327 | { |
328 | m_session->setVolume(volume); |
329 | } |
330 | |
331 | QDir QGstreamerRecorderControl::defaultDir() const |
332 | { |
333 | QStringList dirCandidates; |
334 | |
335 | if (m_session->captureMode() & QGstreamerCaptureSession::Video) |
336 | dirCandidates << QStandardPaths::writableLocation(type: QStandardPaths::MoviesLocation); |
337 | else |
338 | dirCandidates << QStandardPaths::writableLocation(type: QStandardPaths::MusicLocation); |
339 | |
340 | dirCandidates << QDir::home().filePath(fileName: "Documents" ); |
341 | dirCandidates << QDir::home().filePath(fileName: "My Documents" ); |
342 | dirCandidates << QDir::homePath(); |
343 | dirCandidates << QDir::currentPath(); |
344 | dirCandidates << QDir::tempPath(); |
345 | |
346 | for (const QString &path : qAsConst(t&: dirCandidates)) { |
347 | QDir dir(path); |
348 | if (dir.exists() && QFileInfo(path).isWritable()) |
349 | return dir; |
350 | } |
351 | |
352 | return QDir(); |
353 | } |
354 | |
355 | QString QGstreamerRecorderControl::generateFileName(const QDir &dir, const QString &ext) const |
356 | { |
357 | |
358 | int lastClip = 0; |
359 | const auto list = dir.entryList(nameFilters: QStringList() << QString("clip_*.%1" ).arg(a: ext)); |
360 | for (const QString &fileName : list) { |
361 | int imgNumber = fileName.midRef(position: 5, n: fileName.size()-6-ext.length()).toInt(); |
362 | lastClip = qMax(a: lastClip, b: imgNumber); |
363 | } |
364 | |
365 | QString name = QString("clip_%1.%2" ).arg(a: lastClip+1, |
366 | fieldWidth: 4, //fieldWidth |
367 | base: 10, |
368 | fillChar: QLatin1Char('0')).arg(a: ext); |
369 | |
370 | return dir.absoluteFilePath(fileName: name); |
371 | } |
372 | |