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,需要的小伙伴可以私聊小编。

相关推荐
louisgeek22 分钟前
Android NSD 网络服务发现
android
张可1 小时前
历时两年半开发,Fread 项目现在决定开源,基于 Kotlin Multiplatform 和 Compose Multiplatform 实现
android·前端·kotlin
音视频牛哥1 小时前
[对话牛哥]技术不是炫技,是让世界听见你声音的开始
音视频开发·视频编码·直播
余辉zmh1 小时前
【Linux系统篇】:信号的生命周期---从触发到保存与捕捉的底层逻辑
android·java·linux
孤鸿玉2 小时前
[Flutter小试牛刀] 低配版signals,添加多层监听链
android·前端·响应式设计
雨和卡布奇诺2 小时前
LiveData源码浅析
android
淡蓝色_justin2 小时前
Hilt-plus 简介
android·android jetpack
app1e2343 小时前
ctfshow web入门 命令执行(29-77)
android·前端
音视频牛哥4 小时前
一线牵,万象生:探索Android平台GB28181设备接入SDK的精细技术
音视频开发·视频编码·直播