JCHub

  • Home
  • Category
    • A/V
    • WebRTC
    • Beauty of Programming
    • Linux
    • Windows
    • Moments of Life
    • Campus Life
  • Reference
    • API Reference
    • Utilities
    • AV Test
    • Doc
  • Message Board
  • About
音视频
WebRTC

WebRTC研究:RTP中的序列号以及时间戳比较

在使用RTP协议时,如果需要网络对抗,保障QoS(Quality of Service,服务质量),我们需要通过序列号以及时间戳的比较,进行丢包判断。但是有个问题,比如一个RTP包,序列号为number1:5000,另一个RTP包序列号为number2:60000,可以说60000一定比5000大,是个更新的RTP包吗? 当然不是了,首先我们先重温下RTP数据包的结构。 在RFC3550中RTP固定报头结构按如下定义: [crayon-69c24079d5d94442276108/] 可以看到,RTP序列号sequence number用两字节表示,时间戳timestamp用四字节表示。所以序列号以及时间戳就存在一个取值范围。由于是无符号数,所以序列号范围为:[0,2^16-1],时间戳范围为:[0,2^32-1]。当达到最大值时,将发生所谓的回绕。例如,序列号到了2^16-1,下个包序列号就将是0。所以我们不能直接根据数学意义上的大小进行序列号以及时间戳的比较。 在WebRTC中定义了一个大小比较算法,包含数字回绕处理,判断是否是更新的数字。下面说下算法原理: 1)假设有两个U类型的无符号整数:value, prev_value; 2)定义一个常量kBreakpoint,为U类型取值范围的一半; 3)value > prev_value,满足value - prev_value = kBreakpoint时,value大于prev_value; 4)value与prev_value不相等,满足(U)(valude - prev_value) < kBreakpoint时,value大于prev_value。 总结起来就是value与prev_value距离小于取值范围的一半且不相等或者value与prev_value距离等于取值范围的一半,value大于prev_value,就可以说明value大于prev_value。 在modules\include\module_common_types_public.h,相应代码如下: [crayon-69c24079d5d9b651409254/] 顺便也说下如上函数的一个应用。在WebRTC丢包重传判断中(jitter_buffer模块),需要保存上一次收到的最新RTP序列号latest_received_sequence_number_,以便用于丢包判断(请参考:WebRTC研究:丢包判断),这个RTP包必须是新的数据,不能是重传的旧数据。所以每来一个RTP包时,可以将该RTP包序列号传入如上函数,与之前收到的最新RTP序列号进行对比,从而判断当前RTP包是不是最新的。相应代码如下: [crayon-69c24079d5d9d781878109/] 参考 [1].rfc1982.https://tools.ietf.org/html/rfc1982

2019年7月6日 5comments 4595hotness 12likes Jeff Read all
WebRTC

WebRTC研究:关键帧请求

WebRTC采用UDP传输流媒体数据,不可避免存在丢包情况。WebRTC主要采用FEC(Forward Error Correction,前向纠错)以及NACK(negative-acknowledge character,否定应答)对抗网络丢包。对于NACK,遇到丢包了才通知发送端重传对应数据包,但不是所有情况下某个包丢了就一定重传该包,有些场景下,重传该包会带来其它问题,例如增大延时,缓存过大,同时也可能发送端没有该数据包缓存,导致无法重传,此时会放弃重传该包。由于关键帧可以单独解码出图像,不参考前后视频帧,所以会采取请求关键帧这种更便捷的方式替代重传该数据包,使解码端能立刻刷新出新图像,避免丢包过多,长时间等待重传数据包导致的画面停顿问题,以及获取不到重传包导致后续数据解码花屏问题。除了丢包,在WebRTC还存在其他请求关键帧的场景。 关键帧请求场景 下面结合代码列举几种常见场景。 H264解码无sps,pps信息 解码H264时无法获取sps,pps,导致无法解码,此时就需要请求获取关键帧,在H264SpsPpsTracker中,相关处理代码如下: [crayon-69c24079d8346571073668/] 丢失包过多 在非常高的丢包率情况下,丢失的包太多,若都一一重传,将造成延时增大(等帧数据完整了才会去解码渲染),此时新来的数据也只能一直缓存,所以jitterbuffer大小也会不断增大,此时不如直接请求发送一个关键帧来得实际,以前丢的那些包都不管了,由于关键帧可以单独解码,所以不会造成解码端花屏马赛克现象。但是由于前面那些视频帧都丢弃了,此时生成的关键帧会与之前播放的视频存在不连贯性,所以画面变化大时会有轻微卡顿现象,相当于跳帧了。NackModule中相关处理代码如下: [crayon-69c24079d834d516696060/] 上面代码中,要重传的包数量nack_list_.size()在进行RemovePacketsUntilKeyFrame()操作后若还超过规定大小,就开始清空要重传的数据包列表:nack_list_.clear(),然后请求关键帧。 丢失包过旧 发送端默认缓存600个RTP包,如果丢失的包太旧,超出缓存范围,此时发送端就没有该数据包的缓存,就无法重传该包。在VCMJitterBuffer中,相关处理代码如下: [crayon-69c24079d834f968406575/] 获取帧数据超时 要解码的帧数据存放在FrameBuffer中,解码器解码时如果超过规定时间一直无法从FrameBuffer中获取解码数据,此时就会请求关键帧。相关处理代码如下: [crayon-69c24079d8350923229266/] 解码出错 当解码器返回解码失败,或者解码器返回请求关键帧结果时,需要请求关键帧。相关处理代码如下: [crayon-69c24079d8351245822112/] 在上面我们列举了几种需要关键帧请求的情况,我们只需要规定好RTCP报文格式,就能通知编码发送端发送关键帧。关键帧请求RTCP报文格式比较简单,在RFC4585(RTP/AVPF)以及RFC5104(AVPF)规定了两种不同的关键帧请求报文格式:Picture Loss Indication (PLI)、Full Intra Request (FIR)。WebRTC中关键帧请求也只用到了这两种消息,相关代码如下: [crayon-69c24079d8352454510818/] Picture Loss Indication (PLI) 在RFC4585中定义,属于RTCP反馈消息中的一种。RTCP反馈消息数据包格式按如下规定: [crayon-69c24079d8353195428463/] 其中PT字段按如下规定: [crayon-69c24079d8356492736165/] 对于PLI,由于只需要通知发送关键帧,无需携带其他消息,所以FCI部分为空。对于FMT规定为1,PT规定为PSFB。 在WebRTC源码中,PLI相关解析封装代码位于webrtc::Pli中。相关代码如下: [crayon-69c24079d8357471090144/] PLI消息用于解码端通知编码端我要解码的图像的编码数据丢失了。对于基于帧间预测的视频编码类型,编码端收到PLI消息就要知道视频数据丢失了,由于帧间预测需要基于前后完整的视频帧才能解码(例如H264中,存在B帧,需要参考前后帧才能解码),前面的数据丢失了,后面的视频帧不能正常解码出图像,此时编码端可以直接生成一个关键帧,然后发送给解码端。 Full Intra Request (FIR) 在RFC5104中定义。参照上一小节RTCP反馈消息数据包格式,对于FMT规定为4,PT规定为PSFB。由于FIR可用于通知多个编码发送端(例如多点视频会议情况),所以用到了FCI部分,填充多个发送端的ssrc信息。具体包格式如下: [crayon-69c24079d8358016526255/] 在WebRTC源码中,FIR相关解析封装代码位于webrtc::Fir中。相关处理代码就不贴出来了,类似PLI处理,除了FCI部分要填充一些信息。 当解码端需要刷新时,可以发送FIR消息给编码端,编码端此时发送关键帧,刷新解码端。这有点类似PLI消息,但是PLI消息是用于丢包情况下的通知,而FIR却不是,在有些非丢包情况下,FIR就要用到。举两个例子: 1)解码端需要切换到另一路不同视频时,由于需要新的解码参数,所以可通过发送FIR消息,通知编码端生成关键帧,获取新的解码参数,刷新视频解码器; 2)在视频会议中,新用户随机时刻加入,各个编码端发送的视频不一定都是关键帧,所以新用户不一定能正常解码。此时该新加入用户发送FIR消息,通知各个编码端给它发关键帧,获取关键帧后即可正常解码。 总结 本文主要介绍了几种关键帧请求场景,讲了AVPF中定义的两种关键帧请求消息,虽然这两种消息获取的结果一样,但是表达的意义却不一样,用于不同场景,使用时需要区分下。

2019年5月28日 8comments 6209hotness 15likes Jeff Read all
音视频

ffmpeg视频编码YUV与AVFrame对应关系

最近群里有人问:NV12格式,怎么对应AVFrame中的data[0],data[1],data[2]。其实ffmpeg视频编码,YUV与AVFrame对应关系很简单。 在视频编码时,我们需要把YUV数据拷贝到AVFrame.data中,视频编码有硬件加速以及非硬件加速两种,所以对应关系也有两种。 硬件加速编码 指通过显卡进行硬件加速编码,例如指定vaapi进行编码。使用硬件加速编码时,YUV输入格式一般都是NV12,我做过的Intel以及Nvidia编码都是这样。之所以使用NV12格式,在Intel开发文档有这样说明: [crayon-69c24079d98b1123219015/] 因为ffmpeg底层也是调用相关显卡SDK做硬件加速编码,所以我们把YUV数据拷贝给AVFrame时,也得按NV12格式。对于NV12格式,Y数据在最前,接着是UV交错排布,类似这样:YYYYYYYY UVUV,所以对于AVFrame,我们得把Y数据拷贝data[0],UV数据拷贝给data[1]。 [crayon-69c24079d98b6501459235/] 非硬件加速编码 指通过CPU进行编码,例如指定libx264,libx265进行编码。对应关系就比较简单了,YUV三个分量数据依次对应data[0],data[1],data[2]。 [crayon-69c24079d98b8491971031/]

2019年3月24日 2comments 3706hotness 2likes Jeff Read all
音视频

安防视频播放秒开优化

在视频播放中,播放器立即出图像(秒开)非常重要。能够极大提高用户的体验度。网上有很多关于直播行业秒开的优化经验,但是没有安防GB28181的,GB28181标准视频播放跟直播还是有些不一样的,下面说下一些GB28181标准视频秒开的优化经验。 直播行业视频封装传输遵循RTMP/HLS/DASH这些标准。安防摄像头视频播放大多遵循GBT28181。目前常见的视频编码格式有H264/H265/MPEG-4/Svac,通常都封装为PS流(Program Stream,封装标准参考iso13818-1 2.5小节)格式,然后作为负载封装成RTP包传输。在直播行业,视频流的采集主要是用户的手机或者PC设备,而安防行业视频流的采集来自于各大厂商的摄像头。 视频秒开需要从播放端,发送端以及网络传输考虑。视频秒开关键点是要立马获取到关键帧,后续视频帧的解码都得参考关键帧,有了关键帧,解码器才能立刻解码,然后出图像。 发送端 发送端一般分为两种:直连的设备以及流媒体服务器。直连设备是指播放端直接连接摄像机播放。 摄像机设置 由于视频来自摄像机,无论发送端是哪种,我们都要修改摄像机的关键帧间隔(GOP),一般设置2倍的帧率。这样确保在较短时间内,能够获取到关键帧播放,设置太短,会增大数据量,设置太长会造成等待关键帧时间太久,一旦出现丢包等异常,影响较大。如下图是某款海康摄像机配置,我们配置视频帧率为25帧,关键帧(I帧)间隔为2s,即50帧。 服务器设置 如果不是摄像机直连,通过我们自己的流媒体服务器的话就需要对我们的服务器做处理。这时我们需要做关键帧缓存,确保推给播放端的流第一帧就是关键帧。在互联网直播中通常用到了CDN,很多CDN厂商都会缓存一组GOP数据。安防行业一般都是私有网络,没有CDN,那服务端具体要怎么做呢? 服务端为每一路摄像机信号维护一个关键帧缓冲区,缓存最新的一组GOP数据(关键帧以及相关的一组P帧),每当该摄像机有新关键帧数据来时,都要更新这个缓冲区 当有播放端请求某摄像机信号时,先判断当前摄像机实时流是否是关键帧第一个包 是的话直接推实时流,同时更新缓冲区 不是的话就得从关键帧缓冲区拿数据推给播放端,直到实时流来了关键帧,此时切换到从实时流推流,然后更新缓冲区 播放端 解复用(Demux) Demux是指解析视频的封装格式,得到包含的音视频原始码流,Demux时间越短,就越快得到视频流,从而加快秒开速度。我们这里的Demux过程主要是解析RTP负载数据,对于每个RTP包,去除头部12字节头部数据后就是负载数据。由于国标视频基本都是封装为PS流格式,所以需要解复用PS流,从PS流里得到原始视频数据。对于PS流的Demux有两个方法,一个是自己写,PS流结构不是很复杂,1000行以内代码可以搞定,如果嫌麻烦,还有一个方法是使用ffmpeg,对于ffmpeg如何demux PS流,可以参考ffmpeg的avio_reading例子,通过探测流的方式demux PS流。 如果是自己写的Demux程序,我们在Demux PS流需要搜索各种头部,由于存在丢包等异常情况,所以搜索头部太久时需要做处理,丢弃无用的数据,避免耗时太久。由于PS中包长度都是用两字节表示,长度为2^16,所以我们可以设置一个值,比2^16大点,当搜索的字节数大于这个值还没搜索一个PS流的包头(0x000001开头),此时就要丢弃之前数据,处理新数据,因为之前数据很大可能丢包或其他问题。 如果是使用ffmpeg做PS流的Demux,有几点需要注意。由于ffmpeg Demux未知流时,需要探测一定大小数据,甚至会尝试解码未知流,这个过程如果不做优化会耗时很久。 ffmpeg通过AVIO方式探测流格式主要通过avformat_find_stream_info函数实现,我们可以通过设置AVFormatContext的probesize与max_analyze_duration限制ffmpeg探测大小与时长,提高Demux速度,我一般按如下设置: [crayon-69c24079dac3a372150396/] 限制流分析大小64K,时长2s。但有点需要注意的是如果送入播放端首帧视频不是关键帧avformat_find_stream_info很大可能性失败,因为ffmpeg在max_analyze_duration内如果获取不到关键帧数据基本会探测失败。所以使用ffmpeg探测PS流时需要确保首帧视频就是关键帧(视频重要参数都存储在关键帧内),这样在max_analyze_duration内才能获取到PS流信息。至于如何判断PS流的关键帧数据,这个很简单,可以根据PS流头部判断,PS流封装的关键帧都含有system_header(0x000001BB开头)与program_stream_map(0x000001BC开头)。 解码 Demux得到原始视频码流后就可以开始解码了,能硬解码就硬解码,硬解码速度会优于软解,尤其在解码路数多时。 渲染 解码得到YUV或RGB数据后,我们需要渲染到屏幕显示,这是最后一步了。渲染也存在是否硬件加速的区别。比如windows平台,优先D3D硬件加速渲染,充分发挥显卡的能力,加快渲染速度。如果前面解码是用硬解码,此时也必须硬件加速渲染。否则又要搬运显存中的硬解数据到内存,由于解码后的数据一般较大,所以这个过程很耗时,同时影响性能,提高CPU占用率。 网络传输 传输分为TCP与UDP。由于TCP的特性,所以TCP获取到首帧视频耗时长点,同时延时也大。所以优先UDP传输。但是UDP传输又存在丢包,乱序等问题,造成视频花屏,所以使用UDP传输时,需要做好抗丢包,拥塞控制等处理,这个我们以后会讨论。

2019年2月23日 0comments 3186hotness 3likes Jeff Read all
音视频

Intel media SDK编码去除多余信息

使用Intel media SDK硬编码H264数据时,生成的每一帧H264数据都包含Access unit delimiter+Picture parameter set+Additional information (SEI)。如下图是使用默认参数生成的一个非IDR帧H264数据。 IDR帧数据结构如下: 根据nal uint type表 可知: 默认编码IDR帧结构:0x00 00 00 01 09(分隔符)|| 0x00 00 00 01 27(SPS)|| 0x00 00 00 01 28(PPS)|| 0x00 00 00 01 06(SEI)|| 0x00 00 01 25(IDR) 默认编码非IDR帧结构:0x00 00 00 01 09(分隔符)|| 0x00 00 00 01 28(PPS)|| 0x00 00 00 01 06(SEI)|| 0x00 00 01 21(非IDR) 但是默认编码有个问题,不是所有播放器可以支持直接播放,测了下VLC不支持(以前文章说过VLC播放带SEI的视频花屏问题),PotPlayer可以播放,这个问题在于编码出来的每帧数据都多了分隔符与SEI以及PPS,不是所有的播放器都去解析这些头部,这方面NVIDIA Video Codec硬编码就简单多了,IDR帧结构就SPS+PPS+IDR,非IDR帧就一个头部。所以为了兼容大多数播放器我们需要去掉多余的分隔符、SEI以及PPS信息,这些信息用途不大,PPS没必要每帧都带,反而增加数据量。如何去除呢,一种方法是手动解析这些头部,然后手动去除,另一种方法是使用编码的扩展参数,这个编码扩展参数如果没认真研究官方文档,还真不知道怎么用。 如下是编码扩展参数如何使用的代码。代码中我们将设置的参数放到一个数组中,然后传递给编码参数mfxVideoParam的ExtParam成员。 [crayon-69c24079db668196003051/] mfxExtCodingOption.PicTimingSEI: Set this flag to insert the picture timing SEI with pic_struct syntax element. See sub-clauses D.1.2 and D.2.2 of the ISO /IEC 14496-10 specification for the definition of this syntax element. See the CodingOptionValue enumerator for values of this option. The default value is ON. 也就是这个成员用于在每帧插入图像时序SEI信息,默认是开启的。 mfxExtCodingOption.AUDelimite: Set this flag to insert the Access Unit Delimiter NAL. See the CodingOptionValue enumerator for values of this option.该标志用于插入分隔符。 mfxExtCodingOption2.RepeatPPS: This flag controls picture parameter set repetition in AVC encoder. Turn ON this flag to repeat PPS with each frame. See the CodingOptionValue enumerator for values of this option. The default value is ON. This parameter is valid only during initialization.用于设置AVC编码器在每一帧编码中插入PPS。默认是开启的。 通过关闭如上编码参数,即可去除多余的数据,编码生成"简洁"的H264帧。

2018年8月14日 1comments 3047hotness 4likes Jeff Read all
Web

web页面npapi插件资源管理问题

最近写的一个npapi视频插件花了一个月基本搞定了要求功能,最近一直在做测试及优化,然后就是配合前端使用。前端那边有个功能是视频播放区域窗口切换,他找我说切换后浏览器卡住了。我问了他切换是怎么实现的,他每次切换后原有的插件标签都没了,然后加载了新的插件标签。我想了下,原有插件标签没了,相当于这个npapi插件对象拥有的资源都被强行释放了,然后又加载新的插件,一个插件对象本身会占有许多资源,包括内存及显存,每次切换都会不断的释放,初始化,造成很多不必要的开销,甚至导致页面卡顿,最坏的是插件崩溃了。 这种情况下,前端写带有插件界面不能像平时那么随意了。怪不得很多公司做的视频窗口切换都是由插件完成,所有的播放窗口都在一个插件内,插件管理窗口切换,这样前端不用考虑什么内存加载释放问题,但是会导致播放界面不灵活,窗口布局都被插件写死了,限制了前端发挥。我现在做的是一个窗口对应一个插件,界面设计更灵活,有点类似自己实现了一个Html5的video标签。 既然插件不能频繁释放加载,参照线程池思想,我们就让所有的插件对象始终存在,需要时再拿出来,不需要就隐藏,不释放。那如何在页面上隐藏插件标签呢,这里面也是有很多限制条件的。比如:插件标签的父标签不能改变,每个插件本身绑定一个窗口句柄,父标签变了,相当于新窗口了,原来那个插件还是被浏览器强制释放了。所以我们要让插件标签一直存在,通过一些技巧让它不用时暂时消失。我试了下设置父标签:visibility: hidden属性也有问题存在,虽然切换不卡了,但是切换回来视频却不显示了。这问题也是棘手。我自己对前端也是不怎么熟悉,只能找资料了。后面找了很久查到可通过如下方法。 假设插件标签object,我放到一个div中,这个div是浮动的,绝对定位的,当插件要移动位置或者隐藏时,只需要计算好位置,然后把这个浮动的div移动到对应位置即可。我想浏览器内部对绝对定位的标签(脱离文档流)在界面改变后没有释放该标签窗口资源,改变位置也只是移动下该标签窗口,不受其它标签影响,不像其它标签,一改变都要重新释放创建。

2018年6月16日 0comments 1758hotness 0likes Jeff Read all
Web

基于FireBreath的npapi插件在Firefox下的调试

最近要做基于浏览器的视频播放,可以播放各种格式的流。首先为了快速出产品先做一个npapi插件,没用activex,因为IE限制了前端们的想象力与创造力,H5播放技术留在最后面做。刚开始做npapi插件用的是火狐原生api开发,比较繁琐。后面找了一个叫做firebreath的开发框架,开发的插件可以在IE,firefox以及chrome旧版本使用,提高了些开发效率,不过还是很多坑,网上资料也少。 看了一天firebreath官网文档,就开始动手搭建开发环境,搭建还是很快的,下个cmake,python,按官网教程一步步来都不会有问题。 随着播放插件越来越复杂必定少不了调试了。不过发现官网给的插件调试方法用不了。官网给的方法是直接附加到firefox进程,不过进不了断点。后来摸索了下,得附加到plugin-container.exe才可以,火狐这样做也是为了安全与稳定性,将插件与标签页进程做了隔离。附加进程操作如下图: 参考链接: 1)FireBreath:http://www.firebreath.org/

2018年5月15日 0comments 2203hotness 1likes Jeff Read all
音视频分享

ffmpeg从mp4提取音频命令

从mp4中提取出aac文件(听歌必备): [crayon-69c24079dca8b106043991/]

2018年3月19日 0comments 3330hotness 4likes Jeff Read all
12345
Copyright Statement

Unauthorized reproduction or plagiarism in any form is strictly prohibited. For reprint requests, please contact via email.

Recent Comments
Mirzoemele Published at 3 months ago(01 01202613104 06 06pm26) Double blind randomised controlled trial of two to...
PedarPhago Published at 7 months ago(08 08202583109 12 12pm25) Association between selective serotonin reuptake i...
EsielTooft Published at 8 months ago(07 07202573112 29 29am25) International scientific apply guidelines for the ...
dongxuh Published at 8 months ago(07 07202573103 27 27pm25) 真心不错的博客,有机会能一起分享
南南 Published at 8 months ago(07 07202573103 15 15pm25) 写的超棒!

COPYRIGHT © 2026 jianchihu.net. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang