エンコーディング・マジック・アワー
この記事はeeic Advent Calendar 2015の22日目の記事として書かれた.
最近動画エンコードについてよく聞かれるので,録画PCネタの続編ということで記事にすることにした. 私は動画処理に別段詳しいわけでもなく,専門分野もマルチメディアとはまるで関係ないのだが, x264やFFmpegといった動画変換ソフトウェアの使い方については日頃から色々試してはいるので, とても適当に今まで得た知見をまとめようと思う. おそらくこの記事には間違いも多々含まれているだろうから,コメントは随時募集している.
前置き
去年の夏,気が狂ったように深夜アニメを片っ端から録画してHDDに貯め始めた結果, 我が家の録画PCのHDDはあっという間に満杯になった. 放送波のデータはMPEG-2 TS (Transport Stream, しばしば単にTSと呼ばれる)で送られてくるが, その圧縮率は決して高くない *1 ので,1クールに放送される全てのアニメを圧縮せずに全部録り貯めると体感7TB (これはビッグデータだろうか?)ぐらいになる. 富豪的に解決するにはHDDを買い足せばいいわけだが, 今最も単位容量あたり価格の安い3TBのHDD(現在の価格は大体1万円弱)を増やすとしても, 1年に10台は買い足す必要がある. 貧乏な院生が10万円/年を定常的に注ぎこむのはなかなか厳しいし, 仮に資金調達が上手くいったとしても,HDDを交換する手間などを考えるととても現実的な選択肢ではない.
そのような事情から,多くの録画PC持ちの人々はTSをそのまま保存するのではなく, MPEG-4のような圧縮率の高いフォーマットに圧縮(エンコード) *2し直すことで, 録画動画の容量削減を行っている. TS→MPEG-4の変換については非常に多種多様なやり方があるが, 映像に関してはH.264,音声はAACでエンコードするのが最も一般的であろう. 最近は新しい映像コーデックとしてH.265も登場しているが, まだ対応ソフトが非常に限定されていて,エンコーダも発展途上であるから,今回は取り扱わない.
ソフト vs. ハード
映像のエンコード手法は, ざっくり分けてソフトウェアエンコードとハードウェアエンコードがある. ここでいうハードウェアエンコードとは, GPUなどに備わっているエンコード専用回路を利用して映像を圧縮することである. 一方でソフトウェアエンコードとは, CPUの機能だけを使うソフトウェアによるエンコードであり, H.264の場合はx264というオープンソースのソフトウェアが有名である. FFmpegやAviutlといった著名な動画変換ソフトはフロントエンド*3であり, 内部ではx264に代表されるコーデックを呼び出している.
今回取り上げるのはソフトウェアエンコードの話題である. 一見すると,ハードウェアエンコードの方が専用回路を使っている分だけ優れていそうなのだが, ソフトウェアに比べるとチューニングできるパラメータが限定的であるが故に,現状ではその使用用途も限定的である. また,x264のようなソフトは圧縮効率を高めるために日進月歩で非常に細かい工夫がなされているため,ハードではそれに追い付けていないのが現状である*4. もちろん,ハードウェアエンコードには省電力,高速といった利点があるため, そのような利点が活かされる場面 (例えばストリーミング配信)では採用するべきだろう. ソフトウェアエンコードは「エネルギー効率や変換時間はどうでもいいが,とにかく画質と圧縮効率を向上させたい」という要件を満たすために適切な選択肢であり,録画データを保存するなら普通はそういう要件になると思う.
要件定義
動画エンコードを行うにあたって,我々が吟味する主なパラメータは次のようになる.
- 画質
- ファイルサイズ
- エンコード時間
- 再生負荷
- 再生側で最終的にMPEG-4をデコードしないと動画として見られないから,再生時の処理も一応考慮する必要がある.
- 再生負荷を重視するのは主に古い環境やモバイル機器を使っている人たちで,将来的にはこれらも改善されていくと考えるのが私は妥当だと思う.
- 再生時の“シークのなめらかさ”も忘れがちであるが重要である.関係してくるのはkeyintなどである.
x264のようなコーデックに与えるオプションによって,これらのパラメータは大きく変動する. 基本的には,あるオプションを調整するとこれらのパラメータのうちのどれかが改善する代わりに, どれかが悪化するというトレードオフの関係になっている. すなわち,全てを改善するような魔法のオプションというのは存在せず, どれかのオプションをいじるとどこかしらしわ寄せがいくことになる. そのため,エンコードを行うにあたって, 出力結果の要件をきちんと定めて, それらを全てクリアできるようなオプションを導き出すという手順が必要になる.
代表的なトレードオフは次のように挙げられる.
- 画質 vs. サイズ
crf
,qcomp
- サイズ vs. (エンコード時間, 再生負荷)
ref
,bframes
,me
,subme
,trellis
,keyint
,rc-lookahead
, etc.
オプション決定に際しての基本的な方針としては次のようになる.まず,大抵の場合その人が許容できる画質があるだろうから,それをcrf
とqcomp
を試しながら決定する.その上で,ref
やbframes
といったファイルサイズに影響のあるパラメータを調整して,許容できるファイルサイズ(とエンコード時間)になるまで試す.その段階でファイルサイズを見て愕然とすることもあるだろうから,その際は適度に画質を落としながら妥協点を探る.後は直接これらのパラメータに関係しないその他のオプションを調整すれば完成である.以上のような基本的な流れが,FFmpegの公式サイトにきちんとまとめられていたことにこの記事を書いている最中に気付いた.
コンテナの話
動画エンコードについて触れる前に,MPEG-4コンテナに関する話や,デインターレースといった前処理の話をまとめておく.
音ズレの恐怖
MPEG-2やMPEG-4は,映像と音声を多重化 (mux)して保存することができる, いわゆる動画コンテナと呼ばれる形式である. 特に元々の放送波はストリーミングなので, 映像と音声のタイミングを適切に同期させるためのデータが含まれている.
割と多くのウェブサイトでは,「MPEG-2 TSをエンコードする前に,最初に映像と音声を分離 (demux)して, 別々にエンコードしたのち,結合 (mux)しよう」という解説をしているところが多い. 理屈としてはこれで最も柔軟な運用ができるのだが, 私の体感として,一度でも映像と音声を分離してしまうと,かなり注意していても音ズレが起きる. MPEGコンテナには音声再生を開始するオフセット値 (名前は忘れた)があり, この値をきちんと出力のコンテナフォーマットに引き継がなければいけない. 分離や結合の際にオフセット調整を行ってくれるソフトを使えば上手くいくはずであるが,それにはあまり期待しないほうがいい. しかも,音ズレが起きているかを確かめる作業というのはとにかくつらくて, 複数の動画を再生して映像と音声のタイミングのずれ (数百ミリ秒)を検証するのは決して楽ではない.
私も当初はdemuxを行って映像・音声に別々のエンコードソフトを使うなどしていたが, 後になって音ズレ動画を量産していることに気付いた. ネット上にあふれる音ズレ動画はこうして作られているのだなぁと納得した瞬間である. そして結局,MPEG-2を映像+音声のままFFmpegに突っ込んで, 映像+音声としてMPEG-4を出力させる,というのが音ズレが起こらないし簡単だという結論に達した. FFmpeg以外のソフトでも同じことがいえるだろう.
ちなみに,TSに含まれている音声もAACだから,FFmpegなら-vcodec:a copy
とやってしまえば
別に再エンコードをかけずにそのままMPEG-4にmuxできるのではないか,
ということを私は当初考えていたが,色々試してみてもこれはうまくいかなかった.
TSのストリーミング用AACは途中でぶつ切れになっていることがあるらしく,
出力ファイルが再生できないといった問題が発生したので,
今のところは諦めて再エンコードするという方針で落ち着いている.
解像度
解像度とは要するに画面上のピクセル数のことで, エンコードの前処理で縮小処理をかけて解像度を下げれば, 当然ながらファイルは大幅に縮む. しかし,私としては,録画保存用途なら解像度は下げないことを強く推奨したい. 最近ではディスプレイの高解像度化が進んでいて, フルHD画質 (1920×1080)ぐらいなら普通に表示できてしまうので, そういうディスプレイで低解像度の動画を見た時には虚しい気分になること間違いなしである. もちろん,モバイル用途等で必要に応じて解像度を下げることはありだと思う.
デインターレース時の60p化
テレビの放送波は約30fps (正確には29.97fps)の映像が送信されてくるが, その映像はアナログ時代の名残でインターレースと呼ばれる方式に沿っている. 雑に解説すると,画像を縞々の線 ("走査線")に2分割して, 更新する走査線を入れ替えながら動画を再生する仕組みになっている. こんな謎な方式は,地デジ化の際に駆逐してしまえばよかったのではないかと思うのだが, 私は別に仕様策定の事情について知らないのでこの辺にしておこう.
****** 偶
****** 奇
****** 偶 ←全然そうはみえないかもしれないが
****** 奇 6ピクセル ×6ピクセル の画面だと思ってほしい
****** 偶
****** 奇
インターレース (60i) ← 画面半分が1秒に60個
1/30 秒
_______________
| |
++++++ ++++++
------ ------
++++++ ++++++
------ ------ ...
++++++ ++++++
------ ------
偶 奇 偶 奇
---> 時間t
プログレッシブ (30p) ← 画面全体が1秒に30個
1/30 秒
_______________
| |
++++++ ++++++
++++++ ++++++
++++++ ++++++
++++++ ++++++ ...
++++++ ++++++
++++++ ++++++
---> 時間t
インターレース方式の映像を,フレーム毎に全ての走査線が更新されるように変換し, プログレッシブ方式の映像を得るのがデインターレースである. FFmpeg始め多くの動画変換ソフトには,yadifという有名なデインターレースのアルゴリズムが実装されている. デインターレースも実は奥深いことがWikipediaを読むとわかる.
デインターレースで一般的によく使われているのがyadif=0
として30pの動画に変換する方法なのだが,
上で示しているように偶奇のコマのタイミングが微妙に違うので,これらのコマを混合するような処理を行っている.
そのため,例えば上下に移動するテロップのような連続したコマがブレて見えるようになる.
ネット上には,これを防ぐためにフィルタをかけるといった手法も散見されるが,下手に動画を加工することはやめておいたほうがいい.
「アニメは元々24fpsで作られているから24pに変換しよう」などとフレームレート変換を試している人もいるが同じことである.
私としては,yadif=1
としてフレーム数を倍にし,60p化すること (Bobデインターレースともよばれる)をオススメしたい.
60fpsになったからといって動画サイズが2倍になるわけではなく,
フレーム間の差分が小さくなることから,Bフレームなどの仕組みが働いて上手く圧縮されるためである.
30fpsの動画と比べても1.1倍程度のサイズで収まるはずである.
(ちなみに60p化というのは,YouTube等にネタとして上げられている,映像がぬるぬるになるフィルタとは全く異なる.)
60p化の問題点は,フレーム数が単純に2倍になるためにエンコード時間も約2倍になってしまうことである. その点からみると,画質を優先してエンコード時間を犠牲にする設定であるといえる. また,ほとんどの再生機器は60pの動画を再生できるが,再生機器がヘボいと結局テロップはカクカクしてしまうかもしれない.
60pにしたからといって,データを補間している都合上はデインターレースによるノイズ (アーティファクト)が完全に消えるわけではない. 普通のテレビ受像機はその辺うまいことやっているように見えるので,詳しい方がいたら教えてほしい.
x264のオプション
私が最も重要だと思うポイントは,「自分で意味が分からないオプションは付けない」というものである. x264の開発者は我々よりもずっと動画エンコードに詳しいのだから, 可能な限りx264のデフォルトの設定をそのまま活かすべきだろう. オプションの意味を理解して,利点と欠点がある程度明確になったら,その段階で初めて調整するのが賢明である.
とはいうものの,私も未だによく分からないオプションが多くあって,ネットの情報を元に微調整したものがいくつかある. それらのオプションは,奇をてらわずにデフォルトのパラメータからわずかに違う値に変更するようにしている.
もう一つ,上で言ったことと既に相反するのだが,
「x264のチューニング *5は使わない」という点も重要である.
例えば,チューニングにanimation (--tune animation
)というものがあって,いかにもアニメエンコードに有用そうに見えるわけだが,
実際には極端な設定値になっていることで有名である.
これはx264を使い始めたエンコ職人見習い用の罠であると思ってよい.
オプションをきちんと理解した上で設定するなら構わないが,
そのレベルに達した頃にはチューニングのようなデフォルトの設定集を使う必要はなくなっているはずである.
CRFとqcomp
x264での基本は,Constant Rate Factor (CRF)という値に基づいて,画質を一定に保ちながらエンコードするシングルパスの手法である. x264には他にも2段階エンコードというものもあるが, これは1段目のエンコードでファイルサイズを予測するための仕組みであり, 容量に特段の制約がある場合のみに使用すべきである. 2段階でエンコードしたからといって,CRFでエンコードした時よりも画質が良くなるわけではない.
CRFの値は完全に好みの問題で,各々がどの程度の画質で妥協できるかに依っている.
私は画質とファイルサイズを比較対照しながら,
アニメならcrf=23
(FFmpegのデフォルト値!)ぐらいが一番コスパがよいのではないかと思って今はそう設定している.
CRF比較用の画像を用意したので参考にして欲しい. 甘城ブリリアントパークのOP (1分30秒)を切り出した後にエンコードをかけている.
time ffmpeg -i input.ts -crf 18 -preset slow -filter:v yadif=1 -x264opts keyint=600:no-psy -ssim 1 crf=18.mp4
左の絵がオリジナルで,右の絵がエンコード後の画像である.上の方のCRFが小さいものほど画質がよい.
静止画をパッと見ただけでは全然違いが分からないのだが,動画で見るともっと歴然とした違いがある.
私の目でも,拡大してみるとcrf=25
ぐらいまで下がると明らかに線がガタガタしているようにみえる.
個人的にはcrf=23〜24あたりが人間が知覚できるかどうかの微妙なラインなのではないかと思っている.
ところでラティファ様はかわいい.
元画像が大きすぎて記事編集中に既にブラウザがクラッシュしかけたので,元画像は下記リンクを参照のこと.
画質に影響を与えるもう一つのパラメータはqcompである.
qcompは,映像の変動率が大きいところにどの程度ビットレートを割り振るかを決めている値 (0.0〜1.0)である.
qcompが高いほど,動きの激しい箇所を優先して大きくビットレートを割り振るので,
画質が向上する代わりにファイルサイズが増加する.
qcompの値はデフォルトが0.6で,ネット上では少し高めにしている設定例 (0.7とか)が多く見られるのだが,
私の目ではqcomp=0.7
にしてもファイルがブクブク太る割に目立った効果が感じられなかったので,
qcomp=0.6
のままにしている.
crfとqcompの値に関しては,私は色々妥協を重ねた結果こうしているのだが, HDDの空き容量が余りまくっていて満足できないという人は画質を上げるべきである. 私も昔はcrf=20ぐらいに上げた設定を使っていたのだが, 本当に好みの番組は再エンコードせずにTSのまま保存したくなってしまうということに気付き, エンコード後の超高画質を目指すことはあまり意味が無いのではないかと思うようになった. かといって,後でから見返す気にならないほどの低画質は耐え難いので, 妥協点は常に探っていく必要がある.
プロファイルとレベル
H.264でハイビジョン画質をエンコードするには,基本的にハイプロファイルを使用する. 「メインプロファイルの方がよい」と力説しているサイトも散見されるが, SD解像度のエンコードについて解説しているようで, 解像度そのままでフルHD解像度でエンコードするなら素直にハイプロファイルにすることをオススメする.
H.264にはレベルという概念もあり, 再生機器を限定している必要条件になっている. そのため,レベルを無意味に上げ過ぎると再生可能な機種が限定されるが, 別に画質が向上するわけでもないので何の利点も無い. レベルを決める場合は,下記のWikipediaの表を参考にして, 入力映像に対応した最低のレベルにする.
https://ja.wikipedia.org/wiki/H.264#.E3.83.AC.E3.83.99.E3.83.AB
ディジタルテレビのビットレートをカバーしているのはHigh Profile Level 4.0ぐらいからだから, これぐらいに設定しておけば問題はないだろう. *6 このレベルを再生できないような太古のモバイル機器を使っている人は…… 新しい端末に買い換えたほうがいいかもしれない.
ref, bframes
MPEGの基本的な仕組みとして,近い時刻のフレーム群は似ている場合が多いので, 近くのフレームからの差分のみを記録することでデータを圧縮している. どこからの差分を記録するかでフレームの種類が分かれる.
- Iフレーム: 画面全体を保持しているフレーム.キーフレームとも.
- H.264の場合,シークなどの切れ目になっているGroup Of Pictures (GOP)という単位の先頭のキーフレームはIDRと呼ばれて特別扱いされる.IDRじゃないIフレームからはシークができない.
- Pフレーム: 以前のフレームからの差分を保持しているフレーム.
- Bフレーム: 以前・以後からの差分を保持しているフレーム.
ref
(FFmpegだとrefs
)は「参照フレーム数」のことで,エンコード時にIフレームやBフレームが参照できる元フレームがどれだけ離れていてもよいかを示す.
refを増やせば増やすほど最適なフレームを選択できるのでビットレートを削減できるが,
試行するフレーム数が増える分だけエンコード時間はどんどん伸びていく.
再生時には各フレームをメモリに保持していなければいけないので,
refを上げれば上げるほど再生負荷も増大する.
bframes
(FFmpegだとbf
)は「Bフレームの最大連続数」を表していて,その性質上ref以下である必要がある.
bframesも増加させるとビットレートを削減できる一方,
エンコード時間と再生負荷が増大する.
私が色々試した限りでは,--ref 4
, --bframes 4
ぐらいが妥当なようである.
ちなみにこの文章を書いていて初めて気付いたが,Level 4.0で1080pの動画をエンコードする場合のrefの最大値は4と決まっているらしい.
あと1増やしていたら規格外の動画を生産していたようだ.
keyint
keyint
は「IDRフレーム間の最大フレーム数」である.
keyintを上げるとIDRフレームが少なくなるため,ファイルサイズが削減できる.
その代わり,再生時にはIDRフレームからしかデコードが開始できないため,
keyintを上げるほどシークが遅くなっていく.
一般的に,keyintの値は「フレームレート×10」(10秒間隔でならシークが高速に終わる)ぐらいにするのが無難であるという説が有力である.
yadif=1
で60pにデインターレースした映像をエンコードする場合は,--keyint 600
ということになる
*7.
yadif=0
ならkeyint=300
で,この数値のほうがネットではよく見かける.
デフォルトのkeyintは250となっているが,これはおそらく海外で使われている25pの規格に合わせているのだと思う.
高画質のためのオプション色々
x264にはプリセット (チューニングとは異なる)という予め用意された設定集があって, サイズと負荷のトレードオフを決めることができる. 画質とファイルサイズ重視なら,使うのはだいたいslow 〜 veryslowぐらいになるはずで, 細かいことを気にしないならプリセットを使ってもいいかもしれない. プリセットを参考にしながら私が調整した値を以下に示す.
--b-adapt 2
--me umh
--subme 8
--trellis 2
- 2だとエンコード時間と再生負荷が激増する,と解説するサイトが多いが,高画質にするなら2が無難.
--partitions all
- 画像のブロック分割の手法らしい.細かく指定できるのだが,とりあえず全部オン.
--direct auto
- Direct MV prediction modeというもので,モーションベクトルの予測のモードらしい.
auto
以外にしているのはあまり見たことがない.
- Direct MV prediction modeというもので,モーションベクトルの予測のモードらしい.
--rc-lookahead 50
- 先読みに関係しているらしいがあまり良く知らない.
- slowだと50,slower以上だと60になっている値.微妙すぎて違いがよく分からず.60p用に変えたほうがいいのかもしれない.
psy-rd
psy-rdは,心理的に複雑に見える箇所の圧縮率を下げることで,画質が上がったかのように見せるためのオプション.
アニメのような平坦な映像に対しては低めの値にしたほうがいいらしいので,
私はデフォルトの1.0から少し下げた--psy-rd 0.5:0.0
にしている (比べてみても違いはよくわからない).
psy-rdに関しては以下のサイトが詳しい.あくまで心理的な効果を狙ったものなので,
再現性を追求するならオフにするのが望ましいらしい.
そのうち気が変わったら--no-psy
にするかもしれない.
https://encoders-jpdoc.rhcloud.com/#see_psy-rd
ちなみにpsy-trellisという実験的な機能もあるのだが,こちらはオフ (psy-rdの0.0の部分)にしている.
aq-strength
aq-strengthはAdaptive Quantization (AQ)という画面上のビット再配分をする機能の強度を変える値で,デフォルトは1.0.
下げたほうが輪郭が綺麗に残りやすいらしくアニメ向きらしいので,私は少し下げて--aq-strength 0.8
にしている.
個人的にはこれも変化が分かりづらいパラメータの一つだと思う.
AQのモードを変えるaq-modeというオプションもあるが,これは試したことはない.
独断と偏見によるプリセット解釈 その1:因幡製作所業務マニュアル - ブロマガ
その他微妙なオプション
以下はさらによく分からないオプション集で,どれかに決めなければいけないので適当に決めたものである. そのうち微調整するかもしれない.
--min-keyint 4
- keyintは「IDRフレーム間の最大間隔」だったのに対し,min-keyintは「IDRフレーム間の最小間隔」.
- 小さめの値に設定しておくとシークが楽になるが,1にしてしまうとGOPが切れまくってビットが無駄遣いされるらしい.そんなわけで4にしたはずだが,それ以上明確な根拠は多分ない.
--scenecut 60
- どのぐらい積極的にIDRフレームを挿入するかを示す値で,デフォルトは40.
- 多めにIDRを挿入したほうが後々編集など扱いやすいという理由でこうしたような気がする.デフォルトでいい気がする.
--deblock
- デフォルトのまま.デブロックフィルタについては詳しく検証していない.
--merange
- これもデフォルト (16)のままにしている.プリセットだとslowerでも16で,veryslowからようやく24に上がる.
FFmpegでの例
最後に,これまでの設定をまとめてFFmpegに与える例を示す. x264とFFmpegで微妙にオプションの名前が異なることに注意.
-threads 6
となっているのはスレッド数で,コア数×1.5にするのが無難と言われている.
-movflags +faststart
は,ストリーミング用にメタデータを先頭に移動しておくオプションである.
ffmpeg \
-i "input.ts" \
-codec:v libx264 \
-profile:v high -level 4.0 \
-filter:v yadif=1:-1:0 \
-crf 23 \
-qcomp 0.6 \
-refs 4 \
-bf 4 \
-aq-strength 0.8 \
-psy-rd 0.5: \
-x264opts \
keyint=600\
:min-keyint=4\
:scenecut=40\
:b-adapt=2\
:direct=auto\
:me=umh\
:subme=8\
:trellis=2\
:rc-lookahead=50\
:partitions=all \
-codec:a libfdk_aac \
-b:a 192k \
-threads 6 \
-movflags +faststart \
"output.mp4"
雑感
*1:古めの規格なのでそもそも圧縮効率があまりよくなく,ストリーミング用なので圧縮率も低めに設定してある,はず.
*2:本当は再エンコードというのはTSをデコード→MPEG-4にエンコードという2段のフェーズを経ているから, 放送局がTSにエンコード→変換ソフトウェアのフロントエンドがデコードというフェーズを経る時点で本来の画質よりも落ちている.
*3:しかしただのガワといえるほど単純なシステムではない.
*4:x264がハードウェアの機能を一部借りれないかというアイディアは昔からあるらしく,例えばQSVに関してはIntelが仕様を公開してくれないとかでうまくいっていないという話をどこかで見た.
*5:紛らわしいのだがチューニングとプリセットは異なる.
*6:と断言したはいいものの,本当にこのレベルで規格内なのかこの文章を書いている間に不安になってきた.