/* * Mobicents, Communications Middleware * * Copyright (c) 2008, Red Hat Middleware LLC or third-party * contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Middleware LLC. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * * Boston, MA 02110-1301 USA */ package org.mobicents.media.server.impl.resource.audio; import java.io.IOException; import java.net.URL; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.AudioFormat.Encoding; import org.apache.log4j.Logger; import org.mobicents.media.Buffer; import org.mobicents.media.Format; import org.mobicents.media.format.AudioFormat; import org.mobicents.media.server.impl.AbstractSource; import org.mobicents.media.server.impl.rtp.sdp.AVProfile; import org.mobicents.media.server.spi.Timer; import org.mobicents.media.server.spi.dsp.Codec; import org.mobicents.media.server.spi.events.NotifyEvent; import org.mobicents.media.server.spi.resource.AudioPlayer; import org.xiph.speex.spi.SpeexAudioFileReader; import org.xiph.speex.spi.SpeexEncoding; /** * * @author Oleg Kulikov */ public class AudioPlayerImpl extends AbstractSource implements AudioPlayer, Runnable { /** supported formats definition */ private final static Format[] FORMATS = new Format[]{ AVProfile.L16_MONO, AVProfile.L16_STEREO, AVProfile.PCMA, AVProfile.PCMU, AVProfile.SPEEX, AVProfile.GSM, Codec.LINEAR_AUDIO }; /** GSM Encoding constant used by Java Sound API */ private final static Encoding GSM_ENCODING = new Encoding("GSM0610"); /** format of the file */ private AudioFormat format; /** audio stream */ private transient AudioInputStream stream = null; /** Name (path) of the file to play */ private String file; /** Flag indicating end of media */ private volatile boolean eom = false; /** The countor for errors occured during processing */ private static transient Logger logger = Logger.getLogger(AudioPlayerImpl.class); /** * Creates new instance of the Audio player. * * @param name the name of the AudioPlayer to be created. * @param timer source of synchronization. */ public AudioPlayerImpl(String name, Timer timer) { super(name); setSyncSource(timer); } /** * (Non Java-doc.) * * @see org.mobicents.media.server.spi.resource.AudioPlayer#setURL(java.lang.String) */ public void setURL(String url) { this.file = url; } /** * (Non Java-doc.) * * @see org.mobicents.media.server.spi.resource.AudioPlayer#getURL() */ public String getURL() { return file; } @Override public void beforeStart() throws Exception { closeAudioStream(); if (file.endsWith("spx")) { stream = new SpeexAudioFileReader().getAudioInputStream(new URL(file)); } else { stream = AudioSystem.getAudioInputStream(new URL(file)); } format = getFormat(stream); if (format == null) { throw new IOException("Unsupported format: " + stream.getFormat()); } eom = false; } @Override public void afterStop() { closeAudioStream(); } /** * Gets the format of specified stream. * * @param stream * the stream to obtain format. * @return the format object. */ private AudioFormat getFormat(AudioInputStream stream) { Encoding encoding = stream.getFormat().getEncoding(); if (encoding == Encoding.ALAW) { return AVProfile.PCMA; } else if (encoding == Encoding.ULAW) { return AVProfile.PCMU; } else if (encoding == SpeexEncoding.SPEEX) { return AVProfile.SPEEX; } else if (encoding.equals(GSM_ENCODING)) { return AVProfile.GSM; } else if (encoding == Encoding.PCM_SIGNED) { int sampleSize = stream.getFormat().getSampleSizeInBits(); if (sampleSize != 16) { return null; } int sampleRate = (int) stream.getFormat().getSampleRate(); if (sampleRate == 44100) { int channels = stream.getFormat().getChannels(); return channels == 1 ? AVProfile.L16_MONO : AVProfile.L16_STEREO; } else if (sampleRate == 8000) { return Codec.LINEAR_AUDIO; } else { return null; } } return null; } /** * Calculates size of packets for the currently opened stream. * * @return the size of packets in bytes; */ private int getPacketSize(long packetDuration) { int pSize = (int) (packetDuration * format.getChannels() * format.getSampleSizeInBits() * format.getSampleRate() / 8000); if (pSize < 0) { // For Format for which bit is AudioFormat.NOT_SPECIFIED, 160 is // passed pSize = 160; if (format == AVProfile.GSM) { //For GSM the RTP Packet size is 33 pSize = (int)(33 * (packetDuration/20)); } } return pSize; } /** * Reads packet from currently opened stream. * * @param packet * the packet to read * @param offset * the offset from which new data will be inserted * @return the number of actualy read bytes. * @throws java.io.IOException */ private int readPacket(byte[] packet, int offset, int psize) throws IOException { int length = 0; try { while (length < psize) { int len = stream.read(packet, offset + length, psize - length); if (len == -1) { return length; } length += len; } return length; } catch (Exception e) { e.printStackTrace(); } return length; } /** * Reads packet from currently opened stream into specified buffer. * * @param buffer * the buffer object to insert data to * @throws java.io.IOException */ private void readPacket(Buffer buffer, int psize) throws IOException { buffer.setLength(readPacket((byte[]) buffer.getData(), buffer.getOffset(), psize)); } /** * Perform padding buffer with zeros to aling length. * * @param buffer * the buffer for padding. */ private void padding(Buffer buffer, int psize) { int count = psize - buffer.getLength(); byte[] data = (byte[]) buffer.getData(); int offset = buffer.getOffset() + buffer.getLength(); for (int i = 0; i < count; i++) { data[i + offset] = 0; } buffer.setLength(psize); } /** * Closes audio stream */ private void closeAudioStream() { try { if (stream != null) { stream.close(); } } catch (IOException e) { } } @Override public void evolve(Buffer buffer, long timestamp, long seq) { if (getDuration() == 0) { buffer.setFlags(Buffer.FLAG_DISCARD); return; } long duration = getDuration(); int psize = this.getPacketSize(duration); try { readPacket(buffer, psize); } catch (IOException e) { failed(NotifyEvent.TX_FAILED, e); return; } if (buffer.getLength() == 0) { eom = true; //padding(buffer); } else if (buffer.getLength() < psize) { padding(buffer, psize); } buffer.setDuration(getDuration()); buffer.setFormat(format); buffer.setTimeStamp(timestamp); buffer.setEOM(eom); buffer.setSequenceNumber(seq); } public Format[] getFormats() { return FORMATS; } }