Android APP 音视频(01)MediaCodec解码H264码流

说明: 此MediaCodec解码H264实操主要针对Android12.0系统。通过读取sd卡上的H264码流Me获取视频数据,将数据通过mediacodec解码输出到surfaceview上。


1 H264码流和MediaCodec解码简介

1.1 H264码流简介

H.264,也被称为MPEG-4 AVC(Advanced Video Coding),是一种广泛使用的数字视频压缩标准,主要用于视频编码。H.264标准由ITU-T视频编码专家组(VCEG)和ISO/IEC动态图像专家组(MPEG)共同开发,旨在提供比之前的视频编码标准更高的数据压缩效率。

H.264是一种基于块的编码技术,它将视频帧分为多个宏块(Macroblocks,MBs),每个宏块包含亮度信息和色度信息。

关于H264码流相关概念还有:

帧类型,包括I、P、B三种类型,说明如下:

  • I帧(Intra-coded frames):关键帧,不依赖其他帧进行解码,包含完整的图像信息。
  • P帧(Predictive-coded frames):预测帧,依赖前一个I帧或P帧进行解码,包含相对于前一帧的差分信息。
  • B帧(Bidirectional predictive-coded frames):双向预测帧,依赖前后两个帧进行解码,用于提高压缩效率。

编码过程:包括帧内预测(Intra prediction)、帧间预测(Inter prediction)、变换(Transform)、量化(Quantization)和熵编码(Entropy coding)等步骤。

码流结构:H.264码流由一系列的NAL单元(Network Abstraction Layer Units)组成,每个NAL单元包含一个头部和数据负载,头部定义了负载的类型和重要性。

等等概念,想要有更多了解,可查看以下文章,持续更新中:

系统化学习 H264视频编码(01)基础概念

 

系统化学习 H264视频编码(02) I帧 P帧 B帧 引入及相关概念解读

系统化学习 H264视频编码(03)数据压缩流程及相关概念

。。。

1.2 MediaCodec解码说明

MediaCodec 是 Android 提供的一个音视频编解码器类,允许应用程序对音频和视频数据进行编码(压缩)和解码(解压缩)。它在 Android 4.1(API 级别 16)版本中引入,广泛应用于处理音视频数据,如播放视频、录制音频等。

以下是 MediaCodec 解码的基本步骤:

  1. 创建 MediaCodec 实例:通过调用 MediaCodec.createDecoderByType 方法并传入解码类型(如 "video/avc" 或 "audio/mp4a-latm")来创建解码器。

  2. 配置解码参数:通过调用 configure 方法配置解码器,传入解码参数如解码格式、输出格式等。

  3. 准备输出 Surface:为解码器准备输出 Surface。输出 Surface 用于接收解码后的数据,并显示在屏幕上。

  4. 开始解码:调用 start 方法启动解码器。

  5. 发送输入数据:将待解码的数据通过 write 方法发送到解码器的输入队列。

  6. 处理输出数据:监听输出队列,通过 dequeueOutputBuffer 方法获取解码后的数据,并将其显示在屏幕上。

  7. 停止解码:解码完成后,调用 stop 方法停止解码器。

  8. 释放资源:调用 release 方法释放解码器资源。

通过这些步骤,应用程序可以实现对视频和音频数据的高效编解码处理。针对本工程,主要通过从sd卡上读取h264码流,通过mediacodec解码视频并播放到surfaceview上。

2 MediaCodec解码H264码流代码完整解读(android Q)

2.1 关于权限部分的处理

关于权限,需要在AndroidManifest.xml中添加权限,具体如下所示:

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"tools:ignore="ScopedStorage" />

关于运行时权限的请求等,这里给出一个工具类参考代码,具体如下所示:

public class Permission {public static final int REQUEST_MANAGE_EXTERNAL_STORAGE = 1;//需要申请权限的数组private static final String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.CAMERA};//保存真正需要去申请的权限private static final List<String> permissionList = new ArrayList<>();public static int RequestCode = 100;public static void requestManageExternalStoragePermission(Context context, Activity activity) {if (!Environment.isExternalStorageManager()) {showManageExternalStorageDialog(activity);}}private static void showManageExternalStorageDialog(Activity activity) {AlertDialog dialog = new AlertDialog.Builder(activity).setTitle("权限请求").setMessage("请开启文件访问权限,否则应用将无法正常使用。").setNegativeButton("取消", null).setPositiveButton("确定", (dialogInterface, i) -> {Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);activity.startActivityForResult(intent, REQUEST_MANAGE_EXTERNAL_STORAGE);}).create();dialog.show();}public static void checkPermissions(Activity activity) {for (String permission : permissions) {if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {permissionList.add(permission);}}if (!permissionList.isEmpty()) {requestPermission(activity);}}public static void requestPermission(Activity activity) {ActivityCompat.requestPermissions(activity,permissionList.toArray(new String[0]),RequestCode);}
}

这样,如果后面又更多的权限,都可以使用该方法来处理,处理方式为:

Permission.checkPermissions(this);
Permission.requestManageExternalStoragePermission(getApplicationContext(), this);

2.2 解码的处理

关于解码部分,主要是MediaCodec的初始化、解码处理部分,代码如下所示:

public class H264Decoder implements  Runnable {private final String path;private final String TAG = "H264Decoder";MediaCodec mediaCodec;boolean enablePlay = false;public H264Decoder(String path, Surface surface, int width , int height) {this.path = path;try {mediaCodec = MediaCodec.createDecoderByType("video/avc");MediaFormat mediaformat = MediaFormat.createVideoFormat("video/avc", width, height);mediaformat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);mediaCodec.configure(mediaformat, surface, null, 0);} catch (IOException e) {throw new RuntimeException(e);}}public void play() {enablePlay = true;mediaCodec.start();new Thread(this).start();}public void stop(){enablePlay = false;}@Overridepublic void run() {try {byte[] bytes = null;try {//注意:这里是从文件中一次性读H264取码流数据,因此不适合特别大的视频bytes = getBytes(path);} catch (Exception e) {throw new RuntimeException(e);}int startIndex = 0;MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();while (enablePlay) {int nextFrameStart = findByFrame(bytes, startIndex+5, bytes.length);//MediaCodec输入缓冲区操作int inIndex =  mediaCodec.dequeueInputBuffer(10000);if (inIndex >= 0) {ByteBuffer byteBuffer = mediaCodec.getInputBuffer(inIndex);int length = nextFrameStart - startIndex;byteBuffer.put(bytes, startIndex, length);mediaCodec.queueInputBuffer(inIndex, 0, length, 0, 0);startIndex = nextFrameStart;}//MediaCodec输出缓冲区操作int outIndex =mediaCodec.dequeueOutputBuffer(info,10000);if (outIndex >= 0) {try {//这里延迟下,避免刷的过快Thread.sleep(40);} catch (InterruptedException e) {throw new RuntimeException(e);}mediaCodec.releaseOutputBuffer(outIndex, true);}}} catch (Exception e) {Log.i(TAG, "run decoder error:"+e.toString());}}private int findByFrame( byte[] bytes, int start, int totalSize) {for (int i = start; i <= totalSize-4; i++) {//这里是一帧的结束符 00 00 00 01 或者 00 00 01if (((bytes[i] == 0x00) && (bytes[i + 1] == 0x00) && (bytes[i + 2] == 0x00) && (bytes[i + 3] == 0x01))||((bytes[i] == 0x00) && (bytes[i + 1] == 0x00) && (bytes[i + 2] == 0x01))) {return i;}}return -1;}public byte[] getBytes(String path) throws IOException {InputStream is = new DataInputStream(Files.newInputStream(new File(path).toPath()));int len;int size = 1024;byte[] buf;ByteArrayOutputStream bos = new ByteArrayOutputStream();buf = new byte[size];while ((len = is.read(buf, 0, size)) != -1)bos.write(buf, 0, len);buf = bos.toByteArray();return buf;}
}

2.3 主流程代码参考实现

这里以 H264decoderActivity 为例,给出一个MediaCodec解码功能代码的参考实现。具体实现如下:

public class H264decoderActivity extends AppCompatActivity {H264Decoder h264Decoder;private final String TAG = "MainActivity";Context mContext;Surface surface;private boolean isPlaying = false; // 用于跟踪播放状态@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);mContext = this;setContentView(R.layout.h264_decode_activity_main);ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);return insets;});initSurface();Permission.checkPermissions(this);Permission.requestManageExternalStoragePermission(getApplicationContext(), this);Button playButton = findViewById(R.id.button);playButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {// 切换播放状态isPlaying = !isPlaying;// 根据播放状态更新按钮文本if (isPlaying) {playButton.setText(R.string.stopplay);//Environment.DIRECTORY_DOWNLOADS), "ags/out.h264").getAbsolutePath(),h264Decoder = new H264Decoder(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "ags/outputtest4.h264").getAbsolutePath(),surface,1280,720);h264Decoder.play();} else {playButton.setText(R.string.startplay);h264Decoder.stop();}}});}private void initSurface() {SurfaceView mSurface = findViewById(R.id.preview);mSurface.getHolder().addCallback(new SurfaceHolder.Callback() {@Overridepublic void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {Log.d(TAG,"surfaceCreated");surface=surfaceHolder.getSurface();}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {Log.d(TAG,"surfaceChanged");}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {Log.d(TAG,"surfaceDestroyed");}});}
}

这里涉及的layout布局文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><SurfaceViewandroid:id="@+id/preview"android:layout_width="372dp"android:layout_height="240dp"android:visibility="visible"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><Buttonandroid:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/playtest"app:layout_constraintTop_toBottomOf="@id/preview"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintHorizontal_bias="0.5"tools:ignore="MissingConstraints" /></androidx.constraintlayout.widget.ConstraintLayout>

2.4 解码 demo实现效果

这里是找一个mp4格式的测试视频,使用ffmpeg将mp4格式中的视频码流输出出来。使用命令为:

$ffmpeg -i inputtest.mp4 -vcodec libx264 -preset slow -b:v 2000k -crf 21 out.h264

将其push到sd卡上,完整路径为:/sdcard/Download/ags/outputtest4.h264。实际运行效果展示如下:

446068457f4e42d5ac6720ec2279d858.png

 

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

uni-app 影视类小程序开发从零到一 | 开源项目分享

引言 在数字娱乐时代&#xff0c;对于电影爱好者而言&#xff0c;随时随地享受精彩影片成为一种日常需求。分享一款基于 uni-app 开发的影视类小程序。它不仅提供了丰富的影视资源推荐&#xff0c;还融入了个性化知乎日报等内容&#xff0c;是不错的素材&#xff0c;同时对电影…

就业管理功能概述:构建智慧校园企业招聘平台

在智慧校园整体解决方案中&#xff0c;就业管理模块连接着学校与企业两端&#xff0c;更成为学生们步入社会、开启职业生涯梦想的关键门户。这一功能的核心价值&#xff0c;在于它如何巧妙地运用科技的力量&#xff0c;简化招聘流程&#xff0c;提升招聘效率&#xff0c;同时为…

5G赋能车联网,无人驾驶引领未来出行

无人驾驶车联网应用已成为智能交通领域的重要发展趋势。随着无人驾驶技术的不断进步和5G网络的广泛部署&#xff0c;5G工业路由器在无人驾驶车联网中的应用日益广泛&#xff0c;为无人驾驶车辆提供了稳定、高效、低时延的通信保障。 5G工业路由器的优势 低时延&#xff1a;5G网…

Python教程(一):环境搭建及PyCharm安装

目录 引言1. Python简介1.1 编译型语言 VS 解释型语言 2. Python的独特之处3. Python应用全览4. Python版本及区别5. 环境搭建5.1 安装Python&#xff1a; 6. 开发工具&#xff08;IDE&#xff09;6.1 PyCharm安装教程6.2 永久使用教程 7. 编写第一个Hello World结语 引言 在当…

Open3D 可视化窗口中查看点的坐标数据

目录 一、概述 1.1实现步骤 1.2应用 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 3.1选取点 3.2数据显示 前期试读&#xff0c;后续会将博客加入下列链接的专栏&#xff0c;欢迎订阅 Open3D与点云深度学习的应用_白葵新的博客-CSDN博客 一、概述 可以使用Op…

Java语言程序设计基础篇_编程练习题**15.19 (游戏:手眼协调)

**15.19 (游戏:手眼协调) 请编写一个程序&#xff0c;显示一个半径为10像素的实心圆&#xff0c;该圆放置在面板上的随机位置&#xff0c;并填充随机的顔色&#xff0c;如图15-29b所示。单击这个圆时&#xff0c;它会消失&#xff0c;然后在另一个随机的位置显示新的随机颜色的…

【工具】轻松转换JSON与Markdown表格——自制Obsidian插件

文章目录 一、插件简介二、功能详解三、使用教程四、插件代码五、总结 一、插件简介 JsonMdTableConverter是一款用于Obsidian的插件&#xff0c;它可以帮助用户在JSON格式和Markdown表格之间进行快速转换。这款插件具有以下特点&#xff1a; 轻松识别并转换JSON与Markdown表格…

Java | Leetcode Java题解之第278题第一个错误的版本

题目&#xff1a; 题解&#xff1a; public class Solution extends VersionControl {public int firstBadVersion(int n) {int left 1, right n;while (left < right) { // 循环直至区间左右端点相同int mid left (right - left) / 2; // 防止计算时溢出if (isBadVers…

【linux驱动开发】卸载驱动时报错:Trying to free already-free IRQ 0

【linux驱动开发】free_irq时报错:Trying to free already-free IRQ 0 卸载驱动时报错Trying to free already-free IRQ 0 第一次加载卸载驱动没有任何问题。第二次加载驱动&#xff0c;按键中断触发失效&#xff0c;卸载驱动时报错:Trying to free already-free IRQ 0 看了…

牛客周赛50轮+cf955+abc363

D-小红的因式分解_牛客周赛 Round 50 (nowcoder.com) 思路&#xff1a; 巨蠢的题目&#xff0c;ax^2bxca1*a2*x^2(b1*a2b2*a1)xb1*b2&#xff0c;即&#xff1a; aa1*a2,ba1*b2a2*b1,cb1*b2 数据范围很小&#xff0c;直接暴力枚举吧&#xff08;注意条件&#xff09; 代码…

简单使用SpringMVC写一个图书管理系统的登入功能和图书展示功能

准备好前端的代码 这里已经准备好了前端的代码&#xff0c;这里仅仅简单的介绍登入功能&#xff0c;和展示图书列表的功能。 如图&#xff1a; 如上图所示&#xff0c;这里的前端代码还是比较多的&#xff0c;在这里我介绍&#xff0c;login.html还有book_list.html这两个。 l…

【快速逆向四/无过程/有源码】浙江工商职业技术学院 统一身份认证

逆向日期&#xff1a;2024.07.23 使用工具&#xff1a;Node.js 加密方法&#xff1a;RSAUtils 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 AES解密处理&#xff08;直接解密即可&#xff09;&#xff08;crypto-js.js 标准算法&#xf…

万界星空科技MES系统的智能排产功能

万界星空科技MES系统通过一系列先进的手段和算法进行智能排产&#xff0c;这些手段确保了生产过程的优化和效率的提升。 1、智能分析&#xff1a; MES系统通过收集和分析生产过程中的数据&#xff0c;能够对生产过程进行智能分析。这包括分析哪些工序需要生产&#xff0c;哪些…

01 RabbitMQ:简单介绍

01 RabbitMQ&#xff1a;简单介绍 1. 简单介绍1.1. 什么是消息队列&#xff1f;1.2. 底层实现两大主流方式1.3. 两大主流方式对比1.4. 各个MQ产品的对比 2. RabbitMQ简介1.2. 官网1.3. 体系结构1.3.1. Producer1.3.2. Consumer1.3.3. Connection1.3.4. Channel1.3.5. Broker1.3…

【iOS】——属性关键字

属性关键字的类型 在iOS中属性关键字分为四种类型&#xff1a; 可访问性: readonly ,readwrite原子性 &#xff1a; atomic &#xff0c;nonatomic内存管理 &#xff1a; retain/strong/copy&#xff0c; assign/unsafe_unretained&#xff0c;weak方法命名&#xff1a;sette…

Next.js中构建完整的身份验证系统【翻译】

案例使用第三方库&#xff1a;NextAuth.js, Shadcn/ui, react-hook-form, and Zod等 prisma: is an open-source database toolkit. We will use it to store user credentials.next-auth: Authentication for Next.js.react-hook-form: a library that helps you validate fo…

Github Desktop 关于将本地文件夹设置为新仓库的 使用笔记

实际要达到的结果: 将UE5工程同步到Github,工程太大,我们只需要将必要的工程文件夹同步即可,缓存等一些不必要的文件夹则不需要同步 最终效果预览: 1. 将本地文件夹设置为新仓库 将本地文件夹作为仓库一般你是没有这个仓库的,所以你需要新建一个仓库 如果忽略某些不必要的文…

# OpenCV 图像预处理—形态学:膨胀、腐蚀、开运算、闭运算 原理详解

文章目录 形态学概念膨胀使用膨胀操作来修复裂痕示例代码关键解析&#xff1a; 腐蚀使用腐蚀操作消除噪点示例代码&#xff1a; 开运算—先腐蚀后膨胀闭运算—先膨胀后腐蚀 形态学概念 首先看这两张图片 一张图周围有大大小小的噪音和彩点&#xff0c;另一张图片中字母有间隙&…

go语言Gin框架的学习路线(十一)

目录 GORM的CRUD教程 更新操作 更新所有字段 更新指定字段 使用 Select 和 Omit 更新 无 Hooks 更新 批量更新 删除操作 删除记录 批量删除 软删除 物理删除 示例代码 GORM的CRUD教程 CRUD 是 "Create, Read, Update, Delete"&#xff08;创建、查询、…

Google Cloud Platform数据工程简介

Google Cloud Platform数据工程简介 前言 云计算的出现为数据驱动型组织提供了采用成本效益高且可扩展的数据工程解决方案的机会。在云服务提供商中&#xff0c;Google Cloud Platform (GCP) 是近年来表现优异的领导者之一。GCP的增长被归因于其在企业和初创公司中的日益普及…