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

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

Related

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

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.

AVAssetWriter: Momentary Lag When Starting Write

I am developing an app that records a video using AVAssetWriter (the source media are sample buffers output from captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection).
I've noticed that the very first time I instantiate my AVAssetWriter, then call startWriting() and startSession(atSourceTime: CMTime), there is a lag of about 30ms. This causes the video preview on the screen to momentarily hang, which also results in the first few frames of the video not write properly.
Curiously, if I then re-instantiate my AVAssetWriter and take all of the same steps again, everything works fine from that point onward. This only occurs the very first time.
While perhaps too lengthy to post, this is the function in which I am certain the error is occurring;
func insert(pixel buffer: CVPixelBuffer, with time: CMTime) {
// Check for unknown status
if fileWriter.status == .unknown {
guard startingVideoTime == nil else {
print("We've received an error setting the starting video time.")
return
}
startingVideoTime = time
if fileWriter.startWriting() {
fileWriter.startSession(atSourceTime: startingVideoTime!)
isRecording = true
}
}
// Append buffer
if videoInput.isReadyForMoreMediaData {
append(pixel: buffer, with: time)
isRecording = true
}
}
For posterity, I am calling the above function from my captureOutput, only doing so when my self.isRecording = true, which I set when the user taps a "Start Record" button.
let videoCompressionSettings: [String: Any] = [
AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoWidthKey: NSNumber(value: 1080),
AVVideoHeightKey: NSNumber(value: 1920)
]
let audioCompressionSettings: [String: Any] = [
AVNumberOfChannelsKey: NSNumber(value: 1),
AVEncoderAudioQualityForVBRKey: NSNumber(value: 91),
AVEncoderBitRatePerChannelKey:NSNumber(value: 9600),
AVEncoderBitRateStrategyKey: AVAudioBitRateStrategy_Variable,
AVFormatIDKey: NSNumber(value: 1633772320),
AVSampleRateKey: NSNumber(value: 44100)
]
// My own wrapper for AVAssetWriter
movieWriterManager = MovieWriterManager(videoUrl: recordingVideoURL(), audioUrl: recordingAudioURL(), videoCompressionSettings: videoCompressionSettings, audioCompressionSettings: audioCompressionSettings)
movieWriterManager?.warmup()
You may try to buy time by running the same steps at AppDelegate's didFinishLaunchingWithOptions with a default videoCompressionSettings and audioCompressionSettings. It's actually not blocking UI thread but only the video output connection of AVCaptureVideoDataOutput.

AudioKit empty file using renderToFile with AKSequencer

I'm trying to use AudioKit.renderToFile() to export short MIDI passages to audio (m4a):
// renderSequencer is an instance of AKSequencer
self.renderSequencer.loadMIDIFile(fromURL: midiURL)
Conductor.sharedInstance.setInstrument(renderItem.soundID, forOfflineRender: true)
// we only have one track with note content
for track in self.renderSequencer.tracks {
if track.isNotEmpty {
track.setMIDIOutput(Conductor.sharedInstance.midiIn)
}
}
let audioCacheDir = self.module.stateManager.audioCacheDirectory
// strip name off midi file
let midiFileName = String(midiURL.lastPathComponent.split(separator: ".")[0])
audioFileName = midiFileName
audioFileURL = audioCacheDir.appendingPathComponent("\(midiFileName).m4a")
if let audioFileURL = audioFileURL {
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 2,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
let audioFile: AVAudioFile = try! AVAudioFile(forWriting: audioFileURL, settings: settings)
// get time in seconds of audio file (with 4-beat tail)
var duration: Float64 = 0.0
MusicSequenceGetSecondsForBeats(seq, (16.0 + 4), &duration)
// render sequence
do { try AudioKit.renderToFile(audioFile, duration: duration) {
self.renderSequencer.setRate(60.0)
self.renderSequencer.play()
}
} catch { print("Error performing offline file render!") }
}
This does produce an audio file of the expected duration, but it is silent. I've also tried logging from my MIDI output and can see that the events "played" from inside the preload closure are actually being sent/handled.
Mostly, I suppose, I'm curious to know whether this is actually expected to work. I've seen a couple of posts suggesting that renderToFile from MIDI is not supported (while others have suggested they have it working).
I did, btw, also post an issue on the audiokit GitHub.

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.

AVAudioRecorder settings empty after constructor call

I'm trying to record audio using the microphone and AVAudioRecorder.
It works on iOS 8 but my code does not work anymore on iOS 9.
The recordSettings dictionary is set properly, then I give it to the
AVAudioRecorder:URL:settings constructor.
But, just after, recorder.settings is empty, an assertion failure is thrown
let recordSettings: [String: AnyObject] = [
AVNumberOfChannelsKey: NSNumber(integer: 2),
AVFormatIDKey: NSNumber(integer: Int(kAudioFormatMPEG4AAC)),
AVEncoderBitRateKey: NSNumber(integer: 64)]
var recorder: AVAudioRecorder!
do {
recorder = try AVAudioRecorder(URL: tempURL, settings:recordSettings) // recordSettings.count = 3
assert(recorder.settings.count != 0, "Audio Recorder does not provide settings") // assertion failure threw
} catch let error as NSError {
print("error when intitializing recorder: \(error)")
return
}
Anyone can help me ? Is it a bug ?
EDIT : In my entire code I did not test recorder.settings just after. I did instantiate recorder like my code above, then I did that :
recorder.delegate = self
recorder.prepareToRecord()
recorder.meteringEnabled = true
And it crashes in this line :
for i in 1...(recorder.settings[AVNumberOfChannelsKey] as! Int) {
...
}
It crashes because recorder.settings[AVNumberOfChannelsKey] is nil
I'm not sure why you're checking the settings property, but
from the AVAudioRecorder header file, on the settings property:
these settings are fully valid only when prepareToRecord has been called
so you must call prepareToRecord() first BUT it will fail/return false, because your bitrate is way too low! Its unit is bits per second, not kilobits per second:
AVEncoderBitRateKey: NSNumber(integer: 64000)
This worked on iOS 8 because your too-low bitrate was simply discarded. Looks like it became an error in iOS 9.

Resources