пятница, 3 июня 2011 г.

Работа со звуком в Android-приложении

На днях возникла задача: получить и проанализировать амплитуду сигнала с микрофона в Android. Задача, в принципе, простая. В обычном случае мы используем класс MediaRecorder таким вот образом:
MediaRecorder recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recorder.setOutputFile("/dev/null");
recorder.prepare();
recorder.start();
for(int i=0; i<10; i++) {
 System.err.println("current amplitude is:"+recorder.getMaxAmplitude());
 try {
   Thread.sleep(1000);
 } catch (Exception e) {}
}
recorder.stop();
recorder.reset();
recorder.release();

В этом фрагменте мы теоретически должны получить 10 отметок максимальной амплитуды сигнала за периоды чтения по секунде с первой до десятой. Для этого мы читаем сигнал с микрофона в "никуда", и используем getMaxAmplitude(), который согласно документации должен возвращать значение максимальной амплитуды за период с последнего вызова метода до данного момента. В чём проблема? На моём LG P500 этот метод всегда возвращает ноль :(
Исследования на тему "Почему" дали немного. Гораздо интереснее ответ на вопрос

Как всё-таки получать амплитуду сигнала?
Класс MediaRecorder крайне неудобен для работы с сигналом напрямую. Даже если бы я смог его использовать, запись данных в файл - далеко не всё, что обычно нужно при захвате звука. Для наложения фильтров и анализа сигнала удобнее получить сигнал в массив и работать с ним "на лету". К счастью есть ещё один класс в Android SDK, который нас выручит. Это AudioRecord.
Работать с ним несколько сложнее, но мы не боимся трудностей :)

public class AmplitudeReader extends Thread {
 
  private AudioRecord audioRecord;
  private int bufflen;
  private static final int SAMPPERSEC = 48000;
 
  public AmplitudeReader() {
    android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
    int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_DEFAULT;
    int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
    bufflen = AudioRecord.getMinBufferSize(SAMPPERSEC, channelConfiguration, audioEncoding);
    audioRecord = new AudioRecord(android.media.MediaRecorder.AudioSource.MIC, SAMPPERSEC, channelConfiguration, audioEncoding, bufflen);
    audioRecord.startRecording();
  }
 
  private short getMax(short[] arr, int count) {
    short m = 0;
    for (int i = 0; i < count; i++) {
      short c = (short) Math.abs(arr[i]);
      if (m < c) {
        m = c;
      }
    }
    return m;
  }
 
  @Override
  public void run() {
    for(int i=0; i<11; i++) {
      short[] curr = new short[bufflen];
      int currread = audioRecord.read(curr, 0, bufflen);
      System.err.println("current amplitude is:" + getMax(curr, currread));
    }
    audioRecord.stop();
    audioRecord.release();
  }
}

В этом примере мы выводим в лог десять значений амплитуды, снятые "на лету" с каждого буфера чтения. Размер буфера система нам установит сама исходя из настроек. Обычно это 4Кb. Понятно, что получением амплитуды мы можем не ограничиться. Сигнал после считывания представлен у нас в массиве байтов (или short, смотря какой метод использовать для чтения). Совершая с элементами этого массива различные действия мы можем усиливать сигнал, подавлять постоянную составляющую, сглаживать и т.п.
И ещё одна особенность: для использования MediaRecorder нам понадобятся 2 разрешения: на чтение и на запись звука на карту: android.permission.RECORD_AUDIO и android.permission.WRITE_EXTERNAL_STORAGE, для использования AudioRecord - только первое из них.

6 комментариев:

  1. Не очень понятно, что есть AudioReader1 и когда выполняется код внутри него

    ОтветитьУдалить
  2. Спасибо за замечание. Поправил исходник. Теперь, надеюсь понятнее :)

    ОтветитьУдалить
  3. Не могли бы Вы дать рекомендации по портированию с С# на андроид. Написана программа для обработки речи, но как ее запустить в эклипсе?

    ОтветитьУдалить
  4. подвешивает приложение ..

    while (count < 10)

    почему такое вырожение, если переменная count у нас не меняется?

    если надо зациклить намертово то
    while (true){...}

    ОтветитьУдалить
  5. А можно пример простого плеера то есть функции для воспроизведения 1го аудиофайла!?

    ОтветитьУдалить
  6. неужели вы думаете, что максимальный (или минимальный) элемент массива, это и есть показатель амплитуды? Если да (а судя по исходникам, так и есть), то вынужден вас огорчить, это не так ))

    ОтветитьУдалить