音视频入门基础:H.264专题(11)——计算视频分辨率的公式

发布于:2024-07-11 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、引言

通过FFmpeg命令可以获取到H.264裸流文件的视频分辨率:

在vlc中也可以获取到视频分辨率(vlc底层也使用了FFmpeg进行解码):

所以FFmpeg和vlc是怎样获取到H.264编码的视频的分辨率呢?它们其实是通过SPS中的属性(pic_width_in_mbs_minus1、pic_height_in_map_units_minus1 、frame_mbs_only_flag、frame_cropping_flag、chroma_format_idc、SubWidthC、SubHeightC、frame_crop_left_offset、frame_crop_right_offset、frame_crop_top_offset、frame_crop_bottom_offset)解析出这个视频的宽高的。

网上广泛流传的计算视频宽高的公式为:

width = (pic_width_in_mbs_minus1 + 1) * 16;
height = (pic_height_in_map_units_minus1 + 1) * 16;

这是不严谨的,因为某些视频的宽高不是16的倍数。而且还得考虑特殊情况,也就是场编码的情况。

二、根据H.264官方文档得到真正计算视频分辨率的公式

上述属性在H.264官方文档《T-REC-H.264-202108-I!!PDF-E.pdf》第44页到45页描述:

根据H.264官方文档《T-REC-H.264-202108-I!!PDF-E.pdf》第78页:

pic_width_in_mbs_minus1加1是指以宏块为单位的每个解码图像的宽度。

以宏块为单元的图像宽度变量PicWidthInMbs=pic_width_in_mbs_minus1 + 1。

亮度分量的图像宽度变量PicWidthInSamplesL=PicWidthInMbs * 16

pic_height_in_map_units_minus1加1表示以条带组映射为单位的一个解码帧或场的高度。PicHeightInMapUnits=pic_height_in_map_units_minus1+1。

PicSizeInMapUnits=PicWidthInMbs * PicHeightInMapUnits

FrameHeightInMbs = ( 2 – frame_mbs_only_flag ) * PicHeightInMapUnits

根据第22页:

在单色采样中只有一个样点阵列,名义上当做亮度阵列。

在4:2:0样点中,两个色度阵列的高度和宽度均为亮度阵列的一半。

在4:2:2样点中,两个色度阵列的高度等于亮度阵列的高度,宽度为亮度阵列的一半。

在4:2:4样点中,两个色度阵列的高度和宽度与亮度阵列的相等。

SPS属性中的chroma_format_idc = 0,并且separate_colour_plane_flag = 0时,色度格式为单色,此时SubWidthC和SubHeightC没有意义。

chroma_format_idc = 1,并且separate_colour_plane_flag = 0时,色度格式为YUV420,此时SubWidthC为2,SubHeightC为2。

chroma_format_idc = 2,并且separate_colour_plane_flag = 0时,色度格式为YUV422,此时SubWidthC为2,SubHeightC为1。

chroma_format_idc = 3,并且separate_colour_plane_flag = 0时,色度格式为YUV444,此时SubWidthC为1,SubHeightC为1。

chroma_format_idc = 3,并且separate_colour_plane_flag = 1时,色度格式为YUV444,此时SubWidthC和SubHeightC没有意义。

根据第79页:

如果chroma_format_idc等于0,CropUnitX和CropUnitY按下列方式计算:

CropUnitX = 1

CropUnitY = 2- frame_mbs_only_flag

否则(chroma_format_idc等于1、2或3),CropUnitX和CropUnitY按下列公式计算:

CropUnitX = SubWidthC

CropUnitY = SubHeightC * (2 - frame_mbs_only_flag)

当frame_cropping_flag等于0时,frame_crop_left_offset、frame_crop_right_offset、frame_crop_top_offset和frame_crop_bottom_offset的值应等于0。

根据第385页:

输出裁剪后的帧区域的宽度CroppedWidth = PicWidthInSamplesL − CropUnitX * ( frame_crop_left_offset + frame_crop_right_offset )

输出裁剪后的帧区域的高度 CroppedHeight = 16 * FrameHeightInMbs − CropUnitY * ( frame_crop_top_offset + frame_crop_bottom_offset )
其中CroppedWidth和CroppedHeight可以认为是视频的宽高。

三、得到最终计算视频分辨率的公式

综上所述,计算H.264编码的视频分辨率的公式为:
int SubWidthC;
int SubHeightC;

if (sps->chroma_format_idc == 0 && sps->separate_colour_plane_flag == 0) { //monochrome
    SubWidthC = SubHeightC = 0;
}
else if (sps->chroma_format_idc == 1 && sps->separate_colour_plane_flag == 0) { //4:2:0 
    SubWidthC = SubHeightC = 2;
}
else if (sps->chroma_format_idc == 2 && sps->separate_colour_plane_flag == 0) { //4:2:2 
    SubWidthC = 2;
    SubHeightC = 1;
}
else if (sps->chroma_format_idc == 3) { //4:4:4
    if (sps->separate_colour_plane_flag == 0) {
        SubWidthC = SubHeightC = 1;
    }
    else if (sps->separate_colour_plane_flag == 1) {
        SubWidthC = SubHeightC = 0;
    }
}

int PicWidthInMbs = sps->pic_width_in_mbs_minus1 + 1;

int PicHeightInMapUnits = sps->pic_height_in_map_units_minus1 + 1;
int FrameHeightInMbs = (2 - sps->frame_mbs_only_flag) * PicHeightInMapUnits;

int crop_left = 0;
int crop_right = 0;
int crop_top = 0;
int crop_bottom = 0;

if (sps->frame_cropping_flag) {
    crop_left = sps->frame_crop_left_offset;
    crop_right = sps->frame_crop_right_offset;
    crop_top = sps->frame_crop_top_offset;
    crop_bottom = sps->frame_crop_bottom_offset;
}

int width = PicWidthInMbs * 16 - SubWidthC * (crop_left + crop_right);
int height = FrameHeightInMbs * 16 - SubHeightC * (2 - sps->frame_mbs_only_flag) * (crop_top + crop_bottom);

四、计算视频分辨率的例子

假如某个H.264编码的视频的SPS中的chroma_format_idc值为1,pic_width_in_mbs_minus1值为79,pic_height_in_map_units_minus1值为44,frame_mbs_only_flag值为1,frame_cropping_flag值为0,如下所示:

由于chroma_format_idc等于3的情况下,separate_colour_plane_flag才有意义。上述chroma_format_idc的值为1,所以separate_colour_plane_flag值为默认的0:
所以根据最终计算公式:
SubWidthC = SubHeightC = 2,

int PicWidthInMbs
= sps->pic_width_in_mbs_minus1 + 1
= 79 + 1
= 80

int PicHeightInMapUnits
= sps->pic_height_in_map_units_minus1 + 1
= 45

int FrameHeightInMbs
= (2 - sps->frame_mbs_only_flag) * PicHeightInMapUnits
= (2 - 1) * 45
= 45

int width = PicWidthInMbs * 16 - SubWidthC * (crop_left + crop_right)
= 80 * 16 - 2* (0 + 0)
= 1280

int height = FrameHeightInMbs * 16 - SubHeightC * (2 - sps->frame_mbs_only_flag) * (crop_top + crop_bottom)
= 45 * 16 - 2 * (2 - 1) * (0 + 0)
= 720

所以该视频的分辨率为1280 * 720。

五、参考文章

H264 getting frame height and width from sequence parameter set (SPS) NAL unit