Javaを使用してネットワーク経由でサウンドをブロードキャストする

ネットワークを介した音の伝送を試すことは、私にとって興味深いことでした。

このためにJavaテクノロジーを選択しました。

その結果、Java SEのトランスミッター、Java SEのレシーバー、Androidのレシーバーの3つのコンポーネントを作成しました。



Java SEでは、 javax.sound.sampledパッケージのクラスがサウンドの操作に使用され、Androidでは、クラスandroid.media.AudioFormatandroid.media.AudioManagerおよびandroid.media.AudioTrackが使用されました

ネットワーク標準のSocketおよびServerSocketを使用します。



これらのコンポーネントを使用して、ロシア極東とオランダとの間で音声通信セッションを正常に行うことができました。



そしてもう1つの可能性のあるアプリケーション-たとえば、Virtual Audio Cableなどの仮想サウンドカードをインストールすると、他のデバイスに音楽をストリーミングできるため、アパートの複数の部屋で音楽を同時に聴くことができます(適切な数のデバイスがある場合)。





1.送信機。





サウンドをブロードキャストする方法は簡単です。バイトストリームをマイクから読み取り、ソケットの出力ストリームに書き込みます。



マイクを使用してネットワークを介してデータを送信する作業は、個別のストリームで行われます。



mr = new MicrophoneReader(); mr.start(); ServerSocket ss = new ServerSocket(7373); while (true) { Socket s = ss.accept(); Sender sndr = new Sender(s); senderList.add(sndr); sndr.start(); }
      
      







マイクストリーム:



 public void run() { try { microphone = AudioSystem.getTargetDataLine(format); DataLine.Info info = new DataLine.Info(TargetDataLine.class, format); microphone = (TargetDataLine) AudioSystem.getLine(info); microphone.open(format); data = new byte[CHUNK_SIZE]; microphone.start(); while (!finishFlag) { synchronized (monitor) { if (senderNotReady==sendersCreated) { monitor.notifyAll(); continue; } numBytesRead = microphone.read(data, 0, CHUNK_SIZE); } System.out.print("Microphone reader: "); System.out.print(numBytesRead); System.out.println(" bytes read"); } } catch (LineUnavailableException e) { e.printStackTrace(); } }
      
      







UPD。 注: CHUNK_SIZEパラメーターを正しく選択することが重要です。 値が小さすぎるとst音が聞こえ、大きすぎると音の遅れが顕著になります。



サウンドストリーム:



 public void run() { try { OutputStream os = s.getOutputStream(); while (!finishFlag) { synchronized (monitor) { senderNotReady++; monitor.wait(); os.write(data, 0, numBytesRead); os.flush(); senderNotReady--; } System.out.print("Sender #"); System.out.print(senderNumber); System.out.print(": "); System.out.print(numBytesRead); System.out.println(" bytes sent"); } } catch (Exception e) { e.printStackTrace(); } }
      
      







ネストされた外部クラスdataの変数、 numBytesReadsenderNotReadysendersCreated 、およびmonitorの両方のストリームクラスをvolatileとして宣言する必要があります。

モニターオブジェクトは、スレッドを同期するために使用されます。



2. Receiver for Java SE。





メソッドも簡単です-ソケットからバイトストリームを読み取り、オーディオ出力に書き込みます。



 try { InetAddress ipAddr = InetAddress.getByName(host); Socket s = new Socket(ipAddr, 7373); InputStream is = s.getInputStream(); DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, format); speakers = (SourceDataLine) AudioSystem.getLine(dataLineInfo); speakers.open(format); speakers.start(); Scanner sc = new Scanner(System.in); int numBytesRead; byte[] data = new byte[204800]; while (true) { numBytesRead = is.read(data); speakers.write(data, 0, numBytesRead); } } catch (Exception e) { e.printStackTrace(); }
      
      







3. Android用レシーバー。





方法は同じです。

唯一の違いは、 javax.sound.sampled.SourceDataLineの代わりにandroid.media.AudioTrack 使用することです。

また、Androidでは、メインアプリケーションフローでネットワーク操作が発生しないことも考慮する必要があります。

サービスを作成したので、気にしないことに決めました。メインのアクティビティからワークフローを開始します。



 toogle.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (!isRunning) { isRunning = true; toogle.setText("Stop"); rp = new ReceiverPlayer(hostname.getText().toString()); rp.start(); } else { toogle.setText("Start"); isRunning = false; rp.setFinishFlag(); } } });
      
      







ワークフローコード自体:



 class ReceiverPlayer extends Thread { volatile boolean finishFlag; String host; public ReceiverPlayer(String hostname) { host = hostname; finishFlag = false; } public void setFinishFlag() { finishFlag = true; } public void run() { try { InetAddress ipAddr = InetAddress.getByName(host); Socket s = new Socket(ipAddr, 7373); InputStream is = s.getInputStream(); int bufferSize = AudioTrack.getMinBufferSize(16000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT); int numBytesRead; byte[] data = new byte[bufferSize]; AudioTrack aTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 16000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM); aTrack.play(); while (!finishFlag) { numBytesRead = is.read(data, 0, bufferSize); aTrack.write(data, 0, numBytesRead); } aTrack.stop(); s.close(); } catch (Exception e) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); Log.e("Error",sw.toString()); } } }
      
      







4.オーディオ形式に関する注意。





Java SEはjavax.sound.sampled.AudioFormatクラスを使用します



Androidでは、オーディオパラメーターはandroid.media.AudioTrackオブジェクトのコンストラクターに直接渡されます。



私のコードで使用されたこれらのクラスのコンストラクターを検討してください。



Java SE:



AudioFormat(float sampleRate、int sampleSizeInBits、intチャネル、boolean signed、boolean bigEndian)

線形PCMエンコーディングと指定されたパラメーターでAudioFormatを構築します。



Android:



AudioTrack(int streamType、int sampleRateInHz、int channelConfig、int audioFormat、int bufferSizeInBytes、intモード)



正常に再生するには、レシーバーとトランスミッターのパラメーターsampleRate / sampleRatesampleSizeInBits / audioFormat 、およびchannels / channelConfigが互いに対応している必要があります。



さらに、Androidのモード値はAudioTrack.MODE_STREAMに設定する必要があります。



また、Androidで正常に再生するには、 署名済みのリトルエンディアン形式でデータを転送する必要があることを実験的に確立できました。

signed = true; bigEndian = false。



その結果、次の形式が選択されました。



 // Java SE: AudioFormat format = new AudioFormat(16000.0f, 16, 2, true, bigEndian); // Android: AudioTrack aTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 16000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM);
      
      







5.テスト。





Windows 8のラップトップとDebian Wheezyのデスクトップの間では、すべてが問題なくすぐに起動しました。



Androidの受信機は最初はノイズのみを生成していましたが、この問題は、オーディオ形式の符号 付きパラメーターbigEndianパラメーターを正しく選択することで解決しました。



Raspberry Pi(Raspbian Wheezy)では、最初にst音が聞こえました。鳥類の軽量仮想Javaマシンをインストールする形で松葉杖が必要でした。



次の起動スクリプトを作成しました。



 case "$1" in start) java -avian -jar jAudioReceiver.jar 192.168.1.50 & echo "kill -KILL $!">kill_receiver.sh ;; stop) ./kill_receiver.sh ;; esac
      
      







すべてのコンポーネントのソースコードは次のとおりです。



github.com/tabatsky/NetworkingAudio






All Articles