AVAudioRecorder settings empty after constructor call - ios

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.

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.

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.

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
}
}

swift2 AVAudioRecorder

I am trying the following code using swift 2, which should be fine in swift 1.
class NewSoundViewController: UIViewController {
required init(coder aDecoder: NSCoder) {
let audioURL = NSURL.fileURLWithPathComponents([
NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0],
"MyAudio.m4a"
])
do {
let session = AVAudioSession.sharedInstance()
try session.setCategory(AVAudioSessionCategoryPlayAndRecord)
} catch {
print("Session errors.")
}
do {
let recordSettings: [String: AnyObject] = [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVSampleRateKey: 44100.0,
AVNumberOfChannelsKey: 2,
]
self.audioRecorder = try AVAudioRecorder(URL: audioURL!, settings: recordSettings)
self.audioRecorder.meteringEnabled = true
self.audioRecorder.prepareToRecord()
} catch let error as NSError{
print(error.description)
} catch {
print("Other errors")
}
super.init(coder: aDecoder)
}
I got compiling error
Type 'AudioFormatID' does not conform to protocol 'AnyObject'`
at the line AVFormatIDKey: kAudioFormatMPEG4AAC,.
If I comment out the line, I passed build, but got a runtime error
Error Domain=NSOSStatusErrorDomain Code=1718449215 "The operation couldn’t be completed. (OSStatus error 1718449215.)"
I also tried AVFormatIDKey: NSNumber(unsignedInt: kAudioFormatMPEG4AAC),, and got runtime error. Xcode seemed go to debug mode It red highlighted self.audioRecorder = try AVAudioRecorder(URL: audioURL!, settings: recordSettings) and said
Thread 1: EXC_BAD_ACCESS(code=1, address=0x0)
Can anyone please help me?
I also tried AVFormatIDKey: NSNumber(unsignedInt: kAudioFormatMPEG4AAC)
Well, that is the correct thing to say. Basically you are asking two questions here; the compiler error you've already solved. Now you're having a runtime error, but that's completely different matter.
As for the runtime error, it's probably just a figment of trying to test on the Simulator. I ran your code on the device (after fixing the line in question so that it would compile) and it's fine.
EDIT In a comment, you revealed that you tested this on a device running iOS 8.3. That's the problem! You need to test on a device, and it needs to be a device running iOS 9. Then you'll find that your code runs without crashing.
I had the same issue with the settings. AVFormatIDKey was not accepted with the error mentioned in the original post.
The recordSettings needs to be explicitly casted in Swift 2.
These are my settings for recording that works. If I just skipped the AVFormatIDKey, the microphone only worked for a fraction of a second.
let recordSettings = [AVSampleRateKey : NSNumber(float: Float(44100.0)),
AVFormatIDKey : NSNumber(int: Int32(kAudioFormatAppleLossless)),
AVNumberOfChannelsKey : NSNumber(int: 1),
AVEncoderAudioQualityKey : NSNumber(int: Int32(AVAudioQuality.Medium.rawValue)),
AVEncoderBitRateKey : NSNumber(int: Int32(320000))]
You don't need to upgrade the device to iOS 9. I built and ran this for iOS 8.4
I had a similar issue and it turns out that when I updated from swift 1.2 to 2.0 the signature of "settings" changed to a non optional [String : AnyObject]
//Swift 1.2
AVAudioRecorder(URL: audioURL!, settings: nil)
if I pass an empty dictionary it doesn't crash for me anymore and works as before.
//Swift 2.0
AVAudioRecorder(URL: audioURL!, settings: [:])

Resources