知乎专栏 |
private AudioRecord getAudioRecord() { final int SAMPLE_RATE = 16000; final int WAVE_FRAM_SIZE = 20 * 2 * 16000 / 1000; //20ms audio for 16k/16bit/mono try { int bufferSize = AudioRecord.getMinBufferSize(16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); // AudioRecord recorder = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, 16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize); AudioRecord recorder = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, WAVE_FRAM_SIZE * 4); if (recorder.getState() == AudioRecord.STATE_INITIALIZED) { return recorder; } } catch (Exception e) { Log.e(TAG, "Error in Audio Record"); } return null; }
AudioDeviceInfo.TYPE_REMOTE_SUBMIX 用于投屏,电视等设备录音
AudioRecord record = new AudioRecord(AudioSource.REMOTE_SUBMIX, 44100, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, bufferSize)
当设备中有多个麦克风时,我们希望切换到另一个麦克风,可以采用此方法。
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 16000 * 4); AudioManager audioManager = (AudioManager) MainApplication.getContext().getSystemService(Context.AUDIO_SERVICE); AudioDeviceInfo[] audioDeviceInfos = new AudioDeviceInfo[]{}; audioDeviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS); for (AudioDeviceInfo device : audioDeviceInfos) { if (device.getType() == AudioDeviceInfo.TYPE_USB_DEVICE && device.getProductName().equals("USB-Audio - USB PnP Sound Device")) { audioRecord.setPreferredDevice(device); Log.d(TAG, "Set microphone: " + device.getProductName()); } Log.d(TAG, device.getId() + " | " + device.getProductName() + " | " + device.getType()); } audioRecord.startRecording(); Log.d(TAG, "AudioRecord state: " + audioRecord.getState()); if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { Log.e(TAG, "audio recorder not init"); } else { byte[] audioData = new byte[1024]; for (int i = 0; i < 100; i++) { int ret = audioRecord.read(audioData, 0, 1024); Log.d(TAG, String.valueOf(audioData)); } }
if (ActivityCompat.checkSelfPermission(MainApplication.getContext(), Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { // https://www.netkiller.cn/android/ if (mAudioRecorder == null) { AudioManager audioManager = (AudioManager) MainApplication.getContext().getSystemService(Context.AUDIO_SERVICE); // 启动蓝牙SCO连接 audioManager.startBluetoothSco(); // 启用蓝牙SCO音频路由 audioManager.setBluetoothScoOn(true); // 设置音频模式为通信模式(这会优先使用蓝牙麦克风) audioManager.setMode(AudioManager.MODE_COMMUNICATION_REDIRECT); // 获取所有音频输入设备 AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS); // 查找蓝牙设备 AudioDeviceInfo bluetoothDevice = null; for (AudioDeviceInfo device : devices) { if (device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO || device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) { bluetoothDevice = device; break; } } mAudioRecorder = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, WAVE_FRAM_SIZE * 4); if (bluetoothDevice != null) { if (mAudioRecorder.setPreferredDevice(bluetoothDevice)) { Log.i(TAG, "成功设置蓝牙麦克风为首选设备 " + bluetoothDevice.getProductName()); } AudioDeviceInfo currentDevice = mAudioRecorder.getRoutedDevice(); if (currentDevice != null) { Log.d(TAG, "当前音频输入设备: " + currentDevice.getProductName()); } } } else { Log.w(TAG, "AudioRecord has been new ..."); } } else { Log.e(TAG, "未获得录音权限 RECORD_AUDIO permission!"); }
package cn.netkiller.conference.test; import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageManager; import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.Bundle; import android.util.Log; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import java.nio.ByteBuffer; import cn.netkiller.conference.MainApplication; import cn.netkiller.conference.R; public class TestActivity extends AppCompatActivity { private static final String TAG = TestActivity.class.getSimpleName(); public String meetingJoinUrl; public String taskId; private AudioRecord audioRecord; @SuppressLint("MissingInflatedId") protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { // TODO: Consider calling // ActivityCompat#requestPermissions // here to request the missing permissions, and then overriding // public void onRequestPermissionsResult(int requestCode, String[] permissions, // int[] grantResults) // to handle the case where the user grants the permission. See the documentation // for ActivityCompat#requestPermissions for more details. return; } audioRecord = new AudioRecord(MediaRecorder.AudioSource.UNPROCESSED, 16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 16000 * 4); AudioManager audioManager = (AudioManager) MainApplication.getContext().getSystemService(Context.AUDIO_SERVICE); AudioDeviceInfo[] audioDeviceInfos = new AudioDeviceInfo[]{}; audioDeviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS); for (AudioDeviceInfo device : audioDeviceInfos) { Log.d(TAG, device.getId() + " | " + device.getProductName() + " | " + device.getType()); if ((device.getType() == AudioDeviceInfo.TYPE_USB_DEVICE || device.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) && device.getProductName().toString().contains("USB-Audio")) { audioRecord.setPreferredDevice(device); Log.d(TAG, "Set microphone: " + device.getProductName()); } } audioRecord.startRecording(); Log.d(TAG, "AudioRecord state: " + audioRecord.getState()); if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { Log.e(TAG, "audio recorder not init"); } else { new Thread(new AudioRecordRunnable()).start(); } } @Override protected void onDestroy() { super.onDestroy(); if (audioRecord != null) { audioRecord.stop(); audioRecord.release(); } } private class AudioRecordRunnable implements Runnable { public void run() { Log.d(TAG, "Thread buffer start"); ByteBuffer audioBuffer = ByteBuffer.allocate(1024); while (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) { int read = audioRecord.read(audioBuffer, audioBuffer.capacity()); if (read > 0) { Log.d(TAG, audioBuffer.toString()); } // Log.d(TAG, String.valueOf(read)); } // try (FileOutputStream fos = new FileOutputStream("output.pcm")) { // while (isRecording) { // int read = audioRecord.read(audioBuffer, audioBuffer.capacity()); // if (read > 0) { // fos.write(audioBuffer.array(), 0, read); // } // } // } catch (Exception e) { // Log.e(TAG, "Error writing audio", e); // } } } }
package com.ideasprite.conference.ai.media.audio; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.MediaRecorder; import android.media.audiofx.AcousticEchoCanceler; import android.media.audiofx.AutomaticGainControl; import android.media.audiofx.NoiseSuppressor; import android.os.Environment; import android.util.Log; import androidx.core.app.ActivityCompat; import com.ideasprite.conference.MainApplication; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; public class Microphone { private static final String TAG = Microphone.class.getSimpleName(); // 音频配置参数 private static final int SAMPLE_RATE = 16000; // 44.1kHz private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; private static final int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT); // 单例模式 private static Microphone instance; // private volatile boolean isRecording = false; private static boolean isRecording = false; // 音频数据队列 // private final BlockingQueue<byte[]> audioQueue = new LinkedBlockingQueue<>(); // AudioRecord 实例 private AudioRecord audioRecord; private FileOutputStream fileOutputStream; // private volatile boolean isProcessing = false; public Microphone() { if (ActivityCompat.checkSelfPermission( MainApplication.getContext(), Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { // 初始化AudioRecord if (audioRecord == null) { audioRecord = new AudioRecord( MediaRecorder.AudioSource.VOICE_RECOGNITION, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize * 2); // 使用两倍最小缓冲区 } if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) { Log.d(TAG, "AudioRecord 初始化成功"); if (AutomaticGainControl.isAvailable()) { AutomaticGainControl automaticGainControl = AutomaticGainControl.create(audioRecord.getAudioSessionId()); int status = automaticGainControl.setEnabled(true); if (status == AutomaticGainControl.SUCCESS) { Log.d(TAG, "AGC 启用成功"); } else { Log.e(TAG, "AGC 启用失败,状态码: " + status); } } else { Log.d(TAG, "AutomaticGainControl 失败"); } if (NoiseSuppressor.isAvailable()) { NoiseSuppressor noiseSuppressor = NoiseSuppressor.create(audioRecord.getAudioSessionId()); // if (ns != null) { int status = noiseSuppressor.setEnabled(true); if (status == NoiseSuppressor.SUCCESS) { Log.d(TAG, "NoiseSuppressor 启用成功"); } else { Log.e(TAG, "NoiseSuppressor 启用失败,状态码: " + status); } // } } else { Log.d(TAG, "NoiseSuppressor 失败"); } if (AcousticEchoCanceler.isAvailable()) { AcousticEchoCanceler acousticEchoCanceler = AcousticEchoCanceler.create(audioRecord.getAudioSessionId()); if (acousticEchoCanceler != null) { int status = acousticEchoCanceler.setEnabled(true); if (status == AcousticEchoCanceler.SUCCESS) { Log.d(TAG, "AcousticEchoCanceler AEC 启用成功"); } else { Log.e(TAG, "AcousticEchoCanceler AEC 启用失败,状态码: " + status); } } } else { Log.d(TAG, "AudioRecord AcousticEchoCanceler 失败"); } } else { Log.d(TAG, "AudioRecord state: " + audioRecord.getState()); } } } public static synchronized Microphone getInstance() { if (instance == null) { instance = new Microphone(); } Log.d(TAG, "Microphone getInstance()"); return instance; } /** * 将 PCM 文件转换为 WAV 文件(单方法实现) * * @param pcmFilePath PCM 文件路径 * @param wavFilePath WAV 文件路径 * @param sampleRate 采样率(如 44100) * @param channels 声道数(1=单声道,2=立体声) * @param bitDepth 位深度(如 16 位) * @return 成功返回 true,失败返回 false */ public static boolean convertPcmToWav(String pcmFilePath, String wavFilePath, int sampleRate, int channels, int bitDepth) { FileInputStream fis = null; FileOutputStream fos = null; try { File pcmFile = new File(pcmFilePath); File wavFile = new File(wavFilePath); // 创建 WAV 文件输出流 fos = new FileOutputStream(wavFile); // 写入 WAV 文件头 long fileSize = 36 + pcmFile.length(); // RIFF 头 fos.write("RIFF".getBytes()); fos.write(intToLittleEndian((int) (fileSize - 8))); fos.write("WAVE".getBytes()); // fmt 块 fos.write("fmt ".getBytes()); fos.write(intToLittleEndian(16)); // fmt 块大小 fos.write(shortToLittleEndian((short) 1)); // 音频格式:PCM fos.write(shortToLittleEndian((short) channels)); // 声道数 fos.write(intToLittleEndian(sampleRate)); // 采样率 fos.write(intToLittleEndian(sampleRate * channels * bitDepth / 8)); // 字节率 fos.write(shortToLittleEndian((short) (channels * bitDepth / 8))); // 块对齐 fos.write(shortToLittleEndian((short) bitDepth)); // 位深度 // data 块 fos.write("data".getBytes()); fos.write(intToLittleEndian((int) pcmFile.length())); // 数据大小 // 复制 PCM 数据到 WAV 文件 fis = new FileInputStream(pcmFile); byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { fos.write(buffer, 0, bytesRead); } return true; } catch (IOException e) { e.printStackTrace(); return false; } finally { // 关闭流 try { if (fis != null) fis.close(); if (fos != null) fos.close(); } catch (IOException e) { e.printStackTrace(); } } } // int 转小端字节数组 private static byte[] intToLittleEndian(int value) { return new byte[]{ (byte) (value & 0xFF), (byte) ((value >> 8) & 0xFF), (byte) ((value >> 16) & 0xFF), (byte) ((value >> 24) & 0xFF) }; } // short 转小端字节数组 private static byte[] shortToLittleEndian(short value) { return new byte[]{ (byte) (value & 0xFF), (byte) ((value >> 8) & 0xFF) }; } public static void applyGainInPlace(byte[] pcmData, float gainFactor) { for (int i = 0; i < pcmData.length; i += 2) { // 从字节中解析 short(小端序) short sample = (short) ((pcmData[i + 1] << 8) | (pcmData[i] & 0xFF)); // 应用增益 float amplified = sample * gainFactor; // 限制范围 amplified = Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, amplified)); short limited = (short) amplified; // 写回字节数组 pcmData[i] = (byte) (limited & 0xFF); pcmData[i + 1] = (byte) ((limited >> 8) & 0xFF); } } public void bluetooth() { AudioManager audioManager = (AudioManager) MainApplication.getContext().getSystemService(Context.AUDIO_SERVICE); // 启动蓝牙SCO连接 audioManager.startBluetoothSco(); // 启用蓝牙SCO音频路由 audioManager.setBluetoothScoOn(true); // 设置音频模式为通信模式(这会优先使用蓝牙麦克风) audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); // 获取所有音频输入设备 AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS); // Log.d(TAG, "Bluetooth: start setup"); // 查找蓝牙设备 for (AudioDeviceInfo device : devices) { Log.d(TAG, "Bluetooth: device: " + device.getProductName()); if (device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO || device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) { Log.d(TAG, "Bluetooth: " + String.format("Product name: %s, Type: %s, Id: %s", device.getProductName(), device.getType(), device.getId())); if (device.getProductName().toString().contains("DELI")) { if (audioRecord.setPreferredDevice(device)) { Log.d(TAG, "Setting bluetooth: " + String.format("Product name: %s, Type: %s, Id: %s", device.getProductName(), device.getType(), device.getId())); break; } } } } AudioDeviceInfo currentDevice = audioRecord.getRoutedDevice(); if (currentDevice != null) { Log.d(TAG, "AudioDeviceInfo: " + currentDevice.getProductName()); } } /** * 开始音频采集和处理 */ public void start() { if (isRecording) { Log.w(TAG, "Already running"); return; } isRecording = true; String dir = String.valueOf(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)); // String dir = Environment.getExternalStorageDirectory().getPath(); String date = new SimpleDateFormat("yyyy-MM-dd.hhmmss").format(new Date()); String filePath = String.format("%s/%s.pcm", dir, date); Log.i(TAG, "Start recording " + filePath); try { fileOutputStream = new FileOutputStream(filePath); } catch (FileNotFoundException e) { e.printStackTrace(); } audioRecord.startRecording(); this.bluetooth(); Log.d(TAG, "AudioRecord startRecording()"); if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { Log.e(TAG, "audio recorder not init"); } else { new Thread(() -> { byte[] audioData = new byte[minBufferSize]; // 1KB缓冲区 // short[] audioData = new short[minBufferSize]; // 1KB缓冲区 try { while (isRecording) { int read = audioRecord.read(audioData, 0, audioData.length); if (read > 0) { // applyGainInPlace(audioData, 3.0f); fileOutputStream.write(audioData, 0, read); // for (short s : audioData) { // fileOutputStream.write((s >> 8) & 0xFF); // 高位字节 // fileOutputStream.write(s & 0xFF); // 低位字节 // } } // Log.d(TAG, "Thread " + Thread.currentThread().getName() + " audioRecord.read " + audioBuffer.length); } } catch (IOException e) { e.printStackTrace(); } finally { try { fileOutputStream.flush(); fileOutputStream.close(); boolean success = convertPcmToWav( filePath, filePath.replace(".pcm", ".wav"), 16000, // 采样率 44.1kHz 1, // 单声道 16 // 16位深度 ); if (success) { Log.d("AudioUtil", "PCM 转 WAV 成功"); } else { Log.e("AudioUtil", "PCM 转 WAV 失败"); } } catch (IOException e) { e.printStackTrace(); } } }).start(); } } /** * 停止音频采集和处理 */ public void stop() { if (audioRecord != null) { audioRecord.stop(); } isRecording = false; Log.i(TAG, "Stop recording "); } /** * 释放资源 */ public void release() { stop(); if (audioRecord != null) { audioRecord.release(); audioRecord = null; } Log.i(TAG, "release"); } public void rmsAudioData(byte[] audioData) { // 这里只是简单打印日志,实际应用中可以进行音频分析、编码等操作 Log.d(TAG, "Processing audio data, length: " + audioData.length); // 示例:计算音频数据的RMS值(粗略表示音量) long sum = 0; for (byte b : audioData) { sum += b * b; } double rms = Math.sqrt(sum / (double) audioData.length); Log.d(TAG, "Audio RMS: " + rms); } public void save(String filename) { if (!isRecording) { Log.i(TAG, "录音存储失败"); return; } String filepath = MainApplication.getContext().getExternalFilesDir("debug") + "/" + filename + ".pcm"; Log.i(TAG, "录音存储到 " + filepath); try (FileOutputStream fileOutputStream = new FileOutputStream(filepath)) { for (int i = 1; i < 100; i++) { // byte[] audioData = read(); // if (audioData != null) { // fileOutputStream.write(audioData); // Log.i(TAG, "Thread " + Thread.currentThread().getName() + " 存储 " + filename + " 录音 " + audioData.length); // } } // stop(); } catch (IOException e) { e.printStackTrace(); } // finally { // fileOutputStream.flush(); // fileOutputStream.close(); // } } }