精华内容
下载资源
问答
  • 功能: HI3519AV100 sample_venc demo中移植RTSP服务器,实现VLC播放 SDK版本Hi3519AV100-2.0.1.0 交叉编译链:arm-himix200-linux 编译: make -f makefile 编译sample_venc等,编译生成lib19AMediaComm.a库。...
  • Rtsp 服务器

    2014-10-29 15:17:03
    Rtsp协议 写的一个服务器,比较清晰
  • 在windows环境下,通过vlc工具搭建RTSP 服务器指导文档
  • 期翼流服务器( smart_rtmpd ),是一款用于直播,录播性能卓越的服务器。如果您不理解,可以理解为和 nginx-rtmp, srs ,功能类似,但是性能比 nginx-rtmp 高很多,甚至比 srs 还要高的直播(录播)服务器,特点是...
  • rtsp服务器

    2013-05-07 15:55:33
    rtp发送h264编码文件,客户端支持通用播放器(如vlc,mplayer)
  • RTSP服务器端软件

    2018-08-07 17:45:29
    RTSP信令控制媒体流,支持500路客户端同时调阅,性能功能同时可以测试
  • 直接从IP摄像头获取数据(H265数据需稍微改动),然后作为服务器转发,支持多个摄像头同时连接。摄像头的地址和用户名密码在代码中更改即可。下载后可留言交流。
  • RTSP服务器 (C语言)

    2018-09-20 18:13:15
    hi3516A输出h264码流,rtsp服务器完成推流,环形缓冲,异步监测。
  • 直接从IP摄像头获取数据,然后作为服务器转发,支持多个摄像头同时连接。摄像头的地址和用户名密码在代码中更改即可。
  • RTSP服务器

    2019-09-22 19:10:47
    RTSP请求报文 RTSP响应报文 例子

    RTSP请求报文在这里插入图片描述
    RTSP响应报文在这里插入图片描述
    例子
    在这里插入图片描述
    状态码:

    1XX: Informational – 请求被接收到,继续处理。
    2XX: Success – 请求被成功的接收,解析并接受。
    3XX: Redirection – 为完成请求需要更多的操作。
    4XX: Client Error – 请求消息中包含语法错误或是不能够被有效执行。
    5XX: Server Error – 服务器响应失败,无法处理正确的有效的请求消息。
    

    200代表请求成功
    在这里插入图片描述
    RTSP信令集
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    6、PAUSE

    主要功能:
    暂停流媒体播放
    关键字段:无从服务器获取参数,目前主要获取时间范围
    保持RTSP连接(发送空的GET_PARAMETER)
    关键字段(电信扩展):
    x-Timeshift_Range: clock=20100318T021915.84Z-20100318T031915.84Z
    x-Timeshift_Current: clock=20100318T031915.84Z
    可能存在的问题:长时间Pause后,RTSP的TCP连接超时中断。解决办法——定期发送心跳包维持连接(参见GetParam)

    7、GET PARAMETER

    从服务器获取参数,目前主要获取时间范围
    保持RTSP连接(发送空的GET_PARAMETER)
    关键字段(电信扩展):
    x-Timeshift_Range: clock=20100318T021915.84Z-20100318T031915.84Z
    x-Timeshift_Current: clock=20100318T031915.84Z

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    Transport为AVP为UDP协议,TCP为TCP协议
    在这里插入图片描述
    点播流程图
    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • VLC搭建RTSP服务器的过程,本人亲测可用。唯一不足是只能搭建一路RTSP流,郁闷
  • ffmepg-rtsp服务器

    2019-04-25 16:31:00
    支持跨平台编译rtsp服务器自己需要跨平台编译dlib,live555,ffmpeg,ffmepg-live555编解码发布直播,不依赖第三放库(ngix等)做直播。支持抓取本地视频,视频摄像头,桌面,可用vlc等播放器观看测试。
  • 基于ffmpeg和live555开发的rtsp服务器,支持h264/h265编码,支持mp4等格式。测试文件放在执行文件当前目录,URL格式为:rtsp://ip:port/filename
  • 基于Live555写的RTSP服务器,有图形界面,支持采集USB摄像头的视频和麦克风的音频,支持编码参数设置。
  • linux下最小RTSP服务器实现

    热门讨论 2014-04-13 11:25:02
    linux环境下,RTSP服务器最小实现,C语言编写。稍加修改可移植到windows下,适合初学者。
  • VLC搭建RTSP服务器详细步骤,用于在Android下开发RTSP视频程序时进行测试
  • Pi-RTSP-Server:Raspberry Pi RTSP服务器用于实时视频翻译
  • RTSP 服务器C语言

    2019-09-10 15:28:26
    RTSP是实时流媒体传输协议,服务器和客户端之间通过RTSP协议实现握手和认证过程,通过RTP协议传输视频数据包,本资源通过C语言实现了RTSP服务器的功能。
  • rtsp服务器(c语言实现)

    热门讨论 2013-10-31 15:28:16
    rstp服务器,c语言实现,编译运行没有问题,是学习流媒体很不错的资料。吐血上传。
  • onvif前端模拟器+rtsp服务器+音视频

    热门讨论 2015-04-20 16:45:46
    1、运行rtsp服务器:./live555MediaServer 确保当前目录下有音视频文件。 2、运行onvif模拟器:./onvif_server_from-jovision 此时,Onvif Device Manager 或者 Onvif Device Test Tool 搜索到模拟摄像机,并能获取...
  • 从零开始写一个RTSP服务器(一)RTSP协议讲解

    万次阅读 多人点赞 2019-08-09 20:17:55
    从零开始写一个RTSP服务器系列 从零开始写一个RTSP服务器(一)不一样的RTSP协议讲解 从零开始写一个RTSP服务器(二)RTP传输H.264(待写) 从零开始写一个RTSP服务器(三)一个传输H.264的RTSP服务器(待写) 从零开始...

    从零开始写一个RTSP服务器系列

    ★我的开源项目-RtspServer

    从零开始写一个RTSP服务器(一)RTSP协议讲解

    从零开始写一个RTSP服务器(二)RTSP协议的实现

    从零开始写一个RTSP服务器(三)RTP传输H.264

    从零开始写一个RTSP服务器(四)一个传输H.264的RTSP服务器

    从零开始写一个RTSP服务器(五)RTP传输AAC

    从零开始写一个RTSP服务器(六)一个传输AAC的RTSP服务器

    从零开始写一个RTSP服务器(七)多播传输RTP包

    从零开始写一个RTSP服务器(八)一个多播的RTSP服务器

    从零开始写一个RTSP服务器(九)一个RTP OVER RTSP/TCP的RTSP服务器

    从零开始写一个RTSP服务器(一)不一样的RTSP协议讲解

    前言

    • 为什么要写这个系列?

      因为我自己在学习rtsp协议想自己从零写一个rtsp服务器的时候,由于rtsp比较复杂,所以觉得这个过程非常的困难,网上许多相关文章或模棱两可,或是复制粘贴。所以想写这样一个系列,来帮助想要学习rtsp协议或者想要从零写一个rtsp服务器的初学者

    • 本系列的文章特点

      并系列文章实现追求精简,能够让人明白rtsp协议的实现过程,不追求复杂和完美

      如果想要实现一个比较完善的rtsp服务器,可以参考我的开源项目-RtspServer

    言归正传,下面开始本系列的文章

    一、什么是RTSP协议?

    RTSP是一个实时传输流协议,是一个应用层的协议

    通常说的RTSP包括RTSP协议RTP协议RTCP协议

    对于这些协议的作用简单的理解如下

    RTSP协议:负责服务器与客户端之间的请求与响应

    RTP协议:负责传输媒体数据

    RTCP协议:在RTP传输过程中提供传输信息

    rtsp承载与rtp和rtcp之上,rtsp并不会发送媒体数据,而是使用rtp协议传输

    rtp并没有规定发送方式,可以选择udp发送或者tcp发送

    二、RTSP协议详解

    rtsp的交互过程就是客户端请求,服务器响应,下面看一看请求和响应的数据格式

    2.1 RTSP数据格式

    RTSP协议格式与HTTP协议格式类似

    • RTSP客户端的请求格式

      method url vesion\r\n
      CSeq: x\r\n
      xxx\r\n
      ...
      \r\n
      

      method:方法,表明这次请求的方法,rtsp定义了很多方法,稍后介绍

      url:格式一般为rtsp://ip:port/session,ip表主机ip,port表端口好,如果不写那么就是默认端口,rtsp的默认端口为554,session表明请求哪一个会话

      version:表示rtsp的版本,现在为RTSP/1.0

      CSeq:序列号,每个RTSP请求和响应都对应一个序列号,序列号是递增的

    • RTSP服务端的响应格式

      vesion 200 OK\r\n
      CSeq: x\r\n
      xxx\r\n
      ...
      \r\n
      

      version:表示rtsp的版本,现在为RTSP/1.0

      CSeq:序列号,这个必须与对应请求的序列号相同

    2.2 RTSP请求的常用方法

    方法描述
    OPTIONS获取服务端提供的可用方法
    DESCRIBE向服务端获取对应会话的媒体描述信息
    SETUP向服务端发起建立请求,建立连接会话
    PLAY向服务端发起播放请求
    TEARDOWN向服务端发起关闭连接会话请求

    2.3 RTSP交互过程

    有了上述的知识,我们下面来讲解一个RTSP的交互过程

    OPTIONS

    • C–>S

      OPTIONS rtsp://192.168.31.115:8554/live RTSP/1.0\r\n
      CSeq: 2\r\n
      \r\n
      

      客户端向服务器请求可用方法

    • S–>C

      RTSP/1.0 200 OK\r\n
      CSeq: 2\r\n
      Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY\r\n
      \r\n
      

      服务端回复客户端,当前可用方法OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY

    DESCRIBE

    • C–>S

      DESCRIBE rtsp://192.168.31.115:8554/live RTSP/1.0\r\n
      CSeq: 3\r\n
      Accept: application/sdp\r\n
      \r\n
      

      客户端向服务器请求媒体描述文件,格式为sdp

    • S–>C

      RTSP/1.0 200 OK\r\n
      CSeq: 3\r\n
      Content-length: 146\r\n
      Content-type: application/sdp\r\n
      \r\n
      
      v=0\r\n
      o=- 91565340853 1 in IP4 192.168.31.115\r\n
      t=0 0\r\n
      a=contol:*\r\n
      m=video 0 RTP/AVP 96\r\n
      a=rtpmap:96 H264/90000\r\n
      a=framerate:25\r\n
      a=control:track0\r\n
      

      服务器回复了sdp文件,这个文件告诉客户端当前服务器有哪些音视频流,有什么属性,具体稍后再讲解

      这里只需要直到客户端可以根据这些信息得知有哪些音视频流可以发送

    SETUP

    • C–>S

      SETUP rtsp://192.168.31.115:8554/live/track0 RTSP/1.0\r\n
      CSeq: 4\r\n
      Transport: RTP/AVP;unicast;client_port=54492-54493\r\n
      \r\n
      

      客户端发送建立请求,请求建立连接会话,准备接收音视频数据

      解析一下Transport: RTP/AVP;unicast;client_port=54492-54493\r\n

      RTP/AVP:表示RTP通过UDP发送,如果是RTP/AVP/TCP则表示RTP通过TCP发送

      unicast:表示单播,如果是multicast则表示多播

      client_port=54492-54493:由于这里希望采用的是RTP OVER UDP,所以客户端发送了两个用于传输数据的端口,客户端已经将这两个端口绑定到两个udp套接字上,54492表示是RTP端口,54493表示RTCP端口(RTP端口为某个偶数,RTCP端口为RTP端口+1)

    • S–>C

      RTSP/1.0 200 OK\r\n
      CSeq: 4\r\n
      Transport: RTP/AVP;unicast;client_port=54492-54493;server_port=56400-56401\r\n
      Session: 66334873\r\n
      \r\n
      

      服务端接收到请求之后,得知客户端要求采用RTP OVER UDP发送数据,单播客户端用于传输RTP数据的端口为54492,RTCP的端口为54493

      服务器也有两个udp套接字,绑定好两个端口,一个用于传输RTP,一个用于传输RTCP,这里的端口号为56400-56401

      之后客户端会使用54492-54493这两端口和服务器通过udp传输数据,服务器会使用56400-56401这两端口和这个客户端传输数据

    PLAY

    • C–>S

      PLAY rtsp://192.168.31.115:8554/live RTSP/1.0\r\n
      CSeq: 5\r\n
      Session: 66334873\r\n
      Range: npt=0.000-\r\n
      \r\n
      

      客户端请求播放媒体

    • S–>C

      RTSP/1.0 200 OK\r\n
      CSeq: 5\r\n
      Range: npt=0.000-\r\n
      Session: 66334873; timeout=60\r\n
      \r\n
      

      服务器回复之后,会开始使用RTP通过udp向客户端的54492端口发送数据

    TEARDOWN

    • C–>S

      TEARDOWN rtsp://192.168.31.115:8554/live RTSP/1.0\r\n
      CSeq: 6\r\n
      Session: 66334873\r\n
      \r\n
      
    • S–>C

      RTSP/1.0 200 OK\r\n
      CSeq: 6\r\n
      \r\n
      

    2.4 sdp格式

    我们上面避开没有讲sdp文件,这里来好好补一补

    sdp格式由多行的type=value组成

    sdp会话描述由一个会话级描述和多个媒体级描述组成。会话级描述的作用域是整个会话,媒体级描述描述的是一个视频流或者音频流

    会话级描述v=开始到第一个媒体级描述结束

    媒体级描述m=开始到下一个媒体级描述结束

    下面是上面示例的sdp文件,我们就来好好分析一下这个sdp文件

    v=0\r\n
    o=- 91565340853 1 in IP4 192.168.31.115\r\n
    t=0 0\r\n
    a=contol:*\r\n
    m=video 0 RTP/AVP 96\r\n
    a=rtpmap:96 H264/90000\r\n
    a=framerate:25\r\n
    a=control:track0\r\n
    

    这个示例的sdp文件包含一个会话级描述一个媒体级描述,分别如下

    • 会话级描述

      v=0\r\n
      o=- 91565340853 1 IN IP4 192.168.31.115\r\n
      t=0 0\r\n
      a=contol:*\r\n
      

      v=0

      表示sdp的版本
      o=- 91565340853 1 IN IP4 192.168.31.115
      格式为 o=<用户名> <会话id> <会话版本> <网络类型><地址类型> <地址>
      用户名:-
      会话id:91565340853,表示rtsp://192.168.31.115:8554/live请求中的live这个会话
      会话版本:1
      网络类型:IN,表示internet
      地址类型:IP4,表示ipv4
      地址:192.168.31.115,表示服务器的地址

    • 媒体级描述

      m=video 0 RTP/AVP 96\r\n
      a=rtpmap:96 H264/90000\r\n
      a=framerate:25\r\n
      a=control:track0\r\n
      

      m=video 0 RTP/AVP 96\r\n

      格式为 m=<媒体类型> <端口号> <传输协议> <媒体格式 >
      媒体类型:video

      端口号:0,为什么是0?因为上面在SETUP过程会告知端口号,所以这里就不需要了

      传输协议:RTP/AVP,表示RTP OVER UDP,如果是RTP/AVP/TCP,表示RTP OVER TCP

      媒体格式:表示负载类型(payload type),一般使用96表示H.264

      a=rtpmap:96 H264/90000

      格式为a=rtpmap:<媒体格式><编码格式>/<时钟频率>

      a=framerate:25

      表示帧率

      a=control:track0

      表示这路视频流在这个会话中的编号

    三、RTP协议

    3.1 RTP包格式

    rtp包由rtp头部和rtp荷载构成

    • RTP头部

    在这里插入图片描述

    版本号(V):2Bit,用来标志使用RTP版本

    填充位§:1Bit,如果该位置位,则该RTP包的尾部就包含填充的附加字节

    扩展位(X):1Bit,如果该位置位,则该RTP包的固定头部后面就跟着一个扩展头部

    CSRC技术器(CC):4Bit,含有固定头部后面跟着的CSRC的数据

    标记位(M):1Bit,该位的解释由配置文档来承担

    载荷类型(PT):7Bit,标识了RTP载荷的类型

    序列号(SN):16Bit,发送方在每发送完一个RTP包后就将该域的值增加1,可以由该域检测包的丢失及恢复

    ​ 包的序列。序列号的初始值是随机的

    时间戳:32比特,记录了该包中数据的第一个字节的采样时刻

    同步源标识符(SSRC):32比特,同步源就是RTP包源的来源。在同一个RTP会话中不能有两个相同的SSRC值

    贡献源列表(CSRC List):0-15项,每项32比特,这个不常用

    • rtp荷载

      rtp载荷为音频或者视频数据

    3.2 RTP OVER TCP

    RTP默认是采用UDP发送的,格式为RTP头+RTP载荷,如果是使用TCP,那么需要在RTP头之前再加上四个字节

    第一个字节:$,辨识符

    第二个字节:通道,在SETUP的过程中获取

    第三第四个字节: RTP包的大小,最多只能12位,第三个字节保存高4位,第四个字节保存低8位

    四、RTCP

    RTCP用于在RTP传输过程中提供传输信息,可以报道RTP传输情况,还可以用来音视频同步,这里就不详细讲解了

    展开全文
  • 在RK3399上实现的RTSP服务器执行文件和相关库,LIVE555是服务器实现框架,librtsp是根据框架编写的RTSP处理逻辑,支持264,265,AAC
  • 从零开始写一个RTSP服务器系列 ★我的开源项目-RtspServer 从零开始写一个RTSP服务器(一)不一样的RTSP协议讲解 从零开始写一个RTSP服务器(二)RTSP协议的实现 从零开始写一个RTSP服务器(三)RTP传输H.264 从零...

    从零开始写一个RTSP服务器系列

    ★我的开源项目-RtspServer

    从零开始写一个RTSP服务器(一)RTSP协议讲解

    从零开始写一个RTSP服务器(二)RTSP协议的实现

    从零开始写一个RTSP服务器(三)RTP传输H.264

    从零开始写一个RTSP服务器(四)一个传输H.264的RTSP服务器

    从零开始写一个RTSP服务器(五)RTP传输AAC

    从零开始写一个RTSP服务器(六)一个传输AAC的RTSP服务器

    从零开始写一个RTSP服务器(七)多播传输RTP包

    从零开始写一个RTSP服务器(八)一个多播的RTSP服务器

    从零开始写一个RTSP服务器(九)一个RTP OVER RTSP/TCP的RTSP服务器

    从零开始写一个RTSP服务器(九)一个RTP OVER RTSP/TCP的RTSP服务器

    一、RTP OVER RTSP(TCP)的实现

    1.1 发送RTP包方式

    对于RTP OVER UDP 的实现,我们使用TCP连接来发送RTSP交互,然后创建新的UDP套接字来发送RTP包

    对于RTP OVER RTSP(TCP)来说,我们会复用使用原先发送RTSP的socket来发送RTP包

    1.2 如何区分RTSP、RTP、RTCP?

    如上面所说,我们复用发送RTSP交互的socket来发送RTP包和RTCP信息,那么对于客户端来说,如何区分这三种数据呢?

    我们将这三个分为两类,一类是RTSP,一类是RTP、RTCP

    发送RTSP信息的情况没有变化,还是更以前一样的方式

    发送RTP、RTCP包,在每个包前面都加上四个字节

    字节描述
    第一个字节‘$’,标识符,用于与RTSP区分
    第二个字节channel,用于区分RTP和RTCP
    第三和第四个字节RTP包的大小

    由此我们可知,第一个字节’$'用于与RTSP区分,第二个字节用于区分RTP和RTCP

    RTP和RTCP的channel是在RTSP的SETUP过程中,客户端发送给服务端的

    所以现在RTP的打包方式要在之前的每个RTP包前面加上四个字节,如下所示

    在这里插入图片描述

    二、RTP OVER RTSP(TCP)的RTSP交互过程

    OPTIONS

    • C–>S

      OPTIONS rtsp://127.0.0.1:8554/live RTSP/1.0\r\n
      CSeq: 2\r\n
      \r\n
      
    • S–>C

      RTSP/1.0 200 OK\r\n
      CSeq: 2\r\n
      Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY\r\n
      \r\n
      

    DESCRIBE

    • C–>S

      DESCRIBE rtsp://127.0.0.1:8554/live RTSP/1.0\r\n
      CSeq: 3\r\n
      Accept: application/sdp\r\n
      \r\n
      
    • S–>C

      RTSP/1.0 200 OK\r\n
      CSeq: 3\r\n
      Content-length: 146
      Content-type: application/sdp
      \r\n
      v=0
      o=- 91565667683 1 IN IP4 127.0.0.1
      t=0 0
      a=control:*
      m=video 0 RTP/AVP 96
      a=rtpmap:96 H264/90000
      a=framerate:25
      a=control:track0
      

    SETUP

    • C–>S

      SETUP rtsp://127.0.0.1:8554/live/track0 RTSP/1.0\r\n
      CSeq: 4\r\n
      Transport: RTP/AVP/TCP;unicast;interleaved=0-1\r\n
      \r\n
      

      RTP/AVP/TCP表示使用RTP OVER TCP,interleaved=0-1表示这个会话连接的RTP channel为0,RTCP channel为1

    • S–>C

      RTSP/1.0 200 OK\r\n
      CSeq: 4\r\n
      Transport: RTP/AVP/TCP;unicast;interleaved=0-1\r\n
      Session: 327b23c6\r\n
      \r\n
      

    PLAY

    • C–>S

      PLAY rtsp://127.0.0.1:8554/live RTSP/1.0\r\n
      CSeq: 5\r\n
      Session: 327b23c6\r\n
      Range: npt=0.000-\r\n
      \r\n
      
    • S–>C

      RTSP/1.0 200 OK\r\n
      CSeq: 5\r\n
      Range: npt=0.000-\r\n
      Session: 327b23c6; timeout=60\r\n
      \r\n
      

    三、实现过程

    3.1 RTP发包

    经过上面的介绍,我们知道RTP OVER TCP和RTP OVER UDP的RTP发包方式是不同的,RTP OVER TCP需要在整一个RTP包前面加上四个字节,为此我修改了RTP发包部分

    • RTP Packet 结构体

      /*
       * 作者:_JT_
       * 博客:https://blog.csdn.net/weixin_42462202
       */
       
      struct RtpPacket
      {
          char header[4];
          struct RtpHeader rtpHeader;
          uint8_t payload[0];
      };
      

      header:前四个字节

      rtpHeader:RTP包头部

      payload:RTP包载荷

    • RTP的发包函数修改

      每次发包前都需要添加四个字节的头,并且通过tcp发送

      /*
       * 作者:_JT_
       * 博客:https://blog.csdn.net/weixin_42462202
       */
       
      rtpSendPacket()
      {
          ...
          
          rtpPacket->header[0] = '$';
          rtpPacket->header[1] = rtpChannel;
          rtpPacket->header[2] = (size & 0xFF00 ) >> 8;
          rtpPacket->header[3] = size & 0xFF;
          
          ...
          send(...);
          ...
      }
      

    3.2 服务器的实现

    下面开始介绍RTP OVER TCP服务器的实现过程

    3.2.1 创建socket套接字

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
    
    main()
    {
        serverSockfd = createTcpSocket();
        bindSocketAddr(serverSockfd, "0.0.0.0", SERVER_PORT);
        listen(serverSockfd, 10);
        ...
        while(1)
        {
            ...
        }
    }
    

    3.2.2 接收客户端连接

    main()
    {
        ...
        while(1)
        {
            acceptClient(serverSockfd, clientIp, &clientPort);
            doClient(clientSockfd, clientIp, clientPort);
        }
    }
    

    接收客户端连接后,执行doClient处理客户端请求

    3.2.3 解析请求

    接收请求后,解析请求,先解析方法,再解析序列号,如果是SETUP,那么就将RTP通道和RTCP通道解析出来

    然后处理不同的请求方法

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
    
    doClient()
    {
        while(1)
        {
            /* 接收数据 */
            recv(clientSockfd, rBuf, BUF_MAX_SIZE, 0);
    
            /* 解析命令 */
            sscanf(line, "%s %s %s\r\n", method, url, version);
            ...
            sscanf(line, "CSeq: %d\r\n", &cseq);
            ...
            if(!strcmp(method, "SETUP"))
                sscanf(line, "Transport: RTP/AVP/TCP;unicast;interleaved=%hhu-%hhu\r\n",
                       &rtpChannel, &rtcpChannel);
    
            /* 处理请求 */
            if(!strcmp(method, "OPTIONS"))
                handleCmd_OPTIONS(sBuf, cseq)
            else if(!strcmp(method, "DESCRIBE"))
                handleCmd_DESCRIBE(sBuf, cseq, url);
            else if(!strcmp(method, "SETUP"))
                handleCmd_SETUP(sBuf, cseq, rtpChannel);
            else if(!strcmp(method, "PLAY"))
                handleCmd_PLAY(sBuf, cseq);
    
            send(clientSockfd, sBuf, strlen(sBuf), 0);
        }
    }
    

    3.2.4 处理请求

    • OPTIONS

      handleCmd_OPTIONS()
      {
          sprintf(result, "RTSP/1.0 200 OK\r\n"
                          "CSeq: %d\r\n"
                          "Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n"
                          "\r\n",
                          cseq);
      }
      
    • DESCRIBE

      handleCmd_DESCRIBE()
      {
          sprintf(sdp, "v=0\r\n"
                       "o=- 9%ld 1 IN IP4 %s\r\n"
                       "t=0 0\r\n"
                       "a=control:*\r\n"
                       "m=video 0 RTP/AVP 96\r\n"
                       "a=rtpmap:96 H264/90000\r\n"
                       "a=control:track0\r\n",
                       time(NULL), localIp);
          
          sprintf(result, "RTSP/1.0 200 OK\r\nCSeq: %d\r\n"
                          "Content-Base: %s\r\n"
                          "Content-type: application/sdp\r\n"
                          "Content-length: %d\r\n\r\n"
                          "%s",
                          cseq,
                          url,
                          strlen(sdp),
                          sdp);
      }
      
    • SETUP

      handleCmd_SETUP()
      {
         sprintf(result, "RTSP/1.0 200 OK\r\n"
                          "CSeq: %d\r\n"
                          "Transport: RTP/AVP/TCP;unicast;interleaved=%hhu-%hhu\r\n"
                          "Session: 66334873\r\n"
                          "\r\n",
                          cseq,
                          rtpChannel,
                          rtpChannel+1
                          );
      }
      
    • PLAY

      handleCmd_PLAY()
      {
          sprintf(result, "RTSP/1.0 200 OK\r\n"
                          "CSeq: %d\r\n"
                          "Range: npt=0.000-\r\n"
                          "Session: 66334873; timeout=60\r\n\r\n",
                          cseq);
      }
      

    3.2.5 发送RTP包

    在发送完PLAY回复之后,开始发送RTP包

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
    
    doClient()
    {
        ...
        
            
    	while(1)
        {
            ...
             
            send(clientSockfd, sBuf, strlen(sBuf), 0);
            
            if(!strcmp(method, "PLAY"))
            {
                while(1)
                {
                    /* 获取一帧数据 */
                    getFrameFromH264File(fd, frame, 500000);
                    
                    /* 发送RTP包 */
                    rtpSendH264Frame(clientSockfd);
                }
            }
        }
    }
    

    3.3 源码

    rtsp_server.c

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <time.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <assert.h>
    
    #include "tcp_rtp.h"
    
    #define H264_FILE_NAME  "test.h264"
    #define SERVER_PORT     8554
    #define BUF_MAX_SIZE    (1024*1024)
    
    static int createTcpSocket()
    {
        int sockfd;
        int on = 1;
    
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(sockfd < 0)
            return -1;
    
        setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));
    
        return sockfd;
    }
    
    static int createUdpSocket()
    {
        int sockfd;
        int on = 1;
    
        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(sockfd < 0)
            return -1;
    
        setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));
    
        return sockfd;
    }
    
    static int bindSocketAddr(int sockfd, const char* ip, int port)
    {
        struct sockaddr_in addr;
    
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = inet_addr(ip);
    
        if(bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0)
            return -1;
    
        return 0;
    }
    
    static int acceptClient(int sockfd, char* ip, int* port)
    {
        int clientfd;
        socklen_t len = 0;
        struct sockaddr_in addr;
    
        memset(&addr, 0, sizeof(addr));
        len = sizeof(addr);
    
        clientfd = accept(sockfd, (struct sockaddr *)&addr, &len);
        if(clientfd < 0)
            return -1;
        
        strcpy(ip, inet_ntoa(addr.sin_addr));
        *port = ntohs(addr.sin_port);
    
        return clientfd;
    }
    
    static inline int startCode3(char* buf)
    {
        if(buf[0] == 0 && buf[1] == 0 && buf[2] == 1)
            return 1;
        else
            return 0;
    }
    
    static inline int startCode4(char* buf)
    {
        if(buf[0] == 0 && buf[1] == 0 && buf[2] == 0 && buf[3] == 1)
            return 1;
        else
            return 0;
    }
    
    static char* findNextStartCode(char* buf, int len)
    {
        int i;
    
        if(len < 3)
            return NULL;
    
        for(i = 0; i < len-3; ++i)
        {
            if(startCode3(buf) || startCode4(buf))
                return buf;
            
            ++buf;
        }
    
        if(startCode3(buf))
            return buf;
    
        return NULL;
    }
    
    static int getFrameFromH264File(int fd, char* frame, int size)
    {
        int rSize, frameSize;
        char* nextStartCode;
    
        if(fd < 0)
            return fd;
    
        rSize = read(fd, frame, size);
        if(!startCode3(frame) && !startCode4(frame))
            return -1;
        
        nextStartCode = findNextStartCode(frame+3, rSize-3);
        if(!nextStartCode)
        {
            //lseek(fd, 0, SEEK_SET);
            //frameSize = rSize;
            return -1;
        }
        else
        {
            frameSize = (nextStartCode-frame);
            lseek(fd, frameSize-rSize, SEEK_CUR);
        }
    
        return frameSize;
    }
    
    static int rtpSendH264Frame(int socket, int rtpChannel, struct RtpPacket* rtpPacket, uint8_t* frame, uint32_t frameSize)
    {
        uint8_t naluType; // nalu第一个字节
        int sendBytes = 0;
        int ret;
    
        naluType = frame[0];
    
        if (frameSize <= RTP_MAX_PKT_SIZE) // nalu长度小于最大包场:单一NALU单元模式
        {
            /*
             *   0 1 2 3 4 5 6 7 8 9
             *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             *  |F|NRI|  Type   | a single NAL unit ... |
             *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             */
            memcpy(rtpPacket->payload, frame, frameSize);
            ret = rtpSendPacket(socket, rtpChannel, rtpPacket, frameSize);
            if(ret < 0)
                return -1;
    
            rtpPacket->rtpHeader.seq++;
            sendBytes += ret;
            if ((naluType & 0x1F) == 7 || (naluType & 0x1F) == 8) // 如果是SPS、PPS就不需要加时间戳
                goto out;
        }
        else // nalu长度小于最大包场:分片模式
        {
            /*
             *  0                   1                   2
             *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
             * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             * | FU indicator  |   FU header   |   FU payload   ...  |
             * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             */
    
            /*
             *     FU Indicator
             *    0 1 2 3 4 5 6 7
             *   +-+-+-+-+-+-+-+-+
             *   |F|NRI|  Type   |
             *   +---------------+
             */
    
            /*
             *      FU Header
             *    0 1 2 3 4 5 6 7
             *   +-+-+-+-+-+-+-+-+
             *   |S|E|R|  Type   |
             *   +---------------+
             */
    
            int pktNum = frameSize / RTP_MAX_PKT_SIZE;       // 有几个完整的包
            int remainPktSize = frameSize % RTP_MAX_PKT_SIZE; // 剩余不完整包的大小
            int i, pos = 1;
    
            /* 发送完整的包 */
            for (i = 0; i < pktNum; i++)
            {
                rtpPacket->payload[0] = (naluType & 0x60) | 28;
                rtpPacket->payload[1] = naluType & 0x1F;
                
                if (i == 0) //第一包数据
                    rtpPacket->payload[1] |= 0x80; // start
                else if (remainPktSize == 0 && i == pktNum - 1) //最后一包数据
                    rtpPacket->payload[1] |= 0x40; // end
    
                memcpy(rtpPacket->payload+2, frame+pos, RTP_MAX_PKT_SIZE);
                ret = rtpSendPacket(socket, rtpChannel, rtpPacket, RTP_MAX_PKT_SIZE+2);
                if(ret < 0)
                    return -1;
    
                rtpPacket->rtpHeader.seq++;
                sendBytes += ret;
                pos += RTP_MAX_PKT_SIZE;
            }
    
            /* 发送剩余的数据 */
            if (remainPktSize > 0)
            {
                rtpPacket->payload[0] = (naluType & 0x60) | 28;
                rtpPacket->payload[1] = naluType & 0x1F;
                rtpPacket->payload[1] |= 0x40; //end
    
                memcpy(rtpPacket->payload+2, frame+pos, remainPktSize+2);
                ret = rtpSendPacket(socket, rtpChannel, rtpPacket, remainPktSize+2);
                if(ret < 0)
                    return -1;
    
                rtpPacket->rtpHeader.seq++;
                sendBytes += ret;
            }
        }
    
    out:
    
        return sendBytes;
    }
    
    static char* getLineFromBuf(char* buf, char* line)
    {
        while(*buf != '\n')
        {
            *line = *buf;
            line++;
            buf++;
        }
    
        *line = '\n';
        ++line;
        *line = '\0';
    
        ++buf;
        return buf; 
    }
    
    static int handleCmd_OPTIONS(char* result, int cseq)
    {
        sprintf(result, "RTSP/1.0 200 OK\r\n"
                        "CSeq: %d\r\n"
                        "Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n"
                        "\r\n",
                        cseq);
                    
        return 0;
    }
    
    static int handleCmd_DESCRIBE(char* result, int cseq, char* url)
    {
        char sdp[500];
        char localIp[100];
    
        sscanf(url, "rtsp://%[^:]:", localIp);
    
        sprintf(sdp, "v=0\r\n"
                     "o=- 9%ld 1 IN IP4 %s\r\n"
                     "t=0 0\r\n"
                     "a=control:*\r\n"
                     "m=video 0 RTP/AVP 96\r\n"
                     "a=rtpmap:96 H264/90000\r\n"
                     "a=control:track0\r\n",
                     time(NULL), localIp);
        
        sprintf(result, "RTSP/1.0 200 OK\r\nCSeq: %d\r\n"
                        "Content-Base: %s\r\n"
                        "Content-type: application/sdp\r\n"
                        "Content-length: %d\r\n\r\n"
                        "%s",
                        cseq,
                        url,
                        strlen(sdp),
                        sdp);
        
        return 0;
    }
    
    static int handleCmd_SETUP(char* result, int cseq, uint8_t rtpChannel)
    {
       sprintf(result, "RTSP/1.0 200 OK\r\n"
                        "CSeq: %d\r\n"
                        "Transport: RTP/AVP/TCP;unicast;interleaved=%hhu-%hhu\r\n"
                        "Session: 66334873\r\n"
                        "\r\n",
                        cseq,
                        rtpChannel,
                        rtpChannel+1
                        );
        
        return 0;
    }
    
    static int handleCmd_PLAY(char* result, int cseq)
    {
        sprintf(result, "RTSP/1.0 200 OK\r\n"
                        "CSeq: %d\r\n"
                        "Range: npt=0.000-\r\n"
                        "Session: 66334873; timeout=60\r\n\r\n",
                        cseq);
        
        return 0;
    }
    
    static void doClient(int clientSockfd, const char* clientIP, int clientPort)
    {
        char method[40];
        char url[100];
        char version[40];
        int cseq;
        char *bufPtr;
        char* rBuf = malloc(BUF_MAX_SIZE);
        char* sBuf = malloc(BUF_MAX_SIZE);
        char line[400];
        uint8_t rtpChannel;
        uint8_t rtcpChannel;
    
        while(1)
        {
            int recvLen;
    
            recvLen = recv(clientSockfd, rBuf, BUF_MAX_SIZE, 0);
            if(recvLen <= 0)
                goto out;
    
            rBuf[recvLen] = '\0';
            printf("---------------C->S--------------\n");
            printf("%s", rBuf);
    
            /* 解析方法 */
            bufPtr = getLineFromBuf(rBuf, line);
            if(sscanf(line, "%s %s %s\r\n", method, url, version) != 3)
            {
                printf("parse err\n");
                goto out;
            }
    
            /* 解析序列号 */
            bufPtr = getLineFromBuf(bufPtr, line);
            if(sscanf(line, "CSeq: %d\r\n", &cseq) != 1)
            {
                printf("parse err\n");
                goto out;
            }
    
            /* 如果是SETUP,那么就再解析channel */
            if(!strcmp(method, "SETUP"))
            {
                while(1)
                {
                    bufPtr = getLineFromBuf(bufPtr, line);
                    if(!strncmp(line, "Transport:", strlen("Transport:")))
                    {
                        sscanf(line, "Transport: RTP/AVP/TCP;unicast;interleaved=%hhu-%hhu\r\n",
                                        &rtpChannel, &rtcpChannel);
                        break;
                    }
                }
            }
    
            if(!strcmp(method, "OPTIONS"))
            {
                if(handleCmd_OPTIONS(sBuf, cseq))
                {
                    printf("failed to handle options\n");
                    goto out;
                }
            }
            else if(!strcmp(method, "DESCRIBE"))
            {
                if(handleCmd_DESCRIBE(sBuf, cseq, url))
                {
                    printf("failed to handle describe\n");
                    goto out;
                }
            }
            else if(!strcmp(method, "SETUP"))
            {
                if(handleCmd_SETUP(sBuf, cseq, rtpChannel))
                {
                    printf("failed to handle setup\n");
                    goto out;
                }
            }
            else if(!strcmp(method, "PLAY"))
            {
                if(handleCmd_PLAY(sBuf, cseq))
                {
                    printf("failed to handle play\n");
                    goto out;
                }
            }
            else
            {
                goto out;
            }
    
            printf("---------------S->C--------------\n");
            printf("%s", sBuf);
            send(clientSockfd, sBuf, strlen(sBuf), 0);
    
            /* 开始播放,发送RTP包 */
            if(!strcmp(method, "PLAY"))
            {
                int frameSize, startCode;
                char* frame = malloc(500000);
                struct RtpPacket* rtpPacket = (struct RtpPacket*)malloc(500000);
                int fd = open(H264_FILE_NAME, O_RDONLY);
                assert(fd > 0);
                rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_H264, 0,
                                0, 0, 0x88923423);
    
                printf("start play\n");
    
                while (1)
                {
                    frameSize = getFrameFromH264File(fd, frame, 500000);
                    if(frameSize < 0)
                    {
                        break;
                    }
    
                    if(startCode3(frame))
                        startCode = 3;
                    else
                        startCode = 4;
    
                    frameSize -= startCode;
    
                    rtpSendH264Frame(clientSockfd, rtpChannel, rtpPacket, frame+startCode, frameSize);
                    rtpPacket->rtpHeader.timestamp += 90000/25;
    
                    usleep(1000*1000/25);
                }
                free(frame);
                free(rtpPacket);
                goto out;
            }
    
        }
    out:
        printf("finish\n");
        close(clientSockfd);
        free(rBuf);
        free(sBuf);
    }
    
    int main(int argc, char* argv[])
    {
        int serverSockfd;
        int ret;
    
        serverSockfd = createTcpSocket();
        if(serverSockfd < 0)
        {
            printf("failed to create tcp socket\n");
            return -1;
        }
    
        ret = bindSocketAddr(serverSockfd, "0.0.0.0", SERVER_PORT);
        if(ret < 0)
        {
            printf("failed to bind addr\n");
            return -1;
        }
    
        ret = listen(serverSockfd, 10);
        if(ret < 0)
        {
            printf("failed to listen\n");
            return -1;
        }
    
        printf("rtsp://127.0.0.1:%d\n", SERVER_PORT);
    
        while(1)
        {
            int clientSockfd;
            char clientIp[40];
            int clientPort;
    
            clientSockfd = acceptClient(serverSockfd, clientIp, &clientPort);
            if(clientSockfd < 0)
            {
                printf("failed to accept client\n");
                return -1;
            }
    
            printf("accept client;client ip:%s,client port:%d\n", clientIp, clientPort);
    
            doClient(clientSockfd, clientIp, clientPort);
        }
    
        return 0;
    }
    

    tcp_rtp.h

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
    
    #ifndef _RTP_H_
    #define _RTP_H_
    #include <stdint.h>
    
    #define RTP_VESION              2
    
    #define RTP_PAYLOAD_TYPE_H264   96
    #define RTP_PAYLOAD_TYPE_AAC    97
    
    #define RTP_HEADER_SIZE         12
    #define RTP_MAX_PKT_SIZE        1400
    
    /*
     *
     *    0                   1                   2                   3
     *    7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0
     *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *   |V=2|P|X|  CC   |M|     PT      |       sequence number         |
     *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *   |                           timestamp                           |
     *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *   |           synchronization source (SSRC) identifier            |
     *   +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
     *   |            contributing source (CSRC) identifiers             |
     *   :                             ....                              :
     *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *
     */
    struct RtpHeader
    {
        /* byte 0 */
        uint8_t csrcLen:4;
        uint8_t extension:1;
        uint8_t padding:1;
        uint8_t version:2;
    
        /* byte 1 */
        uint8_t payloadType:7;
        uint8_t marker:1;
        
        /* bytes 2,3 */
        uint16_t seq;
        
        /* bytes 4-7 */
        uint32_t timestamp;
        
        /* bytes 8-11 */
        uint32_t ssrc;
    };
    
    struct RtpPacket
    {
        char header[4];
        struct RtpHeader rtpHeader;
        uint8_t payload[0];
    };
    
    void rtpHeaderInit(struct RtpPacket* rtpPacket, uint8_t csrcLen, uint8_t extension,
                        uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,
                       uint16_t seq, uint32_t timestamp, uint32_t ssrc);
    int rtpSendPacket(int socket, uint8_t rtpChannel, struct RtpPacket* rtpPacket, uint32_t dataSize);
    
    #endif //_RTP_H_
    

    tcp_rtp.c

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
    
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include "tcp_rtp.h"
    
    void rtpHeaderInit(struct RtpPacket* rtpPacket, uint8_t csrcLen, uint8_t extension,
                        uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,
                       uint16_t seq, uint32_t timestamp, uint32_t ssrc)
    {
        rtpPacket->rtpHeader.csrcLen = csrcLen;
        rtpPacket->rtpHeader.extension = extension;
        rtpPacket->rtpHeader.padding = padding;
        rtpPacket->rtpHeader.version = version;
        rtpPacket->rtpHeader.payloadType =  payloadType;
        rtpPacket->rtpHeader.marker = marker;
        rtpPacket->rtpHeader.seq = seq;
        rtpPacket->rtpHeader.timestamp = timestamp;
        rtpPacket->rtpHeader.ssrc = ssrc;
    }
    
    int rtpSendPacket(int socket, uint8_t rtpChannel, struct RtpPacket* rtpPacket, uint32_t dataSize)
    {
        int ret;
    
        rtpPacket->header[0] = '$';
        rtpPacket->header[1] = rtpChannel;
        rtpPacket->header[2] = ((dataSize+RTP_HEADER_SIZE) & 0xFF00 ) >> 8;
        rtpPacket->header[3] = (dataSize+RTP_HEADER_SIZE) & 0xFF;
    
        rtpPacket->rtpHeader.seq = htons(rtpPacket->rtpHeader.seq);
        rtpPacket->rtpHeader.timestamp = htonl(rtpPacket->rtpHeader.timestamp);
        rtpPacket->rtpHeader.ssrc = htonl(rtpPacket->rtpHeader.ssrc);
    
        ret = send(socket, (void*)rtpPacket, dataSize+RTP_HEADER_SIZE+4, 0);
    
        rtpPacket->rtpHeader.seq = ntohs(rtpPacket->rtpHeader.seq);
        rtpPacket->rtpHeader.timestamp = ntohl(rtpPacket->rtpHeader.timestamp);
        rtpPacket->rtpHeader.ssrc = ntohl(rtpPacket->rtpHeader.ssrc);
    
        return ret;
    }
    

    四、测试

    rtsp_server.ctcp_rtp.htcp_rtp.c保存下来

    编译运行,程序默认打开test.h264,如果你没有视频源的话,可以从RtspServer的example目录下获取

    # gcc rtsp_server.c tcp_rtp.c
    # ./a.out
    

    运行后得到一个url

    rtsp://127.0.0.1:8554
    

    如何启动RTP OVER TCP?

    需要设置vlc的模式

    打开工具>>首选项>>输入/编解码器>>live555 流传输>>RTP over RTSP(TCP)

    然后选择RTP over RTSP(TCP)

    点击保存

    在这里插入图片描述

    输入url

    • 运行效果

      在这里插入图片描述

    可算把这个系列写完了,还真是累,当然希望对于学习RTSP协议的人有所帮助,这也是我的初衷

    展开全文
  • NVRServer:一个简单的rtsp服务器,可以使用“ rtsp:”从摄像机提取流,将视频另存为ts流,并为rtsp服务器提供实时流和本地视频,
  • ffmpeg直接采集屏幕;VLC的x264库进行压缩编码;live555作为服务器,侦听554端口,当有连接时,开始录制屏幕并发送。
  • 从零开始写一个RTSP服务器系列 从零开始写一个RTSP服务器(一)不一样的RTSP协议讲解 从零开始写一个RTSP服务器(二)RTSP协议的实现 从零开始写一个RTSP服务器(三)RTP传输H.264 从零开始写一个RTSP服务器(四)一...

    从零开始写一个RTSP服务器系列

    ★我的开源项目-RtspServer

    从零开始写一个RTSP服务器(一)RTSP协议讲解

    从零开始写一个RTSP服务器(二)RTSP协议的实现

    从零开始写一个RTSP服务器(三)RTP传输H.264

    从零开始写一个RTSP服务器(四)一个传输H.264的RTSP服务器

    从零开始写一个RTSP服务器(五)RTP传输AAC

    从零开始写一个RTSP服务器(六)一个传输AAC的RTSP服务器

    从零开始写一个RTSP服务器(七)多播传输RTP包

    从零开始写一个RTSP服务器(八)一个多播的RTSP服务器

    从零开始写一个RTSP服务器(九)一个RTP OVER RTSP/TCP的RTSP服务器

    从零开始写一个RTSP服务器(四)一个传输H.264的RTSP服务器


    本篇文章的目的:这篇文章的目的是实现一个播放H.264的RTSP服务器,相信如果是仔细看了前面几篇文章的话,这简直是易如反掌,就是把前面的东西拼到一起

    所以这篇文章并不会讲述新的知识,只是把前面的东西拼凑到一起,整理一下思路,最后给出一个示例,下面开始讲解一下我提供的这个示例的运行流程

    一、建立套接字

    一开始进入main函数后,就监听服务器tcp套接字,绑定端口号,然后开始监听

    然后再分别建立用于RTP和RTCP的udp套接字,绑定好端口

    然后进入循环中开始服务

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
    
    main()
    {
    	/* 创建服务器tcp套接字,绑定端口,监听 */
        serverSockfd = createTcpSocket();
    	bindSocketAddr(serverSockfd, "0.0.0.0", SERVER_PORT);
    	listen(serverSockfd, 10);
    	
        /* 建立用于RTP和RTCP的udp套接字,绑定好端口 */
        serverRtpSockfd = createUdpSocket();
        serverRtcpSockfd = createUdpSocket();
        bindSocketAddr(serverRtpSockfd, "0.0.0.0", SERVER_RTP_PORT);
        bindSocketAddr(serverRtcpSockfd, "0.0.0.0", SERVER_RTCP_PORT);
        
    	while(1)
    	{
            ...
    	}
    }
    

    二、接收客户端连接

    在while循环中接收客户端,然后调用doClient服务

    main()
    {
        ...
        while(1)
    	{
            clientSockfd = acceptClient(serverSockfd, clientIp, &clientPort);
            doClient(clientSockfd, clientIp, clientPort, serverRtpSockfd, serverRtcpSockfd);
    	}
    }
    

    上面其实就是一个TCP服务器的基本步骤,没有什么特别的

    下面来看一看doClient函数

    三、解析命令

    doClient就是一个while循环(这是一个同时只能服务一个客户的服务器),不断地接收命令解析命令,然后调用相应地操作

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
     
    doClient()
    {
    	while(1)
    	{
         	recv(clientSockfd, rBuf, BUF_MAX_SIZE, 0);   
            ...
            sscanf(line, "%s %s %s\r\n", method, url, version);
            ...
            sscanf(line, "CSeq: %d\r\n", &cseq)
            ...
    	}
    }
    

    四、处理请求

    在解析完客户端命令后,会调用相应的请求,处理完之后讲接收打印到sBuf中,然后发送给客户端

    doClient()
    {
    	while(1)
    	{
    		...
             /* 处理请求 */
             if(!strcmp(method, "OPTIONS"))
                 handleCmd_OPTIONS(sBuf, cseq);
            else if(!strcmp(method, "DESCRIBE"))
                handleCmd_DESCRIBE(sBuf, cseq, url);
            else if(!strcmp(method, "SETUP"))
                handleCmd_SETUP(sBuf, cseq, clientRtpPort);
            else if(!strcmp(method, "PLAY"))
                handleCmd_PLAY(sBuf, cseq);
            
            /* 放回结果 */
            send(clientSockfd, sBuf, strlen(sBuf), 0);
    	}
    }
    

    下面来看看各个请求的行动

    4.1 OPTIONS

    返回可用方法

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
     
    static int handleCmd_OPTIONS(char* result, int cseq)
    {
        sprintf(result, "RTSP/1.0 200 OK\r\n"
                        "CSeq: %d\r\n"
                        "Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n"
                        "\r\n",
                        cseq);
                    
        return 0;
    }
    

    4.2 DESCRIBE

    返回sdp文件信息,这是一个H.264的媒体描述信息,详细内容已经在前面文章讲解过,这里不再累赘

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
    
    static int handleCmd_DESCRIBE(char* result, int cseq, char* url)
    {
        char sdp[500];
        char localIp[100];
    
        sscanf(url, "rtsp://%[^:]:", localIp);
    
        sprintf(sdp, "v=0\r\n"
                     "o=- 9%ld 1 IN IP4 %s\r\n"
                     "t=0 0\r\n"
                     "a=control:*\r\n"
                     "m=video 0 RTP/AVP 96\r\n"
                     "a=rtpmap:96 H264/90000\r\n"
                     "a=control:track0\r\n",
                     time(NULL), localIp);
        
        sprintf(result, "RTSP/1.0 200 OK\r\nCSeq: %d\r\n"
                        "Content-Base: %s\r\n"
                        "Content-type: application/sdp\r\n"
                        "Content-length: %d\r\n\r\n"
                        "%s",
                        cseq,
                        url,
                        strlen(sdp),
                        sdp);
        
        return 0;
    }
    

    4.3 SETUP

    SETUP过程发送服务端RTP端口和RTCP端口

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
    
    static int handleCmd_SETUP(char* result, int cseq, int clientRtpPort,
                                int* localRtpSockfd, int* localRtcpSockfd)
    {
        sprintf(result, "RTSP/1.0 200 OK\r\n"
                        "CSeq: %d\r\n"
                        "Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d\r\n"
                        "Session: 66334873\r\n"
                        "\r\n",
                        cseq,
                        clientRtpPort,
                        clientRtpPort+1,
                        SERVER_RTP_PORT,
                        SERVER_RTCP_PORT);
        
        return 0;
    }
    

    4.4 PLAY

    PLAY操作回复后,会开始发送RTP包

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
    
    static int handleCmd_PLAY(char* result, int cseq)
    {
        sprintf(result, "RTSP/1.0 200 OK\r\n"
                        "CSeq: %d\r\n"
                        "Range: npt=0.000-\r\n"
                        "Session: 66334873; timeout=60\r\n\r\n",
                        cseq);
        
        return 0;
    }
    

    五、H.264 RTP打包发送

    从H.264文件中读取一个NALU,向客户端发送RTP包(目的IP,目的RTP端口)

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
    
    doClient()
    {
    	while(1)
    	{
    		...
    		...
    		...
    		if(!strcmp(method, "PLAY"))
    		{
                while(1)
                {
                    /* 获取一帧 */
                	frameSize = getFrameFromH264File(fd, frame, 500000);
                    
                    /* RTP打包发送 */
                	rtpSendH264Frame(localRtpSockfd, clientIP, clientRtpPort,
                					rtpPacket, frame+startCode, frameSize);
    		
                }
    	
            }
    
        }
    }
    

    下面看一看RTP打包过程,RTP打包实现了单NALU打包和分片打包

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
    
    static int rtpSendH264Frame(int socket, const char* ip, int16_t port,
                                struct RtpPacket* rtpPacket, uint8_t* frame, uint32_t frameSize)
    {
        /* 如果包比较小,则采用单NALU打包 */
        if (frameSize <= RTP_MAX_PKT_SIZE)
        {
            rtpSendPacket(socket, ip, port, rtpPacket, frameSize);
        }
        else //否则采用分片打包
        {   
            for (i = 0; i < pktNum; i++)
            {
                /* 填充载荷的前两个字节 */
                rtpPacket->payload[0] = (naluType & 0x60) | 28;
                rtpPacket->payload[1] = naluType & 0x1F;
                
                /* 发送RTP包 */
                rtpSendPacket(socket, ip, port, rtpPacket, RTP_MAX_PKT_SIZE+2);
            }
        }
    }
    

    六、源码

    我也懒得建个Git仓了,源码就直接贴到这里吧,虽然有点长,嘻嘻

    总共有3个文件,h264_rtsp_server.crtp.crtp.h

    h264_rtsp_server.c

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <time.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <assert.h>
    
    #include "rtp.h"
    
    #define H264_FILE_NAME   "test.h264"
    #define SERVER_PORT      8554
    #define SERVER_RTP_PORT  55532
    #define SERVER_RTCP_PORT 55533
    #define BUF_MAX_SIZE     (1024*1024)
    
    static int createTcpSocket()
    {
        int sockfd;
        int on = 1;
    
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(sockfd < 0)
            return -1;
    
        setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));
    
        return sockfd;
    }
    
    static int createUdpSocket()
    {
        int sockfd;
        int on = 1;
    
        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(sockfd < 0)
            return -1;
    
        setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));
    
        return sockfd;
    }
    
    static int bindSocketAddr(int sockfd, const char* ip, int port)
    {
        struct sockaddr_in addr;
    
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = inet_addr(ip);
    
        if(bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0)
            return -1;
    
        return 0;
    }
    
    static int acceptClient(int sockfd, char* ip, int* port)
    {
        int clientfd;
        socklen_t len = 0;
        struct sockaddr_in addr;
    
        memset(&addr, 0, sizeof(addr));
        len = sizeof(addr);
    
        clientfd = accept(sockfd, (struct sockaddr *)&addr, &len);
        if(clientfd < 0)
            return -1;
        
        strcpy(ip, inet_ntoa(addr.sin_addr));
        *port = ntohs(addr.sin_port);
    
        return clientfd;
    }
    
    static inline int startCode3(char* buf)
    {
        if(buf[0] == 0 && buf[1] == 0 && buf[2] == 1)
            return 1;
        else
            return 0;
    }
    
    static inline int startCode4(char* buf)
    {
        if(buf[0] == 0 && buf[1] == 0 && buf[2] == 0 && buf[3] == 1)
            return 1;
        else
            return 0;
    }
    
    static char* findNextStartCode(char* buf, int len)
    {
        int i;
    
        if(len < 3)
            return NULL;
    
        for(i = 0; i < len-3; ++i)
        {
            if(startCode3(buf) || startCode4(buf))
                return buf;
            
            ++buf;
        }
    
        if(startCode3(buf))
            return buf;
    
        return NULL;
    }
    
    static int getFrameFromH264File(int fd, char* frame, int size)
    {
        int rSize, frameSize;
        char* nextStartCode;
    
        if(fd < 0)
            return fd;
    
        rSize = read(fd, frame, size);
        if(!startCode3(frame) && !startCode4(frame))
            return -1;
        
        nextStartCode = findNextStartCode(frame+3, rSize-3);
        if(!nextStartCode)
        {
            //lseek(fd, 0, SEEK_SET);
            //frameSize = rSize;
            return -1;
        }
        else
        {
            frameSize = (nextStartCode-frame);
            lseek(fd, frameSize-rSize, SEEK_CUR);
        }
    
        return frameSize;
    }
    
    static int rtpSendH264Frame(int socket, const char* ip, int16_t port,
                                struct RtpPacket* rtpPacket, uint8_t* frame, uint32_t frameSize)
    {
        uint8_t naluType; // nalu第一个字节
        int sendBytes = 0;
        int ret;
    
        naluType = frame[0];
    
        if (frameSize <= RTP_MAX_PKT_SIZE) // nalu长度小于最大包场:单一NALU单元模式
        {
            /*
             *   0 1 2 3 4 5 6 7 8 9
             *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             *  |F|NRI|  Type   | a single NAL unit ... |
             *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             */
            memcpy(rtpPacket->payload, frame, frameSize);
            ret = rtpSendPacket(socket, ip, port, rtpPacket, frameSize);
            if(ret < 0)
                return -1;
    
            rtpPacket->rtpHeader.seq++;
            sendBytes += ret;
            if ((naluType & 0x1F) == 7 || (naluType & 0x1F) == 8) // 如果是SPS、PPS就不需要加时间戳
                goto out;
        }
        else // nalu长度小于最大包场:分片模式
        {
            /*
             *  0                   1                   2
             *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
             * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             * | FU indicator  |   FU header   |   FU payload   ...  |
             * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             */
    
            /*
             *     FU Indicator
             *    0 1 2 3 4 5 6 7
             *   +-+-+-+-+-+-+-+-+
             *   |F|NRI|  Type   |
             *   +---------------+
             */
    
            /*
             *      FU Header
             *    0 1 2 3 4 5 6 7
             *   +-+-+-+-+-+-+-+-+
             *   |S|E|R|  Type   |
             *   +---------------+
             */
    
            int pktNum = frameSize / RTP_MAX_PKT_SIZE;       // 有几个完整的包
            int remainPktSize = frameSize % RTP_MAX_PKT_SIZE; // 剩余不完整包的大小
            int i, pos = 1;
    
            /* 发送完整的包 */
            for (i = 0; i < pktNum; i++)
            {
                rtpPacket->payload[0] = (naluType & 0x60) | 28;
                rtpPacket->payload[1] = naluType & 0x1F;
                
                if (i == 0) //第一包数据
                    rtpPacket->payload[1] |= 0x80; // start
                else if (remainPktSize == 0 && i == pktNum - 1) //最后一包数据
                    rtpPacket->payload[1] |= 0x40; // end
    
                memcpy(rtpPacket->payload+2, frame+pos, RTP_MAX_PKT_SIZE);
                ret = rtpSendPacket(socket, ip, port, rtpPacket, RTP_MAX_PKT_SIZE+2);
                if(ret < 0)
                    return -1;
    
                rtpPacket->rtpHeader.seq++;
                sendBytes += ret;
                pos += RTP_MAX_PKT_SIZE;
            }
    
            /* 发送剩余的数据 */
            if (remainPktSize > 0)
            {
                rtpPacket->payload[0] = (naluType & 0x60) | 28;
                rtpPacket->payload[1] = naluType & 0x1F;
                rtpPacket->payload[1] |= 0x40; //end
    
                memcpy(rtpPacket->payload+2, frame+pos, remainPktSize+2);
                ret = rtpSendPacket(socket, ip, port, rtpPacket, remainPktSize+2);
                if(ret < 0)
                    return -1;
    
                rtpPacket->rtpHeader.seq++;
                sendBytes += ret;
            }
        }
    
    out:
    
        return sendBytes;
    }
    
    static char* getLineFromBuf(char* buf, char* line)
    {
        while(*buf != '\n')
        {
            *line = *buf;
            line++;
            buf++;
        }
    
        *line = '\n';
        ++line;
        *line = '\0';
    
        ++buf;
        return buf; 
    }
    
    static int handleCmd_OPTIONS(char* result, int cseq)
    {
        sprintf(result, "RTSP/1.0 200 OK\r\n"
                        "CSeq: %d\r\n"
                        "Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n"
                        "\r\n",
                        cseq);
                    
        return 0;
    }
    
    static int handleCmd_DESCRIBE(char* result, int cseq, char* url)
    {
        char sdp[500];
        char localIp[100];
    
        sscanf(url, "rtsp://%[^:]:", localIp);
    
        sprintf(sdp, "v=0\r\n"
                     "o=- 9%ld 1 IN IP4 %s\r\n"
                     "t=0 0\r\n"
                     "a=control:*\r\n"
                     "m=video 0 RTP/AVP 96\r\n"
                     "a=rtpmap:96 H264/90000\r\n"
                     "a=control:track0\r\n",
                     time(NULL), localIp);
        
        sprintf(result, "RTSP/1.0 200 OK\r\nCSeq: %d\r\n"
                        "Content-Base: %s\r\n"
                        "Content-type: application/sdp\r\n"
                        "Content-length: %d\r\n\r\n"
                        "%s",
                        cseq,
                        url,
                        strlen(sdp),
                        sdp);
        
        return 0;
    }
    
    static int handleCmd_SETUP(char* result, int cseq, int clientRtpPort)
    {
        sprintf(result, "RTSP/1.0 200 OK\r\n"
                        "CSeq: %d\r\n"
                        "Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d\r\n"
                        "Session: 66334873\r\n"
                        "\r\n",
                        cseq,
                        clientRtpPort,
                        clientRtpPort+1,
                        SERVER_RTP_PORT,
                        SERVER_RTCP_PORT);
        
        return 0;
    }
    
    static int handleCmd_PLAY(char* result, int cseq)
    {
        sprintf(result, "RTSP/1.0 200 OK\r\n"
                        "CSeq: %d\r\n"
                        "Range: npt=0.000-\r\n"
                        "Session: 66334873; timeout=60\r\n\r\n",
                        cseq);
        
        return 0;
    }
    
    static void doClient(int clientSockfd, const char* clientIP, int clientPort,
                                            int serverRtpSockfd, int serverRtcpSockfd)
    {
        char method[40];
        char url[100];
        char version[40];
        int cseq;
        int clientRtpPort, clientRtcpPort;
        char *bufPtr;
        char* rBuf = malloc(BUF_MAX_SIZE);
        char* sBuf = malloc(BUF_MAX_SIZE);
        char line[400];
    
        while(1)
        {
            int recvLen;
    
            recvLen = recv(clientSockfd, rBuf, BUF_MAX_SIZE, 0);
            if(recvLen <= 0)
                goto out;
    
            rBuf[recvLen] = '\0';
            printf("---------------C->S--------------\n");
            printf("%s", rBuf);
    
            /* 解析方法 */
            bufPtr = getLineFromBuf(rBuf, line);
            if(sscanf(line, "%s %s %s\r\n", method, url, version) != 3)
            {
                printf("parse err\n");
                goto out;
            }
    
            /* 解析序列号 */
            bufPtr = getLineFromBuf(bufPtr, line);
            if(sscanf(line, "CSeq: %d\r\n", &cseq) != 1)
            {
                printf("parse err\n");
                goto out;
            }
    
            /* 如果是SETUP,那么就再解析client_port */
            if(!strcmp(method, "SETUP"))
            {
                while(1)
                {
                    bufPtr = getLineFromBuf(bufPtr, line);
                    if(!strncmp(line, "Transport:", strlen("Transport:")))
                    {
                        sscanf(line, "Transport: RTP/AVP;unicast;client_port=%d-%d\r\n",
                                        &clientRtpPort, &clientRtcpPort);
                        break;
                    }
                }
            }
    
            if(!strcmp(method, "OPTIONS"))
            {
                if(handleCmd_OPTIONS(sBuf, cseq))
                {
                    printf("failed to handle options\n");
                    goto out;
                }
            }
            else if(!strcmp(method, "DESCRIBE"))
            {
                if(handleCmd_DESCRIBE(sBuf, cseq, url))
                {
                    printf("failed to handle describe\n");
                    goto out;
                }
            }
            else if(!strcmp(method, "SETUP"))
            {
                if(handleCmd_SETUP(sBuf, cseq, clientRtpPort))
                {
                    printf("failed to handle setup\n");
                    goto out;
                }
            }
            else if(!strcmp(method, "PLAY"))
            {
                if(handleCmd_PLAY(sBuf, cseq))
                {
                    printf("failed to handle play\n");
                    goto out;
                }
            }
            else
            {
                goto out;
            }
    
            printf("---------------S->C--------------\n");
            printf("%s", sBuf);
            send(clientSockfd, sBuf, strlen(sBuf), 0);
    
            /* 开始播放,发送RTP包 */
            if(!strcmp(method, "PLAY"))
            {
                int frameSize, startCode;
                char* frame = malloc(500000);
                struct RtpPacket* rtpPacket = (struct RtpPacket*)malloc(500000);
                int fd = open(H264_FILE_NAME, O_RDONLY);
                assert(fd > 0);
                rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_H264, 0,
                                0, 0, 0x88923423);
    
                printf("start play\n");
                printf("client ip:%s\n", clientIP);
                printf("client port:%d\n", clientRtpPort);
    
                while (1)
                {
                    frameSize = getFrameFromH264File(fd, frame, 500000);
                    if(frameSize < 0)
                    {
                        break;
                    }
    
                    if(startCode3(frame))
                        startCode = 3;
                    else
                        startCode = 4;
    
                    frameSize -= startCode;
                    rtpSendH264Frame(serverRtpSockfd, clientIP, clientRtpPort,
                                        rtpPacket, frame+startCode, frameSize);
                    rtpPacket->rtpHeader.timestamp += 90000/25;
    
                    usleep(1000*1000/25);
                }
                free(frame);
                free(rtpPacket);
                goto out;
            }
    
        }
    out:
        printf("finish\n");
        close(clientSockfd);
        free(rBuf);
        free(sBuf);
    }
    
    int main(int argc, char* argv[])
    {
        int serverSockfd;
        int serverRtpSockfd, serverRtcpSockfd;
    
        serverSockfd = createTcpSocket();
        if(serverSockfd < 0)
        {
            printf("failed to create tcp socket\n");
            return -1;
        }
    
        if(bindSocketAddr(serverSockfd, "0.0.0.0", SERVER_PORT) < 0)
        {
            printf("failed to bind addr\n");
            return -1;
        }
    
        if(listen(serverSockfd, 10) < 0)
        {
            printf("failed to listen\n");
            return -1;
        }
    
        serverRtpSockfd = createUdpSocket();
        serverRtcpSockfd = createUdpSocket();
        if(serverRtpSockfd < 0 || serverRtcpSockfd < 0)
        {
            printf("failed to create udp socket\n");
            return -1;
        }
    
        if(bindSocketAddr(serverRtpSockfd, "0.0.0.0", SERVER_RTP_PORT) < 0 ||
            bindSocketAddr(serverRtcpSockfd, "0.0.0.0", SERVER_RTCP_PORT) < 0)
        {
            printf("failed to bind addr\n");
            return -1;
        }
        
        printf("rtsp://127.0.0.1:%d\n", SERVER_PORT);
    
        while(1)
        {
            int clientSockfd;
            char clientIp[40];
            int clientPort;
    
            clientSockfd = acceptClient(serverSockfd, clientIp, &clientPort);
            if(clientSockfd < 0)
            {
                printf("failed to accept client\n");
                return -1;
            }
    
            printf("accept client;client ip:%s,client port:%d\n", clientIp, clientPort);
    
            doClient(clientSockfd, clientIp, clientPort, serverRtpSockfd, serverRtcpSockfd);
        }
    
        return 0;
    }
    

    rtp.c

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
    
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include "rtp.h"
    
    void rtpHeaderInit(struct RtpPacket* rtpPacket, uint8_t csrcLen, uint8_t extension,
                        uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,
                       uint16_t seq, uint32_t timestamp, uint32_t ssrc)
    {
        rtpPacket->rtpHeader.csrcLen = csrcLen;
        rtpPacket->rtpHeader.extension = extension;
        rtpPacket->rtpHeader.padding = padding;
        rtpPacket->rtpHeader.version = version;
        rtpPacket->rtpHeader.payloadType =  payloadType;
        rtpPacket->rtpHeader.marker = marker;
        rtpPacket->rtpHeader.seq = seq;
        rtpPacket->rtpHeader.timestamp = timestamp;
        rtpPacket->rtpHeader.ssrc = ssrc;
    }
    
    int rtpSendPacket(int socket, const char* ip, int16_t port, struct RtpPacket* rtpPacket, uint32_t dataSize)
    {
        struct sockaddr_in addr;
        int ret;
    
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = inet_addr(ip);
    
        rtpPacket->rtpHeader.seq = htons(rtpPacket->rtpHeader.seq);
        rtpPacket->rtpHeader.timestamp = htonl(rtpPacket->rtpHeader.timestamp);
        rtpPacket->rtpHeader.ssrc = htonl(rtpPacket->rtpHeader.ssrc);
    
        ret = sendto(socket, (void*)rtpPacket, dataSize+RTP_HEADER_SIZE, 0,
                        (struct sockaddr*)&addr, sizeof(addr));
    
        rtpPacket->rtpHeader.seq = ntohs(rtpPacket->rtpHeader.seq);
        rtpPacket->rtpHeader.timestamp = ntohl(rtpPacket->rtpHeader.timestamp);
        rtpPacket->rtpHeader.ssrc = ntohl(rtpPacket->rtpHeader.ssrc);
    
        return ret;
    }
    

    rtp.h

    /*
     * 作者:_JT_
     * 博客:https://blog.csdn.net/weixin_42462202
     */
    
    #ifndef _RTP_H_
    #define _RTP_H_
    #include <stdint.h>
    
    #define RTP_VESION              2
    
    #define RTP_PAYLOAD_TYPE_H264   96
    #define RTP_PAYLOAD_TYPE_AAC    97
    
    #define RTP_HEADER_SIZE         12
    #define RTP_MAX_PKT_SIZE        1400
    
    /*
     *
     *    0                   1                   2                   3
     *    7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0
     *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *   |V=2|P|X|  CC   |M|     PT      |       sequence number         |
     *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *   |                           timestamp                           |
     *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *   |           synchronization source (SSRC) identifier            |
     *   +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
     *   |            contributing source (CSRC) identifiers             |
     *   :                             ....                              :
     *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *
     */
    struct RtpHeader
    {
        /* byte 0 */
        uint8_t csrcLen:4;
        uint8_t extension:1;
        uint8_t padding:1;
        uint8_t version:2;
    
        /* byte 1 */
        uint8_t payloadType:7;
        uint8_t marker:1;
        
        /* bytes 2,3 */
        uint16_t seq;
        
        /* bytes 4-7 */
        uint32_t timestamp;
        
        /* bytes 8-11 */
        uint32_t ssrc;
    };
    
    struct RtpPacket
    {
        struct RtpHeader rtpHeader;
        uint8_t payload[0];
    };
    
    void rtpHeaderInit(struct RtpPacket* rtpPacket, uint8_t csrcLen, uint8_t extension,
                        uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,
                       uint16_t seq, uint32_t timestamp, uint32_t ssrc);
    int rtpSendPacket(int socket, const char* ip, int16_t port, struct RtpPacket* rtpPacket, uint32_t dataSize);
    
    #endif //_RTP_H_
    

    七、测试

    将三个文件保存下来,h264_rtsp_server.crtp.crtp.h

    编译

    # gcc h264_rtsp_server.c rtp.c
    

    运行,程序默认会打开test.h264的视频文件,如果你没有视频源的话,可以从RtspServer的example目录下获取

    # ./a.out
    

    运行后会打印一个url

    rtsp://127.0.0.1:8554
    

    在vlc中输入url,即可看到视频

    • 运行效果

      在这里插入图片描述

    展开全文
  • 搭建RTSP服务器

    千次阅读 2020-01-15 15:03:43
    网上现在公网的RTSP服务地址都已经失效,无法使用,可以自己搭建RTSP服务器用于测试,这边介绍使用VLC搭建RTSP服务器。 安装打开VLC RTSP媒体源选择 从本地媒体文件(本地媒体文件mp4/avi等) 从网络流(RTSP...
  • RTSP服务器支持: RTP / UDP单播 RTP / UDP多播 RTP / TCP RTP / RTSP / HTTP HTTP服务器支持(可使用-S选项获取可在传输流中混合的捕获格式): HLS MPEG-DASH 依存关系 liblivemedia-dev > live.2012.01.07...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 21,081
精华内容 8,432
关键字:

rtsp服务器