first commit

This commit is contained in:
2024-12-04 09:19:38 +07:00
commit f62fbb5d7a
223 changed files with 24288 additions and 0 deletions

57
pom.xml Normal file
View File

@@ -0,0 +1,57 @@
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>AxisAudio</groupId>
<artifactId>AxisAudio</artifactId>
<version>0.0.1-SNAPSHOT</version>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.6.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpmime -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.14</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,44 @@
package bass;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import bass.BassLibrary.BASS_FILEPROCS;
import bass.BassLibrary.DOWNLOADPROC;
/**
* BASS AAC Library
* @author rdkartono
*
*/
public interface BassAACLibrary extends Library {
BassAACLibrary BASS_AAC = (BassAACLibrary) Native.load("bass_aac",BassAACLibrary.class);
public interface Constant{
// additional error codes returned by BASS_ErrorGetCode
public static final int BASS_ERROR_MP4_NOSTREAM = 6000; // non-streamable due to MP4 atom order ("mdat" before "moov")
// Additional BASS_SetConfig options
public static final int BASS_CONFIG_MP4_VIDEO = 0x10700; // play the audio from MP4 videos
public static final int BASS_CONFIG_AAC_MP4 = 0x10701; // support MP4 in BASS_AAC_StreamCreateXXX functions (no need for BASS_MP4_StreamCreateXXX)
public static final int BASS_CONFIG_AAC_PRESCAN = 0x10702; // pre-scan ADTS AAC files for seek points and accurate length
// Additional BASS_AAC_StreamCreateFile/etc flags
public static final int BASS_AAC_FRAME960 = 0x1000; // 960 samples per frame
public static final int BASS_AAC_STEREO = 0x400000; // downmatrix to stereo
// BASS_CHANNELINFO type
public static final int BASS_CTYPE_STREAM_AAC = 0x10b00; // AAC
public static final int BASS_CTYPE_STREAM_MP4 = 0x10b01; // AAC in MP4
}
public int BASS_AAC_StreamCreateFile(String file, long offset, long length, int flags);
public int BASS_AAC_StreamCreateFile(Pointer file, long offset, long length, int flags);
public int BASS_AAC_StreamCreateURL(String url, int offset, int flags, DOWNLOADPROC proc, Pointer user);
public int BASS_AAC_StreamCreateFileUser(int system, int flags, BASS_FILEPROCS procs, Pointer user);
public int BASS_MP4_StreamCreateFile(String file, long offset, long length, int flags);
public int BASS_MP4_StreamCreateFile(Pointer file, long offset, long length, int flags);
public int BASS_MP4_StreamCreateFileUser(int system, int flags, BASS_FILEPROCS procs, Pointer user);
}

View File

@@ -0,0 +1,21 @@
package bass;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import bass.BassEncLibrary.ENCODEPROCEX;
/**
* Bass Encoder AAC Plugin
* Must load together with BassEncLibrary
* @author rdkartono
*
*/
public interface BassEncAACLibrary extends Library {
BassEncAACLibrary BASSENC_AAC = (BassEncAACLibrary) Native.load("bassenc_aac",BassEncAACLibrary.class);
public int BASS_Encode_AAC_GetVersion();
public int BASS_Encode_AAC_Start(int handle, String options, int flags, ENCODEPROCEX proc, Pointer user);
public int BASS_Encode_AAC_StartFile(int handle, String options, int flags, String filename);
}

View File

@@ -0,0 +1,172 @@
package bass;
import com.sun.jna.Callback;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
/**
* BASS Encoder Library
* @author rdkartono
*
*/
public interface BassEncLibrary extends Library {
BassEncLibrary BASSENC = (BassEncLibrary) Native.load("bassenc", BassEncLibrary.class);
public interface ENCODEPROC extends Callback
{
void ENCODEPROC(int handle, int channel, Pointer buffer, int length, Pointer user);
/* Encoding callback function.
handle : The encoder
channel: The channel handle
buffer : Buffer containing the encoded data
length : Number of bytes
user : The 'user' parameter value given when starting the encoder */
}
public interface ENCODEPROCEX extends Callback
{
void ENCODEPROCEX(int handle, int channel, Pointer buffer, int length, long offset, Pointer user);
/* Encoding callback function.
handle : The encoder
channel: The channel handle
buffer : Buffer containing the encoded data
length : Number of bytes
offset : File offset of the data
user : The 'user' parameter value given when starting the encoder */
}
public interface ENCODERPROC extends Callback
{
int ENCODERPROC(int handle, int channel, Pointer buffer, int length, int maxout, Pointer user);
/* Encoder callback function.
handle : The encoder
channel: The channel handle
buffer : Buffer containing the PCM data (input) and receiving the encoded data (output)
length : Number of bytes in (-1=closing)
maxout : Maximum number of bytes out
user : The 'user' parameter value given when calling BASS_Encode_StartUser
RETURN : The amount of encoded data (-1=stop) */
}
public interface ENCODECLIENTPROC extends Callback
{
boolean ENCODECLIENTPROC(int handle, boolean connect, String client, StringBuffer headers, Pointer user);
/* Client connection notification callback function.
handle : The encoder
connect: true/false=client is connecting/disconnecting
client : The client's address (xxx.xxx.xxx.xxx:port)
headers: Request headers (optionally response headers on return)
user : The 'user' parameter value given when calling BASS_Encode_ServerInit
RETURN : true/false=accept/reject connection (ignored if connect=false) */
}
public interface ENCODENOTIFYPROC extends Callback
{
void ENCODENOTIFYPROC(int handle, int status, Pointer user);
/* Encoder death notification callback function.
handle : The encoder
status : Notification (BASS_ENCODE_NOTIFY_xxx)
user : The 'user' parameter value given when calling BASS_Encode_SetNotify */
}
public int BASS_Encode_GetVersion();
public int BASS_Encode_Start(int handle, String cmdline, int flags, ENCODEPROC proc, Pointer user);
public int BASS_Encode_StartLimit(int handle, String cmdline, int flags, ENCODEPROC proc, Pointer user, int limit);
public int BASS_Encode_StartUser(int handle, String file, int flags, ENCODERPROC proc, Pointer user);
public boolean BASS_Encode_AddChunk(int handle, String id, Pointer buffer, int length);
public boolean BASS_Encode_Write(int handle, Pointer buffer, int length);
public boolean BASS_Encode_Stop(int handle);
public boolean BASS_Encode_StopEx(int handle, boolean queue);
public boolean BASS_Encode_SetPaused(int handle, boolean paused);
public int BASS_Encode_IsActive(int handle);
public boolean BASS_Encode_SetNotify(int handle, ENCODENOTIFYPROC proc, Pointer user);
public long BASS_Encode_GetCount(int handle, int count);
public boolean BASS_Encode_SetChannel(int handle, int channel);
public int BASS_Encode_GetChannel(int handle);
public boolean BASS_Encode_UserOutput(int handle, long offset, Pointer buffer, int length);
public boolean BASS_Encode_CastInit(int handle, String server, String pass, String content, String name, String url, String genre, String desc, String headers, int bitrate, int flags);
public boolean BASS_Encode_CastSetTitle(int handle, String title, String url);
public boolean BASS_Encode_CastSendMeta(int handle, int type, Pointer data, int length);
public String BASS_Encode_CastGetStats(int handle, int type, String pass);
public int BASS_Encode_ServerInit(int handle, String port, int buffer, int burst, int flags, ENCODECLIENTPROC proc, Pointer user);
public boolean BASS_Encode_ServerKick(int handle, String client);
public interface Constant{
// Additional error codes returned by BASS_ErrorGetCode
public static final int BASS_ERROR_CAST_DENIED = 2100; // access denied (invalid password)
public static final int BASS_ERROR_SERVER_CERT = 2101; // missing/invalid certificate
// Additional BASS_SetConfig options
public static final int BASS_CONFIG_ENCODE_PRIORITY = 0x10300;
public static final int BASS_CONFIG_ENCODE_QUEUE = 0x10301;
public static final int BASS_CONFIG_ENCODE_CAST_TIMEOUT = 0x10310;
// Additional BASS_SetConfigPtr options
public static final int BASS_CONFIG_ENCODE_CAST_PROXY = 0x10311;
public static final int BASS_CONFIG_ENCODE_CAST_BIND = 0x10312;
public static final int BASS_CONFIG_ENCODE_SERVER_CERT = 0x10320;
public static final int BASS_CONFIG_ENCODE_SERVER_KEY = 0x10321;
// BASS_Encode_Start flags
public static final int BASS_ENCODE_NOHEAD = 1; // don't send a WAV header to the encoder
public static final int BASS_ENCODE_FP_8BIT = 2; // convert floating-point sample data to 8-bit integer
public static final int BASS_ENCODE_FP_16BIT = 4; // convert floating-point sample data to 16-bit integer
public static final int BASS_ENCODE_FP_24BIT = 6; // convert floating-point sample data to 24-bit integer
public static final int BASS_ENCODE_FP_32BIT = 8; // convert floating-point sample data to 32-bit integer
public static final int BASS_ENCODE_FP_AUTO = 14; // convert floating-point sample data back to channel's format
public static final int BASS_ENCODE_BIGEND = 16; // big-endian sample data
public static final int BASS_ENCODE_PAUSE = 32; // start encording paused
public static final int BASS_ENCODE_PCM = 64; // write PCM sample data (no encoder)
public static final int BASS_ENCODE_RF64 = 128; // send an RF64 header
public static final int BASS_ENCODE_QUEUE = 0x200; // queue data to feed encoder asynchronously
public static final int BASS_ENCODE_WFEXT = 0x400; // WAVEFORMATEXTENSIBLE "fmt" chunk
public static final int BASS_ENCODE_CAST_NOLIMIT = 0x1000; // don't limit casting data rate
public static final int BASS_ENCODE_LIMIT = 0x2000; // limit data rate to real-time
public static final int BASS_ENCODE_AIFF = 0x4000; // send an AIFF header rather than WAV
public static final int BASS_ENCODE_DITHER = 0x8000; // apply dither when converting floating-point sample data to integer
public static final int BASS_ENCODE_AUTOFREE = 0x40000; // free the encoder when the channel is freed
// BASS_Encode_GetCount counts
public static final int BASS_ENCODE_COUNT_IN = 0; // sent to encoder
public static final int BASS_ENCODE_COUNT_OUT = 1; // received from encoder
public static final int BASS_ENCODE_COUNT_CAST = 2; // sent to cast server
public static final int BASS_ENCODE_COUNT_QUEUE = 3; // queued
public static final int BASS_ENCODE_COUNT_QUEUE_LIMIT = 4; // queue limit
public static final int BASS_ENCODE_COUNT_QUEUE_FAIL = 5; // failed to queue
public static final int BASS_ENCODE_COUNT_IN_FP = 6; // sent to encoder before floating-point conversion
// BASS_Encode_CastInit content MIME types
public static final String BASS_ENCODE_TYPE_MP3 = "audio/mpeg";
public static final String BASS_ENCODE_TYPE_OGG = "audio/ogg";
public static final String BASS_ENCODE_TYPE_AAC = "audio/aacp";
// BASS_Encode_CastInit flags
public static final int BASS_ENCODE_CAST_PUBLIC = 1; // add to public directory
public static final int BASS_ENCODE_CAST_PUT = 2; // use PUT method
public static final int BASS_ENCODE_CAST_SSL = 4; // use SSL/TLS encryption
// BASS_Encode_CastGetStats types
public static final int BASS_ENCODE_STATS_SHOUT = 0; // Shoutcast stats
public static final int BASS_ENCODE_STATS_ICE = 1; // Icecast mount-point stats
public static final int BASS_ENCODE_STATS_ICESERV = 2; // Icecast server stats
// BASS_Encode_ServerInit flags
public static final int BASS_ENCODE_SERVER_NOHTTP = 1; // no HTTP headers
public static final int BASS_ENCODE_SERVER_META = 2; // Shoutcast metadata
public static final int BASS_ENCODE_SERVER_SSL = 4; // support SSL/TLS encryption
public static final int BASS_ENCODE_SERVER_SSLONLY = 8; // require SSL/TLS encryption
// Encoder notifications
public static final int BASS_ENCODE_NOTIFY_ENCODER = 1; // encoder died
public static final int BASS_ENCODE_NOTIFY_CAST = 2; // cast server connection died
public static final int BASS_ENCODE_NOTIFY_SERVER = 3; // server died
public static final int BASS_ENCODE_NOTIFY_CAST_TIMEOUT = 0x10000; // cast timeout
public static final int BASS_ENCODE_NOTIFY_QUEUE_FULL = 0x10001; // queue is out of space
public static final int BASS_ENCODE_NOTIFY_FREE = 0x10002; // encoder has been freed
}
}

View File

@@ -0,0 +1,22 @@
package bass;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import bass.BassEncLibrary.ENCODEPROCEX;
/**
* BASS Encoder MP3 plugin
* Must load together with BassEncLibrary
* @author rdkartono
*
*/
public interface BassEncMP3Library extends Library{
BassEncMP3Library BASSENC_MP3 = (BassEncMP3Library) Native.load("bassenc_mp3",BassEncMP3Library.class);
public int BASS_Encode_MP3_GetVersion();
public int BASS_Encode_MP3_Start(int handle, String options, int flags, ENCODEPROCEX proc, Pointer user);
public int BASS_Encode_MP3_StartFile(int handle, String options, int flags, String filename);
}

View File

@@ -0,0 +1,27 @@
package bass;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import bass.BassEncLibrary.ENCODEPROC;
/**
* Bass Encoder OGG Plugin
* Must load together with BassEncLibrary
* @author rdkartono
*
*/
public interface BassEncOGGLibrary extends Library {
BassEncOGGLibrary BASSENC_OGG = (BassEncOGGLibrary) Native.load("bassenc_ogg",BassEncOGGLibrary.class);
public int BASS_Encode_OGG_GetVersion();
public int BASS_Encode_OGG_Start(int handle, String options, int flags, ENCODEPROC proc, Pointer user);
public int BASS_Encode_OGG_StartFile(int handle, String options, int flags, String filename);
public boolean BASS_Encode_OGG_NewStream(int handle, String options, int flags);
public interface Constant{
// BASS_Encode_OGG_NewStream flags
public static final int BASS_ENCODE_OGG_RESET = 0x1000000;
}
}

View File

@@ -0,0 +1,27 @@
package bass;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import bass.BassEncLibrary.ENCODEPROC;
/**
* Bass Encoder OPUS Plugin
* Must load together with BassEncLibrary
* @author rdkartono
*
*/
public interface BassEncOPUSLibrary extends Library{
BassEncOPUSLibrary BASSENC_OPUS = (BassEncOPUSLibrary) Native.load("bassenc_opus",BassEncOPUSLibrary.class);
public interface Constant{
// BASS_Encode_OPUS_NewStream flags
public static final int BASS_ENCODE_OPUS_RESET = 0x1000000;
public static final int BASS_ENCODE_OPUS_CTLONLY = 0x2000000;
}
public int BASS_Encode_OPUS_GetVersion();
public int BASS_Encode_OPUS_Start(int handle, String options, int flags, ENCODEPROC proc, Pointer user);
public int BASS_Encode_OPUS_StartFile(int handle, String options, int flags, String filename);
public boolean BASS_Encode_OPUS_NewStream(int handle, String options, int flags);
}

1363
src/bass/BassLibrary.java Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,141 @@
package bass;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import anywheresoftware.b4a.BA.Hide;
import bass.BassLibrary.SYNCPROC;
/**
* Bass Mixer Library
* @author rdkartono
*
*/
public interface BassMixLibrary extends Library {
BassMixLibrary BASSMIX = (BassMixLibrary) Native.load("bassmix",BassMixLibrary.class);
public interface Constant{
// Additional BASS_SetConfig options
public static final int BASS_CONFIG_MIXER_BUFFER = 0x10601;
public static final int BASS_CONFIG_MIXER_POSEX = 0x10602;
public static final int BASS_CONFIG_SPLIT_BUFFER = 0x10610;
// BASS_Mixer_StreamCreate flags
public static final int BASS_MIXER_RESUME = 0x1000; // resume stalled immediately upon new/unpaused source
public static final int BASS_MIXER_POSEX = 0x2000; // enable BASS_Mixer_ChannelGetPositionEx support
public static final int BASS_MIXER_NOSPEAKER = 0x4000; // ignore speaker arrangement
public static final int BASS_MIXER_QUEUE = 0x8000; // queue sources
public static final int BASS_MIXER_END = 0x10000; // end the stream when there are no sources
public static final int BASS_MIXER_NONSTOP = 0x20000; // don't stall when there are no sources
// BASS_Mixer_StreamAddChannel/Ex flags
public static final int BASS_MIXER_CHAN_ABSOLUTE = 0x1000; // start is an absolute position
public static final int BASS_MIXER_CHAN_BUFFER = 0x2000; // buffer data for BASS_Mixer_ChannelGetData/Level
public static final int BASS_MIXER_CHAN_LIMIT = 0x4000; // limit mixer processing to the amount available from this source
public static final int BASS_MIXER_CHAN_MATRIX = 0x10000; // matrix mixing
public static final int BASS_MIXER_CHAN_PAUSE = 0x20000; // don't process the source
public static final int BASS_MIXER_CHAN_DOWNMIX = 0x400000; // downmix to stereo/mono
public static final int BASS_MIXER_CHAN_NORAMPIN = 0x800000; // don't ramp-in the start
public static final int BASS_MIXER_BUFFER = BASS_MIXER_CHAN_BUFFER;
public static final int BASS_MIXER_LIMIT = BASS_MIXER_CHAN_LIMIT;
public static final int BASS_MIXER_MATRIX = BASS_MIXER_CHAN_MATRIX;
public static final int BASS_MIXER_PAUSE = BASS_MIXER_CHAN_PAUSE;
public static final int BASS_MIXER_DOWNMIX = BASS_MIXER_CHAN_DOWNMIX;
public static final int BASS_MIXER_NORAMPIN = BASS_MIXER_CHAN_NORAMPIN;
// Mixer attributes
public static final int BASS_ATTRIB_MIXER_LATENCY = 0x15000;
public static final int BASS_ATTRIB_MIXER_THREADS = 0x15001;
public static final int BASS_ATTRIB_MIXER_VOL = 0x15002;
// Additional BASS_Mixer_ChannelIsActive return values
public static final int BASS_ACTIVE_WAITING = 5;
public static final int BASS_ACTIVE_QUEUED = 6;
// BASS_Split_StreamCreate flags
public static final int BASS_SPLIT_SLAVE = 0x1000; // only read buffered data
public static final int BASS_SPLIT_POS = 0x2000;
// Splitter attributes
public static final int BASS_ATTRIB_SPLIT_ASYNCBUFFER = 0x15010;
public static final int BASS_ATTRIB_SPLIT_ASYNCPERIOD = 0x15011;
// Envelope types
public static final int BASS_MIXER_ENV_FREQ = 1;
public static final int BASS_MIXER_ENV_VOL = 2;
public static final int BASS_MIXER_ENV_PAN = 3;
public static final int BASS_MIXER_ENV_LOOP = 0x10000; // flag: loop
public static final int BASS_MIXER_ENV_REMOVE = 0x20000; // flag: remove at end
// Additional sync types
public static final int BASS_SYNC_MIXER_ENVELOPE = 0x10200;
public static final int BASS_SYNC_MIXER_ENVELOPE_NODE = 0x10201;
public static final int BASS_SYNC_MIXER_QUEUE = 0x10202;
// Additional BASS_Mixer_ChannelSetPosition flag
public static final int BASS_POS_MIXER_RESET = 0x10000; // flag: clear mixer's playback buffer
// Additional BASS_Mixer_ChannelGetPosition mode
public static final int BASS_POS_MIXER_DELAY = 5;
// BASS_CHANNELINFO types
public static final int BASS_CTYPE_STREAM_MIXER = 0x10800;
public static final int BASS_CTYPE_STREAM_SPLIT = 0x10801;
}
// Envelope node
@Structure.FieldOrder({"pos, value"})
public static class BASS_MIXER_NODE extends Structure {
public BASS_MIXER_NODE() {}
public BASS_MIXER_NODE(long _pos, float _value) { pos=_pos; value=_value; }
public long pos;
public float value;
@Override
@Hide
public void autoWrite() {super.autoWrite();}
@Override
@Hide
public void autoRead() {super.autoRead();}
}
public int BASS_Mixer_GetVersion();
public int BASS_Mixer_StreamCreate(int freq, int chans, int flags);
public boolean BASS_Mixer_StreamAddChannel(int handle, int channel, int flags);
public boolean BASS_Mixer_StreamAddChannelEx(int handle, int channel, int flags, long start, long length);
public int BASS_Mixer_StreamGetChannels(int handle, int[] channels, int count);
public int BASS_Mixer_ChannelGetMixer(int handle);
public int BASS_Mixer_ChannelIsActive(int handle);
public int BASS_Mixer_ChannelFlags(int handle, int flags, int mask);
public boolean BASS_Mixer_ChannelRemove(int handle);
public boolean BASS_Mixer_ChannelSetPosition(int handle, long pos, int mode);
public long BASS_Mixer_ChannelGetPosition(int handle, int mode);
public long BASS_Mixer_ChannelGetPositionEx(int channel, int mode, int delay);
public int BASS_Mixer_ChannelGetLevel(int handle);
public boolean BASS_Mixer_ChannelGetLevelEx(int handle, float[] levels, float length, int flags);
public int BASS_Mixer_ChannelGetData(int handle, Pointer buffer, int length);
public int BASS_Mixer_ChannelSetSync(int handle, int type, long param, SYNCPROC proc, Pointer user);
public boolean BASS_Mixer_ChannelRemoveSync(int channel, int sync);
public boolean BASS_Mixer_ChannelSetMatrix(int handle, float[][] matrix);
public boolean BASS_Mixer_ChannelSetMatrixEx(int handle, float[][] matrix, float time);
public boolean BASS_Mixer_ChannelGetMatrix(int handle, float[][] matrix);
public boolean BASS_Mixer_ChannelSetEnvelope(int handle, int type, BASS_MIXER_NODE[] nodes, int count);
public boolean BASS_Mixer_ChannelSetEnvelopePos(int handle, int type, long pos);
public long BASS_Mixer_ChannelGetEnvelopePos(int handle, int type, Float value);
public int BASS_Split_StreamCreate(int channel, int flags, int[] chanmap);
public int BASS_Split_StreamGetSource(int handle);
public int BASS_Split_StreamGetSplits(int handle, int[] splits, int count);
public boolean BASS_Split_StreamReset(int handle);
public boolean BASS_Split_StreamResetEx(int handle, int offset);
public int BASS_Split_StreamGetAvailable(int handle);
}

View File

@@ -0,0 +1,23 @@
package bass;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import bass.BassLibrary.BASS_FILEPROCS;
import bass.BassLibrary.DOWNLOADPROC;
public interface BassOPUSLibrary extends Library {
BassOPUSLibrary BASS_OPUS = (BassOPUSLibrary) Native.load("bassopus",BassOPUSLibrary.class);
public interface Constant{
// BASS_CHANNELINFO type
public static final int BASS_CTYPE_STREAM_OPUS = 0x11200;
// Additional attributes
public static final int BASS_ATTRIB_OPUS_ORIGFREQ = 0x13000;
}
public int BASS_OPUS_StreamCreateFile(String file, long offset, long length, int flags);
public int BASS_OPUS_StreamCreateFile(Pointer file, long offset, long length, int flags);
public int BASS_OPUS_StreamCreateURL(String url, int offset, int flags, DOWNLOADPROC proc, Pointer user);
public int BASS_OPUS_StreamCreateFileUser(int system, int flags, BASS_FILEPROCS procs, Pointer user);
}

View File

@@ -0,0 +1,159 @@
package bass;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Converts a PCM byte stream into an A-Law or u-Law byte stream
* Source : http://thorntonzone.com/manuals/Compression/Fax,%20IBM%20MMR/MMSC/mmsc/uk/co/mmscomputing/sound/
* The PCM-Format is :
* PCM 8000.0 Hz, 16 bit, mono, SIGNED, little-endian
* static AudioFormat pcmformat = new AudioFormat(8000,16,1,true,false);
* @author rdkartono
*
*/
public class CompressInputStream extends FilterInputStream{
/*
Convert mono PCM byte stream into A-Law u-Law byte stream
static AudioFormat alawformat= new AudioFormat(AudioFormat.Encoding.ALAW,8000,8,1,1,8000,false);
static AudioFormat ulawformat= new AudioFormat(AudioFormat.Encoding.ULAW,8000,8,1,1,8000,false);
PCM 8000.0 Hz, 16 bit, mono, SIGNED, little-endian
static AudioFormat pcmformat = new AudioFormat(8000,16,1,true,false);
*/
static private Compressor alawcompressor=new ALawCompressor();
static private Compressor ulawcompressor=new uLawCompressor();
private Compressor compressor=null;
public CompressInputStream(InputStream in, boolean useALaw)throws IOException{
super(in);
compressor=(useALaw)?alawcompressor:ulawcompressor;
}
public int read()throws IOException{
throw new IOException(getClass().getName()+".read() :\n\tDo not support simple read().");
}
public int read(byte[] b)throws IOException{
return read(b,0,b.length);
}
public int read(byte[] b, int off, int len)throws IOException{
int i,sample;
byte[] inb;
inb=new byte[len<<1]; // get 16bit PCM data
len=in.read(inb);
if(len==-1){return -1;};
i=0;
while(i<len){
sample = (inb[i++]&0x00FF);
sample |= (inb[i++]<<8);
b[off++]=(byte)compressor.compress((short)sample);
}
return len>>1;
}
}
abstract class Compressor{
protected abstract int compress(short sample);
}
/*
Mathematical Tools in Signal Processing with C++ and Java Simulations
by Willi-Hans Steeb
International School for Scientific Computing
*/
class ALawCompressor extends Compressor{
static final int cClip = 32635;
static final int[] ALawCompressTable ={
1,1,2,2,3,3,3,3,
4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,
5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7
};
protected int compress(short sample){
int sign;
int exponent;
int mantissa;
int compressedByte;
sign = ((~sample) >> 8) & 0x80;
if(sign==0){ sample *= -1;}
if(sample > cClip){ sample = cClip; }
if(sample >= 256){
exponent = ALawCompressTable[(sample >> 8) & 0x007F];
mantissa = (sample >> (exponent + 3) ) & 0x0F;
compressedByte = 0x007F & ((exponent << 4) | mantissa);
}else{
compressedByte = 0x007F & (sample >> 4);
}
compressedByte ^= (sign ^ 0x55);
return compressedByte;
}
}
class uLawCompressor extends Compressor{
static final int cClip = 32635;
static final int cBias = 0x84;
int[] uLawCompressTable ={
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};
protected int compress(short sample){
int sign;
int exponent;
int mantissa;
int compressedByte;
sign = (sample >> 8) & 0x80;
if(sign!=0){ sample *= -1;}
if(sample > cClip){ sample = cClip; }
sample += cBias;
exponent = uLawCompressTable[(sample >> 7) & 0x00FF];
mantissa = (sample >> (exponent + 3)) & 0x0F;
compressedByte = ~(sign | (exponent << 4) | mantissa);
return compressedByte&0x000000FF;
}
}

View File

@@ -0,0 +1,115 @@
package bass;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Converts an A-Law into u-Law byte stream or vice versa
* Source : http://thorntonzone.com/manuals/Compression/Fax,%20IBM%20MMR/MMSC/mmsc/uk/co/mmscomputing/sound/
* @author rdkartono
*
*/
public class ConvertInputStream extends FilterInputStream{
/*
Convert A-Law or u-Law byte stream into u-Law or A-Law byte stream
static AudioFormat alawformat= new AudioFormat(AudioFormat.Encoding.ALAW,8000,8,1,1,8000,false);
static AudioFormat ulawformat= new AudioFormat(AudioFormat.Encoding.ULAW,8000,8,1,1,8000,false);
*/
static private byte[] alaw2ulawtable={
41, 42, 39, 40, 45, 46, 43, 44,
33, 34, 31, 32, 37, 38, 35, 36,
57, 58, 55, 56, 61, 62, 59, 60,
49, 50, 47, 48, 53, 54, 51, 52,
10, 11, 8, 9, 14, 15, 12, 13,
2, 3, 0, 1, 6, 7, 4, 5,
26, 27, 24, 25, 30, 31, 28, 29,
18, 19, 16, 17, 22, 23, 20, 21,
98, 99, 96, 97, 102, 103, 100, 101,
93, 93, 92, 92, 95, 95, 94, 94,
116, 118, 112, 114, 124, 126, 120, 122,
106, 107, 104, 105, 110, 111, 108, 109,
72, 73, 70, 71, 76, 77, 74, 75,
64, 65, 63, 63, 68, 69, 66, 67,
86, 87, 84, 85, 90, 91, 88, 89,
79, 79, 78, 78, 82, 83, 80, 81,
-87, -86, -89, -88, -83, -82, -85, -84,
-95, -94, -97, -96, -91, -90, -93, -92,
-71, -70, -73, -72, -67, -66, -69, -68,
-79, -78, -81, -80, -75, -74, -77, -76,
-118, -117, -120, -119, -114, -113, -116, -115,
-126, -125, -128, -127, -122, -121, -124, -123,
-102, -101, -104, -103, -98, -97, -100, -99,
-110, -109, -112, -111, -106, -105, -108, -107,
-30, -29, -32, -31, -26, -25, -28, -27,
-35, -35, -36, -36, -33, -33, -34, -34,
-12, -10, -16, -14, -4, -2, -8, -6,
-22, -21, -24, -23, -18, -17, -20, -19,
-56, -55, -58, -57, -52, -51, -54, -53,
-64, -63, -65, -65, -60, -59, -62, -61,
-42, -41, -44, -43, -38, -37, -40, -39,
-49, -49, -50, -50, -46, -45, -48, -47,
};
static private byte[] ulaw2alawtable={
42, 43, 40, 41, 46, 47, 44, 45,
34, 35, 32, 33, 38, 39, 36, 37,
58, 59, 56, 57, 62, 63, 60, 61,
50, 51, 48, 49, 54, 55, 52, 53,
11, 8, 9, 14, 15, 12, 13, 2,
3, 0, 1, 6, 7, 4, 5, 26,
27, 24, 25, 30, 31, 28, 29, 18,
19, 16, 17, 22, 23, 20, 21, 107,
104, 105, 110, 111, 108, 109, 98, 99,
96, 97, 102, 103, 100, 101, 123, 121,
126, 127, 124, 125, 114, 115, 112, 113,
118, 119, 116, 117, 75, 73, 79, 77,
66, 67, 64, 65, 70, 71, 68, 69,
90, 91, 88, 89, 94, 95, 92, 93,
82, 82, 83, 83, 80, 80, 81, 81,
86, 86, 87, 87, 84, 84, 85, -43,
-86, -85, -88, -87, -82, -81, -84, -83,
-94, -93, -96, -95, -90, -89, -92, -91,
-70, -69, -72, -71, -66, -65, -68, -67,
-78, -77, -80, -79, -74, -73, -76, -75,
-117, -120, -119, -114, -113, -116, -115, -126,
-125, -128, -127, -122, -121, -124, -123, -102,
-101, -104, -103, -98, -97, -100, -99, -110,
-109, -112, -111, -106, -105, -108, -107, -21,
-24, -23, -18, -17, -20, -19, -30, -29,
-32, -31, -26, -25, -28, -27, -5, -7,
-2, -1, -4, -3, -14, -13, -16, -15,
-10, -9, -12, -11, -53, -55, -49, -51,
-62, -61, -64, -63, -58, -57, -60, -59,
-38, -37, -40, -39, -34, -33, -36, -35,
-46, -46, -45, -45, -48, -48, -47, -47,
-42, -42, -41, -41, -44, -44, -43, -43,
};
private byte[] table=null;
public ConvertInputStream(InputStream in, boolean useALaw2uLaw)throws IOException{
super(in);
table=(useALaw2uLaw)?alaw2ulawtable:ulaw2alawtable;
}
public int read()throws IOException{
int b=in.read();
if(b==-1){return -1;};
return table[b];
}
public int read(byte[] b, int off, int len)throws IOException{
byte[] inb=new byte[len];
len=in.read(inb);
if(len==-1){return -1;};
for(int i=0;i<len;i++){
b[off++]=table[inb[i]&0x00FF];
}
return len;
}
}

View File

@@ -0,0 +1,135 @@
package bass;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Converts an A-Law or u-Law byte stream into a PCM byte stream
* Source : http://thorntonzone.com/manuals/Compression/Fax,%20IBM%20MMR/MMSC/mmsc/uk/co/mmscomputing/sound/
* The PCM-Format is :
* PCM 8000.0 Hz, 16 bit, mono, SIGNED, little-endian
* static AudioFormat pcmformat = new AudioFormat(8000,16,1,true,false);
* @author rdkartono
*
*/
public class DecompressInputStream extends FilterInputStream{
/*
Convert A-Law or u-Law byte stream into mono PCM byte stream
static AudioFormat alawformat= new AudioFormat(AudioFormat.Encoding.ALAW,8000,8,1,1,8000,false);
static AudioFormat ulawformat= new AudioFormat(AudioFormat.Encoding.ULAW,8000,8,1,1,8000,false);
PCM 8000.0 Hz, 16 bit, mono, SIGNED, little-endian
static AudioFormat pcmformat = new AudioFormat(8000,16,1,true,false);
*/
/*
Mathematical Tools in Signal Processing with C++ and Java Simulations
by Willi-Hans Steeb
International School for Scientific Computing
*/
static private int[] alawtable={
0x80ea,0x80eb,0x80e8,0x80e9,0x80ee,0x80ef,0x80ec,0x80ed,
0x80e2,0x80e3,0x80e0,0x80e1,0x80e6,0x80e7,0x80e4,0x80e5,
0x40f5,0xc0f5,0x40f4,0xc0f4,0x40f7,0xc0f7,0x40f6,0xc0f6,
0x40f1,0xc0f1,0x40f0,0xc0f0,0x40f3,0xc0f3,0x40f2,0xc0f2,
0x00aa,0x00ae,0x00a2,0x00a6,0x00ba,0x00be,0x00b2,0x00b6,
0x008a,0x008e,0x0082,0x0086,0x009a,0x009e,0x0092,0x0096,
0x00d5,0x00d7,0x00d1,0x00d3,0x00dd,0x00df,0x00d9,0x00db,
0x00c5,0x00c7,0x00c1,0x00c3,0x00cd,0x00cf,0x00c9,0x00cb,
0xa8fe,0xb8fe,0x88fe,0x98fe,0xe8fe,0xf8fe,0xc8fe,0xd8fe,
0x28fe,0x38fe,0x08fe,0x18fe,0x68fe,0x78fe,0x48fe,0x58fe,
0xa8ff,0xb8ff,0x88ff,0x98ff,0xe8ff,0xf8ff,0xc8ff,0xd8ff,
0x28ff,0x38ff,0x08ff,0x18ff,0x68ff,0x78ff,0x48ff,0x58ff,
0xa0fa,0xe0fa,0x20fa,0x60fa,0xa0fb,0xe0fb,0x20fb,0x60fb,
0xa0f8,0xe0f8,0x20f8,0x60f8,0xa0f9,0xe0f9,0x20f9,0x60f9,
0x50fd,0x70fd,0x10fd,0x30fd,0xd0fd,0xf0fd,0x90fd,0xb0fd,
0x50fc,0x70fc,0x10fc,0x30fc,0xd0fc,0xf0fc,0x90fc,0xb0fc,
0x8015,0x8014,0x8017,0x8016,0x8011,0x8010,0x8013,0x8012,
0x801d,0x801c,0x801f,0x801e,0x8019,0x8018,0x801b,0x801a,
0xc00a,0x400a,0xc00b,0x400b,0xc008,0x4008,0xc009,0x4009,
0xc00e,0x400e,0xc00f,0x400f,0xc00c,0x400c,0xc00d,0x400d,
0x0056,0x0052,0x005e,0x005a,0x0046,0x0042,0x004e,0x004a,
0x0076,0x0072,0x007e,0x007a,0x0066,0x0062,0x006e,0x006a,
0x002b,0x0029,0x002f,0x002d,0x0023,0x0021,0x0027,0x0025,
0x003b,0x0039,0x003f,0x003d,0x0033,0x0031,0x0037,0x0035,
0x5801,0x4801,0x7801,0x6801,0x1801,0x0801,0x3801,0x2801,
0xd801,0xc801,0xf801,0xe801,0x9801,0x8801,0xb801,0xa801,
0x5800,0x4800,0x7800,0x6800,0x1800,0x0800,0x3800,0x2800,
0xd800,0xc800,0xf800,0xe800,0x9800,0x8800,0xb800,0xa800,
0x6005,0x2005,0xe005,0xa005,0x6004,0x2004,0xe004,0xa004,
0x6007,0x2007,0xe007,0xa007,0x6006,0x2006,0xe006,0xa006,
0xb002,0x9002,0xf002,0xd002,0x3002,0x1002,0x7002,0x5002,
0xb003,0x9003,0xf003,0xd003,0x3003,0x1003,0x7003,0x5003,
};
static private int[] ulawtable={
0x8482,0x8486,0x848a,0x848e,0x8492,0x8496,0x849a,0x849e,
0x84a2,0x84a6,0x84aa,0x84ae,0x84b2,0x84b6,0x84ba,0x84be,
0x84c1,0x84c3,0x84c5,0x84c7,0x84c9,0x84cb,0x84cd,0x84cf,
0x84d1,0x84d3,0x84d5,0x84d7,0x84d9,0x84db,0x84dd,0x84df,
0x04e1,0x04e2,0x04e3,0x04e4,0x04e5,0x04e6,0x04e7,0x04e8,
0x04e9,0x04ea,0x04eb,0x04ec,0x04ed,0x04ee,0x04ef,0x04f0,
0xc4f0,0x44f1,0xc4f1,0x44f2,0xc4f2,0x44f3,0xc4f3,0x44f4,
0xc4f4,0x44f5,0xc4f5,0x44f6,0xc4f6,0x44f7,0xc4f7,0x44f8,
0xa4f8,0xe4f8,0x24f9,0x64f9,0xa4f9,0xe4f9,0x24fa,0x64fa,
0xa4fa,0xe4fa,0x24fb,0x64fb,0xa4fb,0xe4fb,0x24fc,0x64fc,
0x94fc,0xb4fc,0xd4fc,0xf4fc,0x14fd,0x34fd,0x54fd,0x74fd,
0x94fd,0xb4fd,0xd4fd,0xf4fd,0x14fe,0x34fe,0x54fe,0x74fe,
0x8cfe,0x9cfe,0xacfe,0xbcfe,0xccfe,0xdcfe,0xecfe,0xfcfe,
0x0cff,0x1cff,0x2cff,0x3cff,0x4cff,0x5cff,0x6cff,0x7cff,
0x88ff,0x90ff,0x98ff,0xa0ff,0xa8ff,0xb0ff,0xb8ff,0xc0ff,
0xc8ff,0xd0ff,0xd8ff,0xe0ff,0xe8ff,0xf0ff,0xf8ff,0x0000,
0x7c7d,0x7c79,0x7c75,0x7c71,0x7c6d,0x7c69,0x7c65,0x7c61,
0x7c5d,0x7c59,0x7c55,0x7c51,0x7c4d,0x7c49,0x7c45,0x7c41,
0x7c3e,0x7c3c,0x7c3a,0x7c38,0x7c36,0x7c34,0x7c32,0x7c30,
0x7c2e,0x7c2c,0x7c2a,0x7c28,0x7c26,0x7c24,0x7c22,0x7c20,
0xfc1e,0xfc1d,0xfc1c,0xfc1b,0xfc1a,0xfc19,0xfc18,0xfc17,
0xfc16,0xfc15,0xfc14,0xfc13,0xfc12,0xfc11,0xfc10,0xfc0f,
0x3c0f,0xbc0e,0x3c0e,0xbc0d,0x3c0d,0xbc0c,0x3c0c,0xbc0b,
0x3c0b,0xbc0a,0x3c0a,0xbc09,0x3c09,0xbc08,0x3c08,0xbc07,
0x5c07,0x1c07,0xdc06,0x9c06,0x5c06,0x1c06,0xdc05,0x9c05,
0x5c05,0x1c05,0xdc04,0x9c04,0x5c04,0x1c04,0xdc03,0x9c03,
0x6c03,0x4c03,0x2c03,0x0c03,0xec02,0xcc02,0xac02,0x8c02,
0x6c02,0x4c02,0x2c02,0x0c02,0xec01,0xcc01,0xac01,0x8c01,
0x7401,0x6401,0x5401,0x4401,0x3401,0x2401,0x1401,0x0401,
0xf400,0xe400,0xd400,0xc400,0xb400,0xa400,0x9400,0x8400,
0x7800,0x7000,0x6800,0x6000,0x5800,0x5000,0x4800,0x4000,
0x3800,0x3000,0x2800,0x2000,0x1800,0x1000,0x0800,0x0000,
};
private int[] table=null;
public DecompressInputStream(InputStream in, boolean useALaw)throws IOException{
super(in);
table=(useALaw)?alawtable:ulawtable;
}
public int read()throws IOException{
throw new IOException(getClass().getName()+".read() :\n\tDo not support simple read().");
}
public int read(byte[] b)throws IOException{
return read(b,0,b.length);
}
public int read(byte[] b, int off, int len)throws IOException{
byte[] inb;
int value;
inb=new byte[len>>1]; // get A-Law or u-Law bytes
len=in.read(inb);
if(len==-1){return -1;};
for(int i=0;i<len;i++){
value = table[inb[i]&0x00FF];
b[off++]=(byte)((value>>8)&0x00FF); // little-endian
b[off++]=(byte)(value&0x00FF);
}
return len<<1;
}
}

231
src/bass/G711.java Normal file
View File

@@ -0,0 +1,231 @@
package bass;
/** G.711 codec.
* This class provides methods for u-law, A-law and linear PCM conversions.
*/
public class G711 {
static final int SIGN_BIT=0x80; // Sign bit for a A-law byte.
static final int QUANT_MASK=0xf; // Quantization field mask.
static final int NSEGS=8; // Number of A-law segments.
static final int SEG_SHIFT=4; // Left shift for segment number.
static final int SEG_MASK=0x70; // Segment field mask.
static final int[] seg_end={ 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF };
// copy from CCITT G.711 specifications
/** u- to A-law conversions */
static final int[] _u2a={
1, 1, 2, 2, 3, 3, 4, 4,
5, 5, 6, 6, 7, 7, 8, 8,
9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24,
25, 27, 29, 31, 33, 34, 35, 36,
37, 38, 39, 40, 41, 42, 43, 44,
46, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 62,
64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79,
81, 82, 83, 84, 85, 86, 87, 88,
89, 90, 91, 92, 93, 94, 95, 96,
97, 98, 99, 100, 101, 102, 103, 104,
105, 106, 107, 108, 109, 110, 111, 112,
113, 114, 115, 116, 117, 118, 119, 120,
121, 122, 123, 124, 125, 126, 127, 128 };
/** A- to u-law conversions */
static final int[] _a2u={
1, 3, 5, 7, 9, 11, 13, 15,
16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31,
32, 32, 33, 33, 34, 34, 35, 35,
36, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 47, 48, 48, 49, 49,
50, 51, 52, 53, 54, 55, 56, 57,
58, 59, 60, 61, 62, 63, 64, 64,
65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79, 79,
80, 81, 82, 83, 84, 85, 86, 87,
88, 89, 90, 91, 92, 93, 94, 95,
96, 97, 98, 99, 100, 101, 102, 103,
104, 105, 106, 107, 108, 109, 110, 111,
112, 113, 114, 115, 116, 117, 118, 119,
120, 121, 122, 123, 124, 125, 126, 127 };
static int search(int val, int[] table)
{ for (int i=0; i<table.length; i++) if (val<=table[i]) return i;
return table.length;
}
/** Converts a 16-bit linear PCM value to 8-bit A-law.
*
* It accepts an 16-bit integer and encodes it as A-law data.
*
* Linear Input Code Compressed Code
* ----------------- ---------------
* 0000000wxyza 000wxyz
* 0000001wxyza 001wxyz
* 000001wxyzab 010wxyz
* 00001wxyzabc 011wxyz
* 0001wxyzabcd 100wxyz
* 001wxyzabcde 101wxyz
* 01wxyzabcdef 110wxyz
* 1wxyzabcdefg 111wxyz
*
* For further information see John C. Bellamy's Digital Telephony, 1982,
* John Wiley & Sons, pps 98-111 and 472-476.
*/
public static int linear2alaw(int pcm_val) // 2's complement (16-bit range)
{ int mask;
int seg;
//unsigned char aval;
int aval;
if (pcm_val>=0)
{ mask=0xD5; // sign (7th) bit = 1
}
else
{ mask=0x55; // sign bit = 0
pcm_val=-pcm_val-8;
}
// Convert the scaled magnitude to segment number.
seg=search(pcm_val,seg_end);
// Combine the sign, segment, and quantization bits.
if (seg>=8) // out of range, return maximum value.
return (0x7F^mask);
else
{ aval=seg<<SEG_SHIFT;
if (seg<2) aval|=(pcm_val>>4)&QUANT_MASK;
else aval|=(pcm_val>>(seg+3))&QUANT_MASK;
return (aval^mask);
}
}
/** Converts an A-law value to 16-bit linear PCM
*/
//public static int alaw2linear(unsigned char a_val)
public static int alaw2linear(int a_val)
{ int t;
int seg;
a_val^=0x55;
t=(a_val&QUANT_MASK)<<4;
//seg=((unsigned)a_val&SEG_MASK)>>SEG_SHIFT;
seg=(a_val&SEG_MASK)>>SEG_SHIFT;
switch (seg)
{ case 0:
t+=8;
break;
case 1:
t+=0x108;
break;
default:
t+=0x108;
t<<=seg-1;
}
return ((a_val&SIGN_BIT)!=0)? t : -t;
}
/** Bias for linear code. */
public static final int BIAS=0x84;
/** Converts a linear PCM value to u-law
*
* In order to simplify the encoding process, the original linear magnitude
* is biased by adding 33 which shifts the encoding range from (0 - 8158) to
* (33 - 8191). The result can be seen in the following encoding table:
*
* Biased Linear Input Code Compressed Code
* ------------------------ ---------------
* 00000001wxyza 000wxyz
* 0000001wxyzab 001wxyz
* 000001wxyzabc 010wxyz
* 00001wxyzabcd 011wxyz
* 0001wxyzabcde 100wxyz
* 001wxyzabcdef 101wxyz
* 01wxyzabcdefg 110wxyz
* 1wxyzabcdefgh 111wxyz
*
* Each biased linear code has a leading 1 which identifies the segment
* number. The value of the segment number is equal to 7 minus the number
* of leading 0's. The quantization interval is directly available as the
* four bits wxyz. The trailing bits (a - h) are ignored.
*
* Ordinarily the complement of the resulting code word is used for
* transmission, and so the code word is complemented before it is returned.
*
* For further information see John C. Bellamy's Digital Telephony, 1982,
* John Wiley & Sons, pps 98-111 and 472-476.
*/
public static int linear2ulaw(int pcm_val) // 2's complement (16-bit range)
{ int mask;
int seg;
//unsigned char uval;
int uval;
// Get the sign and the magnitude of the value.
if (pcm_val<0)
{ pcm_val=BIAS-pcm_val;
mask=0x7F;
}
else
{ pcm_val+=BIAS;
mask=0xFF;
}
// Convert the scaled magnitude to segment number.
seg=search(pcm_val,seg_end);
// Combine the sign, segment, quantization bits; and complement the code word.
if (seg>=8) return (0x7F^mask); // out of range, return maximum value.
else
{ uval=(seg<<4) | ((pcm_val>>(seg+3)) & 0xF);
return (uval^mask);
}
}
/** ConvertS a u-law value to 16-bit linear PCM.
*
* First, a biased linear code is derived from the code word. An unbiased
* output can then be obtained by subtracting 33 from the biased code.
*
* Note that this function expects to be passed the complement of the
* original code word. This is in keeping with ISDN conventions.
*/
//public static int ulaw2linear(unsigned char u_val)
public static int ulaw2linear(int u_val)
{ int t;
// Complement to obtain normal u-law value.
u_val=~u_val;
// Extract and bias the quantization bits. Then shift up by the segment number and subtract out the bias.
t=((u_val&QUANT_MASK)<<3) + BIAS;
//t<<=((unsigned)u_val&SEG_MASK)>>SEG_SHIFT;
t<<=(u_val&SEG_MASK)>>SEG_SHIFT;
return ((u_val&SIGN_BIT)!=0)? (BIAS-t) : (t-BIAS);
}
/** A-law to u-law conversion.
*/
//public static int alaw2ulaw(unsigned char aval)
public static int alaw2ulaw(int aval)
{ aval&=0xff;
return ((aval & 0x80)!=0)? (0xFF^_a2u[aval^0xD5]) : (0x7F^_a2u[aval^0x55]);
}
/** u-law to A-law conversion.
*/
//public static int ulaw2alaw(unsigned char uval)
public static int ulaw2alaw(int uval)
{ uval&=0xff;
return ((uval&0x80)!=0)? (0xD5^(_u2a[0xFF^uval]-1)) : (0x55^(_u2a[0x7F^uval]-1));
}
}

970
src/bass/JSIPAudio.java Normal file
View File

@@ -0,0 +1,970 @@
package bass;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Stream;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.keywords.Bit;
import anywheresoftware.b4a.keywords.DateTime;
import bass.BassLibrary.BASS_DEVICEINFO;
import bass.BassLibrary.FloatValue;
import bass.BassLibrary.RECORDPROC;
import code.common;
/**
* SIP Audio Converter
* @author rdkartono
*
*/
@BA.ShortName("JSIPAudio")
@BA.Events(values= {
"log(msg as string)",
"fileconvertfinish(sourcefile as string, targetfile as string)",
"filestreaming(filename as string, handle as int, data() as byte)",
"inputstreaming(inputname as string, handle as int, data() as byte)"
})
public class JSIPAudio {
public JSIPAudio() {
Me = this;
currentfolder = new File("").getAbsolutePath();
inited_playback = new HashSet<Integer>();
inited_recorder = new HashSet<Integer>();
loaded_plugin = new HashSet<Integer>();
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
System.out.println("JSIPAudio ShutdownHook called");
inited_recorder.forEach(recdev->{
if (BassLibrary.BASS.BASS_RecordSetDevice(recdev)) {
if (BassLibrary.BASS.BASS_RecordFree()) {
System.out.println("Recorder Device="+recdev+" freed");
} else System.out.println(BassLibrary.BASS.GetBassError("BASS_RecordFree"));
} else System.out.println(BassLibrary.BASS.GetBassError("BASS_RecordSetDevice"));
});
inited_recorder.clear();
inited_playback.forEach(playdev ->{
if (BassLibrary.BASS.BASS_SetDevice(playdev)) {
if (BassLibrary.BASS.BASS_Free()) {
System.out.println("Playback Device="+playdev+" freed");
} else System.out.println(BassLibrary.BASS.GetBassError("BASS_Free"));
} else System.out.println(BassLibrary.BASS.GetBassError("BASS_SetDevice"));
});
inited_playback.clear();
loaded_plugin.forEach(ph ->{
if (BassLibrary.BASS.BASS_PluginFree(ph)) {
System.out.println("Plugin freed");
} else System.out.println(BassLibrary.BASS.GetBassError("BASS_PluginFree"));
});
loaded_plugin.clear();
}
});
}
private final String currentfolder ;
private BA ba;
private String event;
private final Object Me;
private boolean need_log_event = false;
private boolean inited = false;
private final ExecutorService exec = Executors.newCachedThreadPool();
private final Set<Integer> inited_playback ;
private final Set<Integer> inited_recorder ;
private final Set<Integer> loaded_plugin;
public ULaw ULaw;
public PCM PCM16;
public PCM LPCM24;
public AAC AAC;
/**
* Initialize JSIPAudio
* @param eventname Event Name
*/
public void Initialize(BA ba, String eventname) {
this.ba = ba;
this.event = eventname;
check_b4x_events();
extract_libraries();
ULaw = new ULaw(16,1);
PCM16 = new PCM(16,1);
AAC = new AAC(16,1);
LPCM24 = new PCM(24,1);
inited = true;
}
/**
* Check for B4X events requirement
*/
private void check_b4x_events() {
if (this.ba!=null) {
if (common.ValidString(this.event)) {
need_log_event = this.ba.subExists(event+"_log");
}
}
}
private void extract_libraries() {
raise_log(common.extract_library(currentfolder, "bass"));
raise_log(common.extract_library(currentfolder, "bassmix"));
raise_log(common.extract_library(currentfolder, "bass_aac"));
raise_log(common.extract_library(currentfolder, "bassenc"));
raise_log(common.extract_library(currentfolder, "bassenc_aac"));
raise_log("Bass Version = "+Bit.ToHexString(BassLibrary.BASS.BASS_GetVersion()));
raise_log("BassMix Version = "+Bit.ToHexString(BassMixLibrary.BASSMIX.BASS_Mixer_GetVersion()));
raise_log("BassEnc Version = "+Bit.ToHexString(BassEncLibrary.BASSENC.BASS_Encode_GetVersion()));
raise_log("BassEnc_AAC Version = "+Bit.ToHexString(BassEncAACLibrary.BASSENC_AAC.BASS_Encode_AAC_GetVersion()));
int pluginhandle = BassLibrary.BASS.BASS_PluginLoad("bass_aac", 0);
if (pluginhandle!=0) {
loaded_plugin.add(pluginhandle);
raise_log("Plugin AAC Loaded");
} else raise_log(BassLibrary.BASS.GetBassError("BASS_PluginLoad"));
}
/**
* Set Channel Volume
* @param handle handle value
* @param value 0 - 100
*/
public void SetChannelVolume(int handle, int value) {
if (value<0) value = 0;
if (value>100) value = 100;
if (handle!=0) {
if (!BassLibrary.BASS.BASS_ChannelSetAttribute(handle, BassLibrary.Constant.BASS_ATTRIB_VOL, value/100.0f)) {
raise_log(BassLibrary.BASS.GetBassError("BASS_ChannelSetAttribute"));
}
}
}
/**
* Get Channel Volume
* @param handle handle value
* @return -1 if failed, 0 - 100 if success
*/
public int GetChannelVolume(int handle) {
if (handle!=0) {
FloatValue val = new FloatValue();
if (BassLibrary.BASS.BASS_ChannelGetAttribute(handle, BassLibrary.Constant.BASS_ATTRIB_VOL, val)) {
int intval = (int)(val.value*100);
if (intval<0) intval = 0;
if (intval>100) intval = 100;
return intval;
}
}
return -1;
}
/**
* Set Master Volume for specific Playback Device
* @param devid start from 1. No-sound cant be controlled
* @param value 0 - 100
* @return true if SetVolume success
*/
public boolean SetMasterVolume(int devid, int value) {
if (value<0) value = 0;
if (value>100) value = 100;
if (devid>0) {
if (BassLibrary.BASS.BASS_SetDevice(devid)) {
if (BassLibrary.BASS.BASS_SetVolume(value/100.0f)) {
return true;
} else raise_log(BassLibrary.BASS.GetBassError("BASS_SetVolume"));
} else raise_log(BassLibrary.BASS.GetBassError("BASS_SetDevice"));
}
return false;
}
/**
* Get Master Volume for specific Playback Device
* @param devid start from 1
* @return -1 if GetVolume failed
*/
public int GetMasterVolume(int devid) {
if (devid>0) {
if (BassLibrary.BASS.BASS_SetDevice(devid)) {
float val = BassLibrary.BASS.BASS_GetVolume();
if (val>=0) {
int result = (int)(val * 100);
if (result>100) result = 100;
return result;
} else raise_log(BassLibrary.BASS.GetBassError("BASS_GetVolume"));
} else raise_log(BassLibrary.BASS.GetBassError("BASS_SetDevice"));
}
return -1;
}
/**
* Set Record Volume
* @param devid start from 0
* @param value 0 - 100
* @return true if success
*/
public boolean SetRecordVolume(int devid, int value) {
// value negative = leave current
// value = 0 = silent
// value = 100 = max
if (value<-1) value = -1;
if (value>100) value = 100;
if (BassLibrary.BASS.BASS_RecordSetInput(devid, BassLibrary.Constant.BASS_INPUT_ON, value/100.0f)) {
return true;
} else {
raise_log(BassLibrary.BASS.GetBassError("BASS_RecordSetInput"));
return false;
}
}
/**
* Get Record Volume
* @param devid start from 0
* @return -1 if failed, 0 - 100 if success
*/
public int GetRecordVolume(int devid) {
FloatValue val = new FloatValue();
int result = BassLibrary.BASS.BASS_RecordGetInput(devid, val);
if (result==-1) {
// error
raise_log(BassLibrary.BASS.GetBassError("BASS_RecordGetInput"));
return -1;
} else {
int intval = (int)(val.value * 100);
return intval;
}
}
/**
* Check if already initialized
* @return true if initialized
*/
public boolean IsInitialized() {
return inited;
}
/**
* Get all Playback Devices
* @return array of BASS_DEVICEINFO
*/
public BASS_DEVICEINFO[] GetPlaybackDevices() {
int ii = 0;
boolean success = true;
List<BASS_DEVICEINFO> result = new ArrayList<BASS_DEVICEINFO>();
do {
BASS_DEVICEINFO dev = new BASS_DEVICEINFO();
success = BassLibrary.BASS.BASS_GetDeviceInfo(ii, dev);
if (success) {
ii++;
result.add(dev);
}
} while(success);
return result.toArray(new BASS_DEVICEINFO[0]);
}
/**
* Get All Recorder Devices
* @return array of BASS_DEVICEINFO
*/
public BASS_DEVICEINFO[] GetRecorderDevices() {
int ii = 0;
boolean success = true;
List<BASS_DEVICEINFO> result = new ArrayList<BASS_DEVICEINFO>();
do {
BASS_DEVICEINFO dev = new BASS_DEVICEINFO();
success = BassLibrary.BASS.BASS_RecordGetDeviceInfo(ii, dev);
if (success) {
ii++;
result.add(dev);
}
} while(success);
return result.toArray(new BASS_DEVICEINFO[0]);
}
/**
* Get Playback Device Info
* @param devid 0 = no sound, 1 = first device
* @return BASS_DEVICEINFO object, or null if failed
*/
public BASS_DEVICEINFO GetPlaybackDeviceInformation(int devid) {
BASS_DEVICEINFO dev = new BASS_DEVICEINFO();
if (BassLibrary.BASS.BASS_GetDeviceInfo(devid, dev)) {
return dev;
} else raise_log(BassLibrary.BASS.GetBassError("BASS_GetDeviceInfo"));
return null;
}
/**
* Get Recorder Device Info
* @param devid 0 = first device
* @return BASS_DEVICEINFO object, or null if failed
*/
public BASS_DEVICEINFO GetRecorderDeviceInformation(int devid) {
BASS_DEVICEINFO dev = new BASS_DEVICEINFO();
if (BassLibrary.BASS.BASS_RecordGetDeviceInfo(devid, dev)) {
return dev;
} else raise_log(BassLibrary.BASS.GetBassError("BASS_RecordGetDeviceInfo"));
return null;
}
private void raise_log(String msg) {
if (need_log_event) {
ba.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg});
} else {
System.out.println(msg);
}
}
public class ULaw extends basicfunctions{
public ULaw(int bits, int channel) {
super(48000, BassLibrary.Constant.BASS_DEVICE_MONO | BassLibrary.Constant.BASS_DEVICE_FREQ);
this.channel = channel;
this.bits = bits;
this.need_fileconvert_event = (ba!=null) ? ba.subExists(fileconvert_event) : false;
this.need_filestreaming_event = (ba!=null) ? ba.subExists(filestreaming_event) : false;
this.need_inputstreaming_event = (ba!=null) ? ba.subExists(inputstreaming_event) : false;
}
public final int[] samplingrate = {8000};
public final int channel;;
public final int bits;
public final String MIME = "audio/basic";
private final boolean need_fileconvert_event;
private final boolean need_filestreaming_event;
private final boolean need_inputstreaming_event;
private final String fileconvert_event = "ulaw_fileconvertfinish";
private final String filestreaming_event = "ulaw_filestreaming";
private final String inputstreaming_event = "ulaw_inputstreaming";
private void raise_fileconvert(String sourcefile, String targetfile) {
if (need_fileconvert_event) ba.raiseEventFromDifferentThread(Me, null, 0, fileconvert_event, false, new Object[] {sourcefile, targetfile});
}
private void raise_filestreaming(String filename, int handle, byte[] data) {
if (need_filestreaming_event) ba.raiseEventFromDifferentThread(Me,null,0,filestreaming_event, false, new Object[] {filename, handle, data});
}
private void raise_inputstreaming(String inputname, int handle, byte[] data) {
if (need_inputstreaming_event) ba.raiseEventFromDifferentThread(Me, null, 0, inputstreaming_event, false, new Object[] {inputname, handle, data});
}
/**
* Convert PCM 16bit Bytes to ULaw
* @param pcm PCM Data
* @return ULaw data
*/
private byte[] ConvertPCMToULaw(byte[] pcm) {
//TODO check di sini, harusnya readcount = pcm.length / 2
// karena PCM 16 bit, ULaw = 8 bit
// tapi gak tau juga, belum cek
int readcount = pcm.length;
byte[] cc = new byte[readcount];
ShortBuffer sb = ByteBuffer.wrap(pcm).asShortBuffer();
ShortBuffer sc = ByteBuffer.wrap(cc).asShortBuffer();
// sampe sini dapat PCM nya , convert ke ULaw
// convert dari PCM to ULaw
while(sb.hasRemaining()) {
sc.put((short)G711.linear2ulaw(sb.get()));
}
return cc;
}
/**
* Convert file from source to target, in G711 U-Law format
* will raise event ulaw_fileconvertfinish(sourcefile , targetfile) when finished
* @param sourcefilename Source File
* @param targetsamplingrate target sampling rate, only 8000 is supported
* @param targetfilename Target File
* @return true if convesion can be started
*/
@Override
public boolean FileConvert(final String sourcefilename, int targetsamplingrate, final String targetfilename) {
targetsamplingrate = samplingrate[0];
OpenFile op = new OpenFile(sourcefilename, 0, targetsamplingrate, true);
if (op.isSuccess()) {
// sampe sini sudah plugged ke Mixer
inited_playback.add(0);
try(FileOutputStream fos = new FileOutputStream(targetfilename)){
exec.submit(()->{
long timestamp = DateTime.getNow();
int totalread = 0;
int readcount = 0;
try {
do {
Pointer ptr = new Memory(1024);
readcount = BassLibrary.BASS.BASS_ChannelGetData(op.getMixerhandle(), ptr, 1024);
if (readcount>0) {
byte[] bb = ptr.getByteArray(0, readcount);
byte[] cc = ConvertPCMToULaw(bb);
totalread+=readcount;
fos.write(cc);
}
} while(readcount>0);
fos.close();
} catch(IOException e) {
raise_log(MessageFormat.format("FileOutputStream {0} IOException={1}", targetfilename, e.getMessage()));
}
// sampe sini selesai
long delta = DateTime.getNow() - timestamp;
raise_log(MessageFormat.format("ULaw FileConvert from {0} to {1} finished, Bytes Read={2}, Conversion took {3} ms", sourcefilename, targetfilename, totalread, delta));
raise_fileconvert(sourcefilename, targetfilename);
});
return true;
} catch(IOException e) {
raise_log("FileOutputStream create exception="+e.getMessage());
return false;
} finally {
String[] closeerror = op.CloseHandles();
Stream.of(closeerror).forEach(err -> raise_log(err));
}
} else {
Stream.of(op.getErrorlist()).forEach(err -> raise_log(err));
Stream.of(op.CloseHandles()).forEach(err-> raise_log(err));
return false;
}
}
/**
* Open File for streaming
* will raise event ulaw_filestreaming
* @param playdevice 0 = no sound, 1 = first audio
* @param sourcefilename Source File to stream
* @param targetsamplingrate target sampling rate, only 8000 is supported
* @return 0 if failed, or other value if success
*/
@Override
public int OpenFileForStreaming(final int playdevice, final String sourcefilename, int targetsamplingrate) {
targetsamplingrate = samplingrate[0];
OpenFile op = new OpenFile(sourcefilename, playdevice, targetsamplingrate);
if (op.isSuccess()) {
inited_playback.add(playdevice);
// sampe sini , channel sudah di add
if (op.StartPlayback()) {
exec.submit(()->{
int dsphandle = BassLibrary.BASS.BASS_ChannelSetDSP(op.getMixerhandle(), (dsp_handle, channel_handle, buffer, length, user)->{
// di sini dapat PCM dari Mixer
if (length>0) {
byte[] data = buffer.getByteArray(0, length);
byte[] ulaw = ConvertPCMToULaw(data);
raise_filestreaming(sourcefilename, op.getFilehandle(), ulaw);
}
}, null, 0);
if (dsphandle!=0) {
// sampe sini , DSP sudah created
long timestamp = DateTime.getNow();
raise_log("Mixer Playback for "+sourcefilename+" started");
// tinggal loop aja sampe selesai
do {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
raise_log("Interrupt Exception="+e.getMessage());
}
} while(BassLibrary.BASS.BASS_ChannelIsActive(op.getFilehandle())==BassLibrary.Constant.BASS_ACTIVE_PLAYING);
// finish playback !!
long delta = DateTime.getNow() - timestamp;
raise_log("Mixer Playback for "+sourcefilename+" finished, duration="+delta+" ms");
} else raise_log(BassLibrary.BASS.GetBassError("BASS_ChannelSetDSP"));
// sampe sini selesai semua
Stream.of(op.CloseHandles()).forEach(err->raise_log(err));
});
return op.getFilehandle();
} else {
raise_log(BassLibrary.BASS.GetBassError("BASS_ChannelStart"));
Stream.of(op.CloseHandles()).forEach(err-> raise_log(err));
return 0;
}
} else {
Stream.of(op.getErrorlist()).forEach(err-> raise_log(err));
Stream.of(op.CloseHandles()).forEach(err-> raise_log(err));
return 0;
}
}
/**
* Open Input for streaming
* will raise event ulaw_inputstreaming
* @param recdevice 0 = first recording device
* @param targetsamplingrate target sampling rate, only 8000 is supported
* @return 0 if failed, other value if success
*/
@Override
public int OpenInputForStreaming(final int recdevice, int targetsamplingrate) {
// default ke 8000
targetsamplingrate = samplingrate[0];
OpenInput oi = new OpenInput(recdevice, targetsamplingrate, new RECORDPROC() {
@Override
public boolean RECORDPROC(int handle, Pointer buffer, int length, Pointer user) {
byte[] data = buffer.getByteArray(0,length);
byte[] ulaw = ConvertPCMToULaw(data);
// inputname ada di Pointer user
raise_inputstreaming(user==null ? "": user.getString(0), handle, ulaw);
return true; // terusin RecordProc
}
});
if (oi.isSuccess()) {
inited_recorder.add(oi.getRecorderdev());
String[] errorstart = oi.StartRecord();
if (errorstart.length>0) {
// ada error
Stream.of(oi.getErrorlist()).forEach(err->raise_log(err));
Stream.of(oi.StopRecord()).forEach(err->raise_log(err));
return 0;
} else return oi.getRecordhandle(); // aman
} else {
Stream.of(oi.getErrorlist()).forEach(err->raise_log(err));
Stream.of(oi.StopRecord()).forEach(err->raise_log(err));
return 0;
}
}
}
public class PCM extends basicfunctions{
public PCM(int bits, int channel) {
super(48000, BassLibrary.Constant.BASS_DEVICE_MONO | BassLibrary.Constant.BASS_DEVICE_FREQ);
this.channel = channel;
this.bits = bits;
this.MIME = bits==24 ? "audio/L24" : "audio/L16";
this.fileconvert_event = (bits==24 ? "pcm24" : "pcm16")+"_fileconvertfinish";
this.filestreaming_event = (bits==24 ? "pcm24" : "pcm16")+"_filestreaming";
this.inputstreaming_event = (bits==24 ? "pcm24" : "pcm16")+"_inputstreaming";
this.need_fileconvert_event = (ba!=null) ? ba.subExists(fileconvert_event) : false;
this.need_filestreaming_event = (ba!=null) ? ba.subExists(filestreaming_event) : false;
this.need_inputstreaming_event = (ba!=null) ? ba.subExists(inputstreaming_event) : false;
}
public final int[] samplingrate = {16000, 32000, 44100, 48000};
public final int channel;
public final int bits;
public final String MIME;
private final boolean need_fileconvert_event;
private final boolean need_filestreaming_event;
private final boolean need_inputstreaming_event;
private final String fileconvert_event;
private final String filestreaming_event;
private final String inputstreaming_event;
private void raise_fileconvert(String sourcefile, String targetfile) {
if (need_fileconvert_event) ba.raiseEventFromDifferentThread(Me, null, 0, fileconvert_event, false, new Object[] {sourcefile, targetfile});
}
private void raise_filestreaming(String filename, int handle, byte[] data) {
if (need_filestreaming_event) ba.raiseEventFromDifferentThread(Me,null,0,filestreaming_event, false, new Object[] {filename, handle, data});
}
private void raise_inputstreaming(String inputname, int handle, byte[] data) {
if (need_inputstreaming_event) ba.raiseEventFromDifferentThread(Me, null, 0, inputstreaming_event, false, new Object[] {inputname, handle, data});
}
private byte[] Convert_PCM16_to_PCM24(byte[] data) {
// TODO belum pernah dicek
int length16 = data.length; // length data dalam 16 bit
int length = length16 / 2; // jumlah samples nya, yaitu 16bit dibagi 2
int length24 = length * 3; // jumlah data dengan samples yang sama, tapi 24 bit
byte[] result = new byte[length24];
int jj = 0;
for (int ii=0;ii<length;ii++) {
result[jj++] = data[2*ii]; // byte pertama
result[jj++] = data[(2*ii)+1]; // byte kedua
result[jj++] = 0; // byte ketiga di skip
}
return result;
}
@Override
public boolean FileConvert(String sourcefilename, final int targetsamplingrate, String targetfilename) {
final int samplingfreq = Arrays.stream(samplingrate).filter(xx -> xx==targetsamplingrate).findAny().orElse(samplingrate[0]);
OpenFile op = new OpenFile(sourcefilename, 0, samplingfreq, true);
if (op.isSuccess()) {
inited_playback.add(0);
// sampe sini sudah plugged ke Mixer
try(FileOutputStream fos = new FileOutputStream(targetfilename)){
exec.submit(()->{
long timestamp = DateTime.getNow();
int totalread = 0;
int readcount = 0;
do {
Pointer ptr = new Memory(1024);
readcount = BassLibrary.BASS.BASS_ChannelGetData(op.getMixerhandle(), ptr, 1024);
if (readcount>0) {
// ada data
byte[] bb = ptr.getByteArray(0, readcount);
// tulis ke FileOutputStream
try {
if (bits==24) {
byte[] cc = Convert_PCM16_to_PCM24(bb);
fos.write(cc);
totalread+=cc.length;
} else {
fos.write(bb);
totalread+=bb.length;
}
} catch (IOException e) {
raise_log("FileOutputStream write exception="+e.getMessage());
break;
}
}
} while(readcount>0);
// close FileOutputStream
try {
fos.close();
} catch (IOException e) {
raise_log("FileOutputStream close exception="+e.getMessage());
}
Stream.of(op.CloseHandles()).forEach(err-> raise_log(err));
long delta = DateTime.getNow() - timestamp;
raise_log(MessageFormat.format("FileConvert from {0} to {1} finished, Bytes Read={2}, Conversion took {3} ms", sourcefilename, targetfilename, totalread, delta));
raise_fileconvert(sourcefilename, targetfilename);
});
return true;
} catch(IOException e) {
raise_log(MessageFormat.format("FileOutputStream {0} Exception={1}", targetfilename, e.getMessage()));
return false;
} finally {
Stream.of(op.CloseHandles()).forEach(err->raise_log(err));
}
} else {
Stream.of(op.getErrorlist()).forEach(err->raise_log(err));
Stream.of(op.CloseHandles()).forEach(err->raise_log(err));
return false;
}
}
@Override
public int OpenFileForStreaming(int playdevice, final String sourcefilename, int targetsamplingrate) {
final int samplingfreq = Arrays.stream(samplingrate).filter(xx -> xx==targetsamplingrate).findAny().orElse(samplingrate[0]);
OpenFile op = new OpenFile(sourcefilename, playdevice, samplingfreq);
if (op.isSuccess()) {
inited_playback.add(playdevice);
if (op.StartPlayback()) {
final int dsphandle = BassLibrary.BASS.BASS_ChannelSetDSP(op.getMixerhandle(), (dsp_h, ch_h, buffer,length, user)->{
// dsp nya disini
byte[] data = buffer.getByteArray(0, length);
byte[] cc = Convert_PCM16_to_PCM24(data);
raise_filestreaming(sourcefilename, op.getFilehandle(),cc);
}, null, 0);
if (dsphandle==0) {
raise_log(BassLibrary.BASS.GetBassError("BASS_ChannelSetDSP"));
Stream.of(op.CloseHandles()).forEach(err->raise_log(err));
return 0;
}
// sampe sini berhasil
return op.getFilehandle();
}
else {
raise_log(BassLibrary.BASS.GetBassError("BASS_ChannelStart"));
Stream.of(op.CloseHandles()).forEach(err->raise_log(err));
return 0;
}
} else {
Stream.of(op.getErrorlist()).forEach(err->raise_log(err));
Stream.of(op.CloseHandles()).forEach(err->raise_log(err));
return 0;
}
}
@Override
public int OpenInputForStreaming(final int recdevice, final int targetsamplingrate) {
final int samplingfreq = Arrays.stream(samplingrate).filter(xx -> xx==targetsamplingrate).findAny().orElse(samplingrate[0]);
OpenInput oi = new OpenInput(recdevice, samplingfreq, new RECORDPROC() {
@Override
public boolean RECORDPROC(int handle, Pointer buffer, int length, Pointer user) {
byte[] data = buffer.getByteArray(0, length);
String recdevname = user==null ? "" : user.getString(0);
if (bits==24) {
raise_inputstreaming(recdevname, handle, Convert_PCM16_to_PCM24(data));
} else {
raise_inputstreaming(recdevname, handle, data);
}
return true;
}
});
if (oi.isSuccess()) {
inited_recorder.add(oi.getRecorderdev());
return oi.getRecordhandle();
} else {
Stream.of(oi.getErrorlist()).forEach(err->raise_log(err));
Stream.of(oi.StopRecord()).forEach(err->raise_log(err));
return 0;
}
}
}
public class AAC extends basicfunctions{
public AAC(int bits, int channel) {
super(48000, BassLibrary.Constant.BASS_DEVICE_MONO | BassLibrary.Constant.BASS_DEVICE_FREQ);
this.bits = bits;
this.channel = channel;
this.need_fileconvert_event = (ba!=null) ? ba.subExists(fileconvert_event) : false;
this.need_filestreaming_event = (ba!=null) ? ba.subExists(filestreaming_event) : false;
this.need_inputstreaming_event = (ba!=null) ? ba.subExists(inputstreaming_event) : false;
}
public final int[] samplingrate = {8000, 16000, 32000, 44100, 48000};
public final int bits;
public final int channel;
public final String MIME = "audio/mpeg4-generic";
private final boolean need_fileconvert_event;
private final boolean need_filestreaming_event;
private final boolean need_inputstreaming_event;
private final String fileconvert_event = "aac_fileconvertfinish";
private final String filestreaming_event = "aac_filestreaming";
private final String inputstreaming_event = "aac_inputstreaming";
private void raise_fileconvert(String sourcefile, String targetfile) {
if (need_fileconvert_event) ba.raiseEventFromDifferentThread(Me, null, 0, fileconvert_event, false, new Object[] {sourcefile, targetfile});
}
private void raise_filestreaming(String filename, int handle, byte[] data) {
if (need_filestreaming_event) ba.raiseEventFromDifferentThread(Me,null,0,filestreaming_event, false, new Object[] {filename, handle, data});
}
private void raise_inputstreaming(String inputname, int handle, byte[] data) {
if (need_inputstreaming_event) ba.raiseEventFromDifferentThread(Me, null, 0, inputstreaming_event, false, new Object[] {inputname, handle, data});
}
@Override
public boolean FileConvert(String sourcefilename, final int targetsamplingrate, String targetfilename) {
final int samplingfreq = Arrays.stream(samplingrate).filter(xx->xx==targetsamplingrate).findAny().orElse(samplingrate[0]);
OpenFile op = new OpenFile(sourcefilename, 0, samplingfreq);
if (op.isSuccess()) {
inited_playback.add(0);
// sampe sini sudah plugged ke Mixer
// create encoder handle dari mixer handle
final int encodehandle = BassEncAACLibrary.BASSENC_AAC.BASS_Encode_AAC_StartFile(op.getMixerhandle(), null, 0, targetfilename);
if (encodehandle==0) {
// gak bisa create encoder
raise_log(BassLibrary.BASS.GetBassError("BASS_Encode_AAC_StartFile"));
// free handle
Stream.of(op.CloseHandles()).forEach(err->raise_log(err));
return false;
}
// sampe sini encoder jalan
exec.submit(()->{
do {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
raise_log("InterruptException");
}
} while (BassLibrary.BASS.BASS_ChannelIsActive(op.getMixerhandle())==BassLibrary.Constant.BASS_ACTIVE_PLAYING);
// free encoder
BassEncLibrary.BASSENC.BASS_Encode_Stop(encodehandle);
// free handle
Stream.of(op.CloseHandles()).forEach(err->raise_log(err));
raise_fileconvert(sourcefilename, targetfilename);
});
return true;
} else {
Stream.of(op.getErrorlist()).forEach(err->raise_log(err));
Stream.of(op.CloseHandles()).forEach(err->raise_log(err));
return false;
}
}
@Override
public int OpenFileForStreaming(int playdevice, final String sourcefilename, int targetsamplingrate) {
// cari sampling rate yang supported. kalau gak ketemu, pilih default
final int samplingfreq = Arrays.stream(samplingrate)
.filter(xx -> xx == targetsamplingrate)
.findFirst()
.orElse(samplingrate[0]);
OpenFile op = new OpenFile(sourcefilename, playdevice, samplingfreq);
if (op.isSuccess()) {
inited_playback.add(playdevice);
// sampe sini sudah plugged ke Mixer
if (op.StartPlayback()) {
final int encodehandle = BassEncAACLibrary.BASSENC_AAC.BASS_Encode_AAC_Start(op.getMixerhandle(), null, 0,
(encode_handle, source_handle, buffer, length, offset, user)->{
raise_filestreaming(sourcefilename, op.getFilehandle(), buffer.getByteArray(0,length));
}
, null);
if (encodehandle==0) {
raise_log(BassLibrary.BASS.GetBassError("BASS_Encode_AAC_Start"));
// free handle
Stream.of(op.CloseHandles()).forEach(err->raise_log(err));
return 0;
}
exec.submit(()->{
do {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
raise_log("InterruptException");
}
} while(BassLibrary.BASS.BASS_ChannelIsActive(op.getMixerhandle())==BassLibrary.Constant.BASS_ACTIVE_PLAYING);
BassEncLibrary.BASSENC.BASS_Encode_Stop(encodehandle);
// free handle
Stream.of(op.CloseHandles()).forEach(err->raise_log(err));
});
return op.getFilehandle();
} else {
raise_log(BassLibrary.BASS.GetBassError("BASS_ChannelStart"));
Stream.of(op.CloseHandles()).forEach(err->raise_log(err));
return 0;
}
} else {
Stream.of(op.getErrorlist()).forEach(err->raise_log(err));
Stream.of(op.CloseHandles()).forEach(err->raise_log(err));
return 0;
}
}
@Override
public int OpenInputForStreaming(int recdevice, final int targetsamplingrate) {
final int samplingfreq = Arrays.stream(samplingrate).filter(xx -> xx==targetsamplingrate).findAny().orElse(samplingrate[0]);
OpenInput oi = new OpenInput(recdevice, samplingfreq,null);
if (oi.isSuccess()) {
inited_recorder.add(oi.getRecorderdev());
// sampe sini bisa recording
final int encodehandle = BassEncAACLibrary.BASSENC_AAC.BASS_Encode_AAC_Start(oi.getRecordhandle(), null, 0, (encode_handle, source_handle, buffer, length, offset, user)->{
byte[] data = buffer.getByteArray(0, length);
raise_inputstreaming(oi.getDeviceName(), source_handle, data);
}, null);
if (encodehandle!=0) {
return oi.getRecordhandle();
} else {
raise_log(BassLibrary.BASS.GetBassError("BASS_Encode_AAC_Start"));
Stream.of(oi.StopRecord()).forEach(err->raise_log(err));
}
} else {
Stream.of(oi.getErrorlist()).forEach(err->raise_log(err));
Stream.of(oi.StopRecord()).forEach(err->raise_log(err));
}
return 0;
}
}
}

View File

@@ -0,0 +1,350 @@
package bass;
import java.util.ArrayList;
import java.util.List;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import bass.BassLibrary.BASS_DEVICEINFO;
import bass.BassLibrary.RECORDPROC;
import lombok.Getter;
public abstract class basicfunctions {
abstract public boolean FileConvert(String sourcefilename, int targetsamplingrate, String targetfilename);
abstract public int OpenFileForStreaming(int playdevice, String sourcefilename, int targetsamplingrate);
abstract public int OpenInputForStreaming(int recdevice, int targetsamplingrate);
//used in BASS_Init
protected final int devinit_freq;
protected final int devinit_flag;
public basicfunctions(int init_freq, int init_flag) {
this.devinit_freq = init_freq;
this.devinit_flag = init_flag;
}
/**
* Stop/Close Input For Streaming
* @param rechandle
* @return true if Input can be stopped
*/
public boolean CloseInputForStreaming(int rechandle) {
if (BassLibrary.BASS.BASS_ChannelFree(rechandle)) {
return true;
} else {
return false;
}
}
/**
* BASS_Init dengan devinit_freq dan devinit_flag
* @param device playback device start from 0=no sound, 1=first real device
* @return true if playback device inited or already inited
*/
private boolean BassInit(int device) {
if (!BassLibrary.BASS.BASS_Init(device, devinit_freq, devinit_flag)) {
// ada gagal init, cek dulu
int err = BassLibrary.BASS.BASS_ErrorGetCode();
if (err!=BassLibrary.Constant.BASS_ERROR_ALREADY) {
// gak boleh gagal
//raise_log(BassLibrary.BASS.GetBassError("BASS_Init", err));
return false;
}
}
// sampe sini sukses
return true;
}
/**
* BASS_RecordInit
* @param device recorder device, start from 0=first real device
* @return true if recording device inited or already inited
*/
private boolean RecordInit(int device) {
if (!BassLibrary.BASS.BASS_RecordInit(device)) {
// ada gagal init, cek dulu
int err = BassLibrary.BASS.BASS_ErrorGetCode();
if (err!=BassLibrary.Constant.BASS_ERROR_ALREADY) {
// gak boleh gagal
//raise_log(BassLibrary.BASS.GetBassError("BASS_RecordInit", err));
return false;
}
}
return true;
}
/**
* OpenInput helper
* @author rdkartono
*
*/
protected class OpenInput{
private final int recorderdev;
private final @Getter int samplingrate;
private boolean success = false;
private String[] errorlist;
private int recordhandle=0;
private final RECORDPROC recproc;
private @Getter BASS_DEVICEINFO devinfo;
public String[] getErrorlist() {
return errorlist;
}
public int getRecorderdev() {
return recorderdev;
}
public int getRecordhandle() {
return recordhandle;
}
public boolean isSuccess() {
return success;
}
/**
* Open Input
* @param dev 0 = real device
* @param samplingrate sampling rate
* @param proc RECORDPROC to receive PCM data
*/
public OpenInput(int dev, int samplingrate, RECORDPROC proc) {
this.recorderdev = dev;
this.samplingrate = samplingrate;
this.recproc = proc;
errorlist = OpenSequence();
}
private String[] OpenSequence() {
List<String> error = new ArrayList<String>();
if (RecordInit(recorderdev)) {
devinfo = new BASS_DEVICEINFO();
if (!BassLibrary.BASS.BASS_RecordGetDeviceInfo(recorderdev, devinfo)) devinfo = null;
Pointer user = null;
if (devinfo!=null) {
user = new Memory(devinfo.name.length()+1);
user.setString(0, devinfo.name);
}
recordhandle = BassLibrary.BASS.BASS_RecordStart(samplingrate, 1, BassLibrary.Constant.BASS_RECORD_PAUSE, recproc, user);
if (recordhandle!=0) {
success = true;
} else error.add(BassLibrary.BASS.GetBassError("BASS_RecordStart"));
} else error.add(BassLibrary.BASS.GetBassError("BASS_RecordInit"));
return error.toArray(new String[0]);
}
public Pointer PointerToDeviceName() {
if (devinfo==null)
return null;
else {
Pointer pp = new Memory(devinfo.name.length()+1);
pp.setString(0, devinfo.name);
return pp;
}
}
public String getDeviceName() {
return devinfo==null ? "" : devinfo.name;
}
/**
* Start Recording
* @return error messages if available
*/
public String[] StartRecord() {
List<String> error = new ArrayList<String>();
if (recordhandle!=0) {
if (BassLibrary.BASS.BASS_ChannelStart(recordhandle)) {
// nothing...
} else error.add(BassLibrary.BASS.GetBassError("BASS_ChannelStart"));
} else error.add("RecordHandle is zero");
return error.toArray(new String[0]);
}
/**
* Stop Recording
* @return error messages if available
*/
public String[] StopRecord() {
List<String> error = new ArrayList<String>();
if (recordhandle!=0) {
if (BassLibrary.BASS.BASS_ChannelFree(recordhandle)) {
// nothing...
} else error.add(BassLibrary.BASS.GetBassError("BASS_ChannelStop"));
recordhandle = 0;
} else error.add("RecordHandle is zero");
return error.toArray(new String[0]);
}
}
/**
* OpenFile helper
* @author rdkartono
*
*/
protected class OpenFile{
private final @Getter int playbackdev;
private final @Getter int samplingrate;
private final @Getter String filename;
private final @Getter boolean decodingonly;
private boolean success = false;
private int filehandle;
private int mixerhandle;
private int mixerflag;
private String[] errorlist;
private @Getter long ByteSize;
private @Getter double Duration;
private @Getter BASS_DEVICEINFO devinfo;
public String[] getErrorlist() {
return errorlist;
}
public int getFilehandle() {
return filehandle;
}
public int getMixerhandle() {
return mixerhandle;
}
public boolean isSuccess() {
return success;
}
/**
* Create OpenFile object
* @param filename source file name
* @param playbackdev 0 =no sound, 1 = real device
* @param samplingrate sampling rate
* @param decodingonly if true, mixer will have Decode flag
*/
public OpenFile(String filename, int playbackdev, int samplingrate, boolean decodingonly) {
this.playbackdev = playbackdev;
this.samplingrate = samplingrate;
this.filename = filename;
this.decodingonly = decodingonly;
this.errorlist = OpenSequence();
if (success) {
ByteSize = BassLibrary.BASS.BASS_ChannelGetLength(filehandle, BassLibrary.Constant.BASS_POS_BYTE);
Duration = BassLibrary.BASS.BASS_ChannelBytes2Seconds(filehandle, ByteSize);
}
devinfo = new BASS_DEVICEINFO();
if (!BassLibrary.BASS.BASS_GetDeviceInfo(playbackdev, devinfo)) {
devinfo = null;
}
}
public Pointer PointerToDeviceName() {
if (devinfo==null)
return null;
else {
Pointer pp = new Memory(devinfo.name.length()+1);
pp.setString(0, devinfo.name);
return pp;
}
}
public String getDeviceName() {
return devinfo==null ? "" : devinfo.name;
}
/**
* Create OpenFile Object
* @param filename source file anme
* @param playbackdev 0 = no sound, 1 = real device
* @param samplingrate samplingrate
*/
public OpenFile(String filename, int playbackdev, int samplingrate) {
this(filename, playbackdev, samplingrate, false);
}
private String[] OpenSequence() {
List<String> error = new ArrayList<String>();
if (BassInit(playbackdev)) {
filehandle = BassLibrary.BASS.BASS_StreamCreateFile(filename, 0, 0, BassLibrary.Constant.BASS_STREAM_DECODE);
if (filehandle!=0) {
mixerflag = decodingonly ? BassLibrary.Constant.BASS_STREAM_DECODE : 0;
mixerhandle = BassMixLibrary.BASSMIX.BASS_Mixer_StreamCreate(samplingrate, 1, mixerflag);
if (mixerhandle!=0) {
if (BassMixLibrary.BASSMIX.BASS_Mixer_StreamAddChannel(mixerhandle, filehandle, BassLibrary.Constant.BASS_STREAM_AUTOFREE)) {
success = true;
} else error.add(BassLibrary.BASS.GetBassError("BASS_Mixer_StreamAddChannel"));
} else error.add(BassLibrary.BASS.GetBassError("BASS_Mixer_StreamCreate"));
} else error.add(BassLibrary.BASS.GetBassError("BASS_StreamCreateFile"));
} else error.add(BassLibrary.BASS.GetBassError("Bass_Init"));
return error.toArray(new String[0]);
}
/**
* Start Playback
* @return true if can be started
*/
public boolean StartPlayback() {
if (filehandle!=0) {
// file sudah kebuka
if (mixerhandle!=0) {
// mixer sudah kebuka
if (success) {
// sudah plugged in
if (!decodingonly) {
// bukan channel decoding
return BassLibrary.BASS.BASS_ChannelStart(filehandle);
}
}
}
}
return false;
}
/**
* Close Opened handles
* @return array of string containing error messages
*/
public String[] CloseHandles() {
List<String> error = new ArrayList<String>();
if (filehandle!=0) {
if (success) {
// sudah plug ke mixer
if (!BassMixLibrary.BASSMIX.BASS_Mixer_ChannelRemove(filehandle)) {
error.add(BassLibrary.BASS.GetBassError("BASS_Mixer_ChannelRemove"));
}
}
if (!BassLibrary.BASS.BASS_ChannelFree(filehandle)) {
error.add(BassLibrary.BASS.GetBassError("BASS_ChannelFree"));
}
filehandle = 0;
}
if (mixerhandle!=0) {
if (!BassLibrary.BASS.BASS_ChannelFree(mixerhandle)) {
error.add(BassLibrary.BASS.GetBassError("BASS_ChannelFree"));
}
mixerhandle = 0;
}
return error.toArray(new String[0]);
}
}
}

View File

@@ -0,0 +1,146 @@
package code;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import bass.BassLibrary;
import bass.BassMixLibrary;
import net.sourceforge.peers.Logger;
import net.sourceforge.peers.media.SoundSource;
/**
* Pengganti /AxisAudio/src/net/sourceforge/peers/media/FileReader.java
* karena terbatas codec nya
* @author rdkartono
*
*/
public class BassFileReader implements SoundSource {
private Logger logger;
// Device = 0 --> no sound
private final int bassdev = 0;
// Device BassInit normal 48khz
private final int initfreq = 48000;
// Device BassInit set ke mono, 16 bit
private final int initflag = BassLibrary.Constant.BASS_DEVICE_MONO | BassLibrary.Constant.BASS_DEVICE_16BITS;
// flag ketika open file, BASS_STREAM_DECODE karena akan plug ke mixer
private final int openflag = BassLibrary.Constant.BASS_STREAM_DECODE;
// Mixer frequency untuk convert ke 8khz
private final int mixerfreq = 8000;
// Mixer channel untuk convert ke Mono
private final int mixerchannel = 1;
// Mixer create flag BASS_STREAM_DECODE karena untuk ambil data aja, bukan playback
private final int mixerflag = BassLibrary.Constant.BASS_STREAM_DECODE;
// Mixer_AddChannel flag BAS_STREAM_AUTOFREE , supaya kalau channel habis & selesai, channel free sendiri
//private final int addchannelflag = BassLibrary.Constant.BASS_STREAM_AUTOFREE;
private final int addchannelflag = 0;
// sekali baca data, 256 bytes
private final int BUFFER_SIZE = 256;
private final String filename;
private int filehandle = 0;
private int mixerhandle = 0;
private long filesize = 0;
private double duration = 0;
// indicator if file opened, mixer created, and file plugged to mixer
private boolean channeladded = false;
private BassFileReaderListener listener; // belum kepake
public BassFileReader(final String filename, BassFileReaderListener listener) {
this.filename = filename;
this.listener = listener;
if (BassLibrary.BASS.BASS_GetVersion()!=0) {
if (BassMixLibrary.BASSMIX.BASS_Mixer_GetVersion()!=0) {
boolean initresult = BassLibrary.BASS.BASS_Init(bassdev, initfreq, initflag);
if (initresult) {
System.out.println("BassFileReader Bass_Init device = "+bassdev+" success");
} else System.out.println("BassFileReader BASS_Init error = "+BassLibrary.BASS.GetBassError("BASS_Init"));
filehandle = BassLibrary.BASS.BASS_StreamCreateFile(false, filename, 0, 0, openflag);
if (filehandle!=0){
filesize = BassLibrary.BASS.BASS_ChannelGetLength(filehandle, BassLibrary.Constant.BASS_POS_BYTE);
duration = BassLibrary.BASS.BASS_ChannelBytes2Seconds(filehandle, filesize);
if (listener!=null) listener.fileopened(filename, filesize, duration);
mixerhandle = BassMixLibrary.BASSMIX.BASS_Mixer_StreamCreate(mixerfreq, mixerchannel, mixerflag);
if (mixerhandle!=0) {
if (BassMixLibrary.BASSMIX.BASS_Mixer_StreamAddChannel(mixerhandle, filehandle, addchannelflag)) {
// sequence pertama sukses
if (listener!=null) listener.filereadstart(filename);
channeladded = true;
} else {
logger_error("BassFileReader BASS_Mixer_StreamAddChannel error = "+BassLibrary.BASS.GetBassError("BASS_Mixer_StreamAddChannel"));
close();
}
} else {
logger_error("BassFileReader BASS_Mixer_StreamCreate error = "+BassLibrary.BASS.GetBassError("BASS_Mixer_StreamCreate"));
close();
}
} else logger_error("BassFileReader BASS_StreamCreateFile error = "+BassLibrary.BASS.GetBassError("BASS_StreamCreateFile"));
} else logger_error("BassFileReader BASS_Mixer_GetVersion invalid");
} else logger_error("BassFileReader BASS_GetVersion invalid");
}
public synchronized void close() {
if (mixerhandle!=0) {
if (BassLibrary.BASS.BASS_StreamFree(mixerhandle)) {
logger_info("mixer closed in BassFileReader");
} else logger_error("BassFileReader mixerhandle BASS_StreamFree error = "+BassLibrary.BASS.GetBassError("BASS_StreamFree"));
mixerhandle = 0;
}
if (filehandle!=0) {
if (BassLibrary.BASS.BASS_StreamFree(filehandle)) {
logger_info(filename+" closed in BassFileReader");
} else logger_error("BassFileReader filehandle BASS_StreamFree error = "+BassLibrary.BASS.GetBassError("BASS_StreamFree"));
filehandle = 0;
}
if (listener!=null) listener.fileclosed(filename);
channeladded = false;
}
@Override
public synchronized byte[] readData() {
if (channeladded) {
Pointer pbuf = new Memory(BUFFER_SIZE);
int readsize = BassLibrary.BASS.BASS_ChannelGetData(mixerhandle, pbuf, BUFFER_SIZE);
if (readsize>0) {
byte[] rr = pbuf.getByteArray(0, readsize);
if (listener!=null) listener.filereadresult(filename, readsize, rr);
try {
Thread.sleep(15);
return rr;
} catch (InterruptedException e) {
logger_error("BassFileReader interrupted");
}
} else if (readsize==-1) {
// ada error
logger_error("BassFileReader BASS_ChannelGetData error = "+BassLibrary.BASS.GetBassError("BASS_ChannelGetData"));
} else {
// selesai
if (listener!=null) listener.filereadfinish(filename);
logger_info("BassFileReader BASS_ChannelGetData readsize = "+readsize+", closing it");
close();
}
}
return null;
}
public void AddListener(BassFileReaderListener listener) {
this.listener = listener;
}
private void logger_error(String msg) {
if (logger!=null)
logger.error(msg);
else System.out.println(msg);
}
private void logger_info(String msg) {
if (logger!=null)
logger.info(msg);
else System.out.println(msg);
}
}

View File

@@ -0,0 +1,14 @@
package code;
/**
* Java Event untuk BassFileReader
* @author rdkartono
*
*/
public interface BassFileReaderListener {
public void fileopened(String filename, long filesize, double duration);
public void fileclosed(String filename);
public void filereadresult(String filename, int readlength, byte[] pcmdata);
public void filereadstart(String filename);
public void filereadfinish(String filename);
}

View File

@@ -0,0 +1,271 @@
package code;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import bass.BassLibrary;
import bass.BassLibrary.FloatValue;
import bass.BassLibrary.SYNCPROC;
import net.sourceforge.peers.media.AbstractSoundManager;
/**
* Pengganti /AxisAudio/src/net/sourceforge/peers/javaxsound/JavaxSoundManager.java
* karena terbatas codec nya
* @author rdkartono
*
*/
public class BassSoundManager extends AbstractSoundManager {
private BassSoundManagerListener bsml;
private final int record_device, play_device;
private final int init_freq = 48000;
private final int samplingrate = 8000;
private final int play_initflag = BassLibrary.Constant.BASS_DEVICE_16BITS | BassLibrary.Constant.BASS_DEVICE_MONO;
private final int streamcreate_flag = 0;
private final int recordstart_flag = 0;
private final int read_size = 320;
private int streamhandle, recordhandle;
public BassSoundManager(int outputdevice , int inputdevice, BassSoundManagerListener listener) {
this.bsml = listener;
this.record_device = inputdevice;
this.play_device = outputdevice;
}
public void AddListener(BassSoundManagerListener listener) {
this.bsml = listener;
}
public boolean IsInitialized() {
return (streamhandle!=0) && (recordhandle!=0);
}
public void setInputChannelVolume(int value) {
if (value<0) value = 0;
if (value>100) value = 100;
if (!BassLibrary.BASS.BASS_RecordSetInput(record_device, BassLibrary.Constant.BASS_INPUT_ON, value/100.0f)) {
// gagal
logger_error("BASS_RecordSetInput error = "+BassLibrary.BASS.GetBassError("BASS_RecordSetInput"));
}
}
public int getInputChannelVolume() {
FloatValue vol = new FloatValue();
int code = BassLibrary.BASS.BASS_RecordGetInput(record_device, vol);
if (code==-1) {
logger_error("BASS_RecordGetInput error = "+BassLibrary.BASS.GetBassError("BASS_RecordGetInput"));
return 0;
} else {
int value = (int)(vol.value * 100);
if (value<0) value = 0;
if (value>100) value = 100;
return value;
}
}
public void setOutputChannelVolume(int value) {
if (value<0) value = 0;
if (value>100) value = 100;
if (!BassLibrary.BASS.BASS_SetVolume(value/100.0f)) {
logger_error("BASS_SetVolume error = "+BassLibrary.BASS.GetBassError("BASS_SetVolume"));
}
}
public int getOutputChannelVolume() {
float fl = BassLibrary.BASS.BASS_GetVolume() ;
if (fl==-1) {
logger_error("BASS_GetVolume error = "+BassLibrary.BASS.GetBassError("BASS_GetVolume"));
return 0;
} else {
int value = (int)(fl * 100);
if (value<0) value = 0;
if (value>100) value = 100;
return value;
}
}
@Override
public byte[] readData() {
if (recordhandle!=0) {
int rs = 0;
Pointer buf = new Memory(read_size);
while(true) {
if (BassLibrary.BASS.BASS_ChannelIsActive(recordhandle)!=BassLibrary.Constant.BASS_ACTIVE_PLAYING) break;
rs = BassLibrary.BASS.BASS_ChannelGetData(recordhandle, buf, read_size);
if (rs<0) {
// ada error
if (bsml!=null) bsml.log_error("readData BASS_ChannelGetData error = "+BassLibrary.BASS.GetBassError("BASS_ChannelGetData"));
return null;
} else if (rs>0) {
// ada hasil
byte[] result = buf.getByteArray(0, rs);
if (bsml!=null) bsml.ReadToRTP(rs, result);
return result;
} else {
// nol
try {
Thread.sleep(2);
continue;
} catch (InterruptedException e) {
return null;
}
}
}
}
return null;
}
@Override
public void init() {
if (streamhandle!=0 || recordhandle!=0) close();
if (BassLibrary.BASS.BASS_GetVersion()!=0) {
BassLibrary.BASS.BASS_Init(play_device, init_freq, play_initflag);
BassLibrary.BASS.BASS_RecordInit(record_device);
recordhandle = BassLibrary.BASS.BASS_RecordStart(samplingrate, 1, recordstart_flag,null, null);
if (recordhandle!=0) {
BassLibrary.BASS.BASS_ChannelSetSync(recordhandle, BassLibrary.Constant.BASS_SYNC_DEV_FAIL, 0, devfail, null);
BassLibrary.BASS.BASS_ChannelSetSync(recordhandle, BassLibrary.Constant.BASS_SYNC_STALL, 0, bufferinginformation, null);
if (BassLibrary.BASS.BASS_ChannelStart(recordhandle)) {
logger_info("InputChannel Started");
} else logger_error("BASS_ChannelStart recordhandle error = "+BassLibrary.BASS.GetBassError("BASS_ChannelStart"));
} else logger_error("BASS_RecordStart error = "+BassLibrary.BASS.GetBassError("BASS_RecordStart"));
streamhandle = BassLibrary.BASS.BASS_StreamCreate(samplingrate, 1, streamcreate_flag, BassLibrary.Constant.STREAMPROC_PUSH, null);
if (streamhandle!=0) {
BassLibrary.BASS.BASS_ChannelSetSync(streamhandle, BassLibrary.Constant.BASS_SYNC_DEV_FAIL, 0, devfail, null);
BassLibrary.BASS.BASS_ChannelSetSync(streamhandle, BassLibrary.Constant.BASS_SYNC_STALL, 0, bufferinginformation, null);
if (BassLibrary.BASS.BASS_ChannelStart(streamhandle)) {
logger_info("OutputChannel Started");
} else logger_error("BASS_ChannelStart streamhandle error = "+BassLibrary.BASS.GetBassError("BASS_ChannelStart"));
} else logger_error("BASS_StreamCreate error = "+BassLibrary.BASS.GetBassError("BASS_StreamCreate"));
if (IsInitialized()) {
logger_info("init complete");
new Thread() {
public void run() {
int inputvalue = 0;
int outputvalue = 0;
while(IsInitialized()) {
inputvalue = BassLibrary.BASS.BASS_ChannelGetLevel(recordhandle);
outputvalue = BassLibrary.BASS.BASS_ChannelGetLevel(streamhandle);
if (inputvalue>=0) {
inputvalue = (int) ((BassLibrary.Utils.LOWORD(inputvalue) * 1.0f)/32768);
if (bsml!=null) bsml.ChannelLevel(true, inputvalue);
}
if (outputvalue>=0) {
outputvalue = (int) ((BassLibrary.Utils.LOWORD(outputvalue) * 1.0f)/32768);
if (bsml!=null) bsml.ChannelLevel(false, outputvalue);
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
logger_info("ChannelLevel thread interrupted");
}
}
}
}.start();
if (bsml!=null) bsml.Opened();
return;
}
logger_info("init failed");
close();
}
}
@Override
public void close() {
boolean somethingclosed = false;
if (streamhandle!=0) {
BassLibrary.BASS.BASS_StreamFree(streamhandle);
streamhandle = 0;
somethingclosed = true;
}
if (recordhandle!=0) {
BassLibrary.BASS.BASS_ChannelStop(recordhandle);
recordhandle = 0;
somethingclosed = true;
}
if (somethingclosed) {
if (bsml!=null) bsml.Closed();
}
}
@Override
public int writeData(byte[] buffer, int offset, int length) {
if (streamhandle!=0) {
Pointer buf = new Memory(length);
buf.write(0, buffer, 0, length);
int rs = BassLibrary.BASS.BASS_StreamPutData(streamhandle, buf, length);
if (rs==-1) {
if (bsml!=null) bsml.log_error("writeData BASS_StreamPutData error = "+BassLibrary.BASS.GetBassError("BASS_StreamPutData"));
} else {
if (bsml!=null) bsml.WriteFromRTP(length, buffer);
return length;
}
}
return 0;
}
private void logger_error(String msg) {
if (bsml!=null) bsml.log_error(msg);
}
private void logger_info(String msg) {
if (bsml!=null) bsml.log_info(msg);
}
SYNCPROC devfail = new SYNCPROC() {
@Override
public void SYNCPROC(int handle, int channel, int data, Pointer user) {
if (channel==streamhandle) {
if (bsml!=null) bsml.DeviceFailure("OutputChannel stops unexpectedly");
} else if (channel==recordhandle) {
if (bsml!=null) bsml.DeviceFailure("InputChannel stops unexpectedly");
}
}
};
SYNCPROC bufferinginformation = new SYNCPROC() {
@Override
public void SYNCPROC(int handle, int channel, int data, Pointer user) {
if (channel==streamhandle) {
if (bsml!=null) bsml.BufferingInformation("OutputChannel "+ (data==0?"Stalled":"Resumed"));
} else if (channel==recordhandle) {
if (bsml!=null) bsml.BufferingInformation("InputChannel "+ (data==0?"Stalled":"Resumed"));
}
}
};
}

View File

@@ -0,0 +1,18 @@
package code;
/**
* Java Event untuk BassSoundManager
* @author rdkartono
*
*/
public interface BassSoundManagerListener {
public void Opened();
public void Closed();
public void WriteFromRTP(int length, byte[] pcmdata);
public void ReadToRTP(int length, byte[] pcmdata);
public void log_info(String msg);
public void log_error(String msg);
public void DeviceFailure(String msg);
public void BufferingInformation(String msg);
public void ChannelLevel(boolean is_input, int value);
}

510
src/code/CgiClient.java Normal file
View File

@@ -0,0 +1,510 @@
package code;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.CookieStore;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.ContentProducer;
import org.apache.http.entity.EntityTemplate;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.auth.DigestScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
/**
* CGI Client to VAPIX Device
* @author rdkartono
*
*/
public class CgiClient {
protected final HttpHost target;
private final UsernamePasswordCredentials userpasscred;
private final CredentialsProvider credsProvider;
private final CookieStore cookieStore;
protected final CloseableHttpClient httpclient;
protected final DigestScheme digestAuth;
protected final AuthCache authCache;
protected RequestConfig reqconfig;
protected InetAddress localip = null;
/**
* Initialize CGI Client
* @param hostname host name or IP address, default to 192.168.1.1 if null
* @param port port number, default to 80 if invalid number
* @param username specified username , default to "root" if null
* @param password specified password, default to "pass" if null
*/
public CgiClient(String hostname, int port, String username, String password) {
if (!common.ValidString(hostname)) hostname="192.168.1.1";
if (!common.ValidPortNumber(port)) port = 80;
if (!common.ValidString(username)) username="root";
if (!common.ValidString(password)) password = "pass";
setUsingLocalIP(null);
target = new HttpHost(hostname, port, "http");
userpasscred = new UsernamePasswordCredentials(username, password);
credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope(hostname, port),
userpasscred);
cookieStore = new BasicCookieStore();
httpclient = HttpClients.custom()
.setDefaultCookieStore(cookieStore)
.setDefaultCredentialsProvider(credsProvider)
.build();
digestAuth = new DigestScheme();
digestAuth.overrideParamter("qop", "auth");
digestAuth.overrideParamter("nc", "0");
digestAuth.overrideParamter("cnonce", DigestScheme.createCnonce());
authCache = new BasicAuthCache();
authCache.put(target, digestAuth);
}
public void setUsingLocalIP(String ip) {
try {
localip = common.ValidString(ip) ? InetAddress.getByName(ip) : InetAddress.getLocalHost();
return;
} catch (UnknownHostException e) {
System.out.println("setUsingLocalIP exception on ip="+ip+", Message = "+e.getMessage());
}
localip = null;
}
protected RequestConfig getRequestConfig() {
RequestConfig result = RequestConfig.custom()
.setLocalAddress(localip)
.build();
return result;
}
/**
* Close HTTP Client used to this object
* @return true if can be closed
*/
public boolean CloseHttpClient() {
if (httpclient!=null) {
try {
httpclient.close();
return true;
} catch (IOException e) {
raise_log("CloseHttpClient exception="+e.getMessage());
}
}
return false;
}
// public CloseableHttpResponse HttpPost_ByteArray(String urlpath, byte[] value) {
// if (!common.ValidString(urlpath)) urlpath = "/"; // to default
// raise_log("About to HttpPost from "+urlpath);
// HttpPost httppost = new HttpPost(urlpath);
// httppost.setConfig(getRequestConfig());
// HttpClientContext localContext = HttpClientContext.create();
// localContext.setAuthCache(authCache);
//
// CloseableHttpResponse response = null;
//
//
// }
public CloseableHttpResponse HttpPost_Filename(String filepath, String urlpath, String... values) {
if (!common.ValidString(urlpath)) urlpath = "/"; // to default
if (common.ValidArrayOfString(values)) {
urlpath+=Stream.of(values).reduce("?", (partialstring,nextelement)->partialstring+"&"+nextelement);
}
raise_log("About to HttpPost from "+urlpath);
HttpPost httppost = new HttpPost(urlpath);
httppost.setConfig(getRequestConfig());
HttpClientContext localContext = HttpClientContext.create();
localContext.setAuthCache(authCache);
CloseableHttpResponse response = null;
File fis = new File(filepath);
if (fis!=null && fis.exists() && fis.isFile()) {
try {
HttpEntity entity = MultipartEntityBuilder
.create()
.addBinaryBody("file_upload", fis)
.build();
httppost.setEntity(entity);
httppost.setHeader("Content-Type","multipart/form-data");
response = httpclient.execute(target, httppost, localContext);
//int code = GetStatusCode(response);
//String message = GetStatusMessage(response);
//String content = common.ConcatListOfString(GetContent(response));
//raise_log("HttpPost_Filename Response Code="+code+", Message="+message+", Content="+content);
} catch (IOException e) {
raise_log("HttpPost_Filename exception="+e.getMessage());
}
} else raise_log("HttpPost_Filename failed, invalid filepath="+filepath);
return response;
}
public CloseableHttpResponse HttpPost(String ContentType, ContentProducer cp, String urlpath, String... parameters) {
if (!common.ValidString(urlpath)) urlpath = "/"; // to default
raise_log("About to HttpPost from "+urlpath);
HttpPost httppost = new HttpPost(urlpath);
httppost.setConfig(getRequestConfig());
HttpClientContext localContext = HttpClientContext.create();
localContext.setAuthCache(authCache);
CloseableHttpResponse response = null;
HttpEntity entity = new EntityTemplate(cp);
httppost.setEntity(entity);
httppost.setHeader("Content-type", ContentType);
try {
response = httpclient.execute(target, httppost, localContext);
return response;
} catch (IOException e) {
raise_log("HttpPost exception = "+e.getMessage());
}
return null;
}
/**
* HTTP Post
* @param urlpath target URL
* @param value Post values that will be written in Body
* @return null if failed;
*/
public CloseableHttpResponse HttpPost(String urlpath, JsonObject value) {
if (!common.ValidString(urlpath)) urlpath = "/"; // to default
//raise_log("About to HttpPost from "+urlpath);
HttpPost httppost = new HttpPost(urlpath);
httppost.setConfig(getRequestConfig());
HttpClientContext localContext = HttpClientContext.create();
localContext.setAuthCache(authCache);
CloseableHttpResponse response = null;
try {
StringEntity poststring = new StringEntity(value.toString());
httppost.setEntity(poststring);
httppost.setHeader("Content-type", "application/json");
response = httpclient.execute(target,httppost, localContext);
//int code = GetStatusCode(response);
//String message = GetStatusMessage(response);
//raise_log("HttpPost Response Code="+code+", Message="+message);
} catch (IOException e) {
raise_log("HttpPost exception="+e.getMessage());
}
return response;
}
/**
* Doing HTTP Get to specified urlpath
* @param urlpath url path, default to "/" if null
* @return CloseableHttpResponse object or null if failed
*/
public CloseableHttpResponse HttpGet(String urlpath, String... parameters){
if (!common.ValidString(urlpath)) urlpath = "/"; // to default
if (common.ValidArrayOfString(parameters)) {
urlpath+=Stream.of(parameters).reduce("?", (partialstring,nextelement)->partialstring+"&"+nextelement);
}
//raise_log("About to HttpGet from "+urlpath);
HttpGet httpget = new HttpGet(urlpath);
httpget.setConfig(getRequestConfig());
HttpClientContext localContext = HttpClientContext.create();
localContext.setAuthCache(authCache);
CloseableHttpResponse response = null;
try {
response = httpclient.execute(target, httpget, localContext);
int code = GetStatusCode(response);
//String message = GetStatusMessage(response);
//raise_log("HttpGet Response Code="+code+", Message="+message);
if (code==401) {
// rejected karena authentication
Map<String, String> wwwauth = GetWWWAuthentication(response);
if (wwwauth!=null && wwwauth.size()>0) {
raise_log("HttpGet rejected, Need to update WWW authentication");
String opaque = wwwauth.get("opaque");
if (common.ValidString(opaque)) {
replace_digestauthvalue("opaque", opaque);
}
String nonce = wwwauth.get("nonce");
if (common.ValidString(nonce)) {
replace_digestauthvalue("nonce",nonce);
}
String realm = wwwauth.get("Digest realm");
if (common.ValidString(realm)) {
replace_digestauthvalue("realm",realm);
}
}
}
} catch (IOException e) {
raise_log("HttpGet exception="+e.getMessage());
}
return response;
}
private void replace_digestauthvalue(String key, String newvalue) {
if (digestAuth!=null) {
if (common.ValidString(key)) {
if (common.ValidString(newvalue)) {
String oldvalue = digestAuth.getParameter(key);
if (oldvalue==null) {
raise_log("Set key="+key+" value="+newvalue);
digestAuth.overrideParamter(key, newvalue);
} else if (newvalue.equals(oldvalue)==false) {
raise_log("Replacing key="+key+", old value="+oldvalue+" with new value="+newvalue);
digestAuth.overrideParamter(key, newvalue);
} else raise_log("key="+key+", already same value");
}
}
}
}
/**
* Get HTTP Status Code from CloseableHttpResponse object
* @param response CloseableHttpResponse object
* @return 0 if failed
*/
public int GetStatusCode(CloseableHttpResponse response) {
if (response!=null) {
if (response.getStatusLine()!=null) {
return response.getStatusLine().getStatusCode();
}
}
return 0;
}
/**
* Get HTTP Status Message from CloseableHttpResponse object
* @param response CloseableHttpResponse object
* @return null if failed
*/
public String GetStatusMessage(CloseableHttpResponse response) {
if (response!=null) {
if (response.getStatusLine()!=null) {
return response.getStatusLine().getReasonPhrase();
}
}
return null;
}
/**
* Get HTTP Content Type from CloseableHttpResponse object
* @param response CloseableHttpResponse object
* @return null if failed
*/
public String GetContentType(CloseableHttpResponse response) {
HttpEntity ent = GetResponseEntity(response);
return ent!=null ? ent.getContentType().getValue() : null;
}
/**
* Get Content Length
* @param response CloseableHttpResponse object
* @return 0 if failed, or -1 if unknown from HTTP Response
*/
public long GetContentLength(CloseableHttpResponse response) {
HttpEntity ent = GetResponseEntity(response);
return ent!=null ? ent.getContentLength() : 0;
}
/**
* Get FileName from CloseableHttpResponse
* @param response CloseableHttpResponse object
* @return null if not available
*/
public String GetFileName(CloseableHttpResponse response) {
if (response!=null) {
String vv = response.getFirstHeader("Content-Disposition").getValue();
if (common.ValidString(vv)) {
Pattern pp = Pattern.compile("filename=\"(\\S+)\"");
Matcher mm = pp.matcher(vv);
if (mm.find()) {
return mm.group(1);
}
}
}
return null;
}
/**
* Get HTTP Entity from a CloseableHttpResponse
* @param response CloseableHttpResponse object
* @return null if failed
*/
public HttpEntity GetResponseEntity(CloseableHttpResponse response) {
if (response!=null) {
return response.getEntity();
}
return null;
}
/**
* Close CloseableHttpResponse Object
* @param response CloseableHttpResponse object
*/
public void Close(CloseableHttpResponse response) {
if (response!=null) {
try {
response.close();
} catch (IOException e) {
raise_log("Close exception="+e.getMessage());
}
response = null;
}
}
/**
* Get WWW-Authenticate headers from CloseableHttpResponse
* @param response CloseableHttpResponse object
* @return Empty Map if failed
*/
private Map<String, String> GetWWWAuthentication(CloseableHttpResponse response){
if (response!=null) {
Map<String, String> wwwAuth = Arrays
.stream(response.getHeaders("WWW-Authenticate")[0]
.getElements())
.collect(Collectors.toMap(HeaderElement::getName,
HeaderElement::getValue));
return wwwAuth;
} else return new HashMap<String, String>();
}
/**
* Get Content from CloseableHttpResponse object
* @param response CloseableHttpResponse object
* @return List of String
*/
public List<String> GetContent(CloseableHttpResponse response){
List<String> result = new ArrayList<String>();
if (response!=null) {
HttpEntity entity = response.getEntity();
if (entity!=null) {
try(InputStream is = entity.getContent()) {
BufferedReader buf = new BufferedReader(new InputStreamReader(is));
buf.lines().forEach(ss->result.add(ss));
} catch (UnsupportedOperationException | IOException e) {
raise_log("GetContent exception="+e.getMessage());
}
}
}
return result;
}
/**
* Get Content from body, expect to return Bytes array
* @param response CloseableHttpResponse
* @return null if failed
*/
public byte[] GetContent_BytesArray(CloseableHttpResponse response) {
if (response!=null) {
HttpEntity entity = response.getEntity();
if (entity!=null) {
try (InputStream is = entity.getContent()) {
ByteBuffer result = ByteBuffer.allocate(0);
int len = 0;
while(true) {
byte[] xx = new byte[1024];
len = is.read(xx);
if (len>0) {
ByteBuffer temp = ByteBuffer.allocate(result.capacity()+len);
temp.put(result);
temp.put(xx,0,len);
result = temp;
} else break;
}
return result.array();
} catch (UnsupportedOperationException | IOException e) {
raise_log("GetContent_BytesArray failed, exception="+e.getMessage());
}
}
}
return null;
}
/**
* Get content as JsonObject
* @param response CloseableHttpResponse
* @return null if failed
*/
public JsonObject GetContentAsJson(CloseableHttpResponse response) {
if (response!=null) {
return JsonParser.parseString(common.ConcatListOfString(GetContent(response))).getAsJsonObject();
}
return null;
}
private void raise_log(String msg) {
System.out.println(msg);
}
}

123
src/code/JVAPIX.java Normal file
View File

@@ -0,0 +1,123 @@
package code;
import java.util.HashMap;
import java.util.Map;
import anywheresoftware.b4a.BA;
@BA.Version(0.06f)
@BA.ShortName("jVAPIX")
@BA.Events(values= {
"log(msg as string)"
})
public class JVAPIX {
private BA ba = null;
private String event = null;
private Object Me = this;
//private ExecutorService exec = Executors.newCachedThreadPool();
private boolean inited = false;
private boolean need_log_event = false;
private Map<String, VapixDevice> devices = new HashMap<String,VapixDevice>();
/**
* Initialize jAxisAudio
* @param event eventname
*/
public void Initialize(BA ba, String event) {
this.ba = ba;
this.event = event;
if (this.ba!=null) {
if (this.event!=null) {
if (this.event.length()>0) {
inited = true;
need_log_event = this.ba.subExists(event+"_log");
}
}
}
}
/**
* Connect to a Vapix Device
* Connected VapixDevice is stored in a Map
* @param hostname hostname or IP
* @param port port number
* @param username username
* @param password password
* @return true if can be connected and a valid Vapix Device
*/
public boolean Connect(String hostname, int port, String username, String password, String localip) {
VapixDevice dev = new VapixDevice(hostname, port, username, password, localip);
boolean success = dev.GetParametersFromDevice();
if (!success) success = dev.GetParametersFromDevice(); // ambil kedua kali
if (success) {
if (dev.getHTTPVersion().equals("3") && dev.IsAudioDevice()) {
raise_log("Device at "+hostname+":"+port+" is VapixAudio");
VapixAudio va = new VapixAudio(dev);
VapixDevice prev = devices.put(hostname, va);
if (prev!=null) {
raise_log("Connect to "+hostname+":"+port+" has replaced a VapixDevice with SerialNumber="+prev.getSerialNumber()+", MAC="+prev.getMACAddress());
}
}
return true;
}
return success; //kalau gagal , berarti bukan Vapix Device
}
/**
* Get all Connected Vapix Devices
* @return array of IP Address
*/
public String[] ConnectedDevices() {
if (devices.size()>0) {
return devices.keySet().stream().toArray(String[]::new);
} else return new String[0];
}
/**
* Disconnect a Vapix Device
* @param ip IP Address to disconnect
* @return true if can be disconnect
*/
public boolean DisconnectDevice(String ip) {
if (devices.size()>0) {
VapixDevice dev = devices.get(ip);
if (dev!=null) {
dev.Close();
devices.remove(ip);
dev = null;
}
}
return false;
}
/**
* Get Connected Vapix Device with specific ip address
* Object returned must be check if instanceof valid type, such as VapixAudio
* @param ipaddress ip address to search
* @return null if not exists
*/
public Object GetConnectedDevice(String ipaddress) {
return devices.get(ipaddress);
}
/**
* Check if this Object is initialized
* @return true if inited
*/
public boolean IsInitialized() {
return inited;
}
private void raise_log(String msg) {
if (ba!=null) {
if (need_log_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg});
} else System.out.println(msg);
}
}

1940
src/code/VapixAudio.java Normal file

File diff suppressed because it is too large Load Diff

510
src/code/VapixDevice.java Normal file
View File

@@ -0,0 +1,510 @@
package code;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import com.google.gson.JsonObject;
import anywheresoftware.b4a.BA;
@BA.Events(values= {
"log(msg as string)"
})
/**
* General VAPIX Device
* @author rdkartono
*
*/
public class VapixDevice {
protected final CgiClient client;
private final Map<String,String> parameters;
protected BA ba;
protected String event;
protected boolean need_log_event = false;
protected Object Me = this;
protected boolean ba_inited = false;
public void InitializeEvent(BA ba, String event) {
this.ba = ba;
this.event = event;
if (this.ba!=null) {
if (common.ValidString(this.event)) {
ba_inited = true;
need_log_event = ba.subExists(this.event+"_log");
}
}
}
/**
* Create Generic Vapix Device that point to another VapixDevice
* @param other Other Vapix Device
*/
protected VapixDevice(VapixDevice other) {
this.client = (other!=null ? other.client : null);
this.parameters = (other!=null ? other.parameters : new HashMap<String,String>());
}
/**
* Create Generic Vapix Device
* @param hostname host name or IP address
* @param port port number
* @param username username to login
* @param password password to login
*/
protected VapixDevice(String hostname, int port, String username, String password) {
client = new CgiClient(hostname, port, username, password);
parameters = new HashMap<String,String>();
}
/**
* Create Generic Vapix Device
* @param hostname host name or IP Address
* @param port port number
* @param username username to login
* @param password password to login
* @param localip local ip address / ethernet to use for connecting the device
*/
protected VapixDevice(String hostname, int port, String username, String password, String localip) {
this(hostname, port, username, password);
client.setUsingLocalIP(localip);
}
public void Close() {
if (client!=null) {
client.CloseHttpClient();
}
}
/**
* Get Serial Number
* @return null if failed
*/
public String getSerialNumber() {
return GetParameterValue("Properties.System.SerialNumber");
}
/**
* Get Product Name
* @return null if failed
*/
public String getProductName() {
return GetParameterValue("Brand.ProdShortName");
}
/**
* Get Product Type
* @return null if failed
*/
public String getProductType() {
return GetParameterValue("Brand.ProdType");
}
/**
* Get IP Address
* @return null if failed
*/
public String getIPAddress() {
return GetParameterValue("Network.eth0.IPAddress");
}
/**
* Get Subnet Mask
* @return null if failed
*/
public String getSubnetMask() {
return GetParameterValue("Network.eth0.SubnetMask");
}
/**
* Get MAC Address
* @return null if failed
*/
public String getMACAddress() {
return GetParameterValue("Network.eth0.MACAddress");
}
/**
* Get Network Hostname recognized in DHCP Router
* @return null if failed
*/
public String getHostName() {
return GetParameterValue("Network.HostName");
}
/**
* Get Network Gateway
* @return null if failed
*/
public String getGateway() {
return GetParameterValue("Network.Routing.DefaultRouter");
}
/**
* Get Primary DNS (DNS1)
* @return null if failed
*/
public String getDNS1() {
return GetParameterValue("Network.Resolver.NameServer1");
}
/**
* Get Secondary DNS (DNS2)
* @return null if faild
*/
public String getDNS2() {
return GetParameterValue("Network.Resolver.NameServer2");
}
/**
* Check if this is an Vapix Audio Device
* @return true if correct
*/
public boolean IsAudioDevice() {
return ("yes".equals(GetParameterValue("Properties.Audio.Audio")));
}
/**
* Get HTTP Version
* @return string value
*/
public String getHTTPVersion() {
return GetParameterValue("Properties.API.HTTP.Version");
}
/**
* Get saved parameters with specific key
* Call GetParametersFromDevice to save parameters locally
* @param key parameter title
* @return null if failed
*/
public String GetParameterValue(String key) {
String result = parameters.entrySet().stream()
.filter(e->e.getKey().contains(key))
.map(Map.Entry::getValue)
.findFirst()
.orElse(null);
return result;
}
/**
* Replace Parameter Value
* @param key Specific key to replace the value
* @param value new value
* @return previous value replaced, or null if not available
*/
protected String SetParameterValue(String key, String value) {
for(Map.Entry<String, String> entry : parameters.entrySet()) {
if (entry.getKey().contains(key)) {
return entry.setValue(value);
}
}
return null;
}
/**
* Get Array of Parameter Keys
* @return parameter keys
*/
public String[] GetParameterKeys() {
return parameters.keySet().toArray(new String[0]);
}
/**
* Re-Get parameters from Vapix Device
* @return true if success
*/
public boolean GetParametersFromDevice(){
List<String> vv = HTTPGet("/axis-cgi/param.cgi","action=list");
if (common.ValidListOfString(vv)) {
raise_log("GetParameters success");
vv.forEach(ss -> {
int index = ss.indexOf('=');
if (index!=-1) {
// ada '='
String key = ss.substring(0, index);
String value = ss.substring(index+1);
String prev_value = parameters.put(key, value);
if (common.ValidString(prev_value)) {
raise_log("key="+key+", old_value="+prev_value+", new_value="+value);
}
}
});
return true;
} else raise_log("GetParameters failed");
return false;
}
/**
* General Purpose HTTP Get
* @param url url path
* @param parameters parameters that follow HTTP Get
* @return List of String if success, or null if failed
*/
protected List<String> HTTPGet(String url, String... parameters){
List<String> result = null;
CloseableHttpResponse response = client.HttpGet(url,parameters);
if (client.GetStatusCode(response)==200) {
result = client.GetContent(response);
}
client.Close(response);
return result;
}
/**
* General Purpose HTTP Get
* return String as content, or null if falied
* @param expected_contenttype expected content type, put null if not expecting
* @param url URL
* @param parameters Parameters
* @return null if failed
*/
protected String HTTPGet_GetString(String expected_contenttype, String url, String... parameters) {
CloseableHttpResponse response = client.HttpGet(url, parameters);
int code = client.GetStatusCode(response);
//String message = client.GetStatusMessage(response);
String contenttype = client.GetContentType(response);
String content = common.ConcatListOfString(client.GetContent(response));
//raise_log(MessageFormat.format("HTTPGet_GetString to {0}, Code={1}, Message={2}, Content Type={3}, Content={4}", url, code, message, contenttype, content));
client.Close(response);
if (code==200) {
if (common.Strings_Valid_and_Match(expected_contenttype, contenttype)){
return content;
}
}
return null;
}
/**
* General Purpose HTTP Get, but specifically want to find a string
* @param findme String to find
* @param url URL path
* @param parameters parameters that follow HTTP Get
* @return null if findme not found
*/
protected String HTTPGet_FindString(String findme, String url, String... parameters) {
List<String> xx = HTTPGet(url, parameters);
if (xx!=null && xx.size()>0) {
return xx.stream()
.filter(ss -> ss.contains(findme))
.findAny()
.orElse(null);
}
return null;
}
/**
* General Purpose HTTP Get, but specifically want to find a string
* @param findme String to find
* @param url URL Path
* @param parameters parameters that follow HTTP Get
* @return array of string, contains any string that contains findme
*/
protected String[] HTTPGet_FindStrings(String findme, String url, String... parameters) {
List<String> xx = HTTPGet(url,parameters);
if (xx!=null && xx.size()>0) {
List<String> result = xx.stream()
.filter(ss -> ss.contains(findme))
.collect(Collectors.toList());
return result.toArray(new String[0]);
}
return new String[0];
}
/**
* General Purpuse HTTP Get, but dont need the result in Body
* Just need status code
* @param url URL Path
* @param parameters parameters that follow HTTP Get
* @return HTTP Get status code
*/
protected int HTTPGet_NoResult(String url, String... parameters) {
CloseableHttpResponse response = client.HttpGet(url,parameters);
int code = client.GetStatusCode(response);
String message = client.GetStatusMessage(response);
client.Close(response);
raise_log("HTTPGet_NoResult to "+url+", Code="+code+", Message="+message);
return code;
}
/**
* General Purpose HTTP Get, return bytes array from ContentInputStream
* @param expected_ContentType expected content type
* @param url URL to download
* @param parameters some parameters
* @return byte array if success, or null if failed
*/
protected byte[] HTTPGet_BytesResult(String expected_ContentType, String url, String... parameters) {
CloseableHttpResponse response = client.HttpGet(url, parameters);
int code = client.GetStatusCode(response);
byte[] result = client.GetContent_BytesArray(response);
client.Close(response);
if (code==200) {
return result;
}
return null;
}
/**
* Download file
* @param savelocation location path
* @param url URL to download
* @param parameters some parameters needed
* @return DownloadResult object, or null if invalid
*/
protected DownloadResult HTTPGet_DownloadFile(String savelocation, String url, String... parameters) {
CloseableHttpResponse response = client.HttpGet(url, parameters);
int code = client.GetStatusCode(response);
if (code==200) {
HttpEntity entity = client.GetResponseEntity(response);
if (entity!=null) {
String filename = client.GetFileName(response);
if (common.ValidString(filename)) {
File ff = new File(savelocation, filename);
try (FileOutputStream fos = new FileOutputStream(ff)) {
entity.writeTo(fos);
return new DownloadResult(savelocation, filename, ff.length());
} catch (IOException e) {
raise_log("HTTPGet_DownloadFile exception = "+e.getMessage());
}
} else raise_log("HTTPGet_DownloadFile filename not available");
} else raise_log("HTTPGet_DownloadFile HttpEntity not available");
} else raise_log("HTTPGet_DownloadFile code not 200");
return null;
}
public class DownloadResult{
public String path;
public String filename;
public long size;
public DownloadResult(String path, String filename, long size) {
this.path = path;
this.filename = filename;
this.size = size;
}
}
/**
* HTTP Get to specific url and expect some Content Type to be considered success
* @param expected_ContentType content type message to expect
* @param url URL
* @param parameters parameters
* @return true if Code=200 and contain expected_ContentType
*/
protected boolean HTTPGet_BooleanResult(String expected_ContentType, String url, String... parameters) {
CloseableHttpResponse response = client.HttpGet(url, parameters);
int code=client.GetStatusCode(response);
String message = client.GetStatusMessage(response);
String contentype = client.GetContentType(response);
raise_log(MessageFormat.format("HTTPGet_BooleanResult to {0}, Code={1}, Message={2}, ContentType={3}", url, code, message, contentype));
client.Close(response);
if (code==200) {
if (contentype.contains(expected_ContentType)) {
return true;
}
}
return false;
}
/**
* HTTP Post a Json Object
* @param url URL to post
* @param data Json Object to post
* @return Json Object result
*/
protected JsonObject HTTPPost(String url, JsonObject data) {
CloseableHttpResponse response = client.HttpPost(url, data);
int code = client.GetStatusCode(response);
JsonObject result = client.GetContentAsJson(response);
client.Close(response);
if (code==200) {
return result;
}
return null;
}
/**
* HTTP Post a Json Object
* @param url URL to post
* @param data Json Object to post
* @return true if success (code 200)
*/
protected boolean HTTPPost_BooleanResult(String url, JsonObject data) {
CloseableHttpResponse response = client.HttpPost(url, data);
int code = client.GetStatusCode(response);
client.Close(response);
return (code==200) ;
}
/**
* Upload file using HTTP Post
* @param filename file to upload
* @param url URL to post
* @return clip ID, or -1 if failed
*/
protected int HTTPPost_UploadFile(String filename, String url, String... parameters) {
CloseableHttpResponse response = client.HttpPost_Filename(filename, url, parameters);
int code = client.GetStatusCode(response);
List<String> content = client.GetContent(response);
client.Close(response);
if (code==200) {
// bisa dua macam status
// p1 kalau file sudah ada, tapi di upload ulang
Pattern p1 = Pattern.compile("updated=MediaClip.M(\\d+)");
// p2 kalau file belum ada, di upload baru
Pattern p2 = Pattern.compile("uploaded=(\\d+)");
for(String ss : content) {
//System.out.println(ss);
Matcher m1 = p1.matcher(ss);
Matcher m2 = p2.matcher(ss);
if (m1.find()) {
//System.out.println("Found clip "+m1.group(1)+" using updated");
return Integer.valueOf(m1.group(1));
} else if (m2.find()) {
//System.out.println("Found clip "+m2.group(1)+" using uploaded");
return Integer.valueOf(m2.group(1));
}
}
}
return -1;
}
protected void raise_log(String msg) {
if (need_log_event) {
ba.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg});
} else System.out.println(msg);
}
}

View File

@@ -0,0 +1,151 @@
package code;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import javax.sound.sampled.AudioFormat;
import anywheresoftware.b4a.BA.Hide;
/**
* Writes sound data into a wav file
* Source : http://thorntonzone.com/manuals/Compression/Fax,%20IBM%20MMR/MMSC/mmsc/uk/co/mmscomputing/sound/
* @author rdkartono
*
*/
@Hide
public class WaveOutputFile extends RandomAccessFile{
byte[] h=new byte[58];
public WaveOutputFile(File file,AudioFormat format)throws FileNotFoundException,IOException{
super(file,"rw");
write(getHeader(format));
}
public WaveOutputFile(String file,AudioFormat format)throws FileNotFoundException,IOException{
super(file,"rw");
write(getHeader(format));
}
private byte[] getHeader(AudioFormat format){
/*
riff chunk = [format chunk, fact chunk, data chunk]
We don't know the length of the data chunk yet.
Need fact chunk for compressed data.
*/
// start riff chunk
int i=0;
h[i++]='R'; h[i++]='I'; h[i++]='F'; h[i++]='F'; // 0 : 'RIFF'
h[i++]= 50; h[i++]= 0 ; h[i++]= 0 ; h[i++]= 0 ; // 4 : length of riff chunk [8..(data size+58)]
h[i++]='W'; h[i++]='A'; h[i++]='V'; h[i++]='E'; // 8 : 'WAVE'
// start format chunk
h[i++]='f'; h[i++]='m'; h[i++]='t'; h[i++]=' '; // 12: 'fmt '
h[i++]= 18; h[i++]= 0 ; h[i++]= 0 ; h[i++]= 0 ; // 16 : length of format chunk = 0x12 = 18 [20..38]
AudioFormat.Encoding encoding=format.getEncoding();
if(encoding.equals(AudioFormat.Encoding.PCM_SIGNED)){
h[i++]= 1 ; h[i++]= 0 ; // 20 : PCM = 1
}else if(encoding.equals(AudioFormat.Encoding.PCM_UNSIGNED)){
throw new IllegalArgumentException(getClass().getName()
+".getHeader(AudioFormat format)\n\tDo not support PCM unsigned data"
);
}else if(encoding.equals(AudioFormat.Encoding.ALAW)){
h[i++]= 6 ; h[i++]= 0 ; // 20 : A-Law = 6
}else if(encoding.equals(AudioFormat.Encoding.ULAW)){
h[i++]= 7 ; h[i++]= 0 ; // 20 : u-Law = 7
}else{
throw new IllegalArgumentException(getClass().getName()
+".getHeader(AudioFormat format)\n\tDo not support encoding ["+encoding+"]"
);
}
int channels=format.getChannels();
if(channels==1){
h[i++]= 1 ; h[i++]= 0 ; // 22 : mono = 1
}else if(channels==2){
h[i++]= 2 ; h[i++]= 0 ; // 22 : stereo = 2
}else{
throw new IllegalArgumentException(getClass().getName()
+".getHeader(AudioFormat format)\n\tDo not support "+channels+" channels"
);
}
int sr = (int)format.getFrameRate();
h[i++]=(byte)( sr & 0x000000FF); // 24 : sample rate
h[i++]=(byte)((sr>>8) & 0x000000FF);
h[i++]=(byte)((sr>>16) & 0x000000FF);
h[i++]=(byte)((sr>>24) & 0x000000FF);
int ss = format.getFrameSize();
int bs = sr * ss;
h[i++]=(byte)( bs & 0x000000FF); // 28 : bytes per second
h[i++]=(byte)((bs>>8) & 0x000000FF);
h[i++]=(byte)((bs>>16) & 0x000000FF);
h[i++]=(byte)((bs>>24) & 0x000000FF);
h[i++]=(byte)( ss & 0x000000FF); // 32 : bytes per sample
h[i++]=(byte)((ss>>8) & 0x000000FF);
int ssib = format.getSampleSizeInBits();
h[i++]=(byte)( ssib & 0x000000FF); // 34 : bits per sample
h[i++]=(byte)((ssib>>8)& 0x000000FF);
h[i++]= 0 ; h[i++]= 0 ; // 36 : number of bytes of additional compressor-specific information
// end format chunk // 38
// start fact chunk // 38
h[i++]='f'; h[i++]='a'; h[i++]='c'; h[i++]='t'; // 38: 'fact'
h[i++]= 4 ; h[i++]= 0 ; h[i++]= 0 ; h[i++]= 0 ; // 42 : length of fact chunk = 0x04 = 4 [46..50]
h[i++]= 0 ; h[i++]= 0 ; h[i++]= 0 ; h[i++]= 0 ; // 46 : data size
// end fact chunk // 50
// start data chunk // 50
h[i++]='d'; h[i++]='a'; h[i++]='t'; h[i++]='a'; // 50: 'data'
h[i++]= 0 ; h[i++]= 0 ; h[i++]= 0 ; h[i++]= 0 ; // 54 : length of data chunk [58..(data size+58)]
// data size bytes
// end data chunk // data size + 58
// end riff chunk // data size + 58
return h;
}
public void close()throws IOException{
long size = length();
if(size>=0x7FFFFFFFl){
throw new IOException(getClass().getName()+".close()\n\tData size ["+size+"] is too big for WAV file.");
}
int i=4;
int len=(int)size-8;
h[i++]=(byte)( len & 0x000000FF); // 4 : length of riff chunk [8..(data size+58)]
h[i++]=(byte)((len>>8) & 0x000000FF);
h[i++]=(byte)((len>>16) & 0x000000FF);
h[i++]=(byte)((len>>24) & 0x000000FF);
i=46;
len=(int)size-58; // data size
h[i++]=(byte)( len & 0x000000FF); // 46 : 'fact' data size
h[i++]=(byte)((len>>8) & 0x000000FF);
h[i++]=(byte)((len>>16) & 0x000000FF);
h[i++]=(byte)((len>>24) & 0x000000FF);
i=54;
h[i++]=(byte)( len & 0x000000FF); // 54 : length of data chunk [58..(data size+58)]
h[i++]=(byte)((len>>8) & 0x000000FF);
h[i++]=(byte)((len>>16) & 0x000000FF);
h[i++]=(byte)((len>>24) & 0x000000FF);
seek(0);
write(h); // write header at beginning of file
super.close();
}
}

560
src/code/common.java Normal file
View File

@@ -0,0 +1,560 @@
package code;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.text.MessageFormat;
import java.util.List;
import java.util.stream.Stream;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.sun.jna.Native;
import net.sourceforge.peers.sip.syntaxencoding.SipHeaderFieldName;
import net.sourceforge.peers.sip.syntaxencoding.SipHeaderFieldValue;
import net.sourceforge.peers.sip.syntaxencoding.SipHeaders;
public class common {
/**
* Check if value is a valid String
* Not Null, and length at least 1 character
* @param vv String value
* @return true if valid
*/
public static boolean ValidString(String vv) {
if (vv != null) {
if (vv.length()>0) {
return true;
}
}
return false;
}
/**
* Check if value is a valid Port number
* @param vv between 1 ~ 65535
* @return true if valid
*/
public static boolean ValidPortNumber(int vv) {
if (vv>0) {
if (vv<65536) {
return true;
}
}
return false;
}
/**
* Check if valus is a valid array of string
* array is not null, array length at least 1
* and all members of array is valid string
* @param vv array of string
* @return true if valid
*/
public static boolean ValidArrayOfString(String[] vv) {
if (vv!=null) {
if (vv.length>0) {
return Stream.of(vv).allMatch(ss-> ValidString(ss));
}
}
return false;
}
public static boolean ValidArrayOfBytes(byte[] vv) {
if (vv!=null) {
if (vv.length>0) {
return true;
}
}
return false;
}
public static boolean ValidListOfString(List<String> vv) {
if (vv!=null) {
if (vv.size()>0) {
return true;
}
}
return false;
}
public static String getClassValues(Class<?> cc, Object obj) {
Field[] fields = cc.getFields();
if (fields!=null && fields.length>0) {
int length = fields.length;
String[] fs = new String[length];
for(int i=0;i<length;i++) {
Field ff = fields[i];
try {
fs[i] = ff.getName()+":"+ff.get(obj);
} catch (IllegalArgumentException | IllegalAccessException e) {
fs[i] = ff.getName()+":N/A";
System.out.println("getClassValues exception="+e.getMessage());
}
}
return String.join(", ", fs);
} else return "";
}
public static JsonObject GetJsonObjectValue_JsonObject(JsonObject jo, String key) {
if (jo!=null) {
if (jo.has(key)) {
JsonElement je = jo.get(key);
if (je!=null && je.isJsonObject()) {
return je.getAsJsonObject();
}
}
}
return null;
}
public static JsonArray GetJsonObjectValue_JsonArray(JsonObject jo, String key) {
if (jo!=null) {
if (jo.has(key)) {
JsonElement je = jo.get(key);
if (je!=null && je.isJsonArray()) {
return je.getAsJsonArray();
}
}
}
return null;
}
public static double GetJsonObjectValue_Double(JsonObject jo, String key) {
if (jo!=null) {
if (jo.has(key)) {
try {
return jo.get(key).getAsDouble();
} catch(UnsupportedOperationException | NumberFormatException | IllegalStateException e) {
System.out.println("GetJsonObjectValue_Double exception key="+key+", message="+e.getMessage());
}
}
}
return 0;
}
public static float GetJsonObjectValue_Float(JsonObject jo, String key) {
if (jo!=null) {
if (jo.has(key)) {
try {
return jo.get(key).getAsFloat();
} catch(UnsupportedOperationException | NumberFormatException | IllegalStateException e) {
System.out.println("GetJsonObjectValue_Float exception key="+key+", message="+e.getMessage());
}
}
}
return 0.0f;
}
public static long GetJsonObjectValue_Long(JsonObject jo, String key) {
if (jo!=null) {
if (jo.has(key)) {
try {
return jo.get(key).getAsLong();
} catch(UnsupportedOperationException | NumberFormatException | IllegalStateException e) {
System.out.println("GetJsonObjectValue_Long exception key="+key+", message="+e.getMessage());
}
}
}
return 0l;
}
public static int GetJsonObjectValue_Integer(JsonObject jo, String key) {
if (jo!=null) {
if (jo.has(key)) {
try {
return jo.get(key).getAsInt();
} catch(UnsupportedOperationException | NumberFormatException | IllegalStateException e) {
System.out.println("GetJsonObjectValue_Integer exception key="+key+", message="+e.getMessage());
}
}
}
return 0;
}
public static String GetJsonObjectValue_String(JsonObject jo, String key) {
if (jo!=null) {
if (jo.has(key)) {
try {
return jo.get(key).getAsString();
} catch(UnsupportedOperationException | IllegalStateException e) {
System.out.println("GetJsonObjectValue_String exception key="+key+", message="+e.getMessage());
}
}
}
return null;
}
public static String[] GetJsonObjectValue_Strings(JsonObject jo, String key) {
if (jo!=null) {
if (jo.has(key)) {
JsonElement je = jo.get(key);
if (je.isJsonArray()) {
JsonArray ja = je.getAsJsonArray();
int length = ja.size();
if (length>0) {
String[] result = new String[length];
for(int ii=0;ii<length;ii++) {
result[ii] = ja.get(ii).getAsString();
}
return result;
}
}
}
}
return new String[0];
}
public static boolean GetJsonObjectValue_Boolean(JsonObject jo, String key) {
if (jo!=null) {
if (jo.has(key)) {
try {
return jo.get(key).getAsBoolean();
} catch(UnsupportedOperationException | IllegalStateException e) {
System.out.println("GetJsonObjectValue_Boolean exception key="+key+", message="+e.getMessage());
}
}
}
return false;
}
public static void SetJsonObjectValue(JsonObject jo, String key, int value) {
if (jo!=null) {
jo.remove(key);
jo.addProperty(key, value);
}
}
public static void SetJsonObjectValue(JsonObject jo, String key, boolean value) {
if (jo!=null) {
jo.remove(key);
jo.addProperty(key, value);
}
}
public static void SetJsonObjectValue(JsonObject jo, String key, String[] value) {
if (jo!=null) {
jo.remove(key);
JsonArray arr = new JsonArray();
if (ValidArrayOfString(value)) {
for(String ss : value) if (ValidString(ss)) arr.add(ss);
}
jo.add(key, arr);
}
}
public static void SetJsonObjectValue(JsonObject jo, String key, String value) {
if (jo!=null) {
jo.remove(key);
jo.addProperty(key, value);
}
}
/**
* Concat arrays of bytes
* @param arrays
* @return array of bytes
*/
public static byte[] ConcatBytes(byte[]... arrays) {
if (arrays!=null && arrays.length>0) {
try(ByteArrayOutputStream os = new ByteArrayOutputStream()){
for(byte[] arr : arrays) {
if (arr != null && arr.length>0) os.write(arr);
}
return os.toByteArray();
} catch (IOException e) {
System.out.println("ConcatBytes exception = "+e.getMessage());
}
}
return new byte[0];
}
/**
* Concat two arrays of bytes
* @param a
* @param b
* @return array of bytes
*/
public static byte[] ConcatBytes(byte[] a, byte[] b) {
int alen = ValidArrayOfBytes(a) ? a.length : 0;
int blen = ValidArrayOfBytes(b) ? b.length : 0;
if (blen>0) {
byte[] result = new byte[alen+blen];
System.arraycopy(a, 0, result, 0, alen);
System.arraycopy(b, 0, result, alen, blen);
return result;
} else return a; // gak ada b, gak ada yang mau ditambah
}
public static String ConcatListOfString(List<String> rr) {
if (rr!=null && rr.size()>0) {
StringBuilder str = new StringBuilder();
rr.forEach(ss -> str.append(ss));
return str.toString();
}
return null;
}
public static String ConcatString(String...strings) {
if (strings!=null && strings.length>0) {
StringBuilder str = new StringBuilder();
for(String ss : strings) {
str.append(ss);
}
return str.toString();
}
return null;
}
/**
* Create two level Command for Axis
* format : { cmd : { key : value} }
* @param cmd Command
* @param key Key
* @param value Value
* @return JsonObject if valid, or null if invalid
*/
public static JsonObject Create_2Level_Command(String cmd, String key, String value) {
if (ValidString(cmd)) {
if (ValidString(key)) {
if (ValidString(value)) {
JsonObject ja = new JsonObject();
JsonObject jb = new JsonObject();
jb.addProperty(key, value);
ja.add(cmd, jb);
return ja;
}
}
}
return null;
}
public static JsonObject Create_2Level_Command(String cmd, String key, JsonObject value) {
if (ValidString(cmd)) {
if (ValidString(key)) {
if (value!=null) {
JsonObject ja = new JsonObject();
JsonObject jb = new JsonObject();
jb.add(key, value);
ja.add(cmd, jb);
return ja;
}
}
}
return null;
}
public static JsonObject Create_2Level_Command(String cmd, String key, JsonArray value) {
if (ValidString(cmd)) {
if (ValidString(key)) {
if (value!=null) {
JsonObject ja = new JsonObject();
JsonObject jb = new JsonObject();
jb.add(key, value);
ja.add(cmd, jb);
return ja;
}
}
}
return null;
}
public static JsonObject Create_1Level_Command(String command, String value) {
if (ValidString(command)) {
JsonObject ja = new JsonObject();
if (ValidString(value)) {
ja.addProperty(command, value);
return ja;
}
}
return null;
}
public static JsonObject Create_1Level_Command(String command, JsonObject value) {
if (ValidString(command)) {
if (value!=null) {
JsonObject ja = new JsonObject();
ja.add(command, value);
return ja;
}
}
return null;
}
/**
* Extract Library from Resource path to current folder
* @param currentfolder path to extract
* @param libname library name
* @return absolute path of extracted library, or return IOException message
*/
public static String extract_library(String currentfolder, String libname) {
try {
File existinglib = new File(currentfolder, System.mapLibraryName(libname));
File xx = Native.extractFromResourcePath(libname);
if (existinglib.exists() && existinglib.isFile() && existinglib.length()==xx.length()) {
xx.delete(); // gak perlu
return existinglib.getAbsolutePath();
} else {
if (xx.renameTo(existinglib)) {
return existinglib.getAbsolutePath();
} else return xx.getAbsolutePath();
}
} catch (IOException e) {
return MessageFormat.format("IOException Libname={0} Message={1}", libname, e.getMessage());
}
}
public static boolean Strings_Valid_and_Match(String str1, String str2) {
if (ValidString(str1)) {
if (ValidString(str2)) {
return str1.equals(str2);
}
}
return false;
}
public static void Sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
}
}
public static void Sleep(int sec) {
try {
Thread.sleep(sec * 1000);
}catch (InterruptedException e) {
}
}
/**
* Check if ByteBuffer ready
* Ready means not null, and capacity() not zero
* @param bx ByteBuffer to check
* @return true if bytebuffer ready
*/
public static boolean ByteBufferReady(ByteBuffer bx) {
if (bx!=null) {
return bx.capacity()!=0;
}
return false;
}
/**
* Check if ByteBuffer in Write Mode
* Write Mode : limit = capacity
* @param bx ByteBuffer to check
* @return true if in write mode
*/
public static boolean IsWriteMode(ByteBuffer bx) {
if (ByteBufferReady(bx)) {
return bx.limit()==bx.capacity();
}
return false;
}
/**
* Check if ByteBuffer in Read Mode
* Read Mode : limit != capacity
* @param bx ByteBuffer to check
* @return true if in read mode
*/
public static boolean IsReadMode(ByteBuffer bx) {
if (ByteBufferReady(bx)) {
return bx.limit()!=bx.capacity();
}
return false;
}
/**
* Change to Write Mode
* @param bx ByteBuffer to change
* @return true if success
*/
public static boolean ChangeToWriteMode(ByteBuffer bx) {
if (ByteBufferReady(bx)) {
bx.compact();
}
return false;
}
/**
* Change to Read Mode
* @param bx ByteBuffer to change
* @return true if success
*/
public static boolean ChangeToReadMode(ByteBuffer bx) {
if (ByteBufferReady(bx)) {
bx.flip();
return true;
}
return false;
}
/**
* How much byte space available to write
* Available : limit - position (remaining)
* @param bx ByteBuffer to check
* @return 0 if not available or wrong mode
*/
public static int AvailableToWrite(ByteBuffer bx) {
if (IsWriteMode(bx)) {
return bx.remaining();
}
return 0;
}
/**
* How Much byte space available to Read
* Available : limit - position (remaining)
* @param bx ByteBuffer to check
* @return 0 if not available or wrong mode
*/
public static int AvailableToRead(ByteBuffer bx) {
if (IsReadMode(bx)) {
return bx.remaining();
}
return 0;
}
/**
* Get SIP Header Value
* @param head SipHeaders to get
* @param headername header name to get
* @return null if failed
*/
public static String GetSIPHeaderValue(SipHeaders head, String headername) {
if (head!=null) {
SipHeaderFieldName _fn = new SipHeaderFieldName(headername);
if (head.contains(_fn)) {
SipHeaderFieldValue _fv = head.get(_fn);
if (_fv!=null) {
return _fv.getValue();
}
}
}
return null;
}
}

1145
src/code/jSIPClient.java Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/linux-arm/libbass.so Normal file

Binary file not shown.

Binary file not shown.

BIN
src/linux-arm/libbassenc.so Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/linux-arm/libbassmix.so Normal file

Binary file not shown.

BIN
src/linux-x86-64/libbass.so Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/linux-x86/libbass.so Normal file

Binary file not shown.

Binary file not shown.

BIN
src/linux-x86/libbassenc.so Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/linux-x86/libbassmix.so Normal file

Binary file not shown.

View File

@@ -0,0 +1,53 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package net.sourceforge.peers;
import java.net.InetAddress;
import net.sourceforge.peers.media.MediaMode;
import net.sourceforge.peers.sip.syntaxencoding.SipURI;
public interface Config {
public void save();
public InetAddress getLocalInetAddress();
public InetAddress getPublicInetAddress();
public String getUserPart();
public String getDomain();
public String getPassword();
public SipURI getOutboundProxy();
public int getSipPort();
public MediaMode getMediaMode();
public boolean isMediaDebug();
public String getMediaFile();
public int getRtpPort();
public void setLocalInetAddress(InetAddress inetAddress);
public void setPublicInetAddress(InetAddress inetAddress);
public void setUserPart(String userPart);
public void setDomain(String domain);
public void setPassword(String password);
public void setOutboundProxy(SipURI outboundProxy);
public void setSipPort(int sipPort);
public void setMediaMode(MediaMode mediaMode);
public void setMediaDebug(boolean mediaDebug);
public void setMediaFile(String mediaFile);
public void setRtpPort(int rtpPort);
}

View File

@@ -0,0 +1,129 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007-2013 Yohann Martineau
*/
package net.sourceforge.peers;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import net.sourceforge.peers.sip.Utils;
public class FileLogger implements Logger {
public final static String LOG_FILE = File.separator + "logs"
+ File.separator + "peers.log";
public final static String NETWORK_FILE = File.separator + "logs"
+ File.separator + "transport.log";
private PrintWriter logWriter;
private PrintWriter networkWriter;
private Object logMutex;
private Object networkMutex;
private SimpleDateFormat logFormatter;
private SimpleDateFormat networkFormatter;
public FileLogger(String peersHome) {
if (peersHome == null) {
peersHome = Utils.DEFAULT_PEERS_HOME;
}
try {
logWriter = new PrintWriter(new BufferedWriter(
new FileWriter(peersHome + LOG_FILE)));
networkWriter = new PrintWriter(new BufferedWriter(
new FileWriter(peersHome + NETWORK_FILE)));
} catch (IOException e) {
System.out.println("logging to stdout");
logWriter = new PrintWriter(System.out);
networkWriter = new PrintWriter(System.out);
}
logMutex = new Object();
networkMutex = new Object();
logFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
networkFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
}
@Override
public final void debug(String message) {
synchronized (logMutex) {
logWriter.write(genericLog(message.toString(), "DEBUG"));
logWriter.flush();
}
}
@Override
public final void info(String message) {
synchronized (logMutex) {
logWriter.write(genericLog(message.toString(), "INFO "));
logWriter.flush();
}
}
@Override
public final void error(String message) {
synchronized (logMutex) {
logWriter.write(genericLog(message.toString(), "ERROR"));
logWriter.flush();
}
}
@Override
public final void error(String message, Exception exception) {
synchronized (logMutex) {
logWriter.write(genericLog(message, "ERROR"));
exception.printStackTrace(logWriter);
logWriter.flush();
}
}
private final String genericLog(String message, String level) {
StringBuffer buf = new StringBuffer();
buf.append(logFormatter.format(new Date()));
buf.append(" ");
buf.append(level);
buf.append(" [");
buf.append(Thread.currentThread().getName());
buf.append("] ");
buf.append(message);
buf.append("\n");
return buf.toString();
}
@Override
public final void traceNetwork(String message, String direction) {
synchronized (networkMutex) {
StringBuffer buf = new StringBuffer();
buf.append(networkFormatter.format(new Date()));
buf.append(" ");
buf.append(direction);
buf.append(" [");
buf.append(Thread.currentThread().getName());
buf.append("]\n\n");
buf.append(message);
buf.append("\n");
networkWriter.write(buf.toString());
networkWriter.flush();
}
}
}

View File

@@ -0,0 +1,156 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2012 Yohann Martineau
*/
package net.sourceforge.peers;
import java.net.InetAddress;
import net.sourceforge.peers.media.MediaMode;
import net.sourceforge.peers.sip.syntaxencoding.SipURI;
public class JavaConfig implements Config {
private InetAddress localInetAddress;
private InetAddress publicInetAddress;
private String userPart;
private String domain;
private String password;
private SipURI outboundProxy;
private int sipPort;
private MediaMode mediaMode;
private boolean mediaDebug;
private String mediaFile;
private int rtpPort;
@Override
public void save() {
throw new RuntimeException("not implemented");
}
@Override
public InetAddress getLocalInetAddress() {
return localInetAddress;
}
@Override
public InetAddress getPublicInetAddress() {
return publicInetAddress;
}
@Override
public String getUserPart() {
return userPart;
}
@Override
public String getDomain() {
return domain;
}
@Override
public String getPassword() {
return password;
}
@Override
public SipURI getOutboundProxy() {
return outboundProxy;
}
@Override
public int getSipPort() {
return sipPort;
}
@Override
public MediaMode getMediaMode() {
return mediaMode;
}
@Override
public boolean isMediaDebug() {
return mediaDebug;
}
@Override
public int getRtpPort() {
return rtpPort;
}
@Override
public void setLocalInetAddress(InetAddress inetAddress) {
localInetAddress = inetAddress;
}
@Override
public void setPublicInetAddress(InetAddress inetAddress) {
publicInetAddress = inetAddress;
}
@Override
public void setUserPart(String userPart) {
this.userPart = userPart;
}
@Override
public void setDomain(String domain) {
this.domain = domain;
}
@Override
public void setPassword(String password) {
this.password = password;
}
@Override
public void setOutboundProxy(SipURI outboundProxy) {
this.outboundProxy = outboundProxy;
}
@Override
public void setSipPort(int sipPort) {
this.sipPort = sipPort;
}
@Override
public void setMediaMode(MediaMode mediaMode) {
this.mediaMode = mediaMode;
}
@Override
public void setMediaDebug(boolean mediaDebug) {
this.mediaDebug = mediaDebug;
}
@Override
public void setRtpPort(int rtpPort) {
this.rtpPort = rtpPort;
}
@Override
public String getMediaFile() {
return mediaFile;
}
@Override
public void setMediaFile(String mediaFile) {
this.mediaFile = mediaFile;
}
}

View File

@@ -0,0 +1,30 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2013 Yohann Martineau
*/
package net.sourceforge.peers;
public interface Logger {
public void debug(String message);
public void info(String message);
public void error(String message);
public void error(String message, Exception exception);
public void traceNetwork(String message, String direction);
}

View File

@@ -0,0 +1,361 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010-2013 Yohann Martineau
*/
package net.sourceforge.peers;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import net.sourceforge.peers.media.MediaMode;
import net.sourceforge.peers.sip.RFC3261;
import net.sourceforge.peers.sip.syntaxencoding.SipURI;
import net.sourceforge.peers.sip.syntaxencoding.SipUriSyntaxException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class XmlConfig implements Config {
public final static int RTP_DEFAULT_PORT = 8000;
private Logger logger;
private File file;
private Document document;
// persistent variables
private InetAddress localInetAddress;
private String userPart;
private String domain;
private String password;
private SipURI outboundProxy;
private int sipPort;
private MediaMode mediaMode;
private boolean mediaDebug;
private String mediaFile;
private int rtpPort;
// corresponding DOM nodes
private Node ipAddressNode;
private Node userPartNode;
private Node domainNode;
private Node passwordNode;
private Node outboundProxyNode;
private Node sipPortNode;
private Node mediaModeNode;
private Node mediaDebugNode;
private Node mediaFileNode;
private Node rtpPortNode;
// non-persistent variables
private InetAddress publicInetAddress;
//private InetAddress
public XmlConfig(String fileName, Logger logger) {
file = new File(fileName);
this.logger = logger;
if (!file.exists()) {
if (logger!=null) logger.debug("config file " + fileName + " not found");
return;
}
DocumentBuilderFactory documentBuilderFactory =
DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder;
try {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
if (logger!=null) logger.error("parser configuration exception", e);
return;
}
try {
document = documentBuilder.parse(file);
} catch (SAXException e) {
if (logger!=null) logger.error("cannot parse " + fileName,e );
return;
} catch (IOException e) {
if (logger!=null) logger.error("IOException", e);
return;
}
Element documentElement = document.getDocumentElement();
ipAddressNode = getFirstChild(documentElement, "ipAddress");
String address = ipAddressNode.getTextContent();
try {
if (isNullOrEmpty(ipAddressNode)) {
localInetAddress = InetAddress.getLocalHost();
} else {
localInetAddress = InetAddress.getByName(address);
}
} catch (UnknownHostException e) {
if (logger!=null) logger.error("unknown host: " + address, e);
}
userPartNode = getFirstChild(documentElement, "userPart");
if (isNullOrEmpty(userPartNode)) {
if (logger!=null) logger.error("userpart not found in configuration file");
} else {
userPart = userPartNode.getTextContent();
}
domainNode = getFirstChild(documentElement, "domain");
if (isNullOrEmpty(domainNode)) {
if (logger!=null) logger.error("domain not found in configuration file");
} else {
domain = domainNode.getTextContent();
}
passwordNode = getFirstChild(documentElement, "password");
if (!isNullOrEmpty(passwordNode)) {
password = passwordNode.getTextContent();
}
outboundProxyNode = getFirstChild(documentElement, "outboundProxy");
if (!isNullOrEmpty(outboundProxyNode)) {
String uri = outboundProxyNode.getTextContent();
try {
outboundProxy = new SipURI(uri);
} catch (SipUriSyntaxException e) {
if (logger!=null) logger.error("sip uri syntax exception: " + uri, e);
}
}
sipPortNode = getFirstChild(documentElement, "sipPort");
if (isNullOrEmpty(sipPortNode)) {
sipPort = RFC3261.TRANSPORT_DEFAULT_PORT;
} else {
sipPort = Integer.parseInt(sipPortNode.getTextContent());
}
mediaModeNode = getFirstChild(documentElement, "mediaMode");
if (isNullOrEmpty(mediaModeNode)) {
mediaMode = MediaMode.captureAndPlayback;
} else {
mediaMode = MediaMode.valueOf(mediaModeNode.getTextContent());
}
mediaDebugNode = getFirstChild(documentElement, "mediaDebug");
if (isNullOrEmpty(mediaDebugNode)) {
mediaDebug = false;
} else {
mediaDebug = Boolean.parseBoolean(mediaDebugNode.getTextContent());
}
mediaFileNode = getFirstChild(documentElement, "mediaFile");
if (!isNullOrEmpty(mediaFileNode)) {
mediaFile = mediaFileNode.getTextContent();
}
if (mediaMode == MediaMode.file) {
if (mediaFile == null || "".equals(mediaFile.trim())) {
if (logger!=null) logger.error("streaming from file but no file provided");
}
}
rtpPortNode = getFirstChild(documentElement, "rtpPort");
if (isNullOrEmpty(rtpPortNode)) {
rtpPort = RTP_DEFAULT_PORT;
} else {
rtpPort = Integer.parseInt(rtpPortNode.getTextContent());
if (rtpPort % 2 != 0) {
if (logger!=null) logger.error("rtp port provided is " + rtpPort
+ " rtp port must be even");
}
}
}
private boolean isNullOrEmpty(Node node) {
return node == null || "".equals(node.getTextContent().trim());
}
private Node getFirstChild(Node parent, String childName) {
if (parent == null || childName == null) {
return null;
}
NodeList nodeList = parent.getChildNodes();
for (int i = 0; i < nodeList.getLength(); ++i) {
Node node = nodeList.item(i);
if (childName.equals(node.getNodeName())) {
return node;
}
}
return null;
}
@Override
public void save() {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer;
try {
transformer = transformerFactory.newTransformer();
} catch (TransformerConfigurationException e) {
if (logger!=null) logger.error("cannot create transformer", e);
return;
}
FileWriter fileWriter;
try {
fileWriter = new FileWriter(file);
} catch (IOException e) {
if (logger!=null) logger.error("cannot create file writer", e);
return;
}
StreamResult streamResult = new StreamResult(fileWriter);
DOMSource domSource = new DOMSource(document);
try {
transformer.transform(domSource, streamResult);
} catch (TransformerException e) {
if (logger!=null) logger.error("cannot save config file", e);
return;
}
if (logger!=null) logger.debug("config file saved");
}
@Override
public InetAddress getLocalInetAddress() {
return localInetAddress;
}
@Override
public InetAddress getPublicInetAddress() {
return publicInetAddress;
}
@Override
public String getUserPart() {
return userPart;
}
@Override
public String getDomain() {
return domain;
}
@Override
public String getPassword() {
return password;
}
@Override
public SipURI getOutboundProxy() {
return outboundProxy;
}
@Override
public int getSipPort() {
return sipPort;
}
@Override
public MediaMode getMediaMode() {
return mediaMode;
}
@Override
public boolean isMediaDebug() {
return mediaDebug;
}
@Override
public int getRtpPort() {
return rtpPort;
}
@Override
public void setLocalInetAddress(InetAddress inetAddress) {
this.localInetAddress = inetAddress;
ipAddressNode.setTextContent(inetAddress.getHostAddress());
}
@Override
public void setPublicInetAddress(InetAddress inetAddress) {
this.publicInetAddress = inetAddress;
}
@Override
public void setUserPart(String userPart) {
this.userPart = userPart;
userPartNode.setTextContent(userPart);
}
@Override
public void setDomain(String domain) {
this.domain = domain;
domainNode.setTextContent(domain);
}
@Override
public void setPassword(String password) {
this.password = password;
passwordNode.setTextContent(password);
}
@Override
public void setOutboundProxy(SipURI outboundProxy) {
this.outboundProxy = outboundProxy;
if (outboundProxy == null) {
outboundProxyNode.setTextContent("");
} else {
outboundProxyNode.setTextContent(outboundProxy.toString());
}
}
@Override
public void setSipPort(int sipPort) {
this.sipPort = sipPort;
sipPortNode.setTextContent(Integer.toString(sipPort));
}
@Override
public void setMediaMode(MediaMode mediaMode) {
this.mediaMode = mediaMode;
mediaModeNode.setTextContent(mediaMode.toString());
}
@Override
public void setMediaDebug(boolean mediaDebug) {
this.mediaDebug = mediaDebug;
mediaDebugNode.setTextContent(Boolean.toString(mediaDebug));
}
@Override
public void setRtpPort(int rtpPort) {
this.rtpPort = rtpPort;
rtpPortNode.setTextContent(Integer.toString(rtpPort));
}
@Override
public String getMediaFile() {
return mediaFile;
}
@Override
public void setMediaFile(String mediaFile) {
this.mediaFile = mediaFile;
}
}

View File

@@ -0,0 +1,214 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010, 2011, 2012 Yohann Martineau
*/
package net.sourceforge.peers.javaxsound;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;
import net.sourceforge.peers.Logger;
import net.sourceforge.peers.media.AbstractSoundManager;
import net.sourceforge.peers.sip.Utils;
public class JavaxSoundManager extends AbstractSoundManager {
private AudioFormat audioFormat;
private TargetDataLine targetDataLine;
private SourceDataLine sourceDataLine;
private DataLine.Info targetInfo;
private DataLine.Info sourceInfo;
private FileOutputStream microphoneOutput;
private FileOutputStream speakerInput;
private boolean mediaDebug;
private Logger logger;
private String peersHome;
public JavaxSoundManager(boolean mediaDebug, Logger logger, String peersHome) {
this.mediaDebug = mediaDebug;
this.logger = logger;
this.peersHome = peersHome;
if (peersHome == null) {
this.peersHome = Utils.DEFAULT_PEERS_HOME;
}
// linear PCM 8kHz, 16 bits signed, mono-channel, little endian
audioFormat = new AudioFormat(8000, 16, 1, true, false);
targetInfo = new DataLine.Info(TargetDataLine.class, audioFormat);
sourceInfo = new DataLine.Info(SourceDataLine.class, audioFormat);
}
@Override
public void init() {
if (logger!=null) logger.debug("openAndStartLines");
if (mediaDebug) {
SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
String date = simpleDateFormat.format(new Date());
StringBuffer buf = new StringBuffer();
buf.append(peersHome).append(File.separator);
buf.append(MEDIA_DIR).append(File.separator);
buf.append(date).append("_");
buf.append(audioFormat.getEncoding()).append("_");
buf.append(audioFormat.getSampleRate()).append("_");
buf.append(audioFormat.getSampleSizeInBits()).append("_");
buf.append(audioFormat.getChannels()).append("_");
buf.append(audioFormat.isBigEndian() ? "be" : "le");
try {
microphoneOutput = new FileOutputStream(buf.toString()
+ "_microphone.output");
speakerInput = new FileOutputStream(buf.toString()
+ "_speaker.input");
} catch (FileNotFoundException e) {
if (logger!=null) if (logger!=null) logger.error("cannot create file", e);
return;
}
}
// AccessController.doPrivileged added for plugin compatibility
AccessController.doPrivileged(
new PrivilegedAction<Void>() {
@Override
public Void run() {
try {
targetDataLine = (TargetDataLine) AudioSystem.getLine(targetInfo);
targetDataLine.open(audioFormat);
} catch (LineUnavailableException e) {
if (logger!=null) if (logger!=null) logger.error("target line unavailable", e);
return null;
} catch (SecurityException e) {
if (logger!=null) if (logger!=null) logger.error("security exception", e);
return null;
} catch (Throwable t) {
if (logger!=null) if (logger!=null) logger.error("throwable " + t.getMessage());
return null;
}
targetDataLine.start();
try {
sourceDataLine = (SourceDataLine) AudioSystem.getLine(sourceInfo);
sourceDataLine.open(audioFormat);
} catch (LineUnavailableException e) {
if (logger!=null) if (logger!=null) logger.error("source line unavailable", e);
return null;
}
sourceDataLine.start();
return null;
}
});
}
@Override
public synchronized void close() {
if (logger!=null) logger.debug("closeLines");
if (microphoneOutput != null) {
try {
microphoneOutput.close();
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("cannot close file", e);
}
microphoneOutput = null;
}
if (speakerInput != null) {
try {
speakerInput.close();
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("cannot close file", e);
}
speakerInput = null;
}
// AccessController.doPrivileged added for plugin compatibility
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
if (targetDataLine != null) {
targetDataLine.close();
targetDataLine = null;
}
if (sourceDataLine != null) {
sourceDataLine.drain();
sourceDataLine.stop();
sourceDataLine.close();
sourceDataLine = null;
}
return null;
}
});
}
@Override
public synchronized byte[] readData() {
if (targetDataLine == null) {
return null;
}
int ready = targetDataLine.available();
while (ready == 0) {
try {
Thread.sleep(2);
ready = targetDataLine.available();
} catch (InterruptedException e) {
return null;
}
}
if (ready <= 0) {
return null;
}
byte[] buffer = new byte[ready];
targetDataLine.read(buffer, 0, buffer.length);
if (mediaDebug) {
try {
microphoneOutput.write(buffer, 0, buffer.length);
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("cannot write to file", e);
return null;
}
}
return buffer;
}
@Override
public int writeData(byte[] buffer, int offset, int length) {
int numberOfBytesWritten = sourceDataLine.write(buffer, offset, length);
if (mediaDebug) {
try {
speakerInput.write(buffer, offset, numberOfBytesWritten);
} catch (IOException e) {
if (logger!=null) logger.error("cannot write to file", e);
return -1;
}
}
return numberOfBytesWritten;
}
}

View File

@@ -0,0 +1,28 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2013 Yohann Martineau
*/
package net.sourceforge.peers.media;
public abstract class AbstractSoundManager implements SoundSource {
public final static String MEDIA_DIR = "media";
public abstract void init();
public abstract void close();
public abstract int writeData(byte[] buffer, int offset, int length);
}

View File

@@ -0,0 +1,79 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2008, 2009, 2010, 2011 Yohann Martineau
*/
package net.sourceforge.peers.media;
import java.io.IOException;
import java.io.PipedOutputStream;
import java.util.concurrent.CountDownLatch;
import net.sourceforge.peers.Logger;
public class Capture implements Runnable {
public static final int SAMPLE_SIZE = 16;
public static final int BUFFER_SIZE = SAMPLE_SIZE * 20;
private PipedOutputStream rawData;
private boolean isStopped;
private SoundSource soundSource;
private Logger logger;
private CountDownLatch latch;
public Capture(PipedOutputStream rawData, SoundSource soundSource,
Logger logger, CountDownLatch latch) {
this.rawData = rawData;
this.soundSource = soundSource;
this.logger = logger;
this.latch = latch;
isStopped = false;
}
public void run() {
byte[] buffer;
while (!isStopped) {
buffer = soundSource.readData();
try {
if (buffer == null) {
break;
}
rawData.write(buffer);
rawData.flush();
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("input/output error", e);
return;
}
}
latch.countDown();
if (latch.getCount() != 0) {
try {
latch.await();
} catch (InterruptedException e) {
if (logger!=null) if (logger!=null) logger.error("interrupt exception", e);
}
}
}
public synchronized void setStopped(boolean isStopped) {
this.isStopped = isStopped;
}
}

View File

@@ -0,0 +1,129 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007-2013 Yohann Martineau
*/
package net.sourceforge.peers.media;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.CountDownLatch;
import net.sourceforge.peers.Logger;
import net.sourceforge.peers.rtp.RFC3551;
import net.sourceforge.peers.rtp.RtpSession;
import net.sourceforge.peers.sdp.Codec;
public class CaptureRtpSender {
public static final int PIPE_SIZE = 4096;
private RtpSession rtpSession;
private Capture capture;
private Encoder encoder;
private RtpSender rtpSender;
public CaptureRtpSender(RtpSession rtpSession, SoundSource soundSource,
boolean mediaDebug, Codec codec, Logger logger, String peersHome)
throws IOException {
super();
this.rtpSession = rtpSession;
// the use of PipedInputStream and PipedOutputStream in Capture,
// Encoder and RtpSender imposes a synchronization point at the
// end of life of those threads to a void read end dead exceptions
CountDownLatch latch = new CountDownLatch(3);
PipedOutputStream rawDataOutput = new PipedOutputStream();
PipedInputStream rawDataInput;
try {
rawDataInput = new PipedInputStream(rawDataOutput, PIPE_SIZE);
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("input/output error", e);
return;
}
PipedOutputStream encodedDataOutput = new PipedOutputStream();
PipedInputStream encodedDataInput;
try {
encodedDataInput = new PipedInputStream(encodedDataOutput,
PIPE_SIZE);
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("input/output error");
rawDataInput.close();
return;
}
capture = new Capture(rawDataOutput, soundSource, logger, latch);
switch (codec.getPayloadType()) {
case RFC3551.PAYLOAD_TYPE_PCMU:
encoder = new PcmuEncoder(rawDataInput, encodedDataOutput,
mediaDebug, logger, peersHome, latch);
break;
case RFC3551.PAYLOAD_TYPE_PCMA:
encoder = new PcmaEncoder(rawDataInput, encodedDataOutput,
mediaDebug, logger, peersHome, latch);
break;
default:
encodedDataInput.close();
rawDataInput.close();
throw new RuntimeException("unknown payload type");
}
rtpSender = new RtpSender(encodedDataInput, rtpSession, mediaDebug,
codec, logger, peersHome, latch);
}
public void start() throws IOException {
capture.setStopped(false);
encoder.setStopped(false);
rtpSender.setStopped(false);
Thread captureThread = new Thread(capture,
Capture.class.getSimpleName());
Thread encoderThread = new Thread(encoder,
Encoder.class.getSimpleName());
Thread rtpSenderThread = new Thread(rtpSender,
RtpSender.class.getSimpleName());
captureThread.start();
encoderThread.start();
rtpSenderThread.start();
}
public void stop() {
if (capture != null) {
capture.setStopped(true);
}
if (encoder != null) {
encoder.setStopped(true);
}
if (rtpSender != null) {
rtpSender.setStopped(true);
}
}
public synchronized RtpSession getRtpSession() {
return rtpSession;
}
public RtpSender getRtpSender() {
return rtpSender;
}
}

View File

@@ -0,0 +1,26 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package net.sourceforge.peers.media;
public abstract class Decoder {
public abstract byte[] process(byte[] media);
}

View File

@@ -0,0 +1,95 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package net.sourceforge.peers.media;
import java.util.ArrayList;
import java.util.List;
import net.sourceforge.peers.rtp.RFC4733;
import net.sourceforge.peers.rtp.RtpPacket;
public class DtmfFactory {
public List<RtpPacket> createDtmfPackets(char digit) {
List<RtpPacket> packets = new ArrayList<RtpPacket>();
byte[] data = new byte[4];
// RFC4733
if (digit == '*') {
data[0] = 10;
} else if (digit == '#') {
data[0] = 11;
} else if (digit >= 'A' && digit <= 'D') {
data[0] = (byte) (digit - 53);
} else {
data[0] = (byte) (digit - 48);
}
data[1] = 10; // volume 10
// Set Duration to 160
// duration 8 bits
data[2] = 0;
// duration 8 bits
data[3] = -96;
RtpPacket rtpPacket = new RtpPacket();
rtpPacket.setData(data);
rtpPacket.setPayloadType(RFC4733.PAYLOAD_TYPE_TELEPHONE_EVENT);
rtpPacket.setMarker(true);
packets.add(rtpPacket);
// two classical packets
rtpPacket = new RtpPacket();
// set duration to 320
data = data.clone();
data[2] = 1;
data[3] = 64;
rtpPacket.setData(data);
rtpPacket.setMarker(false);
rtpPacket.setPayloadType(RFC4733.PAYLOAD_TYPE_TELEPHONE_EVENT);
packets.add(rtpPacket);
rtpPacket = new RtpPacket();
// set duration to 320
data = data.clone();
data[2] = 1;
data[3] = -32;
rtpPacket.setData(data);
rtpPacket.setMarker(false);
rtpPacket.setPayloadType(RFC4733.PAYLOAD_TYPE_TELEPHONE_EVENT);
packets.add(rtpPacket);
data = data.clone();
// create three end event packets
data[1] = -0x76; // end event flag + volume set to 10
// set Duration to 640
data[2] = 2; // duration 8 bits
data[3] = -128; // duration 8 bits
for (int r = 0; r < 3; r++) {
rtpPacket = new RtpPacket();
rtpPacket.setData(data);
rtpPacket.setMarker(false);
rtpPacket.setPayloadType(RFC4733.PAYLOAD_TYPE_TELEPHONE_EVENT);
packets.add(rtpPacket);
}
return packets;
}
}

View File

@@ -0,0 +1,79 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009, 2010, 2012 Yohann Martineau
*/
package net.sourceforge.peers.media;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import net.sourceforge.peers.Logger;
public class Echo implements Runnable {
public static final int BUFFER_SIZE = 2048;
private DatagramSocket datagramSocket;
private InetAddress remoteAddress;
private int remotePort;
private boolean isRunning;
private Logger logger;
public Echo(DatagramSocket datagramSocket,
String remoteAddress, int remotePort, Logger logger)
throws UnknownHostException {
this.datagramSocket = datagramSocket;
this.remoteAddress = InetAddress.getByName(remoteAddress);
this.remotePort = remotePort;
this.logger = logger;
isRunning = true;
}
@Override
public void run() {
try {
while (isRunning) {
byte[] buf = new byte[BUFFER_SIZE];
DatagramPacket datagramPacket = new DatagramPacket(buf,
buf.length);
try {
datagramSocket.receive(datagramPacket);
} catch (SocketTimeoutException e) {
if (logger!=null) logger.debug("echo socket timeout");
continue;
}
datagramPacket = new DatagramPacket(buf,
datagramPacket.getLength(), remoteAddress, remotePort);
datagramSocket.send(datagramPacket);
}
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("input/output error", e);
} finally {
datagramSocket.close();
}
}
public synchronized void stop() {
isRunning = false;
}
}

View File

@@ -0,0 +1,148 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2008, 2009, 2010, 2011 Yohann Martineau
*/
package net.sourceforge.peers.media;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import net.sourceforge.peers.Logger;
public abstract class Encoder implements Runnable {
private PipedInputStream rawData;
private PipedOutputStream encodedData;
private boolean isStopped;
private FileOutputStream encoderOutput;
private FileOutputStream encoderInput;
private boolean mediaDebug;
private Logger logger;
private String peersHome;
private CountDownLatch latch;
public Encoder(PipedInputStream rawData, PipedOutputStream encodedData,
boolean mediaDebug, Logger logger, String peersHome,
CountDownLatch latch) {
this.rawData = rawData;
this.encodedData = encodedData;
this.mediaDebug = mediaDebug;
this.logger = logger;
this.peersHome = peersHome;
this.latch = latch;
isStopped = false;
}
public void run() {
byte[] buffer;
if (mediaDebug) {
SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
String date = simpleDateFormat.format(new Date());
String dir = peersHome + File.separator
+ AbstractSoundManager.MEDIA_DIR + File.separator;
String fileName = dir + date + "_g711_encoder.output";
try {
encoderOutput = new FileOutputStream(fileName);
fileName = dir + date + "_g711_encoder.input";
encoderInput = new FileOutputStream(fileName);
} catch (FileNotFoundException e) {
if (logger!=null) if (logger!=null) logger.error("cannot create file", e);
return;
}
}
int ready;
while (!isStopped) {
try {
ready = rawData.available();
while (ready == 0 && !isStopped) {
try {
Thread.sleep(2);
ready = rawData.available();
} catch (InterruptedException e) {
if (logger!=null) if (logger!=null) logger.error("interrupt exception", e);
}
}
if (isStopped) {
break;
}
buffer = new byte[ready];
rawData.read(buffer);
if (mediaDebug) {
try {
encoderInput.write(buffer);
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("cannot write to file", e);
}
}
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("input/output error", e);
return;
}
byte[] ulawData = process(buffer);
if (mediaDebug) {
try {
encoderOutput.write(ulawData);
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("cannot write to file", e);
break;
}
}
try {
encodedData.write(ulawData);
encodedData.flush();
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("input/output error", e);
return;
}
}
if (mediaDebug) {
try {
encoderOutput.close();
encoderInput.close();
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("cannot close file", e);
return;
}
}
latch.countDown();
if (latch.getCount() != 0) {
try {
latch.await();
} catch (InterruptedException e) {
if (logger!=null) if (logger!=null) logger.error("interrupt exception", e);
}
}
}
public synchronized void setStopped(boolean isStopped) {
this.isStopped = isStopped;
}
public abstract byte[] process(byte[] media);
}

View File

@@ -0,0 +1,96 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2012 Yohann Martineau
*/
package net.sourceforge.peers.media;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import net.sourceforge.peers.Logger;
// To create an audio file for peers, you can use audacity:
//
// Edit > Preferences
//
// - Peripherals
// - Channels: 1 (Mono)
// - Quality
// - Sampling frequency of default: 8000 Hz
// - Default Sample Format: 16 bits
//
// Validate
//
// Record audio
//
// File > Export
//
// - File name: test.raw
//
// Validate
public class FileReader implements SoundSource {
public final static int BUFFER_SIZE = 256;
private FileInputStream fileInputStream;
private Logger logger;
public FileReader(String fileName, Logger logger) {
this.logger = logger;
try {
fileInputStream = new FileInputStream(fileName);
} catch (FileNotFoundException e) {
if (logger!=null) if (logger!=null) logger.error("file not found: " + fileName, e);
}
}
public synchronized void close() {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("io exception", e);
}
fileInputStream = null;
}
}
@Override
public synchronized byte[] readData() {
if (fileInputStream == null) {
return null;
}
byte buffer[] = new byte[BUFFER_SIZE];
try {
if (fileInputStream.read(buffer) >= 0) {
Thread.sleep(15);
return buffer;
} else {
fileInputStream.close();
fileInputStream = null;
}
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("io exception", e);
} catch (InterruptedException e) {
if (logger!=null) logger.debug("file reader interrupted");
}
return null;
}
}

View File

@@ -0,0 +1,68 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package net.sourceforge.peers.media;
import java.io.IOException;
import net.sourceforge.peers.Logger;
import net.sourceforge.peers.rtp.RFC3551;
import net.sourceforge.peers.rtp.RtpListener;
import net.sourceforge.peers.rtp.RtpPacket;
import net.sourceforge.peers.rtp.RtpSession;
import net.sourceforge.peers.sdp.Codec;
public class IncomingRtpReader implements RtpListener {
private RtpSession rtpSession;
private AbstractSoundManager soundManager;
private Decoder decoder;
public IncomingRtpReader(RtpSession rtpSession,
AbstractSoundManager soundManager, Codec codec, Logger logger)
throws IOException {
if (logger!=null) logger.debug("playback codec:" + codec.toString().trim());
this.rtpSession = rtpSession;
this.soundManager = soundManager;
switch (codec.getPayloadType()) {
case RFC3551.PAYLOAD_TYPE_PCMU:
decoder = new PcmuDecoder();
break;
case RFC3551.PAYLOAD_TYPE_PCMA:
decoder = new PcmaDecoder();
break;
default:
throw new RuntimeException("unsupported payload type");
}
rtpSession.addRtpListener(this);
}
public void start() {
rtpSession.start();
}
@Override
public void receivedRtpPacket(RtpPacket rtpPacket) {
byte[] rawBuf = decoder.process(rtpPacket.getData());
if (soundManager != null) {
soundManager.writeData(rawBuf, 0, rawBuf.length);
}
}
}

View File

@@ -0,0 +1,340 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010-2013 Yohann Martineau
*/
package net.sourceforge.peers.media;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import code.BassFileReader;
import net.sourceforge.peers.Logger;
import net.sourceforge.peers.rtp.RtpPacket;
import net.sourceforge.peers.rtp.RtpSession;
import net.sourceforge.peers.sdp.Codec;
import net.sourceforge.peers.sip.core.useragent.UserAgent;
public class MediaManager {
public static final int DEFAULT_CLOCK = 8000; // Hz
private UserAgent userAgent;
private CaptureRtpSender captureRtpSender;
private IncomingRtpReader incomingRtpReader;
private RtpSession rtpSession;
private DtmfFactory dtmfFactory;
private Logger logger;
private DatagramSocket datagramSocket;
//private FileReader fileReader;
private BassFileReader fileReader;
public MediaManager(UserAgent userAgent, Logger logger) {
this.userAgent = userAgent;
this.logger = logger;
dtmfFactory = new DtmfFactory();
}
private void startRtpSessionOnSuccessResponse(String localAddress,
String remoteAddress, int remotePort, Codec codec,
SoundSource soundSource) {
InetAddress inetAddress;
try {
inetAddress = InetAddress.getByName(localAddress);
} catch (UnknownHostException e) {
if (logger!=null) if (logger!=null) logger.error("unknown host: " + localAddress, e);
return;
}
rtpSession = new RtpSession(inetAddress, datagramSocket,
userAgent.isMediaDebug(), logger, userAgent.getPeersHome());
try {
inetAddress = InetAddress.getByName(remoteAddress);
rtpSession.setRemoteAddress(inetAddress);
} catch (UnknownHostException e) {
if (logger!=null) if (logger!=null) logger.error("unknown host: " + remoteAddress, e);
}
rtpSession.setRemotePort(remotePort);
try {
captureRtpSender = new CaptureRtpSender(rtpSession,
soundSource, userAgent.isMediaDebug(), codec, logger,
userAgent.getPeersHome());
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("input/output error", e);
return;
}
try {
captureRtpSender.start();
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("input/output error", e);
}
}
public void successResponseReceived(String localAddress,
String remoteAddress, int remotePort, Codec codec) {
switch (userAgent.getMediaMode()) {
case captureAndPlayback:
AbstractSoundManager soundManager = userAgent.getSoundManager();
soundManager.init();
startRtpSessionOnSuccessResponse(localAddress, remoteAddress,
remotePort, codec, soundManager);
try {
incomingRtpReader = new IncomingRtpReader(
captureRtpSender.getRtpSession(), soundManager, codec,
logger);
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("input/output error", e);
return;
}
incomingRtpReader.start();
break;
case echo:
Echo echo;
try {
echo = new Echo(datagramSocket, remoteAddress, remotePort,
logger);
} catch (UnknownHostException e) {
if (logger!=null) if (logger!=null) logger.error("unknown host amongst "
+ localAddress + " or " + remoteAddress);
return;
}
userAgent.setEcho(echo);
Thread echoThread = new Thread(echo, Echo.class.getSimpleName());
echoThread.start();
break;
case file:
String fileName = userAgent.getConfig().getMediaFile();
//fileReader = new FileReader(fileName, logger);
fileReader = new BassFileReader(fileName,null);
startRtpSessionOnSuccessResponse(localAddress, remoteAddress,
remotePort, codec, fileReader);
try {
incomingRtpReader = new IncomingRtpReader(
captureRtpSender.getRtpSession(), null, codec,
logger);
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("input/output error", e);
return;
}
incomingRtpReader.start();
break;
case none:
default:
break;
}
}
private void startRtpSession(String destAddress, int destPort,
Codec codec, SoundSource soundSource) {
rtpSession = new RtpSession(userAgent.getConfig()
.getLocalInetAddress(), datagramSocket,
userAgent.isMediaDebug(), logger, userAgent.getPeersHome());
try {
InetAddress inetAddress = InetAddress.getByName(destAddress);
rtpSession.setRemoteAddress(inetAddress);
} catch (UnknownHostException e) {
if (logger!=null) if (logger!=null) logger.error("unknown host: " + destAddress, e);
}
rtpSession.setRemotePort(destPort);
try {
captureRtpSender = new CaptureRtpSender(rtpSession,
soundSource, userAgent.isMediaDebug(), codec, logger,
userAgent.getPeersHome());
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("input/output error", e);
return;
}
try {
captureRtpSender.start();
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("input/output error", e);
}
}
public void handleAck(String destAddress, int destPort, Codec codec) {
switch (userAgent.getMediaMode()) {
case captureAndPlayback:
AbstractSoundManager soundManager = userAgent.getSoundManager();
soundManager.init();
startRtpSession(destAddress, destPort, codec, soundManager);
try {
//FIXME RTP sessions can be different !
incomingRtpReader = new IncomingRtpReader(rtpSession,
soundManager, codec, logger);
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("input/output error", e);
return;
}
incomingRtpReader.start();
break;
case echo:
Echo echo;
try {
echo = new Echo(datagramSocket, destAddress, destPort, logger);
} catch (UnknownHostException e) {
if (logger!=null) if (logger!=null) logger.error("unknown host amongst "
+ userAgent.getConfig().getLocalInetAddress()
.getHostAddress() + " or " + destAddress);
return;
}
userAgent.setEcho(echo);
Thread echoThread = new Thread(echo, Echo.class.getSimpleName());
echoThread.start();
break;
case file:
if (fileReader != null) {
fileReader.close();
}
String fileName = userAgent.getConfig().getMediaFile();
//fileReader = new FileReader(fileName, logger);
fileReader = new BassFileReader(fileName, null);
startRtpSession(destAddress, destPort, codec, fileReader);
try {
incomingRtpReader = new IncomingRtpReader(rtpSession,
null, codec, logger);
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("input/output error", e);
return;
}
incomingRtpReader.start();
break;
case none:
default:
break;
}
}
public void updateRemote(String destAddress, int destPort, Codec codec) {
switch (userAgent.getMediaMode()) {
case captureAndPlayback:
try {
InetAddress inetAddress = InetAddress.getByName(destAddress);
rtpSession.setRemoteAddress(inetAddress);
} catch (UnknownHostException e) {
if (logger!=null) if (logger!=null) logger.error("unknown host: " + destAddress, e);
}
rtpSession.setRemotePort(destPort);
break;
case echo:
//TODO update echo socket
break;
case file:
try {
InetAddress inetAddress = InetAddress.getByName(destAddress);
rtpSession.setRemoteAddress(inetAddress);
} catch (UnknownHostException e) {
if (logger!=null) if (logger!=null) logger.error("unknown host: " + destAddress, e);
}
rtpSession.setRemotePort(destPort);
break;
default:
break;
}
}
public void sendDtmf(char digit) {
if (captureRtpSender != null) {
List<RtpPacket> rtpPackets = dtmfFactory.createDtmfPackets(digit);
RtpSender rtpSender = captureRtpSender.getRtpSender();
rtpSender.pushPackets(rtpPackets);
}
}
public void stopSession() {
if (rtpSession != null) {
rtpSession.stop();
while (!rtpSession.isSocketClosed()) {
try {
Thread.sleep(15);
} catch (InterruptedException e) {
if (logger!=null) logger.debug("sleep interrupted");
}
}
rtpSession = null;
}
if (incomingRtpReader != null) {
incomingRtpReader = null;
}
if (captureRtpSender != null) {
captureRtpSender.stop();
captureRtpSender = null;
}
if (datagramSocket != null) {
datagramSocket = null;
}
switch (userAgent.getMediaMode()) {
case captureAndPlayback:
AbstractSoundManager soundManager = userAgent.getSoundManager();
if (soundManager != null) {
soundManager.close();
}
break;
case echo:
Echo echo = userAgent.getEcho();
if (echo != null) {
echo.stop();
userAgent.setEcho(null);
}
break;
case file:
fileReader.close();
break;
default:
break;
}
}
public void setDatagramSocket(DatagramSocket datagramSocket) {
this.datagramSocket = datagramSocket;
}
public DatagramSocket getDatagramSocket() {
return datagramSocket;
}
// public FileReader getFileReader() {
// return fileReader;
// }
public BassFileReader getFileReader() {
return fileReader;
}
}

View File

@@ -0,0 +1,24 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package net.sourceforge.peers.media;
public enum MediaMode {
none, captureAndPlayback, echo, file
}

View File

@@ -0,0 +1,71 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Oleg Kulikov, Yohann Martineau
*/
package net.sourceforge.peers.media;
public class PcmaDecoder extends Decoder {
private static short aLawDecompressTable[] = new short[]{
-5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
-7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
-2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
-3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
-22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944,
-30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136,
-11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472,
-15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568,
-344, -328, -376, -360, -280, -264, -312, -296,
-472, -456, -504, -488, -408, -392, -440, -424,
-88, -72, -120, -104, -24, -8, -56, -40,
-216, -200, -248, -232, -152, -136, -184, -168,
-1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
-1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
-688, -656, -752, -720, -560, -528, -624, -592,
-944, -912, -1008, -976, -816, -784, -880, -848,
5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736,
7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784,
2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368,
3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392,
22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472,
15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
344, 328, 376, 360, 280, 264, 312, 296,
472, 456, 504, 488, 408, 392, 440, 424,
88, 72, 120, 104, 24, 8, 56, 40,
216, 200, 248, 232, 152, 136, 184, 168,
1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184,
1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696,
688, 656, 752, 720, 560, 528, 624, 592,
944, 912, 1008, 976, 816, 784, 880, 848
};
@Override
public byte[] process(byte[] media) {
byte[] res = new byte[media.length * 2];
int j = 0;
for (int i = 0; i < media.length; i++) {
short s = aLawDecompressTable[media[i] & 0xff];
res[j++] = (byte) s;
res[j++] = (byte) (s >> 8);
}
return res;
}
}

View File

@@ -0,0 +1,98 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010, 2011 Oleg Kulikov, Yohann Martineau
*/
package net.sourceforge.peers.media;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.CountDownLatch;
import net.sourceforge.peers.Logger;
public class PcmaEncoder extends Encoder {
private final static int cClip = 32635;
private static byte aLawCompressTable[] = new byte[]{
1, 1, 2, 2, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7
};
public PcmaEncoder(PipedInputStream rawData, PipedOutputStream encodedData,
boolean mediaDebug, Logger logger, String peersHome,
CountDownLatch latch) {
super(rawData, encodedData, mediaDebug, logger, peersHome, latch);
}
@Override
public byte[] process(byte[] media) {
byte[] compressed = new byte[media.length / 2];
int j = 0;
for (int i = 0; i < compressed.length; i++) {
short sample = (short) (((media[j++] & 0xff) | (media[j++]) << 8));
compressed[i] = linearToALawSample(sample);
}
return compressed;
}
/**
* Compress 16bit value to 8bit value
*
* @param sample 16-bit sample
* @return compressed 8-bit value.
*/
private byte linearToALawSample(short sample) {
int sign;
int exponent;
int mantissa;
int s;
sign = ((~sample) >> 8) & 0x80;
if (!(sign == 0x80)) {
sample = (short) -sample;
}
if (sample > cClip) {
sample = cClip;
}
if (sample >= 256) {
exponent = (int) aLawCompressTable[(sample >> 8) & 0x7F];
mantissa = (sample >> (exponent + 3)) & 0x0F;
s = (exponent << 4) | mantissa;
} else {
s = sample >> 4;
}
s ^= (sign ^ 0x55);
return (byte) s;
}
}

View File

@@ -0,0 +1,103 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Oleg Kulikov, Yohann Martineau
*/
package net.sourceforge.peers.media;
public class PcmuDecoder extends Decoder {
// private final static int cBias = 0x84;
// private int QUANT_MASK = 0xf;
// private final static int SEG_SHIFT = 4;
// private final static int SEG_MASK = 0x70;
// private final static int SIGN_BIT = 0x80;
private static short muLawDecompressTable[] = new short[]{
-32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956,
-23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764,
-15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412,
-11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316,
-7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
-5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
-3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
-2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
-1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
-1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
-876, -844, -812, -780, -748, -716, -684, -652,
-620, -588, -556, -524, -492, -460, -428, -396,
-372, -356, -340, -324, -308, -292, -276, -260,
-244, -228, -212, -196, -180, -164, -148, -132,
-120, -112, -104, -96, -88, -80, -72, -64,
-56, -48, -40, -32, -24, -16, -8, 0,
32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
876, 844, 812, 780, 748, 716, 684, 652,
620, 588, 556, 524, 492, 460, 428, 396,
372, 356, 340, 324, 308, 292, 276, 260,
244, 228, 212, 196, 180, 164, 148, 132,
120, 112, 104, 96, 88, 80, 72, 64,
56, 48, 40, 32, 24, 16, 8, 0
};
@Override
public byte[] process(byte[] media) {
byte[] res = new byte[media.length * 2];
int j = 0;
for (int i = 0; i < media.length; i++) {
short s = muLawDecompressTable[media[i] & 0xff];
res[j++] = (byte) s;
res[j++] = (byte) (s >> 8);
}
return res;
}
//TODO compare what's the fastest: table lookup or real conversion
/*
* ulaw2linear() - Convert a u-law value to 16-bit linear PCM
*
* First, a biased linear code is derived from the code word. An unbiased
* output can then be obtained by subtracting 33 from the biased code.
*
* Note that this function expects to be passed the complement of the
* original code word. This is in keeping with ISDN conventions.
*/
// private short ulaw2linear(byte u_val) {
// int t;
//
// /* Complement to obtain normal u-law value. */
// u_val = (byte) ~u_val;
//
// /*
// * Extract and bias the quantization bits. Then
// * shift up by the segment number and subtract out the bias.
// */
// t = ((u_val & QUANT_MASK) << 3) + cBias;
// t <<= (u_val & SEG_MASK) >> SEG_SHIFT;
//
// boolean s = (u_val & SIGN_BIT) == SIGN_BIT;
// return (short) (s ? (cBias - t) : (t - cBias));
// }
}

View File

@@ -0,0 +1,130 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010, 2011 Oleg Kulikov, Yohann Martineau
*/
package net.sourceforge.peers.media;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.CountDownLatch;
import net.sourceforge.peers.Logger;
public class PcmuEncoder extends Encoder {
private final static int cBias = 0x84;
private final static short seg_end[] = new short[]{0xFF, 0x1FF, 0x3FF, 0x7FF,
0xFFF, 0x1FFF, 0x3FFF, 0x7FFF
};
/*
* linear2ulaw() - Convert a linear PCM value to u-law
*
* In order to simplify the encoding process, the original linear magnitude
* is biased by adding 33 which shifts the encoding range from (0 - 8158) to
* (33 - 8191). The result can be seen in the following encoding table:
*
* Biased Linear Input Code Compressed Code
* ------------------------ ---------------
* 00000001wxyza 000wxyz
* 0000001wxyzab 001wxyz
* 000001wxyzabc 010wxyz
* 00001wxyzabcd 011wxyz
* 0001wxyzabcde 100wxyz
* 001wxyzabcdef 101wxyz
* 01wxyzabcdefg 110wxyz
* 1wxyzabcdefgh 111wxyz
*
* Each biased linear code has a leading 1 which identifies the segment
* number. The value of the segment number is equal to 7 minus the number
* of leading 0's. The quantization interval is directly available as the
* four bits wxyz. * The trailing bits (a - h) are ignored.
*
* Ordinarily the complement of the resulting code word is used for
* transmission, and so the code word is complemented before it is returned.
*
* For further information see John C. Bellamy's Digital Telephony, 1982,
* John Wiley & Sons, pps 98-111 and 472-476.
*/
private static byte linear2ulaw(short pcm_val) {
int mask;
int seg;
byte uval;
/* Get the sign and the magnitude of the value. */
if (pcm_val < 0) {
pcm_val = (short) (cBias - pcm_val);
mask = 0x7F;
} else {
pcm_val += cBias;
mask = 0xFF;
}
/* Convert the scaled magnitude to segment number. */
seg = search(pcm_val, seg_end, 8);
/*
* Combine the sign, segment, quantization bits;
* and complement the code word.
*/
if (seg >= 8) /* out of range, return maximum value. */ {
return (byte)(0x7F ^ mask);
} else {
uval = (byte)((seg << 4) | ((pcm_val >> (seg + 3)) & 0xF));
return (byte)(uval ^ mask);
}
}
private static int search(int val, short[] table, int size) {
int i;
for (i = 0; i < size; i++) {
if (val <= table[i]) {
return (i);
}
}
return (size);
}
public PcmuEncoder(PipedInputStream rawData, PipedOutputStream encodedData,
boolean mediaDebug, Logger logger, String peersHome,
CountDownLatch latch) {
super(rawData, encodedData, mediaDebug, logger, peersHome, latch);
}
/**
* Perform compression using U-law. Retrieved from Mobicents media server
* code.
*
* @param media the input uncompressed media
* @return the output compressed media.
*/
public byte[] process(byte[] media) {
byte[] compressed = new byte[media.length / 2];
int j = 0;
for (int i = 0; i < compressed.length; i++) {
short sample = (short) ((media[j++] & 0xff) | ((media[j++]) << 8));
compressed[i] = linear2ulaw(sample);
}
return compressed;
}
}

View File

@@ -0,0 +1,200 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2008, 2009, 2010, 2011 Yohann Martineau
*/
package net.sourceforge.peers.media;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PipedInputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import net.sourceforge.peers.Logger;
import net.sourceforge.peers.rtp.RtpPacket;
import net.sourceforge.peers.rtp.RtpSession;
import net.sourceforge.peers.sdp.Codec;
public class RtpSender implements Runnable {
private PipedInputStream encodedData;
private RtpSession rtpSession;
private boolean isStopped;
private FileOutputStream rtpSenderInput;
private boolean mediaDebug;
private Codec codec;
private List<RtpPacket> pushedPackets;
private Logger logger;
private String peersHome;
private CountDownLatch latch;
public RtpSender(PipedInputStream encodedData, RtpSession rtpSession,
boolean mediaDebug, Codec codec, Logger logger, String peersHome,
CountDownLatch latch) {
this.encodedData = encodedData;
this.rtpSession = rtpSession;
this.mediaDebug = mediaDebug;
this.codec = codec;
this.peersHome = peersHome;
this.latch = latch;
isStopped = false;
pushedPackets = Collections.synchronizedList(
new ArrayList<RtpPacket>());
}
public void run() {
if (mediaDebug) {
SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
String date = simpleDateFormat.format(new Date());
String fileName = peersHome + File.separator
+ AbstractSoundManager.MEDIA_DIR + File.separator + date
+ "_rtp_sender.input";
try {
rtpSenderInput = new FileOutputStream(fileName);
} catch (FileNotFoundException e) {
if (logger!=null) if (logger!=null) logger.error("cannot create file", e);
return;
}
}
RtpPacket rtpPacket = new RtpPacket();
rtpPacket.setVersion(2);
rtpPacket.setPadding(false);
rtpPacket.setExtension(false);
rtpPacket.setCsrcCount(0);
rtpPacket.setMarker(false);
rtpPacket.setPayloadType(codec.getPayloadType());
Random random = new Random();
int sequenceNumber = random.nextInt();
rtpPacket.setSequenceNumber(sequenceNumber);
rtpPacket.setSsrc(random.nextInt());
int buf_size = Capture.BUFFER_SIZE / 2;
byte[] buffer = new byte[buf_size];
int timestamp = 0;
int numBytesRead;
int tempBytesRead;
long sleepTime = 0;
long offset = 0;
long lastSentTime = System.nanoTime();
// indicate if its the first time that we send a packet (dont wait)
boolean firstTime = true;
while (!isStopped) {
numBytesRead = 0;
try {
while (!isStopped && numBytesRead < buf_size) {
// expect that the buffer is full
tempBytesRead = encodedData.read(buffer, numBytesRead,
buf_size - numBytesRead);
numBytesRead += tempBytesRead;
}
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("input/output error", e);
return;
}
byte[] trimmedBuffer;
if (numBytesRead < buffer.length) {
trimmedBuffer = new byte[numBytesRead];
System.arraycopy(buffer, 0, trimmedBuffer, 0, numBytesRead);
} else {
trimmedBuffer = buffer;
}
if (mediaDebug) {
try {
rtpSenderInput.write(trimmedBuffer); // TODO use classpath
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("cannot write to file", e);
break;
}
}
if (pushedPackets.size() > 0) {
RtpPacket pushedPacket = pushedPackets.remove(0);
rtpPacket.setMarker(pushedPacket.isMarker());
rtpPacket.setPayloadType(pushedPacket.getPayloadType());
byte[] data = pushedPacket.getData();
rtpPacket.setData(data);
} else {
if (rtpPacket.getPayloadType() != codec.getPayloadType()) {
rtpPacket.setPayloadType(codec.getPayloadType());
rtpPacket.setMarker(false);
}
rtpPacket.setData(trimmedBuffer);
}
rtpPacket.setSequenceNumber(sequenceNumber++);
timestamp += buf_size;
rtpPacket.setTimestamp(timestamp);
if (firstTime) {
rtpSession.send(rtpPacket);
lastSentTime = System.nanoTime();
firstTime = false;
continue;
}
sleepTime = 19500000 - (System.nanoTime() - lastSentTime) + offset;
if (sleepTime > 0) {
try {
Thread.sleep(Math.round(sleepTime / 1000000f));
} catch (InterruptedException e) {
if (logger!=null) if (logger!=null) logger.error("Thread interrupted", e);
return;
}
rtpSession.send(rtpPacket);
lastSentTime = System.nanoTime();
offset = 0;
} else {
rtpSession.send(rtpPacket);
lastSentTime = System.nanoTime();
if (sleepTime < -20000000) {
offset = sleepTime + 20000000;
}
}
}
if (mediaDebug) {
try {
rtpSenderInput.close();
} catch (IOException e) {
if (logger!=null) if (logger!=null) logger.error("cannot close file", e);
return;
}
}
latch.countDown();
if (latch.getCount() != 0) {
try {
latch.await();
} catch (InterruptedException e) {
if (logger!=null) if (logger!=null) logger.error("interrupt exception", e);
}
}
}
public synchronized void setStopped(boolean isStopped) {
this.isStopped = isStopped;
}
public void pushPackets(List<RtpPacket> rtpPackets) {
this.pushedPackets.addAll(rtpPackets);
}
}

View File

@@ -0,0 +1,30 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2012 Yohann Martineau
*/
package net.sourceforge.peers.media;
public interface SoundSource {
/**
* read raw data linear PCM 8kHz, 16 bits signed, mono-channel, little endian
* @return
*/
public byte[] readData();
}

View File

@@ -0,0 +1,64 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package net.sourceforge.peers.nat;
import java.io.IOException;
import java.net.InetAddress;
import org.w3c.dom.Document;
public class Client {
private Server server;
//private String email;
private PeerManager peerManager;
public Client(String email, String localInetAddress, int localPort) {
//this.email = email;
// TODO automatic global access interface discovery
try {
InetAddress localAddress = InetAddress.getByName(localInetAddress);
server = new Server(localAddress, localPort);
peerManager = new PeerManager(localAddress, localPort);
} catch (IOException e) {
e.printStackTrace();
return;
}
server.update(email);
Document document = server.getPeers(email);
peerManager.setDocument(document);
peerManager.start();
}
/**
* @param args
*/
public static void main(String[] args) {
if (args.length != 3) {
System.err.println("usage: java ... <email> <localAddress>" +
" <localPort>");
System.exit(1);
}
new Client(args[0], args[1], Integer.parseInt(args[2]));
}
}

View File

@@ -0,0 +1,117 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package net.sourceforge.peers.nat;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class PeerManager extends Thread {
private InetAddress localAddress;
private int localPort;
private Document document;
public PeerManager(InetAddress localAddress, int localPort) {
this.localAddress = localAddress;
this.localPort = localPort;
}
public void setDocument(Document document) {
this.document = document;
}
public void run() {
DatagramSocket datagramSocket;
try {
datagramSocket = new DatagramSocket(localPort, localAddress);
} catch (SocketException e) {
e.printStackTrace();
return;
}
// UDPReceiver udpReceiver = new UDPReceiver(datagramSocket);
// udpReceiver.start();
while (true) {
Element root = document.getDocumentElement();
NodeList peers = root.getChildNodes();
for (int i = 0; i < peers.getLength(); ++i) {
Node node = peers.item(i);
if (node.getNodeName().equals("peer")) {
createConnection(node, datagramSocket);
}
}
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
}
private void createConnection(Node peer, DatagramSocket datagramSocket) {
NodeList childNodes = peer.getChildNodes();
String ipAddress = null;
String port = null;
for (int i = 0; i < childNodes.getLength(); ++i) {
Node node = childNodes.item(i);
String nodeName = node.getNodeName();
if (nodeName.equals("ipaddress")) {
ipAddress = node.getTextContent();
} else if (nodeName.equals("port")) {
port = node.getTextContent();
}
}
if (ipAddress == null || port == null) {
return;
}
int remotePort = Integer.parseInt(port);
try {
InetAddress remoteAddress = InetAddress.getByName(ipAddress);
// DatagramSocket datagramSocket = new DatagramSocket(localPort, localAddress);
for (int i = 0; i < 5; ++i) {
String message = "hello world " + System.currentTimeMillis();
byte[] buf = message.getBytes();
DatagramPacket datagramPacket =
new DatagramPacket(buf, buf.length, remoteAddress, remotePort);
datagramSocket.send(datagramPacket);
System.out.println("> sent:\n" + message);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
//datagramSocket.close();
} catch (IOException e) {
e.printStackTrace();
return;
}
}
}

View File

@@ -0,0 +1,175 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package net.sourceforge.peers.nat;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
public class Server {
public static final String SERVER_HOST = "peers.sourceforge.net";
public static final String PREFIX = "/peers";
//public static final int SOCKET_TIMEOUT = 30000;//millis
//private InetAddress localAddress;
//private int localPort;
private InetAddress remoteAddress;
private int remotePort;
private Socket socket;
//TODO constructor without parameters
public Server(InetAddress localAddress, int localPort) throws IOException {
super();
//this.localAddress = localAddress;
//this.localPort = localPort;
this.remoteAddress = InetAddress.getByName(SERVER_HOST);
this.remotePort = 80;
socket = new Socket(remoteAddress, remotePort, localAddress, localPort);
//socket.setSoTimeout(SOCKET_TIMEOUT);
}
/**
* This method will update public address on the web server.
* @param email user identifier
*/
public void update(String email) {
String encodedEmail;
try {
encodedEmail = URLEncoder.encode(email, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return;
}
StringBuffer urlEnd = new StringBuffer();
urlEnd.append("update2.php?email=");
urlEnd.append(encodedEmail);
get(urlEnd.toString());
close();
}
public Document getPeers(String email) {
String encodedEmail;
try {
encodedEmail = URLEncoder.encode(email, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
StringBuffer urlBuf = new StringBuffer();
urlBuf.append("http://");
urlBuf.append(SERVER_HOST);
urlBuf.append(PREFIX);
urlBuf.append("/getassocasxml.php?email=");
urlBuf.append(encodedEmail);
URL url;
try {
url = new URL(urlBuf.toString());
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
System.out.println("retrieved peers");
DocumentBuilderFactory documentBuilderFactory
= DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder;
try {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
e.printStackTrace();
return null;
}
try {
URLConnection urlConnection = url.openConnection();
InputStream inputStream = urlConnection.getInputStream();
return documentBuilder.parse(inputStream);
} catch (IOException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
}
return null;
}
private String get(String urlEnd) {
StringBuffer get = new StringBuffer();
get.append("GET ");
get.append(PREFIX);
get.append('/');
get.append(urlEnd);
get.append(" HTTP/1.1\r\n");
get.append("Host: ");
get.append(SERVER_HOST);
get.append("\r\n");
get.append("\r\n");
try {
socket.getOutputStream().write(get.toString().getBytes());
} catch (IOException e) {
e.printStackTrace();
return null;
}
System.out.println("> sent:\n" + get.toString());
StringBuffer result = new StringBuffer();
try {
byte[] buf = new byte[256];
int read = 0;
while ((read = socket.getInputStream().read(buf)) > -1) {
byte[] exactBuf = new byte[read];
System.arraycopy(buf, 0, exactBuf, 0, read);
result.append(new String(exactBuf));
}
} catch (SocketTimeoutException e) {
System.out.println("socket timeout");
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
System.out.println("< received:\n" + result.toString());
return result.toString();
}
public void close() {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,49 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package net.sourceforge.peers.nat;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPReceiver extends Thread {
private DatagramSocket datagramSocket;
public UDPReceiver(DatagramSocket datagramSocket) {
super();
this.datagramSocket = datagramSocket;
}
@Override
public void run() {
try {
while (true) {
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
datagramSocket.receive(packet);
System.out.println("< received:\n"
+ new String(packet.getData()));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,26 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package net.sourceforge.peers.nat.api;
public interface DataReceiver {
public void dataReceived(byte[] data, String peerId);
}

View File

@@ -0,0 +1,47 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package net.sourceforge.peers.nat.api;
public abstract class PeersClient {
/**
* creates a new peers client
* @param myId the string identifier corresponding
* to the computer or to a person (email).
* @param dataReceiver object that will receive incoming traffic.
*/
public PeersClient(String myId, DataReceiver dataReceiver) {
}
/**
* creates a UDP connection to a peer.
* @param peerId unique peer identifier (email for example).
* @return an object that allows to send data to the peer.
*/
public abstract UDPTransport createUDPTransport(String peerId);
/**
* creates a TCP connection to a peer.
* @param peerId unique peer identifier (email for example).
* @return an object that allows to send data to the peer.
*/
public abstract TCPTransport createTCPTransport(String peerId);
}

View File

@@ -0,0 +1,24 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package net.sourceforge.peers.nat.api;
public interface TCPTransport extends Transport {
}

View File

@@ -0,0 +1,25 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package net.sourceforge.peers.nat.api;
public interface Transport {
public void sendData(byte[] data);
}

View File

@@ -0,0 +1,24 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package net.sourceforge.peers.nat.api;
public interface UDPTransport extends Transport {
}

View File

@@ -0,0 +1,34 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package net.sourceforge.peers.rtp;
public class RFC3551 {
// payload types
public static final int PAYLOAD_TYPE_PCMU = 0;
public static final int PAYLOAD_TYPE_PCMA = 8;
// encoding names
public static final String PCMU = "PCMU";
public static final String PCMA = "PCMA";
}

View File

@@ -0,0 +1,32 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package net.sourceforge.peers.rtp;
public class RFC4733 {
// payload types
public static final int PAYLOAD_TYPE_TELEPHONE_EVENT = 101;
// encoding names
public static final String TELEPHONE_EVENT = "telephone-event";
}

View File

@@ -0,0 +1,26 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package net.sourceforge.peers.rtp;
public interface RtpListener {
public void receivedRtpPacket(RtpPacket rtpPacket);
}

View File

@@ -0,0 +1,124 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package net.sourceforge.peers.rtp;
public class RtpPacket {
private int version;
private boolean padding;
private boolean extension;
private int csrcCount;
private boolean marker;
private int payloadType;
private int sequenceNumber;
private long timestamp;
private long ssrc;
private long[] csrcList;
private byte[] data;
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public boolean isPadding() {
return padding;
}
public void setPadding(boolean padding) {
this.padding = padding;
}
public boolean isExtension() {
return extension;
}
public void setExtension(boolean extension) {
this.extension = extension;
}
public int getCsrcCount() {
return csrcCount;
}
public void setCsrcCount(int csrcCount) {
this.csrcCount = csrcCount;
}
public boolean isMarker() {
return marker;
}
public void setMarker(boolean marker) {
this.marker = marker;
}
public int getPayloadType() {
return payloadType;
}
public void setPayloadType(int payloadType) {
this.payloadType = payloadType;
}
public int getSequenceNumber() {
return sequenceNumber;
}
public void setSequenceNumber(int sequenceNumber) {
this.sequenceNumber = sequenceNumber;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public long getSsrc() {
return ssrc;
}
public void setSsrc(long ssrc) {
this.ssrc = ssrc;
}
public long[] getCsrcList() {
return csrcList;
}
public void setCsrcList(long[] csrcList) {
this.csrcList = csrcList;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
}

View File

@@ -0,0 +1,121 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package net.sourceforge.peers.rtp;
import net.sourceforge.peers.Logger;
// RFC 3550
public class RtpParser {
private Logger logger;
public RtpParser(Logger logger) {
this.logger = logger;
}
public RtpPacket decode(byte[] packet) {
if (packet.length < 12) {
if (logger!=null) if (logger!=null) logger.error("RTP packet too short");
return null;
}
RtpPacket rtpPacket = new RtpPacket();
int b = (int)(packet[0] & 0xff);
rtpPacket.setVersion((b & 0xc0) >> 6);
rtpPacket.setPadding((b & 0x20) != 0);
rtpPacket.setExtension((b & 0x10) != 0);
rtpPacket.setCsrcCount(b & 0x0f);
b = (int)(packet[1] & 0xff);
rtpPacket.setMarker((b & 0x80) != 0);
rtpPacket.setPayloadType(b & 0x7f);
b = (int)(packet[2] & 0xff);
rtpPacket.setSequenceNumber(b * 256 + (int)(packet[3] & 0xff));
b = (int)(packet[4] & 0xff);
rtpPacket.setTimestamp(b * 256 * 256 * 256
+ (int)(packet[5] & 0xff) * 256 * 256
+ (int)(packet[6] & 0xff) * 256
+ (int)(packet[7] & 0xff));
b = (int)(packet[8] & 0xff);
rtpPacket.setSsrc(b * 256 * 256 * 256
+ (int)(packet[9] & 0xff) * 256 * 256
+ (int)(packet[10] & 0xff) * 256
+ (int)(packet[11] & 0xff));
long[] csrcList = new long[rtpPacket.getCsrcCount()];
for (int i = 0; i < csrcList.length; ++i)
csrcList[i] = (int)(packet[12 + i] & 0xff) << 24
+ (int)(packet[12 + i + 1] & 0xff) << 16
+ (int)(packet[12 + i + 2] & 0xff) << 8
+ (int)(packet[12 + i + 3] & 0xff);
rtpPacket.setCsrcList(csrcList);
int dataOffset = 12 + csrcList.length * 4;
int dataLength = packet.length - dataOffset;
byte[] data = new byte[dataLength];
System.arraycopy(packet, dataOffset, data, 0, dataLength);
rtpPacket.setData(data);
return rtpPacket;
}
public byte[] encode(RtpPacket rtpPacket) {
byte[] data = rtpPacket.getData();
int packetLength = 12 + rtpPacket.getCsrcCount() * 4 + data.length;
byte[] packet = new byte[packetLength];
int b = (rtpPacket.getVersion() << 6)
+ ((rtpPacket.isPadding() ? 1 : 0) << 5)
+ ((rtpPacket.isExtension() ? 1 : 0) << 4)
+ (rtpPacket.getCsrcCount());
packet[0] = new Integer(b).byteValue();
b = ((rtpPacket.isMarker() ? 1 : 0) << 7)
+ rtpPacket.getPayloadType();
packet[1] = new Integer(b).byteValue();
b = rtpPacket.getSequenceNumber() >> 8;
packet[2] = new Integer(b).byteValue();
b = rtpPacket.getSequenceNumber() & 0xff;
packet[3] = new Integer(b).byteValue();
b = (int)(rtpPacket.getTimestamp() >> 24);
packet[4] = new Integer(b).byteValue();
b = (int)(rtpPacket.getTimestamp() >> 16);
packet[5] = new Integer(b).byteValue();
b = (int)(rtpPacket.getTimestamp() >> 8);
packet[6] = new Integer(b).byteValue();
b = (int)(rtpPacket.getTimestamp() & 0xff);
packet[7] = new Integer(b).byteValue();
b = (int)(rtpPacket.getSsrc() >> 24);
packet[8] = new Integer(b).byteValue();
b = (int)(rtpPacket.getSsrc() >> 16);
packet[9] = new Integer(b).byteValue();
b = (int)(rtpPacket.getSsrc() >> 8);
packet[10] = new Integer(b).byteValue();
b = (int)(rtpPacket.getSsrc() & 0xff);
packet[11] = new Integer(b).byteValue();
for (int i = 0; i < rtpPacket.getCsrcCount(); ++i) {
b = (int)(rtpPacket.getCsrcList()[i] >> 24);
packet[12 + i * 4] = new Integer(b).byteValue();
b = (int)(rtpPacket.getCsrcList()[i] >> 16);
packet[12 + i * 4 + 1] = new Integer(b).byteValue();
b = (int)(rtpPacket.getCsrcList()[i] >> 8);
packet[12 + i * 4 + 2] = new Integer(b).byteValue();
b = (int)(rtpPacket.getCsrcList()[i] & 0xff);
packet[12 + i * 4 + 3] = new Integer(b).byteValue();
}
System.arraycopy(data, 0, packet, 12 + rtpPacket.getCsrcCount() * 4,
data.length);
return packet;
}
}

Some files were not shown because too many files have changed in this diff Show More