You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:38:14