Android音频开发之录制音频(WAV及MP3格式)

附GitHub源码:MultimediaExplore

首先看下音频录制跟播放效果简图:

上面是录音:长按即可录音,支持声波动画,右滑删除等。支持录制pcm、wav、mp3格式音频。

下面是播放:点击左边扬声器icon,开始播放刚录制的本地音频文件【也支持在线音频播放】,支持播放进度,支持切换播放模式(听筒/扬声器/耳机)等。

一、音频录制权限:

无论在做开发任何功能之前,总得先添加及申请相关权限,后续的工作才能正常进行下去。音频录制所需权限如下,而且要在代码中动态申请这些敏感权限,同意后才能正常录制:

<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);

}

三、音频录制:

音频录制类主要有两个封装类:分别是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 这个音频编解码库。以下是lame编解码方法及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:

本项目提供的源码中有lame的jni源码,如果想生成libmp3lame.so文件,供自己的项目使用,那么需要对Mp3Encoder.c 和Mp3Encoder.h文件进行修改,然后通过ndk build 生成该so文件。

修改的内容主要是把文件中的Mp3Encoder路径改成自己项目中的Mp3Encoder的路径,否则会找不到相关的native方法。

另外一般情况下,cpu类型支持 armeabi-v7a 、arm64-v8a 两种即可,如果想支持其他的可在Application.mk中添加。

上面文件修改完毕,通过ndk-build指令即可编译生成对应的 libmp3lame.so 文件。

将so不同CPU类型的文件放置 jniLibs 下,或者通过sourceSets配置的路径下,如:

sourceSets.main {

jni.srcDirs = []//disable automatic ndk-build call

jniLibs.srcDirs = ['libs']

}

即可进行MP3音频格式的录制。

四、音频录制管理【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();

}

}

以上就是Android音频开发之录制音频(WAV及MP3格式)的详细内容,更多关于Android 音频录制的资料请关注其它相关文章!

以上是 Android音频开发之录制音频(WAV及MP3格式) 的全部内容, 来源链接: utcz.com/p/244165.html

回到顶部