2015-11-16 2 views
15

У меня возникла проблема с созданием AudioInputStream из Socket. Вот важные части:«Бесконечный» AudioInputStream из сокета

public class SoundStream extends Thread { 
    private int port; 
    private String IP; 
    private Socket socket; 

    private SoundObject soundObject; 

    private OpenAL openAL; 
    private Source source; 

    private boolean run = true; 

    public SoundStream(int port, String IP, SoundObject soundObject) { 
     this.soundObject = soundObject; 
     this.port = port; 
     this.IP = IP; 
    } 

    public void run() { 
     try { 
      this.socket = new Socket(this.IP, this.port); 
      this.openAL = new OpenAL(); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
     this.mainCycleMethod(); 
    } 

    private void mainCycleMethod() { 
     while (run) { 
      this.soundObject.blockAndWait(); 
      switch (this.soundObject.getAndResetEvent()) { 
       case 0: 
        this.run = false; 
        this.close(); 
        break; 
       case 1: 
        this.setPitch(); 
        break; 
       case 2: 
        this.closeSource(); 
        this.play(); 
        break; 
       case 3: 
        this.pause(true); 
        break; 
       case 4: 
        this.pause(false); 
        break; 
      } 
     } 
    } 

    private BufferedInputStream getInputStream() throws Exception { 
     return new BufferedInputStream(socket.getInputStream()); 
    } 

    private void setPitch() { 
     if(this.source != null) { 
      try { 
       this.source.setPitch(this.soundObject.getPitch()); 
      } catch (ALException e) { 
       e.printStackTrace(); 
      } 
     } 
    } 

    private void play() { 
     try { 
      AudioInputStream audioInputStream = new AudioInputStream(this.getInputStream(), this.soundObject.getAudioFormat(), AudioSystem.NOT_SPECIFIED); 
//   AudioInputStream audioInputStream_tmp = AudioSystem.getAudioInputStream(this.getInputStream()); 
//   AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(this.soundObject.getAudioFormat(), audioInputStream_tmp); 
      this.source = openAL.createSource(audioInputStream); 
      this.source.setGain(1f); 
      this.source.play(); 
     } catch (Exception ex) { 
      ex.printStackTrace(); 
     } 
    } 

    private void close() { 
     this.closeSource(); 
     this.openAL.close(); 
     try { 
      this.socket.close(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 

    private void closeSource() { 
     if(this.source!=null) { 
      this.source.close(); 
     } 
    } 

    private void pause(boolean pause) { 
     if(this.source != null) { 
      try { 
       if (pause) { 
        this.source.pause(); 
       } else { 
        this.source.play(); 
       } 
      } catch (ALException ex) { 
       ex.printStackTrace(); 
      } 
     } 
    } 
} 


public class SoundObject extends AbstractEventObject { 
    public AudioFormat getAudioFormat() { 
     boolean signed = false; 
     //true,false 
     boolean bigEndian = false; 
     //true,false 
     return new AudioFormat(this.frequency, this.bits, this.channels, signed, bigEndian); 
    } 
. 
. 
. 
. 
} 

Этот код бросает UnsupportedAudioFileException на этой линии:

AudioInputStream audioInputStream_tmp = AudioSystem.getAudioInputStream(this.getInputStream()); 

Однако, когда я использую этот код:

AudioInputStream audioInputStream = new AudioInputStream(this.getInputStream(), this.soundObject.getAudioFormat(), 100000); 

он воспроизводит звук, но только после того, как он загружает эти 100000 отснятых кадров в аудиоинтерфейс. После того, как он воспроизводит все 100000 кадров, он заканчивается.

Я предполагаю, что я решил бы эту проблему, если бы я мог передать AudioFormat непосредственно в качестве параметра во время первой инициализации AudioInputStream, но это кажется невозможным. Я получаю спецификации аудиоформата с сервера.

Я думаю, что одним из возможных решений было бы создать dataline, который я могу передать как параметр для конструктора AudioInputStream. Однако я не уверен, как получить данные из сокета напрямую в dataline. Я знаю решение, которое использует бесконечный цикл, в котором он считывает данные и записывает их в dataline. Но это кажется расточительным. Есть ли более прямой подход?

Надеюсь, что с помощью библиотеки java-openAL можно решить, потому что мне нужно изменить скорость, и я надеюсь, что мне не придется это делать сам.

Thanks

+1

В качестве первого шага вы можете попробовать использовать AudioInputStream audioInputStream = новый AudioInputStream (this.getInputStream(), this.soundObject.getAudioFormat(), AudioSystem.NOT_SPECIFIED); 'и посмотреть, что произойдет. – Roman

+0

Это не решает мою проблему, потому что она блокируется методом openAL.createSource (audioInputStream).Вероятно, он ждет завершения всего InputStream. Спасибо –

+0

Что такое 'openAL'? Можете ли вы показать полный исходный код (возможно, [mcve])? – Roman

ответ

2

Я, наконец, решил проблему. Как выяснилось, java-openAL имеет встроенную поддержку потоковой передачи, но это не было в документации на GitHub, поэтому я не заметил сначала. В классе Source есть метод createOutputStream, который возвращает OutputStream. Вы можете записать байты непосредственно в OutputStream.

Вот мой код:

В этом фрагменте я инициализации OpenAL:

public void run() { 
    try { 
     this.socket = new Socket(this.IP, this.port); 
     this.openAL = new OpenAL(); 
    } catch (Exception ex) { 
     Log.severe(ex.toString()); 
    } 
    this.mainCycleMethod(); 
} 

Вот мой метод игры, который вызывается, когда InputStream доступен:

private void play() { 
    try { 
     this.source = openAL.createSource(); 
     this.outputWriter = new OutputWriter(this.socket.getInputStream(), this.source, this.soundObject.getAudioFormat()); 
     this.source.setGain(1f); 
     this.outputWriter.start(); 
    } catch (Exception ex) { 
     Log.severe(ex.toString()); 
    } 
} 

У вас есть для использования метода createSource без параметров он возвращает новый экземпляр Source. Не вызывайте метод воспроизведения в источнике, он обрабатывается классом SourceOutputStream, экземпляр которого возвращается методом createOutputStream. Нет ничего плохого в вызове метода воспроизведения вручную, но у меня был плохой опыт, когда буферы пустые. В основном, это не начинается позже, когда вы начинаете передавать данные в OpenAL.

Вот мой OutputWriter код, который заботится о прохождении байтов из InputStream в OutputStream:

package cz.speechtech.sound; 

import org.urish.openal.ALException; 
import org.urish.openal.Source; 

import javax.sound.sampled.AudioFormat; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.OutputStream; 

/** 
* Created by honza on 16.12.15. 
*/ 
public class OutputWriter extends Thread { 
    private InputStream inputStream; 
    private OutputStream outputStream; 

    private int STREAMING_BUFFER_SIZE = 24000; 
    private int NUMBER_OF_BUFFERS = 4; 

    private boolean run = true; 

    public OutputWriter(InputStream inputStream, Source source, AudioFormat audioFormat) { 
     this.inputStream = inputStream; 
     try { 
      this.outputStream = source.createOutputStream(audioFormat, this.NUMBER_OF_BUFFERS, 1024); 
     } catch (ALException e) { 
      e.printStackTrace(); 
     } 
    } 

    public void run() { 
     byte[] buffer = new byte[this.STREAMING_BUFFER_SIZE]; 
     int i; 
     try { 
      Thread.sleep(1000); // Might cause problems 
      while (this.run) { 
       i = this.inputStream.read(buffer); 
       if (i == -1) break; 
       outputStream.write(buffer, 0, i); 
      } 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 

    public synchronized void stopRunning() { 
     this.run = false; 
     try { 
      this.outputStream.close(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 

} 

Иметь хороший день.

Смежные вопросы