package com.yeyupiaoling.androidclient; import android.Manifest; import android.annotation.SuppressLint; import android.content.pm.PackageManager; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.Bundle; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.Button; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import javax.net.ssl.HostnameVerifier; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.WebSocket; import okhttp3.WebSocketListener; import okio.ByteString; public class MainActivity extends AppCompatActivity { public static final String TAG = MainActivity.class.getSimpleName(); // WebSocket地址,如果服务端没有使用SSL,请使用ws:// public static final String ASR_HOST = "wss://192.168.0.1:10095"; // 采样率 public static final int SAMPLE_RATE = 16000; // 声道数 public static final int CHANNEL = AudioFormat.CHANNEL_IN_MONO; // 返回的音频数据的格式 public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; private AudioRecord audioRecord; private boolean isRecording = false; private int minBufferSize; private AudioView audioView; private String allAsrText = ""; private String asrText = ""; // 控件 private Button recordBtn; private TextView resultText; private WebSocket webSocket; @SuppressLint("ClickableViewAccessibility") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 请求权限 if (!hasPermission()) { requestPermission(); } // 录音参数 minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL, AUDIO_FORMAT); // 显示识别结果控件 resultText = findViewById(R.id.result_text); // 显示录音状态控件 audioView = findViewById(R.id.audioView); audioView.setStyle(AudioView.ShowStyle.STYLE_HOLLOW_LUMP, AudioView.ShowStyle.STYLE_NOTHING); // 按下识别按钮 recordBtn = findViewById(R.id.record_button); recordBtn.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_UP) { isRecording = false; stopRecording(); recordBtn.setText("按下录音"); } else if (event.getAction() == MotionEvent.ACTION_DOWN) { if (webSocket != null){ webSocket.cancel(); webSocket = null; } allAsrText = ""; asrText = ""; isRecording = true; startRecording(); recordBtn.setText("录音中..."); } return true; }); } // 开始录音 private void startRecording() { // 准备录音器 try { // 确保有权限 if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { requestPermission(); return; } // 创建录音器 audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, CHANNEL, AUDIO_FORMAT, minBufferSize); } catch (IllegalStateException e) { e.printStackTrace(); } // 开启一个线程将录音数据写入文件 Thread recordingAudioThread = new Thread(() -> { try { setAudioData(); } catch (Exception e) { e.printStackTrace(); } }); recordingAudioThread.start(); // 启动录音器 audioRecord.startRecording(); audioView.setVisibility(View.VISIBLE); } // 停止录音器 private void stopRecording() { audioRecord.stop(); audioRecord.release(); audioRecord = null; audioView.setVisibility(View.GONE); } // 读取录音数据 private void setAudioData() throws Exception { // 如果使用正常的wss,可以去掉这个 HostnameVerifier hostnameVerifier = (hostname, session) -> { // 总是返回true,表示不验证域名 return true; }; // 建立WebSocket连接 OkHttpClient client = new OkHttpClient.Builder() .hostnameVerifier(hostnameVerifier) .build(); Request request = new Request.Builder() .url(ASR_HOST) .build(); webSocket = client.newWebSocket(request, new WebSocketListener() { @Override public void onOpen(@NonNull WebSocket webSocket, @NonNull Response response) { // 连接成功时的处理 Log.d(TAG, "WebSocket连接成功"); } @Override public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) { // 接收到消息时的处理 Log.d(TAG, "WebSocket接收到消息: " + text); try { JSONObject jsonObject = new JSONObject(text); String t = jsonObject.getString("text"); boolean isFinal = jsonObject.getBoolean("is_final"); if (!t.equals("")) { // 拼接识别结果 String mode = jsonObject.getString("mode"); if (mode.equals("2pass-offline")) { asrText = ""; allAsrText = allAsrText + t; // 这里可以做一些自动停止录音识别的程序 } else { asrText = asrText + t; } } // 显示语音识别结果消息 if (!(allAsrText + asrText).equals("")) { runOnUiThread(() -> resultText.setText(allAsrText + asrText)); } // 如果检测的录音停止就关闭WebSocket连接 if (isFinal) { webSocket.close(1000, "关闭WebSocket连接"); } } catch (JSONException e) { throw new RuntimeException(e); } } @Override public void onClosing(@NonNull WebSocket webSocket, int code, @NonNull String reason) { // 关闭连接时的处理 Log.d(TAG, "WebSocket关闭连接: " + reason); } @Override public void onFailure(@NonNull WebSocket webSocket, @NonNull Throwable t, Response response) { // 连接失败时的处理 Log.d(TAG, "WebSocket连接失败: " + t + ": " + response); } }); String message = getMessage("2pass", "5, 10, 5", 10, true); webSocket.send(message); audioRecord.startRecording(); byte[] bytes = new byte[minBufferSize]; while (isRecording) { int readSize = audioRecord.read(bytes, 0, minBufferSize); if (readSize > 0) { ByteString byteString = ByteString.of(bytes); webSocket.send(byteString); audioView.post(() -> audioView.setWaveData(bytes)); } } JSONObject obj = new JSONObject(); obj.put("is_speaking", false); webSocket.send(obj.toString()); // webSocket.close(1000, "关闭WebSocket连接"); } // 发送第一步的JSON数据 public String getMessage(String mode, String strChunkSize, int chunkInterval, boolean isSpeaking) { try { JSONObject obj = new JSONObject(); obj.put("mode", mode); JSONArray array = new JSONArray(); String[] chunkList = strChunkSize.split(","); for (String s : chunkList) { array.put(Integer.valueOf(s.trim())); } obj.put("chunk_size", array); obj.put("chunk_interval", chunkInterval); obj.put("wav_name", "default"); // 热词 obj.put("hotwords", "阿里巴巴 达摩院"); obj.put("wav_format", "pcm"); obj.put("is_speaking", isSpeaking); return obj.toString(); } catch (Exception e) { e.printStackTrace(); } return ""; } // 检查权限 private boolean hasPermission() { return checkSelfPermission(android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED && checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; } // 请求权限 private void requestPermission() { requestPermissions(new String[]{android.Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); } }