H.264有两种封装模式:

(1)annexb模式:传统模式,使用start code来分隔NAL, SPS和PPS是在ES流的头部;

(2)mp4模式:没有start code,使用NALU长度(固定字节,通常为4个字节)来分隔NAL。AVCodecContext的extradata内部保存着分隔的字节数,SPS和PPS;

1. 找到SPS

视频的宽高保存在SPS中。那么提取宽高首先要找到SPS。annexb模式直接读取视频数据,根据NAL type找到SPS即可。mp4模式应该从extradata中找到SPS。

mp4格式的extradata格式如下图:

FFMpeg中解析extradata的函数是ff_h264_decode_extradata()。注意第5字节的最后2位,表示的就是NAL size的字节数-1。在AVCC格式中,每个NAL前面都会有NAL size字段。NAL size可能是1字节、2字节或4字节(4字节较常见),解析extradata重要目的就是确认这个值,之后2个字节是SPS长度,长度之后就是SPS。

2. 从SPS中提取宽高

以下代码是网上常见的,但是很明显写的有问题,宽高不一定非要是16的倍数。

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

当宽高不是16整数倍时,frame_cropping_flag值为1,frame_mbs_only_flag为1以下为修正的代码:

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <math.h> typedef unsigned int UINT;
typedef unsigned char BYTE;
typedef unsigned long DWORD; UINT Ue(BYTE *pBuff, UINT nLen, UINT &nStartBit)
{
//计算0bit的个数
UINT nZeroNum = ;
while (nStartBit < nLen * )
{
if (pBuff[nStartBit / ] & (0x80 >> (nStartBit % ))) //&:按位与,%取余
{
break;
}
nZeroNum++;
nStartBit++;
}
nStartBit ++; //计算结果
DWORD dwRet = ;
for (UINT i=; i<nZeroNum; i++)
{
dwRet <<= ;
if (pBuff[nStartBit / ] & (0x80 >> (nStartBit % )))
{
dwRet += ;
}
nStartBit++;
}
return ( << nZeroNum) - + dwRet;
} int Se(BYTE *pBuff, UINT nLen, UINT &nStartBit)
{
int UeVal=Ue(pBuff,nLen,nStartBit);
double k=UeVal;
int nValue=ceil(k/);//ceil函数:ceil函数的作用是求不小于给定实数的最小整数。ceil(2)=ceil(1.2)=cei(1.5)=2.00
if (UeVal % ==)
nValue=-nValue;
return nValue;
} DWORD u(UINT BitCount,BYTE * buf,UINT &nStartBit)
{
DWORD dwRet = ;
for (UINT i=; i<BitCount; i++)
{
dwRet <<= ;
if (buf[nStartBit / ] & (0x80 >> (nStartBit % )))
{
dwRet += ;
}
nStartBit++;
}
return dwRet;
} /**
* H264的NAL起始码防竞争机制
*
* @param buf SPS数据内容
*
* @无返回值
*/
void de_emulation_prevention(BYTE* buf,unsigned int* buf_size)
{
int i=,j=;
BYTE* tmp_ptr=NULL;
unsigned int tmp_buf_size=;
int val=; tmp_ptr=buf;
tmp_buf_size=*buf_size;
for(i=;i<(tmp_buf_size-);i++)
{
//check for 0x000003
val=(tmp_ptr[i]^0x00) +(tmp_ptr[i+]^0x00)+(tmp_ptr[i+]^0x03);
if(val==)
{
//kick out 0x03
for(j=i+;j<tmp_buf_size-;j++)
tmp_ptr[j]=tmp_ptr[j+]; //and so we should devrease bufsize
(*buf_size)--;
}
} return;
} /**
* 解码SPS,获取视频图像宽、高信息
*
* @param buf SPS数据内容
* @param nLen SPS数据的长度
* @param width 图像宽度
* @param height 图像高度 * @成功则返回1 , 失败则返回0
*/
int h264_decode_sps(BYTE * buf,unsigned int nLen,int &width,int &height,int &fps)
{
UINT StartBit=;
fps=;
de_emulation_prevention(buf,&nLen); int forbidden_zero_bit=u(,buf,StartBit);
int nal_ref_idc=u(,buf,StartBit);
int nal_unit_type=u(,buf,StartBit);
if(nal_unit_type==)
{
int profile_idc=u(,buf,StartBit);
int constraint_set0_flag=u(,buf,StartBit);//(buf[1] & 0x80)>>7;
int constraint_set1_flag=u(,buf,StartBit);//(buf[1] & 0x40)>>6;
int constraint_set2_flag=u(,buf,StartBit);//(buf[1] & 0x20)>>5;
int constraint_set3_flag=u(,buf,StartBit);//(buf[1] & 0x10)>>4;
int reserved_zero_4bits=u(,buf,StartBit);
int level_idc=u(,buf,StartBit); int seq_parameter_set_id=Ue(buf,nLen,StartBit); if( profile_idc == || profile_idc == ||
profile_idc == || profile_idc == )
{
int chroma_format_idc=Ue(buf,nLen,StartBit);
if( chroma_format_idc == )
int residual_colour_transform_flag=u(,buf,StartBit);
int bit_depth_luma_minus8=Ue(buf,nLen,StartBit);
int bit_depth_chroma_minus8=Ue(buf,nLen,StartBit);
int qpprime_y_zero_transform_bypass_flag=u(,buf,StartBit);
int seq_scaling_matrix_present_flag=u(,buf,StartBit); int seq_scaling_list_present_flag[];
if( seq_scaling_matrix_present_flag )
{
for( int i = ; i < ; i++ ) {
seq_scaling_list_present_flag[i]=u(,buf,StartBit);
}
}
}
int log2_max_frame_num_minus4=Ue(buf,nLen,StartBit);
int pic_order_cnt_type=Ue(buf,nLen,StartBit);
if( pic_order_cnt_type == )
int log2_max_pic_order_cnt_lsb_minus4=Ue(buf,nLen,StartBit);
else if( pic_order_cnt_type == )
{
int delta_pic_order_always_zero_flag=u(,buf,StartBit);
int offset_for_non_ref_pic=Se(buf,nLen,StartBit);
int offset_for_top_to_bottom_field=Se(buf,nLen,StartBit);
int num_ref_frames_in_pic_order_cnt_cycle=Ue(buf,nLen,StartBit); int *offset_for_ref_frame=new int[num_ref_frames_in_pic_order_cnt_cycle];
for( int i = ; i < num_ref_frames_in_pic_order_cnt_cycle; i++ )
offset_for_ref_frame[i]=Se(buf,nLen,StartBit);
delete [] offset_for_ref_frame;
}
int num_ref_frames=Ue(buf,nLen,StartBit);
int gaps_in_frame_num_value_allowed_flag=u(,buf,StartBit);
int pic_width_in_mbs_minus1=Ue(buf,nLen,StartBit);
int pic_height_in_map_units_minus1=Ue(buf,nLen,StartBit); //width=(pic_width_in_mbs_minus1+1)*16;
//height=(pic_height_in_map_units_minus1+1)*16; int frame_mbs_only_flag=u(,buf,StartBit);
if(!frame_mbs_only_flag)
int mb_adaptive_frame_field_flag=u(,buf,StartBit); int direct_8x8_inference_flag=u(,buf,StartBit);
int frame_cropping_flag=u(,buf,StartBit);
int frame_crop_left_offset=;
int frame_crop_right_offset=;
int frame_crop_top_offset=;
int frame_crop_bottom_offset=;
if(frame_cropping_flag)
{
frame_crop_left_offset=Ue(buf,nLen,StartBit);
frame_crop_right_offset=Ue(buf,nLen,StartBit);
frame_crop_top_offset=Ue(buf,nLen,StartBit);
frame_crop_bottom_offset=Ue(buf,nLen,StartBit);
} width = ((pic_width_in_mbs_minus1 +)*) - frame_crop_left_offset* - frame_crop_right_offset*;
height= (( - frame_mbs_only_flag)* (pic_height_in_map_units_minus1 +) * ) - (frame_crop_top_offset * ) - (frame_crop_bottom_offset * ); int vui_parameter_present_flag=u(,buf,StartBit);
if(vui_parameter_present_flag)
{
int aspect_ratio_info_present_flag=u(,buf,StartBit);
if(aspect_ratio_info_present_flag)
{
int aspect_ratio_idc=u(,buf,StartBit);
if(aspect_ratio_idc==)
{
int sar_width=u(,buf,StartBit);
int sar_height=u(,buf,StartBit);
}
}
int overscan_info_present_flag=u(,buf,StartBit);
if(overscan_info_present_flag)
int overscan_appropriate_flagu=u(,buf,StartBit);
int video_signal_type_present_flag=u(,buf,StartBit);
if(video_signal_type_present_flag)
{
int video_format=u(,buf,StartBit);
int video_full_range_flag=u(,buf,StartBit);
int colour_description_present_flag=u(,buf,StartBit);
if(colour_description_present_flag)
{
int colour_primaries=u(,buf,StartBit);
int transfer_characteristics=u(,buf,StartBit);
int matrix_coefficients=u(,buf,StartBit);
}
}
int chroma_loc_info_present_flag=u(,buf,StartBit);
if(chroma_loc_info_present_flag)
{
int chroma_sample_loc_type_top_field=Ue(buf,nLen,StartBit);
int chroma_sample_loc_type_bottom_field=Ue(buf,nLen,StartBit);
}
int timing_info_present_flag=u(,buf,StartBit);
if(timing_info_present_flag)
{
int num_units_in_tick=u(,buf,StartBit);
int time_scale=u(,buf,StartBit);
fps=time_scale/(*num_units_in_tick);
}
}
return true;
}
else
return false;
}

参考资料:

1. http://www.latelee.org/my-study/get-width-height-framerate-from-bitstream.html

2. https://blog.csdn.net/yue_huang/article/details/75126155

H.264从SPS中提取视频宽高的更多相关文章

  1. 从H264码流中获取视频宽高 (SPS帧) 升级篇

    之前写过 <从H264码流中获取视频宽高 (SPS帧)> . 但发现很多局限性,而且有时解出来是错误的. 所以重新去研究了. 用了 官方提供的代码库来解析. 花了点时间,从代码库里单独把解 ...

  2. 从H264码流中获取视频宽高 (SPS帧)

    获取.h264视频宽高的方法 花了2个通宵终于搞定.(后面附上完整代码) http://write.blog.csdn.net/postedit/7852406 图像的高和宽在H264的SPS帧中.在 ...

  3. 多媒体开发之sps---解析sps得到图像的宽高

    (1)通过块的宽高解析出真个h264的分辨率 如何解析SDP中包含的H.264的SPS和PPS串 http://www.pernet.tv.sixxs.org/thread-109-1-1.html ...

  4. JavaScript中的各种宽高以及位置总结

    JavaScript中的各种宽高以及位置总结 在javascript中操作dom节点让其运动的时候,常常会涉及到各种宽高以及位置坐标等概念,如果不能很好地理解这些属性所代表的意义,就不能理解js的运动 ...

  5. LODOP中的各种宽高和位置简短问答

    LODOP中的位置边距,可查看本博客另一篇相关博文:LODOOP中的各种边距 打印项.整体偏移.可打区域.内部边距关于LODOP中的各种宽高,可查看本博文简短问答下方的正文:.该文其实有两个以前的相关 ...

  6. AntDesign VUE:上传组件图片/视频宽高、文件大小、image/video/pdf文件类型等限制(Promise、Boolean)

    文件大小限制 - Promise checkFileSize(file, rules) { return new Promise((resolve, reject) => { file.size ...

  7. 一步一步解析H.264码流的NALU(SPS,PSS,IDR)获取宽高和帧率

    分析H.264码流的工具 CodecVisa,StreamEye以及VM Analyzer NALU是由NALU头和RBSP数据组成,而RBSP可能是SPS,PPS,Slice或SEI 而且SPS位于 ...

  8. 一种H.264高清视频的无参考视频质量评价算法(基于QP和跳过宏块数)

    本文记录一种无参考视频质量评价算法.这是我们自己实验室前两年一个师姐做的,算法还是比较准确的,在此记录一下. 注意本算法前提是高清视频.而且是H.264编码方式. 该方法主要使用两个码流里面的参数进行 ...

  9. JavaScript及jQuery中的各种宽高属性图解

    文/poetries(简书作者)原文链接:http://www.jianshu.com/p/60332df38393 著作权归作者所有,转载请联系作者获得授权, 并标注“简书作者”.   作者声明:本 ...

随机推荐

  1. 排序算法(8)--Merge Sorting--归并排序--Merge sort--归并排序

    1.基本思想  归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用.将已有序的子序列合并,得到完全有序的序列:即先使每个子序 ...

  2. LOJ#505. 「LibreOJ β Round」ZQC 的游戏(最大流)

    题意 题目链接 Sol 首先把第一个人能吃掉的食物删掉 然后对每个人预处理出能吃到的食物,直接限流跑最大流就行了 判断一下最后的最大流是否等于重量和 注意一个非常恶心的地方是需要把除1外所有人都吃不到 ...

  3. ubuntu 搭建samba共享方案

    1.samba服务安装搭建 sudo apt-get install samba sudo vim /etc/samba/smb.conf workgroup = szsoft 设置用户密码登陆方式s ...

  4. Vue -- vue-cli(vue脚手架) npm run build打包优化

    这段时间公司新项目立项,开发组选用 Vue2.0 进行开发.当然也就一并用到 vue cli 进行自动化构建.结果在基础版本开发完成后,用 npm run build 命令打包上线时,发现以下几个问题 ...

  5. sql server对并发的处理-乐观锁和悲观锁(转)

    假如两个线程同时修改数据库同一条记录,就会导致后一条记录覆盖前一条,从而引发一些问题. 例如: 一个售票系统有一个余票数,客户端每调用一次出票方法,余票数就减一. 情景: 总共300张票,假设两个售票 ...

  6. EOFException异常详解

    最近线上的系统被检测出有错误日志,领导让我检查下问题,我就顺便了解了下这个异常. 了解一个类,当然是先去看他的API,EOFException的API如下: 通过这个API,我们可以得出以下信息: 这 ...

  7. WebStorm 中 dva 项目用 start 命令需要不断重启项目问题

    问题: 用dva-cli 构建的项目,用webstorm进行开发,通过 npm start进行启动,经常修改了文件之后,浏览器里面的内容没有刷新,需要重新执行npm start才行. 解决办法: we ...

  8. November 01st, 2017 Week 44th Wednesday

    People always want to lead an active life, and is not it? 人们总要乐观生活,不是吗? Be active, and walk towards ...

  9. H.__mro__) MRO- C3算法

  10. DevExpress03、GridControl

    设计数据源并绑定字段: 数据源可以是实现下列接口之一的任何类型: IList 接口,包括一维数组.List<T>等! IListSource 接口,例如,DataTable 和 DataS ...