【Linux】ffmpegのRFF処理のバグFix

ことの発端は返信がなかったのと、有識者がいなそうだったので、closeしましたが、ここで投稿したIssueです。

-GitHub-
https://github.com/tobitti0/JoinLogoScpTrialSetLinux/issues/22

Linux版join_logo_scpを動作させたかったのですが、一部チャンネルにてエラーが発生。追いかけるとL-SMASH-Worksで使っているLWlibavsourceで正しくデータが読めていないようでした。

もう少し追いかけてみると、Repeat制御が入った際に正しくフレームのデータが渡されていないことが原因な所までわかりました。適当にデバッグ用の出力をしてみると、L-SMASH-Works/AviSynth
/lwlibav_source.cppで、リピート制御をするステップに入っているにも関わらず、フレーム内のリピートコントロールのフラグがfalseの状態で渡されていることが原因ようです。

Issueで指摘していた一部放送局というのがBS朝日などですが、無料放送だとBS朝日ともう一つぐらいしかRepeat First Field(RFF)となるフレームを使っていないので、因果としても正しそうなことがわかります。

となると、L-SMASH-Worksを改造するにせよ、バグがあるにせよffmpegを見ないといけないことには始まらないので読み始めましたがさすがに20年程度開発のあるプログラムの実績は読むのがしんどいこと…(あと仕事以外で読みたくないw)

周りの情報を見ても、L-SMASH-Works側の処理は間違いはないと思われるので今回はL-SMASH-Worksはそのままでffmpegを触ることにしました。

というわけで、中間の理解する過程は飛ばします。おそらくバグのあるコミットはこれ。

-GitHub-
https://github.com/FFmpeg/FFmpeg/commit/4a376f45ab58f29045be6df1af8121702299c701

プルリクだすのと様々な仕様をケアするのが面倒なので、勝手に修正します。ffmpegは使うことはあれど、コードを直接触ることはほとんどないnoobなのでお作法とか知りませんしね。自分の環境で動けばええんやの精神。MPEG-2で正しく動けばOK。

まず以下のlibavcodec/mpegvideo_parser.c不要なコードを消します。このコミットで追加された変数の意義が(私が)理解できないので葬ります。MPEG1かMPEG2かみたいな感じで処理しているっぽいですが、いまいちわからん。フィールド構造ピクチャーの情報を正しく処理するために処理が追加されていますが、わざわざこの流れでやる必要があるのか理解できていないです。他の仕様との兼ね合いでしょうか?

    // number of picture coding extensions (i.e. MPEG2 pictures)
    // in this packet - should be 1 or 2
    int nb_pic_ext = 0;
    // when there are two pictures in the packet this indicates
    // which field is in the first of them
    int first_field = AV_FIELD_UNKNOWN;

~~~~~~~~~~~~~~~~

  if (!nb_pic_ext) {
                            // remember parity of the first field for the case
                            // when there are 2 fields in packet
                            switch (s->picture_structure) {
                            case AV_PICTURE_STRUCTURE_BOTTOM_FIELD: first_field = AV_FIELD_BB; break;
                            case AV_PICTURE_STRUCTURE_TOP_FIELD:    first_field = AV_FIELD_TT; break;
                            }
                        }

                        nb_pic_ext++;

~~~~~~~~~~~~~~~~
if (avctx->codec_id == AV_CODEC_ID_MPEG1VIDEO || nb_pic_ext > 1) {
        s->repeat_pict       = 1;
        s->picture_structure = AV_PICTURE_STRUCTURE_FRAME;
        s->field_order       = nb_pic_ext > 1 ? first_field : AV_FIELD_PROGRESSIVE;
    }

この部分の条件判定式を消します。この変更もバグというかRFFのフィールドを正しく判定できない可能性がある(日本の放送データだのプログレッシブのフレームかつRFFがあるパターンだと確実にダメ)なので、条件判定式もいじります。

if (!pc->progressive_sequence && !progressive_frame) {
                            if (top_field_first)
                                s->field_order = AV_FIELD_TT;
                            else
                                s->field_order = AV_FIELD_BB;
                        } else
                            s->field_order = AV_FIELD_PROGRESSIVE;

この条件の!progressive_frameを消して以下のようにします。

 if (!pc->progressive_sequence) {
                            if (top_field_first)
                                s->field_order = AV_FIELD_TT;
                            else
                                s->field_order = AV_FIELD_BB;
                        } else
                            s->field_order = AV_FIELD_PROGRESSIVE;

次に判定用の変数を定義して以下のように変更する。フィールド構造ピクチャの情報をbuf[2]を0b00000011(=3)でマスク処理。ここの部分は該当箇所を探して追記する形です。

if (bytes_left >= 5) {
          追加した変数→  int picture_structure  = buf[2] & 3;
                        int    top_field_first = buf[3] & (1 << 7);
                        int repeat_first_field = buf[3] & (1 << 1);
                        int  progressive_frame = buf[4] & (1 << 7);

                        /* check if we must repeat the frame */
                        s->repeat_pict = 1;

真ん中の消し去った部分には以下のコードを追記する。変数picture_structureがどの値になっているかで最終的な値を設定する。

  if (!nb_pic_ext) {
                            // remember parity of the first field for the case
                            // when there are 2 fields in packet
                            switch (s->picture_structure) {
                            case AV_PICTURE_STRUCTURE_BOTTOM_FIELD: first_field = AV_FIELD_BB; break;
                            case AV_PICTURE_STRUCTURE_TOP_FIELD:    first_field = AV_FIELD_TT; break;
                            }
                        }

                        nb_pic_ext++;
------------------------------------------------------------------------
↓↓以下のように変更する↓↓

switch (picture_structure) {
                        case AV_PICTURE_STRUCTURE_TOP_FIELD:
                             s->picture_structure = AV_PICTURE_STRUCTURE_TOP_FIELD;
                             break;
                        case AV_PICTURE_STRUCTURE_BOTTOM_FIELD:
                             s->picture_structure = AV_PICTURE_STRUCTURE_BOTTOM_FIELD;
                             break;
                        case AV_PICTURE_STRUCTURE_FRAME:
                             s->picture_structure = AV_PICTURE_STRUCTURE_FRAME;
                             break;
                        }

後は普通にmake してmake installでOK。

これで動くようになったのでエンコードしておかしくないか確認。RFFのフラグをみるとのもしんどかったので目視で確認したところ変なフレームはなさそうでした。

インターレース解除の性能の問題ですが、vpp_qsvだと以下のように画像がジャギっていたのでフラグが正しく設定できていなかったのかと思いましたが、実際はただデコーダが低品質なだけでした。bwdifでインターレースフラグがないものは解除しない設定にして確認したところ問題なかったのでヨシ!という雑な判定でOKをだしました。

vpp_qsv=deinterlace=2
bwdif=0:-1:1

ひとまず動く形にして目に見えて大きな問題がないことは確認したのですが、コードがわかる方、この修正ではまずいと判断できる方はご指摘いただけますと幸いです。一応今のところは問題なくエンコードできています。

余談かつ問題ある方法ではありますが、L-SMASH-Worksではffmpeg7系での変更に対応していないので、makeが通りません。6系まで使える感じです。しかし、MPEG2周りに変更は入っていないのもありバイナリ的互換があるようで、ffmpeg6系でmakeした後にffmpeg7系をインストールすると7系の環境でもDTVで使うようなL-SMASH-Worksの動作は問題ありませんでした。

以上となります。お読みいただきありがとうございました。

投稿日:
カテゴリー: DTVLinux

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です