RTSP协议实现发送ACC音频数据

一.AAC音频格式介绍


        AAC音频格式:Advanced Audio Coding(高级音频解码),是一种由MPEG—4标准定义的有损音频压缩格式。音频压缩编码的输出码流,以音频帧的形式存在。每个音频帧包含若干个音频采样的压缩数据,AAC的一个音频帧包含960或1024个样值,这些压缩编码后的音频帧称为原始数据块(RawData Block),由于原始数据块以帧的形式存在,即简称为原始帧。     

二.AAC编码封装格式

 原始帧是可变的,如果对原始帧进行ADTS的封装,得到的原始帧为ADTS帧;如果对原始帧进行ADIF封装,得到的原始帧为ADIF帧。它们的区别如下:

ADIF:AudioData Interchange Format,音频数据交换格式。这种格式明确解码必须在明确定义的音频数据流的开始处进行,常用于磁盘文件中;

ADTS:AudioData Transport Stream,音频数据传输流。这种格式的特点是它一个有同步字的比特流,且允许在音频数据流的任意帧解码,也就是说,它每一帧都有信息头。

三.ADTS帧组成

     一个AAC原始数据库长度是可变的,对原始帧加上ADTS头进行ADTS封装就形成了ADTS帧。AAC音频的每一帧(ADTS帧)体由ADTS Header和AAC Audio Data(包含1~4个音频原始帧)组成,其中,ADTS Header占7个字节或9个字节,由两部分组成:固定头信息(adts_fixed_header)、可变头信息(adts_variable_header)固定头信息中的数据每一帧都是相同的,主要定义了音频的采样率、声道数、帧长度等关键信息,这是解码AAC所需关键信息;可变头信息则在帧与帧之间可变。

ADTS组成结

ADTS Header结构体

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 必须为0uint8_t protectionAbsent;  //1 bit 1代表没有CRC,0代表有CRCuint8_t profile;           //1 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可能需要此字段,不同编码器使用情况不同。这个在使用音频编码的时候需要注意。/* 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个采样及相关数据)*/uint8_t numberOfRawDataBlockInFrame; //2 bit
};

ADTS Header参数含义:

syncword:占12bits。同步头信息,表示一个ADTS帧的开始,总是0xFFF。正是因为它的存在,才支持解码任意帧;ID:            占1bit。MPEG的版本,0为MPGE-4,1为MPGE-2;Layer:      占2bits。总是”00”;protection_absent:占1bit。=0时,ADTS Header长度占9字节;=1时,ADTS Header占7字节;profile:     占2bit。使用哪个级别的AAC,值00、01、10分别对应Mainprofile、LC、SSR;
sampling_frequency_index:占4bits。表示使用的采样率下标,通过这个下标在Sampling Frequencies[ ]数组中查找得知采样率的值,如0xb,对应的采样率为8000Hz;channel_configuration:表示声道数,如1-单声道,2-立体声orininal_copy:    1bit    编码时设置为0,解码时忽略。
home :   1bit    编码时设置为0,解码时忽略。
copyrightIdentificationBit:   1bit    编码时设置为0,解码时忽略。
copyrightIdentificationStart:    1bit    编码时设置为0,解码时忽略。
aacFrameLength:    13bit    一个ADTS帧的⻓度,包括ADTS头和AAC原始流。
adtsBufferFullness :   11bit    缓冲区充满度,0x7FF说明是码率可变的码流,不需要此字段。CBR可能需要此字段,不同编码器使用情况不同。具体查看附录。
numberOfRawDataBlockInFrame:    2bit    表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧,为0表示说ADTS帧中只有一个AAC数据.

四.ACC音频的RTP打包方式

 AAC的RTP打包方式就是将ADTS帧取出ADTS头部,取出AAC数据,每帧数据封装成一个RTP包,需要注意的是,并不是将AAC数据直接拷贝到RTP的载荷中。AAC封装成RTP包,在RTP载荷中的前四个字节是有特殊含义的,然后再是AAC数据,如下图所示。

RTP头部1bit1bit1bit1bit                         ACC  DATA

RTP包的结构由RTP头部和RTP载荷组成,由上图所知,RTP载荷分为4个字节和ACC的数据组成。我们需要注意的,RTP封装包并不是将AAC数据直接拷贝到RTP的载荷中,AAC封装成RTP包,在RTP载荷中的前四个字节是有特殊含义的,然后再封装AAC数据。

其中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位

RTSP协议推送音频数据代码实现

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 <WinSock2.h>
#include <WS2tcpip.h>
#include <windows.h>
#include "rtp.h"#define SERVER_PORT     8554
#define SERVER_RTP_PORT  55532
#define SERVER_RTCP_PORT 55533
#define BUF_MAX_SIZE    (1024*1024)
#define AAC_FILE_NAME   "../data/test-long.aac"static int createTcpSocket() {int sockfd;int on = 1;sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0)return -1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));return sockfd;
}static int createUdpSocket() {int sockfd;int on = 1;sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0)return -1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));return sockfd;
}static int bindSocketAddr(int sockfd, const char* ip, int port) {struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip);if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr)) < 0)return -1;return 0;
}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 ����Ϊ0uint8_t protectionAbsent;  //1 bit 1����û��CRC��0������CRCuint8_t profile;           //1 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֡�ij��Ȱ���ADTSͷ��AACԭʼ��unsigned int adtsBufferFullness;           //11 bit �����������ȣ�0x7FF˵�������ʿɱ������������Ҫ���ֶΡ�CBR������Ҫ���ֶΣ���ͬ������ʹ�������ͬ�������ʹ����Ƶ�����ʱ����Ҫע�⡣/* 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���������������)*/uint8_t 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 = ((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;}
}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;
}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 char* getLineFromBuf(char* buf, char* line) {while (*buf != '\n'){*line = *buf;line++;buf++;}*line = '\n';++line;*line = '\0';++buf;return buf;
}static int handleCmd_OPTIONS(char* result, int cseq) {sprintf(result, "RTSP/1.0 200 OK\r\n""CSeq: %d\r\n""Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n""\r\n",cseq);return 0;
}static int handleCmd_DESCRIBE(char* result, int cseq, char* url) {char sdp[500];char localIp[100];sscanf(url, "rtsp://%[^:]:", localIp);sprintf(sdp, "v=0\r\n""o=- 9%ld 1 IN IP4 %s\r\n""t=0 0\r\n""a=control:*\r\n""m=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;
}static int handleCmd_SETUP(char* result, int cseq, int clientRtpPort) {sprintf(result, "RTSP/1.0 200 OK\r\n""CSeq: %d\r\n""Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d\r\n""Session: 66334873\r\n""\r\n",cseq,clientRtpPort,clientRtpPort + 1,SERVER_RTP_PORT,SERVER_RTCP_PORT);return 0;
}static int handleCmd_PLAY(char* result, int cseq) {sprintf(result, "RTSP/1.0 200 OK\r\n""CSeq: %d\r\n""Range: npt=0.000-\r\n""Session: 66334873; timeout=10\r\n\r\n",cseq);return 0;
}static void doClient(int clientSockfd, const char* clientIP, int clientPort) {int serverRtpSockfd = -1, serverRtcpSockfd = -1;char method[40];char url[100];char version[40];int CSeq;int clientRtpPort, clientRtcpPort;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("%s rBuf = %s \n", __FUNCTION__, 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}}else if (strstr(line, "CSeq")) {if (sscanf(line, "CSeq: %d\r\n", &CSeq) != 1) {// error}}else if (!strncmp(line, "Transport:", strlen("Transport:"))) {// Transport: RTP/AVP/UDP;unicast;client_port=13358-13359// Transport: RTP/AVP;unicast;client_port=13358-13359if (sscanf(line, "Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n",&clientRtpPort, &clientRtcpPort) != 2) {// errorprintf("parse Transport error \n");}}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, clientRtpPort)){printf("failed to handle setup\n");break;}serverRtpSockfd = createUdpSocket();serverRtcpSockfd = createUdpSocket();if (serverRtpSockfd < 0 || serverRtcpSockfd < 0){printf("failed to create udp socket\n");break;}if (bindSocketAddr(serverRtpSockfd, "0.0.0.0", SERVER_RTP_PORT) < 0 ||bindSocketAddr(serverRtcpSockfd, "0.0.0.0", SERVER_RTCP_PORT) < 0){printf("failed to bind addr\n");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("%s sBuf = %s \n", __FUNCTION__, sBuf);send(clientSockfd, sBuf, strlen(sBuf), 0);//��ʼ���ţ�����RTP��if (!strcmp(method, "PLAY")) {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);if (ret <= 0){printf("fread err\n");break;}rtpSendAACFrame(serverRtpSockfd, clientIP, clientRtpPort,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 (serverRtpSockfd) {closesocket(serverRtpSockfd);}if (serverRtcpSockfd > 0) {closesocket(serverRtcpSockfd);}free(rBuf);free(sBuf);}int main() {// ���windows socket startWSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){printf("PC Server Socket Start Up Error \n");return -1;}// ���windows socket endint rtspServerSockfd;int ret;rtspServerSockfd = createTcpSocket();if (rtspServerSockfd < 0){printf("failed to create tcp socket\n");return -1;}ret = bindSocketAddr(rtspServerSockfd, "0.0.0.0", SERVER_PORT);if (ret < 0){printf("failed to bind addr\n");return -1;}ret = listen(rtspServerSockfd, 10);if (ret < 0){printf("failed to listen\n");return -1;}printf("%s rtsp://127.0.0.1:%d\n", __FILE__, SERVER_PORT);while (1){int clientSockfd;char clientIp[40];int clientPort;clientSockfd = acceptClient(rtspServerSockfd, 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);}closesocket(rtspServerSockfd);return 0;
}

rtp.cpp

#include <sys/types.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <windows.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)
{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] = 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
#pragma comment(lib, "ws2_32.lib")
#include <stdint.h>#define RTP_VESION              2#define RTP_PAYLOAD_TYPE_H264   96
#define RTP_PAYLOAD_TYPE_AAC    97#define RTP_HEADER_SIZE         12
#define RTP_MAX_PKT_SIZE        1400/**    0                   1                   2                   3*    7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0*   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+*   |V=2|P|X|  CC   |M|     PT      |       sequence number         |*   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+*   |                           timestamp                           |*   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+*   |           synchronization source (SSRC) identifier            |*   +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+*   |            contributing source (CSRC) identifiers             |*   :                             ....                              :*   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+**/
struct RtpHeader
{/* byte 0 */uint8_t csrcLen : 4;uint8_t extension : 1;uint8_t padding : 1;uint8_t version : 2;/* byte 1 */uint8_t payloadType : 7;uint8_t marker : 1;/* bytes 2,3 */uint16_t seq;/* bytes 4-7 */uint32_t timestamp;/* bytes 8-11 */uint32_t ssrc;
};struct RtpPacket
{struct RtpHeader rtpHeader;uint8_t payload[0];
};void rtpHeaderInit(struct RtpPacket* rtpPacket, uint8_t csrcLen, uint8_t extension,uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,uint16_t seq, uint32_t timestamp, uint32_t ssrc);int rtpSendPacketOverTcp(int clientSockfd, struct RtpPacket* rtpPacket, uint32_t dataSize);
int rtpSendPacketOverUdp(int serverRtpSockfd, const char* ip, int16_t port, struct RtpPacket* rtpPacket, uint32_t dataSize);

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营29期|day 23 任务以及具体安排

669. 修剪二叉搜索树 class Solution {public TreeNode trimBST(TreeNode root, int low, int high) {if (root null) {return null;}if (root.val < low) {return trimBST(root.right, low, high);}if (root.val > high) {return trimBST(root.left, low, high);}// ro…

【数学建模】2024年华数杯国际赛B题-光伏发电Photovoltaic Power 思路、代码、参考论文

1 问题背景 中国电力构成包括传统能源(如煤炭、石油、天然气)、可再生能源(如水电、风能、太阳能、核能)和其他形式的电力。这些发电模式在满足中国巨大的电力需求方面发挥着至关重要的作用。据最新数据显示&#xff0c;中国总发电量超过20万亿千瓦时&#xff0c;居世界第一。…

企业怎么传输大容量视频?

在企业中&#xff0c;视频的应用越来越广泛&#xff0c;不论是在内部沟通、培训、宣传&#xff0c;还是在外部合作、推广、展示方面&#xff0c;视频都扮演着不可或缺的角色。然而&#xff0c;由于视频文件通常较大&#xff0c;传输时往往会面临网速慢、容量限制、安全风险等问…

【开源之美】:hello-algo

图文并茂的方式讲解常用算法&#xff0c;适合算法知识点学习和回顾总结。 一、算法知识点 二、前往地址 https://www.hello-algo.com/

云边协同的 RTC 如何助力即构全球实时互动业务实践

作者&#xff1a;即构科技 由 51 CTO 主办的“WOT 全球技术创新大会 2023深圳站”于 11 月 24 日 - 25 日召开&#xff0c;即构科技后台技术总监肖潇以“边缘容器在全球音视频场景的探索与实践”为主题进行分享。 边缘计算作为中心云计算的补充&#xff0c;通过边缘容器架构和…

【性能调优】local模式下flink处理离线任务能力分析

文章目录 一. flink的内存管理1.Jobmanager的内存模型2.TaskManager的内存模型2.1. 模型说明2.2. 通讯、数据传输方面2.3. 框架、任务堆外内存2.4. 托管内存 3.任务分析 二. 单个节点的带宽瓶颈1. 带宽相关理论2. 使用speedtest-cli 测试带宽3. 任务分析3. 其他工具使用介绍 本…

【欢迎您的到来】这里是开源库get_local_info作者的付费专栏

您好&#xff0c; 我是带剑书生&#xff0c;开源库get_local_info的作者&#xff0c;欢迎您的到来&#xff0c;这里是我的付费专栏&#xff0c;会用更简洁的语言&#xff0c;更通俗的话语&#xff0c;来帮助您更好的学习rust&#xff0c;这里不仅仅讲解Rust在某些应用功能实现上…

Kafka的安装、管理和配置

Kafka的安装、管理和配置 1.Kafka安装 官网: https://kafka.apache.org/downloads 下载安装包,我这里下载的是https://archive.apache.org/dist/kafka/3.3.1/kafka_2.13-3.3.1.tgz Kafka是Java生态圈下的一员&#xff0c;用Scala编写&#xff0c;运行在Java虚拟机上&#xf…

【Internet Protocol】ip介绍,如何组局域网实现远程桌面和文件共享

文章目录 1.何为“上网”1.1 定义1.2 为什么连了WiFi就能上网了&#xff1f; 2.ip2.1 什么是ip2.2 为什么区分广域网和局域网&#xff0c;ip的唯一性2.3 如何查看设备的ip2.4 什么叫"ping"2.5 区分是否两个ip是否在同一局域网2.5.1 最稳妥的方式&#xff1a;ip&m…

Flutter 综述

Flutter 综述 1 介绍1.1 概述1.2 重要节点1.3 移动开发中三种跨平台框架技术对比1.4 flutter 技术栈1.5 IDE1.6 Dart 语言1.7 应用1.8 框架 2 Flutter的主要组成部分3 资料书籍 《Flutter实战第二版》Dart 语言官网Flutter中文开发者社区flutter 官网 4 搭建Flutter开发环境参考…

【印象深刻的实战经历】两次全国大学生数学建模经历分享

目录 &#x1f33c;初次接触 初次参加培训 分享培训所得 比赛开始 &#x1f525;再次接触 参加校赛 机缘巧合 再次培训 比赛开始 &#x1f4d5;技巧总结 从问题的实际意义分析大体上可分为 从问题的解决方法上分析 做国赛题目的步骤 赛前准备 选题 寻找思路…

智能安全帽定制_基于联发科MT6762平台的智能安全帽方案

智能安全帽是一种具备多项功能的高科技产品&#xff0c;其功能集成了视频通话监控、高清图像采集、无线数据传输、语音广播对讲、定位轨迹回放、静默报警、危险救援报警、脱帽报警、碰撞报警、近电报警以及智能调度系统等&#xff0c;同时还支持多功能模块的自由添加&#xff0…

蓝桥杯每日一题----货物摆放

题目 分析 上来一看&#xff0c;三个for循环&#xff0c;从1到n&#xff0c;寻找满足lwhn的个数&#xff0c;但是这样根本跑不出来答案&#xff0c;n太大了&#xff0c;1e15的级别&#xff0c;O&#xff08;n&#xff09;的时间复杂度都不行&#xff0c;更何况是O&#xff08;…

【Filament】材质系统

1 前言 本文主要介绍 Filament 的材质系统&#xff0c;官方介绍详见 → Filament Materials Guide。材质系统中会涉及到一些空间和变换的知识点&#xff0c;可以参考&#xff1a;【Unity3D】空间和变换、【Unity3D】Shader常量、变量、结构体、函数、【OpenGL ES】MVP矩阵变换、…

【USTC】verilog 习题练习 21-25

21 基于端口名称的实例化 题目描述 创建一 verilog 电路&#xff0c;实现对模块 mod_a 基于端口名称的实例化&#xff0c;如下图所示&#xff1a; 其中mod_a模块的代码为&#xff1a; module mod_a (output out1,output out2,input in1,input in2,input in3,in…

【JMeter】JMeter连OceanBase数据库

1、下载OB&#xff08;OceanBase简称&#xff0c;下同&#xff09;&#xff0c;下载地址&#xff1a;https://www.oceanbase.com/softwarecenter-enterprise 2、将下载下来的jar包放到jmeter安装目录的 lib 目录下&#xff0c;或者打开JMeter客户端&#xff0c;在测试计划中引入…

Python ❀ 使用代码实现API接口调用详解

文章目录 1. 工具准备1.1. requests代码包1.2. BurpSuite抓包工具 2. 操作过程2.1. 一个简单的请求2.1.1. Burp获取响应2.1.2. 转发获取响应 2.2. 构造GET类型URL参数2.3. 构造请求头部2.4. 构造POST类型payload数据2.4.1. urlencoded格式2.4.2. json格式 本文主要讲解常用API接…

第九站(17天):C++IO流

文件IO流 对象:文件,控制台,特定数据类型stringstream (写数据输出流out,读数据输入流in) ofstream : ofstream outfile;//输出流:从键盘输出数据,写入到文件 //文件打开默认位ios::out//字节覆盖写 //可以截断设置为:ios::out | ios::trunc//将之前文件全部…

2024年1月【ORACLE战报】| 新年第一波OCP证书来了!

相关文章&#xff1a; 2023年12月【考试战报】|ORACLE OCP 19C考试通过 2023年10月【考试战报】|ORACLE OCP 19C考试通过 2023.7月最新OCP考试通过|微思-ORACLE官方授权中心 OCP 19C题库稳定&#xff01;https://download.csdn.net/download/XMWS_IT/88309681?ops_request_…

Midjourney Prompt 常用参数列表

完整参数列表 参数名称调用方法使用案例注意事项V5V4V3niji版本在关键词后加空格&#xff0c;然后带上版本参数&#xff1a; --v 或者 —v--version 或者 —versionvibrant california poppies --v 5版本仅支持 1、2、3、4、5。长宽比在关键词后加空格&#xff0c;然后带上长…