【JAVA】使用百度语音识别 Rest API,遇到识别结果显示乱码的问题和解决


遇到乱码问题

在使用百度语音识别 JAVA Rest API 的时候,把应用部署到外部Tomcat,
发现返回的语音识别结果是乱码,而在IDEA上测试API,返回的结果正常。


百度语音识别 Rest API

短语音识别 简介
GitHub地址

处理、上传文件,获取识别结果。过程:

com/baidu/speech/restapi/asrdemo/ AsrMain.java

public static void main(String[] args) throws IOException, DemoException {
        AsrMain demo = new AsrMain();
        String result = demo.run();
        // 打印识别结果result
        log.info("识别结束:结果是:");
        log.info(result);
    }

public String run() throws IOException, DemoException {
		...
       	// 以Json方式上传文件,获取返回的字符串
            result = runJsonPostMethod(token);
        ...
        return result;
    }

// 默认以Json方式上传文件
// 注:本文作者添加:传入了参数 WAV文件路径+文件名
private String runJsonPostMethod(String token, String fileName) throws IOException, DemoException {         
	...
    byte[] content = getFileContent(fileName);
    String speech = base64Encode(content);
    ...
    // 从HttpURLConnection 获取返回的字符串
	String result = ConnUtil.getResponseString(conn);
	...
	return result;
}

// 将文件读取到FileInputStream,作为bytes返回
private byte[] getFileContent(String fileName) throws IOException{
    File file = new File(fileName);
    if (!file.canRead()) {
        log.error("文件不存在或者不可读: " + file.getAbsolutePath());
    }
    try(FileInputStream fileInputStream = new FileInputStream(file)) {
        // 将InputStream内的内容全部读取,作为bytes返回
        return ConnUtil.getInputStreamContent(fileInputStream);
    } catch (Exception e) {
        log.error("读取文件到输入流过程错误:" + e.getMessage(), e);
        return null;
    }
}

com/baidu/speech/restapi/common/ ConnUtil.java:

    // 从HttpURLConnection 获取返回的字符串
    public static String getResponseString(HttpURLConnection conn) throws IOException {
        // 从连接信息返回的内容
        return new String(getResponseBytes(conn));
    }

	// 从HttpURLConnection 获取返回的 bytes
    public static byte[] getResponseBytes(HttpURLConnection conn) throws IOException, DemoException {
        int responseCode = conn.getResponseCode();
        InputStream inputStream = conn.getInputStream();
        ...
        byte[] result = getInputStreamContent(inputStream);
        return result;
    }

    // 将InputStream内的内容全部读取,作为bytes返回
    public static byte[] getInputStreamContent(InputStream is) throws IOException {
        byte[] b = new byte[1024];
        // 定义一个输出流存储接收到的数据
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        // 开始接收数据
        int len = 0;
        while (true) {
            len = is.read(b);
            if (len == -1) {
                // 数据读完
                break;
            }
            byteArrayOutputStream.write(b, 0, len);
        }
        return byteArrayOutputStream.toByteArray();
    }

Rest API 的使用

String myApiKey = "myApiKey";
String mySecretKey = "mySecretKey";
String fileName = "D:/test.wav";
// 获得token: TokenHolder为API的 common 包中的类
TokenHolder holder =
        new TokenHolder(myApiKey, mySecretKey, "audio_voice_assistant_get");
holder.resfresh();
String token = holder.getToken();
// 默认以json方式上传音频文件
String result = runJsonPostMethod(token, fileName);

// 解析返回的json字符串
JsonParser jsonParser = new JsonParser();
// 将json字符串转化成json对象
JsonObject jsonObject = jsonParser.parse(result).getAsJsonObject();

// 返回结果中的错误码。若错误码为0,则识别成功。
String stringErrorNumber = jsonObject.get("err_no").getAsString();
// 返回结果中的语音识别文字段落
String stringErrorMessage = jsonObject.get("err_msg").getAsString();

int errorNumber;
if(stringErrorNumber != null) {
	errorNumber = Integer.valueOf(stringErrorNumber);
} else {
	errorNumber = 0;
}
// 根据错误码,进行简单分类
switch (errorNumber) {
	case 0:     // 识别成功
	    log.info("识别结束,结果是:" + jsonObject.get("result").getAsString());
	    break;
	case 3307:  // recognition error 识别出错,无法识别
		// TO-DO
	    break;
	case 3308:  // speech too long 音频文件时长过
	    // TO-DO 
	    break;
	default:	// 识别不成功
	    log.error("百度ASR结果错误:" + wavFullName
	        + ",错误代码:" + errorNumber
	        + ",错误信息:" + stringErrorMessage);
}

乱码现象和解决过程

1. 乱码现象

用1个时长不到60s的,符合百度短语音识别 REST API 格式的,WAV文件,用IDEA测试,结果:IDEA测试
可见,result 里边显示的是正常的中文识别结果。

打包应用,部署到外部Tomcat,运行测试,也识别成功,日志显示却是乱码:
识别结果乱码

2. 解决过程

尝试修改Tomcat编码,改 UTF-8,或者改GBK,都无效;
解决各种tomcat中文乱码问题
修复tomcat9.0中文乱码问题
tomcat配置及中文乱码问题的解决方案

在JAVA代码中,用 String(str.getBytes(“GBK”), “UTF-8”); 也无效:
知乎:java中GBK编码格式转成UTF8,用一段方法实现怎么做?

public static String recover(String str) throws Throwable {
	return new String(str.getBytes("GBK"), "UTF-8");
}

使用后,依旧乱码:

String jsonBack = new String(jsonBack.getBytes("GBK"), StandardCharsets.UTF_8);
log.info("ASR结果: " + jsonBack);

然后,某天在网上看到这两个图:
常见乱码问题和产生原因
聊天记录
搜了一下,发现相关文章:
[转载]字符乱码说明

看上去,本文遇上的是第一种情况:以GBK方式读取UTF-8编码的中文,百度API返回的结果就是UTF-8格式,这应该不用怀疑的,在IDEA内部Tomcat的UTF-8环境下测试都显示正常。但尝试过修改了外部Tomcat的编码,没用。干脆从JAVA代码切入,看看能不能修改一点代码就能解决。

在获取识别的返回结果中,寻找能够设置字符串String编码格式的地方。从最底层的
getResponseBytes(HttpURLConnection conn) 和 getInputStreamContent(InputStream is) 看起,两个函数中没发现字符串String。

继续往上,发现 getResponseString(HttpURLConnection conn) 里边有 new String:

// 从HttpURLConnection 获取返回的字符串
public static String getResponseString(HttpURLConnection conn) throws IOException {
    // 从连接信息返回的内容
    return new String(getResponseBytes(conn));
}

于是,直接尝试加入UTF-8编码格式:

public static String getResponseString(HttpURLConnection conn) throws IOException {
    // 从连接信息返回的内容,先用UTF-8解码
    return new String(getResponseBytes(conn), StandardCharsets.UTF_8);
}

部署到外部Tomcat测试,结果显示正常!!!

成功

// 定义包名,对应项目结构中的包路径 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; // 用于保存恢复Activity状态 import android.os.Handler; // 用于线程间通信 import android.os.Looper; // 线程消息循环 import android.os.Message; // 消息对象 // 导入语音识别与文本语音相关类 import android.speech.tts.TextToSpeech; // 文本语音功能 // 导入数据换与编码工具类 import android.util.Base64; // Base64编码解码 import android.util.Log; // 日志记录 import android.widget.Button; // 按钮控件 import android.widget.Toast; // 短时提示信息 // 导入AndroidX注解支持 import androidx.annotation.NonNull; // 表示非空注解 // 导入权限兼容处理工具类 import androidx.appcompat.app.AppCompatActivity; // 兼容Activity基类 import androidx.core.app.ActivityCompat; // 兼容权限请求 import androidx.core.content.ContextCompat; // 权限状态检查 // 导入JSON解析库 import org.json.JSONException; // JSON异常 import org.json.JSONObject; // JSON对象操作 // 导入IO流相关类 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; // 服务器端Socket import java.net.Socket; // 客户端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; // 原子布尔值 // 主Activity类,继承自AppCompatActivity并实现TTS初始化监听器 public class MainActivity extends AppCompatActivity implements TextToSpeech.OnInitListener { // 日志标签UI组件定义 private static final String TAG = "AudioRecorder"; // 日志标签 private Button startRecordButton, stopRecordButton; // 录音控制按钮 private AudioRecord audioRecord; // 音频录制实例 // 音频采样率缓冲区大小配置 private static final int SAMPLE_RATE = 16000; // 采样率(Hz) private static final int BUFFER_SIZE; // 缓冲区大小 // 静态代码块用于初始化缓冲区大小 static { // 计算最小缓冲区大小并确保不小于4096字节 int 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; // 服务器Socket private volatile boolean isServerRunning = true; // 服务器运行状态 private volatile Socket clientSocket; // 客户端Socket private volatile BufferedWriter socketWriter; // Socket写入器 // 文字语音(TTS)引擎播放器 private TextToSpeech ttsEngine; // TTS引擎实例 private boolean isTtsInitialized = false; // TTS初始化状态 private AudioTrack audioTrack; // 音频播放轨道 // 控制是否已发送开始/结束提示 private boolean isStartMessageSent = false; // 开始消息发送状态 private boolean isStopMessageSent = false; // 停止消息发送状态 private boolean isTransmitStarted = false; // 传输开始状态 private boolean isStopTransmitSent = false; // 停止传输发送状态 // 新增音频播放状态控制字段 private boolean isPaused = false; // 是否处于暂停状态 private byte[] pausedAudioData; // 暂停时的音频数据 private int pausedPosition; // 暂停位置 // TTS音频队列播放状态 private final Queue<byte[]> ttsQueue = new LinkedList<>(); // TTS音频队列 private boolean isTtsPlaying = false; // TTS播放状态 // 主线程消息处理器 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(); isStartMessageSent = true; isStopMessageSent = false; sendControlPacket("startRecorder"); playTts("开始录音"); break; case 0x13: // 数据发送 break; case 0x14: // 停止录音 Toast.makeText(MainActivity.this, "停止录音", Toast.LENGTH_SHORT).show(); isStopMessageSent = true; isStartMessageSent = false; sendControlPacket("stopRecorder"); playTts("停止录音"); break; case 0x15: // 控制指令 Toast.makeText(MainActivity.this, "收到指令: " + msg.obj, Toast.LENGTH_SHORT).show(); break; case 0x16: // 错误 Toast.makeText(MainActivity.this, "错误: " + msg.obj, Toast.LENGTH_LONG).show(); break; case 0x18: // TTS音频 handleTtsAudio((String) msg.obj); break; case 0x19: // 聊天开始 Toast.makeText(MainActivity.this, "聊天开始: " + msg.obj, Toast.LENGTH_SHORT).show(); break; case 0x20: // 聊天回复 Toast.makeText(MainActivity.this, "回复: " + msg.obj, Toast.LENGTH_LONG).show(); break; case 0x21: // 播放完成 Toast.makeText(MainActivity.this, "播放完成", Toast.LENGTH_SHORT).show(); // 播放下一段音频 playNextTtsAudio(); break; case 0x22: // 暂停播放 Toast.makeText(MainActivity.this, "播放已暂停", Toast.LENGTH_SHORT).show(); pauseCurrentPlayback(); break; case 0x23: // 继续播放 Toast.makeText(MainActivity.this, "继续播放", Toast.LENGTH_SHORT).show(); resumeCurrentPlayback(); break; case 0x24: // 清除队列 Toast.makeText(MainActivity.this, "队列已清除", Toast.LENGTH_SHORT).show(); clearTtsQueue(); 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); // 绑定停止录音按钮 stopRecordButton.setEnabled(false); // 默认禁用停止按钮 } private void setupClickListeners() { startRecordButton.setOnClickListener(v -> startRecording()); // 设置开始录音点击事件 stopRecordButton.setOnClickListener(v -> stopRecording()); // 设置停止录音点击事件 } 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()) { releaseAudioResources(); } if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) { sendErrorMessage("客户端未连接"); 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.set(true); startRecordButton.setEnabled(false); stopRecordButton.setEnabled(true); if (scheduler != null) { scheduler.shutdownNow(); } scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(this::uploadAudioData, 0, 100, TimeUnit.MILLISECONDS); handler.sendEmptyMessage(0x12); 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(); stopRecordButton.setEnabled(false); startRecordButton.setEnabled(true); isStopMessageSent = false; isStartMessageSent = false; handler.sendEmptyMessage(0x14); sendControlPacket("stopRecorder"); playTts("停止录音"); } private void playTts(String text) { if (isTtsInitialized) { ttsEngine.speak(text, TextToSpeech.QUEUE_FLUSH, null); Log.i(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) { return; } byte[] buffer = new byte[BUFFER_SIZE]; try { int bytesRead = audioRecord.read(buffer, 0, BUFFER_SIZE); if (bytesRead > 0) { if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING && !isTransmitStarted) { sendControlPacket("startTransmit"); playTts("开始传输"); audioRecord.startRecording(); isTransmitStarted = true; } if (bytesRead < BUFFER_SIZE / 2 && isTransmitStarted) { sendControlPacket("stopTransmit"); playTts("停止传输"); isTransmitStarted = false; } JSONObject json = new JSONObject(); json.put("type", "recording"); json.put("data", Base64.encodeToString(buffer, 0, bytesRead, Base64.NO_WRAP)); synchronized (this) { if (socketWriter != null) { socketWriter.write(json.toString()); socketWriter.write("\n\n"); socketWriter.flush(); } } } } catch (Exception e) { Log.e(TAG, "发送音频数据失败", e); sendErrorMessage("发送音频数据失败: " + e.getMessage()); } } @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; } } } private void sendControlPacket(String type) { if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) { return; } try { JSONObject packet = new JSONObject(); packet.put("type", type); synchronized (this) { if (socketWriter != null) { socketWriter.write(packet.toString()); 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 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); 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(); } }); } 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 && 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); } } finally { closeSocket(socket); } } /** * 处理接收到的JSON数据包 * @param jsonObject 接收到的JSON对象 */ private void handleReceivedPacket(JSONObject jsonObject) { try { // 获取数据包类型字段 String type = jsonObject.getString("type"); // 获取可选的数据字段(可能不存在) Object data = jsonObject.opt("data"); Message msg; // 根据数据包类型进行处理 switch (type) { case "tts_audio": // TTS音频数据 // 创建带数据的消息并发送到主线程 msg = handler.obtainMessage(0x18, data.toString()); handler.sendMessage(msg); break; case "chat_start": // 聊天开始事件 // 解析查询内容并发送消息 msg = handler.obtainMessage(0x19, jsonObject.getJSONObject("data").getString("query")); handler.sendMessage(msg); break; case "chat_reply": // 聊天回复事件 // 解析回复内容并发送消息 msg = handler.obtainMessage(0x20, jsonObject.getJSONObject("data").getString("reply")); handler.sendMessage(msg); break; case "play_complete": // 播放完成通知 // 发送空消息触发下一段播放 handler.sendEmptyMessage(0x21); break; case "playSound": // 播放指定音频 // 添加到播放队列 addTtsAudioToQueue(Base64.decode(data.toString(), Base64.DEFAULT)); break; case "pauseSound": // 暂停播放 handler.sendEmptyMessage(0x22); break; case "resumeSound": // 继续播放 handler.sendEmptyMessage(0x23); break; case "stopSound": // 停止播放 handler.sendEmptyMessage(0x24); break; case "clearSound": // 清除队列 // 清空播放队列 handler.sendEmptyMessage(0x24); break; default: // 其他未知类型 // 发送通用处理消息 msg = handler.obtainMessage(0x15, type + ": " + data); handler.sendMessage(msg); break; } } catch (JSONException e) { // 记录JSON处理异常日志 Log.e(TAG, "处理数据包失败", e); } } /** * 处理TTS音频数据 - 将Base64数据解码后加入播放队列 * @param base64Data Base64编码的PCM音频数据 */ private void handleTtsAudio(String base64Data) { // 解码Base64数据为字节数组 byte[] pcmData = Base64.decode(base64Data, Base64.DEFAULT); // 添加到音频播放队列 addTtsAudioToQueue(pcmData); } /** * 将PCM音频数据添加到播放队列 * @param pcmData 要添加的PCM音频数据 */ private void addTtsAudioToQueue(byte[] pcmData) { synchronized (ttsQueue) { // 如果不是暂停状态,则直接加入队列 if (!isPaused) { // 将音频数据加入队列尾部 ttsQueue.offer(pcmData); // 如果当前没有正在播放,则立即开始播放 if (!isTtsPlaying) { playNextTtsAudio(); } } else { // 替换当前暂停的音频数据 pausedAudioData = pcmData; pausedPosition = 0; } } } /** * 播放队列中的下一段音频 */ private void playNextTtsAudio() { synchronized (ttsQueue) { // 如果队列为空,标记播放结束 if (ttsQueue.isEmpty()) { isTtsPlaying = false; return; } // 取出队列头部的音频数据 byte[] pcmData = ttsQueue.poll(); // 开始播放该段音频 playPcm(pcmData); // 标记为正在播放状态 isTtsPlaying = true; } } /** * 播放PCM格式的音频数据,并设置播放完成监听 * @param pcmData PCM音频数据字节数组 */ private void playPcm(byte[] pcmData) { // 停止任何正在进行的音频播放 stopAudioPlayback(); // 设置音频参数 int sampleRate = 16000; // 采样率 int channelConfig = AudioFormat.CHANNEL_OUT_MONO; // 单声道输出 int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 16位PCM编码 // 计算最小缓冲区大小 int bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat); // 创建音频播放器实例 audioTrack = new AudioTrack( AudioManager.STREAM_MUSIC, // 音频流类型 sampleRate, // 采样率 channelConfig, // 声道配置 audioFormat, // 音频格式 bufferSize, // 缓冲区大小 AudioTrack.MODE_STREAM); // 播放模式 // 设置播放位置监听器 audioTrack.setPlaybackPositionUpdateListener(new AudioTrack.OnPlaybackPositionUpdateListener() { @Override public void onMarkerReached(AudioTrack track) { // 播放完成后发送通知 handler.sendEmptyMessage(0x21); } @Override public void onPeriodicNotification(AudioTrack track) { // 周期性通知,不需要实现 } }); // 计算帧数(单声道,16位,所以每帧2字节) int frameCount = pcmData.length / 2; // 设置标记位置(在最后一帧) audioTrack.setNotificationMarkerPosition(frameCount); audioTrack.play(); audioTrack.write(pcmData, 0, pcmData.length); } // 停止音频播放(添加状态重置) private void stopAudioPlayback() { if (audioTrack != null) { if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { audioTrack.stop(); } // 移除监听器,防止回调使用已释放的对象 audioTrack.setPlaybackPositionUpdateListener(null); audioTrack.release(); audioTrack = null; } // 重置播放状态 synchronized (ttsQueue) { ttsQueue.clear(); isTtsPlaying = false; } } // 暂停当前播放 private void pauseCurrentPlayback() { if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { audioTrack.pause(); isPaused = true; // 保存当前音频数据播放位置 if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) { int position = audioTrack.getPlaybackHeadPosition(); if (position > 0) { pausedPosition = position * 2; // 换为字节位置 if (pausedPosition < audioTrack.getBufferSizeInFrames() * 2) { int remaining = audioTrack.getBufferSizeInFrames() * 2 - pausedPosition; byte[] remainingData = new byte[remaining]; System.arraycopy(pausedAudioData, pausedPosition, remainingData, 0, remaining); pausedAudioData = remainingData; } } } } } // 继续当前播放 private void resumeCurrentPlayback() { if (isPaused && pausedAudioData != null) { // 从暂停位置继续播放 playPcmFromPosition(pausedAudioData, pausedPosition); isPaused = false; } else if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) { audioTrack.play(); } else { // 没有暂停的音频,播放队列中的下一段 playNextTtsAudio(); } } // 从指定位置播放PCM音频数据 private void playPcmFromPosition(byte[] pcmData, int startPosition) { // 停止任何正在进行的音频播放 stopAudioPlayback(); // 设置音频参数 int sampleRate = 16000; // 采样率 int channelConfig = AudioFormat.CHANNEL_OUT_MONO; // 单声道输出 int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 16位PCM编码 // 计算最小缓冲区大小 int bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat); // 创建音频播放器实例 audioTrack = new AudioTrack( AudioManager.STREAM_MUSIC, // 音频流类型 sampleRate, // 采样率 channelConfig, // 声道配置 audioFormat, // 音频格式 bufferSize, // 缓冲区大小 AudioTrack.MODE_STREAM); // 播放模式 // 设置播放位置监听器 audioTrack.setPlaybackPositionUpdateListener(new AudioTrack.OnPlaybackPositionUpdateListener() { @Override public void onMarkerReached(AudioTrack track) { // 播放完成后发送通知 handler.sendEmptyMessage(0x21); } @Override public void onPeriodicNotification(AudioTrack track) { // 周期性通知,不需要实现 } }); // 计算剩余数据长度 int remainingLength = pcmData.length - startPosition; // 创建新数组存储从指定位置开始的数据 byte[] remainingData = new byte[remainingLength]; System.arraycopy(pcmData, startPosition, remainingData, 0, remainingLength); // 计算帧数(单声道,16位,所以每帧2字节) int frameCount = remainingLength / 2; // 设置标记位置(在最后一帧) audioTrack.setNotificationMarkerPosition(frameCount); audioTrack.play(); audioTrack.write(remainingData, 0, remainingLength); } // 清空TTS播放队列 private void clearTtsQueue() { synchronized (ttsQueue) { ttsQueue.clear(); // 停止当前播放并重置状态 if (audioTrack != null) { if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { audioTrack.stop(); } audioTrack.release(); audioTrack = null; } isTtsPlaying = false; isPaused = false; pausedAudioData = null; pausedPosition = 0; } } 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; } } } private void closeServerSocket() { try { if (serverSocket != null && !serverSocket.isClosed()) { serverSocket.close(); } } catch (IOException e) { Log.w(TAG, "关闭ServerSocket失败", e); } } @Override protected void onDestroy() { super.onDestroy(); isServerRunning = false; if (ttsEngine != null) { ttsEngine.stop(); ttsEngine.shutdown(); } closeServerSocket(); closeSocket(clientSocket); executorService.shutdownNow(); releaseAudioResources(); stopAudioPlayback(); } @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(); } } } public boolean isStopTransmitSent() { return isStopTransmitSent; } public void setStopTransmitSent(boolean stopTransmitSent) { isStopTransmitSent = stopTransmitSent; } public boolean isStartMessageSent() { return isStartMessageSent; } public void setStartMessageSent(boolean startMessageSent) { isStartMessageSent = startMessageSent; } public boolean isStopMessageSent() { return isStopMessageSent; } public void setStopMessageSent(boolean stopMessageSent) { isStopMessageSent = stopMessageSent; } } 生成JAVa在ider用的后台接收代码
最新发布
07-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值