详解AAC-ADTS,并实现aac封装到rtp数据包,进行rtsp传输_rtp aac 文件传输与接收-程序员宅基地

技术标签: aac  mpeg-2  音视频  服务器  pcm  ffmpeg  

一、AAC

AAC介绍

AAC(Advanced Audio Coding),中文名:高级音频编码。出现于1997年,基于MPEG-2的音频编码技术。由Fraunhofer IIS、杜比实验室、AT&T、索尼等公司共同开发,目的是取代MP3格式。
2000年,MPEG-4标准出现后,AAC重新集成了其特性,加入了SBR技术和PS技术,为了区别于传统的MPEG-2 AAC又称为MPEG-4 AAC。

AAC音视频格式

AAC音视频格式有两种:
ADIF:音频数据交换格式,只有一个统一的头,必须得到所有数据后解码,适用于本地文件。
ADTS:音视数据传输流,每一帧都有头信息,任意帧解码,适用于传输流。

通常情况下,1024个采样点作为音频的一帧

二、ADTS

1.AAC-ADTS结构

AAC-ADTS结构

adts的结构是每一帧都带adts header,头部后面跟着aac的原始流(aac es)

2.ADTS的头结构

ADTS头部结构

adts_fixed_header:每一帧的内容是不变的。
adts_variable_header:每一帧的内容是存在变化的。
crc:16bits,protection_absent字段为0时存在。
ADTS头信息的长度是7个字节或9字节(有CRC的情况)。

adts的头部一共有15个字段:

字段 长度 内容
adts_fixed_header
syncword 12 所有位必须为1,即0xFFF,代表着一个ADTS帧的开始
id 1 设置MPEG标识符,0代表MPEG-4, 1代表MPEG-2
layer 2 总是00
protection_absent 1 误码效验,1代表没有CRC,0代表有CRC
profile 2 AAC级别(MPEG-2 AAC中定义了3种profile,MPEG-4 AAC中定义了6种profile)
sampling_frequency_index 4 采样率,常见:44100HZ(0x4)
private_bit 1 编码时设置为0,解码时忽略
cannel_configuration 3 声道数量
orininal_copy 1 编码时设置为0,解码时忽略
home 1 编码时设置为0,解码时忽略
adts_variable_heade
copyrigth_identification_bit 1 编码时设置为0,解码时忽略
copyrigth_identification_stat 1 编码时设置为0,解码时忽略
aac_frame_length 13 一个ADTS帧的长度包括ADTS头和AAC原始流
adts_bufferfullness 11 缓冲区充满度,0x7FF说明是码率可变的码流,不需要此字段。CBR可能需要此字段,不同编码器使用情况不同。
number_of_raw_data_blocks_in_frame 2 表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧

结构体如下:

struct AdtsHeader {
    
    unsigned int syncword;  //12 bit 同步字 '1111 1111 1111',一个ADTS帧的开始
    uint8_t id;        //1 bit 0代表MPEG-4, 1代表MPEG-2。
    uint8_t layer;     //2 bit 必须为0
    uint8_t protectionAbsent;  //1 bit 1代表没有CRC,0代表有CRC
    uint8_t profile;           //2 bit AAC级别(MPEG-2 AAC中定义了3种profile,MPEG-4 AAC中定义了6种profile)
    uint8_t samplingFreqIndex; //4 bit 采样率
    uint8_t privateBit;        //1bit 编码时设置为0,解码时忽略
    uint8_t channelCfg;        //3 bit 声道数量
    uint8_t originalCopy;      //1bit 编码时设置为0,解码时忽略
    uint8_t home;               //1 bit 编码时设置为0,解码时忽略

    uint8_t copyrightIdentificationBit;   //1 bit 编码时设置为0,解码时忽略
    uint8_t copyrightIdentificationStart; //1 bit 编码时设置为0,解码时忽略
    unsigned int aacFrameLength;               //13 bit 一个ADTS帧的长度包括ADTS头和AAC原始流
    unsigned int adtsBufferFullness;           //11 bit 缓冲区充满度,0x7FF说明是码率可变的码流,不需要此字段。CBR可能需要此字段,不同编码器使用情况不同。这个在使用音频编码的时候需要注意。
    uint8_t numberOfRawDataBlockInFrame; //2 bit
};

2.profile

AAC共有9种规格,以适应不同的场合的需要:

MPEG -2 AAC Main 主规格
MPEG -2 AAC LC (Low Complexity)
MPEG -2 AAC SSR 可变采样率规格( Scaleable Sample Rate )
MPEG -4 AAC LC 低复杂度规格( Low Complexity )–﹣现在的手机比较常见的mP4文件中的音频部份就包括了该规格音频文件
MPEG -4 AAC Main 主规格注:包含了除增益控制之外的全部功能,其音质最好
MPEG -4 AAC SSR 可变采样率规格( Scaleable Sample Rate )
MPEG -4 AAC LTP 长时期预测规格( Long Term Predicition )
MPEG -4 AAC LD 低延迟规格( Low Delay )
MPEG -4AAC HE高效率规格( HighEfficiency )–﹣这种规格适合用于低码率编码,有 Nero ACC 编码器支持

3.AAC ES

一个frame的原始数据包含1024个样本时间段的音频数据。

AAC的RTP打包

关于RTSP,PTP等内容在我另一篇文章中有说明:
RTSP和RTP说明

AAC的RTP打包

AAC的RTP打包方式是将ADTS帧去除ADTS头部,取出AAC数据,每帧数据封装成一个RTP包。但并不是将AAC数据直接拷贝到RTP的载荷中。AAC封装成RTP包,在RTP载荷中的前四个字节是有特殊含义的,然后再是AAC数据。
AAC的RTP打包
其中RTP载荷
第一个字节为0x00
第二个字节为0x10
第三个字节和第四个字节保存AAC Data的大小(最多只能保存13bit,第三个字节保存数据大小的高八位,第四个字节的高5位保存数据大小的低5位)

    rtpPacket->payload[0] = 0x00;
    rtpPacket->payload[1] = 0x10;
    rtpPacket->payload[2] = (frameSize & 0x1FE0) >> 5; //高8位
    rtpPacket->payload[3] = (frameSize & 0x1F) << 3; //低5位

AAC的时间戳计算

例如:

采样频率是44100
一般AAC每个1024个采样为一帧
所以一秒就有 44100 / 1024 = 43帧
时间增量就是 44100 / 43 = 1025
一帧的时间为 1 / 43 = 23ms

三、传输音频aac的RTSP服务器

准备aac音频文件

ffmpeg -i test.mp4 -vn -acodec aac test.aac

-i 表示输入文件
-vm disable video 丢掉视频
-acodec 设置音频编码格式

源码

#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];
};
struct AdtsHeader {
     //头部信息的长度是七个或九个字节(有CRC的情况)
    //adts_fixed_header
    unsigned int syncword;  //12 bit 同步字 '1111 1111 1111',一个ADTS帧的开始
    uint8_t id;        //1 bit 0代表MPEG-4, 1代表MPEG-2。
    uint8_t layer;     //2 bit 必须为0
    uint8_t protectionAbsent;  //1 bit 1代表没有CRC,0代表有CRC
    uint8_t profile;           //2 bit AAC级别(MPEG-2 AAC中定义了3种profile,MPEG-4 AAC中定义了6种profile)
    uint8_t samplingFreqIndex; //4 bit 采样率
    uint8_t privateBit;        //1bit 编码时设置为0,解码时忽略
    uint8_t channelCfg;        //3 bit 声道数量
    uint8_t originalCopy;      //1bit 编码时设置为0,解码时忽略
    uint8_t home;               //1 bit 编码时设置为0,解码时忽略
    //adts_variable_header
    uint8_t copyrightIdentificationBit;   //1 bit 编码时设置为0,解码时忽略
    uint8_t copyrightIdentificationStart; //1 bit 编码时设置为0,解码时忽略
    unsigned int aacFrameLength;               //13 bit 一个ADTS帧的长度包括ADTS头和AAC原始流
    unsigned int adtsBufferFullness;           //11 bit 缓冲区充满度,0x7FF说明是码率可变的码流,不需要此字段。CBR可能需要此字段,不同编码器使用情况不同。这个在使用音频编码的时候需要注意。

    uint8_t numberOfRawDataBlockInFrame; //2 bit
};
int parseAdtsHeader(uint8_t* in, struct AdtsHeader* res)//解析adtsheader in码流
{
    
    int frame_number = 0;
    memset(res, 0, sizeof(*res));
    if ((in[0] == 0xFF) && ((in[1] & 0xF0) == 0xF0))//ADTS帧的开始 必须以0xFFF开头 
    {
    
        res->id = ((uint8_t)in[1] & 0x08) >> 3;//第二个字节与0x08与运算之后,获得第13位bit对应的值 
        res->layer = ((uint8_t)in[1] & 0x06) >> 1;//第二个字节与0x06与运算之后,右移1位,获得第14,15位两个bit对应的值
        res->protectionAbsent = (uint8_t)in[1] & 0x01;
        res->profile = ((uint8_t)in[2] & 0xc0) >> 6;
        res->samplingFreqIndex = ((uint8_t)in[2] & 0x3c) >> 2;
        res->privateBit = ((uint8_t)in[2] & 0x02) >> 1;
        res->channelCfg = ((((uint8_t)in[2] & 0x01) << 2) | (((unsigned int)in[3] & 0xc0) >> 6));
        res->originalCopy = ((uint8_t)in[3] & 0x20) >> 5;
        res->home = ((uint8_t)in[3] & 0x10) >> 4;
        res->copyrightIdentificationBit = ((uint8_t)in[3] & 0x08) >> 3;
        res->copyrightIdentificationStart = (uint8_t)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 = ((uint8_t)in[6] & 0x03);

        return 0;
    }
    else
    {
    
        printf("failed to parse adts header\n");
        return -1;
    }
}
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;
}
static int rtpSendAACFrame(int socket, const char* ip, int16_t port,
    struct RtpPacket* rtpPacket, uint8_t* frame, uint32_t frameSize) {
    
    //打包文档:https://blog.csdn.net/yangguoyu8023/article/details/106517251/
    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 = rtpSendPacketOverUdp(socket, ip, port, rtpPacket, frameSize + 4);
    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;
}
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=audio 0 RTP/AVP 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=fmtp:97 SizeLength=13;\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;
}

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_Aac_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);
            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);
                break;
            }
            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);//aacFrameLength记录了一个ADTS frame的大小
                if (ret <= 0)
                {
    
                    printf("fread err\n");
                    break;
                }

                rtpSendAACFrame(Rtp_Serv_Sockfd, clientIP, Rtp_Clnt_Port,
                    rtpPacket, frame, adtsHeader.aacFrameLength - 7);

                Sleep(1);
                //usleep(23223);//1000/43.06 * 1000
            }

            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_Aac_Replay(Client_Sock, clientIp, clientPort);
    }
    closesocket(Rtsp_Serv_Sock);
    return 0;
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_63174159/article/details/137397797

智能推荐

js-选项卡原理_选项卡js原理-程序员宅基地

文章浏览阅读90次。【代码】js-选项卡原理。_选项卡js原理

设计模式-原型模式(Prototype)-程序员宅基地

文章浏览阅读67次。原型模式是一种对象创建型模式,它采用复制原型对象的方法来创建对象的实例。它创建的实例,具有与原型一样的数据结构和值分为深度克隆和浅度克隆。浅度克隆:克隆对象的值类型(基本数据类型),克隆引用类型的地址;深度克隆:克隆对象的值类型,引用类型的对象也复制一份副本。UML图:具体代码:浅度复制:import java.util.List;/*..._prototype 设计模式

个性化政府云的探索-程序员宅基地

文章浏览阅读59次。入选国内首批云计算服务创新发展试点城市的北京、上海、深圳、杭州和无锡起到了很好的示范作用,不仅促进了当地产业的升级换代,而且为国内其他城市发展云计算产业提供了很好的借鉴。据了解,目前国内至少有20个城市确定将云计算作为重点发展的产业。这势必会形成新一轮的云计算基础设施建设的**。由于云计算基础设施建设具有投资规模大,运维成本高,投资回收周期长,地域辐射性强等诸多特点,各地在建...

STM32问题集之BOOT0和BOOT1的作用_stm32boot0和boot1作用-程序员宅基地

文章浏览阅读9.4k次,点赞2次,收藏20次。一、功能及目的 在每个STM32的芯片上都有两个管脚BOOT0和BOOT1,这两个管脚在芯片复位时的电平状态决定了芯片复位后从哪个区域开始执行程序。BOOT1=x BOOT0=0 // 从用户闪存启动,这是正常的工作模式。BOOT1=0 BOOT0=1 // 从系统存储器启动,这种模式启动的程序_stm32boot0和boot1作用

C语言函数递归调用-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏22次。C语言函数递归调用_c语言函数递归调用

明日方舟抽卡模拟器wiki_明日方舟bilibili服-明日方舟bilibili服下载-程序员宅基地

文章浏览阅读410次。明日方舟bilibili服是一款天灾驾到战斗热血的创新二次元废土风塔防手游,精妙的二次元纸片人设计,为宅友们源源不断更新超多的纸片人老婆老公们,玩家将扮演废土正义一方“罗德岛”中的指挥官,与你身边的感染者们并肩作战。与同类塔防手游与众不同的几点,首先你可以在这抽卡轻松获得稀有,同时也可以在战斗体系和敌军走位机制看到不同。明日方舟bilibili服设定:1、起因不明并四处肆虐的天灾,席卷过的土地上出..._明日方舟抽卡模拟器

随便推点

Maven上传Jar到私服报错:ReasonPhrase: Repository version policy: SNAPSHOT does not allow version: xxx_repository version policy snapshot does not all-程序员宅基地

文章浏览阅读437次。Maven上传Jar到私服报错:ReasonPhrase: Repository version policy: SNAPSHOT does not allow version: xxx_repository version policy snapshot does not all

斐波那契数列、素数、质数和猴子吃桃问题_斐波那契日-程序员宅基地

文章浏览阅读1.2k次。斐波那契数列(Fibonacci Sequence)是由如下形式的一系列数字组成的:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …上述数字序列中反映出来的规律,就是下一个数字是该数字前面两个紧邻数字的和,具体如下所示:示例:比如上述斐波那契数列中的最后两个数,可以推导出34后面的数为21+34=55下面是一个更长一些的斐波那契数列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584,_斐波那契日

PHP必会面试题_//该层循环用来控制每轮 冒出一个数 需要比较的次数-程序员宅基地

文章浏览阅读363次。PHP必会面试题1. 基础篇1. 用 PHP 打印出前一天的时间格式是 2017-12-28 22:21:21? //&gt;&gt;1.当前时间减去一天的时间,然后再格式化echo date('Y-m-d H:i:s',time()-3600*24);//&gt;&gt;2.使用strtotime,可以将任何字符串时间转换成时间戳,仅针对英文echo date('Y-m-d H:i:s',str..._//该层循环用来控制每轮 冒出一个数 需要比较的次数

windows用mingw(g++)编译opencv,opencv_contrib,并install安装_opencv mingw contrib-程序员宅基地

文章浏览阅读1.3k次,点赞26次,收藏26次。windows下用mingw编译opencv貌似不支持cuda,选cuda会报错,我无法解决,所以没选cuda,下面两种编译方式支持。打开cmake gui程序,在下面两个框中分别输入opencv的源文件和编译目录,build-mingw为你创建的目录,可自定义命名。1、如果已经安装Qt,则Qt自带mingw编译器,从Qt安装目录找到编译器所在目录即可。1、如果已经安装Qt,则Qt自带cmake,从Qt安装目录找到cmake所在目录即可。2、若未安装Qt,则安装Mingw即可,参考我的另外一篇文章。_opencv mingw contrib

5个高质量简历模板网站,免费、免费、免费_hoso模板官网-程序员宅基地

文章浏览阅读10w+次,点赞42次,收藏309次。今天给大家推荐5个好用且免费的简历模板网站,简洁美观,非常值得收藏!1、菜鸟图库https://www.sucai999.com/search/word/0_242_0.html?v=NTYxMjky网站主要以设计类素材为主,办公类素材也很多,简历模板大部个偏简约风,各种版式都有,而且经常会更新。最重要的是全部都能免费下载。2、个人简历网https://www.gerenjianli.com/moban/这是一个专门提供简历模板的网站,里面有超多模板个类,找起来非常方便,风格也很多样,无须注册就能免费下载,_hoso模板官网

通过 TikTok 联盟提高销售额的 6 个步骤_tiktok联盟-程序员宅基地

文章浏览阅读142次。你听说过吗?该计划可让您以推广您的产品并在成功销售时支付佣金。它提供了新的营销渠道,使您的产品呈现在更广泛的受众面前并提高品牌知名度。此外,TikTok Shop联盟可以是一种经济高效的产品或服务营销方式。您只需在有人购买时付费,因此不存在在无效广告上浪费金钱的风险。这些诱人的好处是否足以让您想要开始您的TikTok Shop联盟活动?如果是这样,本指南适合您。_tiktok联盟