|  | @@ -7,8 +7,11 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #import "KSMergeEnginePlayer.h"
 | 
	
		
			
				|  |  |  #import <AVFoundation/AVFoundation.h>
 | 
	
		
			
				|  |  | +#import <Accelerate/Accelerate.h>
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +#define READ_FILE_LENGTH (8192)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +#define BUFFER_SIZE (2048)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @interface KSMergeEnginePlayer ()
 | 
	
		
			
				|  |  |  /** 定时器 */
 | 
	
	
		
			
				|  | @@ -34,24 +37,14 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @property (nonatomic, strong) AVAudioPCMBuffer *mixBuffer;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -@property (nonatomic, strong) AVAudioPCMBuffer *bgBuffer;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -@property (nonatomic, strong) AVAudioPCMBuffer *recordBuffer;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  @property (nonatomic, assign) AVAudioFramePosition currentFrame;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @property (nonatomic, assign) double sampleRate;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -@property (nonatomic, assign) BOOL stopMix; // 是否停止mix
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -@property (nonatomic, strong) dispatch_semaphore_t mixChangeSemaphore; // mix信号量
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -@property (nonatomic, assign) BOOL stopChangeVolume; // 是否停止音量修改循环
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -@property (nonatomic, strong) dispatch_semaphore_t volumeChangeSemaphore;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  @property (nonatomic, assign) BOOL isInterrupt; // 是否被打断
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +@property (nonatomic, assign) NSInteger timeCount;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  @end
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -68,8 +61,6 @@
 | 
	
		
			
				|  |  |  - (void)configDefault {
 | 
	
		
			
				|  |  |      self.recordVolume = 1.0f;
 | 
	
		
			
				|  |  |      self.bgVolume = 1.0f;
 | 
	
		
			
				|  |  | -    self.mixChangeSemaphore = dispatch_semaphore_create(1); // 初始化信号量
 | 
	
		
			
				|  |  | -    self.volumeChangeSemaphore = dispatch_semaphore_create(1); // 初始化信号量,初始值为1
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  - (void)configEngine {
 | 
	
	
		
			
				|  | @@ -111,225 +102,235 @@
 | 
	
		
			
				|  |  |          if (error) {
 | 
	
		
			
				|  |  |              self.audioEngine = nil;
 | 
	
		
			
				|  |  |              // 错误回调
 | 
	
		
			
				|  |  | -            [self sendInterruptError:error];
 | 
	
		
			
				|  |  | +            if (self.delegate && [self.delegate respondsToSelector:@selector(enginePlayerDidError:error:)]) {
 | 
	
		
			
				|  |  | +                [self.delegate enginePlayerDidError:self error:error];
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  - (void)prepareNativeSongWithUrl:(NSURL *)recordAudioUrl bgMusic:(NSURL *)bgMusicUrl {
 | 
	
		
			
				|  |  | -    [self loadAuidoFile:recordAudioUrl isBgm:NO];
 | 
	
		
			
				|  |  | -    [self loadAuidoFile:bgMusicUrl isBgm:YES];
 | 
	
		
			
				|  |  | -    self.sampleRate = self.audioFile.fileFormat.sampleRate;
 | 
	
		
			
				|  |  | -    [self configEngine];
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    AVAudioFormat *outputFormat = [self.audioEngine.mainMixerNode outputFormatForBus:0];
 | 
	
		
			
				|  |  | -    [self.audioEngine connect:self.nodePlayer to:self.audioEngine.mainMixerNode format:outputFormat];
 | 
	
		
			
				|  |  | -    [self startEngine];
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    if (self.audioEngine && self.audioEngine.isRunning) {
 | 
	
		
			
				|  |  | -        dispatch_async(self.sourceQueue, ^{
 | 
	
		
			
				|  |  | +    dispatch_async(self.sourceQueue, ^{
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        [self loadAuidoFile:recordAudioUrl isBgm:NO];
 | 
	
		
			
				|  |  | +        [self loadAuidoFile:bgMusicUrl isBgm:YES];
 | 
	
		
			
				|  |  | +        self.sampleRate = self.audioFile.fileFormat.sampleRate;
 | 
	
		
			
				|  |  | +        [self configEngine];
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        AVAudioFormat *outputFormat = [self.audioEngine.mainMixerNode outputFormatForBus:0];
 | 
	
		
			
				|  |  | +        [self.audioEngine connect:self.nodePlayer to:self.audioEngine.mainMixerNode format:outputFormat];
 | 
	
		
			
				|  |  | +//        [self addTapBus];
 | 
	
		
			
				|  |  | +        [self startEngine];
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        if (self.audioEngine && self.audioEngine.isRunning) {
 | 
	
		
			
				|  |  |              [self prepareBufferFrame];
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +- (void)addTapBus {
 | 
	
		
			
				|  |  | +    BOOL delegateRespondsToDidGenerateSpectrum = [self.delegate respondsToSelector:@selector(player:didGenerateSpectrum:)];
 | 
	
		
			
				|  |  | +    self.analyzer = [[KSRealtimeAnalyzer alloc] initWithFFTSize:BUFFER_SIZE];
 | 
	
		
			
				|  |  | +    AVAudioFormat *outputFormat = [self.audioEngine.mainMixerNode outputFormatForBus:0];
 | 
	
		
			
				|  |  | +    @weakObj(self);
 | 
	
		
			
				|  |  | +    [self.audioEngine.mainMixerNode removeTapOnBus:0];
 | 
	
		
			
				|  |  | +    [self.audioEngine.mainMixerNode installTapOnBus:0 bufferSize:BUFFER_SIZE format:outputFormat block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
 | 
	
		
			
				|  |  | +        @strongObj(self);
 | 
	
		
			
				|  |  | +        if (!self || !self.nodePlayer.isPlaying) {
 | 
	
		
			
				|  |  | +            return;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        // 将频谱分析任务提交到后台队列
 | 
	
		
			
				|  |  | +            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
 | 
	
		
			
				|  |  | +                if (self.analyzer.isAnalise == NO) {
 | 
	
		
			
				|  |  | +                    // 分析音频缓冲区
 | 
	
		
			
				|  |  | +                    NSArray<NSArray<NSNumber *> *> *spectra = [self.analyzer analyseWithBuffer:buffer];
 | 
	
		
			
				|  |  | +                    
 | 
	
		
			
				|  |  | +                    // 回到主线程更新 UI 或调用委托方法
 | 
	
		
			
				|  |  | +                    dispatch_async(dispatch_get_main_queue(), ^{
 | 
	
		
			
				|  |  | +                        if (delegateRespondsToDidGenerateSpectrum) {
 | 
	
		
			
				|  |  | +                            [self.delegate player:self didGenerateSpectrum:spectra];
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +                    });
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            });
 | 
	
		
			
				|  |  | +    }];
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  - (void)loadAuidoFile:(NSURL *)audioFileUrl isBgm:(BOOL)isBgm {
 | 
	
		
			
				|  |  | -    dispatch_sync(self.sourceQueue, ^{
 | 
	
		
			
				|  |  | -        NSError *error = nil;
 | 
	
		
			
				|  |  | -        AVAudioFile *audioFile = nil;
 | 
	
		
			
				|  |  | -        AVAudioFormat *audioFormat = nil;
 | 
	
		
			
				|  |  | -        @try {
 | 
	
		
			
				|  |  | -            audioFile = [[AVAudioFile alloc] initForReading:audioFileUrl error:&error];
 | 
	
		
			
				|  |  | -            audioFormat = audioFile.processingFormat;
 | 
	
		
			
				|  |  | -            
 | 
	
		
			
				|  |  | -        } @catch (NSException *exception) {
 | 
	
		
			
				|  |  | -            audioFile = nil;
 | 
	
		
			
				|  |  | -            audioFormat = nil;
 | 
	
		
			
				|  |  | -        } @finally {
 | 
	
		
			
				|  |  | -            if (error) {
 | 
	
		
			
				|  |  | -                // 错误回调
 | 
	
		
			
				|  |  | +    NSError *error = nil;
 | 
	
		
			
				|  |  | +    AVAudioFile *audioFile = nil;
 | 
	
		
			
				|  |  | +    AVAudioFormat *audioFormat = nil;
 | 
	
		
			
				|  |  | +    @try {
 | 
	
		
			
				|  |  | +        audioFile = [[AVAudioFile alloc] initForReading:audioFileUrl error:&error];
 | 
	
		
			
				|  |  | +        audioFormat = audioFile.processingFormat;
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +    } @catch (NSException *exception) {
 | 
	
		
			
				|  |  | +        audioFile = nil;
 | 
	
		
			
				|  |  | +        audioFormat = nil;
 | 
	
		
			
				|  |  | +    } @finally {
 | 
	
		
			
				|  |  | +        if (error) {
 | 
	
		
			
				|  |  | +            // 错误回调
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        else { // 加载成功
 | 
	
		
			
				|  |  | +            if (isBgm) {
 | 
	
		
			
				|  |  | +                self.bgAudioFile = audioFile;
 | 
	
		
			
				|  |  | +                self.bgAudioFormat = audioFormat;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | -            else { // 加载成功
 | 
	
		
			
				|  |  | -                if (isBgm) {
 | 
	
		
			
				|  |  | -                    self.bgAudioFile = audioFile;
 | 
	
		
			
				|  |  | -                    self.bgAudioFormat = audioFormat;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -                else {
 | 
	
		
			
				|  |  | -                    self.audioFile = audioFile;
 | 
	
		
			
				|  |  | -                    self.audioFormat = audioFormat;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | +            else {
 | 
	
		
			
				|  |  | +                self.audioFile = audioFile;
 | 
	
		
			
				|  |  | +                self.audioFormat = audioFormat;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -    });
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -- (void)prepareBufferFrame {
 | 
	
		
			
				|  |  | +- (void)resetMixBuffer {
 | 
	
		
			
				|  |  |      AVAudioFrameCount minFrameCount = (AVAudioFrameCount)MIN(self.bgAudioFile.length, self.audioFile.length);
 | 
	
		
			
				|  |  |      // mixBuffer
 | 
	
		
			
				|  |  |      AVAudioFormat *outputFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:self.bgAudioFormat.sampleRate channels:2];
 | 
	
		
			
				|  |  |      self.mixBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:outputFormat frameCapacity:minFrameCount];
 | 
	
		
			
				|  |  |      self.mixBuffer.frameLength = minFrameCount;
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    self.bgBuffer = [self loadAudioSegment:self.bgAudioFile startFrame:0 frameCount:minFrameCount];
 | 
	
		
			
				|  |  | -    self.recordBuffer = [self loadAudioSegment:self.audioFile startFrame:0 frameCount:minFrameCount];
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    if (self.delegate && [self.delegate respondsToSelector:@selector(enginePlayerIsReadyPlay:)]) {
 | 
	
		
			
				|  |  | -        self.isReady = YES;
 | 
	
		
			
				|  |  | -        [self.delegate enginePlayerIsReadyPlay:self];
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -- (AVAudioPCMBuffer *)loadAudioSegment:(AVAudioFile *)audioFile startFrame:(AVAudioFramePosition)startFrame frameCount:(AVAudioFrameCount)frameCount {
 | 
	
		
			
				|  |  | -    AVAudioFormat *audioFromat = audioFile.processingFormat;
 | 
	
		
			
				|  |  | -    AVAudioFrameCount frameToRead = (AVAudioFrameCount)MIN(frameCount, (AVAudioFrameCount)audioFile.length - startFrame);
 | 
	
		
			
				|  |  | -    if (startFrame > audioFile.length) {
 | 
	
		
			
				|  |  | -        return nil;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -    AVAudioPCMBuffer *buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:audioFromat frameCapacity:frameToRead];
 | 
	
		
			
				|  |  | -    buffer.frameLength = frameToRead;
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    audioFile.framePosition = startFrame;
 | 
	
		
			
				|  |  | -    if (frameToRead > 0) {
 | 
	
		
			
				|  |  | -        @try {
 | 
	
		
			
				|  |  | -            [audioFile readIntoBuffer:buffer frameCount:frameToRead error:nil];
 | 
	
		
			
				|  |  | -        } @catch (NSException *exception) {
 | 
	
		
			
				|  |  | -            
 | 
	
		
			
				|  |  | -        } @finally {
 | 
	
		
			
				|  |  | -            
 | 
	
		
			
				|  |  | +- (void)prepareBufferFrame {
 | 
	
		
			
				|  |  | +    [self resetMixBuffer];
 | 
	
		
			
				|  |  | +    dispatch_main_async_safe(^{
 | 
	
		
			
				|  |  | +        if (self.delegate && [self.delegate respondsToSelector:@selector(enginePlayerIsReadyPlay:)]) {
 | 
	
		
			
				|  |  | +            self.isReady = YES;
 | 
	
		
			
				|  |  | +            [self.delegate enginePlayerIsReadyPlay:self];
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -    return buffer;
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -- (void)mixBuffers:(AVAudioPCMBuffer *)bgBuffer bgBufferVolume:(float)bgBufferVolume withRecordBuffer:(AVAudioPCMBuffer *)recordBuffer recordVolume:(float)recordVolume offset:(NSInteger)offsetTime startPosition:(AVAudioFrameCount)startPosition {
 | 
	
		
			
				|  |  | -    if (!bgBuffer && !recordBuffer) {
 | 
	
		
			
				|  |  | -        return;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -    NSLog(@"------- start");
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    AVAudioFrameCount minFrameCount = MIN(bgBuffer.frameLength, recordBuffer.frameLength);
 | 
	
		
			
				|  |  | -    AVAudioFrameCount offsetFrame = labs(offsetTime)/1000.0 * recordBuffer.format.sampleRate;
 | 
	
		
			
				|  |  | +// 预计加载buffer
 | 
	
		
			
				|  |  | +- (void)prepareBuffer:(AVAudioFramePosition)startPosition offset:(NSInteger)offsetTime mixStart:(AVAudioFramePosition)mixStartPosition {
 | 
	
		
			
				|  |  |      
 | 
	
		
			
				|  |  | -    float *bgLeftChannel = bgBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | -    float *bgRightChannel = bgBuffer.floatChannelData[1];
 | 
	
		
			
				|  |  | -    // 录音文件未单声道
 | 
	
		
			
				|  |  | -    float *recordLeftChannel = recordBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    float *mixLeftChannel = self.mixBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | -    float *mixRightChannel = self.mixBuffer.floatChannelData[1];
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    for (int frame = 0; frame < minFrameCount; frame++) {
 | 
	
		
			
				|  |  | -        if (self.stopMix) {
 | 
	
		
			
				|  |  | -            NSLog(@"------- stop mix");
 | 
	
		
			
				|  |  | -            dispatch_semaphore_signal(self.mixChangeSemaphore); // 释放信号量
 | 
	
		
			
				|  |  | +    dispatch_async(self.sourceQueue, ^{
 | 
	
		
			
				|  |  | +        if (!self.bgAudioFile || !self.audioFile) {
 | 
	
		
			
				|  |  |              return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        int bgFrame = frame+startPosition;
 | 
	
		
			
				|  |  | -        float leftChannel = (bgFrame < bgBuffer.frameLength) ? bgLeftChannel[bgFrame] : 0;
 | 
	
		
			
				|  |  | -        float rightChannel = (bgFrame < bgBuffer.frameLength) ? bgRightChannel[bgFrame] : 0;
 | 
	
		
			
				|  |  | -        
 | 
	
		
			
				|  |  | -        int recordFrame = (offsetTime < 0) ? (bgFrame - offsetFrame) : (bgFrame + offsetFrame);
 | 
	
		
			
				|  |  | -        
 | 
	
		
			
				|  |  | -        float recordData = (recordFrame >= 0 && recordFrame < recordBuffer.frameLength) ? recordLeftChannel[recordFrame] : 0;
 | 
	
		
			
				|  |  | -        
 | 
	
		
			
				|  |  | -        
 | 
	
		
			
				|  |  | -        float mixLeftData = [self mixChannelData:leftChannel bgVolume:bgBufferVolume recordData:recordData recordVolume:recordVolume];
 | 
	
		
			
				|  |  | -        float mixRightData = [self mixChannelData:rightChannel bgVolume:bgBufferVolume recordData:recordData recordVolume:recordVolume];
 | 
	
		
			
				|  |  | +        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);
 | 
	
		
			
				|  |  |          
 | 
	
		
			
				|  |  | -        mixLeftChannel[frame] = MAX(-1.0, MIN(1.0, mixLeftData));
 | 
	
		
			
				|  |  | -        mixRightChannel[frame] = MAX(-1.0, MIN(1.0, mixRightData));
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -    NSLog(@"---------finish");
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -- (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);
 | 
	
		
			
				|  |  | -    self.bgVolume = bgVolume;
 | 
	
		
			
				|  |  | -    self.recordVolume = recordVolume;
 | 
	
		
			
				|  |  | -    if (self.bgBuffer && self.recordBuffer) {
 | 
	
		
			
				|  |  | -        self.stopChangeVolume = YES;
 | 
	
		
			
				|  |  | -        // 停止上一次修改音量
 | 
	
		
			
				|  |  | -        dispatch_async(self.sourceQueue, ^{
 | 
	
		
			
				|  |  | -            // 等待上一次的操作完成
 | 
	
		
			
				|  |  | -            dispatch_semaphore_wait(self.volumeChangeSemaphore, DISPATCH_TIME_FOREVER);
 | 
	
		
			
				|  |  | -            self.stopChangeVolume = NO;
 | 
	
		
			
				|  |  | -            // 开始新的音量修改操作
 | 
	
		
			
				|  |  | -            AVAudioFramePosition startFrame = self.currentFrame;
 | 
	
		
			
				|  |  | -            NSLog(@"----- current frame -----%lld", startFrame);
 | 
	
		
			
				|  |  | -            [self modifyMixBuffer:self.bgBuffer bgBufferVolume:bgVolume withRecordBuffer:self.recordBuffer recordVolume:recordVolume offset:self.offsetTime startPosition:startFrame tagIndex:0];
 | 
	
		
			
				|  |  | -            // 释放信号量,标记音量修改操作完成
 | 
	
		
			
				|  |  | -            dispatch_semaphore_signal(self.volumeChangeSemaphore);
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -- (void)modifyMixBuffer:(AVAudioPCMBuffer *)bgBuffer bgBufferVolume:(float)bgBufferVolume withRecordBuffer:(AVAudioPCMBuffer *)recordBuffer recordVolume:(float)recordVolume offset:(NSInteger)offsetTime startPosition:(AVAudioFramePosition)startFrame tagIndex:(NSInteger)tagIndex {
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    AVAudioFrameCount minFrameCount = MIN(bgBuffer.frameLength, recordBuffer.frameLength);
 | 
	
		
			
				|  |  | -    AVAudioFrameCount offsetFrame = labs(offsetTime)/1000.0 * recordBuffer.format.sampleRate;
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    float *bgLeftChannel = bgBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | -    float *bgRightChannel = bgBuffer.floatChannelData[1];
 | 
	
		
			
				|  |  | -    // 录音文件未单声道
 | 
	
		
			
				|  |  | -    float *recordLeftChannel = recordBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    float *mixLeftChannel = self.mixBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | -    float *mixRightChannel = self.mixBuffer.floatChannelData[1];
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    // 先处理后续播放的buffer
 | 
	
		
			
				|  |  | -    NSLog(@"------- volume change start");
 | 
	
		
			
				|  |  | -    for (int frame = (int)startFrame; frame < minFrameCount; frame++) {
 | 
	
		
			
				|  |  | -        if (self.stopChangeVolume) {
 | 
	
		
			
				|  |  | -            NSLog(@"------- stop volume change");
 | 
	
		
			
				|  |  | -            dispatch_semaphore_signal(self.volumeChangeSemaphore); // 释放信号量
 | 
	
		
			
				|  |  | +        self.bgAudioFile.framePosition = startPosition;
 | 
	
		
			
				|  |  | +        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) {
 | 
	
		
			
				|  |  |              return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +        AVAudioPCMBuffer *recordBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:self.audioFile.processingFormat frameCapacity:frameToRead];
 | 
	
		
			
				|  |  | +        recordBuffer.frameLength = frameToRead;
 | 
	
		
			
				|  |  |          
 | 
	
		
			
				|  |  | -        float leftChannel = bgLeftChannel[frame];
 | 
	
		
			
				|  |  | -        float rightChannel = bgRightChannel[frame];
 | 
	
		
			
				|  |  | +        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;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        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];
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          
 | 
	
		
			
				|  |  | -        int recordFrame = (offsetTime < 0) ? (frame - offsetFrame) : (frame + offsetFrame);
 | 
	
		
			
				|  |  | -        float recordData = (recordFrame >= 0 && recordFrame < recordBuffer.frameLength) ? recordLeftChannel[recordFrame] : 0;
 | 
	
		
			
				|  |  | +        float *bgLeftChannel = bgBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | +        float *bgRightChannel = bgBuffer.floatChannelData[1];
 | 
	
		
			
				|  |  | +        if (bgBuffer.format.channelCount == 1) {
 | 
	
		
			
				|  |  | +            bgRightChannel = bgBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        // 录音文件未单声道
 | 
	
		
			
				|  |  | +        float *recordLeftChannel = recordBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  |          
 | 
	
		
			
				|  |  | -        float mixLeftData = [self mixChannelData:leftChannel bgVolume:bgBufferVolume recordData:recordData recordVolume:recordVolume];
 | 
	
		
			
				|  |  | -        float mixRightData = [self mixChannelData:rightChannel bgVolume:bgBufferVolume recordData:recordData recordVolume:recordVolume];
 | 
	
		
			
				|  |  | +        float *mixLeftChannel = self.mixBuffer.floatChannelData[0];
 | 
	
		
			
				|  |  | +        float *mixRightChannel = self.mixBuffer.floatChannelData[1];
 | 
	
		
			
				|  |  |          
 | 
	
		
			
				|  |  | -        mixLeftChannel[frame-self.startPosition] = MAX(-1.0, MIN(1.0, mixLeftData));
 | 
	
		
			
				|  |  | -        mixRightChannel[frame-self.startPosition] = MAX(-1.0, MIN(1.0, mixRightData));
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -    NSLog(@"------- volume change end");
 | 
	
		
			
				|  |  | +        for (int frame = 0; frame < frameToRead; frame++) {
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            AVAudioFramePosition mixIndex = frame + startPosition - mixStartPosition;
 | 
	
		
			
				|  |  | +            float leftChannel = (frame < bgBuffer.frameLength) ? bgLeftChannel[frame] : 0;
 | 
	
		
			
				|  |  | +            float rightChannel = (frame < bgBuffer.frameLength) ? bgRightChannel[frame] : 0;
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            float recordData = (frame < recordBuffer.frameLength) ? recordLeftChannel[frame] : 0;
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            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];
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 防止数组越界
 | 
	
		
			
				|  |  | +            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);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  - (void)scheduleBufferFromPosition:(AVAudioFramePosition)startPosition {
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    self.stopMix = YES;
 | 
	
		
			
				|  |  | +    [self resetMixBuffer];
 | 
	
		
			
				|  |  |      self.startPosition = startPosition;
 | 
	
		
			
				|  |  | -    dispatch_async(self.sourceQueue, ^{
 | 
	
		
			
				|  |  | -        // 等待上一次的操作完成
 | 
	
		
			
				|  |  | -        dispatch_semaphore_wait(self.mixChangeSemaphore, DISPATCH_TIME_FOREVER);
 | 
	
		
			
				|  |  | -        self.stopMix = NO;
 | 
	
		
			
				|  |  | +    [self prepareBuffer:startPosition offset:self.offsetTime mixStart:startPosition];
 | 
	
		
			
				|  |  | +    // 加载缓冲区
 | 
	
		
			
				|  |  | +    [self.nodePlayer scheduleBuffer:self.mixBuffer atTime:nil options:AVAudioPlayerNodeBufferInterruptsAtLoop completionHandler:^{
 | 
	
		
			
				|  |  |          
 | 
	
		
			
				|  |  | -        [self mixBuffers:self.bgBuffer bgBufferVolume:self.bgVolume withRecordBuffer:self.recordBuffer recordVolume:self.recordVolume offset:self.offsetTime startPosition:(AVAudioFrameCount)startPosition];
 | 
	
		
			
				|  |  | -        // 释放信号量,标记修改操作完成
 | 
	
		
			
				|  |  | -        dispatch_semaphore_signal(self.mixChangeSemaphore);
 | 
	
		
			
				|  |  | -        // 加载缓冲区
 | 
	
		
			
				|  |  | -        [self.nodePlayer scheduleBuffer:self.mixBuffer atTime:nil options:AVAudioPlayerNodeBufferInterruptsAtLoop completionHandler:^{
 | 
	
		
			
				|  |  | -            
 | 
	
		
			
				|  |  | -        }];
 | 
	
		
			
				|  |  | -    });
 | 
	
		
			
				|  |  | +    }];
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +- (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);
 | 
	
		
			
				|  |  | +    self.bgVolume = bgVolume;
 | 
	
		
			
				|  |  | +    self.recordVolume = recordVolume;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  // 打断处理
 | 
	
		
			
				|  |  |  - (void)handleInterruption:(NSNotification *)notification {
 | 
	
		
			
				|  |  |      NSDictionary *info = notification.userInfo;
 | 
	
	
		
			
				|  | @@ -386,6 +387,7 @@
 | 
	
		
			
				|  |  |          [self.delegate enginePlayerDidError:self error:error];
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  #pragma mark ------ play action
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  - (void)changeRecordDelay:(NSInteger)delayMs {
 | 
	
	
		
			
				|  | @@ -406,8 +408,7 @@
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  - (void)stopPlay {
 | 
	
		
			
				|  |  | -    self.stopMix = YES;
 | 
	
		
			
				|  |  | -    self.stopChangeVolume = YES;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      if (self.nodePlayer.isPlaying) {
 | 
	
		
			
				|  |  |          [self.nodePlayer stop];
 | 
	
		
			
				|  |  |      }
 | 
	
	
		
			
				|  | @@ -437,9 +438,6 @@
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      
 | 
	
		
			
				|  |  | -    // 停止修改音量循环
 | 
	
		
			
				|  |  | -    self.stopChangeVolume = YES;
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  |      AVAudioFramePosition startFrame = startTime / 1000.0 * self.audioFormat.sampleRate;
 | 
	
		
			
				|  |  |      // 跳转进度
 | 
	
		
			
				|  |  |      self.currentFrame = startFrame;
 | 
	
	
		
			
				|  | @@ -459,15 +457,17 @@
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  - (void)freePlayer {
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    if (self.nodePlayer.isPlaying) {
 | 
	
		
			
				|  |  | -        [self stopPlay];
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | +    [self stopPlay];
 | 
	
		
			
				|  |  |      [self.audioEngine stop];
 | 
	
		
			
				|  |  | +    // 停止并清理定时器
 | 
	
		
			
				|  |  | +    if (_timer) {
 | 
	
		
			
				|  |  | +        [_timer invalidate];
 | 
	
		
			
				|  |  | +        _timer = nil;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  - (void)startTimer {
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | +    self.timeCount = 0;
 | 
	
		
			
				|  |  |      [self.timer setFireDate:[NSDate distantPast]];
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -488,7 +488,7 @@
 | 
	
		
			
				|  |  |      
 | 
	
		
			
				|  |  |      if (!_timer) {
 | 
	
		
			
				|  |  |          __weak typeof(self)weakSelf = self;
 | 
	
		
			
				|  |  | -        _timer = [NSTimer scheduledTimerWithTimeInterval:0.1 repeats:YES block:^(NSTimer * _Nonnull timer) {
 | 
	
		
			
				|  |  | +        _timer = [NSTimer scheduledTimerWithTimeInterval:0.01 repeats:YES block:^(NSTimer * _Nonnull timer) {
 | 
	
		
			
				|  |  |              [weakSelf timeFunction];
 | 
	
		
			
				|  |  |          }];
 | 
	
		
			
				|  |  |          [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
 | 
	
	
		
			
				|  | @@ -503,12 +503,18 @@
 | 
	
		
			
				|  |  |      float progress = currentTime/self.totalDuration;
 | 
	
		
			
				|  |  |      NSDate *date = [NSDate date];
 | 
	
		
			
				|  |  |      NSTimeInterval inteveral = [date timeIntervalSince1970];
 | 
	
		
			
				|  |  | -    if (currentTime > self.totalDuration) {
 | 
	
		
			
				|  |  | +    if (currentTime >= self.totalDuration) {
 | 
	
		
			
				|  |  |          if (self.delegate && [self.delegate respondsToSelector:@selector(enginePlayFinished:)]) {
 | 
	
		
			
				|  |  |              [self.delegate enginePlayFinished:self];
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      else {
 | 
	
		
			
				|  |  | +        // 定时器10ms出触发一次 buffer每100ms执行一次
 | 
	
		
			
				|  |  | +        if (self.timeCount % 10 == 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];
 | 
	
		
			
				|  |  |          }
 | 
	
	
		
			
				|  | @@ -516,6 +522,11 @@
 | 
	
		
			
				|  |  |      
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +- (void)scheduleMixBuffer {
 | 
	
		
			
				|  |  | +    if (self.nodePlayer.isPlaying) {
 | 
	
		
			
				|  |  | +        [self prepareBuffer:self.currentFrame offset:self.offsetTime mixStart:self.startPosition];
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  - (NSTimeInterval)getCurrentPlayTime {
 | 
	
		
			
				|  |  |      AVAudioTime *nodeTime = [self.nodePlayer lastRenderTime];
 | 
	
	
		
			
				|  | @@ -557,4 +568,10 @@
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      return NO;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +- (void)dealloc {
 | 
	
		
			
				|  |  | +    NSLog(@"---- KSMergeEnginePlayer dealloc");
 | 
	
		
			
				|  |  | +    [[NSNotificationCenter defaultCenter] removeObserver:self];
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  @end
 |