diff --git a/src/uni_modules/ty-recording/utssdk/app-ios/index.uts b/src/uni_modules/ty-recording/utssdk/app-ios/index.uts index b0358f33..118cff78 100644 --- a/src/uni_modules/ty-recording/utssdk/app-ios/index.uts +++ b/src/uni_modules/ty-recording/utssdk/app-ios/index.uts @@ -3,10 +3,24 @@ import { StartAudioRecordFailImpl, AudioErrorCode } from '../unierror.uts'; import { UTSiOS } from "DCloudUTSFoundation" +import { AVAudioEngine, AVAudioSession, AVAudioFormat, AVAudioInputNode, AVAudioPCMBuffer } from "AVFoundation"; +import { NSData, NSMutableData } from "Foundation"; -// 线程安全的录制状态标识 +// @argumentLabel("forBus") +/** + * 音频采集状态(全局变量,保证生命周期) + */ let isRecording = false; - +/** + * 音频引擎实例(全局变量,避免异步回调中被释放) + */ +let audioEngine: AVAudioEngine | null = null; +/** + * 音频输入节点(全局变量,避免被释放) + */ +let inputNode: AVAudioInputNode | null = null; +// 全局复用缓冲区(避免每次创建新数组) +let reuseInt32Array: Int32Array | null = new Int32Array(4800); /** * 启动音频采集(iOS端核心实现) @@ -16,19 +30,83 @@ let isRecording = false; export function startAudioRecord(options: StartAudioRecordOptions): void { // 避免重复启动 console.log('避免重复启动', isRecording); - if (isRecording) return; + // if (isRecording) return; try { const audioSession = AVAudioSession.sharedInstance(); - const authStatus = audioSession.recordPermission(); - console.log('asaadda', authStatus); + UTSiOS.try(audioSession.setCategory( + AVAudioSession.Category.playAndRecord, + mode=AVAudioSession.Mode.spokenAudio, + options= AVAudioSession.CategoryOptions.duckOthers // 可选:其他音频静音 + )); + // UTSiOS.try(audioSession.setCategory(AVAudioSession.Category.playAndRecord)); + // UTSiOS.try(audioSession.setMode(AVAudioSession.Mode.spokenAudio)); + UTSiOS.try(audioSession.setActive(true)); + audioSession.requestRecordPermission((granted: boolean) => { + // 闭包内处理权限结果(异步执行) + if (granted) { + // 权限通过:继续初始化采集逻辑 + try { + // 创建音频引擎(移到闭包内,保证权限通过后才执行) + audioEngine = AVAudioEngine.init(); + if (audioEngine === null) { // 解包:先判断是否为nil + throw new Error("音频输入节点为空,无法获取音频格式"); + } + inputNode = audioEngine!.inputNode; + if (inputNode === null) { // 解包:先判断是否为nil + throw new Error("音频输入节点为空,无法获取音频格式"); + } + const unwrappedInputNode = inputNode!; + const inputFormat = unwrappedInputNode.inputFormat(forBus= 0); + unwrappedInputNode.installTap( + onBus= 0, // 总线编号,通常传 0 + bufferSize= 1024, // 缓冲区大小,常用 1024/2048 + format= inputFormat, // 音频格式 + block=(buffer: AVAudioPCMBuffer, time: any) => { + const floatData = buffer.floatChannelData; + const channelData = floatData![0]; + const frameLength = buffer.frameLength as number; + const bytesPerSample = 2; // 16位整型=2字节/样本 + const totalBytes = frameLength * bytesPerSample; + + console.log(channelData); + console.log(totalBytes); + const pcmData = NSMutableData.dataWithCapacity(20); // 可变Data,支持写入 + + // const data = new Float32Array(int32Array.byteLength); + // data.set(int32Array.buffer, 1); // 内存块拷贝,替代逐字节写入 + + options.onFrame?.(pcmData); + // if (floatData !== null) { + // // 单声道取第0个通道(立体声可取[0]左声道、[1]右声道) + // const channelData = floatData[0]; + + // options.onFrame?.(channelData) + // } + } + ); + UTSiOS.try(audioEngine!.start()); + if (audioEngine!.isRunning) { + console.log("引擎确实处于运行状态"); + isRecording = true; + } else { + console.error("调用 start() 但引擎未运行"); + } + isRecording = true; + } catch (error) { + isRecording = false; + } + } else { + // 权限被拒绝:回调错误 + isRecording = false; + } + }); isRecording = true; } catch (error) { // 初始化失败回调错误 - const err = new StartAudioRecordFailImpl(AudioErrorCode.AUDIO_FORMAT_ILLEGAL); + const err = new StartAudioRecordFailImpl(9010002); options.onError?.(err); // 重置状态 - return; } } @@ -37,7 +115,34 @@ export function startAudioRecord(options: StartAudioRecordOptions): void { * 停止音频采集(配套停止逻辑,保证功能完整) * @param options 回调配置 */ -@UTSJS.keepAlive + export function stopAudioRecord(): void { + if (audioEngine === null) { // 解包:先判断是否为nil + isRecording = false; + return; // 直接返回,不执行后续逻辑 + } + const _audioEngine = audioEngine! + if (_audioEngine.isRunning) { + _audioEngine.stop(); + } + if (inputNode === null) { // 解包:先判断是否为nil + isRecording = false; + return; // 直接返回,不执行后续逻辑 + } + const _inputNode = inputNode! + _inputNode.removeTap(onBus= 0); // 移除音频回调 + + // 停用音频会话 + const audioSession = AVAudioSession.sharedInstance(); + + try { + UTSiOS.try(audioSession.setActive(false)); + }catch (e) { + console.log(e) + } + inputNode = null; + audioEngine = null; // 释放全局引擎 + isRecording = false; + } \ No newline at end of file