View Javadoc
1   /*
2    * Copyright (c) 2002-2021, City of 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   * Mixing of multiple AudioInputStreams to one AudioInputStream. This class takes a collection of AudioInputStreams and mixes them together. Being a subclass of
47   * AudioInputStream itself, reading from instances of this class behaves as if the mixdown result of the input streams is read.
48   * <p>
49   * This class uses the FloatSampleBuffer for easy conversion using normalized samples in the buffers.
50   */
51  public class MixingFloatAudioInputStream extends AudioInputStream
52  {
53      private AudioInputStream [ ] _audioInputStreamArray;
54  
55      /**
56       * Attenuate the stream by how many dB per mixed stream. For example, if attenuationPerStream is 2dB, and 3 streams are mixed together, the mixed stream
57       * will be attenuated by 6dB. Set to 0 to not attenuate the signal, which will usually give good results if only 2 streams are mixed together.
58       */
59      private float _attenuationPerStream = 0.1f;
60  
61      /**
62       * The linear factor to apply to all samples (derived from attenuationPerStream). This is a factor in the range of 0..1 (depending on attenuationPerStream
63       * and the number of streams).
64       */
65      private float _attenuationFactor = 1.0f;
66      private float _parametredAttenuationFactor;
67      private FloatSampleBuffer _mixBuffer;
68      private FloatSampleBuffer _readBuffer;
69  
70      /**
71       * A buffer for byte to float conversion.
72       */
73      private byte [ ] _tempBuffer;
74  
75      /**
76       *
77       * @param audioFormat
78       *            the audio Format
79       * @param original
80       *            the original
81       * @param background
82       *            the background
83       * @param backgroundAttenuationFactor
84       *            the backgroundAttenuationFactor
85       */
86      public MixingFloatAudioInputStream( AudioFormat audioFormat, AudioInputStream original, AudioInputStream background, float backgroundAttenuationFactor )
87      {
88          super( new ByteArrayInputStream( new byte [ 0] ), audioFormat, AudioSystem.NOT_SPECIFIED );
89  
90          _audioInputStreamArray = new AudioInputStream [ 2];
91          _audioInputStreamArray [0] = original;
92          _audioInputStreamArray [1] = background;
93  
94          // set up the static mix buffer with initially no samples. Note that
95          // using a static mix buffer prevents that this class can be used at
96          // once from different threads, but that wouldn't be useful anyway. But
97          // by re-using this buffer we save a lot of garbage collection.
98          _mixBuffer = new FloatSampleBuffer( audioFormat.getChannels( ), 0, audioFormat.getSampleRate( ) );
99  
100         // ditto for the read buffer. It is used for reading samples from the
101         // underlying streams.
102         _readBuffer = new FloatSampleBuffer( );
103 
104         // calculate the linear attenuation factor
105         _attenuationFactor = decibel2linear( -1.0f * _attenuationPerStream * 2 );
106 
107         if ( backgroundAttenuationFactor > _attenuationFactor )
108         {
109             _parametredAttenuationFactor = _attenuationFactor;
110         }
111         else
112         {
113             _parametredAttenuationFactor = backgroundAttenuationFactor;
114         }
115     }
116 
117     /**
118      * The maximum of the frame length of the input stream is calculated and returned. If at least one of the input streams has length
119      * <code>AudioInputStream.NOT_SPECIFIED</code>, this value is returned.
120      *
121      * @return the frame length
122      */
123     public long getFrameLength( )
124     {
125         long lLengthInFrames = 0;
126 
127         for ( AudioInputStream stream : _audioInputStreamArray )
128         {
129             long lLength = stream.getFrameLength( );
130 
131             if ( lLength == AudioSystem.NOT_SPECIFIED )
132             {
133                 return AudioSystem.NOT_SPECIFIED;
134             }
135             else
136             {
137                 lLengthInFrames = Math.max( lLengthInFrames, lLength );
138             }
139         }
140 
141         return lLengthInFrames;
142     }
143 
144     /**
145      * @return the byte read
146      * @throws IOException
147      *             the IOException
148      */
149     public int read( ) throws IOException
150     {
151         byte [ ] samples = new byte [ 1];
152         int ret = read( samples );
153 
154         if ( ret != 1 )
155         {
156             return -1;
157         }
158 
159         return samples [0];
160     }
161 
162     /**
163      * @param abData
164      *            the Data
165      * @param nOffset
166      *            the Offset
167      * @param nLength
168      *            the Length
169      * @return the byte read
170      * @throws IOException
171      *             the IOException
172      *
173      */
174     public int read( byte [ ] abData, int nOffset, int nLength ) throws IOException
175     {
176         // set up the mix buffer with the requested size
177         _mixBuffer.changeSampleCount( nLength / getFormat( ).getFrameSize( ), false );
178 
179         // initialize the mixBuffer with silence
180         _mixBuffer.makeSilence( );
181 
182         // remember the maximum number of samples actually mixed
183         int maxMixed = 0;
184 
185         for ( int streamNumber = 0; streamNumber < _audioInputStreamArray.length; streamNumber++ )
186         {
187             // calculate how many bytes we need to read from this stream
188             int needRead = _mixBuffer.getSampleCount( ) * _audioInputStreamArray [streamNumber].getFormat( ).getFrameSize( );
189 
190             // set up the temporary byte buffer
191             if ( ( _tempBuffer == null ) || ( _tempBuffer.length < needRead ) )
192             {
193                 _tempBuffer = new byte [ needRead];
194             }
195 
196             // read from the source stream
197             int bytesRead = _audioInputStreamArray [streamNumber].read( _tempBuffer, 0, needRead );
198 
199             if ( bytesRead == -1 )
200             {
201                 // end of stream: remove it from the list of streams.
202                 continue;
203             }
204 
205             // now convert this buffer to float samples
206             _readBuffer.initFromByteArray( _tempBuffer, 0, bytesRead, _audioInputStreamArray [streamNumber].getFormat( ) );
207 
208             if ( maxMixed < _readBuffer.getSampleCount( ) )
209             {
210                 maxMixed = _readBuffer.getSampleCount( );
211             }
212 
213             // the actual mixing routine: add readBuffer to mixBuffer
214             // can only mix together as many channels as available
215             int maxChannels = Math.min( _mixBuffer.getChannelCount( ), _readBuffer.getChannelCount( ) );
216 
217             for ( int channel = 0; channel < maxChannels; channel++ )
218             {
219                 // get the arrays of the normalized float samples
220                 float [ ] readSamples = _readBuffer.getChannel( channel );
221                 float [ ] mixSamples = _mixBuffer.getChannel( channel );
222 
223                 // Never use readSamples.length or mixSamples.length: the length
224                 // of the array may be longer than the actual buffer ("lazy"
225                 // deletion).
226                 int maxSamples = Math.min( _mixBuffer.getSampleCount( ), _readBuffer.getSampleCount( ) );
227 
228                 // in a loop, add each "read" sample to the mix buffer
229                 // can only mix as many samples as available. Also apply the
230                 // attenuation factor.
231 
232                 // Note1: the attenuation factor could also be applied only once
233                 // in a separate loop after mixing all the streams together,
234                 // saving processor time in case of many mixed streams.
235 
236                 // Note2: adding everything together here will not cause
237                 // clipping, because all samples are in float format.
238                 if ( streamNumber == 1 )
239                 {
240                     for ( int sample = 0; sample < maxSamples; sample++ )
241                     {
242                         mixSamples [sample] += ( _parametredAttenuationFactor * readSamples [sample] );
243                     }
244                 }
245                 else
246                 {
247                     for ( int sample = 0; sample < maxSamples; sample++ )
248                     {
249                         mixSamples [sample] += ( _attenuationFactor * readSamples [sample] );
250                     }
251                 }
252             }
253         }
254 
255         if ( maxMixed == 0 )
256         {
257             // nothing written to the mixBuffer
258             if ( _audioInputStreamArray.length == 0 )
259             {
260                 // nothing mixed, no more streams available: end of stream
261                 return -1;
262             }
263 
264             // nothing written, but still streams to read from
265             return 0;
266         }
267 
268         // finally convert the mix Buffer to the requested byte array.
269         // This routine will handle clipping, i.e. if there are samples > 1.0f
270         // in the mix buffer, they will be clipped to 1.0f and converted to the
271         // specified audioFormat's sample format.
272         _mixBuffer.convertToByteArray( 0, maxMixed, abData, nOffset, getFormat( ) );
273 
274         return maxMixed * getFormat( ).getFrameSize( );
275     }
276 
277     /**
278      * calls skip() on all input streams. There is no way to assure that the number of bytes really skipped is the same for all input streams. Due to that, this
279      * method always returns the passed value. In other words: the return value is useless (better ideas appreciated).
280      *
281      * @param lLength
282      *            the length
283      * @return the length
284      * @throws IOException
285      *             the IOException
286      */
287     public long skip( long lLength ) throws IOException
288     {
289         for ( AudioInputStream stream : _audioInputStreamArray )
290         {
291             stream.skip( lLength );
292         }
293 
294         return lLength;
295     }
296 
297     /**
298      * The minimum of available() of all input stream is calculated and returned.
299      *
300      * @return the available
301      * @throws IOException
302      *             the IOException
303      */
304     public int available( ) throws IOException
305     {
306         int nAvailable = 0;
307 
308         for ( AudioInputStream stream : _audioInputStreamArray )
309         {
310             nAvailable = Math.min( nAvailable, stream.available( ) );
311         }
312 
313         return nAvailable;
314     }
315 
316     /**
317      * @throws IOException
318      *             the IOException
319      */
320     public void close( ) throws IOException
321     {
322         // TODO: should we close all streams in the list?
323     }
324 
325     /**
326      * Calls mark() on all input streams.
327      *
328      * @param nReadLimit
329      *            the read limit
330      */
331     public void mark( int nReadLimit )
332     {
333         for ( AudioInputStream stream : _audioInputStreamArray )
334         {
335             stream.mark( nReadLimit );
336         }
337     }
338 
339     /**
340      * Calls reset() on all input streams.
341      *
342      * @throws IOException
343      *             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
374      *            the decibels
375      * @return the decibel linear
376      */
377     public static float decibel2linear( float decibels )
378     {
379         return (float) Math.pow( 10.0, decibels / 20.0 );
380     }
381 }
382 /** * MixingFloatAudioInputStream.java ** */