ITPub博客

首页 > 应用开发 > IT综合 > Android短视频系统硬编码—实现音视频编码(一)

Android短视频系统硬编码—实现音视频编码(一)

IT综合 作者:云豹科技晓彤 时间:2021-07-01 17:19:27 0 删除 编辑

短视频系统编解码对许多Android程序员来说都是Android中比较难的一个知识点。在Android 4.1以前,Android并没有提供硬编硬解的API,所以之前基本上都是采用FFMpeg来做短视频系统软件编解码的,现在FFMpeg在Android的编解码上依旧广泛应用。本篇博客主要讲到的是利用Android4.1增加的API MediaCodec和Android 4.3增加的API MediaMuxer进行Mp4视频的录制。

概述

通常来说,对于同一平台同一硬件环境,硬编硬解的速度是快于软件编解码的。而且相比软件编解码的高CPU占用率来说,硬件编解码也有很大的优势,所以在硬件支持的情况下,一般硬件编解码是我们的首选。
在Android中,我们可以直接使用MediaRecord来进行录像,但是在很多适合MediaRecord并不能满足我们的需求,比如我们需要对短视频系统录制的视频加水印或者其他处理后,所有的短视频系统都按照同一的大小传输到服务器等。
在本篇博客中,将会讲到的是利用AudioRecord录音,利用OpenGL渲染相机数据并做处理。然后利用MediaCodec对音频和视频分别进行编码,使用MediaMuxer将编码后的短视频系统音视频进行混合保存为Mp4的编码过程与代码示例。
值得注意的是,短视频系统音视频编解码用到的MediaCodec是Android 4.1新增的API,音视频混合用到的MediaMuxer是Android 4.3新增的API,所以本篇博客的示例只实用于Android 4.3以上的设备。

AudioRecord(录音API)

AudioRecord是相对MediaRecord更为底层的API,使用AudioRecord也可以很方便的完成短视频系统的录音功能。AudioRecord录音录制的是原始的PCM音频数据,我们可以使用AudioTrack来播放PCM音频文件。
AudioRecord最简单的使用代码如下:

private int sampleRate=44100;   //采样率,默认44.1k
private int channelCount=2;     //音频采样通道,默认2通道
private int channelConfig=AudioFormat.CHANNEL_IN_STEREO;        //通道设置,默认立体声
private int audioFormat=AudioFormat.ENCODING_PCM_16BIT;     //设置采样数据格式,默认16比特PCM
private FileOutputStream fos;       //用于保存录音文件
//音频录制实例化和录制过程中需要用到的数据
bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)*2;
buffer=new byte[bufferSize];
//实例化AudioRecord
mRecorder=new AudioRecord(MediaRecorder.AudioSource.MIC,sampleRate,channelConfig,
    audioFormat,bufferSize);
//开始录制
mRecorder.startRecording();
//循环读取数据到buffer中,并保存buffer中的数据到文件中
int length=mRecorder.read(buffer,0,bufferSize);
fos.write(buffer,0,length);
//中止循环并结束录制
isRecording=false;
mRecorder.stop();

按照上面的步骤,我们就能成功的录制PCM音频文件了,但是处于传输和存储方面的考虑,一般来说,我们是不会直接录制PCM音频文件的。而是在录制过程中就对音频数据进行编码为aac、mp3、wav等其他格式的音频文件。

MediaCodec(硬件编解码API)

理解MediaCodec

MediaCodec的使用在Android Developer官网上有详细的说明。官网上的图能够很好的说明MediaCodec的使用方式。我们只需理解这个图,然后熟悉下MediaCodec的API就可以很快的上手使用MediaCodec来进行音视频的编解码工作了。

MediaCodec

针对于上图,我们可以把InputBuffers和OutputBuffers简单的理解为它们共同组成了一个环形的传送带,传送带上铺满了空盒子。短视频系统编解码开始后,我们需要得到一个空盒子(dequeueInputBuffer),然后往空盒子中填充原料(需要被编/解码的音/视频数据),并且放回到传送带你取出时候的那个位置上面(queueInputBuffer)。传送带经过处理器(Codec)后,盒子里面的原料被加工成了你所期望的东西(编解码后的数据),你就可以按照你放入原料时候的顺序,连带着盒子一起取出加工好的东西(dequeueOutputBuffer),并将取出来的东西贴标签(加数据头之类的非必须)和装箱(组合编码后的帧数据)操作,同样之后也要把盒子放回到原来的位置(releaseOutputBuffer)。

音频编码实例
在官网上有更规范的使用示例,结合上面的音频录制,编码为AAC音频文件示例代码如下:

private String mime = "audio/mp4a-latm";    //录音编码的mime
private int rate=256000;                    //编码的key bit rate
//相对于上面的音频录制,我们需要一个编码器的实例
MediaFormat format=MediaFormat.createAudioFormat(mime,sampleRate,channelCount);
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
format.setInteger(MediaFormat.KEY_BIT_RATE, rate);
mEnc=MediaCodec.createEncoderByType(mime);
mEnc.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);  //设置为编码器
//同样,在设置录音开始的时候,也要设置编码开始
mEnc.start();
//之前的音频录制是直接循环读取,然后写入文件,这里需要做编码处理再写入文件
//这里的处理就是和之前传送带取盒子放原料的流程一样了,注意一般在子线程中循环处理
int index=mEnc.dequeueInputBuffer(-1);
if(index>=0){
    final ByteBuffer buffer=mEnc.getInputBuffer(index);
    buffer.clear();
    int length=mRecorder.read(buffer,bufferSize);
    if(length>0){
        mEnc.queueInputBuffer(index,0,length,System.nanoTime()/1000,0);
    }
}
MediaCodec.BufferInfo mInfo=new MediaCodec.BufferInfo();
int outIndex;
//每次取出的时候,把所有加工好的都循环取出来
do{
    outIndex=mEnc.dequeueOutputBuffer(mInfo,0);
    if(outIndex>=0){
        ByteBuffer buffer=mEnc.getOutputBuffer(outIndex);
        buffer.position(mInfo.offset);
        //AAC编码,需要加数据头,AAC编码数据头固定为7个字节
        byte[] temp=new byte[mInfo.size+7];
        buffer.get(temp,7,mInfo.size);
        addADTStoPacket(temp,temp.length);
        fos.write(temp);
        mEnc.releaseOutputBuffer(outIndex,false);
    }else if(outIndex ==MediaCodec.INFO_TRY_AGAIN_LATER){
        //TODO something
    }else if(outIndex==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
        //TODO something
    }
}while (outIndex>=0);
//编码停止,发送编码结束的标志,循环结束后,停止并释放编码器
mEnc.stop();
mEnc.release();

AAC编码加文件头的实现参照AAC编码规则,将数据填入就好了,网上很容易找到,具体实现如下:

/**
* 给编码出的aac裸流添加adts头字段
* @param packet 要空出前7个字节,否则会搞乱数据
* @param packetLen
*/
private void addADTStoPacket(byte[] packet, int packetLen) {
   int profile = 2;  //AAC LC
   int freqIdx = 4;  //44.1KHz
   int chanCfg = 2;  //CPE
   packet[0] = (byte)0xFF;
   packet[1] = (byte)0xF9;
   packet[2] = (byte)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
   packet[3] = (byte)(((chanCfg&3)<<6) + (packetLen>>11));
   packet[4] = (byte)((packetLen&0x7FF) >> 3);
   packet[5] = (byte)(((packetLen&7)<<5) + 0x1F);
   packet[6] = (byte)0xFC;
}


————————————————
声明:声明:本文由云豹科技转发自CSDN【 湖广午王】博客,如有侵权请联系作者删除
原文链接:https://blog.csdn.net/junzia/article/details/54018671


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70002045/viewspace-2779318/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
音视频软件开发相关知识科普账号

注册时间:2021-06-11

  • 博文量
    163
  • 访问量
    60338