从SPS帧解析视频分辨率
一 H.264句法
1.1元素分层结构
H.264编码器输出的Bit流中,每个Bit都隶属于某个句法元素。句法元素被组织成有层次的结构,分别描述各个层次的信息。
图1
H.264分层结构由五层组成,分别是序列参数集、图像参数集、片(Slice)、和宏块和子块。参数集是一个独立的数据单位,不依赖于参数集外的其它句法元素。图2描述了参数集与参数集外的句法元素之间的关系。
图2
一个参数集不对应某一个特定的图像或序列,同一序列参数集可以被多个图像参数集引用,同理,同一个图像参数集也可以被多个图像引用。只在编码器认为需要更新参数集的内容时,才会发出新的参数集。
在H.264中,图像以序列为单位进行组织。一个序列的第一个图像叫做IDR图像,IDR图像都是I帧,H.264引入IDR图像为了解码的同步,当解码器解码到IDR图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR图像之后的图像永远不会使用IDR之前的图像的数据来解码。
IDR是I帧,但I帧不一定是IDR。I帧之后的图像有可能会使用I帧之前的图像做运动参考。
1.2描述子
描述子描述从Bit流中取出句法元素的方法。
编号 |
语法 |
说明 |
1 |
ae(e) |
CABAC |
2 |
b(8) |
读进连续的8个Bit |
3 |
ce(v) |
CAVLC |
4 |
f(n) |
读进连续的n个Bit |
5 |
i(n)/i(v) |
读进连续的若干Bit,并把它们解释为有符号整数 |
6 |
me(v) |
映射指数Golomb熵编码 |
7 |
se(v) |
有符号指数Golomb熵编码 |
8 |
te(v) |
截断指数Golomb熵编码 |
9 |
u(n)/u(v) |
读进连续的若干Bit,并把它们解释为无符号整数 |
10 |
ue(v) |
无符号指数Golomb熵编码 |
表1
1.3句法的表示方法
句法元素的名称由小写字母和一系列下划线组成,变量名称是大小写字母组成,中间没有下划线。
二 句法表
定义了H.264的句法,指明在码流中依次出现的句法元素及它们出现的条件、提取描述子等。句法表是分层嵌套的。
句法表中的C字段表示该句法元素的分类,这是为片区服务,分类的具体含义如下表描述。
nal_unit_type |
NAL类型 |
C |
0 |
未使用 |
|
1 |
不分区、非IDR的片 |
2,3,4 |
2 |
片分区A |
2 |
3 |
片分区B |
3 |
4 |
版分区C |
4 |
5 |
IDR图像中的片 |
2,3 |
6 |
补充增强信息单元(SEI) |
5 |
7 |
序列参数集 |
0 |
8 |
图像参数集 |
1 |
9 |
分界符 |
6 |
10 |
序列结束 |
7 |
11 |
码流结束 |
8 |
12 |
填充 |
9 |
13..23 |
保留 |
|
24..31 |
不保留 |
表2
2.1 NAL语法
句法 |
C |
Desc |
nal_nuit(NumBytesInNALunit){/* NumBytesInNALunit为统计出来的数据长度 */ |
||
forbidden_zero_bit /* 等于0 */ |
All |
f(1) |
nal_ref_idc/* 当前NAL的优先级,取值范围0-3 */ |
All |
u(2) |
nal_unit_type /* NAL类型,见表2描述 */ |
All |
u(5) |
NumBytesInRBSP=0 |
||
for(i=1;i<NumBytesInNALunit;i++){ |
||
if(i+2<NumBytesInNALunit && next_bits(24)==0x000003{ |
||
/* 0x000003伪起始码,需要删除0x03这个字节 */ |
||
rbsp_byte[NumBytesInRBSP++] |
All |
b(8) |
rbsp_byte[NumBytesInRBSP++] |
All |
b(8) |
i+=2/* 取出前两个0x00后,跳过0x03 */ |
||
emulation_prevention_three_byte/* equal to 0x03 */ |
All |
f(8) |
}else{ |
||
rbsp_byte[NumBytesInRBSP++] /* 继续读取后面的字节 */ |
All |
b(8) |
} |
||
} |
表3
2.2序列参数集(SPS)
句法 |
C |
Desc |
seq_parameter_set_rbsp(){ |
||
profile_idc/* 指明所用的Profile */ |
0 |
u(8) |
constraint_set0_flag |
0 |
u(1) |
constraint_set1_flag |
0 |
u(1) |
constraint_set1_flag |
0 |
u(1) |
reserved_zero_5bits /* equal to 0 */ |
0 |
u(5) |
level_idc /* 指明所用的Level */ |
0 |
u(8) |
seq_parameter_set_id /* 指明本序列参数集的id号,0-31,被图像集引用,编码需要产生新的序列集时,使用新的id,而不是改变原来参数集的内容 */ |
0 |
ue(v) |
log2_max_frame_num_minus4/* 为读取元素frame_num服务,frame_num标识图像的解码顺序,frame_num的解码函数是ue(v),其中v=log2_max_frame_num_minus4+4,该元素同时指明frame_num的最大值MaxFrameNum=2( log2_max_frame_num_minus4+4)*/ |
0 |
ue(v) |
pic_order_cnt_type /* 指明poc的编码方法,poc标识图像的播放顺序,poc可以由frame_num计算,也可以显示传送。poc共三种计算方式 */ |
0 |
ue(v) |
if(pic_order_cnt_type==0) |
||
log2_max_pic_order_cnt_lsb_minus4 /* 指明变量MaxPicOrderCntLsb的值, MaxPicOrderCntLsb=2(log2_max_pic_order_cnt_lsb_minus4+4) */ |
0 |
ue(v) |
else if(pic_order_cnt_type==1){ |
||
delta_pic_order_always_zero_flag /* 等于1时,元素delta_pic_order_cnt[0]和delta_pic_order_cnt[1]不在片头中出现,并且它们的默认值是0,等于0时,上述两元素出现的片头中 */ |
0 |
u(1) |
offset_for_non_ref_pic /* 用来计算非参考帧或场的poc,[-231,231-1] */ |
0 |
se(v) |
offset_for_top_to_bottom_field/* 计算帧的底场的poc */ |
0 |
se(v) |
num_ref_frames_inpic_order_cnt_cycle /* 用来解码poc,[0.255] */ |
0 |
ue(v) |
for(i=0;i<num_ref_frames_inpic_order_cnt_cycle;i++) |
||
offset_for_ref_frame[i]/* 用来解码poc,对于循环中的每个元素指定一个偏移 */ |
0 |
se(v) |
} |
||
num_ref_frames /* 参考帧队列可达到的最大长度,[0,16] */ |
0 |
ue(v) |
gaps_in_frame_num_value_allowed_flag /* 为1,允许slice header中的frame_num不连续 */ |
0 |
u(1) |
pic_width_inmbs_minus1 /* 本元素加1,指明以宏块为单位的图像宽度 PicWidthInMbs=pic_width_in_mbs_minus1+1 */ |
0 |
ue(v) |
pic_height_in_map_units_minus1 /* 本元素加1,指明以宏块为单位的图像高宽度 PicHeightInMapUnitsMbs=pic_height_in_map_units_minus1+1 */ |
0 |
ue(v) |
frame_mbs_only_flag /* 等于0表示本序列中所有图像均为帧编码;等于1,表示可能是帧,也可能场或帧场自适应,具体编码方式由其它元素决定。结合前一元素:FrameHeightInMbs=(2-frame_mbs_only_flag)*PicHeightInMapUnits */ |
0 |
ue(v) |
if(frame_mbs_only_flag) |
||
mb_adaptiv_frame_field_flag /* 指明本序列是否是帧场自适应模式: frame_mbs_only_flag=1,全部是帧 frame_mbs_only_flag=0, mb_adaptiv_frame_field_flag=0,帧场共存 frame_mbs_only_flag=0, mb_adaptiv_frame_field_flag=1,帧场自适应和场共存*/ |
0 |
u(1) |
direct_8x8_inference_flag /* 用于指明B片的直接和skip模式下的运动矢量的计算方式 */ |
0 |
u(1) |
frame_cropping_flag /* 解码器是否要将图像裁剪后输出,如果是,后面为裁剪的左右上下的宽度 */ |
0 |
u(1) |
if(frame_cropping_flag){ |
||
frame_crop_left_offset |
0 |
ue(1) |
frame_crop_right_offset |
0 |
ue(1) |
frame_crop_top_offset |
0 |
ue(1) |
frame_crop_bottom_offset |
0 |
ue(1) |
} |
||
vui_parameters_present_flag /* 指明vui子结构是否出现在码流中,vui子结构在附录中指明,用于表征视频格式的信息 */ |
0 |
u(1) |
if(vui_parameters_present_flag) |
||
vui_parameters() |
0 |
|
rbsp_trailing_bits() |
0 |
|
} |
表4
//代码//
以下是解析宽高的代码:
001
|
bool UDPReceiver::getResolution( int channel, int &Width, int & Height)
|
002
|
{
|
003
|
BYTE *buf = new BYTE [1024];
|
004
|
int nLen;
|
005
|
AVPacket packet;
|
006
|
BOOL bSpsComplete = FALSE;
|
007
|
// Find SPS
|
008
|
while (!bSpsComplete)
|
009
|
{
|
010
|
int ret = av_read_frame(m_pFormatContext[channel], &packet);
|
011
|
if (packet.flags & AV_PKT_FLAG_KEY)
|
012
|
{
|
013
|
BYTE * p = packet.data;
|
014
|
BYTE last_nal_type = 0;
|
015
|
int last_nal_pos = 0;
|
016
|
for ( int i=0; i<packet.size-5; i++)
|
017
|
{
|
018
|
p = packet.data + i;
|
019
|
if (p[0]==0x00&&p[1]==0x00&&p[2]==0x00&&p[3]==0x01)
|
020
|
{
|
021
|
if (last_nal_type == 0x67)
|
022
|
{
|
023
|
nLen = i-last_nal_pos;
|
024
|
memcpy (buf, packet.data+last_nal_pos, nLen);
|
025
|
bSpsComplete = TRUE;
|
026
|
}
|
027
|
last_nal_type = p[4];
|
028
|
last_nal_pos = i;
|
029
|
if (bSpsComplete)
|
030
|
{
|
031
|
break ;
|
032
|
}
|
033
|
}
|
034
|
}
|
035
|
if (last_nal_type == 0x67 && bSpsComplete == FALSE)
|
036
|
{
|
037
|
nLen = packet.size - last_nal_pos;
|
038
|
memcpy (buf, packet.data+last_nal_pos, nLen);
|
039
|
bSpsComplete = TRUE;
|
040
|
}
|
041
|
}
|
042
|
}
|
043
|
// Analyze SPS to find width and height
|
044
|
UINT StartBit=0;
|
045
|
buf = buf + 4;
|
046
|
int forbidden_zero_bit=u(1,buf,StartBit);
|
047
|
int nal_ref_idc=u(2,buf,StartBit);
|
048
|
int nal_unit_type=u(5,buf,StartBit);
|
049
|
if (nal_unit_type==7)
|
050
|
{
|
051
|
int profile_idc=u(8,buf,StartBit);
|
052
|
int constraint_set0_flag=u(1,buf,StartBit); //(buf[1] & 0x80)>>7;
|
053
|
int constraint_set1_flag=u(1,buf,StartBit); //(buf[1] & 0x40)>>6;
|
054
|
int constraint_set2_flag=u(1,buf,StartBit); //(buf[1] & 0x20)>>5;
|
055
|
int constraint_set3_flag=u(1,buf,StartBit); //(buf[1] & 0x10)>>4;
|
056
|
int reserved_zero_4bits=u(4,buf,StartBit);
|
057
|
int level_idc=u(8,buf,StartBit);
|
058
|
|
059
|
int seq_parameter_set_id=Ue(buf,nLen,StartBit);
|
060
|
|
061
|
if ( profile_idc == 100 || profile_idc == 110 ||
|
062
|
profile_idc == 122 || profile_idc == 144 )
|
063
|
{
|
064
|
int chroma_format_idc=Ue(buf,nLen,StartBit);
|
065
|
if ( chroma_format_idc == 3 )
|
066
|
int residual_colour_transform_flag=u(1,buf,StartBit);
|
067
|
int bit_depth_luma_minus8=Ue(buf,nLen,StartBit);
|
068
|
int bit_depth_chroma_minus8=Ue(buf,nLen,StartBit);
|
069
|
int qpprime_y_zero_transform_bypass_flag=u(1,buf,StartBit);
|
070
|
int seq_scaling_matrix_present_flag=u(1,buf,StartBit);
|
071
|
|
072
|
int seq_scaling_list_present_flag[8];
|
073
|
if ( seq_scaling_matrix_present_flag )
|
074
|
{
|
075
|
for ( int i = 0; i < 8; i++ ) {
|
076
|
seq_scaling_list_present_flag[i]=u(1,buf,StartBit);
|
077
|
}
|
078
|
}
|
079
|
}
|
080
|
int log2_max_frame_num_minus4=Ue(buf,nLen,StartBit);
|
081
|
int pic_order_cnt_type=Ue(buf,nLen,StartBit);
|
082
|
if ( pic_order_cnt_type == 0 )
|
083
|
int log2_max_pic_order_cnt_lsb_minus4=Ue(buf,nLen,StartBit);
|
084
|
else if ( pic_order_cnt_type == 1 )
|
085
|
{
|
086
|
int delta_pic_order_always_zero_flag=u(1,buf,StartBit);
|
087
|
int offset_for_non_ref_pic=Se(buf,nLen,StartBit);
|
088
|
int offset_for_top_to_bottom_field=Se(buf,nLen,StartBit);
|
089
|
int num_ref_frames_in_pic_order_cnt_cycle=Ue(buf,nLen,StartBit);
|
090
|
|
091
|
int *offset_for_ref_frame= new int [num_ref_frames_in_pic_order_cnt_cycle];
|
092
|
for ( int i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ )
|
093
|
offset_for_ref_frame[i]=Se(buf,nLen,StartBit);
|
094
|
delete [] offset_for_ref_frame;
|
095
|
}
|
096
|
int num_ref_frames=Ue(buf,nLen,StartBit);
|
097
|
int gaps_in_frame_num_value_allowed_flag=u(1,buf,StartBit);
|
098
|
int pic_width_in_mbs_minus1=Ue(buf,nLen,StartBit);
|
099
|
int pic_height_in_map_units_minus1=Ue(buf,nLen,StartBit);
|
100
|
|
101
|
Width=(pic_width_in_mbs_minus1+1)*16;
|
102
|
Height=(pic_height_in_map_units_minus1+1)*16;
|
103
|
|
104
|
return true ;
|
105
|
}
|
106
|
else
|
107
|
{
|
108
|
return false ;
|
109
|
}
|
110
|
}
|
111
|
|
112
|
// Ue find the num of zeros and get (num+1) bits from the first 1, and
|
113
|
// change it to decimal
|
114
|
// e.g. 00110 -> return 6(110)
|
115
|
UINT UDPReceiver::Ue( BYTE *pBuff, UINT nLen, UINT &nStartBit)
|
116
|
{
|
117
|
//计算0bit的个数
|
118
|
UINT nZeroNum = 0;
|
119
|
while (nStartBit < nLen * 8)
|
120
|
{
|
121
|
if (pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8))) //&:按位与,%取余
|
122
|
{
|
123
|
break ;
|
124
|
}
|
125
|
nZeroNum++;
|
126
|
nStartBit++;
|
127
|
}
|
128
|
nStartBit ++;
|
129
|
|
130
|
|
131
|
//计算结果
|
132
|
DWORD dwRet = 0;
|
133
|
for ( UINT i=0; i<nZeroNum; i++)
|
134
|
{
|
135
|
dwRet <<= 1;
|
136
|
if (pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8)))
|
137
|
{
|
138
|
dwRet += 1;
|
139
|
}
|
140
|
nStartBit++;
|
141
|
}
|
142
|
return (1 << nZeroNum) - 1 + dwRet;
|
143
|
}
|
144
|
|
145
|
int UDPReceiver::Se( BYTE *pBuff, UINT nLen, UINT &nStartBit)
|
146
|
{
|
147
|
int UeVal=Ue(pBuff,nLen,nStartBit);
|
148
|
double k=UeVal;
|
149
|
int nValue=std:: ceil (k/2); //ceil函数:ceil函数的作用是求不小于给定实数的最小整数。ceil(2)=ceil(1.2)=cei(1.5)=2.00
|
150
|
if (UeVal % 2==0)
|
151
|
nValue=-nValue;
|
152
|
return nValue;
|
153
|
}
|
154
|
|
155
|
// u Just returns the BitCount bits of buf and change it to decimal.
|
156
|
// e.g. BitCount = 4, buf = 01011100, then return 5(0101)
|
157
|
DWORD UDPReceiver::u( UINT BitCount, BYTE * buf, UINT &nStartBit)
|
158
|
{
|
159
|
DWORD dwRet = 0;
|
160
|
for ( UINT i=0; i<BitCount; i++)
|
161
|
{
|
162
|
dwRet <<= 1;
|
163
|
if (buf[nStartBit / 8] & (0x80 >> (nStartBit % 8)))
|
164
|
{
|
165
|
dwRet += 1;
|
166
|
}
|
167
|
nStartBit++;
|
168
|
}
|
169
|
return dwRet;
|
170
|
}
|
从SPS帧解析视频分辨率相关推荐
- 解析视频分辨率和时长
解析视频分辨率和时长 下载工具 使用的工具是:FFmpeg 分为win 和 linux 需要提前安装 FFmpeg windows版本 linux版本 有道笔记 windows 安装 将下载的进行解压 ...
- 【FFmpeg】java实现利用ffmpeg视频上传转码同时截取一帧保存为同名图片,并获取视频分辨率
〇.前情提要 完成了视频上传,接下来是给上传的视频保存一张同名的jpg文件在同样文件夹中,并且获取到视频的分辨率. macOS Catalina 10.15.1 ffmpeg version 4.3. ...
- H264及H265 I帧解析 [转]
H264及H265 I帧解析 [转] h264 I帧的判断 参考原文:https://blog.csdn.net/dxpqxb/article/details/13289205?utm_source= ...
- 解析视频编码原理——从孙艺珍的电影说起(一)
更多博文,请看音视频系统学习的浪漫马车之总目录 视频理论基础: 视频基础知识扫盲 音视频开发基础知识之YUV颜色编码 解析视频编码原理--从孙艺珍的电影说起(一) 解析视频编码原理--从孙艺珍的电影说 ...
- 视频分辨率无损放大软件 Topaz Video Enhance AI 2.3.0
视频分辨率无损放大软件 Topaz Video Enhance AI 2.3.0 Topaz Video Enhance AI是一款非常好用的视频分辨率放大软件,用户可以通过这款软件将视频的分辨率进行 ...
- 视频图像处理基础知识4(视频分辨率参考 行频 隔行扫描 逐行扫描)【转】
转自:http://blog.csdn.net/Times_poem/article/details/51470065 版权声明:本文为博主原创文章,未经博主允许不得转载. 需求说明:视频处理基本知识 ...
- ffmpeg命令行录制一个具有非IDR性质的I帧的视频
之前在代码上写过几篇ffmpeg桌面录制的博客,用ffprobe查看里面的帧时,全部都是IDR这种I帧,没有普通的I帧,如下所示: <frame media_type="video&q ...
- php获得视频分辨率,php+ffmpeg 获取视频相关信息(缩略图、视频分辨率)
ffmpeg是一款开源.跨平台的视频处理程序,可用在Windows.mac.linux等平台,可以方便的运用多种语言脚本来调用其执行视频的操作. 下面介绍使用ffmpeg获取视频首帧的方法. & ...
- 视频图像处理基础知识4(视频分辨率参考 行频 隔行扫描 逐行扫描)
需求说明:视频处理基本知识 第一部分:视频分辨率参数 第二部分:分辨率.行频.场频 第一部分:视频分辨率参数 1080p的画面分辨率为1920×1080 [视频分辨率参考]共 ...
最新文章
- ubuntukylin-14.04.2-desktop-amd64中python2.7版本安装机器学习库
- 2556. [NOIP2016]玩具谜题
- python判断字符大小写转换_Python 字符串大小写转换的简单实例
- signature=e5535ff98b93aa63c455611822dc57c2,高校新生预激综合征6例报告
- 乱世寻龙java_乱世王者寻龙季怎么玩?乱世王者寻龙季玩法技巧一览
- HihoCoder1449 后缀自动机三·重复旋律6
- JavaEE学习02--HTTP协议
- php导出word乱码,php导出的word会乱码吗
- 现金支票打印模板excel_Excel的正确使用技巧-Excel的提速大法
- 高仿QQ空间广告位 ——— 一个位置来回切换两张广告图
- 秦曾昌人工智能课程---7、决策树集成学习Tree Ensembles
- Visual Studio 2017安装使用方法
- 学习reflux的总结
- 爬取豆瓣前250电影数据
- 安卓系统使用外接USB声卡(XMOS)进行单声道音频数据采集
- usb_cam的ROS2甜点
- Netkiller Java 手札之前言
- CRM系统优化企业内控建设
- 最近比较火的圣诞树HTML代码
- Linux教程之查看文件(cat,head,tail,less,more)
热门文章
- 【UVA 12657】移动盒子 Boxes in a Line
- 一共81个,开源大数据处理工具汇总(下)转
- ef power tools mysql_Entity Framework Code First ---EF Power Tool 和MySql一起使用遇到的问题...
- python实现kmeans算法对图片的聚类分割
- 母牛的故事 1243ACM实验题
- Android系统安全 — 3.1-展锐平台secureboot安全启动流程和使用
- matlab实现牛顿下山法
- nodemon:运行提示错误:无法加载文件 xxxx
- html5中奖名单特效,jQuery基于json动态随机获取中奖名单抽奖代码
- 我的淘宝新店的辛酸与感恩