MediaCodec 解码H264/H265码流视频
zhezhongyun 2025-07-03 20:54 3 浏览
MediaCodec 解码H264/H265码流视频
1、使用MediaCodec目的
MediaCodec是Android底层多媒体框架的一部分,通常与MediaExtractor、MediaMuxer、AudioTrack结合使用,可以编码H264、H265、AAC、3gp等常见的音视频格式
MediaCodec工作原理是处理输入数据以产生输出数据
1.1 MediaCodec工作流程
MediaCodec的数据流分为input和output流,并通过异步的方式处理两路数据流,直到手动释放output缓冲区,MediaCodec才将数据处理完毕
- input流:客户端输入待解码或者待编码的数据
- output流:客户端输出的已解码或者已编码的数据
1.2 MediaCodec API说明
- getInputBuffers:获取需要输入流队列,返回ByteBuffer数组
- queueInputBuffer:输入流入队
- dequeueInputBuffer: 从输入流队列中取数据进行编码操作
- getOutputBuffers:获取已经编解码之后的数据输出流队列,返回ByteBuffer数组
- dequeueOutputBuffer:从输出队列中取出已经编码操作之后的数据
- releaseOutputBuffer: 处理完成,释放output缓冲区
领音视频资料→「链接」
1.3 基本流程
- 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()方法释放其资源
1.4 基本使用
//解码器
val mVideoDecoder = MediaCodec.createDecoderByType("video/avc")
//编码器
val mVideoEncoder = MediaCodec.createEncoderByType("video/avc")
2、MediaCodec 解码H264/H265
使用MediaCodec 解码H264/H265码流视频,那必须谈下MediaCodec这个神器。附官网数据流程图如下:
input:ByteBuffer输入方;
output:ByteBuffer输出方;
- 使用者从MediaCodec请求一个空的输入buffer(ByteBuffer),填充满数据后将它传递给MediaCodec处理。
- MediaCodec处理完这些数据并将处理结果输出至一个空的输出buffer(ByteBuffer)中。
- 使用者从MediaCodec获取输出buffer的数据,消耗掉里面的数据,使用完输出buffer的数据之后,将其释放回编解码。
2.1 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为 AAC
mediaCodec = MediaCodec.createDecoderByType("video/avc");
//创建配置
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 540, 960);
//设置解码预期的帧速率【以帧/秒为单位的视频格式的帧速率的键】
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
//配置绑定mediaFormat和surface
mediaCodec.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 {
@Override
public 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;存在则大于0
int inIndex = mediaCodec.dequeueInputBuffer(10000);
if (inIndex >= 0) {
//根据返回的index拿到可以用的buffer
ByteBuffer 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 {
//等待查询空的buffer
continue;
}
//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;
}
}
2.2 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为 AAC
mediaCodec = MediaCodec.createDecoderByType("video/hevc");
//创建配置
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/hevc", 368, 384);
//设置解码预期的帧速率【以帧/秒为单位的视频格式的帧速率的键】
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
//配置绑定mediaFormat和surface
mediaCodec.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 {
@Override
public 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;存在则大于0
int inIndex = mediaCodec.dequeueInputBuffer(10000);
if (inIndex >= 0) {
//根据返回的index拿到可以用的buffer
ByteBuffer 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 {
//等待查询空的buffer
continue;
}
//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;
}
}
2.3 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;
@Override
protected 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() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
h264DeCodePlay = new H264DeCodePlay(videoPath, holder.getSurface());
h264DeCodePlay.decodePlay();
// h265DeCodePlay = new H265DeCodePlay(videoPath, holder.getSurface());
// h265DeCodePlay.decodePlay();
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
}
});
}
}
测试的H264 / H265码流视频通过FFmpeg抽取可得到。
命令行:
ffmpeg -i 源视频.mp4 -codec copy -bsf: h264_mp4toannexb -f h264 输出视频.h264
最后效果如下:
相关推荐
- 用豆包生成的BMI计算器(豆包的热量是多少?)
-
<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8...
- Android 开发中文引导-应用小部件
-
应用小部件是可以嵌入其它应用(例如主屏幕)并收到定期更新的微型应用视图。这些视图在用户界面中被叫做小部件,并可以用应用小部件提供者发布。可以容纳其他应用部件的应用组件叫做应用部件的宿主(1)。下面的截...
- Qt推流(视频文件/视频流/摄像头/桌面转流媒体rtmp+hls+webrtc)
-
一、前言说明推流直播就是把采集阶段封包好的内容传输到服务器的过程。其实就是将现场的视频信号从手机端,电脑端,摄影机端打包传到服务器的过程。“推流”对网络要求比较高,如果网络不稳定,直播效果就会很差,观...
- 一看就会!谷歌广告转化跟踪详细设置指南来了
-
在出海推广业务中,投放广告最常见的目的是获取订单,但我们怎么知道有没有达成投放目的呢?谷歌转化跟踪技术就可以做到!熟悉谷歌的卖家朋友都知道,转化跟踪在最近几年变得越来越复杂了,虽然有很多选项可以自定义...
- Android原生编解码接口MediaCodec详解
-
作者:躬行之MediaCodec是Android中的编解码器组件,用来访问底层提供的编解码器,通常与MediaExtractor、MediaSync、MediaMuxer、MediaCrypt...
- 手把手搭建RTSP流媒体服务器(rtsp 流媒体)
-
0.引言本文主要讲解如何搭建RTSP流媒体服务器的过程,使用开源项目ZLMediaKit。通过这个开源项目,推RTSP流到服务器,然后拉流端可以拉取RTSP、RTMP等流。ZLMediaKit码云链接...
- MediaInfo 24.04.0 是一个关于多媒体文件的信息提供工具
-
MediaInfo24.04.0是一个关于多媒体文件的信息提供工具(仅当文件中包含信息时才提供):包括常规信息(标题、作者、导演、专辑、曲目编号、日期、时长等);视频信息(编解码器、画面比例、帧率...
- rmvb格式视频怎么打开,rmvb转MP4认准这个方法
-
一、rmvb是什么格式? RMVB是一种视频文件格式,其中的VB指的是可变比特率。比起上一代的RM格式,RMVB 格式的画面比较清晰,因为它是降低了静态画面下的比特率。 二、制作rmvb ①...
- 教你用Plex Media Server,把铁威马变成你的“私人好莱坞”!
-
TNAS(铁威马NAS)中可以安装多媒体服务器、影视、PlexMediaServer、EmbyServer作为个人媒体服务器使用。PlexMediaServer可以组织整理TNAS上的媒体...
- 你肯定用过!经典Windows软件被抛弃
-
Windows系统这些年持续更新的过程中,不断融入新的软件和功能的同时,一些经典的应用也渐渐成为了历史……Windows媒体播放器被抛弃Windows系统不断地推陈出新,一些老旧的组件也难免被抛弃,在...
- 博思得Q8标签打印全能手(博思得标签打印机安装教程)
-
2014-12-0905:35:00作者:宋达希【中关村在线办公打印频道原创】服装吊牌、洗涤标签、产品说明标签等都要用到标签打印机,这些标签涵盖多种尺寸的长度和宽度以及材质。另外作为一件商品或者产...
- flv文件用什么播放器打开,这样做不踩雷!
-
FLV是FLASHVIDEO的简称,是随着FlashMX的推出发展而来的视频格式。它的出现有效地解决了视频文件导入Flash后,使导出的SWF文件体积庞大,不能在网络上很好的使用等问题。一、...
- media player怎么转换格式?音频转换神器推荐!
-
Windowsmediaplayer怎么转换格式?WindowsMediaPlayer是微软公司出品的一款多媒体播放器,通常简称“WMP”。提供了编辑音频和视频文件的功能。用户可以使用该软件导...
- 视频参数检查工具更新:MediaInfo 23.10
-
MediaInfo提供有关视频或音频文件的技术和标签信息。信息示例包括编解码器、比特率、每秒帧数、宽度、高度、频道数、持续时间、标题、作者、字幕语言和章节名称。多种方式可以查看信息(文本、工作表、树和...
- 多媒体管理软件:JRiver Media Center 31.0.68 (64位)
-
JRiverMediaCenter64位是适用于大量库的完整媒体解决方案。它组织、播放和标记所有类型的媒体文件,并对Xbox、PS3、UPnP、DLNA和TiVo进行翻录、刻录。JRiverM...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML常用标签 (29)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- HTML button formtarget 属性 (30)
- CSS 水平对齐 (Horizontal Align) (30)