一、概述

   MediaCodec是Android提供的硬件编解码器API,根据此api用户可以对媒体格式的文件执行编解码。其单独没法工作还需要配合上一节介绍的MediaExtractor

  案例:本例最主要的是三个类,分别是BaseDecoder.java 、AudioDecoder、VideoDecoder.java即音视频解码类实例

  MediaCodec原理

二、代码实例

  1.BaseDecoder.java:硬件解码器基类

  

import android.media.MediaCodec;
import android.media.MediaFormat;
import android.util.Log;

import com.yw.thesimpllestplayer.extractor.IExtractor;

import java.io.File;
import java.nio.ByteBuffer;

/**
 * @ProjectName: TheSimpllestplayer
 * @Package: com.yw.thesimpllestplayer.mediaplayer.decoder
 * @ClassName: BaseDecoder
 * @Description: 硬件解码器基类
 * @Author: wei.yang
 * @CreateDate: 2021/11/6 10:32
 * @UpdateUser: 更新者:wei.yang
 * @UpdateDate: 2021/11/6 10:32
 * @UpdateRemark: 更新说明:
 * @Version: 1.0
 */
public abstract class BaseDecoder implements IDecoder {
    private String filePath = null;

    public BaseDecoder(String filePath) {
        this.filePath = filePath;
    }

    private static final String TAG = "BaseDecoder";
//-------------线程相关------------------------
    /**
     * 解码器是否在运行
     */
    private boolean mIsRunning = true;

    /**
     * 线程等待锁
     */
    private Object mLock = new Object();

    /**
     * 是否可以进入解码
     */
    private boolean mReadyForDecode = false;
    //---------------状态相关-----------------------
    /**
     * 音视频解码器(硬件解码器)
     */
    private MediaCodec mCodec = null;

    /**
     * 音视频数据读取器
     */
    private IExtractor mExtractor = null;
    /**
     * 解码输入缓存区
     */
    private ByteBuffer[] mInputBuffers = null;
    /**
     * 解码输出缓存区
     */
    private ByteBuffer[] mOutputBuffers = null;

    /**
     * 解码数据信息
     */
    private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
    /**
     * 初始化解码状态
     */
    private DecodeState mState = DecodeState.STOP;

    /**
     * 解码状态回调
     */
    protected IDecoderStateListener mStateListener;
    /**
     * 流数据是否结束
     */
    private boolean mIsEOS = false;
    /**
     * 视频宽度
     */
    private int mVideoWidth = 0;
    /**
     * 视频高度
     */
    private int mVideoHeight = 0;
    /**
     * 视频时长
     */
    private long mDuration = 0;
    /**
     * 视频结束时间
     */
    private long mEndPos = 0;
    /**
     * 开始解码时间,用于音视频同步
     */
    private long mStartTimeForSync = -1L;
    /**
     * 是否需要音视频渲染同步
     */
    private boolean mSyncRender = true;

    @Override
    public void pause() {
        mState = DecodeState.PAUSE;
    }

    @Override
    public void goOn() {
        mState = DecodeState.DECODING;
        notifyDecode();
    }

    @Override
    public long seekTo(long pos) {
        return 0;
    }

    @Override
    public long seekAndPlay(long pos) {
        return 0;
    }

    @Override
    public void stop() {
        mState = DecodeState.STOP;
        mIsRunning = false;
        notifyDecode();
    }

    @Override
    public boolean isDecoding() {
        return mState == DecodeState.DECODING;
    }

    @Override
    public boolean isSeeking() {
        return mState == DecodeState.SEEKING;
    }

    @Override
    public boolean isStop() {
        return mState == DecodeState.STOP;
    }

    @Override
    public int getWidth() {
        return mVideoWidth;
    }

    @Override
    public int getHeight() {
        return mVideoHeight;
    }

    @Override
    public long getDuration() {
        return mDuration;
    }

    @Override
    public long getCurTimeStamp() {
        return mBufferInfo.presentationTimeUs / 1000;
    }

    @Override
    public int getRotationAngle() {
        return 0;
    }

    @Override
    public MediaFormat getMediaFormat() {
        return mExtractor.getFormat();
    }

    @Override
    public int getTrack() {
        return 0;
    }

    @Override
    public String getFilePath() {
        return filePath;
    }

    @Override
    public IDecoder withoutSync() {
        mSyncRender = false;
        return this;
    }

    @Override
    public void setSizeListener(IDecoderProgress iDecoderProgress) {

    }

    @Override
    public void setStateListener(IDecoderStateListener iDecoderStateListener) {
        this.mStateListener = iDecoderStateListener;
    }

    @Override
    public void run() {

        //解码开始时改变解码状态
        if (mState == DecodeState.STOP) {
            mState = DecodeState.START;
        }
        if (mStateListener != null) {
            mStateListener.decoderPrepare(this);
        }

        //初始化并启动解码器,如果解码失败则暂停线程
        if (!init()) return;
        //开始解码
        Log.e(TAG, "开始解码");
        try {
            while (mIsRunning) {//循环解码渲染
                if (mState != DecodeState.START &&
                        mState != DecodeState.DECODING &&
                        mState != DecodeState.SEEKING) {
                    Log.i(TAG, "进入等待:" + mState);
                    //解码进入等待
                    waitDecode();
                    // ---------【同步时间矫正】-------------
                    //恢复同步的起始时间,即去除等待流失的时间
                    //当前系统时间减去bufferinfo中的时间=等待解码流失的时间
                    mStartTimeForSync = System.currentTimeMillis() - getCurTimeStamp();
                }
                //停止解码,就直接退出循环了
                if (!mIsRunning ||
                        mState == DecodeState.STOP) {
                    mIsRunning = false;
                    break;
                }
                //更新开始解码时间
                if (mStartTimeForSync == -1L) {
                    mStartTimeForSync = System.currentTimeMillis();
                }
                //如果数据没有解码完毕,将数据推入解码器解码
                if (!mIsEOS) {
                    //【解码步骤:2. 见数据压入解码器输入缓冲】
                    mIsEOS = pushBufferToDecoder();
                }

                //将解码后的数据从缓冲区中拉取出来
                int outputBufferIndex = pullBufferFromDecoder();
                if (outputBufferIndex >= 0) {
                    // ---------【音视频同步】-------------
                    if (mSyncRender && mState == DecodeState.DECODING) {
                        sleepRender();
                    }
                    //渲染
                    if (mSyncRender) {
                        render(mOutputBuffers[outputBufferIndex], mBufferInfo);
                    }
                    //将解码数据传出去
                    Frame frame = new Frame();
                    frame.buffer = mOutputBuffers[outputBufferIndex];
                    frame.setBufferInfo(mBufferInfo);
                    if (mStateListener != null) {
                        mStateListener.decodeOneFrame(this, frame);
                    }


                    //释放输出缓冲
                    mCodec.releaseOutputBuffer(outputBufferIndex, true);
                    if (mState == DecodeState.START) {
                        mState = DecodeState.PAUSE;
                    }
                }
                //判断是否解码完成
                if (mBufferInfo.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                    Log.e(TAG, "解码结束");
                    mState = DecodeState.FINISH;
                    if (mStateListener != null) {
                        mStateListener.decoderFinish(this);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            finishDecode();
            release();
        }
    }

    /**
     * 初始化解码器,如果初始化失败则停止解码
     *
     * @return
     */
    private boolean init() {
        //如果文件路径不存在或者文件路径指定的文件为空
        if (filePath == null || !new File(filePath).exists()) {
            Log.e(TAG, "文件路径异常");
            if (mStateListener != null) {
                mStateListener.decoderError(this, "文件路径为空");
            }
            return false;
        }
        if (!check()) return false;
        //初始化数据提取器
        mExtractor = initExtractor(filePath);
        if (mExtractor == null || mExtractor.getFormat() == null) {
            Log.e(TAG, "无法解析文件");
            if (mStateListener != null) {
                mStateListener.decoderError(this, "无法解析文件");
            }
            return false;
        }
        //初始化参数
        if (!initParams()) return false;
        //初始化渲染器
        if (!initRender()) return false;
        //初始化解码器
        if (!initCodec()) return false;

        return true;
    }

    /**
     * 初始化媒体时长及初始化媒体参数
     *
     * @return
     */
    private boolean initParams() {
        try {
            MediaFormat format = mExtractor.getFormat();
            mDuration = format.getLong(MediaFormat.KEY_DURATION) / 1000;
            if (mEndPos == 0L) mEndPos = mDuration;
            initSpecParams(format);
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "提取媒体文件时长或者初始化媒体参数失败:" + e.getMessage());
            if (mStateListener != null) {
                mStateListener.decoderError(this, "提取媒体文件时长或者初始化媒体参数失败");
            }
            return false;
        }
        return true;
    }

    private boolean initCodec() {
        try {
            //获取媒体类型
            String mimeType = mExtractor.getFormat().getString(MediaFormat.KEY_MIME);
            //创建解码器
            mCodec = MediaCodec.createDecoderByType(mimeType);
            //配置解码器
            if (!configCodec(mCodec, mExtractor.getFormat())) {
                //解码线程进入等待
                waitDecode();
            }
            //开始解码
            mCodec.start();
            //从缓冲区中去取输入缓冲
            mInputBuffers = mCodec.getInputBuffers();
            //从缓冲区中取出输出缓冲
            mOutputBuffers = mCodec.getOutputBuffers();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 解码线程进入等待
     */
    private void waitDecode() {
        try {
            if (mState == DecodeState.PAUSE) {
                if (mStateListener != null) {
                    mStateListener.decoderPause(this);
                }
            }
            synchronized (mLock) {
                mLock.wait();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 通知解码线程继续运行
     */
    protected void notifyDecode() {
        synchronized (mLock) {
            mLock.notifyAll();
        }
        if (mState == DecodeState.DECODING) {
            if (mStateListener != null) {
                mStateListener.decoderRunning(this);
            }
        }
    }

    /**
     * 向缓冲区中压缩解码前的数据
     *
     * @return
     */
    private boolean pushBufferToDecoder() {
        //从缓冲区中获取一个bufferindex
        int inputBufferIndex = mCodec.dequeueInputBuffer(1000);
        boolean isEndOfStream = false;
        if (inputBufferIndex >= 0) {
            //根据inputBufferIndex获取inputBuffer
            ByteBuffer inputBuffer = mInputBuffers[inputBufferIndex];
            //使用数据提取器Extractor读取一帧数据
            int sampleSize = mExtractor.readBuffer(inputBuffer);
            if (sampleSize < 0) {//如果数据帧兑取失败则说明读完了
                mCodec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                isEndOfStream = true;
            } else {
                //将读取到的数据送入缓冲区(压入)
                mCodec.queueInputBuffer(inputBufferIndex, 0, sampleSize, mExtractor.getCurrentTimestamp(), 0);
            }
        }
        return isEndOfStream;
    }

    /**
     * 从缓冲区中取出解码后的数据
     *
     * @return
     */
    private int pullBufferFromDecoder() {
        //从缓冲区中取出outputbufferindex
        int outputBufferIndex = mCodec.dequeueOutputBuffer(mBufferInfo, 1000);
        switch (outputBufferIndex) {
            case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                break;
            case MediaCodec.INFO_TRY_AGAIN_LATER:
                break;
            case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                mOutputBuffers = mCodec.getOutputBuffers();
                break;
            default:
                return outputBufferIndex;
        }
        return -1;
    }

    /**
     * 音视频同步
     */
    private void sleepRender() {
        try {
            long passTime = System.currentTimeMillis() - mStartTimeForSync;
            long currTime = getCurTimeStamp();
            if (currTime > passTime) {
                Thread.sleep(currTime - passTime);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 释放解码器
     */
    private void release() {
        try {
            Log.i(TAG, "解码停止,释放解码器");
            mState = DecodeState.STOP;
            mIsEOS = false;
            mExtractor.stop();
            mCodec.stop();
            mCodec.release();
            if (mStateListener != null) {
                mStateListener.decoderDestroy(this);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 检查子类参数
     *
     * @return
     */
    public abstract boolean check();

    /**
     * 初始化数据提取器
     *
     * @param filePath 媒体文件路径
     * @return 数据提取器
     */
    public abstract IExtractor initExtractor(String filePath);

    /**
     * 初始化子类自己持有的媒体参数
     *
     * @param format
     */
    public abstract void initSpecParams(MediaFormat format);

    /**
     * 配置解码器
     *
     * @param codec  硬件解码器
     * @param format 媒体格式参数
     * @return
     */
    public abstract boolean configCodec(MediaCodec codec, MediaFormat format);

    /**
     * 初始化渲染器
     *
     * @return
     */
    public abstract boolean initRender();

    /**
     * 执行渲染操作
     *
     * @param outputBuffer 输出的渲染数据
     * @param bufferInfo   解码出来的数据
     */
    public abstract void render(ByteBuffer outputBuffer, MediaCodec.BufferInfo bufferInfo);

    /**
     * 结束解码
     */
    public abstract void finishDecode();
}

 

  2.AudioDecoder.java:音频解码器

import android.media.MediaCodec;
import android.media.MediaFormat;

import com.yw.thesimpllestplayer.audioplayer.AudioPlayer;
import com.yw.thesimpllestplayer.extractor.AudioExtractor;
import com.yw.thesimpllestplayer.extractor.IExtractor;

import java.nio.ByteBuffer;

/**
 * @ProjectName: TheSimpllestplayer
 * @Package: com.yw.thesimpllestplayer.mediaplayer.decoder
 * @ClassName: AudioDecoder
 * @Description: 音频解码器
 * @Author: wei.yang
 * @CreateDate: 2021/11/6 13:55
 * @UpdateUser: 更新者:wei.yang
 * @UpdateDate: 2021/11/6 13:55
 * @UpdateRemark: 更新说明:
 * @Version: 1.0
 */
public class AudioDecoder extends BaseDecoder {
    private AudioPlayer audioPlayer;

    public AudioDecoder(String filePath) {
        super(filePath);
    }

    @Override
    public boolean check() {
        return true;
    }

    @Override
    public IExtractor initExtractor(String filePath) {
        return new AudioExtractor(filePath);
    }

    @Override
    public void initSpecParams(MediaFormat format) {
        if (audioPlayer == null) {
            audioPlayer = new AudioPlayer(format);
        }

    }

    @Override
    public boolean configCodec(MediaCodec codec, MediaFormat format) {
        codec.configure(format, null, null, 0);
        return true;
    }

    @Override
    public boolean initRender() {
        audioPlayer.initPlayer();
        return true;
    }

    @Override
    public void render(ByteBuffer outputBuffer, MediaCodec.BufferInfo bufferInfo) {
        audioPlayer.play(outputBuffer, bufferInfo);
    }

    @Override
    public void finishDecode() {
        audioPlayer.stop();
        audioPlayer.release();

    }
}

 

  2.VideoDecoder.java:视频解码器

import android.media.MediaCodec;
import android.media.MediaFormat;
import android.util.Log;
import android.view.Surface;

import com.yw.thesimpllestplayer.extractor.IExtractor;
import com.yw.thesimpllestplayer.extractor.VideoExtractor;

import java.nio.ByteBuffer;

/**
 * @ProjectName: TheSimpllestplayer
 * @Package: com.yw.thesimpllestplayer.mediaplayer.decoder
 * @ClassName: VideoDecoder
 * @Description: 视频解码器
 * @Author: wei.yang
 * @CreateDate: 2021/11/6 13:49
 * @UpdateUser: 更新者:wei.yang
 * @UpdateDate: 2021/11/6 13:49
 * @UpdateRemark: 更新说明:
 * @Version: 1.0
 */
public class VideoDecoder extends BaseDecoder {
    private static final String TAG = "VideoDecoder";
    private Surface surface;

    public VideoDecoder(String filePath, Surface surface) {
        super(filePath);
        this.surface = surface;
    }

    @Override
    public boolean check() {
        if (surface == null) {
            Log.e(TAG, "Surface不能为空");
            if (mStateListener != null) {
                mStateListener.decoderError(this, "显示器为空");
            }
            return false;
        }
        return true;
    }

    @Override
    public IExtractor initExtractor(String filePath) {
        return new VideoExtractor(filePath);
    }

    @Override
    public void initSpecParams(MediaFormat format) {

    }

    @Override
    public boolean configCodec(MediaCodec codec, MediaFormat format) {
        if (surface != null) {
            codec.configure(format, surface, null, 0);
            notifyDecode();
        } else {
            if (mStateListener != null) {
                mStateListener.decoderError(this, "配置解码器失败,因为Surface为空");
            }
            Log.e(TAG, "配置解码器失败,因为Surface为空");
            return false;
        }
        return true;
    }

    @Override
    public boolean initRender() {
        return true;
    }

    @Override
    public void render(ByteBuffer outputBuffer, MediaCodec.BufferInfo bufferInfo) {

    }

    @Override
    public void finishDecode() {

    }
}