まず最初に断っておきますが、私は JavaScript についてはあまり知識がありません。間違いがあるのは避けられません。
flv.js プロジェクトのコードにはある程度の規模があります。勉強したい場合は、demux から始めることをお勧めします。demux を理解すると、前のメディア データ処理の主要な手順を習得できるようになります。ダウンロードとその後のメディアデータの再生がわかりやすくなりました。
まず、HTML5 ビデオの再生で flv 形式が使用されるのはなぜでしょうか。
フラッシュのせいで。タイトル画像は「Flash RIP」を使用しています。Flash は消滅しつつありますが、その影響はまだ残っています。Flash 技術は、過去 10 年間、Flash を中心に構築されてきました。 . RTMP および flv over http プロトコルをサポートしました。 Web での Flash 再生と互換性を持たせるために、インターネット ライブ ブロードキャストを行う企業は常に flv メディア形式を選択します。 Flash から HTML5 への移行期間中に、HTML5 が Flash プロトコルをサポートできればスムーズな移行が可能になりますが、HTML5 は Flash プロトコルをネイティブにサポートしていません。 flv.js プロジェクトは、HTML5 が Flash プロトコルをサポートするという問題を解決します。これが、flv.js の出現と短期的な人気の歴史的背景です。
flv.js の demux は、FLV メディア データ形式用のパーサーのセットです。FLV 形式を理解したい場合は、次のドキュメントを注意深く読む必要があります。
Adobe の公式 flv 形式の説明
http://www.adobe.com/content/dam/Adobe/en/devnet/flv/pdfs/video_file_format_spec_v10.pdf
flv.js の使用方法? さて、本題の flv.js コード解釈: demux 部分に移りましょう
コードを開きます https://github.com/Bilibili/flv.js/blob/master/src/demux/flv-demuxer.js
static probe(buffer) { let data = new Uint8Array(buffer); let mismatch = {match: false}; if (data[0] !== 0x46 || data[1] !== 0x4C || data[2] !== 0x56 || data[3] !== 0x01) { return mismatch; }
0x46 0x4c 0x56 これらの数字は、実際には flv ファイルのヘッダーを表す ASCII コードです。次の 0x01 は、データが flv 形式であるかどうかを検出するために使用します。 。
りー5 バイト目を取り出し、その 6 ビットと 8 ビットはそれぞれオーディオ データとビデオ データがあるかどうかを示します。他のビットは予約ビットなので無視できます。
このプローブはparseChunksによって呼び出され、少なくとも13バイトを読み取った後、それがflvデータであるかどうかを判断し、その後の解析を続行します。なぜ 13 なのかというと、flv のファイルヘッダーが 13 バイトであるためです。この 13 バイトには、次の 4 バイトのサイズが含まれています。最初のタグは前のタグに存在しないため、最初のサイズは常に 0 になります。
parseChunks の背後にあるコードは、タグを常に解析しています。各タグには異なるタイプがあり、オーディオ、ビデオ、およびスクリプト データに対応する 8、9、および 18 のみです。 。
りーこのコードはタグのタイプを判断しています。タグ ヘッダーが 11 バイトで、その後にタグ本体が続くため、オフセットとこれらのオフセットを加算した値が次のタグ位置にジャンプするため、数字 11 に注意してください。
タグ ヘッダーの形式は次のとおりです。UI は unsigned int を表し、その後にビット数が続きます。
UI8タグタイプ
UI24データサイズ
UI24タイムスタンプ
UI8タイムスタンプ拡張
UI24ストリームID
正確に 11 バイトであることがわかりますか? トラフィックを節約するために、Adobe は 24 ビットで表現できる場合は 32 ビットを使用しませんが、それでも最上位バイトを保存するためにタイムスタンプに拡張ビットを設定します。この奇妙なコードは、最初に 3 バイトを取得し、ビッグエンディアンに従って整数に変換し、次に 4 番目のバイトを上位ビットに置きます。
りータグ ヘッダーを解析した後、さまざまなタグ タイプに応じてさまざまな解析関数が呼び出されます。
let hasAudio = ((data[4] & 4) >>> 2) !== 0; let hasVideo = (data[4] & 1) !== 0;
TAG type: 8 audio
AUDIODATA の最初のバイトは、基本的に ACC 16 ビット ステレオ 44.1kHz サンプリングであるため、最も一般的な番号は 0xAF で、次に AACAUDIODATA タイプ: 9 が続きます。
最も重要なのはビデオです、
if (tagType !== 8 && tagType !== 9 && tagType !== 18) { Log.w(this.TAG, `Unsupported tag type ${tagType}, skipped`); // consume the whole tag (skip it) offset += 11 + dataSize + 4; continue; }
let ts2 = v.getUint8(4); let ts1 = v.getUint8(5); let ts0 = v.getUint8(6); let ts3 = v.getUint8(7); let timestamp = ts0 | (ts1 << 8) | (ts2 << 16) | (ts3 << 24);
这里有个坑,参考adobe的文档,这是CTS是个有符号的24位整数,SI24,就是说它有可能是个负数,所以我怀疑flv.js解析cts的代码有bug,没有处理负数情况。因为负数的24位整型到32位负数转换的时候要手工处理高位的符号位和补码问题。(我只是怀疑,没有调试确认过,但是我在处理YY直播数据的时候是踩过这个坑的,个别包含 B frame的视频是会出现CTS为负数的情况的)
packetType有两种,0 表示 AVCDecoderConfigurationRecord,这个是H.264的视频信息头,包含了 sps 和 pps,AVCDecoderConfigurationRecord的格式不是flv定义的,而是264标准定义的,如果用ffmpeg去解码,这个结构可以直接放到 codec的extradata里送给ffmpeg去解释。
flv.js作者选择了自己来解析这个数据结构,也是迫不得已,因为JS环境下没有ffmpeg,解析这个结构主要是为了提取 sps和pps。虽然理论上sps允许有多个,但其实一般就一个。
let config = SPSParser.parseSPS(sps);
pps的信息没什么用,所以作者只实现了sps的分析器,说明作者下了很大功夫去学习264的标准,其中的Golomb解码还是挺复杂的,能解对不容易,我在PC和手机平台都是用ffmpeg去解析的。SPS里面包括了视频分辨率,帧率,profile level等视频重要信息。
packetTtype 为 1 表示 NALU,NALU= network abstract layer unit,这是H.264的概念,网络抽象层数据单元,其实简单理解就是一帧视频数据。
NALU的头有两种标准,一种是用 00 00 00 01四个字节开头这叫 start code,另一个叫mp4风格以Big-endian的四字节size开头,flv用了后一种,而我们在H.264的裸流里常见的是前一种。
TAG type : 18 Script Data
除了音视频数据外还有 ScriptData,这是一种类似二进制json的对象描述数据格式,JavaScript比较惨只能自己写实现,其它平台可以用 librtmp的代码去做。
我觉得作者处理解决flv播放问题外,也为前端贡献了 amf 解析,sps解析,Golomb解码等基础代码,这些是可以用在其他项目里的。
在用传输协议获取了flv数据流后,用demux分离出音视频数据的属性和数据包,这为后面的播放打下了基础,从demux入手去读代码是个不错的切入点,而且一定要配合 flv file format spec一起看,反复多看几遍争取熟记在心。我现在已经可以从wireshark的抓包数据里人肉分析flv数据包了,对于debug相当有帮助。
相关文章:
如何看待B站 (bilibili) 开源 HTML5 播放器内核 flv.js?
以上がflv.jsの使い方は? flv.js コードの包括的な解釈の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。