JAVA SOUND APIの基本

こんにちは、Habr! 記事「Javaサウンド、入門、パート1、再生」の翻訳を紹介します。

JAVAのサウンド、パート1、始まり。 音を鳴らす



これは、Java Sound APIに完全に慣れる8つのレッスンのシリーズの最初のレッスンです。

人間の知覚における音とは何ですか? これは、気圧の変化が耳の中の小さな感覚領域に伝わるときに感じる感覚です。

また、Sound APIを作成する主な目的は、適切なタイミングで適切な被験者の耳に圧力波を伝達するのに役立つコードを記述する手段を提供することです。

Javaのサウンドの種類:

  1. Java Sound APIは、主に2つのタイプのオーディオ(サウンド)をサポートしています。
  2. デジタル化され、ファイルとして直接録音されたサウンド
  3. MIDIファイルとして記録します。 非常に離れていますが、楽器が希望の順序で演奏される楽譜に似ています。

これらのタイプは本質的にかなり異なります。ほとんどの場合、外部ソースからファイルに録音するためにデジタル化する必要があるサウンド、またはそのようなファイルから以前に録音されたサウンドを再生するためにデジタル化する必要があるサウンドを扱っているため、最初に集中します。

プレビュー


Java Sound APIは、 ラインとミキサーの概念に基づいています。

次:
オーディオミキサーに適用される音のアナログ表現の物理的および電気的特性について説明します

最初のロックバンドのシナリオに戻ります。この場合、6つのマイクと2つのステレオスピーカーを使用します。 これは、オーディオミキサーの動作を理解するために必要です。

次に、ライン、ミキサー、オーディオデータのフォーマットなど、Java Soundプログラミングのトピックをいくつか見ていきます。

SourceDataLine、Clip、Mixer、AudioFormatの各オブジェクト間に存在する関係を理解し​​、オーディオを再生する簡単なプログラムを作成します。

以下に、録音した音を録音して再生するために使用できるこのプログラムの例を示します。

将来、この目的で使用されるプログラムコードの完全な説明を提供します。 しかし、このレッスンでは決して完全ではありません。

コード例と考慮事項


アナログサウンドの物理的および電気的特性

このレッスンの目的は、Java Sound APIを使用したJavaプログラミングの基本を紹介することです。

Java Sound APIは、オーディオミキサーの概念に基づいています。これは、ロックコンサートから自宅でのCDの再生まで、ほとんどどこでもサウンドを再生するときに一般的に使用されるデバイスです。 しかし、オーディオミキサーの動作の詳細な説明に着手する前に、アナログサウンド自体の物理的および電気的特性を理解しておくと役立ちます。

図を見てください。 1



Vasya Pupyrkinはスピーチを押します。

この図は、Vasyaが広域アドレスシステムと呼ばれるシステムを使用してスピーチを行っている様子を示しています。 このようなシステムは通常、マイク、アンプ、スピーカーで構成されます。 このシステムの目的は、Vasyaの声を強化して、大勢の人でも聞こえるようにすることです。

ぐらつき

手短に言えば、Vasyaが話すと、声帯が空気粒子を喉頭で振動させます。 これにより音波が発生し、それがマイクメンブレンを振動させ、Vasyaのオリジナルの音の振動を正確にシミュレートする非常に小さな振幅の電気振動に変えます。 アンプは、その名前が示すように、これらの電気振動を増幅します。 その後、スピーカーに到達します。スピーカーは、増幅された電気振動を非常に増幅された音波に逆変換しますが、Vasya Pupyrkinの声帯で生成された同じ波を正確に繰り返します。

ダイナミックマイク

それでは、図を見てみましょう。 図2は、ダイナミックと呼ばれるマイクデバイスの概略図です。


2ダイナミックマイク回路

音の振動は膜に影響します

音の振動の圧力は、マイク内部の柔軟な膜に作用します。 これにより膜が振動し、膜の振動が音波の振動を繰り返します。

可動コイル

細いワイヤーのコイルがマイクのメンブレンに取り付けられています。 膜が振動すると、コイルは強力な永久磁石で作られたコアの磁場内で往復運動もします。 そしてファラデーも確立したように、コイルに電流が発生します。

電気信号は音波の形に従います。

したがって、コイルに誘導された非常に弱い電流から、交互の電気信号が得られ、マイクロホンの膜に作用する音波の形を繰り返します。 さらに、交流電圧の形のこの信号は、図1の増幅器の入力に供給されます。 1。

拡声器

実際、スピーカーの動作原理はダイナミックマイクのデバイスを繰り返し、反対方向でのみオンになります。 (当然、この場合、巻線ははるかに太く、膜は増幅された信号での動作を保証するためにはるかに大きくなります)




ラウドスピーカー膜の振動は空気粒子に影響を与え、強力な音波を作成します。 これらの波の形状は、Vasyaの声帯によって作成されるはるかに低い強度の音波の形状を正確に繰り返します。 しかし、新しい波の強さは、Vasyaからの音の振動が大勢の人の後ろの列にさえ立っている人々の耳に届くようにするのに十分です。

ロックコンサート

この時までに、あなたはこれがすべてJava Sound APIと何の関係があるのか​​と疑問に思うかもしれません。 しかし、もう少し待ってください。オーディオミキサーの基本的な仕組みを紹介しています。

上記の回路は非常にシンプルでした。 Vasya Pupyrkin、1つのマイク、アンプ、スピーカーで構成されていました。 次に、図2の回路を考えます。 4、最初の音楽グループのロックコンサートのために準備されたステージを提示します。



6つのマイクと2つのスピーカー

図 4つの6つのマイクがステージ上にあります。 ステージの両側に2つのスピーカー(スピーカー)があります。 コンサートが始まると、演奏者は6つのマイクのそれぞれで歌ったり、音楽を演奏したりします。 したがって、6つの電気信号があり、それらを個別に増幅してから両方のスピーカーに供給する必要があります。 これに加えて、演奏者はさまざまなサウンド特殊効果、たとえばリバーブを使用できます。リバーブは、スピーカーに適用する前に電気信号に変換する必要があります。

ステージの両側にある2つのスピーカーは、ステレオサウンドの効果を作り出すように設計されています。 つまり、右側のステージにあるマイクからの電気信号は、右側にもあるスピーカーに落ちます。 同様に、左側のマイクからの信号は、シーンの左側にあるスピーカーに送る必要があります。 ただし、ステージの中央近くにある他のマイクからの電気信号は、適切な比率で両方のスピーカーに既に送信されているはずです。 また、中央の2つのマイクは、両方のスピーカーに均等に信号を送信する必要があります。

オーディオミキサー

上記のタスクは、オーディオミキサーと呼ばれる電子デバイスによって実行されます。

音声回線(チャンネル)

著者はオーディオミキサーの専門家ではありませんが、彼の謙虚な理解では、典型的なオーディオミキサーは、それぞれの元のオーディオ信号またはライン(チャネル)を表す互いに独立した電気信号を一定数受信する能力を持っています

(オーディオチャネルの概念は、Java Sound APIを詳細に理解し始めると非常に重要になります。

各オーディオチャンネルの独立した処理

いずれにせよ、標準のオーディオミキサーには、他のチャンネルとは独立して各オーディオラインを増幅する機能があります。 また、ミキサーには通常、たとえばオーディオラインのリバーブなど、サウンドの特殊効果をスーパーインポーズする機能があります。 最終的に、ミキサーは、その名前が示すように、出力チャンネルへの各オーディオラインの寄与を制御するために、設定される出力チャンネルの個々の電気信号をすべてミックスできます(このコントロールは通常、パンまたはパンと呼ばれます-空間での分布)。

ステレオサウンドに戻る

したがって、図 4、オーディオミキサーのサウンドエンジニアは、6つのマイクからの信号を結合して、それぞれがスピーカーに送信される2つの出力信号を取得することができます。

操作を正常に行うには、ステージ上のマイクの物理的な位置に応じて、各マイクからの信号を適切な割合で供給する必要があります。 (パンニングを変更することにより、資格のあるサウンドエンジニアは、必要に応じて、たとえばコンサート中にリードボーカリストがステージ内を移動する場合に、各マイクの寄与を変更できます)。

プログラミングの世界に戻る時間

物理的な世界からプログラミングの世界に戻りましょう。 Sunによると、 「Java Soundには特別なハードウェア構成は含まれていません。 さまざまなオーディオコンポーネントをシステムにインストールし、APIを介してユーザーが利用できるように設計されています。 Java Soundは、サウンドカードからの標準の入力および出力機能(たとえば、オーディオファイルの記録と再生)、および複数のオーディオストリームをミックスする機能をサポートしています。

ミキサーとチャンネル

既に述べたように、Java Sound APIはミキサーとチャンネルの概念に基づいて構築されています。 物理的な世界からプログラミングの世界に移行する場合、Sunはミキサーに関して次のように記述します。

「ミキサーとは、1つ以上のチャンネルを持つオーディオデバイスです。 しかし、オーディオ信号を実際にミキシングするミキサーには、ソースソースの複数の入力チャネルと、少なくとも1つの出力ターゲットチャネルが必要です。

入力行はSourceDataLineオブジェクトを持つクラスのインスタンスにでき、出力行はTargetDataLineオブジェクトにできます。 ミキサーは、事前に録音されループされたサウンドを入力として受け取り、その入力ソースチャンネルをClipインターフェイスを実装するクラスオブジェクトのインスタンスとして定義することもできます。

チャネル回線インターフェース。

Sunは、Lineインターフェースから次のことを報告します。「 Lineは、入力または出力オーディオポート、ミキサー、またはミキサーとの間のオーディオパスなどのデジタルオーディオパイプラインの要素です。 チャンネルを通過する音声データは、モノまたはマルチチャンネル(ステレオなど)です。 ...チャンネルには、ゲイン、パン、リバーブなどのコントロールを設定できます。

用語をまとめる

したがって、上記のSunからの引用は、次の用語を意味していました

ソースデータライン
ターゲットゲタタリン

クリップ
コントロール

図5は、これらの用語を使用して簡単なオーディオ出力プログラムを作成する例を示しています。



プログラムスクリプト

ソフトウェアの観点から 図5は、1つのClipオブジェクトと2つのSourceDataLineオブジェクトで取得したMixerオブジェクトを示しています。

クリップとは

クリップは、ミキサーの入力にあるオブジェクトであり、その内容は時間とともに変化しません。 つまり、オーディオデータを再生する前にClipオブジェクトにロードします。 Clipオブジェクトのオーディオコンテンツは1回以上再生できます。 クリップをループバックすると、コンテンツが何度も再生されます。

入力ストリーム

一方、SourceDataLineオブジェクトは、ミキサーの入力にあるストリームオブジェクトです。 このタイプのオブジェクトは、オーディオデータのストリームを受信し、リアルタイムでミキサーに送信できます。 必要なオーディオデータは、オーディオファイル、ネットワーク接続、メモリバッファなど、さまざまなソースから取得できます。

さまざまなタイプのチャネル

したがって、ClipおよびSourceDataLineオブジェクトは、Mixerオブジェクトの入力チャネルと見なすことができます。 これらの各入力チャンネルには、パン、ゲイン、リバーブという独自のチャンネルを設定できます。

オーディオコンテンツを再生する

このような単純なシステムでは、Mixerは入力ラインからデータを読み取り、コントロールを使用して入力信号をミキシングし、スピーカー、ライン出力、ヘッドフォンジャックなどの1つ以上の出力チャンネルに出力を提供します。

リスト11は、マイクポートからオーディオデータをキャプチャし、このデータをメモリに保存し、スピーカーポートから再生する単純なプログラムを示しています。

キャプチャと再生のみを説明します。 上記のプログラムのほとんどは、ユーザー用のウィンドウとグラフィカルインターフェイスを作成して、記録と再生を制御できるようにすることです。 この部分については、目標を超えるものとして説明しません。 しかし、その後、データのキャプチャと再生を検討します。 このレッスンで負けを議論し、次のキャプチャを行います。 途中で、Java Sound APIでのオーディオチャネルの使用について説明します。

キャプチャされたデータは、ByteArrayOutputStreamオブジェクトに保存されます。

コードスニペットコードスニペットは、マイクからオーディオデータを読み取り、ByteArrayOutputStreamオブジェクトとして保存します。

リスト1で始まるplayAudioというメソッドは、キャプチャされてByteArrayOutputStreamオブジェクトに保存されたオーディオデータを再生します。

private void playAudio() { try{ byte audioData[] = byteArrayOutputStream. toByteArray(); InputStream byteArrayInputStream = new ByteArrayInputStream( audioData); 

リスト1

標準コードから始めます。

リスト1のプログラムのスニペットは、実際にはまだJava Soundに関連していません。

その目的は次のとおりです。


これは、後で再生するために音声データを利用可能にするために必要です。

Sound APIに移動する

リスト2のコード行は、すでにJava Sound APIに関連しています。

  AudioFormat audioFormat = getAudioFormat(); 

リスト2

ここでは、このトピックについて簡単に触れます。これについては、次のレッスンで詳しく説明します。

2つの独立した形式

ほとんどの場合、オーディオデータの2つの独立した形式を扱っています。

オーディオデータを含むファイル形式(任意)(データはメモリに格納されるため、プログラムではまだ作成されていません)

提出された音声データの形式は、それ自体です。

オーディオ形式とは何ですか?

Sunがそれについて書いていることは次のとおりです。

「各データチャネルには、データストリームに関連付けられた独自のオーディオ形式があります。 形式(AudioFormatのインスタンス)は、オーディオストリームのバイト順を決定します。 形式パラメーターは、チャネル数、サンプリング周波数、量子化ビット、エンコード方法などです。通常のエンコード方法は、PCMとその変形の線形パルス符号変調です。

バイトシーケンス

ソースオーディオデータは、バイナリデータのバイトシーケンスです。 このシーケンスを整理および解釈する方法にはさまざまなオプションがあります。 これらすべてのオプションを詳細に扱うことはしませんが、ここでプログラムで使用するオーディオ形式について少し説明します。

余談

ここでは、今のところplayAudioメソッドをそのままにして、リスト2のgetAudioFormatメソッドを見てください。

完全なgetAudioFormatメソッドをリスト3に示します。

  private AudioFormat getAudioFormat(){ float sampleRate = 8000.0F; int sampleSizeInBits = 16; int channels = 1; boolean signed = true; boolean bigEndian = false; return new AudioFormat( sampleRate, sampleSizeInBits, channels, signed, bigEndian); }//end getAudioFormat 

リスト3

初期化された変数の宣言に加えて、リスト3のコードには1つの実行可能式が含まれています。

AudioFormatオブジェクト

getAudioFormatメソッドは、AudioFormatクラスのオブジェクトのインスタンスを作成して返します。 Sunはこのクラスについて次のように書いています。

「AudioFormatクラスは、オーディオストリーム内のデータの特定の順序を定義します。 AudioFormatオブジェクトのフィールドを参照すると、バイナリデータストリームのビットを正しく解釈する方法に関する情報を取得できます。

最も単純なコンストラクターを使用します

AudioFormatクラスには2種類のコンストラクターがあります(最も単純なコンストラクターを使用します)。 このコンストラクターには次のパラメーターが必要です。


リスト3を見るとわかるように、今回のケースでは、AudioFormatオブジェクトのインスタンスに次のパラメーターを使用しました。


デフォルトでは、データはリニアPCMでエンコードされます。

使用したコンストラクターは、線形パルスコード変調と上記のパラメーターを使用してAudioFormatオブジェクトのインスタンスを作成します(次のレッスンでは線形PCMおよびその他のエンコード方法に戻ります)

再びplayAudioメソッドに戻る

Javaサウンドでオーディオデータ形式がどのように機能するかを理解したので、playAudioメソッドに戻りましょう。 使用可能なオーディオデータを再生する場合、AudioInputStreamクラスのオブジェクトが必要になります。 リスト4でそのインスタンスを取得します。

  audioInputStream = new AudioInputStream( byteArrayInputStream, audioFormat, audioData.length/audioFormat. getFrameSize()); 

リスト4

AudioInputStreamコンストラクターのパラメーター


フレームサイズを取得

リスト4からわかるように、3番目のパラメーターの値は計算を使用して作成されます。 これは、これまで言及していないオーディオ形式の属性の1つにすぎず、フレームと呼ばれます。

フレームとは何ですか?

プログラムで使用されている単純な線形PCMの場合、フレームには、特定の時間のすべてのチャネルのサンプルのセットが含まれています。

したがって、フレームサイズは、バイト単位のカウントサイズにチャネル数を掛けたものに等しくなります。

ご想像のとおり、getFrameSizeというメソッドはフレームサイズをバイト単位で返します。

フレームサイズの計算

したがって、フレーム内のオーディオデータの長さは、オーディオデータシーケンス内の合計バイト数を1フレーム内のバイト数で除算することによって計算できます。 この計算は、リスト4の3番目のパラメーターに使用されます。

SourceDataLineオブジェクトの取得

次に説明するプログラムの次の部分は、単純なオーディオ出力システムです。 図5の図からわかるように、この問題を解決するにはSourceDataLineオブジェクトが必要です。

SourceDataLineオブジェクトのインスタンスを取得する方法はいくつかありますが、そのすべてが非常に注意が必要です。 リスト5のコードは、SourceDataLineオブジェクトのインスタンスへの参照を取得して保存します。

(このコードはSourceDataLineオブジェクトをインスタンス化するだけではありません。それはかなり遠回りの方法で取得します。)

  DataLine.Info dataLineInfo = new DataLine.Info( SourceDataLine.class, audioFormat); sourceDataLine = (SourceDataLine) AudioSystem.getLine( dataLineInfo); 

リスト5

SourceDataLineオブジェクトとは何ですか?

これについて、サンは次のように書いています。

「SourceDataLineは、データを書き込むことができるデータチャネルです。 ミキサーの入力として機能します。 アプリケーションは、バイトシーケンスをSourceDataLineに書き込みます。SourceDataLineは、データをバッファリングし、ミキサーに配信します。 ミキサーは、次のステージで処理するデータを、たとえば出力ポートに送信できます。

このペアリングの命名規則は、チャネルとミキサーの関係を反映していることに注意してください。

AudioSystemクラスのGetLineメソッド

SourceDataLineオブジェクトのインスタンスを取得する方法の1つは、AudioSystemクラスから静的なgetLineメソッドを呼び出すことです(これについては、次のレッスンで詳しく報告します)。

getLineメソッドは、Line.Info型の入力パラメーターを必要とし、既に定義されているLine.Infoオブジェクトの説明と一致するLineオブジェクトを返します。

別の短い余談

Sunは、Line.Infoオブジェクトに関する次の情報を報告します。

「チャネルには独自の情報オブジェクト(Line.Infoのインスタンス)があります。このオブジェクトは、混合オーディオデータを出力としてチャネルに直接送信するミキサー(存在する場合)と、チャネルから直接入力としてオーディオデータを受信するミキサー(存在する場合)を示します。 Lineの種類はLine.Infoのサブクラスに対応できます。これにより、特定のタイプのチャネルに関連する他のタイプのパラメーターを指定できます。

DataLine.Infoオブジェクト

リスト5の最初の式は、DataLine.Infoオブジェクトの新しいインスタンスを作成します。これは、Line.Infoオブジェクトの特別な形式(サブクラス)です。

DataLine.Infoクラスには、オーバーロードされたコンストラクターがいくつかあります。 最も使いやすいものを選択しました。 このコンストラクターには2つのパラメーターが必要です。

クラスオブジェクト

最初のパラメーターはClassです。これは、SourceDataLine.classとして定義したクラスを表します

2番目のパラメーターは、チャネルに必要なデータ形式を決定します。 既に定義済みのAudioFormatオブジェクトのインスタンスを使用します。

すでに必要な場所にいますか?

残念ながら、最も必要なSourceDataLineオブジェクトはまだありません。 これまでのところ、必要なSourceDataLineオブジェクトに関する情報のみを提供するオブジェクトがあります。

SourceDataLineオブジェクトの取得

リスト5の2番目の式は、最終的に必要なSourceDataLineのインスタンスを作成して保存します。 これは、AudioSystemクラスの静的getLineメソッドを呼び出し、dataLineInfoをパラメーターとして渡すことで発生します。 (次のレッスンでは、Lineオブジェクトを取得する方法を見て、Mixerオブジェクトを直接操作します)。

getLineメソッドは、SourceDataLineの親であるLine型のオブジェクトへの参照を返します。 したがって、戻り値がSourceDataLineとして保存される前に、ここでダウンキャストが必要です。

SourceDataLineオブジェクトを使用する準備をしましょう

SourceDataLineオブジェクトのインスタンスを取得したら、リスト6に示すように、それを開いて実行する準備をする必要があります。

  sourceDataLine.open(audioFormat); sourceDataLine.start(); 

リスト6

開封方法

リスト6からわかるように、AudioFormatオブジェクトをSourceDataLineオブジェクトのopeningメソッドに送信しました。

Sunによると、これはメソッドです。

「以前に定義された形式で回線(チャネル)を開き、必要なシステムリソースを受け取り、作業(作業)状態にできるようにします。」

ディスカバリー状態

このスレッドでは、Sunが彼について書いたことはほとんどありません。

「チャネルの開閉は、システムリソースの分配に影響します。 チャネルを正常に開くと、必要なすべてのリソースがチャネルに提供されます。

オーディオデータの入力ポートと出力ポートがあるミキサーを開くには、とりわけ、必要なソフトウェアコンポーネントの作業と初期化が行われるプラットフォームのハードウェアの使用が含まれます。

ミキサーへの、またはミキサーからのオーディオデータのルートであるチャンネルを開くには、ミキサーリソースの無制限の初期化と受信の両方が含まれます。 つまり、ミキサーのチャンネル数には限りがあるため、独自のチャンネルニーズを持つアプリケーション(および1つのアプリケーションでさえも)がミキサーリソースを正しく共有する必要があります) ''

チャネルでstartメソッドを呼び出します

Sunによると、チャネルのstartメソッドを呼び出すと、次のことを意味します。

「チャネルはI / Oラインを使用できます。 すでに動作している回線を使用しようとすると、メソッドは何もしません。 しかし、データバッファーが空になった後、バッファーが完全に読み込まれた後に処理できなかった最初のフレームから、I / Oの開始が再開されます。

もちろん、この場合、チャネルは停止しませんでした。 初めて発売して以来。

これで必要なものはほぼすべて揃った

この時点で、以前に録音してByteArrayOutputStreamオブジェクトのインスタンスに保存したオーディオデータを再生するために必要なすべてのオーディオリソースを受け取りました。 (このオブジェクトはコンピューターのRAMにのみ存在することを思い出してください)。

フローを開始します

ストリームを作成して開始し、オーディオを再生します。 リスト7のコードは、このスレッドを作成して開始します。

(このスレッドのstartメソッドの呼び出しと、リスト6のSourceDataLineオブジェクトのstartメソッドの呼び出しを混同しないでください。これらはまったく異なる操作です)

 Thread playThread = new Thread(new PlayThread()); playThread.start(); } catch (Exception e) { System.out.println(e); System.exit(0); }//end catch }//end playAudio 

リスト7

気取らないコード

リスト7のプログラムのスニペットは、非常に単純ですが、Javaでのマルチスレッドプログラミングの例を示しています。 理解できない場合は、Javaを学習するための専門的なトピックでこのトピックに精通してください。

ストリームが開始されると、事前に録音されたすべてのオーディオデータが最後まで再生されるまで動作します。

新しいスレッドオブジェクト

リスト7のコードは、PlayThreadクラスに属するThreadオブジェクトのインスタンスを作成します。 このクラスは、プログラムの内部クラスとして定義されています。 その説明はリスト8から始まります。

 class PlayThread extends Thread{ byte tempBuffer[] = new byte[10000]; 

リスト8

Threadクラスのrunメソッド

tempBuffer変数(バイトの配列を参照)を宣言することを除いて、このクラスの完全な定義は、単にrunメソッドの定義です。 既にご存じのとおり、Threadオブジェクトでstartメソッドを呼び出すと、このオブジェクトのrunメソッドが実行されます

このスレッドのrunメソッドはリスト9から始まります。

 public void run(){ try{ int cnt; //  //    -1 // while((cnt = audioInputStream. read(tempBuffer, 0, tempBuffer.length)) != -1){ if(cnt > 0){ //   //    //    //   . sourceDataLine.write( tempBuffer, 0, cnt); }//end if }//end while 

リスト9

runメソッドのプログラムフラグメントの最初の部分

runメソッドには2つの重要な部分が含まれています。最初の部分をリスト9に示します。

つまり、ここではループを使用してAudioInputStreamからオーディオデータを読み取り、SourceDataLineに渡します。

SourceDataLineオブジェクトに送信されたデータは、デフォルトのオーディオ出力に自動的に転送されます。 内蔵のコンピュータースピーカーまたはライン出力を使用できます。 (次のレッスンで必要なサウンドデバイスを決定する方法を学習します)。 cnt変数とtempBufferデータバッファーは、読み取り操作と書き込み操作の間のデータの流れを制御するために使用されます。

AudioInputStreamからのデータの読み取り

AudioInputStreamオブジェクトからの読み取りサイクルは、指定された最大バイト数のデータをAudioInputStreamから読み取り、そのバイト配列を配置します。

戻り値

さらに、このメソッドは読み取られたバイトの合計数を返します。記録されたシーケンスの終わりに達した場合は-1を返します。 読み取られたバイト数は、cnt変数に格納されます。

SourceDataLine書き込みループ

読み取られたバイト数がゼロより大きい場合、SourceDataLineにデータを書き込むサイクルへの移行があります。 このループでは、オーディオデータがミキサーに入ります。 バイトは、インデックスに従ってバイト配列から読み取られ、チャネルバッファに書き込まれます。

入力ストリームが乾燥したとき

読み取りループが-1を返すとき、これは以前に記録されたすべてのオーディオデータが終了し、さらに制御がリスト10のプログラムフラグメントに渡されることを意味します。

  sourceDataLine.drain(); sourceDataLine.close(); }catch (Exception e) { System.out.println(e); System.exit(0); }//end catch }//end run }//   PlayThread 

リスト10

ロックして待つ

リスト10のコードは、SourceDataLineオブジェクトのdrainメソッドを呼び出して、プログラムがブロックし、SourceDataLineで内部バッファーが空になるのを待機できるようにします。 バッファーが空の場合、次の部分全体がコンピューターの音声出力に配信されることを意味します。

SourceDataLineを閉じる

次に、プログラムはcloseメソッドを呼び出してチャネルを閉じます。これにより、チャネルが使用するすべてのシステムリソースが解放されたことを示します。 Sunは、次のチャネル閉鎖を報告しています。

「チャネルを閉じると、このチャネルに関連するすべてのリソースが解放できることを示します。 リソースを解放するには、アプリケーションが終了したときだけでなく、すでに関与しているかどうかに関係なく、アプリケーションはチャネルを閉じる必要があります。 ミキサーはシステムリソースを共有し、繰り返し閉じたり開いたりできると想定されています。 他のチャネルは、閉じた後の再開をサポートする場合としない場合があります。 一般に、線を開くメカニズムは、サブタイプによって異なります。

そして今、物語の終わり

そこで、ここでは、コンピューターの内部メモリからサウンドカードへのオーディオデータの配信を保証するために、プログラムがJava Sound APIを使用する方法について説明しました。

プログラムを実行する

これで、リスト11のプログラムをコンパイルして実行できるようになり、レッスンの最後になります。

オーディオデータをキャプチャして再生する

このプログラムは、マイクからデータを記録し、コンピューターのサウンドカードを介して再生する機能を示しています。 使用方法は非常に簡単です。

プログラムを実行します。 図6に示すシンプルなGUI GUIが画面に表示されます。




何も聞こえない場合は、マイクの感度またはスピーカーの音量を上げてみてください。

プログラムはコンピュータのメモリにレコードを保存しますので、注意してください。 オーディオデータを大量に保存しようとすると、RAMが不足する可能性があります。

おわりに


次は?

このチュートリアルでは、Java Sound APIはミキサーとチャンネルの概念に基づいていることがわかりました。 ただし、説明したコードにはミキサーが明示的に含まれていませんでした。 AudioSystemクラスは、ミキサーに直接アクセスせずにオーディオ処理プログラムを作成できる静的メソッドを提供しました。 つまり、これらの静的メソッドはミキサーを私たちから遠ざけます。

次のレッスンでは、このレッスンで示したものと比較して変更されたデータキャプチャコードを示します。新しいバージョンでは、本当に必要なときにミキサーを使用する方法を示すために、ミキサーを明示的に使用します。

 import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.*; import javax.sound.sampled.*; public class AudioCapture01 extends JFrame{ boolean stopCapture = false; ByteArrayOutputStream byteArrayOutputStream; AudioFormat audioFormat; TargetDataLine targetDataLine; AudioInputStream audioInputStream; SourceDataLine sourceDataLine; public static void main( String args[]){ new AudioCapture01(); }//end main public AudioCapture01(){ final JButton captureBtn = new JButton("Capture"); final JButton stopBtn = new JButton("Stop"); final JButton playBtn = new JButton("Playback"); captureBtn.setEnabled(true); stopBtn.setEnabled(false); playBtn.setEnabled(false); captureBtn.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ captureBtn.setEnabled(false); stopBtn.setEnabled(true); playBtn.setEnabled(false); //  //   //   Stop captureAudio(); } } ); getContentPane().add(captureBtn); stopBtn.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ captureBtn.setEnabled(true); stopBtn.setEnabled(false); playBtn.setEnabled(true); //  //    stopCapture = true; } } ); getContentPane().add(stopBtn); playBtn.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ //  //    playAudio(); } } ); getContentPane().add(playBtn); getContentPane().setLayout( new FlowLayout()); setTitle("Capture/Playback Demo"); setDefaultCloseOperation( EXIT_ON_CLOSE); setSize(250,70); setVisible(true); } //    //     //   ByteArrayOutputStream private void captureAudio(){ try{ //    audioFormat = getAudioFormat(); DataLine.Info dataLineInfo = new DataLine.Info( TargetDataLine.class, audioFormat); targetDataLine = (TargetDataLine) AudioSystem.getLine( dataLineInfo); targetDataLine.open(audioFormat); targetDataLine.start(); //     //    //   //    Thread captureThread = new Thread( new CaptureThread()); captureThread.start(); } catch (Exception e) { System.out.println(e); System.exit(0); } } //    // ,    //  ByteArrayOutputStream private void playAudio() { try{ //  //  byte audioData[] = byteArrayOutputStream. toByteArray(); InputStream byteArrayInputStream = new ByteArrayInputStream( audioData); AudioFormat audioFormat = getAudioFormat(); audioInputStream = new AudioInputStream( byteArrayInputStream, audioFormat, audioData.length/audioFormat. getFrameSize()); DataLine.Info dataLineInfo = new DataLine.Info( SourceDataLine.class, audioFormat); sourceDataLine = (SourceDataLine) AudioSystem.getLine( dataLineInfo); sourceDataLine.open(audioFormat); sourceDataLine.start(); //    //     //     //      Thread playThread = new Thread(new PlayThread()); playThread.start(); } catch (Exception e) { System.out.println(e); System.exit(0); } } //     //  AudioFormat private AudioFormat getAudioFormat(){ float sampleRate = 8000.0F; //8000,11025,16000,22050,44100 int sampleSizeInBits = 16; //8,16 int channels = 1; //1,2 boolean signed = true; //true,false boolean bigEndian = false; //true,false return new AudioFormat( sampleRate, sampleSizeInBits, channels, signed, bigEndian); } //===================================// //    //    class CaptureThread extends Thread{ byte tempBuffer[] = new byte[10000]; public void run(){ byteArrayOutputStream = new ByteArrayOutputStream(); stopCapture = false; try{ while(!stopCapture){ int cnt = targetDataLine.read( tempBuffer, 0, tempBuffer.length); if(cnt > 0){ //     byteArrayOutputStream.write( tempBuffer, 0, cnt); } } byteArrayOutputStream.close(); }catch (Exception e) { System.out.println(e); System.exit(0); } } } //===================================// //   //     class PlayThread extends Thread{ byte tempBuffer[] = new byte[10000]; public void run(){ try{ int cnt; //     -1 while((cnt = audioInputStream. read(tempBuffer, 0, tempBuffer.length)) != -1){ if(cnt > 0){ //    //   //    //    sourceDataLine.write( tempBuffer, 0, cnt); } } sourceDataLine.drain(); sourceDataLine.close(); }catch (Exception e) { System.out.println(e); System.exit(0); } } } //===================================// }//end outer class AudioCapture01.java 

リスト11

Source: https://habr.com/ru/post/J434424/


All Articles