音视频学习H264系列:H264简介

音视频学习H264系列:H264视频编码原理基础分析

音视频学习H264系列:H264视频编码原理进阶分析

音视频学习H264系列:MediaCodec H264/H265解码

音视频学习H264系列:终结篇实战

序言

本篇是有关音视频学习系列中的H264 / H265的解码视频部分,文章大部分记录直接上干货,编码原理基础部分【音视频学习H264系列:H264视频编码原理】后续再补上。欢迎留言讨论。

使用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

附上测试视频下载地址:

H264资源:output.h264-Android文档类资源-CSDN下载

H265资源:https://download.csdn.net/download/Ae_fring/13624500

效果如下:

Android MediaCodec 解码H264/H265码流视频相关推荐

  1. H264/H265码流类型

    文章目录 H.264码流 H.264码流格式 Annex B AVCC H.265码流 H.264码流 H264中,NALU类型1-5为视频帧,其余则为非视频帧.在解码过程中,我们只需要取出NALU头 ...

  2. python小工具------将H264/H265码流文件转为一帧一帧的JPEG文件

    一.功能 输入H264/H265码流路径,此脚本可以将对应的码流转为一帧一帧的JPEG文件,并保存到指定路径. 二.具体实现 import av import sysdef h265ToJpg_dem ...

  3. Android MediaCodec 解码H264码流播放

    视频编解码,编的是什么码?解的又是什么码?有没有想过?现在主流的就是H264码流,Android 采集摄像头原始帧数据 这篇博客讲解的是如何从摄像头从提取YUV画面色值,然后由MediaCodec进行 ...

  4. 从H264/H265码流中获取宽、高及帧率

     在做码流分析时,图像分辨率.帧率这类的基本信息,当然不可少.本文介绍如何从NAL中计算到图像宽.高,还有分辨率.由于H264和H265有相似性,就在一起写了. 一.从码流获得宽.高 1.H264 ...

  5. H264/H265码流的编码码率详解

    1.视频码率概念 视频码率是视频数据(视频色彩量.亮度量.像素量)每秒输出的位数,即单位时间传送的数据位数.一般用的单位是kbps(千位每秒).通俗一点来讲就是采样率,单位时间的采样率越大,精度就越高 ...

  6. java h265,H264/H265码流的编码码率设置

    一.什么是视频码率? 视频码率是视频数据(视频色彩量.亮度量.像素量)每秒输出的位数.一般用的单位是kbps. 二.设置视频码率的必要性 在视频会议应用中,视频质量和网络带宽占用是矛盾的,通常情况下视 ...

  7. <整理总结>H264/265码流数据包格式分析(带mp4v2封装H264/265为MP4的源码示例)

    H264/265码流数据包格式分析 前言: 一.H.264码流解析 I帧P帧B帧说明: 二.H.265码流解析 三.主要源码 前言: 最近在学习使用MP4v2将H264/H265码流以及AAC音频封装 ...

  8. 【转】C#播放H264裸码流

    原文地址:https://www.cnblogs.com/cangyue080180/p/5873351.html 要播放H264裸码流,可以分拆为以下三个工作: 1.解码H264裸码流获取YUV数据 ...

  9. 从原理到实践:使用Mediacodec编码H265并实现解码H265码流

    H265 H265,也称为HEVC(High Efficiency Video Coding),是一种高效视频编码格式.它是H264(AVC)的后继者,也是ITU-T和ISO/IEC联合开发的标准.相 ...

  10. (推荐阅读)H264, H265硬件编解码基础及码流分析

    需求 在移动端做音视频开发不同于基本的UI业务逻辑工作,音视频开发需要你懂得音视频中一些基本概念,针对编解码而言,我们必须提前懂得编解码器的一些特性,码流的结构,码流中一些重要信息如sps,pps,v ...

最新文章

  1. c c++常用算法手册(第3版_嵌入式软件开发必看书籍推荐(C/C++/linux/软件)
  2. ASA LAB-ASA NAT配置大全
  3. 前端常用插件、工具类库汇总(上)
  4. 使用screen后台运行python(基于centOS7.2)
  5. Spring Boot——LocalDateTime格式化配置
  6. 【每日一题】7月16日题目精讲—点权和
  7. 图形显卡_选核芯显卡还是独立显卡?这才是决定笔记本电脑性能的关键
  8. C#中WebBrowser控件的使用
  9. python读取文件乱码
  10. Hbase namespace操作入门
  11. java mongodb 读取文件_Java操作Mongodb之文件读写
  12. 网易云再度升级!用Python爬取下载(一:思路)
  13. matlab中arma,ARMA模型的Matlab代码.doc
  14. 一周学习总结:vue学前准备知识
  15. php怎样做艺术字体,用ps打造科幻艺术字体
  16. java根据word模板导出_java根据word模板导出word文件
  17. 如何提高外贸询单转化率
  18. [转]Flex 处理bmp图片as
  19. numpy array 报错 Layout of the output array img is incompatible with cv::Mat
  20. 情人节送男生的礼物,情人节送礼清单

热门文章

  1. 作业三 使用病毒分析工具对病毒进行分析
  2. 《Redis视频教程》(p5)
  3. python算法书籍-推荐 10 本程序员必读的算法书
  4. linux udhcpc指令,linux下udhcpc的使用
  5. GMP编译make check时出现FAIL t-scan
  6. lisp绘制直齿圆柱齿轮_直齿圆柱齿轮的知识及其画法
  7. 常用的Shell脚本集合
  8. Mac系统下编译并使用ijkplyer播放器
  9. 简单三步,教你搭建一个私有云盘
  10. python数据转换成pdf_用python把ipynb文件转换成pdf文件过程详解