All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openimaj.audio.AudioPlayer Maven / Gradle / Ivy

Go to download

Core definitions of audio streams and samples/chunks. Also contains interfaces for processors for these basic types.

There is a newer version: 1.3.10
Show newest version
/**
 * Copyright (c) 2011, The University of Southampton and the individual contributors.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *   * 	Redistributions of source code must retain the above copyright notice,
 * 	this list of conditions and the following disclaimer.
 *
 *   *	Redistributions in binary form must reproduce the above copyright notice,
 * 	this list of conditions and the following disclaimer in the documentation
 * 	and/or other materials provided with the distribution.
 *
 *   *	Neither the name of the University of Southampton nor the names of its
 * 	contributors may be used to endorse or promote products derived from this
 * 	software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/**
 *
 */
package org.openimaj.audio;

import java.util.ArrayList;
import java.util.List;

import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;

import org.openimaj.audio.timecode.AudioTimecode;
import org.openimaj.audio.util.AudioUtils;
import org.openimaj.time.TimeKeeper;
import org.openimaj.time.Timecode;

/**
 * Wraps the Java Sound APIs into the OpenIMAJ audio core for playing sounds.
 * 

* The {@link AudioPlayer} supports the {@link TimeKeeper} interface so that * other methods can synchronise to the audio timestamps. *

* The Audio Player as a {@link TimeKeeper} supports seeking but it may be * possible that the underlying stream does not support seeking so the seek * method may not affect the time keeper as expected. * * @author David Dupplaw ([email protected]) * @created 8 Jun 2011 * */ public class AudioPlayer implements Runnable, TimeKeeper { /** The audio stream being played */ private AudioStream stream = null; /** The java audio output stream line */ private SourceDataLine mLine = null; /** The current timecode being played */ private AudioTimecode currentTimecode = null; /** The current audio timestamp */ private long currentTimestamp = 0; /** At what timestamp the current timecode was read at */ private long timecodeReadAt = 0; /** The device name on which to play */ private String deviceName = null; /** The mode of the player */ private Mode mode = Mode.PLAY; /** Listeners for events */ private final List listeners = new ArrayList(); /** Whether the system has been started */ private boolean started = false; /** * Number of milliseconds in the sound line buffer. < 100ms is good for * real-time whereas the bigger the better for smooth sound reproduction */ private double soundLineBufferSize = 100; /** * Enumerator for the current state of the audio player. * * @author David Dupplaw ([email protected]) * * @created 29 Nov 2011 */ public enum Mode { /** The audio player is playing */ PLAY, /** The audio player is paused */ PAUSE, /** The audio player is stopped */ STOP } /** * Default constructor that takes an audio stream to play. * * @param a * The audio stream to play */ public AudioPlayer(final AudioStream a) { this(a, null); } /** * Play the given stream to a specific device. * * @param a * The audio stream to play. * @param deviceName * The device to play the audio to. */ public AudioPlayer(final AudioStream a, final String deviceName) { this.stream = a; this.deviceName = deviceName; this.setTimecodeObject(new AudioTimecode(0)); } /** * Set the length of the sound line's buffer in milliseconds. The longer the * buffer the less likely the soundline will be to pop but the shorter the * buffer the closer to real-time the sound output will be. This value must * be set before the audio line is opened otherwise it will have no effect. * * @param ms * The length of the sound line in milliseconds. */ public void setSoundLineBufferSize(final double ms) { this.soundLineBufferSize = ms; } /** * Add the given audio event listener to this player. * * @param l * The listener to add. */ public void addAudioEventListener(final AudioEventListener l) { this.listeners.add(l); } /** * Remove the given event from the listeners on this player. * * @param l * The listener to remove. */ public void removeAudioEventListener(final AudioEventListener l) { this.listeners.remove(l); } /** * Fires the audio ended event to the listeners. * * @param as * The audio stream that ended */ protected void fireAudioEnded(final AudioStream as) { for (final AudioEventListener ael : this.listeners) ael.audioEnded(); } /** * Fires an event that says the samples will be played. * * @param sc * The samples to play */ protected void fireBeforePlay(final SampleChunk sc) { for (final AudioEventListener ael : this.listeners) ael.beforePlay(sc); } /** * Fires an event that says the samples have been played. * * @param sc * The sampled have been played */ protected void fireAfterPlay(final SampleChunk sc) { for (final AudioEventListener ael : this.listeners) ael.afterPlay(this, sc); } /** * Set the timecode object that is updated as the audio is played. * * @param t * The timecode object. */ public void setTimecodeObject(final AudioTimecode t) { this.currentTimecode = t; } /** * Returns the current timecode. * * @return The timecode object. */ public Timecode getTimecodeObject() { return this.currentTimecode; } /** * {@inheritDoc} * * @see java.lang.Runnable#run() */ @Override public void run() { this.setMode(Mode.PLAY); this.timecodeReadAt = 0; if (!this.started) { this.started = true; try { // Open the sound system. this.openJavaSound(); // Read samples until there are no more. SampleChunk samples = null; boolean ended = false; while (!ended && this.mode != Mode.STOP) { if (this.mode == Mode.PLAY) { // System.out.println("loop"); // Get the next sample chunk samples = this.stream.nextSampleChunk(); // Check if we've reached the end of the line if (samples == null) { ended = true; continue; } // Fire the before event this.fireBeforePlay(samples); // Play the samples this.playJavaSound(samples); // Fire the after event this.fireAfterPlay(samples); // If we have a timecode object to update, we'll update // it here if (this.currentTimecode != null) { this.currentTimestamp = samples.getStartTimecode(). getTimecodeInMilliseconds(); this.timecodeReadAt = System.currentTimeMillis(); this.currentTimecode.setTimecodeInMilliseconds(this.currentTimestamp); } } else { // Let's be nice and not loop madly if we're not playing // (we must be in PAUSE mode) try { Thread.sleep(500); } catch (final InterruptedException ie) { } } } // Fire the audio ended event this.fireAudioEnded(this.stream); this.setMode(Mode.STOP); this.reset(); } catch (final Exception e) { e.printStackTrace(); } finally { // Close the sound system this.closeJavaSound(); } } else { // Already playing something, so we just start going again this.setMode(Mode.PLAY); } } /** * Create a new audio player in a separate thread for playing audio. * * @param as * The audio stream to play. * @return The audio player created. */ public static AudioPlayer createAudioPlayer(final AudioStream as) { final AudioPlayer ap = new AudioPlayer(as); new Thread(ap).start(); return ap; } /** * Create a new audio player in a separate thread for playing audio. To find * out device names, use {@link AudioUtils#getDevices()}. * * @param as * The audio stream to play. * @param device * The name of the device to use. * @return The audio player created. */ public static AudioPlayer createAudioPlayer(final AudioStream as, final String device) { final AudioPlayer ap = new AudioPlayer(as, device); new Thread(ap).start(); return ap; } /** * Open a line to the Java Sound APIs. * * @throws Exception * if the Java sound system could not be initialised. */ private void openJavaSound() throws Exception { try { // Get a line (either the one we ask for, or any one). if (this.deviceName != null) this.mLine = AudioUtils.getJavaOutputLine(this.deviceName, this.stream.getFormat()); else this.mLine = AudioUtils.getAnyJavaOutputLine(this.stream.getFormat()); if (this.mLine == null) throw new Exception("Cannot instantiate a sound line."); // If no exception has been thrown we open the line. this.mLine.open(this.mLine.getFormat(), (int) (this.stream.getFormat().getSampleRateKHz() * this.soundLineBufferSize)); // If we've opened the line, we start it running this.mLine.start(); System.out.println("Opened Java Sound Line: " + this.mLine.getFormat()); } catch (final LineUnavailableException e) { throw new Exception("Could not open Java Sound audio line for" + " the audio format " + this.stream.getFormat()); } } /** * Play the given sample chunk to the Java sound line. The line should be * set up to accept the samples that we're going to give it, as we did that * in the {@link #openJavaSound()} method. * * @param chunk * The chunk to play. */ private void playJavaSound(final SampleChunk chunk) { final byte[] rawBytes = chunk.getSamples(); this.mLine.write(rawBytes, 0, rawBytes.length); } /** * Close down the Java sound APIs. */ private void closeJavaSound() { if (this.mLine != null) { // Wait for the buffer to empty... this.mLine.drain(); // ...then close this.mLine.close(); this.mLine = null; } } /** * {@inheritDoc} * * @see org.openimaj.time.TimeKeeper#getTime() */ @Override public AudioTimecode getTime() { // If we've not yet read any samples, just return the timecode // object as it was first given to us. if (this.timecodeReadAt == 0) return this.currentTimecode; // Update the timecode if we're playing (otherwise we'll return the // latest timecode) if (this.mode == Mode.PLAY) this.currentTimecode.setTimecodeInMilliseconds(this.currentTimestamp + (System.currentTimeMillis() - this.timecodeReadAt)); return this.currentTimecode; } /** * {@inheritDoc} * * @see org.openimaj.time.TimeKeeper#stop() */ @Override public void stop() { this.setMode(Mode.STOP); } /** * Set the mode of the player. * * @param m */ public void setMode(final Mode m) { this.mode = m; } /** * {@inheritDoc} * * @see org.openimaj.time.TimeKeeper#supportsPause() */ @Override public boolean supportsPause() { return true; } /** * {@inheritDoc} * * @see org.openimaj.time.TimeKeeper#supportsSeek() */ @Override public boolean supportsSeek() { return true; } /** * {@inheritDoc} * * @see org.openimaj.time.TimeKeeper#seek(long) */ @Override public void seek(final long timestamp) { this.stream.seek(timestamp); } /** * {@inheritDoc} * * @see org.openimaj.time.TimeKeeper#reset() */ @Override public void reset() { this.timecodeReadAt = 0; this.currentTimestamp = 0; this.started = false; this.currentTimecode.setTimecodeInMilliseconds(0); this.stream.reset(); } /** * {@inheritDoc} * * @see org.openimaj.time.TimeKeeper#pause() */ @Override public void pause() { this.setMode(Mode.PAUSE); // Set the current timecode to the time at which we paused. this.currentTimecode.setTimecodeInMilliseconds(this.currentTimestamp + (System.currentTimeMillis() - this.timecodeReadAt)); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy