Android音视频-MediaCode解码播放MP4

简介

日常开发中,我们都习惯于使用第三方播放器进行音视频的播放,如常用的ijkPlayer,Gsyvideoplayer等三方库,本篇文章给大家带来Android原生的视频解码器(MediaCode)对MP4进行解码播放,帮助读者更加深入的学习视频播放原理。

MediaCode是Android原生底层多媒体框架,通常与一起使用MediaExtractor , MediaSync , MediaMuxer , MediaCrypto , MediaDrm , Image , Surface ,和AudioTrack。MediaCode使用底层硬件编解码,编解码速度快,效率高。

MediaCode解码流程

先了解MediaCode对视频解码播放的整个流程。具体解码流程如下:

相关API使用

java 复制代码
//获取可用的输入缓冲区的索引
public int dequeueInputBuffer (long timeoutUs)
// 获取输入缓冲区
public ByteBuffer getInputBuffer(int index)
// 将填满数据的inputBuffer提交到编码队列
public final void queueInputBuffer(int index,int offset, int size, long presentationTimeUs, int flags)
// 获取已成功编解码的输出缓冲区的索引
public final int dequeueOutputBuffer(BufferInfo info, long timeoutUs)
// 获取输出缓冲区
public ByteBuffer getOutputBuffer(int index)
// 释放输出缓冲区
public final void releaseOutputBuffer(int index, boolean render)

解码流程

  1. 创建创建视频多媒体提取器并设置视频路径
scss 复制代码
//创建视频多媒体提取器
 val mVideoExtractor = MediaExtractor()
 //设置视频多媒体路径
 val mVideoExtractor.setDataSource(videoPath)
 
  1. 获取视频的编码格式及视频轨道
ini 复制代码
for (index in 0 until mVideoExtractor.trackCount) {
            val format = mVideoExtractor.getTrackFormat(index)
            val keyMine = format.getString(MediaFormat.KEY_MIME)
            if (keyMine?.startsWith("video/") == true) {
                mVideoTrackIndex = index
                mVideoKeyType = keyMine
                mVideoFormat = format
                continue
            }
        }
  1. 创建 MediaCodec 对象,指定编码器或解码器类型
ini 复制代码
  //创建视频解码器
     mVideoDecoder = MediaCodec.createDecoderByType(mVideoKeyType)
  1. 配置解码器参数,例如SurfaceView,编码格式、分辨率、帧率、码率等
scss 复制代码
   //设置视频轨道
   mVideoExtractor.selectTrack(mVideoTrackIndex)
  //配置解码器参数
  mVideoDecoder.configure(mVideoFormat, MediaCodecVideoActivity.getSurface(), null, 0)
  1. 开始执行解码任务,调用 start() 方法启动解码器。
scss 复制代码
//开始执行解码
mVideoDecoder.start()
  1. 开启线程将解码出来的视频流刷新到SurfaceView上进行显示
ini 复制代码
 if (!isPlay) {
                        //获取可用的输入缓冲区的索引
                        val inputIndex = mVideoDecoder.dequeueInputBuffer(-1)
                        //缓冲区有数据
                        if (inputIndex > 0) {
                            //读取缓冲区数据
                            val byteBuffer = mVideoDecoder.getInputBuffer(inputIndex)
                            //读取一片或一帧数据
                            byteBuffer?.let {
                                var sampleSize = mVideoExtractor.readSampleData(it, 0)
            
                                if (sampleSize < 0) {
                                    // sampleSize = 0;
                                    // isFinishDecode = true
                                    mVideoExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
                                } else {
                                    //读取时间戳
                                    val time = mVideoExtractor.sampleTime
                                    //读取一帧数据
                                    mVideoDecoder.queueInputBuffer(inputIndex, 0, sampleSize, time, 0)
                                    /**
                                     * 进入下一个样本。如果没有更多的样本数据可用,返回false(流结束)
                                     * 。当提取本地文件时,
                                     * advance和readSampleData的行为在并发写同一个本地文件时是未定义的
                                     * ;更具体地说,流结束的信号可能比预期的要早。
                                     */
                                    mVideoExtractor.advance()
                                }

                            }
                        }
                        val bufferInfo = BufferInfo()
                        val outIndex = mVideoDecoder.dequeueOutputBuffer(bufferInfo, 0)
                        if (outIndex >= 0) {
                            /* if (bufferInfo.flags == BUFFER_FLAG_END_OF_STREAM){
                                 isFinishDecode = true
                             }*/
                            mVideoDecoder.releaseOutputBuffer(outIndex, true)

                        }
                        if (bufferInfo.flags == BUFFER_FLAG_END_OF_STREAM) {
                            mVideoExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
                        }
                    }
  1. 释放资源
scss 复制代码
 if (mVideoDecoder!=null){
        mVideoDecoder.stop()
        mVideoDecoder.release()
     }
 mVideoExtractor.release()

实现效果

本人Demo实现效果如下:

完整代码已经上传GitHub,需要的小伙伴可以私聊小编。

相关推荐
Watink Cpper1 小时前
[MySQL初阶]MySQL(8)索引机制:下
android·数据库·b树·mysql·b+树·myisam·innodedb
一起搞IT吧1 小时前
高通camx IOVA内存不足,导致10-15x持续拍照后,点击拍照键定屏无反应,过一会相机闪退
android·数码相机
前行的小黑炭3 小时前
设计模式:为什么使用模板设计模式(不相同的步骤进行抽取,使用不同的子类实现)减少重复代码,让代码更好维护。
android·java·kotlin
ufo00l4 小时前
2025年了,Rxjava解决的用户痛点,是否kotlin协程也能解决,他们各有什么优缺点?
android
古鸽100864 小时前
libutils android::Thread 介绍
android
_一条咸鱼_4 小时前
Android Compose 框架性能分析深度解析(五十七)
android
BrookL4 小时前
Android面试笔记-kotlin相关
android·面试
QING6187 小时前
Kotlin Delegates.notNull用法及代码示例
android·kotlin·源码阅读
QING6187 小时前
Kotlin filterNot用法及代码示例
android·kotlin·源码阅读
张风捷特烈1 天前
Flutter 伪3D绘制#03 | 轴测投影原理分析
android·flutter·canvas