本文转自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. 20145103JAVA第二次实验报告

    实验二 Java面向对象程序设计 实验内容 1.初步掌握单元测试和TDD 2.理解并掌握面向对象三要素:封装.继承.多态 3.初步掌握UML建模 4.熟悉S.O.L.I.D原则 5.了解设计模式 实验 ...

  2. LCD1602

    一.关于LCD1602: 在编写LCD1602程序前,我们必须了解其手册上一些非常重要的信息,如果这些信息不能理解透彻,编程可能会遇到或多或少的问题,在此先大致归纳几点. 1.管脚: 1602共16个 ...

  3. Java Mail 邮件发送Demo

    上周公司的项目要求开发邮件发送功能.自己在网上跟着教程边学边做了一下午,现在基本开发完成了.由于一个同事也想看下该怎么写,顺便学习下.所以我就写成了一遍教程,顺便巩固下邮件发送里面的内容. Demo ...

  4. Faster-rcnn 配置方法

    Faster-rcnn 在Linux下的配置方法 感谢@邓学长 建立过程: (下载库的时候要按照库readme 进行操作) opencv 的包下载安装,安装教程 用git命令将这个库下载到本地 fas ...

  5. Partition List,拆分链表

    问题描述: Given a linked list and a value x, partition it such that all nodes less than x come before no ...

  6. python+opencv链接摄像头

    import cv2 import numpy as numpy cap=cv2.VideoCapture(0) #设置显示分辨率和FPS ,不设置的话会非常卡 cap.set(cv2.CAP_PRO ...

  7. JavaScript内部原理系列-变量对象(Variable object)

    概要 我们总是会在程序中定义一些函数和变量,之后会使用这些函数和变量来构建我们的系统.然而,对于解释器来说,它又是如何以及从哪里找到这些数据的(函数,变量)?当引用一个对象的时候,在解释器内部又发生了 ...

  8. pandas的常用函数

    1.DataFrame的常用函数: (1)np.abs(frame) 绝对值, (2)apply function, lambda f= lambda x: x.max()-x.min(),frame ...

  9. 微信小程序------基本组件

    今天主要是简单的讲一下小程序当中的一些组件,微信文档上也是有的.但我还是坚持写一下,因为写博客可以再一次得到提高,印象更深刻,虽然很简单,但贵在坚持. 先来看看效果图: 1:进度条(progress) ...

  10. Java编码方式再学

    一直以来对编码方式对了解不是很深入.建议读下这几篇博文 学点编码知识又不会死:Unicode的流言终结者和编码大揭秘 编码研究笔记 这几篇博文上回答了内心存在的一些问题,这些问题可能也是大家经常遇到的 ...