View Javadoc

1   /*
2    * JCaptcha, the open source java framework for captcha definition and integration
3    * Copyright (c)  2007 jcaptcha.net. All Rights Reserved.
4    * See the LICENSE.txt file distributed with this package.
5    */
6   
7   package com.octo.captcha.component.sound.wordtosound;
8   
9   import com.octo.captcha.CaptchaException;
10  import com.octo.captcha.component.sound.soundconfigurator.FreeTTSSoundConfigurator;
11  import com.octo.captcha.component.sound.soundconfigurator.SoundConfigurator;
12  import com.sun.speech.freetts.Voice;
13  import com.sun.speech.freetts.VoiceManager;
14  import com.sun.speech.freetts.audio.AudioPlayer;
15  import com.sun.speech.freetts.util.Utilities;
16  
17  import javax.sound.sampled.AudioFormat;
18  import javax.sound.sampled.AudioInputStream;
19  import java.io.ByteArrayInputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.SequenceInputStream;
23  import java.util.Locale;
24  import java.util.Vector;
25  
26  /***
27   * WordToSound implementation with FreeTTS an openSource Text To Speech implementation.
28   *
29   * @author Benoit
30   * @version 1.0
31   */
32  public abstract class AbstractFreeTTSWordToSound implements WordToSound {
33      public static String defaultVoiceName = "kevin16";
34  
35      public static String defaultVoicePackage = "com.sun.speech.freetts.en.us.cmu_time_awb.AlanVoiceDirectory,com.sun.speech.freetts.en.us.cmu_us_kal.KevinVoiceDirectory";
36  
37      private static String FREETTS_PROPERTIES_KEY = "freetts.voices";
38  
39      private int maxAcceptedWordLength;
40  
41      private int minAcceptedWordLength;
42  
43      private SoundConfigurator configurator = null;
44  
45      private Voice voice = null;
46  
47      /***
48       * default Voice, allocated at instanciation
49       */
50      private Voice defaultVoice = null;
51  
52      private Locale locale = null;
53  
54      private VoiceManager voiceManager = null;
55  
56      private boolean isInitiated = false;
57  
58      /***
59       * Constructor
60       *
61       * @deprecated
62       */
63      public AbstractFreeTTSWordToSound() {
64          configurator = new FreeTTSSoundConfigurator(AbstractFreeTTSWordToSound.defaultVoiceName,
65                  AbstractFreeTTSWordToSound.defaultVoicePackage, 1.0f, 100, 100);
66  
67          minAcceptedWordLength = 4;
68          maxAcceptedWordLength = 6;
69          init();
70      }
71  
72      /***
73       * Constructor for a FreeTTS implmentation of WordToSound. This constructor imply that WordToSound only use one
74       * voice define by voiceName, with its own locale
75       *
76       * @param configurator          Voice configuration
77       * @param minAcceptedWordLength Length Minimal of generated words
78       * @param maxAcceptedWordLength Length Maximal of generated words
79       */
80      public AbstractFreeTTSWordToSound(SoundConfigurator configurator, int minAcceptedWordLength,
81                                        int maxAcceptedWordLength) {
82          this.configurator = configurator;
83          this.minAcceptedWordLength = minAcceptedWordLength;
84          this.maxAcceptedWordLength = maxAcceptedWordLength;
85          init();
86      }
87  
88      /***
89       * @see com.octo.captcha.component.sound.wordtosound.WordToSound#getSound(java.lang.String)
90       */
91      public AudioInputStream getSound(String word) throws CaptchaException {
92          //return a sound generated with the default voice.
93          voice = defaultVoice;
94          return addEffects(stringToSound(word));
95      }
96  
97      /***
98       * @see WordToSound#getSound(String, java.util.Locale)
99       */
100     public AudioInputStream getSound(String word, Locale locale) throws CaptchaException {
101 
102         Voice[] voices = voiceManager.getVoices();
103         Voice selectedVoice = null;
104 
105         //if the default voice is corresponding
106         if (defaultVoice.getLocale().equals(locale)) {
107             voice = defaultVoice;
108         } else {
109             //try to find a voice corresponding to the locale
110             for (int i = 0; i < voices.length; i++) {
111                 if (voices[i].getLocale().equals(locale)) {
112                     selectedVoice = voices[i];
113                 }
114             }
115 
116             if (selectedVoice != null) {
117                 selectedVoice.allocate();
118                 voice = selectedVoice;
119                 configureVoice(voice);
120             } else {
121                 throw new CaptchaException("No voice corresponding to the Locale");
122             }
123         }
124 
125         return addEffects(stringToSound(word));
126     }
127 
128     public int getMaxAcceptedWordLength() {
129         return maxAcceptedWordLength;
130     }
131 
132     public int getMinAcceptedWordLength() {
133         return minAcceptedWordLength;
134     }
135 
136     /***
137      * @return the max word lenght accepted by this word2image service
138      * @deprecated misspelled, use {@link #getMaxAcceptedWordLength()} instead
139      */
140     public int getMaxAcceptedWordLenght() {
141         return maxAcceptedWordLength;
142     }
143 
144     /***
145      * @return the min word lenght accepted by this word2image service
146      * @deprecated misspelled, use {@link #getMinAcceptedWordLength()} instead
147      */
148     public int getMinAcceptedWordLenght() {
149         return minAcceptedWordLength;
150     }
151 
152     protected abstract AudioInputStream addEffects(AudioInputStream sound);
153 
154     /***
155      * Method to initialise FreeTTS
156      */
157     private void init() {
158         if (!isInitiated) {
159             //Voices use by freeTTS, we define where they are, currently in the java en_us.jar
160             //add the package
161             addToSystemesPropetites(this.configurator.getLocation());
162 
163             // The VoiceManager manages all the voices for FreeTTS.
164             voiceManager = VoiceManager.getInstance();
165 
166             this.defaultVoice = voiceManager.getVoice(this.configurator.getName());
167 
168             configureVoice(this.defaultVoice);
169 
170             // Allocates the resources for the voice.
171             this.defaultVoice.allocate();
172             isInitiated = true;
173         }
174     }
175 
176     /***
177      * Add the package to the system properties, will be used by FreeTTS to find all data for voices.
178      */
179     private static void addToSystemesPropetites(String soundPackage) {
180         //get the prop, if not exist inti, else add to the prop
181         String packages = System.getProperty(FREETTS_PROPERTIES_KEY);
182         if (packages == null) {
183             packages = soundPackage;
184         } else if (packages.indexOf(soundPackage) == -1) {
185             packages += "," + soundPackage;
186         }
187 
188         System.getProperties().put(FREETTS_PROPERTIES_KEY, packages);
189     }
190 
191     /***
192      * Configue the voice with the SoundConfigurator
193      */
194     private void configureVoice(Voice voice) {
195         voice.setPitch(configurator.getPitch());
196         voice.setVolume(configurator.getVolume());
197         voice.setRate(configurator.getRate());
198     }
199 
200     /***
201      * Main method for this service Return an image with the specified. Synchronisation is very important, for multi
202      * threading execution
203      *
204      * @param sentence Written sentece to transform into speech
205      *
206      * @return the generated sound
207      *
208      * @throws com.octo.captcha.CaptchaException
209      *          if word is invalid or an exception occurs during the sound generation
210      */
211     public synchronized AudioInputStream stringToSound(String sentence) throws CaptchaException {
212         //use the custom (see inner class) InputStreamAudioPlayer, which provide interface to
213         // Audio Stream
214         InputStreamAudioPlayer audioPlayer = new InputStreamAudioPlayer();
215 
216         this.voice.setAudioPlayer(audioPlayer);
217 
218         // Synthesize speech.
219         this.voice.speak(sentence);
220 
221         AudioInputStream ais = audioPlayer.getAudioInputStream();
222         return ais;
223     }
224 
225     /***
226      * Implementation of freeTTS AudioPlayer interface, to produce an audioInputStream, this is not a very clean way
227      * since it doesn't really play. But it is the only way to get a stream easily
228      */
229     private class InputStreamAudioPlayer implements AudioPlayer {
230         private boolean debug = false;
231 
232         private AudioFormat currentFormat = null;
233 
234         private byte[] outputData;
235 
236         private int curIndex = 0;
237 
238         private int totBytes = 0;
239 
240         private Vector outputList;
241 
242         private AudioInputStream audioInputStream;
243 
244         /***
245          * Constructs a InputStreamAudioPlayer
246          *
247          */
248         public InputStreamAudioPlayer() {
249             debug = Utilities.getBoolean("com.sun.speech.freetts.audio.AudioPlayer.debug");
250             outputList = new Vector();
251         }
252 
253         /***
254          * Sets the audio format for this player
255          *
256          * @param format the audio format
257          *
258          * @throws UnsupportedOperationException if the line cannot be opened with the given format
259          */
260         public synchronized void setAudioFormat(AudioFormat format) {
261             currentFormat = format;
262         }
263 
264         /***
265          * Gets the audio format for this player
266          *
267          * @return format the audio format
268          */
269         public AudioFormat getAudioFormat() {
270             return currentFormat;
271         }
272 
273         /***
274          * Pauses audio output
275          */
276         public void pause() {
277         }
278 
279         /***
280          * Resumes audio output
281          */
282         public synchronized void resume() {
283         }
284 
285         /***
286          * Cancels currently playing audio
287          */
288         public synchronized void cancel() {
289         }
290 
291         /***
292          * Prepares for another batch of output. Larger groups of output (such as all output associated with a single
293          * FreeTTSSpeakable) should be grouped between a reset/drain pair.
294          */
295         public synchronized void reset() {
296         }
297 
298         /***
299          * Starts the first sample timer
300          */
301         public void startFirstSampleTimer() {
302         }
303 
304         /***
305          * Closes this audio player
306          */
307         public synchronized void close() {
308             try {
309                 audioInputStream.close();
310             } catch (IOException ioe) {
311                 System.err.println("Problem while closing the audioInputSteam");
312             }
313 
314         }
315 
316         public AudioInputStream getAudioInputStream() {
317             InputStream tInputStream = new SequenceInputStream(outputList.elements());
318             AudioInputStream tAudioInputStream = new AudioInputStream(tInputStream, currentFormat,
319                     totBytes / currentFormat.getFrameSize());
320 
321             return tAudioInputStream;
322         }
323 
324         /***
325          * Returns the current volume.
326          *
327          * @return the current volume (between 0 and 1)
328          */
329         public float getVolume() {
330             return 1.0f;
331         }
332 
333         /***
334          * Sets the current volume.
335          *
336          * @param volume the current volume (between 0 and 1)
337          */
338         public void setVolume(float volume) {
339         }
340 
341         /***
342          * Starts the output of a set of data. Audio data for a single utterance should be grouped between begin/end
343          * pairs.
344          *
345          * @param size the size of data between now and the end
346          */
347         public void begin(int size) {
348             outputData = new byte[size];
349             curIndex = 0;
350         }
351 
352         /***
353          * Marks the end of a set of data. Audio data for a single utterance should be groupd between begin/end pairs.
354          *
355          * @return true if the audio was output properly, false if the output was cancelled or interrupted.
356          */
357         public boolean end() {
358             outputList.add(new ByteArrayInputStream(outputData));
359             totBytes += outputData.length;
360             return true;
361         }
362 
363         /***
364          * Waits for all queued audio to be played
365          *
366          * @return true if the audio played to completion, false if the audio was stopped
367          */
368         public boolean drain() {
369             return true;
370         }
371 
372         /***
373          * Gets the amount of played since the last mark
374          *
375          * @return the amount of audio in milliseconds
376          */
377         public synchronized long getTime() {
378             return -1L;
379         }
380 
381         /***
382          * Resets the audio clock
383          */
384         public synchronized void resetTime() {
385         }
386 
387         /***
388          * Writes the given bytes to the audio stream
389          *
390          * @param audioData audio data to write to the device
391          *
392          * @return <code>true</code> of the write completed successfully, <code> false </code> if the write was
393          *         cancelled.
394          */
395         public boolean write(byte[] audioData) {
396             return write(audioData, 0, audioData.length);
397         }
398 
399         /***
400          * Writes the given bytes to the audio stream
401          *
402          * @param bytes  audio data to write to the device
403          * @param offset the offset into the buffer
404          * @param size   the size into the buffer
405          *
406          * @return <code>true</code> of the write completed successfully, <code> false </code> if the write was
407          *         cancelled.
408          */
409         public boolean write(byte[] bytes, int offset, int size) {
410             System.arraycopy(bytes, offset, outputData, curIndex, size);
411             curIndex += size;
412             return true;
413         }
414 
415         /***
416          * Waits for resume. If this audio player is paused waits for the player to be resumed. Returns if resumed,
417          * cancelled or shutdown.
418          *
419          * @return true if the output has been resumed, false if the output has been cancelled or shutdown.
420          */
421         private synchronized boolean waitResume() {
422             return true;
423         }
424 
425         /***
426          * Returns the name of this audioplayer
427          *
428          * @return the name of the audio player
429          */
430         public String toString() {
431             return "AudioInputStreamAudioPlayer";
432         }
433 
434         /***
435          * Outputs a debug message if debugging is turned on
436          *
437          * @param msg the message to output
438          */
439         private void debugPrint(String msg) {
440             if (debug) {
441                 System.out.println(toString() + ": " + msg);
442             }
443         }
444 
445         /***
446          * Shows metrics for this audio player
447          */
448         public void showMetrics() {
449         }
450     }
451 
452 }
453