前言
上期已经实现了一个基于UDP的RTP传输h264的RTSP服务器,客户端播放器能够向RTSP服务端发起连接建立的请求,并且客户端在发起RTSP的Play请求以后,RTSP服务端在回复了Play请求之后,开始源源不断的通过RTP协议向客户端推送h264视频流。
本期需要实现,客户端建立与RTSP服务端的连接后,并且在RTSP服务端回复了客户端的Play请求以后,服务端需要源源不断的读取一个本地aac音频文件,并将读取到的aac音频码流封装到RTP数据包中,再推送至客户端。这样我们就实现了一个简单的支持RTSP协议的流媒体分发音频流的服务。
一、什么是aac编码?
我们得知麦克风在录音采样后,直接获得的就是音频原始数据pcm。但由于pcm数据较大,所以通常需要压缩之后才能传输或保存,常见的音频压缩技术有aac,g711,opus,mp3等,最常用的就是aac。
接下来先讲一讲aac的码流格式,以及如何将aac封装到RTP数据包,进行传输。
AAC音频格式有2种:
ADIF(Audio Data Interchage Format),音频数据交换格式:只有一个统一的头,必须得到所有数据后解码,适用于本地文件。
ADTS(Audio Data Transport Stream),音视数据传输流:每一帧都有头信息,任意帧解码,适用于传输流。
二、ADTS及使用RTP传输AAC文件
介绍ADTS:
C++ 解析aac-adts的头部信息
如何封装:
使用RTP传输AAC文件
三、实现一个传输音频aac的RTSP服务器
1.main.cpp
//
// Created by sun on 2022/12/9.
//
#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 必须为0
uint8_t protectionAbsent; //1 bit 1代表没有CRC,0代表有CRC
uint8_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
};
static int parseAdtsHeader(uint8_t* in, struct AdtsHeader* res) {
// in 码流
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 = (