实现基于UDP的RTP传输音频aac的RTSP服务器


前言

上期已经实现了一个基于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 = (
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值