1. Разработка аудио для Android (1): основы работы со звуком
2. Разработка аудио для Android (2): запись аудио (форматы WAV и MP3)
3. Разработка аудио для Android (3): использование ExoPlayer для воспроизведения аудио
4. Разработка аудио для Android (4) : Режим воспроизведения аудио
5. Разработка аудио для Android (5): управление индукцией (экран выключен/яркий экран)
Прикрепленный исходный код GitHub: MultimediaExplore
Сначала взгляните на диаграмму эффектов аудиозаписи и воспроизведения:
CSDN не поддерживает локальную загрузку видео, поэтому я сначала загрузил скриншот:
Вышеуказанная запись : длительное нажатие для записи, поддержка анимации звуковой волны, пролистывание вправо для удаления и т. д. Поддержка записи аудио в форматах pcm, wav, mp3.
Воспроизведение осуществляется следующим образом : щелкните значок динамика слева, чтобы начать воспроизведение только что записанного локального аудиофайла [также поддерживается воспроизведение аудио в режиме онлайн], поддерживает ход воспроизведения и поддерживает переключение режимов воспроизведения (трубка/динамик/наушники) и т. д.
1. Разрешение на аудиозапись:
Независимо от того, прежде чем разрабатывать какие-либо функции, вы должны сначала добавить и подать заявку на соответствующие разрешения, чтобы последующая работа могла проходить нормально. Разрешения, необходимые для аудиозаписи, следующие, и эти конфиденциальные разрешения должны динамически применяться в коде и могут быть записаны в обычном режиме только после согласия:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Во-вторых, конфигурация файла записи:
Из базовой концепции звука, упомянутой в первом разделе, мы можем знать, что перед записью звука необходимо выполнить соответствующую настройку записи, которая напрямую определяет качество звука, размер файла, формат звука и т. д. записываемого файла.
/**
* 录音音频的相关配置
*/
private void initConfig() {
recordConfig = new RecordConfig();
//采样位宽
recordConfig.setEncodingConfig(AudioFormat.ENCODING_PCM_16BIT);
//录音格式
recordConfig.setFormat(RecordConfig.RecordFormat.MP3);
// recordConfig.setFormat(RecordConfig.RecordFormat.WAV);
//采样频率
recordConfig.setSampleRate(16000);
String recordDir = String.format(Locale.getDefault(), "%s/Record/zhongyao/",
Environment.getExternalStorageDirectory().getAbsolutePath());
//存储目录
recordConfig.setRecordDir(recordDir);
AudioRecordManager.getInstance().setCurrentConfig(recordConfig);
}
3. Аудиозапись:
Класс аудиозаписи в основном имеет два класса инкапсуляции: AudioRecorder и AudioRecordManager.
AudioRecorder: в основном для записи используйте AudioRecord системы . И объединить записанные аудиофайлы, перекодировать и т. д., чтобы сгенерировать нужные нам аудиофайлы. Этот файл является глобальным синглтоном, что гарантирует наличие только одного экземпляра класса аудиозаписи.
AudioRecordManager: Инкапсуляция и управление AudioRecorder, а также взаимодействие с внешним миром выполняются через этот класс, включая различные вызовы управления жизненным циклом для записи. Сокращено прямое взаимодействие между внешним миром и AudioRecorder, улучшено управление классом записи, который также является глобальным одноэлементным классом.
1. Инициализируйте объект записи:
Здесь объекты bufferSizeInBytes [размер буфера в байтах] и audioRecord генерируются в основном на основе предыдущей конфигурации записи .
/**
* 创建默认的录音对象
*/
public void prepareRecord() {
// 获得缓冲区字节大小
if (bufferSizeInBytes == 0) {
bufferSizeInBytes = AudioRecord.getMinBufferSize(currentConfig.getSampleRate(),
currentConfig.getChannelConfig(), currentConfig.getEncodingConfig());
}
if (audioRecord == null) {
audioRecord = new AudioRecord(AUDIO_INPUT, currentConfig.getSampleRate(),
currentConfig.getChannelConfig(), currentConfig.getEncodingConfig(), bufferSizeInBytes);
}
audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_PREPARE;
}
2. Запись аудиофайлов wav:
Аудиофайл wav без потерь, поэтому качество звука будет близко к оригиналу, но именно потому, что он без потерь, поэтому аудиофайл wav почти несжатый, и, условно говоря, он будет относительно большим.
Чтобы записать аудио в формате wav, вы должны сначала записать и использовать для получения файлов PCM, затем объединить файлы PCM и, наконец, преобразовать их в аудиофайлы wav.
(1) Начать запись файлов PCM:
private void startPcmRecorder() {
audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_START;
notifyState();
Logger.d(TAG, "开始录制 Pcm");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(tmpFile);
audioRecord.startRecording();
byte[] byteBuffer = new byte[bufferSizeInBytes];
while (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_START) {
int end = audioRecord.read(byteBuffer, 0, byteBuffer.length);
notifyData(byteBuffer);
fos.write(byteBuffer, 0, end);
fos.flush();
}
audioRecord.stop();
files.add(tmpFile);
if (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_STOP) {
makeFile();
} else {
Logger.d(TAG, "取消录制...");
}
} catch (Exception e) {
Logger.e(e, TAG, e.getMessage());
notifyError("录音失败");
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (audioRecordStatus != AudioRecordStatus.AUDIO_RECORD_PAUSE) {
audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_IDLE;
notifyState();
Logger.d(TAG, "录音结束");
}
}
(2) Объедините несколько сгенерированных файлов PCM:
/**
* 合并pcm文件
*/
private void mergePcmFile() {
boolean mergeSuccess = mergePcmFiles(resultFile, files);
if (!mergeSuccess) {
notifyError("合并失败");
}
}
(3) Преобразуйте объединенный файл PCM в файл WAV:
/**
* 添加Wav头文件
*/
private void makeWav() {
if (!FileUtil.isFile(resultFile) || resultFile.length() == 0) {
return;
}
byte[] header = WavUtils.generateWavFileHeader((int) resultFile.length(), currentConfig.getSampleRate(), currentConfig.getChannelCount(), currentConfig.getEncoding());
WavUtils.writeHeader(resultFile, header);
}
3. Запись аудиофайлов MP3
По сравнению с аудиофайлами WAV, аудиофайлы MP3 более распространены и используются больше в коммерческих целях, потому что аудио MP3 сжато, а размер файла составляет всего одну двенадцатую от WAV, но лучшего качества звука почти нет.Большая разница. Когда нет высоких требований к качеству звука, например, при записи файлов, формат MP3 является отличным выбором.
(1) Начать запись аудиобуфера:
Вот, чтобы открыть поток Mp3EncodeThread, массив байтов byteBuffer, созданный при записи, непрерывно кодируется и декодируется для создания файлов MP3.
private void startMp3Recorder() {
audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_START;
notifyState();
try {
audioRecord.startRecording();
short[] byteBuffer = new short[bufferSizeInBytes];
while (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_START) {
int end = audioRecord.read(byteBuffer, 0, byteBuffer.length);
if (mp3EncodeThread != null) {
mp3EncodeThread.addChangeBuffer(new Mp3EncodeThread.ChangeBuffer(byteBuffer, end));
}
notifyData(ByteUtils.toBytes(byteBuffer));
}
audioRecord.stop();
} catch (Exception e) {
Logger.e(e, TAG, e.getMessage());
notifyError("录音失败");
}
if (audioRecordStatus != AudioRecordStatus.AUDIO_RECORD_PAUSE) {
if (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_CANCEL) {
deleteMp3Encoded();
} else {
stopMp3Encoded();
}
} else {
Logger.d(TAG, "暂停");
}
}
(2) Аудиокодек MP3:
Собственная аудиозапись Android поддерживает прямое создание файлов WAV, но на самом деле не поддерживает прямое создание файлов MP3. Это соответствует кодеку MP3, который в основном использует библиотеку аудиокодеков с открытым исходным кодом libmp3lame.so . Ниже приведены хромающие методы кодирования и декодирования, а также класс Mp3Encoder:
Метод кодека MP3:
private void lameData(ChangeBuffer changeBuffer) {
if (changeBuffer == null) {
return;
}
short[] buffer = changeBuffer.getData();
int readSize = changeBuffer.getReadSize();
if (readSize > 0) {
int encodedSize = Mp3Encoder.encode(buffer, buffer, readSize, mp3Buffer);
if (encodedSize < 0) {
Logger.e(TAG, "Lame encoded size: " + encodedSize);
}
try {
os.write(mp3Buffer, 0, encodedSize);
} catch (IOException e) {
Logger.e(e, TAG, "Unable to write to file");
}
}
}
Класс MP3Encoder:
public class Mp3Encoder {
static {
System.loadLibrary("mp3lame");
}
public native static void close();
public native static int encode(short[] buffer_l, short[] buffer_r, int samples, byte[] mp3buf);
public native static int flush(byte[] mp3buf);
public native static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate, int quality);
public static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate) {
init(inSampleRate, outChannel, outSampleRate, outBitrate, 7);
}
}
(3) Создайте libmp3lame.so :
Исходный код, предоставленный этим проектом, включает исходный код jni lame.Если вы хотите создать файл libmp3lame.so для своего собственного проекта, вам необходимо изменить файлы Mp3Encoder.c и Mp3Encoder.h, а затем создать файл so через сборку ndk.
Измененный контент в основном предназначен для изменения пути Mp3Encoder в файле на путь Mp3Encoder в вашем собственном проекте , иначе соответствующий собственный метод не будет найден.
Кроме того, в целом тип процессора поддерживает armeabi-v7a и arm64-v8a.Если вы хотите поддерживать другие, вы можете добавить их в Application.mk.
После изменения вышеуказанного файла соответствующий файл libmp3lame.so можно скомпилировать и сгенерировать с помощью команды ndk-build .
Поместите файлы столь разных типов ЦП в jniLibs или по пути, настроенному с помощью исходных наборов, например:
sourceSets.main {
jni.srcDirs = []//disable automatic ndk-build call
jniLibs.srcDirs = ['libs']
}
Вы можете записывать аудио в формате MP3.
4. Управление аудиозаписями [AudioRecordManager]:
AudioRecorderManager в глобальном одноэлементном режиме взаимодействует с бизнесом, что не только обеспечивает единичность экземпляра аудиозаписи, но и лучше управляет жизненным циклом аудио.
/**
* Create by zhongyao on 2021/8/18
* Description:音频录制管理类
*/
public class AudioRecordManager {
private static final String TAG = "AudioRecordManager";
private AudioRecordManager() {
}
public static AudioRecordManager getInstance() {
return AudioRecordManagerHolder.instance;
}
public static class AudioRecordManagerHolder {
public static AudioRecordManager instance = new AudioRecordManager();
}
public void setCurrentConfig(RecordConfig recordConfig) {
AudioRecorder.getInstance().setCurrentConfig(recordConfig);
}
public RecordConfig getCurrentConfig() {
return AudioRecorder.getInstance().getCurrentConfig();
}
/**
* 录音状态监听回调
*/
public void setRecordStateListener(RecordStateListener listener) {
AudioRecorder.getInstance().setRecordStateListener(listener);
}
/**
* 录音数据监听回调
*/
public void setRecordDataListener(RecordDataListener listener) {
AudioRecorder.getInstance().setRecordDataListener(listener);
}
/**
* 录音可视化数据回调,傅里叶转换后的频域数据
*/
public void setRecordFftDataListener(RecordFftDataListener recordFftDataListener) {
AudioRecorder.getInstance().setRecordFftDataListener(recordFftDataListener);
}
/**
* 录音文件转换结束回调
*/
public void setRecordResultListener(RecordResultListener listener) {
AudioRecorder.getInstance().setRecordResultListener(listener);
}
/**
* 录音音量监听回调
*/
public void setRecordSoundSizeListener(RecordSoundSizeListener listener) {
AudioRecorder.getInstance().setRecordSoundSizeListener(listener);
}
public void setStatus(AudioRecordStatus curAudioRecordStatus) {
switch (curAudioRecordStatus) {
case AUDIO_RECORD_IDLE:
break;
case AUDIO_RECORD_PREPARE:
AudioRecorder.getInstance().prepareRecord();
break;
case AUDIO_RECORD_START:
AudioRecorder.getInstance().startRecord();
break;
case AUDIO_RECORD_PAUSE:
AudioRecorder.getInstance().pauseRecord();
break;
case AUDIO_RECORD_STOP:
AudioRecorder.getInstance().stopRecord();
break;
case AUDIO_RECORD_CANCEL:
AudioRecorder.getInstance().cancelRecord();
break;
case AUDIO_RECORD_RELEASE:
AudioRecorder.getInstance().releaseRecord();
break;
default:
break;
}
}
public AudioRecordStatus getStatus() {
return AudioRecorder.getInstance().getAudioRecordStatus();
}
public String getAudioPath() {
return AudioRecorder.getInstance().getAudioPath();
}
}