65 votes

Classe Android AudioRecord - traitez rapidement l'audio d'un micro en direct, configurez la fonction de rappel

Je veux enregistrer de l'audio à partir du microphone et de l'accès pour possible la lecture en temps quasi-réel. Je ne suis pas sûr de la façon d'utiliser le Android AudioRecord classe pour enregistrer de l'audio micro et d'y accéder rapidement.

Pour le AudioRecord classe, le site officiel dit "l'application interroge le AudioRecord objet dans le temps', et 'la taille de la mémoire tampon rempli détermine la durée de l'enregistrement avant l'exécution non lus de données". Plus tard, il est suggéré qu'une plus grande mémoire tampon doit être utilisé lors de l'interrogation de moins en moins fréquemment. Ils n'ont jamais fait de montrer un exemple de code.

Un exemple que j'ai vu dans un livre utilise le AudioRecord classe continue de lire un tampon fraîchement remplie avec live audio micro, et puis l'app écrit ces données à un fichier SD. Le pseudo-code ressemble à quelque chose comme

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

Comment ce code synchronise sa lecture avec le taux d'enregistrement est difficile - est le booléen "isRecording" séquencé et hors d'ailleurs? Il semble que ce code peut lire trop souvent ou trop peu, selon le temps de la lecture et de l'écriture prend.

Le site doc dit aussi que le AudioRecord classe a une classe imbriquée nommé OnRecordPositionUpdateListener qui est définie comme une interface. L'information suggère que quelque part, vous spécifiez la période que vous souhaitez pour être informé de l'avancement de l'enregistrement, et le nom de votre gestionnaire d'événements, et un appel est automatiquement mis à votre gestionnaire d'événements à la fréquence spécifiée. Je pense que la structure, en pseudo-code serait quelque chose comme:

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
)

J'ai besoin de trouver un code spécifique qui me permet de saisir et de traiter les mic audio avec un retard de moins de 500 ms. Android offre une autre classe appelée MediaRecorder, mais il ne prend pas en charge la diffusion en continu, et je veux écouter de la vivre mic audio via un réseau Wi-Fi en temps quasi-réel. Où puis-je trouver des exemples concrets?

34voto

tonys Points 2334

Après avoir expérimenté de nombreuses techniques de notifications et de nombreuses autres techniques, j'ai choisi ce 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(buffer);
                  }
             } catch(Throwable x) { 
               log.warning(TAG,"Error reading voice audio",x);
             } finally { 
               close(recorder);
             }
         }

      private void close() { 
          stopped = true;
        }

    }
 

Jusqu'à présent, cela fonctionne assez fort sur la demi-douzaine de téléphones Android sur lesquels je l'ai essayé.

20voto

Dave MacLean Points 3430

Je me demande si vous pourriez combiner ces réponses de la manière suivante...

Utilisation setPositionNotificationPeriod(160) avant la boucle while. Cela devrait provoquer le callback sera appelée à chaque fois que 160 cadres de lecture. Au lieu du processus appelant(tampon) à l'intérieur de la thread qui fait la lecture de la boucle, processus d'appel(tampon) de la fonction de rappel. Utiliser une variable pour garder une trace de la dernière lecture de la mémoire tampon, de sorte que vous traitez le droit. Comme il est maintenant, vous bloquez sur les lisez, alors vous n'êtes pas de la lecture pendant que vous êtes à la transformation. Je pense qu'il serait préférable de séparer ces deux-là.

11voto

gregm Points 5441

Voici le code que vous devez utiliser le OnRecordPositionUpdateListener et de la Période de Notification.

J'ai remarqué que, dans la pratique, il n'envoie pas de notification de manière cohérente dans le même temps exact, je veux, mais c'est assez proche.

À propos de detectAfterEvery:

La taille de detectEvery doit être assez grand pour contenir juste la quantité de données que vous souhaitez. Ainsi, pour cet exemple, nous avons un taux d'échantillonnage de 44100 Hz, cela signifie que nous voulons 44100 échantillons par seconde. Par le réglage de l' setPositionNotificationPeriod à 44100, le code indique Android pour rappel, après il a enregistré 44100 échantillons, qui est d'environ 1 seconde.

Le code complet est ici:

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

2voto

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

}
 

Il lit l'audio enregistré avec un retard inférieur à 100 ms.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X