2009-05-25 44 views
22

我想在iPhone上編程一個簡單的音頻音序器,但我無法獲得準確的時間。最後幾天,我嘗試了所有可能的iPhone音頻技術,從AudioServicesPlaySystemSound和AVAudioPlayer以及OpenAL到AudioQueues。如何在iphone上編寫實時精確的音頻音序器?

在我上次的嘗試中,我嘗試了使用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]; 

在我創建聲音引擎的初始化,加載一些聲音,不同的緩衝區,然後用的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緩衝區加載(在init代碼中),然後準備立即開始與alSourcePlay(source); - 所以問題可能與NSTimer?

現在,在appstore中有數十個音序器應用程序,它們具有準確的時序。胃內變焦和繪圖完成後,即使在180 bpm時,「idrum」也有完美的穩定拍子。所以必須有一個解決方案。

有人有什麼想法嗎?

感謝您提前提供任何幫助!

最好的問候,

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

我覺得@Walchy現在出去吃午飯......不知道哪他最終選擇這樣做的方式? – 2014-10-17 04:14:40

+0

不幸的是,我不能發佈示例代碼,但對於應用商店中的Playback應用,我們使用回調來提供從文件中讀取的音頻數據。每個文件都具有相同的音頻特徵,所以爲了提供精確的時序,我們只是跳到音頻文件數據中的那個採樣點。它超級準確(下載應用程序並獲得當天的免費遊戲)。我們可以循環部分並跳轉到文件中的部分,同時一次播放20個以上的曲目(尚未達到曲目限制)。 – 2015-10-29 17:49:07

回答

8

的NSTimer有它將觸發當上絕對沒有任何擔保。它在runloop上安排自己的火災時間,當runloop接近定時器時,它會查看是否有任何定時器過期。如果是這樣,它運行它們的選擇器。非常適合各種各樣的任務;這個沒用。

這裏的第一步是您需要將音頻處理移動到其自己的線程並取消UI線程。爲了計時,你可以使用普通的C方法來構建自己的時序引擎,但是我首先看CAAnimation,尤其是CAMediaTiming。

請記住,可可中有很多東西只能在主線程上運行。例如,不要在後臺線程上執行任何UI工作。一般來說,仔細閱讀文檔以瞭解他們對線程安全性的看法。但是一般來說,如果線程之間沒有太多的通信(在大多數情況下不應該是IMO),Cocoa中的線程非常容易。看看NSThread。

6

即時通訊使用remoteIO輸出做類似的事情。我不依賴NSTimer。我使用渲染回調中提供的時間戳來計算我所有的時間。我不知道如何acurate iphone的hz率是,但我確定它非常接近44100hz,所以我只是計算什麼時候我應該加載下一個節拍基於什麼樣的當前樣本數量。

使用遠程io的示例項目可以找到here查看renderTime回調inTimeStamp參數。

編輯:這種方法工作的例子(在App Store上,可以發現here

+0

當然您需要一個bpm,但爲了安排下一個樣本播放,您必須找到樣本直到下一個節拍(這將由您的bpm定義)並使用當前時間戳來執行此操作。 – 2009-07-28 12:07:08

+0

非常感謝您發帖的方式。 – griotspeak 2011-03-29 02:58:13

0

我想的時間管理一個更好的辦法是有一個BPM設置(120,例如)並取而代之。在編寫/製作音樂/音樂應用程序時,分鐘和秒的測量幾乎沒有用處。

如果你看任何排序的應用程序,他們都通過節拍而不是時間。在事情的另一面,如果你看一個波形編輯器,它使用分鐘和秒。

我不確定以任何方式實現這種代碼方式的最佳方式,但我認爲這種方法將爲您節省很多麻煩。

4

我選擇使用RemoteIO AudioUnit和使用AudioFileServices API填充擺動緩衝區(一個緩衝區用於讀取,一個用於寫入然後交換)的後臺線程。然後在AudioUnit線程中處理和混合緩衝區。當它應該開始加載下一個swing緩衝區時,AudioUnit線程向bgnd線程發出信號。所有的處理都在C中,並使用posix線程API。所有的UI材料都在ObjC中。

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

乾杯,

1

真的接近時刻最精確的方法是計數的音頻樣本,做任何你需要的時候樣品一定數量的已經過去了的事情。你的輸出採樣率是所有與聲音有關的東西的基礎,所以這是主時鐘。

你不必檢查每個樣本,每隔幾毫秒就可以做到這一點。

3

您已經在這裏得到了一些很好的答案,但我認爲我會提供一些適用於我的解決方案的代碼。當我開始研究這個時,我實際上是在尋找遊戲中運行循環的工作方式,並發現了一個非常好的解決方案,使用mach_absolute_time已經非常高效。

你可以閱讀一下它的功能here但缺點是它返回的時間精度達到納秒級。但是,它返回的數字不是很長時間,它隨着CPU的變化而變化,因此必須先創建mach_timebase_info_data_t結構,然後使用它來規範時間。

// Gives a numerator and denominator that you can apply to mach_absolute_time to 
// get the actual nanoseconds 
mach_timebase_info_data_t info; 
mach_timebase_info(&info); 

uint64_t currentTime = mach_absolute_time(); 

currentTime *= info.numer; 
currentTime /= info.denom; 

如果我們想讓它剔每16個音符,你可以做這樣的事情:

uint64_t interval = (1000 * 1000 * 1000)/16; 
uint64_t nextTime = currentTime + interval; 

在這一點上,currentTime將包含納秒的一些號碼,你想要它每interval納秒通過,我們在nextTime存儲。然後,您可以建立一個while循環,這樣的事情:

while (_running) { 
    if (currentTime >= nextTime) { 
     // Do some work, play the sound files or whatever you like 
     nextTime += interval; 
    } 

    currentTime = mach_absolute_time(); 
    currentTime *= info.numer; 
    currentTime /= info.denom; 
} 

mach_timebase_info東西是有點混亂,但是一旦你在那裏得到它,它工作得很好。這對我的應用程序來說是非常高效的。另外值得注意的是,你不想在主線程上運行它,所以將它拋棄到自己的線程是明​​智的。你可以把所有上面的代碼在其名爲run自己的方法,並且用類似啓動:

[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; 

所有你在這裏看到的代碼是一個項目,我的簡化開放源代碼,你可以看到它和如果有任何幫助,請自己運行here。乾杯。

1

可能會改善實時響應的另一件事是在使音頻會話處於活動狀態之前,將音頻會話的kAudioSessionProperty_PreferredHardwareIOBufferDuration設置爲幾毫秒(例如0.005秒)。這將導致RemoteIO更頻繁地請求更短的回調緩衝區(在實時線程上)。不要在這些實時音頻回調中花費大量時間,否則將終止應用程序的音頻線程和所有音頻。

只計算較短的RemoteIO回調緩衝區的精度要比使用NSTimer高10倍,並且延遲時間更短。對音頻回調緩衝區內的樣本進行計數以定位聲音混合的開始會給您提供亞毫秒相對時間。

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 
    } 
} 
1

如果提前構建的時候你的序列是但不限於:您可以使用AVMutableComposition獲得精確的時間。這將起到4聲音均勻間隔超過1秒:

// 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

多的細節:precise timing with AVMutableComposition