/* * Copyright 2007 Sun Microsystems, Inc. * * This file is part of jVoiceBridge. * * jVoiceBridge is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation and distributed hereunder * to you. * * jVoiceBridge 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, see <http://www.gnu.org/licenses/>. * * Sun designates this particular file as subject to the "Classpath" * exception as provided by Sun in the License file that accompanied this * code. */ package com.sun.voip; import java.io.File; import java.io.IOException; import java.util.ArrayList; /** * Read and manager audio treatments */ public class TreatmentManager implements MixDataSource { private String treatment; private int repeatCount; private int sampleRate; private int channels; private boolean isPaused = false; private boolean isStopped = false; /* * This ArrayList contains one byte[] element per audio file. */ private ArrayList<AudioSource> treatments = new ArrayList(); private ArrayList treatmentDoneListeners = new ArrayList(); private static String[] soundPath; static { String s = System.getProperty("com.sun.voip.server.Bridge.soundPath", "/com/sun/voip/server/sounds"); String[] sp = s.split(":"); soundPath = new String[sp.length + 1]; for (int i = 0; i < sp.length; i++) { soundPath[i] = sp[i]; } /* * On Windows user.dir contains a ":" such as C:\ */ soundPath[sp.length] = System.getProperty("user.dir"); } public TreatmentManager(String treatment, int repeatCount) throws IOException { this(treatment, repeatCount, 8000, 1); } public TreatmentManager(String treatment, int repeatCount, int sampleRate, int channels) throws IOException { this.treatment = treatment; this.repeatCount = repeatCount; this.sampleRate = sampleRate; this.channels = channels; if (Logger.logLevel >= Logger.LOG_MOREINFO) { Logger.println("new treatment: " + treatment + " repeat " + repeatCount + " sampleRate " + sampleRate + " channels " + channels); } parseTreatment(treatment); } public String getId() { return treatment; } public int getSampleRate() { AudioSource audioSource = this.audioSource; if (audioSource == null) { audioSource = getAudioSource(); } if (audioSource == null) { return 0; } return audioSource.getSampleRate(); } public int getChannels() { AudioSource audioSource = this.audioSource; if (audioSource == null) { audioSource = getAudioSource(); } if (audioSource == null) { return 0; } return audioSource.getChannels(); } public void pause(boolean isPaused) { this.isPaused = isPaused; Logger.println("TreatmentManager paused " + isPaused); } public boolean isPaused() { return isPaused; } private int[] previousContribution; private int[] currentContribution; public String getSourceId() { return treatment; } public boolean contributionIsInCommonMix() { return true; } public int[] getPreviousContribution() { return previousContribution; } public int[] getCurrentContribution() { return currentContribution; } public void saveCurrentContribution() { previousContribution = currentContribution; currentContribution = getLinearData(RtpPacket.PACKET_PERIOD); } private int treatmentIndex = 0; private AudioSource audioSource; private SampleRateConverter sampleRateConverter; public byte[] getLinearDataBytes(int sampleTime) { if (isStopped) { return null; } int[] intData = getLinearData(sampleTime); if (intData == null) { return null; } byte[] byteData = new byte[intData.length * 2]; try { AudioConversion.intsToBytes(intData, byteData, 0); } catch (Exception e) { Logger.println("getLinearDataBytes, intData len " + intData.length + " byteData len " + byteData.length); e.printStackTrace(); return null; } return byteData; } public int[] getLinearData(int sampleTime) { if (isPaused) { return null; } synchronized (treatments) { audioSource = getAudioSource(); if (audioSource == null) { Logger.println( "Audio source is null, stopping treatment " + treatment); stopTreatment(); return null; } } int[] linearData = null; try { linearData = audioSource.getLinearData(sampleTime); } catch (IOException e) { Logger.println("Can't read linear data for " + treatment + " " + e.getMessage()); } if (linearData != null) { if (sampleRateConverter != null) { try { linearData = sampleRateConverter.resample(linearData); } catch (IOException e) { Logger.println("Can't resample treatment! " + e.getMessage()); return null; } } return linearData; } try { audioSource.rewind(); } catch (IOException e) { Logger.println("Can't rewind treatment " + treatment + " " + e.getMessage()); } treatmentIndex++; synchronized (treatments) { if (treatmentIndex >= treatments.size()) { /* * We're done playing all of the data, repeat if necessary */ if (repeatCount != -1) { if (repeatCount == 0) { if (Logger.logLevel >= Logger.LOG_MOREINFO) { Logger.println("done playing treatment " + treatment); } stopTreatment(); return null; // all done } repeatCount--; } treatmentIndex = 0; } } sampleRateConverter = null; audioSource = getAudioSource(); if (audioSource == null) { return null; } try { linearData = audioSource.getLinearData(sampleTime); if (linearData != null && sampleRateConverter != null) { linearData = sampleRateConverter.resample(linearData); } return linearData; } catch (IOException e) { Logger.println("Can't read linear data for " + treatment + " " + e.getMessage()); } return null; } private AudioSource getAudioSource() { synchronized (treatments) { if (treatments.size() == 0) { if (Logger.logLevel >= Logger.LOG_MOREINFO) { Logger.println("Treatments list is empty for " + treatment); } return null; } AudioSource audioSource = treatments.get(treatmentIndex); if (sampleRateConverter != null) { return audioSource; } if (sampleRate != audioSource.getSampleRate() || channels != audioSource.getChannels()) { try { sampleRateConverter = new SampleRateConverter("Treatment", audioSource.getSampleRate(), audioSource.getChannels(), sampleRate, channels); } catch (IOException e) { Logger.println("Can't resample treatment! " + e.getMessage()); return null; } } return audioSource; } } /* * Register to be notified when the treatment finishes. */ public void addTreatmentDoneListener(TreatmentDoneListener listener) { synchronized (treatmentDoneListeners) { treatmentDoneListeners.add(listener); } } public void removeTreatmentDoneListener(TreatmentDoneListener listener) { synchronized (treatmentDoneListeners) { treatmentDoneListeners.remove(listener); } } public void stopTreatment() { stopTreatment(true); } public void stopTreatment(boolean notify) { if (isStopped) { return; } isStopped = true; for (AudioSource audioSource : treatments) { audioSource.done(); } synchronized (treatments) { treatments.clear(); } if (notify == false) { return; } repeatCount = 0; synchronized (treatmentDoneListeners) { while (treatmentDoneListeners.size() > 0) { TreatmentDoneListener listener = (TreatmentDoneListener) treatmentDoneListeners.remove(0); listener.treatmentDoneNotification(this); } } if (audioSource != null) { try { audioSource.done(); } catch (Exception e) { Logger.println("Exception calling audioSource.done() " + e.getMessage()); } } } private void addTreatment(String path) throws IOException { if (Logger.logLevel >= Logger.LOG_MOREINFO) { Logger.println("adding " + path); } synchronized (treatments) { if (path.substring(0, 1).equals(File.separator) || path.startsWith("/") || path.startsWith("http://")) { AudioSource as = FileAudioSource.getAudioSource(path); if (as == null) { throw new IOException("Invalid treatment: " + path); } treatments.add(as); return; } for (int i = 0; i < soundPath.length; i++) { String s = soundPath[i] + File.separator + path; AudioSource as = FileAudioSource.getAudioSource(s); if (as != null) { treatments.add(as); return; } } throw new IOException("Invalid treatment: " + path); } } /* * The rest of this file contains methods to parse treatments and * add them to the treatment manager linear data vector. */ private static String[] dtmf = { "dtmf0.au", "dtmf1.au", "dtmf2.au", "dtmf3.au", "dtmf4.au", "dtmf5.au", "dtmf6.au", "dtmf7.au", "dtmf8.au", "dtmf9.au", "dtmfpound.au", "dtmfstar.au" }; /* * The treatment specifier is as follows: * "file:[<commas>]<path>[<commas>];file:[<commas>]<path><commas>; ...] * "dtmf:[<commas>]<0-9#*>[<commas>][<0-9#*>][<commas>] ... * "tts:<text>;<text>; ... * "s:<frequency>.<milliseconds>[+<frequency>.<milliseconds>...] * each "," is worth 100 ms of silence. */ private void parseTreatment(String treatment) throws IOException { String treatments[] = treatment.split(";"); if (Logger.logLevel >= Logger.LOG_MOREINFO) { for (int i = 0; i < treatments.length; i++) { Logger.println("treatments: " + treatments[i]); } } for (int i = 0; i < treatments.length; i++) { String currentTreatment = treatments[i]; if (currentTreatment.indexOf("dtmf:") == 0) { addDtmfTreatment(currentTreatment.substring(5)); } else if (currentTreatment.indexOf("d:") == 0) { addDtmfTreatment(currentTreatment.substring(2)); } else if (currentTreatment.indexOf("tts:") == 0) { addTtsTreatment(currentTreatment.substring(4)); } else if (currentTreatment.indexOf("t:") == 0) { addTtsTreatment(currentTreatment.substring(2)); } else if (currentTreatment.indexOf("s:") == 0) { addSineWaveTreatment(currentTreatment.substring(2)); } else { if (currentTreatment.indexOf("file:") == 0) { currentTreatment = currentTreatment.substring(5); } addFileTreatment(currentTreatment); } } } /* * treatment looks like [<commas>]<number>[<commas>]... */ private void addDtmfTreatment(String treatment) throws IOException { String s = treatment.replaceAll("[ \t]", ""); for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == ',') { addSilence(); } else { addOneDtmfTreatment(s.charAt(i)); } } } private void addOneDtmfTreatment(char c) throws IOException { int n; if (c == '#') { n = 10; } else if (c == '*') { n = 11; } else { try { n = Integer.parseInt(String.valueOf(c)); } catch (NumberFormatException e) { Logger.error("invalid dtmf treatment code '" + c + "'"); return; } } if (n < 0 || n > 11) { Logger.error("invalid dtmf treatment code " + c); return; } String s = dtmf[n]; addTreatment(s); return; } /* * treatment is like <text> */ private void addTtsTreatment(String treatment) throws IOException { int[] linearData = null; try { linearData = FreeTTSClient.textToSpeech(treatment); } catch (IOException e) { Logger.println("Can't convert text to speech '" + treatment + "' " + e.getMessage()); throw new IOException("Can't convert text to speech '" + treatment + "'"); } treatment = "FreeTTS"; /* * TTS data is always 16000/1 */ synchronized (treatments) { treatments.add(new LinearDataAudioSource(linearData, 16000, 1)); } } private void addSineWaveTreatment(String treatment) throws IOException { String s = treatment.replaceAll("[\t]", ""); String[] notes; notes = treatment.split("[\\+]"); for (int i = 0; i < notes.length; i++) { int frequency; int duration = 2000; float volume = 1.0F; String[] param = notes[i].split("[\\.]", 3); if (param.length < 1) { throw new IOException("missing frequency " + treatment); } try { frequency = Integer.parseInt(param[0]); } catch (NumberFormatException e) { throw new IOException("invalid duration " + treatment); } if (param.length > 1) { try { duration = Integer.parseInt(param[1]); } catch (NumberFormatException e) { throw new IOException("invalid duration " + treatment); } } if (param.length > 2) { try { volume = Float.parseFloat(param[2]); } catch (NumberFormatException e) { throw new IOException("invalid volume " + treatment); } } if (Logger.logLevel >= Logger.LOG_MOREINFO) { Logger.println("Sine treatment: frequency " + frequency + " duration " + duration + " volume " + volume); } synchronized (treatments) { treatments.add(new SineWaveAudioSource(frequency, duration, volume, sampleRate, channels)); } } } private void addFileTreatment(String treatment) throws IOException { /* * The treatment looks like this: * [<commas>]<path>[<commas>] */ //String s = treatment.replaceAll("[ \t]", ""); String s = treatment; while (s.length() > 0) { if (s.charAt(0) == ',') { addSilence(); s = s.substring(1); // skip comma } else { String currentTreatment = s; int i = s.indexOf(","); // find trailing commas if (i > 0) { currentTreatment = s.substring(0, i); addTreatment(currentTreatment); s = s.substring(i); } else { addTreatment(s); break; } } } } private void addSilence() throws IOException { addTreatment("silence.100ms.au"); } public String toAbbreviatedString() { return treatment; } }