TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

AndroidのAudioRecordの問題

音声スペクトルモニター - 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で録音している。

AndroidiPhoneと比べてサウンド関連のアプリが少ないのは、
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;
    }
}