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 проекту апплета:

<dependencies>
    <dependency>
    <groupId>javazoom</groupId>
    <artifactId>jlayer</artifactId>
    <version>1.0.1</version>
    </dependency>
</dependencies>

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

<build>
    <plugins>
        <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
            <appendAssemblyId>false</appendAssemblyId>
        <descriptors>
            <descriptor>assembly.xml</descriptor>
        </descriptors>
        </configuration>
        <executions>
            <execution>
                <id>make-assembly</id>
                <phase>package</phase>
                <goals>
                    <goal>single</goal>
                </goals>
            </execution>
        </executions>
        </plugin>
    </plugins>
</build>

assembly.xml

<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-
        plugin/assembly/1.1.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/plugins/maven-
        assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/
        assembly-1.1.0.xsd">
    <id>with-dependencies</id>
    <formats>
        <format>jar</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <dependencySets>
        <dependencySet>
            <unpack>true</unpack>
            <useTransitiveDependencies>false
            </useTransitiveDependencies>
            <excludes>
                <exclude>org.easytesting:*</exclude>
                <exclude>junit:*</exclude>
                <exclude>org.apache.maven.plugins:maven-surefire-
                report-plugin</exclude>
                <exclude>net.sourceforge.jexcelapi:*</exclude>
            </excludes>
        </dependencySet>
    </dependencySets>
</assembly>

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

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

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

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

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

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

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

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

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

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

Кто есть кто?

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

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

FileInputStream stream = new FileInputStream(audioFile.getPath());
Header header = new Bitstream(stream).readFrame();
if (header.toString().contains("Layer III")) {
    final Player player = new Player(stream);
    // play mp3
    player.play();
} else {
    // play wav file
}

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

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

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

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

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

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

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

public static void playSound(File soundFile) throws IOException, 
    JavaLayerException {
    if (isWave(FileUtils.readFileToString(soundFile))) {
        //mp3 file playes
        final FileInputStream stream = new FileInputStream(
            soundFile.getPath());
        final Player player = new Player(stream);
        player.play();
    } else {
        // wav file playes
        AudioClip audioClip = AppletManager.applet.getAudioClip(
            soundFile.toURL());
        audioClip.play();
    }
}

private static boolean isWave(String fileString) {
    return fileString.substring(0, 50).contains("WAVE");
}

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

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

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

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

String bip = "example.mp3";
Media hit = new Media(bip);
MediaPlayer mediaPlayer = new MediaPlayer(hit);
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 *