详解RTSP协媾和H264码流举行RTP封装,并分析实现传输h264的RSTP服务器 ...

打印 上一主题 下一主题

主题 690|帖子 690|积分 2070

媒介

本篇为实现一个基于UDP的RTP传输H264的RTSP服务器,并能够举行rtsp拉流播放,并会介绍RTSP协议,RTP协议,RTCP协议,H264码流以及如何将H264码流举行RTP封装。
一、有关协议

1.RTSP协议

RTSP协议是实时传播输协议,是TCP/IP协议体系中的一个应用层协议。该协议是一种双向实时数据传输协议,一样平常利用TCP或RTP完成数据传输。
最基本的交互过程如下:

OPTIONS方法:

c—>s
客户端向服务器端发送OPTIONS,请求可用的方法。
   OPTIONS rtsp://127.0.0.1:8888 RTSP/1.0
CSeq: 1
User-Agent: Lavf60.16.100
  s—>c
服务器端回复客户端,消息中包含当前可用的方法。
   RTSP/1.0 200 OK
CSeq: 1
Public: OPTIONS, DESCRIBE, SETUP, PLAY
  此方法主要用来扣问流媒体服务器支持哪些RTSP方法,如此请求及相应实例,说明支持OPTIONS, DESCRIBE, SETUP, PLAY。(此方法不是必须的,客户端可直接跳过此方法,直接发送DESCRIBE)
DESCRIBE方法:

c—>s
客户端向服务器请求媒体描述文件,一样平常通过rtsp开头的url来发起请求,格式为sdp。
   DESCRIBE rtsp://127.0.0.1:8888 RTSP/1.0
Accept: application/sdp
CSeq: 2
User-Agent: Lavf60.16.100
  s—>c
服务器回复客户端sdp文件,该文件告诉客户端服务器有哪些音视频流,有什么属性,如编解码器信息,帧率等。(本篇仅为视频流)
   RTSP/1.0 200 OK
CSeq: 2
Content-Base: rtsp://127.0.0.1:8888
Content-type: application/sdp
Content-length: 125
v=0
o=- 91712162505 1 IN IP4 127.0.0.1
t=0 0
a=control:*
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
a=control:track0
  客户端用此方法从服务器获取媒体流干系的信息,可以包含多个媒体流类型信息,通过SDP来描述和区分不同媒体类型的媒体源,客户端根据服务器支持的媒体源通过setup创建媒体流通道。(sdp后面会报告。)
SETUP方法:

c—>s
客户端向服务器端发起创建毗连请求,请求创建会话毗连,预备开始接收音视频数据,请求信息描述了期望音视频数据包基于UDP还是TCP传输,指定了RTP,RTCP端口,以及是单播还是组播等信息。
   SETUP rtsp://127.0.0.1:8888/track0 RTSP/1.0
Transport: RTP/AVP/UDP;unicast;client_port=9320-9321
CSeq: 3
User-Agent: Lavf60.16.100
  s—>c
服务器端收到客户端请求后,根据客户端请求的端标语确定发送控制数据的端口以及音视频数据的端口
   RTSP/1.0 200 OK
CSeq: 3
Transport: RTP/AVP;unicast;client_port=9320-9321;server_port=55532-55533
Session: 66334873
  此方法根据流媒体服务器返回的SDP描述信息,举行流媒体传输通道的创建,假如sdp描述多个媒体源,客户端可根据需要创建媒体传输链路,play方法后,服务器根据setup创建的媒体传播输通道发送媒体流,一样平常有RTP over udp和RTP over tcp两张流的传输方式,其setup有一定的区别。
PLAY方法:

c—>s
客户端向服务端请求播放媒体。
   PLAY rtsp://127.0.0.1:8888 RTSP/1.0
Range: npt=0.000-
CSeq: 4
User-Agent: Lavf60.16.100
Session: 66334873
  s—>c
   RTSP/1.0 200 OK
CSeq: 4
Range: npt=0.000-
Session: 66334873; timeout=10
  PLAY消息是告诉服务器端可以利用在SETUP消息中所指定的传输机置开始传送数据。
其他方法:

TEARDOWN方法:客户端发起表示停止媒体占用,并开释干系资源。
PAUSE方法:录像回放时会用到,用以暂停流媒体传输。
2.RTP协议

1.RTP协议

RTP协议:实时传输协议是一个网络传输协议,负责服务器与客户端之间传输媒体数据。
RTP由RTP报头和数据(有用载荷)两部门组成即:

2.RTP报头格式:

  1.       0                   1                   2                   3
  2.       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
  3.      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  4.      |V  |P|X|  CC   |M|     PT      |       sequence number         |
  5.      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  6.      |                           timestamp                           |
  7.      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  8.      |           synchronization source (SSRC) identifier            |
  9.      +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
  10.      |            contributing source (CSRC) identifiers             |
  11.      :                             ....                              :
  12.      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
复制代码
version RTP协议的版本号,占2位,当前协议版本号为2。
padding 添补标志,占1位,假如P=1,则在该报文的尾部添补一个或多个额外的八位组,它们不是有用载荷的一部门。
extension 占1位,假如X=1,则在RTP报头后跟有一个扩展报头
csrcLen CSRC计数器,占4位,指示CSRC 标识符的个数。
marker 标记,占1位,不同的有用载荷有不同的寄义,对于视频,标记一帧的竣事;对于音频,标记会话的开始。
ayloadType 有用载荷类型,占7位,用于说明RTP报文中有用载荷的类型,如GSM音频、JPEM图像等。
seq 占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。接收者通过序列号来检测报文丢失情况,重新排序报文,恢复数据。
timestamp 占32位,时戳反映了该RTP报文的第一个八位组的采样时刻。接收者利用时戳来盘算耽误和耽误抖动,并举行同步控制。
ssrc 占32位,用于标识同步信源。该标识符是随机选择的,参加同一视频集会的两个同步信源不能有雷同的SSRC。
csrc 每个特约信源标识符占32位,可以有0-15个。每个CSRC标识了包含在该RTP报文有用载荷中的所有特约信源。
3.RTCP协议

RTCP是实时传输控制协议,负责提供有关RTP传输质量的反馈,就是确保RTP传输的质量。与RTP一起作用,为RTP所提供的服务质量提供反馈,不提供数据加密或身份认证,但其伴生协议SRTCP(安全实时传输控制协议)
4.关于SDP

SDP是什么:

SDP是一个用来描述多媒体会话的应用层控制协议,它是一个基于文本的协议,常用在实时音视频中用来交换信息。
SDP格式
   type = value
  SDP会话级描述:

SDP会话描述由一个会话级描述和多个媒体级描述组成。
会话级的作用域是整个会话。其位置是从’v=’行开始到下一个媒体描述为止。
一个会话级描述包括:
   1.会话的名称和目标
2.会话存活时间
3.会话中包括多个媒体信息
  媒体级描述是对单个的媒体流举行描述,其位置是从’m=’行开始到下一个媒体描述为止。
多个媒体级描述包括:
   媒体格式
传输协议
纯属IP和端口
媒体负载类型
  会话级描述主要包含以下字段:

(上表来自于:h264和h265视频流SDP描述详解—壹零仓)
二、H264

1.H264简介

参考:H263简介—屁小猪
2.H264码流举行RTP封装

H.264由一个一个NALU组成,每个NALU之间用00 00 00 01或00 00 01分隔开,每个NALU的第一次字节由特别的寄义。
  1.             0 1 2  3 4 5 6 7
  2.            -+-+-+-+-+-+-+-+-
  3.            |F|NRI|  Type   |
  4.            +-+-+-+-+-+-+-+-+
复制代码
F:克制位,为1时表示语法错误。
NRI:参考级别,该值越大,该NAL越重要。
Type:NAL单元数据类型,标识该NAL单元的数据类型是哪种。
常用的nalu type:
   0x06 SEI
0x67 SPS
0x68 PPS
0x65 IDR
0x61 I帧
0x41 P帧
0x01 B帧
  3.H264三种RTP打包方式

单NALU打包:一个 RTP 包包含一个完整的 NALU 。
聚合打包:对于较小的 NALU ,一个 RTP 包可包含多个完整的 NALU 。
分片打包:对于较大的 NALU ,一个 NALU 可以分为多个 RTP 包发送。
分片打包

   分片打包:每个 RTP 包都有大小限定的,由于 RTP 一样平常都是利用 UDP 发送, UDP 没有流量控制,以是要限定每一次发送的大小,以是假如一个 NALU 的太大,就需要分成多个 RTP 包发送。
  由于RTP的头部是固定的,以是只能在RTP载荷中去添加额外信息来说明这个RTP包表示同一个NALU,即在RTP载荷开始有两个字节的信息,然后再是NALU的内容。

第一个字节位FU Indicator:
  1.             FU Indicator
  2.            0 1 2 3 4 5 6 7
  3.           +-+-+-+-+-+-+-+-+
  4.           |F|NRI|  Type   |
  5.           +---------------+
复制代码
高三位于NALU的高三位雷同。
Type = 28 (11100),表示该RTP包的一个分片。(H.264的规定)
第二个字节位FU Header
  1.               FU Header
  2.             0 1 2 3 4 5 6 7
  3.             +-+-+-+-+-+-+-+-+
  4.             |S|E|R|  Type   |
  5.             +---------------+
复制代码
S:标记该分片打包的第一个RTP包。
E:标记该分片打包的最后一个RTP包。
Type:NALU的type。
代码实现传输h264的RSTP服务器

   需先预备.h264文件
用ffmpeg下令行解封装.mp4生成.h264视频文件
    ffmpeg -i baozha.mp4 -codec copy -bsf: h264_mp4toannexb -f h264 baozha.h264
  


1.结构体

RTP头部结构体:

  1. struct RtpHeader
  2. {
  3.     uint8_t csrcLen : 4;//CSRC计数器,占4位,指示CSRC 标识符的个数。
  4.     uint8_t extension : 1;//占1位,如果X=1,则在RTP报头后跟有一个扩展报头。
  5.     uint8_t padding : 1;//填充标志,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。
  6.     uint8_t version : 2;//RTP协议的版本号,占2位,当前协议版本号为2。
  7.     uint8_t payloadType : 7;//有效载荷类型,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等。
  8.     uint8_t marker : 1;//标记,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。
  9.     uint16_t seq;//占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。接收者通过序列号来检测报文丢失情况,重新排序报文,恢复数据。
  10.     uint32_t timestamp;//占32位,时戳反映了该RTP报文的第一个八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制。
  11.     uint32_t ssrc;//占32位,用于标识同步信源。该标识符是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。
  12. };
复制代码
RTP包结构体:

  1. struct RtpPacket
  2. {
  3.     struct RtpHeader rtpHeader;
  4.     uint8_t payload[0];
  5. };
复制代码
初始化RTP包

  1. void rtpHeaderInit(struct RtpPacket* rtpPacket, uint8_t csrcLen, uint8_t extension,
  2.     uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,
  3.     uint16_t seq, uint32_t timestamp, uint32_t ssrc)
  4. {
  5.     rtpPacket->rtpHeader.csrcLen = csrcLen;
  6.     rtpPacket->rtpHeader.extension = extension;
  7.     rtpPacket->rtpHeader.padding = padding;
  8.     rtpPacket->rtpHeader.version = version;
  9.     rtpPacket->rtpHeader.payloadType = payloadType;
  10.     rtpPacket->rtpHeader.marker = marker;
  11.     rtpPacket->rtpHeader.seq = seq;
  12.     rtpPacket->rtpHeader.timestamp = timestamp;
  13.     rtpPacket->rtpHeader.ssrc = ssrc;
  14. }
复制代码
2.源代码

  1. #include<iostream>#include<stdlib.h>#include <stdint.h>#include <string.h>#include <time.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <WinSock2.h>#include <WS2tcpip.h>#include <windows.h>//#include "rtp.h"using namespace std;#pragma comment(lib, "ws2_32.lib")#pragma warning( disable : 4996 )#define H264_FILE_NAME   "D:/rtspceshiship/baozha.h264"#define AAC_FILE_NAME   "D:/rtspceshiship/baozha.aac"#define SERVER_PORT      8888#define SERVER_RTP_PORT  55532#define SERVER_RTCP_PORT 55533#define BUF_MAX_SIZE     (1024*1024)#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        1400struct RtpHeader
  2. {
  3.     uint8_t csrcLen : 4;//CSRC计数器,占4位,指示CSRC 标识符的个数。
  4.     uint8_t extension : 1;//占1位,如果X=1,则在RTP报头后跟有一个扩展报头。
  5.     uint8_t padding : 1;//填充标志,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。
  6.     uint8_t version : 2;//RTP协议的版本号,占2位,当前协议版本号为2。
  7.     uint8_t payloadType : 7;//有效载荷类型,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等。
  8.     uint8_t marker : 1;//标记,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。
  9.     uint16_t seq;//占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。接收者通过序列号来检测报文丢失情况,重新排序报文,恢复数据。
  10.     uint32_t timestamp;//占32位,时戳反映了该RTP报文的第一个八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制。
  11.     uint32_t ssrc;//占32位,用于标识同步信源。该标识符是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。
  12. };
  13. struct RtpPacket
  14. {
  15.     struct RtpHeader rtpHeader;
  16.     uint8_t payload[0];
  17. };
  18. 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;}void rtpHeaderInit(struct RtpPacket* rtpPacket, uint8_t csrcLen, uint8_t extension,
  19.     uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,
  20.     uint16_t seq, uint32_t timestamp, uint32_t ssrc)
  21. {
  22.     rtpPacket->rtpHeader.csrcLen = csrcLen;
  23.     rtpPacket->rtpHeader.extension = extension;
  24.     rtpPacket->rtpHeader.padding = padding;
  25.     rtpPacket->rtpHeader.version = version;
  26.     rtpPacket->rtpHeader.payloadType = payloadType;
  27.     rtpPacket->rtpHeader.marker = marker;
  28.     rtpPacket->rtpHeader.seq = seq;
  29.     rtpPacket->rtpHeader.timestamp = timestamp;
  30.     rtpPacket->rtpHeader.ssrc = ssrc;
  31. }
  32. int rtpSendPacketOverUdp(int serverRtpSockfd, 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(serverRtpSockfd, (char*)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;}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;}int getFrameFromH264File(FILE* fp, char* frame, int size) {    int rSize, frameSize;    char* nextStartCode;    if (!fp)        return -1;    rSize = fread(frame, 1, size, fp);    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);        fseek(fp, frameSize - rSize, SEEK_CUR);    }    return frameSize;}int rtpSendH264Frame(int serverRtpSockfd, const char* ip, int16_t port,    struct RtpPacket* rtpPacket, char* frame, uint32_t frameSize){    uint8_t naluType; // nalu第一个字节    int sendBytes = 0;    int ret;    naluType = frame[0];//每个nalu第一次字节都有特别寄义    printf("frameSize=%d \n", frameSize);    if (frameSize <= RTP_MAX_PKT_SIZE) // nalu长度小于最大包长:单一NALU单元模式    {        memcpy(rtpPacket->payload, frame, frameSize);        ret = rtpSendPacketOverUdp(serverRtpSockfd, 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长度小于最大包场:分片模式    {        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;//FU Indicatoe            rtpPacket->payload[1] = naluType & 0x1F;//FU Hrader 中间包就是这个00011111            if (i == 0) //第一包数据                rtpPacket->payload[1] |= 0x80; // start 第一包数据变为01011111            else if (remainPktSize == 0 && i == pktNum - 1) //最后一包数据                rtpPacket->payload[1] |= 0x40; // end 变为00111111            memcpy(rtpPacket->payload + 2, frame + pos, RTP_MAX_PKT_SIZE);            ret = rtpSendPacketOverUdp(serverRtpSockfd, 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 = rtpSendPacketOverUdp(serverRtpSockfd, ip, port, rtpPacket, remainPktSize + 2);            if (ret < 0)                return -1;            rtpPacket->rtpHeader.seq++;            sendBytes += ret;        }    }    rtpPacket->rtpHeader.timestamp += 90000 / 36;//时间戳的累加out:    return sendBytes;}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: %zu\r\n\r\n"        "%s",        cseq,        url,        strlen(sdp),        sdp);    return 0;}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;}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=10\r\n\r\n",        cseq);    return 0;}int Rtsp_H264_Replay(int clientSockfd, const char* clientIP, int clientPort){    char method[100];    char url[100];    char version[100];    int CSeq;    int Rtp_Serv_Sockfd = -1, Rtcp_Serv_Sockfd = -1;    int Rtp_Clnt_Port, Rtcp_Clnt_Port;    char* rBuf = (char*)malloc(BUF_MAX_SIZE);    char* sBuf = (char*)malloc(BUF_MAX_SIZE);    while (1)    {        int recvLen;        recvLen = recv(clientSockfd, rBuf, BUF_MAX_SIZE, 0);        rBuf[recvLen] = '\0';        cout << "c--->s" << endl;        printf("rBuf = %s \n",rBuf);        const char* sep = "\n";        char* line = strtok(rBuf, sep);//将rbuf举行拆分        while (line)        {            if (strstr(line, "OPTIONS") || strstr(line, "DESCRIBE") || strstr(line, "SETUP") || strstr(line, "PLAY"))            {                if (sscanf(line, "%s %s %s\r\n", method, url, version) != 3) {                    cout << "解析出错" << endl;                }            }            else if (strstr(line, "CSeq")) {                if (sscanf(line, "CSeq: %d\r\n", &CSeq) != 1) {                    cout << "Cseq 解析出错" << endl;                }            }            else if (!strncmp(line, "Transport:", strlen("Transport:"))) {                // Transport: RTP/AVP/UDP;unicast;client_port=13358-13359                // Transport: RTP/AVP;unicast;client_port=13358-13359                if (sscanf(line, "Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n",                    &Rtp_Clnt_Port, &Rtcp_Clnt_Port) != 2) {                    cout << "Transport 解析出错" << endl;                }            }            line = strtok(NULL, sep);        }        if (!strcmp(method, "OPTIONS"))         {            if (handleCmd_OPTIONS(sBuf, CSeq))            {                printf("failed to handle options\n");                break;            }        }        else if (!strcmp(method, "DESCRIBE"))         {            if (handleCmd_DESCRIBE(sBuf, CSeq, url))            {                printf("failed to handle describe\n");                break;            }        }        else if (!strcmp(method, "SETUP"))         {            if (handleCmd_SETUP(sBuf, CSeq, Rtp_Clnt_Port))            {                printf("failed to handle setup\n");                break;            }            int on = 1;            if ((Rtp_Serv_Sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ||                (Rtcp_Serv_Sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)            {                cout << "Rtp_Serv_Sockfd || Rtcp_Serv_Sockfd create error" << endl;            }            setsockopt(Rtp_Serv_Sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));            setsockopt(Rtcp_Serv_Sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));            struct sockaddr_in Rtp_Serv_Sockfd_Addr, Rtcp_Serv_Sockfd_Addr;            Rtp_Serv_Sockfd_Addr.sin_family = AF_INET;            Rtp_Serv_Sockfd_Addr.sin_port = htons(SERVER_PORT);            Rtp_Serv_Sockfd_Addr.sin_addr.s_addr = htonl(INADDR_ANY);            Rtcp_Serv_Sockfd_Addr.sin_family = AF_INET;            Rtcp_Serv_Sockfd_Addr.sin_port = htons(SERVER_PORT);            Rtcp_Serv_Sockfd_Addr.sin_addr.s_addr = htonl(INADDR_ANY);            if ((bind(Rtp_Serv_Sockfd, (const sockaddr*)&Rtp_Serv_Sockfd_Addr, sizeof(Rtp_Serv_Sockfd_Addr))) < 0 ||                (bind(Rtcp_Serv_Sockfd, (const sockaddr*)&Rtcp_Serv_Sockfd_Addr, sizeof(Rtcp_Serv_Sockfd_Addr))) < 0)            {                cout << "fail to bind Rtp_Serv_Sockfd || Rtcp_Serv_Sockfd" << endl;            }        }        else if (!strcmp(method, "PLAY"))         {            if (handleCmd_PLAY(sBuf, CSeq))            {                printf("failed to handle play\n");                break;            }        }        else         {            printf("未定义的method = %s \n", method);            break;        }        cout << "s--->c" << endl;        cout << sBuf << endl;        send(clientSockfd, sBuf, strlen(sBuf), 0);        if (!strcmp(method, "PLAY"))        {            printf("start play\n");            printf("client ip:%s\n", clientIP);            printf("client port:%d\n", Rtp_Clnt_Port);            int frameSize, startCode;            char* frame = (char*)malloc(500000);            struct RtpPacket* rtpPacket = (struct RtpPacket*)malloc(500000);            FILE* fp = fopen(H264_FILE_NAME, "rb");            if (!fp) {                printf("读取 %s 失败\n", H264_FILE_NAME);                break;            }            rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_H264, 0,                0, 0, 0x88923423);            while (1)            {                frameSize = getFrameFromH264File(fp, frame, 500000);                if (frameSize < 0)                {                    printf("读取%s竣事,frameSize=%d \n", H264_FILE_NAME, frameSize);                    break;                }                if (startCode3(frame))                    startCode = 3;                else                    startCode = 4;                frameSize -= startCode;                rtpSendH264Frame(Rtp_Serv_Sockfd, clientIP, Rtp_Clnt_Port,                    rtpPacket, frame + startCode, frameSize);                Sleep(25);            }            free(frame);            free(rtpPacket);            break;        }        memset(method, 0, sizeof(method) / sizeof(char));        memset(url, 0, sizeof(url) / sizeof(char));        CSeq = 0;    }    closesocket(clientSockfd);    if (Rtp_Serv_Sockfd) {        closesocket(Rtp_Serv_Sockfd);    }    if (Rtcp_Serv_Sockfd > 0) {        closesocket(Rtcp_Serv_Sockfd);    }    free(rBuf);    free(sBuf);    return 0;}int main(){    WSADATA wsaData;//启动windows socket    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)    {        printf("PC Server Socket Start Up Error \n");        return -1;    }        int Rtsp_Serv_Sock, Client_Sock;;    Rtsp_Serv_Sock = socket(AF_INET, SOCK_STREAM, 0);    if (Rtsp_Serv_Sock < 0)    {        cout << "Rtsp_Serv_Sock init error" << endl;        return -1;    }    int on = 1;    if ((setsockopt(Rtsp_Serv_Sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on))) == -1)//设置TCP答应重用当地地址    {        cout << "fail to setsockopt()" << endl;        return -1;    }        struct sockaddr_in Serv_Addr,Client_Addr;    Serv_Addr.sin_family = AF_INET;    Serv_Addr.sin_port = htons(SERVER_PORT);    Serv_Addr.sin_addr.s_addr = htonl(INADDR_ANY);    if (bind(Rtsp_Serv_Sock, (const sockaddr*)&Serv_Addr, sizeof(Serv_Addr)) == SOCKET_ERROR)    {        cout << "fail to bind Rtsp_Serv_Sock" << endl;        return -1;    }    if (listen(Rtsp_Serv_Sock, 10) < 0)    {        cout << "failed to listen Rtsp_Serv_Sock" << endl;        return -1;    }    printf("%s rtsp://127.0.0.1:%d\n", __FILE__, SERVER_PORT);    while (1)    {        char clientIp[40];        int clientPort;        int len = 0;        memset(&Client_Addr, 0, sizeof(Client_Addr));        len = sizeof(Client_Addr);                if ((Client_Sock = accept(Rtsp_Serv_Sock, (struct sockaddr*)&Client_Addr, &len)) < 0)        {            cout << "fail to accept Client_Sock" << endl;        }        strcpy(clientIp, inet_ntoa(Client_Addr.sin_addr));        clientPort = ntohs(Client_Addr.sin_port);        Rtsp_H264_Replay(Client_Sock, clientIp, clientPort);    }    closesocket(Rtsp_Serv_Sock);        return 0;}
复制代码
3.运行效果





免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

去皮卡多

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表