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");
    }
}

 至此硬编就完成了,硬解的流程也是类似,这里就不说了,具体可以参照代码。

AudioDemo

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值