wake-up-neo.net

Android AudioRecord class - Live-Mikrofon-Audio schnell verarbeiten, Rückruffunktion einrichten

Ich möchte Audio vom Mikrofon aufnehmen und auf dieses zugreifen, um es nahezu in Echtzeit wiedergeben zu können. Ich bin mir nicht sicher, wie ich die Android AudioRecord-Klasse verwenden soll, um etwas Mikrofon-Audio aufzunehmen und schnell darauf zuzugreifen.

Für die AudioRecord-Klasse heißt es auf der offiziellen Website "Die App fragt das AudioRecord-Objekt rechtzeitig ab" und "Die Größe des zu füllenden Puffers bestimmt die Dauer der Aufzeichnung, bevor ungelesene Daten überlaufen". Später wird empfohlen, einen größeren Puffer zu verwenden, wenn weniger häufig abgefragt wird. Sie zeigen nie wirklich ein Beispiel im Code.

Ein Beispiel, das ich in einem Buch gesehen habe, verwendet die AudioRecord-Klasse, um kontinuierlich einen Puffer zu lesen, der frisch mit Live-Mikrofon-Audio gefüllt ist, und dann schreibt die App diese Daten in eine SD-Datei. Der Pseudocode sieht ungefähr so ​​aus:

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();

Wie dieser Code seinen Lesevorgang mit der Aufnahmerate synchronisiert, ist unklar - wird der boolesche "isRecording" -Sequenzvorgang an anderer Stelle ordnungsgemäß aktiviert und deaktiviert? Je nachdem, wie lange das Lesen und Schreiben dauert, kann dieser Code entweder zu häufig oder zu selten gelesen werden.

Das Site-Dokument besagt außerdem, dass die AudioRecord-Klasse eine verschachtelte Klasse mit dem Namen OnRecordPositionUpdateListener hat, die als Schnittstelle definiert ist. Aus den Informationen geht hervor, dass Sie in irgendeiner Weise den Zeitraum angeben, in dem Sie über den Fortschritt der Aufzeichnung informiert werden möchten, sowie den Namen Ihres Ereignishandlers. In der angegebenen Häufigkeit wird automatisch ein Anruf an Ihren Ereignishandler gesendet. Ich denke die Struktur im Pseudocode wäre so etwas wie -

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
)

Ich muss einen bestimmten Code finden, mit dem ich Mikrofon-Audio mit einer Verzögerung von weniger als 500 ms aufzeichnen und verarbeiten kann. Android bietet eine weitere Klasse namens MediaRecorder an, die jedoch kein Streaming unterstützt, und ich möchte Live-Mikrofon-Audio über ein Wi-Fi-Netzwerk nahezu in Echtzeit streamen. Wo finde ich welche? konkrete Beispiele?

64
kenj

Nachdem ich mit den Benachrichtigungen und einigen anderen Techniken experimentiert hatte, entschied ich mich für diesen Code:

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;
        }

    }

Bisher funktioniert es ziemlich robust auf einem halben Dutzend Android Handys, auf denen ich es ausprobiert habe.

33
tonys

Ich frage mich, ob Sie diese Antworten folgendermaßen kombinieren könnten ...

Verwenden Sie setPositionNotificationPeriod (160) vor der while-Schleife. Dies sollte dazu führen, dass der Rückruf jedes Mal aufgerufen wird, wenn 160 Frames gelesen werden. Anstatt process (buffer) innerhalb des Threads aufzurufen, der die Leseschleife ausführt, rufen Sie process (buffer) über den Rückruf auf. Verwenden Sie eine Variable, um den letzten Lesepuffer zu verfolgen und den richtigen zu verarbeiten. So wie es jetzt ist, blockieren Sie das Lesen, dann lesen Sie nicht, während Sie es verarbeiten. Ich denke, es ist vielleicht besser, diese beiden zu trennen.

20
Dave MacLean

Hier ist der Code, den Sie benötigen, um den OnRecordPositionUpdateListener und den Benachrichtigungszeitraum zu verwenden.

Mir ist aufgefallen, dass die Benachrichtigung in der Praxis nicht immer genau zur gleichen Zeit gesendet wird, wie ich es möchte, aber sie ist nah genug.

Über detectAfterEvery:

Die Größe von detectEvery muss so groß sein, dass nur die gewünschte Datenmenge gespeichert werden kann. Für dieses Beispiel haben wir also eine Abtastrate von 44100 Hz, das heißt, wir wollen 44100 Abtastungen pro Sekunde. Wenn Sie setPositionNotificationPeriod auf 44100 setzen, weist der Code Android nach der Aufzeichnung von 44100 Samples, also etwa alle 1 Sekunde, an, einen Rückruf durchzuführen.

Der vollständige Code ist hier :

        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);
        }
11
gregm
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();

}

Das aufgezeichnete Audio wird mit einer Verzögerung von weniger als 100 ms wiedergegeben.