package com.example.demoapplication;
import android.Manifest; // 导入录音权限类
import android.content.pm.PackageManager; // 导入包管理器权限类
import android.media.AudioFormat; // 导入音频格式类
import android.media.AudioManager; // 导入音频管理类
import android.media.AudioRecord; // 导入音频录制类
import android.media.MediaPlayer; // 导入媒体播放器类
import android.media.MediaRecorder; // 导入媒体录制器类
import android.os.Bundle; // 导入Android数据包类
import android.os.Handler; // 导入消息处理类
import android.os.Looper; // 导入线程循环类
import android.os.Message; // 导入消息传递类
import android.speech.tts.TextToSpeech; // 导入文本转语音接口
import android.util.Log; // 导入日志工具类
import android.widget.Button; // 导入按钮组件
import android.widget.Toast; // 导入提示框类
import androidx.annotation.NonNull; // 导入非空注解
import androidx.appcompat.app.AppCompatActivity; // 导入AppCompatActivity基类
import androidx.core.app.ActivityCompat; // 导入兼容库活动权限工具类
import androidx.core.content.ContextCompat; // 导入兼容库内容上下文工具类
import org.json.JSONObject; // 导入JSON对象类
import java.io.BufferedReader; // 导入缓冲字符读取流
import java.io.BufferedWriter; // 导入缓冲字符写入流
import java.io.File; // 导入文件操作类
import java.io.FileOutputStream; // 导入文件输出流
import java.io.IOException; // 导入输入输出异常类
import java.io.InputStreamReader; // 导入字符输入流包装器
import java.io.OutputStreamWriter; // 导入字符输出流包装器
import java.net.ServerSocket; // 导入服务器套接字
import java.net.Socket; // 导入网络连接套接字
import java.util.LinkedList; // 导入链表队列结构
import java.util.Locale; // 导入语言环境类
import java.util.Queue; // 导入队列接口
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 static final int PERMISSION_REQUEST_CODE = 1; // 权限请求代码
private static final int SAMPLE_RATE = 16000; // 音频采样率
private static final int BUFFER_SIZE; // 缓冲区大小
static {
// 计算最小缓冲区大小
int minBufferSize = AudioRecord.getMinBufferSize(
SAMPLE_RATE,
AudioFormat.CHANNEL_IN_MONO, // 单声道采集
AudioFormat.ENCODING_PCM_16BIT // 16位PCM编码
);
BUFFER_SIZE = Math.max(minBufferSize, 4096); // 取最大值作为最终缓冲区大小
}
// UI组件声明
private Button startRecordButton, stopRecordButton; // 录音控制按钮
private Button playSoundButton, pauseSoundButton, stopSoundButton, resumeSoundButton, clearSoundsButton; // 播放控制按钮
// 音频录制相关变量
private AudioRecord audioRecord; // 音频录制对象
private ScheduledExecutorService scheduler; // 定时任务调度器
private AtomicBoolean isRecording = new AtomicBoolean(false); // 是否正在录音标志
private final Queue<byte[]> recordingQueue = new LinkedList<>(); // 录制数据队列
// 网络通信相关变量
private final ExecutorService executorService = Executors.newCachedThreadPool(); // 弹性线程池
private ServerSocket serverSocket; // 服务器端监听套接字
private volatile boolean isServerRunning = true; // 服务器运行状态标志
private volatile Socket clientSocket; // 客户端连接套接字
private volatile BufferedWriter socketWriter; // 套接字写入器
// 文本转语音相关变量
private TextToSpeech ttsEngine; // TTS引擎实例
private boolean isTtsInitialized = false; // TTS初始化状态标志
// MP3播放相关变量
private MediaPlayer mediaPlayer; // 媒体播放器实例
private final Queue<byte[]> mp3PlaybackQueue = new LinkedList<>(); // 播放队列
private final AtomicBoolean isPlaying = new AtomicBoolean(false); // 是否正在播放
private final AtomicBoolean isPaused = new AtomicBoolean(false); // 是否暂停
private volatile boolean isPlaybackThreadActive = false; // 播放线程活跃状态
// 同步锁对象
private final Object playbackQueueLock = new Object(); // 播放队列锁
private final Object recordingQueueLock = new Object(); // 录制队列锁
private final Object mediaPlayerLock = new Object(); // 媒体播放器锁
// 主线程消息处理器
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();
sendJsonPacket("startRecorder", null);
playTts("开始录音");
break;
case 0x14: // 停止录音事件
Toast.makeText(MainActivity.this, "停止录音", Toast.LENGTH_SHORT).show();
sendJsonPacket("stopRecorder", null);
playTts("停止录音");
break;
case 0x16: // 错误消息事件
Toast.makeText(MainActivity.this, "错误: " + msg.obj, Toast.LENGTH_LONG).show();
break;
case 0x17: // 播放完成事件
Toast.makeText(MainActivity.this, "播放完成", Toast.LENGTH_SHORT).show();
isPlaying.set(false);
isPlaybackThreadActive = false;
updatePlayButtonsState();
break;
case 0x18: // 添加到播放队列
Toast.makeText(MainActivity.this, "已添加到播放队列", Toast.LENGTH_SHORT).show();
break;
case 0x19: // 更新播放按钮状态
updatePlayButtonsState();
break;
case 0x20: // 开始播放MP3
Toast.makeText(MainActivity.this, "开始播放MP3", Toast.LENGTH_SHORT).show();
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // 设置界面布局
// 初始化文本转语音功能
ttsEngine = new TextToSpeech(this, this);
// 初始化视图组件
initViews();
setupClickListeners(); // 绑定点击事件
// 检查必要权限
checkPermissions();
// 启动服务器并开始监听
startServer(30000);
startSocketListener();
}
private void initViews() {
// 绑定UI控件
startRecordButton = findViewById(R.id.startRecordButton);
stopRecordButton = findViewById(R.id.stopRecordButton);
playSoundButton = findViewById(R.id.playSoundButton);
pauseSoundButton = findViewById(R.id.pauseSoundButton);
stopSoundButton = findViewById(R.id.stopSoundButton);
resumeSoundButton = findViewById(R.id.resumeSoundButton);
clearSoundsButton = findViewById(R.id.clearSoundsButton);
// 设置初始按钮状态
stopRecordButton.setEnabled(false);
pauseSoundButton.setEnabled(false);
stopSoundButton.setEnabled(false);
resumeSoundButton.setEnabled(false);
}
private void setupClickListeners() {
// 设置录音按钮点击事件
startRecordButton.setOnClickListener(v -> startRecording());
stopRecordButton.setOnClickListener(v -> stopRecording());
// 设置播放控制按钮点击事件
playSoundButton.setOnClickListener(v -> playNextInQueue());
pauseSoundButton.setOnClickListener(v -> pausePlayback());
stopSoundButton.setOnClickListener(v -> stopPlayback());
resumeSoundButton.setOnClickListener(v -> resumePlayback());
clearSoundsButton.setOnClickListener(v -> {
clearPlaybackQueue(); // 清空播放队列
handler.sendEmptyMessage(0x19); // 发送更新消息
});
}
// ==================== 录音功能模块 ====================
private void startRecording() {
// 检查录音权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
sendErrorMessage("没有录音权限");
return;
}
// 如果已经在录音则先释放资源
if (isRecording.get()) {
releaseAudioResources();
}
try {
// 创建音频录制对象
audioRecord = new AudioRecord(
MediaRecorder.AudioSource.MIC, // 使用麦克风输入
SAMPLE_RATE, // 采样率
AudioFormat.CHANNEL_IN_MONO, // 单声道
AudioFormat.ENCODING_PCM_16BIT, // 16位PCM编码
BUFFER_SIZE // 缓冲区大小
);
// 检查初始化状态
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
throw new IllegalStateException("AudioRecord 初始化失败");
}
// 开始录音
audioRecord.startRecording();
isRecording.set(true);
startRecordButton.setEnabled(false);
stopRecordButton.setEnabled(true);
updatePlayButtonsState(); // 更新播放按钮状态
// 关闭旧的调度器
if (scheduler != null && !scheduler.isShutdown()) {
scheduler.shutdownNow();
}
// 创建新的单线程调度器
scheduler = Executors.newSingleThreadScheduledExecutor();
// 定期采集音频数据(每100ms)
scheduler.scheduleAtFixedRate(this::captureAudioData, 0, 100, TimeUnit.MILLISECONDS);
handler.sendEmptyMessage(0x12); // 发送开始录音通知
} 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);
startRecordButton.setEnabled(true);
updatePlayButtonsState(); // 更新播放按钮状态
handler.sendEmptyMessage(0x14); // 发送停止录音通知
}
private void captureAudioData() {
if (!isRecording.get() || audioRecord == null) return;
byte[] buffer = new byte[BUFFER_SIZE]; // 创建缓冲区
try {
// 从音频设备读取数据
int bytesRead = audioRecord.read(buffer, 0, BUFFER_SIZE);
if (bytesRead > 0) {
synchronized (recordingQueueLock) {
recordingQueue.offer(buffer.clone()); // 存储音频数据
}
sendJsonPacket("recording", buffer); // 发送数据包
}
} catch (Exception e) {
Log.e(TAG, "音频采集失败", e);
}
}
// ==================== MP3播放功能模块 ====================
private void addMp3ToQueue(byte[] mp3Data) {
synchronized (playbackQueueLock) {
mp3PlaybackQueue.offer(mp3Data); // 将MP3数据加入播放队列
}
handler.sendEmptyMessage(0x18); // 发送加入队列通知
// 如果没有播放且线程不活跃,开始播放
if (!isPlaying.get() && !isPlaybackThreadActive) {
playNextInQueue();
}
}
private void playNextInQueue() {
if (isPlaybackThreadActive || mp3PlaybackQueue.isEmpty()) return;
isPlaybackThreadActive = true;
executorService.execute(() -> { // 在线程池中执行
try {
while (!mp3PlaybackQueue.isEmpty() && !isPaused.get()) {
byte[] mp3Data;
synchronized (playbackQueueLock) {
mp3Data = mp3PlaybackQueue.poll(); // 取出队列中的MP3数据
}
if (mp3Data != null) {
playMp3Data(mp3Data); // 播放MP3数据
}
}
if (!isPaused.get()) {
isPlaying.set(false);
isPlaybackThreadActive = false;
handler.sendEmptyMessage(0x17); // 发送播放完成通知
}
} catch (Exception e) {
Log.e(TAG, "MP3播放失败", e);
sendErrorMessage("MP3播放失败: " + e.getMessage());
}
});
}
private void playMp3Data(final byte[] mp3Data) {
try {
// 创建临时文件
File tempFile = File.createTempFile("audio_", ".mp3", getCacheDir());
try (FileOutputStream fos = new FileOutputStream(tempFile)) {
fos.write(mp3Data); // 写入文件
}
runOnUiThread(() -> {
synchronized (mediaPlayerLock) {
try {
releaseMediaPlayer(); // 释放之前的播放器资源
mediaPlayer = new MediaPlayer(); // 创建新播放器
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); // 设置音频流类型
mediaPlayer.setDataSource(tempFile.getAbsolutePath()); // 设置数据源
// 设置准备监听器
mediaPlayer.setOnPreparedListener(mp -> {
mediaPlayer.start(); // 开始播放
isPlaying.set(true); // 标记为正在播放
isPaused.set(false); // 标记为未暂停
handler.sendEmptyMessage(0x20); // 发送开始播放通知
updatePlayButtonsState(); // 更新按钮状态
});
// 设置完成监听器
mediaPlayer.setOnCompletionListener(mp -> {
// 删除临时文件
tempFile.delete();
// 播放下一个文件
if (!playNextInQueueImmediately()) {
isPlaying.set(false);
isPlaybackThreadActive = false;
handler.sendEmptyMessage(0x17);
}
});
// 设置错误监听器
mediaPlayer.setOnErrorListener((mp, what, extra) -> {
Log.e(TAG, "MediaPlayer error: " + what + ", " + extra);
sendErrorMessage("播放错误: " + what);
tempFile.delete(); // 删除临时文件
return false;
});
mediaPlayer.prepareAsync(); // 准备播放
} catch (IOException e) {
Log.e(TAG, "MediaPlayer初始化失败", e);
tempFile.delete(); // 删除临时文件
}
}
});
} catch (IOException e) {
Log.e(TAG, "创建临时文件失败", e);
}
}
private boolean playNextInQueueImmediately() {
if (!mp3PlaybackQueue.isEmpty()) {
byte[] nextData;
synchronized (playbackQueueLock) {
nextData = mp3PlaybackQueue.poll(); // 取出下一个播放项
}
if (nextData != null) {
playMp3Data(nextData); // 直接播放
return true;
}
}
return false;
}
// ==================== 播放控制 ====================
private void pausePlayback() {
synchronized (mediaPlayerLock) {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause(); // 暂停播放
isPaused.set(true); // 设置暂停标志
updatePlayButtonsState(); // 更新按钮状态
}
}
}
private void resumePlayback() {
synchronized (mediaPlayerLock) {
if (mediaPlayer != null && isPaused.get()) {
mediaPlayer.start(); // 恢复播放
isPaused.set(false); // 取消暂停标志
updatePlayButtonsState(); // 更新按钮状态
}
}
}
private void stopPlayback() {
synchronized (mediaPlayerLock) {
if (mediaPlayer != null) {
try {
mediaPlayer.stop(); // 停止播放
} catch (IllegalStateException e) {
Log.e(TAG, "停止播放失败", e);
}
}
}
clearPlaybackQueue(); // 清空播放队列
isPlaying.set(false); // 设置未播放状态
isPaused.set(false); // 取消暂停状态
updatePlayButtonsState(); // 更新按钮状态
}
private void clearPlaybackQueue() {
synchronized (playbackQueueLock) {
mp3PlaybackQueue.clear(); // 清空播放队列
}
}
private void releaseMediaPlayer() {
synchronized (mediaPlayerLock) {
if (mediaPlayer != null) {
try {
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop(); // 停止播放
}
mediaPlayer.release(); // 释放资源
} catch (Exception e) {
Log.e(TAG, "释放MediaPlayer失败", e);
} finally {
mediaPlayer = null; // 清空引用
}
}
}
}
// ==================== 辅助方法 ====================
private void updatePlayButtonsState() {
runOnUiThread(() -> {
boolean hasRecordings = !mp3PlaybackQueue.isEmpty(); // 是否有录音
boolean isPlayingState = isPlaying.get() && !isPaused.get(); // 是否正在播放
boolean isPausedState = isPaused.get(); // 是否暂停
// 设置按钮启用状态
playSoundButton.setEnabled(hasRecordings && !isPlayingState);
pauseSoundButton.setEnabled(isPlayingState);
stopSoundButton.setEnabled(isPlaying.get() || isPausedState);
resumeSoundButton.setEnabled(isPausedState);
clearSoundsButton.setEnabled(hasRecordings);
});
}
private void playTts(String text) {
if (isTtsInitialized) {
ttsEngine.speak(text, TextToSpeech.QUEUE_FLUSH, null); // 播放TTS
}
}
private void releaseAudioResources() {
if (audioRecord != null) {
try {
// 如果正在录音则停止
if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
audioRecord.stop();
}
audioRecord.release(); // 释放资源
} catch (IllegalStateException e) {
Log.e(TAG, "停止录音失败", e);
} finally {
audioRecord = null; // 清除引用
}
}
if (scheduler != null) {
try {
scheduler.shutdownNow(); // 关闭调度器
// 等待关闭完成
if (!scheduler.awaitTermination(500, TimeUnit.MILLISECONDS)) {
Log.w(TAG, "录音线程池未正常关闭");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重新设置中断标志
} finally {
scheduler = null; // 清除引用
}
}
}
private void sendJsonPacket(String type, Object data) {
if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) {
return; // 如果连接无效则返回
}
try {
JSONObject packet = new JSONObject(); // 创建JSON包
packet.put("type", type); // 设置类型
if (data != null) {
packet.put("data", data); // 添加数据
}
synchronized (this) {
if (socketWriter != null) {
socketWriter.write(packet.toString()); // 写入JSON字符串
socketWriter.write("\n\n"); // 添加分隔符
socketWriter.flush(); // 刷新缓冲区
}
}
} catch (Exception e) {
Log.e(TAG, "发送数据包失败: " + type, e);
}
}
private void sendErrorMessage(String message) {
handler.obtainMessage(0x16, message).sendToTarget(); // 发送错误消息
}
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
);
}
// 检查存储权限(用于临时文件)
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSION_REQUEST_CODE + 1
);
}
}
// ==================== 网络通信 ====================
private void startServer(int port) {
executorService.execute(() -> { // 在线程池中执行
try {
serverSocket = new ServerSocket(port); // 创建服务器套接字
Log.i(TAG, "服务器启动: " + port); // 打印日志
while (isServerRunning) { // 服务器运行循环
try {
Socket socket = serverSocket.accept(); // 接受连接
clientSocket = socket; // 保存客户端套接字
synchronized (this) {
// 创建套接字写入器
socketWriter = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF-8")
);
}
handler.sendEmptyMessage(0x11); // 发送连接成功通知
} catch (IOException e) {
if (isServerRunning) Log.e(TAG, "接受连接失败", e);
}
}
} catch (IOException e) {
Log.e(TAG, "服务器启动失败", e);
runOnUiThread(() -> Toast.makeText(
this,
"服务器启动失败: " + e.getMessage(),
Toast.LENGTH_LONG
).show());
} finally {
closeServerSocket(); // 关闭服务器套接字
}
});
}
private void startSocketListener() {
executorService.execute(() -> { // 在线程池中执行
while (isServerRunning) {
if (clientSocket != null && !clientSocket.isClosed()) {
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream(), "UTF-8")
); // 创建输入流读取器
// 先读取数据长度
String lengthLine = reader.readLine();
if (lengthLine == null) break;
int dataLength = Integer.parseInt(lengthLine.trim()); // 解析数据长度
byte[] mp3Data = new byte[dataLength]; // 创建数据缓冲区
// 读取二进制数据
int bytesRead = 0;
while (bytesRead < dataLength) {
int count = clientSocket.getInputStream().read(
mp3Data, bytesRead, dataLength - bytesRead);
if (count == -1) break;
bytesRead += count;
}
if (bytesRead == dataLength) {
addMp3ToQueue(mp3Data); // 添加到播放队列
} else {
Log.w(TAG, "数据不完整: " + bytesRead + "/" + dataLength);
}
} catch (IOException | NumberFormatException e) {
Log.e(TAG, "Socket读取失败", e);
closeSocket(clientSocket); // 关闭套接字
}
} else {
try {
Thread.sleep(500); // 等待500毫秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重新设置中断标志
break;
}
}
}
});
}
private void closeServerSocket() {
try {
if (serverSocket != null && !serverSocket.isClosed()) {
serverSocket.close(); // 关闭服务器套接字
}
} catch (IOException e) {
Log.w(TAG, "关闭ServerSocket失败", e);
}
}
private void closeSocket(Socket socket) {
try {
if (socket != null && !socket.isClosed()) {
socket.close(); // 关闭套接字
}
} catch (IOException e) {
Log.w(TAG, "关闭Socket失败", e);
}
if (socket == clientSocket) {
clientSocket = null; // 清除客户端套接字引用
synchronized (this) {
socketWriter = null; // 清除写入器引用
}
}
}
// ==================== 文本转语音 ====================
@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; // 设置初始化完成标志
}
}
}
// ==================== 生命周期 ====================
@Override
protected void onDestroy() {
super.onDestroy();
isServerRunning = false; // 停止服务器
// 释放TTS资源
if (ttsEngine != null) {
ttsEngine.stop();
ttsEngine.shutdown();
}
// 释放网络资源
closeServerSocket();
closeSocket(clientSocket);
// 释放音频资源
stopRecording();
releaseMediaPlayer();
// 关闭线程池
executorService.shutdown();
try {
// 等待关闭完成
if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
executorService.shutdownNow(); // 强制关闭
}
} catch (InterruptedException e) {
executorService.shutdownNow(); // 强制关闭
Thread.currentThread().interrupt(); // 重新设置中断标志
}
}
}
我这个安卓端口是多少