first commit
This commit is contained in:
57
pom.xml
Normal file
57
pom.xml
Normal 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>
|
||||
44
src/bass/BassAACLibrary.java
Normal file
44
src/bass/BassAACLibrary.java
Normal 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);
|
||||
}
|
||||
21
src/bass/BassEncAACLibrary.java
Normal file
21
src/bass/BassEncAACLibrary.java
Normal 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);
|
||||
}
|
||||
172
src/bass/BassEncLibrary.java
Normal file
172
src/bass/BassEncLibrary.java
Normal 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
|
||||
|
||||
}
|
||||
}
|
||||
22
src/bass/BassEncMP3Library.java
Normal file
22
src/bass/BassEncMP3Library.java
Normal 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);
|
||||
}
|
||||
27
src/bass/BassEncOGGLibrary.java
Normal file
27
src/bass/BassEncOGGLibrary.java
Normal 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;
|
||||
}
|
||||
}
|
||||
27
src/bass/BassEncOPUSLibrary.java
Normal file
27
src/bass/BassEncOPUSLibrary.java
Normal 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
1363
src/bass/BassLibrary.java
Normal file
File diff suppressed because it is too large
Load Diff
141
src/bass/BassMixLibrary.java
Normal file
141
src/bass/BassMixLibrary.java
Normal 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);
|
||||
|
||||
}
|
||||
23
src/bass/BassOPUSLibrary.java
Normal file
23
src/bass/BassOPUSLibrary.java
Normal 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);
|
||||
}
|
||||
159
src/bass/CompressInputStream.java
Normal file
159
src/bass/CompressInputStream.java
Normal 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;
|
||||
}
|
||||
}
|
||||
115
src/bass/ConvertInputStream.java
Normal file
115
src/bass/ConvertInputStream.java
Normal 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;
|
||||
}
|
||||
}
|
||||
135
src/bass/DecompressInputStream.java
Normal file
135
src/bass/DecompressInputStream.java
Normal 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
231
src/bass/G711.java
Normal 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
970
src/bass/JSIPAudio.java
Normal 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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
350
src/bass/basicfunctions.java
Normal file
350
src/bass/basicfunctions.java
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
146
src/code/BassFileReader.java
Normal file
146
src/code/BassFileReader.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
14
src/code/BassFileReaderListener.java
Normal file
14
src/code/BassFileReaderListener.java
Normal 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);
|
||||
}
|
||||
271
src/code/BassSoundManager.java
Normal file
271
src/code/BassSoundManager.java
Normal 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"));
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
18
src/code/BassSoundManagerListener.java
Normal file
18
src/code/BassSoundManagerListener.java
Normal 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
510
src/code/CgiClient.java
Normal 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
123
src/code/JVAPIX.java
Normal 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
1940
src/code/VapixAudio.java
Normal file
File diff suppressed because it is too large
Load Diff
510
src/code/VapixDevice.java
Normal file
510
src/code/VapixDevice.java
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
151
src/code/WaveOutputFile.java
Normal file
151
src/code/WaveOutputFile.java
Normal 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
560
src/code/common.java
Normal 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
1145
src/code/jSIPClient.java
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/linux-aarch64/libbass.so
Normal file
BIN
src/linux-aarch64/libbass.so
Normal file
Binary file not shown.
BIN
src/linux-aarch64/libbass_aac.so
Normal file
BIN
src/linux-aarch64/libbass_aac.so
Normal file
Binary file not shown.
BIN
src/linux-aarch64/libbassenc.so
Normal file
BIN
src/linux-aarch64/libbassenc.so
Normal file
Binary file not shown.
BIN
src/linux-aarch64/libbassenc_aac.so
Normal file
BIN
src/linux-aarch64/libbassenc_aac.so
Normal file
Binary file not shown.
BIN
src/linux-aarch64/libbassenc_flac.so
Normal file
BIN
src/linux-aarch64/libbassenc_flac.so
Normal file
Binary file not shown.
BIN
src/linux-aarch64/libbassenc_mp3.so
Normal file
BIN
src/linux-aarch64/libbassenc_mp3.so
Normal file
Binary file not shown.
BIN
src/linux-aarch64/libbassenc_ogg.so
Normal file
BIN
src/linux-aarch64/libbassenc_ogg.so
Normal file
Binary file not shown.
BIN
src/linux-aarch64/libbassenc_opus.so
Normal file
BIN
src/linux-aarch64/libbassenc_opus.so
Normal file
Binary file not shown.
BIN
src/linux-aarch64/libbassmix.so
Normal file
BIN
src/linux-aarch64/libbassmix.so
Normal file
Binary file not shown.
BIN
src/linux-arm/libbass.so
Normal file
BIN
src/linux-arm/libbass.so
Normal file
Binary file not shown.
BIN
src/linux-arm/libbass_aac.so
Normal file
BIN
src/linux-arm/libbass_aac.so
Normal file
Binary file not shown.
BIN
src/linux-arm/libbassenc.so
Normal file
BIN
src/linux-arm/libbassenc.so
Normal file
Binary file not shown.
BIN
src/linux-arm/libbassenc_aac.so
Normal file
BIN
src/linux-arm/libbassenc_aac.so
Normal file
Binary file not shown.
BIN
src/linux-arm/libbassenc_flac.so
Normal file
BIN
src/linux-arm/libbassenc_flac.so
Normal file
Binary file not shown.
BIN
src/linux-arm/libbassenc_mp3.so
Normal file
BIN
src/linux-arm/libbassenc_mp3.so
Normal file
Binary file not shown.
BIN
src/linux-arm/libbassenc_ogg.so
Normal file
BIN
src/linux-arm/libbassenc_ogg.so
Normal file
Binary file not shown.
BIN
src/linux-arm/libbassenc_opus.so
Normal file
BIN
src/linux-arm/libbassenc_opus.so
Normal file
Binary file not shown.
BIN
src/linux-arm/libbassmix.so
Normal file
BIN
src/linux-arm/libbassmix.so
Normal file
Binary file not shown.
BIN
src/linux-x86-64/libbass.so
Normal file
BIN
src/linux-x86-64/libbass.so
Normal file
Binary file not shown.
BIN
src/linux-x86-64/libbass_aac.so
Normal file
BIN
src/linux-x86-64/libbass_aac.so
Normal file
Binary file not shown.
BIN
src/linux-x86-64/libbassenc.so
Normal file
BIN
src/linux-x86-64/libbassenc.so
Normal file
Binary file not shown.
BIN
src/linux-x86-64/libbassenc_aac.so
Normal file
BIN
src/linux-x86-64/libbassenc_aac.so
Normal file
Binary file not shown.
BIN
src/linux-x86-64/libbassenc_flac.so
Normal file
BIN
src/linux-x86-64/libbassenc_flac.so
Normal file
Binary file not shown.
BIN
src/linux-x86-64/libbassenc_mp3.so
Normal file
BIN
src/linux-x86-64/libbassenc_mp3.so
Normal file
Binary file not shown.
BIN
src/linux-x86-64/libbassenc_ogg.so
Normal file
BIN
src/linux-x86-64/libbassenc_ogg.so
Normal file
Binary file not shown.
BIN
src/linux-x86-64/libbassenc_opus.so
Normal file
BIN
src/linux-x86-64/libbassenc_opus.so
Normal file
Binary file not shown.
BIN
src/linux-x86-64/libbassmix.so
Normal file
BIN
src/linux-x86-64/libbassmix.so
Normal file
Binary file not shown.
BIN
src/linux-x86/libbass.so
Normal file
BIN
src/linux-x86/libbass.so
Normal file
Binary file not shown.
BIN
src/linux-x86/libbass_aac.so
Normal file
BIN
src/linux-x86/libbass_aac.so
Normal file
Binary file not shown.
BIN
src/linux-x86/libbassenc.so
Normal file
BIN
src/linux-x86/libbassenc.so
Normal file
Binary file not shown.
BIN
src/linux-x86/libbassenc_aac.so
Normal file
BIN
src/linux-x86/libbassenc_aac.so
Normal file
Binary file not shown.
BIN
src/linux-x86/libbassenc_flac.so
Normal file
BIN
src/linux-x86/libbassenc_flac.so
Normal file
Binary file not shown.
BIN
src/linux-x86/libbassenc_mp3.so
Normal file
BIN
src/linux-x86/libbassenc_mp3.so
Normal file
Binary file not shown.
BIN
src/linux-x86/libbassenc_ogg.so
Normal file
BIN
src/linux-x86/libbassenc_ogg.so
Normal file
Binary file not shown.
BIN
src/linux-x86/libbassenc_opus.so
Normal file
BIN
src/linux-x86/libbassenc_opus.so
Normal file
Binary file not shown.
BIN
src/linux-x86/libbassmix.so
Normal file
BIN
src/linux-x86/libbassmix.so
Normal file
Binary file not shown.
53
src/net/sourceforge/peers/Config.java
Normal file
53
src/net/sourceforge/peers/Config.java
Normal 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);
|
||||
|
||||
}
|
||||
129
src/net/sourceforge/peers/FileLogger.java
Normal file
129
src/net/sourceforge/peers/FileLogger.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
156
src/net/sourceforge/peers/JavaConfig.java
Normal file
156
src/net/sourceforge/peers/JavaConfig.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
30
src/net/sourceforge/peers/Logger.java
Normal file
30
src/net/sourceforge/peers/Logger.java
Normal 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);
|
||||
|
||||
}
|
||||
361
src/net/sourceforge/peers/XmlConfig.java
Normal file
361
src/net/sourceforge/peers/XmlConfig.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
214
src/net/sourceforge/peers/javaxsound/JavaxSoundManager.java
Normal file
214
src/net/sourceforge/peers/javaxsound/JavaxSoundManager.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
28
src/net/sourceforge/peers/media/AbstractSoundManager.java
Normal file
28
src/net/sourceforge/peers/media/AbstractSoundManager.java
Normal 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);
|
||||
}
|
||||
79
src/net/sourceforge/peers/media/Capture.java
Normal file
79
src/net/sourceforge/peers/media/Capture.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
129
src/net/sourceforge/peers/media/CaptureRtpSender.java
Normal file
129
src/net/sourceforge/peers/media/CaptureRtpSender.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
26
src/net/sourceforge/peers/media/Decoder.java
Normal file
26
src/net/sourceforge/peers/media/Decoder.java
Normal 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);
|
||||
|
||||
}
|
||||
95
src/net/sourceforge/peers/media/DtmfFactory.java
Normal file
95
src/net/sourceforge/peers/media/DtmfFactory.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
79
src/net/sourceforge/peers/media/Echo.java
Normal file
79
src/net/sourceforge/peers/media/Echo.java
Normal 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;
|
||||
}
|
||||
}
|
||||
148
src/net/sourceforge/peers/media/Encoder.java
Normal file
148
src/net/sourceforge/peers/media/Encoder.java
Normal 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);
|
||||
|
||||
}
|
||||
96
src/net/sourceforge/peers/media/FileReader.java
Normal file
96
src/net/sourceforge/peers/media/FileReader.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
68
src/net/sourceforge/peers/media/IncomingRtpReader.java
Normal file
68
src/net/sourceforge/peers/media/IncomingRtpReader.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
340
src/net/sourceforge/peers/media/MediaManager.java
Normal file
340
src/net/sourceforge/peers/media/MediaManager.java
Normal 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;
|
||||
}
|
||||
}
|
||||
24
src/net/sourceforge/peers/media/MediaMode.java
Normal file
24
src/net/sourceforge/peers/media/MediaMode.java
Normal 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
|
||||
}
|
||||
71
src/net/sourceforge/peers/media/PcmaDecoder.java
Normal file
71
src/net/sourceforge/peers/media/PcmaDecoder.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
98
src/net/sourceforge/peers/media/PcmaEncoder.java
Normal file
98
src/net/sourceforge/peers/media/PcmaEncoder.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
103
src/net/sourceforge/peers/media/PcmuDecoder.java
Normal file
103
src/net/sourceforge/peers/media/PcmuDecoder.java
Normal 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));
|
||||
// }
|
||||
|
||||
}
|
||||
130
src/net/sourceforge/peers/media/PcmuEncoder.java
Normal file
130
src/net/sourceforge/peers/media/PcmuEncoder.java
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
200
src/net/sourceforge/peers/media/RtpSender.java
Normal file
200
src/net/sourceforge/peers/media/RtpSender.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
30
src/net/sourceforge/peers/media/SoundSource.java
Normal file
30
src/net/sourceforge/peers/media/SoundSource.java
Normal 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();
|
||||
|
||||
}
|
||||
64
src/net/sourceforge/peers/nat/Client.java
Normal file
64
src/net/sourceforge/peers/nat/Client.java
Normal 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]));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
117
src/net/sourceforge/peers/nat/PeerManager.java
Normal file
117
src/net/sourceforge/peers/nat/PeerManager.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
175
src/net/sourceforge/peers/nat/Server.java
Normal file
175
src/net/sourceforge/peers/nat/Server.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
src/net/sourceforge/peers/nat/UDPReceiver.java
Normal file
49
src/net/sourceforge/peers/nat/UDPReceiver.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/net/sourceforge/peers/nat/api/DataReceiver.java
Normal file
26
src/net/sourceforge/peers/nat/api/DataReceiver.java
Normal 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);
|
||||
}
|
||||
47
src/net/sourceforge/peers/nat/api/PeersClient.java
Normal file
47
src/net/sourceforge/peers/nat/api/PeersClient.java
Normal 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);
|
||||
}
|
||||
24
src/net/sourceforge/peers/nat/api/TCPTransport.java
Normal file
24
src/net/sourceforge/peers/nat/api/TCPTransport.java
Normal 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 {
|
||||
|
||||
}
|
||||
25
src/net/sourceforge/peers/nat/api/Transport.java
Normal file
25
src/net/sourceforge/peers/nat/api/Transport.java
Normal 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);
|
||||
}
|
||||
24
src/net/sourceforge/peers/nat/api/UDPTransport.java
Normal file
24
src/net/sourceforge/peers/nat/api/UDPTransport.java
Normal 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 {
|
||||
|
||||
}
|
||||
34
src/net/sourceforge/peers/rtp/RFC3551.java
Normal file
34
src/net/sourceforge/peers/rtp/RFC3551.java
Normal 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";
|
||||
|
||||
}
|
||||
32
src/net/sourceforge/peers/rtp/RFC4733.java
Normal file
32
src/net/sourceforge/peers/rtp/RFC4733.java
Normal 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";
|
||||
|
||||
}
|
||||
26
src/net/sourceforge/peers/rtp/RtpListener.java
Normal file
26
src/net/sourceforge/peers/rtp/RtpListener.java
Normal 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);
|
||||
|
||||
}
|
||||
124
src/net/sourceforge/peers/rtp/RtpPacket.java
Normal file
124
src/net/sourceforge/peers/rtp/RtpPacket.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
121
src/net/sourceforge/peers/rtp/RtpParser.java
Normal file
121
src/net/sourceforge/peers/rtp/RtpParser.java
Normal 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
Reference in New Issue
Block a user