
第4回 音波を解析してみた
第2回、第3回のアプリ作成によって、可聴域の音波送受信を確認できましたが、果たして超音波の送受信はできていたのでしょうか?今回は取得した音波を解析して、超音波の送受信ができているかどうかを検証してみます。
検証の流れ
超音波の送受信ができているかどうかを検証するために、今回は音波受信アプリ(第3回で作成)を改造して音波解析アプリを作成します。 音波解析アプリを用いて、受信した音声データにどのような周波数が含まれているかを明らかにします。 その中に、音波送信アプリ(第2回で作成)で発生させた音の周波数が含まれていれば、超音波の送受信が成功したといえます! まとめると、検証の流れは以下のようになります。
- 音波解析アプリを作成
- 音波送信アプリから音波を送信し、音波解析アプリで受信
- 音波送信アプリが発生させた周波数の音波が含まれていることを確認
高速フーリエ変換について
音波解析アプリのポイントは、受信した音声に、どのような周波数の音が含まれているかを明らかにすることです。難しそうな話に聞こえますが、高速フーリエ変換(FFT)という方法で、簡単に求めることができます。そこで、音波解析アプリの説明を行う前に、高速フーリエ変換について簡単に説明しておきます。
高速フーリエ変換とは、時間を基にしたデータを、周波数を基にしたデータに変換するためのアルゴリズムです。一定時間ごとにサンプリングされた音声データのフーリエ変換結果は、その音声を一定間隔の周波数ごとに分解した形となります。
上記の例では、N個のサンプリングされた音声データを高速フーリエ変換することによって、周波数がf1からfN/2の波に分解されていることになります。このとき、それぞれの数周波数fnは、以下の式で表されます。

- fsはサンプリング周波数
- Nは音声データのサイズ
- n = 1, 2 , …, N/2
また、周波数fnとして分解された波のデータは、an+bni という複素数の形で表現されます。少々難しく感じますが、以下のように複素平面上で表すとイメージしやすくなります。
複素数で表現することによって、その波の強さ(振幅)Anと、位相の進みθnを同時に表現できていることが分かりますね。今回は波の強さAnを知る必要があるため、この複素数の絶対値を求めればよいことになります。
アプリの作成
今回は、Jtransformsというライブラリを使用します。Jtransformsには、DoubleFFT_1Dというクラスが用意されており、このクラスのrealForward()メソッドを使用して、FFTの結果を得ることができます。JtransformsでのFFTの結果は以下のように、2n-1番目を実部、2n番目を虚部として、周波数fnの音を表しています。(n = 1, 2 , …, N/2)
前回紹介したAudioRecorderクラスによって得られた音声データを、readForward()メソッドの入力引数に設定してコールして、それぞれの周波数の強さをグラフにプロットすれば、リアルタイムで周囲の音の周波数成分を視覚化することができます。以下は実装例の一部となります。グラフへのプロットの方法はこちらのサイトを参考にしました。
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 |
private static final int SAMPLING_RATE = 44100; @Override public void onPeriodicNotification(AudioRecord recorder) { // 音声データを読み込み int size = mAudioRecord.read(mRecvBuf, 0, mRecvBufSize / 2); // FFTインスタンス生成 DoubleFFT_1D fft = new DoubleFFT_1D(size); double[] fftData = new double[size]; for (int i = 0; i < size; i++) { // データ型を変換 fftData[i] = (double) mRecvBuf[i]; } // FFTの実施 fft.realForward(fftData); double[] power = new double[size / 2]; double maxPower = 0; int maxIndex = 0; for (int i = 0; i < size; i += 2) { power[i / 2] = (int) Math.sqrt(Math.pow(fftData[i], 2) + Math.pow(fftData[i + 1], 2)); if (maxPower < power[i / 2]) { maxPower = power[i / 2]; maxIndex = i / 2; } } // 最も強い周波数をログに出力 Log.d(TAG, "Peek = " + (int) ((SAMPLING_RATE / (double) FFT_SIZE) * maxIndex) + "Hz"); mDb = new float[size / 2]; for (int i = 0; i < size / 2; i++) { // グラフに入るように強さを調節 mDb[i] = (float) power[i] / 20000; } mHandler.post(new Runnable() { @Override public void run() { synchronized (mLock) { // グラフに出力 mVisualizerView.updateVisualizer(mDb); } } }); } |
アプリを動かしてみた
アプリを動かしてみた結果がこちらです。
画面左側の音波送信アプリで音波を発生させ、右側の音波解析アプリでその音波を解析しています。 音波解析アプリ画面上の赤い線が、18,000Hzの音波の強さを表しています。音波送信アプリで18,000Hzの音波を送出させたとき、期待通りこの赤い線上に解析結果が出力されていることが分かります。
まとめ
今回の検証で、これまで作ってきたアプリによって超音波の送受信が本当に出来ていたことがわかりました。 データ通信を行うためには、送信側でデータを超音波に変換し、受信側で超音波をデータに変換する仕組みが必要です。 次回はそうしたデータ通信を行うためのライブラリ作成に挑戦したいと思います。 お楽しみに!
とっても参考になっています
記事投稿ありがたいです
とても参考になりました。
現在僕はスマートフォン内臓の通常マイクと、背面にあるカムコーダと呼ばれるもう一つのマイクを用いて新しいスマートフォン操作手法を考えています。しかしAudioRecordでマイクとカムコーダ同時に音を取得しようとすると取得音の値がどちらも取れなくなるということが起こりました。スレッドを分けてみても値の取得ができず、行き詰まってしまいました。これは何が原因と考えられますか?よければ返信お願いします。