ONVIF网络摄像头(IPC)客户端开发—RTSP RTCP RTP加载AAC音频流
前言:
RTSP,RTCP,RTP一般是一起使用,在FFmpeg和live555这些库中,它们为了更好的适用性,所以实现起来非常复杂,直接查看FFmpeg和Live555源代码来熟悉这些协议非常吃力,这里将它们独立出来实现,以便更好的理解协议。本文主要介绍RTSP,RTCP,RTP加载AAC音频流。
说明:
(1)大华IPC摄像头作为服务端
(2)在ubuntu16.04中编译实现测试程序
(3)服务端IP: 192.168.0.120
(4)客户端IP: 192.168.0.128
协议介绍:
用一句简单的话总结:RTSP发起/终结流媒体、RTP传输流媒体数据 、RTCP对RTP进行控制,同步。RTSP属于四层网络当中的应用层,RTP,RTCP属于传输层。RTSP和RTCP协议在之前博客中已经介绍过,这里不再重复。可以参考博客:
《ONVIF网络摄像头(IPC)客户端开发—最简RTSP客户端实现》
《ONVIF网络摄像头(IPC)客户端开发—RTSP RTCP RTP加载H264视频流》
RTP加载音频流与RTP加载视频流有些不一样,加载视频流一般使用单一NAL单元模式和分片封包模式,加载音频使用的是组合封包模式(Aggregation Packet)。组合封包模式开始是12字节的RTP头信息,接着的是组合封包头长度(2)字节和组合封包头(2字节),最后面是音频流数据,示意图如下:

AAC格式
在RTP流中传输的AAC音频流是不带有ADTS(Audio Data Transport Stream)信息的,所以客户端在接收到AAC数据流的时候需要自己手动添加上ADTS信息才能被正常的播放。ADTS格式如下:

上图来源于网络,AAC ES就是RTP接收到的AAC音频数据,其他参数值基本上都是固定的,需要填充的是采样频率,通道数和长度(注意:该长度=ADTS+AAC ES)。采样率定义如下:
There are 13 supported frequencies:
0: 96000 Hz
1: 88200 Hz
2: 64000 Hz
3: 48000 Hz
4: 44100 Hz
5: 32000 Hz
6: 24000 Hz
7: 22050 Hz
8: 16000 Hz
9: 12000 Hz
10: 11025 Hz
11: 8000 Hz
12: 7350 Hz
13: Reserved
14: Reserved
15: frequency is written explictly
声道定义如下:
0: Defined in AOT Specifc Config
1: 1 channel: front-center
2: 2 channels: front-left, front-right
3: 3 channels: front-center, front-left, front-right
4: 4 channels: front-center, front-left, front-right, back-center
5: 5 channels: front-center, front-left, front-right, back-left, back-right
6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel
7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel
8-15: Reserved
启动音频流:
RTSP客户端发送DESCRIBE,服务端会回复一个SDP,SDP里面有描述了音频流和视频流信息:
v=0
o=- 2229913047 2229913047 IN IP4 0.0.0.0
s=Media Server
c=IN IP4 0.0.0.0
t=0 0
a=control:*
a=packetization-supported:DH
a=rtppayload-supported:DH
a=range:npt=now-
m=video 0 RTP/AVP 96
a=control:trackID=0
a=framerate:15.000000
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=64002A;sprop-parameter-sets=Z2QAKqwsaoHgCJ+WbgICAgQA,aO48sAA=
a=recvonly
m=audio 0 RTP/AVP 97
a=control:trackID=1
a=rtpmap:97 MPEG4-GENERIC/16000
a=fmtp:97 streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1408
a=recvonly
m=application 0 RTP/AVP 107
a=control:trackID=4
a=rtpmap:107 vnd.onvif.metadata/90000
a=recvonly
从上面可以看到trackID=0 是一个h264视频流,trackID=1是一路AAC音频流,并且可以看到音频流采样率为16000,profile=1,sizelength=13;等等。
为了测试音频流,这里直接写成固定的,在启动的时候直接启动trackID=1音频流:
SETUP rtsp://192.168.0.120:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif/trackID=1 RTSP/1.0
CSeq: 4
Authorization: Digest username="admin", realm="Login to F8990C5E0599500D",nonce="780e768a-7f13-4f66-a08c-99d76e99e518",uri="rtsp://192.168.0.120:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif",response="920e21b61578599683bff9129352a6c7"
User-Agent: Caibiao_Lee
Transport:RTP/AVP;unicast;client_port=55182-55183
代码实现:
这里贴出RTP数据包解析和ADTS头添加的测试函数:
/********************************************************
Function: RTP_Client_UnPackAACAndStore
Description: 根据RTP协议解析网络接收到的AAC数据包,并且为AAC数据
添加ADTS头信息,最后将数据写入到文件中去。
Input:
*pu8Addr:RTP接收数据的地址
s32Len :RTP接收数据的长度
OutPut:none
Return: 0 成功;非0 失败
Others:
注意ADTS头长度的填充,这个长度包括了它自己本身
Author: Caibiao Lee
Date: 2019-10-05
*********************************************************/
static int RTP_Client_UnPackAACAndStore(unsigned char *pu8Addr,int s32Len)
{
RTP_HEADER_S *l_pstRTPHeader = NULL;
RTP_PACKET_S l_stRTPPack = {0};
RTP_PACKET_S *l_pstRTPPack = &l_stRTPPack;
unsigned short l_u16HeadrLen = 0;
unsigned short l_u16Header = 0;
unsigned short l_u16WriteLen = 0;
if((NULL==pu8Addr)||(s32Len<=0))
{
printf("input para error \n",__FUNCTION__,__LINE__);
return -1;
}
l_pstRTPHeader = (RTP_HEADER_S*)pu8Addr;
/**RTP 信息提取**/
l_pstRTPPack->u8Version = l_pstRTPHeader->bit1Version;
l_pstRTPPack->u8Padding = l_pstRTPHeader->bit1Padding;
l_pstRTPPack->u8Extension = l_pstRTPHeader->bit1Extension;
l_pstRTPPack->u8Cc = l_pstRTPHeader->bit4CsrcLen;
l_pstRTPPack->u8Marker = l_pstRTPHeader->bit1Marker;
l_pstRTPPack->u8Pt = l_pstRTPHeader->bit7PayLoadType;
/**RTP 序列号**/
l_pstRTPPack->u32SeqNum = 0;
l_pstRTPPack->u32SeqNum = (pu8Addr[2] & 0xff);
l_pstRTPPack->u32SeqNum <<= 8;
l_pstRTPPack->u32SeqNum |= (pu8Addr[3] & 0xff);
/**RTP 时间戳**/
l_pstRTPPack->u32TimeStamp = (pu8Addr[4] & 0xff);
l_pstRTPPack->u32TimeStamp <<= 8;
l_pstRTPPack->u32TimeStamp |= (pu8Addr[5] & 0xff);
l_pstRTPPack->u32TimeStamp <<= 8;
l_pstRTPPack->u32TimeStamp |= (pu8Addr[6] & 0xff);
l_pstRTPPack->u32TimeStamp <<= 8;
l_pstRTPPack->u32TimeStamp |= (pu8Addr[7] & 0xff);
/**RTP 同步源ID**/
l_pstRTPPack->u32Ssrc = (pu8Addr[8] & 0xff);
l_pstRTPPack->u32Ssrc <<= 8;
l_pstRTPPack->u32Ssrc |= (pu8Addr[9] & 0xff);
l_pstRTPPack->u32Ssrc <<= 8;
l_pstRTPPack->u32Ssrc |= (pu8Addr[10] & 0xff);
l_pstRTPPack->u32Ssrc <<= 8;
l_pstRTPPack->u32Ssrc |= (pu8Addr[11] & 0xff);
#if 0
printf("\n\n");
printf("u8Version :%d \n",l_pstRTPPack->u8Version);
printf("u8Padding :%d \n",l_pstRTPPack->u8Padding);
printf("u8Extension :%d \n",l_pstRTPPack->u8Extension);
printf("u8Cc :%d \n",l_pstRTPPack->u8Cc);
printf("u8Marker :%d \n",l_pstRTPPack->u8Marker);
printf("u8Pt :%d \n",l_pstRTPPack->u8Pt);
printf("u32SeqNum :%d \n",l_pstRTPPack->u32SeqNum);
printf("u32TimeStamp :%d \n",l_pstRTPPack->u32TimeStamp);
printf("u32Ssrc :%u \n",l_pstRTPPack->u32Ssrc);
printf("pu32Paylen :%d \n",l_pstRTPPack->u32Paylen);
#endif
l_u16HeadrLen = pu8Addr[12];
l_u16HeadrLen <<=8;
l_u16HeadrLen = pu8Addr[13];
l_u16Header = pu8Addr[14];
l_u16Header <<=8;
l_u16Header = pu8Addr[15];
/**ADTS**/
if(g_WriteFd>0)
{
/**for debug**/
unsigned char ADTS[] = {0xFF, 0xF1, 0x00, 0x00, 0x00, 0x00, 0xFC};
int audioSamprate = 16000;/**音频采样率**/
int audioChannel = 2; /**音频声道 1或2**/
int audioBit = 16; /**16位 固定**/
switch(audioSamprate) /**bit2~bit6 表示帧率**/
{
case 8000: /**11**/
ADTS[2] = 0x2c;
break;
case 16000: /**8**/
ADTS[2] = 0x60;
break;
case 32000: /**5**/
ADTS[2] = 0x54;
break;
case 44100: /**4**/
ADTS[2] = 0x50;
break;
case 48000: /**3**/
ADTS[2] = 0x4C;
break;
case 96000: /**0**/
ADTS[2] = 0x40;
break;
default:
break;
}
ADTS[3] = (audioChannel==2)?0x80:0x40;/**bit **/
l_u16WriteLen = s32Len - 16 + 7;
l_u16WriteLen <<= 5; /**8bit * 2 - 11 = 5(headerSize 11bit)**/
l_u16WriteLen |= 0x1F; /**5 bit 1**/
ADTS[4] = l_u16WriteLen>>8;
ADTS[5] = l_u16WriteLen & 0xFF;
fwrite(ADTS,1,7,g_WriteFd);
l_u16WriteLen = s32Len - 16;
fwrite(&pu8Addr[16],1,l_u16WriteLen,g_WriteFd);
}
return 0;
}
参考博客:
《AAC的ADTS头文件信息介绍》
《多媒体封装格式详解--- AAC ADTS格式分析》
工程下载:
在 liwen01 公众号中回复 网络编程 获取工程代码,本章代码工程名为:RtspRtcpRtpLoad_AAC.tar.gz
---------------------------End---------------------------
长按识别二维码
关注 liwen01 公众号
ONVIF网络摄像头(IPC)客户端开发—RTSP RTCP RTP加载AAC音频流的更多相关文章
- Android--------WebView+H5开发仿美团 预加载,加载失败和重新加载
Android嵌入式开发已经占大多数了,很多界面都是以网页的形式展示,WebView可以使得网页轻松的内嵌到app里,还可以直接跟js相互调用. 本博客主要是模仿美团的旅游出行模块的预加载,网页加载失 ...
- iOS开发UI篇—懒加载
iOS开发UI篇—懒加载 1.懒加载基本 懒加载——也称为延迟加载,即在需要的时候才加载(效率低,占用内存小).所谓懒加载,写的是其get方法. 注意:如果是懒加载的话则一定要注意先判断是否已经有了, ...
- seajs实现JavaScript 的 模块开发及按模块加载
seajs实现了JavaScript 的 模块开发及按模块加载.用来解决繁琐的js命名冲突,文件依赖等问题,其主要目的是令JavaScript开发模块化并可以轻松愉悦进行加载. 官方文档:http:/ ...
- Android开发中如何解决加载大图片时内存溢出的问题
Android开发中如何解决加载大图片时内存溢出的问题 在Android开发过程中,我们经常会遇到加载的图片过大导致内存溢出的问题,其实类似这样的问题已经屡见不鲜了,下面将一些好的解决方案分享给 ...
- Linux内核启动代码分析二之开发板相关驱动程序加载分析
Linux内核启动代码分析二之开发板相关驱动程序加载分析 1 从linux开始启动的函数start_kernel开始分析,该函数位于linux-2.6.22/init/main.c start_ke ...
- Google官方网络框架-Volley的使用解析Json以及加载网络图片方法
Google官方网络框架-Volley的使用解析Json以及加载网络图片方法 Volley是什么? Google I/O 大会上,Google 推出 Volley的一个网络框架 Volley适合什么场 ...
- 【Java Web开发学习】Spring加载外部properties配置文件
[Java Web开发学习]Spring加载外部properties配置文件 转载:https://www.cnblogs.com/yangchongxing/p/9136505.html 1.声明属 ...
- 基于JRebel开发的MybatisPlus热加载插件
前言 前天项目中使用了mybatis-plus,但是搭配Jrebel开发项目时,发现修改mapper的xml,或者mapper方法中的注解,Jrebel并没有能够reload mapper.于是就有了 ...
- .NET混合开发解决方案11 WebView2加载的网页中JS调用C#方法
系列目录 [已更新最新开发文章,点击查看详细] WebView2控件应用详解系列博客 .NET桌面程序集成Web网页开发的十种解决方案 .NET混合开发解决方案1 WebView2简介 .NE ...
- Android 框架修炼-自己开发高效异步图片加载框架
一.概述 目前为止,第三方的图片加载框架挺多的,比如UIL , Volley Imageloader等等.但是最好能知道实现原理,所以下面就来看看设计并开发一个加载网络.本地的图片框架. 总所周知,图 ...
随机推荐
- spring是否线程安全
spring 管理的bean默认是单例的,可通过 scope 属性设置scope="singleton" 默认是单例,可修改为scope="prototype" ...
- 某RBAC管理系统审计
某RBAC管理系统审计 前言 这个管理系统的审计我去年就开始了但烂尾了,那时候太热闹了log4j2,cs的cve反制等等.这个都给忘了,所以本篇可能有些图有点老,现在就是旧图没一个个换遇到的新的就加上 ...
- Git commit emoji 对照表
emoji emoji代码 commit说明 (调色板) :art: 改进代码结构/代码格式 ️ (闪电) :zap: 提升性能 (赛马) :racehorse: 提升性能 (火焰) :fire: 移 ...
- Luogu1419 区间问题 二分 单调优化
原题链接 题意 给定一段长度为1e5的序列A,并且给我们一个范围 \([S, T]\), 要求我们求出一段长度在这个范围内的连续子序列,并且要使这个连续子序列的平均值最大,输出这个平均值. 思路 一开 ...
- 华为IoT首席架构师王启军:全栈工程师“养成记”
在王启军的公众号里,有一篇<My Team>的文章,里面记录了早年他所带团队成长的心得. 这个被他称为完美组合的团队,并不是来自大厂名企,彼时王启军给不起高待遇,团队核心成员中还有很多人是 ...
- JAVA已过气?中俄大佬对话告诉你俄罗斯最受欢迎的编程语言是什么!
摘要:中俄大佬对话:俄罗斯最受欢迎的编程语言是什么?Gitee如何抗住数据压力? 众所周知,Java作为一门非常成熟的语言,国内拥趸者众多,但随着后浪们的崛起,如今的Java在国际上是否还占据主流地位 ...
- 窗口到底有多滑动?揭秘TCP/IP滑动窗口的工作原理
本文分享自华为云社区<窗口到底有多滑动?揭秘TCP/IP滑动窗口的工作原理>,作者: Lion Long. 当涉及网络性能优化和数据传输可靠性时,TCP/IP滑动窗口是一个关键的技术.本文 ...
- 高性能网络设计秘笈:深入剖析Linux网络IO与epoll
本文分享自华为云社区<高性能网络设计秘笈:深入剖析Linux网络IO与epoll>,作者: Lion Long . 一.epoll简介 epoll是Linux内核中一种可扩展的IO事件处理 ...
- 来喽,来喽,Python 3.9正式版发布了~~~
摘要:2020年10月5日,在全国人员欢度国庆节和中秋节时,Python 3.9 悄摸摸地正式发布了. 2020年10月5日,在全国人员欢度国庆节和中秋节时,Python 3.9 悄摸摸地正式发布了. ...
- Multi-Architecture镜像制作指南已到,请查收!
摘要:使用Multi-Architecture镜像,可以让docker根据系统架构去拉取对应的镜像,服务的部署脚本等可以在不同架构的系统间使用相同的配置,减化服务配置,提高了服务在不同系统架构间的一致 ...