第 2 章: Sampled パッケージの概要


 

ここでは、JavaTM Sound API のデジタルオーディオアーキテクチャの概要を説明します。アーキテクチャには javax.sound.sampled パッケージからアクセスできます。 最初に、パッケージの中心機能である書式付きのオーディオデータの再生と取り込みについて説明します。 次に、再生と取り込みに必要な 3 つの基本要素である、オーディオデータ形式、ライン、ミキサーについて説明します。 Line インタフェースとそのサブインタフェースについては簡単に説明します。

設計目標

Java Sound API の要素の考察を始める前に、javax.sound.sampled パッケージの位置付けについて説明します。

作業の中心はデータ転送

javax.sound.sampled パッケージは、音声の転送に関係するものです。つまり Java Sound API は再生と取り込みが中心であるといえます。 Java Sound API が取り組む主な作業は、書式付きオーディオデータのバイトをシステムの内外にどのように移動させるかということです。 この作業には、オーディオ入出力デバイスのオープン、およびリアルタイムのサウンドデータを格納する複数のバッファの管理が伴います。 また、入力であれ出力であれ、複数のオーディオストリームを 1 本のストリームにミキシングする作業も伴います。 ユーザがサウンドのフローの開始、一時停止、再開、停止を要求する時、システム内外へのサウンドの転送は、適切に処理される必要があります。

基本のオーディオ入出力を中心的にサポートするため、Java Sound API にはさまざまなオーディオデータ形式間での変換用のメソッドと、共通タイプのサウンドファイルの読み込み/書き込み用のメソッドが提供されています。 ただし、この API は包括的なサウンドファイルツールキットを目指すものではありません。 特定の Java Sound API の実装では、ファイルタイプやデータ形式変換の拡張セットのサポートを必要としません。 サードパーティのサービスプロバイダから、既存の実装にプラグインして追加のファイルタイプとファイル変換をサポートできるモジュールが提供されています。

オーディオのバッファ付き処理とバッファなし処理

Java Sound API では、バッファ付き方式のストリーミングと、バッファなしのメモリ内方式のストリーミングの両方で音声の転送を処理することができます。 ここでは「ストリーミング」という用語はオーディオバイトのリアルタイム処理という一般的な意味で使用し、インターネット上でよく使用されるオーディオ送信という特定の形式で使用される場合を指しません。 つまり、オーディオのストリームとは単に、処理 (再生、録音など) されるレートとほぼ同じレートで到着する連続したオーディオバイト群です。 バイトに対する操作は、すべてのデータが到着する前に開始します。 ストリーミングモデルで、特にオーディオ出力ではなくオーディオ入力の場合は、サウンドの長さと、すべてが到着するまでの時間があらかじめわかっているとは限りません。 操作が停止するまで、バッファ内のオーディオデータを一度に 1 つずつ処理するだけです。 オーディオ出力 (再生) の場合も、再生するサウンドが大きすぎて一度にメモリに入りきらないときは、データをバッファリングする必要があります。 つまり、オーディオバイトを複数のチャンク (塊) に分けてサウンドエンジンに渡し、サウンドエンジンは適切な時に各サンプルの再生処理を行います。 各チャンクの適正なデータ量を簡単に知るための機構が用意されています。

Java Sound API では、再生のみの場合はバッファなしの転送も可能ですが、すべてのオーディオデータが手元にあり、メモリに入りきるサイズであることが条件です。 この場合は、アプリケーションプログラムによるオーディオのバッファリングは必要ありませんが、必要に応じてバッファ付きのリアルタイム技法を利用することもできます。 また、あらかじめサウンド全体をいったんメモリにロードしておいてから再生することもできます。 この方法ではあらかじめすべてのサウンドデータがロードされているので、たとえばユーザが [開始 (Start)] ボタンをクリックすると同時に再生を開始できます。 この方法は、最初のバッファがいっぱいになるまで待つ必要のあるバッファ付きモデルよりも有利です。 さらに、バッファなしのメモリ内方式のモデルでは、簡単にサウンドをループ (循環) させたりデータ上の任意の位置にセットしたりすることができます。

この再生のための 2 つのモデルについては第 4 章「オーディオの再生」で詳細に説明します。 バッファ付きの録音については、第 5 章「オーディオの取り込み」で説明します。

基本要素: フォーマット、ミキサー、ライン

Java Sound API を使ってサウンドを再生したり取り込んだりするには、少なくとも、書式付きオーディオデータ、ミキサー、ラインの 3 つの要素が必要です。 次に、それぞれについて説明します。

書式付きオーディオデータとは

書式付きオーディオデータとは、いくつかの標準形式のうちのいずれかにフォーマットされているサウンドを指します。 Java Sound API では、「データ形式」と「ファイル形式」を区別しています。

データ形式

データ形式は、「raw」サンプリングオーディオデータ (すでにサウンドファイルから読み取られているサンプルやマイクロフォン入力から取り込まれているサンプルなど) の一連のバイトを解釈する方法を示します。 たとえば、1 つのサンプルが何ビットで構成されているか (サウンドの最短瞬間の表示) や、サウンドのサンプリングレート (サンプリングの間隔) を知る必要があります。 再生または取り込みの設定を行うときは、再生または取り込みを行うサウンドのデータ形式を指定します。

Java Sound API ではデータ形式は AudioFormat オブジェクトで表されます。このオブジェクトには次の属性があります。

PCM は音の波形のエンコーディング手法の 1 つです。 Java Sound API には、振幅の線形量子化を使うものと符号付き/符号なしの整数値を使うものの 2 種類の PCM エンコーディングがあります。 線形量子化とは、サンプル内に保存されている数値がその瞬間の元の音圧に正比例し (ひずみを除く)、同様にその瞬間の音で振動するスピーカや鼓膜の変位に比例することを意味します。 たとえば、コンパクトディスクは線形 PCM エンコーディングサウンドを使用します。 mu-law エンコーディングと a-law エンコーディングは一般的な非線形エンコーディングで、オーディオデータをより小さく圧縮することができます。これらのエンコーディング手法は、通常、電話や発話の録音に使用されます。 非線形エンコーディングでは非線形関数を使って、元の音声の振幅を内部値にマップします。この関数は、ボリュームの大きい音より小さい音に、より高い振幅分解能を持たせるように設計することができます。

1 つのフレームには、特定の時間のすべてのチャネルのデータが含まれます。 PCM エンコーディングデータの場合は、フレームは単に所定の瞬間におけるすべてのチャネルの同時サンプルのセットであり、それ以外の情報は付随しません。 この場合、フレームレートはサンプリングレートに等しく、バイト単位のフレームサイズは、チャネル数×ビット単位のサンプルサイズ÷バイト内のビット数で求められます。

その他のエンコーディング手法では、フレームにサンプル以外の情報が付随することがあります。またフレームレートがサンプリングレートとまったく異なることがあります。 たとえば、MP3 (MPEG-1 Audio Layer 3) エンコーディングを考えます。この手法は Java Sound API の現バージョンでは明示的には言及してはいませんが、Java Sound API の実装またはサードパーティサービスプロバイダによりサポートされる場合があります。 MP3 では、各フレームにはチャネルごとのサンプルだけでなく、一連のサンプルのまとまった圧縮データが含まれます。 各フレームにサンプル群全体が縮約されるため、フレームレートはサンプリングレートよりも遅くなります。 フレームにはヘッダも含まれます。 ヘッダがあっても、バイト単位のフレームサイズは、相当する数の PCM フレームを合計したバイトサイズよりも小さくなります。 つまり、MP3 は、PCM よりもコンパクトなデータにすることを目的としています。 このようなエンコーディングでは、サンプリングレートとサンプルサイズとは、符号化された音声の最終的な変換目的であり、DA コンバータ (DAC) に渡される、PCM データのことです。

ファイル形式

ファイル形式は、サウンドファイルの構造について、ファイル内の raw オーディオデータの形式だけでなくファイル内に保存できるほかの情報も含めて指定します。 サウンドファイルには、WAVE (WAV としても知られ、PC に関連する場合が多い)、AIFF (Macintosh に関連する場合が多い)、AU (UNIX システムに関連する場合が多い) などのさまざまな標準形式があります。 サウンドファイルのタイプが異なるとそれぞれの構造も異なります。 たとえば、ファイルのヘッダ内のデータの配列が異なることがあります。 ヘッダには説明情報が含まれ、通常はファイルの実際のオーディオサンプルの前にありますが、ファイル形式によっては説明とオーディオデータを連続した「チャンク」の形にすることもあります。 ヘッダには、そのオーディオをサウンドファイルに保存するために使用したデータ形式の指定が含まれます。 どのタイプのサウンドファイルでもさまざまなデータ形式を使用できます (通常は 1 つのファイルに 1 つのデータ形式)。また、異なるファイル形式のファイルに同じデータ形式を使用することもできます。

Java Sound API ではファイル形式は AudioFileFormat オブジェクトで表されます。このオブジェクトには次の属性があります。

AudioSystem クラス (第 3 章「オーディオシステムリソースへのアクセス」を参照) には、異なるファイル形式でサウンドの読み込みと書き込みを行うためのメソッドと、異なるデータ形式間における形式の変換のためのメソッドがあります。 これらのメソッドの中には、AudioInputStream と呼ばれる一種のストリームを介してファイルの内容にアクセスできるものがあります。 AudioInputStream は一般的な Java InputStream クラスのサブクラスで、順次に読み出されるバイト群を縮約しています。 AudioInputStream はスーパークラスの InputStream に、そのバイトのオーディオデータ形式の知識 (AudioFormat オブジェクトで表される) を追加したものです。 サウンドファイルを AudioInputStream として読み込むことにより、サウンドファイルの構造 (ヘッダ、チャンクなど) を考慮する必要なくサンプルに直接アクセスできます。 1 回のメソッド呼び出しで、データ形式とファイルタイプについて必要なすべての情報を取得できます。

ミキサーとは

サウンド用のアプリケーションプログラミングインタフェース (API) の多くは、オーディオデバイスという概念を使用します。 「デバイス」とは、多くの場合、物理的な入出力装置へのソフトウェアインタフェースのことです。 たとえば、サウンド入力デバイスは、マイクロフォン入力、ラインレベルのアナログ入力、場合によってはデジタルオーディオ入力など、サウンドカードの入力機能を表すことがあります。

Java Sound API では、デバイスは、Mixer オブジェクトによって表されます。 ミキサーの目的は、1 つまたは複数のオーディオ入力ストリームと、1 つまたは複数のオーディオ出力ストリームを処理することです。 通常の場合、ミキサーは実際、複数の入力ストリームをミキシングして 1 本の出力ストリームにします。 Mixer オブジェクトは、サウンドカードのような物理装置のサウンドミキシング機能を表すことができます。そのような装置では、さまざまな入力装置からコンピュータに送られるサウンドやアプリケーションプログラムから出力装置へ送られるサウンドをミキシングする必要があります。

また一方で、Mixer オブジェクトは、物理装置への固有のインタフェースを持たずに完全にソフトウェア内に実装したサウンドミキシング機能を表すこともできます。

Java Sound API では、サウンドカード上のマイクロフォン入力などの構成要素は、それ自体ではデバイス (ミキサー) とはみなされず、ミキサーへの入出力の「ポート」とみなされます。 ポートは通常、ミキサーに入る、またはミキサーから出ていく 1 本のオーディオストリームを作ります。ただし、ストリームはステレオのように複数チャネルでも構いません。 ミキサーには、このようなポートが複数あることがあります。 たとえば、サウンドカードの出力機能を表すミキサーは複数のオーディオストリームをミキシングして、ミキサーに接続しているさまざまな出力ポートの 1 つまたはすべてにミキシングした信号を送ります。 これらの出力ポートとして、ヘッドフォンジャック、内蔵スピーカ、ラインレベル出力などがあります。

ライブコンサートやレコーディングスタジオで使われている実際のミキシングコンソールを思い浮かべると、Java Sound API のミキサーの概念が理解しやすくなります (下の図を参照)。

物理ミキシングコンソール

物理装置としてのミキサーには複数の「ストリップ」(スライスとも呼ばれる) があり、それぞれのストリップは 1 つのオーディオ信号が処理のためにミキサーに送られる経路を表します。 ストリップには、そのストリップ内の信号のボリュームとパン (ステレオイメージでの配置) を調節するためのつまみとコントロールがあります。 また、ミキサーにはリバーブ (残響) などのサウンドエフェクトのための独立したバスがあり、内部または外部のリバーブユニットにこのバスを接続できます。 各ストリップには、そのストリップの信号のうち、リバーブをかける信号の量を調節するポテンショメータがあります。 リバーブをかけた (「ウェット」) ミックスは、次にストリップからの「ドライ」の信号とミキシングされます。 物理ミキサーは、ミキシングされたこの最終信号を出力バスに送り、出力バスは通常はテープレコーダ (またはディスクによる録音システム) とスピーカに接続しています。

ライブコンサートのステレオ録音を思い出してみてください。 ステージ上の多数のマイクや電子楽器から出ているケーブル (または無線接続) はミキシングコンソールの入力プラグに差し込まれています。 それぞれの入力は、図に示すようにミキサーの別々のストリップにつながっています。 音響技師が、ゲイン、パン、リバーブ調節の設定を決めます。 すべてのストリップとリバーブユニットの出力が 2 本のチャネルにミキシングされます。 この 2 本のチャネルは、ミキサーの 2 つの出力に送られます。この出力には、ステレオテープレコーダの入力に接続しているケーブルが差し込まれています。 この 2 本のチャネルは、アンプを介して音楽の種類とホールの大きさに合わせて選択されたスピーカに送られます。

次に、録音スタジオで、楽器や歌手の音声をマルチトラックテープレコーダの別々のトラックに録音する場合を考えます。 すべての楽器と歌手の音声を録音し終わってから、録音技師が「ミックスダウン」を行い、テープ録音されたすべてのトラックをまとめて、コンパクトディスクに配布可能な 2 チャネル (ステレオ) 録音にします。 この場合、ミキサーの各ストリップへの入力はマイクロフォンではなく、マルチトラック録音の 1 つのトラックです。 ここでも、録音技師はストリップのコントロールを使って、各トラックのボリューム、パン、およびリバーブの量を決定することができます。 ライブコンサートの例と同様に、ミキサーの出力は再びステレオレコーダと複数のステレオスピーカに送られます。

上記の 2 つの例は、ミキサーの 2 つの使用方法を示しています。 複数の入力チャネルを取り込んで少数のトラックにまとめて記録し、ミキシング済みの信号を保存する方法と、複数のトラックを再生しながら少数のトラックにミックスダウンする方法です。

Java Sound API では、ミキサーは入力 (オーディオの取り込み) と出力 (オーディオの再生) に同様に使用できます。 入力の場合は、ミキシングのためのオーディオを取得する「ソース」は、1 つ以上の入力ポートです。 ミキサーは、取り込んでミキシングしたオーディオストリームを「ターゲット」に送ります。ターゲットは、アプリケーションプログラムがミキシング済みオーディオデータを取り出すことのできるオブジェクトです。 オーディオ出力の場合は、状況が逆になります。 ミキサーのオーディオソースは、1 つ以上のアプリケーションプログラムがサウンドデータを書き込むことのできる複数のバッファを持つ 1 つ以上のオブジェクトであり、ミキサーのターゲットは 1 つ以上の出力ポートです。

ラインとは

物理ミキシングコンソールの例は Java Sound API の「ライン」の概念の理解にも役立ちます。

ラインとは、デジタルオーディオの「パイプライン」、つまりオーディオをシステムの内外に転送するための経路の 1 つの要素です。 通常、ラインはミキサーへの入力または出力の経路です (技術的には、ミキサー自体も一種のラインです)。

オーディオ入出力ポートはラインです。 これらは、物理ミキシングコンソールに接続されているマイクロフォンとスピーカに似ています。 ラインの種類にはこのほか、アプリケーションプログラムがミキサーから入力オーディオを取得したりミキサーに出力オーディオを送るために使用するデータ パスがあります。 これらのデータ パスは、物理ミキシングコンソールに接続されているマルチトラックレコーダのトラックに似ています。

Java Sound API のラインと物理的なミキサーのラインの違いの 1 つは、Java Sound API を流れるオーディオデータはモノラルの場合とマルチチャネル (ステレオなど) の場合があることです。 一方、物理ミキサーの入力と出力はそれぞれ、通常は単一チャネルのサウンドです。 物理ミキサーから複数の出力チャネルを取り出すには、通常は複数の物理出力が使われます (少なくともアナログサウンドの場合は、デジタル出力ジャックはマルチチャネルの場合が多い)。 Java Sound API では、1 つのライン内のチャネルの数は、そのラインを流れているデータの AudioFormat によって指定されます。

オーディオ出力構成のライン

ここで、ラインとミキサーについて、いくつかの種類を挙げて考えてみます。 次の図は、Java Sound API の実装の一部として使用できる、簡単なオーディオ出力システム内の数種類のラインを示します。

オーディオ出力用ラインの構成例

この例では、アプリケーションプログラムはオーディオ入力ミキサーの使用可能な入力へのアクセス方法を持っています。 それは、1 つまたは複数の「クリップ」と「ソースデータライン」です。 クリップとは、オーディオデータをあらかじめロードしてから再生できるミキサー入力 (ラインの一種) です。ソースデータラインは、オーディオデータのリアルタイムのストリームを受け取るミキサー入力です。 アプリケーションプログラムはサウンドファイルからクリップにオーディオデータをあらかじめロードします。 次に、アプリケーションプログラムはその他のオーディオデータを、1 回に 1 バッファ分ずつソースデータラインに送ります。 各ラインにはリバーブ、ゲイン、パンのコントロールがあります。ミキサーはすべてのラインからデータを読み込み、ドライのオーディオ信号とリバーブをかけたウェットのオーディオ信号をミキシングします。 ミキサーは、スピーカ、ヘッドフォンジャック、ラインアウトジャックなどの 1 つ以上の出力ポートに最終的な出力を配信します。

この図では、さまざまなラインは独立した矩形で表されていますが、これらはすべてミキサーが所有するもので、ミキサーの一部と考えることができます。 リバーブ、ゲイン、およびパンの矩形は、ラインを流れているデータに対してミキサーが実行する処理コントロールを表すもので、ラインではありません。

これは、この API でサポートできるミキサーの一例です。 すべてのオーディオ構成に、図で示されているすべての機能があるわけではありません。 個々のソースデータラインでパンをサポートしない場合や、ミキサーがリバーブを実装しない場合などもあります。

オーディオ入力構成のライン

単純なオーディオ入力システムも同様です。

オーディオ入力用ラインの構成例

ここでは、データは 1 つ以上の入力ポート (通常はマイクロフォンまたはライン入力ジャック) からミキサーに流れます。 ゲインとパンが適用され、ミキサーはミキサーのターゲットデータラインを経由して、取り込んだデータをアプリケーションに配信します。 ターゲットデータラインは、ストリーミングされた入力サウンドの混合が含まれるミキサー出力です。 もっとも単純なミキサーのターゲットデータラインは 1 つだけですが、取り込んだデータを同時に複数のターゲットデータラインに配信できるミキサーもあります。

Line インタフェースの階層

ここまではラインとミキサーについて、機能面からいくつか見てきましたが、ここからは、プログラムの視点からの考察を行います。 いくつかのラインは、基本インタフェース Line のサブインタフェースによって定義されています。 インタフェースの階層を次に示します。

Line インタフェースの階層

基本インタフェース Line には、すべてのラインに共通する最小限の機能が記述されています。

次に、Line インタフェースのサブインタフェースについて考えます。

Ports は、オーディオデバイスに対するオーディオの入力または出力のための単純なラインです。 すでに説明したとおり、一般的なポートの種類には、マイクロフォン、ライン入力、CD-ROM ドライブ、スピーカ、ヘッドフォン、ライン出力などがあります。

Mixer インタフェースは当然ミキサーを表し、すでに説明したようにハードウェアデバイスまたはソフトウェアデバイスです。 Mixer インタフェースは、ミキサーのラインを取得するためのメソッドを提供します。 ミキサーのラインには、オーディオをミキサーに送り込むソースラインと、ミキサーがミキシング済みのオーディオを送り出すターゲットラインがあります。 オーディオ入力ミキサーの場合、ソースラインはマイクロフォン入力などの入力ポートで、ターゲットラインはオーディオをアプリケーションプログラムに送る TargetDataLines (このあとで説明) です。 一方、オーディオ出力ミキサーの場合、ソースラインはアプリケーションプログラムがオーディオデータを送り込む ClipsSourceDataLines (このあとで説明) で、ターゲットラインはスピーカなどの出力ポートです。

Mixer は、1 つ以上のソースラインと 1 つ以上のターゲットラインを持つものと定義されます。 この定義によれば、ミキサーは実際にデータをミキシングしなくても構わないので、ミキサーのソースラインが 1 本だけという可能性もあります。 Mixer API はさまざまなデバイスを包含するためのものですが、通常は、この API でミキシングがサポートされています。

Mixer インタフェースでは同期がサポートされるので、複数のミキサーのラインを同期グループとして扱うよう指定できます。 指定後は、各ラインを個別に制御する必要はなく、グループのラインのどれかに単一のメッセージを送ることにより、グループのすべてのデータラインを開始、停止、またはクローズすることができます。 この機能を持つミキサーを使うと、ライン間でサンプル精度の同期が得られます。

汎用の Line インタフェースには、再生と録音の開始と停止を行う手段はありません。 そのためには、データラインが必要です。 DataLine インタフェースは、Line の機能以外に次の補足的なメディア関連機能を提供します。

  • オーディオ形式
    各データラインのデータストリームには、オーディオ形式が関連付けられています。
  • メディア位置
    データラインは、メディア内の現在の位置をサンプルフレーム数で表して通知できます。 これは、オープン後にデータラインより取り込まれたか、またはレンダリングされたサンプルフレームの数を表します。
  • バッファサイズ
    データラインの内部バッファのサイズ (バイト数) です。 ソースデータラインの場合は、内部バッファへのデータの書き込みが可能です。ターゲットデータラインの場合は、内部バッファからのデータの読み込みが可能です。
  • レベル (オーディオ信号の現在の振幅)

  • 再生または取り込みの開始および停止

  • 再生と取り込みの一時停止と再開

  • フラッシュ (キューから未処理データを破棄する)

  • ドレイン (すべての未処理データが掃き出され、データラインのバッファが空になるまでブロックする)

  • アクティブ状態
    アクティブな再生動作またはミキサーの入出力オーディオデータの取り込み動作に携わっているデータラインは「アクティブ状態」とみなされます。
  • イベント
    アクティブな再生動作またはミキサーの入出力オーディオデータの取り込み動作の開始時と終了時に、START および STOP イベントが生成されます。
  • TargetDataLine は、ミキサーからオーディオデータを受け取ります。 通常、ミキサーはマイクロフォンなどのポートからオーディオデータを取り込んでいるため、ターゲットラインのバッファにデータを置く前に、取り込んだオーディオを処理またはミキシングできます。 TargetDataLine インタフェースは、ターゲットデータラインのバッファからデータを読み込むためのメソッド、および現在読み込みが可能なデータ量を特定するためのメソッドを提供します。

    SourceDataLine は、再生用のオーディオデータを受け取ります。 SourceDataLine は、再生用にソースデータラインのバッファにデータを書き込むためのメソッド、およびラインがブロックされずに受け取る用意ができているデータ量を特定するためのメソッドを提供します。

    Clip は、再生前にオーディオデータをロードできるデータラインです。 データはストリーミングではなく事前にロードされるため、クリップのデュレーションが再生前にわかり、メディア内での開始位置を任意に選択できます。 クリップはループできます。つまり、再生時に 2 つの指定したループ点間のすべてのデータを、指定した回数または無期限に繰り返すことができます。

    この章では、サンプリングオーディオ API の重要なインタフェースとクラスのほとんどについて説明しました。 次章からは、これらのオブジェクトをアプリケーションプログラムからアクセスして使用する方法について説明します。