Я хочу вызывать 20 раз в секунду installTapOnBus:bufferSize:format:block:

Я хочу отображать сигнал в режиме реального времени с микрофона. Я был реализован с использованием installTapOnBus:bufferSize:format:block:, Эта функция вызывается три раза в секунду. Я хочу, чтобы эта функция вызывалась 20 раз в секунду. Где я могу установить?

AVAudioSession *audioSession = [AVAudioSession sharedInstance];

NSError* error = nil;
if (audioSession.isInputAvailable) [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
if(error){
    return;
}

[audioSession setActive:YES error:&error];
if(error){
    retur;
}

self.engine = [[[AVAudioEngine alloc] init] autorelease];

AVAudioMixerNode* mixer = [self.engine mainMixerNode];
AVAudioInputNode* input = [self.engine inputNode];
[self.engine connect:input to:mixer format:[input inputFormatForBus:0]];

// tap ... 1 call in 16537Frames
// It does not change even if you change the bufferSize
[input installTapOnBus:0 bufferSize:4096 format:[input inputFormatForBus:0] block:^(AVAudioPCMBuffer* buffer, AVAudioTime* when) {

    for (UInt32 i = 0; i < buffer.audioBufferList->mNumberBuffers; i++) {
        Float32 *data = buffer.audioBufferList->mBuffers[i].mData;
        UInt32 frames = buffer.audioBufferList->mBuffers[i].mDataByteSize / sizeof(Float32);

        // create waveform
        ...
    }
}];

[self.engine startAndReturnError:&error];
if (error) {
    return;
}

4 ответа

Говорят, что поддержка Apple ответила нет: (сентябрь 2014)

Да, в настоящее время у нас есть фиксированный размер буфера крана (0,375 с), и указанный клиентом размер буфера для крана не действует.

но кто-то изменяет размер буфера и получает 40 мс. https://devforums.apple.com/thread/249510?tstart=0

Не могу это проверить, neen в ObjC:(

UPD это работает! только одна строка:

    [input installTapOnBus:0 bufferSize:1024 format:[mixer outputFormatForBus:0] block:^(AVAudioPCMBuffer *buffer, AVAudioTime *when) {
    buffer.frameLength = 1024; //here

Ссылка на класс AVAudioNode гласит, что реализация может выбрать размер буфера, отличный от того, который вы предоставляете, поэтому, насколько я знаю, мы застряли с очень большим размером буфера. Это прискорбно, потому что AVAudioEngine является отличной оболочкой Core Audio. Поскольку мне тоже нужно использовать входной касатель для чего-то другого, кроме записи, я рассматриваю в качестве альтернативы The Amazing Audio Engine, а также Core Audio C API (см. IBook Learning Core Audio для отличных учебников по нему).

*** Обновление: оказывается, что вы можете получить доступ к AudioUnit AVAudioInputNode и установить на него обратный вызов рендеринга. С помощью AVAudioSession вы можете установить желаемый размер буфера вашего аудио-сеанса (не гарантируется, но, безусловно, лучше, чем нажатие узлов). До сих пор я получил размеры буфера всего 64 выборки, используя этот подход. Я вернусь сюда с кодом, как только у меня будет возможность проверить это.

Начиная с iOS 13 в 2019 году, существует AVAudioSinkNode, который может лучше выполнить то, что вы ищете. Хотя вы могли бы также создать обычный AVAudioUnit / Node и присоединить его к входу / выходу, разница с AVAudioSinkNode заключается в том, что вывод не требуется. Это делает его более похожим на касание и позволяет обойти проблемы с неполными цепями, которые могут возникнуть при использовании обычного аудиоустройства / узла.

Для дополнительной информации:

Соответствующий код Swift находится на странице 10 (с небольшой ошибкой, исправленной ниже) PDF-файла сессии.

// Create Engine
let engine = AVAudioEngine()
// Create and Attach AVAudioSinkNode
let sinkNode = AVAudioSinkNode() { (timeStamp, frames, audioBufferList) ->
OSStatus in
 …
}
engine.attach(sinkNode) 

Я полагаю, что вам все равно придется следовать типичным правилам аудио в реальном времени при использовании этого (например, без выделения / освобождения памяти, без вызовов ObjC, без блокировки или ожидания блокировок и т. Д.). Кольцевой буфер все еще может быть полезен здесь.

Не знаю почему или даже если это работает, просто пробую несколько вещей. Но наверняка NSLogs указывают 21-миллисекундный интервал, 1024 выборки, приходящих на буфер...

        AVAudioEngine* sEngine = NULL;
        - (void)applicationDidBecomeActive:(UIApplication *)application 
        {
            /*
             Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
             */

            [glView startAnimation];

            AVAudioSession *audioSession = [AVAudioSession sharedInstance];

            NSError* error = nil;
            if (audioSession.isInputAvailable) [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
            if(error){
                return;
            }

            [audioSession setActive:YES error:&error];
            if(error){
                return;
            }

            sEngine = [[AVAudioEngine alloc] init];

            AVAudioMixerNode* mixer = [sEngine mainMixerNode];
            AVAudioInputNode* input = [sEngine inputNode];
            [sEngine connect:input to:mixer format:[input inputFormatForBus:0]];

            __block NSTimeInterval start = 0.0;

            // tap ... 1 call in 16537Frames
            // It does not change even if you change the bufferSize
            [input installTapOnBus:0 bufferSize:1024 format:[input inputFormatForBus:0] block:^(AVAudioPCMBuffer* buffer, AVAudioTime* when) {

                if (start == 0.0)
                    start = [AVAudioTime secondsForHostTime:[when hostTime]];

                // why does this work? because perhaps the smaller buffer is reused by the audioengine, with the code to dump new data into the block just using the block size as set here?
                // I am not sure that this is supported by apple?
                NSLog(@"buffer frame length %d", (int)buffer.frameLength);
                buffer.frameLength = 1024;
                UInt32 frames = 0;
                for (UInt32 i = 0; i < buffer.audioBufferList->mNumberBuffers; i++) {
                    Float32 *data = buffer.audioBufferList->mBuffers[i].mData;
                    frames = buffer.audioBufferList->mBuffers[i].mDataByteSize / sizeof(Float32);
                    // create waveform
                    ///
                }
                NSLog(@"%d frames are sent at %lf", (int) frames, [AVAudioTime secondsForHostTime:[when hostTime]] - start);
            }];

            [sEngine startAndReturnError:&error];
            if (error) {
                return;
            }

        }

Вы могли бы использовать CADisplayLink для достижения этой цели. CADisplayLink будет давать вам обратный вызов каждый раз, когда обновляется экран, что обычно будет намного больше, чем 20 раз в секунду (поэтому может потребоваться дополнительная логика для ограничения или ограничения количества выполнений вашего метода в вашем случае).

Это, очевидно, решение, которое совершенно не зависит от вашей аудио-работы, и если вам требуется решение, которое отражает вашу сессию, оно может не работать. Но когда нам нужны частые повторяющиеся обратные вызовы на iOS, это часто - предпочтительный подход, так что это идея.

Другие вопросы по тегам