一、AudioRecord入门知识
- PCM音频采集java程序对AudioRecord常见的调用方法大致如下
- 创建AudioRecord对象的参数分析
- 第一个参数:audioSource音频源
- 第二个参数:采样率sampleRateInHz(赫兹)
- 第三个参数:channelConfig 声道配置 描述音频声道的配置,例如左声道/右声道/前声道/后声道。
- 第四个参数audioFormat 音频格式 表示音频数据的格式。
- 给音频文件添加头部信息,并且转换格式成wav
- AudioRecord APP 调试
- 1. 录音保存时外部存储权限问题
- 2. 文件保存路径问题
- 3. 输入输出流文件操作报错:Attempt to invoke virtual method 'java.io.File android.content.Context,getExternalFilesDir(java.lang.String)' on a null object reference.
- 4. 录音暂停生成多个pcm文件产生文件覆盖问题:设置currentFileName变量保存文件名
- 5. 多个pcm文件打包转成wav格式, wav文件大小为0
- 6. 打包apk时报错: could not determine the dependencies of task’: app: lintVitalRelease’.
- 7. 打包时ERROR: Key was created with errors:
PCM音频采集java程序对AudioRecord常见的调用方法大致如下
// 对象创建:new
audioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);
其中,数据缓冲区大小bufferSizeInBytes可以使用方法 AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat) 获得。
// 启动音频采集
audioRecord.startRecording();
// 停止采集
audioRecord.stop();
即:new一个audioRecord之后就可以start()启动音频采集,通过stop()停止采集。
备注:AudioRecord没有提供暂停的API,目前所实现的暂停,就是不断的使用停止和再次录制,形成多个文件,然后再合并成一个文件并转码成wav。
// 释放资源
audioRecord.release();
创建AudioRecord对象的参数分析
(转自)
第一个参数:audioSource音频源
第二个参数:采样率sampleRateInHz(赫兹)
只能在4000到192000的范围内取值
在AudioFormat类里
public static final int SAMPLE_RATE_HZ_MIN = 4000; 最小4000
public static final int SAMPLE_RATE_HZ_MAX = 192000; 最大192000
第三个参数:channelConfig 声道配置 描述音频声道的配置,例如左声道/右声道/前声道/后声道。
在AudioFormat类里:
public static final int CHANNEL_IN_LEFT = 0x4; //左声道
public static final int CHANNEL_IN_RIGHT = 0x8; //右声道
public static final int CHANNEL_IN_FRONT = 0x10; //前声道
public static final int CHANNEL_IN_BACK = 0x20; //后声道
public static final int CHANNEL_IN_LEFT_PROCESSED = 0x40;
public static final int CHANNEL_IN_RIGHT_PROCESSED = 0x80;
public static final int CHANNEL_IN_FRONT_PROCESSED = 0x100;
public static final int CHANNEL_IN_BACK_PROCESSED = 0x200;
public static final int CHANNEL_IN_PRESSURE = 0x400;
public static final int CHANNEL_IN_X_AXIS = 0x800;
public static final int CHANNEL_IN_Y_AXIS = 0x1000;
public static final int CHANNEL_IN_Z_AXIS = 0x2000;
public static final int CHANNEL_IN_VOICE_UPLINK = 0x4000;
public static final int CHANNEL_IN_VOICE_DNLINK = 0x8000;
public static final int CHANNEL_IN_MONO = CHANNEL_IN_FRONT; //单声道
public static final int CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT); //立体声道(左右声道)
第四个参数audioFormat 音频格式 表示音频数据的格式。
注意!一般的手机设备可能只支持 16位PCM编码,如果其他的都会报错为坏值.
public static final int ENCODING_PCM_16BIT = 2; //16位PCM编码
public static final int ENCODING_PCM_8BIT = 3; //8位PCM编码
public static final int ENCODING_PCM_FLOAT = 4; //4位PCM编码
public static final int ENCODING_AC3 = 5;
public static final int ENCODING_E_AC3 = 6;
public static final int ENCODING_DTS = 7;
public static final int ENCODING_DTS_HD = 8;
//MP3编码 此格式可能会因为不设备不支持报错
public static final int ENCODING_MP3 = 9;
public static final int ENCODING_AAC_LC = 10;
public static final int ENCODING_AAC_HE_V1 = 11;
public static final int ENCODING_AAC_HE_V2 = 12;
给音频文件添加头部信息,并且转换格式成wav
偏移地址 | 命名· | 内容 |
01-03 | ChunkId | “RIFF” |
04-07 | ChunkSize | 下个地址开始到文件尾的总字节数(此Chunk的数据大小) |
08-11 | fccType | “WAVE” |
12-15 | SubChunkId1 | "fmt ",最后一位空格。 |
16-19 | SubChunkSize1 | 一般为16,表示fmt Chunk的数据块大小为16字节 |
20-21 | FormatTag | 1:表示是PCM 编码 |
22-23 | Channels 声道数 | 单声道为1,双声道为2 |
24-27 | Channels采样率 | |
28-31 | BytesPerSec | 码率 :采样率 * 采样位数 * 声道个数,bytePerSecond = sampleRate * (bitsPerSample / 8) * channels |
32-33 | BlockAlign | 每次采样的大小:位宽*声道数/8 |
34-35 | BitsPerSample | 位宽 |
36-39 | SubChunkId2 | “data” |
40-43 | SubChunkSize2 | 音频数据的长度 |
44-… | data | 音频数据 |
AudioRecord APP 调试
1. 录音保存时外部存储权限问题
Android5.0. 之后,将录音文件保存在emulator外部存储器时,需要动态获取外部存储器的存储权限:
- 在AndroidManifest.xml文件中添加如下内容:
<!--音频录制权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!--读取和写入存储权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- 在MainActivity.java中添加申请权限代码
// 动态申请外部存储读写权限
private void requestPermissions() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
//没有授权,编写申请权限代码
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
} else {
Log.d("AudioRecorder", "requestPermissions: 有写SD权限");
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
//没有授权,编写申请权限代码
ActivityCompat.requestPermissions(MainActivity.this, new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, 100);
} else {
Log.d("AudioRecorder", "requestMyPermissions: 有读SD权限");
}
}
2. 文件保存路径问题
// 过时方法
private static final String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/AudioRecordFile";
// 保存路径:
/storage/emulated/0/Android/data/packname/files/MUSIC/AudioRecordFile
private static String filePath;
......
private String getFilePath(Context context) {
filePath = context.getExternalFilesDir(Environment.DIRECTORY_MUSIC).getAbsolutePath() + "/AudioRecordFile/";
return filePath;
}
顺便贴出各种保存路径对应的方法:
//路径:./storage/emulator/0/Android/data/类名/DCIM/ DIR_NAME
context.getExternalFilesDir(Environment.DIRECTORY_DCIM).getAbsolutePath() + File.separator + DIR_NAME;
1). Environment.getDataDirectory() = /data
这个方法是获取内部存储的根路径
2). getFilesDir().getAbsolutePath() = /data/user/0/packname/files
这个方法是获取某个应用在内部存储中的files路径
3). getCacheDir().getAbsolutePath() = /data/user/0/packname/cache
这个方法是获取某个应用在内部存储中的cache路径
4). getDir(“myFile”, MODE_PRIVATE).getAbsolutePath() = /data/user/0/packname/app_myFile
这个方法是获取某个应用在内部存储中的自定义路径
方法2,3,4的路径中都带有包名,说明他们是属于某个应用
…………………………………………………………………………………………
5). Environment.getExternalStorageDirectory().getAbsolutePath() = /storage/emulated/0
这个方法是获取外部存储的根路径
6)、Environment.getExternalStoragePublicDirectory(“”).getAbsolutePath() = /storage/emulated/0
这个方法是获取外部存储的根路径
7). getExternalFilesDir(“”).getAbsolutePath() = /storage/emulated/0/Android/data/packname/files
这个方法是获取某个应用在外部存储中的files路径
8). getExternalCacheDir().getAbsolutePath() = /storage/emulated/0/Android/data/packname/cache
这个方法是获取某个应用在外部存储中的cache路径
3. 输入输出流文件操作报错:Attempt to invoke virtual method ‘java.io.File android.content.Context,getExternalFilesDir(java.lang.String)’ on a null object reference.
Context context = this;
getFilePath()方法调用获取文件路径:
private String getFilePath(Context context) {
filePath = context.getExternalFilesDir(Environment.DIRECTORY_MUSIC).getAbsolutePath() + "/AudioRecordFile/";
return filePath;
}
4. 录音暂停生成多个pcm文件产生文件覆盖问题:设置currentFileName变量保存文件名
private static String fileName = "AudioRecordTest"; //文件名
......
private void writeDatatoFile() {
......
String currentName = fileName;
// 设置currentName,在文件名后面加一个数字,防止暂停录音产生的文件覆盖问题
if(status == Status.STATUS_PAUSE) {
currentName += fileNameList.size();
}
currentName += ".pcm";
fileNameList.add(currentName);
// 向列表添加PCM文件路径+文件名
pcmFileList.add(filePath + currentName);
......
}
5. 多个pcm文件打包转成wav格式, wav文件大小为0
将保存文件(文件路径+文件名)的列表设置为全局变量,每次录音产生pcm文件时都将完整文件路径+文件名加入列表(见问题4代码)
6. 打包apk时报错: could not determine the dependencies of task’: app: lintVitalRelease’.
在build.gradle(:app)中加入代码:
lintOptions {
checkReleaseBuilds false
abortOnError false
}
7. 打包时ERROR: Key was created with errors:
Warning:
JKS 秘钥库使用专用格式。建议使用 “keytool -importkeystore -srckeystore home/user/AndroidStudioProject/AudioRecordDemo/apk/AudioRecoder.jks -destkeystore home/user/AndroidStudioProject/AudioRecordDemo/apk/AudioRecoder.jks -deststoretype pkcs12” 迁移到行业标准格式PKCS12.
参考:android打包生成APK