Java에서 사운드 재생

K. Macharia 2023년10월12일
  1. Java에서 클립을 사용하여 사운드 재생
  2. Java에서SourceDataLine을 사용하여 사운드 재생
Java에서 사운드 재생

오디오 파일을 재생하려면 Java 응용 프로그램이 필요할 수 있습니다. 사운드는 시간 기반 데이터이며 사용자의 인식에 맞게 렌더링 되려면 정확한 속도로 전달되어야합니다. 데이터가 전달되는 속도를 변경하면 재생중인 사운드가 왜곡됩니다. Java Sound API의 목표는 사운드가 재생 될 때 사운드 데이터가 올바른 속도로 지속적으로 전달되도록하는 것입니다. 라인을 통해 사운드를 재생하여 부드럽고 일관되게 유지함으로써이를 달성합니다. Java가 제공하는 두 종류의 라인은 Clip과 SourceDataLine입니다.

이 둘의 차이점은 사운드 데이터를 지정하는 접근 방식에 있습니다. Clip을 사용하면 모든 사운드 데이터가 재생 프로세스 전에 한 번 지정되는 반면 SourceDataLine에서는 재생 프로세스 전체에 연속 버퍼 쓰기가 있습니다. 이 두 가지 방법은AIFF,AIFC,WAVE,AUSND형식의 오디오 파일 만 지원합니다. 개발자가 둘 중 하나를 사용하고 동일한 결과를 기대할 수있는 시나리오가 많이 있지만 하나가 다른 것보다 더 나은 결과를 제공하는 시나리오도 있습니다. 다음은 애플리케이션 요구에 가장 효과적인 라인을 선택하는 방법에 대한 설명입니다.

  1. Clip

짧은 사운드 파일을 클립으로 한 번 이상 읽고 재생하고 싶을 때 더 효과적입니다. Clip의 전체 기능은 사용자가 재생중인 사운드를 반복하고 싶을 때 가장 잘 경험할 수 있습니다. 이 기능을 사용하면 사용자가 무작위로 재생을 시작할 위치를 선택할 수도 있습니다. clip은 사운드 파일이로드되기 때문에 더 빠르기 때문에 반복되거나 사용자가 임의의 파일 위치로 이동하도록 선택한 후에 더 많은 버퍼링이 필요하지 않습니다.

  1. SourceDataLine

이것은 사용자가 대용량 사운드 파일을 재생하면서 메모리를 최적화하고자 할 때 더 효과적입니다. 개발자가 재생할 사운드를 모를 때도 가장 좋은 옵션입니다. 이 방법은 재생 중에 응용 프로그램에서 사운드 데이터를 지속적으로 업데이트해야하기 때문에 사운드 변환이 필요한 경우에도 더 효과적입니다.

Java에서 클립을 사용하여 사운드 재생

Clipjavax.sound.sampled패키지에서 사용할 수 있으며 Java 7에서 도입되었습니다.

이 예에서는 임의의 위치에서 시작, 일시 중지, 다시 시작, 중지, 다시 시작 및 시작을 다룹니다.

다음은 관련된 단계입니다.

  • 첫 번째 단계는 오디오 입력 스트림의 개체를 만드는 것입니다. 이 단계는 오디오 파일을 앱에서 사용할 수있는 입력 스트림으로 변환합니다.
  • 두 번째 단계는 오디오 시스템을 사용하여 클립 참조 용 개체를 만드는 것입니다.
  • 이제 세 번째 단계는 1 단계에서 만든 오디오 입력 스트림의 오디오 데이터와 함께 클립 개체를로드하는 것입니다.
  • 다음 단계는 루프, 위치 및 마이크로 초 위치와 같은 클립의 필수 속성을 설정하는 것입니다.
  • 그런 다음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 메소드가 호출 될 때 플레이어가 다시 시작할 수 있도록 저장됩니다. Pause 메서드는clip.getMicrosecondPosition()을 사용하여 일시 중지 지점을 캡처합니다.

Resume :resume 메서드가 호출되면 객체에 저장된 프레임을 사용하여 계속할 위치를 알 수 있습니다. Resumeclip.setMicrosecondPosition(nowFrame)을 사용하여 pause 메소드가 호출되었을 때의 위치로 오디오 스트림을 재설정합니다.

Stop : stop 메서드는 클립을 닫고 중지합니다. 이 메서드가 호출되면 사용자는 프레임이 저장되지 않았기 때문에 이전 위치를 다시 시작할 수 없습니다. 이것이 일시 중지와 중지의 기술적 차이점입니다.

항상 프로그램을 닫기 전에 열려있는 스트림을 닫는 것이 좋습니다. 위의 프로그램을 사용하는 동안 클립을 다시 재생하기 전에 중지하여 적절한 검사를 구현합니다. 이렇게하면 사운드가 일관되게 유지되고 재생이 진행되는 동안 리소스가 효율적으로 활용됩니다. 일반적으로 Java에서 스트림은 재설정되기 전에 오디오 스트림을 재사용 할 수 없습니다. 다시 사용하기 전에 재설정하지 않으면 프로그램에 오류가 발생합니다.

Java에서SourceDataLine을 사용하여 사운드 재생

SourceDataLinejavax.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 프로그램에서 큰 파일이나 온라인 스트리밍 서비스를 효과적으로 활용할 수있는 시나리오를 제한합니다.