106 NSMutableDictionary *settings = [NSMutableDictionary dictionary];
109 int codecId = QDarwinFormatInfo::audioFormatForCodec(encoderSettings.mediaFormat().audioCodec());
110 [settings setObject:[NSNumber numberWithInt:codecId] forKey:AVFormatIDKey];
113 if (codecId != kAudioFormatAppleLossless && codecId != kAudioFormatLinearPCM
114 && encoderSettings.encodingMode() == QMediaRecorder::ConstantQualityEncoding) {
117 switch (encoderSettings.quality()) {
118 case QMediaRecorder::VeryLowQuality:
119 quality = AVAudioQualityMin;
121 case QMediaRecorder::LowQuality:
122 quality = AVAudioQualityLow;
124 case QMediaRecorder::HighQuality:
125 quality = AVAudioQualityHigh;
127 case QMediaRecorder::VeryHighQuality:
128 quality = AVAudioQualityMax;
130 case QMediaRecorder::NormalQuality:
132 quality = AVAudioQualityMedium;
135 [settings setObject:[NSNumber numberWithInt:quality] forKey:AVEncoderAudioQualityKey];
138 bool isBitRateSupported =
false;
139 int bitRate = encoderSettings.audioBitRate();
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;
149 if (isBitRateSupported)
150 [settings setObject:[NSNumber numberWithInt:encoderSettings.audioBitRate()]
151 forKey:AVEncoderBitRateKey];
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;
167 if (!isSampleRateSupported)
169 [settings setObject:[NSNumber numberWithInt:sampleRate] forKey:AVSampleRateKey];
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);
178 if (channelCounts == std::nullopt) {
179 isChannelCountSupported =
true;
181 for (
int i = 0; i < channelCounts.value().count(); i++) {
182 if ((UInt32)channelCount == channelCounts.value()[i]) {
183 isChannelCountSupported =
true;
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];
199 isChannelCountSupported =
false;
203 if (isChannelCountSupported)
204 [settings setObject:[NSNumber numberWithInt:channelCount] forKey:AVNumberOfChannelsKey];
207 if (!isChannelCountSupported) {
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];
217 [settings setObject:[NSNumber numberWithInt:1] forKey:AVNumberOfChannelsKey];
221 if (codecId == kAudioFormatAppleLossless)
222 [settings setObject:[NSNumber numberWithInt:24] forKey:AVEncoderBitDepthHintKey];
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];
235 AVCaptureDevice *device, AVCaptureConnection *connection,
245 NSMutableDictionary *videoSettings = [NSMutableDictionary dictionary];
250 auto codec = encoderSettings.mediaFormat().videoCodec();
251 NSString *c = QDarwinFormatInfo::videoFormatForCodec(codec);
252 [videoSettings setObject:c forKey:AVVideoCodecKey];
257 int w = encoderSettings.videoResolution().width();
258 int h = encoderSettings.videoResolution().height();
260 if (AVCaptureDeviceFormat *currentFormat = device.activeFormat) {
261 CMFormatDescriptionRef formatDesc = currentFormat.formatDescription;
262 CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDesc);
263 FourCharCode formatCodec = CMVideoFormatDescriptionGetCodecType(formatDesc);
269 AVCaptureDeviceFormat *newFormat = nil;
270 if ((w <= 0 || h <= 0)
271 && encoderSettings.videoFrameRate() > 0
272 && !qt_format_supports_framerate(currentFormat, encoderSettings.videoFrameRate())) {
274 newFormat = qt_find_best_framerate_match(device,
276 encoderSettings.videoFrameRate());
278 }
else if (w > 0 && h > 0) {
279 AVCaptureDeviceFormat *f = qt_find_best_resolution_match(device,
280 encoderSettings.videoResolution(),
284 CMVideoDimensions d = CMVideoFormatDescriptionGetDimensions(f.formatDescription);
285 qreal fAspectRatio = qreal(d.width) / d.height;
287 if (w > dim.width || h > dim.height
288 || qAbs((qreal(dim.width) / dim.height) - fAspectRatio) > 0.01) {
294 if (qt_set_active_format(device, newFormat,
false )) {
295 formatDesc = newFormat.formatDescription;
296 dim = CMVideoFormatDescriptionGetDimensions(formatDesc);
299 if (w < 0 || h < 0) {
305 if (w > 0 && h > 0) {
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);
314 h = qRound(w / deviceAspectRatio);
318 w = qMin(w, dim.width);
319 h = qMin(h, dim.height);
323 if (w > 0 && h > 0) {
328 bool isPortrait = nativeSize.width() < nativeSize.height();
330 if (isPortrait && h < w)
332 else if (!isPortrait && w < h)
335 encoderSettings.setVideoResolution(QSize(w, h));
337 w = nativeSize.width();
338 h = nativeSize.height();
339 encoderSettings.setVideoResolution(nativeSize);
341 [videoSettings setObject:[NSNumber numberWithInt:w] forKey:AVVideoWidthKey];
342 [videoSettings setObject:[NSNumber numberWithInt:h] forKey:AVVideoHeightKey];
347 const qreal fps = encoderSettings.videoFrameRate();
348 qt_set_framerate_limits(device, connection, fps, fps);
350 encoderSettings.setVideoFrameRate(qt_current_framerates(device, connection).second);
354 NSMutableDictionary *codecProperties = [NSMutableDictionary dictionary];
356 float quality = -1.f;
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");
363 switch (encoderSettings.quality()) {
364 case QMediaRecorder::VeryLowQuality:
367 case QMediaRecorder::LowQuality:
370 case QMediaRecorder::HighQuality:
373 case QMediaRecorder::VeryHighQuality:
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);
386 bitrate = encoderSettings.videoBitRate();
388 qWarning(
"Encoding mode is not supported");
392 [codecProperties setObject:[NSNumber numberWithInt:bitrate] forKey:AVVideoAverageBitRateKey];
394 [codecProperties setObject:[NSNumber numberWithFloat:quality] forKey:AVVideoQualityKey];
396 [videoSettings setObject:codecProperties forKey:AVVideoCompressionPropertiesKey];
398 return videoSettings;
468 qWarning() << Q_FUNC_INFO <<
"Encoder is not set to a capture session";
473 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"Invalid recorder";
477 if (QMediaRecorder::RecordingState == m_state)
481 auto audioInput = m_service->audioInput();
483 if (!cameraControl && !audioInput) {
484 qWarning() << Q_FUNC_INFO <<
"Cannot record without any inputs";
485 updateError(QMediaRecorder::ResourceError, tr(
"No inputs specified"));
495 const bool audioOnly = settings.videoCodec() == QMediaFormat::VideoCodec::Unspecified;
496 AVCaptureSession *session = m_service
->session()->captureSession();
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());
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())));
514 NSURL *nsFileURL = fileURL.toNSURL();
516 qWarning() << Q_FUNC_INFO <<
"invalid output URL:" << fileURL;
517 updateError(QMediaRecorder::ResourceError, tr(
"Invalid output file URL"));
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"));
526 if (qt_file_exists(nsFileURL)) {
529 qWarning() << Q_FUNC_INFO <<
"invalid output URL:" << fileURL
530 <<
"(file already exists)";
531 updateError(QMediaRecorder::ResourceError, tr(
"File already exists"));
535 applySettings(settings);
537 QVideoOutputOrientationHandler::setIsRecording(
true);
541 [session stopRunning];
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))]) {
550 m_state = QMediaRecorder::RecordingState;
552 actualLocationChanged(fileURL);
553 stateChanged(m_state);
565 [session startRunning];
566 updateError(QMediaRecorder::FormatError, QMediaRecorderPrivate::msgFailedStartRecording());