如何在iphone上编写实时准确的音频音序器?

Wal*_*chy 23 iphone audio timing openal core-audio

我想在iphone上编写一个简单的音频音序器,但我无法获得准确的时序.最后几天我在iphone上尝试了所有可能的音频技术,从AudioServicesPlaySystemSound和AVAudioPlayer以及OpenAL到AudioQueues.

在我的最后一次尝试中,我尝试了使用openAL的CocosDenshion声音引擎,并允许将声音加载到多个缓冲区中,然后在需要时播放它们.这是基本代码:

在里面:

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];
Run Code Online (Sandbox Code Playgroud)

在初始化中,我创建声音引擎,将一些声音加载到不同的缓冲区,然后使用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;

}
Run Code Online (Sandbox Code Playgroud)

这就是它,它应该工作,但它的时机是不稳定和不稳定的.一旦发生其他事情(在视图中绘制),它就会失去同步.

据我所知,声音引擎和openAL缓冲区已加载(在初始化代码中),然后准备立即启动alSourcePlay(source);- 所以问题可能出在NSTimer?

现在appstore中有几十个声音序列器应用程序,它们具有准确的计时.当变焦和绘图完成时,Ig"idrum"在180 bpm时具有完美的稳定节拍.所以必须有一个解决方案.

有人有什么想法吗?

在此先感谢您的帮助!

最好的祝福,

Walchy


感谢您的回答.它让我更进一步,但不幸的是没有达到目的.这是我做的:

nextBeat=[[NSDate alloc] initWithTimeIntervalSinceNow:0.1];
[NSThread detachNewThreadSelector:@selector(drumLoop:) toTarget:self withObject:nil];
Run Code Online (Sandbox Code Playgroud)

在初始化中,我存储下一个节拍的时间并创建一个新线程.

- (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];
    }
}
Run Code Online (Sandbox Code Playgroud)

在序列循环中,我将线程优先级设置得尽可能高,然后进入无限循环.在播放声音后,我计算下一个节拍的下一个绝对时间,并将线程发送到睡眠状态,直到此时为止.

再次这是有效的,它比没有NSThread的尝试更稳定,但如果发生其他事情,它仍然是不稳定的,尤其是GUI的东西.

有没有办法在iphone上使用NSThread获得实时响应?

最好的祝福,

Walchy

Rob*_*ier 9

NSTimer绝对不能保证什么时候开火.它会在runloop上计划自己的触发时间,当runloop到达定时器时,它会看到是否有任何定时器到期.如果是这样,它运行他们的选择器.非常适合各种各样的任务; 对这个没用.

这里的第一步是你需要将音频处理移动到它自己的线程并离开UI线程.对于计时,您可以使用常规C方法构建自己的计时引擎,但我首先考虑CAAnimation,特别是CAMediaTiming.

请记住,Cocoa中有很多东西只能在主线程上运行.例如,不要在后台线程上进行任何UI工作.一般来说,仔细阅读文档,看看他们对线程安全的看法.但一般来说,如果线程之间没有很多通信(在大多数情况下不应该是IMO),那么Cocoa中的线程非常简单.看看NSThread.


Ara*_*and 6

即时通过remoteIO输出做类似的事情.我不依赖于NSTimer.我使用渲染回调中提供的时间戳来计算我的所有时间.我不知道iphone的hz率是多少,但我确定它非常接近44100hz,所以我只计算我应该根据当前样本数加载下一个节拍.

使用远程io的示例项目可以在这里查看渲染回调inTimeStamp参数.

编辑:此方法的示例工作(在应用程序商店,可以在这里找到)


小智 5

我选择使用一个 RemoteIO AudioUnit 和一个后台线程,它使用 AudioFileServices API 填充 Swing 缓冲区(一个缓冲区用于读取,一个用于写入然后交换)。然后在 AudioUnit 线程中处理和混合缓冲区。当它应该开始加载下一个摆动缓冲区时,AudioUnit 线程会向 bgnd 线程发出信号。所有处理都在 C 中进行,并使用了 posix 线程 API。所有 UI 的东西都在 ObjC 中。

IMO,AudioUnit/AudioFileServices 方法提供了最大程度的灵活性和控制。

干杯,