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.mzph.cn/news/875854.shtml

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

相关文章

SCADA系统智能化管理工厂 操作方便易于使用

SCADA系统是将工厂进行智能化升级的专业系统&#xff0c;可根据需求与预算定制&#xff0c;从多设备的采集、数据的显示、数据可视化展示、分析图像、边缘计算、协议解析、数据转发、数据清洗、数据转换、数据存储、数据传输等等&#xff0c;各种功能均可根据实际需求进行定制&…

如何在测试中保护用户隐私!

在当今数据驱动的时代&#xff0c;用户隐私保护成为了企业和开发团队关注的焦点。在软件测试过程中&#xff0c;处理真实用户数据时保护隐私尤为重要。本文将介绍如何在测试中保护用户隐私&#xff0c;并提供具体的方案和实战演练。 用户隐私保护的重要性 用户隐私保护不仅是法…

Spring MVC 应用分层

1. 类名使⽤⼤驼峰⻛格&#xff0c;但以下情形例外&#xff1a;DO/BO/DTO/VO/AO 2. ⽅法名、参数名、成员变量、局部变量统⼀使⽤⼩驼峰⻛格 3. 包名统⼀使⽤⼩写&#xff0c;点分隔符之间有且仅有⼀个⾃然语义的英语单词. 常⻅命名命名⻛格介绍 ⼤驼峰: 所有单词⾸字⺟…

使用api 调试接口 ,配置 Header 、 body 远程调试 线上接口

学习目标&#xff1a; 目标 使用api 调试接口 &#xff0c;配置 Header 、 body 远程调试 线上接口 学习内容&#xff1a; 内容 设置请求方式 2. 选择 POST 提交 3.设置 Header 一般默认的 4个 header 属性就可以直接使用&#xff0c;如有特殊情况&#xff0c;需进行属性设…

『 Linux 』信号的捕捉及部分子问题

文章目录 信号的捕捉sigaction函数未决信号集的置零时机信号处理过程的阻塞可重入函数volatile 关键字SIGCHLD 信号 信号的捕捉 该图为基于信号处理为用户自定义动作的图解; 信号的捕捉 当一个信号被递达时,如果该信号的处理动作是用户自定义的函数(如int sighandler(int))时就…

文物实时状态监控的保护系统

文物是宝贵的历史遗产和文化瑰宝&#xff0c;其保护是我们共同的责任。为了实现文物的安全保护&#xff0c;现代科技提供了各种环境监测设备&#xff0c;可以实时监控文物的状态并采取相应的保护措施。本文将介绍一种利用各种环境监测设备实现文物实时状态监控的保护系统。 一…

【视频讲解】ResNet深度学习神经网络原理及其在图像分类中的应用|附Python代码

全文链接&#xff1a;https://tecdat.cn/?p37134 原文出处&#xff1a;拓端数据部落公众号 分析师&#xff1a;Canglin Li 本文深入探讨了卷积层&#xff08;Convolutional Layer&#xff09;在深度学习框架中的核心作用与操作机制&#xff0c;并分析了其在特征提取、网络构…

近期代码报错解决笔记

1.TypeError: ‘bool’ object is not callable 想print("Type of head:", type(entity_emb[head]))&#xff0c;结果报如下错误&#xff1a; 源代码&#xff1a; 因为 print 仍然被当作一个布尔值处理&#xff0c;而不是作为函数调用。这个问题的根源在于 print …

Adobe Photoshop(Ps)安装包软件下载

一、Adobe Photoshop简介 Adobe Photoshop&#xff08;简称PS&#xff09;是由Adobe Systems公司开发的图像处理软件&#xff0c;它是一款集图像扫描、编辑修改、图像制作、广告创意、图像输入与输出于一体的图形图像处理软件。广泛应用于专业测评、平面设计、广告摄影、影像创…

学习小型gpt源码(自用)

数据集构建_哔哩哔哩_bilibili &#xff08;b站上有一系列课&#xff0c;从数据处理到模型构建和训练使用&#xff09; 什么是batch&#xff1f; 为什么一个batch内的句子要一样长&#xff1f; 不同batch的长度可以不一样&#xff0c;但是同一个batch内长度一样&#xff01;…

【MySQL进阶之路 | 高级篇】数据操作类型的角度理解共享锁,排他锁

1. 从数据操作的类型划分&#xff1a;读锁&#xff0c;写锁 对于数据库并发事务的读-读情况并不会引起什么问题。对于写-写&#xff0c;读-写操作或写-写操作这些情况可能会引起一些问题&#xff0c;需要使用MVCC或者加锁的方式来解决它们。在使用加锁的方式解决问题时&#x…

3.3-LSTM的改进

文章目录 1改进点1.1多层化1.2 dropout1.2.1具体概念1.2.2应该插入到LSTM模型的哪里 1.3权重共享 2改进之后的LSTMLM的代码实现2.1初始化2.2前向计算2.3反向传播 3相应的学习代码的实现4总结 1改进点 1.1多层化 加深神经网络的层数往往能够学习更复杂的模式&#xff1b;因此这…

利用换元法计算积分的常见题型(考研高数复习)

考研中常见的几种换元法积分计算题 (1)被积式仅包含一个根式&#xff1a;根号下为有 a a a 和 x x x 的平方和/平方差 此种类型的积分题型&#xff0c;可以通过构造单个锐角大小为 t t t 的直角三角形&#xff0c;利用勾股定理和三角函数进行代换。 平方和的情况 形如 ∫…

java学习----注释

简介 override介绍&#xff1a; 添加了这个注释其实是做了个语法校验的作用 override定义 Deprecated介绍&#xff1a; 源码&#xff1a; SuppressWarnings介绍&#xff1a; 源码&#xff1a; 元注解 Retention注解介绍&#xff1a; 案列 Target注解介绍&#xff1a; Documente…

LLM - 理解 Transformer 的位置编码 sin cos 的作用与原理

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/140697827 免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。 Transformer 模型中的位置编码是关键技术,通过为每个词嵌入向量添加位…

数据库第五次作业

1. 触发器 建立触发器&#xff0c;订单表中增加订单数量后&#xff0c;商品表商品数量同步减少对应的商品订单出数量,并测试 建立触发器&#xff0c;实现功能:客户取消订单&#xff0c;恢复商品表对应商品的数量 建立触发器&#xff0c;实现功能:客户修改订单&#xff0c;商品…

【微软蓝屏】微软Windows蓝屏问题汇总与应对解决策略

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

2024年铜川宜君半程马拉松,暴晒+爬坡152安全完赛

1、赛事背景 2024年7月21日&#xff0c;我参加了2024年铜川宜君半程马拉松赛&#xff0c;7月举办的赛事很少&#xff0c;全国都算温度比较高的&#xff0c;虽然宜君是一个山城&#xff0c;还是会担心气温会高。 临开赛1、2周&#xff0c;陕西区域降水比较多&#xff0c;赛前一…

【算法专题】双指针算法之LCR 179. 查找总价格为目标值的两个商品(力扣)

欢迎来到 CILMY23的博客 &#x1f3c6;本篇主题为&#xff1a;双指针算法之LCR 179. 查找总价格为目标值的两个商品&#xff08;力扣&#xff09; &#x1f3c6;个人主页&#xff1a;CILMY23-CSDN博客 &#x1f3c6;系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法…

使用nginx解决本地环境访问线上接口跨域问题

前言 前端项目开发过程中&#xff0c;经常会遇到各种各样的跨域问题。 虽然大部分时候&#xff0c;由脚手架自带的proxy功能即可解决问题&#xff0c;如webpack&#xff0c;vite等&#xff1b;但是若没有通过脚手架搭建项目&#xff0c;或者必须使用某些特殊规则转发时&#…