rtsp 协议读取视频进行分析并返回结果到websocket server

一、rtsp 协议读取视频

1.1读取方法ffmpeg

这种方法和opencv是一样的,因为opencv使用的就是ffmpeg,结果不是很好,断线重连不是很好做,有一个好处是不用引入其他库,ffmpeg也用来解码之用。

1.2 live555

速度快,断线重连比较方便,摄像头像海康大华等等时间长了会自动重启内部服务,这时候涉及断线重连。这里使用这种方式。

1.3 自己写接收代码

理解rtsp协议之后使用udp服务接收帧,这种好处是完全控制协议和数据,最大的灵活性,但是也带来代码调试量增多的问题。

二、基础算法分析

注意:实现 rtsp 端口使用 sdp 协议获取图像信息,使用 rtp 协议接收图像,解码后得到 yuv,使用 y 分量得到灰白图像,在需要 rgb 的时候使用 yuv 转化成 rgb。如果算法需要的是灰度图,那就直接解码成yuv并取分量,如果一定要彩色图,那就直接使用ffmpeg解码成彩色。如果使用opencv的mat 矩阵,那就直接解码到mat的内存中,不要拷贝来拷贝去。

1 清晰度算法

梯度函数常被用来提取边缘信息,聚焦良好的图像,具有更尖锐的边缘,应有
更大的梯度函数值。采用 sobel 算子分别提取水平和垂直方向的梯度值,基于
tenengrad 梯度函数清晰度定义如下:

2 色偏度和色偏方向

输入 BGR 颜色后转化成 lab 颜色表示方式
Lab 是由一个亮度通道(channel)和两个颜色通道组成的。在 Lab 颜色空间中,每个
颜色用 L、a、b 三个数字表示,各个分量的含义是这样的:

  • L*代表亮度
  • a*代表从绿色到红色的分量
  • b*代表从蓝色到黄色的分量
    *函数描述: calcCast 计算并返回一幅图像的色偏度以及,色偏方向
    *函数参数: InputImg 需要计算的图片,BGR 存放格式,彩色(3 通道),灰度图无效
  • cast 计算出的偏差值,小于 1 表示比较正常,大于 1 表示存在色偏
  • da 红/绿色偏估计值,da 大于 0,表示偏红;da 小于 0 表示偏绿
  • db 黄/蓝色偏估计值,db 大于 0,表示偏黄;db 小于 0 表示偏蓝
    *函数返回值: 返回值通过 cast、da、db 三个应用返回,无显式返回值

3 亮度异常

转成灰度图以后,计算颜色的直方图,计算分布在 0-255 颜色的值,设定灰度
图平均值为亮度均值如 128,求取每个像素点的与平均值差为 d1,并求取均值
da ,求取 256 种灰度值颜色与均值的差并求取平均值 Ma
求取 K = abs(da)/ abs(Ma) 为亮度系数
cast 计算出的偏差值,小于 1 表示比较正常,大于 1 表示存在亮度异常;当 cast 异常时,
da 大于 0 表示过亮,da 小于 0 表示过暗

for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
a += float(value[i, j]) - 128); 
int x = value[i, j];
Hist[x]++; //直方图
}}
da = a / float(imageGray.rows*imageGray.cols);
float D = abs(da);
float Ma = 0;
for (int i = 0; i < 256; i++)
{
Ma += abs(i - 128 - da)*Hist[i];
}
Ma /= float((imageGray.rows*imageGray.cols));
float M = abs(Ma);
float K = D / M;

4 马赛克

马赛克出现时,大量正方形边出现,求取图像边缘,边缘检测之 Canny 检测
Canny 检测是最优的边缘检测算法:
1.消除噪声:运用高斯内核进行卷积降噪。
2.计算梯度幅值和方向。
3.非极大值抑制:这一步排除非边缘像素,仅仅保留了一些细线条(候选边
缘)
4.滞后阈值:滞后阈值需要两个阈值(高阈值和低阈值):若某一个像素位置
的幅值超过高阈值,该像素被保留。
:若某一个像素位置的幅值小于低阈值,该像素被排除。
:若某个像素位置的幅值在两者之间,该像素仅仅在连接到一个高于高阈值的
像素时被保留。
求取边缘后,在边缘中寻找方形,如果找到,则为有马赛克

5 雪花噪声

雪花噪声为高斯噪声和椒盐噪声,使用中值滤波算法尝试去除,如果原图和
去除后的图有较大的区别,则判断有雪花。
//准备 0°,45°,90°,135°4 个方向的卷积模板。
//用图像先和四个模板做卷积,用四个卷积绝对值最小值 Min 来检测噪声点。
//求灰度图 gray 与其中值滤波图 median。
//判断噪声点:fabs(median - gray)>10 && min>0.1。
//噪声点占整幅图像的比较即为雪花噪声率。

三、进行分析并返回图像结果到web

两种方式,解码接收到h264 -》sps pps,h265-》 vps,sps,pps,h265解码在后端传图像到前端

3.1 使用ws返回图像jpg 叠加数据 返到web上

这里使用这种模式

 function init() {
              canvas = document.createElement('canvas');
              content = canvas.getContext('2d');
             
              canvas.width = 320;
              canvas.height = 240;
              content.scale(1, -1);
              content.translate(0, -240);
              document.body.appendChild(canvas);
              //  container.appendChild( canvas );
              img = new Image();
              img.src = "bg1.jpg";
              canvas.style.position = 'absolute';
              img.onload = function () {
                  content.drawImage(img, 0, 0, canvas.width, canvas.height);
                 // imgDate = content.getImageData(0, 0, canvas.width, canvas.height);
                  //createPotCloud();   //创建点云
              };
          }
 function WebSocketTest() {
            if ("WebSocket" in window) {
                // alert("您的浏览器支持 WebSocket!");
                // 打开一个 web socket
                var ws = new WebSocket("ws://127.0.0.1:2268");
                console.log(ws);
                ws.onopen = function (evt) {
                    console.log("connected");
                    /*let obj = JSON.stringify({
                        test:"qianbo0423"
                    })
                    ws.send(obj);*/
                };
                ws.onmessage = function (evt) {
                    if (typeof (evt.data) == "string") {
                        //textHandler(JSON.parse(evt.data));  
                    } else {
                        var reader = new FileReader();
                        reader.onload = function (evt) {
                            if (evt.target.readyState == FileReader.DONE) {
                                var url = evt.target.result;
                                // console.log(url);
                                img.src = url;
                                //img.src = url;// "bg1.jpg";
                                //var imga = document.getElementById("imgDiv");
                                //imga.innerHTML = "<img src = " + url + " />";
                            }
                        }
                        reader.readAsDataURL(evt.data);
                    }
                };
                ws.onclose = function () {
                    alert("连接已关闭...");
                };
            } else {
                // 浏览器不支持 WebSocket
                alert("您的浏览器不支持 WebSocket!");
            }
        }

3.2 使用flv.js返回数据

这里使用这种模式返回h264数据
flv.js 本身没有追帧功能,修改里面的部分函数可以达到较为实时的播放,页面切换时像flv.js或者hls等会停止播放,这些可以用追帧跳帧解决。

四、websocket server

使用c++来写这个server,因为本身里面调用了视频的许多功能,编解码,分析,使用c++比较划算,自己实现一个websocketserver可以根据RFC6455文档,这里使用了boost库的协程模式写出一个websocket的server。规格如下:

 RFC 6455
	  0                   1                   2                   3
	  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
	 +-+-+-+-+-------+-+-------------+-------------------------------+
	 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
	 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
	 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
	 | |1|2|3|       |K|             |                               |
	 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
	 |     Extended payload length continued, if payload len == 127  |
	 + - - - - - - - - - - - - - - - +-------------------------------+
	 |                               |Masking-key, if MASK set to 1  |
	 +-------------------------------+-------------------------------+
	 | Masking-key (continued)       |          Payload Data         |
	 +-------------------------------- - - - - - - - - - - - - - - - +
	 :                     Payload Data continued ...                :
	 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
	 |                     Payload Data continued ...                |
	 +---------------------------------------------------------------+
*/

websocket server 最重要的时握手过程,前面时http协议,然后时sha1算法进行摘要算法,返回前端,前端浏览器会验证,其实自己写websocket client一般会省略这个过程,但是浏览器的websocket 不会省略。以下为握手过程:

bool func_hand_shake(boost::asio::yield_context &yield)
	{
		DEFINE_EC
		asio::streambuf content_;
		size_t length = asio::async_read_until(v_socket, content_, "\r\n\r\n", yield[ec]);
		ERROR_RETURN_FALSE
		asio::streambuf::const_buffers_type bufs = content_.data();
		std::string lines(asio::buffers_begin(bufs), asio::buffers_begin(bufs) + length);
		//c_header_map hmap;
		//fetch_head_info(lines, hmap, v_app_stream);
		//the url length not over 1024;
		char buf[1024];
		fetch_head_get(lines.c_str(), buf, 1023);
		//v_app_stream = buf;
		cout << "get:" << buf<< endl; //like this--> live/1001 rtmp server must like this


		std::string response, key, encrypted_key;
		//find the get
		//std::string request;
		size_t n = lines.find_first_of('\r');
		//find the Sec-WebSocket-Key
		size_t pos = lines.find("Sec-WebSocket-Key");
		if (pos == lines.npos)
			return false;
		size_t end = lines.find("\r\n", pos);
		key = lines.substr(pos + 19, end - pos - 19) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
		//get the base64 encode string with sha1
		
#if 1
		boost::uuids::detail::sha1 sha1;
		sha1.process_bytes(key.c_str(), key.size());
#endif
#if 0
		SHA1 sha;
		unsigned int message_digest[5];
		sha.Reset();
		sha << server_key.c_str();
		sha.Result(message_digest);
#endif
		unsigned int digest[5];
		sha1.get_digest(digest);

		for (int i = 0; i < 5; i++) {
			digest[i] = htonl(digest[i]);
		}

		encrypted_key = base64_encode(reinterpret_cast<const uint8_t*>(&digest[0]), 20);

		/*
		The handshake from the server looks as follows :

		HTTP / 1.1 101 Switching Protocols
		Upgrade : websocket
		Connection : Upgrade
		Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK + xOo =
		Sec-WebSocket-Protocol: chat
		 */
		 //set the response text
		response.append("HTTP/1.1 101 WebSocket Protocol Handshake\r\n");
		response.append("Upgrade: websocket\r\n");
		response.append("Connection: Upgrade\r\n");
		response.append("Sec-WebSocket-Accept: " + encrypted_key + "\r\n\r\n");
		//response.append("Sec-WebSocket-Protocol: chat\r\n");
		//response.append("Sec-WebSocket-Version: 13\r\n\r\n");
		size_t ret = boost::asio::async_write(v_socket, boost::asio::buffer(response), yield[ec]);
		ERROR_RETURN_FALSE
		//calculate the hash key 
		v_key = hash_add(buf, HASH_PRIME_MIDDLE);
		
		c_flvhubs::instance()->push_session(v_key, shared_from_this());
		return true;
	}

发送:

bool func_set_head_send(uint8_t * frame, int len /*payloadlen*/, int framelen, asio::yield_context &yield)
	{
		*frame = 0x81;//0x81; 1000 0001 text code ; // 1000 0010 binary code
		//*frame = 0x82;
		if (len <= 125) {
			//数据长度小于1个字节
			//mask bit is 0
			*(frame + 1) = (uint8_t)len;
		}
		else if (len <= 0xFFFF) { //65535
			//数据长度小于2个字节
			*(frame + 1) = 126;
			*(frame + 2) = len & 0x000000FF;
			*(frame + 3) = (len & 0x0000FF00) >> 8;
		}
		else {
			//数据长度为8个字节
			*(frame + 1) = 127;
			*(frame + 2) = len & 0x000000FF;
			*(frame + 3) = (len & 0x0000FF00) >> 8;
			*(frame + 4) = (len & 0x00FF0000) >> 16;
			*(frame + 5) = (len & 0xFF000000) >> 24;
			*(frame + 6) = 0;
			*(frame + 7) = 0;
			*(frame + 8) = 0;
			*(frame + 9) = 0;
		}

		DEFINE_EC
		//send the data
		asio::async_write(v_socket, asio::buffer(frame, framelen), yield[ec]);
		if (ec)
		{
			return false;
		}
		return true;
	}

boost的协程模式开发服务器效率挺高的,我用这个做了rtmpserver和其他udpserver,另外一个选择使用go语言,可以看我的其他文章。

五、问题和如何分布式

可以选用这种模式,flv.js自动播放总是要点一下,edge下没有这个问题,可以用技术解决这种小问题,看量,http 的flv有个数限制,http协议限制6个连接数,一个chrome等浏览器最多呈现6个链接,是并发最大数,ws限制为255,所以使用websocket了这种模式,

5.2 分布式推送

准备使用rtp over udp方式进行推送,好处良多,请看我其他文章。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qianbo_insist

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值