音声スペクトルモニター - Google Play のアプリでは、
AudioRecordを使ってリアルタイムに音声処理を行っているが、
AudioRecordは思った通りに動作してくれなくて扱いにくい。
まず、setPositionNotificationPeriodで指定した間隔で呼ばれるはずのonPeriodicNotificationだが、startRecordingで録音を開始しても呼ばれない事がある。
https://sh-dev.sharp.co.jp/android/modules/d3forum/index.php?topic_id=118sh-dev.sharp.co.jp
の情報を見ると、read()によって読み終わった時点で呼ばれるということだが、
確かにstartRecordingの後にread()で読み込むと呼ばれるのだが、
そんなことは、APIリファレンスのどこにも書いていない。
また、全てのAndroidデバイスがサポートしているサンプリング周波数は44.1kHzと
リファレンスに書いているが、44.1kHzでAudioRecordを使用すると、
しばらく動作すると
W/AudioRecord? obtainBuffer timed out (is the CPU pegged?)
というエラーがでて動作が止まってしまう。
これは未だ解決できていない。
(処理が間に合わなくて発生している?)
しかたなく、8kHzで録音している。
AndroidでiPhoneと比べてサウンド関連のアプリが少ないのは、
APIが使いづらく情報が少ないのが関係しているんじゃないかと思う。
Androidでサウンド関連のアプリを作りたければ、OpenSL ESを使わないとダメなのだろうか。
※解決しました。
tadaoyamaoka.hatenablog.com
AudioRecordでの録音を試したサンプルプログラムを載せておきます。
package com.tadaoyamaoka.recordsample; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.Handler; import android.util.AttributeSet; import android.view.SurfaceHolder; import android.view.SurfaceView; import java.util.Timer; import java.util.TimerTask; public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder holder; private AudioRecord record; private Timer timer = new Timer(true); Handler handler = new Handler(); private int count = 0; private int draw_count = 0; // 定数 private static final int AUDIO_SAMPLE_FREQ = 44100; private static final int AUDIO_BUFFER_SIZE = AudioRecord.getMinBufferSize( AUDIO_SAMPLE_FREQ, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); private static final int FRAME_BUFFER_SIZE = AUDIO_BUFFER_SIZE / 2 / 10; // 10FPS private short data[] = new short[FRAME_BUFFER_SIZE]; public MySurfaceView(Context context) { super(context); } public MySurfaceView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } private void init(Context context) { holder = getHolder(); holder.addCallback(this); } @Override public void surfaceCreated(SurfaceHolder holder) { // AudioRecord作成 record = new AudioRecord(MediaRecorder.AudioSource.MIC, AUDIO_SAMPLE_FREQ, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, AUDIO_BUFFER_SIZE); record.setRecordPositionUpdateListener(new AudioRecord.OnRecordPositionUpdateListener() { // フレームごとの処理 @Override public void onPeriodicNotification(AudioRecord recorder) { count++; recorder.read(data, 0, FRAME_BUFFER_SIZE); } @Override public void onMarkerReached(AudioRecord recorder) { } }); record.setPositionNotificationPeriod(FRAME_BUFFER_SIZE); // 録音開始 record.startRecording(); record.read(data, 0, FRAME_BUFFER_SIZE); // 描画用タイマー開始 timer.schedule(new TimerTask() { @Override public void run() { handler.post(new Runnable() { @Override public void run() { draw(); } }); } }, 1000/10, 1000/10); // 10FPS } private void draw() { draw_count++; //Log.d("debug", String.format("%d", count)); Canvas canvas = holder.lockCanvas(); if (canvas != null) { canvas.drawColor(Color.BLACK); Paint paint = new Paint(); paint.setTextSize(30); paint.setColor(Color.WHITE); canvas.drawText(String.format("%d", count), 100, 100, paint); canvas.drawText(String.format("%d", draw_count), 100, 200, paint); holder.unlockCanvasAndPost(canvas); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { timer.cancel(); timer = null; record.stop(); record.release(); record = null; } }