From e8856f4238d9daa8755dc42c9a46c94db999c540 Mon Sep 17 00:00:00 2001 From: JayJiaJun Date: Sat, 22 Feb 2025 21:04:37 +0800 Subject: [PATCH] 2.22 --- src/assets/js/GamepadController.js | 11 +- src/assets/js/audio-transmitter.js | 163 ++++++++++++++++++++++++++++ src/views/CarControl.vue | 164 ++++++++++++++++++++--------- 3 files changed, 286 insertions(+), 52 deletions(-) create mode 100644 src/assets/js/audio-transmitter.js diff --git a/src/assets/js/GamepadController.js b/src/assets/js/GamepadController.js index 4e0a349..70cf0c0 100644 --- a/src/assets/js/GamepadController.js +++ b/src/assets/js/GamepadController.js @@ -22,6 +22,7 @@ class GamepadController { this.directionAxis9 = ""; this.angle = 0; this.rightJoystickPressed = false; + this.speed = 0; // 添加速度属性 // 初始化按钮状态 this.buttons = this.options.buttonsConfig.map(button => ({ ...button, @@ -81,13 +82,19 @@ class GamepadController { // 检查是否在死区 if (Math.abs(axis0) < this.options.deadZone && Math.abs(axis1) < this.options.deadZone) { this.directionAxis0_1 = "未定义"; - this.angle = 0; // 在死区时重置角度为0 + this.angle = 0; + this.speed = 0; // 在死区时速度为0 return; } + // 计算速度(到原点的距离) + // 使用勾股定理计算距离,并将结果限制在0-100之间 + const distance = Math.sqrt(axis0 * axis0 + axis1 * axis1); + this.speed = Math.round(Math.min(distance * 100, 100)); + // 计算方向角度(0-360度) let angle = Math.atan2(axis1, axis0) * (180 / Math.PI); - angle = (angle + 360) % 360; // 确保角度在 0-360 范围内 + angle = (angle + 360) % 360; angle = Math.round(angle); this.angle = angle; diff --git a/src/assets/js/audio-transmitter.js b/src/assets/js/audio-transmitter.js new file mode 100644 index 0000000..c4939f6 --- /dev/null +++ b/src/assets/js/audio-transmitter.js @@ -0,0 +1,163 @@ +// audio-transmitter.js + +export default { + data() { + return { + ws: null, // WebSocket 连接 + mediaRecorder: null, // MediaRecorder 实例 + audioChunks: [], // 音频数据 + status: '点击按钮开始传输音频', + isRecording: false, // 用于判断是否在录音 + audioConfig: { + sampleRate: 44100, // 固定为 44100Hz + bitsPerSample: 16, // 固定为 16 位深度 + channels: 2 // 固定为双声道 + }, + stream: null, + audioContext: null, + worklet: null, + lastSendTime: 0, + sendInterval: 200, // 数据传输间隔(毫秒) + audioWs: null, // 音频 WebSocket 连接 + }; + }, + mounted() { + // 初始化 WebSocket 连接 + this.ws = new WebSocket('ws://192.168.4.103/ws'); // 替换为 ESP32 IP 地址 + + this.ws.onopen = () => { + console.log('WebSocket 连接已打开'); + }; + this.ws.onmessage = (event) => { + console.log(' 接收到来自 ESP32 的消息:', event.data); + }; + this.ws.onclose = () => { + console.log('WebSocket 连接已关闭'); + }; + }, + methods: { + toggleRecording() { + if (this.isRecording) { + this.stopRecording(); + } else { + this.startRecording(); + } + }, + + async startRecording() { + console.log('音频传输器: 开始录音'); + this.status = '开始录音并传输音频...'; + this.isRecording = true; + + const constraints = { + audio: { + sampleRate: this.audioConfig.sampleRate, + channelCount: this.audioConfig.channels, + sampleSize: this.audioConfig.bitsPerSample + } + }; + + console.log('使用的音频配置:', constraints); + + try { + // 初始化音频 WebSocket 连接 + this.audioWs = new WebSocket('ws://192.168.1.60:81'); + + // 等待 WebSocket 连接成功 + await new Promise((resolve, reject) => { + this.audioWs.onopen = () => { + console.log('音频 WebSocket 已连接'); + resolve(); + }; + this.audioWs.onerror = (error) => { + console.error('音频 WebSocket 错误:', error); + reject(error); + }; + }); + + // WebSocket 连接成功后,开始获取麦克风权限 + const stream = await navigator.mediaDevices.getUserMedia(constraints); + console.log('成功获取麦克风权限'); + + // 创建 AudioContext + const audioContext = new AudioContext({ + sampleRate: this.audioConfig.sampleRate + }); + + // 加载 AudioWorklet + await audioContext.audioWorklet.addModule('audio-processor.js'); + + const source = audioContext.createMediaStreamSource(stream); + const worklet = new AudioWorkletNode(audioContext, 'audio-processor'); + + // 监听来自 worklet 的消息 + worklet.port.onmessage = (event) => { + if (this.audioWs && this.audioWs.readyState === WebSocket.OPEN) { + const currentTime = Date.now(); + if (!this.lastSendTime || currentTime - this.lastSendTime > this.sendInterval) { + const pcmData = new Int16Array(event.data); + console.log('发送的音频数据:', Array.from(pcmData.slice(0, 20))); + this.audioWs.send(event.data); + this.lastSendTime = currentTime; + } + } + }; + + source.connect(worklet); + worklet.connect(audioContext.destination); + + // 保存引用以便后续停止 + this.stream = stream; + this.audioContext = audioContext; + this.worklet = worklet; + } catch (error) { + console.error('音频初始化失败:', error); + // 清理音频相关资源 + if (this.stream) { + this.stream.getTracks().forEach(track => track.stop()); + } + if (this.worklet) { + this.worklet.disconnect(); + } + if (this.audioContext) { + this.audioContext.close(); + } + if (this.audioWs) { + this.audioWs.close(); + this.audioWs = null; + } + this.isRecording = false; + + // 不抛出错误,让程序继续执行 + return; + } + }, + + stopRecording() { + console.log('音频传输器: 停止录音'); + this.status = '停止录音'; + this.isRecording = false; + + // 关闭音频资源 + if (this.stream) { + console.log('关闭音频流'); + this.stream.getTracks().forEach(track => track.stop()); + } + if (this.worklet) { + console.log('断开 AudioWorklet'); + this.worklet.disconnect(); + } + if (this.audioContext) { + console.log('关闭音频上下文'); + this.audioContext.close(); + } + + // 关闭音频 WebSocket 连接 + if (this.audioWs) { + console.log('关闭音频 WebSocket 连接'); + this.audioWs.close(); + this.audioWs = null; + } + } + } + }; \ No newline at end of file diff --git a/src/views/CarControl.vue b/src/views/CarControl.vue index e00f5b9..ba7afa1 100644 --- a/src/views/CarControl.vue +++ b/src/views/CarControl.vue @@ -34,7 +34,7 @@
避障 - +
@@ -80,6 +80,7 @@