TS packet 格闘日記

アニメを見ながら TS ときゃっきゃうふふするお仕事。 github: https://github.com/github-mec/mecenc twitter: https://twitter.com/mecenc

10bit エンコード時に一部のプレイヤーで白が黒くなる問題と回避策

具体的には Android の MX Player において、SW デコーダで 10bit color depth の動画を再生すると白いはずのピクセルが黒で表示される問題が発生する。調べてもよく分からなかったのでバグじゃないかと MX Player のフォーラムで質問してみたところ、YCbCr の Y 成分の範囲がおかしいんじゃね?との回答が返ってきた。

[Bug Report] White pixels are rendered as black in 10-bit depth video. - XDA Forums

補足しておくと、放送波で使われる BT.709 色空間において、輝度信号の範囲は 0-255 ではなく 16-235 であり、235 を超える範囲は「whiter than white」な状態である。どう描画するかは受信側次第。

通常アニメ本編はこの 16-235 という範囲を守っており問題になることは少ないが、OP/ED におけるスタッフ一覧などのクレジット文字列はこの範囲を超えた値を含むことが散見される。10bit color depth の動画の場合、MX Player (1.7.36a) の SW デコーダはこれらを黒く描画してしまう。そのためエンコード時に輝度の値を 16-235 の範囲に切り取ってしまうことでこの問題を回避できる。ffmpeg では lutyuv フィルタを使えば良い。

$ ffmpeg -i input.ts -filter:v lutyuv=y=clipval output.mp4

https://www.ffmpeg.org/ffmpeg-filters.html#lut_002c-lutrgb_002c-lutyuv

この回避策には再エンコードが必要なため、できれば MX Player 側で workaround を入れて欲しい。


追記
既存の動画については、MX Player の設定→デコーダー→カラーフォーマット を RGB 32bit にすれば問題を回避できる。しかし再生負荷が増大するなどの問題がある。

ffmpeg で動画から指定秒数毎に正確にサムネイルを抽出する

ffmpeg で指定秒数毎にサムネイルを抽出する方法が Web 上にいくつか転がっていたが(-r 使ったり -filter:v select 使ったり)、どうもまともに動くものが見当たらない。正確に言うと、先頭付近のフレームの扱いが微妙だったり、遅かったり、無駄にコマンドが複雑だったりする。

まともな方法を探して FFmpeg Filters Documentation を漁っていたら fps フィルタでどうにか出来た。サンプルは次の通り。

# 1 秒毎に 1 枚ずつ、0001.jpg, 0002.jpg, ... というファイル名でサムネイルを抽出
$ ffmpeg -i input.ts -filter:v fps=fps=1:round=down %04d.jpg
# 同じく 2 秒毎
$ ffmpeg -i input.ts -filter:v fps=fps=0.5:round=down %04d.jpg

round=down の部分でどのように n 秒毎を定義するかを指定している。down だと端数を切り捨て、up だと切り上げ、round だと近い値にまるめる、つまり四捨五入のような処理が行われる。個人的なおすすめは down である。何故なら up や round だと先頭フレームだけ他とは違う間隔でサムネイルが抽出されるからである。例えば 30fps の動画から round=up で 0.5 秒毎にサムネイルを抽出しようとすると、先頭 2 フレームのタイムスタンプ 0.000 秒と 0.033 秒となり、round=up により 0.033 秒は 0.5 秒に切り上げられ、結果として先頭の連続する 2 フレームが共に出力されてしまう。

もし放送波データを扱い、かつサムネイルを切り出す場所の正確性が問題となる場合、あらかじめ映像と音声を分離しておいたほうが無難である。何故なら映像と音声の開始時刻が違うために、切りだされる位置が想定よりずれるからである。これは Web 上に転がっている他の方法を使う場合にも同様に問題となる。これを回避するために、実際には次の手順を踏むことになる。

# 入力を映像と音声に分離
$ ffmpeg -i input.ts -vcodec copy -an input_video.ts -vn -acodec copy -input_audio.ts
# 映像と音声の開始位置のズレを確認。 start: x.xxxxx と書かれている場所がある。
# 音声と同期を取りたい場合に参照する。今回は無視しても構わない。
$ ffprobe in_video.ts
# 1 秒毎にサムネイルを抽出。
$ ffmpeg -i input.ts -filter:v fps=fps=1:round=down %04d.jpg

この方法だと比較的高速かつ簡単にサムネイルを抽出できる。mecenc では今のところこの方法と crop フィルタを組み合わせて局ロゴ検出に利用している。

Seeking is hard (ffmpeg とシークの話)

ffmpeg で動画の中の特定の場所に飛びたい場合、次のようにすることが多い。

$ ffmpeg -ss seek1 -i input_file -ss seek2 ...

seek1と seek2 はともに時刻を指定する。便宜上、-ss seek1 を前置 ss、-ss seek2 の部分を後置 ss と呼ぶ。どちらも指定時刻へシークするのだが違いがある。前置 ss は高速で不正確、後置 ss は低速で正確だ。次のコマンドを実行すると内部が透けて見える。入力ファイル名は仮に input.mp4 とする。

$ ffmpeg -ss 1 -i input.mp4 -vframes 1 -filter:v showinfo -f null -

$ ffmpeg -i input.mp4 -ss 1 -vframes 1 -filter:v showinfo -f null -

コマンドの意図は次の通り。

  • 動画を開始から1秒後の地点までシークして、そこの画像1枚の情報を showinfo フィルタで標準エラー出力に表示する。
  • 処理した動画は null 形式で標準出力に出力、つまり捨てる。

2つのコマンドは同じ結果を出力するように見えるが、結果は違う。前置 ss は 1 枚分の情報を出力するのに対し、後置 ss はスキップした画像の情報も表示している。おそらく前置 ss では動画の詳細は見ずに動画コンテナに格納されている時刻情報を元にシークしており、後者は画像を 1 枚ずつ処理していると思われる。実際、動画データを変更せずに別のコンテナに詰め替えるだけで前置 ss の正確性は変化する。放送波の TS ファイルはとても大雑把な精度でしかシークできない。また後置 ss も完全に正確なわけではなく、残念ながら数フレームの誤差が発生しているようだ。時刻をフレーム数に変換する部分が変なのかもしれない。

mecenc ではフレーム単位で CM 検出のために動画の解析を行っている、つまり解析箇所に移動するためにシークする必要がある。しかし前置 ss のシークは不正確すぎるし、後置 ss は遅い上に誤差がある以上使えない。よって別の手段が必要である。mecenc では CM 候補の列挙に無音区間を利用しているため、音声解析を先に終わらせておけば解析したい動画の位置は事前にすべて知ることができる。よってファイル全体を一度舐める間に trim フィルタを複数使って、まとめて画像情報を取得している。毎回シークすると気が遠くなるような時間がかかるが、一度全体を舐めるだけであれば許容範囲な時間で収まる。

$ ffmpeg -i input.mp4 \
    -filter:v trim=start_frame=AA:end_frame=BB out/000/%04d.jpg \
    -filter:v trim=start_frame=CC:end_frame=DD out/001/%04d.jpg \
    ....
    -filter:v trim=start_frame=YY:end_frame=ZZ out/012/%04d.jpg

これで正確なシークがそこそこの時間でできるようになった、がニッチ過ぎて参考にできる人は少ないように思う。というかシークするだけでこんな手間をかけさせないで欲しい…

本出来ました

なんとか完成。コピ本だけど。ということでお暇な人はコミケ3日目、火曜日西い32bまでお越しください。

f:id:injury-time:20141228135236p:plain

以降本文中の「本書の要約」よりコピペ。

CM の検出・削除からエンコードまでを自動化するソフトウェア mecenc を実装した。またその過程において放送波における CM らしさについての考察を行った結果、CM 候補の有効な検出方法や CM 開始時刻に関する規則性、インターレースが映像に及ぼす影響など数多くの興味深い性質が明らかとなった。2 週間分のアニメを用いて検出精度を検証した結果、79% のアニメにおいて全ての CM を正確に検出でき、今後の改善により 84% 程度まで向上できる見通しが立った。放送局別に見ると、深夜アニメが多い Tokyo MX では現状 91% のアニメで成功しており、将来 95%を超えるまでに改善できる見通しが立った一方、一部放送局では完全に CM を取り除けた事例が無かった等、mecenc の CM 検出の精度は放送局に強く依存していた。全ての CM を安全確実に取り除くまではいくつかの大きなブレイクスルーが必要である。

CM 検出案

現状のCMカットの流れ

 

  1. TS から音声を抽出。
  2. 音声から無音区間を抽出
  3. 無音区間が15 or 30秒間隔で2つ以上並んでいると CM 候補。5 or 60秒 CM は現状残念ながら無視
  4. CM 候補のシーン切り替わりを適当に検出
  5. シーンの切り替わりが適切な間隔で並んでいればそこが CM。

当然、映像はシーン切り替わりの時にしか見ていないからそんなに精度は高くない。2-3本30分アニメを処理するとそのうち1本は失敗している。主な失敗要因は以下。

 

シーン切り替わりを正確に検出するのが難しい

映像だけから判断すると、黒フレームで終わるシーンと黒フレームで始まるシーンの繋ぎ目を正確に判断するのは困難。他にもシーン冒頭で激しく動く映像があるとそこを繋ぎ目と誤判定してしまう。結果 CM を切り損なう。

次回予告や提供のシーンを間違って切ってしまう

これらのシーンは15秒であることが良くあり、結果現状のアルゴリズムだと誤判定する。

5秒 or 60秒の CM を切り損なう

対応してないから仕方ないね。対応するのは簡単だけど本編が誤爆で削除される可能性が高くなるのでどうしたものか。

音ズレ

一部のアニメの音がずれて CM 候補の列挙に失敗する。ある程度は対策したけど駄目なものもいくつか。

 

シーン切り替え判定以外ほとんど画像を参照していないけど、意外と本編の誤判定は少ない、というかほぼ無い。本編中に無音な場所ってほとんどないし、あってもシーン切り替わりよりは同じシーンで純粋に音が無いシリアスシーンだったりで、問題ないことが多い。

今後の方針

というかざっくりとした改善案。

  • ウォーターマーク(局ロゴ)があればそこは本編
  • "提供" の文字があればそこは本編、というか提供画面。局ごとにフォント固定?
  • シーン切り替わりの時刻を参照。製作の都合か1秒単位でアラインされているっぽい
  • ステレオ or モノラルって本編判定に使えるの?
  • シーン切り替え場所がインターレースしてると、その前後の少なくとも一方は本編?
  • シーン切り替えの検出方法の改善

局ロゴ検出は早く実装しろよって感じだけど。冬コミまでにどこまで実装できるんかいな。

 

はじめました。

コミケに当選してしまったので重い腰を上げました。

地デジの自動CMカット機能付きエンコードスクリプト for Linux をちまちま作っているので、それの開発ログでも書いていきます。

あとCM検出&カットのノウハウをまとめた解説本をコミケに出す予定なので気が向いたら遊びに来てください。

12月30日、3日目火曜日西い32b 「ろすたいむ」です。よろしくお願いいたします。

 

とりあえず現状の状態を as is で github に上げました。

現状CMを完全に除去できる可能性は50-60%といったところなので、コミケまでには95%くらいまであげたいなーと妄想中。できるんかいな。

https://github.com/github-mec/mecenc