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

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

В главе 2 "Обзор пакета Samples" представлены основные классы, относящиеся к звуковым файлам и форматам аудиоданных. В качестве отзыва:

  • Поток аудиоданных, который может быть считан или записан в файл, представлен объектом AudioInputStream. (AudioInputStream наследуется от java.io.InputStream.)
  • Формат этих аудиоданных представлен объектом AudioFormat.

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

  • Существует несколько хорошо известных стандартных форматов звуковых файлов, таких как WAV, AIFF или AU. Различные типы звуковых файлов имеют различные структуры для хранения аудиоданных, а также для хранения описательной информации об аудиоданных. Формат звукового файла представлен в Java Sound API объектом AudioFileFormat. AudioFileFormat включает в себя объект AudioFormat для описания формата аудиоданных, хранящихся в файле, а также включает в себя информацию о типе файла и длине данных в файле.
  • Класс AudioSystem предоставляет методы для (1) хранения потока аудиоданных из AudioInputStream в аудиофайл определенного типа (другими словами, записи файла), (2) извлечения потока аудиобайтов (AudioInputStream) из аудиофайла (другими словами, чтения файла) и (3) преобразования аудиоданных из одного формата данных в другой. Настоящая глава, разделенная на три части, объясняет эти три вида деятельности.

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

Чтение Звуковых Файлов.

Класс AudioSystem предоставляет два типа услуг чтения файлов:

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

Первый из них предоставляется тремя вариантами метода getAudioFileFormat:

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

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

Второй тип функциональности чтения файлов предоставляется этими методами AudioSystem:

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

Эти методы предоставляют вам объект (AudioInputStream), который позволяет вам читать аудиоданные файла, используя один из методов чтения AudioInputStream. Мы сейчас увидим пример.

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

  1. Получите объект AudioInputStream из файла.
  2. Создайте массив байтов, в котором вы будете хранить последовательные порции данных из файла.
  3. Повторно считывать байты из входного аудиопотока в массив. На каждой итерации делайте что-нибудь полезное с байтами в массиве (например, вы можете воспроизводить их, фильтровать, анализировать, отображать или записывать в другой файл).

В следующем примере кода описаны эти шаги.

int totalFramesRead = 0;
File fileIn = new File(somePathName);
// somePathName - это уже существующая строка,
// значение которой было основано на выборе пользователя.
try {
  AudioInputStream audioInputStream = 
    AudioSystem.getAudioInputStream(fileIn);
  int bytesPerFrame = 
    audioInputStream.getFormat().getFrameSize();
  // Установите произвольный размер буфера 1024 кадра.
  int numBytes = 1024 * bytesPerFrame; 
  byte[] audioBytes = new byte[numBytes];
  try {
    int numBytesRead = 0;
    int numFramesRead = 0;
    // Try to read numBytes bytes from the file.
    while ((numBytesRead = 
      audioInputStream.read(audioBytes)) != -1) {
      // Подсчитайте количество фактически прочитанных кадров.
      numFramesRead = numBytesRead / bytesPerFrame;
      totalFramesRead += numFramesRead;
      // Здесь сделайте что-нибудь полезное с аудиоданными,
      // которые теперь находятся в массиве audioBytes ...
    }
  } catch (Exception ex) { 
    // Обработайте ошибку ...
  }
} catch (Exception e) {
  // Обработайте ошибку ...
}

Давайте посмотрим, что происходит в приведенном выше примере кода. Сначала внешнее предложение try создает экземпляр объекта AudioInputStream через вызов метода AudioSystem.getAudioInputStream (File). Этот метод прозрачно выполняет все тесты, необходимые для определения, действительно ли указанный файл является звуковым файлом того типа, который поддерживается Java Sound API. Если проверяемый файл (fileIn в этом примере) не является звуковым файлом или является звуковым файлом неподдерживаемого типа, генерируется исключение UnsupportedAudioFileException. Такое поведение удобно тем, что программисту приложения не нужно беспокоиться ни о тестировании атрибутов файлов, ни о соблюдении каких-либо соглашений об именах файлов. Вместо этого метод getAudioInputStream берет на себя весь низкоуровневый синтаксический анализ и проверку, которые необходимы для проверки входного файла.

Затем внешнее предложение try создает массив байтов audioBytes произвольной фиксированной длины. Мы следим за тем, чтобы его длина в байтах равнялась целому количеству кадров, чтобы мы не прочитали только часть кадра или, что еще хуже, только часть выборки. Этот байтовый массив будет служить буфером для временного хранения фрагмента аудиоданных, считываемых из потока. Если бы мы знали, что будем читать только очень короткие звуковые файлы, мы могли бы сделать этот массив той же длины, что и данные в файле, получая длину в байтах из длины в кадрах, возвращаемой методом getFrameLength AudioInputStream. (На самом деле, мы, вероятно, просто использовали бы вместо этого объект Clip.) Но чтобы избежать нехватки памяти в общем случае, мы вместо этого читаем файл фрагментами, по одному буферу за раз.

Внутреннее предложение try содержит цикл while, в котором мы считываем аудиоданные из AudioInputStream в массив байтов. Вы должны добавить код в этот цикл для обработки аудиоданных в этом массиве любым способом, который подходит для нужд вашей программы. Если вы применяете какую-либо обработку сигналов к данным, вам, вероятно, потребуется дополнительно запросить AudioFormat AudioInputStream, чтобы узнать количество бит на выборку и так далее.

Обратите внимание, что метод AudioInputStream.read (byte []) возвращает количество прочитанных байтов, а не количество выборок или кадров. Этот метод возвращает -1, когда больше нет данных для чтения. Обнаружив это условие, мы выходим из цикла while.

Запись звуковых файлов.

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

Следующий метод AudioSystem создает дисковый файл указанного типа. Файл будет содержать аудиоданные в указанном AudioInputStream:

static int write(AudioInputStream in, 
  AudioFileFormat.Type fileType, File out)

Обратите внимание, что вторым аргументом должен быть один из типов файлов, поддерживаемых системой (например, AU, AIFF или WAV), в противном случае метод write вызовет исключение IllegalArgumentException. Чтобы избежать этого, вы можете проверить, может ли конкретный AudioInputStream быть записан в конкретный тип файла, вызвав этот метод AudioSystem:

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

который вернет true, только если поддерживается конкретная комбинация.

В более общем плане вы можете узнать, какие типы файлов может записывать система, вызвав один из этих методов AudioSystem:

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

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

В следующем отрывке демонстрируется один метод создания выходного файла из AudioInputStream с использованием метода write, упомянутого выше.

File fileOut = new File(someNewPathName);
AudioFileFormat.Type fileType = fileFormat.getType();
if (AudioSystem.isFileTypeSupported(fileType, 
    audioInputStream)) {
  AudioSystem.write(audioInputStream, fileType, fileOut);
}

Первый оператор выше создает новый объект File, fileOut, с путем, указанным пользователем или программой. Второй оператор получает тип файла из уже существующего объекта AudioFileFormat с именем fileFormat, который мог быть получен из другого звукового файла, такого как тот, который был прочитан в разделе "Чтение звуковых файлов" этой главы. (Вместо этого вы можете указать любой поддерживаемый тип файла, который вам нужен, вместо того, чтобы получать тип файла из другого места. Например, вы можете удалить второй оператор и заменить два других вхождения fileType в приведенном выше коде на AudioFileFormat.Type.WAVE.)

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

Наконец, поток, тип файла и выходной файл передаются методу AudioSystem.write для достижения цели записи файла.

Преобразование файлов и форматов данных.

Вспомните из раздела "Что такое форматированные аудиоданные?" в главе 2, "Обзор пакета Samples," , говорится, что Java Sound API различает форматы аудиофайлов и форматы аудиоданных. Эти двое более или менее независимы. Грубо говоря, формат данных относится к способу, которым компьютер представляет каждую точку необработанных данных (образец), в то время как формат файла относится к организации звукового файла, хранящегося на диске. Каждый формат звукового файла имеет особую структуру, которая определяет, например, информацию, хранящуюся в заголовке файла. В некоторых случаях формат файла также включает структуры, содержащие метаданные в той или иной форме, в дополнение к фактическим «необработанным» аудиосэмплам. В оставшейся части этой главы рассматриваются методы Java Sound API, которые позволяют выполнять различные преобразования формата файлов и форматов данных.

Преобразование из одного формата файла в другой.

В этом разделе рассматриваются основы преобразования типов аудиофайлов в Java Sound API. Мы снова представляем гипотетическую программу, цель которой на этот раз - прочитать аудиоданные из произвольного входного файла и записать их в файл типа AIFF. Конечно, входной файл должен быть такого типа, который система способна читать, а выходной файл должен быть такого типа, который система способна записывать. (В этом примере мы предполагаем, что система способна записывать файлы AIFF.) Программа примера не выполняет никакого преобразования формата данных. Если формат данных входного файла не может быть представлен как файл AIFF, программа просто уведомляет пользователя об этой проблеме. С другой стороны, если входной звуковой файл уже является файлом AIFF, программа уведомляет пользователя о том, что его не нужно преобразовывать.

Следующая функция реализует только что описанную логику:

public void ConvertFileToAIFF(String inputPath, 
  String outputPath) {
  AudioFileFormat inFileFormat;
  File inFile;
  File outFile;
  try {
    inFile = new File(inputPath);
    outFile = new File(outputPath);     
  } catch (NullPointerException ex) {
    System.out.println("Error: one of the 
      ConvertFileToAIFF" +" parameters is null!");
    return;
  }
  try {
    // запрос типа файла 
    inFileFormat = AudioSystem.getAudioFileFormat(inFile);
    if (inFileFormat.getType() != AudioFileFormat.Type.AIFF) 
    {
      // inFile - это не AIFF, поэтому попробуем преобразовать его.
      AudioInputStream inFileAIS = 
        AudioSystem.getAudioInputStream(inFile);
      inFileAIS.reset(); // перемотка
      if (AudioSystem.isFileTypeSupported(
             AudioFileFormat.Type.AIFF, inFileAIS)) {
         // inFileAIS можно конвертировать в AIFF.
         // поэтому напишите AudioInputStream
         // в выходной файл.
         AudioSystem.write(inFileAIS,
           AudioFileFormat.Type.AIFF, outFile);
         System.out.println("Successfully made AIFF file, "
           + outFile.getPath() + ", from "
           + inFileFormat.getType() + " file, " +
           inFile.getPath() + ".");
         inFileAIS.close();
         return; // Все сделано сейчас
       } else
         System.out.println("Предупреждение: AIFF преобразование в " 
           + inFile.getPath()
           + " is not currently supported by AudioSystem.");
    } else
      System.out.println("Входной файл " + inFile.getPath() +
          " is AIFF." + " Преобразование не требуется.");
  } catch (UnsupportedAudioFileException e) {
    System.out.println("Error: " + inFile.getPath()
        + " не поддерживается тип аудиофайла!");
    return;
  } catch (IOException e) {
    System.out.println("Ошибка: сбой при попытке чтения " 
      + inFile.getPath() + "!");
    return;
  }
}

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

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

  • AudioSystem.getAudioFileFormat: используется здесь, чтобы определить, является ли входной файл уже типом AIFF. Если это так, функция быстро возвращается; в противном случае попытка конвертации продолжается.
  • AudioSystem.isFileTypeSupported: указывает, может ли система записать файл указанного типа, содержащий аудиоданные из указанного AudioInputStream. В нашем примере этот метод возвращает true, если указанный входной аудиофайл можно преобразовать в формат аудиофайла AIFF. Если AudioFileFormat.Type.AIFF не поддерживается, ConvertFileToAIFF выдает предупреждение о том, что входной файл не может быть преобразован, а затем возвращается.
  • AudioSystem.write: здесь используется для записи аудиоданных из AudioInputStream inFileAIS в выходной файл outFile.

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

Преобразование аудио между различными форматами данных.

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

В предыдущем разделе мы читали данные из файла произвольного типа и сохраняли их в файле AIFF. Обратите внимание, что, хотя мы изменили тип файла, используемого для хранения данных, мы не изменили формат самих аудиоданных. (Наиболее распространенные типы аудиофайлов, включая AIFF, могут содержать аудиоданные различных форматов.) Таким образом, если исходный файл содержал аудиоданные с качеством компакт-диска (размер выборки 16 бит, частота дискретизации 44,1 кГц и два канала), то и наш выходной файл AIFF.

Теперь предположим, что мы хотим указать формат данных выходного файла, а также тип файла. Например, возможно, мы сохраняем много длинных файлов для использования в Интернете и беспокоимся об объеме дискового пространства и времени загрузки, необходимых для наших файлов. Мы можем создать файлы AIFF меньшего размера, которые содержат данные с более низким разрешением, например, данные с размером выборки 8 бит, частотой дискретизации 8 кГц и одним каналом.

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

Основной метод преобразования аудиоданных снова находится в классе AudioSystem. Этот метод является вариантом getAudioInputStream:

AudioInputStream getAudioInputStream(AudioFormat format, AudioInputStream stream)

Эта функция возвращает AudioInputStream, который является результатом преобразования потока AudioInputStream с использованием указанного формата format AudioFormat. Если преобразование не поддерживается AudioSystem, эта функция вызывает исключение IllegalArgumentException.

Чтобы этого избежать, мы можем сначала проверить, может ли система выполнить необходимое преобразование, вызвав этот метод AudioSystem:

boolean isConversionSupported(AudioFormat targetFormat, AudioFormat sourceFormat)

В этом случае мы передадим stream.getFormat () в качестве второго аргумента.

Чтобы создать конкретный объект AudioFormat, мы используем один из двух конструкторов AudioFormat, показанных ниже, либо

 AudioFormat(float sampleRate, int sampleSizeInBits, int channels, boolean signed, boolean bigEndian)

который создает AudioFormat с линейным кодированием PCM и заданными параметрами, или

AudioFormat(AudioFormat.Encoding encoding, 
    float sampleRate, int sampleSizeInBits, int channels,
    int frameSize, float frameRate, boolean bigEndian) 

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

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

AudioFormat outDataFormat = new AudioFormat((float) 8000.0, (int) 8, (int) 1, true, false);

Поскольку приведенный выше конструктор AudioFormat описывает формат с 8-битными выборками, последний параметр конструктора, который указывает, являются ли выборки с прямым порядком байтов или с прямым порядком байтов, не имеет значения. (Большой или прямой порядок байтов является проблемой, только если размер выборки больше одного байта.)

В следующем примере показано, как мы могли бы использовать этот новый AudioFormat для преобразования AudioInputStream, inFileAIS, который мы создали из входного файла:

AudioInputStream lowResAIS;         
  if (AudioSystem.isConversionSupported(outDataFormat,   
    inFileAIS.getFormat())) {
    lowResAIS = AudioSystem.getAudioInputStream
      (outDataFormat, inFileAIS);
  }

Не имеет большого значения, куда мы вставили этот код, если это было после создания inFileAIS. Без теста isConversionSupported вызов завершится ошибкой и вызовет исключение IllegalArgumentException, если конкретное запрошенное преобразование не поддерживается. (В этом случае управление будет передано соответствующему предложению catch в нашей функции.)

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

Последним шагом для создания желаемого звукового файла AIFF с низким разрешением будет замена параметра AudioInputStream в вызове AudioSystem.write (то есть первого параметра) нашим преобразованным потоком lowResAIS следующим образом:

AudioSystem.write(lowResAIS, AudioFileFormat.Type.AIFF,   outFile);

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

Изучение доступных преобразований.

Некоторые методы AudioSystem проверяют свои параметры, чтобы определить, поддерживает ли система конкретное преобразование формата данных или операцию записи файла. (Обычно каждый метод сочетается с другим, который выполняет преобразование данных или записывает файл.) Один из этих методов запроса, AudioSystem.isFileTypeSupported, использовался в нашем примере функции ConvertFileToAIFF, чтобы определить, способна ли система записывать аудио. данные в файл AIFF. Связанный метод AudioSystem, getAudioFileTypes (AudioInputStream), возвращает полный список поддерживаемых типов файлов для данного потока в виде массива экземпляров AudioFileFormat.Type. Метод:

boolean isConversionSupported(AudioFormat.Encoding encoding, AudioFormat format) 

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

boolean isConversionSupported(AudioFormat newFormat, AudioFormat oldFormat) 

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

Эти связанные с форматом запросы помогают предотвратить ошибки при попытке выполнить преобразование формата с помощью Java Sound API.

 

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