一、概述
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() {
}
}