AVAudioEngine首次播放双音频无延迟 后续播放延迟正常问题排查
问题根因
你首次点击触发播放时,代码在AVAudioEngine尚未启动的状态下就获取了_player1.lastRenderTime,此时该值处于无效状态(sampleTime固定为0),基于无效值计算出的startTime1和startTime2不被播放节点识别,两个音频都会忽略调度时间直接立即播放,就出现了没有1.2秒间隔的问题。
后续点击时引擎已经处于运行状态,lastRenderTime返回的是有效渲染时间戳,因此调度逻辑可以正常生效。
修复方案
调整代码执行顺序,同时优化冗余逻辑:
- 将引擎启动、节点连接逻辑提前到
viewDidLoad中执行,避免每次点击重复操作,保证播放调度前引擎已经处于运行状态 - 调度播放前增加
lastRenderTime有效性校验,避免无效时间戳导致调度失败 - 每次点击前先停止之前的播放任务,避免多次点击出现音频重叠混乱
修改后的核心代码
#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
相关产品推荐
相关产品推荐

