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

前言

本篇为实现一个基于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报头格式:

      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  |P|X|  CC   |M|     PT      |       sequence number         |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                           timestamp                           |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |           synchronization source (SSRC) identifier            |
     +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
     |            contributing source (CSRC) identifiers             |
     :                             ....                              :
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

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的第一次字节由特殊的含义。

            0 1 2  3 4 5 6 7 
           -+-+-+-+-+-+-+-+-
           |F|NRI|  Type   | 
           +-+-+-+-+-+-+-+-+

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:

            FU Indicator
           0 1 2 3 4 5 6 7
          +-+-+-+-+-+-+-+-+
          |F|NRI|  Type   |
          +---------------+

高三位于NALU的高三位相同。
Type = 28 (11100),表示该RTP包的一个分片。(H.264的规定)

第二个字节位FU Header

              FU Header
            0 1 2 3 4 5 6 7
            +-+-+-+-+-+-+-+-+
            |S|E|R|  Type   |
            +---------------+

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

在这里插入图片描述
生成.h264

1.结构体

RTP头部结构体:

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

RTP包结构体:

struct RtpPacket
{
    struct RtpHeader rtpHeader;
    uint8_t payload[0];
};

初始化RTP包

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;
}

2.源代码

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

在这里插入图片描述

视频播放

wireshark抓包

  • 31
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值