wfs.js之h264轉碼mp4分析

準備源文件

下載源文件

git clone https://github.com/ChihChengYang/wfs.js.git

編譯后得到wfs.js這個文件

調用

在demo/index.html中,前端對wfs.js進行了調用

var video1 = document.getElementById("video1"),
wfs = new Wfs();    
wfs.attachMedia(video1,'ch1');

對應wfs.js中的function Wfs() 函數

key: 'attachMedia',value: function attachMedia(media) {//arg1 通道var channelName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'chX';//arg2 媒體格式var mediaType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'H264Raw';//arg3 未定義var websocketName = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'play2';// 'H264Raw' 'FMp4'    this.mediaType = mediaType;this.media = media;//MEDIA_ATTACHING 事件處理this.trigger(_events2.default.MEDIA_ATTACHING, { media: media, channelName: channelName, mediaType: mediaType, websocketName: websocketName });

MEDIA_ATTACHING 事件的處理

MEDIA_ATTACHING: 'wfsMediaAttaching',

在var BufferController = function (_EventHandler)中

    key: 'onMediaAttaching',value: function onMediaAttaching(data) {var media = this.media = data.media;this.mediaType = data.mediaType;this.websocketName = data.websocketName;this.channelName = data.channelName;if (media) {// setup the media sourcevar ms = this.mediaSource = new MediaSource();//Media Source listeners//綁定ms的監聽事件//打開時候onMediaSourceOpenthis.onmso = this.onMediaSourceOpen.bind(this);this.onmse = this.onMediaSourceEnded.bind(this);this.onmsc = this.onMediaSourceClose.bind(this);ms.addEventListener('sourceopen', this.onmso);ms.addEventListener('sourceended', this.onmse);ms.addEventListener('sourceclose', this.onmsc);// link video and media Source//連接video和剛剛創建的ms media Sourcemedia.src = URL.createObjectURL(ms);}}

ms的onMediaSourceOpen

在ms被打開的時候。調用 onMediaSourceOpen

key: 'onMediaSourceOpen',value: function onMediaSourceOpen() {var mediaSource = this.mediaSource;if (mediaSource) {// once received, don't listen anymore to sourceopen eventmediaSource.removeEventListener('sourceopen', this.onmso);}if (this.mediaType === 'FMp4') {this.checkPendingTracks();}//調用MEDIA_ATTACHED事件處理this.wfs.trigger(_events2.default.MEDIA_ATTACHED, { media: this.media, channelName: this.channelName, mediaType: this.mediaType, websocketName: this.websocketName });

MEDIA_ATTACHED事件處理

MEDIA_ATTACHED: 'wfsMediaAttached',
    key: 'onMediaAttached',value: function onMediaAttached(data) {if (data.websocketName != undefined) {//創建WebSocket連接var client = new WebSocket('ws://' + window.location.host + '/' + data.websocketName);//調用attachWebsocket方法this.wfs.attachWebsocket(client, data.channelName);} else {console.log('websocketName ERROE!!!');}}
    key: 'attachWebsocket',value: function attachWebsocket(websocket, channelName) {//調用WEBSOCKET_ATTACHING事件處理this.trigger(_events2.default.WEBSOCKET_ATTACHING, { websocket: websocket, mediaType: this.mediaType, channelName: channelName });}

WEBSOCKET_ATTACHING事件處理

WEBSOCKET_ATTACHING: 'wfsWebsocketAttaching',
    key: 'onWebsocketAttaching',value: function onWebsocketAttaching(data) {this.mediaType = data.mediaType;this.channelName = data.channelName;if (data.websocket instanceof WebSocket) {this.client = data.websocket;//websocket 的 onopen事件綁定綁定initSocketClientthis.client.onopen = this.initSocketClient.bind(this);this.client.onclose = function (e) {console.log('Websocket Disconnected!');};}}
    key: 'initSocketClient',value: function initSocketClient(client) {this.client.binaryType = 'arraybuffer';//websocket 的 onmessage 事件綁定綁定receiveSocketMessagethis.client.onmessage = this.receiveSocketMessage.bind(this);//WEBSOCKET_MESSAGE_SENDING事件處理this.wfs.trigger(_events2.default.WEBSOCKET_MESSAGE_SENDING, { commandType: "open", channelName: this.channelName, commandValue: "NA" });console.log('Websocket Open!');}

//WEBSOCKET_MESSAGE_SENDING事件處理

WEBSOCKET_MESSAGE_SENDING: 'wfsWebsocketMessageSending'
    key: 'onWebsocketMessageSending',value: function onWebsocketMessageSending(event) {this.client.send(JSON.stringify({ t: event.commandType, c: event.channelName, v: event.commandValue }));}

這樣把信息通過json發送出去。

接收信息的處理

上面websocket 的 onmessage 事件綁定綁定receiveSocketMessage, 當websocket接收到信息時,調用receiveSocketMessage

    key: 'receiveSocketMessage',value: function receiveSocketMessage(event) {if (document['hidden']) return;this.buf = new Uint8Array(event.data);var copy = new Uint8Array(this.buf);if (this.mediaType === 'FMp4') {this.wfs.trigger(_events2.default.WEBSOCKET_ATTACHED, { payload: copy });}if (this.mediaType === 'H264Raw') {//H264_DATA_PARSING事件處理this.wfs.trigger(_events2.default.H264_DATA_PARSING, { data: copy });}}

H264_DATA_PARSING事件處理

H264_DATA_PARSING: 'wfsH264DataParsing',
        key: 'onH264DataParsing',value: function onH264DataParsing(event) {//讀取數據this._read(event.data);var $this = this;//對于每個nalthis.nals.forEach(function (nal) {//H264_DATA_PARSED事件處理$this.wfs.trigger(_events2.default.H264_DATA_PARSED, {data: nal});});}

H264_DATA_PARSED事件處理

H264_DATA_PARSED: 'wfsH264DataParsed',
    key: 'onH264DataParsed',value: function onH264DataParsed(event) {//分析AVCTrackthis._parseAVCTrack(event.data);if (this.browserType === 1 || this._avcTrack.samples.length >= 20) {// Firefoxthis.remuxer.pushVideo(0, this.sn, this._avcTrack, this.timeOffset, this.contiguous);this.sn += 1;}}

數據經過_parseAVCTrack處理后,調用remuxer.pushVideo方法

    key: 'pushVideo',value: function pushVideo(level, sn, videoTrack, timeOffset, contiguous) {this.level = level;this.sn = sn;var videoData = void 0;// generate Init Segment if neededif (!this.ISGenerated) {this.generateVideoIS(videoTrack, timeOffset);}if (this.ISGenerated) {if (videoTrack.samples.length) {this.remuxVideo_2(videoTrack, timeOffset, contiguous);}}}

最后調用remuxVideo_2方法

    key: 'remuxVideo_2',value: function remuxVideo_2(track, timeOffset, contiguous, audioTrackLength) {var offset = 8,pesTimeScale = this.PES_TIMESCALE,pes2mp4ScaleFactor = this.PES2MP4SCALEFACTOR,mp4SampleDuration,mdat,moof,firstPTS,firstDTS,nextDTS,inputSamples = track.samples,outputSamples = [];/* concatenate the video data and construct the mdat in place(need 8 more bytes to fill length and mpdat type) */mdat = new Uint8Array(track.len + 4 * track.nbNalu + 8);var view = new DataView(mdat.buffer);view.setUint32(0, mdat.byteLength);mdat.set(_mp4Generator2.default.types.mdat, 4);var sampleDuration = 0;var ptsnorm = void 0,dtsnorm = void 0,mp4Sample = void 0,lastDTS = void 0;for (var i = 0; i < inputSamples.length; i++) {var avcSample = inputSamples[i],mp4SampleLength = 0,compositionTimeOffset = void 0;// convert NALU bitstream to MP4 format (prepend NALU with size field)while (avcSample.units.units.length) {var unit = avcSample.units.units.shift();view.setUint32(offset, unit.data.byteLength);offset += 4;mdat.set(unit.data, offset);offset += unit.data.byteLength;mp4SampleLength += 4 + unit.data.byteLength;}var pts = avcSample.pts - this._initPTS;var dts = avcSample.dts - this._initDTS;dts = Math.min(pts, dts);if (lastDTS !== undefined) {ptsnorm = this._PTSNormalize(pts, lastDTS);dtsnorm = this._PTSNormalize(dts, lastDTS);sampleDuration = dtsnorm - lastDTS;if (sampleDuration <= 0) {_logger.logger.log('invalid sample duration at PTS/DTS: ' + avcSample.pts + '/' + avcSample.dts + '|dts norm: ' + dtsnorm + '|lastDTS: ' + lastDTS + ':' + sampleDuration);sampleDuration = 1;}} else {var nextAvcDts = this.nextAvcDts,delta;ptsnorm = this._PTSNormalize(pts, nextAvcDts);dtsnorm = this._PTSNormalize(dts, nextAvcDts);if (nextAvcDts) {delta = Math.round(dtsnorm - nextAvcDts);if ( /*contiguous ||*/Math.abs(delta) < 600) {if (delta) {if (delta > 1) {_logger.logger.log('AVC:' + delta + ' ms hole between fragments detected,filling it');} else if (delta < -1) {_logger.logger.log('AVC:' + -delta + ' ms overlapping between fragments detected');}dtsnorm = nextAvcDts;ptsnorm = Math.max(ptsnorm - delta, dtsnorm);_logger.logger.log('Video/PTS/DTS adjusted: ' + ptsnorm + '/' + dtsnorm + ',delta:' + delta);}}}this.firstPTS = Math.max(0, ptsnorm);this.firstDTS = Math.max(0, dtsnorm);sampleDuration = 0.03;}outputSamples.push({size: mp4SampleLength,duration: this.H264_TIMEBASE,cts: 0,flags: {isLeading: 0,isDependedOn: 0,hasRedundancy: 0,degradPrio: 0,dependsOn: avcSample.key ? 2 : 1,isNonSync: avcSample.key ? 0 : 1}});lastDTS = dtsnorm;}var lastSampleDuration = 0;if (outputSamples.length >= 2) {lastSampleDuration = outputSamples[outputSamples.length - 2].duration;outputSamples[0].duration = lastSampleDuration;}this.nextAvcDts = dtsnorm + lastSampleDuration;var dropped = track.dropped;track.len = 0;track.nbNalu = 0;track.dropped = 0;if (outputSamples.length && navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {var flags = outputSamples[0].flags;flags.dependsOn = 2;flags.isNonSync = 0;}track.samples = outputSamples;moof = _mp4Generator2.default.moof(track.sequenceNumber++, dtsnorm, track);track.samples = [];var data = {id: this.id,level: this.level,sn: this.sn,data1: moof,data2: mdat,startPTS: ptsnorm,endPTS: ptsnorm,startDTS: dtsnorm,endDTS: dtsnorm,type: 'video',nb: outputSamples.length,dropped: dropped};this.observer.trigger(_events2.default.FRAG_PARSING_DATA, data);return data;}

在處理完數據后,進行FRAG_PARSING_DATA事件處理

  FRAG_PARSING_DATA: 'wfsFragParsingData',
    key: 'onFragParsingData',value: function onFragParsingData(data) {var _this2 = this;if (data.type === 'video') {}[data.data1, data.data2].forEach(function (buffer) {if (buffer) {_this2.pendingAppending++;_this2.wfs.trigger(_events2.default.BUFFER_APPENDING, { type: data.type, data: buffer, parent: 'main' });}});}

BUFFER_APPENDING事件處理

BUFFER_APPENDING: 'wfsBufferAppending',
    key: 'onBufferAppending',value: function onBufferAppending(data) {if (!this.segments) {this.segments = [data];} else {this.segments.push(data);}this.doAppending();}
    key: 'doAppending',value: function doAppending() {var wfs = this.wfs,sourceBuffer = this.sourceBuffer,segments = this.segments;if (Object.keys(sourceBuffer).length) {if (this.media.error) {this.segments = [];console.log('trying to append although a media error occured, flush segment and abort');return;}if (this.appending) {return;}if (segments && segments.length) {var segment = segments.shift();try {if (sourceBuffer[segment.type]) {this.parent = segment.parent;sourceBuffer[segment.type].appendBuffer(segment.data);this.appendError = 0;this.appended++;this.appending = true;} else {}} catch (err) {// in case any error occured while appending, put back segment in segments table segments.unshift(segment);var event = { type: ErrorTypes.MEDIA_ERROR };if (err.code !== 22) {if (this.appendError) {this.appendError++;} else {this.appendError = 1;}event.details = ErrorDetails.BUFFER_APPEND_ERROR;event.frag = this.fragCurrent;if (this.appendError > wfs.config.appendErrorMaxRetry) {segments = [];event.fatal = true;return;} else {event.fatal = false;}} else {this.segments = [];event.details = ErrorDetails.BUFFER_FULL_ERROR;return;}}}}}

通過sourceBuffer[segment.type].appendBuffer(segment.data);把數據發送到video去。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/899592.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/899592.shtml
英文地址,請注明出處:http://en.pswp.cn/news/899592.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

協程 Coroutine

協程是 C20 引入的新特性。 文章目錄 基本概念std::coroutine_handlepromise 類型co_yield 基本用法 優勢異步 TCPco_await 基本概念 協程&#xff08;Coroutine&#xff09;是一種比線程更加輕量級的并發編程模型。協程的調度由程序員手動控制。 異步不是并行&#xff0c;但…

uniapp中的流式輸出

一、完整代碼展示 目前大多數的ai對話都是流式輸出&#xff0c;也就是對話是一個字或者多個字逐一進行顯示的下面是一個完整的流式顯示程序&#xff0c;包含的用戶的消息發出和ai的消息回復 <template><view class"chat-container"><view class&quo…

洛谷題單1-P5703 【深基2.例5】蘋果采購-python-流程圖重構

題目描述 現在需要采購一些蘋果&#xff0c;每名同學都可以分到固定數量的蘋果&#xff0c;并且已經知道了同學的數量&#xff0c;請問需要采購多少個蘋果&#xff1f; 輸入格式 輸入兩個不超過 1 0 9 10^9 109 正整數&#xff0c;分別表示每人分到的數量和同學的人數。 輸…

JS 手撕題高頻考點

前端面試中&#xff0c;JS 手撕題是高頻考點&#xff0c;主要考察 編程能力、算法思維、JS 核心知識。以下是最常見的手撕題分類 代碼示例&#xff1a; 目錄 &#x1f4cc; 1. 手寫函數柯里化&#x1f4cc; 2. 手寫 debounce&#xff08;防抖&#xff09;&#x1f4cc; 3. 手寫…

【STM32】知識點介紹一:硬件知識

文章目錄 一、電源引腳簡介二、電平信號三、電路分析 一、電源引腳簡介 VCC、GND、VDD和VSS是電子電路中常見的術語&#xff0c;代表著不同的電源引腳或電壓。 VCC&#xff08;Voltage at the Common Collector&#xff09;&#xff1a;VCC是指集電極&#xff08;Collector&am…

3. 列表元素替換

【問題描述】給定一個列表&#xff0c;將列表中所有的偶數替換為0 【輸入形式】輸入一行&#xff0c;包含若干個整數&#xff0c;用空格分隔 【輸出形式】輸出替換后的列表&#xff0c;每個元素用空格分隔 【樣例輸入】1 2 3 4 5 6 7 8 9 10 【樣例輸出】1 0 3 0 5 0 7 0 9…

問題的根源還是解題的方案

周末的早上照例是要早醒 debug 代碼的&#xff0c;仿佛又回到了 2014 年… 古人幾天甚至幾個月不洗澡&#xff0c;不會臭嗎&#xff1f;有沒有可能古人沒有化纖類衣服&#xff0c;且古人的純天然生活環境其身體菌群和現代人不同&#xff0c;古人就像健康的野生動物一樣即使不洗…

虛擬機安裝linux系統無法上網的解決方法

在虛擬環境中運行Linux系統時&#xff0c;有時會遇到網絡連接問題&#xff0c;特別是在使用虛擬機軟件如VMware或VirtualBox時。本文將詳細介紹一種針對“虛擬機安裝Linux系統無法上網”問題的解決方案&#xff0c;以CentOS 6.5為例&#xff0c;適用于其他基于NAT模式的虛擬機環…

子網劃分淺度解析

文章目錄 ip地址的組成不同類型ip地址的范圍子網掩碼默認子網掩碼子網掩碼如何作用的&#xff1f;默認子網掩碼怎么作用&#xff1f; ip地址的組成 ip地址一般寫作4位點分十進制&#xff08;x.x.x.x&#xff09;&#xff0c;他們由32位二進制組成&#xff0c;每個x由8位二進制…

什么是 SEO(搜索引擎優化)?

您有網站嗎&#xff0c;或者您正在考慮創建一個網站&#xff1f;您想吸引更多人加入您的業務嗎&#xff1f;如果答案是肯定的&#xff0c;那么毫無疑問&#xff1a;SEO 應該是您營銷工作的一部分。這是建立品牌和吸引用戶訪問您的網站的好方法。但它實際上意味著什么呢&#xf…

鴻蒙HarmonyOS NEXT設備升級應用數據遷移流程

數據遷移是什么 什么是數據遷移&#xff0c;對用戶來講就是本地數據的遷移&#xff0c;終端設備從HarmonyOS 3.1 Release API 9及之前版本&#xff08;單框架&#xff09;遷移到HarmonyOS NEXT&#xff08;雙框架&#xff09;后保證本地數據不丟失。例如&#xff0c;我在某APP…

【現代深度學習技術】現代卷積神經網絡04:含并行連接的網絡(GoogLeNet)

【作者主頁】Francek Chen 【專欄介紹】 ? ? ?PyTorch深度學習 ? ? ? 深度學習 (DL, Deep Learning) 特指基于深層神經網絡模型和方法的機器學習。它是在統計機器學習、人工神經網絡等算法模型基礎上&#xff0c;結合當代大數據和大算力的發展而發展出來的。深度學習最重…

【ESP32】ESP32與MQTT通信:實現傳感器數據監測與設備控制

ESP32與MQTT通信 1 項目概覽2 硬件組成3 MQTT協議解析MQTT協議簡介MQTT核心概念本項目中的MQTT應用 4 MQTT Broker選擇EMQX Broker其他常用MQTT Broker 5 代碼解析初始化與配置MQTT消息處理發布傳感器數據 6 MQTT話題TOPIC設計7 EMQX的優勢在IoT項目中的體現8 MQTT通信流程9 應…

[特殊字符]《Curve DAO 系統學習目錄》

本教程旨在系統學習 Curve DAO 項目的整體架構、核心機制、合約設計、治理邏輯與代幣經濟等內容&#xff0c;幫助開發者全面理解其設計理念及運作方式。 目錄總覽&#xff1a; 1. Curve 項目概覽 ? 1.1 Curve 是什么&#xff1f;主要解決什么問題&#xff1f; ? 1.2 與其他…

每天一篇目標檢測文獻(六)——Part One

今天看的是《Object Detection with Deep Learning: A Review》 目錄 一、摘要 1.1 原文 1.2 翻譯 二、介紹 2.1 信息區域選擇 2.2 特征提取 2.3 分類 三、深度學習的簡要回顧 3.1 歷史、誕生、衰落和繁榮 3.2 CNN架構和優勢 一、摘要 1.1 原文 Due to object dete…

Arthas線上問題診斷器

Arthas是Alibaba開源的java診斷工具 解決問題 這個類從哪個jar 包加載的&#xff1f;為什么會報各種相關的Exception&#xff1f; 遇到問題無法在線上debug&#xff0c;不能直通過加載日志再重新發布 有什么辦法可以監控到JVM的實時運行狀態&#xff1f; …

[Lc5_dfs+floodfill] 簡介 | 圖像渲染 | 島嶼數量

目錄 0.floodfill算法簡介 1.圖像渲染 題解 2.島嶼數量 題解 之前我們在 bfs 中有介紹過[Lc15_bfsfloodfill] 圖像渲染 | 島嶼數量 | 島嶼的最大面積 | 被圍繞的區域&#xff0c;現在我們來看看 dfs 又是如何解決的呢 0.floodfill算法簡介 floodfill算法又叫洪水灌溉或者…

JVM類加載器詳解

文章目錄 1.類與類加載器2.類加載器加載規則3.JVM 中內置的三個重要類加載器為什么 獲取到 ClassLoader 為null就是 BootstrapClassLoader 加載的呢&#xff1f; 4.自定義類加載器什么時候需要自定義類加載器代碼示例 5.雙親委派模式類與類加載器雙親委派模型雙親委派模型的執行…

Chapters 15 16:What Is Architecture?Independence_《clean architecture》notes

What Is Architecture?&Independence **Chapter 15: What Is Architecture?****Key Concepts**:**Code Example: Layered Architecture**: **Chapter 16: Independence****Key Concepts**:**Code Example: Dependency Inversion & Interfaces**: **Combined Example:…

【SPP】RFCOMM 層在SPP中互操作性要求深度解析

藍牙串口協議&#xff08;SPP&#xff09;通過 RFCOMM 協議實現 RS232 串口仿真&#xff0c;其互操作性是設備互聯的關鍵。本文基于藍牙核心規范&#xff0c;深度解析 RFCOMM 層的能力矩陣、信號處理、流控機制及實戰開發&#xff0c;結合狀態機、流程圖和代碼示例&#xff0c;…