Speex (evtl. JSpeex) unter Java Sound

barsiq

Mitglied
Hallo, ich habe im Rahmen eines VoIP's-Projektes die ehrenvolle Aufgabe erhalten, PCM in Speex umwandeln zu können, und zwar in mindesten Wideband-Qualität. (16KHz)

Telefoniert wird aus einer Java-Anwendung aus, die mittels IAX-Protokoll mit Asterist kommuniziert.

Implementierung von Speex liegt zZ in 3 Varianten vor (JSpeex, fertiger JNI für Speex 1.0 und selbstentwickelter JNI für Speex 1.2 oder wie auch immer). Alle dieser Varianten zeigen leider ein völlig identisches Verhalten und scheiden irgendwie als Fehlerursache aus.

Das Problem ist ein folgender: es gelingt nicht, Input vom Mikrofon ins Speex zu encoden, sie ans Asterisk zu schicken und im angerufenen Hörer verständlich zu hören - in wideband und ultra-wideband-Qualität (16 bzw. 32 KHz).
Die narrowband-Quali (8000Hz) geht jedoch problemlos von Anfang an und macht einen Fehler im Code nicht gerade wahrscheinlich.
Im Mode 1 - wideband - wird zwar hin und wieder Bruchteile des Gesprochenen übertragen, Mode 2 (ultrawide) ist komplett still.

Es getrifft nur die Rechner-Asterisk-Übertragung, die am Rechner encodiert werden.

Streams vom Asterisk (dort encodiert) schluckt der Decoder in der Anwendung anstandlos, in allen Qualitätsstufen.

Ich poste noch Vollständigkeitshalber den Recorder-Code, es nützt die direkteste Art mit der lib zu arbeiten, ohne wrappende Objecte. Back to the roots so zu sagen. Und aus byte[] bastele ich hier noch float[]. Jedoch ist das Verhalten (und die Probleme) bei allen Implementierungen gleich.

Code:
	private long state; // zeiger auf den Encoder
	private SpeexBits  bits; // Datenobject das vom Speex genutzt wird

	@Override
	public void run() {

		try {
			DataLine.Info info = new DataLine.Info(TargetDataLine.class, pcmFormat);

			Mixer mixer = AudioSystem.getMixer(AudioSystem.getMixerInfo()[audioDevice.getID()]);

			logger.debug("Starte SpeexRecorder mit " + mixer.getMixerInfo());
			logger.debug("Starte SpeexRecorder pcmFormat " + pcmFormat);
			if (mixer.isOpen()) {
				logger.debug("Mixer ist schon geöffnet! Mixer wird geschlossen.");
				mixer.close();
			}
			targetline = (TargetDataLine) mixer.getLine(info);
			targetlineListener = new DHPLineListener();
			targetline.addLineListener(targetlineListener); //Nur für die Ausgabe
			logger.debug("Versuche Targetline zu öffnen: " + targetlineListener.getLineAsString(targetline));
			try {
				targetline.open(pcmFormat, TARGETLINEBUFFSIZE);
			} catch (LineUnavailableException ex) {
				logger.error("TargetLine konnte nicht geöffnet werden. " + ex);
			}
			logger.debug("Targetline ist geöffnet. Starte Targetline");
			targetline.start();
                        ...
			byte[] data = new byte[CODECFRAMESIZE];
			float[] dataF = new float[PCMFRAMESIZE];
			
			bits = new SpeexBits();
			
			state = SpeexLib.speex_encoder_init(SpeexLib.speex_nb_mode);
									
			SpeexLib.speex_bits_init(bits);

			while (!Thread.currentThread().isInterrupted()) {
				//Nur wenn ANSWER-Frame geACKed ist
				if(internalListener instanceof DHPCall && ((DHPCall)internalListener).isAnswered()){

					if (targetline.available() > CODECFRAMESIZE){						

						int read = targetline.read(data, 0, CODECFRAMESIZE);
						
						for (int index = 0; index < CODECFRAMESIZE; index+=2){
							dataF[index>>1] = (data[index+1]<<8) + data[index];
						}
						
						byte[] boutput = new byte[CODECFRAMESIZE];
						SpeexLib.speex_bits_reset(bits);
						SpeexLib.speex_encode(state, dataF, bits);
						int len = SpeexLib.speex_bits_nbytes(bits); 
						SpeexLib.speex_bits_write(bits, boutput, len);

						internalListener.listen(boutput, 0, len); 
					}
				}
				else{
					targetline.flush();
				}
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					logger.debug("catch interrupt() nach sleep");
					Thread.currentThread().interrupt();
				}
			}
		}catch (ArrayIndexOutOfBoundsException e){
			logger.warn("Versuch eines Playerstartes mit dem \"" + audioDevice.getName() + "\".");
			logger.error("Fehler aufgetreten: ", e);
			new Thread(){
				public void run() {
					String message = "Die Sprachdaten können nicht von Mikrofon gelesen werden, da \"" + audioDevice.getName() + "\" ausgewählt." ;
					JOptionPane.showMessageDialog(null, message,"Mikrofon Fehler",JOptionPane.ERROR_MESSAGE);
				};
			}.start();
		}catch (Exception e) {
			logger.error("Fehler aufgetreten: ", e);

		} finally {
			try {
//				fos.close();
				logger.debug("targetline wird geschlossen, Thread ID " + Thread.currentThread().getId());
				targetline.flush();
				targetline.stop();
				logger.debug("Versuche Targetline zu schließen: " + targetlineListener.getLineAsString(targetline));
				if(targetline.isOpen()){
					while (targetline.isOpen()) {
						logger.debug("Frank ist dabei die Line zu schließen!");
						targetline.close();
						SpeexLib.speex_bits_destroy(bits);
						SpeexLib.speex_encoder_destroy(state);
						Thread.yield();
					}
					logger.debug("Targetline geschlossen");
				}else{
					logger.debug("Targetline ist geschlossen gewesen");
				}
				targetline.removeLineListener(targetlineListener);
				targetline = null;
			} catch (Exception ex) {
				logger.error("Fehler im finally-block von Recorder: ", ex);
			} finally {
				endOfThread = true;
			}
		}

Sollte ich das Problem nicht ausreichend geschildert so gelobe ich Besserung auf Anfrage.
 
Zuletzt bearbeitet:
Zurück