2013年7月7日日曜日

MultiByteToWideCharToMultiByte その1

最近、文字のエンコード/デコード関連のコードを書くことが多いのでメモ程度に。

1.FFmpeg/LibavとAvisynth

FFmpeg/Libavのライブラリは文字エンコードはUTF-8で統一されている。
しかし世の中にはUTF-8をそのまま扱うには色々問題があるOS(つまりWindows)が存在するので、2011年4月にいくつかのハックが取り入れられた。

ハックその1:
cmdutils(ffmpeg/avconv/avprobeといったコマンドラインツール用共通コード)は入力されたコマンドラインをWindowsの場合のみACP(ANSI code page、日本語版Windowsなら標準はCP932)からUTF-8に変換する。
ハックその2:
FFmpeg/Libavのツール/ライブラリにおいて、ファイルの開閉はavformatの仕事である。ほとんどのファイルはavformat_open_input()を使って開かれる。
int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options);
avformat_open_inputは普通はio.hのopen()関数を使ってファイルを開くが、Windowsの場合はfilename(UTF-8)をワイド文字(UTF-16LE)に変換してから_wsopen()関数(マイクロソフトによる独自拡張)で開く。
このようになるべく問題が出ないよう配慮されているわけだが、avformatの対応形式の中にはこれだけだとまずいものが存在する。それがAvisynthである。

FFmpeg/LibavにおけるAvisynth入力は、昔DivX社の人が書いてMLに送ったもので、ファイルの操作はVideo for Windowsで行なっていた。
res = AVIFileOpen(&avs->file, s->filename, OF_READ|OF_SHARE_DENY_WRITE, NULL);
このコード、上記のハックが取り入れられるまでは特に問題がなかったのだけど、2011年4月以降はs->filenameの中身がUTF-8でエンコードされた文字列になってしまったので、ASCII以外の文字を使ったファイル名は文字化けしてAVIFileOpen()が失敗するようになったのである。

さて、このバグの存在を自分が知ったのは昨年の5月だった(2ちゃんねるのcygwin/mingwスレで見かけた)。
で、その日のうちにパッチを書いてLibavのMLに送り(Libavに入った変更は大抵3日以内にFFmpegにも取り込まれるので、送るならLibavのほうが手間が省ける)、ML上でのちょっとしたやり取りの後、以下のように変わった。
wchar_t filename_wchar[1024] = { 0 };
char filename_char[1024] = { 0 };
MultiByteToWideChar(CP_UTF8, 0, s->filename, -1, filename_wchar, 1024);
WideCharToMultiByte(CP_THREAD_ACP, 0, filename_wchar, -1, filename_char, 1024, NULL, NULL);
res = AVIFileOpen(&avs->file, filename_char, OF_READ|OF_SHARE_DENY_WRITE, NULL);
ACP -> UTF-16LE -> UTF-8とcmdutilsで変換された文字列を今度は逆にUTF-8 -> UTF-16LE -> ACPと変換するだけ。
変換用の一時的なバッファのサイズが1024で決め打ちなのは「とりあえずこの程度あればいいんじゃね?」と適当に書いただけで特に意味は無い。そしてなぜか誰もこのマジックナンバーにツッコミを入れなかったのでそのままになった。
かくしてffmpeg.exeやavconv.exeで再び日本語ファイル名が(ひょっとするとUTF-8で255文字を超えるファイル名の場合、コケる可能性があるけど)使えるようになった。

あと、ひょっとするとCP_THREAD_ACPよりもCP_OEMCPのほうが良かったのかもしれないけど、そこらへんはよくわからないので分かる人がいたらよろしくお願いします。

次回へ続く。

0 件のコメント:

コメントを投稿