rtsp流轉為fmp4並由WebSocket網關轉發,及對應js播放器


eb端是無法直接播放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的代碼:

  1.  
    if (avformat_alloc_output_context2(&Out_FormatContext, NULL, "mp4", NULL) < 0)
  2.  
    return false;
  3.  
    pb_Buf = (uint8_t*)av_malloc(sizeof(uint8_t)*(D_PB_BUF_SIZE));
  4.  
    Out_FormatContext->pb = avio_alloc_context(pb_Buf, D_PB_BUF_SIZE, 1,(void*)this,NULL,write_buffer,NULL);
  5.  
    if (Out_FormatContext->pb == NULL)
  6.  
    {
  7.  
    avformat_free_context(Out_FormatContext);
  8.  
    Out_FormatContext = NULL;
  9.  
    sendWSString( "fail");
  10.  
    return false;
  11.  
    }
  12.  
    Out_FormatContext->pb->write_flag = 1;
  13.  
    Out_FormatContext->pb->seekable = 1;
  14.  
    Out_FormatContext->flags=AVFMT_FLAG_CUSTOM_IO;
  15.  
    Out_FormatContext->flags |= AVFMT_FLAG_FLUSH_PACKETS;
  16.  
    Out_FormatContext->flags |= AVFMT_NOFILE;
  17.  
    Out_FormatContext->flags |= AVFMT_FLAG_AUTO_BSF;
  18.  
    Out_FormatContext->flags |= AVFMT_FLAG_NOBUFFER;

這里需要注意的是pb不僅write_flag要設置成1,seekable也要設置成1,seekable這個很容易就忽略了,然而這個如果不是1,那么創建會失敗。ffmpeg寫數據輸出到內存部分,參考avio_alloc_context的回調函數用法。

獲取mime的方法:

  1.  
    static std::string GetMIME(uint8_t* data, int len)
  2.  
    {
  3.  
    int n = 0;
  4.  
    if (data[0] == 0)
  5.  
    {
  6.  
    while (n + 3 < len)
  7.  
    {
  8.  
    if ((data[n] == 0) & (data[n + 1] == 0) & (data[n + 2] == 1))
  9.  
    {
  10.  
    n += 3;
  11.  
    break;
  12.  
    }
  13.  
    else
  14.  
    n++;
  15.  
    }
  16.  
    }
  17.  
    n += 1;
  18.  
    if (n + 3 > len)
  19.  
    return "";
  20.  
    char mime[ 10] = {0};
  21.  
    sprintf(mime, "%.2x%.2x%.2x",data[n], data[n + 1], data[n + 2]);
  22.  
    return std::string(mime);
  23.  
    }

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數據后的處理

  1.  
    if(typeof(evt.data)=="string") //服務器傳過來的可能是字符串,判斷是不是
  2.  
    {
  3.  
    var str = evt.data;
  4.  
    console.log(str);
  5.  
    var strs = new Array(); //定義一數組
  6.  
    strs = str.split( ":"); //字符分割
  7.  
    if (strs[0] == "open")
  8.  
    {
  9.  
    var mimestr = strs[1];
  10.  
    this.playurl(mimestr);
  11.  
    }
  12.  
    }
  13.  
    else
  14.  
    {
  15.  
    var result = new Uint8Array(evt.data);
  16.  
    this.queue.push(result);
  17.  
    if (this.needsend == true)
  18.  
    {
  19.  
    this.loadvideo();
  20.  
    }
  21.  
    }

字符串數據這里只用了很簡單的定義,如果ws網關打開rtsp失敗,那么返回的是“fail”,如果返回成功,則是“open:mime”,通過分隔符將mime取出,就可以初始化mime了;如果是二進制數據,則直接放到隊列中。js代碼不熟,有需要的各位自己按需求優化。

這套代碼對rtsp源有格式要求,必須是h264+aac或純h264的rtsp數據,因為mse對能播放的fmp4有要求,代碼中並未對音視頻進行重編碼。另外代碼中是使用rtp over tcp來傳輸的,使用udp模式請修改代碼。最后,本方案達到的延時極低,但在chrome和firefox上對比,firefox的延時略大一些,估計各個瀏覽器的緩存策略造成了差異。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM