Android使用AudioRecord和AudioTrack完成音频的采集和播放以及使用MediaCodec完成硬编和硬解
一、概述
音视频的学习在弄清楚了一些基本概念后,接下来就是要会使用系统提供的一些API,通过实际的应用能更好的帮助我们理解其中的原理。
二、音频的采集
这里我们使用AudioRecord,因为它更接近系统底层,灵活性也更高,能获取到原始的PCM数据。下面来看一下采集的流程:
1)获取权限
2)获取AudioRecord所需的缓冲区
3)创建AudioRecord对象
4)创建保存录制文件夹
5)开始采集
6)停止采集并释放AudioRecord对象
1、获取权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
此处向系统申请了录音和存储权限,5.0以上系统还要在代码上动态申请。
2、获取AudioRecord所需的缓冲区
//创建AudioRecord对象所需的最小缓冲区大小
final int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_HZ, CHANNEL_IN_CONFIG, AUDIO_FORMAT);
这里要通过调用系统API获取,而不是自己随便设置,参数就是系统采集用到的一些参数。
3、创建AudioRecord对象
//创建AudioRecord对象
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_HZ, CHANNEL_IN_CONFIG, AUDIO_FORMAT, minBufferSize);
创建完AudioRecord对象对象后就是进行音频的采集了,具体的代码会在文章最后贴出来。
三、音频的播放
这里使用AudioTrack来完成音频播放,流程如下:
1)获取播放源的路径
2)获取AudioTrack所需的缓冲区
3)创建AudioTrack对象
4)开始播放
5)停止播放并释放AudioTrack对象
1、获取播放源就是找到之前采集的PCM数据
2、获取AudioTrack所需的缓冲区
//创建AudioTrack对象所需的估计最小缓冲区大小
final int minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE_HZ, CHANNEL_OUT_CONFIG, AUDIO_FORMAT);
这里跟获取AudioRecord所需的缓冲区类似
3、创建AudioTrack对象
//创建AudioTrack对象
audioTrack = new AudioTrack(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
new AudioFormat.Builder().setSampleRate(SAMPLE_RATE_HZ)
.setEncoding(AUDIO_FORMAT)
.setChannelMask(CHANNEL_OUT_CONFIG)
.build(),
minBufferSize, mode, AudioManager.AUDIO_SESSION_ID_GENERATE);
里面涉及到的一些参数具体可以看代码,接下来就可以进行音频播放了。
四、使用MediaCodec硬编
MediaCodec是一个比较重要的类,从Android 4.1开始推出,主要用于音视频的硬编和硬解码。相对于软编和软解通过CPU进行编解码,其主要是利用自身的硬件来完成。所以效率更高,但也有缺点,就是适配性比较差,因为市面上的机型太多了,每个厂商又会对底层进行修改。从长远来来看还是比较看好MediaCodec
下面来看一张官方给的MediaCodec工作流程图
所有的编解码步骤都是围绕这张图的流程来做的,这里做一个大概的介绍:
1)整理看可以分为三部分,第一部分是数据输入(input),第二部分是编解码(Codec),最后是数据输出(output)
2)数据输入
a、获取输入缓冲区:int inputIndex = MediaCodec.dequeueInputBuffer(long timeoutUs),用于将需要处理的数据存在里面。
b、获取ByteBuffer对象,并向输入缓冲区添加数据:ByteBuffer inputByteBuffer = MediaCodec.getInputBuffer(inputIndex)
inputByteBuffer.clear();
inputByteBuffer.put(data)
c、把添加了数据的输入缓冲区送到Codec里面进行处理:MediaCodec.queueInputBuffer(inputIndex, 0, data.length, 0, 0)
3)数据输出
a、获取输出缓冲区:int outputIndex = MediaCodec.dequeueOutputBuffer(BufferInfo info, long timeoutUs),BufferInfo包含每 个buffer元素的信息
b、获取到处理后的数据:ByteBuffer outputByteBuffer = MediaCodec.getOutputBuffer(outputIndex)
c、释放输出缓冲区:MediaCodec.releaseOutputBuffer(outputIndex, false)
以上大概讲了一下MediaCodec的使用流程,下面来看下编码的流程:
1、初始化编码器并设置相关参数
/**
* 初始化编码器
*/
public void initEncoder() {
try {
//创建文件夹
File fileFolder = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + FOLDER_NAME);
if (!fileFolder.exists()) {
fileFolder.mkdir();
}
String fileFolderPath = fileFolder.getAbsolutePath();
final File file = new File(fileFolderPath + "/" + ENCODER_FILE + ".aac");
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
mFileOutputStream = new FileOutputStream(file.getAbsoluteFile());
//设置编码参数
MediaFormat mediaFormat = MediaFormat.createAudioFormat(MINE_TYPE_AAC, SAMPLE_RATE_HZ, CHANNEL_COUNT);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, MAX_BUFFER_SIZE);
//根据类型实例化一个编码器
mMediaCodec = MediaCodec.createEncoderByType(MINE_TYPE_AAC);
//MediaCodec.CONFIGURE_FLAG_ENCODE 表示需要配置一个编码器,而不是解码器
mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
if (mMediaCodec == null) {
Log.e(TAG, "Create media encode failed");
return;
}
//start()后进入执行状态,才能做后续的操作
mMediaCodec.start();
//解码后的数据,包含每一个buffer的元数据信息
mBufferInfo = new MediaCodec.BufferInfo();
Log.i(TAG, "Create media encode succeed");
} catch (IOException e) {
e.printStackTrace();
}
}
2、编码
/**
* 编码
*
* @param data
*/
public void encodeData(byte[] data) {
//dequeueInputBuffer(time)需要传入一个时间值,-1表示一直等待,0表示不等待有可能会丢帧,其他表示等待多少毫秒
//获取输入缓存的index
int inputIndex = mMediaCodec.dequeueInputBuffer(-1);
if (inputIndex >= 0) {
ByteBuffer inputByteBuffer = mMediaCodec.getInputBuffer(inputIndex);
inputByteBuffer.clear();
//添加数据
inputByteBuffer.put(data);
//限制ByteBuffer的访问长度
inputByteBuffer.limit(data.length);
//把输入缓存塞回去给MediaCodec
mMediaCodec.queueInputBuffer(inputIndex, 0, data.length, 0, 0);
}
//获取输出缓存的index
int outputIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 0);
while (outputIndex >= 0) {
//获取缓存信息的长度
int byteBufSize = mBufferInfo.size;
//添加ADTS头部后的长度
int bytePacketSize = byteBufSize + 7;
ByteBuffer outByteBuffer = mMediaCodec.getOutputBuffer(outputIndex);
outByteBuffer.position(mBufferInfo.offset);
outByteBuffer.limit(mBufferInfo.offset + mBufferInfo.size);
byte[] targetByte = new byte[bytePacketSize];
//添加ADTS头部
addADTStoPacket(targetByte, bytePacketSize);
//将编码得到的AAC数据 取出到byte[]中 偏移量offset=7
outByteBuffer.get(targetByte, 7, byteBufSize);
outByteBuffer.position(mBufferInfo.offset);
try {
mFileOutputStream.write(targetByte);
} catch (IOException e) {
e.printStackTrace();
}
//释放
mMediaCodec.releaseOutputBuffer(outputIndex, false);
outputIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 0);
}
}
3、停止编码
/**
* 停止编码
*/
public void stopEncode() {
if (mMediaCodec != null) {
mMediaCodec.stop();
mMediaCodec.release();
Log.i(TAG, "Stop encode");
}
}
至此硬编就完成了,硬解的流程也是类似,这里就不说了,具体可以参照代码。
本文地址:https://blog.csdn.net/zhongfangguiyishi/article/details/107287825
下一篇: 20. 有效的括号