LoginSignup
2
1

More than 5 years have passed since last update.

VLCで副字幕を表示可能にする

Posted at

本記事は東京大学工学部電子情報・電気電子工学科の「大規模ソフトウェアを手探る」という実習の報告書を兼ねています。

はじめに

実験の2日目に「FFmpegにコマンドラインオプションを追加する」という目標ではじめましたが、ほんの数時間で目標達成してしまい、物足りなかったのでVLCをいじって副字幕を表示可能にすることを目標にすることにしました。VLCが副字幕に対応していないことによって二つの字幕を同時に表示して言語を勉強したい人たちにとっては使いづらい、と数年前から問題視されており、これを実現すれば世界中の多くの人が喜ぶだろうと思い、このテーマに決めました。この記事ではそこでの試行錯誤とその過程をまとめます。

開発環境を整える

macOS上でVLCをソースからビルドする」の通りにVLCのソースコードをビルドし、それ以降はLLDBデバッガなどを用いて解析、ソースコード変更ののちに再ビルドという作業を繰り返して開発します。

VLCのファイル構造

VLCのファイル構造の中で、今回の目的に必要だったものをピックアップしてみました。

modules/
    codec/
        subsdec.c ... 字幕をデコードして表示する処理
        mkv/
            *.hpp
            *.cpp ... mkvファイルの字幕を取り出す処理
    demux/
        subtitle.c .. 外部字幕を取得する処理
    gui/
        macosx/
            *.h
            *.m ... macOSにおけるGUI部分に関する処理
            UI/
                *.xib ... macOSにおけるGUIそのもの
src/
    input/
        es_out.c ... 字幕の追加・転送・変更に関する処理
        es_out_timeshift.c ... 表示する字幕に関する処理をes_out.cに送る処理
        decoder.c ... 字幕をデコードするために必要なデコーダーを指定する処理
        input.c ... var.cによって変数が変化したときの処理
        var.c ... GUIからの入力を変数に入れる処理

副字幕メニューの追加

基本的にはmodules/gui/macosxフォルダ内を変更して再ビルドすればメニューが追加できます。iOSアプリ開発とmacOSアプリ開発で培ったObjective-Cの知識を使えば、GUIとしてメニューを追加するだけなら簡単にできました。あとはそのメニューとVLC本体を連携するための処理を書く必要があります。字幕トラックメニューに関係する処理は以下の4箇所です。

VLCMainMenu.h
@property (readwrite, weak) IBOutlet NSMenuItem *subtitle_track;
@property (readwrite, weak) IBOutlet NSMenu *subtitle_tracksMenu;
VLCMainMenu.m
[_subtitle_track setTitle: _NS("Subtitles Track")];
[_subtitle_tracksMenu setTitle: _NS("Subtitles Track")];
...
[self setupVarMenuItem:_subtitle_track target: (vlc_object_t *)p_input
                   var:"spu-es" selector: @selector(toggleVar:)];
...
[_subtitle_track setEnabled: b_enabled];

すべての箇所に対して「字幕トラック2」の処理を加えてあげて再ビルドすると、実行時にSubtitle Track 2のメニューが追加されていることが確認できました。ただ、Subtitle Track 1と全く同じ動作をしてしまいます。つまり、Subtitle Track 1を変更すると字幕の言語は変更されるものの、Subtitle Track 2でも同じ言語の字幕が選択されている状態になってしまうのです。これを解決するために、spu-esという文字列に注目しました。VLCMainMenu.mのこの部分のコードがVLC本体に変数を渡している部分だと予想できたので、Subtitle Track 2に関するコードはspu-es2と置き換えることにしました。この状態で再ビルドすると、Subtitle Track 2Subtitle Track 1と違い、常にグレイアウトしている状態になります。これはVLC本体の変数がspu-es2を全く参照していないため、字幕言語を追加する処理すら働いていないので当然なのです。

VLC本体を弄る

上記の問題に対応するためには、「VLCのファイル構造」でも取り上げた、src/input/var.cを変更する必要があります。このファイルはGUIからの入力を変数に代入し、変更があればその都度代入する処理をします。

var.c
static int EsSpuCallback ( vlc_object_t *p_this, char const *psz_cmd,
                           vlc_value_t oldval, vlc_value_t newval, void * );

...
CALLBACK( "spu-es", EsSpuCallback ),
...
case SPU_ES:
        return "spu-es";
...
static int EsSpuCallback( vlc_object_t *p_this, char const *psz_cmd,
                           vlc_value_t oldval, vlc_value_t newval, void *p_data )
{
    input_thread_t *p_input = (input_thread_t*)p_this;
    VLC_UNUSED( psz_cmd); VLC_UNUSED( oldval ); VLC_UNUSED( p_data );

    if( newval.i_int < 0 )
        newval.i_int = -SPU_ES; /* disable spu es */

    input_ControlPushHelper( p_input, INPUT_CONTROL_SET_ES_BY_ID, &newval );

    return VLC_SUCCESS;
}

これらの各部分に対してspu-es2用のコードを追加すればいいのですが、ここでspu-esSPU_ESの関連付けをspu-es2の場合はどうすればよいかで困りました。とりあえずspu-es2SPU_ES2と対応付けることにして他の部分も変えていく方針に決めました。
そうすると、VLCの奥深くの部分まで書き換える必要が出てきました。mkvファイルを開くと呼ばれるmodules/codec/mkvフォルダ内のファイルにはmkvの字幕を処理するものがあり、mp4ファイルを開くと呼ばれるmodules/codec/mp4フォルダ内のファイルにはmp4の字幕を処理するものがある、と動画ファイルのフォーマットごとに字幕の処理について定義されています。これらはすべて字幕を表示するときにSPU_ESという定数しかないことを前提に書かれているため、これらすべてを書き換える必要が出てきますが、さすがにそれは10日という制限のもとでは不可能ですし、不具合が出てもテストしようがないので危険です。結局、以下のように、もっと上のレイヤーであるsrc/input/es_out_timeshift.cを弄って、「SPU_ESの処理が来たときにはSPU_ES2の処理もする」という処理を追加することにしました。

es_out_timeshift.c
if (p_fmt->i_cat == SPU_ES) {
    p_fmt->i_cat = SPU_ES2;

    vlc_mutex_lock( &p_sys->lock );

    TsAutoStop( p_out );

    if( CmdInitAdd( &cmd, p_es, p_fmt, p_sys->b_delayed ) )
    {
        vlc_mutex_unlock( &p_sys->lock );
        free( p_es );
        return NULL;
    }

    TAB_APPEND( p_sys->i_es, p_sys->pp_es, p_es );

    if( p_sys->b_delayed )
        TsPushCmd( p_sys->p_ts, &cmd );
    else
        CmdExecuteAdd( p_sys->p_out, &cmd );

    vlc_mutex_unlock( &p_sys->lock );
    p_fmt->i_cat = SPU_ES;
}

この状態で再ビルドすると、字幕トラック2を開くと字幕トラック1と同じ言語一覧が出てくるようになりました。
このあと、いろいろ迷走してしまったのですが、結局modules/demux/subtitle.cmodules/codec/mkv/mkv.cppを書き換えることによって内部字幕+外部字幕の組み合わせなら複数字幕を表示することに成功しました。残念ながら、外部字幕+外部字幕の組み合わせもしくは内部字幕+内部字幕の組み合わせでは片方の字幕しか表示されないという不具合は最後まで修正することができませんでした。

multisubsについて

実験の発表日の前日に、チームメンバーの一人が以下のプロジェクトを見つけました。
https://repo.or.cz/vlc/multisubs.git/
multisubsという名前で、VLC2.0(3年前のバージョン)に複数字幕表示機能を追加するというものでした。これを参考にすると、spu-es2に対応させる定数は、SPU_ES2ではなくSPU_ESでよいということがわかりました。たしかにSPU_ESだけにすれば、modules/codec/以下にある各コーデックごとの処理を記述したコードに変更を加える必要がなく、単純な構造になります。このコードを参考に、1日徹夜して実装してみました。しかし、SPU_ES2を追加する方法による実装未満の結果しか得られませんでした。字幕トラック1で選択した言語の字幕しか表示されないのです。
VLC2.0の時代から大きくコードが変更されているため、単純にそのまま移植しただけでは動かないのかもしれません。それ以降いろいろ頑張ってコードを変更してみたりデバッグしてみたりしましたが、結局複数字幕の表示は実現できませんでした。(一応コードはプロジェクトのmultisubsブランチにpushしてあります)

まとめ

長時間VLCに変更を加え続けて副字幕の表示を実現しようと試行錯誤しましたが、結局内部字幕+外部字幕のみの対応となってしまい、満足のいく結果は得られませんでした。しかし、VSCodeを用いたデバッグの方法を学んだり、どんなにプロジェクト全体が大きく、すべてのコードの内容が理解できなくとも、一部に対する変更だけならうまくデバッガを用いれば実現できるということを学ぶことができました。この経験を生かして今後のプログラミング人生を歩んでいきたいと思います。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1