MAC开发IOS版本基本功能完成
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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); // 比如4096(2048帧×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); // 比如4096(2048帧×2字节)
|
||||
// console.log("声道数:", audioBuffer.mNumberChannels); // 1
|
||||
// console.log("二进制数据指针:", audioBuffer.mData); // 指向16位PCM数据的内存地址
|
||||
Reference in New Issue
Block a user