001: /*
002: * Copyright (c) 2000 Silvere Martin-Michiellot All Rights Reserved.
003: *
004: * Silvere Martin-Michiellot grants you ("Licensee") a non-exclusive,
005: * royalty free, license to use, modify and redistribute this
006: * software in source and binary code form,
007: * provided that i) this copyright notice and license appear on all copies of
008: * the software; and ii) Licensee does not utilize the software in a manner
009: * which is disparaging to Silvere Martin-Michiellot.
010: *
011: * This software is provided "AS IS," without a warranty of any kind. ALL
012: * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
013: * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
014: * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. Silvere Martin-Michiellot
015: * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES
016: * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
017: * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL
018: * Silvere Martin-Michiellot OR ITS LICENSORS BE LIABLE
019: * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
020: * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
021: * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
022: * OR INABILITY TO USE SOFTWARE, EVEN IF Silvere Martin-Michiellot HAS BEEN
023: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
024: *
025: * This software is not designed or intended for use in on-line control of
026: * aircraft, air traffic, aircraft navigation or aircraft communications; or in
027: * the design, construction, operation or maintenance of any nuclear
028: * facility. Licensee represents and warrants that it will not use or
029: * redistribute the Software for such purposes.
030: *
031: */
032:
033: package com.db.media.audio;
034:
035: // This code is repackaged after the code from Florian Bomers & Matthias Pfisterer from JavaSound Examples
036: // Site http://www.jsresources.org/examples/
037: // Email tritonus-user@lists.sourceforge.net
038:
039: import java.io.*;
040: import java.net.URL;
041:
042: import java.util.ArrayList;
043: import java.util.Collection;
044: import java.util.Iterator;
045: import java.util.List;
046:
047: import javax.sound.sampled.*;
048:
049: /**
050: * This class takes a collection of AudioInputStreams and mixes
051: * them together. Being a subclass of AudioInputStream itself,
052: * reading from instances of this class behaves as if the mixdown
053: * result of the input streams is read.
054: */
055: public class MixingAudioInputStream extends AudioInputStream {
056:
057: private ArrayList audioInputStreamList;
058:
059: public MixingAudioInputStream(AudioFormat audioFormat,
060: Collection audioInputStreams) {
061:
062: super (new ByteArrayInputStream(new byte[0]), audioFormat,
063: AudioSystem.NOT_SPECIFIED);
064: audioInputStreamList = new ArrayList(audioInputStreams);
065:
066: }
067:
068: public boolean addAudioInputStream(AudioInputStream audioStream) {
069:
070: if (!getFormat().matches(audioStream.getFormat())) {
071: AudioInputStream asold = audioStream;
072: audioStream = AudioSystem.getAudioInputStream(getFormat(),
073: asold);
074: if (audioStream == null) {
075: return false;
076: }
077: }
078: synchronized (audioInputStreamList) {
079: audioInputStreamList.add(audioStream);
080: audioInputStreamList.notifyAll();
081: }
082:
083: return true;
084:
085: }
086:
087: public boolean removeAudioInputStream(AudioInputStream audioStream) {
088:
089: if ((audioStream == null)
090: || (!audioInputStreamList.contains(audioStream))) {
091: return false;
092: } else {
093: synchronized (audioInputStreamList) {
094: audioInputStreamList.remove(audioStream);
095: audioInputStreamList.notifyAll();
096: }
097: return true;
098: }
099:
100: }
101:
102: /**
103: * The maximum of the frame length of the input stream is calculated and returned.
104: * If at least one of the input streams has length
105: * <code>AudioInputStream.NOT_SPECIFIED</code>, this value is returned.
106: */
107: public long getFrameLength() {
108:
109: long lLengthInFrames = 0;
110: Iterator streamIterator = audioInputStreamList.iterator();
111: while (streamIterator.hasNext()) {
112: AudioInputStream stream = (AudioInputStream) streamIterator
113: .next();
114: long lLength = stream.getFrameLength();
115: if (lLength == AudioSystem.NOT_SPECIFIED) {
116: return AudioSystem.NOT_SPECIFIED;
117: } else {
118: lLengthInFrames = Math.max(lLengthInFrames, lLength);
119: }
120:
121: }
122:
123: return lLengthInFrames;
124:
125: }
126:
127: public int read() throws IOException {
128:
129: int nSample = 0;
130: Iterator streamIterator = audioInputStreamList.iterator();
131: while (streamIterator.hasNext()) {
132: AudioInputStream stream = (AudioInputStream) streamIterator
133: .next();
134: int nByte = stream.read();
135: if (nByte == -1) {
136: streamIterator.remove();
137: continue;
138: } else {
139: nSample += nByte;
140: }
141: }
142:
143: return (byte) nSample;
144:
145: }
146:
147: public int read(byte[] abData, int nOffset, int nLength)
148: throws IOException {
149:
150: int nSample = 0;
151: int nChannels = getFormat().getChannels();
152: int nFrameSize = getFormat().getFrameSize();
153: /*
154: This value is in bytes. Note that it is the storage size.
155: It may be four bytes for 24 bit samples.
156: */
157: int nSampleSize = nFrameSize / nChannels;
158: boolean bBigEndian = getFormat().isBigEndian();
159: AudioFormat.Encoding encoding = getFormat().getEncoding();
160: byte[] abBuffer = new byte[nFrameSize];
161: int[] anMixedSamples = new int[nChannels];
162: for (int nFrameBoundry = 0; nFrameBoundry < nLength; nFrameBoundry += nFrameSize) {
163: for (int i = 0; i < nChannels; i++) {
164: anMixedSamples[i] = 0;
165: }
166: Iterator streamIterator = audioInputStreamList.iterator();
167: while (streamIterator.hasNext()) {
168: AudioInputStream stream = (AudioInputStream) streamIterator
169: .next();
170: int nBytesRead = stream.read(abBuffer, 0, nFrameSize);
171: /*
172: TODO: we have to handle incomplete reads.
173: */
174: if (nBytesRead == -1) {
175: streamIterator.remove();
176: continue;
177: } else {
178: nSample += nBytesRead;
179: }
180: for (int nChannel = 0; nChannel < nChannels; nChannel++) {
181: int nBufferOffset = nChannel * nSampleSize;
182: int nSampleToAdd = 0;
183: if (encoding
184: .equals(AudioFormat.Encoding.PCM_SIGNED)) {
185: switch (nSampleSize) {
186: case 1:
187: nSampleToAdd = abBuffer[nBufferOffset];
188: break;
189: case 2:
190: nSampleToAdd = AudioConversionTool
191: .bytesToInt16(abBuffer,
192: nBufferOffset, bBigEndian);
193: break;
194: case 3:
195: nSampleToAdd = AudioConversionTool
196: .bytesToInt24(abBuffer,
197: nBufferOffset, bBigEndian);
198: break;
199: case 4:
200: nSampleToAdd = AudioConversionTool
201: .bytesToInt32(abBuffer,
202: nBufferOffset, bBigEndian);
203: break;
204: }
205: }
206: // TODO: pcm unsigned
207: else if (encoding.equals(AudioFormat.Encoding.ALAW)) {
208: nSampleToAdd = AudioConversionTool
209: .alaw2linear(abBuffer[nBufferOffset]);
210: } else if (encoding
211: .equals(AudioFormat.Encoding.ULAW)) {
212: nSampleToAdd = AudioConversionTool
213: .ulaw2linear(abBuffer[nBufferOffset]);
214: }
215: anMixedSamples[nChannel] += nSampleToAdd;
216: } // loop over channels
217: } // loop over streams
218: for (int nChannel = 0; nChannel < nChannels; nChannel++) {
219: int nBufferOffset = nOffset + nFrameBoundry /* * nFrameSize*/
220: + nChannel * nSampleSize;
221: if (encoding.equals(AudioFormat.Encoding.PCM_SIGNED)) {
222: switch (nSampleSize) {
223: case 1:
224: abData[nBufferOffset] = (byte) anMixedSamples[nChannel];
225: break;
226: case 2:
227: AudioConversionTool.intToBytes16(
228: anMixedSamples[nChannel], abData,
229: nBufferOffset, bBigEndian);
230: break;
231: case 3:
232: AudioConversionTool.intToBytes24(
233: anMixedSamples[nChannel], abData,
234: nBufferOffset, bBigEndian);
235: break;
236: case 4:
237: AudioConversionTool.intToBytes32(
238: anMixedSamples[nChannel], abData,
239: nBufferOffset, bBigEndian);
240: break;
241: }
242: }
243: // TODO: pcm unsigned
244: else if (encoding.equals(AudioFormat.Encoding.ALAW)) {
245: abData[nBufferOffset] = AudioConversionTool
246: .linear2alaw((short) anMixedSamples[nChannel]);
247: } else if (encoding.equals(AudioFormat.Encoding.ULAW)) {
248: abData[nBufferOffset] = AudioConversionTool
249: .linear2ulaw(anMixedSamples[nChannel]);
250: }
251: } // (final) loop over channels
252: } // loop over frames
253:
254: return nSample;
255:
256: }
257:
258: /**
259: * calls skip() on all input streams. There is no way to assure that the number of
260: * bytes really skipped is the same for all input streams. Due to that, this
261: * method always returns the passed value. In other words: the return value
262: * is useless (better ideas appreciated).
263: */
264: public long skip(long lLength) throws IOException {
265:
266: int nAvailable = 0;
267: Iterator streamIterator = audioInputStreamList.iterator();
268: while (streamIterator.hasNext()) {
269: AudioInputStream stream = (AudioInputStream) streamIterator
270: .next();
271: stream.skip(lLength);
272: }
273:
274: return lLength;
275:
276: }
277:
278: /**
279: * The minimum of available() of all input stream is calculated and returned.
280: */
281: public int available() throws IOException {
282:
283: int nAvailable = 0;
284: Iterator streamIterator = audioInputStreamList.iterator();
285: while (streamIterator.hasNext()) {
286: AudioInputStream stream = (AudioInputStream) streamIterator
287: .next();
288: nAvailable = Math.min(nAvailable, stream.available());
289: }
290:
291: return nAvailable;
292:
293: }
294:
295: /**
296: * Calls close() on all input streams and closes itself.
297: */
298: public void close() throws IOException {
299:
300: Iterator streamIterator = audioInputStreamList.iterator();
301: while (streamIterator.hasNext()) {
302: AudioInputStream stream = (AudioInputStream) streamIterator
303: .next();
304: stream.close();
305: }
306: super .close();
307:
308: }
309:
310: /**
311: * Calls mark() on all input streams.
312: */
313: public void mark(int nReadLimit) {
314:
315: Iterator streamIterator = audioInputStreamList.iterator();
316: while (streamIterator.hasNext()) {
317: AudioInputStream stream = (AudioInputStream) streamIterator
318: .next();
319: stream.mark(nReadLimit);
320: }
321: super .mark(nReadLimit);
322:
323: }
324:
325: /**
326: * Calls reset() on all input streams and resets itself
327: */
328: public void reset() throws IOException {
329:
330: Iterator streamIterator = audioInputStreamList.iterator();
331: while (streamIterator.hasNext()) {
332: AudioInputStream stream = (AudioInputStream) streamIterator
333: .next();
334: stream.reset();
335: }
336: super .reset();
337:
338: }
339:
340: /**
341: * returns true if all input stream return true for markSupported().
342: */
343: public boolean markSupported() {
344:
345: Iterator streamIterator = audioInputStreamList.iterator();
346: while (streamIterator.hasNext()) {
347: AudioInputStream stream = (AudioInputStream) streamIterator
348: .next();
349: if (!stream.markSupported()) {
350: return false;
351: }
352: }
353:
354: return true;
355:
356: }
357:
358: }
|