package com.example.demoapplication; // 定义包名
// 导入Android系统权限相关类
import android.Manifest;
import android.content.pm.PackageManager;
// 导入音频处理相关的类
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
// 导入Android基础组件和工具类
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;
// 导入Android支持库相关类
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
// 导入JSON处理库
import org.json.JSONException;
import org.json.JSONObject;
// 导入IO和网络相关类
import java.io.BufferedReader;
import java.io.BufferedWriter;
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;
// 主活动类,继承自AppCompatActivity并实现TextToSpeech的初始化监听器
public class MainActivity extends AppCompatActivity implements TextToSpeech.OnInitListener {
private static final String TAG = "AudioRecorder"; // 日志标签
private Button startRecordButton, stopRecordButton; // 录音控制按钮
private Button playSoundButton, pauseSoundButton, stopSoundButton, resumeSoundButton, clearSoundsButton; // 播放控制按钮
private AudioRecord audioRecord; // 音频录制对象
// 音频配置常量
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 // PCM 16位编码
);
BUFFER_SIZE = Math.max(minBufferSize, 4096); // 取最小缓冲区和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; // 服务器Socket
private volatile boolean isServerRunning = true; // 服务器运行状态
private volatile Socket clientSocket; // 客户端Socket
private volatile BufferedWriter socketWriter; // Socket写入器
// TTS和播放控制
private TextToSpeech ttsEngine; // 文语转换引擎
private boolean isTtsInitialized = false; // TTS初始化状态
private AudioTrack audioTrack; // 音频播放轨道
// 录音队列和播放状态
private final Queue<byte[]> recordingQueue = new LinkedList<>(); // 录音数据队列
private final Queue<byte[]> pausedQueue = new LinkedList<>(); // 暂停时的数据队列
private final Queue<byte[]> playbackQueue = new LinkedList<>(); // 播放队列
private boolean isPlaying = false; // 是否正在播放
private boolean isPaused = false; // 是否已暂停
// 主线程Handler用于更新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();
sendControlPacket("startRecorder"); // 发送开始录音控制包
playTts("开始录音"); // 播报语音提示
break;
case 0x14: // 停止录音
Toast.makeText(MainActivity.this, "停止录音", Toast.LENGTH_SHORT).show();
sendControlPacket("stopRecorder"); // 发送停止录音控制包
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 = false; // 更新播放状态
updatePlayButtonsState(); // 更新按钮状态
processNextPlaybackItem(); // 处理下一个播放项
break;
}
}
};
@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); // 启动服务器
}
// 初始化视图组件
private void initViews() {
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 -> addToPlaybackQueue()); // 添加到播放队列点击事件
pauseSoundButton.setOnClickListener(v -> pausePlayback()); // 暂停播放点击事件
stopSoundButton.setOnClickListener(v -> stopPlayback()); // 停止播放点击事件
resumeSoundButton.setOnClickListener(v -> resumePlayback()); // 继续播放点击事件
clearSoundsButton.setOnClickListener(v -> clearAllRecordings()); // 清除录音点击事件
}
// ==================== 录音功能实现 ====================
// 开始录音方法
private void startRecording() {
// 检查录音权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
sendErrorMessage("没有录音权限");
return;
}
// 如果已经在录音,释放资源
if (isRecording.get()) {
releaseAudioResources();
// 确保所有队列都清空后再重新初始化
synchronized (recordingQueue) {
recordingQueue.clear();
}
synchronized (playbackQueue) {
playbackQueue.clear();
}
pausedQueue.clear();
}
try {
// 初始化录音器
audioRecord = new AudioRecord(
MediaRecorder.AudioSource.MIC, // 麦克风作为音频源
SAMPLE_RATE, // 采样率
AudioFormat.CHANNEL_IN_MONO, // 单声道输入
AudioFormat.ENCODING_PCM_16BIT, // PCM 16位编码
BUFFER_SIZE // 缓冲区大小
);
// 检查录音器是否初始化成功
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
throw new IllegalStateException("AudioRecord 初始化失败");
}
// 开始录音
audioRecord.startRecording();
isRecording.set(true); // 更新录音状态
// 清空之前的录音数据(确保同步)
synchronized (recordingQueue) {
recordingQueue.clear();
}
pausedQueue.clear();
// 更新UI状态
startRecordButton.setEnabled(false);
stopRecordButton.setEnabled(true);
updatePlayButtonsState();
// 启动录音数据采集线程
if (scheduler != null) {
scheduler.shutdownNow(); // 关闭之前的调度器
}
scheduler = Executors.newSingleThreadScheduledExecutor(); // 创建新的单线程调度器
scheduler.scheduleAtFixedRate(this::captureAudioData, 0, 100, TimeUnit.MILLISECONDS); // 每100毫秒采集一次音频数据
// 发送开始录音通知
handler.sendEmptyMessage(0x12); // 发送消息给Handler
sendControlPacket("startRecorder"); // 发送开始录音控制包
playTts("开始录音"); // 播报语音提示
} catch (Exception e) {
Log.e(TAG, "录音启动失败", e); // 记录错误日志
sendErrorMessage("录音启动失败: " + e.getMessage()); // 发送错误信息
releaseAudioResources(); // 释放音频资源
}
}
// 停止录音方法
private void stopRecording() {
if (!isRecording.get()) return; // 如果不在录音直接返回
isRecording.set(false); // 更新录音状态
releaseAudioResources(); // 释放音频资源
// 更新UI状态
stopRecordButton.setEnabled(false);
startRecordButton.setEnabled(true);
updatePlayButtonsState();
// 发送停止录音通知
handler.sendEmptyMessage(0x14); // 发送消息给Handler
sendControlPacket("stopRecorder"); // 发送停止录音控制包
playTts("停止录音"); // 播报语音提示
}
// 采集音频数据并保存到队列
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 (recordingQueue) { // 同步操作
recordingQueue.offer(buffer.clone()); // 添加拷贝到队列中
}
}
} catch (Exception e) {
Log.e(TAG, "音频采集失败", e); // 记录错误日志
}
}
// ==================== 录音功能结束 ====================
// ==================== 播放功能实现 ====================
// 添加到播放队列
private void addToPlaybackQueue() {
if (recordingQueue.isEmpty()) { // 如果队列为空
Toast.makeText(this, "没有可播放的录音", Toast.LENGTH_SHORT).show(); // 显示提示
return;
}
// 创建录音数据副本
Queue<byte[]> recordingCopy = new LinkedList<>();
synchronized (recordingQueue) {
for (byte[] data : recordingQueue) {
recordingCopy.offer(data.clone());
}
}
// 添加到播放队列
synchronized (playbackQueue) {
playbackQueue.addAll(recordingCopy);
}
// 如果当前没有播放,立即开始播放
if (!isPlaying && !isPaused) {
executorService.execute(this::playRecordingQueue);
} else {
Toast.makeText(this, "已添加到播放队列", Toast.LENGTH_SHORT).show();
}
}
// 播放录音队列
private void playRecordingQueue() {
isPlaying = true; // 更新播放状态
isPaused = false; // 更新暂停状态
updatePlayButtonsState(); // 更新按钮状态
// 配置音频播放器
int bufferSize = AudioTrack.getMinBufferSize( // 获取最小缓冲区大小
SAMPLE_RATE, // 采样率
AudioFormat.CHANNEL_OUT_MONO, // 单声道输出
AudioFormat.ENCODING_PCM_16BIT // PCM 16位编码
);
// 创建音频播放器
audioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC, // 音乐流类型
SAMPLE_RATE, // 采样率
AudioFormat.CHANNEL_OUT_MONO, // 单声道输出
AudioFormat.ENCODING_PCM_16BIT, // PCM 16位编码
bufferSize, // 缓冲区大小
AudioTrack.MODE_STREAM // 流模式
);
audioTrack.play(); // 开始播放
// 播放队列中的所有录音数据
while (isPlaying && !playbackQueue.isEmpty()) { // 当正在播放且队列不为空时循环
if (isPaused) { // 如果处于暂停状态
// 暂停状态,等待恢复
try {
Thread.sleep(100); // 等待100毫秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 中断线程
}
continue; // 继续循环
}
byte[] audioData; // 音频数据
synchronized (playbackQueue) { // 同步操作
audioData = playbackQueue.poll(); // 从队列取出数据
}
if (audioData != null) { // 如果有数据
if (audioTrack != null) { // 确保audioTrack未被释放
audioTrack.write(audioData, 0, audioData.length); // 写入音频数据
}
}
}
// 播放完成
stopPlayback(); // 停止播放
handler.sendEmptyMessage(0x17); // 发送播放完成消息
}
// 处理下一个播放项
private void processNextPlaybackItem() {
if (!playbackQueue.isEmpty()) {
executorService.execute(this::playRecordingQueue);
}
}
// 暂停播放
private void pausePlayback() {
if (!isPlaying || isPaused) return; // 如果不在播放或已经暂停直接返回
isPaused = true; // 更新暂停状态
// 保存当前播放位置
synchronized (playbackQueue) { // 同步操作
pausedQueue.clear(); // 清空暂停队列
pausedQueue.addAll(playbackQueue); // 将播放队列添加到暂停队列
playbackQueue.clear(); // 清空播放队列
}
updatePlayButtonsState(); // 更新按钮状态
Toast.makeText(this, "播放已暂停", Toast.LENGTH_SHORT).show(); // 显示提示
}
// 继续播放
private void resumePlayback() {
if (!isPlaying || !isPaused) return; // 如果不在播放或未暂停直接返回
isPaused = false; // 更新暂停状态
// 恢复播放位置
synchronized (playbackQueue) { // 同步操作
playbackQueue.clear(); // 清空播放队列
playbackQueue.addAll(pausedQueue); // 将暂停队列添加到播放队列
pausedQueue.clear(); // 清空暂停队列
}
updatePlayButtonsState(); // 更新按钮状态
Toast.makeText(this, "继续播放", Toast.LENGTH_SHORT).show(); // 显示提示
// 启动播放线程
executorService.execute(this::playRecordingQueue); // 在线程池中执行播放任务
}
// 停止播放
private void stopPlayback() {
isPlaying = false; // 更新播放状态
isPaused = false; // 更新暂停状态
if (audioTrack != null) { // 如果音频轨道存在
try {
if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { // 如果正在播放
audioTrack.stop(); // 停止播放
}
audioTrack.release(); // 释放资源
} catch (Exception e) {
Log.e(TAG, "释放AudioTrack失败", e); // 记录错误日志
}
audioTrack = null; // 置空引用
}
synchronized (playbackQueue) {
playbackQueue.clear(); // 清空播放队列
pausedQueue.clear(); // 清空暂停队列
}
updatePlayButtonsState(); // 更新按钮状态
Toast.makeText(this, "播放已停止", Toast.LENGTH_SHORT).show(); // 显示提示
}
// 清除所有录音
private void clearAllRecordings() {
stopPlayback(); // 停止播放
synchronized (recordingQueue) { // 同步操作
recordingQueue.clear(); // 清空录音队列
pausedQueue.clear(); // 清空暂停队列
}
synchronized (playbackQueue) {
playbackQueue.clear(); // 清空播放队列
}
updatePlayButtonsState(); // 更新按钮状态
Toast.makeText(this, "所有录音已清除", Toast.LENGTH_SHORT).show(); // 显示提示
}
// ==================== 播放功能结束 ====================
// ==================== 辅助方法 ====================
// 更新播放按钮状态
private void updatePlayButtonsState() {
runOnUiThread(() -> { // 在主线程执行
boolean hasRecordings = !recordingQueue.isEmpty() || !pausedQueue.isEmpty(); // 是否有录音
boolean isPlayingState = isPlaying && !isPaused; // 是否正在播放
playSoundButton.setEnabled(hasRecordings && !isPlayingState); // 设置播放按钮状态
pauseSoundButton.setEnabled(isPlayingState); // 设置暂停按钮状态
stopSoundButton.setEnabled(isPlaying); // 设置停止按钮状态
resumeSoundButton.setEnabled(isPlaying && isPaused); // 设置继续按钮状态
clearSoundsButton.setEnabled(hasRecordings); // 设置清除按钮状态
});
}
// 播放TTS语音
private void playTts(String text) {
if (isTtsInitialized) { // 如果TTS已初始化
ttsEngine.speak(text, TextToSpeech.QUEUE_FLUSH, null); // 播报文本
}
}
// 释放音频资源
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 sendControlPacket(String type) {
if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) { // 如果连接无效
return; // 直接返回
}
try {
JSONObject packet = new JSONObject(); // 创建JSON对象
packet.put("type", type); // 添加类型字段
synchronized (this) { // 同步操作
if (socketWriter != null) { // 如果写入器存在
socketWriter.write(packet.toString()); // 写入JSON字符串
socketWriter.write("\n\n"); // 写入两个换行符
socketWriter.flush(); // 刷新缓冲区
}
}
} catch (Exception e) {
Log.e(TAG, "发送控制指令失败", 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); // 请求码
}
}
// 启动服务器
private void startServer(int port) {
executorService.execute(() -> { // 在线程池中执行
try {
serverSocket = new ServerSocket(port); // 创建服务器Socket
Log.i(TAG, "服务器启动: " + port); // 记录日志
while (isServerRunning) { // 当服务器运行时循环
try {
Socket socket = serverSocket.accept(); // 接受连接
clientSocket = socket; // 保存客户端Socket
synchronized (this) { // 同步操作
socketWriter = new BufferedWriter( // 创建缓冲写入器
new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); // 使用UTF-8编码
}
handler.sendEmptyMessage(0x11); // 发送客户端连接消息
executorService.execute(() -> startCommunication(socket)); // 启动通信线程
} 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(); // 关闭服务器Socket
}
});
}
// 关闭服务器Socket(空实现)
private void closeServerSocket() {
}
// 开始通信(空实现)
private void startCommunication(Socket socket) {
}
// 其他网络通信相关方法保持不变...
// 为了简洁省略了部分网络通信代码,实际使用时需要完整实现
// TTS初始化回调
@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; // 停止服务器
if (ttsEngine != null) { // 如果TTS引擎存在
ttsEngine.stop(); // 停止TTS
ttsEngine.shutdown(); // 关闭TTS
}
closeServerSocket(); // 关闭服务器Socket
closeSocket(clientSocket); // 关闭客户端Socket
executorService.shutdownNow(); // 关闭线程池
releaseAudioResources(); // 释放音频资源
stopPlayback(); // 停止播放
}
// 关闭Socket(空实现)
private void closeSocket(Socket clientSocket) {
}
}
解决播放完录音app就会闪退
最新发布