MP3:一种以毫秒为单位获取任何给定字节位置的方法



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

现在,我有一个滑动条来显示进度。我使用当前字节位置来更新滑块。但是,我还想以秒为单位显示当前位置。

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

有没有人有经验,如何计算当前的位置在字节到毫秒?

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

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

UPDATE (July 3, 2012)

servlet已经用下面的代码运行了很长一段时间了,一切都运行得很好。成千上万的MP3已经被播放过了,从毫秒到字节的近似效果很好。

UPDATE(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;
    }
}

一个MP3文件或流是一个帧序列,其中每一帧由一个MP3报头和一个MP3数据部分组成。

报头和数据部分信息用于创建一个"听起来像原版"的音频帧。

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

不是这样的。即使您的MP3文件是恒定比特率编码,哪个字节位置编码流中的哪一秒也是可变的。(可以肯定的是,VBR编码使它比CBR具有更多变量,但都是一样的。)可靠地获得此信息的唯一方法是对流进行解码,直到那个点为止,这可能不是您想要做的。这就是为什么即使是专业播放器(如XMMS)也不能在跳过滑块时可靠地更新滑块的原因。

"最难"的方法是做一个循环。在循环中,你调用play()player.play()方法,然后让线程休眠足够的毫秒,这样歌曲就可以结束,而不会开始下一首歌曲,但你必须估计,例如,7mb大小的歌曲大约持续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(路径路径),然后一个变量int长度=数组。长度以便于每首歌取其长度。我展示的这个方法不是特别好,但只要稍加注意,它就足够好了。

最新更新