Skip to content

Latest commit

 

History

History
475 lines (423 loc) · 41.3 KB

validation.ja.md

File metadata and controls

475 lines (423 loc) · 41.3 KB

検証結果と既知の制限

← ドキュメント ・ ← SmartCut ・ English

検証結果

tests/run_tests.sh は出力を復号してソースのフレームハッシュと突き合わせ、 フレーム数・整列・ビット単位で一致したフレーム数を検査する。

ケース 無劣化コピー率
H.264 単一区間 180/222(81.1%)
H.264 複数区間 300/336(89.3%)
H.264 中抜き(末尾まで) 540/540(100%)
H.264 アクセスポイントちょうど 180/180(100%)
H.264 区間が GOP より短い 0/21(全編再エンコードにフォールバック)
HEVC 300/342(87.7%)
H.264 29.97 fps 300/342(87.7%)
H.264 オープン GOP(leading picture が参照される) 0/342(開始点として使わない。パラメータセットが avcC にしか無い MP4 なので、nal_ref_idc だけで判定する。難所 3)
MPEG-2 TS オープン GOP 328/342(95.9%)
MPEG-2 TS 複数区間 296/300(98.7%)
MPEG-2 TS 末尾まで 447/449(99.6%)
Matroska 出力 180/222(81.1%)

13 ケースすべてでフレーム数と整列が一致している。

実素材での検証

合成素材では見えない問題があるので、実際の放送録画に対しても検証している。 tests/verify_real.py <src> <out> <ranges> が、フレーム数・整列・ビット一致率・ タイムライン・インターレース・音声と映像の長さの差をまとめて検査する。

素材 結果
地上波 NHK E テレ(MPEG-2 1440x1080i、真の 29.97) 899/899、98.2% 無劣化、タイムライン一致、インターレース保持、A/V 2.0 ms
BS11(MPEG-2 1920x1080i) 899/899、98.2% 無劣化、上と同じ
AT-X(MPEG-2 1440x1080、2:3 プルダウン) 719/719、99.9% 無劣化、2:3 パターン保持、A/V 2.0 ms
VC-1 のプレス Blu-ray(1920x1080i アニメ)、GOP 途中〜GOP 途中 10 秒 308/308、映像の 90% がバイト一致、部分 GOP は 48 dB で書き直し
VC-1 のプレス Blu-ray(1920x1080p 実写・強いグレイン)、同 10 秒 246/246、90% がバイト一致、45 dB で書き直し、グレインも残存
YouTube の H.264 720p(29.24 fps) 878 フレーム、A/V 2.6 ms
VP9 + Opus YouTube の 1080p23.98、2 区間・4 つの端すべて GOP の途中 432/432、バイト一致 54.9%。計画が示した割合と 0.1 ポイント以内で一致
VP9 + Opus 352x240 29.97、GOP の途中から途中まで 30 秒 900/900、バイト一致 82.9%。libvpx でも libavcodec 内蔵の VP9 でもエラー無く復号
AV1 + Opus 720p30、GOP の途中から途中まで 28 秒 840/840。dav1d でも libaom でもエラー無く復号
可変フレームレート、間隔 33ms〜2.4 秒、30 秒 1 区間 505/505 が録画と同じ位置(0.8.0 より前は 46 枚が 1 フレーム早かった)。長さも 30.0 秒(以前は 28.1 秒)
可変フレームレート、間隔 16ms〜117ms、30 秒 1 区間 628/628 が録画と同じ位置(以前は 598 枚が最大 3 フレームずれていた)
23.976 に 59.94 が混じる配信素材(24 分、H.264)、3 区間 6294/6294、間隔の誤差は最大 0.6ms。0.8.0 より前は 11 枚が欠落し、間隔も 227 か所(最大 45ms)狂っていた
同種の HEVC 10bit 1440x1080、50 秒 1 区間 1259/1259、間隔誤差 0.6ms(以前は 8 枚欠落、位置ずれの中央値 12ms)
AV1 10bit + Opus の配信素材(23 分、GOP 33 秒)、2 区間 6712/6713、間隔誤差は 1ms 以内、dav1d でエラー無く復号

VC-1 のカットだけは、検査の方法を変えるほかない。 verify_real.py は 2 つの ファイルをフレーム番号で突き合わせる。ところが Blu-ray から切り出した断片では、 その番号が録画と対応しない。GOP の途中から始まるのでデコーダが復号できない ピクチャを捨て、そこから先はその枚数だけずれるからである。

そこで tests/run_vc1_tests.sh はバイト列で確認 する。カットの長い一続きが、録画の中にそのまま現れるかを見る。これなら整列は 不要である。自分で書いたピクチャについては、一般的なプレーヤーが使っているデコーダに 通し、置き換えた元のピクチャからどれだけ離れているかを測る。両方が必要である。 わずかに壊れたビットストリームでも復号はできてしまうし、変換の正規化を間違えた ストリームは、明るさだけが違う映像として、何の警告もなく復号されてしまうからである。 エンコーダと画質への影響は Rust コアにまとめてある。

この検証の過程で、実際の TS にありがちな厄介事はひととおり出てきた。

  • start_time が 29288 秒(PCR が実時刻に基づいているため)
  • すべてのアクセスポイントがオープン GOP(776 個中 776 個。leading picture は 捨ててよいもの)
  • フレーム欠落(668 MB の録画に 28 個。受信のドロップアウトによる)
  • 複数ストリームの共存(ARIB 字幕、データ放送など)

音声を何フレーム再エンコードしたか

tests/run_aac_tests.sh は、映像に適用しているのと同じ基準を音声に当てる。出力の ADTS を 1 フレームずつ辿り、録画自身のバイト列と比較する。素材は日本テレビの録画、 2 区間・境界 4 か所である。

モード コンテナ そのままのフレーム 再エンコード ADTS
copy TS 5606/5606(100%) 0 すべて MPEG-2 LC
smart TS 5602/5606(99.929%) 4 すべて MPEG-2 LC
smart MP4 5602/5606(ペイロード) 4 該当なし
reencode TS 0/5607 5607 すべて MPEG-2 LC
smart --aac mpeg4 TS 5602/5606 4 すべて MPEG-2(下記参照)
reencode --aac mpeg4 TS 0/5607 5607 すべて MPEG-4
smart、CM ブロックを 1 つ切り落とし、境界 4 か所すべて無音 TS 1392/1392(100%) 0 すべて MPEG-2 LC

最後の行が通常のカットの姿であり、そこでは出力が copy とバイト単位で一致する。

フレームをコピーしているかぎり、録画と違う種別の AAC を指定されても、2 種類の AAC が 混在するストリームになるので応じられない。指定は注記つきで受け付けず、録画自身の 種別に従う。全編再エンコードなら何もコピーしないので指定に従う。それが reencode --aac mpeg4 の行である。

MP4 は ADTS のフレーミングを捨ててペイロードだけを保持するので、そちらではペイロード を比較する。ペイロードが同じなら音も同じである。

実素材で見つかったバグ: 再エンコード部でインターレースが失われる

再エンコードされた部分 GOP だけが interlaced_frame=0 で出力され、コピー部分と 櫛の状態が食い違っていた。原因は AV_CODEC_FLAG_INTERLACED_DCT と INTERLACED_ME、 およびフィールド順をエンコーダに渡していなかったことである。放送素材ではカット点 ごとに画質が変わることになるので致命的である。修正済み。

2:3 プルダウン対応(フィールド単位のタイムライン)

アニメの多くは 24 fps のフィルム素材を、repeat_first_field によって 29.97 の ストリームに載せている。したがって復号されたピクチャは 0.0334 秒と 0.0500 秒の 間隔が交互に来る。ピクチャ単位で見れば、このストリームは CFR ではない。

手持ちの録画で測ったところ、40 本中 13 本(32%)がプルダウンだった。

局 プルダウン率
AT-X 8/14(57%)
ディズニーチャンネル 2/2(100%)
BS アニマックス 3/10(30%)
BS11 / BS-TBS / BS 日テレ / NHK / キッズ / テレ朝ch2 0%

無視できる頻度ではないので対応している。要点は出力タイムラインの単位をフレーム からフィールドへ落とすことである。プルダウンは 2 フィールドと 3 フィールドの 繰り返しなので、フィールド格子の上なら整数で表現できる。出力のタイムベースは 1/(2 × fps の分子)(29.97 なら 1/60000)となり、1 フィールドがちょうど fps の分母 ティックになる。

副作用として、DTS の作り方も変える必要があった。各ピクチャの継続時間を復号順に 足していく方式は、フィールド長が変動すると表示時刻を追い越してしまい、 マルチプレクサに pts < dts で受け付けられない。表示順の位置から DTS を導く正しい 構成に置き換えた。

作り直した区間のフィールドの繰り返し

フィールド単位のタイムラインは、作り直したピクチャを正しい位置に置いていた。 ところがピクチャ自身はそれを表していなかった。 libavcodec の MPEG-2 エンコーダは repeat_first_field をまったく書かない。そのため、フィルムの区間を作り直すと どのピクチャも 2 フィールドになり、長さはタイムスタンプにしか残らなかった。 タイムスタンプで表示する再生ソフトでは正しく見える。一方、フィールドを数えて表示する 機器(セットトップボックスのデコーダやディスクプレーヤー)には、2 割短い区間と、 後ろのコピー部分とつながらないフィールドの上下が渡っていた。 さらに、最初のピクチャがたまたまフィルムの録画では、コンテナが field_order を プログレッシブと報告する。エンコーダもプログレッシブで開かれ、作り直した区間だけが progressive_sequence=1 を名乗っていた。コピーしたピクチャは 0 である。

フィールドを繰り返してよいのは、フレームとして符号化したピクチャ (progressive_frame と frame_pred_frame_dct が立っているもの)だけである。 libavcodec がそれを書くのはプログレッシブのシーケンスに限られる。そこで、繰り返しを 含むセグメントはプログレッシブのシーケンスとして符号化し、書き出すときに録画の シーケンスへ戻す。シーケンス拡張には録画の progressive_sequence を、各ピクチャには 元のピクチャのフィールド順と繰り返しを書く(Mpeg2Display、 bitstream::mpeg2_set_display)。繰り返しがあるかどうかは、エンコーダを開く前に パケットから読む。フラグは各ピクチャの先頭にそのままのバイトで置かれている。 frame_pred_frame_dct の立ったフレームピクチャは dct_type も frame_motion_type も持たないので、マクロブロックの読み方はどちらのシーケンスでも同じである。

AT-X のクリップ 2 本を切り、エレメンタリーストリームを tests/mpeg2_fields.py で 調べた結果(tests/run_pulldown_tests.sh):

範囲 必要なフィールド数 0.8.6 0.8.7
フィルムのクリップ、全体を作り直し(1.456 秒) 87 70、progressive_sequence が 1 87
フィルムのクリップ、コピーと末尾(2.002 秒) 120 119、シーケンスが 0 と 1 120
フィルムのクリップ、両端とも GOP の途中 453 452、上下の途切れ 2 か所 453
ビデオからフィルムへ、フィルムの頭 455 441、途切れ 1 か所 455
ビデオからフィルムへ、またぐ範囲 846 833、途切れ 1 か所 846

もともとプログレッシブで開いていた場合は、変わったのはヘッダのビットだけなので、 復号したピクチャは 0.8.6 と一致する(framemd5 で 35 枚中 35 枚)。インターレースから フレーム符号化に切り替えたフィルムの区間は、録画のピクチャに対して 49.5 dB で、 以前と変わらない。フィールドの繰り返しが無い放送はこの読み取りの対象外で、 カットは 0.8.6 とバイト単位で同じファイルになる。

フィルムとビデオのピクチャが混ざったセグメントもフレームとして符号化するので、 その中のビデオのピクチャはフィールドではなくフレームで符号化される。フィールドの 並びを保つための選択で、そうなるのは番組がフィルムとビデオを切り替わる 1 秒足らずの 区間だけである。

シーケンス全体がプログレッシブ(progressive_sequence=1)の録画では、繰り返しは フィールドではなくフレーム単位になり、1 枚のピクチャを 2 フレームか 3 フレーム 表示させる。作り直したピクチャからは、この繰り返しも落ちていた(0.8.7 の前の 点検で発見)。いまは同じ書き戻しで、repeat_first_field に top_field_first を 組み合わせて書く。2 フレームなら 0、3 フレームなら 1 である。

範囲の頭が 3 フィールドのピクチャの途中に来た場合も、フィールドが 1 つ余っていた。 0.8.0 で入れた可変フレームレート用の処理が、そのピクチャを開始位置に置いていた ためである。ピクチャは 3 フィールドを名乗ったまま、次のピクチャが 2 フィールド後に 置かれていた。自分の繰り返しで次のピクチャまで届いているピクチャは、この処理の 対象外にした。出力は 0.8.0 より前と同じく、次のピクチャから始まる。

レコーダー盤はフィールド対で符号化されている(実素材で見つかったバグ)

SONY レコーダーの BD-RE は 1440x1080 29.97 の H.264 を PAFF で書く。全フレームが フィールド 2 枚の対で、デマルチプレクサは 2 枚を別々に渡してくる。放送の 1080i は インターレース素材をフレームとして符号化しているので、放送のコーパスにはこれが 無く、録画ディスクを読むまで一度も当たらなかった。

40 秒の切り出しで、書いた 1842 枚のうち 642 枚が落ちた。コピー区間は最初の 1 枚 からブロックノイズの塊だった。原因は 2 つあり、どちらも 1 枚をフレームと見なした ことによる。各フィールドにタイムライン上 1 ではなく 2 フィールドを与えていた。 そしてフレームを数える並べ替えキューが、2 枚必要なところを 1 枚しか抱えて いなかった。 難所 11 を参照。

キューを半フレーム単位で測り、対を 2 フィールドに置くようにした。同じ切り出しが 1842 枚すべてを書き、コピーしたピクチャはすべて録画とビット単位で一致する。63 秒の コピーから 855 フレームを突き合わせた。食い違うのは再エンコードした先頭だけで、 そこは食い違って当然の場所である。ディスクの古い方、シーケンス単位の経路でも同じ 結果になった。

フレーム符号化の素材はこの変更の影響を一切受けない。これは理屈ではなくバイト列で 確かめた。放送録画の切り出しは、変更の前と後で md5 まで同じファイルになる。30 秒の 間に 7 組だけフィールド対を持つ録画も含めてである。libavcodec の MPEG-2 パーサは 各組を 1 パケットに結合するので、これは半フレーム 14 枚ではなくフレーム 7 枚であり、 以前とまったく同じものが出てくる。

再エンコードした先頭の後ろでコピーのピクチャが消えた(解決済み)

H.264 の先頭を再エンコードすると、x264 が書いた IDR から始まり、カウントは 0 から 数える。オーサリング盤とレコーダー盤では、その後ろのコピーがリカバリーポイント付きの I から始まり、シーケンスは再開しない。そのためコピー側のカウントは、先頭の最後の カウントを基準に求まる。それが表示済みのピクチャより小さくなると、libavcodec は コピー側の先頭の何枚かを捨てていた。オーサリング盤で 714 枚中 7 枚、レコーダー盤 (PAFF の 1080i)で 1379 枚中 15 枚である。12 箇所の切り出しでは 718 枚中 698 枚 しか出なかった。ピクチャそのものは正しい。

いまはカウントの逆転が求まったときだけ、先頭の pic_order_cnt_lsb をずらす。 ずらす量は、frame_num の飛びを埋めるために libavcodec が作るフレームと ぶつからない大きさにする。x264 が録画より狭いフィールドで書いていれば、フィールドを広げる。12 箇所の 切り出しは 718 枚すべてが出るようになり、レコーダー盤ではコピーしたフレームが すべて録画とビット単位で一致する。以前に無条件で書き換える方法を試したときは かえって悪化したので、取り下げてある。今回の方法で枚数が減った例は、測った範囲では 無い。付け直しが不要な切り出しは、以前とバイト単位で同じである。 難所 10 を参照。

planner の位相問題(解決済み)

以前は区間の境界を理想格子 round(t*fps)/fps に吸着させていた。これが実際の ストリームのフレーム位相とずれ(テスト素材で 0.010 秒)、区間の端で 1 フレームを 落としていた。

吸着をやめたら直った。現在、セグメントの長さは planner の計算ではなく 実際に書き出されたピクチャから測るので、格子に合わせる対象がそもそも無い。 各セグメントは自分の最初のピクチャを基準に配置され、次のセグメントは前のセグメントが 実際に占めた長さのあとから始まる。これで位相もプルダウンも自動的に追従する。

検証側にもバグがあった

いずれも実素材でのみ表面化した。

  • 正解データをフレーム番号で切り出していた。実際の録画はフレームを落とすので 「フレーム番号 = 時刻 × fps」が成り立たず、比較対象が 27 フレームずれていた。 見かけ上は「一致率 0%」になる。PTS を基準に切り出すよう修正した。
  • 時刻の原点が違っていた。カットは表示タイムライン(フォーマットの start_time、プレーヤーが 00:00 と表示する位置)を基準にしているが、ffmpeg の 復号出力は映像ストリームの start_time を基準にしている。放送録画では音声が 先に始まるので、両者は 0.346 秒ずれる。補正を追加した。

既知の制限

  • 壊れた録画で失われるのは、傷んだ箇所のピクチャであって切り出しではない。放送には 穴が開く(受信の乱れ、アンテナが一瞬衛星を見失う)。そこから 3 つのことが起きる。 libavcodec は傷んだ箇所のパケットを受け取らない。そのパケットが運んでいた ピクチャの表示位置が抜けるので、DTS が越えなければならない間隔が広がる。そして 傷の周辺でタイムスタンプが進まなくなり、複数の音声フレームが同じ時刻を持つ。 以前は、ほかは何ともない録画なのに、この 3 つのどれもが切り出し全体を止めていた。 いまは復号できないパケットを捨て、出力タイムラインに置き場のないピクチャを落とし、 直前より後ろに来ない音声フレームを落とす。字幕文がすでに埋まっているティックに 落ちたときだけは、落とさずに 1 ティックずらす。字幕文は番組の台詞 1 行で、 1 ティックは 90 分の 1 ミリ秒だからである。それぞれ数えて、終わりに一度だけ 報告する。コピーされる部分は、どれにも影響されない。32 局 82 本で測ったところ、 3 つすべてを踏むほど傷んでいたのは 1 本だった。

  • 最初のフレームが 13 ミリ秒早い(Python 実装のみ)。生エレメンタリストリームは タイムスタンプを一切持たない(全パケットが N/A)ので、ffmpeg が -r と POC から 合成する。その過程で最初の has_b_frames 個のパケットが PTS 無しで出てくる。 これを許容するのは MP4 マルチプレクサだけで(-avoid_negative_ts make_zero)、 Matroska と MPEG-TS は受け付けない。MKV 出力を MP4 経由で再多重化しているのは そのためである。2 フレーム目以降の間隔は完全に均一である。libav 実装では パケットごとに PTS/DTS を自分で割り当てるので、この問題は起きない。

  • 後続のピクチャが leading picture を参照している H.264 のオープン GOP は、コピー 開始点として使えない(難所 3)。 leading picture が参照ピクチャであっても、それだけでは使えないことにならない。 パラメータセットがストリームの中にあれば(トランスポートストリームと、そこから 作り直した MP4・Matroska)、leadrefs がエントリポイントより後ろのピクチャの 参照リストを組み立てる。どこにも現れず、frame_num の欠番を埋める推定フレームが 残す B ピクチャのリストに入り込まないときだけ、leading picture を捨てる。 パラメータセットが avcC にしか無い MP4・Matroska と、追跡できないストリームは 従来どおり nal_ref_idc で判定するので、参照ピクチャである leading picture が あれば使えない。 HEVC はもうこれに当たらない。trailing picture が leading picture を参照することを 規格が禁じているので、どのエントリポイントからでもコピーを始められる。 本当に参照されている場合、これは方式に内在する制限である。回避するには leading picture をビットストリームに残したままエディットリストで隠す必要があり、 エレメンタリストリーム連結方式では表現できない。IDR が規則的に入る素材では 問題にならず、放送の H.264 はほぼそれにあたる。

  • トランジション無しで結合したとき、リールとリールの継ぎ目ではカウントを付け直さない (難所 10)。 1 本の録画の中なら、再エンコードした先頭の後ろに IDR でない I から始まるコピーが 続くとき、カウントが逆転する場合と、デコーダが作るフレームがコピー側の I より 上に来る場合だけ付け直す。これで libavcodec がコピー側の 先頭を捨てることは無くなった。一方、あるリールのコピーが次のリールのコピーに 直接つながる継ぎ目には再エンコード区間が無いので、書き換える対象が無い。 HEVC では付け直さない。再エンコード区間の直後や結合したリールの先頭で CRA から 始まるコピーは、その CRA を BLA に書き換えるので、カウントが数え直される。 IDR まで進む --clean-joins は残してあるが、レコーダー盤では役に立たない。idrdiag で 数えると、15 分の録画のエントリーポイント 1786 個のうち IDR は 1 個しかない からである。

  • Python 版の leading picture 参照判定はファイル内の 1 か所を標本にして全体に 適用している(エンコーダが途中で方針を変えないという仮定)。Rust 実装では demux しながら nal_ref_idc を直接読めるので、この仮定は不要である。

  • H.264 / HEVC のプルダウン(SEI の pic_struct)は検出しない。日本の放送 H.264 は CFR なので実害は観測されていないが、MPEG-2 と違って検出していない。

  • Python リファレンス実装は依然として理想格子へ吸着するので、位相問題も残って いる(tests/run_tests.sh の mpeg2 ts multi は xfail)。テストオラクルとしては 機能しているが、Rust 実装のほうが先へ進んでいる。

  • 対応コーデックは H.264 / HEVC / MPEG-2 / MPEG-4 Part 2 / VC-1 / VP9 / AV1 である。 後の 2 つは長らく「エレメンタリストリームを連結する形式を持たない」と書いて あったが、事実と違う。どちらもパラメータセットを持たない。VP9 のキーフレーム は、非圧縮ヘッダに解像度と色の設定を自分で書く。AV1 は、計測した 3 つの エンコーダ(SVT-AV1・libaom・rav1e)がいずれもキーフレームの直前に シーケンスヘッダ OBU を置く。検証素材では、キーフレーム 10 個に対して ヘッダも 10 個あった。シーケンスヘッダを差し替えてよいのはキーフレームの 位置であり、継ぎ目が落ちるのはまさにそこである。つまり、書き直した部分 GOP と そのうしろのコピーとの間に、手を入れる箇所が無い。 tests/run_vp9_av1_tests.sh はコーデックごとに 3 つを測る。フレーム数、 継ぎ目を書いたのとは別の復号器(プレーヤーが実際に使うほう)でエラー無く 復号できること、そして出力のうち録画そのもののバイト列が占める割合である。

  • 継ぎ目を書く AV1 エンコーダは、libavcodec 任せにせず名前で選ぶ。 avcodec_find_encoder(AV_CODEC_ID_AV1) が返すのは libaom-av1 で、既定の cpu-used は 0 である。1080p24 の 2 秒に 348 秒かかった。継ぎ目を書いている というより、書き出しが固まったようにしか見えない。SVT-AV1 の preset 8 なら、 同じ 2 秒を 3.97 秒で書き、画質の差は 0.002 dB に収まる。そこで cut::encoders_for が SVT-AV1・rav1e・libaom の順に名前を挙げ、順に開いて みる。エンコーダが存在することと、その録画を書けることは別である。SVT-AV1 は 4:2:2 を受け付けないが、それが分かるのは開いたときで、探したときではない。

  • AV1 の level は、両端で同じ数を指さない。復号器が AVCodecContext.level に 入れるのは、ストリームの seq_level_idx である。level 2.0 が 0 で、以降 1 ずつ 増える。SVT-AV1 がそこに求めるのは、同じ level に対して 20 である。録画の値を そのまま渡したところ、継ぎ目ごとに Level must be in the range of [2.0-7.3] が出た。AV1 と VP9 では level をエンコーダに決めさせる。ただし rav1e は任せる と 31 を書くので、rav1e の書式で録画の level を渡す(MP4 に書く AV1 の 構成情報)。両端の数え方が一致するコーデックには、従来どおり 録画の値を渡す。

  • 0.8.0 まで、エンコーダはすべて 1 コアで動いていた。avcodec_alloc_context3 が thread_count に残すのは 0 ではなく 1 で、エンコーダはこれをそのまま読む。 前段のデコードが 4 コアで回っている横で、継ぎ目は 1 コアで書かれていたことに なる。もともと扱っていたコーデックでは表に出なかった。H.264 の継ぎ目はどちらでも 1 秒程度の仕事で、実測の差も無い。libvpx は 1080p24 の 2 秒に、1 コアで 6.56 秒、マシンに任せて 2.45 秒かかった。

  • 0.8.1 までは、MP4 や Matroska の H.264・HEVC を .mkv に書き出せなかった。 Matroska は MP4 と同じく NAL の前に長さを置く。ところがコピーしたピクチャを、 トランスポートストリーム用のスタートコードに書き換えて入れていた。読めたのは 再エンコードした継ぎ目の前後だけである。.m4v も同じだった。拡張子から libavformat の ipod muxer に渡され、HEVC はそこで受け付けられなかった。いまは 中身どおり MP4 として書く。形式の違う録画の結合(トランスポートストリームと MP4 をどちらの順でつないだ場合も)も同じで、2 本目の録画を 1 本目の形式のまま コピーしていた。映像も AAC も壊れていた。run_rust_tests.sh がこの組み合わせを すべてデコードし、エラーが出ないことを確かめている。

  • 0.8.0 から 0.8.2 までは、前の番組にだけ 2 本目の音声がある放送録画を、何十回も 読み直していた。1 本のトラックを探す読み取りは、パケットの数か時刻で打ち切る。 ところが 0.8.0 から、その前にほかのストリームをすべて捨てるようにした。すると libavformat は、そのトラックのパケットが見つかるまで何も返さない。録画の最初の 1 分にしか無いトラックは見つからないので、そうした読み取りはどれもファイルの 終わりまで進んだ。2 時間半の録画を開くのに、4 分で済むところが 3 時間かかり、 カットでも継ぎ目ごとにファイルを読み直した。いまはこうした読み取りでは映像も 残す(input::keep_with_pictures)。残す範囲に何も無いトラックは、最後にエラーで 止めるのではなく、その旨を表示してカットから外す。run_bilingual_tests.sh が この形の録画を作り、読んだバイト数を数えている。

  • 0.8.3 までは、読み取りの不具合がほかにも 3 つあった。1 つ目は、エラーが出ても 止まらない読み取りである。ffmpeg-next のパケットの読み取りは、 ファイルの終わり以外のエラーでは同じ読み取りをすぐに繰り返し続ける。作業中に 共有フォルダーから録画が消えると、1 コアを使い切ったまま止められなくなった。 2 つ目は、DVD と Blu-ray の字幕を範囲の手前から読み直す処理である。範囲の頭より 後に字幕の無いトラックでは、範囲ごとにタイトルの終わりまで読んだ。3 つ目は、 クリップの途中で時計が 2^33 を越えたディスクの EP マップで、その後の入口点が すべて捨てられていた。いまは読み取りを input::Packets に通し、失敗が続けば 読むのをやめる。字幕の読み直しは映像を時計として残し、EP マップは libavformat が渡すストリームに合わせて時計の周回をほどく。書き出す処理では、読むのをやめた 場合をファイルの終わりとは扱わない。共有フォルダーが切れたところで終わった出力を 成功と表示せず、エラーで止める。

  • 0.8.4 では、EP マップの周回を継ぎ目ごとにほどき直していた。libavformat は ファイルで最初に出会った時刻から 1 分引いた値を基準にし、ファイルの最後まで それを使う。そのため、継ぎ目で時計が小さな値に戻る録画では、その後の区間の 入口点がピクチャから 1 日ずれていた。いまは libavformat と同じ基準で 1 回だけ ほどく。

  • 0.8.3 までは、再エンコードした音声を、隙間なく並べて書いていた。エンコーダに 渡したサンプル数で位置を決めていたので、トラックが途切れた部分があると、その後が すべて途切れた長さだけ前にずれた。放送の欠落、映像より遅れて始まる音声や先に 終わる音声、そのトラックを持たない録画の結合がこれにあたる。いまは途切れた部分を 無音で埋め、範囲ごとに音声の頭を映像の頭に合わせる。途中に 2 秒の欠落を入れた テスト用の録画では、欠落より後のビープ音が 2 秒早く鳴っていた。いまはコピーした ときの位置との差が 5 ms 以内に収まる。

  • 0.8.3 までは、ロゴ検出とシーンの特徴量が、10 ビットのピクチャを 8 ビットとして 読んでいた。HEVC Main10 のサンプルは 1 つ 2 バイトなのに、どちらもバイト単位で 読んでいたので、右上や右下の隅を読むつもりで画面の中ほどを読んでいた。 テストパターンのロゴの強さは、8 ビットの 2325 に対して 10 ビットでは 8413 と 出ていた。いまは 2305 で、Luma8 がどちらの深さも読む。

  • Dolby Vision はコピーには耐えるが再エンコードには耐えない。毎ピクチャの RPU は ピクチャと一緒にコピーされるので、範囲の両端が録画自身のエントリポイントに落ちる カットは RPU を 1 本残らず引き継ぐ。継ぎ目で書き直されたピクチャには無い。 libavcodec が libx265 を設定できるのは、自分が書けるプロファイルに限られるから である。カットはその旨を注記する。ベースレイヤーが単体で見られる HEVC の録画 (互換 ID が 0 以外か、profile 7・8)では、出力全体で Dolby Vision をやめる。 ストリームレベルの宣言、トランスポートストリームの PMT の 0xB0 記述子、コピー したピクチャの RPU とエンハンスメントレイヤーをすべて外し、ピクチャの裏付けが 無い宣言を残さない。 単体で見られない録画では、どれも残し、書き直したピクチャだけに付かない。計測に 使ったのは profile 4 の録画で、libavcodec はこれを受け付けない。エンハンスメント レイヤーを持ち互換 ID は 0 なので、Dolby Vision は残る側になる。GOP が 4.2 秒 あるので、エントリポイントを外した範囲は長く再エンコードされる。 Rust コアを参照。

  • 後方互換の形で書かれた HLG の伝達関数を保てるのは HEVC だけ。シーケンス ヘッダには bt2020-10 と書かれ、添えられた SEI が本来は HLG であることを示す。 この両方を継ぎ目の向こうでも再現するには libx265 に atc-sei を指示することに なるが、libx264 に相当するものは無い。この書き方で HLG を運ぶ H.264 の録画は、 ここでは計測していない。

  • VC-1 の部分 GOP は SmartCut 自身のエンコーダが書き、書くのはイントラピクチャ だけである(Rust コア)。 libavcodec に VC-1 のエンコーダが無いので、代わりに使えるものが無い。ピクチャは 置き換える予測ピクチャより符号量が大きい(それでもディスク自身の I ピクチャよりは 小さい)。またフォーマットのうち 2 つ、パンスキャンのウィンドウと、ピクチャ端で 量子化を変える指定(DQUANT=2)は、誤って書くくらいならと、明示的に対象外とする。 フィールドピクチャは読めるが、書くときはインターレースフレームにする。 エントリーポイントの指定のうち 2 つは、対象外とせずに対応している。オーバーラップ 平滑化を許すストリームでは、量子化を PQUANT 8 以下に抑える。デコーダはそれより 粗いイントラピクチャを必ず平滑化するが、このエンコーダはそれに備えた処理を しないからである(0.8.3 までは抑えていなかった。書き換えたプログレッシブの素材で 37.9 dB だったものが、いまは 42.3 dB になる)。レンジマッピング (RANGE_MAPY・RANGE_MAPUV)は何もしなくてよい。libavcodec はこれを適用 しない(「Luma scaling is not supported」)ので、渡されるピクチャは符号化時の 値域のままで、そのまま書き戻せば元のピクチャと同じになる。先に値域を狭めて みたところ 17 dB に落ちた。

  • 映像トラックは 1 本のみ。Python リファレンス実装では音声も 1 本のみである。 Rust エンジンは録画が持つ音声トラックをすべて読んですべて書き出す。.ts 出力の ときは次の字幕を引き継ぐ。放送が送る ARIB 字幕、同時に送られる文字スーパー、 4K 放送が代わりに送る TTML、ディスクが描く PGS である (音声、 放送録画ワークフローとの互換、 ディスクが描く字幕を参照)。DVD の字幕は TS に運べるストリーム種別が無いので、既定では Blu-ray の種類に変換して中に 入れる。どちらのディスクの字幕も、代わりにカットとは別のファイルへ書ける。 VobSub の 2 ファイルか、.sup かである。.mp4 に切るときディスクの字幕を 残せるのは、この方法だけである (同上)。Blu-ray の メニュー(IGS)とテキスト字幕(TextST)は運ばない。ただし何も言わずに捨てるのでは なく、置いていくものとして明示する。メニューのボタンはカットには無いディスク 構造を指しており、TextST の書体はストリームではなくディスク側にある (同上)。 SmartCut が読み取らない形式の字幕も運ばない。.mkv や .mp4 の SubRip・ASS・ mov_text・WebVTT、DVB 放送のビットマップ字幕、DVD の外に出たサブピクチャが これにあたり、同じように「字幕ストリーム」として明示する (lib.rs の outline_of)。0.8.28 までは、 何も表示せずにどのカットからも外していた。

  • reencode 以外のモードでは、音声の境界は依然としてフレーム単位に丸められる。 smart が取り除くのは「切った側の音が継ぎ目に残ること」であって、区間がフレームの 整数倍になること自体は変わらない。1 つの瞬間に 2 つのフレームを置けるコンテナが 無いかぎり、継ぎ目では最大 10.7 ミリ秒の無音が増えるか、最大 10.7 ミリ秒の音が 失われる。

  • ここで書き出すフレームは ADTS の CRC を持たない(protection_absent = 1)。 これはフレームごとのフィールドなので、CRC を持つフレームの中に混在しても合法だが、 録画のフレームとバイト単位で同じ形ではない。

  • smart が効くのは、エンコーダのある非可逆コーデックまでである。 AAC・AC-3・E-AC-3・MP2・Blu-ray の LPCM はスマートレンダリングされる。DTS と TrueHD はロスレスなので、承知のうえでバイト単位に運ぶ。このビルドにエンコーダの 無いコーデックは、その旨を示してコピーする。その素材では、既定の動作が copy と 完全に同じになる。以前は AAC までしか効かなかったが、原因はエンコーダの遅延 だった。遅延が 1 フレームちょうどなのは AAC だけで、ほかのコーデックでは録画の フレームの間にパケットが落ちていた。いまは手前に与えるサンプルで格子に戻している (音声)。

  • --aac が効くのは SmartCut が書くフレームだけである。コピーされたフレームは 自分の ADTS ヘッダを保つので、素材を一方の種別からもう一方へ変換することはでき ない。ペイロードが他方のバージョンに無いツールを使っている可能性があり、ヘッダの ビットだけ書き換えると中身と食い違うからである。録画と食い違う指定は、混在 ストリームを作るのではなく注記つきで受け付けない。ただし全編再エンコードのときは 何もコピーしないので指定に従う。