用过Android-MediaCodec做视频编解码的同学,应该都会见过这个定义变量——HEVCProfileMain10HDR10,其定义是在MediaCodecInfo.CodecProfileLevel类下,其定义如下。
// from OMX_VIDEO_HEVCPROFILETYPE
public static final int HEVCProfileMain = 0x01;
public static final int HEVCProfileMain10 = 0x02;
public static final int HEVCProfileMain10HDR10 = 0x1000;
// from OMX_VIDEO_HEVCLEVELTYPE
public static final int HEVCMainTierLevel1 = 0x1;
public static final int HEVCHighTierLevel1 = 0x2;
public static final int HEVCMainTierLevel2 = 0x4;
public static final int HEVCHighTierLevel2 = 0x8;
public static final int HEVCMainTierLevel21 = 0x10;
public static final int HEVCHighTierLevel21 = 0x20;
public static final int HEVCMainTierLevel3 = 0x40;
public static final int HEVCHighTierLevel3 = 0x80;
public static final int HEVCMainTierLevel31 = 0x100;
public static final int HEVCHighTierLevel31 = 0x200;
public static final int HEVCMainTierLevel4 = 0x400;
public static final int HEVCHighTierLevel4 = 0x800;
public static final int HEVCMainTierLevel41 = 0x1000;
public static final int HEVCHighTierLevel41 = 0x2000;
public static final int HEVCMainTierLevel5 = 0x4000;
public static final int HEVCHighTierLevel5 = 0x8000;
public static final int HEVCMainTierLevel51 = 0x10000;
public static final int HEVCHighTierLevel51 = 0x20000;
public static final int HEVCMainTierLevel52 = 0x40000;
public static final int HEVCHighTierLevel52 = 0x80000;
public static final int HEVCMainTierLevel6 = 0x100000;
public static final int HEVCHighTierLevel6 = 0x200000;
public static final int HEVCMainTierLevel61 = 0x400000;
public static final int HEVCHighTierLevel61 = 0x800000;
public static final int HEVCMainTierLevel62 = 0x1000000;
public static final int HEVCHighTierLevel62 = 0x2000000;
一、如何理解HEVC的Profile、Level和Tier?
Profile用来规定码流中使用了哪些编码工具和算法。
Level规定了给定Profile、Tier所对应的解码器处理负担和存储容量参数。主要包括采样率(帧率)、分辨率、码率的最大值、压缩率的最小值、解码图像缓存区的容量(Decoded Picture Buffer, DPB)、编码图像缓存区的容量(Coded Picture Buffer, CPB)等。
Tier规定了每个Level的码率的高低。
在编解码器的兼容性方面:
1.要求支持某个Profile的解码器必须支持该Profile及低于该Profile中的所有特性(向下兼容);
2.要求支持某个Level和Tier的解码器可以解码所有等于和低于这个Level和Tier的码流;
3.支持某一个Profile的编码器,并不要求它支持该Profile的所有特性,但是编码的码流必须符合HEVC的标准,才可被支持该Profile的解码器所解码。
1.1、档次(Profile)
HEVC常用的有三个Main Profile(version 1 profile):即常规8bit像素精度的Main Profile;支持10bit像素精度的Main 10 Profile;支持静止图像的Main Still Picture Profile。
Main Profile特性如下:
1.原始图像采样格式限制为4:2:0
2.CTB的大小从16×16到64×64
3.解码图像的缓存容量限制为6幅图像
4.允许选择波前和片划分方式,但是不能同时选择。如果选择片,其尺寸至少高为64像素,宽为256像素
Main 10 Profile的特性:
主要的特点和Main Profile类似,但是不同之处在于,它能够支持10比特深度。
Main Still Profile的特性:
主要特点和Main Profile类似,也仅支持8bit深度,但区别在于它不支持帧间预测编码。
1.2、水平(Level)
水平(Level)指出了一些对解码端负载和内存占用影响较大的关键参数的约束,这些参数主要包括:采样频率(帧率)、分辨率、码率的最大值,压缩率的最小值、解码图形缓冲区(DPB)的容量、编码图像缓冲区(CPB)的容量等。
此外,水平中还约束了每帧中垂直和水平方向的Tile的最大数量,以及每秒最大的Tile数量。HEVC共设置了13个水平,如下:
1.3、等级(Tier)
对同一水平,按照最大码率和缓存容量要求的不同,HEVC设置了两档等级,定义为高等级(High Tier)和主等级(Main Tier)。
主等级可用于大多数场合,涵盖了13个Level水平,它要求码率较低;高等级可用于特殊要求或苛刻要求的场合,包括4和4以上的8个水平,允许码率较高,在同一水平大约高3-4倍。
也就是说不同Profile有不同的Level,相同Level有两个Tier。
回头再来看Android.MediaCodecInfo.CodecProfileLevel里面的HEVCProfileMain(main 8bit)、HEVCProfileMain10(main 10bit)、HEVCProfileMain10HDR10(main 10bit 支持HDR,怎么个支持法?暂时还没深挖。)
可以用以下简单的代码检测当前Android设备是否支持HEVCProfileMain10HDR10
public boolean testHEVCProfileMain10HDR10() {
boolean crashed = false;
MediaCodec codec = null;
try {
MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_HEVC, 720, 1280);
codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_HEVC);
boolean support = checkAndSetHDR10ProfileLevel(codec, format);
crashed = !support;
codec.configure(format, null, null, 0);
codec.start();
}catch (Exception e){
crashed = true;
YMFLog.error(this, Constant.MEDIACODE, "testHEVCProfileMain10HDR10, reason:" + e.getMessage());
} finally {
if (codec != null) {
try {
codec.stop();
codec.release();
} catch (Exception ex) {
YMFLog.error(null, Constant.MEDIACODE, "release test encoder error! reason:" + ex.getMessage());
}
}
}
return !crashed;
}
public boolean checkAndSetHDR10ProfileLevel(MediaCodec codec, MediaFormat format) {
boolean isSupportMain10HDR10 = false;
MediaCodecInfo.CodecProfileLevel[] mProfileLevels = codec.getCodecInfo().getCapabilitiesForType(MediaFormat.MIMETYPE_VIDEO_HEVC).profileLevels;
int candidateLevel = 0;
int candidateProfile = 0;
for (MediaCodecInfo.CodecProfileLevel info : mProfileLevels) {
if (info.profile <= MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10) {
if (candidateProfile < info.profile) {
candidateProfile = info.profile;
candidateLevel = info.level;
} else if (candidateProfile == info.profile && candidateLevel < info.level) {
candidateLevel = info.level;
}
}
}
format.setInteger(MediaFormat.KEY_PROFILE, candidateProfile);
format.setInteger(MediaFormat.KEY_LEVEL, candidateLevel);
YMFLog.info(this, Constant.MEDIACODE, "current MediaFormat : " + format.toString());
if (candidateProfile == MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10) {
isSupportMain10HDR10 = true;
}
return isSupportMain10HDR10;
}
二、10位深(10bit)?
研究HDR的肯定知道位深这一概念,(实在不知道就到这里)除了常见的 8bit yuv 外,还有 10bit,12bit,14bit,16bit 这样的yuv 数据。网上的资料都会直接跟你说,8bit以外的数据都是以2字节的来存储有效数据,并附带这一张图给你讲述存储的结构。
但其实这样说是不全面的!上图是P16格式下存储的10位有效数据。根据标准SMPTE,位深转换关系是:源数据乘以2^(n-m),这里的n是目标位深,也就是10、12或16,m是源位深度,目标例子是8;所以如果要把8bit yuv数据转成10bit,就是乘4,即左移两位。剩余padding位补0,即如下图所示。
(8bit转10bit有效位深存储结构示意图)
上图格式简称P10,那么P12、P14、P16原理亦如此,所以网上流传的这图是P16格式的存储结构,只是编码器利用了(n)16位深存储的结构 去 保存了(m)10位有效数据而已!还记得上面介绍的,编解码器的兼容性方面:向下兼容特性吗?这里就是利用了该特性。
链接:https://pan.baidu.com/s/1_nmoK2lhHc3wzZx0KYD7sg
提取码:lfeu
大家如果不能理解的话,还可以利用ffmpeg加深一下印象:到这领取10bit格式的解码视频yuv文件 A_720x1280x24_I010_HLG.yuv,利用指令播放yuv文件
ffplay -f rawvideo -video_size 720x1280 -pixel_format ??? A_720x1280x24_I010_HLG.yuv
其中 ??? 可以通过 ffmpeg -pix_fmts 查询当前你的ffmpeg所支持的格式,你觉得是用yuv420p16le 或 yuv420p10le?(le是little ending小端的意思,be是大端的意思,这个yuv文件是小端)大家可以都测试测试。