| 知乎专栏 |
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();
// }
}
}