Search code examples
iosswiftavfoundationavaudioplayeravaudioengine

How to correctly convert AVAudioCompressedBuffer into Data and back


I have an AVAudioCompressedBuffer instance that gets correctly decoded and played by my AVAudioEngine.

The problem is that after converting it to Data and then back to AVAudioCompressedBuffer it is no longer playable and throws a kAudioCodecBadDataError.

This is how I'm currently managing the conversion to and from Data:

// Convert AVAudioCompressedBuffer to Data
let capacity = Int(compressedBuffer.byteLength)
let compressedBufferPointer = compressedBuffer.data.bindMemory(to: UInt8.self, capacity: capacity)
var compressedBytes: [UInt8] = [UInt8].init(repeating: 0, count: capacity)
        compressedBufferPointer.withMemoryRebound(to: UInt8.self, capacity: capacity) { sourceBytes in
compressedBytes.withUnsafeMutableBufferPointer {
                $0.baseAddress!.initialize(from: sourceBytes, count: capacity)
           }
       }
        
let data = Data(compressedBytes)
// Convert Data to AVAudioCompressedBuffer
let compressedBuffer: AVAudioCompressedBuffer = AVAudioCompressedBuffer.init(format: format, packetCapacity: packetCapacity, maximumPacketSize: maximumPacketSize)
compressedBuffer.byteLength = byteLength
compressedBuffer.packetCount = packetCount
data.withUnsafeBytes {
            compressedBuffer.data.copyMemory(from: $0.baseAddress!, byteCount: byteLength)
        }
let buffer = compressedBuffer

The values for all of the buffer attributes (format, packetCapacity, maximumPacketSize, byteLength, packetCount, byteLength) are the same on both ends of the conversion.


Solution

  • It turns out that, for some reason, converting AVAudioCompressedBuffer that way fails to include the buffer's packetDescriptions. These are stored as a C-Style array of AudioStreamPacketDescriptions structs in the buffer. By creating a Codable struct (PacketDescription) and mapping the descriptions objects separately the reassembled buffer worked as expected.

    var packetDescriptions = [PacketDescription]()
            
    for index in 0..<compressedBuffer.packetCount {
       if let packetDescription = compressedBuffer.packetDescriptions?[Int(index)] {
               packetDescriptions.append(
                            .init(mStartOffset: packetDescription.mStartOffset,
                                  mVariableFramesInPacket: packetDescription.mVariableFramesInPacket,
                                  mDataByteSize: packetDescription.mDataByteSize))
                }
            }
    
    packetDescriptions?.enumerated().forEach { index, element in
           compressedBuffer.packetDescriptions?[index] = AudioStreamPacketDescription(mStartOffset: element.mStartOffset!,
                                                                                           mVariableFramesInPacket: element.mVariableFramesInPacket!,
                                                                                           mDataByteSize: element.mDataByteSize!)
            }