使用opt_param 提示来预览修改优化器相关参数的优化效果

本文介绍了在优化数据库性能时,如何利用opt_param提示来预览和测试(optimizer_mode, optimizer_index_caching, optimizer_index_cost_adj)等优化器参数的修改效果。通过alter session或alter system设置参数值,结合opt_param,可以在不影响实际系统情况下评估参数变更的潜在影响,从而实现更精确的调优策略。" 125650516,9070820,SPDK深入探索:使用方法与基础机制解析,"['SPDK', '存储', '用户态', 'DPDK', '编程', '框架']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有时在修改优化器参数来进行调优时,可以采用如下方法进行查看参数修改带来的效果

1、与优化器相关的参数有:optimizer_mode, optimizer_index_caching, optimizer_index_cost_adj

   optimizer_mode:

Values:

package com.example.demoapplication; import android.Manifest; // 录音权限相关 import android.content.pm.PackageManager; // 权限检查相关 import android.media.AudioFormat; // 音频格式定义 import android.media.AudioRecord; // 音频录制功能 import android.media.MediaRecorder; // 媒体录制配置 import android.os.Bundle; // Activity生命周期数据 import android.os.Handler; // 线程通信机制 import android.os.Looper; // 主线程消息循环 import android.os.Message; // 消息传递对象 import android.widget.Button; // UI按钮控件 import android.widget.Toast; // 短时提示信息 import androidx.annotation.NonNull; // 非空注解 import androidx.appcompat.app.AppCompatActivity; // 兼容Activity基类 import androidx.core.app.ActivityCompat; // 动态权限请求 import androidx.core.content.ContextCompat; // 权限状态查询 import java.io.BufferedReader; // 文本流读取 import java.io.BufferedWriter; // 文本流写入 import java.io.IOException; // IO异常处理 import java.io.InputStreamReader; // 字节转字符流 import java.io.OutputStreamWriter; // 字节转字符流 import java.net.ServerSocket; // 服务端监听套接字 import java.net.Socket; // 客户端连接套接字 import java.util.concurrent.ExecutorService; // 线程池管理 import java.util.concurrent.Executors; // 线程池工厂 import java.util.concurrent.ScheduledExecutorService; // 定时任务调度 import java.util.concurrent.TimeUnit; // 时间单位定义 import android.util.Base64; // Base64编码 import org.json.JSONException; import org.json.JSONObject; // JSON对象 public class MainActivity extends AppCompatActivity { private Button startRecordButton; // 开始录音按钮 private Button stopRecordButton; // 停止录音按钮 private Button uploadButton; // 上传文件按钮 private AudioRecord audioRecord; // 音频录制对象 private static final int SAMPLE_RATE = 44100; // 采样率:44.1kHz private static final int BUFFER_SIZE; // 缓冲区大小 static { // 使用更安全的缓冲区大小计算方式 int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); // 确保缓冲区大小是帧大小的整数倍(2通道*2字节) BUFFER_SIZE = ((minBufferSize / (2 * 2)) + 1) * (2 * 2); } private ScheduledExecutorService scheduler; // 录音定时器 private boolean isRecording = false; // 录音状态标志 private static final int PERMISSION_REQUEST_CODE = 1; // 权限请求码 private final ExecutorService executorService = Executors.newCachedThreadPool(); // 通用线程池 private ServerSocket serverSocket; // TCP服务端Socket private volatile boolean isServerRunning = true; // 服务运行状态 private Socket clientSocket; // 当前客户端连接 // 主线程消息处理器 private Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(@NonNull Message msg) { switch (msg.what) { case 0x11: // 客户端连接成功 Toast.makeText(MainActivity.this, "客户端已连接", Toast.LENGTH_SHORT).show(); break; case 0x12: // 数据接收完成 Toast.makeText(MainActivity.this, "开始录音", Toast.LENGTH_SHORT).show(); break; case 0x13: // 上传错误 Toast.makeText(MainActivity.this, "录音数据已发送", Toast.LENGTH_SHORT).show(); break; case 0x14: // 自定义消息类型,用于停止录音提示 Toast.makeText(MainActivity.this, "停止录音", Toast.LENGTH_SHORT).show(); break; case 0x15: // 接收到控制指令 Toast.makeText(MainActivity.this, "收到控制指令:" + msg.obj.toString(), Toast.LENGTH_SHORT).show(); break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); // 初始化视图组件 checkPermissions(); // 检查权限状态 setupClickListeners(); // 设置点击事件监听 startServer(30000); // 启动TCP服务器,监听30000端口 } /** * 视图初始化方法 * 绑定布局中的UI组件并设置初始状态 */ private void initViews() { startRecordButton = findViewById(R.id.startRecordButton); stopRecordButton = findViewById(R.id.stopRecordButton); uploadButton = findViewById(R.id.uploadButton); stopRecordButton.setEnabled(false); // 初始禁用停止按钮 uploadButton.setEnabled(false); // 初始禁用上传按钮 } /** * 权限检查方法 * 如果未授予录音权限则发起请求 */ private void checkPermissions() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, PERMISSION_REQUEST_CODE); } } /** * 按钮点击事件绑定 * 设置各按钮的响应逻辑 */ private void setupClickListeners() { startRecordButton.setOnClickListener(v -> startRecording()); // 开始录音 stopRecordButton.setOnClickListener(v -> stopRecording()); // 停止录音 uploadButton.setOnClickListener(v -> uploadRecording()); // 上传录音 } /** * 开始录音方法 * 初始化AudioRecord并启动录制 */ private void startRecording() { // 添加状态检查和异常处理 if (isRecording || audioRecord != null) { return; // 防止重复启动 } try { audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, BUFFER_SIZE); if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { throw new IllegalStateException("AudioRecord初始化失败"); } audioRecord.startRecording(); isRecording = true; startRecordButton.setEnabled(false); stopRecordButton.setEnabled(true); uploadButton.setEnabled(false); // 创建定时任务发送音频数据 scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(this::uploadAudioData, 0, 160, TimeUnit.MILLISECONDS); handler.sendEmptyMessage(0x12); // 发送开始录音的消息 // 添加发送 { "type": "startRecorder", "data": null } 的逻辑 if (clientSocket != null && !clientSocket.isClosed()) { try { JSONObject startPacket = new JSONObject(); startPacket.put("type", "startRecorder"); startPacket.put("data", JSONObject.NULL); // 设置 data 为 null BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8")); writer.write(startPacket.toString()); writer.write("\n\n"); // 双换行作为结束标识 writer.flush(); } catch (IOException | JSONException e) { e.printStackTrace(); Message msg = handler.obtainMessage(0x13, e.getMessage()); handler.sendMessage(msg); } } else { Toast.makeText(this, "客户端未连接", Toast.LENGTH_SHORT).show(); } } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, "录音启动失败: " + e.getMessage(), Toast.LENGTH_LONG).show(); releaseAudioResources(); } } /** * 停止录音方法 * 释放录音资源并清理状态 */ private void stopRecording() { isRecording = false; releaseAudioResources(); stopRecordButton.setEnabled(false); uploadButton.setEnabled(true); handler.sendEmptyMessage(0x14); // 发送停止录音的消息 // 添加发送 { "type": "stopRecor", "data": null } 的逻辑 if (clientSocket != null && !clientSocket.isClosed()) { try { JSONObject stopPacket = new JSONObject(); stopPacket.put("type", "stopRecor"); stopPacket.put("data", JSONObject.NULL); // 设置 data 为 null BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8")); writer.write(stopPacket.toString()); writer.write("\n\n"); // 双换行作为结束标识 writer.flush(); } catch (IOException | JSONException e) { e.printStackTrace(); Message msg = handler.obtainMessage(0x13, e.getMessage()); handler.sendMessage(msg); } } } /** * 释放音频资源 */ private void releaseAudioResources() { if (audioRecord != null) { try { audioRecord.stop(); } catch (IllegalStateException e) { // 忽略可能的非法状态异常 } try { audioRecord.release(); } finally { audioRecord = null; } } if (scheduler != null) { try { scheduler.shutdownNow(); } finally { scheduler = null; } } } /** * 实时上传音频数据 * 将当前缓冲区数据通过Socket发送给客户端,并添加Base64编码的JSON格式数据 */ private void uploadAudioData() { if (!isRecording || clientSocket == null || clientSocket.isClosed()) return; byte[] buffer = new byte[BUFFER_SIZE]; try { int bytesRead = audioRecord.read(buffer, 0, BUFFER_SIZE); if (bytesRead > 0) { // 使用固定大小缓冲区确保完整性 byte[] validData = new byte[bytesRead]; System.arraycopy(buffer, 0, validData, 0, bytesRead); // 将音频数据转换为Base64编码字符串 String base64Data = Base64.encodeToString(validData, Base64.DEFAULT); // 构建JSON格式字符串 JSONObject json = new JSONObject(); json.put("type", "recording"); json.put("data", base64Data); // 获取输出流并发送数据 BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8")); writer.write(json.toString()); writer.write("\n\n"); // 添加双换行作为结束标识 writer.flush(); handler.sendEmptyMessage(0x13); // 发送录音数据的消息 } } catch (IOException | JSONException e) { Message msg = handler.obtainMessage(0x13, e.getMessage()); handler.sendMessage(msg); } catch (Exception e) { e.printStackTrace(); Message msg = handler.obtainMessage(0x13, "音频读取异常: " + e.getMessage()); handler.sendMessage(msg); } } /** * 上传完整录音文件(当前未使用) * 提示该模式下为实时传输无需手动上传 */ private void uploadRecording() { // 可选:上传录音完整文件逻辑,如有需要可添加实现 Toast.makeText(this, "该模式下无需上传文件,已实时发送", Toast.LENGTH_SHORT).show(); } /** * 启动TCP服务器 * 在指定端口监听客户端连接 * * @param port 监听端口号 */ private void startServer(int port) { executorService.execute(() -> { try { serverSocket = new ServerSocket(port); while (isServerRunning) { Socket socket = serverSocket.accept(); clientSocket = socket; handler.sendEmptyMessage(0x11); // 发送客户端连接成功的消息 // 启动双向通信处理 executorService.execute(() -> startCommunication(socket)); } } catch (IOException e) { e.printStackTrace(); runOnUiThread(() -> Toast.makeText(MainActivity.this, "服务器启动失败: " + e.getMessage(), Toast.LENGTH_LONG).show()); } }); } /** * 启动双向通信 * 处理客户端的连接和数据交互 * * @param socket 客户端Socket连接 */ private void startCommunication(Socket socket) { try { BufferedReader reader = new BufferedReader( new InputStreamReader(socket.getInputStream(), "UTF-8")); StringBuilder packetBuilder = new StringBuilder(); int c; while ((c = reader.read()) != -1) { char ch = (char) c; packetBuilder.append(ch); // 检测到连续两个换行符,表示一个完整的数据包结束 if (packetBuilder.length() >= 2 && packetBuilder.charAt(packetBuilder.length() - 2) == '\n' && packetBuilder.charAt(packetBuilder.length() - 1) == '\n') { String packet = packetBuilder.toString().trim(); // 去除首尾空白字符 packetBuilder.setLength(0); // 清空构建器 if (!packet.isEmpty()) { try { JSONObject jsonObject = new JSONObject(packet); handleReceivedPacket(jsonObject); // 处理接收到的数据包 } catch (JSONException e) { e.printStackTrace(); } } } } } catch (IOException e) { e.printStackTrace(); runOnUiThread(() -> Toast.makeText(MainActivity.this, "通信中断: " + e.getMessage(), Toast.LENGTH_SHORT).show()); } finally { try { socket.close(); } catch (IOException ignored) {} // 重置客户端socket if (socket == clientSocket) { clientSocket = null; } } } /** * 处理接收到的数据包 * 根据数据包类型执行相应操作 * * @param jsonObject 接收到的JSON数据包 */ private void handleReceivedPacket(JSONObject jsonObject) { try { String type = jsonObject.getString("type"); Object data = jsonObject.opt("data"); // 发送消息到主线程进行Toast显示 Message msg = handler.obtainMessage(0x15, type + ": " + data.toString()); handler.sendMessage(msg); // 根据不同类型执行不同操作 switch (type) { case "start_recording": if (!isRecording) { runOnUiThread(this::startRecording); } break; case "stop_recording": if (isRecording) { runOnUiThread(this::stopRecording); } break; case "ping": sendResponse("pong"); // 发送pong响应 break; // 可以添加更多类型的处理 } } catch (JSONException e) { e.printStackTrace(); } } /** * 发送响应数据包 * 使用统一的JSON格式并通过双换行结尾 * * @param responseType 响应类型 */ private void sendResponse(String responseType) { if (clientSocket == null || clientSocket.isClosed()) return; try { JSONObject response = new JSONObject(); response.put("type", responseType); response.put("data", ""); BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8")); writer.write(response.toString()); writer.write("\n\n"); writer.flush(); } catch (JSONException | IOException e) { e.printStackTrace(); } } /** * 发送特定类型的数据包 * 用于发送控制指令或其他消息 * * @param type 数据包类型 * @param data 数据内容 */ private void sendDataPacket(String type, Object data) { if (clientSocket == null || clientSocket.isClosed()) return; try { JSONObject packet = new JSONObject(); packet.put("type", type); packet.put("data", data); BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8")); writer.write(packet.toString()); writer.write("\n\n"); writer.flush(); } catch (JSONException | IOException e) { e.printStackTrace(); } } @Override protected void onDestroy() { super.onDestroy(); isServerRunning = false; try { if (serverSocket != null) serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } executorService.shutdownNow(); if (audioRecord != null) { audioRecord.release(); audioRecord = null; } if (scheduler != null) { scheduler.shutdownNow(); } try { if (clientSocket != null && !clientSocket.isClosed()) clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "权限已授予", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "录音权限被拒绝", Toast.LENGTH_SHORT).show(); } } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } import socket import subprocess import threading import time import json import base64 import numpy as np import queue import pyaudio # 配置参数 CHUNK = 1024 * 2 # 缓冲区大小 FORMAT = pyaudio.paInt16 # 16位PCM格式 CHANNELS = 1 # 单声道 RATE = 44100 # 采样率 SERVER_PORT = 30000 # TCP监听端口 class ADBExecutor: """ADB命令执行器""" ADB_PATH = "adb" HOST_PORT = 35000 ANDROID_PORT = 30000 def __init__(self, adb_path="adb"): self.adb_path = adb_path def get_connected_device(self): """获取当前连接的第一个设备""" try: result = subprocess.check_output([self.adb_path, "devices"], text=True) lines = result.strip().split("\n")[1:] devices = [line.split("\t")[0] for line in lines if "device" in line] return devices[0] if devices else None except Exception as e: print(f"ADB命令执行错误: {e}") return None def setup_port_forwarding(self, device_id): """设置端口转发""" try: subprocess.run( [self.adb_path, "-s", device_id, "forward", f"tcp:{self.HOST_PORT}", f"tcp:{self.ANDROID_PORT}"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) print(f"端口转发已设置: {self.HOST_PORT} <-> {self.ANDROID_PORT}") return True except Exception as e: print(f"端口转发失败: {e}") return False class AdbDeviceListener: """ADB设备监听器""" def __init__(self, adb_executor, interval=2, on_device_callback=None): self.adb_executor = adb_executor self.interval = interval self.on_device_callback = on_device_callback self.running = False self.current_device = None def start(self): """启动设备监听线程""" self.running = True threading.Thread(target=self._listen_loop, daemon=True).start() print("设备监听已启动") def stop(self): """停止设备监听""" self.running = False print("设备监听已停止") def _listen_loop(self): """设备监听主循环""" while self.running: try: device = self.adb_executor.get_connected_device() # 检测设备连接变化 if device and device != self.current_device: if self.current_device: self._handle_event("disconnected", self.current_device) self.current_device = device self._handle_event("connected", device) elif not device and self.current_device: self._handle_event("disconnected", self.current_device) self.current_device = None time.sleep(self.interval) except Exception as e: print(f"设备监听错误: {e}") time.sleep(5) def _handle_event(self, event_type, device_id): """处理设备事件""" if self.on_device_callback: self.on_device_callback(event_type, device_id) print(f"[设备事件] {event_type}: {device_id}") class AudioReceiver: """音频接收器,处理JSON格式的音频数据包""" def __init__(self, host_port): self.host_port = host_port self.socket = None self.running = False self.buffer = bytearray() # 接收缓冲区 self.audio_callback = None self.command_callback = None def connect(self): """连接到设备""" try: self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect(("localhost", self.host_port)) print(f"已连接到端口 {self.host_port}") return True except Exception as e: print(f"连接失败: {e}") self.socket = None return False def disconnect(self): """断开连接""" if self.socket: try: self.socket.close() except: pass self.socket = None def start_receiving(self, audio_callback=None, command_callback=None): """开始接收数据""" self.running = True self.audio_callback = audio_callback self.command_callback = command_callback threading.Thread(target=self._receive_loop, daemon=True).start() print("音频接收已启动") def stop_receiving(self): """停止接收数据""" self.running = False self.disconnect() print("音频接收已停止") def _receive_loop(self): """接收主循环""" while self.running: if not self.socket or not self._is_connected(): if not self.connect(): time.sleep(2) continue try: data = self.socket.recv(4096) if not data: print("连接断开,尝试重连...") self.disconnect() continue self.buffer.extend(data) self._parse_packets() except Exception as e: print(f"接收错误: {e}") self.disconnect() time.sleep(2) def _parse_packets(self): """解析接收到的数据包""" end_marker = b'\n\n' while True: marker_pos = self.buffer.find(end_marker) if marker_pos == -1: break # 没有完整数据包 # 提取完整数据包 packet_bytes = self.buffer[:marker_pos + len(end_marker)] self.buffer = self.buffer[marker_pos + len(end_marker):] try: # 解析JSON json_bytes = packet_bytes[:-len(end_marker)] json_str = json_bytes.decode('utf-8') packet = json.loads(json_str) # 处理不同类型的数据包 self._handle_packet(packet) except json.JSONDecodeError as e: print(f"JSON解析错误: {e}") except Exception as e: print(f"数据包处理错误: {e}") def _handle_packet(self, packet): """处理解析后的数据包""" packet_type = packet.get("type") if packet_type == "recording": # 处理录音数据 if self.audio_callback: audio_data = base64.b64decode(packet.get("data", "")) self.audio_callback(audio_data) elif packet_type in ["startRecorder", "stopRecord"]: # 处理命令 if self.command_callback: self.command_callback(packet_type) else: print(f"未知数据包类型: {packet_type}") def _is_connected(self): """检查连接状态""" if not self.socket: return False try: self.socket.sendall(b'') return True except: return False # 发送命令到安卓设备 def send_command(self, command_type, data=None): """发送命令到安卓设备""" if not self.socket or not self._is_connected(): print(f"发送失败: 未连接到设备") return False try: # 构建JSON数据包 packet = { "type": command_type, "data": base64.b64encode(data).decode('ascii') if data else None } # 转换为JSON字符串并添加结束标志 json_str = json.dumps(packet) packet_bytes = (json_str + "\n\n").encode('utf-8') # 发送数据包 self.socket.sendall(packet_bytes) print(f"已发送命令: {command_type}") return True except Exception as e: print(f"发送错误: {e}") return False def pcm_to_base64_str(pcm_data: bytes) -> str: """将PCM转为Base64编码字符串""" return base64.b64encode(pcm_data).decode('ascii') def pcm_to_utf8(pcm_data: bytearray) -> str: """将16位PCM音频数据转为UTF-8字符串""" def validate_pcm(data: bytearray) -> bool: """验证PCM数据有效性""" return len(data) % 2 == 0 # 16位PCM需为偶数长度 if not validate_pcm(pcm_data): raise ValueError("无效的PCM数据长度,16位PCM需为偶数长度") try: # 转为16位有符号整数数组(小端序) samples = np.frombuffer(pcm_data, dtype='<i2') # 标准化到0-255范围 normalized = ((samples - samples.min()) * (255 / (samples.max() - samples.min()))).astype(np.uint8) # 转换为UTF-8字符串 return bytes(normalized).decode('utf-8', errors='replace') except Exception as e: raise RuntimeError(f"转换失败: {str(e)}") def audio_data_handler(audio_data): """音频数据处理回调""" print(f"收到音频数据: {len(audio_data)} 字节") # 打印前32字节的十六进制表示 if len(audio_data) > 0: hex_preview = ' '.join(f'{b:02x}' for b in audio_data[:32]) print(f"前32字节: {hex_preview}...") # 调试用:将PCM转为UTF-8 if len(audio_data) < 1024: try: utf8_data = pcm_to_utf8(audio_data) print(f"UTF-8预览: {utf8_data[:30]}...") except: pass def command_handler(command_type): """命令处理回调""" if command_type == "startRecorder": print("[命令] 开始录音") elif command_type == "stopRecord": print("[命令] 停止录音") def on_device_callback(event, device_name): """设备事件回调""" if event == "connected": print(f"[设备已连接] {device_name}") # 设置端口转发 adb_executor = ADBExecutor() if adb_executor.setup_port_forwarding(device_name): # 连接并开始接收音频 global audio_receiver audio_receiver = AudioReceiver(ADBExecutor.HOST_PORT) if audio_receiver.connect(): audio_receiver.start_receiving( audio_callback=audio_data_handler, command_callback=command_handler ) print("已开始接收音频数据") else: print("连接失败") else: print("端口转发设置失败") elif event == "disconnected": print(f"[设备已断开连接] {device_name}") if 'audio_receiver' in globals(): audio_receiver.stop_receiving() # 主函数 def run_listener(): adb_executor = ADBExecutor() listener = AdbDeviceListener(adb_executor, interval=2, on_device_callback=on_device_callback) listener.start() try: while True: time.sleep(1) except KeyboardInterrupt: print("程序关闭,停止监听") listener.stop() if 'audio_receiver' in globals(): audio_receiver.stop_receiving() if __name__ == '__main__': run_listener() 把脚本和安卓端优化下 让安卓当客户端 python 当服务端去接收,现在是相反
06-27
import socket import subprocess import websocket import time import os import threading import json import pyaudio import requests import hashlib import base64 from audioplayer import AudioPlayer import numpy as np from runner import set_global_var, get_global_var device_status = {} def listen_devices(): try: # 检测设备连接状态 result = subprocess.check_output("adb devices", shell=True).decode() current_devices = set(line.split('\t')[0] for line in result.splitlines()[1:] if line) # 检测新连接设备 for dev in current_devices - set(device_status.keys()): print(f"[设备已连接] {dev}") device_status[dev] = "connected" # 检测断开设备 for dev in set(device_status.keys()) - current_devices: print(f"[设备已断开连接] {dev}") del device_status[dev] time.sleep(1) except Exception as e: print(f"设备监控错误: {e}") def pcm_to_utf8(pcm_data: bytearray) -> str: """将16位PCM音频数据转为UTF-8字符串""" def validate_pcm(data: bytearray) -> bool: """验证PCM数据有效性""" return len(data) % 2 == 0 # 16位PCM需为偶数长度 if not validate_pcm(pcm_data): raise ValueError("无效的PCM数据长度,16位PCM需为偶数长度") try: # 转为16位有符号整数数组(小端序) samples = np.frombuffer(pcm_data, dtype='<i2') # 标准化到0-255范围 normalized = ((samples - samples.min()) * (255 / (samples.max() - samples.min()))).astype(np.uint8) # 转换为UTF-8字符串 return bytes(normalized).decode('utf-8', errors='replace') except Exception as e: raise RuntimeError(f"转换失败: {str(e)}") # 打印前32字节的十六进制表示 def parse_packets(buffer): """解析接收到的数据包""" # 解析数据包 end_marker = b'\n\n' while buffer.find(end_marker) != -1: packet_bytes = buffer[:buffer.find(end_marker) + len(end_marker)] buffer = buffer[buffer.find(end_marker) + len(end_marker):] try: json_bytes = packet_bytes[:-len(end_marker)] json_str = json_bytes.decode('utf-8') packet = json.loads(json_str) # 处理数据包 packet_type = packet.get("type") if packet_type == "recording": audio_data = base64.b64decode(packet.get("data", "")) print('audio_data ', audio_data) return audio_data elif packet_type in ["startRecorder", "stopRecord"]: pass # command_callback(packet_type) else: print(f"未知数据包类型: {packet_type}") except json.JSONDecodeError as e: print(f"JSON解析错误: {e}") except Exception as e: print(f"数据包处理错误: {e}") def start_server(port=35000): adb_path = "adb.exe" os.system(f"adb forward tcp:{port} tcp:30000") with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect(('localhost', port)) #s.bind(('0.0.0.0', port)) #s.listen(5) print(f"服务器已启动,正在监听端口 {port}...") while True: threading.Thread(target=listen_devices).start() #client_socket, addr = s.accept() #print(f"接收到来自 {addr} 的连接") buffer = bytearray() try: while True: data = s.recv(4096) if data == b'': pass else: print('data', data) #buffer.extend(data) if not data: print("连接断开") break buffer.extend(data) hex_preview = parse_packets(buffer) handle_audio_chunk(hex_preview) print('hex_preview',hex_preview) '''if data==b'': pass else: if len(data) > 0: hex_preview = ' '.join(f'{b:02x}' for b in data[:32]) print(f"前32字节: {hex_preview}...") #handle_audio_chunk(hex_preview) # 调试用:将PCM转为UTF-8 if len(data) < 1024: try: utf8_data = pcm_to_utf8(data) print(f"UTF-8预览: {utf8_data[:30]}...") #handle_audio_chunk(utf8_data) except: pass''' except Exception as e: print(f"接收音频数据异常: {e}") # 全局配置信息 # 播放时是否停止收音 stop_recording_when_playing = True # 打断播放的语音指令 stop_playing_words = ["别说了", "停止", "停下"] # 说话人id voice_speaker_id = 5199 # 语音活动检测-静音时间长度,超过这个时间视为停止说话 vad_silent_time = 1.5 # 语音合成参数 tts_params = {"lan": "zh", "cuid": "test-1234", "ctp": 1, "pdt":993, "spd":5, "pit": 5,"aue": 3} # 语音识别开始指令参数 asr_params = { "type": "START", "data": { "dev_pid": 1912, "dev_key": "com.baidu.open", "format": "pcm", "sample": 16000, "cuid": "my_test_dev", "type": 1, "asr_type": 1, "need_mid": False, "need_session_finish": True } } # 全局状态变量 ws_running = False ws_object = None recorder_running = False sound_play_list = [] current_audio_player = None chat_running = False current_query = '' last_asr_time = 0 def ws_send_start_command(ws): message = json.dumps(asr_params) ws.send_text(message) def ws_send_stop_command(ws): # 发送数据 msg_data = { "type": "FINISH", } message = json.dumps(msg_data) ws.send_text(message) def on_ws_message(ws, message): global current_query, last_asr_time data = json.loads(message) cmd_type = data.get("type") if cmd_type == 'MID_TEXT': mid_text = data.get("result") set_global_var("voicebot.asr.mid_text", mid_text) last_asr_time = time.time() # print("voicebot.asr.mid_text:", mid_text) elif cmd_type == "FIN_TEXT": query = data.get("result") # print("asr result:", query) set_global_var("voicebot.asr.result", query) last_asr_time = time.time() if query and len(query) > 0: current_query += query set_global_var("voicebot.chat.query", current_query) if ws_running == False: ws.close() def on_ws_close(ws, close_status_code, close_msg): print("websocket closed:", close_status_code, close_msg) def on_ws_error(ws, error): print(f"websocket Error: {error}") ws.close() def on_ws_open(ws): print("websocket connection opened:", ws) ws_send_start_command(ws) def check_chat(query:str): # for word in stop_playing_words: # if word in query: # stop_sound_player() # return False # if query in stop_playing_words: # stop_sound_player() # return False if is_playing_or_chatting(): return False return True def stop_sound_player(): global chat_running if current_audio_player: current_audio_player.stop() if len(sound_play_list) > 0: sound_play_list.clear() chat_running = False def run_chat(query:str): global chat_running chat_running = True set_global_var("voicebot.chat.query", query) params = {"query": query} params['username'] = get_global_var("voicebot.username") params['password'] = get_global_var("voicebot.password") response = requests.post("http://127.0.0.1:8010/chat", json=params, stream=True) total_reply = '' buffer = '' for line in response.iter_lines(): if line and chat_running: text = line.decode('utf-8') data = json.loads(text[5:]) content = data.get("content") buffer += content buffer = extract_play_text(buffer) total_reply += content set_global_var("voicebot.chat.reply", total_reply) # print(content, end='', flush=True) chat_running = False buffer = buffer.strip() if len(buffer) > 0: add_play_text(buffer) time.sleep(1) set_global_var("voicebot.chat.query", None) set_global_var("voicebot.chat.reply", None) #提取播放文本 def extract_play_text(total_text:str): separators = ",;。!?:,.!?\n" last_start_pos = 0 min_sentence_length = 4 for i in range(0, len(total_text)): if total_text[i] in separators and i - last_start_pos >= min_sentence_length: text = total_text[last_start_pos: i + 1] last_start_pos = i + 1 add_play_text(text.strip()) return total_text[last_start_pos:] #添加播放文本 def add_play_text(text:str): # print("add play text:", text) if len(text) > 1: sound_play_list.append({"text": text, "mp3_file": None}) # 语音合成 下载声音文件 def download_sound_file(text:str, speaker:int=None): if speaker is None: speaker = voice_speaker_id # print("tts create:", text) mp3_path = "sounds/" + str(speaker) if not os.path.exists(mp3_path): os.mkdir(mp3_path) mp3_file = mp3_path + "/" + hashlib.md5(text.encode('utf-8')).hexdigest() + ".mp3" if os.path.exists(mp3_file): return mp3_file params = tts_params params['per'] = speaker params['text'] = text url = "http://25.83.75.1:8088/Others/tts/text2audio/json" response = requests.post(url, json=params) data = response.json() if data['success'] == False: binary_array = json.loads(data['message']['message']) binary_data = bytes(binary_array) string_data = binary_data.decode('utf-8', errors='replace') data = json.loads(string_data) return "sounds/tts-failed.mp3" else: b64_string = data['result'].get('data') mp3_data = base64.b64decode(b64_string) with open(mp3_file, 'wb') as file: file.write(mp3_data) return mp3_file #开始聊天 def is_playing_or_chatting(): return len(sound_play_list) > 0 #播放下一个声音 def play_next_sound(): global sound_play_list, current_audio_player item = sound_play_list[0] mp3_file = item.get("mp3_file") if mp3_file: player = AudioPlayer(mp3_file) current_audio_player = player try: player.play(block=True) except Exception as e: print("player exception:" + e) current_audio_player = None # print("remained sound:", len(sound_play_list)) if len(sound_play_list) > 0: sound_play_list.pop(0) #运行websocket def run_websocket(): global ws_running, ws_object ws_running = True uri = "ws://25.83.75.1:8088/Others/asr/realtime_asr?sn=voicebot" ws = websocket.WebSocketApp(uri, on_message=on_ws_message, on_close=on_ws_close, on_error=on_ws_error) ws_object = ws ws.on_open = on_ws_open ws.run_forever() ws_running = False # print("websocket end") #开始记录 def start_recorder(chuck_size:int=2560): audio = pyaudio.PyAudio() try: stream = audio.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=chuck_size) return audio, stream except: print("打开麦克风失败") return None, None #获得不发音的时间 def get_silent_chunk(duration:float=0.16): sample_rate = 16000 # 采样率 num_samples = int(sample_rate * duration) # 计算样本数 silent_data = np.zeros(num_samples, dtype=np.int16) silent_bytes = silent_data.tobytes() return silent_bytes #处理音频块 def handle_audio_chunk(chunk_data:bytes): # 接受外部是否收音的要求 recording = get_global_var("voicebot.recording") if ws_object and ws_object.sock and ws_object.sock.connected: if recording == False or (stop_recording_when_playing and is_playing_or_chatting()): # print("ignor audio chunk:", sound_play_list, chat_running) ws_object.send_bytes(get_silent_chunk()) else: ws_object.send_bytes(chunk_data) #运行录音机 def run_recorder(audio=None, stream=None, chuck_size=2560): global recorder_running recorder_running = True set_global_var("voicebot.recording", True) while recorder_running: chunk_data = stream.read(chuck_size) print('chunk_data)',chunk_data) handle_audio_chunk(chunk_data) stream.stop_stream() stream.close() audio.terminate() # print("recorder end") #运行检查 def run_check(): global ws_running, recorder_running, current_query set_global_var("voicebot.running", True) while ws_running and recorder_running: time.sleep(1) if get_global_var("voicebot.running") == False: break if len(current_query) > 0 and last_asr_time > 0 and time.time() - last_asr_time > vad_silent_time: t = threading.Thread(target=run_chat, args=(current_query,)) t.start() current_query = '' ws_running = recorder_running = False set_global_var("voicebot.running", False) # print("语音助手已经停止") #运行播放机 def run_player(): while ws_running and recorder_running: time.sleep(0.1) if len(sound_play_list) > 0: play_next_sound() def run_tts(): while ws_running and recorder_running: time.sleep(0.1) for item in sound_play_list: if item.get("mp3_file") is None: item['mp3_file'] = download_sound_file(item['text']) def run(): active_threads = threading.enumerate() # 打印每个活跃线程的信息 for t in active_threads: if t.name == 'voicebot-runner': return "语音助手已经在运行中了" audio, stream = start_recorder() if audio is None or stream is None: return {"error": "语音助手开启失败,无法访问麦克风"} t = threading.Thread(target=run_websocket) t.daemon = True t.start() t=threading.Thread(target=start_server()) t.daemon = True t.start() t = threading.Thread(target=run_check, name='voicebot-runner') t.daemon = True t.start() t = threading.Thread(target=run_tts) t.daemon = True t.start() t = threading.Thread(target=run_player) t.daemon = True t.start() return "执行成功" if __name__ == "__main__": #run() start_server() 把这个TTS的功能融入到第一个脚本里面生成新脚本,并且修改安卓的代码package com.example.demoapplication; import android.Manifest; import android.content.pm.PackageManager; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.speech.tts.TextToSpeech; import android.util.Base64; import android.util.Log; import android.widget.Button; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Locale; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class MainActivity extends AppCompatActivity implements TextToSpeech.OnInitListener { private static final String TAG = "AudioRecorder"; private Button startRecordButton; private Button stopRecordButton; private Button uploadButton; // 音频录制相关 private AudioRecord audioRecord; private static final int SAMPLE_RATE = 44100; // 音频采样率 private static final int BUFFER_SIZE; // 静态代码块用于初始化缓冲区大小 static { int minBufferSize = 0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) { minBufferSize = AudioRecord.getMinBufferSize( SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT ); } // 确保缓冲区大小有效 BUFFER_SIZE = Math.max(minBufferSize, 4096); } // 多线程任务调度器 private ScheduledExecutorService scheduler; private AtomicBoolean isRecording = new AtomicBoolean(false); // 录音状态标志 private static final int PERMISSION_REQUEST_CODE = 1; // 权限请求码 // 线程池服务 private final ExecutorService executorService = Executors.newCachedThreadPool(); // 网络服务器相关 private ServerSocket serverSocket; private volatile boolean isServerRunning = true; // 服务器运行状态 private volatile Socket clientSocket; // 客户端Socket连接 private volatile BufferedWriter socketWriter; // Socket写入流 // 文本转语音(TTS)相关变量 private TextToSpeech ttsEngine; private boolean isTtsInitialized = false; // 主线程消息处理器,用于UI更新 private final Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(@NonNull Message msg) { switch (msg.what) { case 0x11: // 客户端连接成功 Toast.makeText(MainActivity.this, "客户端已连接", Toast.LENGTH_SHORT).show(); break; case 0x12: // 开始录音 Toast.makeText(MainActivity.this, "开始录音", Toast.LENGTH_SHORT).show(); break; case 0x13: // 数据发送成功 // 减少Toast频率,避免刷屏 if (Math.random() < 0.1) { // 10%概率显示 Toast.makeText(MainActivity.this, "录音数据已发送", Toast.LENGTH_SHORT).show(); } break; case 0x14: // 停止录音 Toast.makeText(MainActivity.this, "停止录音", Toast.LENGTH_SHORT).show(); break; case 0x15: // 控制指令 Toast.makeText(MainActivity.this, "收到控制指令:" + msg.obj.toString(), Toast.LENGTH_SHORT).show(); break; case 0x16: // 错误消息 Toast.makeText(MainActivity.this, "错误: " + msg.obj.toString(), Toast.LENGTH_LONG).show(); break; case 0x17: // 网络状态 Toast.makeText(MainActivity.this, "网络: " + msg.obj.toString(), Toast.LENGTH_SHORT).show(); break; } } }; /** * Activity创建时调用,进行初始化操作。 * @param savedInstanceState 保存的状态数据 */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化TTS引擎 ttsEngine = new TextToSpeech(this, this); initViews(); // 初始化视图组件 setupClickListeners(); // 设置点击事件监听器 checkPermissions(); // 检查权限 startServer(30000); // 启动服务器,端口30000 } /** * 初始化UI视图组件 */ private void initViews() { startRecordButton = findViewById(R.id.startRecordButton); stopRecordButton = findViewById(R.id.stopRecordButton); uploadButton = findViewById(R.id.uploadButton); stopRecordButton.setEnabled(false); uploadButton.setEnabled(false); } /** * 设置按钮点击事件监听器 */ private void setupClickListeners() { startRecordButton.setOnClickListener(v -> startRecording()); stopRecordButton.setOnClickListener(v -> stopRecording()); uploadButton.setOnClickListener(v -> uploadRecording()); } /** * 检查录音权限并请求必要权限 */ private void checkPermissions() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, PERMISSION_REQUEST_CODE); } } /** * 开始录音操作 */ private void startRecording() { // 检查权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { sendErrorMessage("没有录音权限"); return; } // 检查是否正在录音 if (isRecording.get() || audioRecord != null) { sendErrorMessage("录音已在进行中"); return; } // 检查网络连接 if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) { sendErrorMessage("客户端未连接,无法录音"); return; } try { // 初始化 AudioRecord audioRecord = new AudioRecord( MediaRecorder.AudioSource.MIC, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, BUFFER_SIZE ); // 检查初始化状态 if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { throw new IllegalStateException("AudioRecord 初始化失败"); } // 开始录音 audioRecord.startRecording(); isRecording.set(true); // 更新按钮状态 startRecordButton.setEnabled(false); stopRecordButton.setEnabled(true); uploadButton.setEnabled(false); // 创建定时任务发送音频数据 scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(this::uploadAudioData, 0, 100, TimeUnit.MILLISECONDS); // 提高发送频率 handler.sendEmptyMessage(0x12); // 发送开始录音的消息 // 发送开始录音控制指令 sendControlPacket("startRecorder"); // 播放TTS提示音 playTts("开始录音"); } catch (Exception e) { Log.e(TAG, "录音启动失败", e); sendErrorMessage("录音启动失败: " + e.getMessage()); releaseAudioResources(); } } /** * 停止录音操作 */ private void stopRecording() { if (!isRecording.get()) return; isRecording.set(false); releaseAudioResources(); // 更新按钮状态 stopRecordButton.setEnabled(false); uploadButton.setEnabled(true); handler.sendEmptyMessage(0x14); // 发送停止录音的消息 // 发送停止录音控制指令 sendControlPacket("stopRecor"); // 播放TTS提示音 playTts("停止录音"); } /** * 使用TTS播放指定文本 * @param text 要播放的文本内容 */ private void playTts(String text) { if (isTtsInitialized) { // 使用系统TTS播放 ttsEngine.speak(text, TextToSpeech.QUEUE_FLUSH, null); Log.i(TAG, "播放TTS: " + text); } else { Log.w(TAG, "TTS未初始化,无法播放: " + text); } } /** * 释放音频资源 */ private void releaseAudioResources() { if (audioRecord != null) { try { if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { audioRecord.stop(); } } catch (IllegalStateException e) { Log.e(TAG, "停止录音失败", e); } audioRecord.release(); audioRecord = null; } if (scheduler != null) { scheduler.shutdownNow(); scheduler = null; } } /** * 上传音频数据到服务器 */ private void uploadAudioData() { if (!isRecording.get() || clientSocket == null || clientSocket.isClosed() || socketWriter == null) { Log.w(TAG, "无法发送音频数据: 录音未进行或客户端未连接"); return; } byte[] buffer = new byte[BUFFER_SIZE]; try { int bytesRead = audioRecord.read(buffer, 0, BUFFER_SIZE); if (bytesRead > 0) { // 创建JSON数据包 JSONObject json = new JSONObject(); json.put("type", "recording"); json.put("data", Base64.encodeToString(buffer, 0, bytesRead, Base64.NO_WRAP)); // 使用NO_WRAP避免换行符 // 发送数据 synchronized (this) { if (socketWriter != null) { socketWriter.write(json.toString()); socketWriter.write("\n\n"); // 添加双换行作为结束标识 socketWriter.flush(); } } handler.sendEmptyMessage(0x13); // 发送录音数据的消息 } } catch (Exception e) { Log.e(TAG, "发送音频数据失败", e); sendErrorMessage("发送音频数据失败: " + e.getMessage()); } } /** * TTS初始化回调方法 * @param status 初始化状态 */ @Override public void onInit(int status) { if (status == TextToSpeech.SUCCESS) { // 设置默认语言为中文 int result = ttsEngine.setLanguage(Locale.CHINESE); if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Log.e(TAG, "TTS语言不支持中文"); } else { isTtsInitialized = true; Log.i(TAG, "TTS初始化成功,语言设置为中文"); } } else { Log.e(TAG, "TTS初始化失败"); } } /** * 发送控制指令包 * @param type 控制指令类型 */ private void sendControlPacket(String type) { if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) { sendErrorMessage("无法发送控制指令: 客户端未连接"); return; } try { JSONObject packet = new JSONObject(); packet.put("type", type); packet.put("data", JSONObject.NULL); synchronized (this) { if (socketWriter != null) { socketWriter.write(packet.toString()); socketWriter.write("\n\n"); // 双换行作为结束标识 socketWriter.flush(); } } Log.i(TAG, "控制指令发送成功: " + type); } catch (Exception e) { Log.e(TAG, "发送控制指令失败", e); sendErrorMessage("发送控制指令失败: " + e.getMessage()); } } /** * 发送错误消息 * @param message 错误信息 */ private void sendErrorMessage(String message) { Message msg = handler.obtainMessage(0x16, message); handler.sendMessage(msg); } /** * 发送网络状态消息 * @param message 网络状态信息 */ private void sendNetworkMessage(String message) { Message msg = handler.obtainMessage(0x17, message); handler.sendMessage(msg); } /** * 上传录音文件(当前模式下无实际作用) */ private void uploadRecording() { Toast.makeText(this, "该模式下无需上传文件,已实时发送", Toast.LENGTH_SHORT).show(); } /** * 启动服务器监听 * @param port 监听端口号 */ private void startServer(int port) { executorService.execute(() -> { try { serverSocket = new ServerSocket(port); Log.i(TAG, "服务器启动,监听端口: " + port); sendNetworkMessage("服务器启动"); while (isServerRunning) { try { Socket socket = serverSocket.accept(); clientSocket = socket; // 创建输出流 synchronized (this) { socketWriter = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); } handler.sendEmptyMessage(0x11); // 发送客户端连接成功的消息 Log.i(TAG, "客户端已连接: " + socket.getInetAddress()); sendNetworkMessage("客户端已连接"); // 启动双向通信处理 executorService.execute(() -> startCommunication(socket)); } catch (IOException e) { if (isServerRunning) { Log.e(TAG, "接受连接失败", e); sendErrorMessage("接受连接失败: " + e.getMessage()); } } } } catch (IOException e) { Log.e(TAG, "服务器启动失败", e); runOnUiThread(() -> Toast.makeText(MainActivity.this, "服务器启动失败: " + e.getMessage(), Toast.LENGTH_LONG).show()); } finally { closeServerSocket(); } }); } /** * 开始与客户端的通信 * @param socket 客户端Socket连接 */ private void startCommunication(Socket socket) { try (java.io.BufferedReader reader = new java.io.BufferedReader( new java.io.InputStreamReader(socket.getInputStream(), "UTF-8"))) { StringBuilder packetBuilder = new StringBuilder(); int c; while ((c = reader.read()) != -1 && isServerRunning) { char ch = (char) c; packetBuilder.append(ch); // 检测到连续两个换行符,表示一个完整的数据包结束 if (packetBuilder.length() >= 2 && packetBuilder.charAt(packetBuilder.length() - 2) == '\n' && packetBuilder.charAt(packetBuilder.length() - 1) == '\n') { String packet = packetBuilder.toString().trim(); packetBuilder.setLength(0); // 清空构建器 if (!packet.isEmpty()) { try { JSONObject jsonObject = new JSONObject(packet); handleReceivedPacket(jsonObject); } catch (JSONException e) { Log.w(TAG, "JSON解析失败: " + packet, e); } } } } } catch (IOException e) { if (isServerRunning) { Log.e(TAG, "通信中断", e); runOnUiThread(() -> Toast.makeText(MainActivity.this, "通信中断: " + e.getMessage(), Toast.LENGTH_SHORT).show()); } } finally { closeSocket(socket); } } /** * 处理接收到的数据包 * @param jsonObject 接收到的JSON数据包 */ private void handleReceivedPacket(JSONObject jsonObject) { try { String type = jsonObject.getString("type"); Object data = jsonObject.opt("data"); // 发送消息到主线程进行显示 Message msg = handler.obtainMessage(0x15, type + ": " + data); handler.sendMessage(msg); Log.i(TAG, "收到控制指令: " + type); // 根据不同类型执行不同操作 switch (type) { case "start_recording": runOnUiThread(this::startRecording); break; case "stop_recording": runOnUiThread(this::stopRecording); break; case "ping": sendResponse("pong"); break; } } catch (JSONException e) { Log.e(TAG, "处理数据包失败", e); } } /** * 发送响应给客户端 * @param responseType 响应类型 */ private void sendResponse(String responseType) { if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) return; try { JSONObject response = new JSONObject(); response.put("type", responseType); response.put("data", ""); synchronized (this) { if (socketWriter != null) { socketWriter.write(response.toString()); socketWriter.write("\n\n"); socketWriter.flush(); } } Log.i(TAG, "发送响应: " + responseType); } catch (Exception e) { Log.e(TAG, "发送响应失败", e); } } /** * 关闭指定的Socket连接 * @param socket 要关闭的Socket */ private void closeSocket(Socket socket) { try { if (socket != null && !socket.isClosed()) { socket.close(); } } catch (IOException e) { Log.w(TAG, "关闭Socket失败", e); } // 如果是当前客户端Socket,重置引用 if (socket == clientSocket) { clientSocket = null; synchronized (this) { socketWriter = null; } sendNetworkMessage("客户端断开连接"); } } /** * 关闭服务器Socket */ private void closeServerSocket() { try { if (serverSocket != null && !serverSocket.isClosed()) { serverSocket.close(); } } catch (IOException e) { Log.w(TAG, "关闭ServerSocket失败", e); } } /** * Activity销毁时调用,释放所有资源 */ @Override protected void onDestroy() { super.onDestroy(); isServerRunning = false; // 关闭TTS引擎 if (ttsEngine != null) { ttsEngine.stop(); ttsEngine.shutdown(); } // 关闭所有资源 closeServerSocket(); closeSocket(clientSocket); executorService.shutdownNow(); releaseAudioResources(); Log.i(TAG, "应用已销毁"); sendNetworkMessage("服务已停止"); } /** * 权限请求结果回调 * @param requestCode 请求码 * @param permissions 请求的权限数组 * @param grantResults 权限授予结果 */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "录音权限已授予", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "录音权限被拒绝", Toast.LENGTH_SHORT).show(); } } } }
07-01
停止云台控制 接口功能 设备停止云台控制 请求地址 https://open.ys7.com/api/lapp/device/ptz/stop 请求方式 POST 子账户token请求所需最小权限 "Permission":"Ptz" "Resource":"Cam:序列号:通道号" 请求参数 参数名 类型 描述 是否必选 accessToken String 授权过程获取的access_token Y deviceSerial String 设备序列号,存在英文字母的设备序列号,字母需为大写 Y channelNo int 通道号 Y direction int 操作命令:0-上,1-下,2-左,3-右,4-左上,5-左下,6-右上,7-右下,8-放大,9-缩小,10-近焦距,11-远焦距,16-自动控制 N 提示:建议停止云台接口带方向参数。 HTTP请求报文 POST /api/lapp/device/ptz/stop HTTP/1.1 Host: open.ys7.com Content-Type: application/x-www-form-urlencoded accessToken=at.25ne3gkr6fa7coh34ys0fl1h9hryc2kr&deviceSerial=568261888&channelNo=1&direction=1 返回数据 { "code": "200", "msg": "操作成功!" } 返回码 返回码 返回消息 描述 200 操作成功 请求成功 10001 参数错误 参数为空或格式不正确 10002 accessToken异常或过期 重新获取accessToken 10005 appKey异常 appKey被冻结 20002 设备不存在 20006 网络异常 检查设备网络状况,稍后再试 20007 设备不在线 检查设备是否在线 20008 设备响应超时 操作过于频繁,稍后再试 20014 deviceSerial不合法 20032 该用户下通道不存在 该用户下通道不存在 49999 数据异常 接口调用异常 60000 设备不支持云台控制 60001 用户无云台控制权限 60006 云台当前操作失败 稍候再试 60009 正在调用预置点 60020 不支持该命令 确认设备是否支持该操作 开始云台控制 接口功能 对设备进行开始云台控制,开始云台控制之后必须先调用停止云台控制接口才能进行其他操作,包括其他方向的云台转动 请求地址 https://open.ys7.com/api/lapp/device/ptz/start 请求方式 POST 子账户token请求所需最小权限 "Permission":"Ptz" "Resource":"Cam:序列号:通道号" 请求参数 参数名 类型 描述 是否必选 accessToken String 授权过程获取的access_token Y deviceSerial String 设备序列号,存在英文字母的设备序列号,字母需为大写 Y channelNo int 通道号 Y direction int 操作命令:0-上,1-下,2-左,3-右,4-左上,5-左下,6-右上,7-右下,8-物理放大,9-物理缩小,10-调整近焦距,11-调整远焦距,16-自动控制 Y speed int 云台速度:0-慢,1-适中,2-快,海康设备参数不可为0 Y HTTP请求报文 POST /api/lapp/device/ptz/start HTTP/1.1 Host: open.ys7.com Content-Type: application/x-www-form-urlencoded accessToken=at.4g01l53x0w22xbp30ov33q44app1ns9m&deviceSerial=502608888&channelNo=1&direction=2&speed=1 返回数据 { "code": "200", "msg": "操作成功!" } 返回码 返回码 返回消息 描述 200 操作成功 请求成功 10001 参数错误 参数为空或格式不正确 10002 accessToken异常或过期 重新获取accessToken 10005 appKey异常 appKey被冻结 20002 设备不存在 20006 网络异常 检查设备网络状况,稍后再试 20007 设备不在线 检查设备是否在线 20008 设备响应超时 操作过于频繁,稍后再试 20014 deviceSerial不合法 20032 该用户下通道不存在 该用户下通道不存在 49999 数据异常 接口调用异常 60000 设备不支持云台控制 60001 用户无云台控制权限 60002 设备云台旋转达到上限位 60003 设备云台旋转达到下限位 60004 设备云台旋转达到左限位 60005 设备云台旋转达到右限位 60006 云台当前操作失败 稍候再试 60009 正在调用预置点 60020 不支持该命令 确认设备是否支持该操作 MainActivity:package com.hik.netsdk.SimpleDemo.View; import android.app.AlertDialog; import android.content.Intent; import android.graphics.PixelFormat; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.Toast; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.gson.Gson; import com.hik.netsdk.SimpleDemo.R; import com.hik.netsdk.SimpleDemo.View.DevMgtUI.AddDevActivity; import com.videogo.errorlayer.ErrorInfo; import com.videogo.openapi.EZConstants; import com.videogo.openapi.EZOpenSDK; import com.videogo.openapi.EZPlayer; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.util.concurrent.TimeUnit; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback { // 核心参数 private String mDeviceSerial; private String mVerifyCode; private int mCameraNo = 1; private boolean isEzDevice = false; // 萤石云相关参数 private String mAppKey = "a794d58c13154caeb7d2fbb5c3420c65"; private String mAccessToken = ""; // UI组件 private SurfaceView mPreviewSurface; private SurfaceHolder mSurfaceHolder; private Toolbar m_toolbar; private ProgressBar mProgressBar; private RelativeLayout mControlLayout; private ImageButton mRotateButton; // 播放器相关 private SimpleExoPlayer mExoPlayer; private String mPlaybackUrl; private final OkHttpClient mHttpClient = new OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) .build(); // 播放器状态监听器 private final Player.Listener mPlayerListener = new Player.Listener() { @Override public void onPlaybackStateChanged(int state) { switch (state) { case Player.STATE_READY: mProgressBar.setVisibility(View.GONE); Log.d("ExoPlayer", "播放准备就绪"); break; case Player.STATE_BUFFERING: mProgressBar.setVisibility(View.VISIBLE); Log.d("ExoPlayer", "缓冲中..."); break; case Player.STATE_ENDED: Log.d("ExoPlayer", "播放结束"); break; case Player.STATE_IDLE: Log.d("ExoPlayer", "空闲状态"); break; } } @Override public void onPlayerError(com.google.android.exoplayer2.PlaybackException error) { Log.e("ExoPlayer", "播放错误: " + error.getMessage()); handleError("播放错误: " + error.getMessage(), -1); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化UI组件 initUIComponents(); // 获取启动参数 parseIntentParams(); // 初始化SDK initSDK(); // 参数校验 if (mDeviceSerial == null || mDeviceSerial.isEmpty()) { showErrorAndFinish("设备序列号不能为空"); return; } Log.d("MainActivity", "onCreate完成: isEzDevice=" + isEzDevice); } private void initUIComponents() { // 基础UI m_toolbar = findViewById(R.id.toolbar); setSupportActionBar(m_toolbar); // 预览相关UI mPreviewSurface = findViewById(R.id.realplay_sv); mSurfaceHolder = mPreviewSurface.getHolder(); mSurfaceHolder.addCallback(this); mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT); mProgressBar = findViewById(R.id.liveProgressBar); mControlLayout = findViewById(R.id.rl_control); mRotateButton = findViewById(R.id.ib_rotate2); // 设置旋转按钮点击事件 mRotateButton.setOnClickListener(v -> changeScreen()); // 隐藏管理UI DrawerLayout drawer = findViewById(R.id.drawer_layout); if (drawer != null) { drawer.setVisibility(View.GONE); } if (m_toolbar != null) { m_toolbar.setVisibility(View.GONE); } // 初始化控制按钮 initControlButtons(); Log.d("UI", "UI组件初始化完成"); } private void parseIntentParams() { // 获取Intent参数 Intent intent = getIntent(); isEzDevice = true; // 只支持萤石设备 mAccessToken = intent.getStringExtra("accessToken"); // 优先使用bundle Bundle bundle = intent.getExtras(); if (bundle != null) { mDeviceSerial = bundle.getString("devSn"); mVerifyCode = bundle.getString("verifyCode"); mCameraNo = bundle.getInt("cameraNo", 1); } // 兼容直接Extra方式 if (mDeviceSerial == null) mDeviceSerial = intent.getStringExtra("devSn"); if (mVerifyCode == null) mVerifyCode = intent.getStringExtra("verifyCode"); if (mCameraNo == 0) mCameraNo = intent.getIntExtra("cameraNo", 1); Log.d("Params", "设备序列号: " + mDeviceSerial + ", 通道号: " + mCameraNo); } private void initSDK() { try { // 使用反射检查初始化状态 boolean isInitialized = false; try { // 尝试获取实例 EZOpenSDK instance = EZOpenSDK.getInstance(); if (instance != null) { isInitialized = true; } } catch (Exception e) { // 捕获未初始化的异常 isInitialized = false; } // 未初始化时进行初始化 if (!isInitialized) { EZOpenSDK.initLib(getApplication(), mAppKey); Log.d("EZSDK", "萤石SDK初始化完成"); } // 设置AccessToken if (mAccessToken != null && !mAccessToken.isEmpty()) { EZOpenSDK.getInstance().setAccessToken(mAccessToken); Log.d("EZToken", "AccessToken设置成功"); } else { Log.w("EZToken", "AccessToken缺失!"); } } catch (Exception e) { Log.e("EZSDK", "萤石SDK初始化失败", e); handleError("萤石SDK初始化失败: " + e.getMessage(), -1); } } private void initControlButtons() { // 云台控制按钮 findViewById(R.id.ptz_top_btn).setOnClickListener(v -> controlPTZ("UP")); findViewById(R.id.ptz_bottom_btn).setOnClickListener(v -> controlPTZ("DOWN")); findViewById(R.id.ptz_left_btn).setOnClickListener(v -> controlPTZ("LEFT")); findViewById(R.id.ptz_right_btn).setOnClickListener(v -> controlPTZ("RIGHT")); // 变焦控制 findViewById(R.id.focus_add).setOnClickListener(v -> controlZoom("ZOOM_IN")); findViewById(R.id.foucus_reduce).setOnClickListener(v -> controlZoom("ZOOM_OUT")); // 水平布局控制按钮 findViewById(R.id.ptz_top_btn2).setOnClickListener(v -> controlPTZ("UP")); findViewById(R.id.ptz_bottom_btn2).setOnClickListener(v -> controlPTZ("DOWN")); findViewById(R.id.ptz_left_btn2).setOnClickListener(v -> controlPTZ("LEFT")); findViewById(R.id.ptz_right_btn2).setOnClickListener(v -> controlPTZ("RIGHT")); // 添加萤石云特有功能按钮 findViewById(R.id.btn_record).setOnClickListener(v -> startLocalRecord()); findViewById(R.id.btn_stop_record).setOnClickListener(v -> stopLocalRecord()); findViewById(R.id.btn_capture).setOnClickListener(v -> capturePicture()); Log.d("Controls", "控制按钮初始化完成"); } @Override public void surfaceCreated(SurfaceHolder holder) { Log.d("Surface", "Surface created"); // 获取播放地址并开始播放 new Thread(this::fetchPlaybackUrl).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.d("Surface", "Surface changed: " + width + "x" + height); if (mExoPlayer != null) { mExoPlayer.setVideoSurfaceHolder(holder); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.d("Surface", "Surface destroyed"); stopPreview(); } // ======================== 播放地址获取与播放 ======================== private void fetchPlaybackUrl() { try { // 构建请求参数 JSONObject params = new JSONObject(); params.put("accessToken", mAccessToken); params.put("deviceSerial", mDeviceSerial); params.put("channelNo", mCameraNo); params.put("protocol", 2); // 使用HLS协议 if (mVerifyCode != null && !mVerifyCode.isEmpty()) { params.put("code", mVerifyCode); } params.put("expireTime", 7200); // 2小时有效期 // 创建请求体 RequestBody body = RequestBody.create( MediaType.parse("application/json"), params.toString() ); // 创建请求 Request request = new Request.Builder() .url("https://open.ys7.com/api/lapp/v2/live/address/get") .post(body) .build(); // 执行请求 try (Response response = mHttpClient.newCall(request).execute()) { if (!response.isSuccessful()) { handleError("获取播放地址失败: " + response.code(), response.code()); return; } // 解析响应 String responseBody = response.body().string(); JSONObject json = new JSONObject(responseBody); if ("200".equals(json.getString("code"))) { JSONObject data = json.getJSONObject("data"); mPlaybackUrl = data.getString("url"); Log.d("PlaybackURL", "获取到播放地址: " + mPlaybackUrl); // 在主线程初始化播放器 runOnUiThread(this::initExoPlayer); } else { handleError("API错误: " + json.getString("msg"), json.getInt("code")); } } } catch (JSONException | IOException e) { Log.e("FetchURL", "获取播放地址异常", e); handleError("获取播放地址异常: " + e.getMessage(), -1); } } private void initExoPlayer() { // 释放现有播放器 if (mExoPlayer != null) { mExoPlayer.release(); } // 创建新的播放器实例 mExoPlayer = new SimpleExoPlayer.Builder(this).build(); mExoPlayer.addListener(mPlayerListener); mExoPlayer.setVideoSurfaceHolder(mSurfaceHolder); // 创建媒体源 DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory(); MediaSource mediaSource = new HlsMediaSource.Factory(dataSourceFactory) .createMediaSource(MediaItem.fromUri(Uri.parse(mPlaybackUrl))); // 准备播放 mExoPlayer.setMediaSource(mediaSource); mExoPlayer.prepare(); mExoPlayer.setPlayWhenReady(true); mProgressBar.setVisibility(View.VISIBLE); Log.d("ExoPlayer", "播放器初始化完成,开始播放"); } // ======================== 萤石云控制方法 ======================== private void controlPTZ(String direction) { try { EZConstants.EZPTZAction action; switch (direction) { case "UP": action = EZConstants.EZPTZAction.EZPTZActionUp; break; case "DOWN": action = EZConstants.EZPTZAction.EZPTZActionDown; break; case "LEFT": action = EZConstants.EZPTZAction.EZPTZActionLeft; break; case "RIGHT": action = EZConstants.EZPTZAction.EZPTZActionRight; break; default: return; } // 使用EZOpenSDK的API进行控制 EZOpenSDK.getInstance().controlPTZ( mDeviceSerial, mCameraNo, action, EZConstants.EZPTZCommand.EZPTZCommandStart, 2 ); // 300ms后停止控制 new Handler().postDelayed(() -> { EZOpenSDK.getInstance().controlPTZ( mDeviceSerial, mCameraNo, EZConstants.EZPTZAction.EZPTZActionStop, EZConstants.EZPTZCommand.EZPTZCommandStop, 0 ); }, 300); } catch (Exception e) { Log.e("EZPTZ", "云台控制异常", e); } } private void controlZoom(String command) { try { EZConstants.EZPTZAction action = "ZOOM_IN".equals(command) ? EZConstants.EZPTZAction.EZPTZActionZoomIn : EZConstants.EZPTZAction.EZPTZActionZoomOut; // 使用EZOpenSDK的API进行控制 EZOpenSDK.getInstance().controlPTZ( mDeviceSerial, mCameraNo, action, EZConstants.EZPTZCommand.EZPTZCommandStart, 2 ); // 300ms后停止控制 new Handler().postDelayed(() -> { EZOpenSDK.getInstance().controlPTZ( mDeviceSerial, mCameraNo, EZConstants.EZPTZAction.EZPTZActionStop, EZConstants.EZPTZCommand.EZPTZCommandStop, 0 ); }, 300); } catch (Exception e) { Log.e("EZZoom", "变焦控制异常", e); } } // ======================== 萤石云扩展功能 ======================== // 开始本地录像 private void startLocalRecord() { // 这里需要根据实际需求实现 Toast.makeText(this, "开始录像", Toast.LENGTH_SHORT).show(); } // 停止本地录像 private void stopLocalRecord() { // 这里需要根据实际需求实现 Toast.makeText(this, "停止录像", Toast.LENGTH_SHORT).show(); } // 截图 private void capturePicture() { // 这里需要根据实际需求实现 Toast.makeText(this, "截图已保存", Toast.LENGTH_SHORT).show(); } // ======================== 通用功能方法 ======================== public void changeScreen(View view) { changeScreen(); } private void changeScreen() { if (mControlLayout.getVisibility() == View.VISIBLE) { mControlLayout.setVisibility(View.GONE); } else { mControlLayout.setVisibility(View.VISIBLE); } } private void handleError(String message, int errorCode) { String fullMessage = message + " (错误码: " + errorCode + ")"; // 萤石云特有错误码处理 switch (errorCode) { case 400001: fullMessage = "AccessToken无效"; break; case 400002: fullMessage = "设备不存在"; break; case 400007: fullMessage = "设备不在线"; break; case 400034: fullMessage = "验证码错误"; break; case 400035: fullMessage = "设备已被自己添加"; break; case 400036: fullMessage = "设备已被别人添加"; break; default: fullMessage = "萤石云错误: " + errorCode; } new AlertDialog.Builder(this) .setTitle("预览失败") .setMessage(fullMessage) .setPositiveButton("确定", (d, w) -> finish()) .setCancelable(false) .show(); } private void showErrorAndFinish(String message) { Toast.makeText(this, message, Toast.LENGTH_LONG).show(); new Handler().postDelayed(this::finish, 3000); } private void stopPreview() { if (mExoPlayer != null) { mExoPlayer.release(); mExoPlayer = null; Log.d("Preview", "预览已停止"); } } @Override protected void onDestroy() { super.onDestroy(); stopPreview(); Log.d("Lifecycle", "onDestroy"); } @Override protected void onPause() { super.onPause(); if (mExoPlayer != null) { mExoPlayer.setPlayWhenReady(false); Log.d("Lifecycle", "暂停播放"); } } @Override protected void onResume() { super.onResume(); if (mExoPlayer != null) { mExoPlayer.setPlayWhenReady(true); Log.d("Lifecycle", "恢复播放"); } } @Override public void onBackPressed() { DrawerLayout drawer = findViewById(R.id.drawer_layout); if (drawer != null && drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { super.onBackPressed(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main_opt, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.action_1) { startActivity(new Intent(this, AddDevActivity.class)); return true; } return super.onOptionsItemSelected(item); } } 依据上述开发文档中停止云台控制和开始云台控制更新MainActivity代码实现完全符合开发文档的功能。
06-22
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值