001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030/** 031 * 032 */ 033package org.openimaj.audio; 034 035import java.io.ByteArrayOutputStream; 036 037import javax.sound.sampled.AudioSystem; 038import javax.sound.sampled.DataLine; 039import javax.sound.sampled.LineUnavailableException; 040import javax.sound.sampled.TargetDataLine; 041 042/** 043 * Audio grabber that uses the Java Sound API as a sound source. 044 * 045 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 046 * 047 * @created 28 Oct 2011 048 */ 049public class JavaSoundAudioGrabber extends AudioGrabber { 050 /** The current sample chunk */ 051 private final SampleChunk currentSample = new SampleChunk(this.getFormat()); 052 053 /** The Java Sound data line being used to write to */ 054 private TargetDataLine mLine = null; 055 056 /** Whether the stream is grabbing or not */ 057 private boolean stopped = true; 058 059 /** The minimum buffer size required */ 060 private int maxBufferSize = -1; 061 062 /** 063 * Default constructor 064 * @param format The format to attempt to open the sound line 065 */ 066 public JavaSoundAudioGrabber( final AudioFormat format ) { 067 this.setFormat( format ); 068 } 069 070 /** 071 * {@inheritDoc} 072 * 073 * @see org.openimaj.audio.AudioGrabber#run() 074 */ 075 @Override 076 public void run() { 077 try { 078 this.openJavaSound(); 079 080 // Setup a byte array into which to store the bytes we read from 081 // the Java Sound mixer. It's smaller than the mixer's buffer so 082 // that we have time to process stuff before the mixer needs to 083 // refill its buffer. 084 final ByteArrayOutputStream out = new ByteArrayOutputStream(); 085 int numBytesRead = 0; 086 final byte[] data = new byte[this.calculateBufferSize()]; 087 088 // Begin audio capture. 089 this.mLine.start(); 090 091 // Keep going until we're told to top 092 this.stopped = false; 093 while (!this.stopped) { 094 // Read the next chunk of data from the TargetDataLine. 095 numBytesRead = this.mLine.read(data, 0, data.length); 096 097 // Save this chunk of data. 098 out.write(data, 0, numBytesRead); 099 100 // synced on current sample so that the nextSampleChunk() method 101 // can wait until the buffer is full 102 synchronized (this.currentSample) { 103 // Set the samples in our sample chunk 104 this.currentSample.setSamples(data.clone()); 105 this.currentSample.notify(); 106 } 107 108 // Let the listeners know 109 this.fireAudioAvailable(); 110 } 111 112 this.closeJavaSound(); 113 System.out.println("Stopping java sound"); 114 } catch (final Exception e) { 115 e.printStackTrace(); 116 } 117 } 118 119 /** 120 * From 121 * http://www.javadocexamples.com/java_source/ccs/chaos/NoiseGrabber.java 122 * .html Not sure if this will work for non 44.1KHz samples? 123 * 124 * @return 125 */ 126 private int calculateBufferSize() { 127 final int nmax = (this.maxBufferSize == -1 ? this.mLine.getBufferSize() / 4 : this.maxBufferSize); 128 final int[] FAC44100 = { 7, 7, 5, 5, 3, 3, 2, 2 }; 129 int nwad = 1; 130 for (int i = 0; i < 8; i++) 131 if (nwad * FAC44100[i] <= nmax) 132 nwad *= FAC44100[i]; 133 134 return nwad << (this.getFormat().getNBits() / 8); 135 } 136 137 /** 138 * {@inheritDoc} 139 * 140 * @see org.openimaj.audio.AudioGrabber#stop() 141 */ 142 @Override 143 public void stop() { 144 this.stopped = true; 145 } 146 147 /** 148 * {@inheritDoc} 149 * 150 * @see org.openimaj.audio.AudioGrabber#isStopped() 151 */ 152 @Override 153 public boolean isStopped() { 154 return this.stopped; 155 } 156 157 /** 158 * Fire the event and audio is now available 159 */ 160 @Override 161 protected void fireAudioAvailable() { 162 for (final AudioGrabberListener l : this.listeners) 163 l.samplesAvailable(this.currentSample); 164 } 165 166 /** 167 * {@inheritDoc} 168 * 169 * @see org.openimaj.audio.Audio#setFormat(org.openimaj.audio.AudioFormat) 170 */ 171 @Override 172 public void setFormat(final AudioFormat format) { 173 this.currentSample.setFormat(format); 174 super.setFormat(format); 175 } 176 177 /** 178 * {@inheritDoc} 179 * 180 * @see org.openimaj.audio.AudioStream#nextSampleChunk() 181 */ 182 @Override 183 public SampleChunk nextSampleChunk() { 184 synchronized (this.currentSample) { 185 if (this.isStopped()) 186 return null; 187 try { 188 this.currentSample.wait(); 189 } catch (final InterruptedException e) { 190 e.printStackTrace(); 191 } 192 193 return this.currentSample; 194 } 195 } 196 197 /** 198 * Set the maximum size buffer to be returned. 199 * 200 * @param maxBufferSize 201 */ 202 public void setMaxBufferSize(final int maxBufferSize) { 203 this.maxBufferSize = maxBufferSize; 204 } 205 206 /** 207 * Open a line to the Java Sound APIs. 208 * 209 * @throws Exception 210 * if the Java sound system could not be initialised. 211 */ 212 private void openJavaSound() throws Exception { 213 // Convert the OpenIMAJ audio format to a Java Sound audio format object 214 final javax.sound.sampled.AudioFormat audioFormat = new javax.sound.sampled.AudioFormat( 215 (int) (this.getFormat().getSampleRateKHz() * 1000), this 216 .getFormat().getNBits(), this.getFormat() 217 .getNumChannels(), this.getFormat().isSigned(), this 218 .getFormat().isBigEndian()); 219 220 System.out.println("Creating Java Sound Line with " + this.getFormat()); 221 222 // Create info to create an output data line 223 final DataLine.Info info = new DataLine.Info(TargetDataLine.class, audioFormat); 224 225 try { 226 // Get the output line to write to using the given 227 // sample format we just created. 228 this.mLine = (TargetDataLine) AudioSystem.getLine(info); 229 230 // If no exception has been thrown we open the line. 231 this.mLine.open(audioFormat); 232 } catch (final LineUnavailableException e) { 233 throw new Exception("Could not open Java Sound audio line for" 234 + " the audio format " + this.getFormat()); 235 } 236 } 237 238 /** 239 * Close down the Java sound APIs. 240 */ 241 private void closeJavaSound() { 242 if (this.mLine != null) { 243 // Wait for the buffer to empty... 244 this.mLine.drain(); 245 246 // ...then close 247 this.mLine.close(); 248 this.mLine = null; 249 } 250 } 251 252 /** 253 * {@inheritDoc} 254 * 255 * @see org.openimaj.audio.AudioStream#getLength() 256 */ 257 @Override 258 public long getLength() { 259 return -1; 260 } 261}