我的一個應用程序具有簡單的節拍器式功能,可以每分鐘(bpm)播放指定次數的咔嗒聲。我正在通過啓動一個NSTimer來實現這一點,從指定的bpm計算出一個間隔,該間隔調用一個播放聲音的方法。爲什麼我的音頻沒有按時播放?
如果我把一個NSLog行放入播放方法,我可以看到NSTimer準確地發射到大約1毫秒。但是,如果我將聲音輸出記錄到音頻編輯器中,然後測量點擊之間的時間間隔,我可以看到它們的間距不均勻。例如,每分鐘150次,計時器每400毫秒觸發一次。但是大部分聲音在395毫秒後播放,在418毫秒後播放每三分之一或四分之一聲音。
所以聲音不是均勻延遲,而是它們遵循更短和更長時間間隔的模式。看起來好像iOS的聲音定時分辨率較低,並將每個聲音事件四捨五入到最近的可用點,根據需要向上或向下舍入以保持整體軌道。
我已經嘗試過使用系統聲音,AVAudioPlayer和OpenAL,並且使用這三種方法都得到了完全相同的結果。使用每種方法時,我會在視圖加載時執行所有設置,因此每次播放聲音時我只需要播放它。使用AVAudioPlayer時,我在每次播放聲音後都嘗試使用第二個定時器調用prepareToPlay,因此它被初始化並準備下次使用,但得到相同的結果。
這裏的(改編自this tutorial)設立在viewDidLoad中的OpenAL的聲音代碼:
// set up the context and device
ALCcontext *context;
ALCdevice *device;
OSStatus result;
device = alcOpenDevice(NULL); // select the "preferred device"
if (device) {
context = alcCreateContext(device, NULL); // use the device to make a context
alcMakeContextCurrent(context); // set the context to the currently active one
}
// open the sound file
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:@"TempoClick" ofType:@"caf"];
NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath];
AudioFileID fileID;
result = AudioFileOpenURL((CFURLRef)soundFileURL, kAudioFileReadPermission, 0, &fileID);
if (result != 0) DLog(@"cannot open file %@: %ld", soundFilePath, result);
// get the size of the file data
UInt32 fileSize = 0;
UInt32 propSize = sizeof(UInt64);
result = AudioFileGetProperty(fileID, kAudioFilePropertyAudioDataByteCount, &propSize, &fileSize);
if (result != 0) DLog(@"cannot find file size: %ld", result);
DLog(@"file size: %li", fileSize);
// copy the data into a buffer, then close the file
unsigned char *outData = malloc(fileSize);
AudioFileOpenURL((CFURLRef)soundFileURL, kAudioFileReadPermission, 0, &fileID); // we get a "file is not open" error on the next line if we don't open this again
result = AudioFileReadBytes(fileID, false, 0, &fileSize, outData);
if (result != 0) NSLog(@"cannot load data: %ld", result);
AudioFileClose(fileID);
alGenBuffers(1, &tempoSoundBuffer);
alBufferData(self.tempoSoundBuffer, AL_FORMAT_MONO16, outData, fileSize, 44100);
free(outData);
outData = NULL;
// connect the buffer to the source and set some preferences
alGenSources(1, &tempoSoundSource);
alSourcei(tempoSoundSource, AL_BUFFER, tempoSoundBuffer);
alSourcef(tempoSoundSource, AL_PITCH, 1.0f);
alSourcef(tempoSoundSource, AL_GAIN, 1.0f);
alSourcei(tempoSoundSource, AL_LOOPING, AL_FALSE);
然後在播放方法我只要致電:
alSourcePlay(self.tempoSoundSource);
誰能解釋一下什麼是在這裏發生,以及我如何解決它?
更新1:
我有另一個項目,播放音頻單元簡短的聲音,從而快速測試我添加了一個定時器,該項目發揮出自己的咔嚓聲每隔400毫秒。在這種情況下,時機幾乎完美。所以,NSTimer似乎很好,但系統聲音,AVAudioPlayer和OpenAL的播放比音頻單元的準確性要低。
更新2:
我只是重做我的項目使用的音頻單元,現在音頻更準確地回放了。它在任一方向上偶爾漂移最多四毫秒,但這比其他音頻方法要好。我仍然很好奇爲什麼其他方法都顯示短,短,短,長的時間間隔模式 - 就像音頻播放時間被向上或向下四捨五入以映射某種幀速率 - 所以我會任何可以解釋和/或爲其他音頻方法提供解決方法的人都可以打開此問題。
不知道,但可能是你的NSLog造成滯後,如果它存在 – Ahmed
@Ahmed:這是一個很好的想法,但我剛剛評論了NSLog並得到了相同的結果。 – arlomedia
[如何在iPhone上編程實時精確音頻音序器?](http://stackoverflow.com/questions/907137/how-to-program-a-real-time-accurate-audio-sequencer -on-the-iphone) – benzado