Android AudioRecord每帧20ms数据量持续采集
前沿
需求:采集音频数据,按照每帧20ms的数据持续输出.
很显然需要用到AudioRecord
,可以输出未处理的裸PCM
数据.
PCM
:PCM(Pulse Code Modulation)
也被称为脉冲编码调制。PCM
音频数据是未经压缩的音频采样数据裸流,它是由模拟信号经过采样、量化、编码转换成的标准的数字音频数据。
如果需要知道详细的 ====> www.baidu.com
Android
音频相关API
-
AudioRecord
- 官网地址:https://developer.android.com/reference/android/media/AudioRecord?hl=en
- 主要用于音频采集
- 优势:可以实时获取音频数据及时处理.
- 缺点:由于获取是裸数据,需要加上复杂的处理才可以播放.
-
MediaRecorder
- 官网地址:https://developer.android.com/reference/android/media/MediaRecorder?hl=en
- 主要用于音频采集
- 优势:官方封装音频API,简单粗暴
- 缺点:无法实时处理
-
MediaPlayer
- 官方地址:https://developer.android.com/reference/android/media/MediaPlayer?hl=en
- 主要用于音频播放
- 优点:可以播放MP3、AAC、AMR、WAV等多种音频格式文件.
- 缺点:无法同时播放多个音频文件
-
AudioTrack
- 官网地址:https://developer.android.com/reference/android/media/AudioTrack?hl=en
- 主要用于音频播放
- 优点:时间控制精度高.
- 缺点:只能播放解码的PCM数据流.
注意:MediaPlayer
在播放音频时
1、在framework
层还是会创建AudioTrack
2、解码后的PCM
数流传递给AudioTrack
3、AudioFlinger
进行混音
4、传递音频给硬件播放出来
AudioRecord
基础知识
- 采样率
“音频采样率” 是指录音设备在一秒钟内对声音信号的采样次数。常用的音频采样频率有:8kHz、11.025kHz、22.05kHz、16kHz、37.8kHz、44.1kHz、48kHz、96kHz、192kHz等。
通俗理解:每秒录取声音的次数。
注意:手机无法支持所有的采样率,Android
中能保证的是44.1KHz全支持.其他的有可能在部分手机中不支持.
- 量化精度(采样位数)
“采样位数”越大表示的值的范围也就越大 "采样位数"可以理解为采集卡处理声音的解析度。这个数值越大,解析度就越高,录制和回放的声音就越真实。连续的模拟信号按一定的采样频率经数码脉冲取样后,每一个离散的脉冲信号被以一定的量化精度量化成一串二进制编码流,这串编码流的位数即为采样位数,也称为"量化精度"。常见的位数16bit和32bit.
通俗理解:每秒录取声音的精度,就像画面的分辨率,越高声音越真实
* 声道数
声道数分别有:单声道的声道数为1个声道;双声道的声道数为2个声道;立体声道的声道数默认为2个声道;立体声道(4声道)的声道数为4个声道。
常见使用的是:单声道(MONO) 和 双声道 (STEREO)
通俗理解:声道数表示录制或者播放音频的声音源.
- 码率
比特率(又叫做位速率或者码率)是指每秒传送的比特(bit)数。单位为bps(Bit Per Second),比特率越高,传送的数据越大。比特率表示经过编码(压缩)后的音、视频数据每秒钟需要用多少个比特来表示,而比特就是二进制里面最小的单位,要么是0,要么是1。比特率与音、视频压缩的关系,简单的说就是比特率越高,音、视频的质量就越好,但编码后的文件就越大;如果比特率越少则情况刚好相反。 若作为一种数字音乐压缩效率的参考性指标,比特率表示单位时间(1秒)内传送的比特数bps(bit per second,位/秒)的速度。通常使用kbps(通俗地讲就是每秒钟1000比特)作为单位。CD中的数字音乐比特率为1411.2kbps(也就是记录1秒钟的cd音乐,需要1411.2×1000比特的数据),音乐文件的BIT RATE高是意味着在单位时间(1秒)内需要处理的数据量(BIT)多,也就是音乐文件的音质好的意思。但是,BIT RATE高时文件大小变大,会占据很多的内存容量,音乐文件最常用的bit rate是128kbps,MP3文件可以使用的一般是8-320kbps,但不同MP3机在这方面支持的范围不一样,大部分的是32-256Kbps,这个指数当然是越广越好了,不过320Kbps是暂时最高等级了。<1B字节= 8 bit位 / 1024B = 1M> (那一秒的CD音乐需要 1411.2 / 8 = 176.4KB/s的空间,那四分钟的CD音乐需要 (1411.2kbps * 4 * 60)/ 8 / 1024 = 41.34373M)
码率计算公式
基本的算法是:【码率】(kbps)=【文件大小】(字节)X8/【时间】(秒)× 1000
音频文件专用算法:【比特率】(kbps)=【量化采样点】(kHz)×【位深】(bit/采样点)×【声道数量】(一般为2)
- 数据源
/**默认声音**/
public static final int DEFAULT = 0;
/**麦克风声音*/
public static final int MIC = 1;
/**通话上行声音*/
public static final int VOICE_UPLINK = 2;
/**通话下行声音*/
public static final int VOICE_DOWNLINK = 3;
/**通话上下行声音*/
public static final int VOICE_CALL = 4;
/**根据摄像头转向选择麦克风*/
public static final int CAMCORDER = 5;
/**对麦克风声音进行声音识别,然后进行录制*/
public static final int VOICE_RECOGNITION = 6;
/**对麦克风中类似ip通话的交流声音进行识别,默认会开启回声消除和自动增益*/
public static final int VOICE_COMMUNICATION = 7;
/**录制系统内置声音*/
public static final int REMOTE_SUBMIX = 8;
需求:
通过上述的概念,如果我们需要每帧数据中有20ms的数据量,20ms的数据量多大?
[数据] = [采样率(bit/s)] * 0.02 (s) * [通道数] * [位深] / 8;
上述就是我一个帧数据需要发送的大小.
代码
- 计算缓存区大小
/**
* `AudioRecord`内部需要设置一个合适的缓存区大小
* 麦克风采集到的音频数据先放置在缓冲区里面,从这个缓冲区里面读取数据,从而获取到麦克风录制的音频数据。
* 1、采样率
* 2、通道
* 3、位深
**/
public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)
- 创建
AudioRecord
/**
* 1、数据源
* 2、采样率
* 3、通道
* 4、位深
* 5、缓存区大小
**/
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)
- 读取缓存区数据
/**
* 从音频硬件中读取音频数据以记录到字节数组中。
* @param 写入所记录的音频数据的数组.
* @param audioData中的offsetInBytes索引,从该索引中以字节表示数据.
* @param 请求的字节数.
**/
public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes)
- 完整的代码
public class TRTCAudioRunning implements Runnable {
private static final String TAG = "TRTCAudioRunning";
public static final int CHANNEL_SINGLE = 1;
public static final int CHANNEL_DOUBLE = 2;
public static final int BIT_16 = 16;
public static final int BIT_8 = 8;
public static final int NO_ACE = 0;
public static final int DEFAULT_ACE = 1;
private int rate = 48000;
private int channel = CHANNEL_SINGLE;
private int bit = BIT_16;
private int mACEType = NO_ACE;
private AudioRecord mAudioRecord;
private Context mContext;
private Thread mThread;
private boolean isRecording = false;
private byte[] mData = null;
private WeakReference<TRTCAudioListener> mTRTCAudioListenerWeakReference;
private static final TRTCAudioRunning ourInstance = new TRTCAudioRunning();
public static TRTCAudioRunning getInstance() {
return ourInstance;
}
private TRTCAudioRunning() {
}
public void startAudio(Context context, int rate, int channel, int bit, int ACEType) {
stopThread();
this.mContext = context;
this.rate = rate;
this.channel = channel;
this.bit = bit;
this.mACEType = ACEType;
isRecording = true;
mThread = new Thread(this, "AudioRecord");
mThread.start();
}
@Override
public void run() {
if (!isRecording) {
Log.d(TAG, "record is starting");
return;
}
onAudioStart();
initRecord();
int recordTime = 0;
int currentSize = 0;
while (isRecording && !Thread.interrupted() && mAudioRecord != null && recordTime <= 5) {
int size = mAudioRecord.read(mData, currentSize, mData.length - currentSize);
if (size != mData.length - currentSize) {
if (size <= 0) {
Log.e(TAG, "read pcm error");
recordTime++;
} else {
currentSize += size;
}
} else {
currentSize = 0;
recordTime = 0;
sendData(mData, mData.length, TXCTimeUtil.getTimeTick());
}
}
stopAudio();
if (recordTime > 5) {
onError(-1, "audio read data error");
} else {
onAudioStop();
}
}
private void initRecord() {
int rate = this.rate;
int channel = this.channel;
int bit = this.bit;
int ACEType = this.mACEType;
int channelConfig = channel != 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO;
int audioFormat = (bit == 16) ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
int bufferSize = AudioRecord.getMinBufferSize(rate, channelConfig, audioFormat);
try {
if (ACEType == 1) {
Log.d(TAG, "type: aec");
if (mContext != null) {
AudioManager var8 = (AudioManager) this.mContext.getSystemService(Context.AUDIO_SERVICE);
var8.setMode(AudioManager.MODE_IN_COMMUNICATION);
}
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION,
rate, channelConfig, audioFormat, bufferSize * 2);
} else {
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
rate, channelConfig, audioFormat, bufferSize * 2);
}
} catch (Exception e) {
Log.d(TAG, "init record error:" + e.toString());
}
if (mAudioRecord != null && mAudioRecord.getState() == AudioRecord.READ_NON_BLOCKING) {
int buffer = (int) (this.rate * 0.02 * channel * bit) / 8;
mData = new byte[buffer];
if (mAudioRecord != null) {
try {
mAudioRecord.startRecording();
} catch (Exception e) {
Log.e(TAG, "start record is error" + e.toString());
return;
}
}
} else {
stopAudio();
onError(-1, "audio record: initialize the audio failed");
}
}
private void sendData(byte[] data, int length, long timeTick) {
TRTCAudioListener trtcAudioListener = null;
synchronized (this) {
if (mTRTCAudioListenerWeakReference != null) {
trtcAudioListener = mTRTCAudioListenerWeakReference.get();
}
}
if (trtcAudioListener != null) {
trtcAudioListener.onAudioRecordPCM(data, length, timeTick);
}
}
private void onError(int code, String errMsg) {
TRTCAudioListener trtcAudioListener = null;
synchronized (this) {
if (mTRTCAudioListenerWeakReference != null) {
trtcAudioListener = mTRTCAudioListenerWeakReference.get();
}
}
if (trtcAudioListener != null) {
trtcAudioListener.onAudioRecordError(code, errMsg);
}
}
private void onAudioStart() {
TRTCAudioListener trtcAudioListener = null;
synchronized (this) {
if (mTRTCAudioListenerWeakReference != null) {
trtcAudioListener = mTRTCAudioListenerWeakReference.get();
}
}
if (trtcAudioListener != null) {
trtcAudioListener.onAudioRecordStart();
}
}
private void onAudioStop() {
TRTCAudioListener trtcAudioListener = null;
synchronized (this) {
if (mTRTCAudioListenerWeakReference != null) {
trtcAudioListener = mTRTCAudioListenerWeakReference.get();
}
}
if (trtcAudioListener != null) {
trtcAudioListener.onAudioRecordStop();
}
}
private void stopAudio() {
if (mAudioRecord != null) {
Log.d(TAG, "stop audio");
try {
mAudioRecord.setRecordPositionUpdateListener(null);
mAudioRecord.stop();
mAudioRecord.release();
} catch (Exception e) {
Log.e(TAG, "stop audio errir:" + e.toString());
}
mAudioRecord = null;
mData = null;
}
}
public void stopThread() {
isRecording = false;
if (mThread != null && mThread.isAlive() && Thread.currentThread().getId() != mThread.getId()) {
try {
mThread.join();
} catch (InterruptedException e) {
Log.d(TAG, "record stop is error:" + e.toString());
}
}
mThread = null;
}
public void setTRTCAudioListenerWeakReference(TRTCAudioListener TRTCAudioListenerWeakReference) {
mTRTCAudioListenerWeakReference = new WeakReference<>(TRTCAudioListenerWeakReference);
}
public int getRate() {
return rate;
}
public int getChannel() {
return channel;
}
public int getBit() {
return bit;
}
public int getACEType() {
return mACEType;
}
}
参考链接
https://www.jianshu.com/p/1f78c4211ab7
https://www.jianshu.com/p/632dce664c3d
https://www.cnblogs.com/CoderTian/p/6657844.html