Android MediaCodec 简明教程(四):使用 MediaCodec 将视频解码到 Surface,并使用 SurfaceView 播放视频

系列文章目录

  1. Android MediaCodec 简明教程(一):使用 MediaCodecList 查询 Codec 信息,并创建 MediaCodec 编解码器
  2. Android MediaCodec 简明教程(二):使用 MediaCodecInfo.CodecCapabilities 查询 Codec 支持的宽高,颜色空间等能力
  3. Android MediaCodec 简明教程(三):详解如何在同步与异步模式下,使用MediaCodec将视频解码到ByteBuffers,并在ImageView上展示

文章目录

  • 系列文章目录
  • 前言
  • 一、Surface 是什么?
  • 二、MediaCodec 解码到 Surface
    • 2.1 Surface 从哪来?
    • 2. 2 MediaCodec 与 Surface 共享内存,实现零拷贝
    • 2.3 数据流动
    • 2.4 ReleaseOutputBuffer,控制水流速度
    • 2.5 Show me the code
  • 参考


前言

在上一个教程 Android MediaCodec 简明教程(三) 中,我们学会了使用 MediaCodec 解码到 ByteBuffers 上,包括同步模式和异步模式。本章将讨论 MediaCodec 解码到 Surface 的相关知识点。Google 推荐使用 Surface 进行编解码操作,这样效率更高。

一、Surface 是什么?

Android Surface 是一个复杂的概念,它涉及到 Android 图形系统,网络已经有很多相关的博文,例如:

  • 从整体上看Android图像显示系统
  • Android图形系统综述(干货篇)
  • 浅谈Android Surface机制
  • 深入Android系统(十二)Android图形显示系统-1-显示原理与Surface

Surface 是 Android 图形架构的一部分,它与 SurfaceFlinger、SurfaceView、SurfaceTexture、SurfaceHolder 等组件一起,构成了 Android 的图形系统。
Surface 的主要作用是存储图形数据。当你在一个 Surface 上绘制图像时,这些图像会被存储在 Surface 的缓冲区中。然后,这个 Surface 可以被提交给 SurfaceFlinger,SurfaceFlinger 会将这个 Surface 的内容合成到屏幕上。
Surface 通常与其他组件一起使用。例如,你可以使用 Canvas 在一个 Surface 上绘制2D图像,或者使用 OpenGL ES 在一个 Surface 上绘制3D图像。你也可以使用 MediaCodec 或者 Camera 将视频帧输出到一个 Surface 上。
Surface 还有一些其他的特性。例如,它可以被多个进程共享,这使得跨进程的图形数据传输成为可能。它还支持 vsync 信号,这可以帮助你实现平滑的动画效果。

这部分对于我来说过于复杂了,并且这也不是目前要关心的内容,我们抓一个重点即可:Surface 中有一个 BufferQueue,里头存放着图像数据,生产者将数据送入 Queue 中,而消费者从 Queue 获取数据。在 Android 中,Surface 可以被看作是一个画布,你可以在上面绘制图像,然后这些图像会被显示到屏幕上。这个画布并不是一个实体,而是一个虚拟的概念,它代表了一个可以被绘制的区域。

你可以把 Surface 想象成一个电子屏幕上的窗户。你可以在这个窗户上画画,然后这些画就会显示在电子屏幕上。这个窗户就像是你和电子屏幕之间的一个桥梁,你通过这个窗户,将你的画送到电子屏幕上。在这个比喻中,你的画就是图像数据,电子屏幕就是 Android 设备的显示屏,而窗户就是 Surface。你将图像数据(即你的画)送入 Surface(即窗户),然后这些数据就会被渲染到显示屏(即电子屏幕)上。

在 Android Surface的理解和应用 中对 Surface 中 生产者 - 消费者 架构做了较为详细的说明,不再赘述。

二、MediaCodec 解码到 Surface

像上一个教程一样,我们将使用 MediaExtractor 和 MediaCodec 解码视频,不同的是,解码后的视频帧画面将使用 SurfaceView 显示。

2.1 Surface 从哪来?

获取 Surface 最简单的方式是:使用 SurfaceView。例如:

val surfaceView = findViewById<SurfaceView>(R.id.surface_view)
val surface = surfaceView.holder.surface

2. 2 MediaCodec 与 Surface 共享内存,实现零拷贝

MediaCodec 使用 Surface 进行解码时,效率更高的原因是因为 Android 使用了共享内存技术。具体来说,MediaCodec 解码后的数据会直接传递给 Surface 的 BufferQueue,而不是通过拷贝的方式传递给 Surface。这样就避免了从 MediaCodec 到 Surface 之间的数据拷贝,提高了解码效率。

为了实现这种共享内存的方式,我们在调用 configure 方法时需要传入一个 Surface 对象,这样才能让 MediaCodec 和 Surface 共享同一个 BufferQueue。这种优化方式可以有效地减少数据拷贝的次数,提高解码效率,从而更好地满足视频播放等应用的需求。

codec.configure(videoFormat, surface, null, 0)

2.3 数据流动

在解码的过程涉及到三个对象: MediaExtractor、MediaCodec 和 Surface,数据就像流水线一样从一个地方流向另一个地方,并最终被消费。以 SurfaceView 为例,最终的消费者是 SurfaceFlinger,流水线如下图所示:
在这里插入图片描述

2.4 ReleaseOutputBuffer,控制水流速度

MediaCodec 的 releaseOutputBuffer 方法就像一个水龙头,你可以通过它来控制解码的速度。这个方法有两个版本:

  1. releaseOutputBuffer(int index, boolean render):这个版本的方法中,如果 render 参数为 true,那么解码后的数据(Buffer)会立即被送到 Surface 进行渲染(播放),播放完后,这个 Buffer 就会被标记为可用,返回给 MediaCodec。如果 render 参数为 false,那么这个 Buffer 不会被送到 Surface,而是直接被释放,然后被标记为可用,返回给 MediaCodec。

  2. releaseOutputBuffer(int index, long renderTimestampNs):这个版本的方法中,你可以传入一个时间戳 renderTimestampNs。如果这个时间戳小于当前的系统时间,那么 Buffer 的内容会立即被渲染。如果这个时间戳大于当前的系统时间,那么系统会等待,直到系统时间达到这个时间戳,然后再进行渲染。这个特性非常有用,特别是当你需要精确控制音频或视频的播放时间,或者需要同步多个音频或视频流的播放时。

总的来说,releaseOutputBuffer 方法就像一个控制解码速度的调节器,你可以通过它来精确控制音视频的播放。

2.5 Show me the code

所有代码你可以在 DecodeUsingSurfaceActivity 中找到,下面代码中,给出了同步和异步两种实现。

private fun decodeToSurface(surface: Surface){// create and configure media extractorval mediaExtractor = MediaExtractor()resources.openRawResourceFd(R.raw.h264_720p).use {mediaExtractor.setDataSource(it)}val videoTrackIndex = 0mediaExtractor.selectTrack(videoTrackIndex)val videoFormat = mediaExtractor.getTrackFormat(videoTrackIndex)// create and configure media codecval codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)val codecName = codecList.findDecoderForFormat(videoFormat)val codec = MediaCodec.createByCodecName(codecName)// configure with surfacecodec.configure(videoFormat, surface, null, 0)// start decodingval maxInputSize = videoFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)val inputBuffer = ByteBuffer.allocate(maxInputSize)val bufferInfo = MediaCodec.BufferInfo()val timeoutUs = 10000L // 10msvar inputEnd = falsevar outputEnd = falsecodec.start()while (!outputEnd && !stopDecoding) {val isExtractorReadEnd =getInputBufferFromExtractor(mediaExtractor, inputBuffer, bufferInfo)if (isExtractorReadEnd) {inputEnd = true}// get codec input buffer and fill it with data from extractor// timeoutUs is -1L means wait foreverval inputBufferId = codec.dequeueInputBuffer(-1L)if (inputBufferId >= 0) {if (inputEnd) {codec.queueInputBuffer(inputBufferId, 0, 0, 0,MediaCodec.BUFFER_FLAG_END_OF_STREAM)} else {val codecInputBuffer = codec.getInputBuffer(inputBufferId)codecInputBuffer!!.put(inputBuffer)codec.queueInputBuffer(inputBufferId,0,bufferInfo.size,bufferInfo.presentationTimeUs,0)}}// get output buffer from codec and render it to image view// NOTE! dequeueOutputBuffer with -1L is will stuck here,  so wait 10ms hereval outputBufferId = codec.dequeueOutputBuffer(bufferInfo, timeoutUs)if (outputBufferId >= 0) {if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {outputEnd = true}if (bufferInfo.size > 0) {val pts = bufferInfo.presentationTimeUs * 1000L + startTimecodec.releaseOutputBuffer(outputBufferId, pts)}}mediaExtractor.advance()}mediaExtractor.release()codec.stop()codec.release()}private fun decodeToSurfaceAsync(surface: Surface) {// create and configure media extractorval mediaExtractor = MediaExtractor()resources.openRawResourceFd(R.raw.h264_720p).use {mediaExtractor.setDataSource(it)}val videoTrackIndex = 0mediaExtractor.selectTrack(videoTrackIndex)val videoFormat = mediaExtractor.getTrackFormat(videoTrackIndex)// create and configure media codecval codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)val codecName = codecList.findDecoderForFormat(videoFormat)val codec = MediaCodec.createByCodecName(codecName)val maxInputSize = videoFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)val inputBuffer = ByteBuffer.allocate(maxInputSize)val bufferInfo = MediaCodec.BufferInfo()val inputEnd = AtomicBoolean(false)val outputEnd = AtomicBoolean(false)// set codec callback in async modecodec.setCallback(object : MediaCodec.Callback() {override fun onInputBufferAvailable(codec: MediaCodec, inputBufferId: Int) {val isExtractorReadEnd =getInputBufferFromExtractor(mediaExtractor, inputBuffer, bufferInfo)if (isExtractorReadEnd) {inputEnd.set(true)codec.queueInputBuffer(inputBufferId, 0, 0, 0,MediaCodec.BUFFER_FLAG_END_OF_STREAM)} else {val codecInputBuffer = codec.getInputBuffer(inputBufferId)codecInputBuffer!!.put(inputBuffer)codec.queueInputBuffer(inputBufferId,0,bufferInfo.size,bufferInfo.presentationTimeUs,bufferInfo.flags)mediaExtractor.advance()}}override fun onOutputBufferAvailable(codec: MediaCodec,outputBufferId: Int,info: MediaCodec.BufferInfo) {if (info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {outputEnd.set(true)}if(info.size > 0){// render the decoded frameval pts = info.presentationTimeUs * 1000L + startTimecodec.releaseOutputBuffer(outputBufferId, pts)}}override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {e.printStackTrace()}override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {// do nothing}})// configure with surfacecodec.configure(videoFormat, surface, null, 0)// start decodingcodec.start()// wait for processing to completewhile (!outputEnd.get() && !stopDecoding) {Thread.sleep(10)}mediaExtractor.release()codec.stop()codec.release()}

参考

  • Android Surface的理解和应用
  • 从整体上看Android图像显示系统
  • Android图形系统综述(干货篇)
  • 浅谈Android Surface机制
  • 深入Android系统(十二)Android图形显示系统-1-显示原理与Surface

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/655460.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【大数据】Flink SQL 语法篇(二):WITH、SELECT WHERE、SELECT DISTINCT

Flink SQL 语法篇&#xff08;二&#xff09; 1.WITH 子句2.SELECT & WHERE 子句3.SELECT DISTINCT 子句 1.WITH 子句 应用场景&#xff08;支持 Batch / Streaming&#xff09;&#xff1a;With 语句和离线 Hive SQL With 语句一样的&#xff0c;语法糖 1&#xff0c;使用…

python运行报错_AttributeError: module ‘tensorflow‘ has no attribute ‘contrib‘

问题描述&#xff1a; File "/data/Code/resnet.py", line 23, in <module> initializertf.contrib.layers.xavier_initializer_conv2d() AttributeError: module tensorflow has no attribute contrib 问题分析&#xff1a; 这个错误是因为代码中使用…

安装Hive

主要使用远程模式部署hive的metastore服务。在node1机器上安装。 注意&#xff1a;以下两件事在启动hive之前必须确保正常完成。 1、提前安装mysql,确保具有远程访问的权限 2、启动hadoop集群,确保集群正常能够访问 1.上传软件 使用CRT等客户端远程上传 apache-hive-3.1.2-bin.…

糊涂工具包使用 记录

hutool 工具包 配置 引入依赖 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.16</version> </dependency>BeanUtil bean复制 Orders orders BeanUtil.copyProperties(order…

inode生命周期

1.添加inode到inode cache链表 当inode的引用计数器i_count为0后&#xff0c;会调用iput_final去释放 static void iput_final(struct inode *inode) {struct super_block *sb inode->i_sb;const struct super_operations *op inode->i_sb->s_op;unsigned long sta…

PaddleNLP的简单使用

1 介绍 PaddleNLP是一个基于PaddlePaddle深度学习平台的自然语言处理&#xff08;NLP&#xff09;工具库。 它提供了一系列用于文本处理、文本分类、情感分析、文本生成等任务的预训练模型、模型组件和工具函数。 PaddleNLP有统一的应用范式&#xff1a;通过 paddlenlp.Task…

数据结构-数组(详细讲解)

文章目录 数组数组的概述数组的图示一维数组二维数组 数组的定义一维数组的定义二维数组的定义 数组的取值赋值一维数组二维数组 数组的操作一维数组的操作索引实现指针实现 二位数组的操作矩阵转三元组矩阵的乘法 数组 数组的概述 概述&#xff1a;数组是一种线性数据结构&a…

代码随想录算法训练营第三十三天|860. 柠檬水找零

860. 柠檬水找零 public static boolean lemonadeChange(int[] bills) {if (bills.length 0) {return true;}if (bills[0] > 5) {return false;}int fiveNum 0;int tenNum 0;for (int i 0; i < bills.length; i) {if (bills[i] 5) {fiveNum;continue;}if (bills[i]…

导入表注入(iathook)

头文件&#xff08;hookMain.h&#xff09;内容 #pragma once #include<Windows.h>DWORD* g_iatAddr NULL; DWORD* g_unHookAddr NULL;BOOL InstallHook(); //安装钩子 BOOL UninstallHook(); //卸载钩子 DWORD* GetIatAddr(const char* dllName, const char* dllFunc…

【webrtc】m98 : vs2019 直接构建webrtc及unitest工程 1

还是换用了vs2019 构建官方的m98代码打开gn生成的sln 工程 随便点击一个工程turnserver 构建,结果会触发全部构建turnserver构建日志 :构建了整个webrtc ts(1665,8): note: result_of<webrtc::TimeDelta ((lambda at ../../video/adaptation/overuse_frame_detector.cc:55…

C#设置程序开机启动

1&#xff1a;获取当前用户&#xff1a; System.Security.Principal.WindowsIdentity identity System.Security.Principal.WindowsIdentity.GetCurrent();System.Security.Principal.WindowsPrincipal principal new System.Security.Principal.WindowsPrincipal(identity);…

C# 使用WMI监听进程的启动和关闭

写在前面 Windows Management Instrumentation&#xff08;WMI&#xff09;是用于管理基于 Windows 操作系统的数据和操作的基础结构。具体的API可以查看 WMI编程手册。 WMIC 是WMI的命令行管理工具&#xff0c;使用 WMIC&#xff0c;不但可以管理本地计算机&#xff0c;还可…

粒子群算法求解港口泊位调度问题(MATLAB代码)

粒子群算法&#xff08;Particle Swarm Optimization&#xff0c;PSO&#xff09;是一种基于群体智能的优化算法&#xff0c;它通过模拟鸟群或鱼群的行为来寻找最优解。在泊位调度问题中&#xff0c;目标是最小化所有船只在港时间的总和&#xff0c;而PSO算法可以帮助我们找到一…

LarkXR功能更新:引入浏览器原生H265支持

Paraverse平行云自主研发的LarkXR&#xff0c;在CloudXR领域实现了重大突破&#xff0c;为XR云平台的商业化和技术进步树立了新标准。LarkXR通过其分钟级部署大规模云端资源和对所有主流XR引擎的高适配性&#xff0c;成功地解决了Cloud XR商业化过程中的多项挑战。这一创新不仅…

Java把列表数据导出为PDF文件,同时加上PDF水印

一、实现效果 二、遇到的问题 实现导出PDF主体代码参考&#xff1a;Java纯代码实现导出PDF功能&#xff0c;下图是原作者实现的效果 导出报错Font STSong-Light with UniGB-UCS2-H is not recognized.。参考&#xff1a;itext 生成 PDF(五) 使用外部字体 网上都是说jar包的版本…

FastBee开源物联网平台2.0开源版发布啦!!!

一、项目介绍 物美智能(wumei-smart)更名为蜂信物联(FastBee)。 FastBee开源物联网平台&#xff0c;简单易用&#xff0c;更适合中小企业和个人学习使用。适用于智能家居、智慧办公、智慧社区、农业监测、水利监测、工业控制等。 系统后端采用Spring boot&#xff1b;前端采用…

成功解决AttributeError: ‘str‘ object has no attribute ‘decode‘

成功解决AttributeError: ‘str’ object has no attribute ‘decode’. &#x1f335;文章目录&#x1f335; &#x1f333;引言&#x1f333;&#x1f333;报错分析及解决方案&#x1f333;&#x1f333;参考文章&#x1f333;&#x1f333;结尾&#x1f333; &#x1f333;引…

Git安装,Git镜像,Git已安装但无法使用解决经验

git下载地址&#xff1a; Git - 下载 (git-scm.com) <-git官方资源 Git for Windows (github.com) <-github资源 CNPM Binaries Mirror (npmmirror.com) <-阿里镜像&#xff08;推荐&#xff0c;镜…

算法沉淀——前缀和(leetcode真题剖析)

算法沉淀——前缀和 01.一维前缀和02.二维前缀和03.寻找数组的中心下标04.除自身以外数组的乘积05.和为 K 的子数组06.和可被 K 整除的子数组07.连续数组08.矩阵区域和 前缀和算法是一种用于高效计算数组或序列中某个范围内元素之和的技巧。它通过预先计算数组的前缀和&#xf…

Redhat 8.4 一键安装 Oracle 11GR2 单机版

Oracle 一键安装脚本&#xff0c;演示 Redhat 8.4 一键安装 Oracle 11GR2 单机版过程&#xff08;全程无需人工干预&#xff09;&#xff1a;&#xff08;脚本包括 ORALCE PSU/OJVM 等补丁自动安装&#xff09; ⭐️ 脚本下载地址&#xff1a;Shell脚本安装Oracle数据库 脚本…