在 Android 上使用 opus 剪辑从 IOS 发送的声音

sha*_*yan 0 core-audio audiounit ios opus swift

我正在 IOS 中从 audioUnit 录制音频,用 opus 编码字节并通过 UDP 将其发送到 android 端。问题是播放的声音有点削波。我还通过将原始数据从 IOS 发送到 Android 来测试声音,效果非常完美。

我的 AudioSession 代码是

      try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [.defaultToSpeaker])
        try audioSession.setPreferredIOBufferDuration(0.02)
        try audioSession.setActive(true)
Run Code Online (Sandbox Code Playgroud)

我的录音回调代码是:

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;
 }
Run Code Online (Sandbox Code Playgroud)

这里我得到的NumberOfFrames几乎是 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)")
}
Run Code Online (Sandbox Code Playgroud)

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

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

请不要建议通过设置首选 IO 缓冲区持续时间来增加帧大小。我想在不改变这个的情况下做到这一点。

/sf/answers/4051144471/这很有帮助。

/sf/answers/4126310681/ 我已经根据您的建议更新了我的代码,删除了委托数组连接,但 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;
Run Code Online (Sandbox Code Playgroud)

hot*_*aw2 5

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

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