/* * Mobicents Media Gateway * * The source code contained in this file is in in the public domain. * It can be used in any project or product without prior permission, * license or royalty payments. There is NO WARRANTY OF ANY KIND, * EXPRESS, IMPLIED OR STATUTORY, INCLUDING, WITHOUT LIMITATION, * THE IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, * AND DATA ACCURACY. We do not warrant or make any representations * regarding the use of the software or the results thereof, including * but not limited to the correctness, accuracy, reliability or * usefulness of the software. */ package org.mobicents.media.server.impl.dsp; import java.io.IOException; import java.util.ArrayList; import org.mobicents.media.Buffer; import org.mobicents.media.Component; import org.mobicents.media.Format; import org.mobicents.media.MediaSink; import org.mobicents.media.MediaSource; import org.mobicents.media.server.impl.AbstractSink; import org.mobicents.media.server.impl.AbstractSource; import org.mobicents.media.server.impl.BaseComponent; import org.mobicents.media.server.spi.SyncSource; import org.mobicents.media.server.spi.dsp.Codec; import org.mobicents.media.server.spi.dsp.SignalingProcessor; /** * Implements DSP features. * * Processor has input and output and is used to perform required * transcoding if needed for packets followed from source to consumer. * Processor is transparent for packets with format acceptable by consumer * by default. * * @author Oleg Kulikov */ public class Processor extends BaseComponent implements SignalingProcessor { private Format[] inputFormats = new Format[0]; private Input input; private Output output; private transient ArrayList<Codec> codecs = new ArrayList(); private Codec codec; private Buffer buff; private long timestamp; public Processor(String name) { super(name); input = new Input(name); output = new Output(name); output.setSyncSource(input); } protected void add(Codec codec) { codecs.add(codec); } /** * Gets the input for original media * * @return media handler for input media. */ public MediaSink getInput() { return input; } /** * Gets the output stream with transcoded media. * * @return media stream. */ public MediaSource getOutput() { return output; } public void connect(MediaSource source) { input.connect(source); } public void disconnect(MediaSource source) { input.disconnect(source); } public void connect(MediaSink sink) { output.connect(sink); } public void disconnect(MediaSink sink) { output.disconnect(sink); } /** * (Non Java-doc.) * * @see org.mobicents.media.Component#start() */ public void start() { input.start(); output.start(); } /** * (Non Java-doc.) * * @see org.mobicents.media.Component#stop() */ public void stop() { input.stop(); output.stop(); } /** * Implements input of the processor. */ private class Input extends AbstractSink implements SyncSource { private Format fmt; private boolean isAcceptable; public Input(String name) { super(name + ".input"); } /** * (Non Java-doc.) * * @see org.mobicents.media.MediaSink#isAcceptable(org.mobicents.media.Format) */ public boolean isAcceptable(Format format) { if (fmt != null && fmt.matches(format)) { return isAcceptable; } inputFormats = getFormats(); fmt = format; for (Format f : inputFormats) { if (f.matches(format)) { this.isAcceptable = true; break; } } return this.isAcceptable; } /** * (Non Java-doc.) * * @see org.mobicents.media.server.impl.AbstractSink#onMediaTransfer(org.mobicents.media.Buffer) */ public void onMediaTransfer(Buffer buffer) throws IOException { timestamp = buffer.getTimeStamp(); output.transmit(buffer); } /** * Gets list of formats supported by connected other party * * @return the array of format objects. */ protected Format[] getOtherPartyFormats() { return otherParty != null ? otherParty.getFormats() : new Format[0]; } /** * (Non Java-doc.) * * @see org.mobicents.media.MediaSink#getFormats() */ public Format[] getFormats() { Format[] original = output.getOtherPartyFormats(); ArrayList<Format> list = new ArrayList(); for (Format f : original) { list.add(f); for (Codec codec: codecs) { if (codec.getSupportedOutputFormat().matches(f)) { Format ff = codec.getSupportedInputFormat(); if (!list.contains(ff)){ list.add(ff); } } } } Format[] fmts = new Format[list.size()]; list.toArray(fmts); return fmts; } @Override public String toString() { return "Processor.Input[" + getName() + "]"; } public void sync(MediaSource mediaSource) { } public void unsync(MediaSource mediaSource) { } public long getTimestamp() { return timestamp; } } /** * Implements output of the processor. */ private class Output extends AbstractSource { //references to the format of last processed packet private Format format; private boolean started = false; /** * Creates new instance of processor's output. * * @param name - the name of the processor; */ public Output(String name) { super(name + ".output"); } /** * Gets list of formats supported by connected other party * * @return the array of format objects. */ protected Format[] getOtherPartyFormats() { return otherParty != null ? otherParty.getFormats() : new Format[0]; } @Override public void start() { started = true; } @Override public void stop() { started = false; } /** * (Non Java-doc.) * * @see org.mobicents.media.MediaSource#getFormats() */ public Format[] getFormats() { Format[] original = input.getOtherPartyFormats(); ArrayList<Format> list = new ArrayList(); for (Format f : original) { list.add(f); for (Codec codec: codecs) { if (codec.getSupportedInputFormat().matches(f)) { Format ff = codec.getSupportedOutputFormat(); if (!list.contains(ff)){ list.add(ff); } } } } Format[] fmts = new Format[list.size()]; list.toArray(fmts); return fmts; } /** * Checks is this format is accessable by other party. * * @param format the format to check. * * @return true if other party can accept this format. */ private boolean isAcceptable(Format format) { return otherParty.isAcceptable(format); } /** * Transmits buffer to the output handler. * * @param buffer the buffer to transmit */ protected void transmit(Buffer buffer) { if (!started) { buffer.dispose(); return; } //Here we work in ReceiveStream.run method, which runs in local ReceiveStreamTimer // Discard packet silently if output handler is not assigned yet if (otherParty == null) { buffer.dispose(); return; } //compare format of the currently processing packet with last one //and if same use same codec also else reassign codec if (format == null || !format.equals(buffer.getFormat())) { codec = null; format = buffer.getFormat(); if (!this.isAcceptable(buffer.getFormat())) { for (Codec c : codecs) { if (c.getSupportedInputFormat().matches(buffer.getFormat()) && this.isAcceptable(c.getSupportedOutputFormat())) { codec = c; format = buffer.getFormat(); break; } } } } if (codec != null) { codec.process(buffer); } // Codec can delay media transition if it has not enouph media // to perform its job. // It means that Processor should check FLAGS after codec's // work and discard packet if required if (buffer.getFlags() == Buffer.FLAG_DISCARD) { buffer.dispose(); return; } //may be a situation when original format can not be trancoded to //one of the required output. In this case codec map will have no //entry for this format. also codec may has no entry in case of when //transcoding is not required. to differentiate these two cases check //if this format is acceptable by the consumer. //deliver packet to the consumer buff = buffer; run(); } @Override public void evolve(Buffer buffer, long timestamp, long sequenceNumber) { buffer.copy(buff); } @Override public String toString() { return "Processor.Output[" + getName() + "]"; } } }