编程语言:C

        目标平台:arm(hi3519)注:因为代码是纯C语言按道理可以移植到各种平台   

        基础库:librtsp(存在功能补全,有一定的bug)    

        因为项目需求需要在海思平台实现IPC的全部功能,实现RTSPServer的方案很多,冲浪了github平台发现有很多开源的方案,但是也有一些假开源比如EasyRtspServer(一个平台授权几万米^_^),有一些RtspServer是用C++写的,目前海思的交叉编译工具对C++高版本(大于C++11)支持并不好虽然可以编译过去但是运行的时候会有很多意向不到的问题,这也是本文为什么选用librtsp的原因。

        因为以前接触过librtsp,并在海思平台实现了RtspServer(相对来讲还是比较稳定的),但项目需求需要新增鉴权的功能(使用用户名密码登录),翻遍github发现能支持鉴权的开源项目并不多而且多数移植存在兼容性的问题比如前面提到的问题,因此下定决心在librtsp的基础上新增用户名密码鉴权的功能,并修复以前就存在闪退的bug。


1、首先通读代码

        主要的代码集中在了rtsp_demo.c和rtsp_msg.c里,demo里主要解决客户端连接和相关消息发送的功能,msg主要封装了协议解析的部分,因此本文的功能新增也都是在这两个文件中处理的。

2、熟悉鉴权协议和信息交互流程

        新增鉴权的功能,首先需要了解RTSP协议鉴权的步骤《参见》。

3、抓包分析

        3.1、找来海康的摄像头,使用Wireshark抓包工具对鉴权的过程进行分析,如下图

监控RTSP的账号密码_加解密

         3.2、未通过验证客户端(VLC)的发送和服务器应答如下(海康摄像头应答)

        VLC发送

监控RTSP的账号密码_加解密_02

        服务器应答如下(海康摄像头) 

监控RTSP的账号密码_ffmpeg_03

         其中红框内指明了密码加密的方式,目前有两种一种是Digest,MD5加解密,一种是Basic,base64加解密,因为base64相对好实现一些因此本文先采用Basic的验证方式(VLC播放器会自动匹配),当然服务器(相机)可以做两种鉴权方式都支持(接下来再去完善MD5加解密的功能)。

        3.3、客户端回复如下(VLC播放器)

监控RTSP的账号密码_客户端_04

         3.4、通过以上的抓包分析可知,只需要在librtsp内完善DESCRIBE请求中的验证和解密即可(因为本文先用BASIC即base64加解密)


4、修改代码

        4.1、首先在rtsp client结构体内新增passed标志,表明该连接是否通过验证,未通过验证则不发送数据包,如下图

监控RTSP的账号密码_ffmpeg_05

         4.2、在rtsp_do_event函数修改发送的地方新增passed判断(数据发送地方很多自行查找,涉及到音频和视频的发送)

监控RTSP的账号密码_客户端_06

         4.3、处理DESCRIBE请求

        在rtsp_handle_DESCRIBE函数内,新增加解密的功能,如下

监控RTSP的账号密码_网络_07

         4.4、处理完DESCRIBE请求后还有socket的接收和发送的处理,完善authorization的相关处理函数

        4.5、base64代码如下 《参考》

static int base64Decode(uint8_t *input)
{
	unsigned int inlen = strlen(input);
	uint8_t output[1024] = {0};
	//解码需要一张反着的表
	const char *base64_tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

	int off = 0;
	unsigned int i;
	uint8_t reverse_tbl[256] = {0};

	if (NULL == input) {
		return -1;
	}
	if (inlen == 0) {
		return -2;
	}

	uint8_t *p = input;
	while (isprint(*p) && *p != ' ') {
		p++;
	}
	p++;
	inlen = inlen - (p - input);
	
	if (inlen % 4 != 0) {
		/*error not a base64*/
		dbprt("decode auth message is not a base64 message.");
		return -1;
	}

	for (i = 0; i < 64; i++) {
		reverse_tbl[base64_tbl[i]] = i;
	}

	for (i = 0; i < inlen - 4; i += 4) {
		output[off++] = (((reverse_tbl[p[i]] << 2) | (reverse_tbl[p[i + 1]] >> 4)) & 0xFF);
		output[off++] = (((reverse_tbl[p[i + 1]] << 4) | (reverse_tbl[p[i + 2]] >> 2)) & 0xFF);
		output[off++] = (((reverse_tbl[p[i + 2]] << 6) | (reverse_tbl[p[i + 3]])) & 0xFF);
	}

	if (p[i + 2] == '=') {
		output[off++] = (((reverse_tbl[p[i]] << 2) | (reverse_tbl[p[i + 1]] >> 4)) & 0xFF);
	}
	else if (p[i + 3] == '=') {
		output[off++] = (((reverse_tbl[p[i]] << 2) | (reverse_tbl[p[i + 1]] >> 4)) & 0xFF);
		output[off++] = (((reverse_tbl[p[i + 1]] << 4) | (reverse_tbl[p[i + 2]] >> 2)) & 0xFF);
	}
	else {
		output[off++] = (((reverse_tbl[p[i]] << 2) | (reverse_tbl[p[i + 1]] >> 4)) & 0xFF);
		output[off++] = (((reverse_tbl[p[i + 1]] << 4) | (reverse_tbl[p[i + 2]] >> 2)) & 0xFF);
		output[off++] = (((reverse_tbl[p[i + 2]] << 6) | (reverse_tbl[p[i + 3]])) & 0xFF);
	}

	dbprt("decode auth message is : %s", output);

	if (strstr(output, "admin:123456") == NULL) {
		return -1;
	}

	return 0;
}

5、相机程序异常退出的问题解决,因为linux平台在处理socket数据发送的时候如果socket客户端已关闭,会出现程序异常闪退的问题,参见下面博客解决《参考》,如下图send的地方都需要修改

监控RTSP的账号密码_客户端_08

 6、本工程暂不开源,后续完善MD5加解密后再github见,