首先了解RTSP/RTP/RTCP相关概念,尤其是了解RTP协议:RTP与RTCP协议介绍(转载)

  vlc使用模块加载机制调用live555,调用live555的文件是live555.cpp。

一、几个重要的类  

  以下向左箭头(“<-”)为继承关系。

1. RTPInterface

  RTPInterface是RTPSource的成员变量,其成员函数handleRead会读取网络数据存入BufferedPacket内,该类最终会调到UDP的发送接收函数。

Boolean RTPInterface::handleRead(unsigned char* buffer, unsigned bufferMaxSize,
unsigned& bytesRead, struct sockaddr_in& fromAddress, Boolean& packetReadWasIncomplete)

2. BufferedPacket

  BufferedPacket:用于存储媒体的RTP数据包

  BufferedPacket<-H264BufferedPacket:用于存储H264媒体RTP数据包

  该类有一个重要函数fillInData,是由RTPInterface读取数据存入包中。

Boolean BufferedPacket::fillInData(RTPInterface& rtpInterface, Boolean& packetReadWasIncomplete);

  相对于BufferedPacket,有对应的工厂类:

  BufferedPacketFactory:工厂模式生成BufferedPacket包
  BufferedPacketFactory<-H264BufferedPacketFactory:专门生产H264BufferedPacket的工厂

  在SessionsSetup的时候(也是模块加载的时候),会根据Source类型,选定生产BufferedPacket的工厂类型,即如果Source是H264格式的话,就会new H264BufferedPacketFactory,之后在接收数据的时候就会生产H264BufferedPacket用于存储H264媒体数据。

  ReorderingPacketBuffer:MultiFramedRTPSource的成员变量,用于管理多个BufferedPacket。

3. Source相关类  

  Source相关类的继承关系:Medium<-MediaSource<-FramedSource<-RTPSource<-MultiFramedRTPSource<-H264VideoRTPSource。
  在SessionsSetup的时候,会根据数据源的类型,选定Source的类型,即如果数据源是H264格式的话,就会调用

static H264VideoRTPSource* createNew(UsageEnvironment& env, Groupsock* RTPgs,
  unsigned char rtpPayloadFormat,
  unsigned rtpTimestampFrequency = );

二、播放流程的建立

  播放流程的建立可以参考vlc源码分析之播放流程

三、接收RTP数据

  vlc在播放IPC时,会开启一个线程接收网络数据,该线程接收网络数据后会调用Demux()进行分离(因为可能是音频,也可能是视频)。Demux()首先将必要的接口,如StreamRead、StreamClose注册下去,然后就进入事件循环:

p_sys->scheduler->doEventLoop( &p_sys->event_data );

  如果有网络数据到来了,Demux()会做两件事,第一件事是分析RTP包,放入ReorderingPacketBuffer管理的BufferedPacket中,堆栈如下图所示:

  第二件事是读取的BufferedPacket,进行一系列拆包操作后,将数据放入数据fifo中,堆栈如下图所示:

  doEventLoop会进入死循环,直到p_sys->event_data的值被中断或者超时改变,从而退出循环。当有网络数据到来的时候,doEventLoop会执行SingleStep->...->doGetNextFrame1(),在doGetNextFrame1()函数中读取RTP数据。这个过程的代码及注释如下:

// 做了两件事,一件是分析RTP包,放入ReorderingPacketBuffer管理的BufferedPacket中;
// 另一件是读取的BufferedPacket,进行一系列拆包操作后,将数据放入数据fifo中
void MultiFramedRTPSource::networkReadHandler1() {
BufferedPacket* bPacket = fPacketReadInProgress;
if (bPacket == NULL) {
// Normal case: Get a free BufferedPacket descriptor to hold the new network packet:
bPacket = fReorderingBuffer->getFreePacket(this);
} // Read the network packet, and perform sanity checks on the RTP header:
Boolean readSuccess = False;
// do-while(0)结构,出现错误直接break
do {
Boolean packetReadWasIncomplete = fPacketReadInProgress != NULL;
if (!bPacket->fillInData(fRTPInterface, packetReadWasIncomplete)) break;
if (packetReadWasIncomplete) {
// We need additional read(s) before we can process the incoming packet:
fPacketReadInProgress = bPacket;
return;
} else {
fPacketReadInProgress = NULL;
}
#ifdef TEST_LOSS
setPacketReorderingThresholdTime();
// don't wait for 'lost' packets to arrive out-of-order later
if ((our_random()%) == ) break; // simulate 10% packet loss
#endif // Check for the 12-byte RTP header:
if (bPacket->dataSize() < ) break;
// 读取RTP头,向前移4个字节
unsigned rtpHdr = ntohl(*(u_int32_t*)(bPacket->data())); ADVANCE();
// 读取RTP头中的标记位
Boolean rtpMarkerBit = (rtpHdr&0x00800000) != ;
// 读取时间戳,向前移4个字节
unsigned rtpTimestamp = ntohl(*(u_int32_t*)(bPacket->data()));ADVANCE();
// 读取SSRC,向前移4个字节
unsigned rtpSSRC = ntohl(*(u_int32_t*)(bPacket->data())); ADVANCE(); // Check the RTP version number (it should be 2):
// 检查RTP头版本,不是2的话,break
if ((rtpHdr&0xC0000000) != 0x80000000) break; // Skip over any CSRC identifiers in the header:
// 跳过CSRC计数字节
unsigned cc = (rtpHdr>>)&0xF;
if (bPacket->dataSize() < cc) break;
ADVANCE(cc*); // Check for (& ignore) any RTP header extension
// 如果扩展头标志被置位
if (rtpHdr&0x10000000) {
if (bPacket->dataSize() < ) break;
// 获取扩展头
unsigned extHdr = ntohl(*(u_int32_t*)(bPacket->data())); ADVANCE();
// 获取扩展字节数
unsigned remExtSize = *(extHdr&0xFFFF);
if (bPacket->dataSize() < remExtSize) break;
// 直接跳过扩展字节???
ADVANCE(remExtSize);
} // Discard any padding bytes:
// 如果填充标志被置位,直接丢弃不处理
if (rtpHdr&0x20000000) {
if (bPacket->dataSize() == ) break;
unsigned numPaddingBytes
= (unsigned)(bPacket->data())[bPacket->dataSize()-];
if (bPacket->dataSize() < numPaddingBytes) break;
bPacket->removePadding(numPaddingBytes);
}
// Check the Payload Type.
// 检查载荷类型,如果源数据H264类型,则其值为96
// 如果与我们生成的source类型不同,则break
if ((unsigned char)((rtpHdr&0x007F0000)>>)
!= rtpPayloadFormat()) {
break;
} // The rest of the packet is the usable data. Record and save it:
if (rtpSSRC != fLastReceivedSSRC) {
// The SSRC of incoming packets has changed. Unfortunately we don't yet handle streams that contain multiple SSRCs,
// but we can handle a single-SSRC stream where the SSRC changes occasionally:
fLastReceivedSSRC = rtpSSRC;
fReorderingBuffer->resetHaveSeenFirstPacket();
}
// RTP包序号,随RTP数据包而自增,由接收者用来探测包损失
unsigned short rtpSeqNo = (unsigned short)(rtpHdr&0xFFFF);
Boolean usableInJitterCalculation
= packetIsUsableInJitterCalculation((bPacket->data()),
bPacket->dataSize());
struct timeval presentationTime; // computed by:
Boolean hasBeenSyncedUsingRTCP; // computed by:
// 根据数据包的一些信息,进行一些计算和记录
receptionStatsDB()
.noteIncomingPacket(rtpSSRC, rtpSeqNo, rtpTimestamp,
timestampFrequency(),
usableInJitterCalculation, presentationTime,
hasBeenSyncedUsingRTCP, bPacket->dataSize()); // Fill in the rest of the packet descriptor, and store it:
struct timeval timeNow;
gettimeofday(&timeNow, NULL);
// 将计算所得的一些参数再赋值到包中
bPacket->assignMiscParams(rtpSeqNo, rtpTimestamp, presentationTime,
hasBeenSyncedUsingRTCP, rtpMarkerBit,
timeNow);
// 经过以上判断和检查,没有发现问题,则由管理类fReorderingBuffer存储包
if (!fReorderingBuffer->storePacket(bPacket)) break; readSuccess = True;// 读取成功
} while ();
if (!readSuccess) fReorderingBuffer->freePacket(bPacket);// 如果读取不成功,则释放内存 // 将读取到的数据包送至数据fifo中,等待解码线程解码
doGetNextFrame1();
// If we didn't get proper data this time, we'll get another chance
}

四、组装RTP包为音视频数据包

4.1 组帧要点

1. 默认一个音频包就是一个音频帧;

2. sps,pps会和I帧组成一个完整的帧;

3. 帧头:对于sps,默认为帧头。对于判断是slice开头的包,根据片中第一个宏块的地址(first_mb_in_slice,读取时注意是哥伦布编码的)判断当前片是否是视频帧的首片;注意,h264组帧协议RFC3984里,FU-header中的S和E标识的是片头和片尾,不是帧。

4. 帧尾:对于视频数据,如果marker为1,则该片对应的帧为帧尾。

5. 同一视频帧有相同的timestamp;

4.2 组帧实例

 以h264组帧帧为例,组帧协议是RFC3984。上层读取到一个个的RTP包之后,需要读取RTP包的payload组装成h264的视频帧。那么按照什么规则组装呢?这里有一个rfc3984协议,描述了RTP封装h264的方法。简单描述如下:

1. 单个NAL单元包:荷载中只包含一个NAL单元。NAL头类型域等于原始 NAL单元类型,即在范围1到23之间,此时的RTP包仅仅是将NALU的数据前加12个字节的RTP头,即RTP header(12 bytes)+NALU;

2. 聚合包:本类型用于聚合多个NAL单元到单个RTP荷载中。本包有四种版本,单时间聚合包类型A (STAP-A),单时间聚合包类型B (STAP-B),多时间聚合包类型(MTAP)16位位移(MTAP16), 多时间聚合包类型(MTAP)24位位移(MTAP24)。赋予STAP-A, STAP-B, MTAP16, MTAP24的NAL单元类型号分别是 24,25, 26, 27;

3. 分片单元:用于分片单个NAL单元到多个RTP包。现存两个版本FU-A,FU-B,用NAL单元类型 28,29标识;

NAL单元的一个分片由整数个连续NAL单元字节组成。每个NAL单元字节必须正好是该NAL单元一个分片的一部分。相同NAL单元的分片必须使用递增的RTP序号连续顺序发送(第一和最后分片之间没有其他的RTP包)。相似,NAL单元必须按照RTP顺序号的顺序装配。

格式如下:RTP header(12 bytes)+FU indicator(1 bytes)+FU header(1 bytes)+fu payload

FU indicator格式如下:

+---------------+

|0|1|2|3|4|5|6|7|

|F|NRI| Type |

+---------------+

F、NRI是原始NALU的前3位,Type指示的是FU的type

FU header格式如下:

+---------------+

|0|1|2|3|4|5|6|7|

|S|E|R| Type |

+---------------+

S: 1 bit 当设置成1,开始位指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始,开始位设为0。

E: 1 bit 当设置成1, 结束位指示分片NAL单元的结束,即, 荷载的最后字节也是分片NAL单元的最后一个字节。当跟随的 FU荷载不是分片NAL单元的最后分片,结束位设置为0。

R: 1 bit 保留位必须设置为0,接收者必须忽略该位

Type指示的是NALU的type。

注意:原始的NAL头的前三位为FU indicator的前三位,原始的NAL头的后五位为FU header的后五位。解包时,取FU indicator的前三位和FU Header的后五位,即为NALU的首字节。

下图为笔者抓到的一个RTP包,前12个字节是RTP header。5c开始是RTP数据,5c是FU indicator,41是FU header,换算为二进制:

0   是F

10 是NRI

11100 是FU type,这里是28

0 是s

1 是E,说明是1帧的结束

0 保留,必须置0

00001 是NALU type,表示非IDR帧

将读取到的音视频数据包送至数据fifo中,之后就是等待解码线程从数据fifo中拿数据,解码和渲染了,具体可参考vlc源码分析之播放流程

  附:

  配置好的Windows版vlc工程下载:https://github.com/jiayayao/vlc_2.1.0-vs_2010,下载后使用vs2010可以直接编译运行,调试学习非常方便。

vlc源码分析(三) 调用live555接收RTSP数据的更多相关文章

  1. zookeeper源码分析三LEADER与FOLLOWER同步数据流程

    根据二)中的分析,如果一台zookeeper服务器成为集群中的leader,那么一定是当前所有服务器中保存数据最多的服务器,所以在这台服务器成为leader之后,首先要做的事情就是与集群中的其它服务器 ...

  2. vlc源码分析(七) 调试学习HLS协议

    HTTP Live Streaming(HLS)是苹果公司提出来的流媒体传输协议.与RTP协议不同的是,HLS可以穿透某些允许HTTP协议通过的防火墙. 一.HLS播放模式 (1) 点播模式(Vide ...

  3. Dubbo 源码分析 - 服务调用过程

    注: 本系列文章已捐赠给 Dubbo 社区,你也可以在 Dubbo 官方文档中阅读本系列文章. 1. 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与引入.以及集群容错方面的代码.经过 ...

  4. tomcat源码分析(三)一次http请求的旅行-从Socket说起

    p { margin-bottom: 0.25cm; line-height: 120% } tomcat源码分析(三)一次http请求的旅行 在http请求旅行之前,我们先来准备下我们所需要的工具. ...

  5. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  6. VLC源码分析知识总结

    1.  关于#和## 1.1).在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号. 比如在早 ...

  7. Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析

    Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅<Tomcat源码分析二:先看看Tomcat的整体架构>一文 ...

  8. Vue.js 源码分析(三) 基础篇 模板渲染 el、emplate、render属性详解

    Vue有三个属性和模板有关,官网上是这样解释的: el ;提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标 template ;一个字符串模板作为 Vue 实例的标识使用.模板将会 ...

  9. ABP源码分析三:ABP Module

    Abp是一种基于模块化设计的思想构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modu ...

随机推荐

  1. JPA 实体映射

    一.实体基本映射 /* * @Entity:将领域对象标注为一个实体,表示保存到数据库中 * @@Table:保存到数据库中表名,默认表名为类名,可通过name属性命名 * * */ @Entity ...

  2. C++学习笔记: 智能指针

    c++ 智能指针学习新的 class Simple { public: Simple() { number = param; std::cout << "Simple: &quo ...

  3. css权威指南读书笔记-第10章浮动和定位

    这一章看了之后真是豁然开朗,之前虽然写了圣杯布局和双飞翼布局,有些地方也是模糊的,现在打算总结之后再写一遍. 以下都是从<css权威指南>中摘抄的我认为很有用的说明. 浮动元素 一个元素浮 ...

  4. 【VS2012】F5不能自动编译新修改

    VS2012正常使用时,如果文件有修改,按F5运行时,会自动对修改的文件进行重新编译.今天遇到的情况却并没有.度娘了下,网上有相同的问题,解决办法是“工具->选项->项目和解决方案-> ...

  5. Android链接蓝牙电子称

    蓝牙一直是我内心屏蔽的一个模块哈哈哈哈!然而今天我不得不正视它了,我百度了看了好多因为需要设备匹配所以设备不在没办法测试,几天之后设备到了.因为没有接触过,看到返回的打印出来的菱形方块就以为是错了.于 ...

  6. Mybatis学习第四天——Mybatis与Spring整合

    主要介绍mapper配置与mapper的扫描配置,使用dao层的配置这里不多说. 1.导包 1.1 Mybatis的jar包 1.2 Spring的jar包 1.3 Spring与Mybatis整合包 ...

  7. c# 调用 c dll 例子

    // case 1 传递 int* ///////////////////////////////////////////// extern “C” __declspec(dllexport) int ...

  8. Azure 中快速搭建 FTPS 服务

    FTP,FTPS 与 SFTP 的区别 FTP (File Transfer Protocol)是一种常用的文件传输协议,在日常工作中被广泛应用.不过,FTP 协议使用明文传输.如果文件传输发生在公网 ...

  9. 使用FontAwesome

    使用FontAwesome https://github.com/PrideChung/FontAwesomeKit 为什么要使用FontAwesome呢,其实,它的字体就是矢量图,无论是放大还是缩小 ...

  10. Ubuntu18.04 vmware环境下配置静态ip

    各种linux系统发行版本配置静态ip方法各不相同,dhcp获取的话ip可能会变动,如果用xshell等工具连接的时候还要改ip,很是麻烦: 参考了网上各种配置Ubuntu18.04配置静态ip的方法 ...