知识准备
RFC文档地址:http://www.networksorcery.com/enp/rfc/rfc3550.txt
RTSP标准文档规范https://tools.ietf.org/html/rfc7826#page-94
默认情况下,wireshark并没有分析数据包的内容,从而判断是否是rtsp数据包,它是根据端口,默认端口是554,认为是进行rtsp协议会话,所以会在捕获界面显示数据包的Protocol协议,如果知道哪些端口也是进行rtsp会话的情况下,可以在菜单栏中选择分析,点击编码为,在字段中选择tcp port 值填写指定的端口,然后在当前的协议中,选择RTSP。另外,可以通过鼠标右键选择追踪流,点击其中的TCP,查看RTSP的交互过程
OPTIONS
Public: OPTIONS, DESCRIBE, PLAY, PAUSE, SETUP, TEARDOWN, SET_PARAMETER, GET_PARAMETER
返回rtsp服务器支持的请求方法
2)GET_PARAMETER rtsp://192.168.58.173:554/channel=1/trackID=video RTSP/1.0
根据规范,GET_PARAMETER服务器和客户端可以选择实现。
目前有的VLC采用TCP进行rtsp播放视频流,如果不响应GET_PARAMETER会产生断流的情况
在暂停流媒体播放,定期发送GET_PARAMETER作为心跳包维持连接
LIVE555针对该指令请求的回复代码如下,简单回复下当前的版本号
void RTSPServer::RTSPClientSession
::handleCmd_GET_PARAMETER(RTSPServer::RTSPClientConnection* ourClientConnection,
ServerMediaSubsession* /*subsession*/, char const* /*fullRequestStr*/) {
// By default, we implement "GET_PARAMETER" just as a 'keep alive', and send back a dummy response.
// (If you want to handle "GET_PARAMETER" properly, you can do so by defining a subclass of "RTSPServer"
// and "RTSPServer::RTSPClientSession", and then reimplement this virtual function in your subclass.)
setRTSPResponse(ourClientConnection, "200 OK", fOurSessionId, LIVEMEDIA_LIBRARY_VERSION_STRING);
}
问题以及解决方案
1)405 Method Not Allowed
主要是在进行OPTIONS指令包封装的时候,通过wireshark抓包进行编写,报文内容如下:
Request: OPTIONS rtsp:://192.168.1.88 RTSP/1.0\r\n
Method: OPTIONS
URL: rtsp:://192.168.1.88
以为信息的开头是Request,实际上这是解析的语句
错误:
request_stream << "REQUEST: " <<"OPTIONS " << "rtsp://192.168.0.114 " << "RTSP/1.0\r\n";
request_stream << "CSeq: " << "2\r\n";
request_stream << "User-Agent: " << "LibVLC/2.1.5 (Live555 Streaming Media v2014.0)\r\n\r\n";
正确:
request_stream << "OPTIONS " << "rtsp://192.168.0.114 " << "RTSP/1.0\r\n";
request_stream << "CSeq: " << "2\r\n";
request_stream << "User-Agent: " << "LibVLC/2.1.5 (Live555 Streaming Media v2014.0)\r\n\r\n";
2)404 Stream Not Found
主要是在进行DESCRIBE的时候没有填写获取的视频流信息
错误:
request_stream << "DESCRIBE " << "rtsp://192.168.0.114 " << "RTSP/1.0\r\n";
正确:
request_stream << "DESCRIBE " << "rtsp://192.168.0.114/smoke.264 " << "RTSP/1.0\r\n";//error
3) 451 Parameter Not Understood
主要是url后面没有指定trackid,例如指定/trackID=0
错误:
SETUP rtsp://192.168.18.201:554/cam/realmonitor?channel=1&subtype=0 RTSP/1.0
CSeq: 4
User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2017.08.22)
Authorization: Digest username="admin", realm="Login to 4M01111PAJB50A1", nonce="3344152e2d9f717b3c3f29792f31e125", uri="rtsp://192.168.18.201:554/cam/realmonitor?channel=1&subtype=0", response="d69d677d06e9a5471327ff977cc09d9e"
Transport: RTP/AVP;unicast;client_port=45056-45057
RTSP/1.0 451 Parameter Not Understood
CSeq: 4
Session: 1029986489118
正确:
SETUP rtsp://192.168.18.201:554/cam/realmonitor?channel=1&subtype=0/trackID=0 RTSP/1.0
CSeq: 5
User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2017.08.22)
Authorization: Digest username="admin", realm="Login to 4M01111PAJB50A1", nonce="ba0a98388d61508258fc859081a62fb0", uri="rtsp://192.168.18.201:554/cam/realmonitor?channel=1&subtype=0", response="c72d514d419ee7383c4cc1f94a0b785d"
Session: 1039947334568
Transport: RTP/AVP;unicast;client_port=45058-45059
测试代码
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <boost/asio.hpp>
#include "socket.h"
using namespace std;
using namespace boost::asio;
const char pszRtspServerIP[32] = "192.168.0.114";
short sRtspServerPort = 8554;
void WriteFile(char* buf);
{
ofstream ofs;
ofs.open("rtspoption.txt");
ofs << buf << endl;
ofs.close();
}
int HandleOptionCommand(ip::tcp::socket &sock)
{
boost::system::error_code ec;
boost::asio::streambuf request;
std::ostream request_stream(&request);
request_stream << "OPTIONS " << "rtsp://192.168.0.114 " << "RTSP/1.0\r\n";
request_stream << "CSeq: " << "2\r\n";
request_stream << "User-Agent: " << "LibVLC/2.1.5 (Live555 Streaming Media v2014.0)\r\n\r\n";
boost::asio::write(sock, request);
char buf[1024] = { 0 };
size_t len = sock.read_some(buffer(buf), ec);
return 0;
}
int HanleDescribeCommand(ip::tcp::socket &sock)
{
boost::system::error_code ec;
boost::asio::streambuf request;
std::ostream request_stream(&request);
request_stream << "DESCRIBE " << "rtsp://192.168.0.114/smoke.264 " << "RTSP/1.0\r\n";
request_stream << "CSeq: " << "3\r\n";
request_stream << "Accept: " << "application/sdp\r\n";
request_stream << "User-Agent: " << "LibVLC/2.1.5 (Live555 Streaming Media v2014.0)\r\n\r\n";
boost::asio::write(sock, request);
char buf[1024] = { 0 };
size_t len = sock.read_some(buffer(buf), ec);
//a=control:track1
return 0;
}
int HandleSetupCommand(ip::tcp::socket &sock)
{
boost::system::error_code ec;
boost::asio::streambuf request;
std::ostream request_stream(&request);
request_stream << "SETUP " << "rtsp://192.168.0.114/smoke.264 " << "RTSP/1.0\r\n";
request_stream << "CSeq: " << "3\r\n";
request_stream << "Transport: " << "RTP/AVP/TCP;unicast;interleaved=0-1\r\n";
request_stream << "User-Agent: " << "LibVLC/2.1.5 (Live555 Streaming Media v2014.0)\r\n\r\n";
boost::asio::write(sock, request);
char buf[1024] = { 0 };
size_t len = sock.read_some(buffer(buf), ec);
return 0;
}
int main(int argc, char* argv[])
{
io_service iosev;
ip::tcp::socket socket(iosev);
ip::tcp::endpoint ep(ip::address_v4::from_string(pszRtspServerIP), sRtspServerPort);
boost::system::error_code ec;
socket.connect(ep, ec);
if (ec) return -1;
HandleOptionCommand(socket);
HanleDescribeCommand(socket);
HandleSetupCommand(socket);
return 0;
}
RTSP解复用器
AVInputFormat ff_rtsp_demuxer = {
.name = "rtsp",
.long_name = NULL_IF_CONFIG_SMALL("RTSP input"),
.priv_data_size = sizeof(RTSPState),
.read_probe = rtsp_probe,
.read_header = rtsp_read_header,
.read_packet = rtsp_read_packet,
.read_close = rtsp_read_close,
.read_seek = rtsp_read_seek,
.flags = AVFMT_NOFILE,
.read_play = rtsp_read_play,
.read_pause = rtsp_read_pause,
.priv_class = &rtsp_demuxer_class,
};
代码堆栈剖析:
ffplayd.exe!sdp_parse_line(AVFormatContext * s, SDPParseState * s1, int letter, const char * buf) 行 576 C
ffplayd.exe!ff_sdp_parse(AVFormatContext * s, const char * content) 行 721 C
ffplayd.exe!ff_rtsp_setup_input_streams(AVFormatContext * s, RTSPMessageHeader * reply) 行 622 C
ffplayd.exe!ff_rtsp_connect(AVFormatContext * s) 行 1897 C
> ffplayd.exe!rtsp_read_header(AVFormatContext * s) 行 726 C
ffplayd.exe!avformat_open_input(AVFormatContext * * ps, const char * filename, AVInputFormat * fmt, AVDictionary * * options) 行 631 C
ffplayd.exe!read_thread(void * arg) 行 2780 C
ffplayd.exe!SDL_RunThread(void * data) 行 283 C
ffplayd.exe!RunThread(void * data) 行 91 C
ffplayd.exe!RunThreadViaBeginThreadEx(void * data) 行 106 C
[外部代码]
探测格式,主要是根据URL起始字符的匹配
static int rtsp_probe(const AVProbeData *p)
{
if (
#if CONFIG_TLS_PROTOCOL
av_strstart(p->filename, "rtsps:", NULL) ||
#endif
av_strstart(p->filename, "rtsp:", NULL))
return AVPROBE_SCORE_MAX;
return 0;
}
ff_rtsp_setup_input_streams创建rtsp交互连接
ff_rtsp_send_cmd优先发送DESCRIBE指令,海康和大华RTSP指令交互的区别在于,大华在OPTIONS阶段就会请求认证信息RTSP/1.0 401 Unauthorized
int ff_sdp_parse(AVFormatContext *s, const char *content) 解析SDK中的内容,实际上这里就已经完全知道码流的数据格式以及所有的流信息,根本不需要探测码流格式
sdp_parse_rtpmap函数分析出h264码流格式,保存在AVFormatContext中的stream流中
/* 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)
{
//*p = H264/90000
//最终st中保存探测的码流格式
AVCodecParameters *par = st->codecpar;
char buf[256];
int i;
const AVCodecDescriptor *desc;
const char *c_name;
/* See if we can handle this kind of payload.
* The space should normally not be there but some Real streams or
* particular servers ("RealServer Version 6.1.3.970", see issue 1658)
* have a trailing space. */
//关键,buf获取到H264
get_word_sep(buf, sizeof(buf), "/ ", &p);
if (payload_type < RTP_PT_PRIVATE) {
/* We are in a standard case
* (from http://www.iana.org/assignments/rtp-parameters). */
par->codec_id = ff_rtp_codec_id(buf, par->codec_type);
}
if (par->codec_id == AV_CODEC_ID_NONE) {
const RTPDynamicProtocolHandler *handler =
ff_rtp_handler_find_by_name(buf, par->codec_type);
init_rtp_handler(handler, rtsp_st, st);
/* If no dynamic handler was found, check with the list of standard
* allocated types, if such a stream for some reason happens to
* use a private payload type. This isn't handled in rtpdec.c, since
* the format name from the rtpmap line never is passed into rtpdec. */
if (!rtsp_st->dynamic_handler)
par->codec_id = ff_rtp_codec_id(buf, par->codec_type);
}
desc = avcodec_descriptor_get(par->codec_id);
if (desc && desc->name)
c_name = desc->name;
else
c_name = "(null)";
get_word_sep(buf, sizeof(buf), "/", &p);
i = atoi(buf);
switch (par->codec_type) {
case AVMEDIA_TYPE_AUDIO:
av_log(s, AV_LOG_DEBUG, "audio codec set to: %s\n", c_name);
par->sample_rate = RTSP_DEFAULT_AUDIO_SAMPLERATE;
par->channels = RTSP_DEFAULT_NB_AUDIO_CHANNELS;
if (i > 0) {
par->sample_rate = i;
avpriv_set_pts_info(st, 32, 1, par->sample_rate);
get_word_sep(buf, sizeof(buf), "/", &p);
i = atoi(buf);
if (i > 0)
par->channels = i;
}
av_log(s, AV_LOG_DEBUG, "audio samplerate set to: %i\n",
par->sample_rate);
av_log(s, AV_LOG_DEBUG, "audio channels set to: %i\n",
par->channels);
break;
case AVMEDIA_TYPE_VIDEO:
av_log(s, AV_LOG_DEBUG, "video codec set to: %s\n", c_name);
if (i > 0)
avpriv_set_pts_info(st, 32, 1, i);
break;
default:
break;
}
finalize_rtp_handler_init(s, rtsp_st, st);
return 0;
}
附上SDK数据
v=0
o=- 1625669797472718 1625669797472718 IN IP4 192.168.18.204
s=Media Presentation
e=NONE
b=AS:5050
t=0 0
a=control:rtsp://192.168.18.204:554/h264/ch1/main/av_stream/
m=video 0 RTP/AVP 96
b=AS:5000
a=control:rtsp://192.168.18.204:554/h264/ch1/main/av_stream/trackID=1
a=rtpmap:96 H264/90000
a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z0IAKpY1QPAET8s3AQEBAg==,aM48gA==
a=Media_header:MEDIAINFO=494D4B48010100000400000100000000000000000000000000000000000000000000000000000000;
a=appversion:1.0
码流格式探测分析
> ffplayd.exe!rtp_read(URLContext * h, unsigned char * buf, int size) 行 377 C
(调用recvfrom读取RTP传输数据,transfer_func指向了rtp_read的实现)
ffplayd.exe!retry_transfer_wrapper(URLContext * h, unsigned char * buf, int size, int size_min, int (URLContext *, unsigned char *, int) * transfer_func) 行 376 C
ffplayd.exe!ffurl_read(URLContext * h, unsigned char * buf, int size) 行 412 C
ffplayd.exe!udp_read_packet(AVFormatContext * s, RTSPStream * * prtsp_st, unsigned char * buf, int buf_size, __int64 wait_end) 行 2033 C
ffplayd.exe!read_packet(AVFormatContext * s, RTSPStream * * rtsp_st, RTSPStream * first_queue_st, __int64 wait_end) 行 2116 C
ffplayd.exe!ff_rtsp_fetch_packet(AVFormatContext * s, AVPacket * pkt) 行 2202 C
ffplayd.exe!rtsp_read_packet(AVFormatContext * s, AVPacket * pkt) 行 879 C
ffplayd.exe!ff_read_packet(AVFormatContext * s, AVPacket * pkt) 行 856 C
ffplayd.exe!read_frame_internal(AVFormatContext * s, AVPacket * pkt) 行 1582 C
ffplayd.exe!avformat_find_stream_info(AVFormatContext * ic, AVDictionary * * options) 行 3772 C
ffplayd.exe!read_thread(void * arg) 行 2805 C
ffplayd.exe!SDL_RunThread(void * data) 行 283 C
ffplayd.exe!RunThread(void * data) 行 91 C
ffplayd.exe!RunThreadViaBeginThreadEx(void * data) 行 106
重点分析函数ff_rtsp_fetch_packet,该函数调用read_packet获取到RTP数据,调用ff_rtp_parse_packet分析RTP数据,去掉RTP包头,添加起始码,然后封装成AVPacket,但是封装的AVPacket并不是完整的NAL单元的视频流,对于FU-A分包的数据,仍然需要对多个AVPacket进行重新组装
> ffplayd.exe!h264_handle_packet_fu_a(AVFormatContext * ctx, PayloadContext * data, AVPacket * pkt, const unsigned char * buf, int len, int * nal_counters, int nal_mask) 行 291 C
(对FU-A分包的RTP格式数据,会根据是否是第一个包添加起始码,关键是start_bit = fu_header >> 7;)
ffplayd.exe!h264_handle_packet(AVFormatContext * ctx, PayloadContext * data, AVStream * st, AVPacket * pkt, unsigned int * timestamp, const unsigned char * buf, int len, unsigned short seq, int flags) 行 359 C
ffplayd.exe!rtp_parse_packet_internal(RTPDemuxContext * s, AVPacket * pkt, const unsigned char * buf, int len) 行 692 C
ffplayd.exe!rtp_parse_one_packet(RTPDemuxContext * s, AVPacket * pkt, unsigned char * * bufptr, int len) 行 841 C
ffplayd.exe!ff_rtp_parse_packet(RTPDemuxContext * s, AVPacket * pkt, unsigned char * * bufptr, int len) 行 875 C
ffplayd.exe!ff_rtsp_fetch_packet(AVFormatContext * s, AVPacket * pkt) 行 2217 C
ffplayd.exe!rtsp_read_packet(AVFormatContext * s, AVPacket * pkt) 行 879 C
ffplayd.exe!ff_read_packet(AVFormatContext * s, AVPacket * pkt) 行 856 C
ffplayd.exe!read_frame_internal(AVFormatContext * s, AVPacket * pkt) 行 1582 C
ffplayd.exe!av_read_frame(AVFormatContext * s, AVPacket * pkt) 行 1776 C
ffplayd.exe!read_thread(void * arg) 行 3008 C
ffplayd.exe!SDL_RunThread(void * data) 行 283 C
ffplayd.exe!RunThread(void * data) 行 91 C
ffplayd.exe!RunThreadViaBeginThreadEx(void * data) 行 106 C
av_read_frame分包代码剖析
ffplayd.exe!ff_combine_frame(ParseContext * pc, int next, const unsigned char * * buf, int * buf_size) 行 265 C
> ffplayd.exe!h264_parse(AVCodecParserContext * s, AVCodecContext * avctx, const unsigned char * * poutbuf, int * poutbuf_size, const unsigned char * buf, int buf_size) 行 595 C
ffplayd.exe!av_parser_parse2(AVCodecParserContext * s, AVCodecContext * avctx, unsigned char * * poutbuf, int * poutbuf_size, const unsigned char * buf, int buf_size, __int64 pts, __int64 dts, __int64 pos) 行 166 C
ffplayd.exe!parse_packet(AVFormatContext * s, AVPacket * pkt, int stream_index) 行 1461 C
ffplayd.exe!read_frame_internal(AVFormatContext * s, AVPacket * pkt) 行 1675 C
ffplayd.exe!av_read_frame(AVFormatContext * s, AVPacket * pkt) 行 1776 C
ffplayd.exe!read_thread(void * arg) 行 3008 C
ffplayd.exe!SDL_RunThread(void * data) 行 283 C
ffplayd.exe!RunThread(void * data) 行 91 C
ffplayd.exe!RunThreadViaBeginThreadEx(void * data) 行 106 C
[外部代码]
HEVC码流探测剖析
例如:"rtsp://admin:admin12345@192.168.28.136:554/h265/ch1/main/av_stream"
请求海康摄像机H265码流
SDP报文如下:
v=0
o=- 1566124110963848 1566124110963848 IN IP4 192.168.28.136
s=Media Presentation
e=NONE
b=AS:5100
t=0 0
a=control:rtsp://192.168.28.136:554/h265/ch1/main/av_stream/
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.168.28.136:554/h265/ch1/main/av_stream/trackID=1
a=rtpmap:96 H265/90000
a=fmtp:96 sprop-sps=QgEBAWAAAAMAsAAAAwAAAwB7oAPAgBDlja5JMvTcBAQEAg==; sprop-pps=RAHA8vA8kAA=
m=audio 0 RTP/AVP 8
c=IN IP4 0.0.0.0
b=AS:50
a=recvonly
a=control:rtsp://192.168.28.136:554/h265/ch1/main/av_stream/trackID=2
a=rtpmap:8 PCMA/8000
a=Media_header:MEDIAINFO=494D4B48010200000400050011710110401F000000FA000000000000000000000000000000000000;
a=appversion:1.0
static int sdp_parse_rtpmap(AVFormatContext *s,
AVStream *st, RTSPStream *rtsp_st,
int payload_type, const char *p)
通过sdp_parse_rtpmap函数分析SDP中的a=rtpmap:96 H265/90000
在调用函数 const RTPDynamicProtocolHandler *handler =
ff_rtp_handler_find_by_name(buf, par->codec_type);中 获取到h265的处理句柄
rtsp 分包NALU
if (codec_id == AV_CODEC_ID_HEVC)
ret = hevc_parse_nal_header(nal, logctx);
else
ret = h264_parse_nal_header(nal, logctx);
static void parse_fmtp(AVFormatContext *s, RTSPState *rt,
int payload_type, const char *line)
通过h265的句柄调用
static av_cold int hevc_parse_sdp_line(AVFormatContext *ctx, int st_index,
PayloadContext *hevc_data, const char *line)
该函数将SPS/PPS的内容保存在(AVFormatContext结构体中的AVStream流中的extradata变量
创建视频流和音频流
update_stream_avctx函数只是将avcodec_parameters_to_context(st->internal->avctx, st->codecpar);
并没有做其他的操作