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);
}
}
生成能接收音频并且USB插拔记录python脚本
最新发布