Stream audio with Swift

Viewed 354

I'm developing an application that should record a user's voice and stream it to a custom device via the MQTT protocol. The audio specification for the custom device: little-endian, unsigned, 16-bit LPCM at 8khz sample rate. Packets should be 1000 bytes each.

I'm not familiar with AudioEngine and I found this sample of code which I believe fits my case:

func startRecord() {
    audioEngine = AVAudioEngine()
    let bus = 0
    let inputNode = audioEngine.inputNode
    let inputFormat = inputNode.outputFormat(forBus: bus)
    
    var streamDescription = AudioStreamBasicDescription()
    streamDescription.mFormatID = kAudioFormatLinearPCM.littleEndian
    streamDescription.mSampleRate = 8000.0
    streamDescription.mChannelsPerFrame = 1
    streamDescription.mBitsPerChannel = 16
    streamDescription.mBytesPerPacket = 1000
    
    
    let outputFormat = AVAudioFormat(streamDescription: &streamDescription)!
    
    guard let converter: AVAudioConverter = AVAudioConverter(from: inputFormat, to: outputFormat) else {
        print("Can't convert in to this format")
        return
    }
    
    inputNode.installTap(onBus: 0, bufferSize: 1024, format: inputFormat) { (buffer, time) in
        print("Buffer format: \(buffer.format)")
        
        var newBufferAvailable = true
        
        let inputCallback: AVAudioConverterInputBlock = { inNumPackets, outStatus in
            if newBufferAvailable {
                outStatus.pointee = .haveData
                newBufferAvailable = false
                return buffer
            } else {
                outStatus.pointee = .noDataNow
                return nil
            }
        }
        
        let convertedBuffer = AVAudioPCMBuffer(pcmFormat: outputFormat, frameCapacity: AVAudioFrameCount(outputFormat.sampleRate) * buffer.frameLength / AVAudioFrameCount(buffer.format.sampleRate))!
        
        var error: NSError?
        let status = converter.convert(to: convertedBuffer, error: &error, withInputFrom: inputCallback)
        assert(status != .error)
        
        print("Converted buffer format:", convertedBuffer.format)
    }
    
    audioEngine.prepare()
    
    do {
        try audioEngine.start()
    } catch {
        print("Can't start the engine: \(error)")
    }
    
}

But currently, the converter can't convert the input format to my output format and I don't understand why. If I change my output format to something like that:

let outputFormat = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 8000.0, channels: 1, interleaved: false)!

Then it works.

1 Answers

Your streamDescription is wrong, you hadn't filled in all the fields, and mBytesPerPacket was wrong - this is not the same kind of packet your protocol calls for. For uncompressed audio (like LPCM) AudioStreamBasicDescription requires this field to be 1. If your protocol requires samples to be in groups of 1000, then you will have to do that.

Try this

var streamDescription = AudioStreamBasicDescription()
streamDescription.mSampleRate = 8000.0
streamDescription.mFormatID = kAudioFormatLinearPCM
streamDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger // no endian flag means little endian
streamDescription.mBytesPerPacket = 2
streamDescription.mFramesPerPacket = 1
streamDescription.mBytesPerFrame = 2
streamDescription.mChannelsPerFrame = 1
streamDescription.mBitsPerChannel = 16
streamDescription.mReserved = 0
Related