【问题标题】:Clipping sound with opus on Android, sent from IOS在 Android 上使用 opus 剪辑声音,从 IOS 发送
【发布时间】:2020-03-14 19:21:36
【问题描述】:

我正在从 audioUnit 在 IOS 中录制音频,使用 opus 对字节进行编码并通过 UDP 将其发送到 android 端。 问题是播放的声音有点剪辑。我还通过将原始数据从 IOS 发送到 Android 来测试声音,它播放完美。

我的 AudioSession 代码是

      try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [.defaultToSpeaker])
        try audioSession.setPreferredIOBufferDuration(0.02)
        try audioSession.setActive(true)

我的录音回调代码是:

func performRecording(
    _ ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
    inTimeStamp: UnsafePointer<AudioTimeStamp>,
    inBufNumber: UInt32,
    inNumberFrames: UInt32,
    ioData: UnsafeMutablePointer<AudioBufferList>) -> OSStatus
 {
var err: OSStatus = noErr

err = AudioUnitRender(audioUnit!, ioActionFlags, inTimeStamp, 1, inNumberFrames, ioData)

if let mData = ioData[0].mBuffers.mData {
    let ptrData = mData.bindMemory(to: Int16.self, capacity: Int(inNumberFrames))
    let bufferPtr = UnsafeBufferPointer(start: ptrData, count: Int(inNumberFrames))

    count += 1
    addedBuffer += Array(bufferPtr)

    if count == 2 {

        let _ = TPCircularBufferProduceBytes(&circularBuffer, addedBuffer, UInt32(addedBuffer.count * 2))

        count = 0
        addedBuffer = []

        let buffer = TPCircularBufferTail(&circularBuffer, &availableBytes)

        memcpy(&targetBuffer, buffer, Int(min(bytesToCopy, Int(availableBytes))))

        TPCircularBufferConsume(&circularBuffer, UInt32(min(bytesToCopy, Int(availableBytes))))

        self.audioRecordingDelegate(inTimeStamp.pointee.mSampleTime / Double(16000), targetBuffer)


    }
}
return err;
 }

这里我得到 inNumberOfFrames 几乎 341,我将 2 个数组附加在一起以获得更大的 Android 帧大小(需要 640),但我仅在 TPCircularBuffer 的帮助下编码 640。

func gotSomeAudio(timeStamp: Double, samples: [Int16]) {

samples.count))



    let encodedData = opusHelper?.encodeStream(of: samples)
OPUS_SET_BITRATE_REQUEST)


    let myData = encodedData!.withUnsafeBufferPointer {
        Data(buffer: $0)
    }

    var protoModel = ProtoModel()
    seqNumber += 1
    protoModel.sequenceNumber = seqNumber
    protoModel.timeStamp = Date().currentTimeInMillis()
    protoModel.payload = myData

    DispatchQueue.global().async {
        do {
            try self.tcpClient?.send(data: protoModel)
        } catch {
            print(error.localizedDescription)
        }
    }
    let diff = CFAbsoluteTimeGetCurrent() - start
                             print("Time diff is \(diff)")
}

在上面的代码中,我将 opus 编码为 640 帧大小并将其添加到 ProtoBuf 有效负载并通过 UDP 发送。

在 Android 端,我正在解析 Protobuf 并解码 640 帧大小并使用 AudioTrack 播放它。Android 端没有问题,因为我仅使用 Android 录制和播放了声音,但是当我通过 IOS 录制声音时出现问题并通过 Android 端播放。

请不要建议通过设置 Preferred IO Buffer Duration 来增加 frameSize。我想这样做而不改变它。

https://stackoverflow.com/a/57873492/12020007 很有帮助。

https://stackoverflow.com/a/58947295/12020007 我已根据您的建议更新了我的代码,删除了委托数组串联,但在 android 端仍有剪辑。我还计算了编码字节所需的时间,大约为 2-3 毫秒。

更新的回调代码是

var err: OSStatus = noErr
        // we are calling AudioUnitRender on the input bus of AURemoteIO
        // this will store the audio data captured by the microphone in ioData
        err = AudioUnitRender(audioUnit!, ioActionFlags, inTimeStamp, 1, inNumberFrames, ioData)

        if let mData = ioData[0].mBuffers.mData {

            _ = TPCircularBufferProduceBytes(&circularBuffer, mData, inNumberFrames * 2)

            print("mDataByteSize: \(ioData[0].mBuffers.mDataByteSize)")
            count += 1

            if count == 2 {

                count = 0

                let buffer = TPCircularBufferTail(&circularBuffer, &availableBytes)

                memcpy(&targetBuffer, buffer, min(bytesToCopy, Int(availableBytes)))

                TPCircularBufferConsume(&circularBuffer, UInt32(min(bytesToCopy, Int(availableBytes))))

                let encodedData = opusHelper?.encodeStream(of: targetBuffer)


                let myData = encodedData!.withUnsafeBufferPointer {
                    Data(buffer: $0)
                }

                var protoModel = ProtoModel()
                seqNumber += 1
                protoModel.sequenceNumber = seqNumber
                protoModel.timeStamp = Date().currentTimeInMillis()
                protoModel.payload = myData

                    do {
                        try self.udpClient?.send(data: protoModel)
                    } catch {
                        print(error.localizedDescription)
                    }

            }

        }
        return err;

【问题讨论】:

  • 您应该监听应用程序/硬件状态的变化,并且您可以为每个状态设置自定义帧大小。
  • 试试什么? @AbdulRahmanAyub 请坚持主题。
  • @shyayan 请增加frameSize,如果问题解决了告诉我

标签: ios swift core-audio audiounit opus


【解决方案1】:

您的代码在音频回调中执行 Swift 内存分配(数组连接)和 Swift 方法调用(您的录制委托)。 Apple(在音频的 WWDC 会议中)建议不要在实时音频回调上下文中进行任何内存分配或方法调用(尤其是在请求较短的首选 IO 缓冲区持续时间时)。坚持使用 C 函数调用,例如 memcpy 和 TPCircularBuffer。

补充:另外,不要丢弃样本。如果您获得 680 个样本,但一个数据包只需要 640 个,则保留 40 个“剩余”样本并将它们附加在后面的数据包前面。循环缓冲区将为您保存它们。冲洗并重复。当您积累了足够的数据包时,发送您从音频回调中获得的所有样本,或者当您最终积累 1280 (2*640) 或更多时,发送另一个数据包。

【讨论】:

  • 我已根据您的回答更新了我的代码,但在 android 上仍然有剪辑声音。在没有作品编码的情况下,原始数据可以完美播放。问题是我们得到了 341-342 字节,但对于 opus 编码,我们正好需要 640 字节。你能帮帮我吗?
  • 我在重复 AUTest[1096:344695] 55: EXCEPTION (-1): "" 的录制过程中也遇到了一个奇怪的异常,但它似乎对我的录制没有影响。
  • 不推荐在音频回调中编码数据和发送网络数据。这些活动可以管理内存、调用方法和获取锁。所有这些都应该在一个单独的轮询线程中完成。
  • 感谢您的所有帮助 hotpaw2,因为您在回答中添加了不丢弃剩余的 40 个样本,我正在这样做,因为下次会附加剩余的样本。我同意你的观点,不应该在这里进行编码和网络活动,但你能解释一下关于单独的轮询线程的更多信息吗,因为我有一个限制,即为编码器组合 2 个音频包。
  • 在我的测试中,编码器可能是问题,因为回调会不断生成数据,但编码器无法赶上并在另一个回调到来时简单地跳过数据,导致在 android 上剪辑边。没有持续的剪辑,但它有时只是在两者之间剪辑,这让我相信,编码器很慢。您能否指导我以某种方式使回调和编码器彼此独立,或者如您所说,使用单独的轮询线程,您能再指导我一点吗?
猜你喜欢
  • 2020-01-07
  • 2011-09-07
  • 2011-01-20
  • 1970-01-01
  • 2012-10-20
  • 1970-01-01
  • 2014-08-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多