本文转自EasyDarwin团队成员John的博客:http://blog.csdn.net/jyt0551/article/details/74502627

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

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

同时,给我们码农带来的则是痛苦——意味着我们不得不做大量的兼容和适配工作。还好FFmpeg在好久之前就支持265的编解码算法了。这方面的文章也不少,包括雷神也专门写了系列博客,参考:http://blog.csdn.net/leixiaohua1020/article/details/46412897

而在安卓平台,伟大的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)的说明:

Format CSD buffer #0 CSD buffer #1 CSD buffer #2
AAC Decoder-specific information from ESDS* Not Used Not Used
VORBIS Identification header Setup header Not Used
OPUS Identification header Pre-skip in nanosecs

(unsigned 64-bit native-order integer.)

This overrides the pre-skip value in the identification header.
Seek Pre-roll in nanosecs

(unsigned 64-bit native-order integer.)
MPEG-4 Decoder-specific information from ESDS* Not Used Not Used
H.264 AVC SPS (Sequence Parameter Sets*) PPS (Picture Parameter Sets*) Not Used
H.265 HEVC VPS (Video Parameter Sets*) +

SPS (Sequence Parameter Sets*) +

PPS (Picture Parameter Sets*)
Not Used Not Used
VP9 VP9 CodecPrivate Data
(optional)
Not Used Not Used

可以看到,对于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 01
                            vps = i - 1;
                        } else {                      // start with 00 00 01
                            vps = 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 01
                            sps = i - 1;
                        } else {                      // start with 00 00 01
                            sps = 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 01
                            pps = i - 1;
                        } else {                    // start with 00 00 01
                            pps = 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 01
                end = i - 1;
            } else {                    // start with 00 00 01
                end = 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归还给codec
      codec.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, …);
     }

如果一切顺利,应该看到了视频了,every one is happy~

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

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

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

现阶段硬解码已经适用于EasyPlayerPro项目当中。

在这里再简单介绍一下EasyPlayerPro,EasyPlayer Pro专业版全功能播放器是由EasyDarwin开源团队维护的一款支持RTSP、RTMP、HTTP、HLS多种流媒体协议的播放器版本。

EasyPlayerPro下载地址:https://fir.im/EasyPlayerPro

相关介绍见:http://www.easydarwin.org/article/news/117.html

获取更多信息

邮件:support@easydarwin.org

WEB:www.easydarwin.org

QQ群:587254841

Copyright © EasyDarwin.org 2012-2017

EasyPlayerPro安卓流媒体播放器实现Android H.265硬解码流程的更多相关文章

  1. EasyPlayerPro(Windows)流媒体播放器开发之跨语言调用

    下面我们来讲解一下关于EasyPlayerPro接口的调用,主要分为C++和C#两种语言,C++也可以基于VC和QT进行开发,C++以VC MFC框架为例进行讲解,C#以Winform框架为例进行讲解 ...

  2. EasyPlayerPro Windows流媒体播放器(RTSP/RTMP/HTTP/HLS/File/TCP/RTP/UDP都能播)发布啦

    EasyPlayerPro简介 EasyPlayerPro是一款全功能的流媒体播放器,支持RTSP.RTMP.HTTP.HLS.UDP.RTP.File等多种流媒体协议播放.支持本地文件播放,支持本地 ...

  3. EasyPlayerPro(Windows)流媒体播放器开发之框架讲解

    EasyPlayerPro for Windows是基于ffmpeg进行开发的全功能播放器,开发过程中参考了很多开源的播放器,诸如vlc和ffplay等,其中最强大的莫过于vlc,但是鉴于vlc框架过 ...

  4. EasyPlayerPro(Windows)流媒体播放器开发之接口设计

    EasyPlayerPro(windows)接口说明如下: EasyPlayerPro_Open 说明:打开一个媒体流或者媒体文件进行播放,同时返回一个 player 对象指针 参数说明: fileU ...

  5. EasyPlayerPro(Windows)流媒体播放器开发之ffmpeg log输出报错

    EasyPlayerPro主要基于ffmpeg进行开发,在EasyPlayerPro开发过程中,曾遇到一个相对比较棘手的问题,该问题一般在播放不是很标准的流或者网络情况较差,容易出现丢帧的情况特别容易 ...

  6. EasyPlayerPro(Windows)流媒体播放器功能介绍及应用场景

    EasyPLyerPro(Windows)经过为期一个月的开发已经基本完成,虽然目前仍存在一些小问题,但是总体功能还是趋于比较稳定和强大的,下面对其功能和应用场景做简要介绍. 一.EasyPlayer ...

  7. EasyPlayer Android安卓流媒体播放器实现播放同步录像功能实现(附源码)

    本文转自EasyDarwin团队John的博客:http://blog.csdn.net/jyt0551,John是EasyPusher安卓直播推流.EasyPlayer直播流媒体播放端的开发和维护者 ...

  8. 查看Android支持的硬解码信息

    通过/system/etc/media_codecs.xml可以确定当前设备支持哪些硬解码.通过/system/etc/media_profiles.xml可以知道设备支持的具体profile和lev ...

  9. EasyPlayer RTSP Windows(with ActiveX/OCX插件)播放器支持H.265播放与抓图功能

    EasyPlayer作为业界一款比较优秀的RTSP播放器,一直深受用户的好评,经过了近3年的开发和迭代,从一开始的简单PC版本的RTSP播放功能,到如今支持PC(支持ocx插件).Android.iO ...

随机推荐

  1. 20145301 《Java程序设计》第八周学习总结

    20145301 <Java程序设计>第八周学习总结 教材学习内容总结 第十五章部分 - 通用API 日志API 日志: 日志对信息安全意义重大,审计.取证.入侵检测等都会用到日志信息 L ...

  2. c#结构体和字节流之间的相互转换

    结构体转byte数组 1  首先要明白 ,是 在那个命名空间下  System.Runtime.InteropServices; 2  首先得到结构体的大小 2  开辟相应的内存空间 3  将结构体填 ...

  3. Eye Protection FAQ

    Q: Why does smart protection not work? A: Please make sure the checkbox "Eye Protection" i ...

  4. 【bzoj2563】阿狸和桃子的游戏(贪心+构造)

    题目传送门:bzoj2563 先膜拜一波PoPoQQQ的题解:BZOJ 2563 阿狸和桃子的游戏 贪心 其实我们可以这样看:把一条边的权值均分到两个端点,那么取到两个端点就能得到这条边的边权,如果只 ...

  5. LeetCode——Nth Digit

    Question Find the nth digit of the infinite integer sequence 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... ...

  6. JDK 中的监控与故障处理工具-02 (jps)

    jps : JVM Process Status Tool jps 命令可以列出正在运行的虚拟机进程, 并显示虚拟机执行的 main class 的名称(main函数所在的类),以及这些进程的本地虚拟 ...

  7. ABP 源码分析汇总之 AutoMapper

    AutoMapper 是一个对象映射工具, 安装时只需要安装 如下即可: 有关于它的介绍,参考官网:http://automapper.org/ AutoMapper使用比较简单,还是直奔主题,看一下 ...

  8. Quartz(自动任务)中的触发器Trigger

    1.Quartz中的触发器TriggerJob 包含了要执行任务的逻辑,但是 Job 对何时该执行却一无所知.这个事情留给了 Trigger.Quartz Trigger 继承了抽象的 org.qua ...

  9. 关于Spring中applicationContext.xml配置错误“org/springframework/transaction/interceptor/TransactionInterceptor”的问题解决

    问题描述: 在配置spring的applicationContext.xml中的默认事务管理器的时候可能会出现这样的错误: Error occured processing XML 'org/spri ...

  10. DataTemplate——数据模板的一个典型例子

    下面是ListBox.ItemTemplate(数据模板)应用的“典型”例子,概述如下两点: 1:Grid部分,用来“规划” 数据 显示的 布局(即数据长成什么样子)     2:给DataTempl ...