内存中的mp3数据如何加载到Swift中的AVAudioPCMBuffer中?

How can mp3 data in memory be loaded into an AVAudioPCMBuffer in Swift?

我有一个 class 方法来将 mp3 文件读入 AVAudioPCMBuffer,如下所示:

private(set) var fullAudio: AVAudioPCMBuffer?

func initAudio(audioFileURL: URL) -> Bool {
    var status = true
    
    do {
        let audioFile = try AVAudioFile(forReading: audioFileURL)
        let audioFormat = audioFile.processingFormat
        let audioFrameLength = UInt32(audioFile.length)

        fullAudio = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: audioFrameLength)

        if let fullAudio = fullAudio {
            try audioFile.read(into: fullAudio)

            // processing of full audio
        }
    } catch {
        status = false
    }
    
    return status
}

但是,我现在需要能够在不使用文件系统的情况下将相同的 mp3 信息从内存(而不是文件)读取到 AVAudioPCMBuffer 中,其中信息保存在数据类型中,例如使用

形式的函数声明
func initAudio(audioFileData: Data) -> Bool {
    // some code setting up fullAudio
}

如何做到这一点?我查看是否有从保存 mp3 信息的数据到 AVAudioPCMBuffer 的路由(例如通过 AVAudioBuffer 或 AVAudioCompressedBuffer),但还没有找到前进的方向。

我在这个问题上掉进了兔子洞。这可能相当于 Rube Goldberg 式的解决方案:

很多痛苦来自使用 Swift 中的 C。

func data_AudioFile_ReadProc(_ inClientData: UnsafeMutableRawPointer, _ inPosition: Int64, _ requestCount: UInt32, _ buffer: UnsafeMutableRawPointer, _ actualCount: UnsafeMutablePointer<UInt32>) -> OSStatus {
    let data = inClientData.assumingMemoryBound(to: Data.self).pointee
    let bufferPointer = UnsafeMutableRawBufferPointer(start: buffer, count: Int(requestCount))
    let copied = data.copyBytes(to: bufferPointer, from: Int(inPosition) ..< Int(inPosition) + Int(requestCount))
    actualCount.pointee = UInt32(copied)
    return noErr
}

func data_AudioFile_GetSizeProc(_ inClientData: UnsafeMutableRawPointer) -> Int64 {
    let data = inClientData.assumingMemoryBound(to: Data.self).pointee
    return Int64(data.count)
}

extension Data {
    func convertedTo(_ format: AVAudioFormat) -> AVAudioPCMBuffer? {
        var data = self

        var af: AudioFileID? = nil
        var status = AudioFileOpenWithCallbacks(&data, data_AudioFile_ReadProc, nil, data_AudioFile_GetSizeProc(_:), nil, 0, &af)
        guard status == noErr, af != nil else {
            return nil
        }

        defer {
            AudioFileClose(af!)
        }

        var eaf: ExtAudioFileRef? = nil
        status = ExtAudioFileWrapAudioFileID(af!, false, &eaf)
        guard status == noErr, eaf != nil else {
            return nil
        }

        defer {
            ExtAudioFileDispose(eaf!)
        }

        var clientFormat = format.streamDescription.pointee
        status = ExtAudioFileSetProperty(eaf!, kExtAudioFileProperty_ClientDataFormat, UInt32(MemoryLayout.size(ofValue: clientFormat)), &clientFormat)
        guard status == noErr else {
            return nil
        }

        if let channelLayout = format.channelLayout {
            var clientChannelLayout = channelLayout.layout.pointee
            status = ExtAudioFileSetProperty(eaf!, kExtAudioFileProperty_ClientChannelLayout, UInt32(MemoryLayout.size(ofValue: clientChannelLayout)), &clientChannelLayout)
            guard status == noErr else {
                return nil
            }
        }

        var frameLength: Int64 = 0
        var propertySize: UInt32 = UInt32(MemoryLayout.size(ofValue: frameLength))
        status = ExtAudioFileGetProperty(eaf!, kExtAudioFileProperty_FileLengthFrames, &propertySize, &frameLength)
        guard status == noErr else {
            return nil
        }

        guard let pcmBuffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(frameLength)) else {
            return nil
        }

        let bufferSizeFrames = 512
        let bufferSizeBytes = Int(format.streamDescription.pointee.mBytesPerFrame) * bufferSizeFrames
        let numBuffers = format.isInterleaved ? 1 : Int(format.channelCount)
        let numInterleavedChannels = format.isInterleaved ? Int(format.channelCount) : 1
        let audioBufferList = AudioBufferList.allocate(maximumBuffers: numBuffers)
        for i in 0 ..< numBuffers {
            audioBufferList[i] = AudioBuffer(mNumberChannels: UInt32(numInterleavedChannels), mDataByteSize: UInt32(bufferSizeBytes), mData: malloc(bufferSizeBytes))
        }

        defer {
            for buffer in audioBufferList {
                free(buffer.mData)
            }
            free(audioBufferList.unsafeMutablePointer)
        }

        while true {
            var frameCount: UInt32 = UInt32(bufferSizeFrames)
            status = ExtAudioFileRead(eaf!, &frameCount, audioBufferList.unsafeMutablePointer)
            guard status == noErr else {
                return nil
            }

            if frameCount == 0 {
                break
            }

            let src = audioBufferList
            let dst = UnsafeMutableAudioBufferListPointer(pcmBuffer.mutableAudioBufferList)

            if src.count != dst.count {
                return nil
            }

            for i in 0 ..< src.count {
                let srcBuf = src[i]
                let dstBuf = dst[i]
                memcpy(dstBuf.mData?.advanced(by: Int(dstBuf.mDataByteSize)), srcBuf.mData, Int(srcBuf.mDataByteSize))
            }

            pcmBuffer.frameLength += frameCount
        }

        return pcmBuffer
    }
}

更强大的解决方案可能会读取采样率和通道数并提供保留它们的选项。

测试使用:

let url = URL(fileURLWithPath: "/tmp/test.mp3")
let data = try! Data(contentsOf: url)

let format = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 44100, channels: 1, interleaved: false)!
if let d = data.convertedTo(format) {
    let avf = try! AVAudioFile(forWriting: URL(fileURLWithPath: "/tmp/foo.wav"), settings: format.settings, commonFormat: format.commonFormat, interleaved: format.isInterleaved)
    try! avf.write(from: d)
}