在 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 程式中有效受益於大型檔案或線上流服務的方案。