🎥

動画サービス構築の勉強について

2021/10/06に公開

この記事ではもし動画サービスを作ることになったらどのように勉強を進めていけばいいのかについて説明します。

背景

2021年9月に以下のサービスを公開しました。

https://twostream.net/

動画を使ったサービスですが、初めて動画を扱ったこともあり苦戦しました。そのため改めてこういう手順で勉強すれば良かったと思う内容を今回の記事にしました。

学習の全体像

個人的に以下のステップで進むことをおすすめします。

  1. MP4ファイル・HLSファイルを使ってローカル環境で動画を再生しよう
  2. エンコード・コーディック・プロトコルについての勉強
  3. AWS・GCPなどのインフラ環境を使って配信
  4. CDNを使ったキャッシュ・認証について

私の場合あんまり順序を考えずつまみ食いしながら勉強してしまい時間がかかりました(恐らくこのサービスを作らなければ挫折していました)。
またよく1, 2についてあまりよく知らず3からについて飛びついてしまう人がいますがおすすめしません。問題が起きた時に切り分けがするのが難しいというのもあるのですが、最初の段階で例えば大規模配信の記事を読んでもあまり意味が無いからです。

いちいち勉強がめんどくさい動画をアップロードしたらいい感じ配信までしてくれないのかという要望もきっとあると思います(私も最初はそう思ってました)。ただ、そういうサービスでも基本的な知識が無いとどれを選んでいいのか分からないので最低限の知識は必要です。

ローカルで動画再生してみよう

まずローカル環境で動画を再生してみましょう。なぜローカル環境で再生をするのかというと一番簡単であり基本であるからです。
動画配信というのは一般的なWebサービスと同じように、1人にむけて、1万人にむけて、1000万人むけて組むのでは難易度も要件が違います。私が最初動画サービスに関する記事を読みながら勉強してて実は大規模配信向けの記事だったので遠回りした経験があります。そのためまずはローカルでの動画再生についてから始めたいと思います。

まずはローカル環境でサーバーを立てます。ここではcreate-react-appで作成していますが、ローカルでサーバーを建てられるなら何でも大丈夫です

npx create-react-app video-sample-zenn
cd video-sample-zenn
yarn start

次にサンプルとなるMP4のファイルをダウンロードしましょう。
今回は練習なので以下のサイトにあるようなサンプルの動画を使用しました。
https://japanism.info/free-video1-10.html#google_vignette

ダウンロードしたファイルを任意のフォルダにいれて、アクセスしてみると動画を再生できること分かります。

import './App.css';
import mp4 from './free-video3-sky-cafinet.mp4';

function App() {
  return (
    <div className="App">
      <video controls width="850">
        <source src={mp4} />
      </video>
    </div>
  );
}

export default App;

MP4について

ここでそもそもMP4について説明します。
MP4とはデジタルマルチメディアコンテナフォーマットの一つです。簡単に説明すると音声・動画などを一つにまとめる入れ物で仲間にMOV・WAVがあります。
対応するコーデックとしては以下があります。

ビデオ:MPEG-1、MPEG-2、MPEG-4 Visual (MPEG-4 Part 2)、H.264/MPEG-4 AVC (MPEG-4 Part 10)、H.265 (H.265/HEVC)、AV1 など
オーディオ:AAC、HE-AAC、MP3、MP2、MP1、MPEG-4 ALS、TwinVQ、CELP(QCELPとは異なるので注意)、Opus など

参考サイト: https://ja.wikipedia.org/wiki/MP4

重要なのは.mp4というファイルがあったとしても中身がH.264やAV1であったり、オーディオもAACやOpusであることです。そのためちゃんと中身のデータを確認するにはffprobeコマンドを実行して確認することが私は多いです。
ffprobeコマンドはFFmpegをインストールすると一緒にインストールされるコマンドです。

以下の内容からこのfree-video3-sky-cafinet.mp4のファイルはH.264とAACでエンコードされていることが分かります。

ffprobe free-video3-sky-cafinet.mp4

 Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'free-video3-sky-cafinet.mp4':
  Metadata:
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: mp42mp41
    creation_time   : 2017-07-02T09:58:11.000000Z
  Duration: 00:00:30.08, start: 0.000000, bitrate: 10331 kb/s
    Stream #0:0(eng): Video: h264 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080, 10023 kb/s, 29.97 fps, 29.97 tbr, 30k tbn, 59.94 tbc (default)
    Metadata:
      creation_time   : 2017-07-02T09:58:11.000000Z
      handler_name    : ?Mainconcept Video Media Handler
      encoder         : AVC Coding
    Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 317 kb/s (default)
    Metadata:
      creation_time   : 2017-07-02T09:58:11.000000Z
      handler_name    : #Mainconcept MP4 Sound Media Handler

H.264のビデオフォーマットとAACの音声フォーマットはほぼ全てのブラウザでサポートされているため、この.mp4のファイルはどのブラウザでも再生することが可能ということが分かります。
逆にAV1のビデオフォーマットはSafariでサポートされていないので、Safariで再生することはできません。

なぜエンコードする必要があるのか

普段動画を扱っている人にとっては当たり前すぎて疑問を持ちませんが、自分の場合は最初に疑問を持ちました。ただ、よく考えてみるとエンコード処理は必要ということが分かります。例えば、FPS/TPSなどに代表されるシューティングゲームでは60fps以上無いと動きが滑らかではなく満足度が低くなります。ではテトリスのようなゲームでそこまでfpsが必要かどうかは疑問があります。それよりも高性能なパソコンやゲーム機でなくても動いてくれる方がユーザーにとってはありがたいです。

またユーザーもスマートフォン・パソコンやLTE・Wifi接続など環境がバラバラです。スマートフォンでは動画が高画質であるよりも電車内で気軽に再生出来る方が需要があるケースもあります。このようにコンテンツの中身やユーザーが使用する環境を考慮をすると何がベストの動画品質というのは一概に言えません。またサービス提供側も高圧縮・高画質の動画にしようとすると一般的にエンコード処理時間も増えるのでサーバー費用が高くなります。つまりコンテンツの中身・ユーザー環境・サービス側の懐事情を考えて適切なエンコード・インフラ環境を設計する必要があります。そう考えるとなぜ最初のエンコードする必要があるのかと言うと、適切な動画に変換するという目的が強く、データ量の圧縮はその手段と自分は考えています。

HLSを使って動画を再生しよう

先ほどmp4ファイルで再生するのにはある問題があります。それは再生時間に比例して動画ファイルのサイズが大きいことです。そのため初回アクセス時にページが重くなります。20~30秒ぐらいの動画であればそこまで問題ありませんが、1時間以上の動画であればやはり問題があります。
それよりも再生に応じて必要な動画ファイルをダウンロードする方が初回アクセスも軽く、サーバー側も少ない転送量ですみます。

そこで使う技術がHLS(HTTP Live Streaming)というプロトコルです。ただHLSの説明をすると書ききれないので、簡単な使い方だけの説明をします。

エンコード処理

// 以下のコマンドを実行
ffmpeg -i free-video3-sky-cafinet.mp4 -r 30 -c:v libx264 -b:v 512k -c:a aac output.m3u8

// 以下のファイルが作成された
output.m3u8
output0.ts
output1.ts
output2.ts
output3.ts

publicフォルダに先ほど作成したファイルを追加します。そして以下のように書き換えます。

import './App.css';

function App() {
  return (
    <div className="App">
      <video controls width="850">
        <source src="/output.m3u8" />
      </video>
    </div>
  );
}

export default App;

そしてブラウザを起動して表示させて下さい。動画が再生できると思います。

HLSの簡単に仕組みで言うと長い動画を細切れのサイズに分割し必要な箇所だけの動画ファイルだけダウンロードする仕組みになります。先ほどの.tsファイルは分割した動画ファイルになります。
それ以外の.m3u8のファイルは動画の場所やプレイリストの内容を記したテキストファイルになります。実際にvideoタグのsrcに指定しるのはこの.m3u8のファイルです。

これが正しい理解なのかは自身がありませんが、HLSは細かい動画ファイルをダウンロードしているというイメージは持っておいた方がいいです。つまり、画像ファイルと同じような動きです。このイメージが無いとなぜCDN上にキャッシュができるのかというイメージが持てません。

まとめ

今回説明した内容はかなり基本の部分です。全体像の項目を見て貰えれば分かるのですが、動画サービスを真面目に構築するとなるとかなり大変です。
動画知識もですが、クライアント・インフラ・認証・プロトコルなど多岐に色々と知識が要求されます。そう考えると動画サービスはわりと器用貧乏の人が向いている領域だと感じました。

Discussion