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 ** */