MAC开发IOS版本基本功能完成

This commit is contained in:
2026-03-06 14:40:58 +08:00
parent 75b91f8fb3
commit c877b388bd
4 changed files with 104 additions and 63 deletions
+3 -2
View File
@@ -1,6 +1,6 @@
import { ErrorCode, ShowErrorTipOptions } from './touch-btn.types';
import { get_base_url } from '@/api/request.js';
import { getToken, getPhoneEnvBool } from '@/common/common.js';
import { getPhoneEnvBool } from '@/common/common.js';
const IsAndEnv = getPhoneEnvBool('AND')
// const url = '/trapractice/voiceCallSocket/voiceCall'
@@ -8,7 +8,7 @@ const IsAndEnv = getPhoneEnvBool('AND')
// const url = '/traapp/asrAiModelSocket/open';
const url = '/traapp/asrAiModelSocket/open';
const _baseUrl = get_base_url(url).split('http').join('ws');
export const wsUrl = _baseUrl + url + '?summary=' + getToken();
export const wsUrl = _baseUrl + url ;
// export const wsUrl = 'ws://10.10.10.8:8000/ws/asr';
const windowInfo = uni.getWindowInfo();
// 安全区域信息
@@ -26,6 +26,7 @@ export const horizontalSplitX = safeArea.right / 2;
* @returns {Promise<UniApp.SocketTask>} 成功时返回WS实例,失败时reject错误信息
*/
export const initWebSocket = (wsUrl : string) : Promise<UniApp.SocketTask> => {
console.log('wsUrl', wsUrl);
return new Promise((resolve, reject) => {
// 1. 创建WS连接
const ws = uni.connectSocket({
+5 -4
View File
@@ -116,7 +116,7 @@
import { wsUrl, bottomAreaTopBoundary, horizontalSplitX, initWebSocket, showErrorTip, vibrateLight } from './touch-btn';
import { ref, computed, onMounted, nextTick, watch, reactive } from 'vue';
import { onHide, onLaunch } from '@dcloudio/uni-app';
import common, { setupKeyboardHeightListener } from '@/common/common';
import common, { setupKeyboardHeightListener, getToken } from '@/common/common';
import { startAudioRecord, stopAudioRecord, preRequestRecordPermission } from '@/uni_modules/ty-recording';
import { ProtocolCodec, MessageType, ControlCommand, ControlCode, ControlBody } from '@/common/protocolCodec';
import permissionApi from '@/common/permission';
@@ -275,7 +275,7 @@
allText.value = '';
ossKey.value = '';
console.log('开启语音');
initWebSocket(wsUrl)
initWebSocket(wsUrl + '?summary=' + getToken())
.then((socketTask) => {
if (!isRecording.value) {
ws.value = null;
@@ -328,7 +328,7 @@
}
}
} catch (e) {
console.log('后端发来的信息有问题', e);
console.log('后端发来的信息有问题', e, res.data);
}
});
})
@@ -489,9 +489,10 @@
voiceTimer = setTimeout(() => {
console.log('1号定时器', voice_to_text_status.value, status.value);
voiceTimer = null;
if (workMode.value === 'voice-only') {
if (workMode.value === 'voice-only' || (workMode.value === 'both' && _isOutStatus === '01')) {
if (status.value === 'send_over') return;
status.value = 'send_over'; // 恢复成默认状态
console.log('1号定时器纠错');
emit('submitVoice', { ossKey: '', text: '', recordId: recordId.value });
}
}, 4000);
@@ -1,7 +1,5 @@
import { StartAudioRecordOptions } from '../interface.uts';
import { StartAudioRecordFailImpl, AudioErrorCode } from '../unierror.uts';
import { UTSiOS } from "DCloudUTSFoundation"
import { AVAudioEngine, AVAudioSession, AVAudioFormat, AVAudioInputNode, AVAudioPCMBuffer, AVAudioCommonFormat } from "AVFoundation";
import { NSData, NSMutableData } from "Foundation";
@@ -24,7 +22,7 @@ let reuseInt32Array: Int32Array | null = new Int32Array(4800);
// 新增:缓存录制的所有Int16 PCM数据
let recordedPCMData: UInt8[] = [];
// 3. 保存完整PCM数据到文件
const sampleRate = 16000.0; // 和采集时的目标格式一致
const targetSampleRate:Double = 16000.0; // 和采集时的目标格式一致
const channels = 1; // 实际以采集时的声道数为准(可从inputFormat获取)
// 新增:录音启动时间戳(毫秒)
@@ -105,8 +103,7 @@ export function startAudioRecord(options: StartAudioRecordOptions): void {
isRecording = false;
const err = new StartAudioRecordFailImpl(9010002); // 麦克风权限被永久拒绝,请前往设置开启
options.onError?.(err)
} else {
} else {
// 权限通过:继续初始化采集逻辑
try {
// 创建音频引擎(移到闭包内,保证权限通过后才执行)
@@ -132,67 +129,47 @@ export function startAudioRecord(options: StartAudioRecordOptions): void {
startTime: recordStartTime, // 启动时间戳
});
const targetFormat = AVAudioFormat.init(
commonFormat= AVAudioCommonFormat.pcmFormatInt16, // 16位整型(文档枚举值)
sampleRate= inputFormat.sampleRate, // 采样率(Double类型,符合文档定义)
commonFormat= AVAudioCommonFormat.pcmFormatInt16,
sampleRate = targetSampleRate, // 采样率(Double类型,符合文档定义)
channels= inputFormat.channelCount, // 声道数(AVAudioChannelCount
interleaved= false // 是否交错(Bool,单声道无影响)
);
unwrappedInputNode.installTap(
onBus= 0, // 总线编号,通常传 0
bufferSize= 2048, // 缓冲区大小,常用 1024/2048
format= targetFormat, // 音频格式
format= inputFormat, // 音频格式
block=(buffer: AVAudioPCMBuffer, time: any) => {
const frameLen = buffer.frameLength as number;
const bufferListRawPtr = buffer.audioBufferList;
const audioBufferList = bufferListRawPtr.pointee; // 拿到结构体本身
const audioBufferPtr = audioBufferList.mBuffers; // AudioBuffer指针
// // console.log(audioBufferPtr.mData);
const mData = audioBufferPtr.mData;
const inputFrameLen = frameLen as Double;
const sampleRateRatio = targetSampleRate / inputFormat.sampleRate;
const outputFrameCounta = inputFrameLen * sampleRateRatio
const outputFrameCount = AVAudioFrameCount(outputFrameCounta);
const outputBufferOpt = AVAudioPCMBuffer.init(pcmFormat= targetFormat!, frameCapacity= outputFrameCount);
outputBufferOpt!.frameLength = outputFrameCount
let inputBlock: AVAudioConverterInputBlock = (inNumPackets, outStatus):AVAudioBuffer => {
outStatus.pointee = AVAudioConverterInputStatus.haveData; // 告诉转换器有输入数据
return buffer; // 返回当前 Tap 拿到的输入缓冲区
}
let convertError: NSError | null = null;
const converter = AVAudioConverter.init(from= inputFormat, to= targetFormat!);
let convertStatus = converter!.convert(to=outputBufferOpt!,error=UTSiOS.getPointer(convertError),withInputFrom=inputBlock)
// console.log(convertStatus);
// console.log(AVAudioConverterOutputStatus.haveData);
// console.log(convertStatus == AVAudioConverterOutputStatus.haveData);
const int16Ptr = outputBufferOpt!.int16ChannelData;
const dataPtr = int16Ptr![0];
// const floatChannelData = outputBufferOpt!.floatChannelData
// const dataPtr = floatChannelData![0];
// const mDataByteSize:Int = 9600; // 你之前看到的字节数
// const uint8Ptr = mData!.bindMemory(to= UInt8.self, capacity= mDataByteSize);
// const pcmNormalArray: number[] = [];
// for (let i:Int = 0; i < mDataByteSize; i++) {
// // 直接读取指针的第i个字节,转为0-255的普通数字
// pcmNormalArray.push(uint8Ptr[i] as number);
// }
// const halfLength:Int = 4800;
// const firstHalf = pcmNormalArray.slice(0, halfLength);
// const secondHalf = pcmNormalArray.slice(halfLength, mDataByteSize);
// options.onFrame?.(firstHalf);
// options.onFrame?.(secondHalf);
// options.onFrame?.(pcmNormalArray);
// let uint8 = new Uint8Array([4, 5, 8, 12]);
// let array = Array.from(uint8);
// console.log(array);
// options.onFrame?.(array);
// const audioBuffer = audioBufferPtr.pointee; // 拿到AudioBuffer结构体
// console.log("音频数据字节数:", audioBuffer.mDataByteSize); // 比如40962048帧×2字节)
// console.log("声道数:", audioBuffer.mNumberChannels); // 1
// console.log("二进制数据指针:", audioBuffer.mData); // 指向16位PCM数据的内存地址
// const int16Ptr = buffer.int16ChannelData;
// if (int16Ptr != null && frameLen > 0) {
// const dataPtr = int16Ptr![0];
// const CHUNK_FRAME_SIZE = frameLen / 2; // 16位=2字节/帧 → 512帧/分片
// // 关键2:循环拆分 9600 字节为多个小分片
// for (let startFrame:number = 0; startFrame < frameLen; startFrame += CHUNK_FRAME_SIZE) {
// // 计算当前分片的结束帧(避免越界)
// const endFrame = Math.min(startFrame + CHUNK_FRAME_SIZE, frameLen);
// let _recordedPCMData: UInt8[] = [];
// // 只处理当前分片的帧
// for (let i: Int = startFrame as Int; i < endFrame; i++) {
// const int16Value = dataPtr[i];
// let byte1 = UInt8(int16Value & 0xFF);
// let byte2 = UInt8((int16Value >> 8) & 0xFF);
// _recordedPCMData.push(byte1);
// _recordedPCMData.push(byte2);
// }
// options.onFrame?.(_recordedPCMData);
// }
// }
let _recordedPCMData: UInt8[] = [];
for (let i: Int = 0; i < outputFrameCount; i++) {
const int16Value = dataPtr[i];
let byte1 = UInt8(int16Value & 0xFF);
let byte2 = UInt8((int16Value >> 8) & 0xFF);
_recordedPCMData.push(byte1);
_recordedPCMData.push(byte2);
}
options.onFrame?.(_recordedPCMData);
}
);
UTSiOS.try(audioEngine!.start());
@@ -309,3 +286,65 @@ function savePCMToFile(pcmData: UInt8[], sampleRate: number, channels: number):
return null;
}
}
// const int16Ptr = buffer.int16ChannelData;
// if (int16Ptr != null && frameLen > 0) {
// const dataPtr = int16Ptr![0];
// const CHUNK_FRAME_SIZE = frameLen / 2; // 16位=2字节/帧 → 512帧/分片
// // 关键2:循环拆分 9600 字节为多个小分片
// for (let startFrame:number = 0; startFrame < frameLen; startFrame += CHUNK_FRAME_SIZE) {
// // 计算当前分片的结束帧(避免越界)
// const endFrame = Math.min(startFrame + CHUNK_FRAME_SIZE, frameLen);
// let _recordedPCMData: UInt8[] = [];
// // 只处理当前分片的帧
// for (let i: Int = startFrame as Int; i < endFrame; i++) {
// const int16Value = dataPtr[i];
// let byte1 = UInt8(int16Value & 0xFF);
// let byte2 = UInt8((int16Value >> 8) & 0xFF);
// _recordedPCMData.push(byte1);
// _recordedPCMData.push(byte2);
// }
// options.onFrame?.(_recordedPCMData);
// }
// }
// const frameLen = buffer.frameLength as number;
// const bufferListRawPtr = buffer.audioBufferList;
// const audioBufferList = bufferListRawPtr.pointee; // 拿到结构体本身
// const audioBufferPtr = audioBufferList.mBuffers; // AudioBuffer指针
// // // console.log(audioBufferPtr.mData);
// const mData = audioBufferPtr.mData;
// console.log(mData);
// const mDataByteSize:Int = 9600; // 你之前看到的字节数
// const uint8Ptr = mData!.bindMemory(to= UInt8.self, capacity= mDataByteSize);
// const pcmNormalArray: number[] = [];
// for (let i:Int = 0; i < mDataByteSize; i++) {
// // 直接读取指针的第i个字节,转为0-255的普通数字
// pcmNormalArray.push(uint8Ptr[i] as number);
// }
// const halfLength:Int = 4800;
// const firstHalf = pcmNormalArray.slice(0, halfLength);
// const secondHalf = pcmNormalArray.slice(halfLength, mDataByteSize);
// options.onFrame?.(firstHalf);
// options.onFrame?.(secondHalf);
// options.onFrame?.(pcmNormalArray);
// let uint8 = new Uint8Array([4, 5, 8, 12]);
// let array = Array.from(uint8);
// console.log(array);
// options.onFrame?.(array);
// const audioBuffer = audioBufferPtr.pointee; // 拿到AudioBuffer结构体
// console.log("音频数据字节数:", audioBuffer.mDataByteSize); // 比如40962048帧×2字节)
// console.log("声道数:", audioBuffer.mNumberChannels); // 1
// console.log("二进制数据指针:", audioBuffer.mData); // 指向16位PCM数据的内存地址