最近のWebサイト用動画フォーマット事情と<video>要素による自前配信のすすめ

基本的にWebサイト用に配信する動画はYoutubeに全部任せておけば間違いないのですが、どうしてもYoutubeを使えない、使いたくないケースもあるでしょう。

そんな時は自前での動画配信になるのですが、手法も多種多様でどれがいいのやら。

そんなこんなで最近のWebサイト用動画事情を調べてみました。

Youtubeは偉大なので、できるならYoutubeで配信しよう

「video要素による自前配信のすすめ」をいきなりぶち壊す見出しですが、Youtubeは快適に配信するための工夫が色々なされているので、可能ならYoutubeにアップロードするのが良いと思います。

二種類の動画フォーマットを環境に合わせて配信してくれる

Youtubeは圧縮効率の良いVP9と普及率の高いh.264の二つを用意してくれます。
VP9を再生できるブラウザではVP9によるWebMを、対応していないブラウザではh.264によるMP4を配信といった形です。
もしかしたらFlash Player用のフォーマットを入れて三種類かもしれないけど、今の時代どうでもいいよね。
(IEの開発者ツールでIE8をエミュレートしてアクセスするとFlash Playerで見れたりする。)

解像度も環境に合わせて何種類も用意してくれる

例えば解像度が720pの物をアップロードすると、720pに加えて480p、360p、240p、144pの解像度の動画も用意してくれます。

二種類のフォーマット(VP9、h.264)を考えると5種類x2で10種類もの動画を用意してくれているという事になります。
720pの例えで10種類なので、1080pで12種類、1440pで14種類、2160pで16種類、4320pで18種類と更に増えていきます。

これらを全て自前で用意するのは結構面倒ですよね。

DASH形式にすることで融通を利かせている

Youtubeにアップロードされた動画は「DASH」と呼ばれる形式になり、動画は決められた単位で細切れに分割されます。(分割された物をセグメント、またはフラグメントと言います。)

視聴者は次々にセグメントをリクエストしながら再生していきます。DASHの同類として「HTTP Live Streaming」がありますが、仕組みはほとんど一緒です。

DASH形式にする利点としてはJavascript(Media Source Extensions)でごにょごにょしやすいという点じゃないでしょうか。
シークする際にサムネイルを出したり、シークの処理が滑らかだったり、回線に合わせて画質を変えたり、といったYoutubeの機能はDASH+Javascriptでやるのが効率良さそうです。

セグメントへのリクエスト
開発者ツールでネットワークの状況を見るとセグメントへのリクエストが飛んでいるのがわかる。

埋め込み用のAPIもある

スプラトゥーンの公式サイトはしばらくすると背景に動画が流れますが、実はこの動画はYoutubeの埋め込みなんです。
https://www.nintendo.co.jp/wiiu/agmj/

この仕組みにはYoutubeのIFrame APIという物を使ってるようです。 https://developers.google.com/youtube/iframe_api_reference?hl=ja

Youtubeを参考に自前配信を考える

最初に「Youtubeすげー」ってセクション作ったのはYoutubeの凄さを伝えたかったのもありますが、先人の知恵にあやかって自前配信に役立てようという話でもあります。

自前配信をどうにかYoutubeに近づけてみようと思いますが、もうちょっと手軽に考えたいという人もいると思うので『お手軽配信コース』と『アダプティブ配信コース』に分けてみました。

『お手軽配信コース』では単一の動画ファイルによるシンプルな配信を、『アダプティブ配信コース』では「Adaptive Streaming」という手法を用いた通信環境に合わせて動画を切り替える配信を行います。

『アダプティブ配信コース』では『お手軽配信コース』で作った動画を元にするので、『お手軽配信コース』のセクションも見る必要があります。

お手軽配信コース

単一の動画ファイルをそのまま<video>要素で読み込みます。
フルスクリーンなデザインのWebサイトで背景にちょっとした動画を使うならこれで十分だと思います。

<video>要素では複数のビデオソースから「再生できるかできないか」での判断による優先順位を決めて配信できるので、必要に応じてフォーマットを増やす方向性で。

<video>  
  <source src="movie.webm" type="video/webm">
  <source src="movie.mp4" type="video/mp4">
</video>  

こんな感じで指定できます。この例だと最初に「movie.webm」を試し、再生できなかったら「movie.mp4」を再生します。

使うコーデック

h.264は幅広いブラウザで使える優秀なコーデックなので、とりあえずh.264(MP4)を用意しておけば間違いないです。

この記事ではついでにVP9(WebM)も使います。必要なければ用意しなくても大丈夫です。

解像度を決める

「お手軽配信コース」では単一のファイルでの運用なので、解像度は一つしか選べません。

特にこだわりが無ければ720p(1280x720)が一番無難じゃないでしょうか。

<video>要素のコントロールにフルスクリーンの機能が付いている事も多いので、臨場感を楽しむタイプの動画であればユーザーがフルスクリーンにする場面を想定して1080p(1920x1080)が良いかもしれません。
しかし、その場合だとビットレートを上げる必要があり、動画の容量が増えるのでモバイルに優しくないという問題があります。

モバイルを中心に考えた場合は360p(640x360)か480p(854x480)が良いと思います。
PC向けブラウザで見た場合、解像度が小さくても<video>要素のサイズを指定することで要素に合わせたサイズで再生されます。(その分映像は荒くなりますが。)

エンコードの準備

方針が決まったらエンコードしましょう。

この記事では「FFmpeg」というソフトを使ってエンコードします。
Windows、Mac、Linuxといった主要なOS全てに対応しているのでどなたでも使えると思います。

FFmpeg公式

https://ffmpeg.org/

ダウンロードページ

https://ffmpeg.org/download.html
ソースコードでの配布が基本みたいですが、ビルド済みもあります。

ダウンロードした物を適当な所に配置し、binフォルダのffmpegをコマンドラインから実行する事で操作できます。

C:\ffmpegに配置した場合はC:\ffmpeg\bin\ffmpeg ほにゃららといった感じで操作します。

h.264でエンコードする

今回作る動画はこんな感じの構成です。

ffmpeg -i 入力ファイル.mp4 -s 1280x720 -r 30 -c:v libx264 -b:v 1600K -threads 0 -movflags faststart 出力ファイル.mp4  

本当は2passでエンコードしたいけど、解説がややこしくなるので解説は1passで。
2passでエンコードした方が動画は最適化されるので、気になる方は「ffmpeg 2pass」なんかで検索するといいかも。

品質指定やプリセットによるエンコードでも良いですが、Webサイト用だとビットレートが重要だと思うのでビットレートの指定によるエンコードにしています。

音声に関しては指定してないのでデフォルトのままです。デフォルトだとAAC、48000Hz、音声ビットレート128kとかだった気がします。

「よくわからん!」という人のために一区切りづつ解説していきます。FFmpegに詳しい方は-movflags faststart以外は飛ばしてもOKです。

-i 入力ファイル.mp4

エンコード対象のファイルパスを指定します。
MP4じゃなくてもFFmpegが対応している動画フォーマットであれば大丈夫です。(大体の物に対応してる。)

-s 1280x720

解像度を1280x720に指定します。

-c:v libx264

-codec:videoの略だと思われます。基本的に:vと付いたら映像関連のオプションです。
映像コーデックにh.264を指定します。

-r 30

フレームレートを30に指定します。

-b:v 1600K

ビットレートを1600kに指定します。目安という扱いなのか多少上回ったりします。

-threads 0

エンコードに使用するCPUのスレッド数を指定します。0を指定すると自動で判断してくれます。

出力ファイル.mp4

出力されるファイル名を指定します。

-movflags faststart

メタデータをコンテナの先頭に移動させる重要なオプションです。
末尾にあるメタデータを先頭に移動させる事で動画ファイルを末尾まで読まなくても動画の再生が可能になります。

つまりどういう事かというと、本来ならば動画ファイルを全て読み込んでからじゃないと再生が開始されないところを、読み込みながら再生できるようにするオプションです。

VP9でのエンコード

基本的にはh.264と同じですが、VPx系独自のオプションが追加されています。
正直言うと独自オプションの内容もサジ加減もよくわかっていないので、VP9 Encoding Guideの設定を1pass用に変えて使います。

ffmpeg -i 入力ファイル.mp4 -s 1280x720 -r 30 -c:v libvpx-vp9 -b:v 1600K -threads 0 -speed 2 -tile-columns 6 -frame-parallel 1 出力ファイル.webm  

-speed 2

エンコード速度を指定します。
どの程度丁寧にエンコードするか、というオプションだと思います。-speed 0で試したら途方もない時間になった。

-tile-columns 6

これがまたよくわからないのですが、デコーダーのために設定しておかないといけないオプションみたいです。

Most of the current VP9 decoders use tile-based, multi-threaded decoding. In order for the decoders to take advantage of multiple cores, the encoder must set tile-columns and frame-parallel.
http://wiki.webmproject.org/ffmpeg/vp9-encoding-guide

タイルベースってなんでしょうか。

-frame-parallel 1

上の-tile-columnsとセットで使うみたいです。
FFmpegの説明には「Enable frame parallel decodability features」とあるので、値は「有効にする」としての1みたいです。

h.264とVP9のデモページ

デモページを用意しました。記事では1passですが、デモでは2passにしているので2passエンコードの参考にもどうぞ。
http://deerest.co/-page/video-format-demos/

コーデックがnvencになっていますが、内容はlibx264と変わりません。 VP9は対応しているブラウザでないと再生できません。

VP9はやはり綺麗ですね。

エンコードした動画を埋め込む

こんな感じで動画を埋め込みます。

<video poster="thumbnail.jpg" controls>  
  <source src="movie.webm" type="video/webm">
  <source src="movie.mp4" type="video/mp4">
</video>  

動画が一つであればこちらでも。

<video src="movie.mp4" poster="thumbnail.jpg" controls></video>  

冒頭で見せたHTMLと違う箇所があります。

poster属性

再生を開始する前に表示するサムネイル画像を指定できます。

FFmpegでサムネイルも作れます。

ffmpeg -ss サムネイルにしたい位置(秒) -i 入力ファイル.mp4 -vframes 1 出力ファイル.jpg  

controls属性

一時停止や音量調整などができるブラウザ固有のインターフェースを表示します。

その他の属性

MDNに説明を丸投げします。
https://developer.mozilla.org/ja/docs/Web/HTML/Element/video

背景に使うような動画だとcontrolsをオフにして、autoplayloopを指定すると良いんじゃないでしょうか。

レスポンシブ(?)配信

「解像度は一つしか選べません」と説明しましたが、Javascriptでレスポンシブチックな動画配信なら可能です。

<video data-src-desktop="demo_h264_720p.mp4" data-src-mobile="demo_h264_360p.mp4" poster="thumbnail.jpg" controls></video>  
<script>  
const video = document.querySelector('video[data-src-desktop][data-src-mobile]');  
if(matchMedia('(max-width: 640px)').matches){  
  video.src = video.dataset['srcMobile'];
}
else{  
  video.src = video.dataset['srcDesktop'];
}
</script>  

matchMedia()でCSSと同じようにメディアクエリを使用できるので、メディアクエリで分岐して動画パスを切り替えるだけの単純な物です。

単純ながらも結構有用なんじゃないでしょうか。

<source media="">でメディアクエリを指定できるようになる、という話があったみたいですがポシャったようです。とても便利そうだったのに残念。

アダプティブ配信コース

「MPEG-DASH(DASH)」、「HTTP Live Streaming(HLS)」を使って通信環境やデバイスの解像度に合わせた動画を配信します。こういった環境に合わせて柔軟に配信する方式を「アダプティブ配信(Adaptive Streaming)」と呼ぶわけです。

仕組みとしてはアダプティブ配信するための設定を書いたプレイリストを作成し、プレイリストから動画ファイルを参照して再生します。

プレイリストを作成するのに使うソフトウェアはGoogle製の「Shaka Packager」です。
FFmpegでも作れますが、個人的にはShaka Packagerの方が優れていると思う(プレイリストの構造や操作の扱いやすさ、複数フォーマット混ぜやすい点とか)のでこちらにします。

どちらもJavascriptでの再生が必要で、MSE(Media Source Extensions)を使うためにIE11以下は切り捨てる事になります。

Shaka Packagerの導入

こちらもソースコードでの配布が基本みたいですが、Mac用とLinux用はビルド済みの物がリリースされています。
https://github.com/google/shaka-packager

Windows用でビルド済みの物はここにありますが、きちんとリリースされている物ではありません。
https://ci.appveyor.com/project/shaka/shaka-packager

FFmpegと同様にコマンドラインで操作します。
C:\Shaka-Packager\packager-win ほにゃらら

モバイル用に360pの動画を用意する

少なくとも二種類以上の解像度を用意しないとアダプティブ配信の意味がないので、「お手軽配信コース」でエンコードした動画に加えて360pの動画も用意します。

ffmpeg -i 入力ファイル.mp4 -s 640x360 -r 30 -c:v libx264 -b:v 800K -threads 0 出力ファイル.mp4  

解像度が720pの丁度半分なのでビットレートも半分にします。
この後DASH化させるので-movflags faststartは省きました。

このセクションでは使う動画は720pと360pの二種類だけですが、フルスクリーン用の1080pとタブレット用に480pの動画も用意すると丁寧です。

DASHのプレイリスト(.mpd)を作成する

半角スペース区切りでプレイリストの項目を指定します。項目のオプションはカンマで区切ります。

packager input=720p入力ファイル.mp4,stream=video,output=720p映像出力ファイル.mp4 input=720p入力ファイル.mp4,stream=audio,output=音声出力ファイル.mp4 input=360p入力ファイル.mp4,stream=video,output=360p映像出力ファイル.mp4 --profile on-demand --mpd_output 出力ファイル.mpd  

この例だと720pで作った動画の映像と音声、360pで作った動画の映像の合計3つの項目でプレイリストを作ります。その場合、360pの映像が再生された場合でも720pで作った動画の音声が再生されます。

Demuxする機能もあるので入力にMux済みの動画ファイルを入れても音声を出力できます。
もちろん、入力に音声ファイルを入れても大丈夫です。

また、異なるフォーマットの動画ファイルを両方混ぜることもできます。
上記の設定にWebMを混ぜたい場合は単純に

input=720p入力ファイル.webm,stream=video,output=720p映像出力ファイル.webm input=720p入力ファイル.mp4,stream=audio,output=音声出力ファイル.webm input=360p入力ファイル.webm,stream=video,output=360p映像出力ファイル.webm  

を付け加えるだけです。

後に紹介する「Shaka Player」では初期化用の解像度の低い動画(この例だと360pの動画)がある方のフォーマットが優先されるようなので、解像度は両方同じ物を揃えましょう。

オプションに関しては名前通りの機能だけなので説明は省きます。

プレイリストはXMLで出力されるので中身の把握がしやすいです。
動画ファイルのパスなどを変更したい場合は中身の<BaseURL>をいじります。

字幕機能にも対応

WebVTT」での字幕にも対応しています。

動画ファイルの指定と同じく、VTTファイルの入力と出力を指定するだけです。

input=入力ファイル.vtt,stream=text,output=出力ファイル.vtt  

DASHを埋め込む

同じくGoogle製の「Shaka Player」を使用します。
https://github.com/google/shaka-player

CDNもあります。
https://cdnjs.com/libraries/shaka-player

<script src="https://cdnjs.cloudflare.com/ajax/libs/shaka-player/2.0.2/shaka-player.compiled.js"></script>  
<video data-shaka-player="playlist.mpd" poster="thumbnail.jpg" controls></video>  
<script>  
shaka.polyfill.installAll();  
if(shaka.Player.isBrowserSupported()){  
  const video = document.querySelector('video[data-shaka-player]');
  const player = new shaka.Player(video);
  player.load(video.dataset['shakaPlayer']);
}
</script>  

複数の<video>要素がある場合はdocument.querySelectorAllにしてループさせるのが良いと思います。

他の選択肢としてはMicrosoft製の「dash.js」があります。
https://github.com/Dash-Industry-Forum/dash.js

Shaka Playerのデモ

http://shaka-player-demo.appspot.com/demo/

HLSのプレイリストを作成する

Shaka PackagerではHLSのプレイリストも作れます。

packager input=入力ファイル.mp4,stream=video,segment_template=ts/映像出力_$Number$.ts,playlist_name=映像出力.m3u8 input=入力ファイル.mp4,stream=audio,segment_template=ts/音声出力_$Number$.ts,playlist_name=音声出力.m3u8,hls_group_id=audio --single_segment=false --hls_master_playlist_output="マスタープレイリスト.m3u8"  

segment_template

出力されるセグメントファイルのパスを指定します。$Number$が連番になります。

playlist_name

出力されるプレイリストのバスを指定します。 DASHと違い一つの動画ファイルにつき一つのプレイリストが必要になります。

--hls_master_playlist_output

プレイリストを束ねるマスタープレイリストのパスを指定します。

注意点

現在のバージョン(1.5.1)ではシングルセグメントに対応していないので、事前に「ts」フォルダを作り、そこに連番のセグメントファイルが出力されるようにします。
FFmpegはシングルセグメントに対応しているので、どうしてもシングルセグメントがいい場合はFFmpegを使いましょう。

あと、HLSはDASHと違いVP9に対応していません。

HLSを埋め込む

Dailymotion製の「hls.js」を使います。
https://github.com/dailymotion/hls.js

CDNもあります。
https://cdnjs.com/libraries/hls.js

<script src="https://cdnjs.cloudflare.com/ajax/libs/hls.js/0.6.16/hls.min.js"></script>  
<video data-hlsjs-player="playlist.m3u8" poster="thumbnail.jpg" controls></video>  
<script>  
if(Hls.isSupported()){  
  const video = document.querySelector('video[data-hlsjs-player]');
  const player = new Hls();
  player.loadSource(video.dataset['hlsjsPlayer']);
  player.attachMedia(video);
}
</script>  

hls.jsのデモ

http://dailymotion.github.io/hls.js/demo/

ネイティブに対応しているブラウザもある

EdgeやSafari、iOS Safari、Androidブラウザなどはネイティブに再生できるため<video src="playlist.m3u8"></video>のようにシンプルに扱えます。

さいきょうのアダプティブ・クロスブラウザ配信

今までの事を踏まえた上で、IE9-11とiOS、Androidにも対応した"さいきょう"の配信方法を考えます。

まず、アダプティブ配信の扱いについて考えるためにMSEのブラウザ対応状況を確認します。
http://caniuse.com/#feat=mediasource
「IE9から10」、「Windows 7のIE11」、「Opera Mini」、「iOS Safari」が対応していないようです。
「Opera Mini」はどうでもいいとして、「iOS Safari」が対応していないのは問題です。

モバイル端末で単体のMP4をそのまま再生させるのもナンセンスなのでiOSがネイティブに再生できるHLSは用意したほうがいいでしょう。
そうなるとAndroidでもネイティブなHLSを再生させたくなりますが、AndroidはVP9に対応しているのでせっかくならDASHで。

<video poster="thumbnail.jpg" controls>  
<source src="playlist.m3u8" type="application/x-mpegURL">  
</video>  

IE9とIE10、Windows 7のIE11に関しては受け皿である単体のMP4に任せます。

<video poster="thumbnail.jpg" controls>  
<source src="playlist.m3u8" type="application/x-mpegURL">  
<source src="movie.mp4" type="video/mp4">  
</video>  

これを元にShaka Playerの環境を作って完成です。

<script src="https://cdnjs.cloudflare.com/ajax/libs/shaka-player/2.0.2/shaka-player.compiled.js"></script>  
<video data-shaka-player="playlist.mpd" poster="thumbnail.jpg" controls>  
<source src="playlist.m3u8" type="application/x-mpegURL">  
<source src="movie.mp4" type="video/mp4">  
</video>  
<script>  
shaka.polyfill.installAll();  
if(shaka.Player.isBrowserSupported()){  
  var video = document.querySelector('video[data-shaka-player]');
  var player = new shaka.Player(video);
  player.load(video.dataset['shakaPlayer']);
}
</script>  

VP9を使わない場合

VP9を使わない場合はAndroidもHLSで再生させちゃいましょう。

<script src="https://cdnjs.cloudflare.com/ajax/libs/shaka-player/2.0.2/shaka-player.compiled.js"></script>  
<video data-shaka-player="playlist.mpd" poster="thumbnail.jpg" controls>  
<source src="playlist.m3u8" type="application/x-mpegURL">  
<source src="movie.mp4" type="video/mp4">  
</video>  
<script>  
shaka.polyfill.installAll();  
var video = document.querySelector('video[data-shaka-player]');  
if((video.canPlayType('application/x-mpegURL') === '') && shaka.Player.isBrowserSupported()){  
  var player = new shaka.Player(video);
  player.load(video.dataset['shakaPlayer']);
}
</script>  

canPlayType()を使用し、HLSがネイティブに再生できない場合はDASHを使うようにしています。

エラーハンドリング

チュートリアルに載っています。
http://shaka-player-demo.appspot.com/docs/api/tutorial-basic-usage.html

デモページ

http://deerest.co/-page/saikyo-streaming-demo/

ShadowplayのMP4からShaka PackagerでHLS作るとiOSで音割れしたため、iOSだとデモの動画も音割れするかも。
Shaka Packagerの設定そのままで別の動画(ここのBig Buck Bunny)から作ったHLSは音割れしなかったので、Shadowplay + Shaka Packager + iOSの相性が悪いみたいです。
ビデオフォーマットのデモページのMP4だと音割れしていないので、TSに分ける時におかしくなっているのだと思う。
DASHの作成時に出力された音声のみのMP4を再生してみたら音割れしなかったので、Demuxが原因ではなさそう。

同じ条件で作成したBig Buck BunnyのHLSプレイリスト → http://deerest.co/-page/saikyo-streaming-demo/bbb/master_bbb.m3u8
元のMP4ファイル → http://deerest.co/-page/saikyo-streaming-demo/bbb/bbb.mp4

まとめ

「FFmpeg」でエンコードし、「Shaka Packager」で「MPEG-DASH」と「HTTP Live Streaming」のプレイリストを作り、DASHは「Shaka Player」、HLSはネイティブで再生させ、受け皿にh.264を置いておく。