Android最簡單的視頻播放器之MediaCodec硬件解碼器封裝(二)


一、概述

   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() {

    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM