Android 上的作品剪辑声音,发自 IOS

Clipping sound with opus on Android, sent from IOS

我正在 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)

我的录音回调码是:

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 个数组附加在一起以获得更大的帧大小(需要 640) Android 但我只编码 640在 TPCircularBuffer 的帮助下。

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

samples.count))



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


    let myData = encodedData!.withUnsafeBufferPointer {
        Data(buffer: [=14=])
    }

    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)")
}

在上面的代码中,我正在对 640 frameSize 进行编码并将其添加到 ProtoBuf 有效负载并通过 UDP 发送它。

在 Android 方面,我正在解析 Protobuf 并解码 640 帧大小并使用 AudioTrack.There 播放它在 android 方面没有问题,因为我已经录制和播放了声音使用 Android 但是当我通过 IOS 录制声音并通过 Android Side.

播放时出现问题

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

很有帮助。

我已经根据您的建议更新了我的代码,删除了委托数组连接,但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: [=15=])
                }

                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;

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

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