|
@@ -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 {
|