在 Java 中播放声音

K. Macharia 2023年10月12日
  1. 在 Java 中使用 Clip 播放声音
  2. 在 Java 中使用 SourceDataLine 播放声音
在 Java 中播放声音

Java 应用程序有时会被要求播放音频文件。鉴于声音是基于时间的数据,因此必须以正确的速率传递声音才能呈现出来,以供用户感知。数据传输速率的改变会使播放的声音失真。Java Sound API 的目的是确保在播放声音时以正确的速率连续传送声音数据。它通过通过一条线播放声音以确保声音保持平滑和一致来实现此目的。Java 提供的两种行是 Clip 和 SourceDataLine。

两者之间的区别在于指定声音数据的方法。使用 Clip 时,所有声音数据在播放过程之前指定一次,而在 SourceDataLine 中,在整个播放过程中会连续写入缓冲区。这两种方法仅支持以下格式的音频文件:AIFFAIFCWAVEAUSND。在许多情况下,开发人员可以使用这两种方法中的任何一种并期望获得相同的结果,但是在某些情况下,一种方法的效果要优于另一种方法。下面是有关如何选择最有效的行以满足你的应用程序需求的说明。

  1. Clip

如果你想多次播放和播放短声音文件作为剪辑,则效果更好。当用户想要循环播放声音时,Clip 的全部功能强度是最佳体验。此功能还允许用户随机选择要开始播放的位置。Clip 速度更快,因为声音文件已加载,因此在循环播放后或用户选择移动到随机文件位置后不需要更多缓冲。

  1. SourceDataLine

当用户要在播放大型声音文件时优化内存时,此方法会更有效。当开发人员不知道将要播放的声音时,它也是最佳选择。当需要声音转换时,此方法也更有效,因为它要求声音数据在播放过程中由应用程序连续更新。

在 Java 中使用 Clip 播放声音

clip 位于 javax.sound.sampled 包中,并已在 Java 7 中引入。

在此示例中,我们将介绍开始、暂停、继续、停止、重新开始以及在任意位置开始。

以下是涉及的步骤:

  • 第一步是创建音频输入流的对象。此步骤将音频文件转换为应用程序可以使用的输入流。
  • 第二步是使用音频系统创建一个对象以供剪辑参考
  • 第三步是使用来自步骤 1 中创建的音频输入流的音频数据加载剪辑对象。
  • 下一步是设置 clip 的必需属性,例如循环,位置和微秒位置
  • 然后可以开始 clip
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

public class SoundPlayer {
  // define storage for start position
  Long nowFrame;
  Clip clip;

  // get the clip status
  String thestatus;

  AudioInputStream audioStream;
  static String thePath;

  // initialize both the clip and streams
  public SoundPlayer() throws UnsupportedAudioFileException, IOException, LineUnavailableException {
    // the input stream object
    audioStream = AudioSystem.getAudioInputStream(new File(thePath).getAbsoluteFile());

    // the reference to the clip
    clip = AudioSystem.getClip();

    clip.open(audioStream);

    clip.loop(Clip.LOOP_CONTINUOUSLY);
  }

  public static void main(String[] args) {
    try {
      // add the path to the audio file
      thePath = "add the path to the audio file here";

      SoundPlayer simpleSoundPlayer = new SoundPlayer();

      simpleSoundPlayer.play();
      Scanner scanned = new Scanner(System.in);

      // show the options
      while (true) {
        System.out.println("1. pause");
        System.out.println("2. resume");
        System.out.println("3. restart");
        System.out.println("4. stop");
        System.out.println("5. Jump to specific time");
        int a = scanned.nextInt();
        simpleSoundPlayer.gotoChoice(a);
        if (a == 4)
          break;
      }
      scanned.close();
    }

    catch (Exception e) {
      System.out.println("Experienced an error while playing sound.");
      e.printStackTrace();
    }
  }

  // operation is now as per the user's choice

  private void gotoChoice(int a)
      throws IOException, LineUnavailableException, UnsupportedAudioFileException {
    switch (a) {
      case 1:
        pause();
        break;
      case 2:
        resumeAudio();
        break;
      case 3:
        restart();
        break;
      case 4:
        stop();
        break;
      case 5:
        System.out.println("Selected time (" + 0 + ", " + clip.getMicrosecondLength() + ")");
        Scanner scan = new Scanner(System.in);
        long cc = scan.nextLong();
        jump(cc);
        break;
    }
  }

  // play
  public void play() {
    // start the clip
    clip.start();

    thestatus = "play";
  }

  // Pause audio
  public void pause() {
    if (thestatus.equals("paused")) {
      System.out.println("audio is already paused");
      return;
    }
    this.nowFrame = this.clip.getMicrosecondPosition();
    clip.stop();
    thestatus = "paused";
  }

  // resume audio
  public void resumeAudio()
      throws UnsupportedAudioFileException, IOException, LineUnavailableException {
    if (thestatus.equals("play")) {
      System.out.println("The audio is"
          + "being played");
      return;
    }
    clip.close();
    resetAudioStream();
    clip.setMicrosecondPosition(nowFrame);
    this.play();
  }

  // restart audio
  public void restart()
      throws IOException, LineUnavailableException, UnsupportedAudioFileException {
    clip.stop();
    clip.close();
    resetAudioStream();
    nowFrame = 0L;
    clip.setMicrosecondPosition(0);
    this.play();
  }

  // stop audio
  public void stop() throws UnsupportedAudioFileException, IOException, LineUnavailableException {
    nowFrame = 0L;
    clip.stop();
    clip.close();
  }

  // jump to a selected point
  public void jump(long a)
      throws UnsupportedAudioFileException, IOException, LineUnavailableException {
    if (a > 0 && a < clip.getMicrosecondLength()) {
      clip.stop();
      clip.close();
      resetAudioStream();
      nowFrame = a;
      clip.setMicrosecondPosition(a);
      this.play();
    }
  }

  // reset the audio stream
  public void resetAudioStream()
      throws UnsupportedAudioFileException, IOException, LineUnavailableException {
    audioStream = AudioSystem.getAudioInputStream(new File(thePath).getAbsoluteFile());
    clip.open(audioStream);
    clip.loop(Clip.LOOP_CONTINUOUSLY);
  }
}

该程序使用通过 AudioInputStream 从声音数据获得的音频流。这必须是第一步,因为程序仅将数据识别为要重用的流,因此必须将其重置。

操作说明

Pause:要成功暂停播放器,播放器停止后,当前帧必须存储在一个对象中。存储该帧是为了确保播放器在调用 resume 方法时可以继续播放。暂停方法使用 clip.getMicrosecondPosition() 捕获暂停点。

Resume:调用 resume 方法时,它使用存储在对象中的帧来知道从何处继续。Resume 使用 clip.setMicrosecondPosition(nowFrame) 将音频流重置为调用暂停方法时的位置。

Stopstop 方法关闭并停止剪辑。调用此方法时,用户之前无法恢复其位置,因为未存储框架。这是暂停和停止之间的技术区别。

始终建议在关闭程序之前,关闭打开的流。使用上述程序时,通过在再次播放之前停止剪辑来进行适当的检查。这样可以确保声音保持一致,并在进行播放时有效地利用资源。通常,在 Java 中,在这种情况下,音频流在重设之前不能重用。在重用之前未能重置它会导致程序出现错误。

在 Java 中使用 SourceDataLine 播放声音

SourceDataLine 可在 javax.sound.sampled.SourceDataLine 中找到。为了实现 SourceDataLine 声音播放,我们遵循以下步骤。

  • 第一步是创建音频输入流的对象。此步骤将音频文件转换为应用程序可以使用的输入流。
  • 第二步是使用 AudioSystem.getLine() 方法打开一行。
  • 第三步是重复读取在步骤 1 中创建的音频输入流的指定块,并将其转发到 SourceDataLine 的缓冲区。重复此过程,直到音频流结束。

  • 读取和缓冲区完成后,通过关闭该行来释放资源。
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

public class simpleSoundPlayer {
  // defining the byte buffer
  private static final int BUFFER_SIZE = 4096;

  void play(String filePath) {
    File soundFile = new File(filePath);
    try {
      // convering the audio file to a stream
      AudioInputStream sampleStream = AudioSystem.getAudioInputStream(soundFile);

      AudioFormat formatAudio = sampleStream.getFormat();

      DataLine.Info info = new DataLine.Info(SourceDataLine.class, formatAudio);

      SourceDataLine theAudioLine = (SourceDataLine) AudioSystem.getLine(info);

      theAudioLine.open(formatAudio);

      theAudioLine.start();

      System.out.println("Audio Player Started.");

      byte[] bufferBytes = new byte[BUFFER_SIZE];
      int readBytes = -1;

      while ((readBytes = sampleStream.read(bufferBytes)) != -1) {
        theAudioLine.write(bufferBytes, 0, readBytes);
      }

      theAudioLine.drain();
      theAudioLine.close();
      sampleStream.close();

      System.out.println("Playback has been finished.");

    } catch (UnsupportedAudioFileException e) {
      System.out.println("Unsupported file.");
      e.printStackTrace();
    } catch (LineUnavailableException e) {
      System.out.println("Line not found.");
      e.printStackTrace();
    } catch (IOException e) {
      System.out.println("Experienced an error.");
      e.printStackTrace();
    }
  }

  public static void main(String[] args) {
    String thePath = "path to your audio file here";
    simpleSoundPlayer player = new simpleSoundPlayer();
    player.play(thePath);
  }
}

当用户想读取一个大文件而又不浪费大量存储空间时,上面的程序最有效。当用户正在传输实时声音数据并且不想具有不必要的滞后时间时,也可以应用此方法。如果实施得当,这种播放声音的方法可能会导致用户以非常低的内存使用量获得平稳一致的声音。但是,无法关闭线路可能导致内存阻塞,并最终导致声音失真。

SoundDataLine 在以下方面受到限制。

  1. 用户无法从任意位置开始播放
  2. 无法循环播放声音
  3. 无法像使用剪辑一样暂停和继续
  4. 用户无法在播放所选音频文件之前知道其持续时间

这限制了用户可以从 SoundDataLine 程序中有效受益于大型文件或在线流服务的方案。