2

私は 2 つの iOS AudioQueues を持っています。1 つの入力は、サンプルを 1 つの出力に直接送ります。残念ながら、かなり目立つエコー効果があります:(

AudioQueues を使用して低遅延のオーディオを実行することは可能ですか、それとも本当に AudioUnits を使用する必要がありますか? (私は AudioUnits を使用する Novocaine フレームワークを試しましたが、ここではレイテンシーがはるかに小さくなっています。また、このフレームワークは CPU リソースの使用量が少ないように見えることにも気付きました。残念ながら、大きな変更を加えなければ、私の Swift プロジェクトでこのフレームワークを使用することはできませんでした。 .)

これは、C で実装する必要があるコールバックを除いて、主に Swift で行われる私のコードの抜粋です。

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

そして、コールバックを Swift に戻すように設定する C コード:

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);
}
4

2 に答える 2

2

しばらくして、AudioQueues の代わりに AudioUnits を使用しているこの素晴らしい投稿を見つけました。私はそれをSwiftに移植してから、単純に追加しました:

audioSession.setPreferredIOBufferDuration(0.005, error: &error)
于 2015-05-29T14:20:58.483 に答える
1

マイクからオーディオを録音し、そのマイクの耳元で再生する場合、オーディオ スループットが瞬間的ではないため、以前の出力の一部が新しい入力になり、エコーが発生します。この現象はフィードバックと呼ばれます。

これは構造上の問題であるため、記録 API を変更しても効果はありません (ただし、記録/再生バッファ サイズを変更すると、エコーの遅延を制御できます)。マイクが聞こえないように (たとえば、まったく聞こえない、またはヘッドフォンを介して) オーディオを再生するか、エコー キャンセレーションのうさぎの穴を下ります。

于 2015-05-04T00:20:05.087 に答える