Предисловие.

Эта серия уроков предназначена для того, чтобы научить вас использовать Java Sound API. Первый урок из этой серии назывался Java Sound, Введение.  Предыдущий урок был озаглавлен Java Sound, воспроизведение аудиофайлов с помощью Java.

Два типа аудиоданных.

Java Sound API поддерживает два разных типа аудиоданных:

  • Дискретизированные (Сэмплированные) аудиоданные
  • Данные цифрового интерфейса музыкального инструмента (MIDI)

Эти два типа аудиоданных очень разные. На данный момент я концентрируюсь на дискретизированных аудиоданных. Я отложу обсуждение MIDI на потом.

Совет по просмотру.

Возможно, вам будет полезно открыть еще одну копию этого урока в отдельном окне браузера. Это упростит вам перемещение вперед и назад между различными списками и цифрами, пока вы читаете о них.

Анонс.

На предыдущем уроке было показано, как написать программу, которую можно использовать для воспроизведения аудиофайлов, включая те, которые вы создаете с помощью программы Java, и те, которые вы приобрели из других источников.

В предыдущих уроках этой серии было показано, как:

  • Сохранять данные с микрофона в аудиофайлы любого типа по вашему выбору.
  • Захватывать данных микрофона в объект ByteArrayOutputStream и способы использования Sound API для воспроизведения ранее захваченных аудиоданных.
  • Определять микшеры, доступные в вашей системе, и как указать конкретный микшер для использования при получении аудиоданных с микрофона.
  • Узнать об использовании линий и микшеров в Java Sound API.

В этом уроке я научу вас, как использовать события, запускаемые аудиолиниями, для синхронизации аудиодействий с другими действиями.

Обсуждение и образец кода.

Синхронизация других видов деятельности со звуком.

Предположим, вам нужно воспроизвести музыку и вызвать запуск и остановку анимации синхронно с началом и концом музыки. Как бы вы синхронизировали эти два действия?

Вероятно, есть несколько способов выполнить эту задачу. Один из способов - использовать события, запускаемые звуковой линией, для синхронизации начала и конца анимации.

Что такое событие аудиолинии?

Аудиолинии запускают события, когда они открываются, запускаются, останавливаются или закрываются. Вы можете создавать экземпляры объектов-слушателей и регистрировать их в объекте Line, чтобы получать уведомления каждый раз, когда линия запускает событие.

Вы можете определить методы обработчика событий, принадлежащие объектам-слушателям, чтобы выполнять любое действие, которое вам нужно выполнять каждый раз, когда линия запускает событие. На этом уроке я предоставлю образец программы и объясню, как это сделать.

Пользовательский интерфейс.

При выполнении примера программы из этого урока на экране появляется графический интерфейс, показанный на рисунке 1. Как видите, этот графический интерфейс содержит следующие компоненты:

  • Кнопка захвата
  • Кнопка остановки
  • Кнопка воспроизведения

Рисунок 1 Графический интерфейс программы

Работа программы.

Программа демонстрирует захват и последующее воспроизведение аудиоданных, а также демонстрирует создание экземпляров, регистрацию и работу приемников событий линии. Слушатели событий отображают сообщения на экране при возникновении различных событий аудиолинии.

Входные данные с микрофона захватываются и сохраняются в объекте ByteArrayOutputStream, когда пользователь нажимает кнопку Capture.

Сбор данных останавливается, когда пользователь нажимает кнопку Stop .

Воспроизведение начинается, когда пользователь нажимает кнопку Playback .

Отображается вывод обработчика событий.

События линии запускаются каждый раз, когда пользователь нажимает одну из кнопок и вызывает открытие, запуск, остановку или закрытие линии. Зарегистрированные методы обработчика событий отображают на экране информацию о событии и линии каждый раз, когда возникает такое событие. Я приведу пример вывода экрана в соответствующие моменты обсуждения.

Обсудим по фрагментам.

Как обычно, я буду обсуждать эту программу по частям. Полный список программы показан в листинге 16 ближе к концу урока.

Обновленная версия обсуждаемой ранее программы.

Программа, которую я буду обсуждать в этом уроке, представляет собой обновленную версию программы, которую я обсуждал в следующих уроках:

Я очень кратко рассмотрю всю программу, чтобы установить контекст. Однако я сосредоточу свое подробное обсуждение на тех аспектах программы, которые были обновлены для поддержки обработки событий аудиолинии.

(Я настоятельно рекомендую вам вернуться к двум перечисленным выше урокам для подробного обсуждения тех частей программы, которые не связаны с обработкой событий.)

Программа под названием AudioEvents01.

Программа под названием AudioEvents01 демонстрирует использование программы Java для:

  • Захват аудиоданных с микрофона в объект ByteArrayOutputStream.
  • Регистрация объекта прослушивателя событий в линии, используемой для сбора данных.
  • Обработка событий каждый раз, когда линия захвата открывается, запускается, останавливается или закрывается.
  • Воспроизведения данных, хранящихся в объекте ByteArrayOutputStream.
  • Регистрация объекта прослушивателя событий в линии, используемой для воспроизведения захваченных данных.
  • Обработка событий каждый раз, когда линия воспроизведения открывается, запускается, останавливается или закрывается.

Поведение методов обработчика событий.

В этом примере программы поведение методов обработчика событий очень простое. Поведение предоставлено исключительно в целях иллюстрации. Напоминаем, что поведение методов обработчика событий может быть как простым, так и сложным, в зависимости от ваших потребностей. Важным моментом является то, что методы обработчика событий вызываются при запуске событий. Вы можете разработать эти методы, чтобы обеспечить любое поведение, подходящее для вашей ситуации.

Следует ли создавать новую ветку?

Как и во всех формах обработки событий в Java, если задача, которую должен выполнить ваш метод обработчика событий, потребует значительного количества времени для выполнения, вам, вероятно, следует создать новый поток для выполнения этой задачи и заставить ваш метод обработчика событий работать вернуться как можно скорее.

(Я не делал этого в этой программе из-за простоты и скорости поведения моих методов обработки событий.)

Управляющий класс с именем AudioEvents01.

Определение класса для управляющего класса начинается в листинге 1.

public class AudioEvents01 extends JFrame{

  boolean stopCapture = false;
  ByteArrayOutputStream byteArrayOutputStream;
  AudioFormat audioFormat;
  TargetDataLine targetDataLine;
  AudioInputStream audioInputStream;
  SourceDataLine sourceDataLine;

  public static void main(String args[]){
    new AudioEvents01();
  }//end main

Листинг 1

Код в листинге 1 включает объявление некоторых переменных экземпляра и метода main . Поведение метода main заключается в простом создании экземпляра нового объекта управляющего класса.

Конструктор.

Конструктор управляющего класса начинается в листинге 2.

  public AudioEvents01(){//конструктор
    final JButton captureBtn =
                          new JButton("Capture");
    final JButton stopBtn = new JButton("Stop");
    final JButton playBtn =
                         new JButton("Playback");

    captureBtn.setEnabled(true);
    stopBtn.setEnabled(false);
    playBtn.setEnabled(false);

Листинг 2

Код в листинге 2 создает объекты кнопок, показанные на рисунке 1, и устанавливает исходные свойства включения и отключения этих кнопок.

Обработчики событий действия.

Код в листинге 3 создает три объекта ActionListener и регистрирует каждый из этих объектов прослушивателя на одной из трех кнопок, показанных в графическом интерфейсе пользователя на рисунке 1.

    captureBtn.addActionListener(
      new ActionListener(){
        public void actionPerformed(
                                  ActionEvent e){
          captureBtn.setEnabled(false);
          stopBtn.setEnabled(true);
          playBtn.setEnabled(false);
          // Захват входных данных с микрофона,
          // пока не будет нажата кнопка
          // Стоп.
          captureAudio();
        }//конец actionPerformed
      }//конец ActionListener
    );//конец addActionListener()
    getContentPane().add(captureBtn);

    stopBtn.addActionListener(
      new ActionListener(){
        public void actionPerformed(
                                  ActionEvent e){
          captureBtn.setEnabled(true);
          stopBtn.setEnabled(false);
          playBtn.setEnabled(true);
          // Прекратите захват входных
          // данных с микрофона.
          stopCapture = true;
        }//конец actionPerformed
      }//конец ActionListener
    );//конец addActionListener()
    getContentPane().add(stopBtn);

    playBtn.addActionListener(
      new ActionListener(){
        public void actionPerformed(
                                  ActionEvent e){
          // Воспроизведите все данные, которые
          // были сохранены во время захвата.
          playAudio();
        }//конец actionPerformed
      }//конец ActionListener
    );//конец addActionListener()
    getContentPane().add(playBtn);

Листинг 3

Код, очень похожий на тот, что показан в листинге 3, подробно обсуждался в двух перечисленных ранее уроках. Поэтому я не буду здесь повторять это обсуждение.

Заполнение графического интерфейса.

Код в листинге 4 заботится о некоторых дополнительных деталях, необходимых для завершения графического интерфейса и его отображения.

    getContentPane().setLayout(new FlowLayout());
    setTitle("Copyright 2003, R.G.Baldwin");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setSize(250,70);
    setVisible(true);
  }//конец конструктора

Листинг 4

Код в листинге 4 сигнализирует об окончании конструктора.

Метод captureAudio.

Если вы вернетесь к листингу 3, вы увидите, что обработчик событий на кнопке Capture вызывает метод с именем captureAudio, чтобы вызвать фактическую операцию захвата данных. Метод с именем captureAudio захватывает аудиовход с микрофона и сохраняет его в объекте ByteArrayOutputStream.

Код метода с именем captureAudio начинается в листинге 5.

  private void captureAudio(){
    try{
      // Подготовьте все для захвата
      audioFormat = getAudioFormat();
      DataLine.Info dataLineInfo =
                          new DataLine.Info(
                            TargetDataLine.class,
                            audioFormat);
      targetDataLine =
             (TargetDataLine)AudioSystem.getLine(
                                   dataLineInfo);

Листинг 5

Пока ничего нового.

Пока еще нет ничего нового для обсуждения. Код в листинге 5 подробно обсуждался в двух перечисленных ранее уроках, поэтому я не буду повторять это обсуждение здесь.

Главное, что следует отметить в листинге 5, - это создание объекта типа Line и хранение ссылки на этот объект в переменной экземпляра типа TargetDataLine с именем targetDataLine.

Методы открытия, запуска, остановки и закрытия.

В предыдущих уроках подробно обсуждались методы объекта TargetDataLine с именем open start, stop и close. Однако вот некоторая информация от Sun, которую я не обсуждал на этих уроках.

Что касается метода open, Sun сообщает нам:

"Если эта операция завершается успешно, линия помечается как открытая, и слушателям линии отправляется событие OPEN."

Точно так же в отношении метода start Sun сообщает нам:

"Когда начинается захват или воспроизведение звука, генерируется событие START."

Как вы уже, наверное, догадались, Sun вот что говорит о методе stop.

"При остановке захвата или воспроизведения звука генерируется событие STOP."

Наконец, Sun говорит о методе close.

"Если эта операция завершается успешно, линия помечается как закрытая и событие CLOSE отправляется слушателям линии."

Способы регистрации событий.

Если вы знакомы с обработкой событий Java в целом и шаблонами проектирования JavaBeans в частности, вы, вероятно, уже предвидели, что объект TargetDataLine предоставляет следующие методы регистрации событий:

  • addLineListener(слушатель LineListener)
  • removeLineListener(слушатель LineListener)

Метод addLineListener.

Вот что Sun говорит о методе addLineListener.

"Добавляет слушателя к этой линии. При изменении статуса линии вызывается метод слушателя update() с объектом LineEvent, который описывает изменение."

Точно так же метод removeLineListener.

"Удаляет указанного слушателя из списка слушателей этой линии."

The LineListener interface.

Обратите внимание, что оба метода регистрации требуют входящего параметра типа LineListener. Иными словами, в каждом случае входящий параметр должен быть ссылкой на объект, созданный из класса, реализующего интерфейс LineListener.

Вот что говорит Sun об интерфейсе LineListener.

"Экземпляры классов, реализующих интерфейс LineListener, могут регистрироваться для получения событий при изменении статуса линии."

Интерфейс LineListener объявляет единственный метод с именем update, который получает входящий параметр типа LineEvent.

Метод обновления.

Каждый класс, реализующий интерфейс LineListener, должен предоставлять конкретное определение метода обновления. Вот что говорит Sun об этом методе.

"Сообщает слушателю, что состояние линии изменилось. Затем слушатель может вызвать методы LineEvent для получения информации о событии."

Другими словами, всякий раз, когда происходит событие LineEvent, объект Line уведомляет всех зарегистрированных слушателей, вызывая метод update для каждого зарегистрированного объекта слушателя, передавая ссылку на объект LineEvent в качестве параметра методу update. Объект LineEvent инкапсулирует информацию о событии.

Класс LineEvent.

Это подводит нас к сути вопроса, включающего запуск и обработку событий аудиолинии. Вот что говорит Sun о классе LineEvent.

"Класс LineEvent инкапсулирует информацию, которую линия отправляет своим слушателям всякий раз, когда линия открывается, закрывается, запускается или останавливается. Каждое из этих четырех изменений состояния представлено событием соответствующего типа. Слушатель получает событие как параметр своего метода обновления. Запрашивая событие, слушатель может узнать тип события, линия, ответственную за событие, и сколько данных линия обработала, когда событие произошло."

Методы LineEvent.

Начиная с Java SDK 1.4.1, объект класса LineEvent предоставляет следующие методы, которые обработчик событий может использовать для получения информации о событии:

  • getFramePosition - Возвращает позицию аудиоданных линии, когда произошло событие, выраженное в кадрах выборки.
  • getLine - Возвращает ссылку на объект Line, который вызвал событие.
  • getType - Возвращает тип события (открытие, начало, остановка или закрытие) как LineEvent.Type.
  • toString - Возвращает строковое представление события.

Регистрация прослушивателя линии в объекте TargetDataLine.

Наконец, мы увидим приведенное выше обсуждение в коде. Несколько загадочный код в листинге 6 создает экземпляр объекта анонимного слушателя из анонимного класса, реализующего интерфейс LineListener, и регистрирует этот объект слушателя в объекте TargetDataLine, который был создан в листинге 5.

      targetDataLine.addLineListener(
        new LineListener(){

          public void update(LineEvent e){
            System.out.println(
             "Event handler for TargetDataLine");
            System.out.println(
                   "Event type: " + e.getType());
            System.out.println("Line info: " +
                      e.getLine().getLineInfo());
            System.out.println();//пустая строка
          }//конец update

        }//конец LineListener
      );//конец addLineListener()

Листинг 6

Метод update.

Код в листинге 6 определяет метод update, который будет вызываться для зарегистрированного объекта прослушивателя каждый раз, когда происходит событие линии.

Код в методе update начинается с вывода на экран «Обработчика событий для TargetDataLine».

Затем он вызывает метод getType для получения и отображения типа события линии.

После этого он вызывает метод getLine, чтобы получить ссылку на объект Line, который вызвал событие. Он использует эту ссылку для вызова метода getLineInfo в линии и отображения информации о линии.

Пример вывода экрана.

На рисунке 2 показан экран, который появляется после нажатия кнопки Capture в моей системе. (Вывод информации о линии в вашей системе может отличаться.)

(Обратите внимание, что разрывы строк были вручную вставлены на рис. 2 для соответствия этому узкому формату. Полужирный шрифт также был добавлен вручную для акцента.)

Event handler for TargetDataLine
Event type: Open
Line info: interface TargetDataLine supporting
 64 audio formats

Event handler for TargetDataLine
Event type: Start
Line info: interface TargetDataLine supporting
 64 audio formats

фигура 2

Позже, когда мы исследуем код, который используется для захвата данных, вы увидите операторы, которые открывают и запускают линия, вызывая запуск событий OPEN и START, показанных на рисунке 2.

Создайте и запустите поток для захвата аудиоданных.

Код в листинге 7 создает экземпляр нового объекта потока и запускает его выполнение. Цель этого потока - выполнить фактический сбор данных. Метод run потока будет продолжать захватывать аудиоданные с микрофона до тех пор, пока пользователь не нажмет кнопку Stop.

      new CaptureThread().start();
    }catch (Exception e) {
      System.out.println(e);
      System.exit(0);
    }//конец catch
  }// конец метода captureAudio

Листинг 7

Когда поток запускается для захвата данных, метод captureAudio возвращает управление обработчику событий на кнопке Capture, который вскоре после этого завершается. Это освобождает поток обработки событий для обработки события при нажатии кнопки Stop.

Класс CaptureThread.

На этом этапе я собираюсь обсудить внутренний класс, объект которого используется для захвата данных с микрофона. Еще раз, этот класс очень похож на тот, который подробно обсуждался в двух перечисленных ранее уроках. Таким образом, это обсуждение будет очень кратким, с акцентом только на те аспекты кода в классе, которые важны для обработки событий звуковой линии.

Начало класса CaptureThread и его метод run показаны в листинге 8.

class CaptureThread extends Thread{
  // Буфер временного хранения произвольного размера
  byte tempBuffer[] = new byte[10000];
  public void run(){

    byteArrayOutputStream =
                     new ByteArrayOutputStream();
    stopCapture = false;
    try{

Листинг 8

Если вы изучили два перечисленных ранее урока, в коде листинга 8 нет ничего нового или интересного, поэтому я не буду его обсуждать дальше. Я показываю это здесь просто для того, чтобы установить контекст для последующего обсуждения.

Откройте и запустите линию.

Код в листинге 9 не нов, но очень интересен в контексте этого урока.

      targetDataLine.open(audioFormat);
      targetDataLine.start();

Листинг 9

Код в листинге 9 сначала вызывает метод open объекта TargetDataLine. После этого он вызывает метод start объекта TargetDataLine.

Я подробно обсуждал эти два метода на предыдущих уроках. Кроме того, я уже говорил вам ранее в этом уроке, что вызов этих методов вызывает запуск событий OPEN и START. Следовательно, именно вызов этих двух методов вызывает вызов метода update, зарегистрированного в объекте TargetDataLine. Это, в свою очередь, дает экран, показанный на рисунке 2.

Поведение метода обновления.

И снова поведение, которое я разработал в своей версии метода update, очень простое. Он просто отображает некоторую информацию о событии и линии, которая его запустила.

Однако поведение, которое вы проектируете в своей версии метода update, может быть как простым, так и сложным, в зависимости от ваших потребностей.

Цикл, пока переменная stopCapture не станет истинной.

Продолжая использовать метод run класса CaptureThread, код в листинге 10 повторяется до тех пор, пока значение переменной с именем stopCapture не изменится с false на true. (Это происходит, когда пользователь нажимает кнопку the Stop)

      while(!stopCapture){
        // Считайте данные из внутреннего буфера
        // линии данных.
        int cnt = targetDataLine.read(
                              tempBuffer,
                              0,
                              tempBuffer.length);
        if(cnt > 0){
          // Сохранить данные в объекте потока вывода.
          byteArrayOutputStream.write(
                             tempBuffer, 0, cnt);
        }//конец if
      }//конец while
      byteArrayOutputStream.close();

Листинг 10

В течение этого периода код в листинге 10 продолжает захватывать аудиоданные с микрофона и сохранять эти данные в объекте ByteArrayOutputStream. (Я подробно обсуждал работу этого цикла while в предыдущих уроках.)

Остановить и закрыть объект TargetDataLine.

Когда цикл while завершается, два оператора в листинге 11 вызывают соответственно методы stop и close для объекта TargetDataLine.

      targetDataLine.stop();
      targetDataLine.close();

Листинг 11

Пожарные события STOP и CLOSE.

Как объяснялось ранее, это заставляет объект TargetDataLine запускать события STOP и CLOSE соответственно. Это, в свою очередь, приводит к тому, что метод update в объекте прослушивателя вызывается дважды подряд, создавая вывод на экран, показанный на рисунке 3.

Event handler for TargetDataLine
Event type: Stop
Line info: interface TargetDataLine supporting
 64 audio formats

Event handler for TargetDataLine
Event type: Close
Line info: interface TargetDataLine supporting
 64 audio formats

Рисунок 3

Конец метода run .

За исключением обязательного блока catch, код в листинге 11 сигнализирует об окончании метода run класса CaptureThread. Этот код также сигнализирует об окончании класса.

Наконец, код в листинге 11 сигнализирует об окончании обсуждения части этой программы, связанной с захватом данных.

Часть программы для воспроизведения.

Что касается обработки событий звуковой линии, существует очень небольшая разница между частью воспроизведения этой программы и частью захвата данных, описанной выше.

Я подробно обсудил аспекты обработки событий части программы сбора данных в предыдущих параграфах. Я подробно обсуждал другие аспекты воспроизведения программы в уроках, перечисленных ранее. Поэтому в оставшейся части этого урока я лишь кратко рассмотрю аспекты обработки событий в части воспроизведения программы. Как упоминалось ранее, вы можете просмотреть полный список программы в листинге 16 ближе к концу урока.

Метод playAudio.

Эта программа использует метод playAudio для воспроизведения данных, которые были захвачены и сохранены в объекте ByteArrayOutputStream.

Получите объект SourceDataLine.

Часть программы захвата использует объект TargetDataLine для захвата данных микрофона в реальном времени.

Точно так же часть воспроизведения использует объект SourceDataLine для доставки аудиоданных в динамики в реальном времени.

Большая часть исходного кода метода playAudio была удалена из листинга 12 для краткости. Первый оператор в листинге 12 получает ссылку на объект SourceDataLine и назначает ее ссылочной переменной с именем sourceDataLine.

// Код удален из метода playAudio для краткости

      sourceDataLine =
             (SourceDataLine)AudioSystem.getLine(
                                   dataLineInfo);


      // Зарегистрировать прослушиватель линии
      // в объекте SourceDataLine
      sourceDataLine.addLineListener(
        new LineListener(){
          public void update(LineEvent e){
            System.out.println(
             "Event handler for SourceDataLine");
            System.out.println(
                   "Event type: " + e.getType());
            System.out.println("Line info: "
                    + e.getLine().getLineInfo());
            System.out.println();//blank line
          }//конец update
        }//конец LineListener
      );//конец addLineListener()

      // Создать поток для воспроизведения данных
      // и запустить его.
      new PlayThread().start();

Листинг 12

Регистрация прослушивателя линии в объекте SourceDataLine.

После этого код в листинге 12 создает экземпляр объекта LineListener и регистрирует его в объекте SourceDataLine. Определение и поведение  слушателя линии, созданного в листинге 12, по существу такое же, как показано в листинге 6 ранее.

Запустить поток воспроизведения.

Затем код в листинге 12 создает экземпляр объекта потока воспроизведения и запускает его. Метод run потока воспроизведения будет продолжать работать до тех пор, пока не будут исчерпаны аудиоданные, ранее сохраненные в объекте ByteArrayOutputStream.

Класс PlayThread.

Поток воспроизведения создается из класса PlayThread, который начинается в листинге 13.

class PlayThread extends Thread{
  byte tempBuffer[] = new byte[10000];

  public void run(){
    try{
      int cnt;

      sourceDataLine.open(audioFormat);
      sourceDataLine.start();

Листинг 13

Код в листинге 13 вызывает методы open и start объекта SourceDataLine, вызывая запуск событий OPEN и START.

Вывод на экран.

Эти события обрабатываются методом обработчика событий update, определенным в листинге 12. Это приводит к тому, что выходные данные, показанные на рисунке 4, появляются на экране.

Event handler for SourceDataLine
Event type: Open
Line info: interface SourceDataLine supporting
 8 audio formats

Event handler for SourceDataLine
Event type: Start
Line info: interface SourceDataLine supporting
 8 audio formats

Рисунок 4

(Обратите внимание, что информация о линии для SourceDataLine на рисунке 4 немного отличается от аналогичной информации о TargetDataLine на рисунке 2. Информация для вашей системы может отличаться от той, которая показана для моей системы.)

Цикл воспроизведения.

Как и в случае части захвата данных этой программы, часть воспроизведения использует цикл while для передачи данных из объекта ByteArrayOutputStream во внутренний буфер объекта SourceDataLine.

      while((cnt = audioInputStream.read(
                              tempBuffer,
                              0,
                              tempBuffer.length))
                                          != -1){
        if(cnt > 0){
          // Записать данные во внутренний буфер
          // линии данных, откуда они будут
          // доставлены в динамик.
          sourceDataLine.write(
                             tempBuffer, 0, cnt);
        }//конец if
      }//конец while

Листинг 14

Объект SourceDataLine доставляет эти аудиоданные в реальном времени на динамики компьютера.

Передача данных от объекта ByteArrayOutputStream к объекту SourceDataLine продолжается до тех пор, пока данные, хранящиеся в объекте ByteArrayOutputStream, не будут исчерпаны, после чего метод read  в листинге 14 вернет -1.

Метод drain.

Цикл while завершается, когда данные в объекте ByteArrayOutputStream исчерпаны. Однако в этот момент программа не должна завершаться. По всей вероятности, во внутреннем буфере объекта SourceDataLine все еще остаются данные, которые необходимо отправить динамикам в реальном времени. Это цель метода drain, который вызывается в листинге 15.

      sourceDataLine.drain();
      sourceDataLine.close();

Листинг 15

Метод drain блокируется до тех пор, пока внутренний буфер объекта SourceDataLine не станет пустым, после чего он возвращается.

Метод stop не вызывается.

Обратите внимание, что в этом случае я явно не вызывал метод stop, но событие STOP все равно было запущено. По-видимому, вызов метода close в линии, которая была осушена, вызывает как событие STOP, так и событие CLOSE. Вывод на экран, созданный кодом из листинга 15, показан на рисунке 5.

Event handler for SourceDataLine
Event type: Stop
Line info: interface SourceDataLine supporting
 8 audio formats

Event handler for SourceDataLine
Event type: Close
Line info: interface SourceDataLine supporting
 8 audio formats

Рисунок 5

Метод getAudioFormat.

Есть еще один метод, который я не обсуждал в этом уроке. Метод с именем getAudioFormat идентичен методу с таким же именем, который использовался в более ранней версии программы. Я подробно объяснил поведение этого метода в уроках, перечисленных ранее. Поэтому я не буду обсуждать этот метод в этом уроке. Вы можете просмотреть его в листинге 16 ближе к концу урока.

Запуск программы.

На этом этапе вы можете найти полезным скомпилировать и запустить программу, показанную в листинге 16, ближе к концу урока.

Захват данных.

Запустите программу и нажмите кнопку Capture. Поговорите в микрофон в течение короткого периода времени, а затем нажмите кнопку Stop, чтобы прекратить сбор данных. Пока вы это делаете, наблюдайте за выводом на экране командной строки.

(Будьте осторожны и не пытайтесь захватить слишком много аудиоданных. Данные сохраняются в памяти, и если вы попытаетесь захватить слишком много данных, у вас может закончиться память.)

Воспроизведение захваченных данных.

Нажмите кнопку Playback, чтобы записанные данные воспроизводились через динамики вашего компьютера. Снова наблюдайте за выводом на экране командной строки, пока вы это делаете.

Контроль громкости.

Если вы ничего не слышите во время воспроизведения, возможно, вам нужно увеличить громкость динамика. В моем портативном компьютере есть ручная регулировка громкости в дополнение к программным регуляторам громкости, которые доступны через значок динамика на панели задач.

В случае ошибки выполнения.

Если вы получаете ошибку времени выполнения при попытке захвата аудиоданных, см. Комментарии в методе getAudioFormat в листинге 16. Возможно, вам придется попробовать использовать другой аудиоформат. Я получил отзывы от некоторых читателей, которые сказали мне, что формат, который я использовал в этой программе, работает не на всех системах.

Резюме.

На уроке я представил и объяснил программу, демонстрирующую использование событий аудиолинии. Вы можете использовать этот подход для регистрации слушателей и получения уведомлений при открытии, запуске, остановке или закрытии аудиолинии. Получив уведомление, вы можете узнать тип события, линию, отвечающую за событие, информацию об этой линии и объем данных, обработанных линией, когда произошло событие.

Полный листинг программы.

Полный текст кода программы показан в листинге 16.

/* File AudioEvents01.java
Основная цель этой программы -
продемонстрировать обработку событий аудиолинии.

Эта программа демонстрирует захват и последующее
воспроизведение аудиоданных, а также демонстрирует
создание и регистрацию слушателей событий линий.
Слушатели событий отображают сообщения на экране
при возникновении различных событий аудиолинии.

На экране появится графический интерфейс
со следующими кнопками:
Capture
Stop
Playback

Входные данные с микрофона захватываются и
сохраняются в объекте ByteArrayOutputStream,
когда пользователь нажимает кнопку Capture.

Сбор данных останавливается, когда пользователь 
нажимает кнопку Stop.

Воспроизведение начинается, когда пользователь 
нажимает  кнопку Playback.

Ниже приведен экран, выводимый после нажатия
кнопки Capture. Обратите внимание, что в этот
Обратите внимание, что разрывы строк были вставлены
вручную в этот и другие выходные материалы,показанные ниже,
чтобы материал соответствовал этому узкому формату.

Event handler for TargetDataLine
Event type: Open
Line info: interface TargetDataLine supporting
 64 audio formats

Event handler for TargetDataLine
Event type: Start
Line info: interface TargetDataLine supporting
 64 audio formats



Ниже приведен вывод экрана после нажатия
на кнопку Stop.

Event handler for TargetDataLine
Event type: Stop
Line info: interface TargetDataLine supporting
 64 audio formats

Event handler for TargetDataLine
Event type: Close
Line info: interface TargetDataLine supporting
 64 audio formats



Ниже приведен вывод экрана после нажатия
на кнопку Playback.

Event handler for SourceDataLine
Event type: Open
Line info: interface SourceDataLine supporting
 8 audio formats

Event handler for SourceDataLine
Event type: Start
Line info: interface SourceDataLine supporting
 8 audio formats

Event handler for SourceDataLine
Event type: Stop
Line info: interface SourceDataLine supporting
 8 audio formats

Event handler for SourceDataLine
Event type: Close
Line info: interface SourceDataLine supporting
 8 audio formats

Tested using SDK 1.4.0 under Win2000
************************************************/

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.sound.sampled.*;

public class AudioEvents01 extends JFrame{

  boolean stopCapture = false;
  ByteArrayOutputStream byteArrayOutputStream;
  AudioFormat audioFormat;
  TargetDataLine targetDataLine;
  AudioInputStream audioInputStream;
  SourceDataLine sourceDataLine;

  public static void main(String args[]){
    new AudioEvents01();
  }//конец main

  public AudioEvents01(){//конструктор
    final JButton captureBtn =
                          new JButton("Capture");
    final JButton stopBtn = new JButton("Stop");
    final JButton playBtn =
                         new JButton("Playback");

    captureBtn.setEnabled(true);
    stopBtn.setEnabled(false);
    playBtn.setEnabled(false);

    // Регистрация анонимных слушателей
    captureBtn.addActionListener(
      new ActionListener(){
        public void actionPerformed(
                                  ActionEvent e){
          captureBtn.setEnabled(false);
          stopBtn.setEnabled(true);
          playBtn.setEnabled(false);
          // Захват входных данных
          // с микрофона до тех пор, пока
          // не будет нажата кнопка стоп.
          captureAudio();
        }//конец actionPerformed
      }//конец ActionListener
    );//конец addActionListener()
    getContentPane().add(captureBtn);

    stopBtn.addActionListener(
      new ActionListener(){
        public void actionPerformed(
                                  ActionEvent e){
          captureBtn.setEnabled(true);
          stopBtn.setEnabled(false);
          playBtn.setEnabled(true);
          // Прекратить захват входных
          // данных с микрофона.
          stopCapture = true;
        }//конец actionPerformed
      }//конец ActionListener
    );//конец addActionListener()
    getContentPane().add(stopBtn);

    playBtn.addActionListener(
      new ActionListener(){
        public void actionPerformed(
                                  ActionEvent e){
          // Воспроизведи все данные,
          // сохраненные во время захвата.
          playAudio();
        }//конец actionPerformed
      }//конец ActionListener
    );//конец addActionListener()
    getContentPane().add(playBtn);

    getContentPane().setLayout(new FlowLayout());
    setTitle("Copyright 2003, R.G.Baldwin");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setSize(250,70);
    setVisible(true);
  }//конец конструктора

  // Этот метод захватывает аудиовход
  // с микрофона и сохраняет его
  // в объекте ByteArrayOutputStream.
  private void captureAudio(){
    try{
      // Подготовить все для захвата
      audioFormat = getAudioFormat();
      DataLine.Info dataLineInfo =
                          new DataLine.Info(
                            TargetDataLine.class,
                            audioFormat);
      targetDataLine =
             (TargetDataLine)AudioSystem.getLine(
                                   dataLineInfo);

      // Зарегистрировать прослушиватель
      // линии в объекте TargetDataLine
      targetDataLine.addLineListener(
        new LineListener(){
          public void update(LineEvent e){
            System.out.println(
             "Event handler for TargetDataLine");
            System.out.println(
                   "Event type: " + e.getType());
            System.out.println("Line info: " +
                      e.getLine().getLineInfo());
            System.out.println();// пустая строка
          }//конец update
        }//конец LineListener
      );//конец addLineListener()

      // Создать поток, чтобы захватить данные микрофона
      // и запустить его. Он будет работать,
      // пока не будет нажата
      // кнопка Stop.
      new CaptureThread().start();
    }catch (Exception e) {
      System.out.println(e);
      System.exit(0);
    }//конец catch
  }//конец метода captureAudio

  // Этот метод воспроизводит аудиоданные,
  // сохраненные в
  // ByteArrayOutputStream
  private void playAudio() {
    try{
      // Подготовить все для воспроизведения.
      // Получить ранее сохраненные данные
      // в объект байтового массива.
      byte audioData[] = byteArrayOutputStream.
                                   toByteArray();
      // Получить входной поток в массиве байтов,
      // содержащий данные
      InputStream byteArrayInputStream =
                        new ByteArrayInputStream(
                                      audioData);
      AudioFormat audioFormat = getAudioFormat();
      audioInputStream =
                new AudioInputStream(
                  byteArrayInputStream,
                  audioFormat,
                  audioData.length/audioFormat.
                    getFrameSize());

      DataLine.Info dataLineInfo =
                          new DataLine.Info(
                            SourceDataLine.class,
                              audioFormat);

      sourceDataLine =
             (SourceDataLine)AudioSystem.getLine(
                                   dataLineInfo);


      // Зарегистрировать прослушиватель линии
      // в объекте SourceDataLine
      sourceDataLine.addLineListener(
        new LineListener(){
          public void update(LineEvent e){
            System.out.println(
             "Event handler for SourceDataLine");
            System.out.println(
                   "Event type: " + e.getType());
            System.out.println("Line info: "
                    + e.getLine().getLineInfo());
            System.out.println();//blank line
          }//конец update
        }//конец LineListener
      );//конец addLineListener()

      // Создать поток для воспроизведения данных и
      // запустить его. Он будет работать до тех пор,
      // пока не будут воспроизведены все данные,
      // после чего он автоматически остановит линию
      // и вызовет событие Stop.
      new PlayThread().start();
    }catch (Exception e) {
      System.out.println(e);
      System.exit(0);
    }//конец catch
  }//конец playAudio

  // Этот метод создает и возвращает
  // объект AudioFormat для заданного набора параметров
  // формата. Если эти параметры вам не подходят, попробуйте
  // некоторые из других допустимых значений параметров,
  // которые показаны в комментариях
  // после объявлений.
  private AudioFormat getAudioFormat(){
    float sampleRate = 8000.0F;
    //8000,11025,16000,22050,44100
    int sampleSizeInBits = 16;
    //8,16
    int channels = 1;
    //1,2
    boolean signed = true;
    //true,false
    boolean bigEndian = false;
    //true,false
    return new AudioFormat(sampleRate,
                           sampleSizeInBits,
                           channels,
                           signed,
                           bigEndian);
  }//конец getAudioFormat
//=============================================//

// Внутренний класс для захвата данных с микрофона
class CaptureThread extends Thread{
  // Буфер временного хранения произвольного размера
  byte tempBuffer[] = new byte[10000];
  public void run(){

    byteArrayOutputStream =
                     new ByteArrayOutputStream();
    stopCapture = false;
    try{
      targetDataLine.open(audioFormat);
      targetDataLine.start();

      // Цикл, пока stopCapture не будет установлен другим
      // потоком, который обслуживает кнопку Stop.
      while(!stopCapture){
        // Считайте данные из внутреннего буфера
        // линии данных.
        int cnt = targetDataLine.read(
                              tempBuffer,
                              0,
                              tempBuffer.length);
        if(cnt > 0){
          // Сохраните данные в объекте потока вывода.
          byteArrayOutputStream.write(
                             tempBuffer, 0, cnt);
        }//конец if
      }//конец while
      byteArrayOutputStream.close();

      targetDataLine.stop();
      targetDataLine.close();

    }catch (Exception e) {
      System.out.println(e);
      System.exit(0);
    }//конец catch
  }//конец run
}// конец внутреннего класса CaptureThread
//=============================================//

// Внутренний класс для воспроизведения
// сохраненных данных.
class PlayThread extends Thread{
  byte tempBuffer[] = new byte[10000];

  public void run(){
    try{
      int cnt;

      sourceDataLine.open(audioFormat);
      sourceDataLine.start();

      //Loop until the input read method returns
      // -1 for empty stream.
      while((cnt = audioInputStream.read(
                              tempBuffer,
                              0,
                              tempBuffer.length))
                                          != -1){
        if(cnt > 0){
          // Записать данные во внутренний буфер
          // линии данных, откуда они будут
          // доставлены в динамик.
          sourceDataLine.write(
                             tempBuffer, 0, cnt);
        }//конец if
      }//конец while
      // Заблокировать и дождаться, пока внутренний буфер
      // линии данных станет пустым. Когда он 
      // станет пустым, он запустит событие
      // Stop и вернется.
      sourceDataLine.drain();
      sourceDataLine.close();
    }catch (Exception e) {
      System.out.println(e);
      System.exit(0);
    }//конец catch
  }//конец run
}//конец внутреннего класса PlayThread
//=============================================//

}//конец внешнего класса AudioEvents01.java

Листинг 16

 

Предыдущая Следующая