Coverage Report - fr.paris.lutece.plugins.captcha.modules.jcaptcha.service.sound.MixingFloatAudioInputStream
 
Classes in this File Line Coverage Branch Coverage Complexity
MixingFloatAudioInputStream
0 %
0/74
0 %
0/42
3,364
 
 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  0
     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  0
     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  0
         super( new ByteArrayInputStream( new byte[0] ), audioFormat, AudioSystem.NOT_SPECIFIED );
 94  
 
 95  0
         _audioInputStreamArray = new AudioInputStream[2];
 96  0
         _audioInputStreamArray[0] = original;
 97  0
         _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  0
         _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  0
         _readBuffer = new FloatSampleBuffer(  );
 108  
 
 109  
         // calculate the linear attenuation factor
 110  0
         _attenuationFactor = decibel2linear( -1.0f * _attenuationPerStream * 2 );
 111  
 
 112  0
         if ( backgroundAttenuationFactor > _attenuationFactor )
 113  
         {
 114  0
             _parametredAttenuationFactor = _attenuationFactor;
 115  
         }
 116  
         else
 117  
         {
 118  0
             _parametredAttenuationFactor = backgroundAttenuationFactor;
 119  
         }
 120  0
     }
 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  0
         long lLengthInFrames = 0;
 132  
 
 133  0
         for ( AudioInputStream stream : _audioInputStreamArray )
 134  
         {
 135  0
             long lLength = stream.getFrameLength(  );
 136  
 
 137  0
             if ( lLength == AudioSystem.NOT_SPECIFIED )
 138  
             {
 139  0
                 return AudioSystem.NOT_SPECIFIED;
 140  
             }
 141  
             else
 142  
             {
 143  0
                 lLengthInFrames = Math.max( lLengthInFrames, lLength );
 144  
             }
 145  
         }
 146  
 
 147  0
         return lLengthInFrames;
 148  
     }
 149  
 
 150  
     /**
 151  
      * @return the byte read
 152  
      * @throws IOException the IOException
 153  
      */
 154  
     public int read(  ) throws IOException
 155  
     {
 156  0
         byte[] samples = new byte[1];
 157  0
         int ret = read( samples );
 158  
 
 159  0
         if ( ret != 1 )
 160  
         {
 161  0
             return -1;
 162  
         }
 163  
 
 164  0
         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  0
         _mixBuffer.changeSampleCount( nLength / getFormat(  ).getFrameSize(  ), false );
 180  
 
 181  
         // initialize the mixBuffer with silence
 182  0
         _mixBuffer.makeSilence(  );
 183  
 
 184  
         // remember the maximum number of samples actually mixed
 185  0
         int maxMixed = 0;
 186  
 
 187  0
         for ( int streamNumber = 0; streamNumber < _audioInputStreamArray.length; streamNumber++ )
 188  
         {
 189  
             // calculate how many bytes we need to read from this stream
 190  0
             int needRead = _mixBuffer.getSampleCount(  ) * _audioInputStreamArray[streamNumber].getFormat(  )
 191  0
                                                                                                .getFrameSize(  );
 192  
 
 193  
             // set up the temporary byte buffer
 194  0
             if ( ( _tempBuffer == null ) || ( _tempBuffer.length < needRead ) )
 195  
             {
 196  0
                 _tempBuffer = new byte[needRead];
 197  
             }
 198  
 
 199  
             // read from the source stream
 200  0
             int bytesRead = _audioInputStreamArray[streamNumber].read( _tempBuffer, 0, needRead );
 201  
 
 202  0
             if ( bytesRead == -1 )
 203  
             {
 204  
                 // end of stream: remove it from the list of streams.
 205  0
                 continue;
 206  
             }
 207  
 
 208  
             // now convert this buffer to float samples
 209  0
             _readBuffer.initFromByteArray( _tempBuffer, 0, bytesRead, _audioInputStreamArray[streamNumber].getFormat(  ) );
 210  
 
 211  0
             if ( maxMixed < _readBuffer.getSampleCount(  ) )
 212  
             {
 213  0
                 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  0
             int maxChannels = Math.min( _mixBuffer.getChannelCount(  ), _readBuffer.getChannelCount(  ) );
 219  
 
 220  0
             for ( int channel = 0; channel < maxChannels; channel++ )
 221  
             {
 222  
                 // get the arrays of the normalized float samples
 223  0
                 float[] readSamples = _readBuffer.getChannel( channel );
 224  0
                 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  0
                 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  0
                 if ( streamNumber == 1 )
 242  
                 {
 243  0
                     for ( int sample = 0; sample < maxSamples; sample++ )
 244  
                     {
 245  0
                         mixSamples[sample] += ( _parametredAttenuationFactor * readSamples[sample] );
 246  
                     }
 247  
                 }
 248  
                 else
 249  
                 {
 250  0
                     for ( int sample = 0; sample < maxSamples; sample++ )
 251  
                     {
 252  0
                         mixSamples[sample] += ( _attenuationFactor * readSamples[sample] );
 253  
                     }
 254  
                 }
 255  
             }
 256  
         }
 257  
 
 258  0
         if ( maxMixed == 0 )
 259  
         {
 260  
             // nothing written to the mixBuffer
 261  0
             if ( _audioInputStreamArray.length == 0 )
 262  
             {
 263  
                 // nothing mixed, no more streams available: end of stream
 264  0
                 return -1;
 265  
             }
 266  
 
 267  
             // nothing written, but still streams to read from
 268  0
             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  0
         _mixBuffer.convertToByteArray( 0, maxMixed, abData, nOffset, getFormat(  ) );
 276  
 
 277  0
         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  0
         for ( AudioInputStream stream : _audioInputStreamArray )
 293  
         {
 294  0
             stream.skip( lLength );
 295  
         }
 296  
 
 297  0
         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  0
         int nAvailable = 0;
 310  
 
 311  0
         for ( AudioInputStream stream : _audioInputStreamArray )
 312  
         {
 313  0
             nAvailable = Math.min( nAvailable, stream.available(  ) );
 314  
         }
 315  
 
 316  0
         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  0
     }
 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  0
         for ( AudioInputStream stream : _audioInputStreamArray )
 335  
         {
 336  0
             stream.mark( nReadLimit );
 337  
         }
 338  0
     }
 339  
 
 340  
     /**
 341  
      * Calls reset() on all input streams.
 342  
      *
 343  
      * @throws IOException the IOException
 344  
      */
 345  
     public void reset(  ) throws IOException
 346  
     {
 347  0
         for ( AudioInputStream stream : _audioInputStreamArray )
 348  
         {
 349  0
             stream.reset(  );
 350  
         }
 351  0
     }
 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  0
         for ( AudioInputStream stream : _audioInputStreamArray )
 361  
         {
 362  0
             if ( !stream.markSupported(  ) )
 363  
             {
 364  0
                 return false;
 365  
             }
 366  
         }
 367  
 
 368  0
         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  0
         return (float) Math.pow( 10.0, decibels / 20.0 );
 379  
     }
 380  
 }
 381  
 /** * MixingFloatAudioInputStream.java ** */