DASH研究

MP4格式研究

mp4解析在線地址:mp4parser

一個(gè)mp4就是一個(gè)容器: 每個(gè)mp4是由多個(gè)box組成奶躯,每個(gè)box可以嵌套子box,這個(gè)box稱為container box亿驾。
  • Box:每個(gè)Box由Header和Data組成嘹黔。
  • Header:包含了整個(gè)Box的長度size和類型type。當(dāng)size==0時(shí)莫瞬,代表這是文件中最后一個(gè)Box儡蔓;當(dāng)size==1時(shí),意味著Box長度需要更多bits來描述疼邀,在后面會定義一個(gè)64bits的largesize描述Box的長度喂江;當(dāng)type是uuid時(shí),代表Box中的數(shù)據(jù)是用戶自定義擴(kuò)展類型旁振。
  • Data:是Box的實(shí)際數(shù)據(jù)获询,可以是純數(shù)據(jù)也可以是更多的子Boxes。
mp4分有傳統(tǒng)的regular mp4, 和為適應(yīng)流媒體的fragmented Mp4(fMp4)拐袜。
  • regular mp4 主要由ftyp吉嚣,moov/mdat,mdat/moov等box組成蹬铺。
  • ftypbox尝哆,在文件的開始位置,描述的文件的版本甜攀、兼容協(xié)議等秋泄;
  • moovbox,這個(gè)box中不包含具體媒體數(shù)據(jù)规阀,但包含本文件中所有媒體數(shù)據(jù)的宏觀描述信息恒序,moov box下有mvhd和trak box。
  • mvhd中記錄了創(chuàng)建時(shí)間姥敛、修改時(shí)間奸焙、時(shí)間度量標(biāo)尺、可播放時(shí)長等信息彤敛。
  • trak中的一系列子box描述了每個(gè)媒體軌道的具體信息。

在fMp4格式中包含一系列的segments(moof+mdat的組合)了赌,這些segments可以被獨(dú)立的request(利用byte-range request)墨榄,這有利于在不同質(zhì)量級別的碼流之間做碼率切換操作

在regular mp4中,如果我們要在兩個(gè)碼流之間做碼率切換勿她,就需要找到兩個(gè)碼流中對應(yīng)時(shí)間點(diǎn)的byte position袄秩,然而這時(shí)候我們只有一個(gè)巨大的mdat box,要在這里面找到一個(gè)具體的byte position無疑是復(fù)雜的。而且之剧,在regular mp4中郭卫,有時(shí)moov會在巨大的mdat box之后,這也會影響起播的速度背稼。
moofbox(此box存在于fmp4中)贰军,這個(gè)box是視頻分片的描述信息。并不是MP4文件必須的部分蟹肘,但在我們常見的可在線播放的MP4格式文件中確是重中之重词疼。

  • mdatbox,實(shí)際媒體數(shù)據(jù)帘腹。我們最終解碼播放的數(shù)據(jù)都在這里面贰盗。
  • 其他box
    • Free Space Box(free或skip) “free”中的內(nèi)容是無關(guān)緊要的,可以被忽略阳欲。該box被刪除后舵盈,不會對播放產(chǎn)生任何影響。
    • sidx box是segment index box, 是fmp4的分片索引box球化。開發(fā)DASH時(shí)书释,需要解析這里的box,定位到對應(yīng)的視頻數(shù)據(jù)赊窥。

    sidx解析規(guī)則:
    box的header部分爆惧,分別為4B的size大小,4B的type類型(Unicode 值锨能,需要轉(zhuǎn)碼成字符串)扯再。
    box的data部分,關(guān)于DASH最有用的是referenced_size(單位:字節(jié))和subsegment_duration(單位:毫秒)址遇。個(gè)數(shù)就是fmp4的分片數(shù)目熄阻,數(shù)目為:type|size|duration * reference_count
    sidx內(nèi)容規(guī)則如下圖:

sidx 名字 大小
header size Uint32
header type Int32
header larsesize(if size==1) Uint64
data version Uint8
data none Uint24
data reference_ID Uint32
data timescale Uint32
data earliest_presentation_time Uint32
data first_offset Uint32
data reserved Uint16
data reference_count Uint16
data type/size/duration * reference_count Uint32
data SAP * reference_count Uint32
解析代碼如下

const mp4 = {
  parseSidx (dataView, offset) {
      /* 
          注意:
          此方法只能解析sidx -> dash只需解析sidx就行
          以及header里面size不等于1和0的情況 -> 這里需要將來支持一下
          以及data部分version為0的情況 
       */

      let hex2a = function (hex) {
          var str = '';
          for (var i = 0; i < hex.length; i += 2)
              str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
          return str;
      };
      let trim1 = function (str) {
          return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
      };
      const msg = {};
      msg.len = dataView.getUint32(offset); offset += 4;//獲取header頭size
      let type = dataView.getInt32(offset); offset += 4;
      msg.type = trim1(hex2a(type.toString(16)));//獲取header頭type

      msg.version = dataView.getUint8(offset); offset += 4;//獲取version
      msg.reference_ID = dataView.getUint32(offset);offset += 4;//獲取reference_ID
      msg.timescale = dataView.getUint32(offset);offset += 4;//timescale
      msg.earliest_presentation_time = dataView.getUint32(offset); offset += 4;//earliest_presentation_time
      msg.first_offset = dataView.getUint32(offset);  offset += 4;//first_offset
      msg.reserved = dataView.getUint16(offset) ;offset += 2;//reserved
      msg.reference_count = dataView.getUint16(offset) ;offset += 2;//reference_count
      msg.entries = [];
      let reference_count = msg.reference_count;
      while (reference_count--) {
          let entry = {};
          let fourBytes = dataView.getUint32(offset); offset += 4;
          entry['reference_type'] = (fourBytes >> 31) & 1;
          entry['referenced_size'] = (fourBytes & 0x7fffffff);
          entry['subsegment_duration'] = dataView.getUint32(offset);offset += 4;
          fourBytes = dataView.getUint32(offset);offset += 4;
          entry['starts_with_SAP'] = (fourBytes >> 31) & 1;
          entry['SAP_type'] = (fourBytes >> 29) & 7;
          entry['SAP_delta_time'] = (fourBytes & 0x0fffffff);
          msg.entries.push(entry);
      }
      return msg;
  }
};

export {mp4};

video播放原理

  • 原生video標(biāo)簽是不支持流媒體播放的,那么它怎么在不完全加載全部視頻文件的情況下倔约,就開始播放視頻的呢秃殉?原理是,首先(請求range: bytes=0-)加載mp4頭部的一小部分(chunk)浸剩,解析出ftyp和moov钾军,用來定位一幀的位置,在發(fā)出對應(yīng)的range請求绢要,來實(shí)現(xiàn)定位播放吏恭,以及提前播放。是最終在內(nèi)存中下載開始播放位置的整個(gè)視頻重罪。這個(gè)視頻后端需要提供range請求服務(wù)樱哼。
  • 為了讓video支持流媒體播放哀九,需要使用MSE,MSE原理是開辟一塊內(nèi)存搅幅,提供存放流媒體的原始數(shù)據(jù)阅束,用以video標(biāo)簽?zāi)軌虿シ舊mp4視頻。
  • 為了讓video標(biāo)簽?zāi)軌虿シ疟镜豣lob數(shù)據(jù)茄唐,有兩種方法:
    • 使用data uri 格式的數(shù)據(jù)息裸,可以使用FileReader對象將blob數(shù)據(jù)轉(zhuǎn)成data uri。格式為:data:[<MIME type>][;charset=<charset>][;base64],<encoded data>
    • 使用URL對象琢融,指向blob對象界牡。格式為:blob:same origin/pointer
    • 使用data uri的話,編碼會轉(zhuǎn)成base64漾抬,會增大文件size宿亡,并且是直接打在頁面上,有損性能纳令,使用URL對象的話挽荠,解決如上問題
    • URL對象可以使用URL.createObjectURL(blob)生成。
    • URL對象可以指向硬盤或者內(nèi)存空間中的文件平绩,以URL的形式賦給video圈匆,audio或者img等標(biāo)簽,來使得瀏覽器獲取本地文件捏雌,減少http請求數(shù)量跃赚。

其他技術(shù)知識點(diǎn)

  • 傳統(tǒng)上,服務(wù)器通過 AJAX 操作只能返回文本數(shù)據(jù)性湿,即responseType屬性默認(rèn)為text纬傲。XMLHttpRequest第二版XHR2允許服務(wù)器返回二進(jìn)制數(shù)據(jù),這時(shí)分成兩種情況肤频。如果明確知道返回的二進(jìn)制數(shù)據(jù)類型叹括,可以把返回類型(responseType)設(shè)為arraybuffer;如果不知道宵荒,就設(shè)為blob汁雷。若果設(shè)置為arraybuffer,就可以直接獲得arraybuffer對象报咳。blob還需要通過FileReader轉(zhuǎn)成arraybuffer侠讯。
  • cors請求,當(dāng)設(shè)置了header頭時(shí)少孝,會觸發(fā)瀏覽器的預(yù)檢option請求继低。
  • 播放器全屏:
    • 由于video標(biāo)簽使用requestFullScreen,會漏出shadow dom。而自定義的控制欄被隱藏稍走。所以應(yīng)該使用video外層的節(jié)點(diǎn)進(jìn)行全屏袁翁。
    • 退出全屏需要特殊處理一下esc鍵,需要通過document.fullscreenEnabled || window.fullScreen || document.webkitIsFullScreen ||
    • document.msFullscreenEnabled檢測下當(dāng)前是否在全屏狀態(tài)下婿脸。否則在退出全屏并檢測esc時(shí)粱胜,瀏覽器會吧esc鍵給屏蔽掉。 理解有誤狐树,最新理解是退出全屏需要特殊處理一下esc鍵焙压,需要檢測下當(dāng)前是否在全屏狀態(tài)下。否則在退出全屏并檢測esc時(shí)抑钟,瀏覽器會吧esc鍵給屏蔽掉涯曲。
    • document.fullscreenElement: 當(dāng)前處于全屏狀態(tài)的元素 element。
    • document.fullscreenEnabled: 標(biāo)記 fullscreen 當(dāng)前是否可用在塔。
    • 當(dāng)進(jìn)入/退出 全屏模式時(shí),會觸發(fā) fullscreenchange 事件幻件。
      代碼如下:
const api = {
       requestFullScreen : () => {
            const el = this.model.BaseNode;
            const rfs = el.requestFullScreen || el.webkitRequestFullScreen || el.mozRequestFullScreen || el.msRequestFullscreen;      
            if(typeof rfs != "undefined" && rfs) {
                rfs.call(el);
                utils.evt.addEvent(window, 'resize', this.evtHandler.fullWindowOnEscKey);
            };
            return;
        },
        exitFullScreen : () => {
            if (document.exitFullscreen) {  
                document.exitFullscreen();  
            }  
            else if (document.mozCancelFullScreen) {  
                document.mozCancelFullScreen();  
            }  
            else if (document.webkitCancelFullScreen) {  
                document.webkitCancelFullScreen();  
            }  
            else if (document.msExitFullscreen) {  
                document.msExitFullscreen();  
            } 
            utils.evt.removeEvent(window, 'resize', this.evtHandler.fullWindowOnEscKey);
        },
        checkFull : () => {
            //let isFUll = document.fullscreenEnabled || window.fullScreen || document.webkitIsFullScreen || document.msFullscreenEnabled || false;
            let isFull = document.fullscreenElement  || document.webkitCurrentFullScreenElement || document.mozFullScreenElement || null;
            return isFUll;
        },
};
const evtHandler = {
        fullWindowOnEscKey : (evt) => {
            if (!this.tools.checkFull()) {
                utils.dom.removeClassName(this.model.BaseNode, 'krv-fullscreen');
                this.tools.exitFullScreen();
            }
        }
};

  • 瀏覽器兼容問題:

    • safari瀏覽器video標(biāo)簽,視頻資源地址必須添加明確的視頻擴(kuò)展名蛔溃,或者添加source標(biāo)簽绰沥,明確指定type。否則播放不了贺待。-
  • 視頻網(wǎng)站使用的流媒體技術(shù)方案

    • youtube DASH video/webm ajax get url后加range CORS請求分片
    • 騰訊視頻 HLS m3u8+ts CORS請求
    • 愛奇藝 RTMP f4v url后加range CORS請求分片
    • bilibili DASH m4s 在請求體內(nèi)加range 觸發(fā)options來請求分片
    • 優(yōu)酷 HLS m3u8+ts url后加參數(shù) CORS請求分片 但是切換分辨率有縫隙
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末徽曲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子麸塞,更是在濱河造成了極大的恐慌秃臣,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哪工,死亡現(xiàn)場離奇詭異奥此,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)正勒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門得院,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人章贞,你說我怎么就攤上這事祥绞。” “怎么了鸭限?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵蜕径,是天一觀的道長。 經(jīng)常有香客問我败京,道長兜喻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任赡麦,我火速辦了婚禮朴皆,結(jié)果婚禮上帕识,老公的妹妹穿的比我還像新娘。我一直安慰自己遂铡,他們只是感情好肮疗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著扒接,像睡著了一般伪货。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钾怔,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天碱呼,我揣著相機(jī)與錄音,去河邊找鬼宗侦。 笑死愚臀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的凝垛。 我是一名探鬼主播懊悯,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼梦皮!你這毒婦竟也來了炭分?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤剑肯,失蹤者是張志新(化名)和其女友劉穎捧毛,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體让网,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呀忧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了溃睹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片而账。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖因篇,靈堂內(nèi)的尸體忽然破棺而出泞辐,到底是詐尸還是另有隱情,我是刑警寧澤竞滓,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布咐吼,位于F島的核電站,受9級特大地震影響商佑,放射性物質(zhì)發(fā)生泄漏锯茄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一茶没、第九天 我趴在偏房一處隱蔽的房頂上張望肌幽。 院中可真熱鬧晚碾,春花似錦、人聲如沸牍颈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煮岁。三九已至,卻和暖如春涣易,著一層夾襖步出監(jiān)牢的瞬間画机,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工新症, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留步氏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓徒爹,卻偏偏與公主長得像荚醒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子隆嗅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容