我正在尝试编写一个应用程序,可以安排一些(大多数是微小的)音频文件的定时播放。捕获:需要定时准确的音乐。声音不会经常播放(例如,它不是采样器或鼓组),但 需要绝对准确地放置。这个问题分为两部分:
我该如何组织时间安排?我听说NSTimer
不准确,或至少不可靠?我应该以某种方式使用MIDI吗?还是一个紧凑的循环和一些具有高分辨率的时间获取功能?
一旦我确定了时间安排,我知道何时播放我的声音......我应该怎么玩它们? NSSound
是否足够快速可靠以保持所需的准确度?
答案 0 :(得分:18)
有关音乐质量音频的一般信息
这需要从山顶到任何为iOS制作乐器的人大喊:
如果您正在使用MIDI,那么您将在这些问题上摔跤得更少(声音渲染器必须处理大部分问题),但所有乐器都必须处理这些要求:
音乐家期待什么
第一件事是让AudioQueue使用的缓冲区尽可能小。 iPad上的默认值是 1024 样本,这是一个痛苦的长23毫秒。当您想播放具有该缓冲区大小的声音时,即使计算机速度非常快,响应也需要0到23毫秒。如果缓冲区长度超过256个样本,那么您将无法使用它完成大量实际工具。在实践中,还要在其上添加多达30ms的开销(从触摸响应到图形等)。
// 256 / ( 44.1khz) ~ 5.8ms
float latency = 0.005; //in seconds gives a 256 sample buffer
//on an infinitely fast computer, we'd make sure we got a buffer of size 1.
// but it will take too much CPU. :-) some latency is inevitable.
OSStatus status = AudioSessionSetProperty(
kAudioSessionProperty_PreferredHardwareIOBufferDuration,
sizeof(latency),&latency
);
延迟 - 专业音乐家希望手指到耳朵的延迟大约是8毫秒。对我来说,这似乎是iPad的幻想,但这就是目标。只是为了快速播放,想想每分钟120(四分音符)的节拍,并且目标是16倍音符的准确度等等。即使不能快速播放,音乐家也会因高延迟而烦恼,并且会停止使用超过100毫秒的其他伟大乐器( !!!!)任何超过30毫秒的东西,他们不幸地进入专业工具并将录制的声音时间移动几毫秒以修复你的搞砸了。商店里更好的应用程序大约需要30ms。一些非常好的在大约50ms仍然可以。
通过将麦克风放在ipad表面的顶部,然后将其运行到调音台的左声道来测量延迟。然后将ipad运行到右侧通道。以立体声录制声音。然后将其放入声音编辑器中,以便查看立体声波形。您可以查看手指触摸触摸屏的时间,然后查看电子声音开始的时间。那个距离是延迟。其中的差异是抖动。
抖动 - 抖动是延迟的变化。抖动超过几毫秒会使节奏听起来错误。我到了大约5ms。它与AudioQueue缓冲区大小直接相关。 1024个样本缓冲区的示例:如果您在缓冲区的某个随机时间点按下您的手指,那么当您需要响应时,期望是在样本512处。所以最小值从0到1024毫秒不等,但平均为512毫秒...等待缓冲区完成平均情况12毫秒,然后再从其他来源添加延迟。
触摸延迟 - 最重要的是touchesBegan尽快得到服务。即使那意味着touchesMoved,touchesEnded,touchesCancelled需要更长的时间。因此,您可能最终在工作负载中存在一些不对称性以实现此目的。起初这对我来说是一个令人困惑的启示,因为探查器告诉我没有多少时间花在touchesBegan上,但是在触摸结束之前推迟一些工作会有很大的不同。
OpenGL - 你至少应该考虑它。我不相信直接使用UIKit并在我的主应用程序上使用OpenGL时很容易达到这些延迟目标。
实时时钟 - 对于那些不是真正的乐器,但具有严格的节拍同步要求(如鼓机),您可以跟踪真正的时间并制作样本跟踪时钟并假设在您不看时钟时存在漂移。但是我已经厌倦了在我的旧MIDI乐器上打这场战斗......如果你有MIDI,那么MIDI会发出时钟信号;它可能是唯一可以同时将几个电子乐器同步的东西。
声音跳过 - 您的应用所做的一切都与CPU时间竞争。鉴于您已使声音引擎尽可能高效,使缓冲区变小会占用更多CPU。您可能需要换一点延迟才能获得更好的连续性。我有自己的偏见,如果声音引擎会有更多的循环时间,那就可以在你的手段内生活。
定时器。使用CADisplayLink。我没有多想过。这就是我所拥有的。
- (NSInteger)animationFrameInterval { return animationFrameInterval; } - (void)setAnimationFrameInterval:(NSInteger)frameInterval { if (frameInterval >= 16) { animationFrameInterval = frameInterval; if (animating) { [self stopAnimation]; [self startAnimation]; } } } - (void)startAnimation { if (!animating) { displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(drawView:)]; [displayLink setFrameInterval:animationFrameInterval]; [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; animating = TRUE; } } - (void)stopAnimation { if (animating) { [displayLink invalidate]; displayLink = nil; animating = FALSE; } }
我不是在制作这些东西。在解决这些问题之前,请不要在商店中放置“乐器”。在谈论iPad / iPhone应用程序时,Jordan Rudess,Leon Gruenbaum,Chris Dudley和其他专业音乐家过去经常提起这些问题。情况似乎有所改善,但我不认为大多数开发人员都知道真正的音乐家所期望的数字。
答案 1 :(得分:5)
我怀疑您是否能够使用NSTimer / NSSound方法实现样本精确的音频定时。
我认为您最好的选择是直接使用Core Audio。不幸的是,学习曲线有点陡峭,因为它是一个没有Cocoa记录的C API。
最简单的方法是AudioQueue-它应该提供播放音频所需的所有功能,并提供计时。
另一种选择是使用较低级别的AUGraph和音频单元 - 一个带有两个音频单元的简单AUGraph-连接到默认输出单元的AudioFilePlayer(或ScheduledSoundPlayer)也可以正常工作。
答案 2 :(得分:3)
NSTimer对于大多数用途来说是准确的,只要你没有对定时器安排的任何线程进行过多调整就足以让定时器延迟。您需要及时返回运行循环以进行下一个触发日期。幸运的是,NSSound是异步播放的,所以这应该不是问题。
通常会导致NSTimers出现问题的原因是人们将间隔设置得非常低(我看到的一个问题是1厘秒)。如果你所做的事情花费的时间超过了那段时间(并且花费的时间超过1厘秒非常容易),那么你应该在计时器启动后返回运行循环,这会使计时器延迟,这会搞砸你的时间
你只需要尽可能快地实现你的计时器方法,如果你不能让它足够快,做一个NSOperation子类来完成这项工作并让你的计时器方法只是实例化操作,设置它,并将其添加到操作队列中。 (该操作将在另一个线程上运行,因此它不会占用您计划定时器运行循环的线程。)如果定时器可能非常频繁,那么这是微优化的一种情况(由仪器和鲨鱼,当然)可能是有道理的。