java - studio - Android AudioRecord-Klasse-Live-Mic-Audio schnell verarbeiten, Callback-Funktion einrichten




permission record audio android (3)

Ich möchte Audio vom Mikrofon aufnehmen und für eine mögliche Wiedergabe in nahezu Echtzeit abrufen. Ich bin mir nicht sicher, wie ich die Android AudioRecord-Klasse verwenden kann, um einige Mikrofone aufzunehmen und schnell darauf zuzugreifen.

Für die AudioRecord-Klasse lautet die offizielle Website: "Die App fragt das AudioRecord-Objekt rechtzeitig ab" und "Die Größe des zu füllenden Puffers bestimmt die Länge der Aufzeichnung vor dem Überfahren ungelesener Daten". Später wird vorgeschlagen, dass ein größerer Puffer verwendet werden sollte, wenn weniger häufig abgefragt wird. Sie zeigen niemals ein Beispiel im Code.

Ein Beispiel, das ich in einem Buch gesehen habe, verwendet die AudioRecord-Klasse, um kontinuierlich einen Puffer zu lesen, der gerade mit Live-Mic-Audio gefüllt wurde, und dann schreibt die App diese Daten in eine SD-Datei. Der Pseudo-Code 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 sein Lesen mit der Aufzeichnungsrate synchronisiert, ist unklar - wird das boolesche "isRecording" an anderer Stelle richtig ein- und ausgeschaltet? Es scheint, dass dieser Code entweder zu häufig oder zu selten lesen kann, je nachdem, wie lange das Lesen und Schreiben dauert.

Das Website-Dokument weist außerdem darauf hin, dass die AudioRecord-Klasse eine verschachtelte Klasse namens OnRecordPositionUpdateListener aufweist, die als Schnittstelle definiert ist. Die Informationen legen nahe, dass Sie den gewünschten Zeitraum für die Benachrichtigung über den Fortschritt der Aufzeichnung und den Namen des Ereignishandlers angeben. Außerdem wird automatisch ein Anruf an den Ereignishandler mit der angegebenen Häufigkeit gesendet. Ich denke, die Struktur, in Pseudo-Code 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 erfassen und verarbeiten kann. Android bietet eine andere Klasse namens MediaRecorder, unterstützt jedoch kein Streaming und möchte Live-Audio über ein Wi-Fi-Netzwerk nahezu in Echtzeit streamen. Wo finde ich einige konkrete Beispiele?


Hier ist der Code, den Sie für den OnRecordPositionUpdateListener und den Benachrichtigungszeitraum benötigen.

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

Über detectAfterEvery :

Die Größe von detectEvery muss groß genug sein, um nur die gewünschte Datenmenge aufzunehmen. Für dieses Beispiel haben wir eine Samplerate von 44100 Hz, das heißt, wir wollen 44100 Samples pro Sekunde. Wenn setPositionNotificationPeriod auf 44100 gesetzt wird, weist der Code Android auf Rückruf zurück, nachdem 44100 Samples aufgezeichnet wurden, was ungefähr jede Sekunde entspricht.

Der vollständige Code ist here :

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

Ich frage mich, ob Sie diese Antworten auf folgende Weise 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 Rahmen gelesen werden. Statt Prozess (Puffer) innerhalb des Threads aufzurufen, der die Leseschleife ausführt, rufen Sie Prozess (Puffer) vom Rückruf auf. Verwenden Sie eine Variable, um den letzten Lesepuffer zu verfolgen, damit Sie den richtigen verarbeiten können. Wie es jetzt ist, blockieren Sie das Lesen, dann lesen Sie nicht, während Sie gerade arbeiten. Ich denke, es könnte besser sein, diese beiden zu trennen.


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

}

Es spielt das aufgezeichnete Audio weniger als 100 ms Verzögerung.







android-hardware