/* / Copyright (C) 2009 Risto Känsäkoski- Sesca ISW Ltd / / This file is part of SIP-Applet (www.sesca.com, www.purplescout.com) / / This program is free software; you can redistribute it and/or / modify it under the terms of the GNU General Public License / as published by the Free Software Foundation; either version 2 / of the License, or (at your option) any later version. / / This program is distributed in the hope that it will be useful, / but WITHOUT ANY WARRANTY; without even the implied warranty of / MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the / GNU General Public License for more details. / / You should have received a copy of the GNU General Public License / along with this program; if not, write to the Free Software / Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.sesca.audio; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Arrays; import java.util.Random; 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.TargetDataLine; import javax.sound.sampled.AudioFormat.Encoding; import com.sesca.misc.Logger; public class BufferedSpeakerOutput implements AudioDestination { SourceDataLine line; AudioDestinationListener listener=null; double f1 = 1209; double f2 = 697; int leftoverSample = -99999; int index = 0; int buffering = 15; long startTime=0; int bufferDrains = 0; private AudioInputStream rawStream; private AudioInputStream encodedStream; private ByteArrayInputStream byteStream; AudioFormat sourceFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 8000, 16, 1, 2, 8000, false); AudioFormat targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 16000, 16, 1, 2, 16000, false); int sf=1; // number of silent fames inserted public void onReceivedFrame(byte[] b) { // TÄTÄ EI KÄYTETÄ! line.write(b, 0, b.length); } private void init(AudioFormat format) { //format=new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,7900,16,1,2,7900,false); //format=new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,16000,16,1,2,16000,false); DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, format); // tähän // voi // lisätä // buffer // sizen //format=new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,7000,16,1,2,7000,false); if(!AudioSystem.isLineSupported(lineInfo)) { System.err.println("ERROR: AudioLine not supported by this System."); } try { line = (SourceDataLine) AudioSystem.getLine(lineInfo); // if (DEBUG) println("SourceDataLine: "+source_line); line.open(format,2*8192); // tähän voi lisätä buffer sizen Logger.debug("Line opened"); } catch (LineUnavailableException e) { System.err.println("ERROR: LineUnavailableException at AudioReceiver()"); e.printStackTrace(); } if(!line.isOpen()) { Logger.error("Linja on kiinni"); } /* else { line.start(); Logger.debug("Line started"); } */ } public void init() { AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 8000, 16, 1, 2, 8000, false); init(format); } public void close() { line.flush(); line.close(); } public void init(AudioDestinationListener listener, AudioFormat format, int frameSize) { this.listener=listener; init(); } public void go() {} public void onReceivedDestinationFrame(byte[] b) { if (buffering<0)index++; if (startTime==0)startTime=System.nanoTime(); long time=System.nanoTime(); //System.out.println("data to speakers at time "+(time-startTime)/1000000+" ms."); if(line == null) Logger.error("Line=null"); int a = line.available(); int s = line.getBufferSize(); //System.out.println(s-a+" bytes in audio buffer"); //if (s-a<(b.length) && buffering == -1) if (s-a==0 && buffering == -1) { //line.stop(); //buffering = 10; //System.out.println("Buffer is empty!"); buffering=-2; } if (s-a<3200 && buffering<0) { if (listener==null) Logger.error("BufferedSpeakerOutput: listener==null!"); else { if (!listener.getCalleeIsTalking() && listener.isSilentFrame()) { //System.out.println("inserting silent frame ("+sf+")"); //System.out.println("skipping silent frame ("+sf+")"); byte noise[]=Signed16BitIntArrayToUnsignedByteArray(whiteNoise(-64, 64)); line.write(noise, 0, noise.length); sf++; } } } //else if (s-a<9600) line.write(b, 0, b.length); //else System.out.println("delay is too big. skipping audio frame"); //b=upsample(b); if (s-a==0) bufferDrains++; //System.out.println(s-a+" bytes data in buffer ("+b.length+")"); line.write(b, 0, b.length); long audioTime=index*20; //System.out.println(line.getFramePosition()+" audio frames played in "+((System.nanoTime()-startTime)/1000000)+" ms."); long currentTime=System.nanoTime(); long difference=audioTime-((currentTime-startTime)/1000000); //System.out.println(audioTime+" ms played in "+((currentTime-startTime)/1000000)+" ms. Difference: "+difference+" ms. Buffer drains:"+bufferDrains); //System.out.print("audio time="+audioTime+", running time="+((currentTime-startTime)/1000000)+", line time="+(line.getFramePosition()/320)*20*2+", buffer drains:"+bufferDrains); //System.out.println(" buffer available="+a); if (buffering==0) { startTime=System.nanoTime(); line.start(); buffering--; //System.out.println("Buffer filled. Starting output."); } if (buffering>=0) { buffering--; //System.out.println("Filling buffer"); } } public void stop() { if(line.isOpen()) { line.drain(); line.stop(); } else { System.err.print("WARNING: Audio stop error: source line is not open."); } // source_line.close(); } public void play() { if(line.isOpen()) line.start(); else { System.err.print("WARNING: Audio play error: source line is not open."); } } private byte[] lengthen(byte[] frame) { int min[]={65535,65535,65535,65535}; int max[]={0,0,0,0}; int minPos[] ={-1,-1,-1,-1}; int maxPos[] ={-1,-1,-1,-1}; int nextPos[] = new int[9]; nextPos[8]=frame.length-1; for (int i=0;i<frame.length;i+=2) { /* int a=frame[i] & 0xff; int b=frame[i+1] & 0xff; int c=(a+b*0xff); int etumerkki=c&32768; c&=32767; if (etumerkki!=0)c-=32768; */ int a=frame[i] & 0xff; int b=frame[i+1] & 0xff; int c=(a+b*0xff); int part=(i/(frame.length/4)); //System.out.println("part="+part); if (c<min[part]) { min[part]=c; minPos[part]=i; } if (c>max[part]) { max[part]=c; maxPos[part]=i; } // System.out.print(c+" "); } // System.out.println(); // System.out.print("1max="+max[0]+","+maxPos[0]); // System.out.println(" 1min="+min[0]+","+minPos[0]); // System.out.print("2max="+max[1]+","+maxPos[1]); // System.out.println(" 2min="+min[1]+","+minPos[1]); // System.out.print("3max="+max[2]+","+maxPos[2]); // System.out.println(" 3min="+min[2]+","+minPos[2]); // System.out.print("4max="+max[3]+","+maxPos[3]); // System.out.println(" 4min="+min[3]+","+minPos[3]); byte[] out = new byte[frame.length+16]; //Arrays.fill(out, (byte)0); int r=0; for (int u=0;u<4;u++) { nextPos[r]=(minPos[u]<maxPos[u]) ? minPos[u] : maxPos[u]; nextPos[r+1]=(minPos[u]<maxPos[u]) ? maxPos[u] : minPos[u]; r+=2; } r=0; int p=0; for (int u=0;u<frame.length;u+=2) { if (u<=nextPos[p]) { if(u<frame.length) { // System.out.println("u="+u); // System.out.println("r="+r); // System.out.println("frame length="+frame.length); out[r]=frame[u]; out[r+1]=frame[u+1]; r+=2; } } else { out[r]=frame[u-2]; out[r+1]=frame[u-1]; out[r+2]=frame[u]; out[r+3]=frame[u+1]; r+=4; p++; } } for (int i=0;i<out.length;i+=2) { int a=out[i] & 0xff; int b=out[i+1] & 0xff; int c=(a+b*0xff); // System.out.print(c+" "); } // System.out.println("\n"); return out; } private byte[] lengthen2(byte[] frame) { int min[]={65535,65535,65535,65535,65535,65535,65535,65535}; int minPos[] ={-1,-1,-1,-1,-1,-1,-1,-1,-1}; //int nextPos[] = new int[9]; minPos[8]=frame.length-1; for (int i=0;i<frame.length;i+=2) { /* int a=frame[i] & 0xff; int b=frame[i+1] & 0xff; int c=(a+b*0xff); int etumerkki=c&32768; c&=32767; if (etumerkki!=0)c-=32768; */ int a=frame[i] & 0xff; int b=frame[i+1] & 0xff; int c=(a+b*0xff); int part=(i/(frame.length/8)); //System.out.println("part="+part); if (c<min[part]) { min[part]=c; minPos[part]=i; } // System.out.print(c+" "); } // System.out.println(); // System.out.print("1max="+max[0]+","+maxPos[0]); // System.out.println(" 1min="+min[0]+","+minPos[0]); // System.out.print("2max="+max[1]+","+maxPos[1]); // System.out.println(" 2min="+min[1]+","+minPos[1]); // System.out.print("3max="+max[2]+","+maxPos[2]); // System.out.println(" 3min="+min[2]+","+minPos[2]); // System.out.print("4max="+max[3]+","+maxPos[3]); // System.out.println(" 4min="+min[3]+","+minPos[3]); byte[] out = new byte[frame.length+16]; //Arrays.fill(out, (byte)0); int r=0; /* for (int u=0;u<4;u++) { nextPos[r]=(minPos[u]<maxPos[u]) ? minPos[u] : maxPos[u]; nextPos[r+1]=(minPos[u]<maxPos[u]) ? maxPos[u] : minPos[u]; r+=2; } */ r=0; int p=0; for (int u=0;u<frame.length;u+=2) { if (u<=minPos[p]) { if(u<frame.length) { // System.out.println("u="+u); // System.out.println("r="+r); // System.out.println("frame length="+frame.length); out[r]=frame[u]; out[r+1]=frame[u+1]; r+=2; } } else { out[r]=frame[u-2]; out[r+1]=frame[u-1]; out[r+2]=frame[u]; out[r+3]=frame[u+1]; //System.out.println((out[r]&0xff)+(out[r+1]&0xff)*0xff); r+=4; p++; } } /* for (int i=0;i<out.length;i+=2) { int a=out[i] & 0xff; int b=out[i+1] & 0xff; int c=(a+b*0xff); // System.out.print(c+" "); } */ // System.out.println("\n"); return out; } private byte[] upsample(byte[] frame) { //byte out[] = new byte[frame.length*2]; int intArray[]=unsignedByteArrayToSigned16BitIntArray(frame); int doubledArray[]=new int[intArray.length*2]; int p=0; if (leftoverSample!=-99999) doubledArray[0]=(intArray[0]+leftoverSample)/2; else doubledArray[0]=intArray[0]; doubledArray[1]=intArray[0]; //System.out.println(p+":"+doubledArray[p]+" "+leftoverSample); //System.out.println((p+1)+":"+doubledArray[p+1]); leftoverSample=intArray[intArray.length-1]; for (int i=1;i<intArray.length;i++) { p=i*2+1; doubledArray[p]=intArray[i]; doubledArray[p-1]=(intArray[i]+intArray[i-1])/2; //System.out.println(p-1+":"+doubledArray[p-1]+" "); //System.out.println(p+":"+doubledArray[p]+" "+intArray[i]); p+=2; } //printIntArray(doubledArray); return Signed16BitIntArrayToUnsignedByteArray(doubledArray); } public int[] unsignedByteArrayToSigned16BitIntArray(byte[] array) { int intArray[] = new int[array.length/2]; int p=0; for (int i=0;i<array.length;i+=2) { int a=array[i] & 0xff; int b=array[i+1] & 0xff; int c=(a+b*0xff); int etumerkki=c&32768; c&=32767; if (etumerkki!=0)c-=32768; intArray[p]=c; p++; } return intArray; } public byte[] Signed16BitIntArrayToUnsignedByteArray(int[] array) { byte byteArray[] = new byte[array.length*2]; int p=0; for (int i=0;i<array.length;i++) { byteArray[p]=(byte) ( array[i]& 0xFF); byteArray[p+1]=(byte) (int)((array[i] >> 8) & 0xFF); p+=2; } return byteArray; } public void printIntArray(int[] array) { for (int i=0;i<array.length;i++) { System.out.println(array[i]); } } public byte[] upsample2(byte[] frame) { byteStream = new ByteArrayInputStream(frame); rawStream = new AudioInputStream(byteStream, sourceFormat, frame.length / 2); encodedStream = AudioSystem.getAudioInputStream(targetFormat, rawStream); byte[] encodedFrame = new byte[frame.length / 2]; try { encodedStream.read(encodedFrame, 0, encodedFrame.length); } catch (IOException e) { e.printStackTrace(); } return encodedFrame; } private int[] whiteNoise(int min, int max) { int[] noise = new int[160]; Random generator = new Random(); for (int i = 0; i < 160; i++ ) { noise[i] = (generator.nextInt(max - min) + min + 1); } return noise; } }