|  | @@ -10,7 +10,6 @@
 | 
	
		
			
				|  |  |  #import <Accelerate/Accelerate.h>
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #define READ_FILE_LENGTH (8192)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  #define BUFFER_SIZE (2048)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @interface KSMergeEnginePlayer ()
 | 
	
	
		
			
				|  | @@ -47,6 +46,12 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @property (nonatomic, assign) BOOL isCanceled; // 是否在取消
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +@property (nonatomic, strong) dispatch_group_t audioGroup;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +@property (nonatomic, strong) dispatch_queue_t analyzerQueue; // 分享线程
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +@property (nonatomic, strong) dispatch_queue_t mixerQueue;  // 混音专用队列
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  @end
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -87,11 +92,11 @@
 | 
	
		
			
				|  |  |      } @finally {
 | 
	
		
			
				|  |  |          
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |      [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:audioSession];
 | 
	
		
			
				|  |  |      [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRouteChange:) name:AVAudioSessionRouteChangeNotification object:audioSession];
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  - (void)retryInitEngine {
 | 
	
		
			
				|  |  |      // 如果audio engine不存在
 | 
	
		
			
				|  |  |      self.audioEngine = [[AVAudioEngine alloc] init];
 | 
	
	
		
			
				|  | @@ -128,14 +133,30 @@
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  - (void)prepareNativeSongWithUrl:(NSURL *)recordAudioUrl bgMusic:(NSURL *)bgMusicUrl {
 | 
	
		
			
				|  |  | +    self.audioGroup = dispatch_group_create();
 | 
	
		
			
				|  |  |      @weakObj(self);
 | 
	
		
			
				|  |  | -    dispatch_async(self.sourceQueue, ^{
 | 
	
		
			
				|  |  | +    dispatch_group_async(self.audioGroup, self.sourceQueue, ^{
 | 
	
		
			
				|  |  |          @strongObj(self);
 | 
	
		
			
				|  |  |          if (!self || self.isCanceled) {
 | 
	
		
			
				|  |  |              return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          [self loadAuidoFile:recordAudioUrl isBgm:NO];
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    dispatch_group_async(self.audioGroup, self.sourceQueue, ^{
 | 
	
		
			
				|  |  | +        @strongObj(self);
 | 
	
		
			
				|  |  | +        if (!self || self.isCanceled) {
 | 
	
		
			
				|  |  | +            return;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          [self loadAuidoFile:bgMusicUrl isBgm:YES];
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    // 待所有音频全部加载完成
 | 
	
		
			
				|  |  | +    dispatch_group_notify(self.audioGroup, self.sourceQueue, ^{
 | 
	
		
			
				|  |  | +        @strongObj(self);
 | 
	
		
			
				|  |  | +        if (!self || self.isCanceled) {
 | 
	
		
			
				|  |  | +            return;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          self.sampleRate = self.audioFile.fileFormat.sampleRate;
 | 
	
		
			
				|  |  |          [self configEngine];
 | 
	
		
			
				|  |  |          
 | 
	
	
		
			
				|  | @@ -161,20 +182,23 @@
 | 
	
		
			
				|  |  |      BOOL delegateRespondsToDidGenerateSpectrum = [self.delegate respondsToSelector:@selector(player:didGenerateSpectrum:)];
 | 
	
		
			
				|  |  |      AVAudioFormat *outputFormat = [self.audioEngine.mainMixerNode outputFormatForBus:0];
 | 
	
		
			
				|  |  |      [self.audioEngine.mainMixerNode removeTapOnBus:0];
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |      @weakObj(self);
 | 
	
		
			
				|  |  | -    [self.audioEngine.mainMixerNode installTapOnBus:0 bufferSize:BUFFER_SIZE format:outputFormat block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
 | 
	
		
			
				|  |  | +    [self.audioEngine.mainMixerNode installTapOnBus:0 bufferSize:BUFFER_SIZE*4 format:outputFormat block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
 | 
	
		
			
				|  |  | +        // 每累积 2 个缓冲区分析一次
 | 
	
		
			
				|  |  | +        static int counter = 0;
 | 
	
		
			
				|  |  | +        if (++counter % 2 != 0) return;
 | 
	
		
			
				|  |  |          @strongObj(self);
 | 
	
		
			
				|  |  |          if (!self || !self.nodePlayer.isPlaying || self.isCanceled) {
 | 
	
		
			
				|  |  |              return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        // 将频谱分析任务提交到后台队列
 | 
	
		
			
				|  |  | -        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
 | 
	
		
			
				|  |  | +        // 使用专门的队列处理频谱分析
 | 
	
		
			
				|  |  | +        dispatch_async(self.analyzerQueue, ^{
 | 
	
		
			
				|  |  |              if (self.analyzer.isAnalise == NO) {
 | 
	
		
			
				|  |  | -                // 分析音频缓冲区
 | 
	
		
			
				|  |  |                  NSArray<NSArray<NSNumber *> *> *spectra = [self.analyzer analyseWithBuffer:buffer];
 | 
	
		
			
				|  |  |                  
 | 
	
		
			
				|  |  | -                // 回到主线程更新 UI 或调用委托方法
 | 
	
		
			
				|  |  | -                dispatch_async(dispatch_get_main_queue(), ^{
 | 
	
		
			
				|  |  | +                // 使用 dispatch_async_main_safe 优化主线程切换
 | 
	
		
			
				|  |  | +                dispatch_main_async_safe(^{
 | 
	
		
			
				|  |  |                      if (delegateRespondsToDidGenerateSpectrum) {
 | 
	
		
			
				|  |  |                          [self.delegate player:self didGenerateSpectrum:spectra];
 | 
	
		
			
				|  |  |                      }
 | 
	
	
		
			
				|  | @@ -202,13 +226,22 @@
 | 
	
		
			
				|  |  |      } @finally {
 | 
	
		
			
				|  |  |          if (error) {
 | 
	
		
			
				|  |  |              // 错误回调
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        else { // 加载成功
 | 
	
		
			
				|  |  | +            if (self.delegate && [self.delegate respondsToSelector:@selector(enginePlayerDidError:error:)]) {
 | 
	
		
			
				|  |  | +                [self.delegate enginePlayerDidError:self error:error];
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            // 释放已分配资源
 | 
	
		
			
				|  |  | +            if (isBgm) {
 | 
	
		
			
				|  |  | +                self.bgAudioFile = nil;
 | 
	
		
			
				|  |  | +                self.bgAudioFormat = nil;
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                self.audioFile = nil;
 | 
	
		
			
				|  |  | +                self.audioFormat = nil;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  |              if (isBgm) {
 | 
	
		
			
				|  |  |                  self.bgAudioFile = audioFile;
 | 
	
		
			
				|  |  |                  self.bgAudioFormat = audioFormat;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            else {
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  |                  self.audioFile = audioFile;
 | 
	
		
			
				|  |  |                  self.audioFormat = audioFormat;
 | 
	
		
			
				|  |  |              }
 | 
	
	
		
			
				|  | @@ -234,118 +267,176 @@
 | 
	
		
			
				|  |  |      });
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -// 预计加载buffer
 | 
	
		
			
				|  |  | +// 1. 优化 Buffer 处理的线程控制
 | 
	
		
			
				|  |  |  - (void)prepareBuffer:(AVAudioFramePosition)startPosition offset:(NSInteger)offsetTime mixStart:(AVAudioFramePosition)mixStartPosition {
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | +    // 使用 barrier 确保数据同步
 | 
	
		
			
				|  |  |      @weakObj(self);
 | 
	
		
			
				|  |  | -    dispatch_async(self.sourceQueue, ^{
 | 
	
		
			
				|  |  | +    dispatch_barrier_async(self.sourceQueue, ^{
 | 
	
		
			
				|  |  |          @strongObj(self);
 | 
	
		
			
				|  |  | -        if (!self || self.isCanceled) {
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        if (!self.bgAudioFile || !self.audioFile) {
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        AVAudioFramePosition minFrameCount = (AVAudioFramePosition)MIN(self.bgAudioFile.length, self.audioFile.length);
 | 
	
		
			
				|  |  | -        AVAudioFrameCount offsetFrame = labs(offsetTime)/1000.0 * self.audioFile.processingFormat.sampleRate;
 | 
	
		
			
				|  |  | -        if (minFrameCount <= startPosition) {
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        AVAudioFrameCount frameToRead = minFrameCount - startPosition > READ_FILE_LENGTH ? READ_FILE_LENGTH : (AVAudioFrameCount)(minFrameCount - startPosition);
 | 
	
		
			
				|  |  | -        
 | 
	
		
			
				|  |  | +        [self processBufferWithStartPosition:startPosition offset:offsetTime mixStart:mixStartPosition];
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// 2. 将具体的处理逻辑抽取成单独的方法
 | 
	
		
			
				|  |  | +- (void)processBufferWithStartPosition:(AVAudioFramePosition)startPosition
 | 
	
		
			
				|  |  | +                                offset:(NSInteger)offsetTime
 | 
	
		
			
				|  |  | +                              mixStart:(AVAudioFramePosition)mixStartPosition {
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    if (!self || self.isCanceled) {
 | 
	
		
			
				|  |  | +        return;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    if (!self.bgAudioFile ||!self.audioFile) {
 | 
	
		
			
				|  |  | +        return;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    // 计算帧数
 | 
	
		
			
				|  |  | +    AVAudioFramePosition minFrameCount = (AVAudioFramePosition)MIN(self.bgAudioFile.length, self.audioFile.length);
 | 
	
		
			
				|  |  | +    AVAudioFrameCount offsetFrame = labs(offsetTime)/1000.0 * self.audioFile.processingFormat.sampleRate;
 | 
	
		
			
				|  |  | +    if (minFrameCount <= startPosition) {
 | 
	
		
			
				|  |  | +        return;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    // 计算需要读取的帧数
 | 
	
		
			
				|  |  | +    AVAudioFrameCount frameToRead = minFrameCount - startPosition > READ_FILE_LENGTH ? READ_FILE_LENGTH : (AVAudioFrameCount)(minFrameCount - startPosition);
 | 
	
		
			
				|  |  | +    // 读取背景音乐数据
 | 
	
		
			
				|  |  | +    @autoreleasepool  {
 | 
	
		
			
				|  |  |          self.bgAudioFile.framePosition = startPosition;
 | 
	
		
			
				|  |  | -        AVAudioPCMBuffer *bgBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:self.bgAudioFile.processingFormat frameCapacity:frameToRead];
 | 
	
		
			
				|  |  | +        AVAudioPCMBuffer *bgBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:self.bgAudioFile.processingFormat
 | 
	
		
			
				|  |  | +                                                                   frameCapacity:frameToRead];
 | 
	
		
			
				|  |  |          bgBuffer.frameLength = frameToRead;
 | 
	
		
			
				|  |  | -        BOOL readSuccess = [self.bgAudioFile readIntoBuffer:bgBuffer frameCount:frameToRead error:nil];
 | 
	
		
			
				|  |  | -        if (!readSuccess) {
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        if (![self.bgAudioFile readIntoBuffer:bgBuffer frameCount:frameToRead error:nil]) {
 | 
	
		
			
				|  |  |              return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        AVAudioPCMBuffer *recordBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:self.audioFile.processingFormat frameCapacity:frameToRead];
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        // 读取录音数据
 | 
	
		
			
				|  |  | +        AVAudioPCMBuffer *recordBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:self.audioFile.processingFormat
 | 
	
		
			
				|  |  | +                                                                       frameCapacity:frameToRead];
 | 
	
		
			
				|  |  |          recordBuffer.frameLength = frameToRead;
 | 
	
		
			
				|  |  |          
 | 
	
		
			
				|  |  | +        // 处理偏移时间
 | 
	
		
			
				|  |  |          if (offsetTime >= 0) { // 演奏需要提前
 | 
	
		
			
				|  |  | -            self.audioFile.framePosition = startPosition + offsetFrame;
 | 
	
		
			
				|  |  | -            AVAudioFrameCount audioReadFrame = frameToRead;
 | 
	
		
			
				|  |  | -            if (startPosition + offsetFrame + frameToRead > minFrameCount) { // 如果超出
 | 
	
		
			
				|  |  | -                audioReadFrame = (AVAudioFrameCount)(minFrameCount - startPosition - offsetFrame);
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            if (audioReadFrame <= frameToRead) {
 | 
	
		
			
				|  |  | -                BOOL isSuccess = [self.audioFile readIntoBuffer:recordBuffer frameCount:audioReadFrame error:nil];
 | 
	
		
			
				|  |  | -                if (!isSuccess) {
 | 
	
		
			
				|  |  | -                    return;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +            [self processPositiveOffset:offsetTime
 | 
	
		
			
				|  |  | +                          startPosition:startPosition
 | 
	
		
			
				|  |  | +                            frameToRead:frameToRead
 | 
	
		
			
				|  |  | +                           recordBuffer:recordBuffer
 | 
	
		
			
				|  |  | +                          minFrameCount:minFrameCount
 | 
	
		
			
				|  |  | +                            offsetFrame:offsetFrame];
 | 
	
		
			
				|  |  | +        } else { // 演奏需要延后
 | 
	
		
			
				|  |  | +            [self processNegativeOffset:offsetTime
 | 
	
		
			
				|  |  | +                          startPosition:startPosition
 | 
	
		
			
				|  |  | +                            frameToRead:frameToRead
 | 
	
		
			
				|  |  | +                           recordBuffer:recordBuffer
 | 
	
		
			
				|  |  | +                          minFrameCount:minFrameCount
 | 
	
		
			
				|  |  | +                            offsetFrame:offsetFrame];
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        else { // 演奏需要延后
 | 
	
		
			
				|  |  | -            AVAudioFramePosition audioFramePosition = startPosition - offsetFrame;
 | 
	
		
			
				|  |  | -            if (audioFramePosition > 0) {
 | 
	
		
			
				|  |  | -                self.audioFile.framePosition = audioFramePosition;
 | 
	
		
			
				|  |  | -                AVAudioFrameCount audioReadFrame = frameToRead;
 | 
	
		
			
				|  |  | -                if (audioFramePosition + frameToRead > minFrameCount) { // 如果超出
 | 
	
		
			
				|  |  | -                    audioReadFrame = (AVAudioFrameCount)(minFrameCount - audioFramePosition);
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -                // AVAudioFrameCount 无符号整型 uint32_t
 | 
	
		
			
				|  |  | -                if (audioReadFrame <= frameToRead) {
 | 
	
		
			
				|  |  | -                    BOOL isSuccess = [self.audioFile readIntoBuffer:recordBuffer frameCount:audioReadFrame error:nil];
 | 
	
		
			
				|  |  | -                    if (!isSuccess) {
 | 
	
		
			
				|  |  | -                        return;
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            else {
 | 
	
		
			
				|  |  | -                self.audioFile.framePosition = 0;
 | 
	
		
			
				|  |  | -                // 需要读取部分数据
 | 
	
		
			
				|  |  | -                if (offsetFrame - startPosition < frameToRead) {
 | 
	
		
			
				|  |  | -                    AVAudioFrameCount readCount = (AVAudioFrameCount)(offsetFrame - startPosition);
 | 
	
		
			
				|  |  | -//                    NSLog(@"----need readCount --%u", readCount);
 | 
	
		
			
				|  |  | -                    AVAudioPCMBuffer *tempBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:self.audioFile.processingFormat frameCapacity:readCount];
 | 
	
		
			
				|  |  | -                    tempBuffer.frameLength = readCount;
 | 
	
		
			
				|  |  | -                    BOOL isSuccess = [self.audioFile readIntoBuffer:tempBuffer error:nil];
 | 
	
		
			
				|  |  | -                    if (!isSuccess) {
 | 
	
		
			
				|  |  | -                        return;
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                    float *tempData = tempBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | -                    float *recordData = recordBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | -                    // 复制数据到 recordBuffer
 | 
	
		
			
				|  |  | -                    AVAudioFrameCount startFrame = frameToRead - readCount;
 | 
	
		
			
				|  |  | -                    for (AVAudioFrameCount i = 0; i < readCount; i++) {
 | 
	
		
			
				|  |  | -                        recordData[startFrame + i] = tempData[i];
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        // 混音处理
 | 
	
		
			
				|  |  | +        [self mixAudioBuffers:bgBuffer
 | 
	
		
			
				|  |  | +                 recordBuffer:recordBuffer
 | 
	
		
			
				|  |  | +                  frameToRead:frameToRead
 | 
	
		
			
				|  |  | +                startPosition:startPosition
 | 
	
		
			
				|  |  | +                     mixStart:mixStartPosition];
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// 3. 处理正偏移
 | 
	
		
			
				|  |  | +- (void)processPositiveOffset:(NSInteger)offsetTime
 | 
	
		
			
				|  |  | +                startPosition:(AVAudioFramePosition)startPosition
 | 
	
		
			
				|  |  | +                  frameToRead:(AVAudioFrameCount)frameToRead
 | 
	
		
			
				|  |  | +                 recordBuffer:(AVAudioPCMBuffer *)recordBuffer
 | 
	
		
			
				|  |  | +                minFrameCount:(AVAudioFramePosition)minFrameCount
 | 
	
		
			
				|  |  | +                  offsetFrame:(AVAudioFrameCount)offsetFrame {
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    self.audioFile.framePosition = startPosition + offsetFrame;
 | 
	
		
			
				|  |  | +    AVAudioFrameCount audioReadFrame = frameToRead;
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    if (startPosition + offsetFrame + frameToRead > minFrameCount) {
 | 
	
		
			
				|  |  | +        audioReadFrame = (AVAudioFrameCount)(minFrameCount - startPosition - offsetFrame);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    if (audioReadFrame <= frameToRead) {
 | 
	
		
			
				|  |  | +        [self.audioFile readIntoBuffer:recordBuffer frameCount:audioReadFrame error:nil];
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// 4. 处理负偏移
 | 
	
		
			
				|  |  | +- (void)processNegativeOffset:(NSInteger)offsetTime
 | 
	
		
			
				|  |  | +                startPosition:(AVAudioFramePosition)startPosition
 | 
	
		
			
				|  |  | +                  frameToRead:(AVAudioFrameCount)frameToRead
 | 
	
		
			
				|  |  | +                 recordBuffer:(AVAudioPCMBuffer *)recordBuffer
 | 
	
		
			
				|  |  | +                minFrameCount:(AVAudioFramePosition)minFrameCount
 | 
	
		
			
				|  |  | +                  offsetFrame:(AVAudioFrameCount)offsetFrame {
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    AVAudioFramePosition audioFramePosition = startPosition - offsetFrame;
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    if (audioFramePosition > 0) {
 | 
	
		
			
				|  |  | +        self.audioFile.framePosition = audioFramePosition;
 | 
	
		
			
				|  |  | +        AVAudioFrameCount audioReadFrame = frameToRead;
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        if (audioFramePosition + frameToRead > minFrameCount) {
 | 
	
		
			
				|  |  | +            audioReadFrame = (AVAudioFrameCount)(minFrameCount - audioFramePosition);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          
 | 
	
		
			
				|  |  | -        float *bgLeftChannel = bgBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | -        float *bgRightChannel = bgBuffer.floatChannelData[1];
 | 
	
		
			
				|  |  | -        if (bgBuffer.format.channelCount == 1) {
 | 
	
		
			
				|  |  | -            bgRightChannel = bgBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | +        if (audioReadFrame <= frameToRead) {
 | 
	
		
			
				|  |  | +            [self.audioFile readIntoBuffer:recordBuffer frameCount:audioReadFrame error:nil];
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        // 录音文件未单声道
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  | +        self.audioFile.framePosition = 0;
 | 
	
		
			
				|  |  | +        if (offsetFrame - startPosition < frameToRead) {
 | 
	
		
			
				|  |  | +            AVAudioFrameCount readCount = (AVAudioFrameCount)(offsetFrame - startPosition);
 | 
	
		
			
				|  |  | +            AVAudioPCMBuffer *tempBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:self.audioFile.processingFormat
 | 
	
		
			
				|  |  | +                                                                         frameCapacity:readCount];
 | 
	
		
			
				|  |  | +            tempBuffer.frameLength = readCount;
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            if ([self.audioFile readIntoBuffer:tempBuffer error:nil]) {
 | 
	
		
			
				|  |  | +                float *tempData = tempBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | +                float *recordData = recordBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | +                AVAudioFrameCount startFrame = frameToRead - readCount;
 | 
	
		
			
				|  |  | +                memcpy(recordData + startFrame, tempData, readCount * sizeof(float));
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// 5. 混音处理
 | 
	
		
			
				|  |  | +- (void)mixAudioBuffers:(AVAudioPCMBuffer *)bgBuffer
 | 
	
		
			
				|  |  | +           recordBuffer:(AVAudioPCMBuffer *)recordBuffer
 | 
	
		
			
				|  |  | +            frameToRead:(AVAudioFrameCount)frameToRead
 | 
	
		
			
				|  |  | +          startPosition:(AVAudioFramePosition)startPosition
 | 
	
		
			
				|  |  | +               mixStart:(AVAudioFramePosition)mixStartPosition {
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    // 使用异步队列处理混音
 | 
	
		
			
				|  |  | +    dispatch_async(self.mixerQueue, ^{
 | 
	
		
			
				|  |  | +        float *bgLeftChannel = bgBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | +        float *bgRightChannel = bgBuffer.floatChannelData[bgBuffer.format.channelCount > 1 ? 1 : 0];
 | 
	
		
			
				|  |  |          float *recordLeftChannel = recordBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  |          
 | 
	
		
			
				|  |  |          float *mixLeftChannel = self.mixBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  |          float *mixRightChannel = self.mixBuffer.floatChannelData[1];
 | 
	
		
			
				|  |  |          
 | 
	
		
			
				|  |  | -        for (int frame = 0; frame < frameToRead; frame++) {
 | 
	
		
			
				|  |  | +        AVAudioFramePosition mixStartIndex = startPosition - mixStartPosition;
 | 
	
		
			
				|  |  | +        if (mixStartIndex >= 0 && mixStartIndex < self.mixBuffer.frameLength) {
 | 
	
		
			
				|  |  | +            // 应用背景音乐音量
 | 
	
		
			
				|  |  | +            vDSP_vsmul(bgLeftChannel, 1, &self->_bgVolume, bgLeftChannel, 1, frameToRead);
 | 
	
		
			
				|  |  | +            vDSP_vsmul(bgRightChannel, 1, &self->_bgVolume, bgRightChannel, 1, frameToRead);
 | 
	
		
			
				|  |  |              
 | 
	
		
			
				|  |  | -            AVAudioFramePosition mixIndex = frame + startPosition - mixStartPosition;
 | 
	
		
			
				|  |  | -            float leftChannel = (frame < bgBuffer.frameLength) ? bgLeftChannel[frame] : 0;
 | 
	
		
			
				|  |  | -            float rightChannel = (frame < bgBuffer.frameLength) ? bgRightChannel[frame] : 0;
 | 
	
		
			
				|  |  | +            // 应用录音音量
 | 
	
		
			
				|  |  | +            vDSP_vsmul(recordLeftChannel, 1, &self->_recordVolume, recordLeftChannel, 1, frameToRead);
 | 
	
		
			
				|  |  |              
 | 
	
		
			
				|  |  | -            float recordData = (frame < recordBuffer.frameLength) ? recordLeftChannel[frame] : 0;
 | 
	
		
			
				|  |  | +            // 混合左声道
 | 
	
		
			
				|  |  | +            vDSP_vadd(bgLeftChannel, 1, recordLeftChannel, 1, mixLeftChannel + mixStartIndex, 1, frameToRead);
 | 
	
		
			
				|  |  |              
 | 
	
		
			
				|  |  | -            float mixLeftData = [self mixChannelData:leftChannel bgVolume:self.bgVolume recordData:recordData recordVolume:self.recordVolume];
 | 
	
		
			
				|  |  | -            float mixRightData = [self mixChannelData:rightChannel bgVolume:self.bgVolume recordData:recordData recordVolume:self.recordVolume];
 | 
	
		
			
				|  |  | +            // 混合右声道
 | 
	
		
			
				|  |  | +            vDSP_vadd(bgRightChannel, 1, recordLeftChannel, 1, mixRightChannel + mixStartIndex, 1, frameToRead);
 | 
	
		
			
				|  |  |              
 | 
	
		
			
				|  |  | -            // 防止数组越界
 | 
	
		
			
				|  |  | -            if (mixIndex >= 0 && mixIndex < self.mixBuffer.frameLength) {
 | 
	
		
			
				|  |  | -                mixLeftChannel[mixIndex] = fminf(fmaxf(mixLeftData, -1.0f), 1.0f);
 | 
	
		
			
				|  |  | -                mixRightChannel[mixIndex] = fminf(fmaxf(mixRightData, -1.0f), 1.0f);
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +            // 应用增益
 | 
	
		
			
				|  |  | +            float scale = 0.5f;
 | 
	
		
			
				|  |  | +            vDSP_vsmul(mixLeftChannel + mixStartIndex, 1, &scale, mixLeftChannel + mixStartIndex, 1, frameToRead);
 | 
	
		
			
				|  |  | +            vDSP_vsmul(mixRightChannel + mixStartIndex, 1, &scale, mixRightChannel + mixStartIndex, 1, frameToRead);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      });
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  - (void)scheduleBufferFromPosition:(AVAudioFramePosition)startPosition {
 | 
	
		
			
				|  |  |      [self resetMixBuffer];
 | 
	
		
			
				|  |  |      self.startPosition = startPosition;
 | 
	
	
		
			
				|  | @@ -356,14 +447,8 @@
 | 
	
		
			
				|  |  |      }];
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -- (float)mixChannelData:(float)bgData bgVolume:(float)bgVolume recordData:(float)recordData recordVolume:(float)recordVolume {
 | 
	
		
			
				|  |  | -    return (bgData * bgVolume + recordData * recordVolume) / 2;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  - (void)changeVolume:(float)bgVolume recordVolume:(float)recordVolume {
 | 
	
		
			
				|  |  | -//    NSLog(@"bg volume ---- %f,  record volume ---- %f", bgVolume, recordVolume);
 | 
	
		
			
				|  |  | +    //    NSLog(@"bg volume ---- %f,  record volume ---- %f", bgVolume, recordVolume);
 | 
	
		
			
				|  |  |      self.bgVolume = bgVolume;
 | 
	
		
			
				|  |  |      self.recordVolume = recordVolume;
 | 
	
		
			
				|  |  |  }
 | 
	
	
		
			
				|  | @@ -432,7 +517,7 @@
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  - (void)seekToTimePlay:(NSInteger)time {
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |      if (self.audioEngine.isRunning == NO) {
 | 
	
		
			
				|  |  |          [self startEngine];
 | 
	
		
			
				|  |  |      }
 | 
	
	
		
			
				|  | @@ -483,7 +568,7 @@
 | 
	
		
			
				|  |  |      // 跳转进度
 | 
	
		
			
				|  |  |      self.currentFrame = startFrame;
 | 
	
		
			
				|  |  |      [self scheduleBufferFromPosition:startFrame];
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |      if (needPlay) {
 | 
	
		
			
				|  |  |          [self.nodePlayer play];
 | 
	
		
			
				|  |  |          [self startTimer];
 | 
	
	
		
			
				|  | @@ -523,11 +608,25 @@
 | 
	
		
			
				|  |  |      return _sourceQueue;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +- (dispatch_queue_t)analyzerQueue {
 | 
	
		
			
				|  |  | +    if (!_analyzerQueue) {
 | 
	
		
			
				|  |  | +        _analyzerQueue = dispatch_queue_create("com.ks.analyzer", DISPATCH_QUEUE_SERIAL);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return _analyzerQueue;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +- (dispatch_queue_t)mixerQueue {
 | 
	
		
			
				|  |  | +    if (!_mixerQueue) {
 | 
	
		
			
				|  |  | +        _mixerQueue = dispatch_queue_create("com.ks.mixer", DISPATCH_QUEUE_SERIAL);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return _mixerQueue;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  - (NSTimer *)timer {
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  |      if (!_timer) {
 | 
	
		
			
				|  |  |          __weak typeof(self)weakSelf = self;
 | 
	
		
			
				|  |  | -        _timer = [NSTimer scheduledTimerWithTimeInterval:0.01 repeats:YES block:^(NSTimer * _Nonnull timer) {
 | 
	
		
			
				|  |  | +        // 将触发间隔从 0.01 秒调整为 0.1 秒
 | 
	
		
			
				|  |  | +        _timer = [NSTimer scheduledTimerWithTimeInterval:0.1 repeats:YES block:^(NSTimer * _Nonnull timer) {
 | 
	
		
			
				|  |  |              [weakSelf timeFunction];
 | 
	
		
			
				|  |  |          }];
 | 
	
		
			
				|  |  |          [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
 | 
	
	
		
			
				|  | @@ -552,11 +651,11 @@
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      else {
 | 
	
		
			
				|  |  |          // 定时器10ms出触发一次 buffer每100ms执行一次
 | 
	
		
			
				|  |  | -        if (self.timeCount % 10 == 0) {
 | 
	
		
			
				|  |  | +//        if (self.timeCount % 5 == 0) {
 | 
	
		
			
				|  |  |              [self scheduleMixBuffer];
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +//        }
 | 
	
		
			
				|  |  |          self.timeCount++;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  |          if (self.delegate && [self.delegate respondsToSelector:@selector(updatePlayProgress:andTotalTime:andProgress:currentInterval:inPlayer:)]) {
 | 
	
		
			
				|  |  |              [self.delegate updatePlayProgress:currentTime andTotalTime:self.totalDuration andProgress:progress currentInterval:inteveral*1000 inPlayer:self];
 | 
	
		
			
				|  |  |          }
 | 
	
	
		
			
				|  | @@ -589,9 +688,9 @@
 | 
	
		
			
				|  |  |              NSLog(@"播放已停止");
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          double elapsedSamples = (double)currentFrame;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  |          NSTimeInterval currentTime = elapsedSamples / self.sampleRate;
 | 
	
		
			
				|  |  | -//        NSLog(@"当前时间----- %f",currentTime*1000);
 | 
	
		
			
				|  |  | +        //        NSLog(@"当前时间----- %f",currentTime*1000);
 | 
	
		
			
				|  |  |          return currentTime*1000;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      else {
 | 
	
	
		
			
				|  | @@ -612,32 +711,67 @@
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  - (void)releaseAudioResources {
 | 
	
		
			
				|  |  | -    if (self.audioEngine.isRunning) {
 | 
	
		
			
				|  |  | -        [self.audioEngine stop];
 | 
	
		
			
				|  |  | -        [self.audioEngine reset];
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -    // 停止并移除 player node
 | 
	
		
			
				|  |  | -    if (self.nodePlayer) {
 | 
	
		
			
				|  |  | -        [self.nodePlayer stop];
 | 
	
		
			
				|  |  | -        [self.audioEngine detachNode:self.nodePlayer];
 | 
	
		
			
				|  |  | -        self.nodePlayer = nil;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -    if (self.analyzer) {
 | 
	
		
			
				|  |  | -        self.analyzer = nil;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    // 释放音频文件
 | 
	
		
			
				|  |  | -    self.audioFile = nil;
 | 
	
		
			
				|  |  | -    self.audioFormat = nil;
 | 
	
		
			
				|  |  | -    self.bgAudioFile = nil;
 | 
	
		
			
				|  |  | -    self.bgAudioFormat = nil;
 | 
	
		
			
				|  |  | -    self.mixBuffer = nil;
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    // 释放音频引擎
 | 
	
		
			
				|  |  | -    self.audioEngine = nil;
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    // 打印确认释放日志
 | 
	
		
			
				|  |  | -    NSLog(@"Audio resources successfully released.");
 | 
	
		
			
				|  |  | +    // 使用 sync 而不是 async 确保资源释放完成
 | 
	
		
			
				|  |  | +    dispatch_sync(self.sourceQueue, ^{
 | 
	
		
			
				|  |  | +        @try {
 | 
	
		
			
				|  |  | +            // 1. 首先停止所有活动
 | 
	
		
			
				|  |  | +            if (self.nodePlayer.isPlaying) {
 | 
	
		
			
				|  |  | +                [self.nodePlayer stop];
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            if (self.audioEngine.isRunning) {
 | 
	
		
			
				|  |  | +                [self removeTap];
 | 
	
		
			
				|  |  | +                [self.audioEngine stop];
 | 
	
		
			
				|  |  | +                [self.audioEngine reset];
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 2. 移除节点连接
 | 
	
		
			
				|  |  | +            if (self.nodePlayer && self.audioEngine) {
 | 
	
		
			
				|  |  | +                @try {
 | 
	
		
			
				|  |  | +                    [self.audioEngine detachNode:self.nodePlayer];
 | 
	
		
			
				|  |  | +                } @catch (NSException *exception) {
 | 
	
		
			
				|  |  | +                    NSLog(@"Detach node exception: %@", exception);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 3. 使用临时变量持有引用
 | 
	
		
			
				|  |  | +            AVAudioEngine *tempEngine = self.audioEngine;
 | 
	
		
			
				|  |  | +            AVAudioPlayerNode *tempPlayer = self.nodePlayer;
 | 
	
		
			
				|  |  | +            AVAudioFile *tempAudioFile = self.audioFile;
 | 
	
		
			
				|  |  | +            AVAudioFile *tempBgAudioFile = self.bgAudioFile;
 | 
	
		
			
				|  |  | +            AVAudioPCMBuffer *tempMixBuffer = self.mixBuffer;
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 4. 先清空属性引用
 | 
	
		
			
				|  |  | +            self.audioEngine = nil;
 | 
	
		
			
				|  |  | +            self.nodePlayer = nil;
 | 
	
		
			
				|  |  | +            self.analyzer = nil;
 | 
	
		
			
				|  |  | +            self.audioFile = nil;
 | 
	
		
			
				|  |  | +            self.audioFormat = nil;
 | 
	
		
			
				|  |  | +            self.bgAudioFile = nil;
 | 
	
		
			
				|  |  | +            self.bgAudioFormat = nil;
 | 
	
		
			
				|  |  | +            self.mixBuffer = nil;
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 5. 在临时变量作用域内释放资源
 | 
	
		
			
				|  |  | +            @autoreleasepool {
 | 
	
		
			
				|  |  | +                tempEngine = nil;
 | 
	
		
			
				|  |  | +                tempPlayer = nil;
 | 
	
		
			
				|  |  | +                tempAudioFile = nil;
 | 
	
		
			
				|  |  | +                tempBgAudioFile = nil;
 | 
	
		
			
				|  |  | +                tempMixBuffer = nil;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 6. 重置状态
 | 
	
		
			
				|  |  | +            self.isReady = NO;
 | 
	
		
			
				|  |  | +            self.currentFrame = 0;
 | 
	
		
			
				|  |  | +            self.startPosition = 0;
 | 
	
		
			
				|  |  | +            self.timeCount = 0;
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +        } @catch (NSException *exception) {
 | 
	
		
			
				|  |  | +            NSLog(@"Release resources exception: %@", exception);
 | 
	
		
			
				|  |  | +        } @finally {
 | 
	
		
			
				|  |  | +            NSLog(@"Audio resources release completed");
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  - (void)dealloc {
 |