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