サイトアイコン Omomuki Tech

Java標準ライブラリ javax.sound.sampled 詳細ガイド:音声処理の基本から応用まで

この記事から得られる知識

この記事を読むことで、あなたは以下の知識を習得できます。

  • javax.sound.sampled APIの全体像と、Javaにおける音声処理の基本的な仕組みの理解。
  • 短い音声ファイル(効果音など)を効率的に再生するためのClipインタフェースの使い方。
  • 長い音声ファイルやストリーミングデータを扱うためのSourceDataLineインタフェースの使い方と、Clipとの違い。
  • マイクなどの入力デバイスから音声をキャプチャし、ファイルに保存(録音)するためのTargetDataLineインタフェースの活用方法。
  • 音声データの特性を定義するAudioFormatクラスの詳細(サンプリングレート、ビット深度、チャンネル数など)。
  • PCに接続された複数のサウンドデバイス(内蔵スピーカー、USBマイクなど)を識別し、特定のデバイスで再生・録音を行うためのMixerインタフェースの利用法。

第1章: javax.sound.sampledとは? – Java音声処理の入り口

javax.sound.sampledは、Java Platform, Standard Edition (Java SE) に標準で含まれているAPIで、デジタル(サンプリング)オーディオデータの取り込み(録音)、ミキシング、再生といった一連の機能を提供します。 このAPIはJava Sound APIの一部であり、アプリケーションに高度な音声機能を組み込むための基本的なツールセットと位置づけられています。

このライブラリの主な役割は、音声データの移送を管理することです。具体的には、音声ファイルからデータを読み込んで再生デバイスに送ったり、マイクから入力されたデータをキャプチャしてファイルに書き出したりする処理を担います。

主な機能

  • 音声再生: WAV、AU、AIFFといった非圧縮オーディオファイルを再生する機能。
  • 音声録音: マイクなどの入力デバイスから音声を取り込み、データとして扱う機能。
  • ミキシング: 複数の音声ストリームを合成する機能。
  • フォーマット制御: 音声データのフォーマット(サンプリングレート、ビット深度など)を詳細に扱ったり、フォーマット間の変換を行ったりする機能。

サポートされている形式

標準でサポートされているオーディオファイル形式は、主にWAV, AIFF, AU, SNDなどです。 注意点として、広く普及しているMP3形式は標準ではサポートされていません。MP3ファイルを扱いたい場合は、MP3SPIJLayerといったサードパーティ製のライブラリを追加するか、JavaFXのメディア機能を利用する必要があります。

このブログでは、javax.sound.sampledが標準で提供する機能に焦点を当て、Javaアプリケーションで音声を扱うための堅牢な基礎を築くことを目指します。


第2章: 音声再生の基本 – Clipを使ってみよう

javax.sound.sampled.Clipは、音声データを再生前にすべてメモリにロードしてから再生するためのインタフェースです。この特性から、比較的短い音声ファイル(例:ゲームの効果音、UIのクリック音など)の再生に非常に適しています。

データを事前に読み込むため、再生開始までの遅延が少なく、再生中に何度も繰り返し(ループ)再生したり、再生位置を瞬時に変更したりといった操作が得意です。

Clipを使った再生手順

  1. AudioInputStreamの取得: AudioSystem.getAudioInputStream()メソッドを使い、音声ファイルからオーディオ入力ストリームを取得します。
  2. Clipインスタンスの取得: AudioSystem.getLine()またはAudioSystem.getClip()Clipのインスタンスを取得します。
  3. ストリームのオープン: 取得したClipAudioInputStreamopen()メソッドで渡し、音声データをメモリにロードします。
  4. 再生の制御: start()で再生を開始し、stop()で停止、loop()で繰り返し再生を行います。
  5. リソースの解放: 再生が終了したらclose()メソッドでリソースを解放します。

サンプルコード: WAVファイルの再生

以下は、指定されたWAVファイルを再生する簡単なコード例です。


import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;

public class PlaySoundWithClip {
    public static void main(String[] args) {
        // 再生したいWAVファイルのパス
        File audioFile = new File("path/to/your/sound.wav");

        try (AudioInputStream audioStream = AudioSystem.getAudioInputStream(audioFile)) {

            // Clipの情報を取得
            DataLine.Info info = new DataLine.Info(Clip.class, audioStream.getFormat());
            if (!AudioSystem.isLineSupported(info)) {
                System.err.println("このオーディオ形式はサポートされていません。");
                return;
            }

            // Clipインスタンスを取得
            Clip clip = (Clip) AudioSystem.getLine(info);

            // LineListenerを登録して再生終了を検知
            clip.addLineListener(event -> {
                if (event.getType() == LineEvent.Type.STOP) {
                    System.out.println("再生が終了しました。");
                    // プログラムが終了しないように待機している場合、ここで解放する
                    synchronized (clip) {
                        clip.notifyAll();
                    }
                }
            });
            
            // 音声データをClipにロード
            clip.open(audioStream);

            System.out.println("再生を開始します...");
            // 再生を開始
            clip.start();
            
            // 再生が終了するまで待機
            synchronized (clip) {
                clip.wait();
            }

            // リソースを解放
            clip.close();

        } catch (UnsupportedAudioFileException e) {
            System.err.println("サポートされていないオーディオファイルです: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("I/Oエラーが発生しました: " + e.getMessage());
        } catch (LineUnavailableException e) {
            System.err.println("ラインが利用できません: " + e.getMessage());
        } catch (InterruptedException e) {
            System.err.println("待機中に割り込みが発生しました: " + e.getMessage());
        }
    }
}
        

注意点: clip.start()は非同期で実行され、すぐに次の処理に進んでしまいます。そのため、メインスレッドが終了してしまうと再生が途中で止まってしまいます。上記のコードでは、LineListenersynchronizedブロックを使って、再生が完了するまでメインスreadを待機させる処理を入れています。これにより、音が最後まで確実に再生されます。


第3章: ストリーミング再生 – SourceDataLineを使ってみよう

javax.sound.sampled.SourceDataLineは、音声データをストリーミング形式で再生するためのインタフェースです。Clipがデータをすべてメモリに読み込むのとは対照的に、SourceDataLineはデータを小さなチャンク(塊)に分割し、順次再生デバイスに書き込んでいきます。

この方式のため、ファイルサイズが大きい長時間の音声や、リアルタイムで生成される音声データ(例:ネットワーク経由の音声、シンセサイザーの出力)の再生に適しています。メモリ使用量を低く抑えることができるのが最大の利点です。

SourceDataLineを使った再生手順

  1. AudioInputStreamの取得: Clipと同様に、音声ファイルからオーディオ入力ストリームを取得します。
  2. SourceDataLineインスタンスの取得: ファイルのAudioFormatを元にSourceDataLineを取得します。
  3. ラインのオープンと開始: open()でラインを準備し、start()でデータの受け入れを開始します。
  4. データの書き込みループ: AudioInputStreamからバッファにデータを読み込み、それをwrite()メソッドでSourceDataLineに書き込む、という処理をファイルの終わりまで繰り返します。
  5. 終了処理: データの書き込みが終わったら、drain()でバッファ内のデータがすべて再生されるのを待ち、stop()close()でリソースを解放します。

サンプルコード: WAVファイルのストリーミング再生


import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;

public class PlaySoundWithSourceDataLine {
    private static final int BUFFER_SIZE = 4096;

    public static void main(String[] args) {
        File audioFile = new File("path/to/your/long_sound.wav");

        try (AudioInputStream audioStream = AudioSystem.getAudioInputStream(audioFile)) {

            AudioFormat format = audioStream.getFormat();
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);

            if (!AudioSystem.isLineSupported(info)) {
                System.err.println("このオーディオ形式はサポートされていません。");
                return;
            }

            SourceDataLine sourceLine = (SourceDataLine) AudioSystem.getLine(info);
            sourceLine.open(format);
            sourceLine.start();

            System.out.println("再生を開始します...");

            byte[] bufferBytes = new byte[BUFFER_SIZE];
            int readBytes = -1;

            // ストリームの終わり(-1)まで読み込みと書き込みを繰り返す
            while ((readBytes = audioStream.read(bufferBytes)) != -1) {
                sourceLine.write(bufferBytes, 0, readBytes);
            }

            // バッファに残っているデータをすべて再生し終えるまで待つ
            sourceLine.drain();

            System.out.println("再生が終了しました。");

            // リソースを解放
            sourceLine.stop();
            sourceLine.close();

        } catch (UnsupportedAudioFileException e) {
            System.err.println("サポートされていないオーディオファイルです: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("I/Oエラーが発生しました: " + e.getMessage());
        } catch (LineUnavailableException e) {
            System.err.println("ラインが利用できません: " + e.getMessage());
        }
    }
}
        

Clip vs SourceDataLine まとめ

特徴 Clip SourceDataLine
データロード方式 全データをメモリにロード(インメモリ) 逐次的にデータをロード(ストリーミング)
適した用途 短い音声ファイル、効果音 長い音声ファイル、リアルタイム音声
メモリ使用量 大(ファイルサイズに依存) 小(バッファサイズに依存)
ループ再生 loop()メソッドで簡単かつシームレスに実現可能 自前でストリームを巻き戻すなどの実装が必要
再生位置の変更 setFramePosition()などで容易 実装が複雑になる

第4章: 音声データの核心 – AudioFormatとAudioInputStream

これまで何気なく使ってきたAudioFormatAudioInputStreamですが、これらはjavax.sound.sampledの心臓部とも言える重要なクラスです。これらを深く理解することで、より高度な音声処理が可能になります。

AudioInputStream: 音声データへの入り口

AudioInputStreamは、その名の通りオーディオデータへの入力ストリームです。通常のInputStreamと異なるのは、自身が持つ音声データのフォーマット情報(AudioFormat長さ(フレーム単位)を把握している点です。これにより、単なるバイト列ではなく、意味のある音声データとして扱うことができます。主にAudioSystem.getAudioInputStream()メソッドを通じてファイルやURLから取得します。

AudioFormat: 音声データの仕様書

AudioFormatは、サウンドストリーム内のデータの具体的な配置や形式を定義するクラスです。いわば、その音声データの「仕様書」です。この情報がなければ、バイト列を正しく解釈して音として再現することはできません。

主なプロパティは以下の通りです。

プロパティ 説明 一般的な値の例
Encoding (エンコーディング) データの表現形式。最も一般的なのは非圧縮のPCM (Pulse-Code Modulation)です。 AudioFormat.Encoding.PCM_SIGNED, PCM_UNSIGNED
Sample Rate (サンプルレート) 1秒あたりに音の波形を標本化(サンプリング)する回数 (Hz)。値が大きいほど高音質になります。 8000.0F, 22050.0F, 44100.0F (CD音質), 48000.0F
Sample Size In Bits (サンプルサイズ/ビット深度) 1回のサンプルを何ビットのデータで表現するか。値が大きいほど音の強弱(ダイナミックレンジ)を細かく表現できます。 8, 16 (CD音質), 24
Channels (チャンネル数) 音声のチャンネル数。 1 (モノラル), 2 (ステレオ)
Frame Size (フレームサイズ) 1フレームあたりのバイト数。(サンプルサイズ / 8) * チャンネル数で計算されます。 16bitステレオの場合: (16 / 8) * 2 = 4 バイト
Frame Rate (フレームレート) 1秒あたりのフレーム数。通常はサンプルレートと同じ値になります。 44100.0F
Big Endian (バイトオーダー) 1サンプルが複数バイトで構成される場合(例: 16bit)のバイトの順序。 true (ビッグエンディアン), false (リトルエンディアン)

例えば、「CD音質」と一般的に言われるフォーマットは、「サンプルレート 44.1kHz、ビット深度 16bit、ステレオ」であり、これをAudioFormatで表現すると以下のようになります。


float sampleRate = 44100.0F;
int sampleSizeInBits = 16;
int channels = 2;
boolean signed = true; // 符号付きPCM
boolean bigEndian = true; // ビッグエンディアン

AudioFormat cdQualityFormat = new AudioFormat(
    AudioFormat.Encoding.PCM_SIGNED,
    sampleRate,
    sampleSizeInBits,
    channels,
    (sampleSizeInBits / 8) * channels, // frameSize
    sampleRate, // frameRate
    bigEndian
);
System.out.println("CD音質のフォーマット: " + cdQualityFormat);
        

第5章: マイクからの録音 – TargetDataLineの活用

これまでは音声の「出力」を扱ってきましたが、javax.sound.sampledは「入力」、つまり録音機能も提供します。その中心となるのがjavax.sound.sampled.TargetDataLineです。

TargetDataLineは、マイクのような音声入力デバイス(ソース)からデータを取得(ターゲット)するためのラインです。SourceDataLineがデータを書き込むためのラインだったのに対し、TargetDataLineはデータを読み込むためのラインと考えると分かりやすいでしょう。

TargetDataLineを使った録音手順

  1. AudioFormatの定義: どのような品質で録音するかを定義するAudioFormatオブジェクトを作成します。
  2. TargetDataLineの取得: 定義したAudioFormatを指定して、AudioSystem.getTargetDataLine()でラインを取得します。
  3. ラインのオープンと開始: open()でラインを準備し、start()で音声データのキャプチャを開始します。
  4. データの読み込みループ: read()メソッドを使ってTargetDataLineから音声データをバイト配列に読み込みます。この処理を録音したい時間だけ繰り返します。
  5. ファイルへの書き込み: 読み込んだデータをAudioInputStreamでラップし、AudioSystem.write()を使ってWAVファイルなどの形式で保存します。
  6. 終了処理: 録音が終わったらstop()close()でリソースを解放します。

サンプルコード: マイクからの音声をWAVファイルに録音


import javax.sound.sampled.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;

public class RecordAudio {
    private static final long RECORD_TIME = 5000; // 5秒間録音

    public static void main(String[] args) {
        try {
            // 1. 録音フォーマットを定義
            AudioFormat format = new AudioFormat(
                AudioFormat.Encoding.PCM_SIGNED,
                44100.0F, 16, 2, 4, 44100.0F, false);

            DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
            if (!AudioSystem.isLineSupported(info)) {
                System.err.println("このフォーマットでの録音はサポートされていません。");
                return;
            }

            // 2. TargetDataLineを取得
            TargetDataLine line = (TargetDataLine) AudioSystem.getLine(info);
            // 3. ラインをオープンして録音開始
            line.open(format);
            line.start();
            System.out.println("録音を開始します... (5秒間)");

            // 録音データを一時的に保持するストリーム
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[line.getBufferSize() / 5];
            
            // 録音スレッドを開始
            Thread recordThread = new Thread(() -> {
                try {
                    while (true) {
                        int bytesRead = line.read(buffer, 0, buffer.length);
                        baos.write(buffer, 0, bytesRead);
                    }
                } catch (Exception e) {
                    // スレッドが停止されると例外が発生する可能性がある
                }
            });
            
            recordThread.start();
            
            // 指定時間待機
            Thread.sleep(RECORD_TIME);
            
            // 4. 録音を停止
            line.stop();
            line.close();
            recordThread.interrupt(); // 録音スレッドを安全に停止
            System.out.println("録音を終了しました。");
            
            // 5. 録音データをファイルに保存
            byte[] audioData = baos.toByteArray();
            AudioInputStream ais = new AudioInputStream(
                new ByteArrayInputStream(audioData),
                format,
                audioData.length / format.getFrameSize()
            );

            File wavFile = new File("recorded_audio.wav");
            AudioSystem.write(ais, AudioFileFormat.Type.WAVE, wavFile);
            System.out.println("ファイルに保存しました: " + wavFile.getAbsolutePath());

        } catch (LineUnavailableException | InterruptedException | IOException e) {
            e.printStackTrace();
        }
    }
}
        

このコードは、デフォルトの入力デバイス(通常は内蔵マイク)から5秒間音声を録音し、プロジェクトのルートディレクトリにrecorded_audio.wavという名前で保存します。


第6章: 入出力デバイスの選択 – Mixerを使いこなす

最近のPCには、内蔵マイクやスピーカーの他に、USBヘッドセット、外付けオーディオインターフェースなど、複数の音声入出力デバイスが接続されていることが珍しくありません。javax.sound.sampledでは、これらのデバイスをMixerというインタフェースで抽象化しています。

Mixerを使いこなすことで、アプリケーションが使用する音声デバイスを明示的に選択できるようになります。例えば、「USBマイクから録音し、ヘッドホンで再生する」といった制御が可能です。

Mixerの役割と取得方法

ミキサーは、1つまたは複数のラインを持つオーディオデバイスを表します。ラインはミキサーとの間で音声データをやり取りします。

  • ソースライン (Source Lines): 音声を再生するためのライン (SourceDataLine, Clipなど)。ミキサーにデータを送る。
  • ターゲットライン (Target Lines): 音声を録音するためのライン (TargetDataLineなど)。ミキサーからデータを受け取る。

システムで利用可能なすべてのミキサー情報は、AudioSystem.getMixerInfo()メソッドで取得できます。

サンプルコード: 利用可能な入出力デバイスの一覧表示

以下のコードは、システムに接続されているすべてのミキサーをスキャンし、それぞれが持つソースライン(出力デバイス)とターゲットライン(入力デバイス)の情報を表示します。


import javax.sound.sampled.*;

public class ListAudioDevices {
    public static void main(String[] args) {
        System.out.println("利用可能なオーディオデバイス:");
        System.out.println("===============================");

        Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo();
        for (Mixer.Info mixerInfo : mixerInfos) {
            Mixer mixer = AudioSystem.getMixer(mixerInfo);
            System.out.println("\n[ミキサー名]: " + mixerInfo.getName());
            System.out.println("  [説明]: " + mixerInfo.getDescription());
            
            // === 出力デバイス(ソースライン)のチェック ===
            Line.Info[] sourceLineInfos = mixer.getSourceLineInfo();
            if (sourceLineInfos.length > 0) {
                System.out.println("  <サポートする出力ライン>");
                for (Line.Info lineInfo : sourceLineInfos) {
                    System.out.println("    - " + lineInfo.getLineClass().getSimpleName());
                }
            } else {
                System.out.println("  <出力ラインはサポートしていません>");
            }

            // === 入力デバイス(ターゲットライン)のチェック ===
            Line.Info[] targetLineInfos = mixer.getTargetLineInfo();
            if (targetLineInfos.length > 0) {
                System.out.println("  <サポートする入力ライン>");
                for (Line.Info lineInfo : targetLineInfos) {
                    System.out.println("    - " + lineInfo.getLineClass().getSimpleName());
                }
            } else {
                System.out.println("  <入力ラインはサポートしていません>");
            }
        }
        System.out.println("\n===============================");
    }
}
        

このコードを実行すると、お使いのPCに接続されているサウンドカード、USBマイク、仮想オーディオデバイスなどの一覧が表示されます。特定のデバイスを使いたい場合は、Mixer.Infoの名前や説明を元に目的のMixerインスタンスを取得し、そのミキサーのgetLine()メソッドを呼び出してラインを取得します。


まとめ

この記事では、Javaの標準ライブラリであるjavax.sound.sampledについて、その基本的な概念から応用的な使い方までを詳細に解説しました。

短い音声を扱うClip、長い音声をストリーミングするSourceDataLine、録音を行うTargetDataLineという主要なインタフェースの使い分けを理解し、音声データの仕様を定義するAudioFormatの重要性を学びました。さらに、Mixerを使って特定のオーディオデバイスを選択する方法も見てきました。

javax.sound.sampledは、MP3のネイティブサポートがないなど、現代的な要件から見ると一部古さを感じる部分もありますが、WAVなどの基本的な音声形式を扱う上では非常に堅牢で信頼性の高いAPIです。Javaで音声処理の第一歩を踏み出すための、強力な基盤となるでしょう。

このライブラリを使いこなすことで、Javaアプリケーションにリッチなオーディオ機能を組み込み、ユーザー体験を向上させることが可能になります。

モバイルバージョンを終了