一、概述
AudioRecord 是 Android 平台比较重要的类,也是 Java 接口中比较偏底层(平台)的接口,可以通过它从平台的音频输入硬件来获取原始音频 PCM 数据。它的工作原理是要需要通过应用侧轮询调用 read 接口来驱动,每调用一次,系统就会从硬件采集到的数据填充一次,至于传递数据的载体可以是 byte[] 数组 或者 ByteBuffer 。
二、所需权限
应用程序创建 AudioRecord 实例需要在AndroidManifest文件赋予 Manifest.permission.RECORD_AUDIO 权限。没有赋予这个权限,如果使用 AudioRecord.Builder 来构建的话,执行build()函数会抛出 UnsupportedOperationException 异常,即使您捕获了此异常,在获取 AudioRecord 状态 的时候,也是未初始化状态(STATE_UNINITIALIZED)。
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
在 API 23 (Android 6.0) 之后,为了保护用户隐私,对于一些敏感权限(比如录音权限),应用需要在运行时动态申请。示例代码如下:
private static final int PERMISSION_REQUEST_CODE = 1;
// step1: 检查是否有录音权限
private boolean checkPermission() {
int result = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO);
return result == PackageManager.PERMISSION_GRANTED;
}
// step2: 请求录音权限
private void requestPermission() {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, PERMISSION_REQUEST_CODE);
}
// step3: 处理权限请求结果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限被授予,可以进行录音
startRecording();
} else {
// 权限被拒绝,无法进行录音
Toast.makeText(this, "录音权限被拒绝", Toast.LENGTH_SHORT).show();
}
}
}
三、初始化工作
3.1 确定硬件 buffer size
上文提及到,在创建 AudioRecord 对象的时候会初始化与其关联的 buffer , buffer size 是在构造它的时候传递过去的。所以第一步要做的事情就是确定 buffer size ,一般来说,设置硬件 buffer size 最大等于每一帧音频帧的 size ,这样处理起来比较方便。
音频帧大小 = 通道数 ∗ 每个采样点所占字节数 ∗ 采样率 / 帧长 音频帧大小 = 通道数 * 每个采样点所占字节数 * 采样率 / 帧长 音频帧大小=通道数∗每个采样点所占字节数∗采样率/帧长
举例说明:使用场景为双通道,每个采样点需要16bit,采样率48000,帧长(采样时间): 100ms,那么最终一帧数据的大小为:
//帧大小
final int bytesPerFrame = 2 * (16/8) * 48000 / 100; // 1920
//创建buffer
ByteBuffer buffer = ByteBuffer.allocateDirect(bytesPerFrame);
还有一个重要的步骤,就是获取硬件所支持的最小缓冲区大小 minHwBufferSize。因为我们想要设置缓冲区大小 ≥ 实际场景需要的音频帧大小,那如果音频帧大小要小于 minHwBufferSize 呢,所以我们需要做一些处理。如以下代码:
//获取硬件所支持的最小缓冲区大小
int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);
if(minBufferSize < buffer.capacity()){
minBufferSize = buffer.capacity();
}
//这样设置采集和处理音频数据比较平滑
int bufferSizeInBytes = Math.max(BUFFER_SIZE_FACTOR * minBufferSize, byteBuffer.capacity()); // BUFFER_SIZE_FACTOR 一般为2
3.2 构建 AudioRecord 对象
一共有两种方式可以构建出 AudioRecord 对象,分别是通过构造方法和构造者模式。
构造方法
// 1. 通过构造方法创建
AudioRecord audioRecord = null;
try {
audioRecord = new AudioRecord(AudioSource.VOICE_COMMUNICATION, 48000, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
if(audioRecord == null || audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
//构造失败
}
构建者模式
//2. 通过构造者模式创建
AudioRecord audioRecord = null;
try {
recorder = new AudioRecord.Builder()
.setAudioSource(AudioSource.VOICE_COMMUNICATION)
.setAudioFormat(new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(48000)
.setChannelMask(AudioFormat.CHANNEL_IN_STEREO)
.build())
.setBufferSizeInBytes(bufferSizeInBytes)
.build();
} catch (UnsupportedOperationException e) {
e.printStackTrace();
}
if(audioRecord == null || audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
//构造失败
}
我们需要关注的是构建 AudioRecord 所需要的必要参数。
audioSource
音频源,定义了音频信号的默认输入设备和采集配置。具体常量见 AudioSource 这个类。
sampleRateInHz
采样率(单位:赫兹Hz),通常设置 44100Hz 可确保能够在几乎所有设备上都能正常工作,像其他的 22050Hz, 16000Hz, 11025Hz 也许只能在部分的设备上工作。
channelConfig
描述音频通道的配置,一般配置单通道(AudioFormat.CHANNEL_IN_MONO) 和双声道 (AudioFormat.CHANNEL_IN_STEROR)。
audioFormat
音频格式,这里表示采集音频数据的精度,参数可选:AudioFormat.ENCODING_PCM_8BIT,AudioFormat.ENCODING_PCM_16BIT,AudioFormat.ENCODING_PCM_FLOAT。
bufferSizeInBytes
缓存 buffer 的大小(单位: 字节),上文 3.1 章节已提及过。
四、采集音频数据
- 开启音频采集
通过调用 startRecording 接口来控制硬件开启采集状态,可以通过通过 AudioRecord 对象内部的 recordingState 状态来判断是否开启成功。
var result = ErrorCode.SV_NO_ERROR
try {
audioRecord?.startRecording()
} catch (err: IllegalStateException) {
err.printStackTrace()
result = ErrorCode.SV_START_ERROR
}
if(audioRecord!!.recordingState != AudioRecord.RECORDSTATE_RECORDING) {
//通过 recordingState 状态来判断是否开启成功
}
- 创建采集线程
captureThread = AudioCaptureThread()
captureThread?.start() ?: { result = ErrorCode.SV_START_ERROR }
- 读取音频数据
其实 read 函数这里有个细节的地方,来看看 read 函数的定义:
public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes);
public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) {
return read(audioData, offsetInBytes, sizeInBytes, READ_BLOCKING);
}
@IntDef({
READ_BLOCKING,
READ_NON_BLOCKING
})
@Retention(RetentionPolicy.SOURCE)
public @interface ReadMode {}
public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes, @ReadMode int readMode)
如果不带参数 readMode 的情况,默认走的是阻塞模式,调用 read 会一直等待系统那边填充结束后才返回给你。所以走阻塞的情况一般需要创建一条采集线程,以防止阻塞住调用线程。具体怎么选择?一般来说,如果采集的 buffer size 比较小,则选择阻塞模式。buffer size 比较大,可以选择非阻塞,因为非阻塞模式,系统并没有提供填充结束的回调通知,还需要开发者自行的计算采集间隔时间。
while (keepAlive) {
val readSize = audioRecord!!.read(byteBuffer, byteBuffer.capacity())
if(readSize == byteBuffer.capacity()) {
val data = Arrays.copyOf(byteBuffer.array(), byteBuffer.capacity())
fos?.write(data)
} else {
// read failed.
}
}
五、停止采集
-
停止采集,停止采集线程,将 keepAlive 变量置为 false 即可。
-
调用 stop 接口,关闭硬件采集。
runCatching { audioRecord?.stop() }
六、其他接口
上文主要讲述了使用 AudioRecord 接口的整体调用姿势,下面来讲讲 AudioRecord 的几个平时开发很少用,却很实用的接口。
6.1 分段录制
相关配套的接口如下,其中 markerPosition 和 period 区别在于是否需要周期性回调。这类接口的使用场景一般:分段录制,采集进度回调显示等。
// 设置标记帧,采集了 markerInFrames 帧数就回调 OnRecordPositionUpdateListener#onMarkerReached
public int setNotificationMarkerPosition(int markerInFrames)
// 设置一个采集周期,采集到的音频帧等于 periodInFrames 就触发回调 OnRecordPositionUpdateListener#onPeriodicNotification
public int setPositionNotificationPeriod(int periodInFrames);
public interface OnRecordPositionUpdateListener {
/**
* Called on the listener to notify it that the previously set marker has been reached
* by the recording head.
*/
void onMarkerReached(AudioRecord recorder);
/**
* Called on the listener to periodically notify it that the record head has reached
* a multiple of the notification period.
*/
void onPeriodicNotification(AudioRecord recorder);
}
6.2 音频路由相关
设置音频路由偏好
// 指定采集的音频输入设备偏好
public boolean setPreferredDevice(AudioDeviceInfo deviceInfo);
监听音频路由
// 添加音频路由监听
public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener, android.os.Handler handler);
// 取消音频路由监听
public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener);
public interface OnRoutingChangedListener {
public void onRoutingChanged(AudioRouting router);
}
最后
本文比较详细的讲解了 AudioRecord 的运行机制以及使用方面的介绍,下面是 github 上的示例代码链接。
https://github.com/Sound-Vision/audio_record
如果您觉得以上内容对您有所帮助的话,可以关注下我们运营的公众号 声知视界,会定期的推送 音视频技术、移动端技术 为主轴的 科普类、基础知识类、行业资讯类等相关文章。
标签:int,音频,AudioRecord,采集,AudioFormat,Android,public From: https://blog.csdn.net/AVExplorer/article/details/143653098