Как обсуждалось в главе 13, "Введение в интерфейсы поставщика сервисов", Java Sound API включает два пакета, javax.sound.sampled.spi и javax.sound.midi.spi, которые определяют абстрактные классы, которые будут использоваться разработчиками звуковых сервисов. Реализуя и устанавливая подкласс одного из этих абстрактных классов, поставщик сервисов регистрирует новую службу, расширяя функциональные возможности системы времени выполнения. В данной главе рассказывается, как использовать пакет javax.sound.sampled.spi для предоставления новых сервисов для обработки дискретизированного звука.

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

Вступление.

В пакете javax.sound.sampled.spi есть четыре абстрактных класса, представляющих четыре различных типа сервисов, которые вы можете предоставить для системы сэмплированного звука:

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

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

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

Существование новых аудиосервисов может быть полностью прозрачным как для пользователя, так и для прикладного программиста. Все ссылки на приложения проходят через стандартные объекты пакета javax.sound.sampled, в первую очередь AudioSystem, и специальная обработка, которую могут предоставлять новые сервисы, часто полностью скрыта.

В этой главе мы продолжим существующее в предыдущей главе соглашение об обозначении новых подклассов SPI такими именами, как AcmeMixer и AcmeMixerProvider.

Предоставление сервисов по записи аудиофайлов.

Начнем с AudioFileWriter, одного из самых простых классов SPI.

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

AudioFileWriter включает два метода, которые имеют конкретные реализации в базовом классе:

boolean isFileTypeSupported(AudioFileFormat.Type fileType) 
boolean isFileTypeSupported(AudioFileFormat.Type fileType, 
    AudioInputStream stream) 

Первый из этих методов сообщает вызывающей стороне, может ли этот модуль записи файлов записывать звуковые файлы указанного типа. Этот метод является общим запросом, он вернет true, если средство записи файла может записать файл такого типа, при условии, что автору файла переданы соответствующие аудиоданные. Однако возможность записи файла может зависеть от формата конкретных аудиоданных, которые передаются в средство записи файла. Средство записи файлов может поддерживать не все форматы аудиоданных, или ограничение может быть наложено самим форматом файла. (Не все виды аудиоданных могут быть записаны во все виды звуковых файлов.) Второй метод более конкретен, он спрашивает, можно ли записать конкретный AudioInputStream в конкретный тип файла.

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

abstract AudioFileFormat.Type[] getAudioFileTypes() 
abstract AudioFileFormat.Type[]
    getAudioFileTypes(AudioInputStream stream) 

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

Последние два метода AudioFileWriter выполняют фактическую работу по записи файлов:

abstract  int write(AudioInputStream stream, 
    AudioFileFormat.Type fileType, java.io.File out) 
 abstract  int write(AudioInputStream stream, 
    AudioFileFormat.Type fileType, java.io.OutputStream out) 

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

Предоставление услуг чтения аудиофайлов.

Класс AudioFileReader состоит из шести абстрактных методов, которые ваш подкласс должен реализовать - фактически, двух разных перегруженных методов, каждый из которых может принимать аргумент File, URL или InputStream. Первый из этих перегруженных методов принимает запросы о формате указанного файла:

abstract AudioFileFormat getAudioFileFormat(
    java.io.File file) 
abstract AudioFileFormat getAudioFileFormat(
    java.io.InputStream stream) 
abstract AudioFileFormat getAudioFileFormat(
    java.net.URL url) 

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

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

Другой перегруженный метод AudioFileReader предоставляет услуги чтения файлов, возвращая AudioInputStream, из которого могут быть прочитаны аудиоданные файла:

abstract AudioInputStream getAudioInputStream(
    java.io.File file) 
abstract AudioInputStream getAudioInputStream(
     java.io.InputStream stream) 
abstract AudioInputStream getAudioInputStream(
     java.net.URL url) 

Как правило, реализация getAudioInputStream возвращает AudioInputStream в начало фрагмента данных файла (после заголовка), готового к чтению. Однако было бы возможно, чтобы средство чтения файлов возвращало AudioInputStream, аудиоформат которого представляет собой поток данных, который каким-либо образом декодируется из того, что содержится в файле. Важно то, что метод возвращает форматированный поток, из которого могут быть прочитаны аудиоданные, содержащиеся в файле. AudioFormat, инкапсулированный в возвращенном объекте AudioInputStream, будет информировать вызывающую сторону о формате данных потока, который обычно, но не обязательно, совпадает с форматом данных в самом файле.

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

Предоставление сервисов преобразования формата.

Подкласс FormatConversionProvider преобразует AudioInputStream с одним форматом аудиоданных в другой с другим форматом. Первый (входной) поток называется исходным потоком, а второй (выходной) поток называется целевым потоком. Вспомните из главы 2 «Обзор пакета Samples», что AudioInputStream содержит AudioFormat, а AudioFormat, в свою очередь, содержит определенный тип кодирования данных, представленный объектом AudioFormat.Encoding. Формат и кодирование в исходном потоке называются исходным форматом и исходным кодированием, а те, что в целевом потоке, также называются целевым форматом и целевым кодированием.

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

Два варианта getAudioInputStream:

abstract AudioInputStream getAudioInputStream(
    AudioFormat.Encoding targetEncoding, 
    AudioInputStream sourceStream) 

а также

abstract AudioInputStream getAudioInputStream(
    AudioFormat targetFormat,
    AudioInputStream sourceStream) 

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

Типичная реализация getAudioInputStream работает, возвращая новый подкласс AudioInputStream, который оборачивается вокруг оригинала (source) AudioInputStream и применяет преобразование формата данных к своим данным всякий раз, когда вызывается метод read. Например, рассмотрим случай нового подкласса FormatConversionProvider под названием AcmeCodec, который работает с новым подклассом AudioInputStream под названием AcmeCodecStream.

Реализация второго метода getAudioInputStream класса AcmeCodec может быть:

public AudioInputStream getAudioInputStream
      (AudioFormat outputFormat, AudioInputStream stream) {
        AudioInputStream cs = null;
        AudioFormat inputFormat = stream.getFormat();
        if (inputFormat.matches(outputFormat) ) {
            cs = stream;
        } else {
            cs = (AudioInputStream)
                (new AcmeCodecStream(stream, outputFormat));
            tempBuffer = new byte[tempBufferSize];
        }
        return cs;
    }

Фактическое преобразование формата происходит в новых методах read  возвращенного AcmeCodecStream, подкласса AudioInputStream. Опять же, прикладные программы, которые обращаются к этому возвращенному AcmeCodecStream, просто работают с ним как с AudioInputStream, и им не нужно знать детали его реализации.

Все другие методы FormatConversionProvider разрешают запросы о кодировках и форматах ввода и вывода, которые поддерживает объект. Следующие четыре метода, будучи абстрактными, должны быть реализованы:

abstract AudioFormat.Encoding[] getSourceEncodings() 
abstract AudioFormat.Encoding[] getTargetEncodings() 
abstract AudioFormat.Encoding[] getTargetEncodings(
    AudioFormat sourceFormat) 
abstract  AudioFormat[] getTargetFormats(
    AudioFormat.Encoding targetEncoding, 
    AudioFormat sourceFormat) 

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

Остальные четыре метода FormatConversionProvider являются конкретными и обычно не нуждаются в переопределении:

boolean isConversionSupported(
    AudioFormat.Encoding targetEncoding,
    AudioFormat sourceFormat) 
boolean isConversionSupported(AudioFormat targetFormat, 
    AudioFormat sourceFormat) 
boolean isSourceEncodingSupported(
    AudioFormat.Encoding sourceEncoding) 
boolean isTargetEncodingSupported(
    AudioFormat.Encoding targetEncoding) 

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

Предоставление новых типов Микшеров.

Как следует из названия, MixerProvider предоставляет экземпляры микшеров. Каждый конкретный подкласс MixerProvider действует как фабрика для объектов Mixer, используемых прикладной программой. Конечно, определение нового MixerProvider имеет смысл, только если также определены одна или несколько новых реализаций интерфейса Mixer. Как и в приведенном выше примере FormatConversionProvider, где наш метод getAudioInputStream возвратил подкласс AudioInputStream, выполнивший преобразование, наш новый класс AcmeMixerProvider имеет метод getMixer, который возвращает экземпляр другого нового класса, реализующего интерфейс Mixer. Назовем последний класс AcmeMixer. В частности, если микшер реализован на оборудовании, поставщик может поддерживать только один статический экземпляр запрошенного устройства. Если это так, он должен возвращать этот статический экземпляр в ответ на каждый вызов getMixer.

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

Два других метода MixerProvider:

abstract Mixer.Info[] getMixerInfo() 

а также

boolean isMixerSupported(Mixer.Info info) 

Эти методы позволяют аудиосистеме определить, может ли этот конкретный класс провайдера создать устройство, необходимое прикладной программе. Другими словами, объект AudioSystem может перебирать все установленные MixerProviders, чтобы увидеть, какие из них, если таковые имеются, могут предоставить устройство, которое прикладная программа запросила у AudioSystem. (См. Обсуждение в разделе "Получение микшера" в главе 3, "Доступ к ресурсам аудиосистемы.") Метод getMixerInfo возвращает массив объектов, содержащих информацию о типах микшера, доступных из этого объекта поставщика. Система может передавать эти информационные объекты вместе с объектами от других поставщиков в прикладную программу.

Один MixerProvider может предоставить более одного типа микшера. Когда система вызывает метод getMixerInfo класса MixerProvider, он получает список информационных объектов, идентифицирующих различные типы микшера, поддерживаемые этим поставщиком. Затем система может вызвать MixerProvider.getMixer (Mixer.Info) для получения каждого интересующего микшера.

Ваш подкласс должен реализовать getMixerInfo, поскольку он абстрактный. Метод isMixerSupported конкретен и обычно не требует переопределения. Реализация по умолчанию просто сравнивает предоставленный Mixer.Info с каждым из них в массиве, возвращаемом getMixerInfo.

 

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