Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
avfmediaencoder.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4
8#include "avfcamera_p.h"
10#include <QtMultimedia/private/qavfcameradebug_p.h>
11#include <QtMultimedia/private/qavfcamerautility_p.h>
12#include "qaudiodevice.h"
13
14#include "qmediadevices.h"
15#include "private/qmediastoragelocation_p.h"
16#include "private/qmediarecorder_p.h"
18#include "private/qplatformaudiooutput_p.h"
19#include <private/qplatformaudioinput_p.h>
20
21#include <QtCore/qmath.h>
22#include <QtCore/qdebug.h>
23
24#include <private/qcoreaudioutils_p.h>
25
26QT_USE_NAMESPACE
27
28namespace {
29
30bool qt_is_writable_file_URL(NSURL *fileURL)
31{
32 Q_ASSERT(fileURL);
33
34 if (![fileURL isFileURL])
35 return false;
36
37 if (NSString *path = [[fileURL path] stringByExpandingTildeInPath]) {
38 return [[NSFileManager defaultManager]
39 isWritableFileAtPath:[path stringByDeletingLastPathComponent]];
40 }
41
42 return false;
43}
44
45bool qt_file_exists(NSURL *fileURL)
46{
47 Q_ASSERT(fileURL);
48
49 if (NSString *path = [[fileURL path] stringByExpandingTildeInPath])
50 return [[NSFileManager defaultManager] fileExistsAtPath:path];
51
52 return false;
53}
54
55} // namespace
56
57AVFMediaEncoder::AVFMediaEncoder(QMediaRecorder *parent)
58 : QObject(parent)
59 , QPlatformMediaRecorder(parent)
60 , m_state(QMediaRecorder::StoppedState)
61 , m_duration(0)
62 , m_audioSettings(nil)
63 , m_videoSettings(nil)
64 //, m_restoreFPS(-1, -1)
65{
66 m_writer.reset([[QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) alloc] initWithDelegate:this]);
67 if (!m_writer) {
68 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to create an asset writer";
69 return;
70 }
71}
72
74{
75 [m_writer abort];
76
77 if (m_audioSettings)
78 [m_audioSettings release];
79 if (m_videoSettings)
80 [m_videoSettings release];
81}
82
83bool AVFMediaEncoder::isLocationWritable(const QUrl &location) const
84{
85 return location.scheme() == QLatin1String("file") || location.scheme().isEmpty();
86}
87
89{
90 return m_state;
91}
92
94{
95 return m_duration;
96}
97
98void AVFMediaEncoder::updateDuration(qint64 duration)
99{
100 m_duration = duration;
101 durationChanged(m_duration);
102}
103
104static NSDictionary *avfAudioSettings(const QMediaEncoderSettings &encoderSettings, const QAudioFormat &format)
105{
106 NSMutableDictionary *settings = [NSMutableDictionary dictionary];
107
108 // Codec
109 int codecId = QDarwinFormatInfo::audioFormatForCodec(encoderSettings.mediaFormat().audioCodec());
110 [settings setObject:[NSNumber numberWithInt:codecId] forKey:AVFormatIDKey];
111
112 // Setting AVEncoderQualityKey is not allowed when format ID is alac or lpcm
113 if (codecId != kAudioFormatAppleLossless && codecId != kAudioFormatLinearPCM
114 && encoderSettings.encodingMode() == QMediaRecorder::ConstantQualityEncoding) {
115 // AudioQuality
116 int quality;
117 switch (encoderSettings.quality()) {
118 case QMediaRecorder::VeryLowQuality:
119 quality = AVAudioQualityMin;
120 break;
121 case QMediaRecorder::LowQuality:
122 quality = AVAudioQualityLow;
123 break;
124 case QMediaRecorder::HighQuality:
125 quality = AVAudioQualityHigh;
126 break;
127 case QMediaRecorder::VeryHighQuality:
128 quality = AVAudioQualityMax;
129 break;
130 case QMediaRecorder::NormalQuality:
131 default:
132 quality = AVAudioQualityMedium;
133 break;
134 }
135 [settings setObject:[NSNumber numberWithInt:quality] forKey:AVEncoderAudioQualityKey];
136 } else {
137 // BitRate
138 bool isBitRateSupported = false;
139 int bitRate = encoderSettings.audioBitRate();
140 if (bitRate > 0) {
141 QList<AudioValueRange> bitRates = qt_supported_bit_rates_for_format(codecId);
142 for (int i = 0; i < bitRates.count(); i++) {
143 if (bitRate >= bitRates[i].mMinimum &&
144 bitRate <= bitRates[i].mMaximum) {
145 isBitRateSupported = true;
146 break;
147 }
148 }
149 if (isBitRateSupported)
150 [settings setObject:[NSNumber numberWithInt:encoderSettings.audioBitRate()]
151 forKey:AVEncoderBitRateKey];
152 }
153 }
154
155 // SampleRate
156 int sampleRate = encoderSettings.audioSampleRate();
157 bool isSampleRateSupported = false;
158 if (sampleRate >= 8000 && sampleRate <= 192000) {
159 QList<AudioValueRange> sampleRates = qt_supported_sample_rates_for_format(codecId);
160 for (int i = 0; i < sampleRates.count(); i++) {
161 if (sampleRate >= sampleRates[i].mMinimum && sampleRate <= sampleRates[i].mMaximum) {
162 isSampleRateSupported = true;
163 break;
164 }
165 }
166 }
167 if (!isSampleRateSupported)
168 sampleRate = 44100;
169 [settings setObject:[NSNumber numberWithInt:sampleRate] forKey:AVSampleRateKey];
170
171 // Channels
172 int channelCount = encoderSettings.audioChannelCount();
173 bool isChannelCountSupported = false;
174 if (channelCount > 0) {
175 std::optional<QList<UInt32>> channelCounts = qt_supported_channel_counts_for_format(codecId);
176 // An std::nullopt result indicates that
177 // any number of channels can be encoded.
178 if (channelCounts == std::nullopt) {
179 isChannelCountSupported = true;
180 } else {
181 for (int i = 0; i < channelCounts.value().count(); i++) {
182 if ((UInt32)channelCount == channelCounts.value()[i]) {
183 isChannelCountSupported = true;
184 break;
185 }
186 }
187 }
188
189 // if channel count is provided and it's bigger than 2
190 // provide a supported channel layout
191 if (isChannelCountSupported && channelCount > 2) {
192 AudioChannelLayout channelLayout;
193 memset(&channelLayout, 0, sizeof(AudioChannelLayout));
194 auto channelLayoutTags = qt_supported_channel_layout_tags_for_format(codecId, channelCount);
195 if (channelLayoutTags.size()) {
196 channelLayout.mChannelLayoutTag = channelLayoutTags.first();
197 [settings setObject:[NSData dataWithBytes: &channelLayout length: sizeof(channelLayout)] forKey:AVChannelLayoutKey];
198 } else {
199 isChannelCountSupported = false;
200 }
201 }
202
203 if (isChannelCountSupported)
204 [settings setObject:[NSNumber numberWithInt:channelCount] forKey:AVNumberOfChannelsKey];
205 }
206
207 if (!isChannelCountSupported) {
208 // fallback to providing channel layout if channel count is not specified or supported
209 UInt32 size = 0;
210 if (format.isValid()) {
211 auto layout = QCoreAudioUtils::toAudioChannelLayout(format, &size);
212 UInt32 layoutSize = offsetof(AudioChannelLayout, mChannelDescriptions)
213 + layout->mNumberChannelDescriptions * sizeof(AudioChannelDescription);
214 [settings setObject:[NSData dataWithBytes:layout.get() length:layoutSize] forKey:AVChannelLayoutKey];
215 } else {
216 // finally default to setting channel count to 1
217 [settings setObject:[NSNumber numberWithInt:1] forKey:AVNumberOfChannelsKey];
218 }
219 }
220
221 if (codecId == kAudioFormatAppleLossless)
222 [settings setObject:[NSNumber numberWithInt:24] forKey:AVEncoderBitDepthHintKey];
223
224 if (codecId == kAudioFormatLinearPCM) {
225 [settings setObject:[NSNumber numberWithInt:16] forKey:AVLinearPCMBitDepthKey];
226 [settings setObject:[NSNumber numberWithInt:NO] forKey:AVLinearPCMIsBigEndianKey];
227 [settings setObject:[NSNumber numberWithInt:NO] forKey:AVLinearPCMIsFloatKey];
228 [settings setObject:[NSNumber numberWithInt:NO] forKey:AVLinearPCMIsNonInterleaved];
229 }
230
231 return settings;
232}
233
234static NSDictionary *avfVideoSettings(QMediaEncoderSettings &encoderSettings,
235 AVCaptureDevice *device, AVCaptureConnection *connection,
236 QSize nativeSize)
237{
238 if (!device)
239 return nil;
240
241
242 // ### re-add needFpsChange
243// AVFPSRange currentFps = qt_current_framerates(device, connection);
244
245 NSMutableDictionary *videoSettings = [NSMutableDictionary dictionary];
246
247 // -- Codec
248
249 // AVVideoCodecKey is the only mandatory key
250 auto codec = encoderSettings.mediaFormat().videoCodec();
251 NSString *c = QDarwinFormatInfo::videoFormatForCodec(codec);
252 [videoSettings setObject:c forKey:AVVideoCodecKey];
253 [c release];
254
255 // -- Resolution
256
257 int w = encoderSettings.videoResolution().width();
258 int h = encoderSettings.videoResolution().height();
259
260 if (AVCaptureDeviceFormat *currentFormat = device.activeFormat) {
261 CMFormatDescriptionRef formatDesc = currentFormat.formatDescription;
262 CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDesc);
263 FourCharCode formatCodec = CMVideoFormatDescriptionGetCodecType(formatDesc);
264
265 // We have to change the device's activeFormat in 3 cases:
266 // - the requested recording resolution is higher than the current device resolution
267 // - the requested recording resolution has a different aspect ratio than the current device aspect ratio
268 // - the requested frame rate is not available for the current device format
269 AVCaptureDeviceFormat *newFormat = nil;
270 if ((w <= 0 || h <= 0)
271 && encoderSettings.videoFrameRate() > 0
272 && !qt_format_supports_framerate(currentFormat, encoderSettings.videoFrameRate())) {
273
274 newFormat = qt_find_best_framerate_match(device,
275 formatCodec,
276 encoderSettings.videoFrameRate());
277
278 } else if (w > 0 && h > 0) {
279 AVCaptureDeviceFormat *f = qt_find_best_resolution_match(device,
280 encoderSettings.videoResolution(),
281 formatCodec);
282
283 if (f) {
284 CMVideoDimensions d = CMVideoFormatDescriptionGetDimensions(f.formatDescription);
285 qreal fAspectRatio = qreal(d.width) / d.height;
286
287 if (w > dim.width || h > dim.height
288 || qAbs((qreal(dim.width) / dim.height) - fAspectRatio) > 0.01) {
289 newFormat = f;
290 }
291 }
292 }
293
294 if (qt_set_active_format(device, newFormat, false /*### !needFpsChange*/)) {
295 formatDesc = newFormat.formatDescription;
296 dim = CMVideoFormatDescriptionGetDimensions(formatDesc);
297 }
298
299 if (w < 0 || h < 0) {
300 w = dim.width;
301 h = dim.height;
302 }
303
304
305 if (w > 0 && h > 0) {
306 // Make sure the recording resolution has the same aspect ratio as the device's
307 // current resolution
308 qreal deviceAspectRatio = qreal(dim.width) / dim.height;
309 qreal recAspectRatio = qreal(w) / h;
310 if (qAbs(deviceAspectRatio - recAspectRatio) > 0.01) {
311 if (recAspectRatio > deviceAspectRatio)
312 w = qRound(h * deviceAspectRatio);
313 else
314 h = qRound(w / deviceAspectRatio);
315 }
316
317 // recording resolution can't be higher than the device's active resolution
318 w = qMin(w, dim.width);
319 h = qMin(h, dim.height);
320 }
321 }
322
323 if (w > 0 && h > 0) {
324 // Width and height must be divisible by 2
325 w += w & 1;
326 h += h & 1;
327
328 bool isPortrait = nativeSize.width() < nativeSize.height();
329 // Make sure the video has the right aspect ratio
330 if (isPortrait && h < w)
331 qSwap(w, h);
332 else if (!isPortrait && w < h)
333 qSwap(w, h);
334
335 encoderSettings.setVideoResolution(QSize(w, h));
336 } else {
337 w = nativeSize.width();
338 h = nativeSize.height();
339 encoderSettings.setVideoResolution(nativeSize);
340 }
341 [videoSettings setObject:[NSNumber numberWithInt:w] forKey:AVVideoWidthKey];
342 [videoSettings setObject:[NSNumber numberWithInt:h] forKey:AVVideoHeightKey];
343
344 // -- FPS
345
346 if (true /*needFpsChange*/) {
347 const qreal fps = encoderSettings.videoFrameRate();
348 qt_set_framerate_limits(device, connection, fps, fps);
349 }
350 encoderSettings.setVideoFrameRate(qt_current_framerates(device, connection).second);
351
352 // -- Codec Settings
353
354 NSMutableDictionary *codecProperties = [NSMutableDictionary dictionary];
355 int bitrate = -1;
356 float quality = -1.f;
357
358 if (encoderSettings.encodingMode() == QMediaRecorder::ConstantQualityEncoding) {
359 if (encoderSettings.quality() != QMediaRecorder::NormalQuality) {
360 if (codec != QMediaFormat::VideoCodec::MotionJPEG) {
361 qWarning("ConstantQualityEncoding is not supported for MotionJPEG");
362 } else {
363 switch (encoderSettings.quality()) {
364 case QMediaRecorder::VeryLowQuality:
365 quality = 0.f;
366 break;
367 case QMediaRecorder::LowQuality:
368 quality = 0.25f;
369 break;
370 case QMediaRecorder::HighQuality:
371 quality = 0.75f;
372 break;
373 case QMediaRecorder::VeryHighQuality:
374 quality = 1.f;
375 break;
376 default:
377 quality = -1.f; // NormalQuality, let the system decide
378 break;
379 }
380 }
381 }
382 } else if (encoderSettings.encodingMode() == QMediaRecorder::AverageBitRateEncoding){
383 if (codec != QMediaFormat::VideoCodec::H264 && codec != QMediaFormat::VideoCodec::H265)
384 qWarning() << "AverageBitRateEncoding is not supported for codec" << QMediaFormat::videoCodecName(codec);
385 else
386 bitrate = encoderSettings.videoBitRate();
387 } else {
388 qWarning("Encoding mode is not supported");
389 }
390
391 if (bitrate != -1)
392 [codecProperties setObject:[NSNumber numberWithInt:bitrate] forKey:AVVideoAverageBitRateKey];
393 if (quality != -1.f)
394 [codecProperties setObject:[NSNumber numberWithFloat:quality] forKey:AVVideoQualityKey];
395
396 [videoSettings setObject:codecProperties forKey:AVVideoCompressionPropertiesKey];
397
398 return videoSettings;
399}
400
401void AVFMediaEncoder::applySettings(QMediaEncoderSettings &settings)
402{
403 unapplySettings();
404
405 AVFCameraSession *session = m_service->session();
406
407 // audio settings
408 const auto audioInput = m_service->audioInput();
409 const QAudioFormat audioFormat = audioInput ? audioInput->device.preferredFormat() : QAudioFormat();
410 m_audioSettings = avfAudioSettings(settings, audioFormat);
411 if (m_audioSettings)
412 [m_audioSettings retain];
413
414 // video settings
415 AVCaptureDevice *device = session->videoCaptureDevice();
416 if (!device)
417 return;
418 const AVFConfigurationLock lock(device); // prevents activeFormat from being overridden
419 AVCaptureConnection *conn = [session->videoOutput()->videoDataOutput() connectionWithMediaType:AVMediaTypeVideo];
420 auto nativeSize = session->videoOutput()->nativeSize();
421 m_videoSettings = avfVideoSettings(settings, device, conn, nativeSize);
422 if (m_videoSettings)
423 [m_videoSettings retain];
424}
425
426void AVFMediaEncoder::unapplySettings()
427{
428 if (m_audioSettings) {
429 [m_audioSettings release];
430 m_audioSettings = nil;
431 }
432 if (m_videoSettings) {
433 [m_videoSettings release];
434 m_videoSettings = nil;
435 }
436}
437
438void AVFMediaEncoder::setMetaData(const QMediaMetaData &metaData)
439{
440 m_metaData = metaData;
441}
442
444{
445 return m_metaData;
446}
447
448void AVFMediaEncoder::setCaptureSession(QPlatformMediaCaptureSession *session)
449{
450 AVFCameraService *captureSession = static_cast<AVFCameraService *>(session);
451 if (m_service == captureSession)
452 return;
453
454 if (m_service)
455 stop();
456
457 m_service = captureSession;
458 if (!m_service)
459 return;
460
461 connect(m_service, &AVFCameraService::cameraChanged, this, &AVFMediaEncoder::onCameraChanged);
462 onCameraChanged();
463}
464
465void AVFMediaEncoder::record(QMediaEncoderSettings &settings)
466{
467 if (!m_service || !m_service->session()) {
468 qWarning() << Q_FUNC_INFO << "Encoder is not set to a capture session";
469 return;
470 }
471
472 if (!m_writer) {
473 qCDebug(qLcCamera) << Q_FUNC_INFO << "Invalid recorder";
474 return;
475 }
476
477 if (QMediaRecorder::RecordingState == m_state)
478 return;
479
480 AVFCamera *cameraControl = m_service->avfCameraControl();
481 auto audioInput = m_service->audioInput();
482
483 if (!cameraControl && !audioInput) {
484 qWarning() << Q_FUNC_INFO << "Cannot record without any inputs";
485 updateError(QMediaRecorder::ResourceError, tr("No inputs specified"));
486 return;
487 }
488
489 // This is necessary to explicitly recreate m_audioInput inside AVFCameraSession.
490 // Which in turn is necessary for the case when the microphone was disconnected
491 // after stopping recording and reconnected.
493
494 m_service->session()->setActive(true);
495 const bool audioOnly = settings.videoCodec() == QMediaFormat::VideoCodec::Unspecified;
496 AVCaptureSession *session = m_service->session()->captureSession();
497 float rotation = 0;
498
499 if (!audioOnly) {
500 if (!cameraControl || !cameraControl->isActive()) {
501 qCDebug(qLcCamera) << Q_FUNC_INFO << "can not start record while camera is not active";
502 updateError(QMediaRecorder::ResourceError,
503 QMediaRecorderPrivate::msgFailedStartRecording());
504 return;
505 }
506 }
507
508 const QString path(outputLocation().scheme() == QLatin1String("file") ?
509 outputLocation().path() : outputLocation().toString());
510 const QUrl fileURL(QUrl::fromLocalFile(QMediaStorageLocation::generateFileName(
511 path, audioOnly ? QStandardPaths::MusicLocation : QStandardPaths::MoviesLocation,
512 settings.preferredSuffix())));
513
514 NSURL *nsFileURL = fileURL.toNSURL();
515 if (!nsFileURL) {
516 qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL;
517 updateError(QMediaRecorder::ResourceError, tr("Invalid output file URL"));
518 return;
519 }
520 if (!qt_is_writable_file_URL(nsFileURL)) {
521 qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL
522 << "(the location is not writable)";
523 updateError(QMediaRecorder::ResourceError, tr("Non-writeable file location"));
524 return;
525 }
526 if (qt_file_exists(nsFileURL)) {
527 // We test for/handle this error here since AWAssetWriter will raise an
528 // Objective-C exception, which is not good at all.
529 qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL
530 << "(file already exists)";
531 updateError(QMediaRecorder::ResourceError, tr("File already exists"));
532 return;
533 }
534
535 applySettings(settings);
536
537 QVideoOutputOrientationHandler::setIsRecording(true);
538
539 // We stop session now so that no more frames for renderer's queue
540 // generated, will restart in assetWriterStarted.
541 [session stopRunning];
542
543 if ([m_writer setupWithFileURL:nsFileURL
544 cameraService:m_service
545 audioSettings:m_audioSettings
546 videoSettings:m_videoSettings
547 fileFormat:settings.fileFormat()
548 transform:CGAffineTransformMakeRotation(qDegreesToRadians(rotation))]) {
549
550 m_state = QMediaRecorder::RecordingState;
551
552 actualLocationChanged(fileURL);
553 stateChanged(m_state);
554
555 // Apple recommends to call startRunning and do all
556 // setup on a special queue, and that's what we had
557 // initially (dispatch_async to writerQueue). Unfortunately,
558 // writer's queue is not the only queue/thread that can
559 // access/modify the session, and as a result we have
560 // all possible data/race-conditions with Obj-C exceptions
561 // at best and something worse in general.
562 // Now we try to only modify session on the same thread.
563 [m_writer start];
564 } else {
565 [session startRunning];
566 updateError(QMediaRecorder::FormatError, QMediaRecorderPrivate::msgFailedStartRecording());
567 }
568}
569
571{
572 if (!m_service || !m_service->session() || state() != QMediaRecorder::RecordingState)
573 return;
574
575 toggleRecord(false);
576 m_state = QMediaRecorder::PausedState;
577 stateChanged(m_state);
578}
579
581{
582 if (!m_service || !m_service->session() || state() != QMediaRecorder::PausedState)
583 return;
584
585 toggleRecord(true);
586 m_state = QMediaRecorder::RecordingState;
587 stateChanged(m_state);
588}
589
591{
592 if (m_state != QMediaRecorder::StoppedState) {
593 // Do not check the camera status, we can stop if we started.
594 stopWriter();
595 }
596 QVideoOutputOrientationHandler::setIsRecording(false);
597}
598
599
601{
602 if (!m_service || !m_service->session())
603 return;
604
605 if (!enable)
606 [m_writer pause];
607 else
608 [m_writer resume];
609}
610
611void AVFMediaEncoder::assetWriterStarted()
612{
613}
614
615void AVFMediaEncoder::assetWriterFinished()
616{
617
618 const QMediaRecorder::RecorderState lastState = m_state;
619
620 unapplySettings();
621
622 if (m_service) {
623 AVFCameraSession *session = m_service->session();
624
625 if (session->videoOutput()) {
627 }
628 if (session->audioPreviewDelegate()) {
629 [session->audioPreviewDelegate() resetAudioPreviewDelegate];
630 }
631 if (session->videoOutput() || session->audioPreviewDelegate())
632 [session->captureSession() startRunning];
633 }
634
635 m_state = QMediaRecorder::StoppedState;
636 if (m_state != lastState)
637 stateChanged(m_state);
638}
639
640void AVFMediaEncoder::assetWriterError(QString err)
641{
642 updateError(QMediaRecorder::FormatError, err);
643 if (m_state != QMediaRecorder::StoppedState)
644 stopWriter();
645}
646
647void AVFMediaEncoder::onCameraChanged()
648{
649 if (m_service && m_service->avfCameraControl()) {
650 AVFCamera *cameraControl = m_service->avfCameraControl();
651 connect(cameraControl, SIGNAL(activeChanged(bool)),
652 SLOT(cameraActiveChanged(bool)));
653 }
654}
655
656void AVFMediaEncoder::cameraActiveChanged(bool active)
657{
658 Q_ASSERT(m_service);
659 AVFCamera *cameraControl = m_service->avfCameraControl();
660 Q_ASSERT(cameraControl);
661
662 if (!active) {
663 return stopWriter();
664 }
665}
666
667void AVFMediaEncoder::stopWriter()
668{
669 [m_writer stop];
670}
671
672#include "moc_avfmediaencoder_p.cpp"
static NSDictionary * avfAudioSettings(const QMediaEncoderSettings &encoderSettings, const QAudioFormat &format)
static NSDictionary * avfVideoSettings(QMediaEncoderSettings &encoderSettings, AVCaptureDevice *device, AVCaptureConnection *connection, QSize nativeSize)
void resetCaptureDelegate() const
AVFCamera * avfCameraControl() const
AVFCameraSession * session() const
AVFCameraRenderer * videoOutput() const
QMediaMetaData metaData() const override
void setMetaData(const QMediaMetaData &) override
void updateDuration(qint64 duration)
qint64 duration() const override
void stop() override
void resume() override
void toggleRecord(bool enable)
bool isLocationWritable(const QUrl &location) const override
void record(QMediaEncoderSettings &settings) override
~AVFMediaEncoder() override
QMediaRecorder::RecorderState state() const override
void pause() override
void setCaptureSession(QPlatformMediaCaptureSession *session)
bool qt_is_writable_file_URL(NSURL *fileURL)
bool qt_file_exists(NSURL *fileURL)