AVAudioPlayerNode获取当前时间异常,求实时获取正确时间方案
AVAudioPlayerNode 当前时间获取问题解答
Hey there! Let's tackle your question step by step.
关于首次获取到负数 sampleTime 是否正常
首先,你遇到的 -512 或 -1024 这种负数 sampleTime 是正常现象。这是因为 AVAudioPlayerNode 背后的音频引擎会提前预加载若干音频样本到缓冲区中(这些数值通常就是音频缓冲区的大小),在播放刚开始的几帧里,lastRenderTime 对应的播放时间会带有这个预加载偏移量。等播放进入稳定状态后,sampleTime 就会变成正常的递增正数了。
不过这里要提醒你:每次获取 playerTime 时,一定要先检查它的有效性,避免空值或无效值导致的问题。优化后的代码可以这样写:
AVAudioTime *nodeTime = _playerNode.lastRenderTime; if (nodeTime) { AVAudioTime *playerTime = [_playerNode playerTimeForNodeTime:nodeTime]; if (playerTime.valid) { NSTimeInterval seconds = (NSTimeInterval)(playerTime.sampleTime / playerTime.sampleRate); // 这里安全地使用 seconds } }
其他实时获取当前播放时间的方法
如果觉得原方法的首次偏移让你困扰,或者需要更稳定的时间获取方式,试试下面这些方案:
1. 关联宿主时间与播放时间
在调度音频资源时,手动关联系统宿主时间和播放起始时间,之后通过宿主时间来推算当前播放时间,精度很高:
// 调度音频文件时,记录起始的宿主时间和对应的播放时间 AVAudioFile *audioFile = /* 你的音频文件 */; AVAudioTime *startHostTime = [AVAudioTime timeWithHostTime:mach_absolute_time() sampleTime:0 atRate:_playerNode.outputFormat.sampleRate]; [_playerNode scheduleFile:audioFile atTime:startHostTime completionHandler:nil]; // 在 CADisplayLink 回调中计算当前时间 AVAudioTime *currentHostTime = [AVAudioTime timeWithHostTime:mach_absolute_time()]; AVAudioTime *currentPlayerTime = [_playerNode playerTimeForNodeTime:currentHostTime]; if (currentPlayerTime.valid) { NSTimeInterval currentSeconds = (NSTimeInterval)currentPlayerTime.sampleTime / currentPlayerTime.sampleRate; // 使用当前时间 }
2. 本地维护时间计数器(适合精度要求稍低的场景)
如果不需要极致的音频同步精度,可以自己维护一个时间计数器,结合播放状态和速率计算:
// 播放开始时初始化 @property (nonatomic, assign) NSTimeInterval startSystemTime; @property (nonatomic, assign) NSTimeInterval startPlayerTime; @property (nonatomic, assign) BOOL isPlaying; // 启动播放时 self.startSystemTime = CACurrentMediaTime(); self.startPlayerTime = 0.0; // 如果是从中间开始播放,这里设为对应的起始时间 self.isPlaying = YES; // 在 CADisplayLink 中更新 if (self.isPlaying) { NSTimeInterval elapsed = CACurrentMediaTime() - self.startSystemTime; NSTimeInterval currentTime = self.startPlayerTime + elapsed * _playerNode.rate; // 使用 currentTime } // 暂停时更新起始点 - (void)pausePlayer { // 先获取当前准确时间,更新 startPlayerTime AVAudioTime *nodeTime = _playerNode.lastRenderTime; if (nodeTime) { AVAudioTime *playerTime = [_playerNode playerTimeForNodeTime:nodeTime]; if (playerTime.valid) { self.startPlayerTime = (NSTimeInterval)playerTime.sampleTime / playerTime.sampleRate; } } self.startSystemTime = CACurrentMediaTime(); self.isPlaying = NO; [_playerNode pause]; }
3. 使用 AVAudioProcessingTap(高精度音频级回调)
如果需要最精准的时间回调,可以给 playerNode 添加一个音频处理 Tap,在音频渲染的回调中直接获取当前 sampleTime,然后传递到主线程更新 UI。这种方法适合需要和音频帧严格同步的场景:
// 创建处理 Tap AVAudioFormat *outputFormat = _playerNode.outputFormat; AVAudioProcessingTapRef tap; OSStatus status = AVAudioProcessingTapCreate(kCFAllocatorDefault, &tapCallbacks, kAVAudioProcessingTapFlag_PostEffects, &tap); if (status == noErr) { [_playerNode installTapOnBus:0 bufferSize:512 format:outputFormat block:^(AVAudioPCMBuffer *buffer, AVAudioTime *when) { // 这里的 when 参数就是当前渲染的精确时间 if (when.valid) { NSTimeInterval currentSeconds = (NSTimeInterval)when.sampleTime / when.sampleRate; // 回到主线程更新 UI dispatch_async(dispatch_get_main_queue(), ^{ // 更新时间显示 }); } }]; }
注意:使用 ProcessingTap 需要处理音频缓冲区的传递,确保不影响原音频的播放。
内容的提问来源于stack exchange,提问作者guojing
相关产品推荐
相关产品推荐

