音视频开发—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…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...
Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...
在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7
在 Go 项目中降级 go-ansible 从 v2.2.0 到 v1.1.7 具体步骤: 第一步: 修改 go.mod 文件 // 原 v2 版本声明 require github.com/apenella/go-ansible/v2 v2.2.0 替换为: // 改为 v…...
云原生时代的系统设计:架构转型的战略支点
📝个人主页🌹:一ge科研小菜鸡-CSDN博客 🌹🌹期待您的关注 🌹🌹 一、云原生的崛起:技术趋势与现实需求的交汇 随着企业业务的互联网化、全球化、智能化持续加深,传统的 I…...
【Java基础】向上转型(Upcasting)和向下转型(Downcasting)
在面向对象编程中,转型(Casting) 是指改变对象的引用类型,主要涉及 继承关系 和 多态。 向上转型(Upcasting) ⬆️ 定义 将 子类对象 赋值给 父类引用(自动完成,无需强制转换&…...
