2015-11-16 15 views
15

Mam problem z utworzeniem AudioInputStream z Socket. Oto ważne elementy:"Endless" AudioInputStream z gniazda

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); 
    } 
. 
. 
. 
. 
} 

Ten kod rzuca UnsupportedAudioFileException na tej linii:

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

Jednak kiedy używam tego kodu:

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

to odtwarza dźwięk, ale dopiero po ładuje te 100000 próbnych ramek do strumienia audio. Po odtworzeniu wszystkich 100000 klatek się kończy.

Myślę, że rozwiązałbym ten problem, gdybym mógł przekazać AudioFormat bezpośrednio jako parametr podczas pierwszej inwencji AudioInputStream, ale nie wydaje się to możliwe. Otrzymuję specyfikacje formatu audio z serwera.

Myślę, że jednym z możliwych rozwiązań byłoby utworzenie dataliny, którą można przekazać jako parametr do konstruktora AudioInputStream. Jednak nie jestem pewien, jak uzyskać dane z gniazda bezpośrednio do dataline. Znam rozwiązanie, które wykorzystuje nieskończoną pętlę, w której odczytuje dane i zapisuje je do dataliny. Ale wydaje się to marnotrawstwem. Czy istnieje bardziej bezpośrednie podejście?

Mam nadzieję, że da się rozwiązać za pomocą biblioteki java-openAL, ponieważ muszę zmienić prędkość i mam nadzieję, że nie będę musiał tego robić sam.

Dzięki

+1

Jako pierwszy krok można spróbować użyć 'AudioInputStream audioInputStream = new AudioInputStream (this.getInputStream(), this.soundObject.getAudioFormat(), AudioSystem.NOT_SPECIFIED);' i zobacz, co się stanie. – Roman

+0

To nie rozwiązuje mojego problemu, ponieważ blokuje go metoda openAL.createSource (audioInputStream).Prawdopodobnie czeka na zakończenie całego InputStream. Dzięki –

+0

Co to jest 'openAL'? Czy możesz pokazać pełny kod źródłowy (prawdopodobnie [mcve]? – Roman

Odpowiedz

2

W końcu rozwiązałem problem. Jak się okazało, java-openAL ma wbudowaną obsługę przesyłania strumieniowego, ale nie było jej w dokumentacji GitHub, więc na początku tego nie zauważyłem. Istnieje metoda createOutputStream w klasie Source, która zwraca OutputStream. Możesz napisać bajty bezpośrednio do OutputStream.

Oto mój kod:

W tym fragmencie zainicjować 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(); 
} 

Oto moja metoda gra, która jest wywoływana, gdy InputStream jest dostępny:

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()); 
    } 
} 

Masz aby użyć metody createSource bez parametrów, zwraca nową instancję Source. Nie wywołuj metody odtwarzania na źródle, jest obsługiwana przez klasę SourceOutputStream, której instancja jest zwracana przez metodę createOutputStream. Nie ma nic złego w ręcznym wywoływaniu metody odtwarzania, ale miałem złe doświadczenia, gdy bufory są puste. Zasadniczo odtwarzanie nie rozpoczyna się później, gdy rozpoczniesz przesyłanie strumieniowe danych do OpenAL.

Oto mój kod OutputWriter który dba o przejściu bajty od InputStream do 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(); 
     } 
    } 

} 

Miłego dnia.