Предисловие

Эта серия уроков предназначена для того, чтобы научить вас использовать Java Sound API. Первый урок из этой серии назывался «Java Sound API, Введение..». Предыдущий урок был озаглавлен «Java Sound API, Начало работы, Часть 2, Захват с помощью выбранного микшера.».

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

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

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

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

Совет для просмотра.

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

Анонс.

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

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

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

Менее сложная программа.

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

Что такое TargetDataLine?

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

(Объект обеспечивает вывод из микшера, а не из программы. Фактически, он часто служит входом в программу.)

Объект этого типа доставляет аудиоданные из микшера, служа входными данными для других частей программы. Эта концепция подробно обсуждается в уроке "Java Sound API, Начало работы, Часть 2, Захват с помощью выбранного микшера.".

Ввод аудиоданных в программу.

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

(Пример программы в этом уроке считывает аудиоданные из объекта TargetDataLine и записывает их в выходной файл аудио.)

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

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

  • Capture
  • Stop

Figure 1 Program GUI

Кроме того, графический интерфейс содержит пять переключателей, помеченных:

  • AIFC
  • AIFF
  • AU
  • SND
  • WAVE

Это пять типов форматов аудиофайлов, поддерживаемых Java SDK версии 1.4.1. Я расскажу больше об этих типах файлов позже, когда буду обсуждать код.

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

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

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

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

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

Класс с именем AudioRecorder02.

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

public class AudioRecorder02 extends JFrame{

  AudioFormat audioFormat;
  TargetDataLine targetDataLine;

Листинг 1

Этот класс объявляет несколько переменных экземпляра, которые используются позже в программе. Две переменные экземпляра, показанные в листинге 1, используются позже для хранения ссылок на объекты AudioFormat и TargetDataLine. Я расскажу об этих объектах более подробно, когда мы перейдем к той точке программы, где они используются.

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

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

  final JButton captureBtn =
                          new JButton("Capture");
  final JButton stopBtn = new JButton("Stop");

Листинг 2.

Радиокнопки.

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

Обычное ожидание пользователя от поведения переключателей состоит в том, что они будут логически сгруппированы во взаимоисключающие группы (в любой момент времени можно выбрать только одну кнопку в группе). Это взаимоисключающее поведение достигается в Java Swing путем добавления объектов JRadioButton к объекту ButtonGroup.

(Несколько иной подход используется для группировки переключателей в Java AWT.)

Не физическая группа.

Однако обратите внимание, что добавление кнопок к объекту ButtonGroup не создает физической группировки. Добавление переключателей к объекту ButtonGroup просто создает взаимоисключающую логическую группировку.

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

Переменные экземпляра в листинге 3 содержат ссылки на:

  • Один объект JPanel
  • Один объект ButtonGroup
  • Пять объектов JRadioButton
  final JPanel btnPanel = new JPanel();

  final ButtonGroup btnGroup = new ButtonGroup();

  final JRadioButton aifcBtn =
                        new JRadioButton("AIFC");
  final JRadioButton aiffBtn =
                        new JRadioButton("AIFF");
  final JRadioButton auBtn =//selected at startup
                     new JRadioButton("AU",true);
  final JRadioButton sndBtn =
                         new JRadioButton("SND");
  final JRadioButton waveBtn =
                        new JRadioButton("WAVE");

Листинг 3

Создание радиокнопок.

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

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

Основной метод.

Метод main этого Java-приложения, показанный в листинге 4, чрезвычайно прост.

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

Листинг 4

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

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

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

  public AudioRecorder02(){//constructor
    captureBtn.setEnabled(true);
    stopBtn.setEnabled(false);

Листинг 5

Когда программа запускается в первый раз, кнопка Capture активна, а кнопка Stop отключена, как показано на рисунке 1. Как видно из листинга 5, это достигается путем инициализации свойства enabled на каждой из двух кнопок значениями true и false соответственно.

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

Анонимный слушатель из анонимного внутреннего класса.

Возможно, вы знакомы или не знакомы с довольно загадочным стилем программирования, показанным в листинге 6. Код в листинге 6 создает экземпляр объекта ActionListener и регистрирует его на кнопке Capture.

    captureBtn.addActionListener(
      new ActionListener(){
        public void actionPerformed(
                                  ActionEvent e){
          captureBtn.setEnabled(false);
          stopBtn.setEnabled(true);
          //Capture input data from the
          // microphone until the Stop button is
          // clicked.
          captureAudio();
        }//end actionPerformed
      }//end ActionListener
    );//end addActionListener()

Листинг 6

Если вы уже знакомы с этим стилем программирования, просто обратите внимание, что метод actionPerformed, который вызывается, когда пользователь нажимает кнопку Capture,

  • Переключает включенные свойства двух кнопок.
  • Вызывает метод captureAudio, чтобы начать процесс захвата звука.

Загадочный стиль программирования.

Если вы не знакомы с этим стилем программирования, подумайте об этом так. Код в листинге 6 создает экземпляр объекта из безымянного класса (который реализует интерфейс ActionListener) и передает ссылку на этот объект методу addActionListener кнопки Capture, чтобы зарегистрировать объект в качестве прослушивателя кнопки. Каждый раз, когда пользователь нажимает кнопку Capture, вызывается метод actionPerformed, принадлежащий объекту прослушивателя, который ведет себя, как описано выше.

Еще один анонимный слушатель.

Код в листинге 7 использует тот же загадочный синтаксис для создания и регистрации объекта ActionListener на кнопке Stop.

    stopBtn.addActionListener(
      new ActionListener(){
        public void actionPerformed(
                                  ActionEvent e){
          captureBtn.setEnabled(true);
          stopBtn.setEnabled(false);
          //Terminate the capturing of input data
          // from the microphone.
          targetDataLine.stop();
          targetDataLine.close();
        }//end actionPerformed
      }//end ActionListener
    );//end addActionListener()

Листинг 7

Поведение метода обработчика событий actionPerformed в этом случае заключается в остановке и закрытии объекта TargetDataLine.

(Напомним, что объект TargetDataLine - это объект, который получает данные с микрофона и передает их программе. Мы увидим, как это будет сделано позже.)

Метод остановки.

Вот что Sun говорит о вызове метода stop для объекта TargetDataLine.

"Останавливает линию. Остановленная линия должна прекратить операции ввода-вывода. Однако, если линия открыта и работает, она должна сохранять ресурсы, необходимые для возобновления работы. Остановленная линия должна сохранять любые аудиоданные в своем буфере, а не отбрасывать их, чтобы после возобновления ввод-вывод мог продолжаться с того места, где он был остановлен, если это возможно. ... При желании сохраненные данные можно отбросить, вызвав метод flush."

Описание Sun не требует пояснений.

Стоит отметить, что это вызов метода, который заставляет метод write вывода (который будет обсуждаться позже) закрывает выходной файл.

Метод close.

Вот что Sun должна сказать о вызове метода close для объекта TargetDataLine.

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

Это также кажется довольно очевидным. Я расскажу больше о влиянии вызова методов stop и close позже, когда буду обсуждать метод write, используемый для записи данных в выходной аудиофайл.

Поместите кнопки в JFrame.

Два оператора в листинге 8 приводят к тому, что кнопка Capture и кнопка Stop помещаются в объект JFrame. Это простой код построения графического интерфейса.

    getContentPane().add(captureBtn);
    getContentPane().add(stopBtn);

Листинг 8

Включение переключателей во взаимоисключающую группу.

Код в листинге 9 заставляет пять переключателей быть включены во взаимоисключающую логическую группу.

    btnGroup.add(aifcBtn);
    btnGroup.add(aiffBtn);
    btnGroup.add(auBtn);
    btnGroup.add(sndBtn);
    btnGroup.add(waveBtn);

Листинг 9

Класс ButtonGroup.

Переменная с именем btnGroup содержит ссылку на объект класса ButtonGroup. Вот часть того, что Sun говорит о классе ButtonGroup в целом.

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

Изначально все кнопки в группе не выбраны. После выбора любой кнопки в группе всегда выбирается одна кнопка."

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

Использование метода add, показанного в листинге 9, приводит к включению каждой кнопки в группу.

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

Добавление переключателей в JPanel.

Физическая группировка переключателей осуществляется с помощью кода в листинге 10, который добавляет переключатели к объекту контейнера JPanel.

    btnPanel.add(aifcBtn);
    btnPanel.add(aiffBtn);
    btnPanel.add(auBtn);
    btnPanel.add(sndBtn);
    btnPanel.add(waveBtn);

Листинг 10

Менеджером компоновки по умолчанию для объекта JPanel является FlowLayout. Это приводит к тому, что переключатели появляются на панели в ряд слева направо, как показано на рисунке 1. Поскольку панель прозрачна и не имеет видимой границы, на рисунке 1 видны только кнопки. невидимый.

Добавьте JPanel в JFrame.

Код в листинге 11 заставляет панель, содержащую переключатели, помещаться в объект графического интерфейса JFrame, как показано на рисунке 1.

    getContentPane().add(btnPanel);

Листинг 11

Завершите графический интерфейс и сделайте его видимым.

Код в листинге 12 заботится о некоторых оставшихся беспорядках, касающихся графического интерфейса.

    getContentPane().setLayout(new FlowLayout());
    setTitle("Copyright 2003, R.G.Baldwin");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setSize(300,120);
    setVisible(true);
  }//end constructor

Листинг 12

Код в листинге 12 выполняет следующие задачи:

  • Установите для менеджера компоновки объекта GUI JFrame значение FlowLayout. В результате компоненты будут добавлены в контейнер слева направо, сверху вниз. (Помните, что JPanel сам по себе является компонентом, содержащим переключатели, поэтому панель отображается как отдельный компонент в макете.)
  • Задайте заголовок для объекта GUI JFrame.
  • Заставляет кнопку с X в правом верхнем углу объекта JFrame завершать программу при ее нажатии.
  • Установите размер объекта JFrame равным 300 на 120 пикселей.
  • Сделайте все это видимым на экране.

Код в листинге 12 также завершает конструктор.

Захват аудиоданных с микрофона.

Напомним, что обработчик событий на кнопке Capture вызывает метод с именем captureAudio, чтобы запустить процесс сбора данных. Начало метода captureAudio показано в листинге 13.

  private void captureAudio(){
    try{
      audioFormat = getAudioFormat();

Листинг 13

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

Установление формата аудиоданных.

Первым шагом в процессе захвата является определение формата захваченных аудиоданных.

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

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

(Я планирую опубликовать следующий урок, в котором подробно рассматриваются аудиоформаты и форматы файлов.)

Метод getAudioFormat.

Код в листинге 13 вызывает метод getAudioFormat для установки аудиоформата. Я собираюсь на мгновение отложить обсуждение метода captureAudio и обсудить метод getAudioFormat. Я вернусь к обсуждению метода captureAudio после того, как объясню метод getAudioFormat.

Весь метод getAudioFormat показан в листинге 14.

  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);
  }//end getAudioFormat

Листинг 14

Метод getAudioFormat создает и возвращает объект AudioFormat для заданного набора параметров формата.

Может не сработать.

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

Объект AudioFormat.

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

Как видите, код в листинге 14 создает и возвращает объект класса AudioFormat. Конструктор, используемый в листинге 14 для создания экземпляра объекта, требует следующих параметров:

  • sampleRate - Количество выборок, которые будут собираться каждую секунду для каждого канала аудиоданных.
  • sampleSizeInBits - Число битов, которые будут использоваться для описания значения каждого аудиосэмпла.
  • channels - Два канала для стерео и один канал для моно.
  • signed - Состоит ли описание каждого аудиосэмпла как положительных, так и отрицательных значений или только положительных значений.
  • bigEndian - Указывает, хранятся ли аудиоданные в прямом или обратном порядке..

Мой формат.

Как вы можете видеть в листинге 14, моя версия программ делает выборку одного канала 8000 раз в секунду, выделяя шестнадцать бит для каждой выборки, используя числовые значения со знаком, а не формат BigEndian. (Как упоминалось ранее, вам может потребоваться использовать другой формат в вашей системе.)

Кодировка данных по умолчанию - linear PCM.

Есть несколько способов кодирования двоичных аудиоданных в доступные биты. Самый простой способ известен как линейный PCM. По умолчанию конструктор, который я использовал, создает объект AudioFormat с линейной кодировкой PCM и параметрами, перечисленными выше (я расскажу больше о линейном кодировании PCM и других схемах кодирования в будущих уроках).

Объект DataLine.Info.

Теперь, когда у нас установлен аудиоформат, давайте вернемся к обсуждению метода captureAudio, показанного в листинге 15.

//Продолжаем использовать метод captureAudio

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

Листинг 15

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

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

Объект класса TargetDataLine.

The first parameter required by the constructor is a Class object.  In general, a Class object represents the type of an object. In this case, the parameter represents the type of an object instantiated from a class that implements the TargetDataLine interface.

According to Sun,

"A target data line is a type of DataLine from which audio data can be read. The most common example is a data line that gets its data from an audio capture device. (The device is implemented as a mixer that writes to the target data line.)"

As you can see, TargetDataLine matches our needs exactly.

The audio format parameter

The second parameter required by the constructor in Listing 15 is a specification of the required audio format.  That specification is achieved by passing the AudioFormat object's reference that was obtained by invoking the getAudioFormat method in Listing 13.

Getting a TargetDataLine object

The next step in the process is to get a TargetDataLine object to handle data acquisition from the microphone that matches the information encapsulated in the DataLine.Info object instantiated in listing 15.

This is accomplished in Listing 16 by invoking the static getLine method of the AudioSystem class, passing the DataLine.Info object as a parameter.

      targetDataLine = (TargetDataLine)
               AudioSystem.getLine(dataLineInfo);

Listing 16

The AudioSystem class

Here is part of what Sun has to say about the AudioSystem class.

"AudioSystem includes a number of methods for converting audio data between different formats, and for translating between audio files and streams.

It also provides a method for obtaining a Line directly from the AudioSystem without dealing explicitly with mixers."

The boldface portion of the above quotation is what interests us in this program.  According to Sun, the static getLine method:

"Obtains a line that matches the description in the specified Line.Info object."

Note that the getLine method returns a reference to an object of type Line, which must be downcast to type TargetDataLine in Listing 16.

Spawn and start a thread to handle the data capture

The code in Listing 17 creates a new Thread object from the CaptureThread class and starts it running.  As you will see later, the behavior of the thread is to capture audio data from the microphone and to store it in an output audio file.

      new CaptureThread().start();
    }catch (Exception e) {
      e.printStackTrace();
      System.exit(0);
    }//end catch
  }//end captureAudio method

Listing 17

The thread starts running when the user clicks the Capture button in Figure 1, and will continue running until the user clicks the Stop button in Figure 1.

Once the new thread has been created and started, the code in Listing 17, (which is part of the captureAudio method), returns control to the actionPerformed event handler method that is registered on the Capture button.

The captureAudio method ends in Listing 17.

The run method of the CaptureThread class

The CaptureThread class is an inner class.  An object of this class is used to perform the actual capture of the audio data from the microphone and the storage of that data in the output audio file.

Every thread object has a run method, which determines the behavior of the thread.  Listing 18 shows the beginning of the run method of the CaptureThread class, including the declaration of two instance variables that will be used later.

class CaptureThread extends Thread{
  public void run(){
    AudioFileFormat.Type fileType = null;
    File audioFile = null;

Listing 18

The output file type

The first step in the execution of the run method is to determine the type of output file specified by the user. 

(Recall that the user specifies a particular audio file type by the selection of a particular radio button in Figure 1.)

The code in Listing 19 is rather long, but very repetitive and simple.

    if(aifcBtn.isSelected()){
      fileType = AudioFileFormat.Type.AIFC;
      audioFile = new File("junk.aifc");
    }else if(aiffBtn.isSelected()){
      fileType = AudioFileFormat.Type.AIFF;
      audioFile = new File("junk.aif");
    }else if(auBtn.isSelected()){
      fileType = AudioFileFormat.Type.AU;
      audioFile = new File("junk.au");
    }else if(sndBtn.isSelected()){
      fileType = AudioFileFormat.Type.SND;
      audioFile = new File("junk.snd");
    }else if(waveBtn.isSelected()){
      fileType = AudioFileFormat.Type.WAVE;
      audioFile = new File("junk.wav");
    }//end if

Listing 19

Set file type based on selected radio button

This code in Listing 19 examines the radio buttons to determine which radio button was selected by the user prior to clicking the Capture button.  Depending on which radio button was selected, the code in Listing 19:

  • Sets the type of the audio output file.
  • Establishes the file name and extension for the audio output file.  (With a little extra programming effort, you could cause the file name to be provided by the user via a JTextField object in the GUI.)

The AudioFileFormat.Type class

The code in Listing 19 uses constants from the AudioFileFormat.Type class.  Here is what Sun has to say about that class.

"An instance of the Type class represents one of the standard types of audio file. Static instances are provided for the common types."

The class provides the following common types as public static final variables (constants).

  • AIFC
  • AIFF
  • AU
  • SND
  • WAVE

(Because I plan to publish a future tutorial lesson that deals with audio data formats and audio file types in detail, I won't comment further on the five common types in the above list in this lesson.)

The GUI in Figure 1 has one radio button for each of the file types in the above list.  Therefore, the code in Listing 19 establishes a file type, name, and extension for each of the five common types.

Not all file types are supported on all systems

I need to point out that not all of the above-listed file types can be created on all systems.  For example, types AIFC and SND produce a "type not supported" error on my system.

Start acquiring audio data from the microphone

The two statements in Listing 20 cause the audio data acquisition process to begin.

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

Listing 20

The open method

Here is part of what Sun has to say about the open method of the TargetDataLine interface.

"Opens the line with the specified format, causing the line to acquire any required system resources and become operational.  The implementation chooses a buffer size ..."

The start method

Note that this refers to the start method of the TargetDataLine object, (not the start method of the CaptureThread object, which was invoked in Listing 17).

Here is part of what Sun has to say about the start method invoked in Listing 20.

"Allows a line to engage in data I/O. If invoked on a line that is already running, this method does nothing."

The AudioSystem.write method

Everything discussed so far has been leading up to a discussion of the write method of the AudioSystem class, which is invoked in the run method of the thread object in Listing 21.

      AudioSystem.write(
            new AudioInputStream(targetDataLine),
            fileType,
            audioFile);

Listing 21

This is a very significant, very high-level method.  However, for such a significant method, the information provided by Sun is amazingly sparse.  There are two overloaded versions of the write method in Java SDK 1.4.1.  Here is what Sun has to say about the version shown in Listing 21.

"Writes a stream of bytes representing an audio file of the specified file type to the external file provided."

No loops or buffers

To fully appreciate the significance of this method, you should first note that there are no loops and there is no buffer manipulation involved in the code in Listing 21.  Rather, the code in Listing 21 consists of a single statement that invokes the static write method of the AudioSystem class.

By way of comparison, Listing 22 shows the code from a similar program in Java Sound, Getting Started, Part 2, Capture Using Specified Mixer.  This program was used to capture microphone data and to store that data in an object of type ByteArrayOutputStream (a memory buffer).

while(!stopCapture){
  //Read data from the internal buffer of
  // the data line.
  int cnt = targetDataLine.read(tempBuffer,
                        0,
                        tempBuffer.length);

  if(cnt > 0){
    //Save data in output stream object.
    byteArrayOutputStream.write(tempBuffer,
                                0,
                                cnt);
    }//end if
  }//end while

Listing 22

More complex code from earlier program

As you can see, the code in Listing 22 from the earlier program was required to loop while monitoring for a signal to stop data capture.  In addition, the code was required to invoke interlaced read and write methods, while dealing with the internal buffer of the TargetDataLine object and a temporary buffer object as well.

All of these details are handled automatically by the single invocation of the write method in Listing 21.

Features and characteristics of the write method

In addition to its other features, the AudioSystem.write method knows how to detect that the stop method has been invoked on the TargetDataLine object (see Listing 7) and to close the output file when that happens.  Therefore, it was not necessary for me to monitor for a signal to stop data capture and close the output file.

The required parameters for the write method in Listing 21 are:

  • An audio input stream of type AudioInputStream containing audio data to be written to the file.
  • The type of audio file to write, specified as type AudioFileFormat.Type
  • The external file to, which the data should be written, specified as type File.

The second two parameters were already available, having been created earlier.  However, a little extra work was required to create the first parameter.

The AudioInputStream parameter

According to Sun,

"An audio input stream is an input stream with a specified audio format and length."

As of Java SDK 1.4.1, the AudioInputStream class provides the two constructors shown in Figure 2.

 AudioInputStream(InputStream stream
                 AudioFormat format,
                 long length)
This constructor constructs an audio input stream that has the requested format and length in sample frames, using audio data from the specified input stream.

AudioInputStream(TargetDataLine line)
Constructs an audio input stream that reads its data from the target data line indicated.

Figure 2 Constructors for the AudioInputStream class

Since I already had a TargetDataLine object, I elected to use the second constructor to create the AudioInputStream object required for the first parameter of the write method in Listing 21.

End of the run method and end of the class definition

Except for a catch block, that ends the definition of the run method of the CaptureThread class that begins in Listing 18. 

That is also the end of the CaptureThread class, and the end of the program. 

You can view a complete listing of the program in Listing 22 near the end of the lesson.

Run the Program

At this point, you may find it useful to compile and run the program shown in Listing 22 near the end of the lesson.

Not all file types and data formats are supported

As I mentioned earlier, not all file types can be created on all systems.  For example, types AIFC and SND produce a "type
not supported"
error on my system.

Also, not all data formats are supported on all systems.  If the format parameters that I used in Listing 14 don't work well for you, try some of the other allowable parameters, which are listed in comments following the variable declarations.

The program GUI at startup

When you start the program, the GUI shown in Figure 1 should appear on your screen.  Select an audio file type by selecting one of the radio buttons.  Then click the Capture button and start talking into the microphone.  When you have recorded enough audio data, click the Stop button.

An output audio file should be created

At that point in time, an audio file named junk.xx should be in the folder containing the compiled version of your program.  The file extension will depend on the type of audio file you selected with the radio buttons before you clicked the Capture button (see Listing 19 for an identification of the possible extensions).

Play back the audio file

You should be able to play the audio file back using a standard media player such as Windows Media Player.

If you run the program two times in succession for the same audio file type, be sure to release the old file from the media player before attempting to create a new file with the same name and extension.  Otherwise, it won't be possible to create the new file with the same name and extension, and you will get an error message.

If you don't hear anything during playback, you may need to increase your speaker volume.

Summary

In this lesson, I showed you how to use the Java Sound API to capture audio data from a microphone and how to save that data in an audio file type of your choosing.

Complete Program Listing

A complete listing of the program is shown in Listing 22.

/*File AudioRecorder02.java
Copyright 2003, Richard G. Baldwin
 
Эта программа демонстрирует захват аудиоданных 
с микрофона в аудиофайл.
 
На экране появится графический интерфейс со 
следующими кнопками:
  Capture
  Stop
 
Кроме того, на экране появляются пять переключателей, 
позволяющих пользователю выбрать один из следующих 
пяти форматов файлов вывода аудио:
 
  AIFC
  AIFF
  AU
  SND
  WAVE
 
Когда пользователь нажимает кнопку Capture, входные данные 
с микрофона захватываются и сохраняются в аудиофайле с 
именем junk.xx, имеющем указанный формат файла. (xx - это 
расширение файла для указанного формата файла. Вы можете 
легко изменить имя файла на другое, кроме нежелательного, 
если захотите.)
 
Сбор данных останавливается, а выходной файл закрывается, 
когда пользователь нажимает кнопку «Стоп».
 
Должна быть возможность воспроизводить аудиофайл с помощью 
любого из множества доступных медиаплееров, таких как 
Windows Media Player.
 
Не все типы файлов могут быть созданы во всех системах.
Например, типы AIFC и SND вызывают в моей системе ошибку 
«тип не поддерживается».
 
Обязательно освободите старый файл из медиаплеера, прежде 
чем пытаться создать новый файл с тем же расширением.
 
Tested using SDK 1.4.1 under Win2000
************************************************/
 
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.sound.sampled.*;
 
public class AudioRecorder02 extends JFrame{
 
  AudioFormat audioFormat;
  TargetDataLine targetDataLine;
 
  final JButton captureBtn =
                          new JButton("Capture");
  final JButton stopBtn = new JButton("Stop");
 
  final JPanel btnPanel = new JPanel();
  final ButtonGroup btnGroup = new ButtonGroup();
  final JRadioButton aifcBtn =
                        new JRadioButton("AIFC");
  final JRadioButton aiffBtn =
                        new JRadioButton("AIFF");
  final JRadioButton auBtn =//выбран при запуске
                     new JRadioButton("AU",true);
  final JRadioButton sndBtn =
                         new JRadioButton("SND");
  final JRadioButton waveBtn =
                        new JRadioButton("WAVE");
 
  public static void main( String args[]){
    new AudioRecorder02();
  }//конец main
 
  public AudioRecorder02(){//конструктор
    captureBtn.setEnabled(true);
    stopBtn.setEnabled(false);
 
    // Зарегистрировать анонимных слушателей
    captureBtn.addActionListener(
      new ActionListener(){
        public void actionPerformed(
                                  ActionEvent e){
          captureBtn.setEnabled(false);
          stopBtn.setEnabled(true);
          // Захват входных данных с микрофона,
          // пока не будет нажата кнопка Стоп.
          captureAudio();
        }//конец actionPerformed
      }//конец ActionListener
    );//конец addActionListener()
 
    stopBtn.addActionListener(
      new ActionListener(){
        public void actionPerformed(
                                  ActionEvent e){
          captureBtn.setEnabled(true);
          stopBtn.setEnabled(false);
          // Прекратите захват входных данных 
          // с микрофона.
          targetDataLine.stop();
          targetDataLine.close();
        }//конец actionPerformed
      }//конец ActionListener
    );//конец addActionListener()
 
    // Поместить кнопки в JFrame
    getContentPane().add(captureBtn);
    getContentPane().add(stopBtn);
 
    // Включить переключатели в группу
    btnGroup.add(aifcBtn);
    btnGroup.add(aiffBtn);
    btnGroup.add(auBtn);
    btnGroup.add(sndBtn);
    btnGroup.add(waveBtn);
 
    // Добавить переключатели в JPanel
    btnPanel.add(aifcBtn);
    btnPanel.add(aiffBtn);
    btnPanel.add(auBtn);
    btnPanel.add(sndBtn);
    btnPanel.add(waveBtn);
 
    // Поместить JPanel в JFrame
    getContentPane().add(btnPanel);
 
    // Завершить графический интерфейс и сделайте видимым
    getContentPane().setLayout(new FlowLayout());
    setTitle("Copyright 2003, R.G.Baldwin");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setSize(300,120);
    setVisible(true);
  }//конец конструктора
 
  // Этот метод захватывает аудиовход с микрофона
  // и сохраняет его в аудиофайл.
  private void captureAudio(){
    try{
      // Подготовить все для съемки
      audioFormat = getAudioFormat();
      DataLine.Info dataLineInfo =
                          new DataLine.Info(
                            TargetDataLine.class,
                            audioFormat);
      targetDataLine = (TargetDataLine)
               AudioSystem.getLine(dataLineInfo);
 
      // Создайте поток для захвата данных микрофона
      // в аудиофайл и запуска потока. Он будет
      // работать, пока не будет нажата кнопка 
      // Stop. Этот метод вернется после
      // запуска потока.
      new CaptureThread().start();
    }catch (Exception e) {
      e.printStackTrace();
      System.exit(0);
    }//конец catch
  }//конец метода captureAudio
 
  // Этот метод создает и возвращает объект 
  // 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{
  public void run(){
    AudioFileFormat.Type fileType = null;
    File audioFile = null;
 
    // Установите тип файла и расширение файла
    // в зависимости от выбранного переключателя.
    if(aifcBtn.isSelected()){
      fileType = AudioFileFormat.Type.AIFC;
      audioFile = new File("junk.aifc");
    }else if(aiffBtn.isSelected()){
      fileType = AudioFileFormat.Type.AIFF;
      audioFile = new File("junk.aif");
    }else if(auBtn.isSelected()){
      fileType = AudioFileFormat.Type.AU;
      audioFile = new File("junk.au");
    }else if(sndBtn.isSelected()){
      fileType = AudioFileFormat.Type.SND;
      audioFile = new File("junk.snd");
    }else if(waveBtn.isSelected()){
      fileType = AudioFileFormat.Type.WAVE;
      audioFile = new File("junk.wav");
    }//конец if
 
    try{
      targetDataLine.open(audioFormat);
      targetDataLine.start();
      AudioSystem.write(
            new AudioInputStream(targetDataLine),
            fileType,
            audioFile);
    }catch (Exception e){
      e.printStackTrace();
    }//конец catch
 
  }//конец run
}//конец внутреннего класса CaptureThread
//=============================================//
 
}//конец внешнего класса AudioRecorder02.java

Листинг 22

 

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