jjzjj

MediaCodec视频解码流程详解及参考demo

周末的音视频 2023-08-02 原文

一、MediaCodec简介

        MediaCodec是Android自带的底层多媒体支持架构的一部分(通常与 MediaExtractor,MediaSync,MediaMuxer,MediaCrypto,MediaDrm,Image,Surface 和 AudioTrack 一起使用)。可以用来访问底层媒体编解码器,即编码器/解码器的组件。

        使用MediaCodec编解码即我们常说的硬编解码,它的优势是使用底层硬件编解码,效率比软编解码FFmpeg要高出不少。但劣势是由于硬编解码依赖于手机厂商的硬件设计,导致不同机型的效果可能千差万别,不好管控和统一。

        因此在实际开发中,使用软编解码或硬编解码还是要看具体的实际场景。

二、MediaCodec生命周期

        MediaCodec 有三种状态,分别是执行(Executing)、停止(Stopped)和释放(Released),其中执行和停止分别有三个子状态,执行的三个字状态分别是 Flushed、Running 和 Stream-of-Stream,停止的三个子状态分别是 Uninitialized、Configured 和 Error,MediaCodec 生命周期示意图如下:

        同步模式下:

         异步模式下:

         当创建了 MediaCodec 之后,是处于未初始化的 Uninitialized 状态,调用 configure 方法之后就处于 Configured 状态,调用了 start 方法之后,就处于 Executing 状态。

        在 Executing 状态下开始处理数据,它又有三个子状态,分别是:Flushed、RunningEnd of Stream。当一调用 start 方法之后,就进入了 Flushed 状态,从输入缓冲区队列中取出一个缓冲区就进入了 Running 状态,当入队的缓冲区带有 EOS 标志时, 就会切换到 End of Stream 状态, MediaCodec 不再接受入队的缓冲区,但是仍然会对已入队的且没有进行编解码操作的缓冲区进行操作、输出,直到输出的缓冲区带有 EOS 标志,表示编解码操作完成了。

        在 Executing 状态下可以调用 flush 方法,使 MediaCodec 切换到 Flushed 状态。

        在 Executing 状态下可以调用 stop 方法,使 MediaCodec 切换到 Uninitialized 状态,然后再次调用 configure 方法进入 Configured 状态。另外,当调用 reset 方法也会进入到 Uninitialized 状态。

        当不再需要 MediaCodec 时,调用 release 方法将它释放掉,进入 Released 状态。

        当 MediaCodec 工作发生异常时,会进入到 Error 状态,此时还是可以通过 reset 方法恢复过来,进入 Uninitialized 状态。

三、MediaCodec解码流程

        下面将讲解使用mediacodec对mp4的视频文件进行解码的API使用流程。本文主要是采用同步的解码方式。

        解码流程工作模型:

 

1、创建mediacodec并初始化

        可通过createDecoderByType来创建mediacodec:

mediaCodec = MediaCodec.createDecoderByType("video/avc");

        上面表示创建了一个解码器,并指定了解码类型为avc的视频解码器。

        初始化主要是为了给这个解码器设置一些格式,配置等,如下完整的创建和初始代码:

    private void initMediaCodecSys() {
        try {
            //创建
            mediaCodec = MediaCodec.createDecoderByType("video/avc");
            //格式
            mediaFormat = MediaFormat.createVideoFormat("video/avc", 1280, 720);
            mediaExtractor = new MediaExtractor();
            //MP4 文件存放位置
            mediaExtractor.setDataSource(MainActivity.MP4_PLAY_PATH);
            Log.d(TAG, "getTrackCount: " + mediaExtractor.getTrackCount());
            for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
                MediaFormat format = mediaExtractor.getTrackFormat(i);
                String mime = format.getString(MediaFormat.KEY_MIME);
                Log.d(TAG, "mime: " + mime);
                if (mime.startsWith("video")) {
                    mediaFormat = format;
                    mediaExtractor.selectTrack(i);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        Surface surface = MainActivity.getSurface();
        //配置
        mediaCodec.configure(mediaFormat, surface, null, 0);
        mediaCodec.start();
    }

2、视频解码线程

        视频解码线程主要内容就是进行解码的流程控制。这个阶段的相应API有如下:

 // 获取可用的输入缓冲区的索引
 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) 

        获取可用的输入缓冲区的索引:

int inputIndex = mediaCodec.dequeueInputBuffer(-1);

        获取输入缓冲区:

ByteBuffer byteBuffer = mediaCodec.getInputBuffer(inputIndex);

        读取相关数据:

//读取一片或者一帧数据
int sampSize = mediaExtractor.readSampleData(byteBuffer, 0);
//读取时间戳
long time = mediaExtractor.getSampleTime();

        将填满数据的inputBuffer提交到编码队列:

if (sampSize > 0 && time > 0) {
    mediaCodec.queueInputBuffer(inputIndex, 0, sampSize, time, 0);
    //读取一帧后必须调用,提取下一帧
    //控制帧率在30帧左右
    mSpeedController.preRender(time);
    mediaExtractor.advance();
}

        获取已成功编解码的输出缓冲区的索引:

BufferInfo bufferInfo = new BufferInfo();
int outIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);

        输出缓冲区:

if (outIndex >= 0) {
    mediaCodec.releaseOutputBuffer(outIndex, true);
}

        完整的视频解码线程代码如下:

/**
 * Play the MP4 file Thread
 * 解码主流程
 */
private class DecoderMP4Thread extends Thread {
    long pts = 0;

    @Override
    public void run() {
        super.run();
        while (!isDecodeFinish) {
            int inputIndex = mediaCodec.dequeueInputBuffer(-1);
            Log.d(TAG, "inputIndex: " + inputIndex);
            if (inputIndex >= 0) {
                ByteBuffer byteBuffer = mediaCodec.getInputBuffer(inputIndex);
                //读取一片或者一帧数据
                int sampSize = mediaExtractor.readSampleData(byteBuffer, 0);
                //读取时间戳
                long time = mediaExtractor.getSampleTime();
                if (sampSize > 0 && time > 0) {
                    mediaCodec.queueInputBuffer(inputIndex, 0, sampSize, time, 0);
                    //读取一帧后必须调用,提取下一帧
                    //控制帧率在30帧左右
                    mSpeedController.preRender(time);
                    mediaExtractor.advance();
                }
            }
            BufferInfo bufferInfo = new BufferInfo();
            int outIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
            if (outIndex >= 0) {
                mediaCodec.releaseOutputBuffer(outIndex, true);
            }
        }
    }
}

3、解码结束关闭回收

        完成解码播放后,对相关内存回收和关闭处理:

public void close() {
    try {
        Log.d(TAG, "close start");
        if (mediaCodec != null) {
            isDecodeFinish = true;
            try {
                if (mDecodeMp4Thread != null) {
                    mDecodeMp4Thread.join(2000);
                }
            } catch (InterruptedException e) {
                Log.e(TAG, "InterruptedException " + e);
            }
            boolean isAlive = mDecodeMp4Thread.isAlive();
            Log.d(TAG, "close end isAlive :" + isAlive);
            mediaCodec.stop();
            mediaCodec.release();
            mediaCodec = null;
            mSpeedController.reset();
        }
    } catch (IllegalStateException e) {
        e.printStackTrace();
    }
    instance = null;
}

        至此,整个视频解码流程就讲解完毕。

四、demo运行

        本人写了一个最简单的demo,使用mediacodec对mp4文件进行纯视频的解码播放,采用同步方式。

        该demo的解码对象mp4文件是内置在了raw/video.mp4,如果需要替换视频文件,可以替换这里:

         demo运行效果如下:

         点击START进行播放:

         播放过程中点击STOP可以停止播放。

        完整例子已经放到github上,如下:

        https://github.com/weekend-y/mediacodec_demo/tree/master/MediaCodec_DecodeMP4

        

有关MediaCodec视频解码流程详解及参考demo的更多相关文章

  1. 动漫制作技巧如何制作动漫视频 - 2

    动漫制作技巧是很多新人想了解的问题,今天小编就来解答与大家分享一下动漫制作流程,为了帮助有兴趣的同学理解,大多数人会选择动漫培训机构,那么今天小编就带大家来看看动漫制作要掌握哪些技巧?一、动漫作品首先完成草图设计和原型制作。设计草图要有目的、有对象、有步骤、要形象、要简单、符合实际。设计图要一致性,以保证制作的顺利进行。二、原型制作是根据设计图纸和制作材料,可以是手绘也可以是3d软件创建。在此步骤中,要注意的问题是色彩和平面布局。三、动漫制作制作完成后,加工成型。完成不同的表现形式后,就要对设计稿进行加工处理,使加工的难易度降低,并得到一些基本准确的概念,以便于后续的大样、准确的尺寸制定。四、

  2. python ffmpeg 使用 pyav 转换 一组图像 到 视频 - 2

    2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p

  3. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  4. ruby - 如何更改此正则表达式以从未指定 v 参数的 Youtube URL 获取 Youtube 视频 ID? - 2

    目前我正在使用这个正则表达式从YoutubeURL中提取视频ID:url.match(/v=([^&]*)/)[1]我怎样才能改变它,以便它也可以从这个没有v参数的YoutubeURL获取视频ID:http://www.youtube.com/user/SHAYTARDS#p/u/9/Xc81AajGUMU感谢阅读。编辑:我正在使用ruby​​1.8.7 最佳答案 对于Ruby1.8.7,这就可以了。url_1='http://www.youtube.com/watch?v=8WVTOUh53QY&feature=feedf'url

  5. iNFTnews | 周杰伦18年前未发布的作品Demo,藏在了区块链技术里 - 2

    当音乐碰上区块链技术,会擦出怎样的火花?或许周杰伦已经给了我们答案。8月29日下午,B站独家首发周杰伦限定珍藏Demo独家访谈VCR,周杰伦在VCR里分享了《晴天》《青花瓷》《搁浅》《爱在西元前》四首经典歌曲Demo背后的创作故事,并首次公布18年前未发布的神秘作品《纽约地铁》的Demo。在VCR中,方文山和杰威尔音乐提及到“多亏了区块链技术,现在我们可以将这些Demos,变成独一无二具有收藏价值的艺术品,这些Demos可以在薄盒(国内数藏平台)上听到。”如何将音乐与区块链技术相结合,薄盒方面称:“薄盒作为区块链技术服务方,打破传统对于区块链技术只能作为数字收藏的理解。聚焦于区块链技术赋能,在

  6. 物联网MQTT协议详解 - 2

    一、什么是MQTT协议MessageQueuingTelemetryTransport:消息队列遥测传输协议。是一种基于客户端-服务端的发布/订阅模式。与HTTP一样,基于TCP/IP协议之上的通讯协议,提供有序、无损、双向连接,由IBM(蓝色巨人)发布。原理:(1)MQTT协议身份和消息格式有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。MQTT传输的消息分为:主题(Topic)和负载(payload)两部分Topic,可以理解为消息的类型,订阅者订阅(Su

  7. Tcl脚本入门笔记详解(一) - 2

    TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是

  8. 【详解】Docker安装Elasticsearch7.16.1集群 - 2

    开门见山|拉取镜像dockerpullelasticsearch:7.16.1|配置存放的目录#存放配置文件的文件夹mkdir-p/opt/docker/elasticsearch/node-1/config#存放数据的文件夹mkdir-p/opt/docker/elasticsearch/node-1/data#存放运行日志的文件夹mkdir-p/opt/docker/elasticsearch/node-1/log#存放IK分词插件的文件夹mkdir-p/opt/docker/elasticsearch/node-1/plugins若你使用了moba,直接右键新建即可如上图所示依次类推创建

  9. 【Elasticsearch基础】Elasticsearch索引、文档以及映射操作详解 - 2

    文章目录概念索引相关操作创建索引更新副本查看索引删除索引索引的打开与关闭收缩索引索引别名查询索引别名文档相关操作新建文档查询文档更新文档删除文档映射相关操作查询文档映射创建静态映射创建索引并添加映射概念es中有三个概念要清楚,分别为索引、映射和文档(不用死记硬背,大概有个印象就可以)索引可理解为MySQL数据库;映射可理解为MySQL的表结构;文档可理解为MySQL表中的每行数据静态映射和动态映射上面已经介绍了,映射可理解为MySQL的表结构,在MySQL中,向表中插入数据是需要先创建表结构的;但在es中不必这样,可以直接插入文档,es可以根据插入的文档(数据),动态的创建映射(表结构),这就

  10. 等保工作流程和明细 - 2

    一、系统定级信息系统运营使用单位按照等级保护管理办法和定级指南,自主确定信息系统的安全保护等级。有上级主管部门的,应当经上级主管部门审批。跨省或全国统一联网运行的信息系统可以由其主管部门统一确定安全保护等级。定级需要根据信息系统的实际情况合理定级。二、系统备案第二级以上信息系统定级单位到所在地设区的市级以上公安机关办理备案手续。省级单位到省公安厅网安总队备案,各地市单位一般直接到市级网安支队备案,也有部分地市区县单位的定级备案资料是先交到区县公安网监大队的,具体根据各地市要求来。信息系统运营、使用单位或者其主管部门应当在信息系统安全保护等级确定后30日内,到公安机关办理备案手续。三、初次测评信

随机推荐