android 录音原始文件_Android音视频之AudioRecord录音(一)

本文介绍如何使用Android的AudioRecord进行录音,并将原始PCM数据转换为可播放的WAV格式。包括关键参数设置、文件头生成及数据存储。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在音视频开发中,录音当然是必不可少的。首先我们要学会单独的录音功能,当然这里说的录音是指用AudioRecord来录音,读取录音原始数据,读到的就是所谓的PCM数据。对于录音来说,最重要的几个参数要搞明白:

1、simpleRate采样率,采样率就是采样频率,每秒钟记录多少个样本。

2、channelConfig通道配置,其实就是所谓的单通道,双通道之类的,AudioFormat.CHANNEL_IN_MONO单通道,AudioFormat.CHANNEL_IN_STEREO双通道,这里只列了这两种,还有其它的,可自行查阅。

3、audioFormat音频格式,其实就是采样的精度,每个样本的位数,AudioFormat.ENCODING_PCM_8BIT每个样本占8位,AudioFormat.ENCODING_PCM_16BIT每个样本占16位,这里也只用了这两个,别的没研究。

在学习过程中会用到的一些参数,我这里封装了一个类,如下

public classAudioParams {enumFormat {

SINGLE_8_BIT, DOUBLE_8_BIT, SINGLE_16_BIT, DOUBLE_16_BIT

}privateFormat format;intsimpleRate;

AudioParams(intsimpleRate, Format f) {this.simpleRate =simpleRate;this.format =f;

}

AudioParams(int simpleRate, int channelCount, intbits) {this.simpleRate =simpleRate;

set(channelCount, bits);

}intgetBits() {return (format == Format.SINGLE_8_BIT || format == Format.DOUBLE_8_BIT) ? 8 : 16;

}intgetEncodingFormat() {return (format == Format.SINGLE_8_BIT || format == Format.DOUBLE_8_BIT) ?AudioFormat.ENCODING_PCM_8BIT :

AudioFormat.ENCODING_PCM_16BIT;

}int getChannelCount() {return (format == Format.SINGLE_8_BIT || format == Format.SINGLE_16_BIT) ? 1 : 2;}intgetChannelConfig() {return (format == Format.SINGLE_8_BIT || format == Format.SINGLE_16_BIT) ?AudioFormat.CHANNEL_IN_MONO :

AudioFormat.CHANNEL_IN_STEREO;

}intgetOutChannelConfig() {return (format == Format.SINGLE_8_BIT || format == Format.SINGLE_16_BIT) ?AudioFormat.CHANNEL_OUT_MONO :

AudioFormat.CHANNEL_OUT_STEREO;

}void set(int channelCount, intbits) {if ((channelCount != 1 && channelCount != 2) || (bits != 8 && bits != 16)) {throw new IllegalArgumentException("不支持其它格式 channelCount=$channelCount bits=$bits");

}if (channelCount == 1) {if (bits == 8) {

format=Format.SINGLE_8_BIT;

}else{

format=Format.SINGLE_16_BIT;

}

}else{if (bits == 8) {

format=Format.DOUBLE_8_BIT;

}else{

format=Format.DOUBLE_16_BIT;

}

}

}

}

这里固定使用了单通道8位,双通道8位,单通道16位,双通道16位,所以用了枚举来限制。

为了方便把录音数据拿出来显示、存储,这里写了一个回调方法如下

public interfaceRecordCallback {/*** 数据回调

*

*@parambytes 数据

*@paramlen 数据有效长度,-1时表示数据结束*/

void onRecord(byte[] bytes, intlen);

}

有了这些参数,现在就可以录音了,先看一下样例

public voidstartRecord(AudioParams params, RecordCallback callback) {int simpleRate =params.simpleRate;int channelConfig =params.getChannelConfig();int audioFormat =params.getEncodingFormat();//根据AudioRecord提供的api拿到最小缓存大小

int bufferSize =AudioRecord.getMinBufferSize(simpleRate, channelConfig, audioFormat);//创建Record对象

record = newAudioRecord(MediaRecorder.AudioSource.MIC, simpleRate, channelConfig, audioFormat, bufferSize);

recordThread= new Thread(() ->{byte[] buffer = new byte[bufferSize];

record.startRecording();

recording= true;while(recording) {int read = record.read(buffer, 0, bufferSize);//将数据回调到外部

if (read > 0 && callback != null) {

callback.onRecord(buffer, read);

}

}if (callback != null) {//len 为-1时表示结束

callback.onRecord(buffer, -1);

recording= false;

}//释放资源

release();

});

recordThread.start();

}

这个方法就是简单的采集音频数据,这个数据就是最原始的pcm数据。

拿到pcm数据以后,如果直接保存到文件是无法直接播放的,因为这只是一堆数据,没有任何格式说明,如果想让普通播放器可以播放,需要在文件中加入文件头,来告诉播放器这个数据的格式,这里是直接保存成wav格式的数据。下面就是加入wav格式文件头的方法

private static byte[] getWaveFileHeader(int totalDataLen, int sampleRate, int channelCount, intbits) {byte[] header = new byte[44];//RIFF/WAVE header

header[0] = 'R';

header[1] = 'I';

header[2] = 'F';

header[3] = 'F';int fileLength = totalDataLen + 36;

header[4] = (byte) (fileLength & 0xff);

header[5] = (byte) (fileLength >> 8 & 0xff);

header[6] = (byte) (fileLength >> 16 & 0xff);

header[7] = (byte) (fileLength >> 24 & 0xff);//WAVE

header[8] = 'W';

header[9] = 'A';

header[10] = 'V';

header[11] = 'E';//'fmt ' chunk

header[12] = 'f';

header[13] = 'm';

header[14] = 't';

header[15] = ' ';//4 bytes: size of 'fmt ' chunk

header[16] = 16;

header[17] = 0;

header[18] = 0;

header[19] = 0;//pcm format = 1

header[20] = 1;

header[21] = 0;

header[22] = (byte) channelCount;

header[23] = 0;

header[24] = (byte) (sampleRate & 0xff);

header[25] = (byte) (sampleRate >> 8 & 0xff);

header[26] = (byte) (sampleRate >> 16 & 0xff);

header[27] = (byte) (sampleRate >> 24 & 0xff);int byteRate = sampleRate * bits * channelCount / 8;

header[28] = (byte) (byteRate & 0xff);

header[29] = (byte) (byteRate >> 8 & 0xff);

header[30] = (byte) (byteRate >> 16 & 0xff);

header[31] = (byte) (byteRate >> 24 & 0xff);//block align

header[32] = (byte) (channelCount * bits / 8);

header[33] = 0;//bits per sample

header[34] = (byte) bits;

header[35] = 0;//data

header[36] = 'd';

header[37] = 'a';

header[38] = 't';

header[39] = 'a';

header[40] = (byte) (totalDataLen & 0xff);

header[41] = (byte) (totalDataLen >> 8 & 0xff);

header[42] = (byte) (totalDataLen >> 16 & 0xff);

header[43] = (byte) (totalDataLen >> 24 & 0xff);returnheader;

}

根据几个参数设置一下文件头,然后直接写入录音采集到的pcm数据,就可被正常播放了。wav文件头格式定义,可点击这里查看或自行百度。

如果想要通过AudioRecord录音直接保存到文件,可参考下面方法

public voidstartRecord(String filePath, AudioParams params, RecordCallback callback) {int channelCount =params.getChannelCount();int bits =params.getBits();final boolean storeFile = filePath != null && !filePath.isEmpty();

startRecord(params, (bytes, len)->{if(storeFile) {if (file == null) {

File f= newFile(filePath);if(f.exists()) {

f.delete();

}try{

file= new RandomAccessFile(f, "rw");

file.write(getWaveFileHeader(0, params.simpleRate, channelCount, bits));

}catch(IOException e) {

e.printStackTrace();

}

}if (len > 0) {try{

file.write(bytes,0, len);

}catch(IOException e) {

e.printStackTrace();

}

}else{try{//因为在前面已经写入头信息,所以这里要减去头信息才是数据的长度

int length = (int) file.length() - 44;

file.seek(0);

file.write(getWaveFileHeader(length, params.simpleRate, channelCount, bits));

file.close();

}catch(IOException e) {

e.printStackTrace();

}

}

}if (callback != null) {

callback.onRecord(bytes, len);

}

});

}

先通过RandomAccessFile创建文件,先写入文件头,由于暂时我们不知道会录多长,有多少pcm数据,长度先用0表示,等录音结束后,通过seek(int)方法重新写入文件头信息,也可以先把pcm数据保存到临时文件,然后再写入到一个新的文件中,这里就不举例说明了。

最后放入完整类的代码

packagecn.sskbskdrin.record.audio;importandroid.media.AudioRecord;importandroid.media.MediaRecorder;importjava.io.File;importjava.io.IOException;importjava.io.RandomAccessFile;/***@authorsskbskdrin

* @date 2019/April/3*/

public classAudioRecordManager {private AudioParams DEFAULT_FORMAT = new AudioParams(8000, 1, 16);privateAudioRecord record;privateThread recordThread;private boolean recording = false;privateRandomAccessFile file;public voidstartRecord(String filePath, RecordCallback callback) {

startRecord(filePath, DEFAULT_FORMAT, callback);

}public voidstartRecord(String filePath, AudioParams params, RecordCallback callback) {int channelCount =params.getChannelCount();int bits =params.getBits();final boolean storeFile = filePath != null && !filePath.isEmpty();

startRecord(params, (bytes, len)->{if(storeFile) {if (file == null) {

File f= newFile(filePath);if(f.exists()) {

f.delete();

}try{

file= new RandomAccessFile(f, "rw");

file.write(getWaveFileHeader(0, params.simpleRate, channelCount, bits));

}catch(IOException e) {

e.printStackTrace();

}

}if (len > 0) {try{

file.write(bytes,0, len);

}catch(IOException e) {

e.printStackTrace();

}

}else{try{//因为在前面已经写入头信息,所以这里要减去头信息才是数据的长度

int length = (int) file.length() - 44;

file.seek(0);

file.write(getWaveFileHeader(length, params.simpleRate, channelCount, bits));

file.close();

}catch(IOException e) {

e.printStackTrace();

}

}

}if (callback != null) {

callback.onRecord(bytes, len);

}

});

}public voidstartRecord(AudioParams params, RecordCallback callback) {int simpleRate =params.simpleRate;int channelConfig =params.getChannelConfig();int audioFormat =params.getEncodingFormat();//根据AudioRecord提供的api拿到最小缓存大小

int bufferSize =AudioRecord.getMinBufferSize(simpleRate, channelConfig, audioFormat);//创建Record对象

record = newAudioRecord(MediaRecorder.AudioSource.MIC, simpleRate, channelConfig, audioFormat, bufferSize);

recordThread= new Thread(() ->{byte[] buffer = new byte[bufferSize];

record.startRecording();

recording= true;while(recording) {int read = record.read(buffer, 0, bufferSize);//将数据回调到外部

if (read > 0 && callback != null) {

callback.onRecord(buffer, read);

}

}if (callback != null) {//len 为-1时表示结束

callback.onRecord(buffer, -1);

recording= false;

}//释放资源

release();

});

recordThread.start();

}public voidstop() {

recording= false;

}public voidrelease() {

recording= false;if (record != null) {

record.stop();

record.release();

}

record= null;

file= null;

recordThread= null;

}private static byte[] getWaveFileHeader(int totalDataLen, int sampleRate, int channelCount, intbits) {byte[] header = new byte[44];//RIFF/WAVE header

header[0] = 'R';

header[1] = 'I';

header[2] = 'F';

header[3] = 'F';int fileLength = totalDataLen + 36;

header[4] = (byte) (fileLength & 0xff);

header[5] = (byte) (fileLength >> 8 & 0xff);

header[6] = (byte) (fileLength >> 16 & 0xff);

header[7] = (byte) (fileLength >> 24 & 0xff);//WAVE

header[8] = 'W';

header[9] = 'A';

header[10] = 'V';

header[11] = 'E';//'fmt ' chunk

header[12] = 'f';

header[13] = 'm';

header[14] = 't';

header[15] = ' ';//4 bytes: size of 'fmt ' chunk

header[16] = 16;

header[17] = 0;

header[18] = 0;

header[19] = 0;//pcm format = 1

header[20] = 1;

header[21] = 0;

header[22] = (byte) channelCount;

header[23] = 0;

header[24] = (byte) (sampleRate & 0xff);

header[25] = (byte) (sampleRate >> 8 & 0xff);

header[26] = (byte) (sampleRate >> 16 & 0xff);

header[27] = (byte) (sampleRate >> 24 & 0xff);int byteRate = sampleRate * bits * channelCount / 8;

header[28] = (byte) (byteRate & 0xff);

header[29] = (byte) (byteRate >> 8 & 0xff);

header[30] = (byte) (byteRate >> 16 & 0xff);

header[31] = (byte) (byteRate >> 24 & 0xff);//block align

header[32] = (byte) (channelCount * bits / 8);

header[33] = 0;//bits per sample

header[34] = (byte) bits;

header[35] = 0;//data

header[36] = 'd';

header[37] = 'a';

header[38] = 't';

header[39] = 'a';

header[40] = (byte) (totalDataLen & 0xff);

header[41] = (byte) (totalDataLen >> 8 & 0xff);

header[42] = (byte) (totalDataLen >> 16 & 0xff);

header[43] = (byte) (totalDataLen >> 24 & 0xff);returnheader;

}public interfaceRecordCallback {/*** 数据回调

*

*@parambytes 数据

*@paramlen 数据有效长度,-1时表示数据结束*/

void onRecord(byte[] bytes, intlen);

}

}

View Code

如有不对之处还请评论指正

参考链接http://www.cnblogs.com/Amandaliu/archive/2013/02/04/2891604.html 在链接内容基础上修改了amr编码格式为aac编码格式 Android提供了两个API用于实现录音功能:android.media.AudioRecordandroid.media.MediaRecorder。 网上有很多谈论这两个类的资料。现在大致总结下: 1、AudioRecord 主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理(如会说话的汤姆猫、语音) 优点:语音的实时处理,可以用代码实现各种音频的封装 缺点:输出是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩 示例: 使用AudioRecord录音,并实现WAV格式封装。录音20s,输出的音频文件大概为3.5M左右(已写测试代码) 2、MediaRecorder 已经集成了录音、编码、压缩等,支持少量的录音音频格式,大概有.aac(API = 16) .amr .3gp 优点:大部分以及集成,直接调用相关接口即可,代码量小 缺点:无法实时处理音频;输出的音频格式不是很多,例如没有输出mp3格式文件 示例: 使用MediaRecorder类录音,输出amr格式文件录音20s,输出的音频文件大概为33K(已写测试代码) 3、音频格式比较 WAV格式:录音质量高,但是压缩率小,文件大 AAC格式:相对于mp3,AAC格式的音质更佳,文件更小;有损压缩;般苹果或者Android SDK4.1.2(API 16)及以上版本支持播放 AMR格式:压缩比比较大,但相对其他的压缩格式质量比较差,多用于人声,通话录音 至于常用的mp3格式,使用MediaRecorder没有该视频格式输出。些人的做法是使用AudioRecord录音,然后编码成wav格式,再转换成mp3格式 再贴上些测试工程。 功能描述: 1、点击“录音WAV文件”,开始录音录音完成后,生成文件/sdcard/FinalAudio.wav 2、点击“录音AMR文件”,开始录音录音完成后,生成文件/sdcard/FinalAudio.amr 3、点击“停止录音”,停止录音,并显示录音输出文件以及该文件大小。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值