Java の Sound API を使用すると文字化けする問題について

プログラミング
スポンサーリンク

Windows環境で、javax.sound.sampledを使用した以下のコード。

Mixer.Info[] mixerinfo = AudioSystem.getMixerInfo();
for(int i=0;i<mixerinfo.length;i++) {
	System.out.println("****\t"+i+"\t***");
	System.out.println(mixerinfo[i].getName());
}

上記のコードを利用すると、
ヘルプによれば

getName
public final String getName()
ミキサーの名前を取得します。
戻り値: ミキサーの名前を表す文字列

と、名前を取得できるはずです。
しかし、日本語環境の場合、ミキサーの名前に全角文字があると、文字化けした名前を取得してしまいます。


「Port スピーカー」 → 「Port ?X?s?[?J?[」
「Port ライン入力」 → 「Port ???C??????」
「Port マイク」 → 「Port ?}?C?N」
「Port SPDIF出力」 → 「Port SPDIF????」
「Port SPDIF入力」 → 「Port SPDIF?o??」
「Port 再生リダイレクト」 → 「Port ??¶???_?C???N?g」

ネットでこの問題を探してみると、
Java の Sound API を使用した際に文字化けが起こってしまう問題に関しての質問です。
のように困っている人もいたりします。

なぜこういう自体が起こっているかというと、
多分内部で文字コードの変換が適切に行われていないためだと考えられます。
海外では話題が見つからなかったことから、誰か教えにいかないと多分治らないです。

解決策として、例えば。
「Port ???C??????」
これに関して、

for(int i=0;i<x.length();i++) {
	System.out.println((int)x.charAt(i));
}

を実行すると

「 80 111 114 116 32 131 137 131 67 131 147 147 252 151 205」

となることから、?の部分でも、しっかりコードが取得できていることが分かるので、

一度byte[]に変換した後に、
ByteArrayInputStream で、このバイト列を入力ストリームとして、

ByteArrayInputStream is = new ByteArrayInputStream(binary);
String charsetName = "JISAutoDetect";
InputStreamReader inputreader = new InputStreamReader(is, charsetName);
BufferedReader br = new BufferedReader(inputreader);

みたいな形で文字を読みこめば、文字化けを治すことができます。
ByteBufferと、CharsetDecoderでも出来るかも。

ただ一つだけ問題があって分からないことがありました。
再生リダイレクト」が「??¶???_?C???N?g」のように文字化けをしていて、
実際の文字コードは、

「141 196 144 182 131 138 131  95 131  67 131 140 131  78 131 103」

となるはずが、charAtで取得すると

「141 272 182 131 138 131  95 131  67 131 140 131  78 131 103」

となってしまうのです。

「再生」が「141 196 144 182」のはずなのに「141 272 182」

196 0b11000100 0xC4
144 0b10010000 0x90

がなぜ

272 0b0000000100010000 0x0110

となってしまうのかが不思議です。

コメント

  1. こんにちは。

    > ネットでこの問題を探してみると、
    > 「Java の Sound API を使用した際に文字化けが起こってしまう問題に関しての質問です。」
    > のように困っている人もいたりします。

    の質問でまさに困っている (今も) 人です。
    なたでさんが仰るように Java 側のバグだと思うので昔 Sun にバグ報告をしたのですが直る気配はありませんね。

    > 「再生」が「141 196 144 182」のはずなのに「141 272 182」

    これはですね、おそらく Windows 側から渡されたバイト列を Java は UTF-8 だと見なして処理を行っているためだと思われます。 「141」 や 「182」 というバイトは UTF-8 としては無効な値なので何も変換されずに読み込まれる一方、「196 144」 というバイト列は UTF-8 で U+0110 (10 進数値では 272) を表すため変換されて読み込まれるようです。 そして結果として 「141 272 182」 となる、と。

    byte bb[] = { (byte)196, (byte)144 };
    String s = new String( bb, Charset.forName( “UTF-8” ) );
    System.out.println( s ); // “Đ” (U+0110)

    for( byte b : “Đ”.getBytes( Charset.forName( “UTF-8” ) ) ) {
    System.out.println( 0xFF & (int)b );
    }

  2. なたで より:

    ゆうさん、初めまして!

    質問してた人が来てくれてうれしいです。
    コメントありがとうございます!
    なるほど、UTF-8のWikipedia見てきました。

    141 0b10001101
    196 0b11000100
    144 0b10010000
    182 0b10110110

    これが、UTF-8でみると

    141 0b10001101 (上位ビットが10から規格外)
    196 0b11000100 (上位ビットが110なので、2バイト文字)
    144 0b10010000 (196が2バイト文字の1バイト目なので、これが2バイト目)
    182 0b10110110 (上位ビットが10から規格外)

    196 144
    = 0b11000100 0b10010000
    = 0b___00100 0b__010000
    = 0b00100010000 = 0x110 = 272

    で、141 272 182 となってしまったのですね。

    ところで、次の環境で最近実行したら
    Java(TM) SE Runtime Environment (build 1.6.0_29-b11)
    Java HotSpot(TM) 64-Bit Server VM (build 20.4-b02, mixed mode)

    「再生」が「141 32 144 182」になるようになっていました。
    上位2ビットが1なら、32(0x20=半角スペース)にするというようにしたのかな。
    情報捨てちゃってるから完全に元に戻せなくなりましたね。

  3. 匿名 より:

    String name = new String(mixerinfo[i].getName().getBytes(“ISO8859_1”), “Shift-JIS”);

  4. CharlieJiang0w0 より:

    はじめまして、チャーリーです。
    この問題、私は以下の研究を行いました:
    http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8177951
    http://mail.openjdk.java.net/pipermail/sound-dev/2017-June/000564.html
    http://mail.openjdk.java.net/pipermail/sound-dev/2017-June/000565.html
    日本語も中国語も、非ASCII字元です。だからこの問題が中国語も日本語のWindowsである。私はOracleの関係者にE-メールを送りました。私もパッチを書かれました、でも私はどんな反応を得ませんでした。

タイトルとURLをコピーしました