Android最简单的视频播放器之MediaCodec硬件解码器封装(二)
一、概述
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() { } }
分类:
Android
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库