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

问题描述

它不是那样工作的。即使您的MP3文件是恒定比特率编码的,哪个字节位置编码流中的哪一秒也是可变的。(当然,VBR编码使得很多 比CBR变量,但都是一样的。)可靠地获取这些信息的唯一方法是实际数据流解码,最高到这一点,你可能不希望做。这就是为什么当您跳来跳去时,甚至XMMS之类的专业播放器也无法可靠地更新滑块的原因。

解决方法

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

现在,我有一个滑块,可以直观地看到进度。我正在使用当前字节位置来更新滑块。但是,我也想以秒为单位显示当前位置。

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

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

更新

从注释中可以明显看出,没有精确的方法可以将字节转换为毫秒,反之亦然,而无需将MP3文件解码到一定程度(以毫秒或字节为单位),然后确定已读取的字节数或毫秒为单位演奏过的
但是,考虑到一台服务器(例如100个用户将同时请求文件)的服务器,这种方法显然效果不佳。然后,服务器将必须解码MP3文件直到请求的位置,然后从该点返回流。我选择了以性能来交换精度,并采取了一种方法,该方法可以为我提供大概的位置,并且对播放器来说已经足够了,其目的只是播放音轨(而不将音频与其他信号源同步到毫秒)。

我所做的是,播放器(在客户端)现在仅关心毫秒(MS)。也就是说,进度条的当前值和最大值以MS为单位,而不是字节数(如其最初那样)。为了从任何给定位置开始播放,客户端请求服务器(servlet)提供从MS中任何给定位置开始的音频流。servlet使用JAudioTagger获取有关文件的详细信息,然后对MS位置对应的字节位置进行近似计算。我已经对其进行了测试,它可以与CBR(恒定比特率)文件一起很好地工作。该方法不适用于VBR(可变比特率)文件,因为帧大小可能会有所不同。请注意,这只是一个播放器,用于播放音乐文件。并非旨在将音频与其他媒体同步到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;
    }

}

猜你在找的技术问答相关文章

如何检查配对的蓝牙设备是打印机还是扫描仪(Android)
是否允许实体正文进行HTTP DELETE请求?
如何将ZipInputStream转换为InputStream?
java.util.logging Java 8中的变量
PowerMockito.doReturn返回null
Java中的RESTful调用
Swing / Java:如何正确使用getText和setText字符串
特殊字符和重音字符
Android Studio中的ndk.dir错误
错误“找不到主类”