質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Android Studio

Android Studioは、 Google社によって開発された、 Androidのネイティブアプリケーション開発に特化した統合開発ツールです。

Q&A

解決済

3回答

972閲覧

周波数検知でずれをなくしたいです

kiriyaman

総合スコア11

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Android Studio

Android Studioは、 Google社によって開発された、 Androidのネイティブアプリケーション開発に特化した統合開発ツールです。

0グッド

3クリップ

投稿2018/01/09 04:13

###前提・実現したいこと
Android Studioで周波数検知を行うアプリを開発しているのですが、300HZを検知すると301HZで表示されるというように1や2HZのずれが生じてしまいます。
440HZや500HZで試した際にも1や2のずれが生じます。
この少しのずれを直したいです。また原因を知りたいです。

###作成したアプリケーションについて

アプリの内容自体は収音開始ボタンを押し、周波数のピークが表示されるといったものです。

###該当のソースコード

public class FFTActivity extends AppCompatActivity implements Runnable, View.OnClickListener, Handler.Callback { private static final String TAG = "SNC"; private static final int SAMPLE_RATE = 44100; private static final int FFT_SIZE = 4096; private static final short THRESHOLD_AMP = 0x00ff; private static final int MSG_RECORD_START = 100; private static final int MSG_RECORD_END = 110; private static final int MSG_FREQ_PEAK = 120; private static final int MSG_SILENCE = 130; private Handler mHandler; private AudioRecord mAudioRecord = null; private Button mButton01; private TextView mTextView02; private boolean mInRecording = false; private boolean mStop = false; private int mbufSize; private short mRecordBuf[]; private DoubleFFT_1D mFFT; private double mFFTBuffer[]; private int mFFTSize; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate"); mHandler = new Handler(this); setContentView(R.layout.activity_fft); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); mButton01 = (Button) findViewById(R.id.button01); mButton01.setOnClickListener(this); mTextView02 = (TextView) findViewById(R.id.textView02); int bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); System.out.println("FFT_SIZE" + FFT_SIZE); if (FFT_SIZE > mbufSize) mbufSize = FFT_SIZE; // 録音用バッファ mRecordBuf = new short[mbufSize]; // FFT 処理用 mFFTSize = mbufSize; mFFT = new DoubleFFT_1D(mFFTSize); mFFTBuffer = new double[mFFTSize]; mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes); } //画面移動 public void onButton007( View v){ Intent intent = new Intent(this,Main2Activity.class); // 画面指定 startActivity(intent); // 画面を開く } @Override public void onStop() { super.onStop(); Log.d(TAG, "onStop"); } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy"); mStop = true; try { Thread.sleep(2000); } catch (InterruptedException e) { } if (mAudioRecord != null) { if (mAudioRecord.getRecordingState() != AudioRecord.RECORDSTATE_STOPPED) { Log.d(TAG, "cleanup mAudioRecord"); mAudioRecord.stop(); } mAudioRecord = null; } } @Override public void onClick(View v) { if (v == (View)mButton01) { // 集音開始 or 終了 if (!mInRecording) { mInRecording = true; new Thread(this).start(); } else { mInRecording = false; } } return; } @Override public boolean handleMessage(Message msg) { switch (msg.what) { case MSG_RECORD_START: Log.d(TAG, "MSG_RECORD_START"); mButton01.setText("STOP"); break; case MSG_RECORD_END: Log.d(TAG, "MSG_RECORD_END"); mButton01.setText("START"); break; case MSG_FREQ_PEAK: mTextView02.setText(Integer.toString(msg.arg1) + " Hz"); break; case MSG_SILENCE: mTextView02.setText(""); break; } return true; } @Override public void run() { boolean bSilence = false; mHandler.sendEmptyMessage(MSG_RECORD_START); // 集音開始 mAudioRecord.startRecording(); while (mInRecording && !mStop) { mAudioRecord.read(mRecordBuf, 0, mbufSize); bSilence = true; for (int i = 0; i < mbufSize; i++) { short s = mRecordBuf[i]; if (s > THRESHOLD_AMP) { bSilence = false; } } if (bSilence) { // 静寂 mHandler.sendEmptyMessage(MSG_SILENCE); continue; } int freq = doFFT(mRecordBuf); Message msg = new Message(); msg.what = MSG_FREQ_PEAK; msg.arg1 = freq; mHandler.sendMessage(msg); } // 集音終了 mAudioRecord.stop(); mHandler.sendEmptyMessage(MSG_RECORD_END); } private int doFFT(short[] data) { for (int i = 0; i < mFFTSize; i++) { mFFTBuffer[i] = (double)data[i]; } // FFT 実行 mFFT.realForward(mFFTBuffer); // 処理結果の複素数配列から各周波数成分の振幅値を求めピーク分の要素番号を得る double maxAmp = 0; int index = 0; for (int i = 0; i < mFFTSize/2; i++) { double a = mFFTBuffer[i*2]; // 実部 double b = mFFTBuffer[i*2 + 1]; // 虚部 // a+ib の絶対値 √ a^2 + b^2 = r が振幅値 double r = Math.sqrt(a*a + b*b); if (r > maxAmp) { maxAmp = r; index = i; } } // 要素番号・サンプリングレート・FFT サイズからピーク周波数を求める return index * SAMPLE_RATE / FFT_SIZE; } }

###試したこと
FFTサイズやバッファサイズを変更したりと試してみました。
よろしくお願いします。
###補足情報(言語/FW/ツール等のバージョンなど)
android studio

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

###原因
FFTはDFT(離散フーリエ変換)を高速に計算するアルゴリズムの名前(fast Fourier transform)でして、本質的にはDFTのことです。

DFTでは波のデータを離散的なデータサンプルを用いて表します。サンプリング周波数が44.1KHzだとすれば、本来は連続的に変化する波形を1/44100秒に1回ずつその瞬間の値だけを記録してそのばらばらな値を計算に用います。計算機のスピードやメモリーは無限ではないので、このようにして計算量を減らしているわけですが、その代償として「計算結果の周波数は離散的」にしか求まりません。ozwkさん回答にあるように基本周波数F0(=Fs/N)の倍数の周波数に対する計算上のスペクトルが求まるにすぎません。本質的には元の波形の周波数が正確に求まると期待して用いるものではないと言えます。

###対応
結果の周波数の精度を上げるには、Nを大きくとるか、Fsを下げればF0が小さくなり周波数分解能は向上します。(使い勝手からいえば実質的にはどちらも同様の問題を持ちます)

ozwkさん回答の例ですと、Fs=44.1KHz、N=65536ということで周波数分解能=0.67Hzとなるわけですが、このようにNを大きくすればするほど「分析対象にする時間区間が長くなる」ため、その時間区間内で「不変の音」でないと期待通りの結果にはなりません。前の例だと計算対象区間の時間は1.49秒ほどになりますので、その間「同程度の大きさ、周波数の波」が録音できるのでない限り分析しても大して正確な結果は望めないことになります。

質問者さんが1Hz単位の精度を求める目的が「楽器のチューニング」なのであれば「必要なだけ長く音をならせばよい」という考え方もできます。しかしながら「楽曲の波形データから各Noteの音高を分析したい」となると単純にDFTで求めようとするのには無理があることになります。例えば♪=120で八分音符が発音している期間は0.25秒にしかすぎません。どのように頑張ってDFTのパラメーターを調整しても0.25秒のサンプル区間のデータに対してそのままDFTを施した場合は分解能は4Hzより小さくはなりません。

しかしある程度この問題を解決する工夫があります。それはゼロパディングと呼ばれる方法で、解析対象期間を短くしたまま、DFTの計算区間のみを長くするために不足している区間の波形データとして0を補うという方法です。

例えばサンプリング周波数44.1KHzでN=8192だと、その時間は0.19秒程度(♪=120での八分音符の長さよりちょっと短い程度)で、そのままDFTを施した際の周波数分解能は約5.38Hzです。ここに819215個の0をくっつけてDFTを施すと、周波数分解能は44100/(819216)=0.34Hzとなります。なんだかインチキくさいですが、実際に単純な正弦波で試してみるとそこそこ期待通りの結果になります。

別の手法(?)

Nを小さい値のままにして、ピークのスペクトルとその両隣のスペクトルから解析的に「真の周波数を推定する計算手法」があるという記事を見たことがあります。

http://ibako-study.hateblo.jp/entry/2014/02/06/031945

著者さんも「魔法の数式」なんていっておられますが、自分も「なぜこの数式で比較的精度のよい周波数の推定ができるのか」説明できません><
雰囲気としては解析対象の周波数がFs/Nの倍数からずれる程度でピーク周波数の周りにスペクトルがどのように分散するかの特徴を利用した計算なのだろうと思います。


なお、上記のような手法は「分析対象の波形が比較的単純な波形」の場合にはうまくいくように見えますが、一般の音声データ、つまり倍音が含まれた複数の音程の音が含まれたものから楽譜を起こすといったような目的に対して充分かどうかまでは試したことがありません。興味があるのでちょっとやってみたくなります・・・

投稿2018/01/09 09:26

編集2018/01/09 10:11
KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kiriyaman

2018/01/15 16:35

回答ありがとうございます。 周波数に関して勉強不足だったと回答を閲覧して思いました。 張られたURLなどからもう一度勉強してみます。
guest

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hichon

2018/01/16 11:37

何故かt一文字しか表示されていなかったので修正しました。
guest

0

index * SAMPLE_RATE / FFT_SIZE;

SAMPLE_RATE / FFT_SIZEはこのコードだと約10.77Hzです。
indexは整数なので、得られる結果はSAMPLE_RATE / FFT_SIZEの整数倍になります。
1Hzのズレを許容できないにしては荒すぎます。

満足するまでSAMPLE_RATEを小さくするかFFT_SIZEを大きくしましょう。
例えばFFT_SIZEを65336にすると約0.67Hz単位になります。

投稿2018/01/09 04:41

ozwk

総合スコア13521

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kiriyaman

2018/01/09 05:10

回答ありがとうございます。 色々数値を試してみます
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問