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 当服务端去接收,现在是相反
最新发布