iphone - 为什么我的音频没有按时播放?

标签 iphone audio nstimer openal

我的一个应用程序有一个简单的节拍器风格的功能,它每分钟播放指定次数 (bpm) 的点击声音。我通过启动一个 NSTimer 来做到这一点,它的间隔是从指定的 bpm 计算出来的,它调用一个播放声音的方法。

如果我将 NSLog 行放入 play 方法中,我可以看到 NSTimer 精确到大约 1 毫秒。但是,如果我将声音输出记录到音频编辑器中,然后测量点击之间的间隔,我可以看到它们的间隔不均匀。例如,对于 150 bpm,计时器每 400 毫秒触发一次。但大多数声音在 395 毫秒后播放,每隔 3 到 4 个声音在 418 毫秒后播放一次。

因此,声音不是均匀延迟的,而是遵循更短和更长的间隔模式。似乎 iOS 的声音时间分辨率较低,并且将每个声音事件四舍五入到最近的可用点,根据需要向上或向下四舍五入以保持整体跟踪。

我已经用系统声音、AVAudioPlayer 和 OpenAL 进行了尝试,并且用所有三种方法都得到了完全相同的结果。使用每种方法,我都会在 View 加载时进行所有设置,因此每次播放声音时我所要做的就是播放它。使用 AVAudioPlayer,我尝试在每次播放声音后使用第二个计时器调用 prepareToPlay,因此它已初始化并准备好下一次,但得到了相同的结果。

这是在 viewDidLoad 中设置 OpenAL 声音的代码(改编自 this tutorial ):

// 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 不保证您的方法何时真正被触发。

更多信息:How to program a real-time accurate audio sequencer on the iphone?

关于您的编辑:

AVAudioPlayer 需要一些时间来初始化自身。如果你调用 prepareToPlay,它会初始化自己,这样它就可以在调用 play 时立即播放当前加载的声音。播放停止后,它会自行取消初始化,因此您需要再次调用 prepareToPlay 来重新初始化。最好将此类用于流式播放而不是离散声音播放。

使用 OpenAL,一旦您加载了缓冲区,将其附加到源并播放它应该不会导致任何延迟。

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

关于iphone - 为什么我的音频没有按时播放?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6835364/

相关文章:

ios - 适用于 iPhone 3.5 英寸和 4 英寸屏幕尺寸的 Sprite Kit 游戏

audio - www.audiotool.com使用哪种语言编程?

iphone - Objective-c iPhone NSTimer 唯一标识符

objective-c - 检测 NSUndoManager 警报 View 何时可见

uitableview - 如何在uitableview中使用计时器?

iphone - 加载数据uitextfield

ios - 如何禁用 iPhone 和 iPad 设备的旋转动画

iphone - 在 WhirlyGlobe 中使用纹理作为标签

audio - 是否可以向音乐文件添加自定义元数据标签?

google-chrome - 在Chrome中播放WAV文件失败