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的延時略大一些,估計各個瀏覽器的緩存策略造成了差異。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章