AVAssetWriter not working with videoChat mode of AVAudioSession (iPhone 14 Pro only) - avaudiosession

I have a video conferencing app and we are setting up AVAudioSession to videoChat so that we can get echo cancellation.
let options: AVAudioSession.CategoryOptions = [.mixWithOthers , .defaultToSpeaker, .allowBluetooth]
try session.setCategory(.playAndRecord, mode: .videoChat, options: options)
I also want to do local recording of audio samples using AVAssetWriter API. Setup of AssertWriter is as follows:
let audioAssetWriter = try AVAssetWriter(outputURL: fileURL, fileType: .m4a)
let settings: [String: Any] = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: Int(audioParameters.samplingRate),
AVEncoderBitRateKey: Int(audioParameters.bitrate),
AVNumberOfChannelsKey: Int(audioParameters.channels),
AVEncoderAudioQualityKey: AVAudioQuality.max.rawValue
]
let assetWriterAudioInput = AVAssetWriterInput(mediaType: .audio, outputSettings: settings)
assetWriterAudioInput.expectsMediaDataInRealTime = true
audioAssetWriter.add(assetWriterAudioInput)
I am seeing that on the iPhone 14 Pro,
assetWriterAudioInput.append()
is failing. Everything works fine on iPhone 13 and older devices.
Is anyone else seeing this issue on the iPhone 14 Pro.

Related

Sound volume decreasing for no apparent reason

I have an iOS app using SwiftUI. It handles a few sound files and performs some audio recording. This is the function doing the recording work:
func recordAudio(to file: String, for duration: TimeInterval) {
let audioSession:AVAudioSession = AVAudioSession.sharedInstance()
do {try audioSession.setCategory(.playAndRecord, mode: .default)
try audioSession.setActive(true)
let audioFilename = getDocumentsDirectory().appendingPathComponent(file+".m4a"),
audioURL = URL(fileURLWithPath: audioFilename),
settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 2,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
audioRecorder = try AVAudioRecorder(url: audioURL, settings: settings)
audioRecorder.delegate = self
audioRecorder.record(forDuration: TimeInterval(2.0))
} catch let error as NSError {
print("Failed -- Recording !!! -> \(error)")
}
}
At this point, it basically works, but there is a strange behaviour that I neither understand nor like.
Here is the problem:
When I start the app and play a sound file, the volume is right for my taste.
Then without ever adjusting the volume I perform some recording (using the function above).
Finally after the recording is done, I go back to the file I played just before and play it again; the volume has mysteriously gone down, without me knowing why.
Is there something in my function that could explain that?
Or some other cause that someone could think of?
If I restart the app, the volume automatically goes back to normal.
For information, I am using iOS 14.4.2 and Xcode 12.4.
The audio session will be a decreased volume during playback after recording in .playAndRecord mode. After recording, explicitly set to something like .playback to get the volume you're expecting.

Failed to write video with AVAssetWriter

I am using AVAssetWriter to save screen recoding to mp4 (Withd H.264 video and aac audio encoding). Everything works perfectly as expected but some users complained about the 0KB video size issue, that is no data is being written to the output file.
By trying over and over again I was able to reproduce this issue. It happened after a few successful writes (sometimes after 2 sometimes after 5 and on some machines it never happens). I compared the successful flow and failure looked at the logs for it. The only thing which I found out was this
CMIO_Unit_Converter_Audio.cpp:588:RebuildAudioConverter AudioConverterSetProperty() failed (1886547824)
Where
kAudioFormatUnsupportedPropertyError = 1886547824
Which leads me to check the audio format in case of failure but the Audio format for AVAssetWriter was perfectly fine.
I am setting up audio writer input as
AVAssetWriter Inputs
[<AVAssetWriterInput: 0x600002047950, mediaType = vide, outputSettings = {
AVVideoCodecKey = avc1;
AVVideoHeightKey = 840;
AVVideoWidthKey = 1360;
}>, <AVAssetWriterInput: 0x60000205e740, mediaType = soun, outputSettings = {
AVFormatIDKey = 1633772320;
AVNumberOfChannelsKey = 2;
AVSampleRateKey = 44100;
}>]
My code to create AVAssetWriterInput for video and audio is as follow
Audio AVAssetWriterInput
settings = [
AVFormatIDKey : kAudioFormatMPEG4AAC,
AVNumberOfChannelsKey : ch,
AVSampleRateKey : rate,
]
audioInput = AVAssetWriterInput(mediaType: .audio, outputSettings: settings)
Video AVAssetWriterInput
var settings: [String : Any] = [
AVVideoWidthKey : cx,
AVVideoHeightKey : cy,
AVVideoCodecKey : AVVideoCodecType.h264,
]
videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: settings)
I noticed error in the log appears after AVCaptureSession is being started and before AVCaptureVideoDataOutputSampleBufferDelegate's captureOutput. I logged AVCaptureSession inputs and output as well.
AVCaptureSession Inputs
[<AVCaptureScreenInput: 0x6000022e18e0>, <AVCaptureDeviceInput: 0x6000022e0a00 [Built-in Microphone]>]
AVCaptureSession Outputs
[<AVCaptureAudioDataOutput: 0x6000022e1f40>, <AVCaptureVideoDataOutput: 0x60000228b7a0>]
But again I don't see any problem here. I have been trying to figure this out for a good few days but failed to do so. I don't have any idea what triggers it and even when everything is properly set, it fails to write any output. This is only happening with video+audio if I write video only it works properly.

How to set time duration using AVAudioRecorder exactly for 3 minutes in swift

I am working with AVAudioRecorder for setting recording time duration from 3 minutes but it's not estimate proper time interval. It's recording up to 3 min 7 sec but I need to stop recording at 3 min.
I am using this code:
let recordSettings: [String: AnyObject] = [
AVFormatIDKey: NSNumber(value: kAudioFormatMPEG4AAC),
AVNumberOfChannelsKey: NSNumber(value:1),
AVSampleRateKey: NSNumber(value:8000.0)
]
do {
recorder = try AVAudioRecorder(url: soundFileURL, settings: recordSettings)
recorder.delegate = self
recorder.isMeteringEnabled = true
recorder.record(forDuration: 180.00) // record for 3 minutes

AVAssetWriterInput with more than 2 channels

Does someone know how to use the AVAssetWriterInput init with more than 2 channels?
I'm trying to init an audioInput, to add it after on AVAssetWriter this way:
let audioInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: audioOutputSettings)
assetWriter.add(audioInput)
assetWriter.startWriting()
But it crashes when I init the audioInput with the audioOutputSettings dictionary containing the number of channels key greater than 2. The error is:
Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ’*** -[AVAssetWriterInput initWithMediaType:outputSettings:sourceFormatHint:] 6 is not a valid channel count for Format ID ‘aac ’. Use kAudioFormatProperty_AvailableEncodeNumberChannels (<AudioToolbox/AudioFormat.h>) to enumerate available channel counts for a given format.
As you found in the AVAssetWriterInput comment:
If AVNumberOfChannelsKey specifies a channel count greater than 2, the dictionary must also specify a value for AVChannelLayoutKey.
What it fails to mention is that the channel count depends on your format ID, so passing a AudioChannelLayout won't make AAC support anything other than 1 or 2 channels.
Formats that do support 6 channels include LPCM kAudioFormatLinearPCM and, probably more interestingly, High Efficiency AAC (kAudioFormatMPEG4AAC_HE) which supports 2, 4, 6 and 8 channel audio.
The following code creates an AVAssetWriterInput that is ready for 6 channel AAC HE sample buffers:
var channelLayout = AudioChannelLayout()
channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_1_D
let audioOutputSettings: [String : Any] = [
AVNumberOfChannelsKey: 6,
AVFormatIDKey: kAudioFormatMPEG4AAC_HE,
AVSampleRateKey: 44100,
AVChannelLayoutKey: NSData(bytes: &channelLayout, length: MemoryLayout.size(ofValue: channelLayout)),
]
let audioInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioOutputSettings)
Change these two lines:
channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_2_0
AVNumberOfChannelsKey : 2,
I hope it helps you in my code it worked.

Can't record on iPad: Error Domain=NSOSStatusErrorDomain Code=1718449215 "(null)"

I am running on iPad iOS 9.3.4 (latest version as of this writing).
I am running this code:
let settings = [
AVFormatIDKey: NSNumber(unsignedInt: kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100.0,
AVNumberOfChannelsKey: 2 as NSNumber,
AVEncoderAudioQualityKey: AVAudioQuality.High.rawValue
]
do {
audioRecorder = try AVAudioRecorder(URL: audioURL, settings: settings)
audioRecorder.delegate = self
audioRecorder.record()
} catch let error as NSError{
print(error.description)
}
I am catching this error:
Error Domain=NSOSStatusErrorDomain Code=1718449215 "(null)"
When I try to use AVAudioRecorder with Objective-C - I am able to record with no problems. The problem seems to only occur with Swift and only on a device - no issue in simulator.
If I switch out kAudioFormatMPEG4AAC with kAudioFormatLinearPCM, I am able to record - but when I try to play back the recording nothing plays - seems like it hasn't recorded well.
Has anyone been able to record with AVAudioRecorder in Swift lately and have the recording play back on a real iPad? I would like to just have that code.
Output file path extension must be in sync with AVFormatIDKey
For .wav
let recordSettings:[String:Any] = [AVFormatIDKey:kAudioFormatLinearPCM,
AVEncoderAudioQualityKey:AVAudioQuality.max.rawValue,
AVEncoderBitRateKey:320000,
AVNumberOfChannelsKey:2,
AVSampleRateKey:44100.0 ] as [String : Any]
For .m4a
let recordSettings:[String:Any] = [AVFormatIDKey:kAudioFormatAppleLossless,
AVEncoderAudioQualityKey:AVAudioQuality.max.rawValue,
AVEncoderBitRateKey:320000,
AVNumberOfChannelsKey:2,
AVSampleRateKey:44100.0 ] as [String : Any]
Looks like I never set the recording session as being active. I wish the error description was better though.
override init() {
super.init()
recordingSession = AVAudioSession.sharedInstance()
do {
try recordingSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try recordingSession.setActive(true)
recordingSession.requestRecordPermission() { (allowed: Bool) -> Void in
dispatch_async(dispatch_get_main_queue()) {
if allowed {
// success
} else {
// TBD: Show a message to the user that they need to give permission in settings app to proceed
}
}
}
} catch {
// TBD: Show a message to the user that they need to give permission in settings app to proceed
}
}

Resources