Programing

Android AudioRecord 클래스-라이브 마이크 오디오를 빠르게 처리하고 콜백 기능을 설정합니다.

lottogame 2020. 11. 22. 18:51
반응형

Android AudioRecord 클래스-라이브 마이크 오디오를 빠르게 처리하고 콜백 기능을 설정합니다.


마이크에서 오디오를 녹음하고 거의 실시간으로 재생할 수 있도록 액세스하고 싶습니다. Android AudioRecord 클래스를 사용하여 마이크 오디오를 녹음하고 빠르게 액세스하는 방법을 잘 모르겠습니다.

AudioRecord 클래스의 경우 공식 사이트는 '앱이 시간 내에 AudioRecord 객체를 폴링합니다'라고 말하고 '채워지는 버퍼의 크기는 읽지 않은 데이터를 초과 실행하기 전에 녹음의 시간 길이를 결정합니다'라고 말합니다. 나중에 폴링 빈도를 줄이려면 더 큰 버퍼를 사용하는 것이 좋습니다. 실제로 코드에 예제를 표시하지 않습니다.

책에서 본 한 가지 예는 AudioRecord 클래스를 사용하여 라이브 마이크 오디오로 새로 채워진 버퍼를 지속적으로 읽은 다음 앱에서이 데이터를 SD 파일에 씁니다. 의사 코드는 다음과 같습니다.

set up AudioRecord object with buffer size and recording format info
set up a file and an output stream
myAudioRecord.startRecording();
while(isRecording)
{
    // myBuffer is being filled with fresh audio
    read audio data into myBuffer
    send contents of myBuffer to SD file
}
myAudioRecord.stop();

이 코드가 기록 속도와 판독 값을 동기화하는 방법은 불분명합니다. 부울 "isRecording"이 다른 곳에서 적절하게 켜졌다 꺼졌습니까? 이 코드는 읽고 쓰는 데 걸리는 시간에 따라 너무 자주 또는 너무 드물게 읽을 수 있습니다.

사이트 문서는 또한 AudioRecord 클래스에 인터페이스로 정의 된 OnRecordPositionUpdateListener라는 중첩 클래스가 있다고 말합니다. 이 정보는 어떻게 든 녹화 진행 상황에 대한 알림을받을 기간과 이벤트 핸들러의 이름을 지정하고 지정된 빈도로 이벤트 핸들러를 자동으로 호출한다는 것을 제안합니다. 의사 코드의 구조는 다음과 같습니다.

set target of period update message = myListener
set period to be about every 250 ms
other code

myListener()
{
    if(record button was recently tapped)
        handle message that another 250 ms of fresh audio is available
        ie, read it and send it somewhere
)

약 500ms 미만의 지연으로 마이크 오디오를 캡처하고 처리 할 수있는 특정 코드를 찾아야합니다. Android는 MediaRecorder라는 또 다른 클래스를 제공하지만 스트리밍을 지원하지 않으며 Wi-Fi 네트워크를 통해 거의 실시간으로 라이브 마이크 오디오를 스트리밍하고 싶을 수 있습니다. 구체적인 예는 어디에서 찾을 수 있습니까?


알림과 다른 기술을 많이 실험 한 후이 코드를 결정했습니다.

private class AudioIn extends Thread { 
     private boolean stopped    = false;

     private AudioIn() { 

             start();
          }

     @Override
     public void run() { 
            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
            AudioRecord recorder = null;
            short[][]   buffers  = new short[256][160];
            int         ix       = 0;

            try { // ... initialise

                  int N = AudioRecord.getMinBufferSize(8000,AudioFormat.CHANNEL_IN_MONO,AudioFormat.ENCODING_PCM_16BIT);

                   recorder = new AudioRecord(AudioSource.MIC,
                                              8000,
                                              AudioFormat.CHANNEL_IN_MONO,
                                              AudioFormat.ENCODING_PCM_16BIT,
                                              N*10);

                   recorder.startRecording();

                   // ... loop

                   while(!stopped) { 
                      short[] buffer = buffers[ix++ % buffers.length];

                      N = recorder.read(buffer,0,buffer.length);
                      //process is what you will do with the data...not defined here
                      process(buffer);
                  }
             } catch(Throwable x) { 
               Log.w(TAG,"Error reading voice audio",x);
             } finally { 
               close();
             }
         }

      private void close() { 
          stopped = true;
        }

    }

지금까지 제가 사용해 본 6 개의 안드로이드 폰에서 꽤 견고하게 작동하고 있습니다.


이 답변을 다음과 같이 결합 할 수 있는지 궁금합니다.

while 루프 전에 setPositionNotificationPeriod (160)를 사용하십시오. 이렇게하면 160 프레임을 읽을 때마다 콜백이 호출됩니다. 읽기 루프를 수행하는 스레드 내부에서 process (buffer)를 호출하는 대신 콜백에서 process (buffer)를 호출합니다. 변수를 사용하여 마지막 읽기 버퍼를 추적하여 올바른 버퍼를 처리하십시오. 지금처럼 읽기를 차단하면 처리하는 동안 읽지 않습니다. 나는 그 둘을 분리하는 것이 더 나을 것이라고 생각합니다.


다음은 OnRecordPositionUpdateListener 및 알림 기간을 사용하는 데 필요한 코드입니다.

실제로 동일한 정확한 시간에 일관되게 알림을 보내지 않는 것으로 나타났습니다.하지만 충분히 가깝습니다.

소개 detectAfterEvery:

detectEvery의 크기는 원하는 데이터 양만 저장할 수있을만큼 충분히 커야합니다. 따라서이 예의 샘플 속도는 44100Hz입니다. 즉, 초당 44100 개의 샘플을 원한다는 의미입니다. setPositionNotificationPeriod44100으로 설정하면 코드는 약 1 초마다 44100 개의 샘플을 기록한 후 Android에 콜백하도록 지시합니다.

전체 코드는 다음과 같습니다.

        final int sampleRate = 44100;
        int bufferSize =
                AudioRecord.getMinBufferSize(sampleRate,
                        AudioFormat.CHANNEL_CONFIGURATION_MONO,
                        AudioFormat.ENCODING_PCM_16BIT);

//aim for 1 second
        int detectAfterEvery = (int)((float)sampleRate * 1.0f);

        if (detectAfterEvery > bufferSize)
        {
            Log.w(TAG, "Increasing buffer to hold enough samples " + detectAfterEvery + " was: " + bufferSize);
            bufferSize = detectAfterEvery;
        }

        recorder =
                new AudioRecord(AudioSource.MIC, sampleRate,
                        AudioFormat.CHANNEL_CONFIGURATION_MONO,
                        AudioFormat.ENCODING_PCM_16BIT, bufferSize);
        recorder.setPositionNotificationPeriod(detectAfterEvery);

        final short[] audioData = new short[bufferSize];
        final int finalBufferSize = bufferSize;

        OnRecordPositionUpdateListener positionUpdater = new OnRecordPositionUpdateListener()
        {
            @Override
            public void onPeriodicNotification(AudioRecord recorder)
            {
                Date d = new Date();
//it should be every 1 second, but it is actually, "about every 1 second"
//like 1073, 919, 1001, 1185, 1204 milliseconds of time.
                Log.d(TAG, "periodic notification " + d.toLocaleString() + " mili " + d.getTime());
                recorder.read(audioData, 0, finalBufferSize);

                //do something amazing with audio data
            }

            @Override
            public void onMarkerReached(AudioRecord recorder)
            {
                Log.d(TAG, "marker reached");
            }
        };
        recorder.setRecordPositionUpdateListener(positionUpdater);

        Log.d(TAG, "start recording, bufferSize: " + bufferSize);
        recorder.startRecording(); 

//remember to still have a read loop otherwise the listener won't trigger
while (continueRecording)
        {
            recorder.read(audioData, 0, bufferSize);
        }

private int freq =8000;
private AudioRecord audioRecord = null;
private Thread Rthread = null;

private AudioManager audioManager=null;
private AudioTrack audioTrack=null;
byte[] buffer = new byte[freq];

//call this method at start button

protected void Start()

{

loopback();

}

protected void loopback() { 

    android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
    final int bufferSize = AudioRecord.getMinBufferSize(freq,
            AudioFormat.CHANNEL_CONFIGURATION_MONO,
            AudioFormat.ENCODING_PCM_16BIT);


    audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, freq,
            AudioFormat.CHANNEL_CONFIGURATION_MONO,
            MediaRecorder.AudioEncoder.AMR_NB, bufferSize);

    audioTrack = new AudioTrack(AudioManager.ROUTE_HEADSET, freq,
            AudioFormat.CHANNEL_CONFIGURATION_MONO,
            MediaRecorder.AudioEncoder.AMR_NB, bufferSize,
            AudioTrack.MODE_STREAM);



    audioTrack.setPlaybackRate(freq);
     final byte[] buffer = new byte[bufferSize];
    audioRecord.startRecording();
    Log.i(LOG_TAG, "Audio Recording started");
    audioTrack.play();
    Log.i(LOG_TAG, "Audio Playing started");
    Rthread = new Thread(new Runnable() {
        public void run() {
            while (true) {
                try {
                    audioRecord.read(buffer, 0, bufferSize);                                    
                    audioTrack.write(buffer, 0, buffer.length);

                } catch (Throwable t) {
                    Log.e("Error", "Read write failed");
                    t.printStackTrace();
                }
            }
        }
    });
    Rthread.start();

}

녹음 된 오디오를 100ms 지연 미만으로 재생합니다.

참고 URL : https://stackoverflow.com/questions/4525206/android-audiorecord-class-process-live-mic-audio-quickly-set-up-callback-func

반응형