使用 AudioUnit 的精确计时器

Posted

技术标签:

【中文标题】使用 AudioUnit 的精确计时器【英文标题】:Accurate timer using AudioUnit 【发布时间】:2015-07-05 19:21:44 【问题描述】:

我正在尝试制作一个准确的计时器来分析输入。我希望能够测量~200ms 信号中的 1% 偏差。 我的理解是使用 AudioUnit 将能够获得 Stefan Popp's example 实现代码 在更新了一些东西以使其在 xcode 6.3 上运行之后,我的示例正在运行,但是:

    虽然我最终确实想要捕获音频,但我认为应该有某种方法来获取通知,例如 NSTimer,所以我尝试了 AudioUnitAddRenderNotify,但它完全按照它所说的那样 - 即它与渲染,而不仅仅是一个任意的计时器。有什么方法可以触发回调而无需录制或播放?

    当我检查 mSampleTime 时,我发现切片之间的间隔与 inNumberFrames - 512 - 相匹配,结果为 11.6 毫秒。我看到录制和播放的间隔相同。我需要更多的分辨率。

我尝试使用 kAudiosessionProperty_PreferredHardwareIOBufferDuration,但我能找到的所有示例都使用了已弃用的 AudioSessions,所以我尝试转换为 AudioUnits:

Float32 preferredBufferSize = .001; // in seconds
status = AudioUnitSetProperty(audioUnit, kAudioSessionProperty_PreferredHardwareIOBufferDuration, kAudioUnitScope_Output, kOutputBus, &preferredBufferSize, sizeof(preferredBufferSize));

但我得到 OSStatus -10879, kAudioUnitErr_InvalidProperty。

然后我尝试了值为 128 和 256 的 kAudioUnitProperty_MaximumFramesPerSlice,但 inNumberFrames 始终为 512。

UInt32 maxFrames = 128;
status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFrames, sizeof(maxFrames));

[编辑] 我正在尝试将输入的时间(用户选择的 MIDI 或麦克风)与应该的时间进行比较。具体来说,乐器是在节拍/节拍器之前还是之后演奏的?演奏多少?这是针对音乐家的,而不是游戏,因此需要精确。

[编辑] 答案似乎对事件有反应。即他们让我准确地看到事情发生的时间,但是我看不到我是如何准确地事情的。不清楚是我的错。我的应用程序也需要成为节拍器——同步播放节拍上的点击并在节拍上闪烁一个点——然后我可以分析用户的动作以比较时间。但如果我不能准确地演奏节拍,其余的就会崩溃。也许我应该录制音频——即使我不想要它——只是为了从回调中获取 inTimeStamp?

[编辑] 目前我的节拍器是:

- (void) setupAudio

    AVAudioPlayer *audioPlayer;
    NSString *path = [NSString stringWithFormat:@"%@/click.mp3", [[NSBundle mainBundle] resourcePath]];
    NSURL *soundUrl = [NSURL fileURLWithPath:path];
    audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundUrl error:nil];
    [audioPlayer prepareToPlay];

    CADisplayLink *syncTimer;
    syncTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(syncFired:)];
    syncTimer.frameInterval = 30;
    [syncTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];


-(void)syncFired:(CADisplayLink *)displayLink

    [audioPlayer play];
   

【问题讨论】:

此时您的音频播放效果如何?是迷笛吗?还是您手动缓冲音频?有很多 API 可供选择。如果你要做一个卡拉 OK 风格的应用程序,那么你肯定需要从里到外学习核心音频,这需要一段时间。如果您正在做一些更简单的事情,例如检测用户与点击的距离,那么您可以结合使用核心音频和MusicPlayer API 【参考方案1】:

您应该使用循环缓冲区,并在与您自己的计时器上所需的帧数相匹配的块中对信号执行分析。为此,您设置了一个渲染回调,然后在回调中为您的循环缓冲区提供输入音频。然后您设置自己的计时器,该计时器将从缓冲区的尾部拉出并进行分析。这样,您可以每 0.23 秒为缓冲区提供 1024 帧,并且您的分析计时器可能每 0.000725 秒触发一次并分析 32 个样本。 Here 是关于循环缓冲区的相关问题。

编辑

要使用环形缓冲区获得精确计时,您还可以存储与音频缓冲区对应的时间戳。我使用TPCircularBuffer 来做到这一点。 TPCircularBufferPrepareEmptyAudioBufferList、TPCircularBufferProduceAudioBufferList 和 TPCircularBufferNextBufferList 将从环形缓冲区复制和检索音频缓冲区和时间戳。然后当您进行分析时,每个缓冲区都会有一个时间戳对应,无需在渲染线程中完成所有工作,并允许您选择分析窗口。

【讨论】:

我得到了数学和环形缓冲区,但我不明白如何快速安排我的分析计时器。如何每 0.000725 秒安排一次回调?谢谢。 只需使用 NSTimer [NSTimer scheduledTimerWithTimeInterval:32 / 44100.0 target:self selector:@selector(analyze) userInfo:nil repeats:1]; 然后有一个分析方法,从环形缓冲区中提取。 -(void)analyze //do analysis NSTimer 只适用于大约 50-100 毫秒。 CADisplayLink 适用于大约 1/60 秒 = 16.7 毫秒。有时更好有时更糟。 developer.apple.com/library/ios/documentation/Cocoa/Reference/… 您想要做的究竟是什么需要一个准确的计时器?环形缓冲区的好处是您可以将渲染回调与分析分离。您的 NSTimer 应该检查环形缓冲区中是否有足够的数据来进行分析,如果没有就跳过它。您无法比硬件更快地获得音频。如果您确实需要一个准确的计时器,您应该编辑您的问题以包括您正在尝试做的需要一个计时器。【参考方案2】:

如果您使用互相关和/或峰值检测器之类的方法在音频样本缓冲区(或包含样本的环形缓冲区)中查找匹配的样本向量,那么您应该能够对尖锐事件之间的样本进行计数以在一个样本内(1/44100 或 0.0226757 毫秒,44.1k Hz 采样率),加上或减去一些时间估计误差。对于相隔多个音频单元缓冲区的事件,您可以对中间缓冲区内的样本数进行求和并相加,以获得比仅使用(更粗略的)缓冲区计时更精确的时间间隔。

但是,请注意,每个样本缓冲区和扬声器音频输出之间以及麦克风声音接收和缓冲区回调之间存在延迟或延迟。这必须进行测量,因为您可以测量发送样本缓冲区与输入缓冲区自相关估计函数将其取回之间的往返时间。这是硬件缓冲、转换(模拟到数字,反之亦然)和传递数据所需的时间。使用适当的音频会话设置,该延迟可能在 2 到 6 乘以 5.8 毫秒左右,但对于不同的 iOS 设备可能会有所不同。

是的,测量音频最准确的方法是捕获音频并查看实际采样音频流中的数据。

【讨论】:

以上是关于使用 AudioUnit 的精确计时器的主要内容,如果未能解决你的问题,请参考以下文章

Delphi使用TStopwatch计时器精确计时

C++中如何精确计时

如何在VB中做精确度高的计时器??

实战使用 Web Animations API 实现一个精确计时的时钟

使用系统定时器SysTick实现精确延时微秒和毫秒函数

hutool 字符串拼接,数字精确计算,格式化,计时器