En

JazzTeam Software Development Company

Agile Java Development

Поддержка воспроизведения MP3 и WAV форматов в Java

Знакомство

Здравствуй, дорогой читатель! Хочу донести до тебя информацию о том, что данная статья не является единственным и стопроцентно правильным решением проблемы одновременного проигрывания mp3 и wav файлов. В статье всего лишь описан способ, которым получилось решить данную проблему. Я опишу шаги, которые мне пришлось сделать для реализации поставленной задачи, а так же опишу трудности, которые пришлось преодолеть, ошибки, которые пришлось совершить (я нечаянно, честное слово 🙂 ). Так же отмечу, что это моя первая статья такого рода, так что прошу не судить строго. И так, приступим...

Задача и проблематика

Передо мной была поставлена задача - сделать одновременное проигрывание mp3 и wav файлов в java-апплете. При этом звуковые файлы должны присылаться с сервера в виде потока байтов (файлы должны быть закодированы в base64, но это не имеет значения 🙂 ).

Проблема в том, что стандартными средствами java невозможно проигрывать mp3 файлы (разработка велась на java 6). Единственное подходящее стандартное средство для проигрывания mp3 - Java Media Framework, которое мне не подходило, потому как требовало дополнительной установки, а это лишние действия для пользователя.

Поиски решения

Для проигрывания mp3 нужно было найти библиотеку, главный критерий поиска - размер. Так как апплет - это приложение, которое открывается в браузере, то он постоянно загружается с сервера, соответственно размер апплета должен быть минимальным, для его быстрой загрузки.

Скитаясь по страницам гугла, я наткнулся на несколько библиотек, но самой маленькой и к тому же самой популярной из них оказался проект JLayer. Размер jar-ника в 100кб меня вполне устроил, к тому же внутри него есть ещё и конвертер, и декодер mp3, которые можно использовать… ну или удалить их, тем самым, сделав библиотеку ещё легковеснее :).

Начало реализации

Пришедшие с сервера звуковые файлы я сохранял во временную директорию, которая чистилась после закрытия апплета, с рандомным именем. Использовал я такой подход потому, что мне не было известно, какой тип файл приходит: wav или mp3.

Далее я подключил библиотеку к maven проекту апплета:

  1. <dependencies>
  2. <dependency>
  3. <groupId>javazoom</groupId>
  4. <artifactId>jlayer</artifactId>
  5. <version>1.0.1</version>
  6. </dependency>
  7. </dependencies>

Добавил данную библиотеку в jar-ник апплета с помощью maven-assembly-plugin:

  1. <build>
  2. <plugins>
  3. <plugin>
  4. <artifactId>maven-assembly-plugin</artifactId>
  5. <configuration>
  6. <appendAssemblyId>false</appendAssemblyId>
  7. <descriptors>
  8. <descriptor>assembly.xml</descriptor>
  9. </descriptors>
  10. </configuration>
  11. <executions>
  12. <execution>
  13. <id>make-assembly</id>
  14. <phase>package</phase>
  15. <goals>
  16. <goal>single</goal>
  17. </goals>
  18. </execution>
  19. </executions>
  20. </plugin>
  21. </plugins>
  22. </build>

assembly.xml

  1. <assembly xmlns="http://maven.apache.org/plugins/maven-assembly-
  2. plugin/assembly/1.1.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/plugins/maven-
  5. assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/
  6. assembly-1.1.0.xsd">
  7. <id>with-dependencies</id>
  8. <formats>
  9. <format>jar</format>
  10. </formats>
  11. <includeBaseDirectory>false</includeBaseDirectory>
  12. <dependencySets>
  13. <dependencySet>
  14. <unpack>true</unpack>
  15. <useTransitiveDependencies>false
  16. </useTransitiveDependencies>
  17. <excludes>
  18. <exclude>org.easytesting:*</exclude>
  19. <exclude>junit:*</exclude>
  20. <exclude>org.apache.maven.plugins:maven-surefire-
  21. report-plugin</exclude>
  22. <exclude>net.sourceforge.jexcelapi:*</exclude>
  23. </excludes>
  24. </dependencySet>
  25. </dependencySets>
  26. </assembly>

Эта библиотека оказалось очень простой в использовании, нужно было всего лишь создать объект класса javazoom.jl.player.Player и далее вызвать у него метод play().

  1. FileInputStream stream = new FileInputStream(soundFile.getPath());
  2. Player player = new Player(stream);
  3. player.play();

Отмечу, что проигрывание звука происходит в том же потоке, в котором запущен метод play(). Соответственно поток, в котором вызван данный метод блокируется во время проигрывания звука. Это позволяет удобно отслеживать окончание проигрывания звука, если необходимо выполнять какие-либо действия после проигрывания звука.

Для тестирования я использовал mp3 файл с записанным “мяуканьем” котёнка и wav файл с записанным “гавканьем” собаки. Радости моей не было предела, когда использовав вышеприведённый код я услышал столь ожидаемое заветное “мяуканье”. Но радоваться пришлось не долго, так как “гавканья” я не услышал :(. Соответственно библиотека не может воспроизводить wav файлы . Пришлось обратиться к гуглу…

Пляски с бубном

Изначально я подумал: “Как одна из самых популярных библиотек по проигрыванию mp3 на java не может поддерживать wav?”. Но после нескольких минут поисков в гугле я понял, что мой вопрос останется без ответа… Единственное, что в данной ситуации могло помочь - это перекодирование wav в mp3, что средствами JLayer можно было сделать. Но от этого решения я отказался сразу, так как перекодирование - процесс довольно ресурсозатратный, а для апплета, который должен работать на самых слабых машинах, это было недопустимо.

Я решил всё же не отказываться от библиотеки, а использовать для проигрывания wav стандартные средства java.

  1. AudioClip audioClip = applet.getAudioClip(getMediaFile().toURL());
  2. audioClip.play();

Данный код отлично проигрывает wav файлы. Правда, процесс проигрывания менее контролируемый, чем при использовании библиотеки, так как звук в данном случае проигрывается в отдельном потоке, и для определения “окончания проигрывания” звука придётся использовать какие-нибудь другие способы. В эту сторону я больше не размышлял, так как для wav-файлов действий после проигрывания у меня не было.

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

Кто есть кто?

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

Я решил воспользоваться декодером из библиотеки JLayer.

  1. FileInputStream stream = new FileInputStream(audioFile.getPath());
  2. Header header = new Bitstream(stream).readFrame();
  3. if (header.toString().contains("Layer III")) {
  4. final Player player = new Player(stream);
  5. // play mp3
  6. player.play();
  7. } else {
  8. // play wav file
  9. }

Решение тоже не совсем красивое, но, как мне казалось на тот момент, верное. Попробовав такую реализацию получилось отделить wav от mp3. Но начав тестировать данную проверку с разными mp3-файлами выяснилось, что она работает не всегда корректно. Возможно, дело в различных mp3-кодеках… в это я вдаваться не стал, а решил для экономии времени реализовать способ, который планировал использовать изначально.

Открыв mp3 файл я не обнаружил там ничего уникального, возможно плохо искал, а вот в wav почти в самом начале файла в любом случае пишется слово “WAVE” - это я и решил использовать. Проверка получилась проще, чем предполагалось:

  1. soundString.substring(0, 50).contains("WAVE")

Так как слово “WAVE” находится в начале файла, то я решил не проверять его наличие во всей “звуковой строке”, а ограничился 50-ю первыми символами.

Теперь у меня было всё, чтобы сделать окончательную реализацию…

Окончательная реализация

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

  1. public static void playSound(File soundFile) throws IOException,
  2. JavaLayerException {
  3. if (isWave(FileUtils.readFileToString(soundFile))) {
  4. //mp3 file playes
  5. final FileInputStream stream = new FileInputStream(
  6. soundFile.getPath());
  7. final Player player = new Player(stream);
  8. player.play();
  9. } else {
  10. // wav file playes
  11. AudioClip audioClip = AppletManager.applet.getAudioClip(
  12. soundFile.toURL());
  13. audioClip.play();
  14. }
  15. }
  16.  
  17. private static boolean isWave(String fileString) {
  18. return fileString.substring(0, 50).contains("WAVE");
  19. }

В итоге конечно получилось грубовато, но зато действенно. Для получения строки из файла я использую библиотеку commons-io, но можно сделать это вручную, в гугле по этой теме очень много информации.

А может всё было зря?

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

Оказалось, что в Java 7 есть поддержка mp3 файлов:

  1. String bip = "example.mp3";
  2. Media hit = new Media(bip);
  3. MediaPlayer mediaPlayer = new MediaPlayer(hit);
  4. mediaPlayer.play();

Можно было выдрать из JMF библиотечку mp3plugin.jar, и использовать её для проигрывания. Раньше она была доступна для свободной загрузки на сайте java, теперь же oracle просит скачать весь JMF, но ведь нам это не помеха? :). Весит библиотечка даже меньше, чем JLayer, всего 80кб. Возможно, этот метод был бы даже лучше описанного мною в данной статье, но что сделано - то сделано.

Закончим…

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

, , , ,

One thought on “Поддержка воспроизведения MP3 и WAV форматов в Java
  • Хоротьян says:

    private static boolean isWave(String fileString) {
    return fileString.substring(0, 50).contains(“WAVE”);
    } Вы решили весь wav загрузить в память? А если он гига так на 3 этот ваш wav. Используйте InputStream и read.

Leave a Reply

Your email address will not be published. Required fields are marked *