音视频开发—MediaCodec 解码H264/H265码流视频
使用MediaCodec目的
MediaCodec是Android底层多媒体框架的一部分,通常与MediaExtractor、MediaMuxer、AudioTrack结合使用,可以编码H264、H265、AAC、3gp等常见的音视频格式
MediaCodec工作原理是处理输入数据以产生输出数据
MediaCodec工作流程
MediaCodec的数据流分为input和output流,并通过异步的方式处理两路数据流,直到手动释放output缓冲区,MediaCodec才将数据处理完毕
- input流:客户端输入待解码或者待编码的数据
- output流:客户端输出的已解码或者已编码的数据
MediaCodec API说明
- getInputBuffers:获取需要输入流队列,返回ByteBuffer数组
- queueInputBuffer:输入流入队
- dequeueInputBuffer: 从输入流队列中取数据进行编码操作
- getOutputBuffers:获取已经编解码之后的数据输出流队列,返回ByteBuffer数组
- dequeueOutputBuffer:从输出队列中取出已经编码操作之后的数据
- releaseOutputBuffer: 处理完成,释放output缓冲区
基本流程
- MediaCodec的基本使用遵循上图所示,它的生命周期如下所示:
- Stoped:创建好MediaCodec,进行配置,或者出现错误
- Uninitialized: 当创建了一个MediaCodec对象,此时MediaCodec处于Uninitialized,在任何状态调用reset()方法使MediaCodec返回到Uninitialized状态
- Configured: 使用configure(…)方法对MediaCodec进行配置转为Configured状态
- Error: 出现错误
- Executing:可以在Executing状态的任何时候通过调用flush()方法返回到Flushed状态
- Flushed:调用start()方法后MediaCodec立即进入Flushed状态
- Running:调用dequeueInputBuffer后,MediaCodec就转入Running状态
- End-of-Stream:编解码结束后,MediaCodec将转入End-of-Stream子状态
- Released:当使用完MediaCodec后,必须调用release()方法释放其资源
基本使用
//解码器
val mVideoDecoder = MediaCodec.createDecoderByType("video/avc")
//编码器
val mVideoEncoder = MediaCodec.createEncoderByType("video/avc")
MediaCodec 解码H264/H265
使用MediaCodec 解码H264/H265码流视频,那必须谈下MediaCodec这个神器。附官网数据流程图如下:
input:ByteBuffer输入方;
output:ByteBuffer输出方;
- 使用者从MediaCodec请求一个空的输入buffer(ByteBuffer),填充满数据后将它传递给MediaCodec处理。
- MediaCodec处理完这些数据并将处理结果输出至一个空的输出buffer(ByteBuffer)中。
- 使用者从MediaCodec获取输出buffer的数据,消耗掉里面的数据,使用完输出buffer的数据之后,将其释放回编解码。
H264码流解码示例代码如下(基本都做了注释)
package com.zqfdev.h264decodedemo;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.util.Log;
import android.view.Surface;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
/*** @author zhangqingfa* @createDate 2020/12/10 11:39* @description 解码H264播放*/
public class H264DeCodePlay {
private static final String TAG = "zqf-dev";//视频路径private String videoPath;//使用android MediaCodec解码private MediaCodec mediaCodec;private Surface surface;
H264DeCodePlay(String videoPath, Surface surface) {this.videoPath = videoPath;this.surface = surface;initMediaCodec();}
private void initMediaCodec() {try {Log.e(TAG, "videoPath " + videoPath);//创建解码器 H264的Type为 AACmediaCodec = MediaCodec.createDecoderByType("video/avc");//创建配置MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 540, 960);//设置解码预期的帧速率【以帧/秒为单位的视频格式的帧速率的键】mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);//配置绑定mediaFormat和surfacemediaCodec.configure(mediaFormat, surface, null, 0);} catch (IOException e) {e.printStackTrace();//创建解码失败Log.e(TAG, "创建解码失败");}}
/*** 解码播放*/void decodePlay() {mediaCodec.start();new Thread(new MyRun()).start();}
private class MyRun implements Runnable {
@Overridepublic void run() {try {//1、IO流方式读取h264文件【太大的视频分批加载】byte[] bytes = null;bytes = getBytes(videoPath);Log.e(TAG, "bytes size " + bytes.length);//2、拿到 mediaCodec 所有队列buffer[]ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();//开始位置int startIndex = 0;//h264总字节数int totalSize = bytes.length;//3、解析while (true) {//判断是否符合if (totalSize == 0 || startIndex >= totalSize) {break;}//寻找索引int nextFrameStart = findByFrame(bytes, startIndex + 1, totalSize);if (nextFrameStart == -1) break;MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();// 查询10000毫秒后,如果dSP芯片的buffer全部被占用,返回-1;存在则大于0int inIndex = mediaCodec.dequeueInputBuffer(10000);if (inIndex >= 0) {//根据返回的index拿到可以用的bufferByteBuffer byteBuffer = inputBuffers[inIndex];//清空缓存byteBuffer.clear();//开始为buffer填充数据byteBuffer.put(bytes, startIndex, nextFrameStart - startIndex);//填充数据后通知mediacodec查询inIndex索引的这个buffer,mediaCodec.queueInputBuffer(inIndex, 0, nextFrameStart - startIndex, 0, 0);//为下一帧做准备,下一帧首就是前一帧的尾。startIndex = nextFrameStart;} else {//等待查询空的buffercontinue;}//mediaCodec 查询 "mediaCodec的输出方队列"得到索引int outIndex = mediaCodec.dequeueOutputBuffer(info, 10000);Log.e(TAG, "outIndex " + outIndex);if (outIndex >= 0) {try {//暂时以休眠线程方式放慢播放速度Thread.sleep(33);} catch (InterruptedException e) {e.printStackTrace();}//如果surface绑定了,则直接输入到surface渲染并释放mediaCodec.releaseOutputBuffer(outIndex, true);} else {Log.e(TAG, "没有解码成功");}}} catch (IOException e) {e.printStackTrace();}}}
//读取一帧数据private int findByFrame(byte[] bytes, int start, int totalSize) {for (int i = start; i < totalSize - 4; i++) {//对output.h264文件分析 可通过分隔符 0x00000001 读取真正的数据if (bytes[i] == 0x00 && bytes[i + 1] == 0x00 && bytes[i + 2] == 0x00 && bytes[i + 3] == 0x01) {return i;}}return -1;}private byte[] getBytes(String videoPath) throws IOException {InputStream is = new DataInputStream(new FileInputStream(new File(videoPath)));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;}
}
H265示例代码如下
package com.zqfdev.h264decodedemo;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.util.Log;
import android.view.Surface;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
/*** @author zhangqingfa* @createDate 2020/12/10 11:39* @description 解码H264播放*/
public class H265DeCodePlay {
private static final String TAG = "zqf-dev";//视频路径private String videoPath;//使用android MediaCodec解码private MediaCodec mediaCodec;private Surface surface;
H265DeCodePlay(String videoPath, Surface surface) {this.videoPath = videoPath;this.surface = surface;initMediaCodec();}
private void initMediaCodec() {try {Log.e(TAG, "videoPath " + videoPath);//创建解码器 H264的Type为 AACmediaCodec = MediaCodec.createDecoderByType("video/hevc");//创建配置MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/hevc", 368, 384);//设置解码预期的帧速率【以帧/秒为单位的视频格式的帧速率的键】mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);//配置绑定mediaFormat和surfacemediaCodec.configure(mediaFormat, surface, null, 0);} catch (IOException e) {e.printStackTrace();//创建解码失败Log.e(TAG, "创建解码失败");}}
/*** 解码播放*/void decodePlay() {mediaCodec.start();new Thread(new MyRun()).start();}
private class MyRun implements Runnable {
@Overridepublic void run() {try {//1、IO流方式读取h264文件【太大的视频分批加载】byte[] bytes = null;bytes = getBytes(videoPath);Log.e(TAG, "bytes size " + bytes.length);//2、拿到 mediaCodec 所有队列buffer[]ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();//开始位置int startIndex = 0;//h264总字节数int totalSize = bytes.length;//3、解析while (true) {//判断是否符合if (totalSize == 0 || startIndex >= totalSize) {break;}//寻找索引int nextFrameStart = findByFrame(bytes, startIndex + 1, totalSize);if (nextFrameStart == -1) break;Log.e(TAG, "nextFrameStart " + nextFrameStart);MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();// 查询10000毫秒后,如果dSP芯片的buffer全部被占用,返回-1;存在则大于0int inIndex = mediaCodec.dequeueInputBuffer(10000);if (inIndex >= 0) {//根据返回的index拿到可以用的bufferByteBuffer byteBuffer = inputBuffers[inIndex];//清空byteBuffer缓存byteBuffer.clear();//开始为buffer填充数据byteBuffer.put(bytes, startIndex, nextFrameStart - startIndex);//填充数据后通知mediacodec查询inIndex索引的这个buffer,mediaCodec.queueInputBuffer(inIndex, 0, nextFrameStart - startIndex, 0, 0);//为下一帧做准备,下一帧首就是前一帧的尾。startIndex = nextFrameStart;} else {//等待查询空的buffercontinue;}//mediaCodec 查询 "mediaCodec的输出方队列"得到索引int outIndex = mediaCodec.dequeueOutputBuffer(info, 10000);Log.e(TAG, "outIndex " + outIndex);if (outIndex >= 0) {try {//暂时以休眠线程方式放慢播放速度Thread.sleep(33);} catch (InterruptedException e) {e.printStackTrace();}//如果surface绑定了,则直接输入到surface渲染并释放mediaCodec.releaseOutputBuffer(outIndex, true);} else {Log.e(TAG, "没有解码成功");}}} catch (IOException e) {e.printStackTrace();}}}
//读取一帧数据private int findByFrame(byte[] bytes, int start, int totalSize) {for (int i = start; i < totalSize - 4; i++) {//对output.h264文件分析 可通过分隔符 0x00000001 读取真正的数据if (bytes[i] == 0x00 && bytes[i + 1] == 0x00 && bytes[i + 2] == 0x00 && bytes[i + 3] == 0x01) {return i;}}return -1;}private byte[] getBytes(String videoPath) throws IOException {InputStream is = new DataInputStream(new FileInputStream(new File(videoPath)));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;}
}
MainActivity代码如下
package com.zqfdev.h264decodedemo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.File;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
public class MainActivity extends AppCompatActivity {private String[] permiss = {"android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.READ_EXTERNAL_STORAGE"};private H264DeCodePlay h264DeCodePlay;
// private H265DeCodePlay h265DeCodePlay;private String videoPath;
@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);checkPermiss();initView();}private void checkPermiss() {int code = ActivityCompat.checkSelfPermission(this, permiss[0]);if (code != PackageManager.PERMISSION_GRANTED) {// 没有写的权限,去申请写的权限ActivityCompat.requestPermissions(this, permiss, 11);}}private void initView() {File dir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);if (!dir.exists()) dir.mkdirs();final File file = new File(dir, "output.h264");
// final File file = new File(dir, "output.h265");if (!file.exists()) {Log.e("Tag", "文件不存在");return;}videoPath = file.getAbsolutePath();final SurfaceView surface = findViewById(R.id.surface);final SurfaceHolder holder = surface.getHolder();holder.addCallback(new SurfaceHolder.Callback() {@Overridepublic void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {h264DeCodePlay = new H264DeCodePlay(videoPath, holder.getSurface());h264DeCodePlay.decodePlay();
// h265DeCodePlay = new H265DeCodePlay(videoPath, holder.getSurface());
// h265DeCodePlay.decodePlay();}
@Overridepublic void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {}});}
}
测试的H264 / H265码流视频通过FFmpeg抽取可得到。
命令行:
ffmpeg -i 源视频.mp4 -codec copy -bsf: h264_mp4toannexb -f h264 输出视频.h264
最后效果如下:
相关文章:

音视频开发—MediaCodec 解码H264/H265码流视频
使用MediaCodec目的 MediaCodec是Android底层多媒体框架的一部分,通常与MediaExtractor、MediaMuxer、AudioTrack结合使用,可以编码H264、H265、AAC、3gp等常见的音视频格式 MediaCodec工作原理是处理输入数据以产生输出数据 MediaCodec工作流程 Med…...

CVPR 2023|淘宝视频质量评价算法被顶会收录
近日,阿里巴巴大淘宝技术题为《MD-VQA: Multi-Dimensional Quality Assessment for UGC Live Videos》—— 适用于无参考视频质量评价的最新研究成果被计算机视觉领域顶级会议IEEE/CVF Computer Vision and Pattern Recognition Conference 2023(CVPR 20…...

【C++学习】继承
🐱作者:一只大喵咪1201 🐱专栏:《C学习》 🔥格言:你只管努力,剩下的交给时间! C是面向对象的编程语言,它有很多的特性,但是最重要的就是封装,继承…...

【03173】2020年8月高等教育自学考试-软件开发工具
一、单项选择题:1. 区别于一般软件,对软件开发工具而言,下列各项最重要的性能是 A. 效率 B. 响应速度C. 资源消耗 D. 使用方便2. 在软件开发过程的信息需求中,属于跨开发周期的信息是A. 有关系统环境的需求信息 B. 有关软件设计的…...

Java中的String类
String类1.String类1.1 特性1.2 面试题1.3 常用方法1.4 String与其他类型之间的转换2. StringBuilder类、StringBuffer类:可变字符序列1.String类 1.1 特性 String类为final类,不可被继承,代表不可变的字符序列; 实现了Serializ…...

【java】笔试强训Day3【在字符串中找出连续最长的数字串与数组中出现次数超过一半的数字】
目录 ⛳选择题 1.以下代码运行输出的是 2.以下程序的输出结果为 3.下面关于构造方法的说法不正确的是 ( ) 4.在异常处理中,以下描述不正确的有( ) 5.下列描述中,错误的是( ) 6.…...

一文7个步骤从0到1教你搭建Selenium 自动化测试环境
【导语】Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持自动录制动作和自动生成 .Net、Java、Perl等不同语言的测试脚本。本文详细介绍了搭建自动化测试环境所需的工具,让你学习自动化测试不…...

Oracle目录应急清理
Oracle目录应急清理清理错误位置的归档日志清理30天前的监听告警日志清理监听日志清理30天以前的trace文件清理30天以前的审计日志清理错误位置的归档日志 检查$ORACLE_HOME/dbs下是否有归档文件: ls $ORACLE_HOME/dbs/arch* | wc -l检查和修改归档位置࿱…...

使用 OBS 进行区域录制
1. OBS 与区域录屏 实际上 OBS 的使用场景可谓是与区域录屏格格不入的。 虽然我们依旧有一些办法在 OBS 中达到区域录屏的目的,但其操作实在过于繁琐,还不如直接使用 QQ 或者 Windows 最新的自带截屏录屏来进行区域录屏来的方便实在。 但若非常强烈的…...

aws eks 配置授权额外的用户和角色访问集群
参考资料 https://github.com/kubernetes-sigs/aws-iam-authenticator#full-configuration-formathttps://docs.amazonaws.cn/zh_cn/eks/latest/userguide/add-user-role.html 众所周知,aws eks使用 Authenticator 或者 aws 命令来进行账户级别的用户和角色的授权…...

MagicalCoder可视化开发平台:轻松搭建业务系统,为企业创造更多价值
让软件应用开发变得轻松起来,一起探索MagicalCoder可视化开发工具的魔力!你是否为编程世界的各种挑战感到头痛?想要以更高效、简单的方式开发出专业级的项目?MagicalCoder低代码工具正是你苦心寻找的产品!它是一款专为…...

8个不能错过的程序员必备网站,惊艳到我了!!!
程序员是一个需要不断学习的职业,不少朋友每天来逛CSDN、掘金等网站,但一直都抱着“收藏从未停止,学习从未开始”的态度,别骗自己了兄弟。在编程体系中,有很多不错的小工具,可以极大得提升我们的开发效率。…...

Mybatis(二):实现“增删改查”
Mybatis(二):实现“增删改查”前言一、MyBatis的增删改查1、添加2、修改3、删除4、查询4.1 查询一个实体4.1 查询集合二、MyBatis获取参数值的两种方式(重点)1、单个字面量类型的参数2、多个字面量类型的参数3、map集合…...

Faster RCNN 对血液细胞目标检测
目录 1. 介绍 2. 工具函数介绍 utils 2.1 xml 文件的读取 get_label_from_xml 2.2 绘制边界框 draw_bounding_box...

【数据结构】Java实现栈
目录 1. 概念 2. 栈的使用 3. 自己动手实现栈(使用动态数组实现栈) 1. 创建一个MyStack类 2. push入栈 3. pop出栈 4. 查看栈顶元素 5. 判断栈是否为空与获取栈长 6. toString方法 4. 整体实现 4.1 MyStack类 4.2 Test类 4.3 测试结果 1.…...

【数据结构】排序
作者:✿✿ xxxflower. ✿✿ 博客主页:xxxflower的博客 专栏:【数据结构】篇 语录:⭐每一个不曾起舞的日子,都是对生命的辜负。⭐ 文章目录1.排序1.1排序的概念1.2常见的排序算法2.常见排序算法2.1插入排序2.1.1直接插入…...

过拟合、验证集、交叉验证
过拟合 简单描述:训练集误差小,测试集误差大,模型评估指标的方差(variance)较大; 判断方式: 1、观察 train set 和 test set 的误差随着训练样本数量的变化曲线。 2、通过training accuracy 和…...

原力计划来了【协作共赢 成就未来】
catalogue🌟 写在前面🌟 新星计划持续上新🌟 原力计划方向🌟 原力计划拥抱优质🌟 AIGC🌟 参加新星计划还是原力计划🌟 创作成就未来🌟 写在最后🌟 写在前面 哈喽&#x…...

一文了解Jackson注解@JsonFormat及失效解决
背景 项目中使用WRITE_DATES_AS_TIMESTAMPS: true转换日期格式为时间戳未生效。如下: spring:jackson:time-zone: Asia/Shanghaiserialization:WRITE_DATES_AS_TIMESTAMPS: true尝试是否关于时间的注解是否会生效,使用JsonForma和JsonFiled均失效。 常…...

webpack——使用、分析打包代码
世上本无nodejs js最初是在前端浏览器上运行的语言,js代码一旦脱离了浏览器环境,就无法被运行。直到nodejs的出现,我们在电脑上配置了node环境,就可以让js代码脱离浏览器,在node环境中运行。 浏览器不支持模块化 nodej…...

libvirt零知识学习5 —— libvirt源码编译安装(3)
接前一篇文章libvirt零知识学习4 —— libvirt源码编译安装(2) 在上篇文章及上上篇文章中构建libvirt的时候遇到了一个问题“ERROR: Problem encountered: YAJL 2 is required to build QEMU driver”。上篇文章讲到即使安装了相应的YAJL库仍然不能解决问…...

Nmap 的使用教程
Nmap是一个网络侦测和安全审计工具。它可以用于发现网络上的主机和服务,并提供广泛的信息,其中包括操作系统类型和版本、应用程序和服务的详细信息等。在本文中,我们将介绍如何使用Nmap扫描网络主机,识别开放端口以及进行操作系统…...

async与await异步编程
ECMA2017中新加入了两个关键字async与await 简单来说它们是基于promise之上的的语法糖,可以让异步操作更加地简单明了 首先我们需要用async关键字,将函数标记为异步函数 async function f() {} f()异步函数就是指:返回值为promise对象的函…...

移动应用架构设计:如何转变开发流程
移动应用架构设计:如何转变开发流程 2023 年掌握移动应用程序架构的指南(附案例研究) 如果他们要解决这个问题,开发人员需要了解移动架构设计的最佳实践,使他们能够构建用户喜欢的优化应用程序。其中一些做法包括使用…...

NX二次开发 图层函数总结
简介: NX二次开发 图层相关的总结。 函数: uc5007()uc5008()uc5009()UF_LAYER_ask_category_info()获取图层类别的信息UF_LAYER_ask_category_tag()根据图层分类名称查询其图层分类标识UF_LAYER_ask_status()UF_LAYER_ask_work_layer()UF_LAYER_create…...

windows微服务部署
windows部署一.nginx部署1.nginx 官网下载2. 配置nginx3.配置nigix 防止nigix刷新404不生效二.配置redis部署成服务1.在系统配置中 配置为系统变量2.打开快捷登录服务管理#3. 开启redis三.windows部署jar包一.nginx部署 1.nginx 官网下载 地址 官网地址 安装 windows版本 可安…...

Java四种内部类(看这一篇就够了)
🎉🎉🎉点进来你就是我的人了 博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!人生格言:当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔🦾&am…...

蓝桥杯刷题第二十天
第一题:纸张尺寸问题描述在 ISO 国际标准中定义了 A0 纸张的大小为 1189mm 841mm, 将 A0 纸 沿长边对折后为 A1 纸, 大小为 841mm 594mm, 在对折的过程中长度直接取 下整 (实际裁剪时可能有损耗)。将 A1 纸沿长边对折后为 A2 纸, 依此类推。输入纸张的名称, 请输出…...

如何通过命令行查看CentOS版本信息和linux系统信息
1.如何查看已安装的CentOS版本信息: 1.cat /proc/version 2.uname -a 3.uname -r 4.cat /etc/centos-release 5.lsb_release -a 6.hostnamectl1. 第一种方式输出的结果是: Linux version 3.10.0-1127.el7.x86_64 (mockbuildkbuilder.bsys.centos.org) …...

oracle查询表空间大小以及每个表所占空间的大小
1、查询数据库中所有的表空间以及表空间所占空间的大小,直接执行语句就可以了: select tablespace_name, sum(bytes)/1024/1024 from dba_data_files group by tablespace_name; 2、查看表空间物理文件的名称及大小 select tablespace_name, file_id, …...