MP4フォーマットの研究
- mp4 解析オンライン アドレス:mp4parser
- mp4はコンテナです。
- 各 mp4 は複数のボックスで構成されており、各ボックスはサブボックスをネストすることができ、このボックスをコンテナ ボックスと呼びます。
- ボックス、各ボックスはヘッダーとデータで構成されます。
- ヘッダーには、ボックス全体の長さのサイズとタイプが含まれます。
size==0
の場合、これがファイル内の最後のボックスであることを意味します。size==1 の場合、ボックスの長さを記述するためにさらに多くのビットが必要であることを意味し、64 ビットのラージサイズは長さ; type が uuid の場合、Box 内のデータがユーザー定義の拡張型であることを意味します。
- データは Box の実際のデータであり、純粋なデータまたは複数のサブボックスにすることができます。
- MP4 は、従来の通常の MP4 と、ストリーミング メディアに適応したフラグメント化された Mp4 (fMp4) に分かれています。
- 通常の mp4 は主に、ftyp、moov/mdat、mdat/moov などのボックスで構成されています。
- ファイルの先頭にある ftypbox は、ファイルのバージョン、互換性のあるプロトコルなどを記述します。
- moovbox、このボックスには特定のメディア データは含まれませんが、このファイル内のすべてのメディア データのマクロ記述情報が含まれます。moov ボックスの下には、mvhd ボックスと trak ボックスがあります。
- mvhd は、作成時間、変更時間、時間測定スケール、再生可能時間などの情報を記録します。
- trak 内の一連のサブボックスは、各メディア トラックの特定の情報を記述します。
- fMp4 フォーマットには一連のセグメント (moof+mdat の組み合わせ) が含まれており、これらのセグメントは (バイト範囲要求を使用して) 独立して要求できるため、異なる品質レベルのコード ストリーム間でのコード レートの切り替えに役立ちます。
- 通常の mp4 では、2 つのコード ストリーム間でコード レートを切り替えたい場合、2 つのコード ストリーム内の対応する時点のバイト位置を見つける必要がありますが、現時点では巨大な mdat ボックスしかありません。特定のバイト位置を見つけることは間違いなく複雑です。さらに、通常の mp4 では、moov が巨大な mdat ボックスの背後にある場合があり、これも起動速度に影響します。
- moofbox (このボックスは fmp4 に存在します)、このボックスはビデオ フラグメントの説明情報です。これは MP4 ファイルの必須の部分ではありませんが、オンラインで再生できる一般的な MP4 形式ファイルの中で確かに最優先事項です。
- mdatbox、実際のメディア データ。最終的にデコードして再生するデータはすべてその中にあります。
- その他のボックス
- フリースペースボックス(フリーまたはスキップ) 「フリー」の内容は無関係なので無視して構いません。ボックスが削除された後は、再生には影響しません。
- sidx ボックスはセグメント インデックス ボックスであり、fmp4 のセグメント インデックス ボックスです。 DASH を開発するときは、ここでボックスを解析し、対応するビデオ データを見つける必要があります。
- SIDX 解析ルール:
- ボックスのヘッダー部分のサイズは 4B、タイプは 4B (Unicode 値。文字列にトランスコードする必要があります) です。
- ボックスのデータ部分で、DASH に関して最も役立つのは、referenced_size (単位: バイト) と subsegment_duration (単位: ミリ秒) です。数字は fmp4 のフラグメントの数であり、その数字は次のとおりです。
type|size|duration * reference_count
- sidx コンテンツのルールは次のとおりです。
シドックス |
名前 |
サイズ |
ヘッダ |
サイズ |
Uint32 |
ヘッダ |
タイプ |
Int32 |
ヘッダ |
larsesize(サイズ==1の場合) |
Uint64 |
データ |
バージョン |
Uint8 |
データ |
なし |
Uint24 |
データ |
参照ID |
Uint32 |
データ |
タイムスケール |
Uint32 |
データ |
最も早いプレゼンテーション時間 |
Uint32 |
データ |
first_offset |
Uint32 |
データ |
予約済み |
Win16 |
データ |
参照数 |
Win16 |
データ |
type|size|duration * reference_count |
Uint32 |
データ |
SAP * reference_count |
Uint32 |
- 解析コードは次のとおりです。
const mp4 = {
parseSidx (dataView, offset) {
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;
let type = dataView.getInt32(offset); offset += 4;
msg.type = trim1(hex2a(type.toString(16)));
msg.version = dataView.getUint8(offset); offset += 4;
msg.reference_ID = dataView.getUint32(offset);offset += 4;
msg.timescale = dataView.getUint32(offset);offset += 4;
msg.earliest_presentation_time = dataView.getUint32(offset); offset += 4;
msg.first_offset = dataView.getUint32(offset); offset += 4;
msg.reserved = dataView.getUint16(offset) ;offset += 2;
msg.reference_count = dataView.getUint16(offset) ;offset += 2;
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};
動画再生原理
- ネイティブ ビデオ タグはストリーミング メディアの再生をサポートしていません。では、すべてのビデオ ファイルを完全にロードせずにビデオの再生を開始するにはどうすればよいでしょうか?原則として、まず (リクエスト
range: bytes=0-
) で mp4 ヘッダーの一部 (chunk
) を読み込み、ftyp と moov を解析し、それを使用して位置を特定します。対応する範囲リクエストを送信して、位置決め再生と先行再生を実現します。最終的には、再生開始時点のビデオ全体がメモリにダウンロードされます。このビデオ バックエンドは範囲リクエスト サービスを提供する必要があります。
- ビデオがストリーミング メディアの再生をサポートするには、MSE を使用する必要があります。MSE の原理は、メモリを開き、ストリーミング メディアを保存するための元のデータを提供することです。video タグを使用して fmp4 ビデオを再生できます。
- video タグがローカル BLOB データを再生できるようにするには、次の 2 つの方法があります。
- データ URI 形式のデータを使用すると、FileReader オブジェクトを使用して BLOB データをデータ URI に変換できます。形式は次のとおりです。
data:[<MIME type>][;charset=<charset>][;base64],<encoded data>
- BLOB オブジェクトを指す URL オブジェクトを使用します。形式は次のとおりです。
blob:same origin/pointer
- データ URI を使用するとエンコードが Base64 に変換されるためファイル サイズが大きくなり、ページに直接入力することになりパフォーマンスが低下しますが、URL オブジェクトを使用すると上記の問題を解決できます。 。
- URL オブジェクトは、URL.createObjectURL(blob) を使用して生成できます。
- URL オブジェクトは、ハードディスクまたはメモリ空間内のファイルを指すことができ、ブラウザがローカル ファイルを取得して http リクエストの数を減らすことができるように、URL の形式でビデオ、オーディオ、イメージなどのタグに割り当てられます。 。
その他の技術的な知識のポイント
- 従来、サーバーは AJAX 操作を通じてテキスト データのみを返すことができました。つまり、responseType 属性のデフォルトはテキストでした。 XMLHttpRequest の 2 番目のバージョンである XHR2 では、サーバーがバイナリ データを返すことができます。これは 2 つの状況に分けられます。返されるバイナリ データ型が明確にわかっている場合は、戻り値の型 (responseType) を
arraybuffer
に設定できます。不明な場合は、blob
に設定します。 arraybuffer に設定すると、arraybuffer オブジェクトを直接取得できます。 BLOB は、FileReader を通じて配列バッファーに変換する必要もあります。
- cors リクエストでは、ヘッダー ヘッダーが設定されると、ブラウザーのプリフライト オプション リクエストがトリガーされます。
- プレーヤーの全画面表示:
- videoタグはrequestFullScreenを使っているのでシャドウダムが漏れてしまいます。カスタム コントロール バーは非表示になります。したがって、ビデオの外側のノードは全画面で使用する必要があります。
全画面表示を終了するには、esc キーを特別に処理する必要があります。document.fullscreenEnabled || window.fullScreen || document.webkitIsFullScreen || document.msFullscreenEnabled
を使用して、現在全画面表示状態かどうかを検出する必要があります。そうしないと、全画面表示を終了して Esc キーが検出されると、ブラウザは Esc キーをブロックします。 私の理解は間違っており、最新の理解では、フルスクリーンを終了するにはescキーの特別な処理が必要で、現在フルスクリーン状態であるかどうかを検出する必要があると考えられています。そうしないと、全画面表示を終了して Esc キーが検出されると、ブラウザは Esc キーをブロックします。
- document.fullscreenElement: 現在全画面状態にある要素要素。
- document.fullscreenEnabled: フルスクリーンが現在利用可能かどうかをマークします。
- 全画面モードを開始または終了すると、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.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 ブラウザのビデオ タグ、ビデオ リソース アドレスには、明確なビデオ拡張子を追加するか、ソース タグを追加して、タイプを明確に指定する必要があります。そうしないと再生できません。
- 動画 Web サイトで使用されるストリーミング メディア テクノロジ ソリューション
- YouTube DASHビデオ/WebM Ajax URLの後に範囲CORSリクエストの断片化が続く取得
- Tencent Video HLS m3u8+ts CORS リクエスト
- iQiyi RTMP f4v URL の後に範囲 CORS リクエストの断片化が続く
- bilibili DASH m4s は、シャーディングをリクエストするオプションをトリガーするためにリクエスト本文に範囲を追加します
- Youku HLS m3u8+ts URL は CORS リクエストの断片化後にパラメータを追加しますが、解像度を切り替えるときにギャップが発生します