Предисловие

Что такое звук?

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

С точки зрения Java Sound API слово «звук» имеет несколько иное значение. Однако, вероятно, будет справедливо сказать, что конечная цель Sound API - помочь вам в написании программ, которые заставят волны звукового давления попадать в уши целевых людей в определенное время.

Что Sun говорит об API?

Вот что Sun говорит о Java Sound API:

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

Солнце также говорит нам:

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

Таким образом, ваша миссия как Java-программиста - использовать Sound API для создания пользовательских интерфейсов более высокого уровня, основанных на Java Sound.

Нетривиальный API

Использование Sound API связано с рядом довольно сложных проблем. Этот учебный урок предоставит очень краткое введение в некоторые из этих проблем. В будущих уроках мы подробно рассмотрим многие из них.

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

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

Анонс

В этом уроке дается описание звука как с физической точки зрения, так и с точки зрения программирования.

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

Он определяет важные пакеты, включенные в Sound API, и объясняет разницу между пакетами сэмплов и пакетами MIDI.

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

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

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

Пакеты

API поддерживает два существенно разных типа аудио (или звуковых) данных:

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

Оцифрованные аудиоданные

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

  • javax.sound.sampled
  • javax.sound.sampled.spi

Согласно Sun, первый из этих двух пакетов "определяет интерфейсы для захвата, микширования и воспроизведения цифрового (сэмплированного) аудио." В ближайшее время я еще скажу о втором пакете (spi).

MIDI-данные

MIDI-данные можно рассматривать как звук (обычно музыкальный звук или специальные звуковые эффекты), созданный по рецепту. Этот тип аудиоданных поддерживается двумя следующими пакетами Java:

  • javax.sound.midi
  • javax.sound.midi.spi

Согласно Sun, первый из этих двух пакетов «предоставляет интерфейсы для MIDI-синтеза, секвенирования и передачи событий».

А как насчет пакетов spi?

Согласно Sun, каждый из пакетов spi «позволяет поставщикам услуг (в отличие от разработчиков приложений) создавать собственные компоненты, которые могут быть установлены в системе».

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

Что такое выборочные данные?

Я собираюсь отослать вас к другой моей публикации, озаглавленной «Цифровая обработка сигналов (DSP) на Java, дискретные временные ряды», для обсуждения выборочных данных в целом. Поскольку методы DSP часто используются при обработке дискретизированных аудиоданных, вас также могут заинтересовать другие мои публикации по DSP.

Что такое дискретизированные аудиоданные?

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

Пример

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

Рисунок 1 Выборка аудиоданных

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

Аудио компакт-диск (CD)

В качестве другого примера данные на аудио-компакт-диске представляют собой дискретизированные аудиоданные. Насколько я понимаю, электронное представление волн звукового давления, созданных художником, дискретизируется 44 100 раз в секунду. Каждая выборка представлена ​​как 16-битовое целое число со знаком.

Другие источники дискретизированных аудиоданных

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

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

Согласно Sun:

"Термин «дискретизированный звук» относится к типу данных, а не к их происхождению. Сэмплы можно рассматривать как сам звук, тогда как данные MIDI можно рассматривать как рецепт создания музыкального звука.) "

Захват дискретизированного звука

Обычно дискретизированные аудиоданные записываются в два этапа:

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

Рендеринг дискретизированного звука

Рендеринг дискретизированных аудиоданных также обычно выполняется в два этапа:

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

Запустить программу

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

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

Захват и воспроизведение аудиоданных

Эта программа демонстрирует возможность захвата аудиоданных с микрофона и их воспроизведения через динамики вашего компьютера. Инструкции по использованию просты:

  • Запустите программу. На экране появится простой графический интерфейс.
  • Нажмите кнопку «Захват» и говорите в микрофон.
  • Нажмите кнопку «Стоп», чтобы прекратить сбор данных.
  • Нажмите кнопку «Воспроизведение», чтобы воспроизвести записанный голос через динамики системы.

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

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

Резюме

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

Я представил Java Sound API, который обеспечивает высокую степень контроля над аудиофункциями в программах Java.

Я определил важные пакеты, включенные в Sound API, и объяснил разницу между пакетами сэмплов и пакетами MIDI.

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

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

Что дальше?

В следующем уроке я объясню общую архитектуру Sound API, представив такие термины и концепции, как:

  • Линия
  • TargetDataLine
  • SourceDataLine
  • Микшерв
  • Порт
  • Аудиоформат
  • Формат файлов
  • Аудиопоток
  • Клип
  • Управление

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

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

/*File AudioCapture01.java
Эта программа демонстрирует захват
и последующее воспроизведение аудиоданных.

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

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

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

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

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 AudioCapture01
                        extends JFrame{

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

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

  public AudioCapture01(){//конструктор
    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);

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

    stopBtn.addActionListener(
      new ActionListener(){
        public void actionPerformed(
                        ActionEvent e){
          captureBtn.setEnabled(true);
          stopBtn.setEnabled(false);
          playBtn.setEnabled(true);
          //Terminate the capturing of
          // input data from the
          // microphone.
          stopCapture = true;
        }//end actionPerformed
      }//end ActionListener
    );//end addActionListener()
    getContentPane().add(stopBtn);

    playBtn.addActionListener(
      new ActionListener(){
        public void actionPerformed(
                        ActionEvent e){
          //Play back all of the data
          // that was saved during
          // capture.
          playAudio();
        }//end actionPerformed
      }//end ActionListener
    );//end addActionListener()
    getContentPane().add(playBtn);

    getContentPane().setLayout(
                     new FlowLayout());
    setTitle("Capture/Playback Demo");
    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.open(audioFormat);
      targetDataLine.start();

      // Создать поток, чтобы захватить
      // данные микрофона и запустить его.
      // Он будет работать, пока не будет
      // нажата кнопка Stop.
      Thread captureThread =
                new Thread(
                  new CaptureThread());
      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.open(audioFormat);
      sourceDataLine.start();

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

  // Этот метод создает и возвращает объект
  // AudioFormat для заданного набора 
  // параметров формата.
  // parameters don't work well for
  // Если эти параметры вам не подходят,
  // попробуйте некоторые из других допустимых
  // значений параметров, которые показаны 
  // в комментариях после объявлений.
  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{//Loop until stopCapture is set
        // другим потоком, который
        // обслуживает кнопку Stop.
      while(!stopCapture){
        // Считать данные из внутреннего
        // буфера строки данных.
        int cnt = targetDataLine.read(
                    tempBuffer,
                    0,
                    tempBuffer.length);
        if(cnt > 0){
          // Сохранить данные в объекте
          // потока вывода.
          byteArrayOutputStream.write(
                   tempBuffer, 0, cnt);
        }//конец if
      }//конец while
      byteArrayOutputStream.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;
      // Продолжать цикл до тех пор,
      // пока метод read не вернет -1 
      // для пустого потока.
      while((cnt = audioInputStream.
        read(tempBuffer, 0,
            tempBuffer.length)) != -1){
        if(cnt > 0){
          // Записать данные во внутренний 
          // буфер линии данных,
          // откуда они будут доставлены
          // на устройство воспроизведения.
          sourceDataLine.write(
                   tempBuffer, 0, cnt);
        }//конец if
      }//конец while
      // Заблокировать и дождаться,
      // пока внутренний буфер линии 
      // данных опустеет.
      sourceDataLine.drain();
      sourceDataLine.close();
    }catch (Exception e) {
      System.out.println(e);
      System.exit(0);
    }//конец catch
  }//конец run
}//конец внутреннего класса PlayThread
//===================================//

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

Листинг 1

 

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