MP3: способ получить позицию в миллисекундах для любой данной позиции байта? - PullRequest
4 голосов
/ 01 сентября 2011

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

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

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

Кто-нибудь знает, как можно вычислить текущую позицию в байтах в миллисекундах?

UPDATE

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

Что я сделал, так это то, что игрок (на стороне клиента) теперь заботится только о миллисекундах (мс). То есть текущее значение и максимальное значение индикатора выполнения указывается в MS, а не в байтах, как это было сначала. Чтобы начать воспроизведение с любой заданной позиции, клиент запрашивает у сервера (сервлета) предоставить аудиопоток, начинающийся с любой заданной позиции в MS. Сервлет использует JAudioTagger для получения подробной информации о файле, а затем производит приблизительный расчет относительно того, какой позиции байта соответствует позиция MS. Я проверил это, и он хорошо работает с файлами CBR (постоянный битрейт). Этот подход не будет работать с файлами VBR (переменная скорость передачи данных), поскольку размер кадра может варьироваться. Обратите внимание, что это просто плеер, целью которого является воспроизведение музыкальных файлов. Он не предназначен для синхронизации звука с некоторыми другими носителями вплоть до MS. Отрезанный код, который преобразует из MS в байты, представлен ниже.

ОБНОВЛЕНИЕ (3 июля 2012 г.)

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

ОБНОВЛЕНИЕ (3 января 2017 г.)

Сервлет все еще работает с тем же самым кодом, и сотни тысяч MP3 были воспроизведены великолепно. За время производства не было подано ни одной жалобы относительно воспроизведения и времени.

/**
 * Returns the approximate byte position for any given position in
 * milliseconds.
 *
 * http://www.java2s.com/Open-Source/Android/Mp3/needletagger/org/jaudiotagger/audio/mp3/MP3AudioHeader.java.htm
 * http://www.autohotkey.com/forum/topic29420.html
 *
 * @param   file the <code>File</code> for which the byte position for the
 *          provided position in milliseconds is to be returned.
 * @param   ms a <code>long</code> being the position in milliseconds for
 *          which the corresponding byte position is to be returned.
 * @return  a <code>long</code> being the byte position, or <b>-1</b> if the
 *          position in bytes could not be obtained.
 */
public static long getApproximateBytePositionForMilliseconds(File file, long ms) {

    long bytePosition = -1;

    try {

        AudioFile audioFile = AudioFileIO.read(file);
        AudioHeader audioHeader = audioFile.getAudioHeader();

        if (audioHeader instanceof MP3AudioHeader) {
            MP3AudioHeader mp3AudioHeader = (MP3AudioHeader) audioHeader;
            long audioStartByte = mp3AudioHeader.getMp3StartByte();
            long audioSize = file.length() - audioStartByte;
            long frameCount = mp3AudioHeader.getNumberOfFrames();
            long frameSize = audioSize / frameCount;

            double frameDurationInMs = (mp3AudioHeader.getPreciseTrackLength() / (double) frameCount) * 1000;
            double framesForMs = ms / frameDurationInMs;
            long bytePositionForMs = (long) (audioStartByte + (framesForMs * frameSize));
            bytePosition = bytePositionForMs;
        }

        return bytePosition;

    } catch (Exception e) {
        return bytePosition;
    }

}

Ответы [ 3 ]

4 голосов
/ 01 сентября 2011

Файл MP3 или поток - это последовательность кадров, где каждый кадр состоит из заголовка mp3 и части данных mp3.

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

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

2 голосов
/ 01 сентября 2011

Так не работает. Даже если ваш файл MP3 закодирован с постоянной скоростью передачи, какая позиция байта кодирует, какая секунда в потоке является переменной. (Конечно, VBR-кодирование делает ее переменной на больше , чем CBR, но все равно.) Единственный способ надежно получить эту информацию - фактически декодировать поток до той точки, которую вы, вероятно, не хочу делать Вот почему даже профессиональные игроки, такие как XMMS, не могут надежно обновить слайдер, когда вы пропускаете.

0 голосов
/ 22 июля 2018

«Самый сложный» способ - сделать цикл. Внутри цикла вы вызываете свой метод play() или player.play() и после этого укладываете нить в спящий режим на достаточное количество миль, чтобы песня могла закончиться, не запуская следующую песню поверх нее, но вы должны оценить, например, песню с 7 Мб Размер приблизительно равен 3 минутам, поэтому вы должны спать в этом месте. Длина вашей песни определяется как Files.readAllBytes(Path path). Примерно так:

//i put 5 for example lets say 5 songs//
//1000000 bytes is 1MB the same calculation is the same for the rest  length numbers//
for(int i=0; i<5; i++){
   play();
   if(length>1000000 && length<7500000){
     try{
        Thread.sleep(milis);
     }
     catch(Exception e){
        e.printStackTrace;
}

Вы должны создать byte [] array = Files.readAllBytes (Path path), а затем переменную int length = array.length, чтобы каждая песня имела свою длину. Этот метод, который я показываю, не очень хорош, но с немного внимания, он работает достаточно хорошо.

...