H264解析SPS获取宽高等信息

在做音视频开发的时候,存在不解码视频帧的前提下需要获取视频宽高、帧率等信息,而H.264中的SPS数据可为我们提供这些相关的信息。在此之前,我们需要对一些协议和算法有一定的初步了解,后文中有完整的代码展示。

H.264协议

我们在此不讲解H.264协议,但是我们需要了解NAL单元语法(NAL unit syntax)、序列参数集数据语法(Sequence parameter set data syntax)、视频可用参数语法(VUI parameters syntax)。具体可去ITU-T官网下载H.264文档,我查看的是T-REC-H.264-201704-I!!PDF-E.pdf。

Sequence parameter set data syntax

VUI parameters syntax


第一列:相关参数信息名称;

第二列:规定条带数据可以至多划分为三种条带数据类别(这个可以不用理解);

第三列中的u(n)表示:使用n位无符号整数表示,由n位bit换算得到,即从左到右读取n个bit位所表示的值;ue(v)表示:无符号指数哥伦布编码值;ue(v)表示:有符号指数哥伦布编码值。

u(n)代码实现:


   
   
  1. static UINT u(sps_bit_stream *bs, BYTE bitCount)
  2. {
  3. UINT val = 0;
  4. for (BYTE i= 0; i<bitCount; i++) {
  5. val <<= 1;
  6. if (eof(bs)) {
  7. val = 0;
  8. break;
  9. } else if (bs->data[bs->index / 8] & ( 0x80 >> (bs->index % 8))) { //计算index所在的位是否为1
  10. val |= 1;
  11. }
  12. bs->index++;
  13. }
  14. return val;
  15. }

指数哥伦布编码

Golomb编码是一种无损的数据压缩方法,由数学家Solomon W.Golomb在1960年代发明。Golomb编码只能对非负整数进行编码,符号表中的符号出现的概率符合几何分布(Geometric Distribution)时,使用Golomb编码可以取得最优效果,也就是说Golomb编码比较适合小的数字比大的数字出现概率比较高的编码。它使用较短的码长编码较小的数字,较长的码长编码较大的数字。在此,我们只需要了解:无符号指数哥伦布编码ue(v)、有符号指数哥伦布编码se(v)。

无符号指数哥伦布编码(UE)

哥伦布编码的码字code_word由三部分组成:code_word = [M个0] + [1] + [Info]
其中,Info是一个携带信息的M位数据,每个哥伦布码的长度为(2M+1)位,每个码字都可由code_num产生。
根据码字code_word解码出code_num值的过程如下:
1. 首先读入M位以"1"为结尾的0;
2. 根据得到的M,读入接下来的M位Info数据;

3. 根据这个公式得到计算结果code_num = Info – 1 + 2M

代码实现:


   
   
  1. static UINT ue(sps_bit_stream *bs)
  2. {
  3. UINT zeroNum = 0;
  4. while (u(bs, 1) == 0 && !eof(bs) && zeroNum < 32) {
  5. zeroNum ++;
  6. }
  7. return (UINT)(( 1 << zeroNum) - 1 + u(bs, zeroNum));
  8. }

有符号指数哥伦布编码(SE)

有符号的指数哥伦布编码值是通过无符号的指数哥伦布编码的值通过换算得到的,其换算关系为:n = (-1)^(k+1) * ceil(k/2)。

代码实现:


   
   
  1. INT se(sps_bit_stream *bs)
  2. {
  3. INT ueVal = (INT)ue(bs);
  4. double k = ueVal;
  5. INT seVal = (INT) ceil(k / 2); //ceil:返回大于或者等于指定表达式的最小整数
  6. if (ueVal % 2 == 0) { //偶数取反,即(-1)^(k+1)
  7. seVal = -seVal;
  8. }
  9. return seVal;
  10. }

SPS参数分析

视频宽高(Width、Height)获取

从H.264协议文档中可以看出视频宽高涉及到的参数有:

pic_width_in_mbs_minus1加1指定以宏块(16*16)为单位的每个解码图像的宽度,即PicWidthInSamplesL = (pic_width_in_mbs_minus1 + 1) * 16 ;


pic_height_in_map_units_minus1:加1指定解码帧或场中的一个切片组的高度,即PicSizeInMapUnits = PicWidthInMbs * (pic_height_in_map_units_minus1 + 1) ;


frame_mbs_only_flag:等于0指明了视频序列的编码图象可能是编码场或编码帧,等于1指明了每个编码视频序列的编码图像都是只含帧宏块的编码帧,即FrameHeightInMbs = ( 2 − frame_mbs_only_flag ) * PicHeightInMapUnits ;

frame_cropping_flag:等于0表明不存在帧剪切偏移量,等于1表明在sps中下一个使用的帧剪切偏移量参数,即需要使用到frame_crop_left_offset、frame_crop_right_offset、frame_crop_top_offset、frame_crop_bottom_offset四个参数;

frame_crop_left_offset、frame_crop_right_offset、frame_crop_top_offset、frame_crop_bottom_offset:左右上下帧裁剪偏移量,需要根据chroma format计算实际的视频宽高;


chroma_format_idc:亮度取样对应的色度取样值,包括0~3,不存在时默认为1(即YUV420);


根据上述参数的定义说明,计算视频分辨率存在多种情况:1、需要判断是否包含宏片的编码帧或场;2、需要判断视频帧是否被裁剪;3、需要判断采样类型。因此代码大致如下:


   
   
  1. UINT pic_width_in_mbs_minus1 = ue(&bs); //第36位开始
  2. UINT pic_height_in_map_units_minus1 = ue(&bs); //47
  3. UINT frame_mbs_only_flag = u(&bs, 1);
  4. info->width = (INT)(pic_width_in_mbs_minus1 + 1) * 16;
  5. info->height = (INT)( 2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * 16;
  6. if (!frame_mbs_only_flag) {
  7. u(&bs, 1); //mb_adaptive_frame_field_flag
  8. }
  9. u(&bs, 1); //direct_8x8_inference_flag
  10. UINT frame_cropping_flag = u(&bs, 1);
  11. if (frame_cropping_flag) {
  12. UINT frame_crop_left_offset = ue(&bs);
  13. UINT frame_crop_right_offset = ue(&bs);
  14. UINT frame_crop_top_offset = ue(&bs);
  15. UINT frame_crop_bottom_offset= ue(&bs);
  16. //See 6.2 Source, decoded, and output picture formats
  17. INT crop_unit_x = 1;
  18. INT crop_unit_y = 2 - frame_mbs_only_flag; //monochrome or 4:4:4
  19. if (chroma_format_idc == 1) { //4:2:0
  20. crop_unit_x = 2;
  21. crop_unit_y = 2 * ( 2 - frame_mbs_only_flag);
  22. } else if (chroma_format_idc == 2) { //4:2:2
  23. crop_unit_x = 2;
  24. crop_unit_y = 2 - frame_mbs_only_flag;
  25. }
  26. info->width -= crop_unit_x * (frame_crop_left_offset + frame_crop_right_offset);
  27. info->height -= crop_unit_y * (frame_crop_top_offset + frame_crop_bottom_offset);
  28. }

视频帧率(FPS)获取

视频帧率信息在SPS的VUI parameters syntax中,需要根据time_scale、fixed_frame_rate_flag计算得到:fps = time_scale / num_units_in_tick。但是需要判断参数timing_info_present_flag是否存在,若不存在表示FPS在信息流中无法获取。同时还存在另外一种情况:fixed_frame_rate_flag为1时,两个连续图像的HDR输出时间频率为单位,获取的fps是实际的2倍。


   
   
  1. UINT timing_info_present_flag = u(bs, 1);
  2. if (timing_info_present_flag) {
  3.     UINT num_units_in_tick = u(bs, 32);
  4.     UINT time_scale = u(bs, 32);
  5.     UINT fixed_frame_rate_flag = u(bs, 1);
  6.     
  7.     info->fps = (UINT)(( float)time_scale / ( float)num_units_in_tick);
  8.     if (fixed_frame_rate_flag) {
  9.         info->fps = info->fps/ 2;
  10.     }
  11. }

SPS解析代码

H264ParseSPS.h

//
//  H264ParseSPS.h
//
//  Created by lzj<lizhijian_21@163.com> on 2018/7/6.
//  Copyright © 2018年 LZJ. All rights reserved.
//

#ifndef H264ParseSPS_h
#define H264ParseSPS_h
#ifdef __cplusplus
extern “C”
{
#endif
#include <stdio.h>

typedef struct
{

unsigned int profile_idc;
unsigned int level_idc;

<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> width;
<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> height;
<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> fps;       <span class="hljs-comment">//SPS中可能不包含FPS信息</span>

} sps_info_struct;

/**
解析SPS数据信息

@param data SPS数据内容,需要Nal类型为0x7数据的开始(比如:67 42 00 28 ab 40 22 01 e3 cb cd c0 80 80 a9 02)
@param dataSize SPS数据的长度
@param info SPS解析之后的信息数据结构体
@return success:1,fail:0

*/
int h264_parse_sps(const unsigned char *data, unsigned int dataSize, sps_info_struct *info);

#ifdef __cplusplus
}
#endif
#endif /* H264ParseSPS_h */

H264ParseSPS.c


 
 
  1. //
  2. // H264ParseSPS.c
  3. //
  4. // Created by lzj<lizhijian_21@163.com> on 2018/7/6.
  5. // Copyright © 2018年 LZJ. All rights reserved.
  6. //
  7. // See https://www.itu.int/rec/T-REC-H.264-201610-S
  8. //
  9. #include “H264ParseSPS.h”
  10. #include <string.h>
  11. #include <stdlib.h>
  12. #include <math.h>
  13. typedef unsigned char BYTE;
  14. typedef int INT;
  15. typedef unsigned int UINT;
  16. typedef struct
  17. {
  18. const BYTE *data; //sps数据
  19. UINT size; //sps数据大小
  20. UINT index; //当前计算位所在的位置标记
  21. } sps_bit_stream;
  22. /
  23. 移除H264的NAL防竞争码(0x03)
  24. @param data sps数据
  25. @param dataSize sps数据大小
  26. */
  27. static void del_emulation_prevention(BYTE *data, UINT *dataSize)
  28. {
  29. UINT dataSizeTemp = *dataSize;
  30. for (UINT i= 0, j= 0; i<(dataSizeTemp -2); i++) {
  31. int val = (data[i]^ 0x0) + (data[i+ 1]^ 0x0) + (data[i+ 2]^ 0x3); //检测是否是竞争码
  32. if (val == 0) {
  33. for (j=i+ 2; j<dataSizeTemp -1; j++) { //移除竞争码
  34. data[j] = data[j+ 1];
  35. }
  36. (*dataSize)–; //data size 减1
  37. }
  38. }
  39. }
  40. static void sps_bs_init(sps_bit_stream *bs, const BYTE *data, UINT size)
  41. {
  42. if (bs) {
  43. bs->data = data;
  44. bs->size = size;
  45. bs->index = 0;
  46. }
  47. }
  48. /
  49. 是否已经到数据流最后
  50. @param bs sps_bit_stream数据
  51. @return 1:yes,0:no
  52. */
  53. static INT eof(sps_bit_stream *bs)
  54. {
  55. return (bs->index >= bs->size * 8); //位偏移已经超出数据
  56. }
  57. /
  58. 读取从起始位开始的BitCount个位所表示的值
  59. @param bs sps_bit_stream数据
  60. @param bitCount bit位个数(从低到高)
  61. @return value
  62. */
  63. static UINT u(sps_bit_stream *bs, BYTE bitCount)
  64. {
  65. UINT val = 0;
  66. for (BYTE i= 0; i<bitCount; i++) {
  67. val <<= 1;
  68. if (eof(bs)) {
  69. val = 0;
  70. break;
  71. } else if (bs->data[bs->index / 8] & ( 0x80 >> (bs->index % 8))) { //计算index所在的位是否为1
  72. val |= 1;
  73. }
  74. bs->index++; //递增当前起始位(表示该位已经被计算,在后面的计算过程中不需要再次去计算所在的起始位索引,缺点:后面每个bit位都需要去位移)
  75. }
  76. return val;
  77. }
  78. /
  79. 读取无符号哥伦布编码值(UE)
  80. #2^LeadingZeroBits - 1 + (xxx)
  81. @param bs sps_bit_stream数据
  82. @return value
  83. */
  84. static UINT ue(sps_bit_stream *bs)
  85. {
  86. UINT zeroNum = 0;
  87. while (u(bs, 1) == 0 && !eof(bs) && zeroNum < 32) {
  88. zeroNum ++;
  89. }
  90. return (UINT)(( 1 << zeroNum) - 1 + u(bs, zeroNum));
  91. }
  92. /
  93. 读取有符号哥伦布编码值(SE)
  94. #(-1)^(k+1) * Ceil(k/2)
  95. @param bs sps_bit_stream数据
  96. @return value
  97. */
  98. INT se(sps_bit_stream *bs)
  99. {
  100. INT ueVal = (INT)ue(bs);
  101. double k = ueVal;
  102. INT seVal = (INT) ceil(k / 2); //ceil:返回大于或者等于指定表达式的最小整数
  103. if (ueVal % 2 == 0) { //偶数取反,即(-1)^(k+1)
  104. seVal = -seVal;
  105. }
  106. return seVal;
  107. }
  108. /
  109. 视频可用性信息(Video usability information)解析
  110. @param bs sps_bit_stream数据
  111. @param info sps解析之后的信息数据及结构体
  112. @see E.1.1 VUI parameters syntax
  113. */
  114. void vui_para_parse(sps_bit_stream *bs, sps_info_struct *info)
  115. {
  116. UINT aspect_ratio_info_present_flag = u(bs, 1);
  117. if (aspect_ratio_info_present_flag) {
  118. UINT aspect_ratio_idc = u(bs, 8);
  119. if (aspect_ratio_idc == 255) { //Extended_SAR
  120. u(bs, 16); //sar_width
  121. u(bs, 16); //sar_height
  122. }
  123. }
  124. UINT overscan_info_present_flag = u(bs, 1);
  125. if (overscan_info_present_flag) {
  126. u(bs, 1); //overscan_appropriate_flag
  127. }
  128. UINT video_signal_type_present_flag = u(bs, 1);
  129. if (video_signal_type_present_flag) {
  130. u(bs, 3); //video_format
  131. u(bs, 1); //video_full_range_flag
  132. UINT colour_description_present_flag = u(bs, 1);
  133. if (colour_description_present_flag) {
  134. u(bs, 8); //colour_primaries
  135. u(bs, 8); //transfer_characteristics
  136. u(bs, 8); //matrix_coefficients
  137. }
  138. }
  139. UINT chroma_loc_info_present_flag = u(bs, 1);
  140. if (chroma_loc_info_present_flag) {
  141. ue(bs); //chroma_sample_loc_type_top_field
  142. ue(bs); //chroma_sample_loc_type_bottom_field
  143. }
  144. UINT timing_info_present_flag = u(bs, 1);
  145. if (timing_info_present_flag) {
  146. UINT num_units_in_tick = u(bs, 32);
  147. UINT time_scale = u(bs, 32);
  148. UINT fixed_frame_rate_flag = u(bs, 1);
  149. info->fps = (UINT)(( float)time_scale / ( float)num_units_in_tick);
  150. if (fixed_frame_rate_flag) {
  151. info->fps = info->fps/ 2;
  152. }
  153. }
  154. UINT nal_hrd_parameters_present_flag = u(bs, 1);
  155. if (nal_hrd_parameters_present_flag) {
  156. //hrd_parameters() //see E.1.2 HRD parameters syntax
  157. }
  158. //后面代码需要hrd_parameters()函数接口实现才有用
  159. UINT vcl_hrd_parameters_present_flag = u(bs, 1);
  160. if (vcl_hrd_parameters_present_flag) {
  161. //hrd_parameters()
  162. }
  163. if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
  164. u(bs, 1); //low_delay_hrd_flag
  165. }
  166. u(bs, 1); //pic_struct_present_flag
  167. UINT bitstream_restriction_flag = u(bs, 1);
  168. if (bitstream_restriction_flag) {
  169. u(bs, 1); //motion_vectors_over_pic_boundaries_flag
  170. ue(bs); //max_bytes_per_pic_denom
  171. ue(bs); //max_bits_per_mb_denom
  172. ue(bs); //log2_max_mv_length_horizontal
  173. ue(bs); //log2_max_mv_length_vertical
  174. ue(bs); //max_num_reorder_frames
  175. ue(bs); //max_dec_frame_buffering
  176. }
  177. }
  178. //See 7.3.1 NAL unit syntax
  179. //See 7.3.2.1.1 Sequence parameter set data syntax
  180. INT h264_parse_sps(const BYTE *data, UINT dataSize, sps_info_struct *info)
  181. {
  182. if (!data || dataSize <= 0 || !info) return 0;
  183. INT ret = 0;
  184. BYTE *dataBuf = malloc(dataSize);
  185. memcpy(dataBuf, data, dataSize); //重新拷贝一份数据,防止移除竞争码时对原数据造成影响
  186. del_emulation_prevention(dataBuf, &dataSize);
  187. sps_bit_stream bs = { 0};
  188. sps_bs_init(&bs, dataBuf, dataSize); //初始化SPS数据流结构体
  189. u(&bs, 1); //forbidden_zero_bit
  190. u(&bs, 2); //nal_ref_idc
  191. UINT nal_unit_type = u(&bs, 5);
  192. if (nal_unit_type == 0x7) { //Nal SPS Flag
  193. info->profile_idc = u(&bs, 8);
  194. u(&bs, 1); //constraint_set0_flag
  195. u(&bs, 1); //constraint_set1_flag
  196. u(&bs, 1); //constraint_set2_flag
  197. u(&bs, 1); //constraint_set3_flag
  198. u(&bs, 1); //constraint_set4_flag
  199. u(&bs, 1); //constraint_set4_flag
  200. u(&bs, 2); //reserved_zero_2bits
  201. info->level_idc = u(&bs, 8);
  202. ue(&bs); //seq_parameter_set_id
  203. UINT chroma_format_idc = 1; //摄像机出图大部分格式是4:2:0
  204. if (info->profile_idc == 100 || info->profile_idc == 110 || info->profile_idc == 122 ||
  205. info->profile_idc == 244 || info->profile_idc == 44 || info->profile_idc == 83 ||
  206. info->profile_idc == 86 || info->profile_idc == 118 || info->profile_idc == 128 ||
  207. info->profile_idc == 138 || info->profile_idc == 139 || info->profile_idc == 134 || info->profile_idc == 135) {
  208. chroma_format_idc = ue(&bs);
  209. if (chroma_format_idc == 3) {
  210. u(&bs, 1); //separate_colour_plane_flag
  211. }
  212. ue(&bs); //bit_depth_luma_minus8
  213. ue(&bs); //bit_depth_chroma_minus8
  214. u(&bs, 1); //qpprime_y_zero_transform_bypass_flag
  215. UINT seq_scaling_matrix_present_flag = u(&bs, 1);
  216. if (seq_scaling_matrix_present_flag) {
  217. UINT seq_scaling_list_present_flag[ 8] = { 0};
  218. for (INT i= 0; i<((chroma_format_idc != 3)? 8: 12); i++) {
  219. seq_scaling_list_present_flag[i] = u(&bs, 1);
  220. if (seq_scaling_list_present_flag[i]) {
  221. if (i < 6) { //scaling_list(ScalingList4x4[i], 16, UseDefaultScalingMatrix4x4Flag[i])
  222. } else { //scaling_list(ScalingList8x8[i − 6], 64, UseDefaultScalingMatrix8x8Flag[i − 6] )
  223. }
  224. }
  225. }
  226. }
  227. }
  228. ue(&bs); //log2_max_frame_num_minus4
  229. UINT pic_order_cnt_type = ue(&bs);
  230. if (pic_order_cnt_type == 0) {
  231. ue(&bs); //log2_max_pic_order_cnt_lsb_minus4
  232. } else if (pic_order_cnt_type == 1) {
  233. u(&bs, 1); //delta_pic_order_always_zero_flag
  234. se(&bs); //offset_for_non_ref_pic
  235. se(&bs); //offset_for_top_to_bottom_field
  236. UINT num_ref_frames_in_pic_order_cnt_cycle = ue(&bs);
  237. INT *offset_for_ref_frame = (INT )malloc((UINT)num_ref_frames_in_pic_order_cnt_cycle * sizeof(INT));
  238. for (INT i = 0; i<num_ref_frames_in_pic_order_cnt_cycle; i++) {
  239. offset_for_ref_frame[i] = se(&bs);
  240. }
  241. free(offset_for_ref_frame);
  242. }
  243. ue(&bs); //max_num_ref_frames
  244. u(&bs, 1); //gaps_in_frame_num_value_allowed_flag
  245. UINT pic_width_in_mbs_minus1 = ue(&bs); //第36位开始
  246. UINT pic_height_in_map_units_minus1 = ue(&bs); //47
  247. UINT frame_mbs_only_flag = u(&bs, 1);
  248. info->width = (INT)(pic_width_in_mbs_minus1 + 1) * 16;
  249. info->height = (INT)( 2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * 16;
  250. if (!frame_mbs_only_flag) {
  251. u(&bs, 1); //mb_adaptive_frame_field_flag
  252. }
  253. u(&bs, 1); //direct_8x8_inference_flag
  254. UINT frame_cropping_flag = u(&bs, 1);
  255. if (frame_cropping_flag) {
  256. UINT frame_crop_left_offset = ue(&bs);
  257. UINT frame_crop_right_offset = ue(&bs);
  258. UINT frame_crop_top_offset = ue(&bs);
  259. UINT frame_crop_bottom_offset= ue(&bs);
  260. //See 6.2 Source, decoded, and output picture formats
  261. INT crop_unit_x = 1;
  262. INT crop_unit_y = 2 - frame_mbs_only_flag; //monochrome or 4:4:4
  263. if (chroma_format_idc == 1) { //4:2:0
  264. crop_unit_x = 2;
  265. crop_unit_y = 2 * ( 2 - frame_mbs_only_flag);
  266. } else if (chroma_format_idc == 2) { //4:2:2
  267. crop_unit_x = 2;
  268. crop_unit_y = 2 - frame_mbs_only_flag;
  269. }
  270. info->width -= crop_unit_x * (frame_crop_left_offset + frame_crop_right_offset);
  271. info->height -= crop_unit_y * (frame_crop_top_offset + frame_crop_bottom_offset);
  272. }
  273. UINT vui_parameters_present_flag = u(&bs, 1);
  274. if (vui_parameters_present_flag) {
  275. vui_para_parse(&bs, info);
  276. }
  277. ret = 1;
  278. }
  279. free(dataBuf);
  280. return ret;
  281. }
代码中部分未用到的参数并没有获取并赋值,比如:u(&bs, 1);      //forbidden_zero_bit。如需获取相应在这里插入代码片的参数,只需将注释后面对应的变量值进行赋值即可,如:UINT forbidden_zero_bit = u(&bs, 1);

h264_parse_sps接口中sps数据需要第一个字节以Nal类型为0x7作为开始,比如:67 42 00 28 ab 40 22 01 e3 cb cd c0 80 80 a9 02,解析得到的宽高为10801920,fps为0。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值