I am trying to record my microphone, compress the recorded audio buffer, transfer the compressed buffer as bytes to another device, decompress the received bytes into a new buffer and finally playback that buffer.
The following code I use to record the microphone, and to test whether the compression and decompression works without issues (thus leaving out the data transfer part):
// ... code to setup audioEngines for recording and playback
tappableInputNode?.installTap(onBus: bus, bufferSize: 1024, format: format!) { (buffer, when) in
guard let compressedData = compressPCMBuffer(buffer) else {
print("Failed to compress buffer")
return
}
guard let decompressedBuffer = decompressDataToPCMBuffer(compressedData) else {
print("Failed to decompress data")
return
}
let renderTime =
playerNode.playerTime(
forNodeTime: playerNode.lastRenderTime ?? AVAudioTime(hostTime: mach_absolute_time()))
?? AVAudioTime(hostTime: mach_absolute_time())
let delayInSeconds = 0.1
let futureHostTime = renderTime.hostTime + UInt64(delayInSeconds)
let scheduledTime = AVAudioTime(hostTime: futureHostTime)
playerNode.scheduleBuffer(decompressedBuffer, at: scheduledTime, completionCallbackType: .dataPlayedBack) { (type) in
print("Buffer played back")
}
playerNode.play()
}
When playing the initial buffer provided by the tap, everything works fine. But when I try to playback the decompressed Buffer (like in the code above), the only thing I hear is a fast clicking sound.
For compressing the AVAudioPCMBuffer I wrote the following function:
public func compressPCMBuffer(_ buffer: AVAudioPCMBuffer) -> Data? {
var error: NSError?
var opusDesc = AudioStreamBasicDescription()
opusDesc.mFormatID = kAudioFormatOpus
opusDesc.mChannelsPerFrame = buffer.format.channelCount
opusDesc.mSampleRate = buffer.format.sampleRate
guard let audioFormat = AVAudioFormat(streamDescription: &opusDesc) else {
print("Failed to create AVAudioFormat")
return nil
}
guard let converter = AVAudioConverter(from: buffer.format, to: audioFormat) else {
print("Failed to initialize AVAudioConverter")
return nil
}
let outputBuffer = AVAudioCompressedBuffer(
format: audioFormat, packetCapacity: 1, maximumPacketSize: 512
)
let inputBlock: AVAudioConverterInputBlock = { inNumPackets, outStatus in
outStatus.pointee = .haveData
return buffer
}
let status = converter.convert(to: outputBuffer, error: &error, withInputFrom: inputBlock)
if status == .error || error != nil {
print("Audio conversion failed: \(error?.localizedDescription ?? "Unknown error")")
return nil
}
let packetCount = Int(outputBuffer.packetCount)
if packetCount == 0 {
print("No packets were produced during conversion")
return nil
}
let data = outputBuffer.toData()
return data
}
The returned data seems to be 160 - 180 bytes of size, ideal for my use case.
Then, for decompressing the Data to an AVAudioPCMBuffer I have this function:
public func decompressDataToPCMBuffer(_ rawData: Data) -> AVAudioPCMBuffer? {
var opusDesc = AudioStreamBasicDescription()
opusDesc.mFormatID = kAudioFormatOpus
opusDesc.mChannelsPerFrame = 1
opusDesc.mSampleRate = 48000
guard let opusFormat = AVAudioFormat(streamDescription: &opusDesc) else {
print("Failed to create Opus AVAudioFormat")
return nil
}
let inputBuffer = AVAudioCompressedBuffer(
format: opusFormat, packetCapacity: 1, maximumPacketSize: 512
)
inputBuffer.packetCount = 1
inputBuffer.byteLength = UInt32(rawData.count)
rawData.withUnsafeBytes { (rawBufferPointer: UnsafeRawBufferPointer) in
let rawPointer = rawBufferPointer.baseAddress!
inputBuffer.audioBufferList.pointee.mBuffers.mData!.copyMemory(
from: rawPointer, byteCount: rawData.count
)
}
guard
let pcmFormat = AVAudioFormat(
commonFormat: .pcmFormatInt16, sampleRate: opusDesc.mSampleRate,
channels: AVAudioChannelCount(opusDesc.mChannelsPerFrame), interleaved: false
)
else {
print("Failed to create PCM format")
return nil
}
guard let converter = AVAudioConverter(from: opusFormat, to: pcmFormat) else {
print("Failed to create AVAudioConverter")
return nil
}
guard let pcmBuffer = AVAudioPCMBuffer(pcmFormat: pcmFormat, frameCapacity: 4800) else {
print("Failed to create PCM buffer")
return nil
}
var error: NSError? = nil
let inputBlock: AVAudioConverterInputBlock = { inNumPackets, outStatus in
outStatus.pointee = .haveData
return inputBuffer
}
converter.convert(to: pcmBuffer, error: &error, withInputFrom: inputBlock)
if let error = error {
print("Error during conversion: \(error.localizedDescription)")
return nil
}
return pcmBuffer
}
Both functions run correctly without any warnings or errors, but the played audio is corrupted / malformed, with the clicking sound I described earlier.
I have also tried other formats, like AAC, but I get the exact same clicking sound.
Any help is appreciated. Thank you!
I was unable to create my own compression / decompression functions. However, I was able to get it to work using a third party package. https://github.com/alta/swift-opus