一、概述
MediaCodec是Android提供的硬件編解碼器API,根據此api用戶可以對媒體格式的文件執行編解碼。其單獨沒法工作還需要配合上一節介紹的MediaExtractor
案例:本例最主要的是三個類,分別是BaseDecoder.java 、AudioDecoder、VideoDecoder.java即音視頻解碼類實例
二、代碼實例
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() { } }
