【问题标题】:Distorted audio when playing raw PCM data buffer in iOS在 iOS 中播放原始 PCM 数据缓冲区时音频失真
【发布时间】:2021-07-02 00:42:46
【问题描述】:

我正在尝试使用 iOS 中的音频单元播放音频缓冲区。音频缓冲区来自一个 C 库,该库从 Playstation 4 接收音频并使用 Opus 对其进行解码。音频缓冲区的格式是 PCM int16_t。

有了 Audio Unit 和 TPCircularBuffer,我就可以让它播放声音了。然而,它严重扭曲且不干净。

这是我的音频单元类的设置

init() {
    _TPCircularBufferInit(&buffer, 960
                          , MemoryLayout<TPCircularBuffer>.size)
    setupAudio()
}

func play(data: NSMutableData) {
    TPCircularBufferProduceBytes(&buffer, data.bytes, UInt32(data.length))
}

private func setupAudio() {
    do {
        try AVAudioSession.sharedInstance().setActive(true, options: [])
    } catch { }
    
    var audioComponentDesc = AudioComponentDescription(componentType: OSType(kAudioUnitType_Output),
                                                       componentSubType: OSType(kAudioUnitSubType_RemoteIO),
                                                       componentManufacturer: OSType(kAudioUnitManufacturer_Apple),
                                                       componentFlags: 0, componentFlagsMask: 0)
    let inputComponent = AudioComponentFindNext(nil, &audioComponentDesc)
    status = AudioComponentInstanceNew(inputComponent!, &audioUnit)
    
    if status != noErr {
        print("Audio Component Instance New Error \(status.debugDescription)")
    }
    
    var audioDescription = AudioStreamBasicDescription()
    audioDescription.mSampleRate = 48000
    audioDescription.mFormatID = kAudioFormatLinearPCM
    audioDescription.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger
    audioDescription.mChannelsPerFrame = 2
    audioDescription.mFramesPerPacket = 1
    audioDescription.mBitsPerChannel = 16
    audioDescription.mBytesPerFrame = (audioDescription.mBitsPerChannel / 8) * audioDescription.mChannelsPerFrame
    audioDescription.mBytesPerPacket = audioDescription.mBytesPerFrame * audioDescription.mFramesPerPacket
    audioDescription.mReserved = 0
    status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat,
                                  kAudioUnitScope_Input, 0, &audioDescription,
                                  UInt32(MemoryLayout<AudioStreamBasicDescription>.size))
    
    if status != noErr {
        print("Enable IO for playback error \(status.debugDescription)")
    }
    
    var flag: UInt32 = 0
    status = AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO,
                                  kAudioUnitScope_Input, 1, &flag,
                                  UInt32(MemoryLayout.size(ofValue: flag)))
    
    if status != noErr {
        print("Enable IO for playback error \(status.debugDescription)")
    }
    
    var outputCallbackStruct = AURenderCallbackStruct()
    outputCallbackStruct.inputProc = performPlayback
    outputCallbackStruct.inputProcRefCon = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
    status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback,
                                  kAudioUnitScope_Global, 0, &outputCallbackStruct,
                                  UInt32(MemoryLayout<AURenderCallbackStruct>.size))
    status = AudioUnitInitialize(audioUnit)
    if status != noErr {
        print("Failed to initialize audio unit \(status!)")
    }
    status = AudioOutputUnitStart(audioUnit)
    if status != noErr {
        print("Failed to initialize output unit \(status!)")
    }
}

播放功能

private func performPlayback(clientData: UnsafeMutableRawPointer,_ ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>, inTimeStamp: UnsafePointer<AudioTimeStamp>,
                         inBufNumber: UInt32, inNumberFrames: UInt32, ioData: UnsafeMutablePointer<AudioBufferList>?) -> OSStatus {
let player = Unmanaged<AudioPlayer>.fromOpaque(UnsafeRawPointer(clientData)!).takeUnretainedValue()
let buffer = ioData![0].mBuffers
let bytesToCopy = ioData![0].mBuffers.mDataByteSize
var bufferTail: UnsafeMutableRawPointer?
var availableBytes: UInt32 = 0
bufferTail = TPCircularBufferTail(&player.buffer, &availableBytes)
let bytesToWrite = min(bytesToCopy, availableBytes)
memcpy(buffer.mData, bufferTail, Int(bytesToWrite))
TPCircularBufferConsume(&player.buffer, bytesToWrite)
return noErr}

这里是我调用播放函数的地方

private func StreamAudioFrameCallback(buffers: UnsafeMutablePointer<Int16>?,
                                  samplesCount: Int, user: UnsafeMutableRawPointer?) {
let decodedData = NSMutableData()
if let buffer = buffers, samplesCount > 0 {
    let decodedDataSize = samplesCount * MemoryLayout<opus_int16>.size
    decodedData.append(buffer, length: decodedDataSize)
    AudioPlayer.shared.play(data: decodedData)
}

有人熟悉这个吗?任何帮助表示赞赏。

【问题讨论】:

  • 我会确认输入音频是否良好,方法是使用其他工具播放或将其转储到文件中,然后将该文件复制到您的计算机以使用已知良好的工具播放它
  • @ScottStensland 你知道将数据缓冲区(从 pcm)写入文件的好方法吗?
  • 一个 WAV 文件由两部分组成,一个标头,后跟一个有效负载 ... 一个 44 字节的标头,其中存储位深度和采样率等属性 ... 有效负载是 PCM 格式的原始音频...所以只需使用库以 WAV 格式写入输出文件 ...出于您的目的,不要过于重视尝试正确指定填充 44 字节标头的所有细节...只要它宽松您正确存储位深度和采样率
  • @ScottStensland 非常感谢您的回复。我猜对了。在我解码的数据大小中,我将其设置为 int16 size * samples count 这是不正确的。我需要改用 int32 大小。现在我可以很好地播放音频,但是仍然有一些小的噼啪声,有什么建议吗?我仍然使用原始 PCM 数据缓冲区直接播放音频,没有添加 WAV 标头。

标签: ios swift audio audiounit opus


【解决方案1】:

您可能不想开始播放,直到循环缓冲区中的内容超过了覆盖传入数据速率中的最大时间抖动所需的数量。尝试等到循环缓冲区中有半秒钟的音频,然后再开始播放。然后试验填充量。

【讨论】:

  • 谢谢。我根据您的回答完成了。
猜你喜欢
  • 1970-01-01
  • 2013-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-28
  • 2014-10-11
  • 1970-01-01
相关资源
最近更新 更多