ストリーミングオーディオを使用する

はじめに


マイクから生データを受信するには、クラスが責任を負います android.media.AudioRecord android.media.AudioRecord 。 内部バッファにデータを書き込むため、定期的にデータを収集する必要があります。

コンストラクター

オブジェクトを作成するには、次を指定する必要があります。
audioSource録音はどこから来ましたか? 私たちの場合、これ MediaRecorder.AudioSource.MIC
sampleRateInHzヘルツのサンプルレート。 ドキュメントは、44100Hzがすべてのデバイスでサポートされていると主張しています
channelconfigチャネル構成 たぶん CHANNEL_IN_MONO CHANNEL_IN_MONOまたは CHANNEL_IN_STEREO CHANNEL_IN_STEREO モノはどこでも動作します。

重要:これらの定数は、それらが示すチャネルの数と一致しません。 このパラメーターに1または2を渡すことはできません。
audioFormatコーデックとしてよく知られている入力データ形式。 たぶん ENCODING_PCM_16BIT ENCODING_PCM_16BITまたは ENCODING_PCM_8BIT
bufferSizeInBytes同じ内部バッファのサイズ。 それから、オーディオストリームを読み取ることができます。 読み取り部分のサイズはこの値を超えないようにしてください。 このパラメーターの最小許容値は、次の方法で取得できます getMinBufferSize() getMinBufferSize()

オブジェクトは、作成中に必要なシステムリソースを取得しようとします。 彼がどれだけ成功したか、関数を呼び出すことで見つけることができます getState() getState() 。 彼女が戻ったら STATE_INITIALIZED STATE_INITIALIZED 、その後、すべてが正常である場合、 STATE_UNINITIALIZED STATE_UNINITIALIZEDは、エラーが発生したことを意味します。

エラーの原因は2つあります。小さすぎるバッファーと無効なフォーマットです。 1つ目は getMinBufferSize()呼び出すことで回避することgetMinBufferSize() 。 第二に、実際、彼による。

getMinBufferSize()

この静的メソッドは、 AudioRecordオブジェクトが機能する内部バッファーの最小サイズを生成します。 パラメーターは、コンストラクターと同じ意味を持ちます。 書き込みにこの特定の値を使用することはお勧めできません。 システムがまだ何かでビジーである場合、プログラムはまだ行のすべてのデータを読み取る時間がない可能性があり、レコードに穴があります。 私は、サイズを10倍大きくするようアドバイスしました。

リスト形式

getMinBufferSize()メソッドには素晴らしい機能があります- このデバイスに対して無効なパラメーターを誓うためERROR_BAD_VALUE ERROR_BAD_VALUEまたは ERROR ERROR つまり、考えられるすべての組み合わせを調べて、デバイスがサポートしている形式を見つけることができます。

たとえば、次のように:
 int[] rates = {8000, 11025, 22050,44100, 48000, 96000 }; int[] chans = {AudioFormat.CHANNEL_IN_MONO, AudioFormat.CHANNEL_IN_STEREO}; int[] encs = {AudioFormat.ENCODING_PCM_8BIT, AudioFormat.ENCODING_PCM_16BIT}; for(int enc : encs) { for(int ch : chans) { for(int rate : rates) { int t = AudioRecord.getMinBufferSize(rate, ch, enc); if((t != AudioRecord.ERROR) && (t != AudioRecord.ERROR_BAD_VALUE)) { //   } } } } 


データ読み取り

内部バッファからデータを取得するには、メソッドを使用します read() read() 。 次の3つのバージョンがあります。それらのパラメーター:
audioDataデータが書き込まれる配列
audioBufferデータが書き込まれるバッファ
offsetInBytes /
offsetInShorts
記録を開始するインデックス
sizeInShorts要求されたデータブロックのサイズ。 ByteBufferおよびbyte[]単位、 short[]短整数byte[]

すべてが正常であれば、メソッドはByteBufferまたはbyte[]バリアントである場合、またはshort[]短整数を読み取る場合、読み取られたバイト数を返します。 呼び出し時にオブジェクトが正しく初期化されていなかった場合、 ERROR_INVALID_OPERATION返し 、パラメーターに何か問題がある場合-ERROR_BAD_VALUE

重要:メソッドは、要求された量のデータを読み取るまで呼び出しスレッドをブロックします。 内部バッファーに十分な数がない場合、 read()はマイクから来るまで待機します。 したがって、メソッドは別のスレッドから呼び出す必要があります。そうしないと、アプリケーションがハングします。

アプローチ、リトリート、固定

プログラムがマイクからデータを受信できるように、AndroidManifestのxmlファイルで適切な解像度を指定する必要があります。
 <uses-permission android:name="android.permission.RECORD_AUDIO" /> 

記録を開始するには、メソッドを呼び出す必要があります startRecording() startRecording() 、そして終了する- stop() stop() 。 録音は何度でも開始および停止できます。

オブジェクトの処理が完了したら、メソッドを呼び出す必要があります release() release() 。 オブジェクトによってキャプチャされたすべてのシステムリソースを解放します。 その後、オブジェクト使用できず 、オブジェクト参照する変数をnullに設定する必要がありnull

重要:これら3つのメソッドは、前述のメソッドとは異なり、スローされます IllegalStateException IllegalStateException 、それらが初期化されていない(つまり... ...)オブジェクトに対して、または間違った順序で呼び出された場合。 したがって、それらは「慎重に」処理する必要があります。 tryブロックを介して。

使用例

以下のクラスは、上記のすべてを実行します。 さらに、彼は彼に登録済みを送ります Handler 受信したデータに関する Handlerメッセージ。 このデータは別のスレッドで処理されるため、まだ処理されていないデータを新しいデータで上書きしないように、循環バッファーが使用されます。

コードはAudioFormatInfoクラスを使用します。 これは、記録フォーマットを記述する3つのフィールドを持つPOJOです: sampleRateInHzaudioFormatおよびaudioFormat
 package com.MyCompany; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.Handler; import android.os.Process; //AudioFormatInfo - POJO   sampleRateInHz, channelConfig  audioFormat public class AudioReciever implements Runnable { private boolean mIsRunning; private List<Handler> handlers; private AudioFormatInfo format; private AudioRecord mRecord; private final int BUFF_COUNT = 32; public AudioReciever(AudioFormatInfo format) { this.format = format; handlers = new ArrayList<Handler>(); mIsRunning = true; mRecord = null; } public void addHandler(Handler handler) { handlers.add(handler); } public void stop() { mIsRunning = false; } @Override public void run() { //      Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO); mIsRunning = true; int buffSize = AudioRecord.getMinBufferSize(format.getSampleRateInHz(), format.getChannelConfig(), format.getAudioFormat()); if(buffSize == AudioRecord.ERROR) { System.err.println("getMinBufferSize returned ERROR"); return; } if(buffSize == AudioRecord.ERROR_BAD_VALUE) { System.err.println("getMinBufferSize returned ERROR_BAD_VALUE"); return; } //    short,   16-bit if(format.getAudioFormat() != AudioFormat.ENCODING_PCM_16BIT) { System.err.println("unknown format"); return; } //   .    , //      short[][] buffers = new short[BUFF_COUNT][buffSize >> 1]; mRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, format.getSampleRateInHz(), format.getChannelConfig(), format.getAudioFormat(), buffSize * 10); if(mRecord.getState() != AudioRecord.STATE_INITIALIZED) { System.err.println("getState() != STATE_INITIALIZED"); return; } try { mRecord.startRecording(); } catch(IllegalStateException e) { e.printStackTrace(); return; } int count = 0; while(mIsRunning) { int samplesRead = mRecord.read(buffers[count], 0, buffers[count].length); if(samplesRead == AudioRecord.ERROR_INVALID_OPERATION) { System.err.println("read() returned ERROR_INVALID_OPERATION"); return; } if(samplesRead == AudioRecord.ERROR_BAD_VALUE) { System.err.println("read() returned ERROR_BAD_VALUE"); return; } //    sendMsg(buffers[count]); count = (count + 1) % BUFF_COUNT; } try { try { mRecord.stop(); } catch(IllegalStateException e) { e.printStackTrace(); return; } } finally { //   mRecord.release(); mRecord = null; } } private void sendMsg(short[] data) { for(Handler handler : handlers) { handler.sendMessage(handler.obtainMessage(MSG_DATA, data)); } } } 

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


All Articles