如何等到加载音频剪辑?

How to wait till an audio clip is loaded?

我是 JAVA 的新手,我正在尝试阅读剪辑。这是我的代码:

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;

public class TestClipBis {
protected static AudioFormat audioFormat;

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

// Specification of the sound to play
// No control. We assume that the sound can be played on audio system
//File soundFile = new File("chimes.wav");
File soundFile = new File("test.wav");
InputStream is = new FileInputStream(soundFile);
InputStream bufferedIn = new BufferedInputStream(is);
//AudioInputStream sound = AudioSystem.getAudioInputStream(soundFile);
AudioInputStream sound = AudioSystem.getAudioInputStream(bufferedIn);

audioFormat = sound.getFormat();
System.out.println(audioFormat);

// Loading the sound into the memory (a Clip)
DataLine.Info info = new DataLine.Info(Clip.class, sound.getFormat());
System.out.println(info);
//Clip clip = (Clip) AudioSystem.getClip();
Clip clip = (Clip) AudioSystem.getLine(info);

System.out.println("Sound frame lenght : "+sound.getFrameLength());
System.out.println("Clip FrameLength before opening : "+clip.getFrameLength());
System.out.println("Clip will open - "+info);
System.out.println("Info format : "+info.getLineClass());

// Check before this line that everything is in memory
// Yes, but how ?
clip.open(sound);
System.out.println("Clip is open");
System.out.println("Clip FrameLength after opening : "+clip.getFrameLength());
// Due to a bug in Java Sound,
// we explicitly out of the VM when the sounds stop
clip.addLineListener(new LineListener() {
  public void update(LineEvent event) {
    if (event.getType() == LineEvent.Type.STOP) {
        System.out.println("Methode de sortie");
      event.getLine().close();
      System.exit(0);
    }
  }
});


// Playing the clip
clip.start();

System.out.println("IsActive : "+clip.isActive());
//clip.close();

 }
} 

我的问题是如何确保剪辑在打开和播放之前已加载到内存中?使用上面的代码,当我打开并播放声音文件时,我有几秒钟的播放时间但从来没有播放过相同的长度,随机的,也没有完整的歌曲。

或者我应该使用剪辑以外的其他东西来播放歌曲吗?但我想 "move" 进入歌曲,而不是只从头到尾播放它。

编辑:

好的,我尝试了一些东西。首先,我试图查看 "ByteArrayOuptputStream" 是否已写入。我在循环中有一个 "println" 是的,所有内容都写好了,但它不能解决问题。

然后,当我打开剪辑时,我尝试添加参数:audioformat、bytearray、startpoint、bufferlength。没有更好的了。

然后,我注意到当声音停止时,使用了退出的方法。所以,我尝试了 "mute" 那种方法(带有注释符号)。结果不同:它读取文件但声音不稳定。当我检查 CPU 使用时,它大约是 100%。这是猜测问题所在的第一条线索吗?

我尝试制作一个循环,指示开始后的帧位置:所有帧都已读取,但声音仍然不稳定。 我还在启动方法之前和之后尝试了 thead.sleep:没有更好的了。

所以,这是我使用的代码。许多代码部分在"comment quotes"之间,因为它们是尝试,不成功...

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;

public class TestBufferedClip {
protected static AudioFormat audioFormat;
public static ByteBuffer buffer;

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

// Specification of the sound to play
// No control. We assume that the sound can be played on audio system
//File soundFile = new File("chimes.wav");
File soundFile = new File("test.wav");
FileInputStream fis = new FileInputStream(soundFile);

ByteArrayOutputStream baos = new ByteArrayOutputStream((int)soundFile.length());

byte[] buf = new byte[1024];
int n = 0;
int loop = 0;
while ((n=fis.read(buf))>=0) {
    baos.write(buf);
    buf=new byte[1024];
    System.out.println("Loop = "+loop);
    loop+=1;
}
byte[] ba = baos.toByteArray();
System.out.println("ByteArray size : "+ba.length);
ByteArrayInputStream bais = new ByteArrayInputStream(ba);

//AudioInputStream sound = AudioSystem.getAudioInputStream(soundFile);
AudioInputStream sound = AudioSystem.getAudioInputStream(bais);
audioFormat = sound.getFormat();
System.out.println(audioFormat);

// Loading the sound into the memory (a Clip)
DataLine.Info info = new DataLine.Info(Clip.class, sound.getFormat());
System.out.println("Info :"+info);
//Clip clip = (Clip) AudioSystem.getClip();
Clip clip = (Clip) AudioSystem.getLine(info);

System.out.println("Sound frame lenght : "+sound.getFrameLength());
System.out.println("Info format : "+info.getLineClass());

// Check before this line that everything is in memory
// Yes, but how ?
clip.open(audioFormat, ba, 0, ba.length);
//clip.open(sound);
System.out.println("Clip is open");
System.out.println("Clip FrameLength after opening : "+clip.getFrameLength());
// Due to a bug in Java Sound,
// we explicitly out of the VM when the sounds stop
/*
clip.addLineListener(new LineListener() {
  public void update(LineEvent event) {
    if (event.getType() == LineEvent.Type.STOP) {
        System.out.println("Methode de sortie");
      event.getLine().close();
      System.exit(0);
    }
  }
});
*/

// Playing the clip
System.out.println("Before thread sleep");
try {
    Thread.sleep(31000);
} catch (InterruptedException e){
    e.printStackTrace();
}
System.out.println("After thread sleep");

clip.start();

System.out.println("IsActive : "+clip.isActive());
/*
while (clip.isActive()==true) {
    System.out.println("Position = "+clip.getFramePosition());
}
*/
//clip.close();

}
} 

@Phil Freihofner:
在我达到 "start" 点之前,我考虑了您读取和丢弃数据的解决方案。你写了"In order to start at a point within the audio file, using a SourceDataLine, you would have to read and discard data from the audio input line until you got to the desired starting spot"。你是怎样做的 ?当我使用 "SourceDataLine" 方法时,我的启动方法是一个带有 line.write(bytes, 0, bytesreads) 的循环;将声音指向扬声器。
那么,你如何阅读和丢弃?我没有在该行中找到任何 "read" 方法。

首先将整个读入字节缓冲区。通过将文件中的所有内容复制到 ByteArrayOutputStream 来执行此操作。这样您将在内存中拥有整个媒体内容。现在,您可以将数组从 ByteArrayOutputStream.toByteArray() 包装到 ByteArrayInputStream 中,并提供该纯内存流作为音频输入流。

javax.sound.sampled 支持两个对象播放音频。您正在使用的剪辑必须完全加载到内存中才能播放。从好的方面来说,使用微秒或帧位置也可以很容易地将播放定位到从剪辑内开始。

我认为首先将声音加载到字节缓冲区没有任何好处。这是剪辑的冗余缓冲级别。我只会在你尝试做 DSP 或其他需要获取数据的事情时这样做,这超出了 Clip 内置的设置起点的能力。

如果您能够在选择剪辑之前将可能的音频选项预加载为剪辑,这可能是最好的解决方案,只要您不 运行 内存不足。

另一个播放选项是 SourceDataLine。它以每个缓冲区为基础读取和回放文件。因此,它可以比未加载的剪辑启动得更快(无需先将整个文件加载到内存中)。但是,一旦 Clip 被预加载,Clip 将播放而无需重复加载文件。

为了从音频文件中的某个点开始,使用 SourceDataLine,您必须从音频输入行读取并丢弃数据,直到到达所需的起点。您可以通过计算帧来做到这一点(格式会告诉您每帧的字节数)。这种读取和丢弃会稍微扰乱计时,但我的经验是读取和丢弃音频数据比播放快几个数量级,因为播放涉及阻塞队列以将输出保持在所需的帧速率。

查看 Java Sound Tutorials 了解更多信息,其中包括 Clip 和 SourceDataLine API 的链接。

这里是加载剪辑的例子:

File soundFile = new File("test.wav");
AudioInputStream sound = AudioSystem.getAudioInputStream(soundFile);
DataLine.Info info = new DataLine.Info(Clip.class, sound.getFormat());
Clip clip = (Clip) AudioSystem.getLine(info);
clip.open(sound);

来自 test.wav 的数据现在应该在 RAM 中。您使用字节缓冲区和缓冲行所拥有的所有其他东西都是不必要的。

当您准备播放时,使用 clip.setMicrosecondPosition(长毫秒)将您的声音设置为从所需位置开始播放,(如果您从头开始则不需要,除非您已经播放过Clip,在这种情况下,位置将是您停止时的位置)。然后使用 clip.start() 开始播放。

重要说明:如果程序 运行 退出,播放将提前结束。测试这一点的一种方法是在 clip.start() 之后放置一个 Thread.sleep(长毫秒)命令,其中毫秒值比剪辑的长度长。但这不是真正的解决方案,只是防止程序关闭剪辑播放线程的诊断。您应该处理让程序 运行ning 来自主线程,而不是带有音频播放的线程。