diff options
-rw-r--r-- | lib/jspeex.jar | bin | 0 -> 98722 bytes | |||
-rw-r--r-- | src/net/java/sip/communicator/impl/media/MediaControl.java | 10 | ||||
-rw-r--r-- | src/net/java/sip/communicator/impl/media/MediaUtils.java | 22 | ||||
-rw-r--r-- | src/net/java/sip/communicator/impl/media/codec/Constants.java | 4 | ||||
-rw-r--r-- | src/net/java/sip/communicator/impl/media/codec/audio/speex/JavaDecoder.java | 290 | ||||
-rw-r--r-- | src/net/java/sip/communicator/impl/media/codec/audio/speex/JavaEncoder.java | 145 |
6 files changed, 468 insertions, 3 deletions
diff --git a/lib/jspeex.jar b/lib/jspeex.jar Binary files differnew file mode 100644 index 0000000..0a0079b --- /dev/null +++ b/lib/jspeex.jar diff --git a/src/net/java/sip/communicator/impl/media/MediaControl.java b/src/net/java/sip/communicator/impl/media/MediaControl.java index 3529df5..a157cc9 100644 --- a/src/net/java/sip/communicator/impl/media/MediaControl.java +++ b/src/net/java/sip/communicator/impl/media/MediaControl.java @@ -81,8 +81,8 @@ public class MediaControl // javax.media.format.AudioFormat.DVI_RTP; Integer.toString(SdpConstants.DVI4_16000), // javax.media.format.AudioFormat.ALAW; - // PCMU is only supported for decoding ... as good as not supported - //Integer.toString(SdpConstants.PCMA), + Integer.toString(SdpConstants.PCMA), + Integer.toString(110), // javax.media.format.AudioFormat.G728_RTP; Integer.toString(SdpConstants.G728) // javax.media.format.AudioFormat.G729_RTP @@ -132,7 +132,11 @@ public class MediaControl { "net.java.sip.communicator.impl.media.codec.audio.alaw.JavaEncoder", "net.java.sip.communicator.impl.media.codec.audio.alaw.DePacketizer", - "net.java.sip.communicator.impl.media.codec.audio.alaw.Packetizer" + "net.java.sip.communicator.impl.media.codec.audio.alaw.Packetizer", + "net.java.sip.communicator.impl.media.codec.audio.speex.JavaEncoder", + "net.java.sip.communicator.impl.media.codec.audio.speex.JavaDecoder" +// ,"net.java.sip.communicator.impl.media.codec.audio.ilbc.JavaEncoder", +// "net.java.sip.communicator.impl.media.codec.audio.ilbc.JavaDecoder" // "net.java.sip.communicator.impl.media.codec.audio.g729.JavaDecoder", // "net.java.sip.communicator.impl.media.codec.audio.g729.JavaEncoder", // "net.java.sip.communicator.impl.media.codec.audio.g729.DePacketizer", diff --git a/src/net/java/sip/communicator/impl/media/MediaUtils.java b/src/net/java/sip/communicator/impl/media/MediaUtils.java index de93e37..ad3dfc8 100644 --- a/src/net/java/sip/communicator/impl/media/MediaUtils.java +++ b/src/net/java/sip/communicator/impl/media/MediaUtils.java @@ -50,6 +50,12 @@ public class MediaUtils return AudioFormat.DVI_RTP; case SdpConstants.PCMA: return Constants.ALAW_RTP; + case 97: + return Constants.ILBC_RTP; + case 98: + return Constants.ILBC_RTP; + case 110: + return Constants.SPEEX_RTP; case SdpConstants.G728: return AudioFormat.G728_RTP; case SdpConstants.G729: @@ -129,6 +135,22 @@ public class MediaUtils { return Integer.toString(SdpConstants.H261); } + else if (jmfEncoding.equals(Constants.ILBC)) + { + return Integer.toString(97); + } + else if (jmfEncoding.equals(Constants.ILBC_RTP)) + { + return Integer.toString(97); + } + else if (jmfEncoding.equals(Constants.SPEEX)) + { + return Integer.toString(110); + } + else if (jmfEncoding.equals(Constants.SPEEX_RTP)) + { + return Integer.toString(110); + } else { return null; diff --git a/src/net/java/sip/communicator/impl/media/codec/Constants.java b/src/net/java/sip/communicator/impl/media/codec/Constants.java index 884675e..44c9373 100644 --- a/src/net/java/sip/communicator/impl/media/codec/Constants.java +++ b/src/net/java/sip/communicator/impl/media/codec/Constants.java @@ -13,4 +13,8 @@ package net.java.sip.communicator.impl.media.codec; public class Constants { public static final String ALAW_RTP = "ALAW/rtp"; + public static final String SPEEX_RTP = "speex/rtp"; + public static final String SPEEX = "speex"; + public static final String ILBC_RTP = "ilbc/rtp"; + public static final String ILBC = "ilbc"; } diff --git a/src/net/java/sip/communicator/impl/media/codec/audio/speex/JavaDecoder.java b/src/net/java/sip/communicator/impl/media/codec/audio/speex/JavaDecoder.java new file mode 100644 index 0000000..32a1fa3 --- /dev/null +++ b/src/net/java/sip/communicator/impl/media/codec/audio/speex/JavaDecoder.java @@ -0,0 +1,290 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.media.codec.audio.speex; + +import java.io.*; +import javax.media.*; +import javax.media.format.*; + +import org.xiph.speex.*; +import net.java.sip.communicator.impl.media.codec.*; + +/** + * Speex to PCM java decoder + * @author Damian Minkov + **/ +public class JavaDecoder + extends com.ibm.media.codec.audio.AudioCodec +{ + private Format lastFormat = null; + private int numberOfChannels = 0; + private SpeexDecoder decoder = null; + + public JavaDecoder() + { + inputFormats = new Format[] + { + new AudioFormat( + Constants.SPEEX_RTP, + 8000, + 8, + 1, + Format.NOT_SPECIFIED, + Format.NOT_SPECIFIED + )}; + + supportedInputFormats = new AudioFormat[] + { + new AudioFormat(Constants.SPEEX_RTP, + 8000, + 8, + 1, + Format.NOT_SPECIFIED, + Format.NOT_SPECIFIED)}; + + defaultOutputFormats = new AudioFormat[] + { + new AudioFormat(AudioFormat.LINEAR)}; + + PLUGIN_NAME = "Speex Decoder"; + } + + protected Format[] getMatchingOutputFormats(Format in) + { + AudioFormat af = (AudioFormat) in; + + supportedOutputFormats = new AudioFormat[] + { + new AudioFormat( + AudioFormat.LINEAR, + af.getSampleRate(), + 16, + af.getChannels(), + AudioFormat.LITTLE_ENDIAN, //isBigEndian(), + AudioFormat.SIGNED //isSigned()); + )}; + + return supportedOutputFormats; + + } + + + public void open() + {} + + public void close() + {} + + private void initConverter(AudioFormat inFormat) + { + lastFormat = inFormat; + + numberOfChannels = inFormat.getChannels(); + + decoder = new SpeexDecoder(); + + decoder.init(0, + (int) ( (AudioFormat) inFormat).getSampleRate(), + inFormat.getChannels(), + false); + } + + public int process(Buffer inputBuffer, Buffer outputBuffer) + { + if (!checkInputBuffer(inputBuffer)) + { + return BUFFER_PROCESSED_FAILED; + } + + if (isEOM(inputBuffer)) + { + propagateEOM(outputBuffer); + return BUFFER_PROCESSED_OK; + } + + int channels = ( (AudioFormat) outputFormat).getChannels(); + + byte[] inData = (byte[]) inputBuffer.getData(); + + int inpLength = inputBuffer.getLength(); + + int outLength = 0; + + int inOffset = inputBuffer.getOffset(); + int outOffset = outputBuffer.getOffset(); + + Format newFormat = inputBuffer.getFormat(); + + if (lastFormat != newFormat) + { + initConverter( (AudioFormat) newFormat); + } + + try + { + decoder.processData(inData, inOffset, inpLength); + outLength = decoder.getProcessedDataByteSize(); + + byte[] outData = validateByteArraySize(outputBuffer, outLength); + + decoder.getProcessedData(outData, outOffset); + } + catch (StreamCorruptedException ex) + { + ex.printStackTrace(); + } + + updateOutput(outputBuffer, outputFormat, outLength, 0); + + return BUFFER_PROCESSED_OK; + } + +// public java.lang.Object[] getControls() +// { +// if (controls == null) +// { +// controls = new Control[1]; +// controls[0] = new com.sun.media.controls.SilenceSuppressionAdapter(this, false, false); +// } +// return (Object[]) controls; +// } + + static int SpeexSubModeSz[] = + { + 0, 43, 119, 160, + 220, 300, 364, 492, + 79, 0, 0, 0, + 0, 0, 0, 0}; + static int SpeexInBandSz[] = + { + 1, 1, 4, 4, + 4, 4, 4, 4, + 8, 8, 16, 16, + 32, 32, 64, 64}; + + /** + * Counts the samples in given data + * @param data byte[] + * @param len int + * @return int + */ + private static int speex_get_samples(byte[] data, int len) + { + int bit = 0; + int cnt = 0; + int off = 0; + + int c; + + //DEBU(G "speex_get_samples(%d)\n", len); + while ((len * 8 - bit) >= 5) { + /* skip wideband frames */ + off = speex_get_wb_sz_at(data, len, bit); + if (off < 0) { + //DEBU(G "\tERROR reading wideband frames\n"); + break; + } + bit += off; + + if ((len * 8 - bit) < 5) { + //DEBU(G "\tERROR not enough bits left after wb\n"); + break; + } + + /* get control bits */ + c = get_n_bits_at(data, 5, bit); + //DEBU(G "\tCONTROL: %d at %d\n", c, bit); + bit += 5; + + if (c == 15) { + //DEBU(G "\tTERMINATOR\n"); + break; + } else if (c == 14) { + /* in-band signal; next 4 bits contain signal id */ + c = get_n_bits_at(data, 4, bit); + bit += 4; + //DEBUG "\tIN-BAND %d bits\n", SpeexInBandSz[c]); + bit += SpeexInBandSz[c]; + } else if (c == 13) { + /* user in-band; next 5 bits contain msg len */ + c = get_n_bits_at(data, 5, bit); + bit += 5; + //DEBUG "\tUSER-BAND %d bytes\n", c); + bit += c * 8; + } else if (c > 8) { + //DEBU(G "\tUNKNOWN sub-mode %d\n", c); + break; + } else { + /* skip number bits for submode (less the 5 control bits) */ + //DEBU(G "\tSUBMODE %d %d bits\n", c, SpeexSubModeSz[c]); + bit += SpeexSubModeSz[c] - 5; + + cnt += 160; /* new frame */ + } + } + //DEBU(G "\tSAMPLES: %d\n", cnt); + return cnt; + } + + private static int get_n_bits_at(byte[] data, int n, int bit) + { + int byteInt = bit / 8; /* byte containing first bit */ + int rem = 8 - (bit % 8); /* remaining bits in first byte */ + int ret = 0; + + if (n <= 0 || n > 8) + return 0; + + if (rem < n) + { + ret = (data[byteInt] << (n - rem)); + ret |= (data[byteInt +1] >> (8 - n + rem)); + } + else + { + ret = (data[byteInt] >> (rem - n)); + } + + return (ret & (0xff >> (8 - n))); + } + + static int SpeexWBSubModeSz[] = + { + 0, 36, 112, 192, + 352, 0, 0, 0}; + + private static int speex_get_wb_sz_at(byte[] data, int len, int bit) + { + int off = bit; + int c; + + /* skip up to two wideband frames */ + if ( ( (len * 8 - off) >= 5) && + get_n_bits_at(data, 1, off) != 0) + { + c = get_n_bits_at(data, 3, off + 1); + off += SpeexWBSubModeSz[c]; + + if ( ( (len * 8 - off) >= 5) && + get_n_bits_at(data, 1, off) != 0) + { + c = get_n_bits_at(data, 3, off + 1); + off += SpeexWBSubModeSz[c]; + + if ( ( (len * 8 - off) >= 5) && + get_n_bits_at(data, 1, off) != 0) + { + /* too many in a row */ + //DEBU(G "\tCORRUPT too many wideband streams in a row\n"); + return -1; + } + } + + } + return off - bit; + } +} diff --git a/src/net/java/sip/communicator/impl/media/codec/audio/speex/JavaEncoder.java b/src/net/java/sip/communicator/impl/media/codec/audio/speex/JavaEncoder.java new file mode 100644 index 0000000..83f42ba --- /dev/null +++ b/src/net/java/sip/communicator/impl/media/codec/audio/speex/JavaEncoder.java @@ -0,0 +1,145 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.media.codec.audio.speex; + +import javax.media.*; +import javax.media.format.*; + +import org.xiph.speex.*; +import net.java.sip.communicator.impl.media.codec.*; + +/** + * The Speex Encoder + * + * @author Damian Minkov + */ +public class JavaEncoder + extends com.ibm.media.codec.audio.AudioCodec +{ + private Format lastFormat = null; + private int numberOfChannels = 0; + + private static int FRAME_SIZE = 320; + + private SpeexEncoder encoder = null; + + public JavaEncoder() + { + supportedInputFormats = new AudioFormat[] + { + new AudioFormat( + AudioFormat.LINEAR, + 8000, + 16, + 1, + AudioFormat.LITTLE_ENDIAN, //isBigEndian(), + AudioFormat.SIGNED //isSigned()); + )}; + + defaultOutputFormats = new AudioFormat[] + {new AudioFormat(Constants.SPEEX_RTP)}; + + PLUGIN_NAME = "pcm to speex converter"; + } + + protected Format[] getMatchingOutputFormats(Format in) + { + AudioFormat af = (AudioFormat) in; + + supportedOutputFormats = new AudioFormat[] + {new AudioFormat( + Constants.SPEEX_RTP, + af.getSampleRate(), + af.getSampleSizeInBits(), + af.getChannels(), + af.getEndian(), + af.getSigned() + )}; + + return supportedOutputFormats; + } + + public void open() throws ResourceUnavailableException + { + + } + + public void close() + { + + } + + private void initConverter(AudioFormat inFormat) + { + lastFormat = inFormat; + numberOfChannels = inFormat.getChannels(); + + encoder = new SpeexEncoder(); + + encoder.init(0, 4, (int)inFormat.getSampleRate(), 1); + } + + public int process(Buffer inputBuffer, Buffer outputBuffer) + { + if (!checkInputBuffer(inputBuffer)) + { + return BUFFER_PROCESSED_FAILED; + } + + if (isEOM(inputBuffer)) + { + propagateEOM(outputBuffer); + return BUFFER_PROCESSED_OK; + } + + Format newFormat = inputBuffer.getFormat(); + + if (lastFormat != newFormat) + { + initConverter( (AudioFormat) newFormat); + } + + int inpLength = inputBuffer.getLength(); + + byte[] inpData = (byte[]) inputBuffer.getData(); + int inOffset = inputBuffer.getOffset(); + + if (inpLength == 0) + { + return OUTPUT_BUFFER_NOT_FILLED; + } + + if ( (inpLength - inOffset) >= FRAME_SIZE) + { + encoder.processData(inpData, inOffset, FRAME_SIZE); + byte[] buff = new byte[encoder.getProcessedDataByteSize()]; + encoder.getProcessedData(buff, 0); + + byte[] outData = validateByteArraySize(outputBuffer, buff.length); + + System.arraycopy(buff, 0, outData, outputBuffer.getOffset(), + buff.length); + + updateOutput(outputBuffer, outputFormat, outData.length, 0); + + if ( (inpLength - inOffset) > FRAME_SIZE) + { + inputBuffer.setOffset(inOffset + FRAME_SIZE); + + return BUFFER_PROCESSED_OK | INPUT_BUFFER_NOT_CONSUMED; + } + else + { + return BUFFER_PROCESSED_OK; + } + } + else + { + return OUTPUT_BUFFER_NOT_FILLED; + } + } +} |