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

AVAudioEngine首次播放双音频无延迟 后续播放延迟正常问题排查

问题根因

你首次点击触发播放时,代码在AVAudioEngine尚未启动的状态下就获取了_player1.lastRenderTime,此时该值处于无效状态(sampleTime固定为0),基于无效值计算出的startTime1和startTime2不被播放节点识别,两个音频都会忽略调度时间直接立即播放,就出现了没有1.2秒间隔的问题。
后续点击时引擎已经处于运行状态,lastRenderTime返回的是有效渲染时间戳,因此调度逻辑可以正常生效。

修复方案

调整代码执行顺序,同时优化冗余逻辑:

  1. 将引擎启动、节点连接逻辑提前到viewDidLoad中执行,避免每次点击重复操作,保证播放调度前引擎已经处于运行状态
  2. 调度播放前增加lastRenderTime有效性校验,避免无效时间戳导致调度失败
  3. 每次点击前先停止之前的播放任务,避免多次点击出现音频重叠混乱
修改后的核心代码
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()
{
    AVAudioEngine *_engine;
    AVAudioPlayerNode *_player1;
    AVAudioPlayerNode *_player2;
    AVAudioMixerNode *_mainMixer;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _engine = [[AVAudioEngine alloc]init];
    _player1 = [[AVAudioPlayerNode alloc]init];
    _player2 = [[AVAudioPlayerNode alloc]init];
    
    [_engine attachNode:_player1];
    [_engine attachNode:_player2];
    _mainMixer = [_engine mainMixerNode];
    
    // 提前连接节点,格式用通用的立体声44.1kHz即可,也可替换为音频文件实际的processingFormat
    AVAudioFormat *commonFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 sampleRate:44100 channels:2 interleaved:NO];
    [_engine connect:_player1 to:_mainMixer format:commonFormat];
    [_engine connect:_player2 to:_mainMixer format:commonFormat];
    
    // 配置音频会话,保证播放逻辑正常
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
    [[AVAudioSession sharedInstance] setActive:YES error:nil];
    
    // 提前启动引擎
    NSError *error;
    if (![_engine startAndReturnError:&error]) {
        NSLog(@"引擎启动失败: %@", error.localizedDescription);
    }
}

- (IBAction)playSound:(id)sender {
    // 先停止之前的播放任务,避免多次点击出现重叠
    if (_player1.isPlaying) [_player1 stop];
    if (_player2.isPlaying) [_player2 stop];
    
    NSURL *fileURL1 = [[NSBundle mainBundle] URLForResource:@"PianoC4" withExtension:@"mp3"];
    NSURL *fileURL2 = [[NSBundle mainBundle] URLForResource:@"PianoE4" withExtension:@"mp3"];
    AVAudioFile *file1 = [[AVAudioFile alloc]initForReading:fileURL1 error:nil];
    AVAudioFile *file2 = [[AVAudioFile alloc]initForReading:fileURL2 error:nil];
    
    const float kStartDelayTime = 0.025;
    AVAudioFormat *outputFormat = [_player1 outputFormatForBus:0];
    // 获取有效渲染时间,确保引擎已经运行
    AVAudioTime *currentRenderTime = _player1.lastRenderTime;
    if (!currentRenderTime.isSampleTimeValid) {
        // 兜底逻辑,如果采样时间无效用当前时间加小延迟作为基准
        currentRenderTime = [AVAudioTime timeWithHostTime:mach_absolute_time() + (uint64_t)(0.05 * NSEC_PER_SEC)];
    }
    AVAudioFramePosition startSampleTime = currentRenderTime.sampleTime;
    AVAudioTime *startTime1 = [AVAudioTime timeWithSampleTime:(startSampleTime + (kStartDelayTime * outputFormat.sampleRate)) atRate:outputFormat.sampleRate];
    AVAudioTime *startTime2 = [AVAudioTime timeWithSampleTime:(startSampleTime + ((kStartDelayTime + 1.2) * outputFormat.sampleRate)) atRate:outputFormat.sampleRate];
     
    [_player1 scheduleFile:file1 atTime:nil completionHandler:nil];
    [_player2 scheduleFile:file2 atTime:nil completionHandler:nil];
    
    [_player1 playAtTime:startTime1];
    [_player2 playAtTime:startTime2];
}

@end
额外优化建议
  • 如果需要支持同时播放两个音高的模式,只需要调整startTime2的计算逻辑,把1.2秒的偏移改为0即可
  • 可以提前将音频文件读入内存缓存,避免每次点击都重新读取文件导致的微小延迟

内容的提问来源于stack exchange,提问作者islander

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 21:15:02