目录

  1. 音频采集API AudioRecord和MediaRecorder介绍

  2. PCM的介绍

  3. AudioRecord的使用(构造、开始录制、停止录制、其他细节点)

  4. ffplay播放pcm

  5. pcm转为wav

  6. 小结

一、音频采集API AudioRecord和MediaRecorder

AndroidSDK提供了两套音频录制的API,AudioRecord和MediaRecorder。其中MediaRecorder是更加上层的API,他可以直接对手机麦克风录入的音频数据进行压缩编码(比如 mp3),并存储为文件。而AudioRecord更底层些,让开发者能够得到内存中的PCM音频流数据,适用于需要对音频做进一步处理(比如,音效,第三方编码库进行压缩,或者网络传输等)。
MediaRecorder内部也是调用了AudioRecord与Framework层的AudioFlinger进行交互。

二、PCM的介绍

PCM(Pluse code Modulation 脉冲编码调制)是一种无压缩的音频格式。模拟音频信号经过A/D模数转换直接生成的二进制序列时,音频源数据(raw)。

声音经过麦克风,转换为一连串的电压信号,然后经过调制编码把电压信号转为数字信号。PCM格式使用三个参数来表示声音:采样率(sampleRate)、采样位数(audioFormat,有8位和16位,分大小端编码)以及声道数(channels)

采样频率:每秒取得声音样本的次数。采样频率越高,声音的质量也就越好,还原的声音就越真实,但同时它占用的资源越多。
采样位数:即采样值(将采样样本幅度量化的容量),用来衡量声音波动变化的一个参数。也能够说是声卡的分辨率。它的数值越大,分辨率就越高,所发出声音的能力越强。
声道数:有单声道和立体声之分。单声道的声音仅仅能使用一个喇叭发声,立体声的 PCM 能够使两个喇叭同时发声,更能感受到空间效果

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

三、AudioRecord的使用(构造、开始录制写入pcm文件,状态机)

3.1 构造AudioRecord

我们首先通过AudioRecord的构造器,来了解下需要的参数

public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes)

来看下构造方法中的5个参数

音视频开发知识点导图:https://docs.qq.com/doc/DQm1VTHBlQmdmTlN2
audioSource:音频输入源,比如有麦克风等,通过MediaRecord.AudioSource获取。
sampleRateInHz:音频采样率,常见的采样率为44100即44.1KHZ
channelConfig:音频录制时的声道,分为单声道和立体声道,在AudioFormat中定义。
audioFormat:音频格式
bufferSizeInBytes:音频缓冲区大小,不同手机厂商有不同的实现(比如 我的一加手机该值为3584字节),可以通过下面的方法获取。
static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)

具体使用如下:
private void createAudioRecord() {
sampleRateInHz = 44100;
channelConfig = AudioFormat.CHANNEL_IN_MONO;
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig, audioFormat, bufferSize);

    //audioRecord的状态int state = audioRecord.getState();Log.d(TAG, "createAudioRecord: state=" + state + " bufferSize=" + bufferSize);if (AudioRecord.STATE_INITIALIZED != state) {new Exception("AudioRecord无法初始化,请检查录制权限或者是否其他app没有释放录音器");}
}private void initPCMFile() {pcmFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "raw.pcm");Log.d(TAG, "initPCMFile: pcmFile=" + pcmFile);}

3.2 开始录制以及读取录制数到pcm文件

在录音过程中,应用所需要做的就是通过 read(byte[], int, int), read(short[], int, int) or read(ByteBuffer, int) 不断地获取数据

private void startRecord() {

  if (pcmFile.exists()) {pcmFile.delete();}isRecording = true;final byte[] buffer = new byte[bufferSize];audioRecord.startRecording();new Thread(new Runnable() {@Overridepublic void run() {FileOutputStream fileOutputStream = null;try {fileOutputStream = new FileOutputStream(pcmFile);if (fileOutputStream != null) {while (isRecording) {int readStatus = audioRecord.read(buffer, 0, bufferSize);Log.d(TAG, "run: readStatus=" + readStatus);fileOutputStream.write(buffer);}}} catch (IOException e) {e.printStackTrace();Log.e(TAG, "run: ", e);} finally {if (fileOutputStream != null) {try {fileOutputStream.close();} catch (IOException e) {e.printStackTrace();}}}}}).start();
}

3.3 停止录制释放资源

private void stopRecord() {isRecording = false;if (audioRecord != null) {audioRecord.stop();}
}   protected void onDestroy() {super.onDestroy();if (audioRecord != null) {audioRecord.release();}audioRecord = null;
}

3.4 细节点(搞清楚为什么要在子线程开启读取录制数据)

audioRecord的read操作是一个阻塞的操作(READ_BLOCKING),读取到buffersize大小后才释放.所以我们看到步骤三录制时,开启一个子线程进行数据的读取和写入pcm文件。
public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) {
return read(audioData, offsetInBytes, sizeInBytes, READ_BLOCKING);
}

AudioRecord的状态两个属性, 录制状态和初始化状态,
设置录制状态时首先片段是否已经初始化

public static final int STATE_UNINITIALIZED = 0;public static final int STATE_INITIALIZED   = 1;public static final int RECORDSTATE_STOPPED = 1;  public static final int RECORDSTATE_RECORDING = 3;/*** Lock to make sure mRecordingState updates are reflecting the actual state of the object.*/
private final Object mRecordingStateLock = new Object();

四、 ffplay播放pcm

PCM是源数据,没有封装格式头,一般播放器无法解析播放。可以采用ffplay进行播放或者对其进行转为为其他封装格式的文件(比如wav)进行播放

ffplay -f s16le -ar 44100 -ac 1 -i raw.pcm
参数解释

-f s16le: 即 设置音频格式(format)为有符号16位小端格式(signed 16 bits little endian)对应AudioFormat#ENCODING_PCM_16BIT
-ar:即audiorate 音频采样频率 ,对应AudioRecord构造方法中参数sampleRateInHz
-ac:即audiochannels ,采用的声道,单声为1,多声道为2
-i raw.pcm :即要播放的pcm源

另外也可以通过ffplay直接直接把pcm转为wav,然后播放wav
ffmpeg -y -f s16le -ar 44100 -ac 1 -i raw.pcm out.wav
参数解释

-y:表示无需询问,直接覆盖输出文件
-i raw.pcm out.wav : raw.pcm是输入的文件,out.raw是输出的文件

然后可以直接使用ffplay进行wav的播放
ffplay out.wav

五、pcm转为wav

第四步我们看到了采用ffmpeg把pcm转为wav格式,那么有没有其他方式呐?
我们来看下wav的百科:音视频开发知识点导图https://docs.qq.com/doc/DQm1VTHBlQmdmTlN2
. WAV是最常见的声音文件格式之一,是微软公司专门为Windows开发的一种标准数字音频文件,该文件能记录各种单声道或立体声的声音信息,并能保证声音不失真。它符合资源互换文件格式(RIFF)规范
它有两个特点 1. 无压缩;2. 符合RIFF规范。
那么我们可以采用给PCM文件写入wav的头,符合wav格式。具体实现

private void convertPcmToWav() {
wavFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC) , "convert.wav");
if (wavFile.exists()) {
wavFile.delete();
}

FileInputStream fileInputStream = null;FileOutputStream fileOutputStream = null;try {fileInputStream = new FileInputStream(pcmFile);fileOutputStream = new FileOutputStream(wavFile);long audioByteLen = fileInputStream.getChannel().size();long wavByteLen = audioByteLen + 36;addWavHeader(fileOutputStream, audioByteLen, wavByteLen, sampleRateInHz,channelConfig, audioFormat);byte[] buffer = new byte[bufferSize];while (fileInputStream.read(buffer) != -1) {fileOutputStream.write(buffer);}} catch (IOException e) {e.printStackTrace();} finally {try {if (fileInputStream != null) {fileInputStream.close();}if (fileOutputStream != null) {fileOutputStream.close();}} catch (IOException e) {e.printStackTrace();}}}private void addWavHeader(FileOutputStream fileOutputStream, long audioByteLen, long wavByteLen, int sampleRateInHz, int channelConfig, int audioFormat) {byte[] header = new byte[44];// RIFF/WAVE header chunkheader[0] = 'R';header[1] = 'I';header[2] = 'F';header[3] = 'F';header[4] = (byte) (wavByteLen & 0xff);header[5] = (byte) ((wavByteLen >> 8) & 0xff);header[6] = (byte) ((wavByteLen >> 16) & 0xff);header[7] = (byte) ((wavByteLen >> 24) & 0xff);//WAVEheader[8] = 'W';header[9] = 'A';header[10] = 'V';header[11] = 'E';// 'fmt ' chunk 4 个字节header[12] = 'f';header[13] = 'm';header[14] = 't';header[15] = ' ';// 4 bytes: size of 'fmt ' chunk(格式信息数据的大小 header[20] ~ header[35])header[16] = 16;header[17] = 0;header[18] = 0;header[19] = 0;// format = 1 编码方式header[20] = 1;header[21] = 0;// 声道数目int channelSize = channelConfig == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;header[22] = (byte) channelSize;header[23] = 0;// 采样频率header[24] = (byte) (sampleRateInHz & 0xff);header[25] = (byte) ((sampleRateInHz >> 8) & 0xff);header[26] = (byte) ((sampleRateInHz >> 16) & 0xff);header[27] = (byte) ((sampleRateInHz >> 24) & 0xff);// 每秒传输速率long byteRate = audioFormat * sampleRateInHz * channelSize;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) (2 * 16 / 8);header[33] = 0;// bits per sample 每个采样需要的 bit 数header[34] = 16;header[35] = 0;//data chunkheader[36] = 'd';header[37] = 'a';header[38] = 't';header[39] = 'a';// pcm字节数header[40] = (byte) (audioByteLen & 0xff);header[41] = (byte) ((audioByteLen >> 8) & 0xff);header[42] = (byte) ((audioByteLen >> 16) & 0xff);header[43] = (byte) ((audioByteLen >> 24) & 0xff);try {fileOutputStream.write(header, 0, 44);} catch (IOException e) {e.printStackTrace();}
}

通过Beyond Compare对比pcm和wav发现,正如我们代码所示,在pcm数据源前加了44个字节的wav头

生成的wav可以直接播放器播放。

六、收获

通过对AudioRecord的学习实践,
了解音频录制中 (AudioSource录制输入源;smapleRate录制采样率;录制声道配置AudioForamt;采样的格式;以及音频缓冲区大小bufferSize)
了解了音频录制的流程;
pcm和wav的关系以及如何转化;
结合AudioRecord的源码对录制的状态机的同步的设计
AudioRecord的read耗时操作,避免ANR,要在子线程中处理

感谢你的阅读。下一篇我们来实践分析下AudioTrack,即音频的播放

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

音视频开发(二):AudioRecord录制PCM音频相关推荐

  1. Android音视频开发,详说PCM音频重采样、PCM编码

    直播伴音,两种数据能否合在一起?不能叠加在一起 会有噪音 合并以后 再去编码推流 直播的例子 客户端播放器,可以开启多个播放器 对于我们重采样 很多时候就是为了统一格式,就是为了要合并这个流,去推送, ...

  2. Android 音视频开发(二):使用 AudioRecord 采集音频PCM并保存到文件(学习笔记)

    关于 AudioRecord Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风 ...

  3. Android 音视频开发(二) -- Camera1 实现预览、拍照功能

    音视频 系列文章 Android 音视频开发(一) – 使用AudioRecord 录制PCM(录音):AudioTrack播放音频 Android 音视频开发(二) – Camera1 实现预览.拍 ...

  4. 音视频开发(四)——编码音频

    基于QT+FFMPEG的音视频开发(四)--编码音频 一.编码一般步骤 二.编码 2.1 创建编码器(本文创建AAC) 2.2 核心编码 三.源码 我的大部分学习都来自雷神,没有基础去雷神博客转转,每 ...

  5. 音视频开发入门基础知识(音频入门篇)

    RTSP实时音视频开发实战课程:<RTSP实时音视频开发实战> 音视频开发入门基础知识(音频入门篇) 目录 前言 音频的采集和播放 音频常见的格式 音频的编码 前言 在音视频开发入门基础知 ...

  6. Android音视频开发:MediaRecorder录制音频

    Android 多媒体框架针对音频录制提供了两种方法:MediaRecorder和AudioRecord. 区别 MediaRecorder:录制的音频文件是经过压缩后的,需要设置编码器,并且录制的音 ...

  7. 即时通讯音视频开发(七):音频基础及编码原理入门

    前言 即时通讯应用中的实时音视频技术,几乎是IM开发中的最后一道高墙.原因在于:实时音视频技术 = 音视频处理技术 + 网络传输技术 的横向技术应用集合体,而公共互联网不是为了实时通信设计的. 系列文 ...

  8. Android使用AudioRecord录制PCM音频、PCM转AAC、使用MediaRecorder直接录制AAC编码音频

    Android为我们提供了两个音频处理的API:AudioRecord和MediaRecorder AudioRecord:偏底层的api MediaRecorder:对AudioRecord进行包装 ...

  9. 音视频开发二:音视频知识总结

    文章目录 简介 简单理解,音视频原理 音视频理论基础 音频 声音介绍 **为什么要存在数字音频 ?** **什么是数字音频?** 从"模拟信号"到"数字化"的过 ...

最新文章

  1. 发布或重启线上服务时抖动问题解决方案
  2. 年终福利 | 京东虚拟平台团队问答专场
  3. android 观察者,Android开发实现简单的观察者与被观察者示例
  4. CodeForces - 510E Fox And Dinner(最大流+奇偶建边+路径打印)
  5. 你永远不知道女生裙子下面藏着什么
  6. 关于JFace中的右键菜单Action类,ActgionGroup类,MenuManager类
  7. boost:regex分割字符串(带有'\'字符) - zzusimon的专栏 - 博客频道 - CSDN.NET
  8. FreeCAD源码分析:Assembly3模块
  9. 进销存管理系统搭建流程
  10. 服务器ldb文件可以删除,Access数据库锁死,出现.ldb文件解决办法
  11. Android肝帝战纪之ObjectBox移动数据库框架探究与实现
  12. Qt5.12 QML——TextMetrics字体长度的测量指标
  13. JS数组 编程练习 使用Javascript语言,把以下数组 在页面显示如下图所示的图案
  14. Adapter的notifyDataSetChange无效的问题
  15. Excel 将文本格式快速转换为数值格式
  16. 个人如何做一个能赚钱的网站?
  17. 在中国,40岁程序员是如何工作的?
  18. 将Java中的内容直接存储为二进制文件
  19. C++数据结构之单链表(henu.hjy)
  20. wayos虚拟服务器,WAYOS小区版软路由官方说明

热门文章

  1. C/C++内存问题检查利器—Purify (三)
  2. clickhouse常见问题之Too many simultaneous queries
  3. JVM 简介、程序计数器、虚拟机栈
  4. onreadystatechange事件
  5. 奥创,ultron官网,奥创官网,奥创中国区官网,ultron中国区官网,奥创公链,ultron公链,ulx,ultron奥创,ultron,海洋renhe333333
  6. Siamese Multi-Object Tracking
  7. CMOS 图像传感器简介
  8. 建筑绿色化进展神速 新建建筑绿色化已超90%
  9. 使用sklearn学习多项式回归(三)
  10. 潇洒郎:cxfreeze python 3.7变化