22

私の Android Java アプリケーションは、オーディオ データを RAM に記録して処理する必要があります。これが、「MediaRecorder」ではなく「AudioRecord」クラスを使用する理由です(ファイルにのみ記録します)。

今までは、オーディオ データの "read()" によるビジー ループ ポーリングを使用していました。これはこれまでのところ機能していますが、CPU を固定しすぎています。2 つのポーリングの間に、100% の CPU 使用率を回避するためにスレッドをスリープ状態にしました。ただし、スリープの時間が保証されておらず、オーディオ スニペットが失われないようにセキュリティ時間を差し引く必要があるため、これは実際にはクリーンなソリューションではありません。これは CPU 最適化ではありません。並列実行スレッドには、できるだけ多くの空き CPU サイクルが必要です。

「OnRecordPositionUpdateListener」を使用して録音を実装しました。これは非常に有望であり、SDK ドキュメントによると正しい方法に見えます。すべてが機能しているように見えます (オーディオ デバイスを開く、データを read() するなど) が、Listner が呼び出されることはありません。

理由を知っている人はいますか?

情報: エミュレーターではなく、実際のデバイスで作業しています。Busy Loop を使用した録音は、基本的に機能します (ただし、満足はできません)。Callback Listener だけが呼び出されることはありません。

ここに私のソースコードからのスニペットがあります:

public class myApplication extends Activity {

  /* audio recording */
  private static final int AUDIO_SAMPLE_FREQ = 16000;
  private static final int AUDIO_BUFFER_BYTESIZE = AUDIO_SAMPLE_FREQ * 2 * 3; // = 3000ms
  private static final int AUDIO_BUFFER_SAMPLEREAD_SIZE = AUDIO_SAMPLE_FREQ  / 10 * 2; // = 200ms

  private short[] mAudioBuffer = null; // audio buffer
  private int mSamplesRead; // how many samples are recently read
  private AudioRecord mAudioRecorder; // Audio Recorder

  ...

  private OnRecordPositionUpdateListener mRecordListener = new OnRecordPositionUpdateListener() {

    public void onPeriodicNotification(AudioRecord recorder) {
      mSamplesRead = recorder.read(mAudioBuffer, 0, AUDIO_BUFFER_SAMPLEREAD_SIZE);
      if (mSamplesRead > 0) {

        // do something here...

      }
    }

    public void onMarkerReached(AudioRecord recorder) {
      Error("What? Hu!? Where am I?");
    }
  };

  ...

  public void onCreate(Bundle savedInstanceState) {

  try {
      mAudioRecorder = new AudioRecord(
          android.media.MediaRecorder.AudioSource.MIC, 
          AUDIO_SAMPLE_FREQ,
          AudioFormat.CHANNEL_CONFIGURATION_MONO,
          AudioFormat.ENCODING_PCM_16BIT,  
          AUDIO_BUFFER_BYTESIZE);
    } catch (Exception e) {
      Error("Unable to init audio recording!");
    }

    mAudioBuffer = new short[AUDIO_BUFFER_SAMPLEREAD_SIZE];
    mAudioRecorder.setPositionNotificationPeriod(AUDIO_BUFFER_SAMPLEREAD_SIZE);
    mAudioRecorder.setRecordPositionUpdateListener(mRecordListener);
    mAudioRecorder.startRecording();

    /* test if I can read anything at all... (and yes, this here works!) */
    mSamplesRead = mAudioRecorder.read(mAudioBuffer, 0, AUDIO_BUFFER_SAMPLEREAD_SIZE);

  }
}
4

4 に答える 4

14

問題は、まだ読み取りループを実行する必要があることだと思います。コールバックを設定すると、コールバックに指定したフレーム数を読み取ったときにコールバックが発生します。しかし、まだ読み取りを行う必要があります。私はこれを試しましたが、コールバックは問題なく呼び出されます。マーカーを設定すると、記録の開始からそのフレーム数が読み取られたときにコールバックが発生します。言い換えれば、多くの読み取りの後、マーカーをはるかに未来に設定することができ、その時点で起動します。期間をより大きなフレーム数に設定すると、そのフレーム数が読み取られるたびにコールバックが発生します。彼らがこれを行うのは、生データの低レベル処理をタイトなループで実行できるようにするためだと思います。その後、コールバックがサマリーレベルの処理を頻繁に実行できるようになります。

于 2010-09-28T23:35:20.870 に答える
6

これが平均的なノイズを見つけるために使用される私のコードです。デバイスのバッテリーを節約するため、リスナー通知に基づいていることに注意してください。それは間違いなく上記の例に基づいています。それらの例は私にとって多くの時間を節約しました、ありがとう。

private AudioRecord recorder;
private boolean recorderStarted;
private Thread recordingThread;
private int bufferSize = 800;
private short[][] buffers = new short[256][bufferSize];
private int[] averages = new int[256];
private int lastBuffer = 0;

protected void startListenToMicrophone() {
    if (!recorderStarted) {

        recordingThread = new Thread() {
            @Override
            public void run() {
                int minBufferSize = AudioRecord.getMinBufferSize(8000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
                        AudioFormat.ENCODING_PCM_16BIT);
                recorder = new AudioRecord(AudioSource.MIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
                        AudioFormat.ENCODING_PCM_16BIT, minBufferSize * 10);
                recorder.setPositionNotificationPeriod(bufferSize);
                recorder.setRecordPositionUpdateListener(new OnRecordPositionUpdateListener() {
                    @Override
                    public void onPeriodicNotification(AudioRecord recorder) {
                        short[] buffer = buffers[++lastBuffer % buffers.length];
                        recorder.read(buffer, 0, bufferSize);
                        long sum = 0;
                        for (int i = 0; i < bufferSize; ++i) {
                            sum += Math.abs(buffer[i]);
                        }
                        averages[lastBuffer % buffers.length] = (int) (sum / bufferSize);
                        lastBuffer = lastBuffer % buffers.length;
                    }

                    @Override
                    public void onMarkerReached(AudioRecord recorder) {
                    }
                });
                recorder.startRecording();
                short[] buffer = buffers[lastBuffer % buffers.length];
                recorder.read(buffer, 0, bufferSize);
                while (true) {
                    if (isInterrupted()) {
                        recorder.stop();
                        recorder.release();
                        break;
                    }
                }
            }
        };
        recordingThread.start();

        recorderStarted = true;
    }
}

private void stopListenToMicrophone() {
    if (recorderStarted) {
        if (recordingThread != null && recordingThread.isAlive() && !recordingThread.isInterrupted()) {
            recordingThread.interrupt();
        }
        recorderStarted = false;
    }
}
于 2011-11-04T09:15:27.510 に答える
5

「OnRecordPositionUpdateListener」を使用して録音を実装しました。これは非常に有望であり、SDK ドキュメントによると正しい方法に見えます。すべてが機能しているように見えます (オーディオ デバイスを開く、データを read() するなど) が、Listner が呼び出されることはありません。

理由を知っている人はいますか?

OnRecordPositionUpdateListener は、最初の.read().

つまり、ドキュメントに従ってすべてを設定すると、リスナーが呼び出されないことがわかりました。ただし、.read()イニシャルを実行した直後に最初に aを呼び出すと、Listener が呼び出されます (Listener が呼び出されるたびに.start()a を実行した場合)。.read()

言い換えれば、Listener イベントは 1 回.read()または数回に 1 回しか有効ではないように思えます。

また、buffSize/2 未満のサンプルの読み取りを要求すると、Listener が呼び出されないこともわかりました。.read()そのため、リスナーはバッファサイズの少なくとも半分の後にのみ呼び出されるようです。Listener コールバックを使い続けるには、リスナーが実行されるたびに read を呼び出す必要があります。(つまり、read の呼び出しを Listener コードに入れます。)

ただし、データがまだ準備できていないときにリスナーが呼び出されたようで、ブロッキングが発生します。

また、通知期間または時間が bufferSize の半分を超える場合、呼び出されることはないようです。

アップデート:

さらに深く掘り下げていくと、コールバックは a が.read()終了したときにのみ呼び出されるように見えることがわかりました......!

それがバグなのか機能なのかわかりません。私の最初の考えは、読む時間になったらコールバックが欲しいということです。しかし、おそらく Android の開発者は逆の考えを持っていたのでしょう。awhile(1){xxxx.read(...)} を単独でスレッドに入れ、read() が終了するたびに追跡する必要がないようにするために、コールバックは本質的にいつ通知されるかを教えてくれます。読み取りが終了しました。

おー。たぶん、それは私に夜明けをもたらしました。そして、他の人がここで私の前にこれを指摘したと思いますが、それは沈みませんでした:位置または期間のコールバックは、既に読み取られたバイトで動作する必要があります...

たぶん、スレッドの使用にこだわっていると思います。

このスタイルでは、read がブロックされ、十分なデータが返されるのを辛抱強く待つため、スレッドが返されるとすぐに read() を継続的に呼び出します。

次に、独立して、コールバックは x サンプルごとに指定された関数を呼び出します。

于 2013-04-04T06:40:13.153 に答える
0

インテント サービスを介して音声を録音している場合、このコールバックの問題が発生する可能性もあります。私は最近この問題に取り組んでおり、別のスレッドで記録メソッドを呼び出す簡単な解決策を思いつきました。これにより、記録中に問題なく onPeriodicNotification メソッドが呼び出されるはずです。

このようなもの:

public class TalkService extends IntentService {
...
@Override
protected void onHandleIntent(Intent intent) {
Context context = getApplicationContext();
tRecord = new Thread(new recordAudio());
tRecord.start();
...
while (tRecord.isAlive()) {
    if (getIsDone()) {
        if (aRecorder.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED) {
            socketConnection(context, host, port);
        }
    }
} }
...
class recordAudio implements Runnable {

        public void run() {
            try {
                OutputStream osFile = new FileOutputStream(file);
                BufferedOutputStream bosFile = new BufferedOutputStream(osFile);
                DataOutputStream dosFile = new DataOutputStream(bosFile);

                aRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
                        sampleRate, channelInMode, encodingMode, bufferSize);

                data = new short[bufferSize];

                aRecorder.setPositionNotificationPeriod(sampleRate);
                aRecorder
                        .setRecordPositionUpdateListener(new AudioRecord.OnRecordPositionUpdateListener() {
                            int count = 1;

                            @Override
                            public void onPeriodicNotification(
                                    AudioRecord recorder) {
                                Log.e(WiFiDirect.TAG, "Period notf: " + count++);

                                if (getRecording() == false) {
                                    aRecorder.stop();
                                    aRecorder.release();
                                    setIsDone(true);
                                    Log.d(WiFiDirect.TAG,
                                            "Recorder stopped and released prematurely");
                                }
                            }

                            @Override
                            public void onMarkerReached(AudioRecord recorder) {
                                // TODO Auto-generated method stub

                            }
                        });

                aRecorder.startRecording();
                Log.d(WiFiDirect.TAG, "start Recording");
                aRecorder.read(data, 0, bufferSize);
                for (int i = 0; i < data.length; i++) {
                    dosFile.writeShort(data[i]);
                }

                if (aRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
                    aRecorder.stop();
                    aRecorder.release();
                    setIsDone(true);
                    Log.d(WiFiDirect.TAG, "Recorder stopped and released");
                }

            } catch (Exception e) {
                // TODO: handle exception
            }
        }
    }
于 2012-05-28T15:19:16.823 に答える