View Javadoc
1   /*
2    * Copyright (c) 2002-2014, Mairie de Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.captcha.modules.jcaptcha.service.sound;
35  
36  import org.tritonus.share.sampled.FloatSampleBuffer;
37  
38  import java.io.ByteArrayInputStream;
39  import java.io.IOException;
40  
41  import javax.sound.sampled.AudioFormat;
42  import javax.sound.sampled.AudioInputStream;
43  import javax.sound.sampled.AudioSystem;
44  
45  
46  /**
47   * Mixing of multiple AudioInputStreams to one AudioInputStream. This class
48   * takes a collection of AudioInputStreams and mixes them together. Being a
49   * subclass of AudioInputStream itself, reading from instances of this class
50   * behaves as if the mixdown result of the input streams is read.
51   * <p>
52   * This class uses the FloatSampleBuffer for easy conversion using normalized
53   * samples in the buffers.
54   */
55  public class MixingFloatAudioInputStream extends AudioInputStream
56  {
57      private AudioInputStream[] _audioInputStreamArray;
58  
59      /**
60       * Attenuate the stream by how many dB per mixed stream. For example, if
61       * attenuationPerStream is 2dB, and 3 streams are mixed together, the mixed
62       * stream will be attenuated by 6dB. Set to 0 to not attenuate the signal,
63       * which will usually give good results if only 2 streams are mixed
64       * together.
65       */
66      private float _attenuationPerStream = 0.1f;
67  
68      /**
69       * The linear factor to apply to all samples (derived from
70       * attenuationPerStream). This is a factor in the range of 0..1 (depending
71       * on attenuationPerStream and the number of streams).
72       */
73      private float _attenuationFactor = 1.0f;
74      private float _parametredAttenuationFactor;
75      private FloatSampleBuffer _mixBuffer;
76      private FloatSampleBuffer _readBuffer;
77  
78      /**
79       * A buffer for byte to float conversion.
80       */
81      private byte[] _tempBuffer;
82  
83      /**
84       *
85       * @param audioFormat the audio Format
86       * @param original the original
87       * @param background the background
88       * @param backgroundAttenuationFactor the backgroundAttenuationFactor
89       */
90      public MixingFloatAudioInputStream( AudioFormat audioFormat, AudioInputStream original,
91          AudioInputStream background, float backgroundAttenuationFactor )
92      {
93          super( new ByteArrayInputStream( new byte[0] ), audioFormat, AudioSystem.NOT_SPECIFIED );
94  
95          _audioInputStreamArray = new AudioInputStream[2];
96          _audioInputStreamArray[0] = original;
97          _audioInputStreamArray[1] = background;
98  
99          // set up the static mix buffer with initially no samples. Note that
100         // using a static mix buffer prevents that this class can be used at
101         // once from different threads, but that wouldn't be useful anyway. But
102         // by re-using this buffer we save a lot of garbage collection.
103         _mixBuffer = new FloatSampleBuffer( audioFormat.getChannels(  ), 0, audioFormat.getSampleRate(  ) );
104 
105         // ditto for the read buffer. It is used for reading samples from the
106         // underlying streams.
107         _readBuffer = new FloatSampleBuffer(  );
108 
109         // calculate the linear attenuation factor
110         _attenuationFactor = decibel2linear( -1.0f * _attenuationPerStream * 2 );
111 
112         if ( backgroundAttenuationFactor > _attenuationFactor )
113         {
114             _parametredAttenuationFactor = _attenuationFactor;
115         }
116         else
117         {
118             _parametredAttenuationFactor = backgroundAttenuationFactor;
119         }
120     }
121 
122     /**
123      * The maximum of the frame length of the input stream is calculated and
124      * returned. If at least one of the input streams has length
125      * <code>AudioInputStream.NOT_SPECIFIED</code>, this value is returned.
126      *
127      * @return the frame length
128      */
129     public long getFrameLength(  )
130     {
131         long lLengthInFrames = 0;
132 
133         for ( AudioInputStream stream : _audioInputStreamArray )
134         {
135             long lLength = stream.getFrameLength(  );
136 
137             if ( lLength == AudioSystem.NOT_SPECIFIED )
138             {
139                 return AudioSystem.NOT_SPECIFIED;
140             }
141             else
142             {
143                 lLengthInFrames = Math.max( lLengthInFrames, lLength );
144             }
145         }
146 
147         return lLengthInFrames;
148     }
149 
150     /**
151      * @return the byte read
152      * @throws IOException the IOException
153      */
154     public int read(  ) throws IOException
155     {
156         byte[] samples = new byte[1];
157         int ret = read( samples );
158 
159         if ( ret != 1 )
160         {
161             return -1;
162         }
163 
164         return samples[0];
165     }
166 
167     /**
168      * @param abData the Data
169      * @param nOffset the Offset
170      * @param nLength the Length
171      * @return the byte read
172      * @throws IOException the IOException
173      *
174      */
175     public int read( byte[] abData, int nOffset, int nLength )
176         throws IOException
177     {
178         // set up the mix buffer with the requested size
179         _mixBuffer.changeSampleCount( nLength / getFormat(  ).getFrameSize(  ), false );
180 
181         // initialize the mixBuffer with silence
182         _mixBuffer.makeSilence(  );
183 
184         // remember the maximum number of samples actually mixed
185         int maxMixed = 0;
186 
187         for ( int streamNumber = 0; streamNumber < _audioInputStreamArray.length; streamNumber++ )
188         {
189             // calculate how many bytes we need to read from this stream
190             int needRead = _mixBuffer.getSampleCount(  ) * _audioInputStreamArray[streamNumber].getFormat(  )
191                                                                                                .getFrameSize(  );
192 
193             // set up the temporary byte buffer
194             if ( ( _tempBuffer == null ) || ( _tempBuffer.length < needRead ) )
195             {
196                 _tempBuffer = new byte[needRead];
197             }
198 
199             // read from the source stream
200             int bytesRead = _audioInputStreamArray[streamNumber].read( _tempBuffer, 0, needRead );
201 
202             if ( bytesRead == -1 )
203             {
204                 // end of stream: remove it from the list of streams.
205                 continue;
206             }
207 
208             // now convert this buffer to float samples
209             _readBuffer.initFromByteArray( _tempBuffer, 0, bytesRead, _audioInputStreamArray[streamNumber].getFormat(  ) );
210 
211             if ( maxMixed < _readBuffer.getSampleCount(  ) )
212             {
213                 maxMixed = _readBuffer.getSampleCount(  );
214             }
215 
216             // the actual mixing routine: add readBuffer to mixBuffer
217             // can only mix together as many channels as available
218             int maxChannels = Math.min( _mixBuffer.getChannelCount(  ), _readBuffer.getChannelCount(  ) );
219 
220             for ( int channel = 0; channel < maxChannels; channel++ )
221             {
222                 // get the arrays of the normalized float samples
223                 float[] readSamples = _readBuffer.getChannel( channel );
224                 float[] mixSamples = _mixBuffer.getChannel( channel );
225 
226                 // Never use readSamples.length or mixSamples.length: the length
227                 // of the array may be longer than the actual buffer ("lazy"
228                 // deletion).
229                 int maxSamples = Math.min( _mixBuffer.getSampleCount(  ), _readBuffer.getSampleCount(  ) );
230 
231                 // in a loop, add each "read" sample to the mix buffer
232                 // can only mix as many samples as available. Also apply the
233                 // attenuation factor.
234 
235                 // Note1: the attenuation factor could also be applied only once
236                 // in a separate loop after mixing all the streams together,
237                 // saving processor time in case of many mixed streams.
238 
239                 // Note2: adding everything together here will not cause
240                 // clipping, because all samples are in float format.
241                 if ( streamNumber == 1 )
242                 {
243                     for ( int sample = 0; sample < maxSamples; sample++ )
244                     {
245                         mixSamples[sample] += ( _parametredAttenuationFactor * readSamples[sample] );
246                     }
247                 }
248                 else
249                 {
250                     for ( int sample = 0; sample < maxSamples; sample++ )
251                     {
252                         mixSamples[sample] += ( _attenuationFactor * readSamples[sample] );
253                     }
254                 }
255             }
256         }
257 
258         if ( maxMixed == 0 )
259         {
260             // nothing written to the mixBuffer
261             if ( _audioInputStreamArray.length == 0 )
262             {
263                 // nothing mixed, no more streams available: end of stream
264                 return -1;
265             }
266 
267             // nothing written, but still streams to read from
268             return 0;
269         }
270 
271         // finally convert the mix Buffer to the requested byte array.
272         // This routine will handle clipping, i.e. if there are samples > 1.0f
273         // in the mix buffer, they will be clipped to 1.0f and converted to the
274         // specified audioFormat's sample format.
275         _mixBuffer.convertToByteArray( 0, maxMixed, abData, nOffset, getFormat(  ) );
276 
277         return maxMixed * getFormat(  ).getFrameSize(  );
278     }
279 
280     /**
281      * calls skip() on all input streams. There is no way to assure that the
282      * number of bytes really skipped is the same for all input streams. Due to
283      * that, this method always returns the passed value. In other words: the
284      * return value is useless (better ideas appreciated).
285      *
286      * @param lLength the length
287      * @return the length
288      * @throws IOException the IOException
289      */
290     public long skip( long lLength ) throws IOException
291     {
292         for ( AudioInputStream stream : _audioInputStreamArray )
293         {
294             stream.skip( lLength );
295         }
296 
297         return lLength;
298     }
299 
300     /**
301      * The minimum of available() of all input stream is calculated and
302      * returned.
303      *
304      * @return the available
305      * @throws IOException the IOException
306      */
307     public int available(  ) throws IOException
308     {
309         int nAvailable = 0;
310 
311         for ( AudioInputStream stream : _audioInputStreamArray )
312         {
313             nAvailable = Math.min( nAvailable, stream.available(  ) );
314         }
315 
316         return nAvailable;
317     }
318 
319     /**
320      * @throws IOException the IOException
321      */
322     public void close(  ) throws IOException
323     {
324         // TODO: should we close all streams in the list?
325     }
326 
327     /**
328      * Calls mark() on all input streams.
329      *
330      * @param nReadLimit the read limit
331      */
332     public void mark( int nReadLimit )
333     {
334         for ( AudioInputStream stream : _audioInputStreamArray )
335         {
336             stream.mark( nReadLimit );
337         }
338     }
339 
340     /**
341      * Calls reset() on all input streams.
342      *
343      * @throws IOException the IOException
344      */
345     public void reset(  ) throws IOException
346     {
347         for ( AudioInputStream stream : _audioInputStreamArray )
348         {
349             stream.reset(  );
350         }
351     }
352 
353     /**
354      * returns true if all input stream return true for markSupported().
355      *
356      * @return false if mark supported
357      */
358     public boolean markSupported(  )
359     {
360         for ( AudioInputStream stream : _audioInputStreamArray )
361         {
362             if ( !stream.markSupported(  ) )
363             {
364                 return false;
365             }
366         }
367 
368         return true;
369     }
370 
371     /**
372      *
373      * @param decibels the decibels
374      * @return the decibel linear
375      */
376     public static float decibel2linear( float decibels )
377     {
378         return (float) Math.pow( 10.0, decibels / 20.0 );
379     }
380 }
381 /** * MixingFloatAudioInputStream.java ** */