MP3:对于任何给定的字节位置,以毫秒为单位获取位置的方法?

时间:2011-09-01 08:42:04

标签: java mp3 audio-streaming

我创建了一个servlet,它从客户端请求的任何给定字节位置开始返回一个流(来自MP3文件)。这允许客户端在任何给定的字节位置立即开始播放,而无需进行任何本地搜索。

现在,我有一个可视化进度的滑块。我正在使用当前字节位置来更新滑块。但是,我也想在几秒钟内显示当前位置。

这要求服务器可以将当前位置(以字节为单位)转换为以毫秒为单位的位置。然后,服务器可以以毫秒为单位提供流开始位置作为响应头。

有没有人有过如何以字节为单位计算当前位置的经验?

更新

从评论中可以清楚地看出,没有精确的方法可以将转换字节转换为毫秒,反之亦然,无需将MP3文件解码到该点(以毫秒或字节为单位),然后确定已经有多少字节读取或播放的毫秒数。但是,考虑到服务器,例如100个用户同时请求文件,这种方法显然不会表现得太好。然后,服务器必须将MP3文件解码到请求的位置,然后从该点返回流。我选择了与性能交换精度,并采用了一种方法,它给了我近似的位置,这对于一个只是为了播放音轨的播放器来说已经足够好了(而不是将音频与其他音源同步到毫秒级)。

我所做的是玩家(在客户端)现在只关心毫秒(MS)。也就是说,进度条的当前值和最大值是MS,而不是第一次执行的字节。要从任何给定位置开始播放,客户端请求服务器(servlet)提供从MS中任何给定位置开始的音频流。 servlet使用JAudioTagger获取有关文件的详细信息,然后对MS位置对应的字节位置进行近似计算。我已经测试过了,它与CBR(恒定比特率)文件配合得很好。该方法不适用于VBR(可变比特率)文件,因为帧大小可能会有所不同。请注意,这只是一个播放音乐文件的播放器。它并不打算将音频与其他媒体同步到MS。下面提供了从MS转换为字节的代码剪切。

更新(2012年7月3日)

servlet已经使用下面的代码运行了很长一段时间,而且工作得非常好。已经播放了数以千计的MP3,从ms到字节的近似值可以正常工作。

更新(2017年1月3日)

servlet仍然使用完全相同的代码运行,并且已经很好地播放了数十万个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 个答案:

答案 0 :(得分:4)

MP3文件或流是一系列帧,其中每一帧由一个mp3标题和一个mp3数据部分组成。

标题和数据部分信息用于创建“听起来像原始”的音频帧。

因此,mp3文件或流中的位置无法转换为结果音频流中的时间戳。

答案 1 :(得分:2)

它不起作用。即使你的MP3文件是恒定比特率编码,哪个字节位置编码流中的第二个是可变的。 (可以肯定的是,VBR编码比CBR更多更多变量,但都是相同的。)可靠地获取此信息的唯一方法是实际解码流到那一点,你可能不想这样做。这就是为什么即使像XMMS这样的专业玩家在跳过时也无法可靠地更新滑块的原因。

答案 2 :(得分:0)

“最困难”的方式是循环。在循环内,您调用play()player.play()方法,然后使线程进入睡眠状态,以便使歌曲可以完成播放而无需在其上开始播放下一首歌曲,但是例如,您必须估算一首7 mb大小的歌曲的持续时间约为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,以便每首歌曲都采用其长度。稍微注意一下,效果就足够了。