105 NSMutableDictionary *settings = [NSMutableDictionary dictionary];
108 int codecId = QDarwinFormatInfo::audioFormatForCodec(encoderSettings.mediaFormat().audioCodec());
109 settings[AVFormatIDKey] = @(codecId);
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[AVEncoderAudioQualityKey] = @(quality);
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[AVEncoderBitRateKey] = @(encoderSettings.audioBitRate());
154 int sampleRate = encoderSettings.audioSampleRate();
155 bool isSampleRateSupported =
false;
156 if (sampleRate >= 8000 && sampleRate <= 192000) {
157 QList<AudioValueRange> sampleRates = qt_supported_sample_rates_for_format(codecId);
158 for (
int i = 0; i < sampleRates.count(); i++) {
159 if (sampleRate >= sampleRates[i].mMinimum && sampleRate <= sampleRates[i].mMaximum) {
160 isSampleRateSupported =
true;
165 if (!isSampleRateSupported)
167 settings[AVSampleRateKey] = @(sampleRate);
170 int channelCount = encoderSettings.audioChannelCount();
171 bool isChannelCountSupported =
false;
172 if (channelCount > 0) {
173 std::optional<QList<UInt32>> channelCounts = qt_supported_channel_counts_for_format(codecId);
176 if (channelCounts == std::nullopt) {
177 isChannelCountSupported =
true;
179 for (
int i = 0; i < channelCounts.value().count(); i++) {
180 if ((UInt32)channelCount == channelCounts.value()[i]) {
181 isChannelCountSupported =
true;
189 if (isChannelCountSupported && channelCount > 2) {
190 AudioChannelLayout channelLayout;
191 memset(&channelLayout, 0,
sizeof(AudioChannelLayout));
192 auto channelLayoutTags = qt_supported_channel_layout_tags_for_format(codecId, channelCount);
193 if (channelLayoutTags.size()) {
194 channelLayout.mChannelLayoutTag = channelLayoutTags.first();
195 settings[AVChannelLayoutKey] = [NSData dataWithBytes:&channelLayout length:
sizeof(channelLayout)];
197 isChannelCountSupported =
false;
201 if (isChannelCountSupported)
202 settings[AVNumberOfChannelsKey] = @(channelCount);
205 if (!isChannelCountSupported) {
208 if (format.isValid()) {
209 auto layout = QCoreAudioUtils::toAudioChannelLayout(format, &size);
210 UInt32 layoutSize = offsetof(AudioChannelLayout, mChannelDescriptions)
211 + layout->mNumberChannelDescriptions *
sizeof(AudioChannelDescription);
212 settings[AVChannelLayoutKey] = [NSData dataWithBytes:layout.get() length:layoutSize];
215 settings[AVNumberOfChannelsKey] = @(1);
219 if (codecId == kAudioFormatAppleLossless)
220 settings[AVEncoderBitDepthHintKey] = @(24);
222 if (codecId == kAudioFormatLinearPCM) {
223 settings[AVLinearPCMBitDepthKey] = @(16);
224 settings[AVLinearPCMIsBigEndianKey] = @NO;
225 settings[AVLinearPCMIsFloatKey] = @NO;
226 settings[AVLinearPCMIsNonInterleaved] = @NO;
233 AVCaptureDevice *device, AVCaptureConnection *connection,
243 NSMutableDictionary *videoSettings = [NSMutableDictionary dictionary];
248 auto codec = encoderSettings.mediaFormat().videoCodec();
249 NSString *c = QDarwinFormatInfo::videoFormatForCodec(codec);
250 [videoSettings setObject:c forKey:AVVideoCodecKey];
255 int w = encoderSettings.videoResolution().width();
256 int h = encoderSettings.videoResolution().height();
258 if (AVCaptureDeviceFormat *currentFormat = device.activeFormat) {
259 CMFormatDescriptionRef formatDesc = currentFormat.formatDescription;
260 CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDesc);
261 FourCharCode formatCodec = CMVideoFormatDescriptionGetCodecType(formatDesc);
267 AVCaptureDeviceFormat *newFormat = nil;
268 if ((w <= 0 || h <= 0)
269 && encoderSettings.videoFrameRate() > 0
270 && !qt_format_supports_framerate(currentFormat, encoderSettings.videoFrameRate())) {
272 newFormat = qt_find_best_framerate_match(device,
274 encoderSettings.videoFrameRate());
276 }
else if (w > 0 && h > 0) {
277 AVCaptureDeviceFormat *f = qt_find_best_resolution_match(device,
278 encoderSettings.videoResolution(),
282 CMVideoDimensions d = CMVideoFormatDescriptionGetDimensions(f.formatDescription);
283 qreal fAspectRatio = qreal(d.width) / d.height;
285 if (w > dim.width || h > dim.height
286 || qAbs((qreal(dim.width) / dim.height) - fAspectRatio) > 0.01) {
292 if (qt_set_active_format(device, newFormat,
false )) {
293 formatDesc = newFormat.formatDescription;
294 dim = CMVideoFormatDescriptionGetDimensions(formatDesc);
297 if (w < 0 || h < 0) {
303 if (w > 0 && h > 0) {
306 qreal deviceAspectRatio = qreal(dim.width) / dim.height;
307 qreal recAspectRatio = qreal(w) / h;
308 if (qAbs(deviceAspectRatio - recAspectRatio) > 0.01) {
309 if (recAspectRatio > deviceAspectRatio)
310 w = qRound(h * deviceAspectRatio);
312 h = qRound(w / deviceAspectRatio);
316 w = qMin(w, dim.width);
317 h = qMin(h, dim.height);
321 if (w > 0 && h > 0) {
326 bool isPortrait = nativeSize.width() < nativeSize.height();
328 if (isPortrait && h < w)
330 else if (!isPortrait && w < h)
333 encoderSettings.setVideoResolution(QSize(w, h));
335 w = nativeSize.width();
336 h = nativeSize.height();
337 encoderSettings.setVideoResolution(nativeSize);
339 videoSettings[AVVideoWidthKey] = @(w);
340 videoSettings[AVVideoHeightKey] = @(h);
345 const qreal fps = encoderSettings.videoFrameRate();
346 qt_set_framerate_limits(device, connection, fps, fps);
348 encoderSettings.setVideoFrameRate(qt_current_framerates(device, connection).second);
352 NSMutableDictionary *codecProperties = [NSMutableDictionary dictionary];
354 float quality = -1.f;
356 if (encoderSettings.encodingMode() == QMediaRecorder::ConstantQualityEncoding) {
357 if (encoderSettings.quality() != QMediaRecorder::NormalQuality) {
358 if (codec != QMediaFormat::VideoCodec::MotionJPEG) {
359 qWarning(
"ConstantQualityEncoding is not supported for MotionJPEG");
361 switch (encoderSettings.quality()) {
362 case QMediaRecorder::VeryLowQuality:
365 case QMediaRecorder::LowQuality:
368 case QMediaRecorder::HighQuality:
371 case QMediaRecorder::VeryHighQuality:
380 }
else if (encoderSettings.encodingMode() == QMediaRecorder::AverageBitRateEncoding){
381 if (codec != QMediaFormat::VideoCodec::H264 && codec != QMediaFormat::VideoCodec::H265)
382 qWarning() <<
"AverageBitRateEncoding is not supported for codec" << QMediaFormat::videoCodecName(codec);
384 bitrate = encoderSettings.videoBitRate();
386 qWarning(
"Encoding mode is not supported");
390 codecProperties[AVVideoAverageBitRateKey] = @(bitrate);
392 codecProperties[AVVideoQualityKey] = @(quality);
394 videoSettings[AVVideoCompressionPropertiesKey] = codecProperties;
396 return videoSettings;
466 qWarning() << Q_FUNC_INFO <<
"Encoder is not set to a capture session";
471 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"Invalid recorder";
475 if (QMediaRecorder::RecordingState == m_state)
479 auto audioInput = m_service->audioInput();
481 if (!cameraControl && !audioInput) {
482 qWarning() << Q_FUNC_INFO <<
"Cannot record without any inputs";
483 updateError(QMediaRecorder::ResourceError, tr(
"No inputs specified"));
493 const bool audioOnly = settings.videoCodec() == QMediaFormat::VideoCodec::Unspecified;
494 AVCaptureSession *session = m_service
->session()->captureSession();
498 if (!cameraControl || !cameraControl->isActive()) {
499 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"can not start record while camera is not active";
500 updateError(QMediaRecorder::ResourceError,
501 QMediaRecorderPrivate::msgFailedStartRecording());
506 const QString path(outputLocation().scheme() == QLatin1String(
"file") ?
507 outputLocation().path() : outputLocation().toString());
508 const QUrl fileURL(QUrl::fromLocalFile(QMediaStorageLocation::generateFileName(
509 path, audioOnly ? QStandardPaths::MusicLocation : QStandardPaths::MoviesLocation,
510 settings.preferredSuffix())));
512 NSURL *nsFileURL = fileURL.toNSURL();
514 qWarning() << Q_FUNC_INFO <<
"invalid output URL:" << fileURL;
515 updateError(QMediaRecorder::ResourceError, tr(
"Invalid output file URL"));
518 if (!qt_is_writable_file_URL(nsFileURL)) {
519 qWarning() << Q_FUNC_INFO <<
"invalid output URL:" << fileURL
520 <<
"(the location is not writable)";
521 updateError(QMediaRecorder::ResourceError, tr(
"Non-writeable file location"));
524 if (qt_file_exists(nsFileURL)) {
527 qWarning() << Q_FUNC_INFO <<
"invalid output URL:" << fileURL
528 <<
"(file already exists)";
529 updateError(QMediaRecorder::ResourceError, tr(
"File already exists"));
533 applySettings(settings);
535 QVideoOutputOrientationHandler::setIsRecording(
true);
539 [session stopRunning];
541 if ([m_writer setupWithFileURL:nsFileURL
542 cameraService:m_service
543 audioSettings:m_audioSettings
544 videoSettings:m_videoSettings
545 fileFormat:settings.fileFormat()
546 transform:CGAffineTransformMakeRotation(qDegreesToRadians(rotation))]) {
548 m_state = QMediaRecorder::RecordingState;
550 actualLocationChanged(fileURL);
551 stateChanged(m_state);
563 [session startRunning];
564 updateError(QMediaRecorder::FormatError, QMediaRecorderPrivate::msgFailedStartRecording());