Reproducir sonido en Java

K. Macharia 12 octubre 2023
  1. Reproducir sonido usando clip en Java
  2. Reproducir sonido usando SourceDataLine en Java
Reproducir sonido en Java

En ocasiones, se necesitarán aplicaciones Java para reproducir archivos de audio. Dado que el sonido es un dato basado en el tiempo y debe entregarse a la velocidad correcta para que se reproduzca para la percepción del usuario. Un altercado de la velocidad a la que se entregan los datos distorsionará el sonido que se está reproduciendo. El objetivo de la API de sonido de Java es garantizar que los datos de sonido se entreguen a la velocidad correcta y de forma continua cuando se reproduce el sonido. Lo logra al reproducir el sonido a través de una línea para garantizar que se mantenga suave y consistente. Los dos tipos de líneas que proporciona Java son Clip y SourceDataLine.

La diferencia entre los dos está en el enfoque de especificar los datos de sonido. Con Clip, todos los datos de sonido se especifican una vez antes del proceso de reproducción, mientras que en SourceDataLine, hay una escritura de búfer continua durante todo el proceso de reproducción. Estos dos métodos solo admiten archivos de audio en los siguientes formatos: AIFF, AIFC, WAVE, AU y SND. Hay muchos escenarios en los que un desarrollador puede usar cualquiera de los dos y esperar obtener el mismo resultado, pero también hay escenarios en los que uno da mejores resultados que el otro. A continuación se ofrece una explicación sobre cómo elegir la línea más eficaz para las necesidades de su aplicación.

  1. Clip

Es más eficaz cuando desea leer y reproducir un archivo de sonido corto como un clip más de una vez. La fuerza de la funcionalidad completa del clip se experimenta mejor cuando el usuario desea hacer un bucle del sonido que se está reproduciendo. Esta función también permite al usuario seleccionar el lugar donde desea iniciar la reproducción de forma aleatoria. El clip es más rápido porque el archivo de sonido se carga y, por lo tanto, no requiere más almacenamiento en búfer después de ser bucleado o después de que el usuario selecciona moverse a una posición de archivo aleatoria.

  1. SourceDataLine

Esto es más efectivo cuando un usuario desea optimizar la memoria mientras reproduce un archivo de sonido de gran tamaño. También es la mejor opción cuando el desarrollador no conoce el sonido que se reproducirá. Este método también es más eficaz cuando hay una necesidad de transformación de sonido porque requiere que la aplicación actualice continuamente los datos de sonido durante la reproducción.

Reproducir sonido usando clip en Java

El Clip está disponible en el paquete javax.sound.sampled y se introdujo en Java 7.

En este ejemplo, cubriremos iniciar, pausar, reanudar, detener, reiniciar y comenzar en una posición aleatoria.

A continuación se muestran los pasos involucrados:

  • El primer paso es crear un objeto del flujo de entrada de audio. Este paso convierte el archivo de audio en un flujo de entrada que la aplicación puede usar.
  • El segundo paso es usar Audio System para crear un objeto para referencia de clip
  • El tercer paso ahora es cargar el objeto de clip con datos de audio del flujo de entrada de audio que se creó en el paso 1.
  • El siguiente paso es establecer las propiedades requeridas del clip, como el bucle, la posición y la posición del microsegundo.
  • A continuación, puede iniciar el 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);
  }
}

Este programa utiliza un flujo de audio obtenido a partir de los datos de sonido mediante el AudioInputStream. Este debe ser el primer paso porque el programa solo reconoce los datos como un flujo que debe reiniciarse si se va a reutilizar.

Explicación de operaciones

Pause: Para pausar correctamente el reproductor, el fotograma actual debe almacenarse en un objeto después de que el reproductor se detenga. El marco se almacena para garantizar que el jugador pueda reanudar cuando se llame al método resume. El método de pausa usa clip.getMicrosecondPosition() para capturar el punto de pausa.

Resume: Cuando se llama al método Resume, utiliza el marco almacenado en un objeto para saber desde dónde continuar. Resume usa clip.setMicrosecondPosition(nowFrame)para restablecer el flujo de audio a la posición en la que estaba cuando se llamó al método de pausa.

Stop: El método de detención cierra y detiene el clip. Cuando se llama a este método, el usuario no puede reanudar su posición antes porque el marco no se almacenó. Ésta es la diferencia técnica entre pausa y parada.

Siempre es aconsejable que antes de cerrar el programa, se cierren los streams abiertos. Mientras se utilizan los programas anteriores, las comprobaciones adecuadas se implementan deteniendo el clip antes de volver a reproducirlo. Esto asegura que el sonido se mantenga constante y que los recursos se utilicen de manera eficiente mientras la reproducción está en curso. Como regla general, en Java, una transmisión, en este caso, la transmisión de audio no se puede reutilizar antes de restablecerla. Si no lo restablece antes de reutilizarlo, el programa dará un error.

Reproducir sonido usando SourceDataLine en Java

La SourceDataLine se encuentra en javax.sound.sampled.SourceDataLine. Para implementar la reproducción de sonido SourceDataLine, seguimos los siguientes pasos.

  • El primer paso es crear un objeto del flujo de entrada de audio. Este paso convierte el archivo de audio en un flujo de entrada que la aplicación puede usar.
  • El segundo paso es abrir una línea usando el método AudioSystem.getLine().
  • El tercer paso es leer repetidamente los fragmentos especificados del flujo de entrada de audio creado en el paso 1 y reenviarlo al búfer de SourceDataLine. Esto se repite hasta el final de la secuencia de audio.
  • Una vez completada la lectura y el búfer, los recursos se liberan cerrando la línea.
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);
  }
}

El programa anterior es más efectivo cuando el usuario quiere leer un archivo grande sin perder mucho espacio en la memoria. Esto también se puede aplicar cuando el usuario está transmitiendo datos de sonido en tiempo real y no quiere tener retrasos innecesarios. Cuando está bien implementado, este método de reproducción de sonido puede llevar al usuario a obtener un sonido uniforme y consistente con un uso de memoria muy bajo. Sin embargo, no cerrar la línea puede provocar la obstrucción de la memoria y, en última instancia, la distorsión del sonido.

SoundDataLine está limitado de las siguientes formas:

  1. Un usuario no puede empezar a jugar desde una posición arbitraria.
  2. No es posible hacer un bucle de sonido
  3. No es posible pausar y reanudar como lo haría con un clip.
  4. Un usuario no puede saber la duración del archivo de audio seleccionado antes de reproducirlo.

Esto limita los escenarios en los que un usuario puede beneficiarse efectivamente del programa SoundDataLine a archivos grandes o servicios de transmisión en línea.