为什么我的音频不能按时播放?

我的一个应用程序具有简单的节拍器式功能,可以每分钟(bpm)播放特定次数的咔嗒声。 我正在通过启动一个NSTimer来实现这一点,从指定的bpm计算出一个间隔,该间隔调用一个播放声音的方法。

如果我将NSLog行放入play方法中,我可以看到NSTimer准确地发射到大约1毫秒。 但是,如果我将声音输出记录到音频编辑器中,然后测量点击之间的时间间隔,我可以看到它们的间距不均匀。 例如,每分钟150次,计时器每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:

我刚刚重做了我的项目以使用音频单元,现在音频播放更为准确。 它在任一方向上偶尔漂移最多四毫秒,但这比其他音频方法要好。 我仍然很好奇为什么其他方法都显示短,短,短,长的时间间隔模式 - 就像音频播放时间被向上或向下四舍五入以映射某种帧速率 - 所以我会任何可以解释和/或为其他音频方法提供解决方法的人都可以打开此问题。


NSTimer不能保证你的方法何时会被实际触发。

更多信息:如何在iphone上编程实时精确音频音序器?

关于你的编辑:

AVAudioPlayer需要一些时间来初始化自己。 如果您调用prepareToPlay,它将初始化自己,以便在调用播放时立即播放当前加载的声音。 播放停止后,它会自行初始化,因此您需要再次调用prepareToPlay以重新初始化。 最好将这个类用于stream-y回放,而不是离散的声音回放。

使用OpenAL时,一旦你加载了缓冲区,将它附加到源文件并播放它应该不会造成任何延迟。

您可以将音频单元代码封装到.mm文件中,然后从.m模块中调用它,而无需将它们编译为C ++。


好的,我明白了。 音频单元比其他音频方法工作得更好的真正原因是我的音频单元类(我正在从另一个项目中进行调整)是在音频会话中设置缓冲区持续时间属性,如下所示:

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

当我将这些代码添加到OpenAL版本,甚至添加到AVAudioPlayer版本时,我的精度在几毫秒之内,与音频单元相同。 (然而,系统声音仍然不是很准确。)我可以通过增加缓冲区大小和观看播放间隔的准确性来验证连接。

当然,我只花了整整一天的时间来调整我的项目以使用音频单元 - 调整它在C ++下编译,测试中断处理程序等,我希望这可以帮助别人避免同样的麻烦。

链接地址: http://www.djcxy.com/p/67933.html

上一篇: Why are my audio sounds not playing on time?

下一篇: stopping a method when the view is changed