Zunächst möchte ich sagen, dass ich nicht viel über JavaScript weiß. Ich kenne mich nur mit der Audio- und Videoverarbeitung aus. Fehler sind bei mir unvermeidlich.
flv.jsDer Code des Projekts hat einen bestimmten Umfang. Wenn Sie ihn studieren möchten, empfehle ich Ihnen, mit Demux zu beginnen. Sie werden die wichtigsten Schritte beherrschen der Mediendatenverarbeitung wird das vorherige Herunterladen der Mediendaten und die anschließende Wiedergabe der Mediendaten leicht verständlich.
Lassen Sie uns zunächst etwas Hintergrundwissen verbreiten. Warum wird bei der HTML5-Videowiedergabe das FLV-Format verwendet?
Wegen Flash. Mein Titelbild verwendet „Flash RIP“. Flash ist im Sterben, aber sein Einfluss ist in den letzten 10 Jahren immer noch die Basistechnologie für Internetvideos. Eine große Anzahl verwandter Infrastrukturen wurde um Flash herum aufgebaut . Unterstützte RTMP- und FLV-über-http-Protokolle. Um mit der Flash-Wiedergabe im Web kompatibel zu sein, wählen Unternehmen, die Live-Übertragungen im Internet durchführen, ausnahmslos das FLV-Medienformat. Während der Übergangszeit von Flash zu HTML5 wäre es großartig, wenn HTML5 das Flash-Protokoll unterstützen könnte, was einen reibungslosen Übergang ermöglichen würde. HTML5 unterstützt das Flash-Protokoll jedoch nicht nativ. Das flv.js-Projekt löst das Problem der HTML5-Unterstützung des Flash-Protokolls. Dies ist der historische Hintergrund für die Entstehung und kurzfristige Popularität von flv.js.
Der Demux in flv.js ist eine Reihe von Parsern für das FLV-Mediendatenformat. Wenn Sie das FLV-Format verstehen möchten, müssen Sie die folgenden Dokumente sorgfältig lesen.
Offizielle Beschreibung des FLV-Formats von Adobe
http://www.adobe.com/content/dam/Adobe/en/devnet/flv/pdfs/video_file_format_spec_v10.pdf
flv Wie js verwenden? Kommen wir nun zum Punkt: Interpretation des flv.js-Codes: Demux-Teil
Öffnen Sie den Code 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 Diese Zahlen sind eigentlich die ASCII-Codes von „F“, „L“, „V“, die den FLV-Datei-Header darstellen. Das folgende 0x01 ist die Versionsnummer des FLV-Formats. Verwenden Sie diese Nummer, um zu erkennen, ob die Daten im FLV-Format vorliegen .
let hasAudio = ((data[4] & 4) >>> 2) !== 0; let hasVideo = (data[4] & 1) !== 0;
Nehmen Sie das fünfte Byte heraus und geben Sie an, ob Audio- bzw. Videodaten vorhanden sind. Die anderen Bits sind reservierte Bits und können ignoriert werden.
Diese Sonde wird von parseChunks aufgerufen. Nach dem Lesen von mindestens 13 Bytes wird beurteilt, ob es sich um FLV-Daten handelt, und dann wird die anschließende Analyse fortgesetzt. Warum ist es 13? Weil der Datei-Header 13 Bytes groß ist. Diese 13 Bytes entsprechen der Größe des vorherigen Tags Da das erste Tag im vorherigen nicht existiert, ist die erste Größe immer 0.
Der Code hinter parseChunks analysiert ständig Tags. Jedes Tag hat einen anderen Typ: 8, 9 und 18, die Audio-, Video- und Skriptdaten entsprechen .
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; }
Dieser Code beurteilt den Tag-Typ. Achten Sie auf die Zahl 11, da der Tag-Header 11 Bytes lang ist, gefolgt vom Tag-Körper, sodass der Offset plus diese Offsets zur nächsten Tag-Position springen soll.
Das Format des Tag-Headers ist: UI steht für unsigned int, gefolgt von der Anzahl der Bits.
UI8-Tag-Typ
UI24-Datengröße
UI24-Zeitstempel
UI8 TimestampExtended
UI24 StreamID
Sehen Sie, ob es genau 11 Byte sind? Um Datenverkehr zu sparen, wird Adobe niemals 32 Bit verwenden, wenn es in 24 Bit ausgedrückt werden kann, aber es setzt immer noch ein Erweiterungsbit für den Zeitstempel, um das höchste Byte zu speichern. was zu Folgendem führt: Dieser seltsame Code nimmt zunächst drei Bytes, wandelt sie gemäß Big-Endian in Ganzzahlen um und fügt dann das vierte Byte in die oberen Bits ein.
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);
Nach dem Parsen des Tag-Headers werden je nach Tag-Typ unterschiedliche Parsing-Funktionen aufgerufen.
switch (tagType) { case 8: // Audio this._parseAudioData(chunk, dataOffset, dataSize, timestamp); break; case 9: // Video this._parseVideoData(chunk, dataOffset, dataSize, timestamp, byteStart + offset); break; case 18: // ScriptDataObject this._parseScriptData(chunk, dataOffset, dataSize); break; }
TAG-Typ: 8 Audio
Die Audiostruktur ist relativ einfach. Das erste Byte von AUDIODATA stellt das Audioformat dar. Tatsächlich handelt es sich im Grunde um ACC 16bit Stereo 44,1 kHz, daher ist die häufigste Zahl 0xAF, gefolgt von AACAUDIODATA
TAG-Typ: 9 Video
Das Wichtigste, was Sie sich ansehen sollten, ist das Video,
let frameType = (spec & 240) >>> 4; let codecId = spec & 15;
Hier werden zwei wichtige Werte verwendet: FrameType gibt den Frame-Typ an, 1 ist ein Schlüsselframe und 2 ist ein Nicht-Key-Frame. Obwohl flv sechs Videoformate unterstützt, wird tatsächlich nur H.264 für Live-Übertragungen im Internet auf Abruf verwendet. Die CodecId ist also grundsätzlich 7. Der Autor verwendet hier Dezimalzahlen, bei denen es sich tatsächlich um bitweise Werte handelt. Die Verwendung von Hexadezimalzahlen ist besser zu verstehen.
_parseAVCVideoPacket wird zum Parsen der AVCVIDEOPACKET-Struktur verwendet, bei der es sich um das H.264-Videopaket
let packetType = v.getUint8(0); let cts = v.getUint32(0, !le) & 0x00FFFFFF;
handelt Erklären Sie das Konzept von CTS, CompositionTime. Dies entspricht dem DTS im Video, der tatsächlich ein Offset von PTS im Verhältnis zu DTS ist der Unterschied zwischen PTS und DTS.
这里有个坑,参考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?
Das obige ist der detaillierte Inhalt vonWie verwende ich flv.js? Umfassende Interpretation des flv.js-Codes. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!