読者です 読者をやめる 読者になる 読者になる

/dev/null/onishy

どーも。

VLC弄ってみた#2 - 時間をミリ秒にしてみる

第2回目の今回は、VLCにちょっとした改造を加えてみたいと思います。

VLCでは再生時間が秒単位でしか表示されませんが、ミリ秒単位でのシークがしたいことってありますよね。ありますよね?

そういう需要に応えるため、VLCの再生時間をミリ秒で表示させてみます。

環境はUbuntu 15.04/14.04です。MacWindowsではGUIの描画が若干異なるため、恐らくこれだと変わりません。

どこをいじれば良いのかを探す

まずは、どこをいじれば良いのかを探す必要があります。

とりあえず描画部分のパーツを見てみることにしましょう。今回いじりたいのはシーク中の再生時間ですが、これは2ヵ所に表示されています。一つはシークバーの左端、現在の再生時間が表示されている場所で、もう一つはカーソルを合わせた際に出てくるツールチップです。

描画部分がmodule/gui/qt4あたりに入っていることは何となく分かるので、とりあえずこの中をのぞいてみると、utilの中に"timetooltip.cpp" "timetooltip.hpp"というファイルがありました。これがツールチップ情報を持った描画クラスのようです。

/* timetooltip.hpp */
class TimeTooltip : public QWidget
{
Q_OBJECT
public:
explicit TimeTooltip( QWidget *parent = 0 );
void setTip( const QPoint& pos, const QString& time, const QString& text );
virtual void show();

protected:
virtual void paintEvent( QPaintEvent * );

private:
void adjustPosition();
void buildPath();
QPoint mTarget;
QString mTime;
QString mText;
QString mDisplayedText;
QFont mFont;
QRect mBox;
QPainterPath mPainterPath;
QBitmap mMask;
int mTipX;
};

mTextというのが表示されているテキストで、setTip関数を通じてセットされていることが容易に想像できます。

ただこのクラスはパーツを定義しているだけなので、実際にsetTipしている場所を探さなければいけないですね。"timetooltip.hpp"をincludeしているファイルを検索すると、今度は同じディレクトリに"input_slider.cpp" "input_slider.hpp"というファイルが見つかりました。103行目で

/* Tooltip bubble */
mTimeTooltip = new TimeTooltip( this );
mTimeTooltip->setMouseTracking( true );

とあり、ビンゴ!ですね。

さて、実際にsetTipをしている場所を探すと、331行目に

if( likely( size().width() > handleLength() ) ) {
secstotimestr( psz_length, ( ( posX - margin ) * inputLength ) / ( size().width() - handleLength() ) );
mTimeTooltip->setTip( target, psz_length, chapterLabel );
}

という場所がありました。 secstotimestrという関数がキモのようですが、この関数はsrc/misc/mtime.cで次のように定義されています。

/**
* Convert seconds to a time in the format h:mm:ss.
*
* This function is provided for any interface function which need to print a
* time string in the format h:mm:ss
* date.
* \param secs the date to be converted
* \param psz_buffer should be a buffer at least MSTRTIME_MAX_SIZE characters
* \return psz_buffer is returned so this can be used as printf parameter.
*/
char *secstotimestr( char *psz_buffer, int32_t i_seconds )
{
if( unlikely(i_seconds < 0) )
{
secstotimestr( psz_buffer + 1, -i_seconds );
*psz_buffer = '-';
return psz_buffer;
}

div_t d;

d = div( i_seconds, 60 );
i_seconds = d.rem;
d = div( d.quot, 60 );

if( d.quot )
snprintf( psz_buffer, MSTRTIME_MAX_SIZE, "%u:%02u:%02u",
d.quot, d.rem, i_seconds );
else
snprintf( psz_buffer, MSTRTIME_MAX_SIZE, "%02u:%02u",
d.rem, i_seconds );
return psz_buffer;
}

見ての通り、秒数を受け取ってh:mm:ssのフォーマットで文字列を吐き出す関数です。なので、こいつをコピーして、millisecstotimestrという関数を作り、こちらを呼び出すようにしましょう。

実際にいじってみる

次のように関数を定義し、ヘッダファイルも編集しておきます。

char *millisecstotimestr( char *psz_buffer, int32_t i_milliseconds )
{
if( unlikely(i_seconds < 0) )
{
millisecstotimestr( psz_buffer + 1, -i_milliseconds );
*psz_buffer = '-';
return psz_buffer;
}

div_t d;
unsigned short millisec, sec;
d = div( i_milliseconds, 1000);
millisec = d.rem;

d = div( d.quot, 60 );
sec = d.rem;
d = div( d.quot, 60 );

if( d.quot )
snprintf( psz_buffer, MSTRTIME_MAX_SIZE, "%u:%02u:%02u.%03u",
d.quot, d.rem, sec, millisec );
else
snprintf( psz_buffer, MSTRTIME_MAX_SIZE, "%02u:%02u.%03u",
d.rem, sec, millisec );
return psz_buffer;
}

とりあえず、現在位置を秒数で受け取っている分にはどうしようもないので、これをミリ秒で受け取るようにします。受け取る秒は次のように計算されているのでした。

( ( posX - margin ) * inputLength ) / ( size().width() - handleLength() )

ハンドルの位置と動画の長さから秒数を計算し、これがintにキャストされています。なので、ミリ秒を取得するためにはこれを1000倍するだけで良いでしょう。

if( likely( size().width() > handleLength() ) ) {
millisecstotimestr( psz_length, ( ( posX - margin ) * inputLength * 1000) / ( size().width() - handleLength() ) );
mTimeTooltip->setTip( target, psz_length, chapterLabel );
}

動かない??

これでコンパイルを行うと、コンパイルは通りますが起動時に次のようなエラーが出ます。

VLC media player 2.2.1 Terry Pratchett (Weatherwax)
Command Line Interface initialized. Type `help' for help.
> [0000000001229228] [cli] lua interface error: Error loading script [vlc]/share/lua/intf/cli.luac: lua/intf/modules/host.lua:279: Interrupted.
[00000000011224f8] core libvlc: Running vlc with the default interface. Use 'cvlc' to use vlc without interface.
[0000000001229228] core interface error: corrupt module: [vlc dir]/modules/gui/qt4/.libs/libqt4_plugin.so
[00007f194c01f458] core generic error: corrupt module: [vlc dir]/modules/gui/qt4/.libs/libqt4_plugin.so
[0000000001229228] skins2 interface error: no suitable dialogs provider found (hint: compile the qt4 plugin, and make sure it is loaded properly)
[0000000001229228] skins2 interface error: cannot instantiate qt4 dialogs provider
[0000000001229228] [cli] lua interface: Listening on host 

つまり、libqt4がライブラリとして不完全ということです。この原因を探るため、secstotimestrでgrepをかけ、millisecstotimestrとの差を見てみます。すると、なにやら怪しげなファイルにヒットします。

[vlc dir]/src/libvlccore.sym:

358 sdp_AddAttribute
359 sdp_AddMedia
360 secstotimestr
361 services_discovery_AddItem
362 services_discovery_EventManager

どうやらこのlibvlccore.symというファイルは、ライブラリに含まれるシンボルが集まったファイルのようです。 libvlccore.symで再度プロジェクト全体に対してgrepをかけてみます。すると、/src/Makefile.am内に

libvlccore_la_SOURCES = $(SOURCES_libvlc)
libvlccore_la_LDFLAGS = \
$(LDFLAGS_libvlccore) \
-no-undefined \
-export-symbols $(srcdir)/libvlccore.sym \
-version-info 8:0:0
libvlccore_la_LIBADD = $(LIBS_libvlccore) \
../compat/libcompat.la \
$(LTLIBINTL) $(LTLIBICONV) \
$(IDN_LIBS) $(LIBPTHREAD) $(SOCKET_LIBS) $(LIBDL) $(LIBM)
libvlccore_la_DEPENDENCIES = libvlccore.sym
if HAVE_WIN32

とあります。-export-symbolsオプションについてGNU libtoolのドキュメントを見てみると、確かに

-export-symbols symfile

Tells the linker to export only the symbols listed in symfile. The symbol file should end in .sym and must contain the name of one > symbol per line. This option has no effect on some platforms. By default all symbols are exported.

ということらしいです。

つまり、shared libraryでは、望まないシンボルをライブラリに含めないよう、シンボルの指定ができるということです。そのため、ライブラリに含まれないmillisecstotimestrを呼び出そうとするとエラーが起きていたのでした。なるほどなるほど。

ということで、ここにmillisecstotimestrを一行追加して再automake・コンパイルします。これでOKです。

実行結果

f:id:onishy:20151104002409p:plain

ヨッシャヨッシャ!!