onvif学习笔记10:获取RTSP流地址

有网友购买了我的,问了我几个问题,其中一个是实现ONVIF服务器的获取RTSP地址功能,本文整理出一个思路,愿帮到在学习ONVIF路上的诸君。
我录制的视频是很早之前的,一直没抽时间再录一个实战视频,所以凡是购买的网友,均会进行指导。当然,限于能力,也不是所有的问题都解答。

官方WSDL说明

ONVIF协议通过GetStreamUri获取流地址,在页面搜索GetStreamUri可看到该命令的详细信息。
详细字段如图1所示:
图1 GetStreamUri
GetStreamUri是客户端发起的请求,共2大字段:
第1个是ProfileToken,亦即要获取的媒体profile名称——因为不同的profile,可以有不同的媒体参数。最简单的例子,摄像头有2路视频输出,720P和1080P,则会有2个媒体profile(使用不同的token来表示),因此,需要指定获取的是哪一个profile的流地址。
第2个是流参数设置StreamSetup。说明如下:

  • Stream:一个枚举类型,指定单播还是多播。
  • Transport:指定传输的协议。
    GetStreamUriResponse是响应包,其字段由服务端填充,客户端只要组装好GetStreamUri发送出来,等待响应包即可。
  • Uri:即流地址。
  • InvalidAfterConnect:指示只有连接后uri是否有效,设置为false。
  • InvalidAfterReboot:指示重启后uri是否有效,设置为false。
  • Timeout:超时时间,设置为0表示永久有效,——即使profile发生改变。

笔者存疑:InvalidAfterConnect、InvalidAfterReboot在字面上和解释上似乎不通,但对比官方说明中的ValidUntilConnectt ValidUntilReboot似乎又说得通,笔者怀疑是wsdl文档更新过程表述不一致导致的。在代码中,务必让这2个字段设置为false。

代码实现思路

下面分别从客户端、服务器讲讲代码实现思路。

客户端获取代码

首先,设置_trt__GetStreamUri结构体为指定的profile名称。——profile名称由其它函数获取。
其次,设置tt__StreamSetup结构体为单播,枚举值为tt__StreamType__RTP_Unicast。
再次,设置tt__Transport结构体字段,协议为tt__TransportProtocol__UDP。
最后,调用soap_call___trt__GetStreamUri函数。在_trt__GetStreamUriResponse中的MediaUri带有Uri,此亦本文开头提到的流地址

服务端返回代码

在响应GetStreamUri的函数中,判断_trt__GetStreamUri带的字段,如指针有效性、profile有效性,传输协议的判断。
然后设置trt__GetStreamUriResponse的MediaUri,包括Uri、InvalidAfterConnect、InvalidAfterReboot和Timeout。其中,Uri就是RTSP媒体器地址。
关于RTSP地址的设置,可以固定一种格式,在流服务器中固定,再在ONVIF服务器中固定,但这样不方便。也可以在流服务器中生成,然后通过进程通信的方式通知ONVIF服务器,这样方便维护。

代码结构体

文中涉及的结构体较多,较复杂,有删改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class _trt__GetStreamUri
{ public:
/// <PRE><BLOCKQUOTE>
/// Stream Setup that should be used with the uri
/// </BLOCKQUOTE></PRE>
/// Element "StreamSetup" of XSD type "http://www.onvif.org/ver10/schema":StreamSetup.
tt__StreamSetup* StreamSetup 1; ///< Required element.
/// <PRE><BLOCKQUOTE>
/// The ProfileToken element indicates the media profile to use and will define the configuration of the content of the stream.
/// </BLOCKQUOTE></PRE>
/// Element "ProfileToken" of XSD type "http://www.onvif.org/ver10/schema":ReferenceToken.
tt__ReferenceToken ProfileToken 1; ///< Required element.
/// A handle to the soap struct context that manages this instance when instantiated by a context or NULL otherwise (automatically set).
struct soap *soap ;
};

class tt__StreamSetup
{ public:
/// <PRE><BLOCKQUOTE>
/// Defines if a multicast or unicast stream is requested
/// </BLOCKQUOTE></PRE>
/// Element "Stream" of XSD type "http://www.onvif.org/ver10/schema":StreamType.
enum tt__StreamType Stream 1; ///< Required element.
/// Element "Transport" of XSD type "http://www.onvif.org/ver10/schema":Transport.
tt__Transport* Transport 1; ///< Required element.
/// @todo <any namespace="##any" minOccurs="0" maxOccurs="unbounded">
/// @todo Schema extensibility is user-definable.
/// Consult the protocol documentation to change or insert declarations.
/// Use wsdl2h option -x to remove this element.
/// Use wsdl2h option -d for xsd__anyType DOM (soap_dom_element).
/// Size of the array of XML or DOM nodes is 0..unbounded.
std::vector<xsd__anyType > __any 0; ///< Catch any element content in DOM.
/// @todo <anyAttribute namespace="##any">.
/// @todo Schema extensibility is user-definable.
/// Consult the protocol documentation to change or insert declarations.
/// Use wsdl2h option -x to remove this attribute.
/// Use wsdl2h option -d for xsd__anyAttribute DOM (soap_dom_attribute).
@xsd__anyAttribute __anyAttribute ; ///< Store anyAttribute content in DOM soap_dom_attribute linked node structure.
/// A handle to the soap struct context that manages this instance when instantiated by a context or NULL otherwise (automatically set).
struct soap *soap ;
};

enum tt__StreamType
{
tt__StreamType__RTP_Unicast, ///< xs:string value="RTP-Unicast"
tt__StreamType__RTP_Multicast, ///< xs:string value="RTP-Multicast"
};

class tt__Transport
{ public:
/// <PRE><BLOCKQUOTE>
/// Defines the network protocol for streaming, either UDP=RTP/UDP, RTSP=RTP/RTSP/TCP or HTTP=RTP/RTSP/HTTP/TCP
/// </BLOCKQUOTE></PRE>
/// Element "Protocol" of XSD type "http://www.onvif.org/ver10/schema":TransportProtocol.
enum tt__TransportProtocol Protocol 1; ///< Required element.
/// <PRE><BLOCKQUOTE>
/// Optional element to describe further tunnel options. This element is normally not needed
/// </BLOCKQUOTE></PRE>
/// Element "Tunnel" of XSD type "http://www.onvif.org/ver10/schema":Transport.
tt__Transport* Tunnel 0; ///< Optional element.
/// A handle to the soap struct context that manages this instance when instantiated by a context or NULL otherwise (automatically set).
struct soap *soap ;
};

enum tt__TransportProtocol
{
tt__TransportProtocol__UDP, ///< xs:string value="UDP"
tt__TransportProtocol__TCP, ///< xs:string value="TCP"
tt__TransportProtocol__RTSP, ///< xs:string value="RTSP"
tt__TransportProtocol__HTTP, ///< xs:string value="HTTP"
};

响应包结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class _trt__GetStreamUriResponse
{ public:

/// </BLOCKQUOTE></PRE>
/// Element "MediaUri" of XSD type "http://www.onvif.org/ver10/schema":MediaUri.
tt__MediaUri* MediaUri 1; ///< Required element.
/// A handle to the soap struct context that manages this instance when instantiated by a context or NULL otherwise (automatically set).
struct soap *soap ;
};

class tt__MediaUri
{ public:
/// <PRE><BLOCKQUOTE>
/// Stable Uri to be used for requesting the media stream
/// </BLOCKQUOTE></PRE>
/// Element "Uri" of XSD type xs:anyURI.
xsd__anyURI Uri 1; ///< Required element.
/// <PRE><BLOCKQUOTE>
/// Indicates if the Uri is only valid until the connection is established. The value shall be set to "false".
/// </BLOCKQUOTE></PRE>
/// Element "InvalidAfterConnect" of XSD type xs:boolean.
bool InvalidAfterConnect 1; ///< Required element.
/// <PRE><BLOCKQUOTE>
/// Indicates if the Uri is invalid after a reboot of the device. The value shall be set to "false".
/// </BLOCKQUOTE></PRE>
/// Element "InvalidAfterReboot" of XSD type xs:boolean.
bool InvalidAfterReboot 1; ///< Required element.
/// <PRE><BLOCKQUOTE>
/// Duration how long the Uri is valid. This parameter shall be set to PT0S to indicate that this stream URI is indefinitely valid even if the profile changes
/// </BLOCKQUOTE></PRE>
/// Element "Timeout" of XSD type xs:duration.
xsd__duration Timeout 1; ///< Required element.
/// @todo <any namespace="##any" minOccurs="0" maxOccurs="unbounded">
/// @todo Schema extensibility is user-definable.
/// Consult the protocol documentation to change or insert declarations.
/// Use wsdl2h option -x to remove this element.
/// Use wsdl2h option -d for xsd__anyType DOM (soap_dom_element).
/// Size of the array of XML or DOM nodes is 0..unbounded.
std::vector<xsd__anyType > __any 0; ///< Catch any element content in DOM.
/// @todo <anyAttribute namespace="##any">.
/// @todo Schema extensibility is user-definable.
/// Consult the protocol documentation to change or insert declarations.
/// Use wsdl2h option -x to remove this attribute.
/// Use wsdl2h option -d for xsd__anyAttribute DOM (soap_dom_attribute).
@xsd__anyAttribute __anyAttribute ; ///< Store anyAttribute content in DOM soap_dom_attribute linked node structure.
/// A handle to the soap struct context that manages this instance when instantiated by a context or NULL otherwise (automatically set).
struct soap *soap ;
};

后记

文中只针对GetStreamUri字段进行说明,未提到ONVIF的连接、ONVIF鉴权等事宜。
本文出现的函数、枚举,均为很久前笔者生成的ONVIF框架代码,不表示本文发表时生成的ONVIF代码亦如是。
文中提到的profile,可搜索网上资料,也可参考笔者之前写的文章。
ONVIF不实现RTSP流服务,仅提供一个渠道让客户端获取流地址而已。实际项目中,可使用live555(笔者曾经研究过一段时间,其它服务未接触)实现。
由于ONVIF可参考资料较少,文中难免有错漏之处,欢迎指正,一起学习,共同进步。

参考资料:

李迟 2019.4.15 周一 深夜