MP3ファイルが私のオーディオ生活の中心になって久しい。MP3ファイルはファイルサーバのalephに溜めており、mpdで管理再生している。alephにTASCAM US-122をUSB接続し、ミュージックボックスとして利用している。で、Ubuntu 10.04のmpdはそれなりにタコなのだ。きちんと認識できないMP3ファイルがそれなりにある。ID3タグは読めるくせに再生できないというパターンが問題(その逆はDBファイルを書き換えれば済む、DBとか言いながら単なるテキストファイル)。実際に問題になったのは平沢進の「星を知る物」。mpdで再生できない。エンコードを別のソフトに変えると読めたりすることもあるのだがこれは平沢のサイトで配布してたMP3なので劣化なしに再エンコードできない。ともかく以前からこの状態をどうにかしたいと思っていた。
mpdのソースを含めたビルドパッケージを見てわかったのは、madというライブラリがダメな原因だということ。./configure --disable-mad でmadを無効にできる。するとmadの替りにmpg123というライブラリを使うようになる。これはこれでmadとは違うファイルがダメになる。で、さらに --disable-mpg123 を加える。今度はID3タグが大量に読めなくなる。でも、今までより再生での問題はなさそうだ。madでもmpg123でもないMP3ファイルのデコードには何が使われるのだろう。調べるとどうやら色々なファイル形式の最後のフォールバックにはffmpegライブラリが使われている。
ええと、id3tagライブラリをリンクしているのになんで、デコーダーに依存してID3タグを読めたり読めなかったりな動きをするんだろう。そのへんの作りが解せん。
というわけで、ID3タグを拾ってDBに登録するときはmadを使って、再生の時はffmpegを使うと大部分の問題が解決しそうだ。しかしそんなことが出来そうな設定はおろか、MP3のデコードに何を使うかの設定もなさそうだ。ソースを書き換えることにする。
使ったソースはmpd-0.17.6.tar.gz。mpd-0.18.12.tar.xzはglib 2.28以降を要求するので、Ubuntu 10.04ではビルドできない。0.17系はCなのだが、0.18系はソースの拡張子が.cxxなのでC++としてコンパイルされるようだ。ファイル名もdecoder_thred.cがDecoderThread.cxxになってたり割と大胆な改造が行われた形跡がある。
ソースを書き換えたりして色々試行錯誤して、ffmpegもmpg123もmadも各々再生できないMP3ファイルがあると判明。適切で真っ当な改造をしないと巧い事いかない。
演奏時間を適切に拾えないデコーダープラグインは適切に演奏できないと仮定する。で、適切に演奏時間を拾えないことを確実に動作させるように手を入れる。改造のキモは、ffmpegやmpg123のデコーダープラグインのバグを回避することと、プラグインの評価順を変えること。
decoder/ffmpeg_decoder_plugin.c
ffmpeg_decoder_plugin.c関数
コンテキストをタッチする部分を削除。ここでID3タグの内容が消されるようだ。この処理を削除することでの影響は不明。
//ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx);
//int idx = ffmpeg_find_audio_stream(f);
//if (idx >= 0)
// ffmpeg_scan_dictionary(f->streams[idx]->metadata,
// handler, handler_ctx);
演奏時間を適切に拾えなかった場合、偽を返す。
long long dur = -1;
if (f->duration != (int64_t)AV_NOPTS_VALUE) {
dur = f->duration;
tag_handler_invoke_duration(handler, handler_ctx,
f->duration / AV_TIME_BASE);
}
...
return dur >= 0;
decoder/mpg123_decoder_plugin.c
mpd_mpg123_scan_file関数
演奏時間を適切に拾えなかった場合、偽を返す。
num_samples = mpg123_length(handle);
int dur = num_samples / audio_format.sample_rate;
if (num_samples <= 0 || dur < 0) {
decoder_thread.c
decoder_run_file関数
演奏時間を適切に拾えないデコーダーをスキップする。
#include "tag_handler.h"
...
while ...
...
int dur = -1;
struct tag t;
memset(&t, 0, sizeof t);
if (plugin->scan_stream != NULL) {
GMutex *mutex = g_mutex_new();
GCond *cond = g_cond_new();
struct input_stream *is = input_stream_open(path_fs, mutex, cond, NULL);
if (is != NULL) {
if (decoder_plugin_scan_stream(plugin, is, &full_tag_handler, &t)) {
dur = t.time;
}
input_stream_close(is);
}
g_cond_free(cond);
g_mutex_free(mutex);
}
else if (plugin->scan_file != NULL) {
if (decoder_plugin_scan_file(plugin, path_fs, &full_tag_handler, &t)) {
dur = t.time;
}
}
if (dur < 0) {
continue;
}
decoder_list.c
デコーダープラグインの順番を変える。
#ifdef HAVE_FFMPEG
&ffmpeg_decoder_plugin,
#endif
#ifdef HAVE_MPG123
&mpg123_decoder_plugin,
#endif
#ifdef HAVE_MAD
&mad_decoder_plugin,
#endif
デコーダープラグインの順番は、madが例の平沢作品の演奏時間を不適切な正常値で返し、しかも適切にデコードできないという事実から決めた。ようはmadをなるべく使わないようにするということだ。
今のところffmpeg、mpg123、madのどれでも再生できないというパターンは見つかってない。
madとmpg123とffmpegでどれが音が良いやらはようわからん。余程酷い実装でもしてない限りデコード結果にたいして差があるとも思えない。
2014.08.15
OSTRACISM CO.
OSTRA / Takeshi Yoneki