簡體   English   中英

為什么我的音頻沒有按時播放?

[英]Why are my audio sounds not playing on time?

我的一個應用程序有一個簡單的節拍器式功能,可以每分鍾播放指定次數 (bpm) 的咔嗒聲。 我是通過啟動一個 NSTimer 來實現的,它的間隔從指定的 bpm 計算出來,它調用一個播放聲音的方法。

如果我將 NSLog 行放入 play 方法,我可以看到 NSTimer 准確地觸發到大約 1 毫秒。 但是,如果我將聲音輸出記錄到音頻編輯器中,然后測量點擊之間的間隔,我可以看到它們的間隔不均勻。 例如,對於 150 bpm,計時器每 400 毫秒觸發一次。 但大多數聲音在 395 毫秒后播放,418 毫秒后每隔三個或四個聲音播放一次。

所以聲音不是均勻延遲的,而是遵循越來越短的間隔模式。 似乎 iOS 對聲音的計時具有較低的分辨率,並且正在將每個聲音事件四舍五入到最近的可用點,根據需要向上或向下四舍五入以保持整體跟蹤。

我已經用系統聲音、AVAudioPlayer 和 OpenAL 嘗試過這個,並且用所有三種方法都得到了完全相同的結果。 對於每種方法,我都會在視圖加載時進行所有設置,因此每次播放聲音時,我所要做的就是播放它。 使用 AVAudioPlayer,我嘗試在每次播放聲音后使用第二個計時器調用 prepareToPlay,因此它已初始化並准備好下次播放,但得到了相同的結果。

這是在 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:

我剛剛重新設計了我的項目以使用音頻單元,現在音頻播放更准確。 它仍然偶爾會在任一方向上漂移多達 4 毫秒,但這比其他音頻方法要好。 我仍然很好奇為什么其他方法都顯示出短、短、短、長間隔的模式——就像音頻播放時間被向上或向下舍入以映射到某種幀速率——所以我會將這個問題留給任何可以解釋和/或為其他音頻方法提供解決方法的人。

NSTimer 不保證您的方法何時真正被觸發。

更多信息: 如何在 iphone 上對實時准確的音頻音序器進行編程?

關於您的編輯:

AVAudioPlayer 需要一些時間來初始化自己。 如果你調用prepareToPlay,它會初始化自己,這樣它就可以在調用play 時立即播放當前加載的聲音。 播放停止后,它會自行取消初始化,因此您需要再次調用 prepareToPlay 以重新初始化。 最好將此類用於流播放而不是離散聲音播放。

使用 OpenAL,一旦您加載了緩沖區,將其附加到源並播放它應該不會造成任何延遲。

您可以將音頻單元代碼封裝到 .mm 文件中,然后從 .m 模塊調用它,而無需將它們編譯為 C++。

好的,我已經想通了。 音頻單元比其他音頻方法工作得更好的真正原因是我從另一個項目改編的音頻單元類在音頻會話中設置了一個緩沖區持續時間屬性,如下所示:

Float32 preferredBufferSize = .001;
UInt32 size = sizeof(preferredBufferSize);
AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, size, &preferredBufferSize);

當我將此代碼添加到 OpenAL 版本,甚至是 AVAudioPlayer 版本時,我的准確度在幾毫秒內,與音頻單元相同。 (但是,系統聲音仍然不是很准確。)我可以通過增加緩沖區大小並觀察播放間隔變得不那么准確來驗證連接。

當然,我是在花了一整天調整我的項目以使用音頻單元之后才想到的——調整它以在 C++ 下編譯,測試中斷處理程序等。我希望這可以避免其他人遇到同樣的麻煩。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM