AudioFormat

AudioFormat   用于访问 一系列语音格式和通道配置常量

例如用于AudioTrack 和AudioRecord中

The AudioFormat class is used to access a number of audio format and
 * channel configuration constants. They are for instance used
 * in {@link AudioTrack} and {@link AudioRecord}.

 

 

   
    //---------------------------------------------------------
    // Constants
    //--------------------
    /** Invalid audio data format */  非法音频数据格式
    public static final int ENCODING_INVALID = 0;
    /** Default audio data format */  默认音频数据格式
    public static final int ENCODING_DEFAULT = 1;
    // These two values must be kept in sync with JNI code for AudioTrack, AudioRecord  
    /** Audio data format: PCM 16 bit per sample. Guaranteed to be supported by devices. */  音频数据格式 16bit取样  保证设备支持
    public static final int ENCODING_PCM_16BIT = 2;
    /** Audio data format: PCM 8 bit per sample. Not guaranteed to be supported by devices. */  8bit  不保证设备支持
    public static final int ENCODING_PCM_8BIT = 3;

    /** Invalid audio channel configuration */
    /** @deprecated use CHANNEL_INVALID instead  */
    @Deprecated    public static final int CHANNEL_CONFIGURATION_INVALID   = 0;
    /** Default audio channel configuration */
    /** @deprecated use CHANNEL_OUT_DEFAULT or CHANNEL_IN_DEFAULT instead  */
    @Deprecated    public static final int CHANNEL_CONFIGURATION_DEFAULT   = 1;
    /** Mono audio configuration */
    /** @deprecated use CHANNEL_OUT_MONO or CHANNEL_IN_MONO instead  */
    @Deprecated    public static final int CHANNEL_CONFIGURATION_MONO      = 2;
    /** Stereo (2 channel) audio configuration */
    /** @deprecated use CHANNEL_OUT_STEREO or CHANNEL_IN_STEREO instead  */
    @Deprecated    public static final int CHANNEL_CONFIGURATION_STEREO    = 3;


    /** Invalid audio channel mask */      不可用的音频通道
    public static final int CHANNEL_INVALID = 0;
    /** Default audio channel mask */      默认音频通道
    public static final int CHANNEL_OUT_DEFAULT = 1;

    // Channel mask definitions below are translated to the native values defined in
    //  in /system/core/include/system/audio.h in the JNI code of AudioTrack
    public static final int CHANNEL_OUT_FRONT_LEFT = 0x4;            前置左      0100
    public static final int CHANNEL_OUT_FRONT_RIGHT = 0x8;            前置右      1000
    public static final int CHANNEL_OUT_FRONT_CENTER = 0x10;         中央        10000
    public static final int CHANNEL_OUT_LOW_FREQUENCY = 0x20;        低频      100000
    public static final int CHANNEL_OUT_BACK_LEFT = 0x40;            后置左      1000000
    public static final int CHANNEL_OUT_BACK_RIGHT = 0x80;            后置右     10000000
    public static final int CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x100;   中央前置左      100000000
    public static final int CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x200;    中央前置右    1000000000
    public static final int CHANNEL_OUT_BACK_CENTER = 0x400;           中央后置      10000000000
    /** @hide */
    public static final int CHANNEL_OUT_SIDE_LEFT =         0x800;    左声道
    /** @hide */
    public static final int CHANNEL_OUT_SIDE_RIGHT =       0x1000;    右声道
    /** @hide */
    public static final int CHANNEL_OUT_TOP_CENTER =       0x2000;   上中央
    /** @hide */
    public static final int CHANNEL_OUT_TOP_FRONT_LEFT =   0x4000;  上左前置
    /** @hide */
    public static final int CHANNEL_OUT_TOP_FRONT_CENTER = 0x8000;  上中央前置
    /** @hide */
    public static final int CHANNEL_OUT_TOP_FRONT_RIGHT = 0x10000;  上右前置
    /** @hide */
    public static final int CHANNEL_OUT_TOP_BACK_LEFT =   0x20000;    上左后置
    /** @hide */
    public static final int CHANNEL_OUT_TOP_BACK_CENTER = 0x40000;  上中后置
    /** @hide */
    public static final int CHANNEL_OUT_TOP_BACK_RIGHT =  0x80000;    上右后置

    public static final int CHANNEL_OUT_MONO = CHANNEL_OUT_FRONT_LEFT;                    单声道
    public static final int CHANNEL_OUT_STEREO = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT);    立体声道
    public static final int CHANNEL_OUT_QUAD = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |    四声道
            CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT);
    public static final int CHANNEL_OUT_SURROUND = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | 环绕
            CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_BACK_CENTER);

 

  5.1声道是指中央声道,前置左、右声道、后置左、右环绕声道,及所谓的0.1声道重低音声道。一套系统总共可连接6个喇叭。5.1声道已广泛运用于各类传统影院和家庭影院中
    public static final int CHANNEL_OUT_5POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | 
            CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT);

 

7.1声道系统的作用简单来说就是在听者的周围建立起一套前后声场相对平衡的声场,不同于5.1声道声场的是,它在原有的基础上增加了后中声场声道,同时它也不同于普通6.1声道声场,因为7.1声道有双路后中置,而这双路后中置的最大作用就是为了防止听者因为没有坐在皇帝位而在听觉上产生声场的偏差。
    public static final int CHANNEL_OUT_7POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
            CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT |
            CHANNEL_OUT_FRONT_LEFT_OF_CENTER | CHANNEL_OUT_FRONT_RIGHT_OF_CENTER);
    /** @hide */
    public static final int CHANNEL_OUT_7POINT1_SURROUND = (
            CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_FRONT_RIGHT |
            CHANNEL_OUT_SIDE_LEFT | CHANNEL_OUT_SIDE_RIGHT |
            CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT |
            CHANNEL_OUT_LOW_FREQUENCY);

 

注意在这里不是逻辑或||,而是按位或|,因为按之前的描述。每一种声道模式的二进制编码。1的位置都是错开的

所以进行 | 操作后。得到的就是支持的所有模式。

而其中1的位数总和。就是支持声道模式的个数

 



    public static final int CHANNEL_IN_DEFAULT = 1;
    public static final int CHANNEL_IN_LEFT = 0x4;
    public static final int CHANNEL_IN_RIGHT = 0x8;
    public static final int CHANNEL_IN_FRONT = 0x10;
    public static final int CHANNEL_IN_BACK = 0x20;
    public static final int CHANNEL_IN_LEFT_PROCESSED = 0x40;
    public static final int CHANNEL_IN_RIGHT_PROCESSED = 0x80;
    public static final int CHANNEL_IN_FRONT_PROCESSED = 0x100;
    public static final int CHANNEL_IN_BACK_PROCESSED = 0x200;
    public static final int CHANNEL_IN_PRESSURE = 0x400;
    public static final int CHANNEL_IN_X_AXIS = 0x800;
    public static final int CHANNEL_IN_Y_AXIS = 0x1000;
    public static final int CHANNEL_IN_Z_AXIS = 0x2000;
    public static final int CHANNEL_IN_VOICE_UPLINK = 0x4000;
    public static final int CHANNEL_IN_VOICE_DNLINK = 0x8000;
    public static final int CHANNEL_IN_MONO = CHANNEL_IN_FRONT;
    public static final int CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT);

转载于:https://www.cnblogs.com/maxiaodoubao/p/4255711.html

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.AudioTrack; import android.media.MediaRecorder; import android.os.Bundle; 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; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedWriter; import java.io.IOException; 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 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 ); 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; private volatile boolean isServerRunning = true; private volatile Socket clientSocket; private volatile BufferedWriter socketWriter; // TTS和播放控制 private TextToSpeech ttsEngine; private boolean isTtsInitialized = false; 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 final AtomicBoolean isPlaying = new AtomicBoolean(false); private final AtomicBoolean isPaused = new AtomicBoolean(false); private volatile boolean isPlaybackThreadActive = false; // 音频轨道锁对象 private final Object audioTrackLock = new Object(); private final Object playbackQueueLock = new Object(); private final Object recordingQueueLock = new Object(); // 主线程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.set(false); isPlaybackThreadActive = false; updatePlayButtonsState(); break; case 0x18: // 播放队列已添加 Toast.makeText(MainActivity.this, "已添加到播放队列", Toast.LENGTH_SHORT).show(); break; case 0x19: // 播放状态更新 updatePlayButtonsState(); 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(); } 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); // 清空之前的录音数据 synchronized (recordingQueueLock) { recordingQueue.clear(); } pausedQueue.clear(); // 更新UI状态 startRecordButton.setEnabled(false); stopRecordButton.setEnabled(true); updatePlayButtonsState(); // 启动录音数据采集线程 if (scheduler != null && !scheduler.isShutdown()) { scheduler.shutdownNow(); } scheduler = Executors.newSingleThreadScheduledExecutor(); 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(); // 更新UI状态 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()); } } } 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 (recordingQueueLock) { for (byte[] data : recordingQueue) { recordingCopy.offer(data.clone()); } } // 添加到播放队列 synchronized (playbackQueueLock) { playbackQueue.addAll(recordingCopy); } // 如果当前没有播放,立即开始播放 if (!isPlaybackThreadActive && !isPlaying.get()) { executorService.execute(this::playRecordingQueue); } else { handler.sendEmptyMessage(0x18); } } // 播放录音队列 - 修复后的方法 private void playRecordingQueue() { isPlaybackThreadActive = true; isPlaying.set(true); isPaused.set(false); handler.sendEmptyMessage(0x19); // 更新按钮状态 // 配置音频播放器 int bufferSize = AudioTrack.getMinBufferSize( SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT ); // 创建新的AudioTrack synchronized (audioTrackLock) { if (audioTrack != null) { try { audioTrack.stop(); audioTrack.release(); } catch (Exception e) { Log.e(TAG, "释放AudioTrack失败", e); } } try { audioTrack = new AudioTrack( AudioManager.STREAM_MUSIC, SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM ); audioTrack.play(); } catch (IllegalStateException e) { Log.e(TAG, "创建AudioTrack失败", e); stopPlayback(); return; } } // 播放队列中的所有录音数据 while (isPlaying.get() && !playbackQueue.isEmpty()) { if (isPaused.get()) { // 暂停状态,等待恢复 try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } continue; } byte[] audioData; synchronized (playbackQueueLock) { audioData = playbackQueue.poll(); } if (audioData != null) { synchronized (audioTrackLock) { if (audioTrack != null && audioTrack.getState() == AudioTrack.STATE_INITIALIZED) { try { audioTrack.write(audioData, 0, audioData.length); } catch (IllegalStateException e) { Log.e(TAG, "音频写入失败: " + e.getMessage()); break; } } else { Log.w(TAG, "AudioTrack不可用,停止播放"); break; } } } } // 播放完成 stopPlayback(); handler.sendEmptyMessage(0x17); } // 暂停播放 - 修复后的方法 private void pausePlayback() { if (!isPlaying.get() || isPaused.get()) return; isPaused.set(true); // 保存当前播放位置 synchronized (playbackQueueLock) { pausedQueue.clear(); pausedQueue.addAll(playbackQueue); playbackQueue.clear(); } // 暂停音频播放 synchronized (audioTrackLock) { if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { try { audioTrack.pause(); } catch (IllegalStateException e) { Log.e(TAG, "暂停播放失败: " + e.getMessage()); } } } handler.sendEmptyMessage(0x19); // 更新按钮状态 Toast.makeText(this, "播放已暂停", Toast.LENGTH_SHORT).show(); } // 继续播放 - 修复后的方法 private void resumePlayback() { if (!isPlaying.get() || !isPaused.get()) return; isPaused.set(false); // 恢复播放位置 synchronized (playbackQueueLock) { playbackQueue.clear(); playbackQueue.addAll(pausedQueue); pausedQueue.clear(); } // 恢复音频播放 synchronized (audioTrackLock) { if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) { try { audioTrack.play(); } catch (IllegalStateException e) { Log.e(TAG, "恢复播放失败: " + e.getMessage()); } } } handler.sendEmptyMessage(0x19); // 更新按钮状态 Toast.makeText(this, "继续播放", Toast.LENGTH_SHORT).show(); } // 停止播放 - 修复后的方法 private void stopPlayback() { isPlaying.set(false); isPaused.set(false); synchronized (audioTrackLock) { if (audioTrack != null) { try { if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING || audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) { audioTrack.stop(); } audioTrack.release(); } catch (IllegalStateException e) { Log.e(TAG, "停止播放失败: " + e.getMessage()); } finally { audioTrack = null; } } } synchronized (playbackQueueLock) { playbackQueue.clear(); } pausedQueue.clear(); handler.sendEmptyMessage(0x19); // 更新按钮状态 Toast.makeText(this, "播放已停止", Toast.LENGTH_SHORT).show(); } // 清除所有录音 private void clearAllRecordings() { stopPlayback(); synchronized (recordingQueueLock) { recordingQueue.clear(); } pausedQueue.clear(); synchronized (playbackQueueLock) { playbackQueue.clear(); } handler.sendEmptyMessage(0x19); // 更新按钮状态 Toast.makeText(this, "所有录音已清除", Toast.LENGTH_SHORT).show(); } // ==================== 播放功能结束 ==================== // ==================== 辅助方法 ==================== // 更新播放按钮状态 private void updatePlayButtonsState() { runOnUiThread(() -> { boolean hasRecordings = !recordingQueue.isEmpty() || !pausedQueue.isEmpty(); boolean isPlayingState = isPlaying.get() && !isPaused.get(); playSoundButton.setEnabled(hasRecordings && !isPlayingState); pauseSoundButton.setEnabled(isPlayingState); stopSoundButton.setEnabled(isPlaying.get() || isPaused.get()); resumeSoundButton.setEnabled(isPlaying.get() && isPaused.get()); clearSoundsButton.setEnabled(hasRecordings); }); } // 播放TTS语音 private void playTts(String text) { if (isTtsInitialized) { ttsEngine.speak(text, TextToSpeech.QUEUE_FLUSH, null); } } // 释放音频资源 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 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 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); 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(); } }); } // 关闭服务器Socket private void closeServerSocket() { try { if (serverSocket != null && !serverSocket.isClosed()) { serverSocket.close(); } } catch (IOException e) { Log.w(TAG, "关闭ServerSocket失败", e); } } // 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) { ttsEngine.stop(); ttsEngine.shutdown(); } closeServerSocket(); closeSocket(clientSocket); // 停止所有录音和播放 stopRecording(); stopPlayback(); // 优雅关闭线程池 executorService.shutdown(); try { if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) { executorService.shutdownNow(); } } catch (InterruptedException e) { executorService.shutdownNow(); Thread.currentThread().interrupt(); } // 确保所有资源释放 releaseAudioResources(); } // 关闭Socket 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; } } } } 使用时播放完录音还是闪退 播放完成我不想让app程序闪退
07-03
package com.example.demoapplication; import android.Manifest; import android.content.pm.PackageManager; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.AudioTrack; import android.media.MediaRecorder; 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; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; import java.util.Locale; 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 { // 日志标签和UI组件定义 private static final String TAG = "AudioRecorder"; private Button startRecordButton, stopRecordButton; private AudioRecord audioRecord; // 音频采样率和缓冲区大小配置 private static final int SAMPLE_RATE = 16000; 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; private volatile boolean isServerRunning = true; private volatile Socket clientSocket; private volatile BufferedWriter socketWriter; // 文字转语音(TTS)引擎和播放器 private TextToSpeech ttsEngine; private boolean isTtsInitialized = false; private AudioTrack audioTrack; // 控制是否已发送开始/结束提示 private boolean isStartMessageSent = false; private boolean isStopMessageSent = false; private boolean isTransmitStarted = false; // 新增标志位控制startTransmit只显示一次 private boolean isStopTransmitSent = false; // 新增标志位控制stopTransmit只显示一次 // 主线程消息处理器 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,但保留标志位用于其他用途 Toast.makeText(MainActivity.this, "开始录音", Toast.LENGTH_SHORT).show(); isStartMessageSent = true; isStopMessageSent = false; sendControlPacket("startRecorder"); // 发送开始录音指令给客户端 playTts("开始录音"); // 触发TTS播报"开始录音" break; case 0x13: // 数据发送 break; case 0x14: // 停止录音 // 强制显示Toast,忽略标志位检查 Toast.makeText(MainActivity.this, "停止录音", Toast.LENGTH_SHORT).show(); isStopMessageSent = true; isStartMessageSent = false; sendControlPacket("stopRecorder"); // 发送停止录音指令给客户端 playTts("停止录音"); // 触发TTS播报"停止录音" 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(); break; } } }; // Activity生命周期方法 @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对象 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 初始化失败"); } // 启动录音,更新UI状态,启动定时上传任务 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("停止录音"); } // 播放TTS语音 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("开始传输"); // 触发TTS播报"开始传输" audioRecord.startRecording(); // 确保录音已开始 isTransmitStarted = true; // 标记为已发送 } // 在最后一次读取数据后发送停止传输指令 if (bytesRead < BUFFER_SIZE / 2 && isTransmitStarted) { sendControlPacket("stopTransmit"); // 发送停止传输指令 playTts("停止传输"); // 触发TTS播报"停止传输" 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()); } } // 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; } } } // 发送控制指令包 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(); } // 启动TCP服务器 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); } } // 处理接收到的数据包 private void handleReceivedPacket(JSONObject jsonObject) { try { String type = jsonObject.getString("type"); Object data = jsonObject.opt("data"); Message msg; switch (type) { case "tts_audio": 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; default: msg = handler.obtainMessage(0x15, type + ": " + data); handler.sendMessage(msg); break; } } catch (JSONException e) { Log.e(TAG, "处理数据包失败", e); } } // 处理TTS音频数据 private void handleTtsAudio(String base64Data) { byte[] pcmData = Base64.decode(base64Data, Base64.DEFAULT); playPcm(pcmData); } // 播放PCM音频 private void playPcm(byte[] pcmData) { stopAudioPlayback(); int sampleRate = 16000; int channelConfig = AudioFormat.CHANNEL_OUT_MONO; int audioFormat = AudioFormat.ENCODING_PCM_16BIT; int bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat); audioTrack = new AudioTrack( android.media.AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, bufferSize, AudioTrack.MODE_STREAM); audioTrack.play(); audioTrack.write(pcmData, 0, pcmData.length); } // 停止音频播放 private void stopAudioPlayback() { if (audioTrack != null) { if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { audioTrack.stop(); } audioTrack.release(); audioTrack = null; } } // 关闭指定的Socket连接 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; } } } // 关闭服务器Socket private void closeServerSocket() { try { if (serverSocket != null && !serverSocket.isClosed()) { serverSocket.close(); } } catch (IOException e) { Log.w(TAG, "关闭ServerSocket失败", e); } } // Activity销毁时清理资源 @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; } } 修改安卓
07-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值