105 NSMutableDictionary *settings = [NSMutableDictionary dictionary];
108 int codecId = QDarwinFormatInfo::audioFormatForCodec(encoderSettings.mediaFormat().audioCodec());
109 [settings setObject:[NSNumber numberWithInt:codecId] forKey:AVFormatIDKey];
112 if (codecId != kAudioFormatAppleLossless && codecId != kAudioFormatLinearPCM
113 && encoderSettings.encodingMode() == QMediaRecorder::ConstantQualityEncoding) {
116 switch (encoderSettings.quality()) {
117 case QMediaRecorder::VeryLowQuality:
118 quality = AVAudioQualityMin;
120 case QMediaRecorder::LowQuality:
121 quality = AVAudioQualityLow;
123 case QMediaRecorder::HighQuality:
124 quality = AVAudioQualityHigh;
126 case QMediaRecorder::VeryHighQuality:
127 quality = AVAudioQualityMax;
129 case QMediaRecorder::NormalQuality:
131 quality = AVAudioQualityMedium;
134 [settings setObject:[NSNumber numberWithInt:quality] forKey:AVEncoderAudioQualityKey];
137 bool isBitRateSupported =
false;
138 int bitRate = encoderSettings.audioBitRate();
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;
148 if (isBitRateSupported)
149 [settings setObject:[NSNumber numberWithInt:encoderSettings.audioBitRate()]
150 forKey:AVEncoderBitRateKey];
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;
166 if (!isSampleRateSupported)
168 [settings setObject:[NSNumber numberWithInt:sampleRate] forKey:AVSampleRateKey];
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);
177 if (channelCounts == std::nullopt) {
178 isChannelCountSupported =
true;
180 for (
int i = 0; i < channelCounts.value().count(); i++) {
181 if ((UInt32)channelCount == channelCounts.value()[i]) {
182 isChannelCountSupported =
true;
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];
198 isChannelCountSupported =
false;
202 if (isChannelCountSupported)
203 [settings setObject:[NSNumber numberWithInt:channelCount] forKey:AVNumberOfChannelsKey];
206 if (!isChannelCountSupported) {
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];
216 [settings setObject:[NSNumber numberWithInt:1] forKey:AVNumberOfChannelsKey];
220 if (codecId == kAudioFormatAppleLossless)
221 [settings setObject:[NSNumber numberWithInt:24] forKey:AVEncoderBitDepthHintKey];
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];
234 AVCaptureDevice *device, AVCaptureConnection *connection,
244 NSMutableDictionary *videoSettings = [NSMutableDictionary dictionary];
249 auto codec = encoderSettings.mediaFormat().videoCodec();
250 NSString *c = QDarwinFormatInfo::videoFormatForCodec(codec);
251 [videoSettings setObject:c forKey:AVVideoCodecKey];
256 int w = encoderSettings.videoResolution().width();
257 int h = encoderSettings.videoResolution().height();
259 if (AVCaptureDeviceFormat *currentFormat = device.activeFormat) {
260 CMFormatDescriptionRef formatDesc = currentFormat.formatDescription;
261 CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDesc);
262 FourCharCode formatCodec = CMVideoFormatDescriptionGetCodecType(formatDesc);
268 AVCaptureDeviceFormat *newFormat = nil;
269 if ((w <= 0 || h <= 0)
270 && encoderSettings.videoFrameRate() > 0
271 && !qt_format_supports_framerate(currentFormat, encoderSettings.videoFrameRate())) {
273 newFormat = qt_find_best_framerate_match(device,
275 encoderSettings.videoFrameRate());
277 }
else if (w > 0 && h > 0) {
278 AVCaptureDeviceFormat *f = qt_find_best_resolution_match(device,
279 encoderSettings.videoResolution(),
283 CMVideoDimensions d = CMVideoFormatDescriptionGetDimensions(f.formatDescription);
284 qreal fAspectRatio = qreal(d.width) / d.height;
286 if (w > dim.width || h > dim.height
287 || qAbs((qreal(dim.width) / dim.height) - fAspectRatio) > 0.01) {
293 if (qt_set_active_format(device, newFormat,
false )) {
294 formatDesc = newFormat.formatDescription;
295 dim = CMVideoFormatDescriptionGetDimensions(formatDesc);
298 if (w < 0 || h < 0) {
304 if (w > 0 && h > 0) {
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);
313 h = qRound(w / deviceAspectRatio);
317 w = qMin(w, dim.width);
318 h = qMin(h, dim.height);
322 if (w > 0 && h > 0) {
327 bool isPortrait = nativeSize.width() < nativeSize.height();
329 if (isPortrait && h < w)
331 else if (!isPortrait && w < h)
334 encoderSettings.setVideoResolution(QSize(w, h));
336 w = nativeSize.width();
337 h = nativeSize.height();
338 encoderSettings.setVideoResolution(nativeSize);
340 [videoSettings setObject:[NSNumber numberWithInt:w] forKey:AVVideoWidthKey];
341 [videoSettings setObject:[NSNumber numberWithInt:h] forKey:AVVideoHeightKey];
346 const qreal fps = encoderSettings.videoFrameRate();
347 qt_set_framerate_limits(device, connection, fps, fps);
349 encoderSettings.setVideoFrameRate(qt_current_framerates(device, connection).second);
353 NSMutableDictionary *codecProperties = [NSMutableDictionary dictionary];
355 float quality = -1.f;
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");
362 switch (encoderSettings.quality()) {
363 case QMediaRecorder::VeryLowQuality:
366 case QMediaRecorder::LowQuality:
369 case QMediaRecorder::HighQuality:
372 case QMediaRecorder::VeryHighQuality:
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);
385 bitrate = encoderSettings.videoBitRate();
387 qWarning(
"Encoding mode is not supported");
391 [codecProperties setObject:[NSNumber numberWithInt:bitrate] forKey:AVVideoAverageBitRateKey];
393 [codecProperties setObject:[NSNumber numberWithFloat:quality] forKey:AVVideoQualityKey];
395 [videoSettings setObject:codecProperties forKey:AVVideoCompressionPropertiesKey];
397 return videoSettings;
467 qWarning() << Q_FUNC_INFO <<
"Encoder is not set to a capture session";
472 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"Invalid recorder";
476 if (QMediaRecorder::RecordingState == m_state)
480 auto audioInput = m_service->audioInput();
482 if (!cameraControl && !audioInput) {
483 qWarning() << Q_FUNC_INFO <<
"Cannot record without any inputs";
484 updateError(QMediaRecorder::ResourceError, tr(
"No inputs specified"));
494 const bool audioOnly = settings.videoCodec() == QMediaFormat::VideoCodec::Unspecified;
495 AVCaptureSession *session = m_service
->session()->captureSession();
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());
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())));
513 NSURL *nsFileURL = fileURL.toNSURL();
515 qWarning() << Q_FUNC_INFO <<
"invalid output URL:" << fileURL;
516 updateError(QMediaRecorder::ResourceError, tr(
"Invalid output file URL"));
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"));
525 if (qt_file_exists(nsFileURL)) {
528 qWarning() << Q_FUNC_INFO <<
"invalid output URL:" << fileURL
529 <<
"(file already exists)";
530 updateError(QMediaRecorder::ResourceError, tr(
"File already exists"));
534 applySettings(settings);
536 QVideoOutputOrientationHandler::setIsRecording(
true);
540 [session stopRunning];
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))]) {
549 m_state = QMediaRecorder::RecordingState;
551 actualLocationChanged(fileURL);
552 stateChanged(m_state);
564 [session startRunning];
565 updateError(QMediaRecorder::FormatError, QMediaRecorderPrivate::msgFailedStartRecording());