1 /*
2  * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.media.sound;
27 
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.util.Arrays;
31 
32 import javax.sound.sampled.AudioFormat;
33 import javax.sound.sampled.AudioInputStream;
34 import javax.sound.sampled.AudioSystem;
35 import javax.sound.sampled.DataLine;
36 import javax.sound.sampled.LineEvent;
37 import javax.sound.sampled.LineUnavailableException;
38 import javax.sound.sampled.SourceDataLine;
39 
40 /**
41  * SourceDataLine implementation for the SoftMixingMixer.
42  *
43  * @author Karl Helgason
44  */
45 public final class SoftMixingSourceDataLine extends SoftMixingDataLine
46         implements SourceDataLine {
47 
48     private boolean open = false;
49 
50     private AudioFormat format = new AudioFormat(44100.0f, 16, 2, true, false);
51 
52     private int framesize;
53 
54     private int bufferSize = -1;
55 
56     private float[] readbuffer;
57 
58     private boolean active = false;
59 
60     private byte[] cycling_buffer;
61 
62     private int cycling_read_pos = 0;
63 
64     private int cycling_write_pos = 0;
65 
66     private int cycling_avail = 0;
67 
68     private long cycling_framepos = 0;
69 
70     private AudioFloatInputStream afis;
71 
72     private static class NonBlockingFloatInputStream extends
73             AudioFloatInputStream {
74         AudioFloatInputStream ais;
75 
NonBlockingFloatInputStream(AudioFloatInputStream ais)76         NonBlockingFloatInputStream(AudioFloatInputStream ais) {
77             this.ais = ais;
78         }
79 
80         @Override
available()81         public int available() throws IOException {
82             return ais.available();
83         }
84 
85         @Override
close()86         public void close() throws IOException {
87             ais.close();
88         }
89 
90         @Override
getFormat()91         public AudioFormat getFormat() {
92             return ais.getFormat();
93         }
94 
95         @Override
getFrameLength()96         public long getFrameLength() {
97             return ais.getFrameLength();
98         }
99 
100         @Override
mark(int readlimit)101         public void mark(int readlimit) {
102             ais.mark(readlimit);
103         }
104 
105         @Override
markSupported()106         public boolean markSupported() {
107             return ais.markSupported();
108         }
109 
110         @Override
read(float[] b, int off, int len)111         public int read(float[] b, int off, int len) throws IOException {
112             int avail = available();
113             if (len > avail) {
114                 int ret = ais.read(b, off, avail);
115                 Arrays.fill(b, off + ret, off + len, 0);
116                 return len;
117             }
118             return ais.read(b, off, len);
119         }
120 
121         @Override
reset()122         public void reset() throws IOException {
123             ais.reset();
124         }
125 
126         @Override
skip(long len)127         public long skip(long len) throws IOException {
128             return ais.skip(len);
129         }
130 
131     }
132 
SoftMixingSourceDataLine(SoftMixingMixer mixer, DataLine.Info info)133     SoftMixingSourceDataLine(SoftMixingMixer mixer, DataLine.Info info) {
134         super(mixer, info);
135     }
136 
137     @Override
write(byte[] b, int off, int len)138     public int write(byte[] b, int off, int len) {
139         if (!isOpen())
140             return 0;
141         if (len % framesize != 0)
142             throw new IllegalArgumentException(
143                     "Number of bytes does not represent an integral number of sample frames.");
144         if (off < 0) {
145             throw new ArrayIndexOutOfBoundsException(off);
146         }
147         if ((long)off + (long)len > (long)b.length) {
148             throw new ArrayIndexOutOfBoundsException(b.length);
149         }
150 
151         byte[] buff = cycling_buffer;
152         int buff_len = cycling_buffer.length;
153 
154         int l = 0;
155         while (l != len) {
156             int avail;
157             synchronized (cycling_buffer) {
158                 int pos = cycling_write_pos;
159                 avail = cycling_avail;
160                 while (l != len) {
161                     if (avail == buff_len)
162                         break;
163                     buff[pos++] = b[off++];
164                     l++;
165                     avail++;
166                     if (pos == buff_len)
167                         pos = 0;
168                 }
169                 cycling_avail = avail;
170                 cycling_write_pos = pos;
171                 if (l == len)
172                     return l;
173             }
174             if (avail == buff_len) {
175                 try {
176                     Thread.sleep(1);
177                 } catch (InterruptedException e) {
178                     return l;
179                 }
180                 if (!isRunning())
181                     return l;
182             }
183         }
184 
185         return l;
186     }
187 
188     //
189     // BooleanControl.Type.APPLY_REVERB
190     // BooleanControl.Type.MUTE
191     // EnumControl.Type.REVERB
192     //
193     // FloatControl.Type.SAMPLE_RATE
194     // FloatControl.Type.REVERB_SEND
195     // FloatControl.Type.VOLUME
196     // FloatControl.Type.PAN
197     // FloatControl.Type.MASTER_GAIN
198     // FloatControl.Type.BALANCE
199 
200     private boolean _active = false;
201 
202     private AudioFormat outputformat;
203 
204     private int out_nrofchannels;
205 
206     private int in_nrofchannels;
207 
208     private float _rightgain;
209 
210     private float _leftgain;
211 
212     private float _eff1gain;
213 
214     private float _eff2gain;
215 
216     @Override
processControlLogic()217     protected void processControlLogic() {
218         _active = active;
219         _rightgain = rightgain;
220         _leftgain = leftgain;
221         _eff1gain = eff1gain;
222         _eff2gain = eff2gain;
223     }
224 
225     @Override
processAudioLogic(SoftAudioBuffer[] buffers)226     protected void processAudioLogic(SoftAudioBuffer[] buffers) {
227         if (_active) {
228             float[] left = buffers[SoftMixingMainMixer.CHANNEL_LEFT].array();
229             float[] right = buffers[SoftMixingMainMixer.CHANNEL_RIGHT].array();
230             int bufferlen = buffers[SoftMixingMainMixer.CHANNEL_LEFT].getSize();
231 
232             int readlen = bufferlen * in_nrofchannels;
233             if (readbuffer == null || readbuffer.length < readlen) {
234                 readbuffer = new float[readlen];
235             }
236             int ret = 0;
237             try {
238                 ret = afis.read(readbuffer);
239                 if (ret != in_nrofchannels)
240                     Arrays.fill(readbuffer, ret, readlen, 0);
241             } catch (IOException e) {
242             }
243 
244             int in_c = in_nrofchannels;
245             for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) {
246                 left[i] += readbuffer[ix] * _leftgain;
247             }
248             if (out_nrofchannels != 1) {
249                 if (in_nrofchannels == 1) {
250                     for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) {
251                         right[i] += readbuffer[ix] * _rightgain;
252                     }
253                 } else {
254                     for (int i = 0, ix = 1; i < bufferlen; i++, ix += in_c) {
255                         right[i] += readbuffer[ix] * _rightgain;
256                     }
257                 }
258 
259             }
260 
261             if (_eff1gain > 0.0001) {
262                 float[] eff1 = buffers[SoftMixingMainMixer.CHANNEL_EFFECT1]
263                         .array();
264                 for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) {
265                     eff1[i] += readbuffer[ix] * _eff1gain;
266                 }
267                 if (in_nrofchannels == 2) {
268                     for (int i = 0, ix = 1; i < bufferlen; i++, ix += in_c) {
269                         eff1[i] += readbuffer[ix] * _eff1gain;
270                     }
271                 }
272             }
273 
274             if (_eff2gain > 0.0001) {
275                 float[] eff2 = buffers[SoftMixingMainMixer.CHANNEL_EFFECT2]
276                         .array();
277                 for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) {
278                     eff2[i] += readbuffer[ix] * _eff2gain;
279                 }
280                 if (in_nrofchannels == 2) {
281                     for (int i = 0, ix = 1; i < bufferlen; i++, ix += in_c) {
282                         eff2[i] += readbuffer[ix] * _eff2gain;
283                     }
284                 }
285             }
286 
287         }
288     }
289 
290     @Override
open()291     public void open() throws LineUnavailableException {
292         open(format);
293     }
294 
295     @Override
open(AudioFormat format)296     public void open(AudioFormat format) throws LineUnavailableException {
297         if (bufferSize == -1)
298             bufferSize = ((int) (format.getFrameRate() / 2))
299                     * format.getFrameSize();
300         open(format, bufferSize);
301     }
302 
303     @Override
open(AudioFormat format, int bufferSize)304     public void open(AudioFormat format, int bufferSize)
305             throws LineUnavailableException {
306 
307         LineEvent event = null;
308 
309         if (bufferSize < format.getFrameSize() * 32)
310             bufferSize = format.getFrameSize() * 32;
311 
312         synchronized (control_mutex) {
313 
314             if (!isOpen()) {
315                 if (!mixer.isOpen()) {
316                     mixer.open();
317                     mixer.implicitOpen = true;
318                 }
319 
320                 event = new LineEvent(this, LineEvent.Type.OPEN, 0);
321 
322                 this.bufferSize = bufferSize - bufferSize
323                         % format.getFrameSize();
324                 this.format = format;
325                 this.framesize = format.getFrameSize();
326                 this.outputformat = mixer.getFormat();
327                 out_nrofchannels = outputformat.getChannels();
328                 in_nrofchannels = format.getChannels();
329 
330                 open = true;
331 
332                 mixer.getMainMixer().openLine(this);
333 
334                 cycling_buffer = new byte[framesize * bufferSize];
335                 cycling_read_pos = 0;
336                 cycling_write_pos = 0;
337                 cycling_avail = 0;
338                 cycling_framepos = 0;
339 
340                 InputStream cycling_inputstream = new InputStream() {
341 
342                     @Override
343                     public int read() throws IOException {
344                         byte[] b = new byte[1];
345                         int ret = read(b);
346                         if (ret < 0)
347                             return ret;
348                         return b[0] & 0xFF;
349                     }
350 
351                     @Override
352                     public int available() throws IOException {
353                         synchronized (cycling_buffer) {
354                             return cycling_avail;
355                         }
356                     }
357 
358                     @Override
359                     public int read(byte[] b, int off, int len)
360                             throws IOException {
361 
362                         synchronized (cycling_buffer) {
363                             if (len > cycling_avail)
364                                 len = cycling_avail;
365                             int pos = cycling_read_pos;
366                             byte[] buff = cycling_buffer;
367                             int buff_len = buff.length;
368                             for (int i = 0; i < len; i++) {
369                                 b[off++] = buff[pos];
370                                 pos++;
371                                 if (pos == buff_len)
372                                     pos = 0;
373                             }
374                             cycling_read_pos = pos;
375                             cycling_avail -= len;
376                             cycling_framepos += len / framesize;
377                         }
378                         return len;
379                     }
380 
381                 };
382 
383                 afis = AudioFloatInputStream
384                         .getInputStream(new AudioInputStream(
385                                 cycling_inputstream, format,
386                                 AudioSystem.NOT_SPECIFIED));
387                 afis = new NonBlockingFloatInputStream(afis);
388 
389                 if (Math.abs(format.getSampleRate()
390                         - outputformat.getSampleRate()) > 0.000001)
391                     afis = new AudioFloatInputStreamResampler(afis,
392                             outputformat);
393 
394             } else {
395                 if (!format.matches(getFormat())) {
396                     throw new IllegalStateException(
397                             "Line is already open with format " + getFormat()
398                                     + " and bufferSize " + getBufferSize());
399                 }
400             }
401 
402         }
403 
404         if (event != null)
405             sendEvent(event);
406 
407     }
408 
409     @Override
available()410     public int available() {
411         synchronized (cycling_buffer) {
412             return cycling_buffer.length - cycling_avail;
413         }
414     }
415 
416     @Override
drain()417     public void drain() {
418         while (true) {
419             int avail;
420             synchronized (cycling_buffer) {
421                 avail = cycling_avail;
422             }
423             if (avail != 0)
424                 return;
425             try {
426                 Thread.sleep(1);
427             } catch (InterruptedException e) {
428                 return;
429             }
430         }
431     }
432 
433     @Override
flush()434     public void flush() {
435         synchronized (cycling_buffer) {
436             cycling_read_pos = 0;
437             cycling_write_pos = 0;
438             cycling_avail = 0;
439         }
440     }
441 
442     @Override
getBufferSize()443     public int getBufferSize() {
444         synchronized (control_mutex) {
445             return bufferSize;
446         }
447     }
448 
449     @Override
getFormat()450     public AudioFormat getFormat() {
451         synchronized (control_mutex) {
452             return format;
453         }
454     }
455 
456     @Override
getFramePosition()457     public int getFramePosition() {
458         return (int) getLongFramePosition();
459     }
460 
461     @Override
getLevel()462     public float getLevel() {
463         return AudioSystem.NOT_SPECIFIED;
464     }
465 
466     @Override
getLongFramePosition()467     public long getLongFramePosition() {
468         synchronized (cycling_buffer) {
469             return cycling_framepos;
470         }
471     }
472 
473     @Override
getMicrosecondPosition()474     public long getMicrosecondPosition() {
475         return (long) (getLongFramePosition() * (1000000.0 / (double) getFormat()
476                 .getSampleRate()));
477     }
478 
479     @Override
isActive()480     public boolean isActive() {
481         synchronized (control_mutex) {
482             return active;
483         }
484     }
485 
486     @Override
isRunning()487     public boolean isRunning() {
488         synchronized (control_mutex) {
489             return active;
490         }
491     }
492 
493     @Override
start()494     public void start() {
495 
496         LineEvent event = null;
497 
498         synchronized (control_mutex) {
499             if (isOpen()) {
500                 if (active)
501                     return;
502                 active = true;
503                 event = new LineEvent(this, LineEvent.Type.START,
504                         getLongFramePosition());
505             }
506         }
507 
508         if (event != null)
509             sendEvent(event);
510     }
511 
512     @Override
stop()513     public void stop() {
514         LineEvent event = null;
515 
516         synchronized (control_mutex) {
517             if (isOpen()) {
518                 if (!active)
519                     return;
520                 active = false;
521                 event = new LineEvent(this, LineEvent.Type.STOP,
522                         getLongFramePosition());
523             }
524         }
525 
526         if (event != null)
527             sendEvent(event);
528     }
529 
530     @Override
close()531     public void close() {
532 
533         LineEvent event = null;
534 
535         synchronized (control_mutex) {
536             if (!isOpen())
537                 return;
538             stop();
539 
540             event = new LineEvent(this, LineEvent.Type.CLOSE,
541                     getLongFramePosition());
542 
543             open = false;
544             mixer.getMainMixer().closeLine(this);
545         }
546 
547         if (event != null)
548             sendEvent(event);
549 
550     }
551 
552     @Override
isOpen()553     public boolean isOpen() {
554         synchronized (control_mutex) {
555             return open;
556         }
557     }
558 }
559