音视频入门基础:RTP专题(18)——FFmpeg源码中,获取RTP的音频信息的实现(上)

由于本文篇幅较长,分为上、下两篇。

一、引言

通过FFmpeg命令可以获取到SDP描述的RTP流的的音频压缩编码格式、音频压缩编码格式的profile、音频采样率、通道数信息:

ffmpeg -protocol_whitelist "file,rtp,udp" -i XXX.sdp

而由《音视频入门基础:RTP专题(17)——音频的SDP媒体描述》可以知道,SDP协议中,a=rtpmap属性和a=fmtp属性中的config参数都会重复包含音频压缩编码格式、音频采样率、通道数音频这些信息。所以FFmpeg到底获取的是哪个地方的音频信息呢,本文为大家揭开谜底。

二、音频压缩编码格式

FFmpeg获取SDP描述的RTP流的音频压缩编码格式,是从SDP的a=rtpmap属性获取的。比如SDP中某一行的内容为:

a=rtpmap:97 MPEG4-GENERIC/48000/2

FFmpeg识别到上述“a=rtpmap”这个<type>后,会把后面的字符串“MPEG4-GENERIC”提取出来,检测是否存在相应的音视频压缩编码格式。

具体可以参考:《音视频入门基础:RTP专题(5)——FFmpeg源码中,解析SDP的实现》。

当识别到上述“a=rtpmap”这个<type>后,sdp_parse_line函数中会调用sdp_parse_rtpmap函数:

else if (av_strstart(p, "rtpmap:", &p) && s->nb_streams > 0) {/* NOTE: rtpmap is only supported AFTER the 'm=' tag */get_word(buf1, sizeof(buf1), &p);payload_type = atoi(buf1);rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];if (rtsp_st->stream_index >= 0) {st = s->streams[rtsp_st->stream_index];sdp_parse_rtpmap(s, st, rtsp_st, payload_type, p);}s1->seen_rtpmap = 1;if (s1->seen_fmtp) {parse_fmtp(s, rt, payload_type, s1->delayed_fmtp);}} 

sdp_parse_rtpmap函数中又会调用ff_rtp_handler_find_by_name函数:

/* parse the rtpmap description: <codec_name>/<clock_rate>[/<other params>] */
static int sdp_parse_rtpmap(AVFormatContext *s,AVStream *st, RTSPStream *rtsp_st,int payload_type, const char *p)
{
//...if (par->codec_id == AV_CODEC_ID_NONE) {const RTPDynamicProtocolHandler *handler =ff_rtp_handler_find_by_name(buf, par->codec_type);//...}
//...
}

ff_rtp_handler_find_by_name函数定义如下,可以看到其内部调用了rtp_handler_iterate函数:

const RTPDynamicProtocolHandler *ff_rtp_handler_find_by_name(const char *name,enum AVMediaType codec_type)
{void *i = 0;const RTPDynamicProtocolHandler *handler;while (handler = rtp_handler_iterate(&i)) {if (handler->enc_name &&!av_strcasecmp(name, handler->enc_name) &&codec_type == handler->codec_type)return handler;}return NULL;
}

rtp_handler_iterate函数定义如下,可以看到该函数内部遍历了全局数组rtp_dynamic_protocol_handler_list:

static const RTPDynamicProtocolHandler *rtp_handler_iterate(void **opaque)
{uintptr_t i = (uintptr_t)*opaque;const RTPDynamicProtocolHandler *r = rtp_dynamic_protocol_handler_list[i];if (r)*opaque = (void*)(i + 1);return r;
}

rtp_dynamic_protocol_handler_list数组定义如下:

static const RTPDynamicProtocolHandler *const rtp_dynamic_protocol_handler_list[] = {/* rtp */&ff_ac3_dynamic_handler,&ff_amr_nb_dynamic_handler,&ff_amr_wb_dynamic_handler,&ff_dv_dynamic_handler,&ff_g726_16_dynamic_handler,&ff_g726_24_dynamic_handler,&ff_g726_32_dynamic_handler,&ff_g726_40_dynamic_handler,&ff_g726le_16_dynamic_handler,&ff_g726le_24_dynamic_handler,&ff_g726le_32_dynamic_handler,&ff_g726le_40_dynamic_handler,&ff_h261_dynamic_handler,&ff_h263_1998_dynamic_handler,&ff_h263_2000_dynamic_handler,&ff_h263_rfc2190_dynamic_handler,&ff_h264_dynamic_handler,&ff_hevc_dynamic_handler,&ff_ilbc_dynamic_handler,&ff_jpeg_dynamic_handler,&ff_mp4a_latm_dynamic_handler,&ff_mp4v_es_dynamic_handler,&ff_mpeg_audio_dynamic_handler,&ff_mpeg_audio_robust_dynamic_handler,&ff_mpeg_video_dynamic_handler,&ff_mpeg4_generic_dynamic_handler,&ff_mpegts_dynamic_handler,&ff_ms_rtp_asf_pfa_handler,&ff_ms_rtp_asf_pfv_handler,&ff_qcelp_dynamic_handler,&ff_qdm2_dynamic_handler,&ff_qt_rtp_aud_handler,&ff_qt_rtp_vid_handler,&ff_quicktime_rtp_aud_handler,&ff_quicktime_rtp_vid_handler,&ff_rfc4175_rtp_handler,&ff_svq3_dynamic_handler,&ff_theora_dynamic_handler,&ff_vc2hq_dynamic_handler,&ff_vorbis_dynamic_handler,&ff_vp8_dynamic_handler,&ff_vp9_dynamic_handler,&gsm_dynamic_handler,&l24_dynamic_handler,&opus_dynamic_handler,&realmedia_mp3_dynamic_handler,&speex_dynamic_handler,&t140_dynamic_handler,/* rdt */&ff_rdt_video_handler,&ff_rdt_audio_handler,&ff_rdt_live_video_handler,&ff_rdt_live_audio_handler,NULL,
};

rtp_dynamic_protocol_handler_list数组中的每个元素都把SDP协议中的<encoding name>和具体的音视频压缩编码格式对应起来,比如“MPEG4-GENERIC”对应AAC:

const RTPDynamicProtocolHandler ff_mpeg4_generic_dynamic_handler = {.enc_name           = "mpeg4-generic",.codec_type         = AVMEDIA_TYPE_AUDIO,.codec_id           = AV_CODEC_ID_AAC,.priv_data_size     = sizeof(PayloadContext),.parse_sdp_a_line   = parse_sdp_line,.close              = close_context,.parse_packet       = aac_parse_packet,
};

X-MP3-draft-00"对应MP3ADU:

static const RTPDynamicProtocolHandler realmedia_mp3_dynamic_handler = {.enc_name   = "X-MP3-draft-00",.codec_type = AVMEDIA_TYPE_AUDIO,.codec_id   = AV_CODEC_ID_MP3ADU,
};

所以sdp_parse_rtpmap函数中执行语句:ff_rtp_handler_find_by_name(buf, par->codec_type)后,会通过遍历全局数组rtp_dynamic_protocol_handler_list,找到SDP协议中的<encoding name>对应的音视频压缩编码格式(比如“MPEG4-GENERIC”对应的是AAC),将其赋值给st->codecpar->codec_id(即AVCodecParameters的codec_id):

/* parse the rtpmap description: <codec_name>/<clock_rate>[/<other params>] */
static int sdp_parse_rtpmap(AVFormatContext *s,AVStream *st, RTSPStream *rtsp_st,int payload_type, const char *p)
{
//...if (par->codec_id == AV_CODEC_ID_NONE) {const RTPDynamicProtocolHandler *handler =ff_rtp_handler_find_by_name(buf, par->codec_type);//...}
//...
}

然后在sdp_parse_line函数外部,通过avcodec_parameters_to_context函数将AVCodecParameters的codec_id赋值给AVCodecContext的codec_id:

int avcodec_parameters_to_context(AVCodecContext *codec,const AVCodecParameters *par)
{
//...codec->codec_id   = par->codec_id;
//...
}

然后在dump_stream_format函数中,通过avcodec_string函数中的语句:codec_name = avcodec_get_name(enc->codec_id) 拿到AVCodecContext的codec_id对应的音频压缩编码格式名称。最后再在dump_stream_format函数中将音频压缩编码格式打印出来:

void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode)
{
//...codec_name = avcodec_get_name(enc->codec_id);
//...
}

所以FFmpeg获取SDP描述的RTP流的音频压缩编码格式,是从SDP的“a=rtpmap”这一行获取的:

三、音频压缩编码格式的profile

音频压缩编码格式还有附带的profile(规格)。比如音频压缩编码格式为AAC,根据《ISO14496-3-2009.pdf》第124页,还有AAC Main、AAC LC、AAC SSR、AAC LTP这几种规格:

FFmpeg获取SDP描述的RTP流的音频压缩编码格式的profile,获取的是a=fmtp属性中的config参数中的信息,即AudioSpecificConfig中的audioObjectType。由《音视频入门基础:AAC专题(11)——AudioSpecificConfig简介》可以知道,Audio Specific Config中存在一个占5位或11位的audioObjectType属性,表示音频对象类型:

0: Null
1: AAC Main
2: AAC LC (Low Complexity)
3: AAC SSR (Scalable Sample Rate)
4: AAC LTP (Long Term Prediction)
5: SBR (Spectral Band Replication)
6: AAC Scalable
7: TwinVQ
8: CELP (Code Excited Linear Prediction)
9: HXVC (Harmonic Vector eXcitation Coding)
10: Reserved
11: Reserved
12: TTSI (Text-To-Speech Interface)
13: Main Synthesis
14: Wavetable Synthesis
15: General MIDI
16: Algorithmic Synthesis and Audio Effects
17: ER (Error Resilient) AAC LC
18: Reserved
19: ER AAC LTP
20: ER AAC Scalable
21: ER TwinVQ
22: ER BSAC (Bit-Sliced Arithmetic Coding)
23: ER AAC LD (Low Delay)
24: ER CELP
25: ER HVXC
26: ER HILN (Harmonic and Individual Lines plus Noise)
27: ER Parametric
28: SSC (SinuSoidal Coding)
29: PS (Parametric Stereo)
30: MPEG Surround
31: (Escape value)
32: Layer-1
33: Layer-2
34: Layer-3
35: DST (Direct Stream Transfer)
36: ALS (Audio Lossless)
37: SLS (Scalable LosslesS)
38: SLS non-core
39: ER AAC ELD (Enhanced Low Delay)
40: SMR (Symbolic Music Representation) Simple
41: SMR Main
42: USAC (Unified Speech and Audio Coding) (no SBR)
43: SAOC (Spatial Audio Object Coding)
44: LD MPEG Surround
45: USAC

由《音视频入门基础:AAC专题(12)——FFmpeg源码中,解码AudioSpecificConfig的实现》可以知道,

FFmpeg源码中使用decode_audio_specific_config_gb函数来读取AudioSpecificConfig的信息。decode_audio_specific_config_gb函数中会调用ff_mpeg4audio_get_config_gb函数,而ff_mpeg4audio_get_config_gb函数中,通过语句:c->object_type = get_object_type(gb) 获取AudioSpecificConfig的audioObjectType属性。执行decode_audio_specific_config_gb函数后,m4ac指向的变量会得到从AudioSpecificConfig中解码出来的属性:

static inline int get_object_type(GetBitContext *gb)
{int object_type = get_bits(gb, 5);if (object_type == AOT_ESCAPE)object_type = 32 + get_bits(gb, 6);return object_type;
}

然后在decode_audio_specific_config_gb函数外部,通过aac_decode_frame_int函数将上一步得到的audioObjectType属性赋值给AVCodecContext的profile:

static int aac_decode_frame_int(AVCodecContext *avctx, AVFrame *frame,int *got_frame_ptr, GetBitContext *gb,const AVPacket *avpkt)
{
//...// The AV_PROFILE_AAC_* defines are all object_type - 1// This may lead to an undefined profile being signaledac->avctx->profile = ac->oc[1].m4ac.object_type - 1;
//...
}

然后在dump_stream_format函数中,通过avcodec_string函数中的语句:profile = avcodec_profile_name(enc->codec_id, enc->profile)拿到上一步中得到的AVCodecContext的profile。最后再在dump_stream_format函数中将profile打印出来:

void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode)
{
//...profile = avcodec_profile_name(enc->codec_id, enc->profile);
//...
}

所以FFmpeg获取SDP描述的RTP流的的音频压缩编码格式的profile,获取的是a=fmtp属性中的config参数中的信息,即AudioSpecificConfig中的audioObjectType:

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

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

相关文章

双指针算法介绍+算法练习(2025)

一、介绍双指针算法 双指针&#xff08;或称为双索引&#xff09;算法是一种高效的算法技巧&#xff0c;常用于处理数组或链表等线性数据结构。它通过使用两个指针来遍历数据&#xff0c;从而减少时间复杂度&#xff0c;避免使用嵌套循环。双指针算法在解决诸如查找、排序、去重…

如何安装旧版本的Pytorch

不同的项目所使用的Pytorch版本可能不同&#xff0c;一般而言&#xff0c;高版本的Pytorch可以向下兼容的&#xff0c;但有时可能会需要旧版本的Pytorch。 1、首先进入Pytorch官网&#xff08;PyTorch&#xff09;&#xff0c;下滑找到” install previous versions of PyTorc…

Easysearch 使用 AWS S3 进行快照备份与还原:完整指南及常见错误排查

Easysearch 可以使用 AWS S3 作为远程存储库&#xff0c;进行索引的快照&#xff08;Snapshot&#xff09;备份和恢复。同时&#xff0c;Easysearch 内置了 S3 插件&#xff0c;无需额外安装。以下是完整的配置和操作步骤。 1. 在 AWS S3 上创建存储桶 登录 AWS 控制台&#x…

Nginx + Keepalived 高可用集群

一、NginxKeepalived 原理 1.1.Nginx 负载均衡机制 Nginx 是一款轻量级且高性能的 Web 服务器和反向代理服务器&#xff0c;在负载均衡方面有着卓越的表现。其具备强大的七层流量管理能力&#xff0c;能够基于 URL、Cookie、HTTP 头信息等对请求进行精准路由。例如&#xff0…

面试提问(1)

面试提问 1.你能说一说C/C之间的区别吗&#xff1f;2.你能将一些你对构造函数和析构函数的认识吗&#xff1f;3.讲一下继承和多态4.你了解TCP/IP四层网络模型吗&#xff1f;5.你了解三次握手和四次挥手吗&#xff1f;6.讲一下进程和线程&#xff1f;7.你对二叉树的了解有哪些&a…

Adobe Genuine Service Alert 一直弹窗,老是一直弹窗【解决方法】

在使用Adobe系列软件时&#xff0c;若没有正版授权&#xff0c;则会出现弹窗&#xff0c;该弹窗是由Adobe Genuine Service软件弹出的&#xff0c;且该弹窗无法关闭&#xff0c;下文介绍如何永久关闭该弹窗。 方法一&#xff1a; 首先在任务栏鼠标右键打开任务管理器&#xff…

防汛应急包,快速响应,守护安全

根据中国水利部统计&#xff0c;自1949年以来&#xff0c;我国几乎每年都面临洪水威胁&#xff0c;其中20世纪90年代后洪涝灾害频率显著增加&#xff0c;仅1990-2009年间就发生超4000起较大灾害&#xff0c;直接经济损失近3万亿元&#xff0c;受灾人口达20亿人次。在2020年长江…

一文了解JVM的垃圾回收

Java堆内存结构 java堆内存是垃圾回收器管理的主要区域&#xff0c;也被称为GC堆。 为了方便垃圾回收&#xff0c;堆内存被分为新生代、老年代和永久代。 新创建的对象的内存会在新生代中分配&#xff0c;达到一定存活时长后会移入老年代&#xff0c;而永久代存储的是类的元数…

【人工智能 | 大数据】基于人工智能的大数据分析方法

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈智能大数据分析 ⌋ ⌋ ⌋ 智能大数据分析是指利用先进的技术和算法对大规模数据进行深入分析和挖掘&#xff0c;以提取有价值的信息和洞察。它结合了大数据技术、人工智能&#xff08;AI&#xff09;、机器学习&#xff08;ML&a…

【C语言】编译和链接详解

hi&#xff0c;各位&#xff0c;让我们开启今日份博客~ 小编个人主页点这里~ 目录 一、翻译环境和运行环境1、翻译环境1.1预处理&#xff08;预编译&#xff09;1.2编译1.2.1词法分析1.2.2语法分析1.2.3语义分析 1.3汇编1.4链接 2.运行环境 一、翻译环境和运行环境 在ANSI C…

在Simulink中将Excel数据导入可变负载模块的方法介绍

文章目录 数据准备与格式要求Excel数据格式MATLAB预处理数据导入方法使用From Spreadsheet模块(直接导入Excel)通过MATLAB工作区中转(From Workspace模块)使用1-D Lookup Table模块(非线性负载映射)Signal Builder模块(变载工况导入)可变负载模块配置注意事项与调试在S…

Java 大视界 -- Java 大数据在智慧文旅虚拟导游与个性化推荐中的应用(130)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

【微知】tmux如何在一个会话的1个窗口中水平分割或者垂直分割窗口?(垂直 Ctrl + b, %; 切换Ctrl + b, 方向键; ctrl d关闭)

背景 除了直接创建窗口&#xff0c;还可以分割一个窗口。创建窗口参考兄弟篇&#xff1a;tmux如何在某个会话session中创建多个窗口&#xff1f;如何切换&#xff1f;&#xff08;Ctrlb c创建&#xff1b;Ctrlb 数字 切换&#xff1b;Ctrlb &关闭&#xff09; 命令 垂…

强化学习(赵世钰版)-学习笔记(7.时序差分学习)

本章是课程算法与方法中的第四章&#xff0c;介绍的时序差分学习算法是基于随机近似方法设计的强化学习方法&#xff0c;也是model-free的方法。 时序差分算法是一种近似估计策略状态值的算法&#xff0c;具体的形式如下&#xff1a; 本质上是在当前t时刻&#xff0c;被访问到的…

无公网IP也能远程控制Windows:Linux rdesktop内网穿透实战

文章目录 前言1. Windows 开启远程桌面2. Linux安装rdesktop工具3. Win安装Cpolar工具4. 配置远程桌面地址5. 远程桌面连接测试6. 设置固定远程地址7. 固定地址连接测试 前言 如今远程办公已经从一种选择变成了许多企业和个人的必修课&#xff0c;而如何在Linux系统上高效地访…

深度学习与大模型-矩阵

矩阵其实在我们的生活中也有很多应用&#xff0c;只是我们没注意罢了。 1. 矩阵是什么&#xff1f; 简单来说&#xff0c;矩阵就是一个长方形的数字表格。比如你有一个2行3列的矩阵&#xff0c;可以写成这样&#xff1a; 这个矩阵有2行3列&#xff0c;每个数字都有一个位置&a…

【实战ES】实战 Elasticsearch:快速上手与深度实践-8.2.1AWS OpenSearch无服务器方案

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 8.2.1AWS OpenSearch 无服务器方案深度解析与实践指南1. Serverless架构的核心价值与行业趋势1.1 传统Elasticsearch集群的运维挑战1.2 Serverless技术演进路线技术特性对比…

使用 Arduino 和 ESP8266 Wi-Fi 模块发送电子邮件

使用 Arduino Uno 和 ESP8266 Wi-Fi 模块发送电子邮件 我们正在迈向物联网 (IoT) 世界。这项技术在电子和嵌入式系统中起着非常重要的作用。从任何微控制器或嵌入式系统发送电子邮件都是非常基本的事情,这在 IoT 中是必需的。因此,在本文中,我们将学习“如何使用 Wi-Fi 和…

jmeter-AES加密

AES(全称&#xff1a;Advanced Encryption Standard)对称加密算法&#xff0c;也就是加密和解密用到的密钥是相同的&#xff0c;这种加密方式加密速度非常快&#xff0c; 适合经常发送数据的场合&#xff0c;如&#xff1a;数据加密存储、网络通信加密等。 在进行接口测试或接…

四种 No-SQL

在一个常规的互联网服务中&#xff0c;读取与写入的比例大约是 100:1 到 1000:1。然而&#xff0c;从硬盘读取时&#xff0c;数据库连接操作耗时&#xff0c;99% 的时间花费在磁盘寻址上。 为了优化读取性能&#xff0c;非规范化的设计通过添加冗余数据或分组数据来引入。下述…