最近学习了一些关于Android音视频的知识,在此写博客记录一下
学习思路
关于Android音视频的学习思路参考的是这篇博客:https://www.cnblogs.com/renhui/p/7452572.html
初步学习主要分为四项任务:
- AudioRecord录制PCM,输出WAV格式,AudioTrack播放PCM
- Camera采集视频YUV数据,输出H264格式
- MediaExtractor和MediaMuxer解析和封装mp4文件
- MediaCodec编译AAC与H264格式,封装mp4
AudioRecord录制PCM,输出WAV格式,AudioTrack播放PCM
这个任务分为三个部分:
- 使用系统AudioRecord API录制PCM
- 通过为PCM格式文件添加文件头,封装为WAV格式
- 使用系统AudioTrack API播放PCM
使用系统AudioRecord API录制PCM
官方API文档:https://developer.android.com/reference/android/media/AudioRecord
话不多说,封装的PcmRecorder代码如下
1 | /** |
说明一下构造函数中需要的几个参数,顺便回顾下一些音频相关的参数知识:
- sampleRate:采样率,表示每秒的采样点数,根据Android官方文档,目前44.1K是保证在所有设备上都能运行的采样率(详见AudioRecord构造函数doc)
- channel:声道,Android支持单声道(AudioFormat#CHANNEL_IN_MONO表示)或双声道(AudioFormat#CHANNEL_IN_STEREO表示)
- bitWidth:位宽,表示每个采样点的存储大小,一般为8bit(AudioFormat#ENCODING_PCM_8BIT表示)或16bit(AudioFormat#ENCODING_PCM_16BIT表示)
- fileOutputPath:输出文件路径
- queue:存放PCM数据的阻塞队列,用于配合进行Aac编码
需要值得注意的是,当我们需要计算录音时长时,通过记录一个起始录音时间点,再通过当前录音时间点相减的方式并不可靠(大致原因是AudioRecord#start调用后其实并没有马上开始录音)
比较好的方法时通过 采样率的公式来计算录音时长,具体代码如下:
1 | /** |
通过为PCM格式文件添加头数据,封装为WAV格式
在录制完PCM文件后,我们可以通过为其添加文件头,使其变成可以被播放器播放的WAV格式
文件头介绍:https://baike.baidu.com/item/%E6%96%87%E4%BB%B6%E5%A4%B4
WAV格式文件头详细介绍:http://soundfile.sapp.org/doc/WaveFormat/
大小端介绍:https://baike.baidu.com/item/%E5%A4%A7%E5%B0%8F%E7%AB%AF%E6%A8%A1%E5%BC%8F/6750542?fromtitle=%E5%A4%A7%E7%AB%AF%E5%B0%8F%E7%AB%AF&fromid=15925891&fr=aladdin
具体代码如下:
1 | /** |
值得注意的是,在WAV文件头中,字符串相关的参数均为大端模式,而数据类型的均为小端模式
使用系统AudioTrack API播放PCM
AudioTrack官方文档:https://developer.android.com/reference/android/media/AudioTrack
相关代码如下:
1 | public class MainActivity extends AppCompatActivity implements View.OnClickListener { |
值得注意的是,AudioTrack支持两种播放模式,这里用的是AudioTrack#MODE_STREAM模式
- MODE_STREAM:在这种模式下,通过write一次次把音频数据写到AudioTrack中。这和平时通过write系统调用往文件中写数据类似,但这种工作方式每次都需要把数据从用户提供的Buffer中拷贝到AudioTrack内部的Buffer中,这在一定程度上会使引入延时。为解决这一问题,AudioTrack就引入了第二种模式。
- MODE_STATIC:这种模式下,在play之前只需要把所有数据通过一次write调用传递到AudioTrack中的内部缓冲区,后续就不必再传递数据了。这种模式适用于像铃声这种内存占用量较小,延时要求较高的文件。但它也有一个缺点,就是一次write的数据不能太多,否则系统无法分配足够的内存来存储全部数据。
xml文件相对简单,这里不贴出来了,测试时注意申请相关权限
1 | <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> |