この記事は
この記事は DENSO Advent Calendar 2025 の 12 日目の記事です。
動画配信の仕組みを調べていた際、「コンテナ? コーデック? GOP? HLS? .hevc?」と、聞き慣れない用語が次々に出てきます。最初はそれぞれがどうつながるのかイメージしづらく、配信のために何をしたら良いのかよく分かりませんでした。
この記事では、動画配信のためのシステム開発を志す人向けに、最低限押さえておくと良い基礎をできるだけコンパクトにまとめます。
動画を理解するには「コンテナ」と「コーデック」から始める
動画ファイルのデータ構造は、コンテナ(MP4 など)の中に複数のストリームが格納されている形になっています。
関係は次のようになります。
sample.mp4
├─ Video Stream → H.264 codec
├─ Audio Stream → AAC codec
└─ Subtitle Stream → mov_text
コンテナは "フォルダ" のような役割を持ち、動画、音声、字幕といった複数ストリームをまとめ、再生時に同期を取る役割を持ちます。
コーデックは、その中に入っている動画・音声ストリームを どのように圧縮・伸長するか を定義する方式です。
動画の中身を覗いてみる
mp4 などの動画がどんな構造をしているのかを確認したい場合、ffprobe が便利です。
ffprobe -v error -show_format -show_streams -print_format json sample.mp4
出力例:
{
"streams": [
{
"codec_type": "video",
"codec_name": "h264",
"width": 1920,
"height": 1080
},
{
"codec_type": "audio",
"codec_name": "aac",
"sample_rate": "48000"
}
],
"format": {
"duration": "10.04",
"bit_rate": "4150000"
}
}
これを見るだけで、動画がどんな映像・音声ストリームを持っているのかを把握できます。
動画の I / P / B フレームの差分構造
高品質な mp4 は大容量で、そのままでは配信に向きません。
そこで一般的に、HLS や MPEG-DASH のような セグメント分割方式 が使われます。
これを理解するうえで、まず動画そのものの圧縮構造を押さえておく必要があります。
動画は「30枚の JPEG が並んでいる」ような単純な構造ではありません。
実際には以下の 3 種類のフレームで構成されています。
- I-frame: キーフレーム(完全な画像)
- P-frame: 直前フレームとの差分
- B-frame: 前後フレームを参考にした差分
I → P → B → B → P → B → I ...
ここで重要なのは、
動画は I-frame がなければ途中からデコードできない
という点です。
そのため、YouTube などでシークを行うと、指定した時刻より前の最新の I-frame まで巻き戻ってから再生が始まります。これは GOP(Group of Pictures)の概念に基づいています。
HLS, MPEG-DASH とは
HLS(HTTP Live Streaming)と MPEG-DASH は、
動画を小さなチャンク(セグメント)に分割して配信する方式 です。
これにより、
- ネットワーク帯域が変動しても再生が止まりにくい
- 途中からの再生(シーク)がしやすい
- CDN キャッシュ効率が高い
- 画質の自動切り替え(ABR)ができる
といった利点があります。
どちらも基本的な構造は同じで、
「チャンク + マニフェスト」
という 2 段構えで動画が構成されます。
MPEG-DASH の構造
DASH では、MPD(XML のマニフェスト)が使われます。
プレーヤーはこの MPD を読み、どのチャンクをどの順番でロードすべきかを判断します。
構造イメージ:
manifest.mpd
├── video/1080p/
│ ├── init.m4s
│ ├── segment_1.m4s
│ └── segment_2.m4s
└── video/720p/
├── init.m4s
├── segment_1.m4s
└── segment_2.m4s
シンプル化した MPD 例:
<MPD>
<Period>
<AdaptationSet mimeType="video/mp4">
<Representation id="1080p" bandwidth="8000000" width="1920" height="1080">
<BaseURL>1080p/</BaseURL>
<SegmentTemplate timescale="15360"
initialization="init-stream0.m4s"
media="chunk-stream0-$Number$.m4s"
startNumber="1">
<SegmentTimeline>
<S t="0" d="61440" r="389" />
<S d="5632" />
</SegmentTimeline>
</SegmentTemplate>
</Representation>
</AdaptationSet>
</Period>
</MPD>
Representation が画質(ビットレートごとの動画)を表し、
SegmentTemplate がチャンクの構成や並び順を定義しています。
m4s は 2〜6 秒程度の短い動画断片で、
再生がスムーズに行えるよう 必ずキーフレーム境界で分割 されています。
HLS の構造
HLS も構造は同じです。
m3u8 というマニフェストでチャンクの一覧を定義します。
master.m3u8(画質一覧)
├── 1080p.m3u8
│ ├── segment_000.ts
│ ├── segment_001.ts
│ └── ...
└── 720p.m3u8
├── segment_000.ts
├── segment_001.ts
└── ...
プレーヤーはまず master.m3u8 を読み、
続いて選択された画質の m3u8 の内容に従ってチャンクをダウンロードします。
動画を「配信可能な状態」まで持っていくために必要なこと
ここまでが動画の構造と配信方式の基本ですが、実際にサービスとして配信するには、次のような処理が必要になります。
(1) エンコード
(2) セグメント分割
(3) マニフェスト生成
(4) CDN 配信
(5) ブラウザ再生
複数ビットレートの用意
HLS/DASH では、ユーザーのネットワーク状況に応じて画質を切り替えるため、複数のビットレートで動画を作ります。
例:
1080p - 8Mbps
720p - 4Mbps
480p - 1.5Mbps
360p - 800kbps
DASH では以下のように複数の Representation を記述します。
<Representation id="1080p" bandwidth="8000000" width="1920" height="1080">
<BaseURL>1080p/</BaseURL>
<SegmentTemplate media="segment_$Number$.m4s" />
</Representation>
<Representation id="720p" bandwidth="4000000" width="1280" height="720">
<BaseURL>720p/</BaseURL>
<SegmentTemplate media="segment_$Number$.m4s" />
</Representation>
ブラウザ側での再生
ブラウザで HLS/DASH を再生するには専用のライブラリを使うことが多いです。
- hls.js
- dash.js
- Shaka Player
- Video.js(プラグインで HLS/DASH 対応)
これらは内部で次のような処理を行っています。
- マニフェスト(m3u8 / mpd)をパース
- 適切な画質を選択(ABR)
- チャンクを順次ダウンロード
- MediaSource Extensions に流し込む
- 映像として再生する
ユーザーは何も意識しなくても、プレーヤーが最適な画質やバッファ量を調整してくれます。
まとめ
この記事では、動画配信を理解するための土台として、
- コンテナとコーデック
- I/P/B フレームと GOP
- HLS / MPEG-DASH の仕組み
- マニフェストとセグメントの役割
- 配信パイプラインの全体像
- ブラウザで再生されるまでの流れ
について触れました。
「動画ってどうなってるの?」「HLS や DASH の設定、どうすればよいの?」といった疑問の整理に役立てば幸いです。