web-dev-qa-db-fra.com

Android Classe AudioRecord - Traitez l'audio du micro en direct rapidement, configurez la fonction de rappel

Je veux enregistrer l'audio du micro et y accéder pour une lecture possible en temps quasi réel. Je ne sais pas comment utiliser la classe Android AudioRecord pour enregistrer du son micro et y accéder rapidement.

Pour la classe AudioRecord, le site officiel indique `` l'application interroge l'objet AudioRecord à temps '' et `` la taille du tampon en cours de remplissage détermine la durée de l'enregistrement avant de dépasser les données non lues ''. Plus tard, il est suggéré d'utiliser un tampon plus grand lors d'une interrogation moins fréquente. Ils ne montrent jamais réellement un exemple dans le code.

Un exemple que j'ai vu dans un livre utilise la classe AudioRecord pour lire en continu un tampon fraîchement rempli avec du son micro en direct, puis l'application écrit ces données dans un fichier SD. Le pseudo-code ressemble à quelque chose -

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 n'est pas clair - le booléen "isRecording" est-il correctement séquencé et désactivé ailleurs? Il semble que ce code puisse être lu trop fréquemment ou trop rarement, selon le temps que prennent la lecture et l'écriture.

Le document du site indique également que la classe AudioRecord a une classe imbriquée nommée OnRecordPositionUpdateListener qui est définie comme une interface. Les informations suggèrent que, d'une manière ou d'une autre, vous spécifiez la période pendant laquelle vous souhaitez être averti de la progression de l'enregistrement et le nom de votre gestionnaire d'événements, et un appel est automatiquement effectué vers 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 permette de capturer et de traiter l'audio du micro avec un retard de moins d'environ 500 ms. Android propose une autre classe appelée MediaRecorder, mais il ne prend pas en charge la diffusion en continu, et je peux vouloir diffuser du son micro en direct sur un réseau Wi-Fi en temps quasi réel. Où puis-je en trouver des exemples spécifiques?

64
kenj

Après avoir expérimenté beaucoup avec les notifications et un tas d'autres techniques, je me suis installé sur 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 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;
        }

    }

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

33
tonys

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

Utilisez setPositionNotificationPeriod (160) avant la boucle while. Cela devrait entraîner le rappel à chaque fois que 160 images sont lues. Au lieu d'appeler le processus (tampon) à l'intérieur du thread qui fait la boucle de lecture, appelez le processus (tampon) à partir du rappel. Utilisez une variable pour garder une trace du dernier tampon de lecture afin de traiter le bon. Dans l'état actuel des choses, vous bloquez la lecture, puis vous ne lisez pas pendant le traitement. Je pense qu'il serait peut-être préférable de les séparer.

20
Dave MacLean

Voici le code dont vous avez besoin pour utiliser la période OnRecordPositionUpdateListener et la période de notification.

J'ai remarqué qu'en pratique, il n'envoie pas la notification de manière cohérente au même moment précis, je veux, mais il est suffisamment proche.

À propos de detectAfterEvery:

La taille de detectEvery doit être suffisamment grande pour contenir uniquement la quantité de données souhaitée. Donc, pour cet exemple, nous avons un taux d'échantillonnage de 44100 Hz, ce qui signifie que nous voulons 44100 échantillons par seconde. En définissant setPositionNotificationPeriod sur 44100, le code indique Android à rappeler après avoir enregistré 44100 échantillons, soit environ toutes les 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);
        }
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();

}

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

2