背景介绍

H.265是ITU-TVCEG继H.264之后所制定的新的视频编码标准。H.265标准围绕着现有的视频编码标准H.264,保留原来的某些技术,同时对一些相关的技术加以改进。H.265使用先进的技术用以改善码流、编码质量、延时和算法复杂度之间的关系,达到最优化设置。关于H.265对比H.264的优越性,网上有更专业的文章来作分析,因此我们在这里不做过多陈述。

基于其更高的压缩比,H.265适用于安防行业再合适不过了!因为安防行业每天都有海量的视频数据,同时需要实时传输、分析、存储…在带宽和存储成本依然昂贵的今天,我们极度需要更低的码率!更低的码率就等同于更低的成本。因此,各个安防厂商已经逐渐将视频设备由H.264转移到H.265了,这对于H.265编码也有着积极的推动作用。

然而,带给我们码农的则是痛苦——意味着我们不得不做大量的兼容和适配工作。好在FFmpeg已经支持了H.265的编解码算法了。这方面的文章也不少,其中雷神也专门写了一系列博客,参考:FFmpeg的HEVC解码器源代码简单分析:解码器主干部分

本篇文章将着重介绍在Android平台上H.265的硬解码的接口支持
在安卓平台,伟大的Google也给我们带来了H.265(又称HEVC)的硬解码的接口的支持(值得注意的是,也支持H.265软编码。后面我们会有专门文章来做介绍)。大家可以看看MediaCodec的API说明,接口简单基本上就是下面Google画的流程图:

硬解码接口调用

初始化解码器

首先初始化解码器,可以使用解码器类型或者解码器名称进行初始化,一般使用解码器类型即可。

```
// 使用解码器类型初始化
MediaCodec codec = MediaCodec.createDecoderByType("video/hevc");
// 使用解码器名称初始化,名称可通过MediaCodecList遍历所有解码器获取到
MediaCodec codec = MediaCodec.createByCodecName(name);
```

进行参数配置

初始化之后,需要进行配置,这也是最难的地方。配置时针对不同的解码器,需要不同的配置参数。对于HEVC,需要知道宽度、高度和CSD。CSD即:Codec-specific Data,是指跟特定编码算法相关的一些参数,比如AAC的ADTS、H.264的SPS、PPS等。

下面表格是安卓平台支持的编码格式与CSD(code specific data)的说明:

<br/>
<table><thead><tr><th>Format</th><th>CSD buffer #0</th><th>CSD buffer #1</th><th>CSD buffer #2</th></tr></thead><tbody class="mid"><tr><td>AAC</td><td>Decoder-specific information from ESDS<sup>*</sup></td><td class="NA">Not Used</td><td class="NA">Not Used</td></tr><tr><td>VORBIS</td><td>Identification header</td><td>Setup header</td><td class="NA">Not Used</td></tr><tr><td>OPUS</td><td>Identification header</td><td>Pre-skip in nanosecs<br>(unsigned 64-bit <a href="https://developer.android.com/reference/java/nio/ByteOrder.html#nativeOrder()">native-order</a> integer.)<br>This overrides the pre-skip value in the identification header.</td><td>Seek Pre-roll in nanosecs<br>(unsigned 64-bit <a href="https://developer.android.com/reference/java/nio/ByteOrder.html#nativeOrder()">native-order</a> integer.)</td></tr><tr><td>MPEG-4</td><td>Decoder-specific information from ESDS<sup>*</sup></td><td class="NA">Not Used</td><td class="NA">Not Used</td></tr><tr><td>H.264 AVC</td><td>SPS (Sequence Parameter Sets<sup>*</sup>)</td><td>PPS (Picture Parameter Sets<sup>*</sup>)</td><td class="NA">Not Used</td></tr><tr><td>H.265 HEVC</td><td>VPS (Video Parameter Sets<sup>*</sup>) +<br>SPS (Sequence Parameter Sets<sup>*</sup>) +<br>PPS (Picture Parameter Sets<sup>*</sup>)</td><td class="NA">Not Used</td><td class="NA">Not Used</td></tr><tr><td>VP9</td><td>VP9 <a href="http://wiki.webmproject.org/vp9-codecprivate">CodecPrivate</a> Data(optional)</td><td class="NA">Not Used</td><td class="NA">Not Used</td></tr></tbody></table><br/>

可以看到对于H.265,CSD只需要“csd-0”参数,就是把VPS、SPS、PPS拼接到一起即可。因此整个配置过程可以说就是获取这三个分量的过程。

遍历数据

作者参考了雷神的博客后,大体上明白了这三个分量的提取方式。简单的说,就是遍历数据,获取到00 00 01(或 00 00 00 01),再取出下一个字节,提取到nal_type。

```
byte nal_spec = data[i + 3];
int nal_type = (nal_spec >> 1) & 0x03f;
```

再判断nal_type的值,vps/sps/pps对应的nal_type分别是:

```
private static final int NAL_VPS = 32;
private static final int NAL_SPS = 33;
private static final int NAL_PPS = 34;
```

然后再到下一个00 00 01(或 00 00 00 01)结束
提取过程的代码如下:

```
private static byte[] getvps_sps_pps(byte[] data, int offset, int length) {
int i = 0;
int vps = -1, sps = -1, pps = -1;
do {if (vps == -1) {for (i = offset; i < length - 4; i++) {if ((0x00 == data[i]) && (0x00 == data[i + 1]) && (0x01 == data[i + 2])) {byte nal_spec = data[i + 3];int nal_type = (nal_spec >> 1) & 0x03f;if (nal_type == NAL_VPS) {// vps found.if (data[i - 1] == 0x00) {  // start with 00 00 00 01vps = i - 1;} else {                      // start with 00 00 01vps = i;}break;}}}}if (sps == -1) {for (i = vps; i < length - 4; i++) {if ((0x00 == data[i]) && (0x00 == data[i + 1]) && (0x01 == data[i + 2])) {byte nal_spec = data[i + 3];int nal_type = (nal_spec >> 1) & 0x03f;if (nal_type == NAL_SPS) {// vps found.if (data[i - 1] == 0x00) {  // start with 00 00 00 01sps = i - 1;} else {                      // start with 00 00 01sps = i;}break;}}}}if (pps == -1) {for (i = sps; i < length - 4; i++) {if ((0x00 == data[i]) && (0x00 == data[i + 1]) && (0x01 == data[i + 2])) {byte nal_spec = data[i + 3];int nal_type = (nal_spec >> 1) & 0x03f;if (nal_type == NAL_PPS) {// vps found.if (data[i - 1] == 0x00) {  // start with 00 00 00 01pps = i - 1;} else {                    // start with 00 00 01pps = i;}break;}}}}
} while (vps == -1 || sps == -1 || pps == -1);
if (vps == -1 || sps == -1 || pps == -1) {// 没有获取成功。return null;
}
// 计算csd buffer的长度。即从vps的开始到pps的结束的一段数据
int begin = vps;
int end = -1;
for (i = pps; i < length - 4; i++) {if ((0x00 == data[i]) && (0x00 == data[i + 1]) && (0x01 == data[i + 2])) {if (data[i - 1] == 0x00) {  // start with 00 00 00 01end = i - 1;} else {                    // start with 00 00 01end = i;}break;}
}
if (end == -1 || end < begin) {return null;
}
// 拷贝并返回
byte[] buf = new byte[end - begin];
System.arraycopy(data, begin, buf, 0, buf.length);
return buf;
}
```

进行配置

提取成功后,我们再用它进行配置:

```
byte[] csd0 = getvps_sps_pps(data, offset, Math.min(length, 200));
if (csd0== null) {throw new IOException("parse vps sps pps error...");
}
ByteBuffer csd0bf = ByteBuffer.allocate(csd0.length);
csd0bf.put(csd0);
csd0bf.clear();
format.setByteBuffer("csd-0", csd0bf);
format.setInteger(MediaFormat.KEY_WIDTH, width);
format.setInteger(MediaFormat.KEY_HEIGHT, height);
format.setString(MediaFormat.KEY_MIME, MIME_TYPE_HEVC);
// config
codec.configure(format, surface, null, 0);
```

我们将提取到的csd0转成ByteBuffer,再通过setByteBuffer设置到format里面,然后用format进行配置。

启动解码器

配置成功后,我们再启动解码器:

```
codec.start();
```

对视频帧进行解码

接下来就是对视频帧进行解码了。MediaCodec内部维护着一系列输入输出buffer,我们需要将265数据帧输入到输入队列,将解码后的视频数据从输出队列显示到界面。
对于输入,需要外部调用者申请(dequeue)buffer,并将视频帧拷贝到buffer,然后再释放(queue)给Codec;

```int inputBufferId = codec.dequeueInputBuffer(timeoutUs);if (inputBufferId >= 0) {ByteBuffer inputBuffer = codec.getInputBuffer(…);// fill inputBuffer with valid data// 我们需要把我们接收到的视频帧数据copy到inputBuffer里…// 把buffer归还给codeccodec.queueInputBuffer(inputBufferId, …);}
```

对于输出,外部调用者需要dequeue到outputbuffer,然后再做显示:

```int outputBufferId = codec.dequeueOutputBuffer(…);if (outputBufferId >= 0) {ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);// outputBuffer is ready to be processed or rendered.…// 下面可以直接显示,视频会显示在surface上了。codec.releaseOutputBuffer(outputBufferId, …);}
```

如果一切顺利,应该可以看到视频了。

退出时释放解码库

记得退出时要释放解码库~

```codec.stop();codec.release();
```

当然,不是所有的安卓机都支持H.265的硬解码,对于这些不支持硬解码的,使用ffmpeg进行软解即可,这方面资料也不在少数,但是软解码效率就不是很高了。

硬解码已应用于EasyPlayerPro项目

EasyPlayerPro是由TSINGSEE青犀开发和维护的一款精炼、易用、高效、稳定的流媒体播放器,支持RTSP(RTP over TCP/UDP)、RTMP、HTTP、HLS、TCP、UDP等多种流媒体协议,支持各种各样编码格式的流媒体音视频直播流、点播流、文件播放!

EasyPlayerPro:安卓视频播放器Android H.265硬解码方案(内含代码)相关推荐

  1. EasyPlayerPro安卓流媒体播放器实现Android H.265硬解码流程

    本文转自EasyDarwin团队成员John的博客:http://blog.csdn.net/jyt0551/article/details/74502627 H.265编码算法作为新一代视频编码标准 ...

  2. Android H.265硬解码EasyPlayerPro

    H.265编码算法作为新一代视频编码标准,在编码效果上有了很大的进步,同样清晰度的视频,265要比264有着更低的码率.关于265对比264的优越性,网上有更专业的文章来作分析,我也仅对这两种算法略知 ...

  3. 启明云端分享| 采用 B to B设计的RK3399核心板来了,邮票孔,支持4K、H.265 硬解码;核心板内置 EDP、MIPI-DSI、HDMI、DP 显示接口,带有 2 路 MIPI-CSI

    RK3399核心板(邮票孔)–IDO-SOM3909 提示:启明云端旗下触觉智能 采用 B to B设计的RK3399核心板来了,邮票孔,支持4K.H.265 硬解码:核心板内置 EDP.MIPI-D ...

  4. 音视频播放器与 H.265 播放探索

    一.简介 简单来说,H.265 标准围绕着视频编码标准 H.264,保留原来的某些技术,同时对一些相关的技术加以改进. 改进点包括:提高压缩效率.提高鲁棒性和错误恢复能力.减少实时的时延.减少信道获取 ...

  5. Android Video Player. 安卓视频播放器,封装 MediaPlayer、ExoPlayer、IjkPlayer。模仿抖音,悬浮播放,广告播放,列表播放,弹幕

    DKVideoPlayer 项目地址:dueeeke/DKVideoPlayer 简介: Android Video Player. 安卓视频播放器,封装 MediaPlayer.ExoPlayer. ...

  6. android电影播放器,安卓视频播放器哪个好 五款主流视频播放器对比

    如今手机的流行趋势是屏幕越来越大,从3.5寸到3.7寸再到现在不少主流的安卓手机所采用的4.3寸屏幕,用手机来看电影已经是一项在我们日常生活中使用频率非常高的功能.而手机处理器性能的进化也让我们可以轻 ...

  7. 视频教程-FFmpeg+OpenGL ES+OpenSL ES打造Android视频播放器-Android

    FFmpeg+OpenGL ES+OpenSL ES打造Android视频播放器 从事Android移动端开发多年.主导开发过直播.电商.聊天等各种类型APP和游戏SDK:熟悉Android音视频开发 ...

  8. 简易网络视频播放器android

    简易网络视频播放器android demo: Qvod 1.新建: app\src\main\res\xml\network_security_config.xml 作用是可以发送 http请求 &l ...

  9. android tuner 教程,安卓调谐器(Android Tuner)

    安卓调谐器Android Tuner:功能很强大的一个东西,只是大家使用的时候要注意,很多关键系统功能很有可能被误操作改掉了.安卓调谐器Android Tuner是一款应用合集,合并了Battery ...

最新文章

  1. Centos7安装DockerCE
  2. 独家 | 构建符合道德规范的用于人才管理的AI(附链接)
  3. 【Latext】上标下标 ( 右侧上标下标 | 任意字符的正上标记 | 任意字符的正下标记 | 常用数学符号的上标和下标 | 加和 | 乘积 | 交集 | 并集 | 上积 | 极限 | 上弧 )
  4. 基于双向链表的增删改查和排序(C++实现)
  5. java default修饰符_2019最新java面试题附答案
  6. 《零基础》MySQL 安装(二)
  7. javascript 库_您应该在2020年尝试的10个很棒JavaScript库
  8. 【NOIP2013】【Luogu1983】车站分级(建图,拓扑排序)
  9. jeep智能手表软件测评中心的测试,够了,不要太帅:Jeep黑骑士智能手表深度评测...
  10. pythonqq机器人酷q_基于python和酷Q的QQ机器人开发实践(1)
  11. Excel数据分析高级技巧②——数据透视表(组合/切片器/计算字段/数据透视图/条件格式)
  12. php进程是什么,PHP的进程模型是什么
  13. /deep/在chrome89+中出现样式混乱的问题
  14. Apollo代码学习(三)—车辆动力学模型
  15. ES可视化工具--Dejavu--下载、安装、使用
  16. 微软认知服务应用秘籍 – 支持跨平台客户端的视觉服务中间层
  17. Hbase的数据切分
  18. html立体图表样式,项目进度可视化图表
  19. dell r510服务器怎么装系统,DELLR510服务器上安系统
  20. 编译ORB-SLAM2遇到的问题及解决方法

热门文章

  1. 三元:将对20万名贫困家庭儿童进行健康扶贫
  2. 真分数转埃及分数的和 (贪心)
  3. 衡水东方计算机学校校景,衡水东方计算机学校宿舍条件
  4. 1000个苹果要分到10个箱子中去 两种分析方式
  5. 塔米狗2022年地方国企名单,总计816家企业,第二批150家
  6. 春节小游戏之图片分类(Pytorch模型部署)
  7. 重新系统(win11)以后,西部硬盘(机械硬盘)识别不了
  8. 金蝶云星空与钉钉集成案例
  9. 自己动手制作C 语言编译器(7):语句
  10. uniapp 实现销售订单页面-风格1