一、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:


android audiorecord录音 android audiorecorder_外部存储

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外部存储器时,需要动态获取外部存储器的存储权限:

  1. 在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" />
  1. 在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