Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
MixingFloatAudioInputStream |
|
| 3.3636363636363638;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 ** */ |