Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AudioFileWriteBytes (AudioToolbox) fails with error code -38 kAudioFileNotOpenError

I'm working on the app that records user audio, writes data to file using AudioToolbox and processes these data. In analytics I see A LOT of errors that happen in AudioToolbox recording. Almost half of them is -38 kAudioFileNotOpenError error returned from AudioFileWriteBytes() call.

For now it seems like this happens for no reason: user starts recording, everything goes well and then error is logged for 10-80 times in a row. In analytics there is no user actions like going to background or pausing recording (we have this function).

I researched info online but didn't find much info about errors.

I'm posting all of the recording code (with stripped unused or analytics code), because besides -38 kAudioFileNotOpenError I have a bunch of other errors and this might mean that I might be using AudioToolbox in the wrong way. Here are top errors:

  1. AudioFileWriteBytes() returns -38 kAudioFileNotOpenError error: ([1] in code) - ~50% of all errors
  2. AudioUnitRender() returns -10863 kAudioUnitErr_CannotDoInCurrentContext error ([2] in code) - ~5%
  3. AudioFileWriteBytes() returns 1868981823 kAudioFileDoesNotAllow64BitDataSizeError error: ([1] in code) - ~4%
  4. AudioUnitRender() returns -1 error ([2] in code) - ~3%

Any help, comment or suggesting will be very helpful!

Here's the code (it's also available on GitHub: https://github.com/derpoliuk/SO-AudioToolbox-error-quesion):

class Recorder {

    static let shared = Recorder()
    private static let sampleRate: Float64 = 16000

    var processAudioData: ((Data) -> ())?

    fileprivate var remoteIOUnit: AudioComponentInstance?
    private var audioFile: AudioFileID?
    private var startingByte: Int64 = 0
    // Audio recording settings
    private let formatId: AudioFormatID = kAudioFormatLinearPCM
    private let bitsPerChannel: UInt32 = 16
    private let channelsPerFrame: UInt32 = 1
    private let bytesPerFrame: UInt32 = 2 // channelsPerFrame * 2
    private let framesPerPacket: UInt32 = 1
    private let encoderBitRate = 12800
    private let formatFlags: AudioFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked

    func record(atURL url: URL) {
        var status = openFileForWriting(fileURL: url)
        startingByte = 0
        status = prepareAudioToolbox()
        status = startAudioToolboxRecording()
    }

    func openFileForWriting(fileURL: URL) -> OSStatus {
        var asbd = AudioStreamBasicDescription()
        memset(&asbd, 0, MemoryLayout<AudioStreamBasicDescription>.size)
        asbd.mSampleRate = Recorder.sampleRate
        asbd.mFormatID = formatId
        asbd.mFormatFlags = formatFlags
        asbd.mBitsPerChannel = bitsPerChannel
        asbd.mChannelsPerFrame = channelsPerFrame
        asbd.mFramesPerPacket = framesPerPacket
        asbd.mBytesPerFrame = bytesPerFrame
        asbd.mBytesPerPacket = framesPerPacket * bytesPerFrame
        // Set up the file
        var audioFile: AudioFileID?
        var audioErr: OSStatus = noErr
        audioErr = AudioFileCreateWithURL(fileURL as CFURL, AudioFileTypeID(kAudioFileWAVEType), &asbd, .eraseFile, &audioFile)
        if audioErr == noErr {
            self.audioFile = audioFile
        }
        return audioErr
    }

    func prepareAudioToolbox() -> OSStatus {
        var status = noErr
        // Describe the RemoteIO unit
        var audioComponentDescription = AudioComponentDescription()
        audioComponentDescription.componentType = kAudioUnitType_Output
        audioComponentDescription.componentSubType = kAudioUnitSubType_RemoteIO
        audioComponentDescription.componentManufacturer = kAudioUnitManufacturer_Apple
        audioComponentDescription.componentFlags = 0
        audioComponentDescription.componentFlagsMask = 0
        // Get the RemoteIO unit
        var ioUnit: AudioComponentInstance?
        let remoteIOComponent = AudioComponentFindNext(nil, &audioComponentDescription)
        status = AudioComponentInstanceNew(remoteIOComponent!, &ioUnit)
        guard status == noErr else {
            return status
        }
        guard let remoteIOUnit = ioUnit else {
            return 656783
        }
        self.remoteIOUnit = remoteIOUnit
        // Configure the RemoteIO unit for input
        let bus1: AudioUnitElement = 1
        var oneFlag: UInt32 = 1
        status = AudioUnitSetProperty(remoteIOUnit,
                                      kAudioOutputUnitProperty_EnableIO,
                                      kAudioUnitScope_Input,
                                      bus1,
                                      &oneFlag,
                                      UInt32(MemoryLayout<UInt32>.size));
        guard status == noErr else {
            return status
        }
        var asbd = AudioStreamBasicDescription()
        asbd.mSampleRate = Recorder.sampleRate
        asbd.mFormatID = formatId
        asbd.mFormatFlags = formatFlags
        asbd.mBitsPerChannel = bitsPerChannel
        asbd.mChannelsPerFrame = channelsPerFrame
        asbd.mFramesPerPacket = framesPerPacket
        asbd.mBytesPerFrame = bytesPerFrame
        asbd.mBytesPerPacket = framesPerPacket * bytesPerFrame
        // Set format for mic input (bus 1) on RemoteIO's output scope
        status = AudioUnitSetProperty(remoteIOUnit,
                                      kAudioUnitProperty_StreamFormat,
                                      kAudioUnitScope_Output,
                                      bus1,
                                      &asbd,
                                      UInt32(MemoryLayout<AudioStreamBasicDescription>.size))
        guard status == noErr else {
            return status
        }
        // Set the recording callback
        var callbackStruct = AURenderCallbackStruct()
        callbackStruct.inputProc = recordingCallback
        callbackStruct.inputProcRefCon = nil
        status = AudioUnitSetProperty(remoteIOUnit,
                                      kAudioOutputUnitProperty_SetInputCallback,
                                      kAudioUnitScope_Global,
                                      bus1,
                                      &callbackStruct,
                                      UInt32(MemoryLayout<AURenderCallbackStruct>.size));
        guard status == noErr else {
            return status
        }
        // Initialize the RemoteIO unit
        return AudioUnitInitialize(remoteIOUnit)
    }

    func startAudioToolboxRecording() -> OSStatus {
        guard let remoteIOUnit = remoteIOUnit else {
            return 656783
        }
        return AudioOutputUnitStart(remoteIOUnit)
    }

    func writeDataToFile(audioBuffers: UnsafeMutableBufferPointer<AudioBuffer>) -> OSStatus {
        guard let audioFile = audioFile else {
            return 176136
        }
        var startingByte = self.startingByte
        for audioBuffer in audioBuffers {
            var numBytes = audioBuffer.mDataByteSize
            guard let mData = audioBuffer.mData else {
                continue
            }
            // [1] following call fails with `-38` error (`kAudioFileNotOpenError`). Less often it fails with `1868981823` error (`kAudioFileDoesNotAllow64BitDataSizeError`)
            let audioErr = AudioFileWriteBytes(audioFile,
                                               false,
                                               startingByte,
                                               &numBytes,
                                               mData)
            guard audioErr == noErr else {
                return audioErr
            }
            let data = Data(bytes:  mData, count: Int(numBytes))
            processAudioData?(data)
            startingByte += Int64(numBytes)
        }
        self.startingByte = startingByte
        return noErr
    }

}

private func recordingCallback(
    inRefCon: UnsafeMutableRawPointer,
    ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
    inTimeStamp: UnsafePointer<AudioTimeStamp>,
    inBusNumber: UInt32,
    inNumberFrames: UInt32,
    ioData: UnsafeMutablePointer<AudioBufferList>?) -> OSStatus {

    guard let remoteIOUnit = Recorder.shared.remoteIOUnit else {
        return 656783
    }
    var status = noErr
    let channelCount: UInt32 = 1
    var bufferList = AudioBufferList()
    bufferList.mNumberBuffers = channelCount
    let buffers = UnsafeMutableBufferPointer<AudioBuffer>(start: &bufferList.mBuffers,
                                                          count: Int(bufferList.mNumberBuffers))
    buffers[0].mNumberChannels = 1
    buffers[0].mDataByteSize = inNumberFrames * 2
    buffers[0].mData = nil
    // get the recorded samples
    // [2] following call fails with `-10863` error (`kAudioUnitErr_CannotDoInCurrentContext`) and less often with `-1` error
    status = AudioUnitRender(remoteIOUnit,
                             ioActionFlags,
                             inTimeStamp,
                             inBusNumber,
                             inNumberFrames,
                             UnsafeMutablePointer<AudioBufferList>(&bufferList))
    guard status == noErr else {
        return status
    }
    status = Recorder.shared.writeDataToFile(audioBuffers: buffers)
    return status
}
like image 513
derpoliuk Avatar asked Jan 19 '18 15:01

derpoliuk


1 Answers

Ok I hope this help, I think that the problem is in the speed of recording vs writing vs rendering so you can write with blocks so that you can isolate the methods, I have the same problems with graphics so I always block (lock and unlock buffers).

Working with Blocks

like image 106
Tarek.Eladly Avatar answered Sep 22 '22 14:09

Tarek.Eladly