Хабрахабр

[Из песочницы] JAVA SOUND API основы

Привет, Хабр! Представляю вашему вниманию перевод статьи «Java Sound, Getting Started, Part 1, Playback».

Звук в JAVA, часть первая, Начало. Проигрывание звука

Это начальный из серии в восемь уроков, который полностью ознакомит вас с Java Sound API. 
Что такое звук в человеческом восприятии? Это ощущение, которое мы испытываем когда изменение давления воздуха передается на крохотные сенсорные участки внутри наших ушей.

И главная цель создания Sound API, соответственно обеспечить вас средствами для написания кода, который и поможет в передаче волн давления в уши нужному субъекту в нужное время.

Типы звука в Java:

  1. В Java Sound API поддерживаются два основных типа аудио (звука).
  2. Звук оцифрованный и записанный непосредственно в виде файла
  3. Запись в виде MIDI файла. Очень отдаленно, но похожа на нотную запись, где музыкальные инструменты играют в нужной последовательности.

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

Превью

Java Sound API основывается на концепции линий и микшеров.

Далее:
Мы приведём описание физических и электрических характеристик аналогового представления звука применительно к аудио микшеру.

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

Далее мы рассмотрим ряд Java Sound тем для программирования, таких как линии, микшеры, форматы для аудио данных и прочее.

Мы разберемся в связях существующих между объектами SourceDataLine, Clip, Mixer, AudioFormat и создадим простую программу воспроизводящую аудио.

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

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

Пример кода и его рассмотрение

Физические и электрические характеристики аналогового звука

Цель нашего урока ознакомить вас с основами программирования на Java, используя Java Sound API.

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

1 Посмотрите на Рис.

Вася Пупыркин толкает речь.

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

Колебания в воздухе

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

Динамический микрофон

2, на котором изображено схематическое устройства микрофона называемого динамическим. Теперь посмотрим на Рис.

2 Схема динамического микрофона
Рис.

Звуковые колебания воздействуют на мембрану

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

Перемещение катушки

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

Электрический сигнал повторяет форму звуковых волн

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

Громкоговоритель

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

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

Рок концерт

Но подождите ещё чуть-чуть, мы ведем путь к основам работы аудио микшера. К этому времени вы можете задаться вопросом, какое отношение это всё имеет к Java Sound API?

Она состояла из Васи Пупыркина, одного микрофона, усилителя и громкоговорителя. Схема описанная выше была довольно проста. 4, на котором представлена сцена подготовленная для рок концерта начинающей музыкальной группы. Теперь рассмотрим схему с Рис.

Шесть микрофонов и два громкоговорителя

4 шесть микрофонов расположено на сцене. На Рис. Когда начинается концерт, исполнители поют или играют музыку в каждый из шести микрофонов. Два громкоговорителя (динамика) размещены по бокам сцены. В дополнение к этому, исполнители могут задействовать различные звуковые спецэффекты, например, реверберацию, которые тоже необходимо будет перевести в электрические сигналы, перед тем как подать их на громкоговорители. Соответственно у нас будет шесть электрических сигналов, которые должны быть индивидуально усилены и поданы затем на оба динамика.

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

Аудио микшер

Задача рассмотренная выше как раз выполняется электронным устройством, которое называется аудио микшер.

Аудио линия (канал)

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

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

Независимая обработка каждого аудио канала

Также микшер обычно имеет возможность наложения звуковых спецэффектов, таких как, к примеру, реверберация на любую из аудио линий. В любом случае, стандартный аудио микшер имеет возможность усиления каждой аудио линии независимо от остальных других каналов. В конечном же итоге, микшер, как это следует из его названия, может смешивать все индивидуальные электрические сигналы в выходных каналах как ему будет задано, таким образом, чтобы контролировать вклад каждой аудио линии в выходные каналы.(Этот контроль обычно называется pan или панорамирование — распределение в пространстве).

Возвращаясь к стереозвуку

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

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

Время вернуться в мир программирования

Согласно Sun: «Java Sound не предполагает специальной конфигурации аппаратного обеспечения; он спроектирован для того, чтобы позволить различным аудио компонентам быть инсталлированными в систему и быть доступными пользователю через API. Давайте теперь вернёмся из физического мира в мир программирования. Java Sound поддерживает стандартную функциональность входа и выхода со звуковой карты (к примеру, для записи и проигрывания аудио файлов), а также возможность микширования нескольких аудио потоков»

Микшеры и каналы

Если двигаться из физического мира в мир программирования, то Sun пишет следующее, относительно микшера: Как уже было сказано Java Sound API построено на концепции микшеров и каналов.

Но миксер который действительно смешивает аудио сигнал должен иметь несколько входных каналов источников source и как минимум один выходной target канал ». «Микшер это аудио устройство с одним или более каналами.

Микшер может принимать на вход также заранее записанный и закольцованный звук, определяя свои входные source каналы как экземпляры объектов класса реализующий интерфейс Clip. Входные линии могут являться экземплярами классов с объектами SourceDataLine, а выходные — TargetDataLine.

Интерфейс канал Line.

Аудиоданные проходящие через канал могут быть моно или многоканальными (к примеру, стерео). Sun сообщает следующее от интерфейсе Line: «Line это элемент цифрового аудио конвейера такого как входной или выходной аудио порт, микшер или маршрут аудиоданных в или из микшера. … Канал может иметь элементы управления Controls, такие как усиление, панорамирование и реверберацию».

Объединяя термины вместе

Итак, вышеуказанные цитаты от Sun обозначали следующие термины

SourceDataLine
TargetDataLine
Port
Clip
Controls

5 показывает пример использования этих терминов для построения простой программы вывода аудио Рис.

Сценарий программы

5 показывает объект Микшер, полученный с одним объектом Clip и двумя SourceDataLine объектами. С программной точки зрения Рис.

Что такое Clip

Другими словами, вы загружаете аудио данные в объект Clip до того как проиграете его. Clip это объект на входе микшера, содержимое которого не меняется со временем. Вы можете закольцевать Clip и тогда контент будет проигрываться снова и снова. Аудиоконтент объекта Clip может проигран один или более раз.

Входной поток

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

Различные типы каналов

Каждый из этих входных каналов может иметь свои собственные: панорамирование, усиление и реверберацию. Таким образом объекты Clip и SourceDataLine могут быть рассмотрены как входные каналы для объекта Mixer.

Проигрывание аудио контента

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

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

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

Захваченные данные запоминаются в объекте ByteArrayOutputStream.

Фрагмент кода захвата данных обеспечивает чтение аудио данных с микрофона и запоминание их в виде объекта ByteArrayOutputStream.

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

private void playAudio() { try{ byte audioData[] = byteArrayOutputStream. toByteArray(); InputStream byteArrayInputStream = new ByteArrayInputStream( audioData);

Листинг 1

Начинаем со стандартного кода

Фрагмент программы в листинге 1, в действительности пока еще не имеет отношения к Java Sound.

Его назначение состоит в том чтобы:

  • Преобразовать ранее сохраненные данные в массив типа byte.
  • Получить входной поток для байтового массива данных.

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

Переходим к Sound API

Строчка кода из листинга 2 уже имеет отношение к Java Sound API.

AudioFormat audioFormat = getAudioFormat();

Листинг 2

Здесь мы коротко коснёмся темы, которая будет детально обсуждаться в следующем уроке.

Два независимых формата

Чаще всего мы имеем дело с двумя независимыми форматами для аудио данных.

Формат файла, (любой) который содержит аудио данные (в нашей программе пока его нет, так как данные сохраняются в памяти)

Формат представленных аудио данных сам по себе.

Что же такое аудио формат?

Вот что пишет об этом Sun:

Формат (экземпляр AudioFormat) определяет порядок следования байтов а аудио потоке. “Каждый канал данных имеет свой аудио формат связанный с его потоком данным. Обычными способами кодирования могут быть линейная импульсно-кодовая модуляция ИКМ и её разновидности.” Параметрами формата могут являться количество каналов, частота дискретизации, разрядность квантования, способ кодирования и т. д.

Последовательность байтов

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

Небольшое отступление

Здесь мы пока оставим метод playAudio и рассмотрим метод getAudioFormat из листинга 2.

Полностью метод getAudioFormat представлен в листинге 3.

private AudioFormat getAudioFormat()//end getAudioFormat

Листинг 3

Кроме декларации инициализированных переменных код из листинга 3 содержит одно исполняемое выражение.

Объект AudioFormat

Вот что Sun пишет об этом классе: Метод getAudioFormat создает и возвращает экземпляр объекта класса AudioFormat.

Обратившись к полям объекта AudioFormat, вы может получить информацию как правильно интерпретировать биты в двоичном потоке данных.» «Класс AudioFormat определяет конкретную упорядоченность данных в аудио потоке.

Используем простейший конструктор

Для этого конструктора требуемые следующие параметры: Класс AudioFormat имеет два вида конструкторов (мы возьмём самый тривиальный).

  • Частота дискретизации или частота отсчётов в секунду (Доступные величины: 8000, 11025, 16000, 22050 и 44100 отсчётов в секунду)
  • Разрядность данных в битах (доступны 8 и 16 битов на отсчёт)
  • Количество каналов ( один канал для моно и два для стерео)
  • Знаковые или беззнаковые данные, которые используются в потоке (к примеру величина меняется от 0 до 255 или от -127 до +127)
  • Порядок следования байтов Big-endian или little-endian. (если вы передаете байтовым потоком 16-разрядные величины, то важно знать какой байт идет первым — младший или старший, так как встречаются оба варианта).

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

  • 8000 отсчётов в секунду
  • 16 размер данных
  • данные знаковые
  • порядок Little-endian

По умолчанию данные кодируются линейной ИКМ.

Конструктор, который мы использовали, создает экземпляр объекта AudioFormat использующий линейную импульсно-кодовую модуляцию и параметры указанные выше (Мы вернемся к линейной ИКМ и другим способам кодирования в следующих уроках)

Снова возвращаемся к методу playAudio

Как только мы захотим проиграть доступные аудио данные, нам понадобится объект класса AudioInputStream. Теперь, когда мы поняли как устроен формат аудио данных в Java sound, давайте вернёмся к методу playAudio. Мы получим его экземпляр в листинге 4.

audioInputStream = new AudioInputStream( byteArrayInputStream, audioFormat, audioData.length/audioFormat. getFrameSize());

Листинг 4

Параметры для конструктора AudioInputStream

  • Конструктор для класса AudioInputStream требует следующие три параметра:
  • Поток на котором будет основан экземпляр объекта AudioInputStream (как мы видим для этой цели нам служит экземпляр объекта ByteArrayInputStream созданный ранее)
  • Формат аудио данных для этого потока (для этой цели мы уже создали экземпляр объекта AudioFormat)
  • Размер фрейма (кадра) для данных в этом потоке (смотрим описание ниже по тексту)
  • Первые два параметра понятны из кода в листинге 4. Однако третий параметр не так очевиден сам по себе.

Получение размера кадра

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

Что такое кадр?

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

Таким образом размер кадра равен размеру величины отсчета в байтах умноженной на количество каналов.

Как вы уже может быть догадались, метод с названием getFrameSize возвращает размер кадра в байтах.

Подсчёт размера кадров

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

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

Как мы можем видеть из схемы на Рис. Следующая часть программы, которую мы обсудим — это простая система вывода аудио. 5, для решения этой задачи нам потребуется объект SourceDataLine.

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

Он получает его довольно окольным способом.) (Обратите внимание, что этот код не просто создает экземпляр объекта SourceDataLine.

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

Листинг 5

Что представляет собой объект SourceDataLine?

Насчёт этого Sun пишет следующее:

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

Заметьте, что соглашение об именовании для такого сопряжения отражает взаимоотношение между каналом и его микшером»

Метод getLine для класса AudioSystem

Один из способов получения экземпляра объекта SourceDataLine состоит в том, чтобы вызвать статический getLine метод из класса AudioSystem (У нас будет много чего сообщить о нём на следующих уроках).

Info и возвращает объект Line, который соответствует описанию в уже определенном объекте Line. Метод getLine требует входной параметр типа Line. Info.

Еще одно короткое отступление

Info: Sun сообщает следующую информацию об объекте Line.

Info ), который показывает какой микшер (если есть) отправляет смикшированные аудио данные как выходные непосредственно в канал, и какой микшер (если имеется) получает аудио данные как входные непосредственно из канала. «Канал имеет свой информационный объект (экземпляр Line. Info, что позволяет указывать другие виды параметров относящихся уже к конкретным типам каналов» Разновидности Line могут соответствовать подклассам Line.

Info Объект DataLine.

Info, который является специальной формой (подклассом) объекта Line. Первое выражение в листинге 5 создает новый экземпляр объекта DataLine. Info.

Info. Существует несколько перегружаемых конструкторов для класса DataLine. Этот конструктор требует два параметра. Мы выбрали для использования самый простой.

Объект Class

Первый параметр это Class, который представляет класс, который мы определили как SourceDataLine.class

Мы используем для него экземпляр объекта AudioFormat, который уже был определён ранее. Второй параметр определяет желаемый формат данных для канала.

Мы уже там где нужно?

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

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

Это происходит путем вызова статического метода getLine класса AudioSystem и передачи dataLineInfo как параметра. Второе выражение в листинге 5 наконец-то создает и сохраняет так необходимый нам экземпляр объекта SourceDataLine. (На следующем уроке мы рассмотрим как получить объект Line, работая непосредственно с объектом Mixer ).

Поэтому здесь необходимо нисходящее приведение типа перед тем как возвращаемое значение будет сохранено как SourceDataLine. Метод getLine возвращает ссылку на объект типа Line, которым является родительским по отношению к SourceDataLine.

Приготовимся использовать объект SourceDataLine

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

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

Листинг 6

Открывающий метод

Как вы можете видеть из листинга 6, мы отправили объект AudioFormat в открывающий метод для объекта SourceDataLine.

В соответствии с Sun это метод:

«Открывает линию (канал ) с определенным ранее форматом, позволяя ему получать любые требуемые им системные ресурсы и быть в рабочем (действующем) состоянии»

Состояние открытия

Есть еще немного, что пишет о нём Sun в этой теме.

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

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

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

Вызов метода start для канала

Согласно Sun вызов метода старт для канала означает следующее:

Если производится попытка задействовать уже работающую линию, метод ничего не делает. «Каналу разрешается использование линий ввода-вывода. Но после опустошения буфера с данными линия возобновляет запуск ввода-вывода, начиная с первого кадра, который она не успела обработать, после того как буфер был полностью загружен.»

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

Теперь у нас есть почти всё, что нам нужно

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

Запускаем потоки

Код в листинге 7 создает и запускает этот поток. Мы создадим и запустим поток чтобы воспроизвести аудио.

Это абсолютно разные операции) (Не путайте вызов метода start в этом потоке с вызовом метода start в объекте SourceDataLine из листинга 6.

Thread playThread = new Thread(new PlayThread()); playThread.start(); } catch (Exception e) { System.out.println(e); System.exit(0); }//end catch }//end playAudio

Листинг 7

Незатейливый код

Если он вам непонятен, то вам лучше ознакомиться с этой темой в специализированных темах по обучению Java. Фрагмент программы из листинга 7 хоть и очень несложен, но зато показывает пример много-поточного программирования в Java.

Как только поток запущен, он будет работать до тех пор, пока все предварительно записанные аудио данные не будут проиграны до конца.

Новый объект Thread

Это класс определен как внутренний класс в нашей программе. Код в листинге 7 создает экземпляр объекта Thread (потока) принадлежащего классу PlayThread. Его описание начинается в листинге 8.

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

Листинг 8

Метод run в классе Thread

Как вы уже должны знать, вызов метода start в объекте Thread, заставляет выполниться метод run этого объекта За исключением объявления переменной tempBuffer (которая ссылается на массив байт), полное определение этого класса это просто определение метода run.

Метод run для этого потока начинается в листинге 9

public void run(){ try{ int cnt; //Цикл работает // пока не возвращается -1 // while((cnt = audioInputStream. read(tempBuffer, 0, tempBuffer.length)) != -1){ if(cnt > 0){ //Запись данных во // внутренний буфер канала // откуда они отправляются // на звуковой выход. sourceDataLine.write( tempBuffer, 0, cnt); }//end if }//end while

Листинг 9

Первая часть фрагмента программы в методе run

Метод run содержит две важных части, первая из которых показана в листинге 9.

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

Это может быть встроенный динамик компьютера или линейный выход. Данные отправленные объекту SourceDataLine автоматически передаются на звуковой выход работающий по умолчанию. Переменная cnt и буфер данных tempBuffer используется для контроля потока данных между операциями чтения и записи. (Определять нужное звуковое устройств мы научимся в следующих уроках).

Чтение данных из AudioInputStream

Цикл чтения из объекта AudioInputStream, читает заданное максимальное количество байт данных из AudioInputStream и помещает их байтовый массив.

Возвращаемое значение

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

Цикл записи в SourceDataLine

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

Когда входной поток «пересыхает»

Когда цикл чтения возвращает -1, это означает, что все ранее записанные аудио данные закончились и далее контроль передается фрагменту программы в листинге 10.

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

Листинг 10

Блокировка и ожидание

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

Закрытие SourceDataLine

Sun сообщает следующее о закрытии канала: Затем программа вызывает метод close для закрытия канала, показывая тем самым, что все системные ресурсы используемые каналом теперь свободны.

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

А теперь конец истории

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

Запускаем программу

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

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

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

Простой графический интерфейс пользователя ГИП, который показан на Рис. Запускаем программу. 6, должен появиться на экране.

  • Кликните кнопку Capture и запишите какие-либо звуки на микрофон.
  • Кликните кнопку Stop, чтобы остановить запись.
  • Кликните кнопку Playback, чтобы проиграть запись через звуковой выход вашего компьютера.

Если вы ничего не слышите, попробуйте увеличить чувствительность вашего микрофона или громкость динамиков.

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

Заключение

  • Мы выяснили, что Java Sound API основывается на концепции каналов и микшеров.
  • Мы получили начальную информацию о физических и электрических характеристиках аналогового звука, чтобы понять затем устройство аудио микшера.
  • Мы использовали сценарий любительского рок концерта с использованием шести микрофонов и двух стерео колонок, чтобы описать возможность использования аудио микшера.
  • Мы обсудили ряд тем по программированию Java Sound, включая микшеры, каналы, формат данных и прочее.
  • Мы объяснили себе общие взаимосвязи между объектами SourceDataLine, Clip, Mixer, AudioFormat и порты в простой программе вывода аудио данных.
  • Мы ознакомились с программой позволяющей нам первоначально записать, а затем проиграть аудио данные.
  • Мы получили детальное объяснение кода использованного для проигрывания аудио данных записанных предварительно в память компьютера.

Что дальше?

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

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

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); captureBtn.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ captureBtn.setEnabled(false); stopBtn.setEnabled(true); playBtn.setEnabled(false); //Захват данных // с микрофона //пока не нажата Stop captureAudio(); } } ); getContentPane().add(captureBtn); stopBtn.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ captureBtn.setEnabled(true); stopBtn.setEnabled(false); playBtn.setEnabled(true); //Остановка захвата // информации с микрофона stopCapture = true; } } ); getContentPane().add(stopBtn); playBtn.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ //Проигрывание данных // которые были записаны playAudio(); } } ); 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(); //Создаем поток для захвата аудио // и запускаем его //он будет работать //пока не нажмут кнопку Thread captureThread = new Thread( new CaptureThread()); captureThread.start(); } catch (Exception e) { System.out.println(e); System.exit(0); } } //Этот метод проигрывает аудио // данные, которые были сохранены // в 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); } } //Этот метод создает и возвращает // объект 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); }
//===================================// //Внутренний класс для захвата
// данных с микрофона
class CaptureThread extends Thread{ byte tempBuffer[] = new byte[10000]; public void run(){ byteArrayOutputStream = new ByteArrayOutputStream(); stopCapture = false; try{ while(!stopCapture){ int cnt = targetDataLine.read( tempBuffer, 0, tempBuffer.length); if(cnt > 0){ //Сохраняем данные в выходной поток byteArrayOutputStream.write( tempBuffer, 0, cnt); } } byteArrayOutputStream.close(); }catch (Exception e) { System.out.println(e); System.exit(0); } }
}
//===================================//
//Внутренний класс для
// проигрывания сохраненных аудио данных
class PlayThread extends Thread{ byte tempBuffer[] = new byte[10000]; public void run(){ try{ int cnt; // цикл пока не вернется -1 while((cnt = audioInputStream. read(tempBuffer, 0, tempBuffer.length)) != -1){ if(cnt > 0){ //Пишем данные во внутренний // буфер канала // откуда оно передастся // на звуковой выход sourceDataLine.write( tempBuffer, 0, cnt); } } sourceDataLine.drain(); sourceDataLine.close(); }catch (Exception e) { System.out.println(e); System.exit(0); } }
}
//===================================// }//end outer class AudioCapture01.java

Листинг 11

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть