【问题标题】:Low latency input/output AudioQueue低延迟输入/输出音频队列
【发布时间】:2015-05-03 08:27:47
【问题描述】:

我有两个 iOS 音频队列 - 一个输入将样本直接馈送到一个输出。不幸的是,有一个非常明显的回声效果:(

是否可以使用 AudioQueues 进行低延迟音频,还是我真的需要使用 AudioUnits? (我尝试过使用 AudioUnits 的 Novocaine 框架,这里的延迟要小得多。我还注意到这个框架似乎使用更少的 CPU 资源。不幸的是,我无法在我的 Swift 项目中使用这个框架,而无需对其进行重大更改.)

这里是我的一些代码摘录,主要是在 Swift 中完成,除了那些需要在 C 中实现的回调。

private let audioStreamBasicDescription = AudioStreamBasicDescription(
    mSampleRate: 16000,
    mFormatID: AudioFormatID(kAudioFormatLinearPCM),
    mFormatFlags: AudioFormatFlags(kAudioFormatFlagsNativeFloatPacked),
    mBytesPerPacket: 4,
    mFramesPerPacket: 1,
    mBytesPerFrame: 4,
    mChannelsPerFrame: 1,
    mBitsPerChannel: 32,
    mReserved: 0)

private let numberOfBuffers = 80
private let bufferSize: UInt32 = 256

private var active = false

private var inputQueue: AudioQueueRef = nil
private var outputQueue: AudioQueueRef = nil

private var inputBuffers = [AudioQueueBufferRef]()
private var outputBuffers = [AudioQueueBufferRef]()
private var headOfFreeOutputBuffers: AudioQueueBufferRef = nil

// callbacks implemented in Swift
private func audioQueueInputCallback(inputBuffer: AudioQueueBufferRef) {
    if active {
        if headOfFreeOutputBuffers != nil {
            let outputBuffer = headOfFreeOutputBuffers
            headOfFreeOutputBuffers = AudioQueueBufferRef(outputBuffer.memory.mUserData)
            outputBuffer.memory.mAudioDataByteSize = inputBuffer.memory.mAudioDataByteSize
            memcpy(outputBuffer.memory.mAudioData, inputBuffer.memory.mAudioData, Int(inputBuffer.memory.mAudioDataByteSize))
            assert(AudioQueueEnqueueBuffer(outputQueue, outputBuffer, 0, nil) == 0)
        } else {
            println(__FUNCTION__ + ": out-of-output-buffers!")
        }

        assert(AudioQueueEnqueueBuffer(inputQueue, inputBuffer, 0, nil) == 0)
    }
}

private func audioQueueOutputCallback(outputBuffer: AudioQueueBufferRef) {
    if active {
        outputBuffer.memory.mUserData = UnsafeMutablePointer<Void>(headOfFreeOutputBuffers)
        headOfFreeOutputBuffers = outputBuffer
    }
}

func start() {
    var error: NSError?
    audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, withOptions: .allZeros, error: &error)
    dumpError(error, functionName: "AVAudioSessionCategoryPlayAndRecord")
    audioSession.setPreferredSampleRate(16000, error: &error)
    dumpError(error, functionName: "setPreferredSampleRate")
    audioSession.setPreferredIOBufferDuration(0.005, error: &error)
    dumpError(error, functionName: "setPreferredIOBufferDuration")

    audioSession.setActive(true, error: &error)
    dumpError(error, functionName: "setActive(true)")

    assert(active == false)
    active = true

    // cannot provide callbacks to AudioQueueNewInput/AudioQueueNewOutput from Swift and so need to interface C functions
    assert(MyAudioQueueConfigureInputQueueAndCallback(audioStreamBasicDescription, &inputQueue, audioQueueInputCallback) == 0)
    assert(MyAudioQueueConfigureOutputQueueAndCallback(audioStreamBasicDescription, &outputQueue, audioQueueOutputCallback) == 0)

    for (var i = 0; i < numberOfBuffers; i++) {
        var audioQueueBufferRef: AudioQueueBufferRef = nil
        assert(AudioQueueAllocateBuffer(inputQueue, bufferSize, &audioQueueBufferRef) == 0)
        assert(AudioQueueEnqueueBuffer(inputQueue, audioQueueBufferRef, 0, nil) == 0)
        inputBuffers.append(audioQueueBufferRef)

        assert(AudioQueueAllocateBuffer(outputQueue, bufferSize, &audioQueueBufferRef) == 0)
        outputBuffers.append(audioQueueBufferRef)

        audioQueueBufferRef.memory.mUserData = UnsafeMutablePointer<Void>(headOfFreeOutputBuffers)
        headOfFreeOutputBuffers = audioQueueBufferRef
    }

    assert(AudioQueueStart(inputQueue, nil) == 0)
    assert(AudioQueueStart(outputQueue, nil) == 0)
}

然后我的 C 代码将回调设置回 Swift:

static void MyAudioQueueAudioInputCallback(void * inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp * inStartTime,
                                   UInt32 inNumberPacketDescriptions, const AudioStreamPacketDescription * inPacketDescs) {
    void(^block)(AudioQueueBufferRef) = (__bridge void(^)(AudioQueueBufferRef))inUserData;
    block(inBuffer);
}

static void MyAudioQueueAudioOutputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {
    void(^block)(AudioQueueBufferRef) = (__bridge void(^)(AudioQueueBufferRef))inUserData;
    block(inBuffer);
}

OSStatus MyAudioQueueConfigureInputQueueAndCallback(AudioStreamBasicDescription inFormat, AudioQueueRef *inAQ, void(^callback)(AudioQueueBufferRef)) {
    return AudioQueueNewInput(&inFormat, MyAudioQueueAudioInputCallback, (__bridge_retained void *)([callback copy]), nil, nil, 0, inAQ);
}

OSStatus MyAudioQueueConfigureOutputQueueAndCallback(AudioStreamBasicDescription inFormat, AudioQueueRef *inAQ, void(^callback)(AudioQueueBufferRef)) {
    return AudioQueueNewOutput(&inFormat, MyAudioQueueAudioOutputCallback, (__bridge_retained void *)([callback copy]), nil, nil, 0, inAQ);
}

【问题讨论】:

    标签: ios swift audiounit audioqueue novocaine


    【解决方案1】:

    一段时间后,我发现 this 使用 AudioUnits 而不是 AudioQueues 的好帖子。我只是将它移植到 Swift,然后简单地添加:

    audioSession.setPreferredIOBufferDuration(0.005, error: &error)
    

    【讨论】:

    • 你能在某处分享 Swift 版本吗?
    • 它是否也解决了延迟问题? CPU 资源呢?
    • @rsp1984 我查看了我的代码并发现了这条评论:“5 ms 的值似乎会在 iPhone 5 上引入约 1% 的 CPU 使用率”。然后我回想起如果我进一步减少它,CPU使用率开始增加更多。当然,延迟无法消除,但已降低到可接受的水平 :) BTW:iPod Touch 上的延迟比 iPhone 5 上的要大。
    • @Jens Schwarzer 非常感谢!
    【解决方案2】:

    如果您正在录制来自麦克风的音频并在该麦克风的听力范围内播放,那么由于音频吞吐量不是瞬时的,您之前的一些输出会进入新的输入,因此会产生回声。这种现象称为feedback

    这是一个结构性问题,因此更改录制 API 将无济于事(尽管更改录制/播放缓冲区大小将使您能够控制回声中的延迟)。您可以以麦克风听不到的方式播放音频(例如,根本听不到,或通过耳机)或进入echo cancellation 的兔子洞。

    【讨论】:

    • 您好,感谢您的意见。不,这不是我遇到问题的反馈。我正在使用耳机 ;) 正如我的问题中提到的,如果我使用 Novocaine 框架,我不会遇到延迟问题(只是非常轻微)。
    猜你喜欢
    • 1970-01-01
    • 2019-09-29
    • 2012-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-28
    • 1970-01-01
    • 2019-01-04
    相关资源
    最近更新 更多