通过ZLMediaKit学习RTSP协议
分析代码的准备
1.1.编译及运行
1.下载源码
代码从git获取,如果没安装git,需要执行
sudo apt-get install git
cd /opt
#拉取项目代码
git clone https://github.com/ZLMediaKit/ZLMediaKit.git
#国内用户推荐从同步镜像网站gitee下载
git clone --depth 1 https://gitee.com/xia-chu/ZLMediaKit
cd ZLMediaKit
#不要忘了这句命令
git submodule update --init
2..编译
完整的编译需要第三方库,可以查找相关内容。这里只是简单的修改代码后进行编译的步骤:
cd /opt/ZLMediaKit
mkdir build
cd build
cmake .. -DENABLE_WEBRTC=true -DOPENSSL_ROOT_DIR=/opt/openssl -DOPENSSL_LIBRARIES=/opt/openssl/lib
cmake --build . --target MediaServer
(简单的修改文件,只执行这一步。删除build文件夹,才执行上述的全部步骤。)
3.执行
cd /opt/ZLMediaKit/release/linux/Debug
./MediaServer
1.2.推送
1.ffmpeg的推送(以自己的机器为例)
e:
cd e:\Demo\CGAvioRead\Debug
d:
cd d:\CGAvioRead\Debug
ffmpeg -re -stream_loop -1 -i d:/H264_AAC_2021-02-10_1080P.mp4 -vcodec copy -acodec copy -f rtsp -rtsp_transport tcp rtsp://192.168.0.109:554/live/Camera_00001
ffmpeg -re -stream_loop -1 -i e:/H264_AAC_2021-02-10_1080P.mp4 -vcodec copy -acodec copy -f rtsp -rtsp_transport tcp rtsp://10.10.15.30:554/live/Camera_00001
2.OBS推流
可推流rtmp协议(部分版本支持RTSP协议),操作固定,可参考网上资料。
Wirehark截图
通过ffmpeg进行推流。
2.1.RTSP
(1)推流截图
(2)拉流截图
(3)回复截图
2.2.RTCP协议截图
2.3 RTP的截图
1.推流
2.拉流
RTSP协议的处理
RTSP发起/终结流媒体,和rtmp的命令类似。RtspSession::onRecv—》HttpRequestSplitter::input--》RtspSplitter::onRecvHeader—》RtspSession::onWholeRtspPacket
1.RtspSession::onWholeRtspPacket
- 如何区分RTSP协议与其他协议?
在RtspSplitter::onSearchPacketTail_l中,data[0] != '$'。
(2)与处理命令函数RtmpSession::onProcessCmd相似。
(3)Parser类为rtsp/http/sip解析类。在此将收到的内容解析以后存到此类中。
2. RtspSession::sendRtspResponse
(1)参数protocol在定义中默认,protocol = "RTSP/1.0"。
(2)先处理printer << protocol << " " << res_code << "\r\n";即截图的第一行。
(3)接着对key和value进行添加。
(4)添加换行:printer << "\r\n";。
(5)如查sdp不为空,添加sdp。
(6)对组成的数据直接进行发送。
3. RtspSession::handleReq_Options
未过多处理,直接将支持的命令通过sendRtspResponse回复给推流端和拉流端。
===========================================================
4. RtspSession::handleReq_ANNOUNCE
只在推流中会出现此命令,主要是对SDP进行处理。
(1)程序的流程,要明白函数的流程:
NOTICE_EMIT—》Broadcast::PublishAuthInvoker invoker(lamdba函数)—》auto onRes(lamdba函数)。
(2)full_url 是推流的全连接。如rtsp://192.168.169.197:554/live/Camera_00001
(3)_media_info主要存储连接信息:
{_full_url = "rtsp://192.168.169.197:554/live/Camera_00001", _schema = "rtsp", _host = "192.168.169.197", _port = 554, _vhost = "__defaultVhost__", _app = "live",
_streamid = "Camera_00001", _param_strs = ""}
(3)SdpParser sdpParser(parser.Content());对sdp内容进行解析。--5
(4)_sessionid是一个1从字母和数字的数组中随机取出的12位数,不能保证完全不重复。
(5)_sdp_track为2。
(6)执行if (!_push_src),创建推流源。
(7)调用sendRtspResponse回复。
(8)NOTICE_EMIT—invoker—onRes--RtspMediaSourceImp::setSdp—3.1
5. SdpParser::load(const string &sdp)
- RtspSession::handleReq_ANNOUNCE-SdpParser sdpParser(parser.content())中在构造函数中。
- Ffmpeg推流的一个截图。
(3)经过SdpParser解析以后,存到std::vector<SdpTrack::Ptr> _sdp_track中,打印变量为:
6. RtspSession::handleReq_Setup
(1) setup要接收两次,指明流媒体的传输方式 。
(2) parser.fullUrl(),如:tsp://10.10.15.30:554/live/Camera_00001/streamid=0,getTrackIndexByControlUrl是通过对比的方式得到SdpTrack的序号。
(3)strTransport,如:"RTP/AVP/TCP;unicast;interleaved=0-1;mode=record",所以rtpType = Rtsp::RTP_TCP;
(4)经解析后key_values的值为:["interleaved"] = "0-1", ["mode"] = "record", ["RTP/AVP/TCP"] = "", ["unicast"] = ""。
(5)解析出interleaved_rtp,interleaved_rtcp的值。
7. RtspSession::handleReq_RECORD
(1) _content_base,如:rtsp://10.10.15.30:554/live/Camera_00001
(2)track->getControlUrl,如:url=rtsp://10.10.15.30:554/live/Camera_00001/streamid=0,
(3)rtp_info.pop_back()去掉后的“,”
+=========================================================
8.RtspSession::handleReq_Describe
与RtspSession::handleReq_ANNOUNCE相似,拉流时发生。
- authorization(授权):值为空。
- strong_self->emitOnPlay();--9
9. RtspSession::emitOnPlay
(1)无需rtsp专属认证, 那么继续url通用鉴权认证(on_play)
(2) strong_self->onAuthSuccess();--10
10. RtspSession::onAuthSuccess()
(1) MediaSource::findAsync—11
(2)回调函数,对发现的MediaSource进行处理。-12
11. MediaSource::findAsync—》findAsync_l
(1)find_l找到源,对源进行回调处理
(2)find_l--13
12.回调函数的处理
(1)strong_self->_sdp_track的值如下图所示:
(2)为每个_sdp_track创建一个RtcpContextForSend。
(3)为拉流创建_sessionid。
(4)为拉流指定src.
(5) 为每个_sdp_track赋值。
(6)通过sendRtspResponse回复。
13. MediaSource::Ptr find_l
通过这行代码:MediaSource::for_each_media([&](const MediaSource::Ptr &src) { ret = std::move(const_cast<MediaSource::Ptr &>(src)); }, schema, vhost, app, id);找到MediaSource。
=========================================================+
14. RtspSession::handleReq_Play
(1)得到Play源,auto play_src = _play_src.lock();
(2)得到回复信息。
(3) _play_reader->setReadCB-- strong_self->sendRtpPacket,读取和发送。
(4) _RingReader—setDetachCB.
3.1.TRACK的创建
1. RtspMediaSourceImp::setSdp
(1)_demuxer->loadSdp(strSdp);--2
(2)RtspMediaSource::setSdp(strSdp);
不需要编解码: RtspMediaSource::setSdp(strSdp)—对sdp进行解析—注册事件(MediaSource::regist())
2. RtspDemuxer::loadSdp
(1) makeVideoTrack(track);--3
(2) makeAudioTrack(track);--3
(3)注册事件addTrackCompleted,track完成。
3. makeVideoTrack和makeAudioTrack
两者执行的类似,在Factory::getTrackBySdp函数中得到track。
(1)//生成Track对象 H264Track—4
(2) //生成Track对象 AACTrack—5
(3)生成H264RtpDecoder对象和生成AACRtpDecoder对象
只是创建了一个frame对象。
(4)_video_rtp_decoder->addDelegate--FrameWriterInterface* addDelegate
设置rtp解码器代理,生成的frame写入该Track
(5)addTrack--11
4. H264Track的创建
(1)得到sps、pps的frame信息。
(2)H264Track::H264Track--6
5. AACTrack的创建
(1)AACTrack::AACTrack(const string &aac_cfg)
(2)aac_cfg为两个字节,如:0x14, 0x08。
(3)onReady()--9
===========================================================
6. H264Track::H264Track
(1)得到sps、pps的字符信息。
(2)onReady—7
7. H264Track::onReady
getAVCInfo—8
8. getAVCInfo --H264.cpp
(1)h264DecSeqParameterSet-- SPSParser.c
得到SPS的信息
(2)h264GetWidthHeight:得到视频的宽和高。
(3)h264GeFramerate:iVideoFps,得到frame的rate。
9. AACTrack::onReady()
parseAacConfig(_cfg, _sampleRate, _channel);--10
10. parseAacConfig—ACC.cpp
执行else部分,得到samplerate: 16000和channels:1。
11. Demuxer::addTrack
(1)_sink为空。
(2)_listener不为空。
(3)MediaSink该类的作用是等待Track ready()返回true也就是就绪后再通知派生类进行下一步的操作,目的是输入Frame前由Track截取处理下,以便获取有效的信息(譬如sps pps aa_cfg)。
(4)通过代理进入MultiMediaSourceMuxer::onTrackFrame。
RTP协议的处理
RTP传输流媒体数据。
1. RtspSession::onRecv
(1)input—》HttpRequestSplitter::input—2
(2)上面函数的调用通过继承关系:
class RtspSession: public TcpSession, public RtspSplitter, public RtpReceiver , public MediaSourceEvent
{}
class RtspSplitter : public HttpRequestSplitter
{}
2. HttpRequestSplitter::input
(1)onRecvHeader-- RtspSplitter::onRecvHeader--3
(2)onRecvContent-- RtspSplitter::onRecvContent
3. RtspSplitter::onRecvHeader
onRtpPacket(data,len);-- RtspSession::onRtpPacket--4
4. RtspSession::onRtpPacket
通过data[1],即第二个字节(channel)为奇数和偶数来区别rtp和rtcp的处理。
- handleOneRtp—5
(uint8_t *) data + RtpPacket::kRtpTcpHeaderSize:传入参数时将RTSP去掉,前4个字节。
- onRtcpPacket—五
5. RtpMultiReceiver::handleOneRtp--RtpReceiver.h
(1)没有对数据进行改变,交由track处理。
(2)RtpTrackImp _track-- RtpTrackImp : public RtpTrack
(3)_track[index].inputRtp-- RtpTrack::inputRtp--6
6. RtpTrack::inputRtp
(1) 重新将数据封装成一个rtp包。
(2) onBeforeRtpSorted—五
(3)调用sortPacket排序 -- sortPacket(SEQ seq, T packet)(RtpReceiver.h)--7
(4) rtpMaxSize:10
(5)//设置ntp时间戳
rtp->ntp_stamp = _ntp_stamp.getNtpStamp(rtp->getStamp(), sample_rate);
7. PacketSortor—sortPacket(RtpReceiver.h)
(1) // 收到下一个seq
output(seq, std::move(packet));--8
(2) // 清空连续包列表
flushPacket();--9
8. PacketSortor—output
(1) _cb(seq, std::move(packet));
(2) track.setOnSorted([this, index](RtpPacket::Ptr rtp) {
onRtpSorted(std::move(rtp), index);
}-- class RtspSession : public RtpReceiver
(3) RtspSession::onRtpSorted--10
9. flushPacket
做一些清理工作,最终还是调用8。
10. RtspSession::onRtpSorted
_push_src->onWrite-- RtspMediaSourceImp::onWrite –开始分转码与未转码情况。
4.1未转码情况
与RTMP未转情况类似。
1. RtspMediaSourceImp::onWrite
RtspMediaSource::onWrite—2
2.RtspMediaSource::onWrite
主要代码:PacketCache<RtpPacket>::inputPacket。--3
3.PacketCache—inputPacket
(1) 追加数据到最后_cache->emplace_back(std::move(pkt));
(2)flush();--》onFlush
4. RtspMediaSource—onFlush
_ring->write
5. RingBuffer—write
_storage->write(std::move(in), is_key);
6. _RingStorage—write
_data_cache.back().emplace_back写入环形缓存数据。
4.2转码情况—解码
1.RtspMediaSourceImp::onWrite
key_pos = _demuxer->inputRtp(rtp)—2
2.RtspDemuxer::inputRtp
(1)_video_rtp_decoder->inputRtp—4.2.1--1
(2)_audio_rtp_decoder->inputRtp—4.2.2--1
3. RtspSession::handleReq_ANNOUNCE
NoticeCenter::Instance().emitEvent—invoker—onRes--RtspMediaSourceImp::setSdp
4.2.1视频
先要明白_video_rtp_decoder如何得到。
1. H264RtpDecoder::inputRtp--H264Rtp.cpp
decodeRtp(rtp)—2
2.H264RtpDecoder::decodeRtp
根据frame[0]的后5位将rtp分为三类:
(1)24:STAP-A,aggregation packet
unpackStapA—3
(2)28: FU-A, Fragmentation unit
mergeFu—4
(3)一个packet只有一个frame
singleFrame--5
3. H264RtpDecoder::unpackStapA
(1)对于这样的数据包:类型(1个字节)+frame 1 len(2个字节)+frame 1 data(前述两个字节表示的长度) +frame2 len(2个字节)+frame 2 data(前述两个字节表示的长度)+…..
(2)这种情况一般是sps、pps的合集。
(3)分拆完成以后singleFrame—5。
4. H264RtpDecoder::mergeFu
(1)如果是一个frame的开始,加上rpt over rtsp的4字节包头,组织FU indicator和 Fu header两个字节。
(2)两个字节的type是一样的。
(3)合成一个frame以后调用outputFrame(rtp, _frame);--6
5. H264RtpDecoder::singleFrame
(1)加上rpt over rtsp的4字节包头
(2)调用outputFrame(rtp, _frame);--6
6. H264RtpDecoder::outputFrame
(1)_dts的生成。
(2)RtpCodec::inputFrame(frame);--7
7. FrameDispatcher : : inputFrame
(1)RtspDemuxer::makeVideoTrack和RtmpDemuxer::makeAudioTrack中添加代理。
(2)最终执行H264Track::inputFrame。
8. H264Track::inputFrame
(1) inputFrame_l—9
对于b、p、IDR frme直接调用
(2)splitH264
/非I/B/P帧情况下,split一下,防止多个帧粘合在一起。然后调用inputFrame_l—9
9. H264Track::inputFrame_l
在这里会添加pps,sps信息(添加完pps,sps信息的h264数据就能直接用ffmpeg播放了),然后将数据发出去给各个代理。同时进入MultiMediaSourceMuxer::onTrackFrame函数的_ring->write部分。
4.2.2音频
1. AACRtpDecoder::inputRtp
(1)一个rtp包中包含多个frame。
(2) 首2字节表示Au-Header的个数,单位bit,所以除以16得到Au-Header个数。
(3) 之后的2字节是AU_HEADER,其中高13位表示一帧AAC负载的字节长度,低3位无用。
(4) 拆分每个frame.—flushData--2
(5)计算每个frame时间戳。
2. AACRtpDecoder::flushData()
(1) 插入adts头
(2)有adts头则插入adts头—3
3. FrameDispatcher : : inputFrame
(1)RtspDemuxer::makeVideoTrack和RtmpDemuxer::makeAudioTrack中添加代理。
(2)最终执行AACTrack::inputFrame。--4
4. AACTrack::inputFrame
(1) if (frame_len == (int)frame->size()) {
return inputFrame_l(frame);--5
}
(2)已经拆分过了,没调试到拆分的情况。
5. AACTrack::inputFrame_l
(1)makeAacConfig:根据7个字节的adts头生成aac config
(2)onReady:根据aac config生成_sampleRate, _channel。
(3)AudioTrack::inputFrame(frame)—》FrameDispatcher—inputFrame
根据代理进行分发。同时进入MultiMediaSourceMuxer::onTrackFrame函数的_ring->write部分。
4.3转码情况—编码
需要其他的协议,或不设置直接代理。
1. MultiMediaSourceMuxer::onTrackFrame--3
FrameDispatcher::inputFrame(Frame.h)--》FrameWriterInterfaceHelper::inputFrame(Frame.cpp:241)--》MediaSink::addTrack(设置的代理)—2
2. MediaSink::addTrack
RtspDemuxer::makeVideoTrack—》 Demuxer::addTrack—》 RtspMediaSourceImp::addTrack
—> MediaSink::addTrack
3. RtspMediaSourceMuxer—inputFrame
(1)第一个if没有执行。
(2) RtspMuxer::inputFrame—4
4. RtspMuxer::inputFrame
encoder->inputFrame根据编码器的不同,分为音频和视频。
4.3.1视频
1. H264RtpEncoder::inputFrame
(1)这里的frame是带00 00 00 01的。
(2) sps、pps直接取出。
(3)执行的是非低延迟模式。
(4)inputFrame_l—2
2. H264RtpEncoder::inputFrame_l
(1)insertConfigFrame--保证每一个关键帧前都有SPS与PPS
(2)packRtp—3
3. H264RtpEncoder::packRtp
(1)packRtpSmallFrame—>4
采用STAP-A/Single NAL unit packet per H.264 模式
(2) packRtpFu🡪5
4. H264RtpEncoder::packRtpSmallFrame
packRtpStapA--6
5. H264RtpEncoder::packRtpFu
(1)把每一帧分成多个rtp包。每个rtp包的长度为:1386
(2)将一个frame按1386进行拆分。
(3)创建一个没有有效负载的RTP包: makeRtp—>7
(4)FU-A 第1个字节末尾5bit为nalu type,固定为28(FU-A)
(5)FU-A 第2个字节为H264_TYPE。
6.packRtpStapA
(1)创建一个没有有效负载的RTP包: makeRtp—>7
(2)在rt4 个字节的rtsp over tcp+12个字节rtp头+1个字节的类型+2个字节的长度
(3)RtpCodec::inputRtp(rtp, gop_pos);--》8
7. RtpInfo::makeRtp
(1) 4 个字节的rtsp over tcp 头。
(2)12个字节rtp头
(3)有效负载
8. RtpRing—inputRtp
写入到环形缓冲里。
4.3.2音频
1. AACRtpEncoder::inputFrame
(1) frame->prefixSize()为7,即ADTS的长度。
(2)max_size:为584和h264的不同。
(3)if语句执行剩余的长度不够max_size。
(4)执行过makeAACRtp以后的格式为:
4 个字节的rtsp over tcp 头+12个字节rtp头+1个字节(0)+1个字节(16)+2个字节(包含有长度)+有效负载。
(5)makeAACRtp—》2
2. AACRtpEncoder::makeAACRtp
RtpCodec::inputRtp—》3
3.RtpRing—inputRtp
写入到环形缓冲里。
RTCP协议的处理
RTCP对RTP进行控制,同步。
5.1接收
1. RtspSession::onRtpPacket
通道号为奇数时,调用onRtcpPacket—1
2. RtspSession::onRtcpPacket
(1)RtcpHeader::loadFromBytes—》RtcpHeader::net2Host🡪 RtcpSR::net2Host得到ReportItem
(2)收到RTCP包的处理。设置rtp时间戳与ntp时间戳的对应关系。
(3)设置_rtcp_context[track_idx]->onRtcp(rtcp)的一些参数。
5.2发送
1.有两种情况发送
(1)RtspSession::onBeforeRtpSorted:和推送端的发送。--2
(2)RtspSession::sendRtpPacket:拉流端的发送。--2
2. RtspSession::updateRtcpContext
(1). send rtcp every 5 second
(2).发送SR rtcp包—》3
(3).发送RR rtcp包—》4
(4).发送SDEC—》5
每次发送完rtcp以后,要发送SDEC。
3. RtcpContextForSend::createRtcpSR
SR如下图所示
- 创建SR包,只赋值了RtcpHeader部分(网络字节序),ReportItem对象个数为0。—6
- 接下的代码是对rtcp的sender info部分赋值。
- 没有发送report block部分,即ReportItem。
4. RtcpContextForRecv::createRtcpRR
(1)创建RR包,RtcpRR::create(1)—》7
5. RtcpSdes::create
- 有一个SdesChunk,SdesType的值在哪里赋?
- setupHeader对header赋值。
6. RtcpSR::create
参数为0的情况下,调用setupHeader对rtcp的header部分进行赋值。参数为0,说明没有report block 。
7. RtcpRR::create
(1)参数为1,说明有一个report block(ReportItem)。
(2) 调用setupHeader,对header赋值。
六、部分细节的解释
6.1 SDP
1介绍
sdp,英文全称Session Description Protocol,会话描述协议,对应RFC2327,RTSP协议中使用sdp进行媒体信息的描述。语音通话SIP协议、监控安防GB28181国标、webRtc都用到了sdp。
sdp的目的就是在媒体会话中,传递媒体流信息,允许会话描述的接收者去参与会话,定义了会话描述的统一格式!
sdp信息由多行"<type>=<value>"组成,其中<type>是一个字符串,<value>是一个字符串,type表示类型,value的格式视type而定,整个协议区分大小写,"="两侧不允许有空格!
sdp会话描述包含一个会话级描述(session_level_description)和多个媒体级描述(media_level description)组成!会话级描述的作用域是整个会话,其位置从"v="行开始到第一个媒体描述为止;媒体级描述是对单个的媒体流进行描述,如传输过程中的视频流信息,从m=开始到下一个媒体描述为止。
会话级描述主要包含以下字段
媒体级描述主要包含以下字段
2各字段描述
version(必选)
格式: v=<version>
描述: 表示sdp的版本号,不包含次版本号
origin(必选)
格式:o=<username> <sessionid> <version> <network type> <address type> <address>
描述:o=选项对会话的发起者进行了描述;
<username>:是用户的登录名, 如果主机不支持<username>,则用"-"代替,<username> 不能包含空格;
<session id>:是一个数字串,在整个会话中,必须是唯一的,建议使用个NTP 时间戳;
<version>: 该会话公告的版本,供公告代理服务器检测同一会话的如果多个公告哪个是最新公告,基本要求是会话数据修改后该版本值递增,建议使用NTP时间戳
<networktype>: 网络类型,一般为"IN",表示internet
<addresstype>: 地址类型,一般为IP4
<adress>:地址
Session Name(必选)
格式:s=
会话名称,在整个会话中有且只有1个"s="
Connection Data(可选)
格式: c=<networktype> <address type> <connection address>描述:表示媒体连接信息;一个会话级描述中必须有"c="或者在每个媒体级描述中有一个"c="选项,也可能在会话级描述和媒体级描述中都有"c="选项;
network type表示网络类型,一般为IN,表示internet;
address type,地址类型,一般为IP4;
connection address,地址,可能为域名或ip地址两种形式
Bandwidth(可选)
格式: b=<modifier>:<bandwidth-value>
描述:该选项描述了建议的带宽,单位 kbs/s,可选,modifier包括两种类型,CT和AS,CT表示总带宽,AS表示单个媒体带宽的最大值;bandwidth-value表示具体的带宽。
Times(必选)
格式:t=<start time> <stop time>
描述:t字段描述了会话的开始时间和结束时间,<start time> <stop time>为NTP时间,单位是秒;如果<stop time>为0表示过了<start time>之后,会话一直持续;当<start time> 和<stop time>都为0的时候,表示持久会话;建议两个值不设为0,如果设为0,不知道开始时间和结束时间,增大了调度的难度
来看一个实际的抓包文件:
start time和stop time均为0,表示一个持久的会话。
email(可选)
格式:e=<email address>
描述:用来描述邮件地址
phone number(可选)
格式:p=<phone number>
描述:比较简单,用来描述电话号码
URI(可选)
格式:u=<uri>
描述:类似于url的一个值,这里不过多介绍了
a=(*) (可选)
格式 :a=<*>
描述:表示一个会话级别或媒体级别下的0个或多个属性
会话级别中有一个属性a,a=control:rtsp://192.17.1.63:554,表示新增的属性的类型为control,值为rtsp://192.17.1.63:554
media information(必选)
格式:m=<media> <port> <transport type> <fmt list>
描述:
<media>表示媒体类型
有"audio","video","application","data"(不向用户显示的数据),"control"(描述额外的控制通道);
<port>表示媒体流发往传输层的端口,对于RTP,偶数端口用来传输数据,奇数端口用来传输RTCP;
<transport>表示传输协议,与"c="一行相关联,一般用RTP/AVP表示,即 Realtime Transport Protocol using the Audio/Video profile over udp,即我们常说的RTP over udp;
<fmt list>表示媒体格式,分为静态绑定和动态绑定
静态绑定:媒体编码方式与RTP负载类型有确定的一一对应关系,如: m=audio 0 RTP/AVP 8
动态绑定:媒体编码方式没有完全确定,需要使用rtpmap进行进一步的说明: 如:
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
rtpmap(可选)
格式:a=rtpmap:<payload typee> <encoding name>/<clock rate>
描述:
payload type表示动态负载类型,如 98表示h264
encoding name表示编码名称,如H.264
clock rate表示时钟频率,如90000
fmtp
定义指定格式的附加参数
a=fmtp:<payload type> <format specific parameters>
<payload type>:负载类型
<format specific parameters>:具体参数.
3实际举例
v=0
o=- 1586545639954157 1586545639954157 IN IP4 192.17.1.63
s=Media Presentation
e=NONE
b=AS:5100
t=0 0
a=control:rtsp://192.17.1.63:554/
m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
b=AS:5000
a=recvonly
a=x-dimensions:1920,1080
a=control:rtsp://192.17.1.63:554/trackID=1
a=rtpmap:96 H264/90000
a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z01AKI2NQDwBE/LgLcBAQFAAAD6AAAw1DoYACYFAABfXgu8uNDAATAoAAL68F3lwoA==,aO44gA==
m=audio 0 RTP/AVP 8
c=IN IP4 0.0.0.0
b=AS:50
a=recvonlya=control:rtsp://192.17.1.63:554/trackID=2
a=rtpmap:8 PCMA/8000
a=Media_header:MEDIAINFO=494D4B48010300000400000111710110401F000000FA000000000000000000000000000000000000;
a=appversion:1.0
v=0
o=34020000001320000010 0 0 IN IP4 192.17.1.202
s=Play
c=IN IP4 192.17.1.202
t=0 0
m=video 5500 RTP/AVP 96 97 98
a=rtpmap:96 PS/90000
a=rtpmap:97 MPEG4/90000
a=rtpmap:98 H264/90000
a=recvonly
6.2.rtp包
6.2.1 RTP Header格式 ![https://img2020.cnblogs.com/blog/2293816/202112/2293816-20211228112407029-1154329894.png](https://img2023.cnblogs.com/blog/2525040/202312/2525040-20231208131638276-507852843.png)
V: 2bits,表示版本号,
P: 1bit,表示是否支持填充,置为1的时候,表示在packet的末尾进行填充,方便一些针对固定长度算法的封装
X: 1bit, 表示是否支持Rtp头扩展,置为1的时候,RtpHeader之后会跟1个header extension
CC(CSRC count): 4bits,表示头部之后contributing sources identifiers的个数
M: 1bit; 该值为1时,表示该数据包是一帧数据的最后一个数据包。
PT: 7bits,表示传输的多媒体类型(标识了RTP载荷的类型)。常用的的96表示h264,97 表示ACC。
sequence number:16bits(2字节),表示RTP包序号 。
timestamp:32bits(4字节),表示时间戳, 必须使用90 kHz 时钟频率
SSRC:32bits(4字节),用于标识同步信源,参加同一视频会议的两个同步信源不能有相同的SSRC.
CSRC:特约信源标识符,每个CSRC占用4个字节,可以有0~15个。每个CSRC标识了包含在该RTP报文有效载荷中的所有特约信源
6.2.2 RtpHeader的定义
Rtp包都包含什么,可看class RtpHeader的定义。
class RtpHeader {
……
// 版本号,固定为2
uint32_t version : 2;
// padding
uint32_t padding : 1;
// 扩展
uint32_t ext : 1;
// csrc
uint32_t csrc : 4;
// mark
uint32_t mark : 1;
// 负载类型
uint32_t pt : 7;
// 序列号
uint32_t seq : 16;
// 时间戳
uint32_t stamp;
// ssrc
uint32_t ssrc;
// 负载,如果有csrc和ext,前面为 4 * csrc + (4 + 4 * ext_len)
uint8_t payload;
6.3 adts头
参见:https://www.jianshu.com/p/af0165f923e9
1.ADTS全称是(Audio Data Transport Stream),是AAC的一种十分常见的传输格式。
2. 一般是在AAC ES流前添加7个字节的ADTS header。也就是说你可以吧ADTS这个头看作是AAC的frameheader。
3.ADTS内容及结构
ADTS 头中相对有用的信息:采样率、声道数、帧长度。一般情况下ADTS的头信息都是7个字节,分为2部分:
adts_fixed_header();
固定头信息中的数据每一帧都相同,而可变头信息则在帧与帧之间可变。
syncword :同步头代表着1个ADTS帧的开始,所有bit置1,即 0xFFF
ID:MPEG标识符,0标识MPEG-4,1标识MPEG-2
Layer: 直接置00
protection_absent:表示是否误码校验。1 no CRC , 0 has CRC
profile:AAC 编码级别, 0: Main Profile, 1:LC(最常用), 2: SSR, 3: reserved.
sampling_frequency_index:采样率标识
Private bit:直接置0,解码时忽略这个参数
channel_configuration: 声道数标识
original_copy: 直接置0,解码时忽略这个参数
home:直接置0,解码时忽略这个参数
采样率和通道数标识表:
adts_variable_header();
copyright_identification_bit: 直接置0,解码时忽略这个参数(版权—识别)
copyright_identification_start: 直接置0,解码时忽略这个参数
aac_frame_lenght: 当前音频帧的字节数,编码元数据字节数 + 文件头字节数(0 == protection_absent ? 7: 9)
adts_buffer_fullness: 当设置为0x7FF时表示时可变码率
number_of_raw_data_blocks_in_frames: 当前音频包里面包含的音频编码帧数, 置为 aac_nums - 1, 即只有一帧音频时置0
七、相关的C++语言
7.1 mutable 关键字
1. const 关键字用于类的成员函数,成为常成员函数,即:不允许在常成员函数的内部 (实现里) 修改数据成员的值。
2.mutable 关键字用于类的成员变量,即:允许在常成员函数的内部 (实现里) 修改成员变量的值。
3.例子:
(1)void A::get_a() const
{
a = 9; //a 被修饰为mutable类型,a在常成员函数里,能修改a的值(能修改该数据成员)
}
(2)mutable int a; //a 被 mutable 修饰
7.2 C++:匿名函数(Lambda函数)
参考: https://blog.csdn.net/asdasdde/article/details/116268964
1. 语法形式:
auto func = [capture] (params) opt -> ret { func_body; };
其中 func 是可以当作 lambda 表达式的名字,作为一个函数使用,capture 是捕获列表,params 是参数表,opt: 不需要可以省略。 (mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw ()), ret 是返回值类型,func_body 是函数体。
捕获列表:
[] 不捕捉任何变量
[&] 捕获外部作用域中所有变量,并作为引用在函数体内使用 (按引用捕获)
[=] 捕获外部作用域所有变量,在函数内内有个副本使用,拷贝的副本在匿名函数体内部是只读的
[=,&foo] 按值捕获外部作用域中所有变量,并按照引用捕获外部变量 foo
[bar] 按值捕获 bar 变量,同时不捕获其他变量
[&bar] 按引用捕获 bar 变量,同时不捕获其他变量
[this] 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限,如果已经使用了 & 或者 =, 默认添加此选项。
2.例子
(1)例1
#include <iostream>
#include <functional>
using namespace std;
class Test
{
public:
void output(int x, int y)
{
//auto x1 = [] {return m_number; }; // error
auto x2 = [=] {return m_number + x + y; }; // ok
auto x3 = [&] {return m_number + x + y; }; // ok
auto x4 = [this] {return m_number; }; // ok
//auto x5 = [this] {return m_number + x + y; }; // error
auto x6 = [this, x, y] {return m_number + x + y; }; // ok
auto x7 = [this] {return m_number++; }; // ok
}
int m_number = 100;
};
(2)例2
int main(void)
{
int a = 10, b = 20;
// auto f1 = [] {return a; }; // error
auto f2 = [&] {return a++; }; // ok
auto f3 = [=] {return a; }; // ok
// auto f4 = [=] {return a++; }; // error
// auto f5 = [a] {return a + b; }; // error
auto f6 = [a, &b] {return a + (b++); }; // ok
auto f7 = [=, &b] {return a + (b++); }; // ok
return 0;
}
(3)例3
int a = 0;
auto f1 = [=] {return a++; }; // error, 按值捕获外部变量, a是只读的
auto f2 = [=]()mutable {return a++; }; // ok
7.3 auto的用法
参考:https://blog.csdn.net/yl_puyu/article/details/88646962
- 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。
- 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
- auto不能作为函数参数。
- auto不能直接用来声明数组。
- 对象属性不能用 auto 关键字定义。
- 不能用于定义非静态成员变量
class Base {
public :
static int x;
};
auto Base::x = 123;
7.4 using的三种用法
https://blog.csdn.net/shift_wwx/article/details/78742459
1. 命令空间的using声明
(1)using namespace std;
(2)using std::cin;
2. 在子类中引用基类成员
(1)私有成员不能用。
(2)using Base::value;
(3)using::test1只是声明,不需要形参指定,所以test1的两个重载版本在子类中都可使用。test1在基类中是函数,有不同的参数。
3. 使用using起别名
(1)typedef std::vector<int> intvec;
using intvec = std::vector<int>; //这两个写法是等价的
(2)typedef void (*FP) (int, const std::string&);
using FP = void (*) (int, const std::string&);
7.5 std::move
详见:https://www.cnblogs.com/shadow-lr/p/Introduce_Std-move.html
- std::move作用主要可以将一个左值转换成右值引用,从而可以调用C++11右值引用的拷贝构造函数
(2)// 例2:std::vector和std::string的实际例子
int main() {
std::string str1 = "aacasxs";
std::vector<std::string> vec;
vec.push_back(str1); // 传统方法,copy vec.pu
sh_back(std::move(str1)); // 调用移动语义的push_back方法,避免拷贝,str1会失去原有值,变成空字符串
vec.emplace_back(std::move(str1)); // emplace_back效果相同,str1会失去原有值
vec.emplace_back("axcsddcas"); // 当然可以直接接右值
}