/* * 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; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.locks.ReentrantLock; import org.apache.log4j.Logger; import org.mobicents.media.Buffer; import org.mobicents.media.BufferFactory; import org.mobicents.media.Format; import org.mobicents.media.Inlet; import org.mobicents.media.MediaSink; import org.mobicents.media.MediaSource; import org.mobicents.media.server.spi.SyncSource; import org.mobicents.media.server.spi.events.NotifyEvent; /** * The base implementation of the Media source. * * <code>AbstractSource</code> and <code>AbstractSink</code> are implement general wirring contruct. All media * components have to extend one of these classes. * * @author Oleg Kulikov */ public abstract class AbstractSource extends BaseComponent implements MediaSource, Runnable { protected transient MediaSink otherParty; private SyncSource syncSource; private volatile ScheduledFuture thread; private ReentrantLock state = new ReentrantLock(); private BufferFactory bufferFactory = new BufferFactory(1); private long sequenceNumber; private long packetsTransmitted; private long bytesTransmitted; private boolean started; private NotifyEvent evtStarted; private NotifyEvent evtCompleted; private NotifyEvent evtStopped; private boolean warn; private Logger logger; private boolean completed = false; /** packetization period in millseconds */ private int period = 20; private volatile long timestamp; private volatile long duration; /** * Creates new instance of source with specified name. * * @param name * the name of the sink to be created. */ public AbstractSource(String name) { super(name); logger = Logger.getLogger(getClass()); evtStarted = new NotifyEventImpl(this, NotifyEvent.STARTED); evtCompleted = new NotifyEventImpl(this, NotifyEvent.COMPLETED); evtStopped = new NotifyEventImpl(this, NotifyEvent.STOPPED); } /** * (Non Java-doc). * * @see org.mobicents.media.MediaSource#getSyncSource() */ public SyncSource getSyncSource() { return syncSource; } /** * (Non Java-doc). * * @see org.mobicents.media.MediaSource#setSyncSource(SyncSource) */ public void setSyncSource(SyncSource syncSource) { this.syncSource = syncSource; } /** * (Non Java-doc). * * @see org.mobicents.media.MediaSource#getPeriod() */ public int getPeriod() { return period; } /** * (Non Java-doc). * * @see org.mobicents.media.MediaSource#setPeriod(int) */ public void setPeriod(int period) { this.period = period; } /** * This method is called just before start. * * The descendant classes can verride this method and put additional logic */ protected void beforeStart() throws Exception { } /** * (Non Java-doc). * * @see org.mobicents.media.MediaSource#start(). */ public void start() { state.lock(); try { if (started) { return; } if (otherParty == null) { throw new IllegalStateException("Component is not connected: " + this); } sequenceNumber = 0; beforeStart(); // synchronize if (syncSource == null) { throw new IllegalStateException("No source of synchronization: " + this); } syncSource.sync(this); started = true; timestamp = syncSource.getTimestamp(); if (logger.isDebugEnabled()) { logger.debug(this + " started"); } started(); } catch (Exception e) { failed(NotifyEvent.START_FAILED, e); } finally { state.unlock(); } } /** * (Non Java-doc). * * @see org.mobicents.media.MediaSource#stop(). */ public void stop() { state.lock(); try { syncSource.unsync(this); started = false; if (logger.isDebugEnabled()) { logger.debug(this + " stopped"); } stopped(); afterStop(); } finally { state.unlock(); } } /** * This method is called immediately after processing termination. * */ public void afterStop() { } public boolean isMultipleConnectionsAllowed() { return false; } /** * (Non Java-doc). * * @see org.mobicents.media.MediaSource#connect(MediaSink). */ public void connect(MediaSink otherParty) { // check argument if (otherParty == null) { throw new IllegalArgumentException("Other party can not be null"); } if (otherParty instanceof AbstractSink && !otherParty.isMultipleConnectionsAllowed()) { AbstractSink sink = (AbstractSink) otherParty; sink.otherParty = this; this.otherParty = sink; if (logger.isDebugEnabled()) { logger.debug(this + " is connected to " + otherParty); } } else { otherParty.connect(this); } } /** * (Non Java-doc). * * @see org.mobicents.media.MediaSource#diconnection(MediaSink). */ public void disconnect(MediaSink otherParty) { // check argument if (otherParty == null) { throw new IllegalArgumentException("Other party can not be null"); } if (otherParty instanceof AbstractSink && !otherParty.isMultipleConnectionsAllowed()) { if (otherParty == this.otherParty) { AbstractSink sink = (AbstractSink) otherParty; sink.otherParty = null; this.otherParty = null; if (logger.isDebugEnabled()) { logger.debug(this + " is disconnected from " + otherParty); } } else { throw new IllegalArgumentException(otherParty + " was not connected to " + this); } } else { otherParty.disconnect(this); } } public void connect(Inlet inlet) { connect(inlet.getInput()); } public void disconnect(Inlet inlet) { disconnect(inlet.getInput()); } /** * (Non Java-doc). * * @see org.mobicents.media.MediaSink#isConnected(). */ public boolean isConnected() { return otherParty != null; } /** * (Non Java-doc). * * @see org.mobicents.media.MediaSink#isStarted(). */ public boolean isStarted() { return this.started; } /** * This method must be overriden by concrete media source. The media have to fill buffer with media data and * attributes. * * @param buffer the buffer object for media. * @param sequenceNumber * the number of timer ticks from the begining. */ public abstract void evolve(Buffer buffer, long timestamp, long sequenceNumber); protected String getSupportedFormatList() { String s = ""; if (otherParty != null) { Format[] formats = otherParty.getFormats(); for (int i = 0; i < formats.length; i++) { s += formats[i] + ";"; } } return s; } protected long getDuration() { return this.duration; } public void run() { if (otherParty == null) { return; } Buffer buffer = bufferFactory.allocate(); long now = syncSource.getTimestamp(); this.duration = now - timestamp; try { evolve(buffer, now, sequenceNumber); timestamp = now; } catch (Exception e) { logger.error("Not able to evolve data", e); return; } if (buffer.isDiscard()) { buffer.dispose(); return; } if (buffer.isEOM()) { buffer.dispose(); completed(); return; } sequenceNumber++; boolean isAcceptable = false; try { isAcceptable = otherParty.isAcceptable(buffer.getFormat()); } catch (Exception e) { } if (isAcceptable) { try { if (logger.isTraceEnabled()) { logger.trace(this + " sending " + buffer + " to " + otherParty); } otherParty.receive(buffer); packetsTransmitted++; bytesTransmitted += buffer.getLength(); warn = false; } catch (Exception e) { logger.error("Can not deliver packet to " + otherParty, e); failed(NotifyEvent.TX_FAILED, e); } } else { if (!warn) { logger.warn(this + " fmt={" + buffer.getFormat() + "} is not acceptable by " + otherParty + "supported formats: [" + getSupportedFormatList() + "]"); warn = true; } } } /** * Sends notification that media processing has been started. */ protected void started() { sendEvent(evtStarted); } /** * Sends failure notification. * * @param eventID * failure event identifier. * @param e * the exception caused failure. */ protected void failed(int eventID, Exception e) { if (thread != null) { thread.cancel(false); } FailureEventImpl failed = new FailureEventImpl(this, eventID, e); sendEvent(failed); } /** * Sends notification that signal is completed. * */ protected void completed() { syncSource.unsync(this); sendEvent(evtCompleted); } /** * Sends notification that detection is terminated. * */ protected void stopped() { sendEvent(evtStopped); } /** * (Non Java-doc). * * @see org.mobicents.media.MediaSource#getPacketsReceived() */ public long getPacketsTransmitted() { return packetsTransmitted; } /** * (Non Java-doc). * * @see org.mobicents.media.MediaSource#getBytesTransmitted() */ public long getBytesTransmitted() { return bytesTransmitted; } @Override public void resetStats() { this.packetsTransmitted = 0; this.bytesTransmitted = 0; } }