rtsp流转为fmp4并由WebSocket网关转发,及对应js播放器

web端是无法直接播放rtsp流的,目前常用的解决方案是如jsmpeg、flv.js等。这些方案都是要推送流到服务端,之后才能在web上播放视频,相对比较麻烦。我采用websocket结合mse的方式,实现了一个websocket网关,及其对应的js播放器,在这里做下说明,具体代码参考github上我的源码。

这套方案的原理是,ws网关在拉到rtsp流后,取得mime,将其发送给web端,然后将rtsp流转为fmp4格式,以二进制数据格式发给web端;web端用其初始化mse,然后将websocket收到的二进制数据扔给mse,实现视频的播放。

ws网关有两个关键的问题需要解决,一是封装成fmp4后,输出要到内存而不是文件,二是要能取得mime。如果以网上以回调函数作为ffmpeg输出的例子来写,会发现创建失败。mime对应的是编码类型,需要解析流才能得到,具体怎么解决这两个问题,看看下面的说明。

创建输出的AVFormatContext的代码:

if (avformat_alloc_output_context2(&Out_FormatContext, NULL, "mp4", NULL) < 0)
        return false;
    pb_Buf = (uint8_t*)av_malloc(sizeof(uint8_t)*(D_PB_BUF_SIZE));
    Out_FormatContext->pb = avio_alloc_context(pb_Buf, D_PB_BUF_SIZE,1,(void*)this,NULL,write_buffer,NULL);
    if (Out_FormatContext->pb == NULL)
    {
        avformat_free_context(Out_FormatContext);
        Out_FormatContext = NULL;
		sendWSString("fail");
        return false;
    }
	Out_FormatContext->pb->write_flag = 1;
	Out_FormatContext->pb->seekable = 1;
	Out_FormatContext->flags=AVFMT_FLAG_CUSTOM_IO;
	Out_FormatContext->flags |= AVFMT_FLAG_FLUSH_PACKETS;
	Out_FormatContext->flags |= AVFMT_NOFILE;
	Out_FormatContext->flags |= AVFMT_FLAG_AUTO_BSF;
	Out_FormatContext->flags |= AVFMT_FLAG_NOBUFFER;

这里需要注意的是pb不仅write_flag要设置成1,seekable也要设置成1,seekable这个很容易就忽略了,然而这个如果不是1,那么创建会失败。ffmpeg写数据输出到内存部分,参考avio_alloc_context的回调函数用法。

获取mime的方法:

static std::string GetMIME(uint8_t* data, int len)
{
	int n = 0;
	if (data[0] == 0)
	{
		while (n + 3 < len)
		{
			if ((data[n] == 0) & (data[n + 1] == 0) & (data[n + 2] == 1))
			{
				n += 3;
				break;
			}
			else
				n++;
		}
	}
	n += 1;
	if (n + 3 > len)
		return "";
	char mime[10] = {0};
	sprintf(mime,"%.2x%.2x%.2x",data[n], data[n + 1], data[n + 2]);
	return std::string(mime);
}

mime可以通过spspps取得,ffmpeg在创建AVStream后,264的spspps可以从codecpar->extradata取得,在extradata中跳过264的分隔符后,接下来的第2、3、4个字节就可以拼出264的mime。

mime的音频部分可以参考https://wiki.multimedia.cx/index.php?title=MPEG-4_Audio,以"mp4a.40.2"举例,mp4a.40表示音频解码器为aac,.2表示AAC LC,对比ffmpeg的定义可以发现,这个值就是codecpar的profile加1.

另外说明一下movflags,fmp4需要设置成frag_keyframe+empty_moov,这样就是fmp4,我设置的值是frag_keyframe+empty_moov+omit_tfhd_offset+faststart+separate_moof+disable_chpl+default_base_moof+dash,其中omit_tfhd_offset这个设置是针对chrome浏览器的,如果不设置的该项,chrome上是会播放失败的,faststart是为了将moov移动到mdat前面,separate_moof如果不加上,chrome处理音频时会有问题,尚未找出是视频源问题还是共性问题。

js播放器这边很简单,需要说明一下的是收到ws数据后的处理

		if(typeof(evt.data)=="string")            //服务器传过来的可能是字符串,判断是不是
        {
            var str = evt.data;
			console.log(str);
			var strs = new Array(); //定义一数组
			strs = str.split(":"); //字符分割
			if (strs[0] == "open")
			{
				var mimestr = strs[1];
				this.playurl(mimestr);
			}
        }
        else
        {
			var result = new Uint8Array(evt.data);
			this.queue.push(result);
			if (this.needsend == true)
			{
				this.loadvideo();
			}
        }

字符串数据这里只用了很简单的定义,如果ws网关打开rtsp失败,那么返回的是“fail”,如果返回成功,则是“open:mime”,通过分隔符将mime取出,就可以初始化mime了;如果是二进制数据,则直接放到队列中。js代码不熟,有需要的各位自己按需求优化。

这套代码对rtsp源有格式要求,必须是h264+aac或纯h264的rtsp数据,因为mse对能播放的fmp4有要求,代码中并未对音视频进行重编码。另外代码中是使用rtp over tcp来传输的,使用udp模式请修改代码。最后,本方案达到的延时极低,但在chrome和firefox上对比,firefox的延时略大一些,估计各个浏览器的缓存策略造成了差异。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值