rtsp,rtp,gb28181直接转化为html5播放(二)

承接上一节
udp服务器接流转html5播放

效果

先看效果:

rtsp,rtp,gb28181直接转化为html5播放(二)_第1张图片
服务器拉rtsp流,直接转成flv,不经过推流,浏览器使用ws直接连接写得我们写得服务器播放,因为websocket本身是跨域得,可以直接使用文件进行播放。http协议支持得时候注意要启动http服务支持,否则会跨域截断。

rtsp,rtp截流hack

上一章我们使用了udpserver来接流,也就是接得纯rtp,拼接成帧就行,而rtsp,和GB28181都使用rtp协议,【GB28181会使用ps流】,原理都一样,同样获取rtp流以后,可以直接转化成单帧再进行html5传输,如fmp4,或者转化成flv格式,使用flv.js来直接播放,flv内部也是转成了fmp4,区别不是很大。

1.1 截流描述

上一次我们使用了live555来接rtsp,live555接rtsp流,这样加上udp接就比较实用了,可以使用ffmpeg发送测试,也可以使用rtsp直接接入,rtsp连接写入配置文件中:in.txt

#id(int):address(varchar):width(int):height(int)
#[email protected] author:钱波 qianbo 2021-02-28
1 rtsp://admin:[email protected]/h264/ch1/sub/av_stream 1280 720 live/1001
#2 rtsp://127.0.0.1/2.264 1280 720 live/1001
#3 rtsp://127.0.0.1/1.264 640 360 live/1002

带# 得是注释,文件引入了一路海康流,输出流为live/1001

1.2流程

流程为:
1 线程rtsp client 拉到流后发送给flv服务器
2 flv服务器接收到连接后将连接放入到hub中,待接到rtsp sps pps等信息后开始发送meta信息,启动推流协程,并同步每个人得时间戳。注意每个人得时间戳是不一样得。
3 ws浏览器播放端退出在推流协程里面同步判断,退出则在hub中删除该端。

1.3 读配置文件类

struct s_file
{
	std::vector<s_source> v_sources;

	int func_read()
	{
		std::ifstream infile("in.txt", std::ios::in); //以文本模式打开in.txt备读
		if (!infile) { //打开失败
			std::cout << "error opening source file." << std::endl;
			return -1;
		}
		v_sources.clear();
		while (infile.good()) {
			string temp;
			s_source s;

			std::getline(infile, temp);
			if (temp.length() > 3 /*0xEF 0xBB 0xBF*/)
			{
				uint8_t *pos = (uint8_t*)temp.c_str();
				if (*pos == 0xEF)
				{
					temp = temp.substr(3);
					printf("%02x %02x %02x \n", *(pos), *(pos + 1), *(pos + 2));
					cout << "utf8 and BOM" << endl;
				}
			}
			//0xEF, 0xBB, 0xBF
			/*注意如果文件直接使用移入字符串,必须判断是否为结束,否则会多一行*/
			//infile >> temp;
			//if (infile.eof())
			//	break;
			trim(temp);
			if (!ifnote(temp) && !temp.empty())
			{
				istringstream istr(temp);
				istr >>s.id >> s.v_address >> s.d_width >> s.d_height >>s.v_liveflv;
				v_sources.push_back(s);
			}
			else
			{
				//数据库解析字段
			}
		}
		return 0;
	}
};

isnote函数只是判断是否带有注释,注释则不加入其中,比较简单,读者可以自行写出。

1.4 websocket服务器

websocket服务器我们自行编写,使用协程方式来制作,这个在我得其他文章里面也提供了全部源代码,读者可以找一下。

1.5 flv服务器

显然flv服务器建立在websocket服务器之上,加了flv格式得封装,class 图如下图所示

class c_flvserver
{
private:
	//websocket or http
	asio::io_context &v_io_con_webs;
	//calculate the number of coroutine
	std::atomic<uint32_t> v_coroutine_num = 0;



public:
	/*int process_spspps(uint8_t * sps, uint32_t spslen,
		uint8_t * pps,
		uint32_t ppslen,
		uint32_t timestamp,
		int width, int height, int fps,
		uint32_t key
	);*/
	
#if 0
	//关键帧再次同时写入sps和pps,特殊场合使用
	int process_h264_spspps(
		uint8_t *sps, int spslen,
		uint8_t *pps, int ppslen,
		uint8_t * nal,
		uint32_t nal_len,
		uint32_t timestamp,
		bool IsIframe);
#endif
	void storage_meta(uint32_t key,
		ptr_s_memory mem);

	int create_metadata(uint8_t* buf, int width, int height, int frameRate);


	int process_spspps(uint8_t * sps, uint32_t spslen,
		uint8_t * pps,
		uint32_t ppslen,
		uint32_t timestamp,
		uint32_t key
	);

	int process_spspps_sp(uint8_t * sps, uint32_t spslen,
		uint8_t * pps,
		uint32_t ppslen,
		uint32_t timestamp,
		uint32_t key
	);
	int process_h264(uint8_t * nal,
		uint32_t nal_len,
		uint32_t timestamp,
		bool IsIframe,
		uint32_t key
	);
	int process_aac(uint8_t* data, 
		uint32_t data_len, 
		uint32_t timestamp);

	c_flvserver(asio::io_context &iow) :v_io_con_webs(iow)
	{}
	void start(uint16_t port);



protected:
#ifdef SEND_META_SCRIPT
	int  create_metadata(uint8_t* buf, int width, int height, int frameRate);
#endif


protected:


	void storage_header(uint32_t key, int av, ptr_s_memory mem);

	void start_write_spawn(uint32_t key);
	

	bool func_set_head_send(tcp::socket &sock,
		uint8_t * buf, 
		size_t buflen /*payloadlen*/, 
		asio::yield_context &yield);
	//we must add the websockets head,use the func_set_head_send
	int func_send_2session_webs(s_flvhub &flvhub,
		ptr_session_webs ptr,
		ptr_s_memory mem,
		asio::yield_context &yield);


	//we do not add ,just send the flv data
	int func_send_2session_http(s_flvhub &flvhub,
		ptr_session_webs ptr,
		ptr_s_memory mem,
		asio::yield_context &yield);

};

1.6 下面给出关键代码

只有一个流容易,只有一个客户也容易,我们面对得一定是多路流和多个客户并发连接,所以要注意得事项比较多,下面看具体代码:

int c_flvserver::func_send_2session_webs(s_flvhub &flvhub,ptr_session_webs ptr, ptr_s_memory mem,
		asio::yield_context &yield)
{

	if (!ptr->v_socket.is_open())
		return -2;
	DEFINE_EC
	uint8_t * data = mem->v_data;
	size_t len = mem->v_len;
#if 1
	if (!ptr->v_has_send_meta){
		uint8_t * _data = flvhub.v_meta->v_data;
		size_t _len = flvhub.v_meta->v_len;
		if (func_set_head_send(ptr->v_socket, _data, _len, yield) == false)
			return -1;
		ptr->v_has_send_meta = 1;
	}
#endif
	if (flvhub.v_need_video && !ptr->v_has_send_video_head)
	{
		uint8_t * _data = flvhub.v_cache_headerv->v_data;
		size_t _len = flvhub.v_cache_headerv->v_len;
			if (func_set_head_send(ptr->v_socket, _data, (int)_len, yield) == false)
				return -1;
		ptr->v_has_send_video_head = 1;
	}
	if (flvhub.v_need_audio && !ptr->v_has_send_audio_head)
	{
		uint8_t * _data = flvhub.v_cache_headera->v_data;
		size_t _len = flvhub.v_cache_headera->v_len;
		if (func_set_head_send(ptr->v_socket, _data, (int)_len, yield) == false)
			return -1;
		ptr->v_has_send_audio_head = 1;
	}
	if (!ptr->v_has_sent_key_frame)
	{
		switch (*data) //the first byte
		{
		case 8://audio
		{
			cout << "audio waiting key frame." << endl;
			return 0;
		}
		break;
		case 9: //video
		{
			//fix me ,we must storage the key frame to the client
			if (*(data+11 )== 0x17)  //0x27 not keyframe
			{
				cout << "find the keyframe" << endl;
				ptr->v_has_sent_key_frame = 1;
				//开始时间戳计算,meevery 
				ptr->v_start_ts = GetTimestamp32();
			}
			else {
				cout << "...";
				return 0;
			}
		}
		break;
		default:
			return -1;
		break;
		}
	}
#if 1
	//change the timestamp,get the real stamp;
	uint32_t ts = GetTimestamp32() - ptr->v_start_ts;
	*(data + 4) =  (uint8_t)(ts >> 16);
	*(data + 5) =  (uint8_t)(ts >> 8);
	*(data + 6) =  (uint8_t)(ts);
	*(data + 7) =  (uint8_t)(ts >> 24);
#endif
	//flv_const_buffer bb(&header[0], 11, _data, _len);
	if (func_set_head_send(ptr->v_socket, data, (int)len, yield) == false)
		return -1;

	return 0;
}

以上是关键代码,下面给出一个代码结构,读者可以参考:

rtsp,rtp,gb28181直接转化为html5播放(二)_第2张图片
整个工程得代码并不多,比较简单,包含了flvserver,websocketserver,httpserver, rtp over udpserver,都使用协程制作,这样流程简单。包含rtsp得拉流客户端,比较实用,包含了analyse分析模块,可以对流进行图像分析等等,读者可以自行制作。

时间戳问题

如下图所示,明显vlc和ws连接得时间显然是不一样,ws连接时间很长,显示得时间竟然和vlc 相差20秒,这就是时间戳得问题了,所以要进一步处理该问题
rtsp,rtp,gb28181直接转化为html5播放(二)_第3张图片
如图,修正以后,时间戳趋于正常,flv播放比vlc rtsp 相差无几,快1秒左右,应该还有余地,可以在进行优化。
rtsp,rtp,gb28181直接转化为html5播放(二)_第4张图片

总结

本文并不使用拉流后再推流到服务端,而是将服务端直接集成,减少了部署内容,这是优点之一,第二个优点是并没有使用rtmp协议,而是直接转化成浏览器可播放得流,这是优点之二,缺点也很明显,读者可以提出讨论,或者有不明白得地方也可以讨论,知无不言言无不尽,第三篇我们可以加上rtsp服务,待续…

html测试代码

忘了html flv测试代码,加上了

你可能感兴趣的:(c++高级技巧,音视频和c++,java,物联网,rtp,c++,rtp,rtsp,flv,h264)