android studio中mediacodec编解码封装(更新完毕)

大部分网站上一般都只有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<AVData> mFreeQueue;
	private LinkedBlockingQueue<AVData> mDataQueue;

	public class AVData {
		byte[] data;
		int readSize;

		AVData(int bufferSize) {
			data = new byte[bufferSize];
		}
	}

	public BufferPool(int capacity, int bufferSize) {
		mFreeQueue = new LinkedBlockingQueue<AVData>(capacity);
		for (int i = 0; i < capacity; i++) {
			mFreeQueue.add(new AVData(bufferSize));
		}
		mDataQueue = new LinkedBlockingQueue<AVData>(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);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值