序:

只大概说明要点。更具体的方法恕不祥叙。
我的开源工程和很多开源项目都有详细完整的实现代码。
这些要点都是我自己学习的总结,无责任保证正确性。仅做参考。
如发现有问题请丢砖头,跪求各方高人指正错误。Orz

内容:

H264的ES原始数据一般是以NAL(Network Abstract Layer)的格式存在。可以直接用于文件存储和网络传输。每一个NALU(Network Abstract Layer Unit)数据,是由数据头+RBSP数据组成。

首先需要将数据流,分割成一个一个独立的NALU数据。

接着获取NALU的nal_type,i_nal_type的值等于0x7表示这个nalu是个sps数据包。找到并解析这个sps数据包,里面包含有非常重要的帧率信息
time_scale/num_units_in_tick=fps

然后根据nal_type判断slice(H264中的slice类似一个视频帧FRAME的概念)。其中nal_type值小于0x1,或大于0x5,表示这个NALU属于一个slice。

  1. // 检查是否是slice
  2. if ( i_nal_type < 1/*NAL_SLICE*/ || i_nal_type > 5/*NAL_SLICE_IDR*/ )
  3. // 找到slice!!!!!

在找到slice的NALU后,可以逐字节将NALU的数据与0x80进行与运算,结果为真表示这个slice(视频帧FRAME)的结束位置。

  1. // 判断是否帧结束
  2. for (uint32_t i = 3; i < nal_length; i++)
  3. {
  4. if (p_nal[i] & 0x80)
  5. {
  6. // 找到frame_begin!!!!上一帧frame的结束,下一帧frame的开始
  7. }
  8. }

上面的这个代码是摘抄自FFMPEG。他实际作用是判断slice里面的first_mb_in_slice,即第1个宏块在slice中的位置,如果是一帧开始,这个字段的值肯定是标识第1个宏块。因此,也可以完整解析slice的头部信息,解析出first_mb_in_slice,如果是0(注意:这是1个哥伦布数值),即这个NALU是一帧的开始。

为什么这里的代码是逐字节判断0x80?我额外写点某大神的名言:程序猿不是十万个为什么,不是维基猿,程序猿是需求猿。如果某程序猿已经着手开始研究如何解析slice头部格式,他很自然的不会有这个疑问。

另外通过nal_type以及silice_type也可以判断出帧结束位置,VLC里面的代码就是这么干。

解析到位于帧结束位置的NALU,就可以判断出每一帧(slice)的开始和结尾。解析slice的slice_type,根据slice_type,可以判断出这个slice的IPB类型。

  1. // 根据slice类型判断帧类型
  2. switch(slice.i_slice_type)
  3. {
  4. case 2: case 7:
  5. case 4: case 9:
  6. *p_flags = 0x0002/*BLOCK_FLAG_TYPE_I*/;
  7. break;
  8. case 0: case 5:
  9. case 3: case 8:
  10. *p_flags = 0x0004/*BLOCK_FLAG_TYPE_P*/;
  11. break;
  12. case 1:
  13. case 6:
  14. *p_flags = 0x0008/*BLOCK_FLAG_TYPE_B*/;
  15. break;
  16. default:
  17. *p_flags = 0;
  18. break;
  19. }

从现在开始,就有两种办法来计算PTS了。

方法一、根据前后帧的IPB类型,可以得知帧的实际显示顺序,使用前面获取的sps信息中的帧率,以及帧计数frame_count即可计算出PTS。此方法需要做几帧缓存(一般缓存一个group的长度)。

I  P  B  B  I  P  B  B  I  P  B  ... 帧类型
1  2  3  4  5  6  7  8  9  10 11 ... 第几帧
1  4  2  3  5  8  6  7  9  12 10 ... 帧显示顺序

一个I帧与下一个I帧之间,是一个group。
从上图可见,P类型的帧的显示顺序,是排在后面最后一个B帧之后。
所以要获取第7帧的pts,起码要知道他下一帧的类型,才能得知他的显示顺序。

第8帧的pts=1000(毫秒)*7(帧显示顺序)*帧率

方法二、每一个slice的信息里面,都记录有pic_order_cnt_lsb,当前帧在这个group中的显示顺序。通过这个pic_order_cnt_lsb,可以直接计算出当前帧的PTS。此方法不需要做帧缓存。

计算公式:

pts=1000*(i_frame_counter + pic_order_cnt_lsb)*(time_scale/num_units_in_tick)

i_frame_counter是最近一次I帧位置的帧序,通过I帧计数+当前group中的帧序,得到帧实际显示序列位置,乘上帧率,再乘上1000(毫秒)的base_clock(基本时钟频率),得到PTS。

I  P  B  B  I  P  B  B  I  P  B  ... 帧类型
1  2  3  4  5  6  7  8  9  10 11 ... 第几帧
1  4  2  3  5  8  6  7  9  12 10 ... 帧显示顺序
0  6  2  4  0  6  2  4  0  6  2  ... pic_order_cnt_lsb

细心一点可以注意到,在上图,slice里面的pic_order_cnt_lsb是以2进行递增。
通常H264里面的sps中记录的帧率,也是实际帧率的2倍time_scale/num_units_in_tick=fps*2

因此,实际的计算公式应该是这样
pts=1000*(i_frame_counter*2+pic_order_cnt_lsb)* (time_scale/num_units_in_tick)
或者是
pts=1000*(i_frame_counter+pic_order_cnt_lsb/2)* (time_scale/num_units_in_tick/2)

所以,第11帧的pts应该是这么计算
1000*(9*2+2)*(time_scale/num_units_in_tick)

结束语:
这里pts的base_clock都是按照1000(毫秒)计算,如果复用到ts里,base_clock是90k,所以还应该再乘以90。

题外话:关于H264中sps里面记录的帧率是实际帧率的2倍,包括slice里面的pic_order_cnt_lsb也是2倍递增,我推测可能是编码按照分场(顶场、底场)编码所致。另外我注意到sps信息中的offset_for_top_to_bottom_field字段,从命名上,貌似是可以用来标记是否逐场,还是分奇偶场编码。以上都属猜测,有请高人解惑。 Orz

本文出自 “C+侦探de裤子” 博客,请务必保留此出处http://70565912.blog.51cto.com/1358202/533736

如何在H264数据中获取PTS?相关推荐

  1. Android多个imei如何获取,如何在Android 10中获取IMEI号,这是获取在Android 10及以下Android 10中获取IMEI号的代码...

    如何在android 10中获取imei编号,这是获取在android 10及以下android 10中获取imei编号的代码. if (android.os.Build.VERSION.SDK_IN ...

  2. 如何才能在大数据中获取价值

    从数据中获取价值都是一个挑战,不管你所在的行业和企业规模如何.然而,在早期阶段,这一挑战与可用数据量没多大关系.如果对数据处理过程和数据值提取的结构设计不合理,那么至少按照现在的标准,企业有数据和没数 ...

  3. 如何在Node.js中获取本机本地IP地址

    最近在做Cloud related的项目时,遇到一个问题,就是如何在Node.js中获取本机的IP地址.Node.js提供的API中,只能获取本机的hostname. os = require('os ...

  4. 大数据时代:9种从大数据中获取商业价值的方法

    很多大数据都是来自一些新的来源,这代表客户或合作伙伴互动的新渠道.和任何新的数据来源一样,大数据值得探索.通过数据探索,你可以了解一些之前所不知道的商业模式和事实真相. 关于管理大数据的调查显示,89 ...

  5. MySQL中数组内的JSON数据中获取值

    MySQL中JSON数据获取值 1.MySQL中JSON数据中获取值 数据源: {"observeTruth": "111","preventHume ...

  6. 不能直接获取?聊聊如何在Shader Graph中获取深度图

    0x00 前言 在这篇文章中,我们选择了过去几周Unity官方社区交流群以及UUG社区群中比较有代表性的几个问题,总结在这里和大家进行分享.主要涵盖了** StreamingAssets.Profil ...

  7. c语言获取时间并存储,如何在C程序中获取日期和时间值?

    strftime (C89) 马丁提到过,这是一个例子: main.c #include #include #include int main(void) { time_t t = time(NULL ...

  8. 如何在 .NET Core 中获取 CPU 使用率

    这篇文章我们分享一种如何在 .NETCore 中获取 CPU使用率的方法, 它所报告的这个值和 任务管理器 中报告的 CPU 使用值 差不多是一致的. 在 .NET Framework 中,很多人会用 ...

  9. 如何在C#中获取Unix时间戳

    本文翻译自:How to get the unix timestamp in C# 我曾经看过stackoverflow,甚至看过一些建议的问题,但似乎都没有答案,如何在C#中获得unix时间戳? # ...

  10. 如何在Android Studio中获取SHA-1指纹证书以获得调试模式?

    本文翻译自:How to get the SHA-1 fingerprint certificate in Android Studio for debug mode? I have shifted ...

最新文章

  1. android获取地址api,如果在Android中使用位置API给出纬度和经度,如何获取地址
  2. 十年磨一剑!阿里OceanBase创纪录卫冕,中国数据库从此告别卡脖子
  3. 考研数学一2015年真题整理
  4. wxWidgets:wxTrackable类用法
  5. JSON在JS和JAVA的处理
  6. bootstrap -- css -- 表单控件
  7. python任务调度系统web_监听调度系统定时执行任务python_websock
  8. 新手如何学电影解说剪辑全教程
  9. 【深度学习】使用opencv在视频上添加文字和标记框
  10. IOI2020国家集训队集中培训通知及如何进入国家集训队
  11. Hadoop回顾:(一)Hadoop生态系统简介
  12. AI-大型软件研发效能倍增的银弹
  13. 石川圖 / 鱼骨图 / 關鍵要因圖 / 因果圖
  14. 求链表中的中点、上中点、下中点
  15. 谷歌google bard vs chatgpt给我的最大感受,速度真快,注册简单,多种答案提供。。。
  16. 母版页的详细使用介绍
  17. 人间不正经生活语录(一)
  18. 齐岳|聚乙二醇-四氧化三铁-二氢卟吩纳米复合物Fe3O4-PEG-Ce6|肝靶向功能的四氧化三铁Fe3O4纳米粒子
  19. Java基本数据类型转字符串
  20. 英语知识点整理day04

热门文章

  1. .Net remoting, Webservice,WCF,Socket区别
  2. vue系列之vue cli 3引入ts
  3. 关于编辑区无法调用chekbox的问题
  4. javascript DOM操作
  5. Linux mysql 允许远程连接
  6. 巧用层次坐标解决统计图分类轴与系列取值
  7. 【Leetcode】101. 对称二叉树
  8. MyBatis配置项--配置环境(environments)
  9. 或许是介绍Android Studio使用Git最详细的文章
  10. VMware与Hyper-V不兼容