I'm trying to build a Camera app that records Video and Audio buffers (AVCaptureVideoDataOutput and AVCaptureAudioDataOutput) to an mp4/mov file using AVAssetWriter.
When creating the Recording Session, I noticed that it blocks for around 5-7 seconds before starting the recording, so I dug deeper to find out why.
This is how I create my AVAssetWriter:
let assetWriter = try AVAssetWriter(outputURL: tempURL, fileType: .mov)
let videoWriter = self.createVideoWriter(...)
assetWriter.add(videoWriter)
let audioWriter = self.createAudioWriter(...)
assetWriter.add(audioWriter)
assetWriter.startWriting()
There's two slow parts here in that code:
The
createAudioWriter(...)function takes ages!This is how I create the audio
AVAssetWriterInput:// audioOutput is my AVCaptureAudioDataOutput, audioInput is the microphone let settings = audioOutput.recommendedAudioSettingsForAssetWriter(writingTo: .mov) let format = audioInput.device.activeFormat.formatDescription let audioWriter = AVAssetWriterInput(mediaType: .audio, outputSettings: settings, sourceFormatHint: format) audioWriter.expectsMediaDataInRealTime = trueThe above code takes up to 3000ms on an iPhone 11 Pro!
When I remove the recommended settings and just pass
nilasoutputSettings:audioWriter = AVAssetWriterInput(mediaType: .audio, outputSettings: nil) audioWriter.expectsMediaDataInRealTime = true...It initializes almost instantly - something like 30 to 50ms.
Starting the
AVAssetWritertakes ages!Calling this method:
assetWriter.startWriting()...takes takes 3000 to 5000ms on my iPhone 11 Pro!
Does anyone have any ideas why this is so slow? Am I doing something wrong?
It feels like passing nil as the outputSettings is not a good idea, and recommendedAudioSettingsForAssetWriter should be the way to go, but 3 seconds initialization time is not acceptable.
Here's the full code: RecordingSession.swift from react-native-vision-camera. This gets called from here.
I'd appreciate any help, thanks!
EDIT:
These are the recommendedVideoSettingsForAssetWriter settings I get:
["AVVideoCompressionPropertiesKey": {
AllowFrameReordering = 1;
AllowOpenGOP = 1;
AverageBitRate = 47848572;
BaseLayerFrameRate = 15;
ExpectedFrameRate = 60;
MaxAllowedFrameQP = 41;
MaxKeyFrameIntervalDuration = 1;
MinAllowedFrameQP = 15;
MinimizeMemoryUsage = 1;
Priority = 80;
ProfileLevel = "HEVC_Main_AutoLevel";
RealTime = 1;
RelaxAverageBitRateTarget = 1;
},
"AVVideoWidthKey": 2160,
"AVVideoCodecKey": hvc1,
"AVVideoHeightKey": 3840]
(video initializes in ~10ms)
And these are the recommendedAudioSettingsForAssetWriter(..) I get:
["AVEncoderQualityForVBRKey": 91,
"AVEncoderBitRatePerChannelKey": 96000,
"AVEncoderBitRateStrategyKey": AVAudioBitRateStrategy_Variable,
"AVFormatIDKey": 1633772320,
"AVNumberOfChannelsKey": 1,
"AVSampleRateKey": 44100]
(audio initializes in ~1500-3000ms)
Note that subsequent calls are faster. I am also changing the AVAudioSessionCategory while configuring the audio AVAssetWriter, not sure if that has anything to do with it.
EDIT 2:
I just found out that it takes so long to initialize the audio AVAssetWriter because I start the audio AVCaptureSession while I initialize the audio AVAssetWriter:
audioQueue.async { // <-- should be async/non-blocking
AVAudioSession.sharedInstance().setCategory(.playAndRecord,
options: [.mixWithOthers,
.allowBluetoothA2DP,
.defaultToSpeaker,
.allowAirPlay])
audioCaptureSession.startRunning() // <-- this takes 1.5-3s
}
let audioWriter = AVAssetWriterInput(...) <-- w/ recommended settings
Apparently the AVAssetWriter also waits for the AVAudioSession? Maybe there's some internal Mutex going on, because I'm 100% sure that this should run in parallel...
Either way - I can't really start the audio session in advance because I don't want to introduce that mini audio stutter if the user starts the app and has music playing, and I know that Snapchat is also achieving that on the fly - any thoughts?