当前位置: 首页 > article >正文

【RTSP】客户端(三) 音频相关

ADTS头生成

根据给定的频率返回采样率索引

该索引信息主要是在ADTS头中进行使用

int GetSampleRateIndex(int freq){
    int i = 0;
    int freq_arr[13] = {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350};
    for(i = 0; i < 13; i++){
        if(freq == freq_arr[i]){
            return i;
        }
    }
    return 4;  // 默认是44100 Hz
}

生成填充ADTS头信息缓冲区

ADTS 头是 AAC 音频流的标识信息,通常用于传输音频流(例如通过 RTP/RTSP 传输),加入该头部信息的作用在于发送RTP包的时候,让对方得知该包是一个音频

void AdtsHeader(char *adts_header_buffer, int data_len, int profile, int sample_rate_index, int channels)
{
    int adts_len = data_len + 7;  // 总长度 = 数据长度 + 头部长度(7字节)

    adts_header_buffer[0] = 0xff;  // syncword: 0xfff
    adts_header_buffer[1] = 0xf0;  // syncword: 0xfff
    adts_header_buffer[1] |= (0 << 3);  // MPEG Version: 0 for MPEG-4
    adts_header_buffer[1] |= (0 << 1);  // Layer: 0
    adts_header_buffer[1] |= 1;  // protection absent: 1

    adts_header_buffer[2] = (profile) << 6;  // profile
    adts_header_buffer[2] |= (sample_rate_index & 0x0f) << 2;  // sample rate index
    adts_header_buffer[2] |= (0 << 1);  // private bit: 0
    adts_header_buffer[2] |= (channels & 0x04) >> 2;  // channel configuration

    adts_header_buffer[3] = (channels & 0x03) << 6;  // channel configuration
    adts_header_buffer[3] |= (0 << 5);  // original: 0
    adts_header_buffer[3] |= (0 << 4);  // home: 0
    adts_header_buffer[3] |= (0 << 3);  // copyright id bit: 0
    adts_header_buffer[3] |= (0 << 2);  // copyright id start: 0
    adts_header_buffer[3] |= ((adts_len & 0x1800) >> 11);  // frame length: high 2 bits

    adts_header_buffer[4] = (uint8_t)((adts_len & 0x7f8) >> 3);  // frame length: middle 8 bits
    adts_header_buffer[5] = (uint8_t)((adts_len & 0x7) << 5);  // frame length: low 3 bits
    adts_header_buffer[5] |= 0x1f;  // buffer fullness: 0x1f (empty)
    adts_header_buffer[6] = 0xfc;  // reserved bits

    return;
}

RTP音频数据处理

主要处理逻辑

  • 接收 RTP 数据包InputData 方法接收到一个 RTP 数据包并解析其头部,检查负载类型是否是我们关心的 AAC 格式
  • 提取有效负载数据:从 RTP 包中提取出音频数据(即去除 RTP 头和扩展头后的部分)
  • 回调音频数据:通过回调接口将提取到的音频数据传递给外部处理。回调函数接收时间戳和有效负载数据
  • 处理扩展头:如果 RTP 包包含扩展头,跳过扩展头并更新有效负载的指针和长度

代码实现

// 接收RTP包数据以及数据大小
void AACDemuxer::InputData(const uint8_t* data, size_t size) 
{
    // 1. 解析RTP头部
    struct RtpHeader* header = (struct RtpHeader*)data;
    int payload_type = header->payloadType;
    // 如果设置的payload_type和RTP包的payload_type不一致,则直接返回
    if(payload_type != payload_){
        return;
    }
    // 2. 提取有效的负载数据
    const uint8_t* payload = data + sizeof(struct RtpHeader); //去除负载头部的数据
    size_t payload_len = size - sizeof(struct RtpHeader); //负载的长度

    // 3. 解析RTP扩展头
    if(header->extension){
        // 计算拓展头的长度
        const uint8_t* extension_data = payload;
        size_t extension_length = 4 * (extension_data[2] << 8 | extension_data[3]);
        size_t payload_offset = 4 + extension_length;
        // 更新负载指针,同时更新负载长度
        payload = extension_data + payload_offset;
        payload_len -= payload_offset;
    }

    // 4. 回调函数
    if(call_back_){
        call_back_->OnAudioData(header->timestamp, payload, payload_len);
    }
    return;
}
class AACDemuxer : public RTPDemuxer{
public:
    void InputData(const uint8_t* data, size_t size) override;
};

音频解复用器

  • 接收 RTP 包InputData 方法接收一个 RTP 包,并解析包头,获取负载类型(payloadType)和有效负载(音频数据)。
  • 检查负载类型:只有当 RTP 包的负载类型匹配时,才继续处理。
  • 处理 RTP 扩展头:如果 RTP 包包含扩展头,跳过扩展头并更新有效负载数据。
  • 传递音频数据:通过回调将解析出的音频数据传递给外部应用

头文件

class PCMaDemuxer : public RTPDemuxer {
public:
    void InputData(const uint8_t* data, size_t size) override;
};

源文件实现

void PCMADemuxer::InputData(const uint8_t* data, size_t size){
    struct RtpHeader *header = (struct RtpHeader *)data;
    int payload_type = header->payloadType;
    if(payload_type != payload_){
        return;
    }
    const uint8_t* payload = data + sizeof(struct RtpHeader);
    size_t payload_len = size - sizeof(struct RtpHeader);
    // rtp扩展头
    if (header->extension){
        const uint8_t *extension_data = payload;
        size_t extension_length = 4 * (extension_data[2] << 8 | extension_data[3]);
        size_t payload_offset = 4 + extension_length;
        payload = payload + payload_offset;
        payload_len = payload_len - payload_offset;
    }
    if(call_back_){
        call_back_->OnAudioData(ntohl(header->timestamp),  payload, payload_len);
    }
    return;
}

http://www.kler.cn/a/585794.html

相关文章:

  • 计算机视觉算法实战——花卉识别(主页有源码)
  • Spring框架详解(IOC容器-上)
  • JVM 如何保证 Java 程序的安全性?
  • TypeScript 高级类型 vs JavaScript:用“杂交水稻”理解类型编程
  • 【redis】set 类型:基本命令
  • 遥感数据获取、处理、分析到模型搭建全流程学习!DeepSeek、Python、OpenCV驱动空天地遥感数据分析
  • WPF程序使用AutoUpdate实现自动更新
  • Secs/Gem第一讲(基于secs4net项目的ChatGpt介绍)
  • 完善机器人:让 DeepSeek 使用Vue Element UI快速搭建 AI 交互页面
  • 【Linux系统编程】管道
  • 什么是mysql索引回表?
  • 杨辉三角形(信息学奥赛一本通-2043)
  • 智慧应急消防解决方案(35页PPT)(文末有下载方式)
  • doris:SQL 方言兼容
  • 【0x80070666】-已安装另一个版本...(Tableau 安装失败)
  • 裸机开发-GPIO外设
  • Android的第一次面试(Java篇)
  • 为什么 JPA 可以通过 findByNameContaining 自动生成 SQL 语句?
  • 如何在PHP中实现数据加密与解密:保护敏感信息
  • 小语言模型(SLM)技术解析:如何在有限资源下实现高效AI推理