voxforge.org
VoxForge Dev

root/Trunk/SpeechSubmission/VFSpeechSubmission/java/src/speechrecorder/PlayerApplet.java

Revision 2317, 18.1 kB (checked in by kmaclean, 1 year ago)

renaming MoodleSpeex? Java apps to SpeechRecorder?

Line 
1 package speechrecorder;
2
3
4 import java.awt.Container;
5 import java.awt.event.*;
6 import java.io.*;
7 import java.net.*;
8 import javax.sound.sampled.*;
9 import javax.swing.*;
10
11
12 /**
13  * @author  kmaclean
14  */
15 public class PlayerApplet extends JApplet {
16
17     private Downloader dl;
18     private Playback pl;
19  // !!!!!!   
20  //   private static final boolean DEBUG = false; // Whether to output messages to the console
21     private static final boolean DEBUG = true; // Whether to output messages to the console
22     
23 // !!!!!!   
24     private URL url; // The URL as supplied via applet parameter
25     private File tmpFile; // The remote data is downloaded and stored here
26     private long fileSize; // The filesize, irrespective of how much is downloaded
27     private long bytesDownloaded = 0L;
28    
29     private String cookie;
30    
31     private InputStream urlInputStream;
32     private OutputStream fileOutStream;
33     private InputStream fileInputStream;
34     private AudioInputStream audioInputStream;
35     private AudioFormat audioFormat;
36     private AudioFormat targetFormat;
37     private SourceDataLine sourceDataLine;
38    
39     private JProgressBar progressBar;
40     private JSlider slider;
41     private JButton playBut;
42
43     private boolean headerBytesLoaded = false; // Ogg Speex takes 108 (28+80) bytes for header
44     private boolean audioInitialised = false;
45
46     private boolean isPlaying = false;
47
48     public void init(){
49         getParameters();
50         initialiseStreams();
51         layoutGui();
52        
53         dl = new Downloader();
54         dl.start();
55         pl = new Playback();
56     }
57
58     /**
59     * May halt applet if required param (file) does not exist
60     */
61     private void getParameters(){
62         try {
63 // !!!!!!               
64  //           String fileParam = getParameter("file");
65             url = new URL("http://localhost:90/httpd/audiosubmissions/cc-01.wav");
66 // !!!!!!           
67         } catch (NullPointerException err){
68             reportError("\"file\" parameter is required - not found", err);
69             System.exit(1);
70         } catch (MalformedURLException err){
71             reportError("\"file\" parameter is not a correctly-formed URL", err);
72             System.exit(1);
73         }
74
75         try {
76             cookie = getParameter("cookie");
77         } catch (NullPointerException err){
78             reportError("\"cookie\" parameter not found - this may cause problems trying to read the audio file", err);
79             cookie = null;
80         }
81
82     }
83    
84     /**
85     * Set up download, cache writing, and cache reading streams.
86     * May halt applet if anything cannot be opened
87     */
88     private void initialiseStreams(){
89        
90         try {
91             URLConnection conn = url.openConnection();
92            
93             // The cookie value should be a parameter passed in to the applet
94             if(cookie != null) {
95                 conn.setRequestProperty("Cookie", cookie);
96             }
97            
98             conn.connect();
99             urlInputStream = new BufferedInputStream(conn.getInputStream());
100             fileSize = conn.getContentLength();
101            
102             if(DEBUG){
103                 if(fileSize == -1){
104                     System.err.println("initialiseStreams(): File size cannot be determined for the remote file, which is a shame.");
105                 } else {
106                     System.err.println("initialiseStreams(): File size for the remote file is " + fileSize);
107                 }
108             }
109            
110         } catch (IOException err){
111             reportError("Unable to get an input stream from specified URL", err);
112             System.exit(1);
113         }
114        
115         try {
116             tmpFile = File.createTempFile("mdl", ".spx");
117             tmpFile.deleteOnExit();
118 System.err.println("MSPA tmpFile = " + tmpFile);
119         } catch (IOException err){
120             reportError("Unable to create temp file for storing data", err);
121             System.exit(1);
122         }
123        
124         try {
125             fileOutStream = new BufferedOutputStream(
126                  new FileOutputStream(tmpFile));
127         } catch (Exception err){
128             reportError("Unable to open stream for writing audio data to temp file", err);
129             System.exit(1);
130         }
131        
132         try {
133             fileInputStream = new BufferedInputStream(new FileInputStream(tmpFile));
134         } catch (IOException err){
135             reportError("Unable to get an input stream to read the temp file's audio data", err);
136             System.exit(1);
137         }
138        
139     }
140    
141     /**
142     * Create GUI elements
143     */
144     private void layoutGui(){
145
146        Container pane = getContentPane();
147        
148        pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
149
150        playBut = new JButton("Play");
151        playBut.setEnabled(false);
152        playBut.addActionListener(new ActionListener(){
153                        public void actionPerformed(ActionEvent e){
154                            if(playBut.getText().equals("Stop")){
155                                pl.stopPlayback();
156                            }else{
157                                pl.startPlayback();
158                            }
159                        }
160                        });
161        
162        progressBar = new JProgressBar();
163        progressBar.setString("Loading");
164        progressBar.setStringPainted(true);
165        progressBar.setIndeterminate(fileSize == -1);
166        
167        pane.add(playBut);
168        pane.add(progressBar);
169     }
170    
171     /**
172     * Called by the download thread when enough file has downloaded to be able
173     * to interpret the audio format from the header
174     */
175     private void initialiseAudio(){
176        
177        
178         try{
179             fileInputStream = new BufferedInputStream(new FileInputStream(tmpFile));
180            
181        
182             AudioFileFormat aff = AudioSystem.getAudioFileFormat(fileInputStream);
183            
184            
185            
186            
187             System.err.println("AudioFileFormat derived from stream is: " + aff);
188             System.err.println("AudioFileFormat.Type is: " + aff.getType());
189             System.err.println("AudioFormat is: " + aff.getFormat());
190            
191             audioFormat = aff.getFormat();
192             DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
193
194
195             if(DEBUG){
196                 if (!AudioSystem.isLineSupported(info)) {
197                     System.err.println("AudioSystem does not support this DataLine.Info (made out of the AudioFormat): " + info);
198                 } else {
199                     System.err.println("HOORAH! AudioSystem does support this DataLine.Info (made out of the AudioFormat): " + info);
200                 }
201             }
202 //          !!!!!!
203 /*             targetFormat = new AudioFormat(
204                 AudioFormat.Encoding.PCM_SIGNED,
205                 audioFormat.getSampleRate(),
206                 16,
207                 audioFormat.getChannels(),
208                 audioFormat.getChannels() * 2,
209                 audioFormat.getSampleRate(),
210               false);
211 */              
212 //            audioInputStream = new org.xiph.speex.spi.Speex2PcmAudioInputStream(fileInputStream, targetFormat, AudioSystem.NOT_SPECIFIED);
213             audioInputStream = AudioSystem.getAudioInputStream(fileInputStream);
214 // !!!!!!           
215             if(DEBUG){
216                 System.err.println("--- successfully grabbed audioInputStream by direct rather than SPI ---");
217             }
218
219         } catch (IOException err) {
220             reportError("IO error while trying to open cache file for playback", err);
221             return;
222         } catch(UnsupportedAudioFileException err) {
223             reportError("'Unsupported audio file' error while trying to open cache file for playback", err);
224             return;
225         }
226        
227         audioFormat = audioInputStream.getFormat();
228
229         DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
230         // If the audioFormat is not directly supported
231         if (!AudioSystem.isLineSupported(info)) {
232           AudioFormat sourceFormat = audioFormat;
233 // !!!!!!! not required
234 /*          AudioFormat targetFormat = new AudioFormat(
235                 AudioFormat.Encoding.PCM_SIGNED,
236                 sourceFormat.getSampleRate(),
237                 16,
238                 sourceFormat.getChannels(),
239                 sourceFormat.getChannels() * 2,
240                 sourceFormat.getSampleRate(),
241                 false);
242                 
243       
244           audioInputStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream);
245           audioFormat = audioInputStream.getFormat();
246
247           info = new DataLine.Info(SourceDataLine.class, audioFormat);
248 */
249 //        !!!!!!!
250         }
251        
252        
253         try {
254           sourceDataLine = (SourceDataLine) AudioSystem.getLine(info);
255           // We have to open the line for it to be ready to receive audio data.
256           sourceDataLine.open(audioFormat);
257         }
258         catch (Exception err) {
259           reportError("Unable to start the audio output based on the required audio parameters.", err);
260           return;
261         }
262
263
264         // Once init is complete, then playback can be allowed
265         audioInitialised = true;
266         playBut.setEnabled(true);
267     }
268    
269     /**
270     * This seems to be the best way to "rewind" the stream to the beginning
271     * after the audio has played (use of "mark/reset" is problematic). You'd
272     * think there'd be a better way than closing and reopening.
273     * And maybe there is.
274     */
275     private void closeAndReopenStream(){
276         try{
277             audioInputStream.close();
278             // These functions are the same as the initialiseAudio() func
279             //  except we don't need to re-calculate the stream types
280             fileInputStream = new BufferedInputStream(new FileInputStream(tmpFile));
281 // !!!!!!           
282   //          audioInputStream = new org.xiph.speex.spi.Speex2PcmAudioInputStream(fileInputStream, targetFormat, AudioSystem.NOT_SPECIFIED);
283             audioInputStream = AudioSystem.getAudioInputStream(fileInputStream);
284 //          !!!!!!               
285         } catch (IOException err) {
286             reportError("IO error while trying to RE-open cache file for playback", err);
287             return;
288 // !!!!!!           
289         }catch (UnsupportedAudioFileException err) {
290             reportError("UnsupportedAudioFileException error while trying to RE-open cache file for playback", err);
291             return;
292         }
293 // !!!!!!       
294     }
295    
296     /**
297     * Used when fatal errors occur
298     */
299     private void reportError(final String str, final Exception err){
300         System.err.println("PlayerApplet important error: " + str);
301         err.printStackTrace();
302         JButton errButt = new JButton("Error");
303         errButt.addActionListener(new ActionListener(){
304                        public void actionPerformed(ActionEvent e){
305                          JOptionPane.showMessageDialog(null, str + "\n\n" + err, "PlayerApplet error", JOptionPane.ERROR_MESSAGE);
306                        }
307                        });
308         getContentPane().add(errButt);
309     }
310    
311    
312    
313     /**
314     * Called by the playback thread to alert the applet when playback stops
315     */
316     private void playbackHasStopped(){
317        playBut.setText("Play");
318     }
319    
320     /**
321     * Called by the downloader when the amount changes.
322     * Might be nice to update this not quite as often as every dnld chunk.
323     */
324     private void updateDownloadProgress(){
325        progressBar.setValue((int)(fractionDownloaded() * 100.0));
326     }
327
328    
329     /**
330     * If fileSize==-1, will always return zero, since there's no way to know.
331     * Otherwise returns a float between zero and one.
332     */
333     float fractionDownloaded(){
334         if(fileSize==-1){
335             return 0.0f;
336         } else {
337             return ((float)bytesDownloaded) / (float)fileSize;
338         }
339     }
340    
341
342
343     ////////////////////// Playback thread class //////////////////////
344
345     class Playback implements Runnable {
346         long bytePos = 0L;
347         protected Thread thread;
348    
349         void startPlayback(){
350             if(DEBUG){
351                 System.err.println("startPlayback() called");
352             }
353            
354             if(isPlaying){
355                 if(DEBUG){
356                     System.err.println("startPlayback() - isPlaying so not doing anything");
357                 }
358                 return;
359             }
360
361             playBut.setText("Stop");
362            
363             isPlaying = true;
364
365             thread = new Thread(this);
366             thread.setName("Playback");
367             thread.start();
368         }
369        
370         void stopPlayback(){
371             if(DEBUG){
372                 System.err.println("stopPlayback() called");
373             }
374             isPlaying = false;
375         }
376        
377         public void run(){
378             //byte[] buffer = new byte[16384];
379             byte[] buffer = new byte[4096]; // Smallish to make the "stop" button respond quicker
380             int bytesRead;
381             int totalBytesRead=0;           
382            
383             if(!audioInitialised) { // This should never happen, really - play button only active after audio is inited
384                 initialiseAudio();
385             }
386
387             closeAndReopenStream(); // This is required for repeated presses of the Play button to work
388             
389             sourceDataLine.start();
390            
391             try {
392                 while(isPlaying && ((bytesRead = audioInputStream.read(buffer)) != -1 )){
393                     sourceDataLine.write(buffer, 0, bytesRead);
394                     totalBytesRead += bytesRead;
395 //System.err.println("Playback thread: read/written "+bytesRead+" bytes. Total: " + totalBytesRead + " bytes. Available: "+audioInputStream.available());
396
397
398                     // You'll have 0 bytes available if the buffers are still filling,
399                     //  or (seemingly) when the file has ended. The JSpeex libraries
400                     //  don't seem to be dealing with EOF in any sensible way.
401                     // They should return -1 rather than blocking indefinitely!
402                     // I hope that "break"ing here doesn't cause problems by
403                     //  stopping play prematurely - doesn't on my system but
404                     //  maybe on lower-spec systems...?
405                     if(audioInputStream.available()==0) {
406                       break;
407                     }
408                 }
409             } catch (IOException err) {
410                 reportError("I/O error in playback thread, while trying to write audio data to output", err);
411             }
412
413             if(DEBUG){
414                 System.err.println("Playback thread: finished - about to drain&stop.");
415             }
416            
417             sourceDataLine.drain(); // Drain the buffer - otherwise the end of the file may never be played
418             // DON'T STOP - MAY NOT BE ABLE TO PLAY MORE IN FUTURE      sourceDataLine.stop();
419
420             if(DEBUG){
421                 System.err.println("Playback thread: drained & stopped.");
422             }
423
424             playbackHasStopped();
425             isPlaying = false;
426         }
427        
428     } // End class Playback
429     
430    
431    
432     ////////////////////// Downloader thread class //////////////////////
433     
434     /**
435          * @author  kmaclean
436          */
437     class Downloader extends Thread {
438    
439         boolean isComplete = false;
440    
441         /**
442                  * @return
443                  * @uml.property  name="isComplete"
444                  */
445         boolean isComplete(){
446             return isComplete;
447         }
448        
449         public void run(){
450             int numBytes = 0;
451             byte[] byteData = new byte[4096]; // Is this a sensible size? Check later.
452           
453             try{
454                 while((numBytes = urlInputStream.read(byteData, 0, byteData.length)) != -1){
455                     fileOutStream.write(byteData, 0, numBytes);
456                     bytesDownloaded += numBytes;
457                     if(fileSize != -1){
458                         updateDownloadProgress();
459                     }
460                     if(numBytes==0){ // Nothing available just yet so...
461                         yield(); // ...let other threads have a go with the CPU
462                     }else{
463 //                         if((!headerBytesLoaded) && (bytesDownloaded > 4096)){
464 /*
465                          if((!headerBytesLoaded) && (tmpFile.length() > 4096)){
466                              // The amount required above is more than really needed (108 bytes is size of header)
467                              //   but should make very little difference. A bit of padding for safety.
468                              //   What's the smallest theoretical Speex file...? Very small!
469                              headerBytesLoaded = true;
470                              if(DEBUG){
471                                  System.err.println("Assuming Ogg Speex header is readable now");
472                              }
473                             
474                              // Trigger the audio format to be interpreted
475                              initialiseAudio();
476                             
477                          }
478 */
479                     }
480                     if(DEBUG){
481                         System.err.println("Downloader.run(): bytesDownloaded="+bytesDownloaded+", numBytes="+numBytes);
482                     }
483                 }
484                 fileOutStream.flush();
485                 fileOutStream.close();
486                 if(DEBUG){
487                     System.err.println("Downloader.run(): COMPLETED!");
488                 }
489
490                 // This should ideally be run earlier than 100% completion.
491                 // Should only be triggered here if the file is too small to
492                 //  triggered by the check that occurs during the loop above.
493                 if(!headerBytesLoaded) {
494                     initialiseAudio();
495                 }
496
497
498                 isComplete = true;
499                 progressBar.setString("Loaded");
500                 progressBar.setIndeterminate(false);
501                 progressBar.setValue(100);
502                 try{
503                     Thread.sleep(2000L);
504                     progressBar.setString("");
505                 }catch(InterruptedException err){
506                 }
507             }catch(IOException err){
508                 reportError("Downloader.run() encountered IOException while downloading to cache. bytesDownloaded="+bytesDownloaded+", numBytes="+numBytes, err);
509             }
510         }
511     } // End class Downloader
512
513
514 } // End main class
Note: See TracBrowser for help on using the browser.