IOS AudioUnit播放爆裂问题(swift)

Posted

技术标签:

【中文标题】IOS AudioUnit播放爆裂问题(swift)【英文标题】:IOS AudioUnit playback crackle issue (swift) 【发布时间】:2019-11-11 17:05:41 【问题描述】:

我正在尝试从 ios 中的 android 设备播放来自 UDP 的字节。我正在使用 TPCircularBuffer 播放字节。我的代码如下:

let success = initCircularBuffer(&circularBuffer, 1024)
        if success 
            print("Circular buffer init was successful")
         else 
            print("Circular buffer init not successful")
        



func udpReceive() 
        receivingQueue.async 
            repeat 
                do 
                    let datagram = try self.tcpClient?.receive()
                    let byteData = datagram?["data"] as? Data
                    let dataLength = datagram?["length"] as? Int
                    self.dataLength = dataLength!

                    let _ = TPCircularBufferProduceBytes(&self.circularBuffer, byteData!.bytes, UInt32(dataLength! * MemoryLayout<UInt8>.stride * 2))

                 catch 
                    fatalError(error.localizedDescription)
                
             while true
        


    

    func consumeBuffer() -> UnsafeMutableRawPointer? 
        self.availableBytes = 0
        let tail = TPCircularBufferTail(&self.circularBuffer, &self.availableBytes)
        return tail
    

我们正在以 16K 采样率录制并通过 UDP 从 Android 端发送到 IOS,然后我们使用 AudioUnit 播放我们的字节,但问题是我们的声音中的噼啪声和削波声

播放回调代码:

func performPlayback(
        _ ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
        inTimeStamp: UnsafePointer<AudioTimeStamp>,
        inBufNumber: UInt32,
        inNumberFrames: UInt32,
        ioData: UnsafeMutablePointer<AudioBufferList>
    ) -> OSStatus 

        var buffer = ioData[0].mBuffers             

        let bufferTail = consumeBuffer()

        memcpy(buffer.mData, bufferTail, min(self.dataLength, Int(availableBytes)))
        buffer.mDataByteSize = UInt32(min(self.dataLength, Int(availableBytes)))


        TPCircularBufferConsume(&self.circularBuffer, UInt32(min(self.dataLength, Int(availableBytes))))

        return noErr
    

UDP 每个样本向我们发送 1280 个字节。我们认为问题在于未正确设置的 BUFFER SIZE。谁能指导我如何设置适当的缓冲区大小。确实会有很大帮助。我知道@Gruntcakes 作为 voip 工程师https://***.com/a/57136561/12020007 的工作。我还研究了@hotpaw2 的工作,并正在查看https://***.com/a/58545845/12020007 以检查是否存在线程问题。任何形式的帮助将不胜感激。

【问题讨论】:

【参考方案1】:

音频单元回调应仅返回请求的帧数(样本),如 inNumberFrames 参数所示。您的代码似乎将一些不同数量的样本复制到 AudioBufferList 中,这不起作用,因为 iOS 音频单元只会将请求数量的帧发送到音频输出。

您可以在音频会话配置中建议首选的缓冲持续时间,但这只是一个建议。 iOS 可以随意忽略此建议,并使用与设备的音频硬件、系统活动和当前电源状态更匹配的 inNumberFrames。

不要忘记预先填充足够的循环缓冲区以考虑网络 (UDP) 传输时间中的最大预期抖动。也许测量网络数据包到数据包延迟抖动,并计算其统计数据,最小值,最大值,std.dev。等等

如果您的 UDP 缓冲区的大小不是 2 的幂次方,或者包含的样本不是 iOS 硬件采样率,那么您还必须在安全缓冲开销中考虑小数缓冲区和重采样抖动。

【讨论】:

【参考方案2】:

播放回调代码为:

private let AudioController_RecordingCallback: AURenderCallback = (
    inRefCon,
    ioActionFlags/*: UnsafeMutablePointer<AudioUnitRenderActionFlags>*/,
    inTimeStamp/*: UnsafePointer<AudioTimeStamp>*/,
    inBufNumber/*: UInt32*/,
    inNumberFrames/*: UInt32*/,
    ioData/*: UnsafeMutablePointer<AudioBufferList>*/)
    -> OSStatus

首先我们需要根据我们的采样率和通道数等属性来理解回调,这些东西推断出IOS回调将播放的帧数,如果我们的帧数少或多,则inNumberOfFrames 声音将包含噼啪声和削波声。 该问题的解决方案是 TPCircularBuffer,它将准确地将 inNumberOfFrames 存储到 ioData 以正确播放声音。确保您只复制回调在当时产生的帧数,因为它可能会不时变化。

您的代码的问题是您尝试播放 1280 字节,而回调预期的字节数少于这些字节。

TPCircularBuffer 参考https://github.com/michaeltyson/TPCircularBuffer

【讨论】:

是的,你是对的,我正在尝试播放 1280 字节,但 inNumberOfFrames 从 341 到 342 不等,我尝试了你的解决方案并且只播放了 inNumberOfFrames,它成功了!感谢您的帮助

以上是关于IOS AudioUnit播放爆裂问题(swift)的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 AudioUnit (iOS) 播放信号?

AudioQueueBuffers 之间的爆裂噪音

AudioUnit播放PCM问题

如何正确使用 iOS AudioUnit 渲染回调

使用 AudioUnit IOS 从 nsdata 的服务器流播放语音

iOS 中 AudioUnit Graph 的可重用性