大部分网站上一般都只有mediacodec音频(视频)单个解码编码的代码,没有人去把这些操作封装起来易于使用,于是笔者就花时间将音视频编解码四大操作封装了一下方便使用。
这里使用时我们只需要写一个类继承AVProcessor(super加上),然后实例化AVDealData接口里面的方法(对每一帧输出的outData要怎么处理写在里面,可以实现推流?或者图像处理?),调用startMediaCodec开启编码线程,addNewRawAVData方法可以往队列塞数据,然后就完事了,实例化时填好相关参数,不需要关心中间编码过程做了什么。
其它包括aac编解码、视频解码也差不多,按照已经写好的AVProcessor核心类的源码的参数提示来继承就好,其中特别的是视频解码输出显示的surface要自己提供并且stortageFile标志位无效。
附:虽然AVProcessor提供了打印十六进制图像数据的方法(byte2hex),但事实上没有用到,因为在编解码过程的DealRes把编解码数据十六进制打印出来调试可能造成播放卡顿,请注意。
贴上代码和视频编码用法示例(VideoEncoder类是使用例子,这里用于rtmp推流):
AVProcessor:
package com.example.mediaitem2;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaRecorder;
import android.os.Build;
import android.view.Surface;
import androidx.annotation.RequiresApi;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;
public class AVProcessor {
enum AVType
{
VIDEO_ENCODE,
VIDEO_DECODE,
AUDIO_ENCODE,
AUDIO_DECODE
};
//这个类型决定该类被继承或实例化时的用途
protected AVType avtype;
//如果是编码,是否需要将编码的数据存储起来?
protected boolean isMemoryStorage=false;
//如果需要存储文件,那么用这个变量获取存储文件的路径
protected File storageFile;
//这个标志位用来存储编解码器是否处于工作状态
protected boolean isWorking=false;
//这个存储视频的编解码延时
private final int TIME_USEC=10000;
//用于编解码的编解码器
private MediaCodec mediaCodec;
//存储音视频的format
private MediaFormat mediaFormat;
//用于编解码需要两个线程,一个用于将byte数据塞入到队列中,一个负责提取队列中的数据并进行编解码的相关核心操作
private Thread AVDealThread;
//无论是音频还是视频,编解码都需要获取时间戳,startTime负责记录初始时间戳,实际时间戳需要通过当前的扣掉startTime
//特别声明:这里timeStamp的单位是秒
protected long startTime,currentTime,TimeStamp;
//确认是否为第一帧数据的标志位
private boolean isFirstTime=true;
//音视频的码率
protected int AVBitRate;
//这个用于存储暂时的音视频数据
protected byte[] AVData;
//要编解码出来的音视频format
protected String AVFormat;
//存储队列里的元素
private BufferPool bufferPool;
//存储编解码的相关信息
protected MediaCodec.BufferInfo bufferInfo;
//如果要存储音视频的数据,要有一个文件输出流
protected FileOutputStream fos;
//处理相关接口的定义,被子类继承时实现不同的方法
protected AVDealData avDealData;
//编码器的输出数据的暂存地址
protected byte[] outData;
//*************************************接下来是视频的特有参数*************************************//
//首先是视频的长宽
protected int videoWidth,videoHeight;
//如果是视频还有帧率
protected int VideoFrameRate=30;
//I帧之间的间隔
protected int IFrameInterval=1;
//如果视频流是通过Surface传入的,需要设置Surface
private Surface videoSurface;
//************************************接下来是音频的特有参数**************************************//
//声道数量
protected int channelNum;
//采样率
protected int SamepleRate;
//最大传输的数据大小
protected int audioBufferSize;
//AACHeader数据,它的头字节部分的大小为7,编码时需要用到它
byte[] aacHeader=new byte[7];
//视频构造方法(附:当解码时,isMemoryStorage默认为false,因为不可能把图片一帧一帧写在存储空间上)
AVProcessor(AVType type,//决定是编码还是解码
boolean isMemoryStorage,//编码后的数据是否要存储下来?
File storageFile,//如果isMemoryStorage=true,那么需要指定存储路径,否则此栏填Null
String AVFormat,//要编解码的视频类型,比如:"avc"
int AVBitRate,//码率
int videoWidth, int videoHeight,//视频的宽和高
int videoFrameRate,//帧率
Surface videoSurface//如果是解码,一般要有对应的输出接口来显示图像数据,编码时默认通过addRawData塞数据,此参数无效
) throws IOException {
this.avtype=type;
this.isMemoryStorage=isMemoryStorage;
this.storageFile=storageFile;
this.AVFormat=AVFormat;
this.AVBitRate=AVBitRate;
this.videoWidth=videoWidth;
this.videoHeight=videoHeight;
this.videoSurface=videoSurface;
this.VideoFrameRate=videoFrameRate;
VideoEncodeParmsInit();
}
//音频构造方法(解码时默认为aac,若isMemoryStorage=true,那么解码结果为pcm音频)
AVProcessor(AVType type,
boolean isMemoryStorage, File storageFile, String AVFormat,
int AVBitRate, int channelNum, int SamepleRate, int bufferSize) throws IOException {
this.avtype=type;
this.isMemoryStorage=isMemoryStorage;
this.storageFile=storageFile;
this.AVFormat=AVFormat;
this.AVBitRate=AVBitRate;
this.channelNum=channelNum;
this.SamepleRate=SamepleRate;
this.audioBufferSize=bufferSize;
AudioEncodeParmsInit();
}
//视频编解码器参数的初始化
private void VideoEncodeParmsInit() throws IOException {
mediaFormat= MediaFormat.createVideoFormat(AVFormat,videoWidth,videoHeight);
//这里我们默认输入的数据为YUV420格式,如不是此格式则要转换成该格式
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE,AVBitRate);
//VideoFrameRate默认为30,在实际设置过程mediacodec会根据实际情况修改
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE,VideoFrameRate);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,IFrameInterval);
if(avtype==AVType.VIDEO_ENCODE)
{
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
mediaCodec= MediaCodec.createEncoderByType(AVFormat);
mediaCodec.configure(mediaFormat,videoSurface,null, MediaCodec.CONFIGURE_FLAG_ENCODE);
}
else
{
mediaCodec=MediaCodec.createDecoderByType(AVFormat);
mediaCodec.configure(mediaFormat,videoSurface,null,0);
}
mediaCodec.start();
if(isMemoryStorage&&storageFile!=null&&avtype==AVType.AUDIO_ENCODE)
{
fos=new FileOutputStream(storageFile);
}
else if(storageFile==null)
{
System.out.println("AVprocessor:storageFile is null");
}
}
//音频编解码器参数的初始化
private void AudioEncodeParmsInit() throws IOException {
mediaFormat= MediaFormat.createAudioFormat(AVFormat,SamepleRate,channelNum);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE,AVBitRate);
//这里默认音频的版本类型为LC
mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE,
MediaCodecInfo.CodecProfileLevel.AACObjectLC);
mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE,audioBufferSize);
if(avtype==AVType.AUDIO_ENCODE) {
mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
}
else {
mediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
//告诉解码器数据含ADTS帧头
mediaFormat.setInteger(MediaFormat.KEY_IS_ADTS, 0);
byte[] data = new byte[]{(byte) 0x12, (byte)0x08};
ByteBuffer csd_0 = ByteBuffer.wrap(data);
mediaFormat.setByteBuffer("csd-0", csd_0);
mediaCodec.configure(mediaFormat, null, null, 0);
}
mediaCodec.start();
if(isMemoryStorage&&storageFile!=null)
{
fos=new FileOutputStream(storageFile);
}
else if(storageFile==null)
{
System.out.println("AVProcessor:storageFile is null");
}
}
//编解码线程
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void MediaCodecDealingThread() throws InterruptedException, IOException {
bufferInfo=new MediaCodec.BufferInfo();
//编解码结束标志位,这里和isWorking的概念不同,isWorking切换成false编解码器可能还在运行,要一段时间才结束
boolean isEncodeEnd=false;
//这两个参数决定了视频输出的时间戳,必须要设置,否则会出现视频流花屏的现象或音频流的一系列问题
long pts = 0;
long generateIndex = 0;
//如果是AAC编码,还要先初始化好头部的1-3、7字节的数据,中间根据包的长度来决定4-6字节的数据
AacUtil.addADTStoPacket(MediaCodecInfo.CodecProfileLevel.AACObjectLC,
SamepleRate,channelNum,aacHeader,0);
while(!isEncodeEnd)
{
//先获取输入队列的id
int inputBufferId=mediaCodec.dequeueInputBuffer(TIME_USEC);
if(inputBufferId>=0)
{
//首先要从队列中获取Data
BufferPool.AVData Data=
bufferPool.pollDataBuffer(TIME_USEC,
TimeUnit.MILLISECONDS);
if(Data!=null)
{
ByteBuffer inputBuffer =mediaCodec.getInputBuffer(inputBufferId);
//从队列中获取数据
AVData=Data.data;
inputBuffer.put(AVData,0,AVData.length);
switch (avtype)
{
case AUDIO_ENCODE:
mediaCodec.queueInputBuffer(inputBufferId,0,Data.readSize,0,0);
break;
case VIDEO_ENCODE:
pts =computePresentationTime(generateIndex);
mediaCodec.queueInputBuffer(inputBufferId,0,Data.readSize,pts,0);
++generateIndex;
break;
case AUDIO_DECODE:
mediaCodec.queueInputBuffer(inputBufferId,0,Data.readSize,0,0);
break;
case VIDEO_DECODE:
mediaCodec.queueInputBuffer(inputBufferId,0,Data.readSize,0,0);
break;
}
//队列去掉数据
bufferPool.deque(Data);
}
else if(Data==null&&isWorking)
{
mediaCodec.queueInputBuffer(inputBufferId, 0, 0, 0, 0);
}
else
{
//如果不在isWorking的状态底下,那么就要在最后一个包中添加上对应的标志位
mediaCodec.queueInputBuffer(inputBufferId,0,0,0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
}
int outputBufferId = mediaCodec.dequeueOutputBuffer(bufferInfo, TIME_USEC);
if(outputBufferId>=0)
{
ByteBuffer outputBuffer=mediaCodec.getOutputBuffer(outputBufferId);
if(bufferInfo.flags== MediaCodec.BUFFER_FLAG_END_OF_STREAM)
{
isEncodeEnd=true;
}
else if(outputBuffer!=null&&bufferInfo.size>0)
{
//首先,无论是视频帧还是音频帧,需要记录下对应的时间戳
//如果是第一帧
if(isFirstTime)
{
startTime=bufferInfo.presentationTimeUs;
isFirstTime=false;
TimeStamp=0;
}
else
{
currentTime=bufferInfo.presentationTimeUs;
TimeStamp=(currentTime-startTime)/1000;
}
//然后要获取输出的数据
outData=new byte[bufferInfo.size];
outputBuffer.get(outData);
if(isMemoryStorage)
{
switch (avtype)
{
case AUDIO_ENCODE:
AacUtil.setADTSPacketLen(channelNum, aacHeader,
bufferInfo.size + 7);
fos.write(aacHeader);
fos.write(outData);
break;
case VIDEO_ENCODE:
fos.write(outData);
break;
case AUDIO_DECODE:
fos.write(outData);
break;
}
}
avDealData.dealWithResData();
if(videoSurface==null)
mediaCodec.releaseOutputBuffer(outputBufferId,false);
else
mediaCodec.releaseOutputBuffer(outputBufferId,true);
}
}
}
if(fos!=null)
{
fos.flush();
fos.close();
fos=null;
}
//相关资源在编解码操作完成后释放
bufferPool.clear();
bufferPool=null;
mediaCodec.stop();
mediaCodec.release();
mediaCodec=null;
}
//数据键入函数
public void addNewRawAVData(byte[] rawData) throws InterruptedException {
BufferPool.AVData tmpData=bufferPool.takeFreeBuffer();
tmpData.data=rawData;
tmpData.readSize=rawData.length;
bufferPool.enque(tmpData);
}
public static String bytes2hex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
String tmp = null;
for (byte b : bytes) {
// 将每个字节与0xFF进行与运算,然后转化为10进制,然后借助于Integer再转化为16进制
tmp = Integer.toHexString(0xFF & b);
if (tmp.length() == 1) {
tmp = "0" + tmp;
}
sb.append(tmp);
}
return sb.toString();
}
protected interface AVDealData{
//这个接口在被子类继承后,用于定义一些对输出数据的特有的处理需求,比如:推流、图像处理等
void dealWithResData();
};
public void startMediaCodec()
{
if(avtype==AVType.VIDEO_ENCODE||avtype==AVType.VIDEO_DECODE)
bufferPool=new BufferPool(20,videoWidth*videoHeight*2);
else
bufferPool=new BufferPool(20,audioBufferSize);
isWorking=true;
AVDealThread=new Thread(new Runnable() {
@Override
public void run() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
MediaCodecDealingThread();
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}
}
}
});
AVDealThread.start();
}
public void setState(boolean state)
{
isWorking=state;
}
private long computePresentationTime(long frameIndex) {
return 132 + frameIndex * 1000000 / VideoFrameRate;
}
}
BufferPool:
package com.example.mediaitem2;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class BufferPool {
private LinkedBlockingQueue mFreeQueue;
private LinkedBlockingQueue mDataQueue;
public class AVData {
byte[] data;
int readSize;
AVData(int bufferSize) {
data = new byte[bufferSize];
}
}
public BufferPool(int capacity, int bufferSize) {
mFreeQueue = new LinkedBlockingQueue(capacity);
for (int i = 0; i < capacity; i++) {
mFreeQueue.add(new AVData(bufferSize));
}
mDataQueue = new LinkedBlockingQueue(capacity);
}
public int getFreeSize() {
return mFreeQueue.size();
}
public AVData takeFreeBuffer() throws InterruptedException {
return mFreeQueue.take();
}
public AVData pollFreeBuffer() {
return mFreeQueue.poll();
}
public AVData pollFreeBuffer(long timeout, TimeUnit unit) throws InterruptedException {
return mFreeQueue.poll(timeout, unit);
}
public void enque(AVData data) throws InterruptedException {
mDataQueue.put(data);
}
public AVData takeDataBuffer() throws InterruptedException {
return mDataQueue.take();
}
public AVData pollDataBuffer() {
return mDataQueue.poll();
}
public AVData pollDataBuffer(long timeout, TimeUnit unit) throws InterruptedException {
return mDataQueue.poll(timeout, unit);
}
public void deque(AVData data) throws InterruptedException {
mFreeQueue.put(data);
}
public void clear() {
if (mFreeQueue != null) {
mFreeQueue.clear();
mFreeQueue = null;
}
if (mDataQueue != null) {
mDataQueue.clear();
mDataQueue = null;
}
}
}
AacUtil:
package com.example.mediaitem2;
public class AacUtil {
//采样率 对应下标。
private final static int[] sampleData = new int[]{96000, 88200, 64000, 48000, 44100, 32000,
24000, 22050, 16000, 12000, 11025, 8000, 7350};
private AacUtil() {
}
public static int getProfile(int aacLevel) {
return aacLevel - 1;
}
public static int getSamplingIndex(int sampleRate) {
for (int i = 0; i < sampleData.length; i++) {
if (sampleData[i] == sampleRate)
return i;
}
return 0;
}
public static void addADTStoPacket(int aacLevel, int sampleRate, int channelCou, byte[] packet,
int packetLen) {
int profile = getProfile(aacLevel); // AAC LC
int sampling_frequency_index = getSamplingIndex(sampleRate); // 采样率
int chanCfg = channelCou; // channel_configuration
// fill in ADTS data
packet[0] = (byte) 0xFF;
packet[1] = (byte) 0xF1;
packet[2] = (byte) ((profile << 6) + (sampling_frequency_index << 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;
}
/*
adts header 第4~6个字节会由于包长度边度
所以这里可以只修改这三个字节
*/
public static void setADTSPacketLen(int channelCou, byte[] packet,
int packetLen){
int chanCfg = channelCou;
packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
}
}
举例:视频编码VideoEncoder,继承AVProcessor核心类
package com.example.mediaitem2;
import android.media.MediaCodec;
import android.view.Surface;
import java.io.File;
import java.io.IOException;
public class VideoEncoder extends com.example.mediaitem2.AVProcessor {
static {
System.loadLibrary("native-lib");
}
private native void RTMP_SEND(byte[] data,int byteLen,boolean isKeyFrame,long timeStamp);
private byte[] configbyte;
VideoEncoder(AVType type,
boolean isMemoryStorage, File storageFile, String AVFormat,
int AVBitRate, int videoWidth, int videoHeight,int videoFrameRate, Surface videoSurface) throws IOException {
super(type,isMemoryStorage,storageFile,AVFormat,AVBitRate,videoWidth,videoHeight,videoFrameRate,videoSurface);
avDealData=new AVDealData() {
@Override
public void dealWithResData() {
if(bufferInfo.flags== MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
{
configbyte=new byte[bufferInfo.size];
configbyte=outData;
RTMP_SEND(outData,bufferInfo.size,false,TimeStamp);
}
else if(bufferInfo.flags== MediaCodec.BUFFER_FLAG_KEY_FRAME)
{
RTMP_SEND(outData,outData.length,true,TimeStamp);
}
else
{
RTMP_SEND(outData,bufferInfo.size,false,TimeStamp);
}
}
};
}
}
调用时:
videoEncoder=new VideoEncoder(AVProcessor.AVType.VIDEO_ENCODE,true,
file,"video/avc",width*height*5,width,height,30,null);