/** * Copyright 2008 - 2015 The Loon Game Engine Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * @project loon * @author cping * @email:javachenpeng@yahoo.com * @version 0.5 */ package loon.javase; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.Clip; import javax.sound.sampled.Control; import javax.sound.sampled.Line; import javax.sound.sampled.LineEvent; import javax.sound.sampled.LineListener; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.UnsupportedAudioFileException; class JavaSEBigClip implements Clip, LineListener { private SourceDataLine dataLine; private byte[] audioData; private ByteArrayInputStream inputStream; private int loopCount = 1; private int countDown = 1; private int loopPointStart; private int loopPointEnd; private int framePosition; private Thread thread; private boolean active; private long timelastPositionSet; private int bufferUpdateFactor = 2; public JavaSEBigClip() { } public JavaSEBigClip(Clip clip) throws LineUnavailableException { dataLine = AudioSystem.getSourceDataLine(clip.getFormat()); } public byte[] getAudioData() { return audioData; } private long convertFramesToMilliseconds(int frames) { return (frames / (long) dataLine.getFormat().getSampleRate()) * 1000; } private int convertMillisecondsToFrames(long milliseconds) { return (int) (milliseconds / dataLine.getFormat().getSampleRate()); } @Override public void update(LineEvent le) { } @Override public void loop(int count) { loopCount = count; countDown = count; active = true; inputStream.reset(); start(); } @Override public void setLoopPoints(int start, int end) { if (start < 0 || start > audioData.length - 1 || end < 0 || end > audioData.length) { throw new IllegalArgumentException("Loop points '" + start + "' and '" + end + "' cannot be set for buffer of size " + audioData.length); } if (start > end) { throw new IllegalArgumentException("End position " + end + " preceeds start position " + start); } loopPointStart = start; framePosition = loopPointStart; loopPointEnd = end; } @Override public void setMicrosecondPosition(long milliseconds) { framePosition = convertMillisecondsToFrames(milliseconds); } @Override public long getMicrosecondPosition() { return convertFramesToMilliseconds(getFramePosition()); } @Override public long getMicrosecondLength() { return convertFramesToMilliseconds(getFrameLength()); } @Override public void setFramePosition(int frames) { framePosition = frames; int offset = framePosition * format.getFrameSize(); try { inputStream.reset(); inputStream.read(new byte[offset]); } catch (Exception e) { e.printStackTrace(); } } @Override public int getFramePosition() { long timeSinceLastPositionSet = System.currentTimeMillis() - timelastPositionSet; int size = dataLine.getBufferSize() * (format.getChannels() / 2) / bufferUpdateFactor; size /= dataLine.getFormat().getFrameSize(); size *= dataLine.getFormat().getFrameSize(); int framesSinceLast = (int) ((timeSinceLastPositionSet / 1000f) * dataLine .getFormat().getFrameRate()); int framesRemainingTillTime = size - framesSinceLast; return framePosition - framesRemainingTillTime; } @Override public int getFrameLength() { return audioData.length / format.getFrameSize(); } AudioFormat format; @Override public void open(AudioInputStream stream) throws IOException, LineUnavailableException { AudioInputStream is1; format = stream.getFormat(); if (format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) { is1 = AudioSystem.getAudioInputStream( AudioFormat.Encoding.PCM_SIGNED, stream); } else { is1 = stream; } format = is1.getFormat(); InputStream is2 = is1; byte[] buf = new byte[1 << 16]; int numRead = 0; ByteArrayOutputStream baos = new ByteArrayOutputStream(); numRead = is2.read(buf); while (numRead > -1) { baos.write(buf, 0, numRead); numRead = is2.read(buf, 0, buf.length); } is2.close(); audioData = baos.toByteArray(); AudioFormat afTemp; if (format.getChannels() < 2) { int frameSize = format.getSampleSizeInBits() * 2 / 8; afTemp = new AudioFormat(format.getEncoding(), format.getSampleRate(), format.getSampleSizeInBits(), 2, frameSize, format.getFrameRate(), format.isBigEndian()); } else { afTemp = format; } setLoopPoints(0, audioData.length); dataLine = AudioSystem.getSourceDataLine(afTemp); dataLine.open(); inputStream = new ByteArrayInputStream(audioData); } @Override public void open(AudioFormat format, byte[] data, int offset, int bufferSize) throws LineUnavailableException { byte[] input = new byte[bufferSize]; for (int ii = 0; ii < input.length; ii++) { input[ii] = data[offset + ii]; } ByteArrayInputStream inputStream = new ByteArrayInputStream(input); try { AudioInputStream ais1 = AudioSystem .getAudioInputStream(inputStream); AudioInputStream ais2 = AudioSystem.getAudioInputStream(format, ais1); open(ais2); } catch (UnsupportedAudioFileException uafe) { throw new IllegalArgumentException(uafe); } catch (IOException ioe) { throw new IllegalArgumentException(ioe); } } @Override public float getLevel() { return dataLine.getLevel(); } @Override public long getLongFramePosition() { return dataLine.getLongFramePosition() * 2 / format.getChannels(); } @Override public int available() { return dataLine.available(); } @Override public int getBufferSize() { return dataLine.getBufferSize(); } @Override public AudioFormat getFormat() { return format; } @Override public boolean isActive() { return dataLine.isActive(); } @Override public boolean isRunning() { return dataLine.isRunning(); } @Override public boolean isOpen() { return dataLine.isOpen(); } @Override public void stop() { active = false; dataLine.stop(); if (thread != null) { try { active = false; thread.join(); } catch (InterruptedException wakeAndContinue) { } } } public byte[] convertMonoToStereo(byte[] data, int bytesRead) { byte[] tempData = new byte[bytesRead * 2]; if (format.getSampleSizeInBits() == 8) { for (int ii = 0; ii < bytesRead; ii++) { byte b = data[ii]; tempData[ii * 2] = b; tempData[ii * 2 + 1] = b; } } else { for (int ii = 0; ii < bytesRead - 1; ii += 2) { byte b1 = data[ii]; byte b2 = data[ii + 1]; tempData[ii * 2] = b1; tempData[ii * 2 + 1] = b2; tempData[ii * 2 + 2] = b1; tempData[ii * 2 + 3] = b2; } } return tempData; } @Override public void start() { Runnable r = new Runnable() { public void run() { dataLine.start(); active = true; int bytesRead = 0; int frameSize = dataLine.getFormat().getFrameSize(); int bufSize = dataLine.getBufferSize(); boolean startOrMove = true; byte[] data = new byte[bufSize]; int offset = framePosition * frameSize; bytesRead = inputStream.read(new byte[offset], 0, offset); bytesRead = inputStream.read(data, 0, data.length); while (bytesRead != -1 && (loopCount == Clip.LOOP_CONTINUOUSLY || countDown > 0) && active) { int framesRead; byte[] tempData; if (format.getChannels() < 2) { tempData = convertMonoToStereo(data, bytesRead); framesRead = bytesRead / format.getFrameSize(); bytesRead *= 2; } else { framesRead = bytesRead / dataLine.getFormat().getFrameSize(); tempData = Arrays.copyOfRange(data, 0, bytesRead); } framePosition += framesRead; if (framePosition >= loopPointEnd) { framePosition = loopPointStart; inputStream.reset(); countDown--; } timelastPositionSet = System.currentTimeMillis(); byte[] newData = tempData; dataLine.write(newData, 0, newData.length); if (startOrMove) { int len = bufSize / bufferUpdateFactor; len /= frameSize; len *= frameSize; data = new byte[len]; startOrMove = false; } bytesRead = inputStream.read(data, 0, data.length); if (bytesRead < 0 && (--countDown > 0 || loopCount == Clip.LOOP_CONTINUOUSLY)) { inputStream.read(new byte[offset], 0, offset); inputStream.reset(); bytesRead = inputStream.read(data, 0, data.length); } } active = false; countDown = 1; framePosition = 0; inputStream.reset(); dataLine.stop(); } }; thread = new Thread(r); thread.setDaemon(true); thread.start(); } @Override public void flush() { dataLine.flush(); } @Override public void drain() { dataLine.drain(); } @Override public void removeLineListener(LineListener listener) { dataLine.removeLineListener(listener); } @Override public void addLineListener(LineListener listener) { dataLine.addLineListener(listener); } @Override public Control getControl(Control.Type control) { return dataLine.getControl(control); } @Override public Control[] getControls() { if (dataLine == null) { return new Control[0]; } else { return dataLine.getControls(); } } @Override public boolean isControlSupported(Control.Type control) { return dataLine.isControlSupported(control); } @Override public void close() { dataLine.close(); } @Override public void open() throws LineUnavailableException { throw new IllegalArgumentException( "illegal call to open() in interface Clip"); } @Override public Line.Info getLineInfo() { return dataLine.getLineInfo(); } }