
第3回 音波を受信してみた
超音波通信の実現に向け、第2回目では超音波送信の第一歩として、Android端末から音波を送信することに成功しました。
今回は、超音波受信の第一歩として、その音波を別のAndroid端末で受信することに挑戦します。
やりたいこと
一般的な通信は、送信側が情報を波形に変換して送り、受信側で受け取った波形を情報に戻すことで実現されています。
こうした通信を超音波で行うために、まずは第2回目で任意の周波数の音波を送信するアプリを作成しました。その音波を別の端末で受信して同じ波形を復元することができれば、超音波通信の実現にまた一歩近づきます。
そこで今回はそれが可能かどうか、次のようなAndroidアプリを作って検証したいと思います。
- マイクで拾った音声をファイルに保存
- 保存した音声ファイルを受信結果確認のために再生
アプリの作成
音声取得のために使うAPI
Androidアプリで音声を取得するにあたり、Android SDKでは次のようなクラスが提供されています。
- MediaRecorder
動画、音声をファイルに保存するためのクラスです。
サポートするファイル形式はMPEG4、3GPP、AACなどの圧縮された形式に限られます。
http://developer.android.com/reference/android/media/MediaRecorder.html
- AudioRecord
音声をデータとして受け取るためのクラスです。
保存や加工など、受け取ったデータをどう取り扱うかはアプリに委ねられます。
サポートするデータ形式は、ほぼ無圧縮のPCM形式のみです。
http://developer.android.com/reference/android/media/AudioRecord.html
最終的に私達が目指す超音波通信は、人の耳には聴こえない高い周波数帯の音波を使った通信です。しかし、MediaRecorderのサポートするファイル形式では、圧縮のためにそうした高い周波数帯の音が取り除かれている可能性があります。
そのため今回は、ほぼ無圧縮の音声データを取得できるAudioRecordを採用します。
初期化
AudioRecordクラスのオブジェクトを生成します。
今回は、モノラルマイクで拾った音を、サンプリングレート44,100HzのPCM16bit形式で保存するよう設定します。 音声データ受信用バッファのサイズは、別途提供されている計算用のメソッドgetMinBufferSize()で事前に計算した値を使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
final static int SAMPLING_RATE = 44100; ・ ・ ・ // Audio buffer size mRecvBufSize = AudioRecord.getMinBufferSize( SAMPLING_RATE, // sampleRateInHz AudioFormat.CHANNEL_IN_MONO, // channelConfig (stereo or mono) AudioFormat.ENCODING_PCM_16BIT); // audioFormat Log.d(TAG, "mRecvBufSize: " + mRecvBufSize); mRecvBuf = new short[mRecvBufSize / 2]; // "/2" means byte -> short // init AudioRecord mAudioRecord = new AudioRecord( MediaRecorder.AudioSource.MIC, // audioSource SAMPLING_RATE, // sampleRateInHz AudioFormat.CHANNEL_IN_MONO, // channelConfig AudioFormat.ENCODING_PCM_16BIT, // audioFormat mRecvBufSize); // bufferSizeInBytes // set callback period mAudioRecord.setPositionNotificationPeriod(mRecvBufSize / 2); |
音声データ受信・保存
生成したAudioRecordオブジェクトに対して、音声データ受信時に実行したい処理をsetRecordPositionUpdateListener()メソッドで登録します。
今回は、受信した音声データをWaveファイルに書き込む処理を登録します。Waveファイルの保存処理については、こちらのサイトを参考にさせて頂きました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// set callback mAudioRecord.setRecordPositionUpdateListener(new AudioRecord.OnRecordPositionUpdateListener() { @Override public void onPeriodicNotification(AudioRecord recorder) { // receive audio data mAudioRecord.read(mRecvBuf, 0, mRecvBufSize / 2); // write to wave file mWaveFile.addBigEndianData(mRecvBuf); } @Override public void onMarkerReached(AudioRecord recorder) { } }); |
なお、最終的に超音波通信を行う際には、受信した音声データから音波を復元して情報を取り出す処理に置き換える予定です。
再生
今回はMediaPlayerというクラスを使って、保存したWaveファイルを再生しました。
再生するファイルのパスを指定して、prepare() ⇒ start() の順にメソッドを呼ぶだけでOKです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
mMediaPlayer = new MediaPlayer(); ・ ・ ・ // Start playing recorded file mMediaPlayer.reset(); try { mMediaPlayer.setDataSource(WAVEFILE_PATH); mMediaPlayer.prepare(); } catch (IOException e) { e.printStackTrace(); } mMediaPlayer.start(); |
その他
その他、全体の処理は以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
package com.tongarism.ultrasound.receive_test; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaPlayer; import android.media.MediaRecorder; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.CompoundButton; import android.widget.ToggleButton; import java.io.IOException; public class MainActivity extends ActionBarActivity { public static final String TAG = "UltraSoundRevive"; private ToggleButton mRecordToggleButton; private ToggleButton mPlayToggleButton; private AudioRecord mAudioRecord; private int mRecvBufSize; private short[] mRecvBuf; final static int SAMPLING_RATE = 44100; private WaveFile mWaveFile ; private static final String WAVEFILE_PATH = "/sdcard/usw.wav"; private MediaPlayer mMediaPlayer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(com.tongarism.ultrasound.receive_test.R.layout.activity_main); // Init WaveFile generator mWaveFile = new WaveFile(); // Audio buffer size mRecvBufSize = AudioRecord.getMinBufferSize( SAMPLING_RATE, // sampleRateInHz AudioFormat.CHANNEL_IN_MONO, // channelConfig (stereo or mono) AudioFormat.ENCODING_PCM_16BIT); // audioFormat Log.d(TAG, "mRecvBufSize: " + mRecvBufSize); mRecvBuf = new short[mRecvBufSize / 2]; // "/2" means byte -> short // init AudioRecord mAudioRecord = new AudioRecord( MediaRecorder.AudioSource.MIC, // audioSource SAMPLING_RATE, // sampleRateInHz AudioFormat.CHANNEL_IN_MONO, // channelConfig AudioFormat.ENCODING_PCM_16BIT, // audioFormat mRecvBufSize); // bufferSizeInBytes // set callback period mAudioRecord.setPositionNotificationPeriod(mRecvBufSize / 2); // set callback mAudioRecord.setRecordPositionUpdateListener(new AudioRecord.OnRecordPositionUpdateListener() { @Override public void onPeriodicNotification(AudioRecord recorder) { // receive audio data mAudioRecord.read(mRecvBuf, 0, mRecvBufSize / 2); // write to wave file mWaveFile.addBigEndianData(mRecvBuf); } @Override public void onMarkerReached(AudioRecord recorder) { } }); mRecordToggleButton = (ToggleButton) findViewById(com.tongarism.ultrasound.receive_test.R.id.record_toggle_button); mRecordToggleButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { // create wave file mWaveFile.createFile(WAVEFILE_PATH); // start receiving audio data mAudioRecord.startRecording(); mAudioRecord.read(mRecvBuf, 0, mRecvBufSize / 2); mPlayToggleButton.setEnabled(false); } else { // close wave file mWaveFile.close(); // stop receiving audio data mAudioRecord.stop(); mPlayToggleButton.setEnabled(true); } } }); mMediaPlayer = new MediaPlayer(); mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { mPlayToggleButton.setChecked(false); mRecordToggleButton.setEnabled(true); } }); mPlayToggleButton = (ToggleButton) findViewById(com.tongarism.ultrasound.receive_test.R.id.play_toggle_button); mPlayToggleButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { // Start playing recorded file mMediaPlayer.reset(); try { mMediaPlayer.setDataSource(WAVEFILE_PATH); mMediaPlayer.prepare(); } catch (IOException e) { e.printStackTrace(); } mMediaPlayer.start(); // Can't record during playing mRecordToggleButton.setEnabled(false); } else { // Stop playing mMediaPlayer.stop(); mRecordToggleButton.setEnabled(true); } } }); } public void onPause(){ super.onPause(); // stop receiving audio data mAudioRecord.stop(); } public void onDestroy(){ super.onDestroy(); mAudioRecord.release(); mMediaPlayer.release(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(com.tongarism.ultrasound.receive_test.R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == com.tongarism.ultrasound.receive_test.R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } } |
動かしてみた
音波送信アプリから周波数を上げながら音波を発生させ、今回作成したアプリで録音・再生してみた結果がこちらです。
10,000Hzでは、発生させたものと同じ音が再生されるため、音波を受信できていることがわかりますね。さらに、13,000Hz、15,000Hzと周波数を上げても同様に受信できていることがわかります。
ただ、18,000Hzでは可聴領域外のため、実際に音波を受信できているかどうか現段階では判別できませんね。また、受信したい音波だけでなく、周りの雑音や話し声も混ざっています。
まとめ
今回は、超音波受信に向けて通常の音波を受信できることを確認でき、超音波通信にまた一歩近づきました。
次回は受信した音声データを解析し、音波送信アプリで発生させた音波のみを取り出すと共に、可聴領域外の音波も受信できているかどうかを検証したいと思います!
お楽しみに!!
Portions of this page are modifications based on work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.