【问题标题】:I want to call 20 times per second the installTapOnBus:bufferSize:format:block:我想每秒调用 20 次 installTapOnBus:bufferSize:format:block:
【发布时间】:2014-11-24 18:35:00
【问题描述】:

我想实时显示麦克风输入的波形。 我已经使用 installTapOnBus:bufferSize:format:block: 实现了,这个函数在一秒钟内被调用了 3 次。 我想将此函数设置为每秒调用 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;
}

【问题讨论】:

    标签: ios objective-c ios8 avfoundation audio-recording


    【解决方案1】:

    从 2019 年的 iOS 13 开始,有了 AVAudioSinkNode,它可能会更好地完成您所寻找的。虽然您也可以创建一个常规的 AVAudioUnit / Node 并将其附加到输入/输出,但与 AVAudioSinkNode 的区别在于不需要输出。这使它更像是一个水龙头,并规避了使用常规音频单元/节点时可能出现的不完整链的问题。

    更多信息:

    相关的 Swift 代码在会话 PDF 的第 10 页(下面纠正了一个小错误)。

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

    我想您在使用它时仍然必须遵循典型的实时音频规则(例如,不分配/释放内存、不调用 ObjC、不锁定或等待锁定等)。环形缓冲区在这里可能仍然有用。

    【讨论】:

    • 可悲的是,您必须以本机设备的格式处理音频(例如处理位深度、字节字节序等)——但这似乎是我唯一能做的现在想想……
    【解决方案2】:

    他们说,Apple 支持回复:(2014 年 9 月)

    是的,目前我们在内部有一个固定的点击缓冲区大小(0.375s), 并且客户端为点击指定的缓冲区大小没有生效。

    但有人调整缓冲区大小并获得 40 毫秒 https://devforums.apple.com/thread/249510?tstart=0

    无法检查,ObjC 中的 neen :(

    UPD 有效!单行:

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

    【讨论】:

    • 分析构建的应用程序似乎表明所讨论的缓冲区是一个环形缓冲区,这意味着只有您指定的 frameLength 内的音频被“消耗”和释放。在缓冲区开始处理之前似乎有短暂的延迟(有问题的 0.375 秒?),但之后它似乎按预期工作。
    • 请看这个链接stackoverflow.com/questions/45538806/…@djdance
    • 这在某些情况下是不安全的;即,如果将数据写入调度队列中的文件。如果由于某种原因出现延迟,下一次调用 installTap 将具有不同的缓冲区对象,但剩余的样本将不存在,因此它们将丢失。
    • 如果查看AVAudioNode的头文件,它描述了支持的缓冲时长范围:@param bufferSize the requested size of the incoming buffers in sample frames. Supported range is [100, 400] ms.
    【解决方案3】:

    不知道为什么或者即使这还有效,只是尝试了一些东西。但是可以肯定的是 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;
                }
    
            }
    

    【讨论】:

    • 这对我不起作用。它报告了buffer frame length 16537
    【解决方案4】:

    您也许可以使用CADisplayLink 来实现此目的。每次屏幕刷新时,CADisplayLink 都会给您一个回调,这通常会超过每秒 20 次(因此在您的情况下,可能需要额外的逻辑来限制或限制您的方法执行的次数)。

    这显然是一个与您的音频工作完全不同的解决方案,如果您需要一个反映您的会话的解决方案,它可能不起作用。但是当我们需要在 iOS 上频繁重复回调时,这通常是首选的方法,所以这是一个想法。

    【讨论】:

    • 谢谢解答,CADisplayLink的回调中是否可以获取AudioBuffer?
    • 您应该能够在初始化后将缓冲区分配为 ivar。
    【解决方案5】:

    AVAudioNode 类参考指出,实现可能会选择与您提供的缓冲区大小不同的缓冲区大小,因此据我所知,我们遇到了非常大的缓冲区大小。这是不幸的,因为 AVAudioEngine 是一个优秀的 Core Audio 包装器。由于我也需要使用输入抽头来进行录音以外的操作,因此我正在研究 The Amazing Audio Engine 以及 Core Audio C API(有关它的优秀教程,请参阅 iBook Learning Core Audio)作为替代方案。

    ***更新:原来你可以访问 AVAudioInputNode 的 AudioUnit 并在其上安装一个渲染回调。通过 AVAudioSession,您可以设置音频会话所需的缓冲区大小(不保证,但肯定比节点抽头更好)。到目前为止,我已经使用这种方法获得了低至 64 个样本的缓冲区大小。一旦我有机会对此进行测试,我将在这里发布代码。

    【讨论】:

    • 很遗憾,我的实验没有成功(在输入节点上安装渲染回调)。我的回调函数被调用,并且我得到了有效的缓冲区,但是所有的样本值都是 0。据推测,输入节点的特殊行为阻止了它的工作。很抱歉无法解决我们的问题并使 AVAudioEngine 为我们的目的工作。我将使用旧 API 实现 C++ 实现...
    • Melodybox,如果你想为你的解决方案走 C/C++ 之路,看看github.com/abbood/Learning-Core-Audio-Book-Code-Sample。附带的书也很值得购买。不确定您使用的是 OS X 还是 iOS,但如果您使用的是 iOS,请查看所述存储库中的 Chapter10_iOSPlayThrough 项目。
    • devforums.apple.com/thread/249510?tstart=0 这段代码很奇怪,因为它似乎在缓冲区就绪回调期间设置了帧大小,并且有效?!
    • 我认为这是个好主意。这当然是减少了点击的回调时间。这是关于内存泄漏的担忧。
    猜你喜欢
    • 2010-10-24
    • 2013-08-22
    • 2018-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-22
    相关资源
    最近更新 更多