我想在iphone上编写一个简单的音频音序器,但我无法获得准确的时序。最后几天我在iphone上尝试了所有可能的音频技术,从AudioServicesPlaySystemSound和AVAudioPlayer以及OpenAL到AudioQueues。
在我的最后一次尝试中,我尝试了使用openAL的CocosDenshion声音引擎,并允许将声音加载到多个缓冲区中,然后在需要时播放它们。这是基本代码:
INIT:
int channelGroups[1];
channelGroups[0] = 8;
soundEngine = [[CDSoundEngine alloc] init:channelGroups channelGroupTotal:1];
int i=0;
for(NSString *soundName in [NSArray arrayWithObjects:@"base1", @"snare1", @"hihat1", @"dit", @"snare", nil])
{
[soundEngine loadBuffer:i fileName:soundName fileType:@"wav"];
i++;
}
[NSTimer scheduledTimerWithTimeInterval:0.14 target:self selector:@selector(drumLoop:) userInfo:nil repeats:YES];
在初始化中,我创建声音引擎,将一些声音加载到不同的缓冲区,然后使用NSTimer建立音序器循环。
音频循环:
- (void)drumLoop:(NSTimer *)timer
{
for(int track=0; track<4; track++)
{
unsigned char note=pattern[track][step];
if(note)
[soundEngine playSound:note-1 channelGroupId:0 pitch:1.0f pan:.5 gain:1.0 loop:NO];
}
if(++step>=16)
step=0;
}
多数民众赞成它并且它的工作原理应该但是时机不稳定且不稳定。一旦发生其他事情(例如在视图中绘图),它就会失去同步。
据我所知,声音引擎和openAL缓冲区已加载(在初始化代码中),然后可以立即使用alSourcePlay(source);
启动 - 所以问题可能出在NSTimer上?
现在appstore中有几十个声音序列器应用,它们具有准确的时序。胃内当变焦和绘图完成时,“idrum”在180 bpm时具有完美的稳定节拍。所以必须有一个解决方案。
有人有任何想法吗?
提前感谢您的帮助!
致以最诚挚的问候,
Walchy
感谢您的回答。它让我更进一步,但不幸的是没有达到目的。这是我做的:
nextBeat=[[NSDate alloc] initWithTimeIntervalSinceNow:0.1];
[NSThread detachNewThreadSelector:@selector(drumLoop:) toTarget:self withObject:nil];
在初始化中,我存储下一个节拍的时间并创建一个新线程。
- (void)drumLoop:(id)info
{
[NSThread setThreadPriority:1.0];
while(1)
{
for(int track=0; track<4; track++)
{
unsigned char note=pattern[track][step];
if(note)
[soundEngine playSound:note-1 channelGroupId:0 pitch:1.0f pan:.5 gain:1.0 loop:NO];
}
if(++step>=16)
step=0;
NSDate *newNextBeat=[[NSDate alloc] initWithTimeInterval:0.1 sinceDate:nextBeat];
[nextBeat release];
nextBeat=newNextBeat;
[NSThread sleepUntilDate:nextBeat];
}
}
在序列循环中,我将线程优先级设置得尽可能高,然后进入无限循环。在播放声音后,我计算下一个节拍的下一个绝对时间,并将线程发送到睡眠状态,直到此时为止。
这再次起作用,它比没有NSThread的尝试更稳定,但如果发生其他事情,它仍然不稳定,特别是GUI的东西。
有没有办法在iphone上使用NSThread获得实时响应?
致以最诚挚的问候,
Walchy
答案 0 :(得分:9)
NSTimer绝对无法保证何时开火。它会在runloop上计划自己的触发时间,当runloop到达定时器时,它会看到是否有任何定时器到期。如果是这样,它运行他们的选择器。非常适合各种各样的任务;对这个没用。
这里的第一步是你需要将音频处理移动到它自己的线程并离开UI线程。对于计时,您可以使用常规C方法构建自己的计时引擎,但我首先要查看CAAnimation,特别是CAMediaTiming。
请记住,Cocoa中有很多东西只能在主线程上运行。例如,不要在后台线程上进行任何UI工作。一般来说,仔细阅读文档,看看他们对线程安全的看法。但一般来说,如果线程之间没有很多通信(在大多数情况下不应该是IMO),那么Cocoa中的线程非常简单。看看NSThread。
答案 1 :(得分:6)
我正在使用remoteIO输出做类似的事情。我不依赖于NSTimer。我使用渲染回调中提供的时间戳来计算我的所有时间。我不知道iphone的hz率是多少,但我确定它非常接近44100hz,所以我只计算我应该根据当前样本数加载下一个节拍。
可以找到使用远程io的示例项目here查看渲染回调inTimeStamp参数。
编辑:此方法工作的示例(在应用商店中,可以找到here)
答案 2 :(得分:4)
我选择使用RemoteIO AudioUnit和后台线程,使用AudioFileServices API填充swing缓冲区(一个缓冲区用于读取,一个用于写入然后交换)。然后在AudioUnit线程中处理和混合缓冲区。当它应该开始加载下一个摆动缓冲区时,AudioUnit线程会向bgnd线程发出信号。所有处理都在C中并使用posix线程API。所有UI内容都在ObjC中。
IMO,AudioUnit / AudioFileServices方法提供了最大程度的灵活性和控制。
干杯,
本
答案 3 :(得分:1)
真正接近时间的最精确方法是计算音频样本,并在经过一定数量的样本时执行您需要做的任何事情。无论如何,您的输出采样率是与声音相关的所有事物的基础,因此这是主时钟。
您不必检查每个样本,每隔几毫秒执行此操作就足够了。
答案 4 :(得分:1)
可以提高实时响应能力的另一个方面是在激活音频会话之前将音频会话的kAudioSessionProperty_PreferredHardwareIOBufferDuration设置为几毫秒(例如0.005秒)。这将导致RemoteIO更频繁地请求更短的回调缓冲区(在实时线程上)。不要在这些实时音频回调中占用任何重要时间,否则您将终止应用程序的音频线程和所有音频。
与使用NSTimer相比,仅计算较短的RemoteIO回调缓冲区的准确性要高10倍,并且延迟更低。计算音频回调缓冲区中的样本以定位声音混音的开始将为您提供亚毫秒的相对时序。
答案 5 :(得分:1)
通过测量循环中“做一些工作”部分所经过的时间并从nextTime中减去这个持续时间可以大大提高准确性:
while (loop == YES)
{
timerInterval = adjustedTimerInterval ;
startTime = CFAbsoluteTimeGetCurrent() ;
if (delegate != nil)
{
[delegate timerFired] ; // do some work
}
endTime = CFAbsoluteTimeGetCurrent() ;
diffTime = endTime - startTime ; // measure how long the call took. This result has to be subtracted from the interval!
endTime = CFAbsoluteTimeGetCurrent() + timerInterval-diffTime ;
while (CFAbsoluteTimeGetCurrent() < endTime)
{
// wait until the waiting interval has elapsed
}
}
答案 6 :(得分:1)
如果提前构建序列不是限制,您可以使用AVMutableComposition获得精确的时序。这将在1秒内均匀间隔播放4种声音:
// setup your composition
AVMutableComposition *composition = [[AVMutableComposition alloc] init];
NSDictionary *options = @{AVURLAssetPreferPreciseDurationAndTimingKey : @YES};
for (NSInteger i = 0; i < 4; i++)
{
AVMutableCompositionTrack* track = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
NSURL *url = [[NSBundle mainBundle] URLForResource:[NSString stringWithFormat:@"sound_file_%i", i] withExtension:@"caf"];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:options];
AVAssetTrack *assetTrack = [asset tracksWithMediaType:AVMediaTypeAudio].firstObject;
CMTimeRange timeRange = [assetTrack timeRange];
Float64 t = i * 1.0;
NSError *error;
BOOL success = [track insertTimeRange:timeRange ofTrack:assetTrack atTime:CMTimeMake(t, 4) error:&error];
NSAssert(success && !error, @"error creating composition");
}
AVPlayerItem* playerItem = [AVPlayerItem playerItemWithAsset:composition];
self.avPlayer = [[AVPlayer alloc] initWithPlayerItem:playerItem];
// later when you want to play
[self.avPlayer seekToTime:kCMTimeZero];
[self.avPlayer play];
此解决方案的最大功劳:http://forum.theamazingaudioengine.com/discussion/638#Item_5
答案 7 :(得分:0)
我认为时间管理的一个更好的方法是使用bpm设置(例如120),然后改用它。在编写/制作音乐/音乐应用程序时,分钟和秒的测量几乎无用。
如果您查看任何一个测序应用程序,它们都会通过节拍而不是时间来进行。另一方面,如果你看一下波形编辑器,它会使用分钟和秒。
我不确定以任何方式实现此代码的最佳方式,但我认为这种方法可以为您节省很多麻烦。