将InputStream对象传递给Java Sound API失败

4
它可以使用传递给AudioSystem#getAudioFileFormat的File对象,但是为什么在下面使用InputStream对象会失败? 有什么建议吗?
import java.io.*;
import javax.sound.sampled.*;

public class Test {

    public static void main(String[] args) throws Exception {

        AudioSystem.getAudioFileFormat(new File(
                "myaudio.wav"));
        AudioSystem.getAudioFileFormat(new FileInputStream(
                "myaudio.wav"));

    }
}

输出:

Exception in thread "main" java.io.IOException: mark/reset not supported
    at java.io.InputStream.reset(InputStream.java:330)
    at com.sun.media.sound.WaveFileReader.getAudioFileFormat(WaveFileReader.java:88)
    at javax.sound.sampled.AudioSystem.getAudioFileFormat(AudioSystem.java:985)
    at Test.main(Test.java:10)

@EDIT

根据 @René Jeschke@Phil Freihofner@Andrew Thompson 的答案,在 Java Sound APIIO 流 进行交互时,无论在何处都需要使用 mark/reset 作为强制协议。在我看来,应该把 buffered 流的类型而不是 raw 流的类型特别定义为要传递的参数的签名。这样做将产生比任意接受 IO 流 然后诉诸于 IOException 作为不良指标更可取的结果。

3个回答

5

FileInputStream不支持标记/重置(用于随机访问),将其包装在BufferedInputStream中以获得标记/重置支持。

编辑:为什么会这样?getAudioFileFormat遍历当前注册的每个音频文件读取器。每个读取器尝试通过读取一些特定字节来识别文件格式。因此,每个读取器都必须撤消对流的更改(以允许其他读取器在需要时重新读取所有数据)。

当您提供一个File时,这不是问题,因为每个读取器只需打开一个新流,但是当您传递一个流时,每个读取器必须标记当前流位置,执行读取操作,并在完成时将流重置为其开始状态。

这就是为什么您需要BufferedInputStream,因为它添加了一个内存缓冲区以支持标记/重置。

编辑2:因为问题是“为什么使用FileInputStream会失败?”,我没有提出任何解决方法或替代方案,而是尝试解释为什么在使用FileInputStream时会失败。还有一些情况下不能使用URL或类似物(例如考虑包含音频文件的二进制包文件)。


这个问题已经出现了多次,而且可能会继续出现。解释并不差,但我要批评一下提出的“解决方案”,因为使用BufferedInputStream会增加不必要的工作量,而我们可以通过URL加载并避免标记/重置问题。 - Phil Freihofner
当我在解决这个问题时,对问题有了更好的理解,我意识到这个答案中提供的信息非常好,我过于苛刻地给了它一个-1。我很乐意给它一个+1来抵消我的第一反应,但除非您的答案被编辑,否则我无法这样做。非常抱歉!(我不确定是否会收到通知,如果您进行了编辑,但如果您在这里留下了便条,我将收到通知,并返回纠正我的错误。) - Phil Freihofner
我已经在我的回答中添加了一个小脚注。不用在意,@PhilFreihofner;-) - Neet
谢谢您的留言。我要给你点赞,纠正了我的错误评价。是的,您回答了这个问题,而且没有提供变通方法! - Phil Freihofner

1
AudioSystem.getAudioFileFormat() 方法调用了 javax.sound.sampled.spi 包中的抽象类 "AudioFileReader"。
方法 AudioFileFormat getAudioFileFormat(InputStream stream) throws UnsupportedAudioFileException, IOException; 中的代码注释表示可能需要进行 mark/reset。
 * Obtains the audio file format of the input stream provided. The stream must
 * point to valid audio file data. In general, audio file readers may
 * need to read some data from the stream before determining whether they
 * support it. These parsers must
 * be able to mark the stream, read enough data to determine whether they
 * support the stream, and, if not, reset the stream's read pointer to its original
 * position. If the input stream does not support this, this method may fail
 * with an <code>IOException</code>.
相比之下,public abstract AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException;public abstract AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException;这两种形式并不需要这个要求。解析器的支持只需要InputStream作为参数。
对于各种重载,AudioSystem.getAudioInputStream()也有类似的注释。
当在处理音频文件时出现标记/重置错误时,首先尝试通过其URL加载文件,从而避免标记/重置要求(如Andrew Thompson所述)。如果这样做不起作用,可以使用BufferedInputStream,但对于有效的音频文件,不应该出现这种情况。
这个问题也在Oracle Bug数据库中记录为bug#7095006。

1

这对我有效(与其他Wav文件一起)。

AudioSystem.getAudioFileFormat(new File(
            "myaudio.wav").toURI().toURL());

JavaSound info. page 上的代码也使用了 URL。

如果你手头的东西既不是 File 也不是 URL,那么问题可以通过缓冲解决,就像 René Jeschke 提到的那样,或者我通常只需读取所有的 byte[] 并建立一个 ByteArrayInputStream。它是可定位的(支持 mark/reset)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接