《从零开始编写一个直播服务器》 C++ 实现一个最简单的RTSP流媒体服务器

news2024/11/14 0:34:27

流媒体开发系列文章


文章目录

  • 流媒体开发系列文章
  • 前言
  • 一、rtsp流是什么?
  • 二、使用步骤
    • 1.服务器代码
  • 总结


前言

在安防行业中,onvif协议与gb协议是两种标准,gb是国内安防行业的标准,onvif是国外的安防行业的标准,其中gb28181中视频流是ps流、由设备、下级平台推送到上级平台,具有上公有云的特点,而onvif视频流采用的是rtsp,通常用于内网访问,当需要rtsp流可以上公有云的话,可以使用r-rtsp,r-rtsp交互流程正好与rtsp流程相反,由服务端主动发起请求。


一、rtsp流是什么?

RTSP是类似HTTP的应用层协议,一个典型的流媒体框架网络体系可参考下图,其中rtsp主要用于控制命令,rtcp主要用于视频质量的反馈,rtp用于视频、音频流从传输。

在这里插入图片描述
重要概念:
1、RTSP(Real Time Streaming Protocol),RFC2326,实时流传输协议,是TCP/IP协议体系中的一个应用层协议,由哥伦比亚大学、网景和RealNetworks公司提交的IETF RFC标准。该协议定义了一对多应用程序如何有效地通过IP网络传送多媒体数据。RTSP在体系结构上位于RTP和RTCP之上,它使用TCP或UDP完成数据传输。
2、Real-time Transport Protocol或简写RTP,它是由IETF的多媒体传输工作小组1996年在RFC 1889中公布的。RTP协议详细说明了在互联网上传递音频和视频的标准数据包格式。它是创建在UDP协议上的。
3、Real-time Transport Control Protocol或RTP Control Protocol或简写RTCP)是实时传输协议(RTP)的一个姐妹协议。RTCP由RFC 3550定义(取代作废的RFC 1889)。RTP 使用一个 偶数 UDP port ;而RTCP 则使用 RTP 的下一个 port,也就是一个奇数 port。RTCP与RTP联合工作,RTP实施实际数据的传输,RTCP则负责将控制包送至会话中的每个接收者。其主要功能是就RTP正在提供的服务质量做出反馈。

RTSP的消息格式:
RTSP的消息有两大类,一是请求消息(request),一是回应消息(response),两种消息的格式不同。

请求消息格式:
方法 URI RTSP版本 CR LF
消息头 CR LF CR LF
消息体 CR LF
其中方法包括OPTIONS、SETUP、PLAY、TEARDOWN等,URI是接收方(服务端)的地址,例如:rtsp://192.168.22.136:5000/v0,每行后面的CR LF表示回车换行,需要接收端有相应的解析,最后一个消息头需要有两个CR LF。

回应消息格式:
RTSP版本 状态码 解释 CR LF
消息头 CR LF CR LF
消息体 CR LF
其中RTSP版本一般都是RTSP/1.0,状态码是一个数值,200表示成功,解释是与状态码对应的文本解释。
状态码由三位数组成,表示方法执行的结果,定义如下:
1XX:保留,将来使用;
2XX:成功,操作被接收、理解、接受(received,understand,accepted);
3XX:重定向,要完成操作必须进行进一步操作;
4XX:客户端出错,请求有语法错误或无法实现;
5XX:服务器出错,服务器无法实现合法的请求。

RTSP流程
1、OPTIONS
2、DESCRIBE
3、SETUP
4、PLAY
5、TEARDWON
在这里插入图片描述

二、使用步骤

1.服务器代码

rtp.cpp

#include <sys/types.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 rtpSendPacketOverTcp(int clientSockfd, struct RtpPacket* rtpPacket, uint32_t dataSize, char channel)
{

    rtpPacket->rtpHeader.seq = htons(rtpPacket->rtpHeader.seq);
    rtpPacket->rtpHeader.timestamp = htonl(rtpPacket->rtpHeader.timestamp);
    rtpPacket->rtpHeader.ssrc = htonl(rtpPacket->rtpHeader.ssrc);

    uint32_t rtpSize = RTP_HEADER_SIZE + dataSize;
    char* tempBuf = (char *)malloc(4 + rtpSize);
    tempBuf[0] = 0x24;//$
    tempBuf[1] = channel;// 0x00;
    tempBuf[2] = (uint8_t)(((rtpSize) & 0xFF00) >> 8);
    tempBuf[3] = (uint8_t)((rtpSize) & 0xFF);
    memcpy(tempBuf + 4, (char*)rtpPacket, rtpSize);

    int ret = send(clientSockfd, tempBuf, 4 + rtpSize, 0);

    rtpPacket->rtpHeader.seq = ntohs(rtpPacket->rtpHeader.seq);
    rtpPacket->rtpHeader.timestamp = ntohl(rtpPacket->rtpHeader.timestamp);
    rtpPacket->rtpHeader.ssrc = ntohl(rtpPacket->rtpHeader.ssrc);

    free(tempBuf);
    tempBuf = NULL;

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

rtp.h

#pragma once
#include <stdint.h>
#include <iostream>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <cstring>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cassert>
#include <string>
#include <iostream>
#include <memory>
#include <functional>
#include <thread>

#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 rtpSendPacketOverTcp(int clientSockfd, struct RtpPacket* rtpPacket, uint32_t dataSize, char channel);
int rtpSendPacketOverUdp(int serverRtpSockfd, const char* ip, int16_t port, struct RtpPacket* rtpPacket, uint32_t dataSize);

main.cpp

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <thread>
#include "rtp.h"

#define AAC_FILE_NAME   "/home/vagrant/Mycode/data/test.aac"
#define H264_FILE_NAME   "/home/vagrant/Mycode/data/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 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(FILE* fp, char* frame, int size) {
    int rSize, frameSize;
    char* nextStartCode;

    if (fp < 0)
        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;
}

struct AdtsHeader {
    unsigned int syncword;  //12 bit 同步字 '1111 1111 1111',说明一个ADTS帧的开始
    unsigned int id;        //1 bit MPEG 标示符, 0 for MPEG-4,1 for MPEG-2
    unsigned int layer;     //2 bit 总是'00'
    unsigned int protectionAbsent;  //1 bit 1表示没有crc,0表示有crc
    unsigned int profile;           //1 bit 表示使用哪个级别的AAC
    unsigned int samplingFreqIndex; //4 bit 表示使用的采样频率
    unsigned int privateBit;        //1 bit
    unsigned int channelCfg; //3 bit 表示声道数
    unsigned int originalCopy;         //1 bit
    unsigned int home;                  //1 bit

    /*下面的为改变的参数即每一帧都不同*/
    unsigned int copyrightIdentificationBit;   //1 bit
    unsigned int copyrightIdentificationStart; //1 bit
    unsigned int aacFrameLength;               //13 bit 一个ADTS帧的长度包括ADTS头和AAC原始流
    unsigned int adtsBufferFullness;           //11 bit 0x7FF 说明是码率可变的码流

    /* number_of_raw_data_blocks_in_frame
     * 表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧
     * 所以说number_of_raw_data_blocks_in_frame == 0
     * 表示说ADTS帧中有一个AAC数据块并不是说没有。(一个AAC原始帧包含一段时间内1024个采样及相关数据)
     */
    unsigned int numberOfRawDataBlockInFrame; //2 bit
};

static int parseAdtsHeader(uint8_t* in, struct AdtsHeader* res) {
    static int frame_number = 0;
    memset(res, 0, sizeof(*res));

    if ((in[0] == 0xFF) && ((in[1] & 0xF0) == 0xF0)){
        res->id = ((unsigned int)in[1] & 0x08) >> 3;
        res->layer = ((unsigned int)in[1] & 0x06) >> 1;
        res->protectionAbsent = (unsigned int)in[1] & 0x01;
        res->profile = ((unsigned int)in[2] & 0xc0) >> 6;
        res->samplingFreqIndex = ((unsigned int)in[2] & 0x3c) >> 2;
        res->privateBit = ((unsigned int)in[2] & 0x02) >> 1;
        res->channelCfg = ((((unsigned int)in[2] & 0x01) << 2) | (((unsigned int)in[3] & 0xc0) >> 6));
        res->originalCopy = ((unsigned int)in[3] & 0x20) >> 5;
        res->home = ((unsigned int)in[3] & 0x10) >> 4;
        res->copyrightIdentificationBit = ((unsigned int)in[3] & 0x08) >> 3;
        res->copyrightIdentificationStart = (unsigned int)in[3] & 0x04 >> 2;
        res->aacFrameLength = (((((unsigned int)in[3]) & 0x03) << 11) |
            (((unsigned int)in[4] & 0xFF) << 3) |
            ((unsigned int)in[5] & 0xE0) >> 5);
        res->adtsBufferFullness = (((unsigned int)in[5] & 0x1f) << 6 |
            ((unsigned int)in[6] & 0xfc) >> 2);
        res->numberOfRawDataBlockInFrame = ((unsigned int)in[6] & 0x03);
        return 0;
    }
    else{
        printf("failed to parse adts header\n");
        return -1;
    }
}

static int rtpSendAACFrame(int clientSockfd,
    struct RtpPacket* rtpPacket, uint8_t* frame, uint32_t frameSize) {
    int ret;

    rtpPacket->payload[0] = 0x00;
    rtpPacket->payload[1] = 0x10;
    rtpPacket->payload[2] = (frameSize & 0x1FE0) >> 5; //高8位
    rtpPacket->payload[3] = (frameSize & 0x1F) << 3; //低5位
    memcpy(rtpPacket->payload + 4, frame, frameSize);
    ret = rtpSendPacketOverTcp(clientSockfd, rtpPacket, frameSize + 4,0x02);
    if (ret < 0){
        printf("failed to send rtp packet\n");
        return -1;
    }
    rtpPacket->rtpHeader.seq++;
    /*
     * 如果采样频率是44100
     * 一般AAC每个1024个采样为一帧
     * 所以一秒就有 44100 / 1024 = 43帧
     * 时间增量就是 44100 / 43 = 1025
     * 一帧的时间为 1 / 43 = 23ms
     */
    rtpPacket->rtpHeader.timestamp += 1025;
    return 0;
}


static int rtpSendH264Frame(int clientSockfd,
    struct RtpPacket* rtpPacket, char* frame, uint32_t frameSize)
{

    uint8_t naluType; // nalu第一个字节
    int sendByte = 0;
    int ret;
    naluType = frame[0];
    printf("%s frameSize=%d \n", __FUNCTION__, frameSize);
    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 = rtpSendPacketOverTcp(clientSockfd, rtpPacket, frameSize,0x00);
        if(ret < 0)
            return -1;

        rtpPacket->rtpHeader.seq++;
        sendByte += ret;
        if ((naluType & 0x1F) == 7 || (naluType & 0x1F) == 8) // 如果是SPS、PPS就不需要加时间戳
        {
        }

    }
    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 = rtpSendPacketOverTcp(clientSockfd, rtpPacket, RTP_MAX_PKT_SIZE+2,0x00);
            if(ret < 0)
                return -1;

            rtpPacket->rtpHeader.seq++;
            sendByte += 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 = rtpSendPacketOverTcp(clientSockfd, rtpPacket, remainPktSize+2, 0x00);
            if(ret < 0)
                return -1;

            rtpPacket->rtpHeader.seq++;
            sendByte += ret;
        }
    }
    return sendByte;
}

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/TCP 96\r\n"
        "a=rtpmap:96 H264/90000\r\n"
        "a=control:track0\r\n"
        "m=audio 1 RTP/AVP/TCP 97\r\n"
        "a=rtpmap:97 mpeg4-generic/44100/2\r\n"
        "a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1210;\r\n"
        "a=control:track1\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;
}

static int handleCmd_SETUP(char* result, int cseq)
{
    if (cseq == 3) {
        sprintf(result, "RTSP/1.0 200 OK\r\n"
            "CSeq: %d\r\n"
            "Transport: RTP/AVP/TCP;unicast;interleaved=0-1\r\n"
            "Session: 66334873\r\n"
            "\r\n",
            cseq);
    }
    else if (cseq == 4) {
        sprintf(result, "RTSP/1.0 200 OK\r\n"
            "CSeq: %d\r\n"
            "Transport: RTP/AVP/TCP;unicast;interleaved=2-3\r\n"
            "Session: 66334873\r\n"
            "\r\n",
            cseq);
    }
    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=10\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* rBuf = (char*)malloc(BUF_MAX_SIZE);
    char* sBuf = (char*)malloc(BUF_MAX_SIZE);

    while (true) {
        int recvLen;
        recvLen = recv(clientSockfd, rBuf, BUF_MAX_SIZE, 0);
        if (recvLen <= 0) {
            break;
        }
        rBuf[recvLen] = '\0';
        printf("接收请求 rBuf = %s \n", rBuf);

        const char* sep = "\n";

        char* line = strtok(rBuf, sep);
        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) {
                    // error
                    printf("parse error %d",__LINE__);
                }
            }
            else if (strstr(line, "CSeq")) {
                if (sscanf(line, "CSeq: %d\r\n", &CSeq) != 1) {
                    // error
                    printf("parse error %d",__LINE__);
                }
            }
            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/TCP;unicast;interleaved=0-1\r\n") != 0) {
                    // error
                    printf("parse Transport error \n");
                }
            }
            line = strtok(NULL, sep);
        }
        printf("method: %s seq %d\n",method,CSeq);
        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)){
                printf("failed to handle setup\n");
                break;
            }
        }
        else if (!strcmp(method, "PLAY")) {
            if (handleCmd_PLAY(sBuf, CSeq)){
                printf("failed to handle play\n");
                break;
            }
        }
        else {
            printf("未定义的method = %s \n", method);
            break;
        }
        printf("响应 sBuf = %s \n", sBuf);

        send(clientSockfd, sBuf, strlen(sBuf), 0);


        //开始播放,发送RTP包
        if (!strcmp(method, "PLAY")) {
            std::thread t1([&]() {
                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);
                    return;
                }
                rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_H264, 0, 0, 0, 0x88923423);

                printf("start play\n");
                while (true) {
                    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(clientSockfd, rtpPacket, frame + startCode, frameSize);

                    rtpPacket->rtpHeader.timestamp += 90000 / 25;
                    usleep(20000);//1000/25 * 1000
                }
                free(frame);
                free(rtpPacket);
                
            });
            std::thread t2([&]() {
                struct AdtsHeader adtsHeader;
                struct RtpPacket* rtpPacket;
                uint8_t* frame;
                int ret;

                FILE* fp = fopen(AAC_FILE_NAME, "rb");
                if (!fp) {
                    printf("读取 %s 失败\n", AAC_FILE_NAME);
                    return;
                }

                frame = (uint8_t*)malloc(5000);
                rtpPacket = (struct RtpPacket*)malloc(5000);

                rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_AAC, 1, 0, 0, 0x32411);

                while (true){
                    ret = fread(frame, 1, 7, fp);
                    if (ret <= 0){
                        printf("fread err\n");
                        break;
                    }
                    printf("fread ret=%d \n", ret);

                    if (parseAdtsHeader(frame, &adtsHeader) < 0){
                        printf("parseAdtsHeader err\n");
                        break;
                    }
                    ret = fread(frame, 1, adtsHeader.aacFrameLength - 7, fp);
                    if (ret <= 0){
                        printf("fread err\n");
                        break;
                    }
                    rtpSendAACFrame(clientSockfd,rtpPacket, frame, adtsHeader.aacFrameLength - 7);
                    usleep(23223);//1000/43.06 * 1000
                }
                free(frame);
                free(rtpPacket);
            });
            t1.join();
            t2.join();

            break;
        }

        memset(method,0,sizeof(method)/sizeof(char));
        memset(url,0,sizeof(url)/sizeof(char));
        CSeq = 0;
    }

    close(clientSockfd);
    free(rBuf);
    free(sBuf);

}

int main(int argc, char* argv[])
{
    int serverSockfd;
    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");
        fprintf(stderr, "bind socket error %s errno: %d\n", strerror(errno), errno);
        return -1;
    }

    if (listen(serverSockfd, 10) < 0){
        printf("failed to listen\n");
        return -1;
    }
    printf("%s rtsp://10.20.39.168:%d/test\n",__FILE__, SERVER_PORT);

    while (true) {
        int clientSockfd = 0;
        char clientIp[40] = {"\0"};
        int clientPort = 0;

        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);
    }
    close(serverSockfd);
    return 0;
}



/*
编译命令
g++ main.cpp rtp.cpp -o rtsp.server -std=c++11 -lpthread
通过mp4生成h264与aac文件
ffmpeg -i test.mp4 -an -vcodec copy -f h264 test.h264
ffmpeg -i test.mp4 -vn -acodec aac test.aac
*/

运行结果:
编译运行:

1、编译
g++ main.cpp rtp.cpp -o rtsp.server -std=c++11 -lpthread
2、运行
./rtsp.server
3、播放
ffplay.exe  -rtsp_transport tcp rtsp://ip:8554/test

在这里插入图片描述


总结

通过本文的学习,你应该对rtsp视频流有了一定的认识,希望对你后面的学习有所帮助。

授之以鱼不如授之以渔,如果您喜欢,请点赞收藏+关注。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/176995.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AcWing 1013. 机器分配(分组背包问题与方案记录)

一、题目 二、思路 这道题其实不太容易看出背后的模型。这道题本质上是一个分组背包问题。我们将每一个公司看成一组&#xff0c;而在每一个组内&#xff0c;将不同情况下的盈利状况看作物品的价值&#xff0c;而得到这种利益所需的机器数目看作物品的体积。 因此&#xff0c…

SpringSession笔记

第一章、Session会话管理概述Session和Cookie回顾Session机制由于HTTP是无状态的协议&#xff0c;每次浏览器与服务器的交互过程就是一次对话&#xff0c;对话结束之后服务器不能记住你这个人。下次对话的时候服务端无法识别你是上次的人&#xff0c;所以需要一种机制来记录用户…

Win10 突然蓝屏安全模式进不了,没有别的电脑和装机U盘,怎么把资料临时导出来?

环境: Win 10专业版 DELL 3490 移动硬盘/普通U盘 问题描述: Win10 突然蓝屏安全模式进不了,没有别的电脑和装机U盘,怎么把资料临时导出来?目前可以进入Win RE 疑难-高级选项 解决方案: 1.先插入,移动硬盘或者普通U盘 2.多重启几次,自动修复系统失败画面,点击高…

Linux嵌入式开发——压缩与解压缩

文章目录Linux嵌入式开发——压缩与解压缩一、前期准备二、Linux下的压缩格式三、gzip压缩工具1、gzip压缩文件2、gzip压缩文件夹四、bzip2压缩工具五、tar打包工具tar参数对.tar.bz2进行压缩和解压缩压缩解压缩对.tar.gz进行压缩和解压缩压缩解压缩六、其他格式的压缩和解压缩…

Qt扫盲-网络编程概述

网络编程概述一、Qt网络编程概述二、Qt对Http&#xff0c;FTP应用层协议支持三、TCP通信编程支持四、UDP通信编程支持五、主机信息的获取六、网络代理七、底层管理的支持一、Qt网络编程概述 Qt Network模块提供了允许我们编写TCP/IP客户端和服务器的类。它提供了低级类来完成基…

【Java寒假打卡】JavaWeb-Session

【Java寒假打卡】JavaWeb-Session概述常用的方法HttpSession的获取HttpSession的使用概述 常用的方法 HttpSession的获取 HttpSession的使用 在第一个Servlet中获取请求的用户名获取HttpSession对象将用户名设置到共享数据中在第二个Servlet中获取HttpSession对象获取共享数据用…

java8新特性

接口中默认方法修饰为普通方法&#xff0c;实现接口时不需要重写方法Lambda表达式Stream流并行流原理校验当前对象是否为null当前对象为null,设置默认值 接口中默认方法修饰为普通方法Lambda表达式使用Lambda表达式 依赖于函数接口 在接口中只能够允许有一个抽象方法在函数接…

SpringCloud笔记

2023年最新笔记&#xff0c;全文约 3 万字&#xff0c;蕴含 Spring Cloud 常用组件 Nacos、OpenFeign、Seata、Sentinel 等 〇、简介 什么是Spring Cloud&#xff1f; ​ Spring Cloud是一系列框架的有序集合&#xff0c;是一种基于微服务的分布式架构技术。它利用 Spring Boot…

HBuilder的安装与试用

准备把前端框架Layui仔细学习一遍&#xff08;虽然Layui已经过了最流行的时候&#xff0c;但是很多项目都在用它&#xff09;&#xff0c;在B站找了一套《Layui框架精讲全套视频教程》&#xff0c;视频作者实操Layui时用的工具很方便&#xff0c;从弹幕中看到说用的是HBuilder&…

Springcloud 笔记

微服务架构微服务架构是一种架构模式&#xff0c;它体长将单一应用程序划分成一组小的服务&#xff0c;服务之间相互协调&#xff0c;互相配合&#xff0c;为用户提供最终价值。每个服务运行在其独立的进程中&#xff0c;服务与服务之间采用轻量级的通信机制**(如HTTP)互相协作…

电商直通车主图设计教程

商直通车主图设计教程&#xff01;无门槛在线设计&#xff0c;零基础轻松入门的电商设计工具&#xff0c;轻松就能搞定的主图设计工具&#xff0c;下面跟着小编的设计教程&#xff0c;一起学习如何使用在线工具乔拓云轻松设计专属的商品直通车主图&#xff0c;在线模板轻松设计…

Cadence PCB仿真使用Allegro PCB SI生成延迟仿真报告及报告导读图文教程

🏡《Cadence 开发合集目录》   🏡《Cadence PCB 仿真宝典目录》 目录 1,概述2,生成报告3,报告导读4,总结1,概述 本文简单介绍使用Allegro PCB SI生成网络的延迟性能评估的报告的方法,及延迟报告要点导读。 2,生成报告 第1步,选择需要生成报告的网络,然后单击右…

深度估计算法原理与论文解读

论文地址: Monocular Depth Estimation Using Laplacian Pyramid-Based Depth Residuals | IEEE Journals & Magazine | IEEE Xplore 深度估计算法原理 1.深度估计任务概述 深度估计,即通过输入的彩色图像,获得每个像素点离相机距离的远近(热度图) ,热度图的深浅表…

Linux内核显卡驱动(LVDS)初探

目录 0. 前言 1. menuconfig 2. 编译报错与打补丁 3. 设备树与display-timings 4. 拓展&#xff1a;RGB24 0. 前言 这次的工作主要是把某项目设备上(iMX6DL)的内核版本从 4.19.x 升级到 5.15.32&#xff0c;是作为该项目整个BSP升级计划的一部分。 该内核升级工作移交给…

Spring Cloud Alibaba商城实战项目基础篇(day03)

五、后台管理 5.1、商品服务 5.1.1、三级分类 5.1.1.1、查询所有菜单与子菜单 我们需要维护所有菜单以及各种子菜单&#xff0c;子菜单里面可能还有子菜单&#xff0c;所以我们采用递归的方式进行书写。 我们先在CategoryController中修改list方法&#xff0c;让他以组装树…

Day868.索引(上) -MySQL实战

索引&#xff08;上&#xff09; Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于索引&#xff08;上&#xff09;的内容。 某一个 SQL 查询比较慢&#xff0c;分析完原因之后&#xff0c;可能就会说“给某个字段加个索引吧”之类的解决方案。但到底什么是索引&…

linux部署java项目cpu占用100%的排除故障

用top -c命令查看cpu占用高的进程 ![在这里插入图片描述](https://img-blog.csdnimg.cn/12af3f060fb84ce98b24c7247546b50b.png 发现cpu占用为99%的进程pid为24857 用top -Hp 24857查看cpu占用最高的线程 发现占用cpu97.3%的线程id为24926 将24926转为16进制 通过jstack查看进…

袋式除尘器—选型计算

1.处理气体量的计算计算袋式除尘器的处理气体时&#xff0c;首先要求出工况条件下的气体量&#xff0c;即实际通过袋式除尘器的气体量&#xff0c;并且还要考虑除尘器本身的漏风量。这些数据&#xff0c;应根据已有工厂的实际运行经验或检测资料来确定&#xff0c;如果缺乏必要…

Day09 - 子类父类多继承多层继承多态

1. 子类中访问父类中的私有属性和私有方法 如果想使用父类中的私有属性和私有方法,那么就需要在父类中,为这些私有的属性和方法,提供相应的公有的接口方法来间接访问2. 如何为父类中的属性进行初始化 在子类中如果定义了自己的初始化方法,那么这时父类的初始化方法就不会再执…

尚硅谷AJAX教程

优点&#xff1a;无需刷新页面获取数据&#xff0c;允许你根据用户事件来更新部分页面内容 缺点&#xff1a;没有浏览历史&#xff0c;不能回退&#xff0c;存在跨域&#xff0c;SEO不友好 原生XHR请求 get请求 <body><button>获取数据</button><scri…