adaptation of old eclipse code to Intellij

This commit is contained in:
2024-11-18 12:09:33 +07:00
commit db13021be9
173 changed files with 17794 additions and 0 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile default="true" name="Default" enabled="true" />
</annotationProcessing>
</component>
</project>

View File

@@ -0,0 +1,16 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="49" name="Java" />
</Languages>
</inspection_tool>
<inspection_tool class="MethodNameSameAsClassName" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

11
.idea/libraries/google_code_gson.xml generated Normal file
View File

@@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="google.code.gson" type="repository">
<properties maven-id="com.google.code.gson:gson:2.11.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.27.0/error_prone_annotations-2.27.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

32
.idea/libraries/io_javalin.xml generated Normal file
View File

@@ -0,0 +1,32 @@
<component name="libraryTable">
<library name="io.javalin" type="repository">
<properties maven-id="io.javalin:javalin:4.6.8" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/javalin/javalin/4.6.8/javalin-4.6.8.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-server/9.4.51.v20230217/jetty-server-9.4.51.v20230217.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/javax/servlet/javax.servlet-api/3.1.0/javax.servlet-api-3.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-http/9.4.51.v20230217/jetty-http-9.4.51.v20230217.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-util/9.4.51.v20230217/jetty-util-9.4.51.v20230217.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-io/9.4.51.v20230217/jetty-io-9.4.51.v20230217.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-webapp/9.4.51.v20230217/jetty-webapp-9.4.51.v20230217.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-xml/9.4.51.v20230217/jetty-xml-9.4.51.v20230217.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-servlet/9.4.51.v20230217/jetty-servlet-9.4.51.v20230217.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-security/9.4.51.v20230217/jetty-security-9.4.51.v20230217.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-util-ajax/9.4.51.v20230217/jetty-util-ajax-9.4.51.v20230217.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-server/9.4.51.v20230217/websocket-server-9.4.51.v20230217.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-common/9.4.51.v20230217/websocket-common-9.4.51.v20230217.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-api/9.4.51.v20230217/websocket-api-9.4.51.v20230217.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-client/9.4.51.v20230217/websocket-client-9.4.51.v20230217.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-client/9.4.51.v20230217/jetty-client-9.4.51.v20230217.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-servlet/9.4.51.v20230217/websocket-servlet-9.4.51.v20230217.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.32/kotlin-stdlib-jdk8-1.5.32.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.32/kotlin-stdlib-1.5.32.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.32/kotlin-stdlib-common-1.5.32.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.32/kotlin-stdlib-jdk7-1.5.32.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

10
.idea/libraries/net_java_dev_jna.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="net.java.dev.jna" type="repository">
<properties maven-id="net.java.dev.jna:jna:5.6.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.6.0/jna-5.6.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

10
.idea/libraries/projectlombok_lombok.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="projectlombok.lombok" type="repository">
<properties maven-id="org.projectlombok:lombok:1.18.36" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.36/lombok-1.18.36.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

10
.idea/libraries/tinylog.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="tinylog" type="repository">
<properties maven-id="org.tinylog:tinylog:1.3.6" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/tinylog/tinylog/1.3.6/tinylog-1.3.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

50
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state>
<expanded-state>
<State />
<State>
<id>Android</id>
</State>
<State>
<id>CodePlugin DevKit</id>
</State>
<State>
<id>ComplianceLintAndroid</id>
</State>
<State>
<id>CorrectnessLintAndroid</id>
</State>
<State>
<id>Java</id>
</State>
<State>
<id>Java language level migration aidsJava</id>
</State>
<State>
<id>LintAndroid</id>
</State>
<State>
<id>PerformanceLintAndroid</id>
</State>
<State>
<id>Plugin DevKit</id>
</State>
<State>
<id>UsabilityLintAndroid</id>
</State>
</expanded-state>
<selected-state>
<State>
<id>Android</id>
</State>
</selected-state>
</profile-state>
</entry>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/SIPIntercom.iml" filepath="$PROJECT_DIR$/SIPIntercom.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

17
SIPIntercom.iml Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/libs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="net.java.dev.jna" level="project" />
<orderEntry type="library" name="io.javalin" level="project" />
<orderEntry type="library" name="google.code.gson" level="project" />
<orderEntry type="library" name="projectlombok.lombok" level="project" />
<orderEntry type="library" name="tinylog" level="project" />
</component>
</module>

Binary file not shown.

BIN
libs/linux-armhf/libbass.so Normal file

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

BIN
libs/win32-arm64/bass.dll Normal file

Binary file not shown.

BIN
libs/win32-x86-64/bass.dll Normal file

Binary file not shown.

BIN
libs/win32-x86/bass.dll Normal file

Binary file not shown.

View File

@@ -0,0 +1,7 @@
package Audio;
public interface AudioEvent1 {
void fileconvertfinish(String source, String destination);
void filestreaming(String filename, int handle, byte[] data);
void inputstreaming(String inputname, int handle, byte[] data);
}

1092
src/Audio/Bass.java Normal file

File diff suppressed because it is too large Load Diff

45
src/Audio/BassAAC.java Normal file
View File

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

173
src/Audio/BassEnc.java Normal file
View File

@@ -0,0 +1,173 @@
package Audio;
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
*
*/
@SuppressWarnings("unused")
interface BassEnc extends Library {
BassEnc Instance = (BassEnc) Native.load("bassenc", BassEnc.class);
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 */
}
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 */
}
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) */
}
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) */
}
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 */
}
int BASS_Encode_GetVersion();
int BASS_Encode_Start(int handle, String cmdline, int flags, ENCODEPROC proc, Pointer user);
int BASS_Encode_StartLimit(int handle, String cmdline, int flags, ENCODEPROC proc, Pointer user, int limit);
int BASS_Encode_StartUser(int handle, String file, int flags, ENCODERPROC proc, Pointer user);
boolean BASS_Encode_AddChunk(int handle, String id, Pointer buffer, int length);
boolean BASS_Encode_Write(int handle, Pointer buffer, int length);
boolean BASS_Encode_Stop(int handle);
boolean BASS_Encode_StopEx(int handle, boolean queue);
boolean BASS_Encode_SetPaused(int handle, boolean paused);
int BASS_Encode_IsActive(int handle);
boolean BASS_Encode_SetNotify(int handle, ENCODENOTIFYPROC proc, Pointer user);
long BASS_Encode_GetCount(int handle, int count);
boolean BASS_Encode_SetChannel(int handle, int channel);
int BASS_Encode_GetChannel(int handle);
boolean BASS_Encode_UserOutput(int handle, long offset, Pointer buffer, int length);
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);
boolean BASS_Encode_CastSetTitle(int handle, String title, String url);
boolean BASS_Encode_CastSendMeta(int handle, int type, Pointer data, int length);
String BASS_Encode_CastGetStats(int handle, int type, String pass);
int BASS_Encode_ServerInit(int handle, String port, int buffer, int burst, int flags, ENCODECLIENTPROC proc, Pointer user);
boolean BASS_Encode_ServerKick(int handle, String client);
interface Constant{
// Additional error codes returned by BASS_ErrorGetCode
int BASS_ERROR_CAST_DENIED = 2100; // access denied (invalid password)
int BASS_ERROR_SERVER_CERT = 2101; // missing/invalid certificate
// Additional BASS_SetConfig options
int BASS_CONFIG_ENCODE_PRIORITY = 0x10300;
int BASS_CONFIG_ENCODE_QUEUE = 0x10301;
int BASS_CONFIG_ENCODE_CAST_TIMEOUT = 0x10310;
// Additional BASS_SetConfigPtr options
int BASS_CONFIG_ENCODE_CAST_PROXY = 0x10311;
int BASS_CONFIG_ENCODE_CAST_BIND = 0x10312;
int BASS_CONFIG_ENCODE_SERVER_CERT = 0x10320;
int BASS_CONFIG_ENCODE_SERVER_KEY = 0x10321;
// BASS_Encode_Start flags
int BASS_ENCODE_NOHEAD = 1; // don't send a WAV header to the encoder
int BASS_ENCODE_FP_8BIT = 2; // convert floating-point sample data to 8-bit integer
int BASS_ENCODE_FP_16BIT = 4; // convert floating-point sample data to 16-bit integer
int BASS_ENCODE_FP_24BIT = 6; // convert floating-point sample data to 24-bit integer
int BASS_ENCODE_FP_32BIT = 8; // convert floating-point sample data to 32-bit integer
int BASS_ENCODE_FP_AUTO = 14; // convert floating-point sample data back to channel's format
int BASS_ENCODE_BIGEND = 16; // big-endian sample data
int BASS_ENCODE_PAUSE = 32; // start encording paused
int BASS_ENCODE_PCM = 64; // write PCM sample data (no encoder)
int BASS_ENCODE_RF64 = 128; // send an RF64 header
int BASS_ENCODE_QUEUE = 0x200; // queue data to feed encoder asynchronously
int BASS_ENCODE_WFEXT = 0x400; // WAVEFORMATEXTENSIBLE "fmt" chunk
int BASS_ENCODE_CAST_NOLIMIT = 0x1000; // don't limit casting data rate
int BASS_ENCODE_LIMIT = 0x2000; // limit data rate to real-time
int BASS_ENCODE_AIFF = 0x4000; // send an AIFF header rather than WAV
int BASS_ENCODE_DITHER = 0x8000; // apply dither when converting floating-point sample data to integer
int BASS_ENCODE_AUTOFREE = 0x40000; // free the encoder when the channel is freed
// BASS_Encode_GetCount counts
int BASS_ENCODE_COUNT_IN = 0; // sent to encoder
int BASS_ENCODE_COUNT_OUT = 1; // received from encoder
int BASS_ENCODE_COUNT_CAST = 2; // sent to cast server
int BASS_ENCODE_COUNT_QUEUE = 3; // queued
int BASS_ENCODE_COUNT_QUEUE_LIMIT = 4; // queue limit
int BASS_ENCODE_COUNT_QUEUE_FAIL = 5; // failed to queue
int BASS_ENCODE_COUNT_IN_FP = 6; // sent to encoder before floating-point conversion
// BASS_Encode_CastInit content MIME types
String BASS_ENCODE_TYPE_MP3 = "audio/mpeg";
String BASS_ENCODE_TYPE_OGG = "audio/ogg";
String BASS_ENCODE_TYPE_AAC = "audio/aacp";
// BASS_Encode_CastInit flags
int BASS_ENCODE_CAST_PUBLIC = 1; // add to public directory
int BASS_ENCODE_CAST_PUT = 2; // use PUT method
int BASS_ENCODE_CAST_SSL = 4; // use SSL/TLS encryption
// BASS_Encode_CastGetStats types
int BASS_ENCODE_STATS_SHOUT = 0; // Shoutcast stats
int BASS_ENCODE_STATS_ICE = 1; // Icecast mount-point stats
int BASS_ENCODE_STATS_ICESERV = 2; // Icecast server stats
// BASS_Encode_ServerInit flags
int BASS_ENCODE_SERVER_NOHTTP = 1; // no HTTP headers
int BASS_ENCODE_SERVER_META = 2; // Shoutcast metadata
int BASS_ENCODE_SERVER_SSL = 4; // support SSL/TLS encryption
int BASS_ENCODE_SERVER_SSLONLY = 8; // require SSL/TLS encryption
// Encoder notifications
int BASS_ENCODE_NOTIFY_ENCODER = 1; // encoder died
int BASS_ENCODE_NOTIFY_CAST = 2; // cast server connection died
int BASS_ENCODE_NOTIFY_SERVER = 3; // server died
int BASS_ENCODE_NOTIFY_CAST_TIMEOUT = 0x10000; // cast timeout
int BASS_ENCODE_NOTIFY_QUEUE_FULL = 0x10001; // queue is out of space
int BASS_ENCODE_NOTIFY_FREE = 0x10002; // encoder has been freed
}
}

22
src/Audio/BassEncAAC.java Normal file
View File

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

23
src/Audio/BassEncMP3.java Normal file
View File

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

28
src/Audio/BassEncOGG.java Normal file
View File

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

View File

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

View File

@@ -0,0 +1,140 @@
package Audio;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import org.pmw.tinylog.Logger;
import peers.media.SoundSource;
/**
* Pengganti /AxisAudio/src/net/sourceforge/peers/media/FileReader.java
* karena terbatas codec nya
* @author rdkartono
*
*/
@SuppressWarnings("FieldCanBeLocal")
public class BassFileReader implements SoundSource {
// 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 = Bass.BASS_DEVICE_MONO | Bass.BASS_DEVICE_16BITS;
// flag ketika open file, BASS_STREAM_DECODE karena akan plug ke mixer
private final int openflag = Bass.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 = Bass.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 final BassFileReaderListener listener; // belum kepake
private final Bass BASS = Bass.Instance;
private final BassMix BASSMIX = BassMix.Instance;
public BassFileReader(final String filename, BassFileReaderListener listener) {
this.filename = filename;
this.listener = listener;
if (BASS.BASS_GetVersion()!=0) {
if (BASSMIX.BASS_Mixer_GetVersion()!=0) {
boolean initresult = 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 = "+BASS.GetBassError("BASS_Init"));
filehandle = BASS.BASS_StreamCreateFile(false, filename, 0, 0, openflag);
if (filehandle!=0){
filesize = BASS.BASS_ChannelGetLength(filehandle, Bass.BASS_POS_BYTE);
duration = BASS.BASS_ChannelBytes2Seconds(filehandle, filesize);
if (listener!=null) listener.fileopened(filename, filesize, duration);
mixerhandle = BASSMIX.BASS_Mixer_StreamCreate(mixerfreq, mixerchannel, mixerflag);
if (mixerhandle!=0) {
if (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 = "+BASS.GetBassError("BASS_Mixer_StreamAddChannel"));
close();
}
} else {
logger_error("BassFileReader BASS_Mixer_StreamCreate error = "+BASS.GetBassError("BASS_Mixer_StreamCreate"));
close();
}
} else logger_error("BassFileReader BASS_StreamCreateFile error = "+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 (BASS.BASS_StreamFree(mixerhandle)) {
logger_info("mixer closed in BassFileReader");
} else logger_error("BassFileReader mixerhandle BASS_StreamFree error = "+BASS.GetBassError("BASS_StreamFree"));
mixerhandle = 0;
}
if (filehandle!=0) {
if (BASS.BASS_StreamFree(filehandle)) {
logger_info(filename+" closed in BassFileReader");
} else logger_error("BassFileReader filehandle BASS_StreamFree error = "+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 = 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 = "+BASS.GetBassError("BASS_ChannelGetData"));
} else {
// selesai
if (listener!=null) listener.filereadfinish(filename);
logger_info("BassFileReader BASS_ChannelGetData readsize = "+readsize+", closing it");
close();
}
}
return null;
}
private void logger_error(String msg) {
Logger.error(msg);
}
private void logger_info(String msg) {
Logger.info(msg);
}
}

View File

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

133
src/Audio/BassMix.java Normal file
View File

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

24
src/Audio/BassOPUS.java Normal file
View File

@@ -0,0 +1,24 @@
package Audio;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import Audio.Bass.BASS_FILEPROCS;
import Audio.Bass.DOWNLOADPROC;
@SuppressWarnings("unused")
public interface BassOPUS extends Library {
BassOPUS Instance = (BassOPUS) Native.load("bassopus",BassOPUS.class);
interface Constant{
// BASS_CHANNELINFO type
int BASS_CTYPE_STREAM_OPUS = 0x11200;
// Additional attributes
int BASS_ATTRIB_OPUS_ORIGFREQ = 0x13000;
}
int BASS_OPUS_StreamCreateFile(String file, long offset, long length, int flags);
int BASS_OPUS_StreamCreateFile(Pointer file, long offset, long length, int flags);
int BASS_OPUS_StreamCreateURL(String url, int offset, int flags, DOWNLOADPROC proc, Pointer user);
int BASS_OPUS_StreamCreateFileUser(int system, int flags, BASS_FILEPROCS procs, Pointer user);
}

View File

@@ -0,0 +1,162 @@
package Audio;
import org.jetbrains.annotations.NotNull;
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 : <a href="http://thorntonzone.com/manuals/Compression/Fax,%20IBM%20MMR/MMSC/mmsc/uk/co/mmscomputing/sound/">...</a>
* 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
*
*/
@SuppressWarnings("unused")
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 final Compressor alawcompressor=new ALawCompressor();
static private final Compressor ulawcompressor=new uLawCompressor();
private final Compressor compressor;
public CompressInputStream(InputStream in, boolean useALaw) {
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(@NotNull byte[] b)throws IOException{
return read(b,0,b.length);
}
public int read(@NotNull byte[] b, int off, int len)throws IOException{
int i,sample;
byte[] inb;
inb=new byte[len<<1]; // get 16bit PCM data
len=in.read(inb);
if(len==-1){return -1;}
i=0;
while(i<len){
sample = (inb[i++]&0x00FF);
sample |= (inb[i++]<<8);
b[off++]=(byte)compressor.compress((short)sample);
}
return len>>1;
}
}
abstract class Compressor{
protected abstract int compress(short sample);
}
/*
Mathematical Tools in Signal Processing with C++ and Java Simulations
by Willi-Hans Steeb
International School for Scientific Computing
*/
class ALawCompressor extends Compressor{
static final int cClip = 32635;
static final int[] ALawCompressTable ={
1,1,2,2,3,3,3,3,
4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,
5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7
};
protected int compress(short sample){
int sign;
int exponent;
int mantissa;
int compressedByte;
sign = ((~sample) >> 8) & 0x80;
if(sign==0){ sample *= -1;}
if(sample > cClip){ sample = cClip; }
if(sample >= 256){
exponent = ALawCompressTable[(sample >> 8) & 0x007F];
mantissa = (sample >> (exponent + 3) ) & 0x0F;
compressedByte = 0x007F & ((exponent << 4) | mantissa);
}else{
compressedByte = 0x007F & (sample >> 4);
}
compressedByte ^= (sign ^ 0x55);
return compressedByte;
}
}
class uLawCompressor extends Compressor{
static final int cClip = 32635;
static final int cBias = 0x84;
int[] uLawCompressTable ={
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};
protected int compress(short sample){
int sign;
int exponent;
int mantissa;
int compressedByte;
sign = (sample >> 8) & 0x80;
if(sign!=0){ sample *= -1;}
if(sample > cClip){ sample = cClip; }
sample += cBias;
exponent = uLawCompressTable[(sample >> 7) & 0x00FF];
mantissa = (sample >> (exponent + 3)) & 0x0F;
compressedByte = ~(sign | (exponent << 4) | mantissa);
return compressedByte&0x000000FF;
}
}

View File

@@ -0,0 +1,118 @@
package Audio;
import org.jetbrains.annotations.NotNull;
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 : <a href="http://thorntonzone.com/manuals/Compression/Fax,%20IBM%20MMR/MMSC/mmsc/uk/co/mmscomputing/sound/">...</a>
* @author rdkartono
*
*/
@SuppressWarnings("unused")
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 final 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 final 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 final byte[] table;
public ConvertInputStream(InputStream in, boolean useALaw2uLaw){
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(@NotNull byte[] b, int off, int len)throws IOException{
byte[] inb=new byte[len];
len=in.read(inb);
if(len==-1){return -1;}
for(int i=0;i<len;i++){
b[off++]=table[inb[i]&0x00FF];
}
return len;
}
}

View File

@@ -0,0 +1,138 @@
package Audio;
import org.jetbrains.annotations.NotNull;
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 : <a href="http://thorntonzone.com/manuals/Compression/Fax,%20IBM%20MMR/MMSC/mmsc/uk/co/mmscomputing/sound/">...</a>
* 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
*
*/
@SuppressWarnings("unused")
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 final 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 final 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 final int[] table;
public DecompressInputStream(InputStream in, boolean useALaw){
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(@NotNull byte[] b)throws IOException{
return read(b,0,b.length);
}
public int read(@NotNull 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;
}
}

222
src/Audio/G711.java Normal file
View File

@@ -0,0 +1,222 @@
package Audio;
/** G.711 codec.
* This class provides methods for u-law, A-law and linear PCM conversions.
*/
@SuppressWarnings("unused")
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));
}
}

873
src/Audio/JSIPAudio.java Normal file
View File

@@ -0,0 +1,873 @@
package Audio;
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 com.sun.jna.Native;
import Audio.Bass.BASS_DEVICEINFO;
import org.pmw.tinylog.Logger;
/**
* SIP Audio Converter
* @author rdkartono
*
*/
@SuppressWarnings({"unused", "ResultOfMethodCallIgnored"})
public class JSIPAudio {
private final Bass BASS = Bass.Instance;
private final BassMix BASSMIX = BassMix.Instance;
private final BassEnc BASSENC = BassEnc.Instance;
private final BassEncAAC BASSENC_AAC = BassEncAAC.Instance;
private final String currentfolder ;
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 AAC AAC;
public JSIPAudio(AudioEvent1 ulaw_event, AudioEvent1 pcm16_event, AudioEvent1 aac_event) {
currentfolder = new File("").getAbsolutePath();
inited_playback = new HashSet<>();
inited_recorder = new HashSet<>();
loaded_plugin = new HashSet<>();
extract_libraries();
ULaw = new ULaw(16,1,ulaw_event);
PCM16 = new PCM(16,1, pcm16_event);
AAC = new AAC(16,1, aac_event);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
Logger.info("JSIPAudio ShutdownHook called");
inited_recorder.forEach(recdev->{
if (BASS.BASS_RecordSetDevice(recdev)) {
if (BASS.BASS_RecordFree()) {
Logger.info("Recorder Device="+recdev+" freed");
} else Logger.error(BASS.GetBassError("BASS_RecordFree"));
} else Logger.error(BASS.GetBassError("BASS_RecordSetDevice"));
});
inited_recorder.clear();
inited_playback.forEach(playdev ->{
if (BASS.BASS_SetDevice(playdev)) {
if (BASS.BASS_Free()) {
Logger.info("Playback Device="+playdev+" freed");
} else Logger.error(BASS.GetBassError("BASS_Free"));
} else Logger.error(BASS.GetBassError("BASS_SetDevice"));
});
inited_playback.clear();
loaded_plugin.forEach(ph ->{
if (BASS.BASS_PluginFree(ph)) {
Logger.info("Plugin freed");
} else Logger.error(BASS.GetBassError("BASS_PluginFree"));
});
loaded_plugin.clear();
}));
inited = true;
}
private String extract_library(String folder, String libname){
try {
File existinglib = new File(folder, 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());
}
}
private String ToHexString(int value) {
return "0x"+Integer.toHexString(value).toUpperCase();
}
private void extract_libraries() {
raise_log(extract_library(currentfolder, "bass"));
raise_log(extract_library(currentfolder, "bassmix"));
raise_log(extract_library(currentfolder, "bass_aac"));
raise_log(extract_library(currentfolder, "bassenc"));
raise_log(extract_library(currentfolder, "bassenc_aac"));
raise_log("Bass Version = "+ToHexString(BASS.BASS_GetVersion()));
raise_log("BassMix Version = "+ToHexString(BASSMIX.BASS_Mixer_GetVersion()));
raise_log("BassEnc Version = "+ToHexString(BASSENC.BASS_Encode_GetVersion()));
raise_log("BassEnc_AAC Version = "+ToHexString(BASSENC_AAC.BASS_Encode_AAC_GetVersion()));
int pluginhandle = BASS.BASS_PluginLoad("bass_aac", 0);
if (pluginhandle!=0) {
loaded_plugin.add(pluginhandle);
raise_log("Plugin AAC Loaded");
} else raise_log(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 (!BASS.BASS_ChannelSetAttribute(handle, Bass.BASS_ATTRIB_VOL, value/100.0f)) {
raise_log(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) {
Bass.FloatValue val = new Bass.FloatValue();
if (BASS.BASS_ChannelGetAttribute(handle, Bass.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 (BASS.BASS_SetDevice(devid)) {
if (BASS.BASS_SetVolume(value/100.0f)) {
return true;
} else raise_log(BASS.GetBassError("BASS_SetVolume"));
} else raise_log(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 (BASS.BASS_SetDevice(devid)) {
float val = BASS.BASS_GetVolume();
if (val>=0) {
int result = (int)(val * 100);
if (result>100) result = 100;
return result;
} else raise_log(BASS.GetBassError("BASS_GetVolume"));
} else raise_log(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 (BASS.BASS_RecordSetInput(devid, Bass.BASS_INPUT_ON, value/100.0f)) {
return true;
} else {
raise_log(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) {
Bass.FloatValue val = new Bass.FloatValue();
int result = BASS.BASS_RecordGetInput(devid, val);
if (result==-1) {
// error
raise_log(BASS.GetBassError("BASS_RecordGetInput"));
return -1;
} else {
return (int)(val.value * 100);
}
}
/**
* 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;
List<BASS_DEVICEINFO> result = new ArrayList<>();
do {
BASS_DEVICEINFO dev = new BASS_DEVICEINFO();
success = 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;
List<BASS_DEVICEINFO> result = new ArrayList<>();
do {
BASS_DEVICEINFO dev = new BASS_DEVICEINFO();
success = 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 (BASS.BASS_GetDeviceInfo(devid, dev)) {
return dev;
} else raise_log(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 (BASS.BASS_RecordGetDeviceInfo(devid, dev)) {
return dev;
} else raise_log(BASS.GetBassError("BASS_RecordGetDeviceInfo"));
return null;
}
private void raise_log(String msg) {
Logger.info(msg);
}
@SuppressWarnings("BusyWait")
public class ULaw extends basicfunctions{
public final int[] samplingrate = {8000};
public final int channel;
public final int bits;
public final AudioEvent1 event;
public ULaw(int bits, int channel, AudioEvent1 event) {
super(48000, Bass.BASS_DEVICE_MONO | Bass.BASS_DEVICE_FREQ);
this.channel = channel;
this.bits = bits;
this.event = event;
}
/**
* 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 = System.currentTimeMillis();
int totalread = 0;
int readcount;
try {
do {
Pointer ptr = new Memory(1024);
readcount = 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 = System.currentTimeMillis() - timestamp;
raise_log(MessageFormat.format("ULaw FileConvert from {0} to {1} finished, Bytes Read={2}, Conversion took {3} ms", sourcefilename, targetfilename, totalread, delta));
if (event!=null) event.fileconvertfinish(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(JSIPAudio.this::raise_log);
}
} else {
Stream.of(op.getErrorlist()).forEach(JSIPAudio.this::raise_log);
Stream.of(op.CloseHandles()).forEach(JSIPAudio.this::raise_log);
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 = 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);
if (event!=null) event.filestreaming(sourcefilename, op.getFilehandle(), ulaw);
}
}, null, 0);
if (dsphandle!=0) {
// sampe sini , DSP sudah created
long timestamp = System.currentTimeMillis();
raise_log("Mixer Playback for "+sourcefilename+" started");
// tinggal loop aja sampe selesai
do {
try {
Thread.sleep(100);
//Thread.yield();
} catch (InterruptedException e) {
raise_log("Interrupt Exception="+e.getMessage());
}
} while(BASS.BASS_ChannelIsActive(op.getFilehandle())== Bass.BASS_ACTIVE_PLAYING);
// finish playback !!
long delta = System.currentTimeMillis() - timestamp;
raise_log("Mixer Playback for "+sourcefilename+" finished, duration="+delta+" ms");
} else raise_log(BASS.GetBassError("BASS_ChannelSetDSP"));
// sampe sini selesai semua
Stream.of(op.CloseHandles()).forEach(JSIPAudio.this::raise_log);
});
return op.getFilehandle();
} else {
raise_log(BASS.GetBassError("BASS_ChannelStart"));
Stream.of(op.CloseHandles()).forEach(JSIPAudio.this::raise_log);
return 0;
}
} else {
Stream.of(op.getErrorlist()).forEach(JSIPAudio.this::raise_log);
Stream.of(op.CloseHandles()).forEach(JSIPAudio.this::raise_log);
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, (handle, buffer, length, user) -> {
byte[] data = buffer.getByteArray(0,length);
byte[] ulaw = ConvertPCMToULaw(data);
// inputname ada di Pointer user
if (event!=null) event.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(JSIPAudio.this::raise_log);
Stream.of(oi.StopRecord()).forEach(JSIPAudio.this::raise_log);
return 0;
} else return oi.getRecordhandle(); // aman
} else {
Stream.of(oi.getErrorlist()).forEach(JSIPAudio.this::raise_log);
Stream.of(oi.StopRecord()).forEach(JSIPAudio.this::raise_log);
return 0;
}
}
}
public class PCM extends basicfunctions{
public PCM(int bits, int channel, AudioEvent1 event) {
super(48000, Bass.BASS_DEVICE_MONO | Bass.BASS_DEVICE_FREQ);
this.channel = channel;
this.bits = bits;
this.MIME = bits==24 ? "audio/L24" : "audio/L16";
this.event = event;
}
public final int[] samplingrate = {16000, 32000, 44100, 48000};
public final int channel;
public final int bits;
public final String MIME;
public final AudioEvent1 event;
private byte[] Convert_PCM16_to_PCM24(byte[] data) {
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 = System.currentTimeMillis();
int totalread = 0;
int readcount;
do {
Pointer ptr = new Memory(1024);
readcount = 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(JSIPAudio.this::raise_log);
long delta = System.currentTimeMillis() - timestamp;
raise_log(MessageFormat.format("FileConvert from {0} to {1} finished, Bytes Read={2}, Conversion took {3} ms", sourcefilename, targetfilename, totalread, delta));
if (event!=null) event.fileconvertfinish(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(JSIPAudio.this::raise_log);
}
} else {
Stream.of(op.getErrorlist()).forEach(JSIPAudio.this::raise_log);
Stream.of(op.CloseHandles()).forEach(JSIPAudio.this::raise_log);
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 = 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);
if (event!=null) event.filestreaming(sourcefilename, op.getFilehandle(),cc);
}, null, 0);
if (dsphandle==0) {
raise_log(BASS.GetBassError("BASS_ChannelSetDSP"));
Stream.of(op.CloseHandles()).forEach(JSIPAudio.this::raise_log);
return 0;
}
// sampe sini berhasil
return op.getFilehandle();
}
else {
raise_log(BASS.GetBassError("BASS_ChannelStart"));
Stream.of(op.CloseHandles()).forEach(JSIPAudio.this::raise_log);
return 0;
}
} else {
Stream.of(op.getErrorlist()).forEach(JSIPAudio.this::raise_log);
Stream.of(op.CloseHandles()).forEach(JSIPAudio.this::raise_log);
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, (handle, buffer, length, user) -> {
byte[] data = buffer.getByteArray(0, length);
String recdevname = user==null ? "" : user.getString(0);
if (bits==24) {
if (event!=null) event.inputstreaming(recdevname, handle, Convert_PCM16_to_PCM24(data));
} else {
if (event!=null) event.inputstreaming(recdevname, handle, data);
}
return true;
});
if (oi.isSuccess()) {
inited_recorder.add(oi.getRecorderdev());
return oi.getRecordhandle();
} else {
Stream.of(oi.getErrorlist()).forEach(JSIPAudio.this::raise_log);
Stream.of(oi.StopRecord()).forEach(JSIPAudio.this::raise_log);
return 0;
}
}
}
@SuppressWarnings("BusyWait")
public class AAC extends basicfunctions{
public AAC(int bits, int channel, AudioEvent1 event) {
super(48000, Bass.BASS_DEVICE_MONO | Bass.BASS_DEVICE_FREQ);
this.bits = bits;
this.channel = channel;
this.event = event;
}
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 AudioEvent1 event;
@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 = BASSENC_AAC.BASS_Encode_AAC_StartFile(op.getMixerhandle(), null, 0, targetfilename);
if (encodehandle==0) {
// gak bisa create encoder
raise_log(BASS.GetBassError("BASS_Encode_AAC_StartFile"));
// free handle
Stream.of(op.CloseHandles()).forEach(JSIPAudio.this::raise_log);
return false;
}
// sampe sini encoder jalan
exec.submit(()->{
do {
try {
Thread.sleep(100);
//Thread.yield();
} catch (InterruptedException e) {
raise_log("InterruptException");
}
} while (BASS.BASS_ChannelIsActive(op.getMixerhandle())== Bass.BASS_ACTIVE_PLAYING);
// free encoder
BASSENC.BASS_Encode_Stop(encodehandle);
// free handle
Stream.of(op.CloseHandles()).forEach(JSIPAudio.this::raise_log);
if (event!=null) event.fileconvertfinish(sourcefilename, targetfilename);
});
return true;
} else {
Stream.of(op.getErrorlist()).forEach(JSIPAudio.this::raise_log);
Stream.of(op.CloseHandles()).forEach(JSIPAudio.this::raise_log);
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 = BASSENC_AAC.BASS_Encode_AAC_Start(op.getMixerhandle(), null, 0,
(encode_handle, source_handle, buffer, length, offset, user)->{
if (event!=null) event.filestreaming(sourcefilename, op.getFilehandle(), buffer.getByteArray(0,length));
}
, null);
if (encodehandle==0) {
raise_log(BASS.GetBassError("BASS_Encode_AAC_Start"));
// free handle
Stream.of(op.CloseHandles()).forEach(JSIPAudio.this::raise_log);
return 0;
}
exec.submit(()->{
do {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
raise_log("InterruptException");
}
} while(BASS.BASS_ChannelIsActive(op.getMixerhandle())== Bass.BASS_ACTIVE_PLAYING);
BASSENC.BASS_Encode_Stop(encodehandle);
// free handle
Stream.of(op.CloseHandles()).forEach(JSIPAudio.this::raise_log);
});
return op.getFilehandle();
} else {
raise_log(BASS.GetBassError("BASS_ChannelStart"));
Stream.of(op.CloseHandles()).forEach(JSIPAudio.this::raise_log);
return 0;
}
} else {
Stream.of(op.getErrorlist()).forEach(JSIPAudio.this::raise_log);
Stream.of(op.CloseHandles()).forEach(JSIPAudio.this::raise_log);
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 = BASSENC_AAC.BASS_Encode_AAC_Start(oi.getRecordhandle(), null, 0, (encode_handle, source_handle, buffer, length, offset, user)->{
byte[] data = buffer.getByteArray(0, length);
if (event!=null) event.inputstreaming(oi.getDeviceName(), source_handle, data);
}, null);
if (encodehandle!=0) {
return oi.getRecordhandle();
} else {
raise_log(BASS.GetBassError("BASS_Encode_AAC_Start"));
Stream.of(oi.StopRecord()).forEach(JSIPAudio.this::raise_log);
}
} else {
Stream.of(oi.getErrorlist()).forEach(JSIPAudio.this::raise_log);
Stream.of(oi.StopRecord()).forEach(JSIPAudio.this::raise_log);
}
return 0;
}
}
}

View File

@@ -0,0 +1,311 @@
package Audio;
import java.util.ArrayList;
import java.util.List;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import Audio.Bass.BASS_DEVICEINFO;
import Audio.Bass.RECORDPROC;
import lombok.Getter;
@SuppressWarnings("unused")
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;
private final Bass BASS = Bass.Instance;
private final BassMix BASSMIX = BassMix.Instance;
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 Recording Handle
* @return true if Input can be stopped
*/
public boolean CloseInputForStreaming(int rechandle) {
return BASS.BASS_ChannelFree(rechandle);
}
/**
* 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 (!BASS.BASS_Init(device, devinit_freq, devinit_flag)) {
// ada gagal init, cek dulu
int err = BASS.BASS_ErrorGetCode();
// gak boleh gagal
//raise_log(BASS.GetBassError("BASS_Init", err));
return err == Bass.BASS_ERROR_ALREADY;
}
// 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 (!BASS.BASS_RecordInit(device)) {
// ada gagal init, cek dulu
int err = BASS.BASS_ErrorGetCode();
// gak boleh gagal
//raise_log(BASS.GetBassError("BASS_RecordInit", err));
return err == Bass.BASS_ERROR_ALREADY;
}
return true;
}
/**
* OpenInput helper
* @author rdkartono
*
*/
protected class OpenInput{
@Getter private final int recorderdev;
@Getter private final int samplingrate;
@Getter private boolean success = false;
@Getter private final String[] errorlist;
@Getter private int recordhandle=0;
private final RECORDPROC recproc;
private @Getter BASS_DEVICEINFO devinfo;
/**
* 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<>();
if (RecordInit(recorderdev)) {
devinfo = new BASS_DEVICEINFO();
if (!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 = BASS.BASS_RecordStart(samplingrate, 1, Bass.BASS_RECORD_PAUSE, recproc, user);
if (recordhandle!=0) {
success = true;
} else error.add(BASS.GetBassError("BASS_RecordStart"));
} else error.add(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<>();
if (recordhandle!=0) {
if (BASS.BASS_ChannelStart(recordhandle)) {
// nothing...
} else error.add(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<>();
if (recordhandle!=0) {
if (BASS.BASS_ChannelFree(recordhandle)) {
// nothing...
} else error.add(BASS.GetBassError("BASS_ChannelStop"));
recordhandle = 0;
} else error.add("RecordHandle is zero");
return error.toArray(new String[0]);
}
}
/**
* OpenFile helper
* @author rdkartono
*
*/
@Getter
protected class OpenFile{
private final int playbackdev;
private final int samplingrate;
private final String filename;
private final boolean decodingonly;
private boolean success = false;
private int filehandle;
private int mixerhandle;
private int mixerflag;
private final String[] errorlist;
private long ByteSize;
private double Duration;
private BASS_DEVICEINFO devinfo;
/**
* 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 Decoded 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 = BASS.BASS_ChannelGetLength(filehandle, Bass.BASS_POS_BYTE);
Duration = BASS.BASS_ChannelBytes2Seconds(filehandle, ByteSize);
}
devinfo = new BASS_DEVICEINFO();
if (!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<>();
if (BassInit(playbackdev)) {
filehandle = BASS.BASS_StreamCreateFile(false,filename, 0, 0, Bass.BASS_STREAM_DECODE);
if (filehandle!=0) {
mixerflag = decodingonly ? Bass.BASS_STREAM_DECODE : 0;
mixerhandle = BASSMIX.BASS_Mixer_StreamCreate(samplingrate, 1, mixerflag);
if (mixerhandle!=0) {
if (BASSMIX.BASS_Mixer_StreamAddChannel(mixerhandle, filehandle, Bass.BASS_STREAM_AUTOFREE)) {
success = true;
} else error.add(BASS.GetBassError("BASS_Mixer_StreamAddChannel"));
} else error.add(BASS.GetBassError("BASS_Mixer_StreamCreate"));
} else error.add(BASS.GetBassError("BASS_StreamCreateFile"));
} else error.add(BASS.GetBassError("Bass_Init"));
return error.toArray(new String[0]);
}
/**
* Start Playback
* @return true if it 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 BASS.BASS_ChannelStart(filehandle);
}
}
}
}
return false;
}
/**
* Close Opened handles
* @return array of string containing error messages
*/
public String[] CloseHandles() {
List<String> error = new ArrayList<>();
if (filehandle!=0) {
if (success) {
// sudah plug ke mixer
if (!BASSMIX.BASS_Mixer_ChannelRemove(filehandle)) {
error.add(BASS.GetBassError("BASS_Mixer_ChannelRemove"));
}
}
if (!BASS.BASS_ChannelFree(filehandle)) {
error.add(BASS.GetBassError("BASS_ChannelFree"));
}
filehandle = 0;
}
if (mixerhandle!=0) {
if (!BASS.BASS_ChannelFree(mixerhandle)) {
error.add(BASS.GetBassError("BASS_ChannelFree"));
}
mixerhandle = 0;
}
return error.toArray(new String[0]);
}
}
}

6
src/Main.java Normal file
View File

@@ -0,0 +1,6 @@
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}

53
src/peers/Config.java Normal file
View File

@@ -0,0 +1,53 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package peers;
import java.net.InetAddress;
import peers.media.MediaMode;
import 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);
}

156
src/peers/JavaConfig.java Normal file
View File

@@ -0,0 +1,156 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2012 Yohann Martineau
*/
package peers;
import java.net.InetAddress;
import peers.media.MediaMode;
import 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;
}
}

355
src/peers/XmlConfig.java Normal file
View File

@@ -0,0 +1,355 @@
/*
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 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 org.pmw.tinylog.Logger;
import peers.media.MediaMode;
import peers.sip.RFC3261;
import peers.sip.syntaxencoding.SipURI;
import 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 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) {
file = new File(fileName);
if (!file.exists()) {
Logger.debug("config file " + fileName + " not found");
return;
}
DocumentBuilderFactory documentBuilderFactory =
DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder;
try {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
Logger.error("parser configuration exception", e);
return;
}
try {
document = documentBuilder.parse(file);
} catch (SAXException e) {
Logger.error("cannot parse " + fileName,e );
return;
} catch (IOException e) {
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) {
Logger.error("unknown host: " + address, e);
}
userPartNode = getFirstChild(documentElement, "userPart");
if (isNullOrEmpty(userPartNode)) {
Logger.error("userpart not found in configuration file");
} else {
userPart = userPartNode.getTextContent();
}
domainNode = getFirstChild(documentElement, "domain");
if (isNullOrEmpty(domainNode)) {
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) {
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())) {
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) {
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) {
Logger.error("cannot create transformer", e);
return;
}
FileWriter fileWriter;
try {
fileWriter = new FileWriter(file);
} catch (IOException e) {
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) {
Logger.error("cannot save config file", e);
return;
}
Logger.debug("config file saved");
}
@Override
public InetAddress getLocalInetAddress() {
return localInetAddress;
}
@Override
public InetAddress getPublicInetAddress() {
return publicInetAddress;
}
@Override
public String getUserPart() {
return userPart;
}
@Override
public String getDomain() {
return domain;
}
@Override
public String getPassword() {
return password;
}
@Override
public SipURI getOutboundProxy() {
return outboundProxy;
}
@Override
public int getSipPort() {
return sipPort;
}
@Override
public MediaMode getMediaMode() {
return mediaMode;
}
@Override
public boolean isMediaDebug() {
return mediaDebug;
}
@Override
public int getRtpPort() {
return rtpPort;
}
@Override
public void setLocalInetAddress(InetAddress inetAddress) {
this.localInetAddress = inetAddress;
ipAddressNode.setTextContent(inetAddress.getHostAddress());
}
@Override
public void setPublicInetAddress(InetAddress inetAddress) {
this.publicInetAddress = inetAddress;
}
@Override
public void setUserPart(String userPart) {
this.userPart = userPart;
userPartNode.setTextContent(userPart);
}
@Override
public void setDomain(String domain) {
this.domain = domain;
domainNode.setTextContent(domain);
}
@Override
public void setPassword(String password) {
this.password = password;
passwordNode.setTextContent(password);
}
@Override
public void setOutboundProxy(SipURI outboundProxy) {
this.outboundProxy = outboundProxy;
if (outboundProxy == null) {
outboundProxyNode.setTextContent("");
} else {
outboundProxyNode.setTextContent(outboundProxy.toString());
}
}
@Override
public void setSipPort(int sipPort) {
this.sipPort = sipPort;
sipPortNode.setTextContent(Integer.toString(sipPort));
}
@Override
public void setMediaMode(MediaMode mediaMode) {
this.mediaMode = mediaMode;
mediaModeNode.setTextContent(mediaMode.toString());
}
@Override
public void setMediaDebug(boolean mediaDebug) {
this.mediaDebug = mediaDebug;
mediaDebugNode.setTextContent(Boolean.toString(mediaDebug));
}
@Override
public void setRtpPort(int rtpPort) {
this.rtpPort = rtpPort;
rtpPortNode.setTextContent(Integer.toString(rtpPort));
}
@Override
public String getMediaFile() {
return mediaFile;
}
@Override
public void setMediaFile(String mediaFile) {
this.mediaFile = mediaFile;
}
}

View File

@@ -0,0 +1,214 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010, 2011, 2012 Yohann Martineau
*/
package 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 org.pmw.tinylog.Logger;
import peers.media.AbstractSoundManager;
import 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 String peersHome;
public JavaxSoundManager(boolean mediaDebug, String peersHome) {
this.mediaDebug = mediaDebug;
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() {
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) {
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) {
Logger.error("target line unavailable", e);
return null;
} catch (SecurityException e) {
Logger.error("security exception", e);
return null;
} catch (Throwable t) {
Logger.error("throwable " + t.getMessage());
return null;
}
targetDataLine.start();
try {
sourceDataLine = (SourceDataLine) AudioSystem.getLine(sourceInfo);
sourceDataLine.open(audioFormat);
} catch (LineUnavailableException e) {
Logger.error("source line unavailable", e);
return null;
}
sourceDataLine.start();
return null;
}
});
}
@Override
public synchronized void close() {
Logger.debug("closeLines");
if (microphoneOutput != null) {
try {
microphoneOutput.close();
} catch (IOException e) {
Logger.error("cannot close file", e);
}
microphoneOutput = null;
}
if (speakerInput != null) {
try {
speakerInput.close();
} catch (IOException e) {
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) {
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) {
Logger.error("cannot write to file", e);
return -1;
}
}
return numberOfBytesWritten;
}
}

View File

@@ -0,0 +1,28 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2013 Yohann Martineau
*/
package peers.media;
public abstract class AbstractSoundManager implements SoundSource {
public final static String MEDIA_DIR = "media";
public abstract void init();
public abstract void close();
public abstract int writeData(byte[] buffer, int offset, int length);
}

View File

@@ -0,0 +1,80 @@
/*
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 peers.media;
import org.pmw.tinylog.Logger;
import java.io.IOException;
import java.io.PipedOutputStream;
import java.util.concurrent.CountDownLatch;
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 CountDownLatch latch;
public Capture(PipedOutputStream rawData, SoundSource soundSource,
CountDownLatch latch) {
this.rawData = rawData;
this.soundSource = soundSource;
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) {
Logger.error("input/output error", e);
return;
}
}
latch.countDown();
if (latch.getCount() != 0) {
try {
latch.await();
} catch (InterruptedException e) {
Logger.error("interrupt exception", e);
}
}
}
public synchronized void setStopped(boolean isStopped) {
this.isStopped = isStopped;
}
}

View File

@@ -0,0 +1,130 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007-2013 Yohann Martineau
*/
package peers.media;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.CountDownLatch;
import org.pmw.tinylog.Logger;
import peers.rtp.RFC3551;
import peers.rtp.RtpSession;
import 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, 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) {
Logger.error("input/output error", e);
return;
}
PipedOutputStream encodedDataOutput = new PipedOutputStream();
PipedInputStream encodedDataInput;
try {
encodedDataInput = new PipedInputStream(encodedDataOutput,
PIPE_SIZE);
} catch (IOException e) {
Logger.error("input/output error");
rawDataInput.close();
return;
}
capture = new Capture(rawDataOutput, soundSource, latch);
switch (codec.getPayloadType()) {
case RFC3551.PAYLOAD_TYPE_PCMU:
encoder = new PcmuEncoder(rawDataInput, encodedDataOutput,
mediaDebug, peersHome, latch);
break;
case RFC3551.PAYLOAD_TYPE_PCMA:
encoder = new PcmaEncoder(rawDataInput, encodedDataOutput,
mediaDebug, peersHome, latch);
break;
default:
encodedDataInput.close();
rawDataInput.close();
throw new RuntimeException("unknown payload type");
}
rtpSender = new RtpSender(encodedDataInput, rtpSession, mediaDebug,
codec, peersHome, latch);
}
public void start() throws IOException {
capture.setStopped(false);
encoder.setStopped(false);
rtpSender.setStopped(false);
Thread captureThread = new Thread(capture,
Capture.class.getSimpleName());
Thread encoderThread = new Thread(encoder,
Encoder.class.getSimpleName());
Thread rtpSenderThread = new Thread(rtpSender,
RtpSender.class.getSimpleName());
captureThread.start();
encoderThread.start();
rtpSenderThread.start();
}
public void stop() {
if (capture != null) {
capture.setStopped(true);
}
if (encoder != null) {
encoder.setStopped(true);
}
if (rtpSender != null) {
rtpSender.setStopped(true);
}
}
public synchronized RtpSession getRtpSession() {
return rtpSession;
}
public RtpSender getRtpSender() {
return rtpSender;
}
}

View File

@@ -0,0 +1,26 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package peers.media;
public abstract class Decoder {
public abstract byte[] process(byte[] media);
}

View File

@@ -0,0 +1,95 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package peers.media;
import java.util.ArrayList;
import java.util.List;
import peers.rtp.RFC4733;
import 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;
}
}

80
src/peers/media/Echo.java Normal file
View File

@@ -0,0 +1,80 @@
/*
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 peers.media;
import org.pmw.tinylog.Logger;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
public class Echo implements Runnable {
public static final int BUFFER_SIZE = 2048;
private DatagramSocket datagramSocket;
private InetAddress remoteAddress;
private int remotePort;
private boolean isRunning;
public Echo(DatagramSocket datagramSocket,
String remoteAddress, int remotePort)
throws UnknownHostException {
this.datagramSocket = datagramSocket;
this.remoteAddress = InetAddress.getByName(remoteAddress);
this.remotePort = remotePort;
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) {
Logger.debug("echo socket timeout");
continue;
}
datagramPacket = new DatagramPacket(buf,
datagramPacket.getLength(), remoteAddress, remotePort);
datagramSocket.send(datagramPacket);
}
} catch (IOException e) {
Logger.error("input/output error", e);
} finally {
datagramSocket.close();
}
}
public synchronized void stop() {
isRunning = false;
}
}

View File

@@ -0,0 +1,149 @@
/*
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 peers.media;
import org.pmw.tinylog.Logger;
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;
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 String peersHome;
private CountDownLatch latch;
public Encoder(PipedInputStream rawData, PipedOutputStream encodedData,
boolean mediaDebug, String peersHome,
CountDownLatch latch) {
this.rawData = rawData;
this.encodedData = encodedData;
this.mediaDebug = mediaDebug;
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) {
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) {
Logger.error("interrupt exception", e);
}
}
if (isStopped) {
break;
}
buffer = new byte[ready];
rawData.read(buffer);
if (mediaDebug) {
try {
encoderInput.write(buffer);
} catch (IOException e) {
Logger.error("cannot write to file", e);
}
}
} catch (IOException e) {
Logger.error("input/output error", e);
return;
}
byte[] ulawData = process(buffer);
if (mediaDebug) {
try {
encoderOutput.write(ulawData);
} catch (IOException e) {
Logger.error("cannot write to file", e);
break;
}
}
try {
encodedData.write(ulawData);
encodedData.flush();
} catch (IOException e) {
Logger.error("input/output error", e);
return;
}
}
if (mediaDebug) {
try {
encoderOutput.close();
encoderInput.close();
} catch (IOException e) {
Logger.error("cannot close file", e);
return;
}
}
latch.countDown();
if (latch.getCount() != 0) {
try {
latch.await();
} catch (InterruptedException e) {
Logger.error("interrupt exception", e);
}
}
}
public synchronized void setStopped(boolean isStopped) {
this.isStopped = isStopped;
}
public abstract byte[] process(byte[] media);
}

View File

@@ -0,0 +1,96 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2012 Yohann Martineau
*/
package peers.media;
import org.pmw.tinylog.Logger;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
// 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;
public FileReader(String fileName) {
try {
fileInputStream = new FileInputStream(fileName);
} catch (FileNotFoundException e) {
Logger.error("file not found: {}, exceptin : {}" , fileName, e);
}
}
public synchronized void close() {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
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) {
Logger.error("io exception {}", e);
} catch (InterruptedException e) {
Logger.error("file reader interrupted {}", e);
}
return null;
}
}

View File

@@ -0,0 +1,69 @@
/*
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 peers.media;
import java.io.IOException;
import org.pmw.tinylog.Logger;
import peers.rtp.RFC3551;
import peers.rtp.RtpListener;
import peers.rtp.RtpPacket;
import peers.rtp.RtpSession;
import 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)
throws IOException {
Logger.debug("playback codec:" + codec.toString().trim());
this.rtpSession = rtpSession;
this.soundManager = soundManager;
switch (codec.getPayloadType()) {
case RFC3551.PAYLOAD_TYPE_PCMU:
decoder = new PcmuDecoder();
break;
case RFC3551.PAYLOAD_TYPE_PCMA:
decoder = new PcmaDecoder();
break;
default:
throw new RuntimeException("unsupported payload type");
}
rtpSession.addRtpListener(this);
}
public void start() {
rtpSession.start();
}
@Override
public void receivedRtpPacket(RtpPacket rtpPacket) {
byte[] rawBuf = decoder.process(rtpPacket.getData());
if (soundManager != null) {
soundManager.writeData(rawBuf, 0, rawBuf.length);
}
}
}

View File

@@ -0,0 +1,335 @@
/*
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 peers.media;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import Audio.BassFileReader;
import org.pmw.tinylog.Logger;
import peers.rtp.RtpPacket;
import peers.rtp.RtpSession;
import peers.sdp.Codec;
import 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 DatagramSocket datagramSocket;
//private FileReader fileReader;
private BassFileReader fileReader;
public MediaManager(UserAgent userAgent) {
this.userAgent = userAgent;
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) {
Logger.error("unknown host: " + localAddress, e);
return;
}
rtpSession = new RtpSession(inetAddress, datagramSocket,
userAgent.isMediaDebug(), userAgent.getPeersHome());
try {
inetAddress = InetAddress.getByName(remoteAddress);
rtpSession.setRemoteAddress(inetAddress);
} catch (UnknownHostException e) {
Logger.error("unknown host: " + remoteAddress, e);
}
rtpSession.setRemotePort(remotePort);
try {
captureRtpSender = new CaptureRtpSender(rtpSession,
soundSource, userAgent.isMediaDebug(), codec, userAgent.getPeersHome());
} catch (IOException e) {
Logger.error("input/output error", e);
return;
}
try {
captureRtpSender.start();
} catch (IOException e) {
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);
} catch (IOException e) {
Logger.error("input/output error", e);
return;
}
incomingRtpReader.start();
break;
case echo:
Echo echo;
try {
echo = new Echo(datagramSocket, remoteAddress, remotePort);
} catch (UnknownHostException e) {
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);
fileReader = new BassFileReader(fileName,null);
startRtpSessionOnSuccessResponse(localAddress, remoteAddress,
remotePort, codec, fileReader);
try {
incomingRtpReader = new IncomingRtpReader(
captureRtpSender.getRtpSession(), null, codec);
} catch (IOException e) {
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(), userAgent.getPeersHome());
try {
InetAddress inetAddress = InetAddress.getByName(destAddress);
rtpSession.setRemoteAddress(inetAddress);
} catch (UnknownHostException e) {
Logger.error("unknown host: " + destAddress, e);
}
rtpSession.setRemotePort(destPort);
try {
captureRtpSender = new CaptureRtpSender(rtpSession,
soundSource, userAgent.isMediaDebug(), codec, userAgent.getPeersHome());
} catch (IOException e) {
Logger.error("input/output error", e);
return;
}
try {
captureRtpSender.start();
} catch (IOException e) {
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);
} catch (IOException e) {
Logger.error("input/output error", e);
return;
}
incomingRtpReader.start();
break;
case echo:
Echo echo;
try {
echo = new Echo(datagramSocket, destAddress, destPort);
} catch (UnknownHostException e) {
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);
fileReader = new BassFileReader(fileName, null);
startRtpSession(destAddress, destPort, codec, fileReader);
try {
incomingRtpReader = new IncomingRtpReader(rtpSession,
null, codec);
} catch (IOException e) {
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) {
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) {
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) {
Logger.debug("sleep interrupted");
}
}
rtpSession = null;
}
if (incomingRtpReader != null) {
incomingRtpReader = null;
}
if (captureRtpSender != null) {
captureRtpSender.stop();
captureRtpSender = null;
}
if (datagramSocket != null) {
datagramSocket = null;
}
switch (userAgent.getMediaMode()) {
case captureAndPlayback:
AbstractSoundManager soundManager = userAgent.getSoundManager();
if (soundManager != null) {
soundManager.close();
}
break;
case echo:
Echo echo = userAgent.getEcho();
if (echo != null) {
echo.stop();
userAgent.setEcho(null);
}
break;
case file:
fileReader.close();
break;
default:
break;
}
}
public void setDatagramSocket(DatagramSocket datagramSocket) {
this.datagramSocket = datagramSocket;
}
public DatagramSocket getDatagramSocket() {
return datagramSocket;
}
// public FileReader getFileReader() {
// return fileReader;
// }
public BassFileReader getFileReader() {
return fileReader;
}
}

View File

@@ -0,0 +1,24 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package peers.media;
public enum MediaMode {
none, captureAndPlayback, echo, file
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,130 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010, 2011 Oleg Kulikov, Yohann Martineau
*/
package peers.media;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.CountDownLatch;
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, String peersHome,
CountDownLatch latch) {
super(rawData, encodedData, mediaDebug, peersHome, latch);
}
/**
* Perform compression using U-law. Retrieved from Mobicents media server
* code.
*
* @param media the input uncompressed media
* @return the output compressed media.
*/
public byte[] process(byte[] media) {
byte[] compressed = new byte[media.length / 2];
int j = 0;
for (int i = 0; i < compressed.length; i++) {
short sample = (short) ((media[j++] & 0xff) | ((media[j++]) << 8));
compressed[i] = linear2ulaw(sample);
}
return compressed;
}
}

View File

@@ -0,0 +1,201 @@
/*
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 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 org.pmw.tinylog.Logger;
import peers.rtp.RtpPacket;
import peers.rtp.RtpSession;
import 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 String peersHome;
private CountDownLatch latch;
public RtpSender(PipedInputStream encodedData, RtpSession rtpSession,
boolean mediaDebug, Codec codec, 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) {
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) {
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) {
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) {
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) {
Logger.error("cannot close file", e);
return;
}
}
latch.countDown();
if (latch.getCount() != 0) {
try {
latch.await();
} catch (InterruptedException e) {
Logger.error("interrupt exception", e);
}
}
}
public synchronized void setStopped(boolean isStopped) {
this.isStopped = isStopped;
}
public void pushPackets(List<RtpPacket> rtpPackets) {
this.pushedPackets.addAll(rtpPackets);
}
}

View File

@@ -0,0 +1,30 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2012 Yohann Martineau
*/
package peers.media;
public interface SoundSource {
/**
* read raw data linear PCM 8kHz, 16 bits signed, mono-channel, little endian
* @return
*/
public byte[] readData();
}

64
src/peers/nat/Client.java Normal file
View File

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

View File

@@ -0,0 +1,117 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package 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/peers/nat/Server.java Normal file
View File

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

View File

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

View File

@@ -0,0 +1,26 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package peers.nat.api;
public interface DataReceiver {
public void dataReceived(byte[] data, String peerId);
}

View File

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

View File

@@ -0,0 +1,24 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package peers.nat.api;
public interface TCPTransport extends Transport {
}

View File

@@ -0,0 +1,25 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package peers.nat.api;
public interface Transport {
public void sendData(byte[] data);
}

View File

@@ -0,0 +1,24 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package peers.nat.api;
public interface UDPTransport extends Transport {
}

View File

@@ -0,0 +1,34 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package peers.rtp;
public class RFC3551 {
// payload types
public static final int PAYLOAD_TYPE_PCMU = 0;
public static final int PAYLOAD_TYPE_PCMA = 8;
// encoding names
public static final String PCMU = "PCMU";
public static final String PCMA = "PCMA";
}

View File

@@ -0,0 +1,32 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package peers.rtp;
public class RFC4733 {
// payload types
public static final int PAYLOAD_TYPE_TELEPHONE_EVENT = 101;
// encoding names
public static final String TELEPHONE_EVENT = "telephone-event";
}

View File

@@ -0,0 +1,26 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Yohann Martineau
*/
package peers.rtp;
public interface RtpListener {
void receivedRtpPacket(RtpPacket rtpPacket);
}

View File

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

View File

@@ -0,0 +1,119 @@
/*
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 peers.rtp;
import org.pmw.tinylog.Logger;
// RFC 3550
public class RtpParser {
public RtpPacket decode(byte[] packet) {
if (packet.length < 12) {
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;
}
}

View File

@@ -0,0 +1,264 @@
/*
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 peers.rtp;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import org.pmw.tinylog.Logger;
import peers.media.AbstractSoundManager;
/**
* can be instantiated on UAC INVITE sending or on UAS 200 OK sending
*/
public class RtpSession {
private InetAddress remoteAddress;
private int remotePort;
private DatagramSocket datagramSocket;
private ExecutorService executorService;
private List<RtpListener> rtpListeners;
private RtpParser rtpParser;
private FileOutputStream rtpSessionOutput;
private FileOutputStream rtpSessionInput;
private boolean mediaDebug;
private String peersHome;
public RtpSession(InetAddress localAddress, DatagramSocket datagramSocket,
boolean mediaDebug, String peersHome) {
this.mediaDebug = mediaDebug;
this.peersHome = peersHome;
this.datagramSocket = datagramSocket;
rtpListeners = new ArrayList<RtpListener>();
rtpParser = new RtpParser();
executorService = Executors.newSingleThreadExecutor();
}
public synchronized void start() {
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 + "_rtp_session.output";
try {
rtpSessionOutput = new FileOutputStream(fileName);
fileName = dir + date + "_rtp_session.input";
rtpSessionInput = new FileOutputStream(fileName);
} catch (FileNotFoundException e) {
Logger.error("cannot create file", e);
return;
}
}
executorService.submit(new Receiver());
}
public void stop() {
// AccessController.doPrivileged added for plugin compatibility
AccessController.doPrivileged(
new PrivilegedAction<Void>() {
public Void run() {
executorService.shutdown();
return null;
}
}
);
}
public void addRtpListener(RtpListener rtpListener) {
rtpListeners.add(rtpListener);
}
public synchronized void send(RtpPacket rtpPacket) {
if (datagramSocket == null) {
return;
}
byte[] buf = rtpParser.encode(rtpPacket);
final DatagramPacket datagramPacket =
new DatagramPacket(buf, buf.length,
remoteAddress, remotePort);
if (!datagramSocket.isClosed()) {
// AccessController.doPrivileged added for plugin compatibility
AccessController.doPrivileged(
new PrivilegedAction<Void>() {
@Override
public Void run() {
try {
datagramSocket.send(datagramPacket);
} catch (IOException e) {
Logger.error("cannot send rtp packet", e);
} catch (SecurityException e) {
Logger.error("security exception", e);
}
return null;
}
}
);
if (mediaDebug) {
try {
rtpSessionOutput.write(buf);
} catch (IOException e) {
Logger.error("cannot write to file", e);
}
}
}
}
public void setRemoteAddress(InetAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
public void setRemotePort(int remotePort) {
this.remotePort = remotePort;
}
private void closeFileAndDatagramSocket() {
if (mediaDebug) {
try {
rtpSessionOutput.close();
rtpSessionInput.close();
} catch (IOException e) {
Logger.error("cannot close file", e);
}
}
// AccessController.doPrivileged added for plugin compatibility
AccessController.doPrivileged(
new PrivilegedAction<Void>() {
@Override
public Void run() {
datagramSocket.close();
return null;
}
}
);
}
class Receiver implements Runnable {
@Override
public void run() {
int receiveBufferSize;
try {
receiveBufferSize = datagramSocket.getReceiveBufferSize();
} catch (SocketException e) {
Logger.error("cannot get datagram socket receive buffer size",
e);
return;
}
byte[] buf = new byte[receiveBufferSize];
final DatagramPacket datagramPacket = new DatagramPacket(buf,
buf.length);
final int noException = 0;
final int socketTimeoutException = 1;
final int ioException = 2;
int result = AccessController.doPrivileged(
new PrivilegedAction<Integer>() {
public Integer run() {
try {
datagramSocket.receive(datagramPacket);
} catch (SocketTimeoutException e) {
return socketTimeoutException;
} catch (IOException e) {
Logger.error("cannot receive packet", e);
return ioException;
}
return noException;
}
});
switch (result) {
case socketTimeoutException:
try {
executorService.execute(this);
} catch (RejectedExecutionException rej) {
closeFileAndDatagramSocket();
}
return;
case ioException:
return;
case noException:
break;
default:
break;
}
InetAddress remoteAddress = datagramPacket.getAddress();
if (remoteAddress != null &&
!remoteAddress.equals(RtpSession.this.remoteAddress)) {
RtpSession.this.remoteAddress = remoteAddress;
}
int remotePort = datagramPacket.getPort();
if (remotePort != RtpSession.this.remotePort) {
RtpSession.this.remotePort = remotePort;
}
byte[] data = datagramPacket.getData();
int offset = datagramPacket.getOffset();
int length = datagramPacket.getLength();
byte[] trimmedData = new byte[length];
System.arraycopy(data, offset, trimmedData, 0, length);
if (mediaDebug) {
try {
rtpSessionInput.write(trimmedData);
} catch (IOException e) {
Logger.error("cannot write to file", e);
return;
}
}
RtpPacket rtpPacket = rtpParser.decode(trimmedData);
for (RtpListener rtpListener: rtpListeners) {
rtpListener.receivedRtpPacket(rtpPacket);
}
try {
executorService.execute(this);
} catch (RejectedExecutionException rej) {
closeFileAndDatagramSocket();
}
}
}
public boolean isSocketClosed() {
if (datagramSocket == null) {
return true;
}
return datagramSocket.isClosed();
}
}

67
src/peers/sdp/Codec.java Normal file
View File

@@ -0,0 +1,67 @@
/*
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 peers.sdp;
import peers.media.MediaManager;
public class Codec {
private int payloadType;
private String name;
public int getPayloadType() {
return payloadType;
}
public void setPayloadType(int payloadType) {
this.payloadType = payloadType;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Codec)) {
return false;
}
Codec codec = (Codec)obj;
if (codec.getName() == null) {
return name == null;
}
return codec.getName().equalsIgnoreCase(name);
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append(RFC4566.TYPE_ATTRIBUTE).append(RFC4566.SEPARATOR);
buf.append(RFC4566.ATTR_RTPMAP).append(RFC4566.ATTR_SEPARATOR);
buf.append(payloadType).append(" ").append(name).append("/");
buf.append(MediaManager.DEFAULT_CLOCK).append("\r\n");
return buf.toString();
}
}

View File

@@ -0,0 +1,118 @@
/*
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 peers.sdp;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.Hashtable;
import java.util.List;
public class MediaDescription {
private String type;
private InetAddress ipAddress;
// attributes not codec-related
private Hashtable<String, String> attributes;
private int port;
private List<Codec> codecs;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Hashtable<String, String> getAttributes() {
return attributes;
}
public void setAttributes(Hashtable<String, String> attributes) {
this.attributes = attributes;
}
public InetAddress getIpAddress() {
return ipAddress;
}
public void setIpAddress(InetAddress ipAddress) {
this.ipAddress = ipAddress;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public List<Codec> getCodecs() {
return codecs;
}
public void setCodecs(List<Codec> codecs) {
this.codecs = codecs;
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append(RFC4566.TYPE_MEDIA).append(RFC4566.SEPARATOR);
buf.append(type).append(" ").append(port);
buf.append(" RTP/AVP");
for (Codec codec: codecs) {
buf.append(" ");
buf.append(codec.getPayloadType());
}
buf.append("\r\n");
if (ipAddress != null) {
int ipVersion;
if (ipAddress instanceof Inet4Address) {
ipVersion = 4;
} else if (ipAddress instanceof Inet6Address) {
ipVersion = 6;
} else {
throw new RuntimeException("unknown ip version: " + ipAddress);
}
buf.append(RFC4566.TYPE_CONNECTION).append(RFC4566.SEPARATOR);
buf.append("IN IP").append(ipVersion).append(" ");
buf.append(ipAddress.getHostAddress()).append("\r\n");
}
for (Codec codec: codecs) {
buf.append(codec.toString());
}
if (attributes != null) {
for (String attributeName: attributes.keySet()) {
buf.append(RFC4566.TYPE_ATTRIBUTE).append(RFC4566.SEPARATOR);
buf.append(attributeName);
String attributeValue = attributes.get(attributeName);
if (attributeValue != null && !"".equals(attributeValue.trim())) {
buf.append(":").append(attributeValue);
}
buf.append("\r\n");
}
}
return buf.toString();
}
}

View File

@@ -0,0 +1,52 @@
/*
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 peers.sdp;
public class MediaDestination {
private String destination;
private int port;
private Codec codec;
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public Codec getCodec() {
return codec;
}
public void setCodec(Codec codec) {
this.codec = codec;
}
}

View File

@@ -0,0 +1,26 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package peers.sdp;
public class NoCodecException extends Exception {
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,49 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package peers.sdp;
public class RFC4566 {
public static final char VERSION = '0';
public static final char TYPE_VERSION = 'v';
public static final char TYPE_ORIGIN = 'o';
public static final char TYPE_SUBJECT = 's';
public static final char TYPE_INFO = 'i';
public static final char TYPE_URI = 'u';
public static final char TYPE_EMAIL = 'e';
public static final char TYPE_PHONE = 'p';
public static final char TYPE_CONNECTION = 'c';
public static final char TYPE_BANDWITH = 'b';
public static final char TYPE_TIME = 't';
public static final char TYPE_REPEAT = 'r';
public static final char TYPE_ZONE = 'z';
public static final char TYPE_KEY = 'k';
public static final char TYPE_ATTRIBUTE = 'a';
public static final char TYPE_MEDIA = 'm';
public static final char SEPARATOR = '=';
public static final char ATTR_SEPARATOR = ':';
public static final String MEDIA_AUDIO = "audio";
public static final String ATTR_RTPMAP = "rtpmap";
public static final String ATTR_SENDRECV = "sendrecv";
}

View File

@@ -0,0 +1,150 @@
/*
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, 2012 Yohann Martineau
*/
package peers.sdp;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Random;
import org.pmw.tinylog.Logger;
import peers.Config;
import peers.rtp.RFC3551;
import peers.rtp.RFC4733;
import peers.sip.core.useragent.UserAgent;
public class SDPManager {
private SdpParser sdpParser;
private UserAgent userAgent;
private List<Codec> supportedCodecs;
private Random random;
public SDPManager(UserAgent userAgent) {
this.userAgent = userAgent;
sdpParser = new SdpParser();
supportedCodecs = new ArrayList<Codec>();
random = new Random();
//TODO retrieve codecs from configuration file
Codec codec = new Codec();
codec.setPayloadType(RFC3551.PAYLOAD_TYPE_PCMU);
codec.setName(RFC3551.PCMU);
supportedCodecs.add(codec);
codec = new Codec();
codec.setPayloadType(RFC3551.PAYLOAD_TYPE_PCMA);
codec.setName(RFC3551.PCMA);
supportedCodecs.add(codec);
codec = new Codec();
codec.setPayloadType(RFC4733.PAYLOAD_TYPE_TELEPHONE_EVENT);
codec.setName(RFC4733.TELEPHONE_EVENT);
//TODO add fmtp:101 0-15 attribute
supportedCodecs.add(codec);
}
public SessionDescription parse(byte[] sdp) {
try {
return sdpParser.parse(sdp);
} catch (IOException e) {
Logger.error(e.getMessage(), e);
}
return null;
}
public MediaDestination getMediaDestination(
SessionDescription sessionDescription) throws NoCodecException {
InetAddress destAddress = sessionDescription.getIpAddress();
List<MediaDescription> mediaDescriptions = sessionDescription.getMediaDescriptions();
for (MediaDescription mediaDescription: mediaDescriptions) {
if (RFC4566.MEDIA_AUDIO.equals(mediaDescription.getType())) {
for (Codec offerCodec: mediaDescription.getCodecs()) {
if (supportedCodecs.contains(offerCodec)) {
String offerCodecName = offerCodec.getName();
if (offerCodecName.equalsIgnoreCase(RFC3551.PCMU) ||
offerCodecName.equalsIgnoreCase(RFC3551.PCMA)) {
int destPort = mediaDescription.getPort();
if (mediaDescription.getIpAddress() != null) {
destAddress = mediaDescription.getIpAddress();
}
MediaDestination mediaDestination =
new MediaDestination();
mediaDestination.setDestination(
destAddress.getHostAddress());
mediaDestination.setPort(destPort);
mediaDestination.setCodec(offerCodec);
return mediaDestination;
}
}
}
}
}
throw new NoCodecException();
}
public SessionDescription createSessionDescription(SessionDescription offer,
int localRtpPort)
throws IOException {
SessionDescription sessionDescription = new SessionDescription();
sessionDescription.setUsername("user1");
sessionDescription.setId(random.nextInt(Integer.MAX_VALUE));
sessionDescription.setVersion(random.nextInt(Integer.MAX_VALUE));
Config config = userAgent.getConfig();
InetAddress inetAddress = config.getPublicInetAddress();
if (inetAddress == null) {
inetAddress = config.getLocalInetAddress();
}
sessionDescription.setIpAddress(inetAddress);
sessionDescription.setName("-");
sessionDescription.setAttributes(new Hashtable<String, String>());
List<Codec> codecs;
if (offer == null) {
codecs = supportedCodecs;
} else {
codecs = new ArrayList<Codec>();
for (MediaDescription mediaDescription:
offer.getMediaDescriptions()) {
if (RFC4566.MEDIA_AUDIO.equals(mediaDescription.getType())) {
for (Codec codec: mediaDescription.getCodecs()) {
if (supportedCodecs.contains(codec)) {
codecs.add(codec);
}
}
}
}
}
MediaDescription mediaDescription = new MediaDescription();
Hashtable<String, String> attributes = new Hashtable<String, String>();
attributes.put(RFC4566.ATTR_SENDRECV, "");
mediaDescription.setAttributes(attributes);
mediaDescription.setType(RFC4566.MEDIA_AUDIO);
mediaDescription.setPort(localRtpPort);
mediaDescription.setCodecs(codecs);
List<MediaDescription> mediaDescriptions =
new ArrayList<MediaDescription>();
mediaDescriptions.add(mediaDescription);
sessionDescription.setMediaDescriptions(mediaDescriptions);
return sessionDescription;
}
}

View File

@@ -0,0 +1,25 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package peers.sdp;
public class SDPMessage {
}

View File

@@ -0,0 +1,38 @@
/*
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 peers.sdp;
public class SdpLine {
private char type;
private String value;
public char getType() {
return type;
}
public void setType(char type) {
this.type = type;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,236 @@
/*
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 peers.sdp;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import peers.rtp.RFC3551;
public class SdpParser {
public SessionDescription parse(byte[] body) throws IOException {
if (body == null || body.length == 0) {
return null;
}
ByteArrayInputStream in = new ByteArrayInputStream(body);
InputStreamReader inputStreamReader = new InputStreamReader(in);
BufferedReader reader = new BufferedReader(inputStreamReader);
SessionDescription sessionDescription = new SessionDescription();
//version
String line = reader.readLine();
if (line.length() < 3) {
return null;
}
if (line.charAt(0) != RFC4566.TYPE_VERSION
|| line.charAt(1) != RFC4566.SEPARATOR
|| line.charAt(2) != RFC4566.VERSION) {
return null;
}
//origin
line = reader.readLine();
if (line.length() < 3) {
return null;
}
if (line.charAt(0) != RFC4566.TYPE_ORIGIN
|| line.charAt(1) != RFC4566.SEPARATOR) {
return null;
}
line = line.substring(2);
String[] originArr = line.split(" ");
if (originArr == null || originArr.length != 6) {
return null;
}
sessionDescription.setUsername(originArr[0]);
sessionDescription.setId(Long.parseLong(originArr[1]));
sessionDescription.setVersion(Long.parseLong(originArr[2]));
sessionDescription.setIpAddress(InetAddress.getByName(originArr[5]));
//name
line = reader.readLine();
if (line.length() < 3) {
return null;
}
if (line.charAt(0) != RFC4566.TYPE_SUBJECT
|| line.charAt(1) != RFC4566.SEPARATOR) {
return null;
}
sessionDescription.setName(line.substring(2));
//session connection and attributes
Hashtable<String, String> sessionAttributes = new Hashtable<String, String>();
sessionDescription.setAttributes(sessionAttributes);
while ((line = reader.readLine()) != null
&& line.charAt(0) != RFC4566.TYPE_MEDIA) {
if (line.length() > 3
&& line.charAt(0) == RFC4566.TYPE_CONNECTION
&& line.charAt(1) == RFC4566.SEPARATOR) {
String connection = parseConnection(line.substring(2));
if (connection == null) {
continue;
}
sessionDescription.setIpAddress(InetAddress.getByName(connection));
} else if (line.length() > 3
&& line.charAt(0) == RFC4566.TYPE_ATTRIBUTE
&& line.charAt(1) == RFC4566.SEPARATOR) {
String value = line.substring(2);
int pos = value.indexOf(RFC4566.ATTR_SEPARATOR);
if (pos > -1) {
sessionAttributes.put(value.substring(0, pos),
value.substring(pos + 1));
} else {
sessionAttributes.put(value, "");
}
}
}
if (line == null) {
return null;
}
//we are at the first media line
ArrayList<SdpLine> mediaLines = new ArrayList<SdpLine>();
do {
if (line.length() < 3) {
return null;
}
if (line.charAt(1) != RFC4566.SEPARATOR) {
return null;
}
SdpLine mediaLine = new SdpLine();
mediaLine.setType(line.charAt(0));
mediaLine.setValue(line.substring(2));
mediaLines.add(mediaLine);
}
while ((line = reader.readLine()) != null);
ArrayList<MediaDescription> mediaDescriptions = new ArrayList<MediaDescription>();
sessionDescription.setMediaDescriptions(mediaDescriptions);
for (SdpLine sdpLine : mediaLines) {
MediaDescription mediaDescription;
if (sdpLine.getType() == RFC4566.TYPE_MEDIA) {
String[] mediaArr = sdpLine.getValue().split(" ");
if (mediaArr == null || mediaArr.length < 4) {
return null;
}
mediaDescription = new MediaDescription();
mediaDescription.setType(mediaArr[0]);
//TODO manage port range
mediaDescription.setPort(Integer.parseInt(mediaArr[1]));
mediaDescription.setAttributes(new Hashtable<String, String>());
List<Codec> codecs = new ArrayList<Codec>();
for (int i = 3; i < mediaArr.length; ++i) {
int payloadType = Integer.parseInt(mediaArr[i]);
Codec codec = new Codec();
codec.setPayloadType(payloadType);
String name;
switch (payloadType) {
case RFC3551.PAYLOAD_TYPE_PCMU:
name = RFC3551.PCMU;
break;
case RFC3551.PAYLOAD_TYPE_PCMA:
name = RFC3551.PCMA;
break;
default:
name = "unsupported";
break;
}
codec.setName(name);
codecs.add(codec);
//TODO check that sdp offer without rtpmap works
}
mediaDescription.setCodecs(codecs);
mediaDescriptions.add(mediaDescription);
} else {
mediaDescription = mediaDescriptions.get(mediaDescriptions.size() - 1);
String sdpLineValue = sdpLine.getValue();
if (sdpLine.getType() == RFC4566.TYPE_CONNECTION) {
String ipAddress = parseConnection(sdpLineValue);
mediaDescription.setIpAddress(InetAddress.getByName(ipAddress));
} else if (sdpLine.getType() == RFC4566.TYPE_ATTRIBUTE) {
Hashtable<String, String> attributes = mediaDescription.getAttributes();
int pos = sdpLineValue.indexOf(RFC4566.ATTR_SEPARATOR);
if (pos > -1) {
String name = sdpLineValue.substring(0, pos);
String value = sdpLineValue.substring(pos + 1);
pos = value.indexOf(" ");
if (pos > -1) {
int payloadType;
try {
payloadType = Integer.parseInt(value.substring(0, pos));
List<Codec> codecs = mediaDescription.getCodecs();
for (Codec codec: codecs) {
if (codec.getPayloadType() == payloadType) {
value = value.substring(pos + 1);
pos = value.indexOf("/");
if (pos > -1) {
value = value.substring(0, pos);
codec.setName(value);
}
break;
}
}
} catch (NumberFormatException e) {
attributes.put(name, value);
}
} else {
attributes.put(name, value);
}
} else {
attributes.put(sdpLineValue, "");
}
}
}
}
sessionDescription.setMediaDescriptions(mediaDescriptions);
for (MediaDescription description : mediaDescriptions) {
if (description.getIpAddress() == null) {
InetAddress sessionAddress = sessionDescription.getIpAddress();
if (sessionAddress == null) {
return null;
}
description.setIpAddress(sessionAddress);
}
}
return sessionDescription;
}
private String parseConnection(String line) {
String[] connectionArr = line.split(" ");
if (connectionArr == null || connectionArr.length != 3) {
return null;
}
return connectionArr[2];
}
}

View File

@@ -0,0 +1,129 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
*/
package peers.sdp;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.Hashtable;
import java.util.List;
public class SessionDescription {
private long id;
private long version;
private String name;
private String username;
private InetAddress ipAddress;
private List<MediaDescription> mediaDescriptions;
private Hashtable<String, String> attributes;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public InetAddress getIpAddress() {
return ipAddress;
}
public void setIpAddress(InetAddress ipAddress) {
this.ipAddress = ipAddress;
}
public List<MediaDescription> getMediaDescriptions() {
return mediaDescriptions;
}
public void setMediaDescriptions(List<MediaDescription> mediaDescriptions) {
this.mediaDescriptions = mediaDescriptions;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
public Hashtable<String, String> getAttributes() {
return attributes;
}
public void setAttributes(Hashtable<String, String> attributes) {
this.attributes = attributes;
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("v=0\r\n");
buf.append("o=").append(username).append(" ").append(id);
buf.append(" ").append(version);
int ipVersion;
if (ipAddress instanceof Inet4Address) {
ipVersion = 4;
} else if (ipAddress instanceof Inet6Address) {
ipVersion = 6;
} else {
throw new RuntimeException("unknown ip version: " + ipAddress);
}
buf.append(" IN IP").append(ipVersion).append(" ");
String hostAddress = ipAddress.getHostAddress();
buf.append(hostAddress).append("\r\n");
buf.append("s=").append(name).append("\r\n");
buf.append("c=IN IP").append(ipVersion).append(" ");
buf.append(hostAddress).append("\r\n");
buf.append("t=0 0\r\n");
for (String attributeName: attributes.keySet()) {
String attributeValue = attributes.get(attributeName);
buf.append("a=").append(attributeName);
if (attributeValue != null && !"".equals(attributeValue.trim())) {
buf.append(":");
buf.append(attributeValue);
buf.append("\r\n");
}
}
for (MediaDescription mediaDescription: mediaDescriptions) {
buf.append(mediaDescription.toString());
}
return buf.toString();
}
}

View File

@@ -0,0 +1,43 @@
/*
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 peers.sip;
import org.pmw.tinylog.Logger;
public abstract class AbstractState {
protected String id;
public AbstractState(String id) {
this.id = id;
}
public void log(AbstractState state) {
StringBuffer buf = new StringBuffer();
buf.append("SM ").append(id).append(" [");
buf.append(JavaUtils.getShortClassName(this.getClass())).append(" -> ");
buf.append(JavaUtils.getShortClassName(state.getClass())).append("] ");
buf.append(new Exception().getStackTrace()[1].getMethodName());
Logger.debug(buf.toString());
}
}

View File

@@ -0,0 +1,29 @@
/*
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 peers.sip;
public class JavaUtils {
public static String getShortClassName(Class<?> c) {
String name = c.getName();
return name.substring(name.lastIndexOf('.') + 1);
}
}

View File

@@ -0,0 +1,47 @@
/*
This file is part of Peers, a java SIP softphone.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright 2008, 2009, 2010, 2011 Yohann Martineau
*/
package peers.sip;
public class RFC2617 {
// SCHEMES
public static final String SCHEME_DIGEST = "Digest";
// PARAMETERS
public static final String PARAM_NONCE = "nonce";
public static final String PARAM_OPAQUE = "opaque";
public static final String PARAM_REALM = "realm";
public static final String PARAM_RESPONSE = "response";
public static final String PARAM_URI = "uri";
public static final String PARAM_USERNAME = "username";
public static final String PARAM_QOP = "qop";
public static final String PARAM_CNONCE = "cnonce";
public static final String PARAM_NC = "nc";
public static final String PARAM_ALGORITHM= "algorithm";
// MISCELLANEOUS
public static final char PARAM_SEPARATOR = ',';
public static final char PARAM_VALUE_SEPARATOR = '=';
public static final char PARAM_VALUE_DELIMITER = '"';
public static final char DIGEST_SEPARATOR = ':';
}

169
src/peers/sip/RFC3261.java Normal file
View File

@@ -0,0 +1,169 @@
/*
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 peers.sip;
public final class RFC3261 {
//SYNTAX ENCODING
//HEADERS
//Methods
public static final String METHOD_INVITE = "INVITE";
public static final String METHOD_ACK = "ACK";
public static final String METHOD_REGISTER = "REGISTER";
public static final String METHOD_BYE = "BYE";
public static final String METHOD_OPTIONS = "OPTIONS";
public static final String METHOD_CANCEL = "CANCEL";
//Classical form
public static final String HDR_ALLOW = "Allow";
public static final String HDR_AUTHORIZATION = "Authorization";
public static final String HDR_CALLID = "Call-ID";
public static final String HDR_CONTACT = "Contact";
public static final String HDR_CONTENT_ENCODING = "Content-Encoding";
public static final String HDR_CONTENT_LENGTH = "Content-Length";
public static final String HDR_CONTENT_TYPE = "Content-Type";
public static final String HDR_CSEQ = "CSeq";
public static final String HDR_FROM = "From";
public static final String HDR_MAX_FORWARDS = "Max-Forwards";
public static final String HDR_RECORD_ROUTE = "Record-Route";
public static final String HDR_PROXY_AUTHENTICATE = "Proxy-Authenticate";
public static final String HDR_PROXY_AUTHORIZATION = "Proxy-Authorization";
public static final String HDR_ROUTE = "Route";
public static final String HDR_SUBJECT = "Subject";
public static final String HDR_SUPPORTED = "Supported";
public static final String HDR_TO = "To";
public static final String HDR_VIA = "Via";
public static final String HDR_WWW_AUTHENTICATE = "WWW-Authenticate";
//Compact form
public static final char COMPACT_HDR_CALLID = 'i';
public static final char COMPACT_HDR_CONTACT = 'm';
public static final char COMPACT_HDR_CONTENT_ENCODING = 'e';
public static final char COMPACT_HDR_CONTENT_LENGTH = 'l';
public static final char COMPACT_HDR_CONTENT_TYPE = 'c';
public static final char COMPACT_HDR_FROM = 'f';
public static final char COMPACT_HDR_SUBJECT = 's';
public static final char COMPACT_HDR_SUPPORTED = 'k';
public static final char COMPACT_HDR_TO = 't';
public static final char COMPACT_HDR_VIA = 'v';
//Parameters
public static final String PARAM_BRANCH = "branch";
public static final String PARAM_EXPIRES = "expires";
public static final String PARAM_MADDR = "maddr";
public static final String PARAM_RECEIVED = "received";
public static final String PARAM_RPORT = "rport";
public static final String PARAM_SENTBY = "sent-by";
public static final String PARAM_TAG = "tag";
public static final String PARAM_TRANSPORT = "transport";
public static final String PARAM_TTL = "ttl";
public static final String PARAM_SEPARATOR = ";";
public static final String PARAM_ASSIGNMENT = "=";
//Miscellaneous
public static final char FIELD_NAME_SEPARATOR = ':';
public static final String DEFAULT_SIP_VERSION = "SIP/2.0";
public static final String CRLF = "\r\n";
public static final String IPV4_TTL = "1";
public static final char AT = '@';
public static final String LOOSE_ROUTING = "lr";
public static final char LEFT_ANGLE_BRACKET = '<';
public static final char RIGHT_ANGLE_BRACKET = '>';
public static final String HEADER_SEPARATOR = ",";
//STATUS CODES
public static final int CODE_MIN_PROV = 100;
public static final int CODE_MIN_SUCCESS = 200;
public static final int CODE_MIN_REDIR = 300;
public static final int CODE_MAX = 699;
public static final int CODE_100_TRYING = 100;
public static final int CODE_180_RINGING = 180;
public static final int CODE_200_OK = 200;
public static final int CODE_401_UNAUTHORIZED = 401;
public static final int CODE_405_METHOD_NOT_ALLOWED = 405;
public static final int CODE_407_PROXY_AUTHENTICATION_REQUIRED = 407;
public static final int CODE_481_CALL_TRANSACTION_DOES_NOT_EXIST = 481;
public static final int CODE_486_BUSYHERE = 486;
public static final int CODE_487_REQUEST_TERMINATED = 487;
public static final int CODE_500_SERVER_INTERNAL_ERROR = 500;
//REASON PHRASES
public static final String REASON_180_RINGING = "Ringing";
public static final String REASON_200_OK = "OK";
public static final String REASON_405_METHOD_NOT_ALLOWED =
"Method Not Allowed";
public static final String REASON_481_CALL_TRANSACTION_DOES_NOT_EXIST =
"Call/Transaction Does Not Exist";
public static final String REASON_486_BUSYHERE = "Busy Here";
public static final String REASON_487_REQUEST_TERMINATED =
"Request Terminated";
public static final String REASON_500_SERVER_INTERNAL_ERROR =
"Server Internal Error";
//TRANSPORT
public static final String TRANSPORT_UDP = "UDP";
public static final String TRANSPORT_TCP = "TCP";
public static final String TRANSPORT_SCTP = "SCTP";
public static final String TRANSPORT_TLS = "TLS";
public static final int TRANSPORT_UDP_USUAL_MAX_SIZE = 1300;
public static final int TRANSPORT_UDP_MAX_SIZE = 65535;
public static final char TRANSPORT_VIA_SEP = '/';
public static final char TRANSPORT_VIA_SEP2 = ' ';
public static final int TRANSPORT_DEFAULT_PORT = 5060;
public static final int TRANSPORT_TLS_PORT = 5061;
public static final char TRANSPORT_PORT_SEP = ':';
//TRANSACTION
//TRANSACTION USER
public static final int DEFAULT_MAXFORWARDS = 70;
public static final String BRANCHID_MAGIC_COOKIE = "z9hG4bK";
public static final String SIP_SCHEME = "sip";
public static final char SCHEME_SEPARATOR = ':';
//TIMERS (in milliseconds)
public static final int TIMER_T1 = 500;
public static final int TIMER_T2 = 4000;
public static final int TIMER_T4 = 5000;
public static final int TIMER_INVITE_CLIENT_TRANSACTION = 32000;
//TRANSACTION USER
//CORE
public static final String CONTENT_TYPE_SDP = "application/sdp";
}

128
src/peers/sip/Utils.java Normal file
View File

@@ -0,0 +1,128 @@
/*
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 peers.sip;
import java.net.InetAddress;
import peers.sip.core.useragent.UAS;
import peers.sip.syntaxencoding.SipHeaderFieldMultiValue;
import peers.sip.syntaxencoding.SipHeaderFieldName;
import peers.sip.syntaxencoding.SipHeaderFieldValue;
import peers.sip.syntaxencoding.SipHeaders;
import peers.sip.transport.SipMessage;
public class Utils {
public static final String PEERSHOME_SYSTEM_PROPERTY = "peers.home";
public static final String DEFAULT_PEERS_HOME = ".";
public final static SipHeaderFieldValue getTopVia(SipMessage sipMessage) {
SipHeaders sipHeaders = sipMessage.getSipHeaders();
SipHeaderFieldName viaName = new SipHeaderFieldName(RFC3261.HDR_VIA);
SipHeaderFieldValue via = sipHeaders.get(viaName);
if (via instanceof SipHeaderFieldMultiValue) {
via = ((SipHeaderFieldMultiValue)via).getValues().get(0);
}
return via;
}
public final static String generateTag() {
return randomString(8);
}
public final static String generateCallID(InetAddress inetAddress) {
//TODO make a hash using current time millis, public ip @, private @, and a random string
StringBuffer buf = new StringBuffer();
buf.append(randomString(8));
buf.append('-');
buf.append(String.valueOf(System.currentTimeMillis()));
buf.append('@');
buf.append(inetAddress.getHostName());
return buf.toString();
}
public final static String generateBranchId() {
StringBuffer buf = new StringBuffer();
buf.append(RFC3261.BRANCHID_MAGIC_COOKIE);
//TODO must be unique across space and time...
buf.append(randomString(9));
return buf.toString();
}
public final static String getMessageCallId(SipMessage sipMessage) {
SipHeaderFieldValue callId = sipMessage.getSipHeaders().get(
new SipHeaderFieldName(RFC3261.HDR_CALLID));
return callId.getValue();
}
public final static String randomString(int length) {
String chars = "abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIFKLMNOPRSTUVWXYZ" +
"0123456789";
StringBuffer buf = new StringBuffer(length);
for (int i = 0; i < length; ++i) {
int pos = (int)Math.round(Math.random() * (chars.length() - 1));
buf.append(chars.charAt(pos));
}
return buf.toString();
}
public final static void copyHeader(SipMessage src, SipMessage dst, String name) {
SipHeaderFieldName sipHeaderFieldName = new SipHeaderFieldName(name);
SipHeaderFieldValue sipHeaderFieldValue = src.getSipHeaders().get(sipHeaderFieldName);
if (sipHeaderFieldValue != null) {
dst.getSipHeaders().add(sipHeaderFieldName, sipHeaderFieldValue);
}
}
public final static String getUserPart(String sipUri) {
int start = sipUri.indexOf(RFC3261.SCHEME_SEPARATOR);
int end = sipUri.indexOf(RFC3261.AT);
return sipUri.substring(start + 1, end);
}
/**
* adds Max-Forwards Supported and Require headers
* @param headers
*/
public final static void addCommonHeaders(SipHeaders headers) {
//Max-Forwards
headers.add(new SipHeaderFieldName(RFC3261.HDR_MAX_FORWARDS),
new SipHeaderFieldValue(
String.valueOf(RFC3261.DEFAULT_MAXFORWARDS)));
//TODO Supported and Require
}
public final static String generateAllowHeader() {
StringBuffer buf = new StringBuffer();
for (String supportedMethod: UAS.SUPPORTED_METHODS) {
buf.append(supportedMethod);
buf.append(", ");
}
int bufLength = buf.length();
buf.delete(bufLength - 2, bufLength);
return buf.toString();
}
}

View File

@@ -0,0 +1,300 @@
/*
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-2013 Yohann Martineau
*/
package peers.sip.core.useragent;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
import org.pmw.tinylog.Logger;
import peers.Config;
import peers.sip.RFC2617;
import peers.sip.RFC3261;
import peers.sip.syntaxencoding.SipHeaderFieldName;
import peers.sip.syntaxencoding.SipHeaderFieldValue;
import peers.sip.syntaxencoding.SipHeaderParamName;
import peers.sip.syntaxencoding.SipHeaders;
import peers.sip.syntaxencoding.SipUriSyntaxException;
import peers.sip.transactionuser.Dialog;
import peers.sip.transactionuser.DialogManager;
import peers.sip.transport.SipMessage;
import peers.sip.transport.SipRequest;
import peers.sip.transport.SipResponse;
public class ChallengeManager implements MessageInterceptor {
public static final String ALGORITHM_MD5 = "MD5";
private String username;
private String password;
private String realm;
private String nonce;
private String opaque;
private String requestUri;
private String digest;
private String profileUri;
private String qop;
private String cnonce;
private static volatile int nonceCount = 1;
private String nonceCountHex;
private Config config;
// FIXME what happens if a challenge is received for a register-refresh
// and another challenge is received in the mean time for an invite?
private int statusCode;
private SipHeaderFieldValue contact;
private InitialRequestManager initialRequestManager;
private MidDialogRequestManager midDialogRequestManager;
private DialogManager dialogManager;
public ChallengeManager(Config config,
InitialRequestManager initialRequestManager,
MidDialogRequestManager midDialogRequestManager,
DialogManager dialogManager) {
this.config = config;
this.initialRequestManager = initialRequestManager;
this.midDialogRequestManager = midDialogRequestManager;
this.dialogManager = dialogManager;
init();
}
private void init() {
username = config.getUserPart();
password = config.getPassword();
profileUri = RFC3261.SIP_SCHEME + RFC3261.SCHEME_SEPARATOR
+ username + RFC3261.AT + config.getDomain();
}
private String md5hash(String message) {
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance(ALGORITHM_MD5);
} catch (NoSuchAlgorithmException e) {
Logger.error("no such algorithm " + ALGORITHM_MD5, e);
return null;
}
byte[] messageBytes = message.getBytes();
byte[] messageMd5 = messageDigest.digest(messageBytes);
ByteArrayOutputStream out = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(out);
for (byte b : messageMd5) {
int u_b = (b < 0) ? 256 + b : b;
printStream.printf("%02x", u_b);
}
return out.toString();
}
public void handleChallenge(SipRequest sipRequest,
SipResponse sipResponse) {
init();
statusCode = sipResponse.getStatusCode();
SipHeaders responseHeaders = sipResponse.getSipHeaders();
SipHeaders requestHeaders = sipRequest.getSipHeaders();
contact = requestHeaders.get(
new SipHeaderFieldName(RFC3261.HDR_CONTACT));
SipHeaderFieldValue authenticate;
SipHeaderFieldName authenticateHeaderName;
if (statusCode == RFC3261.CODE_401_UNAUTHORIZED) {
authenticateHeaderName = new SipHeaderFieldName(
RFC3261.HDR_WWW_AUTHENTICATE);
} else if (statusCode == RFC3261.CODE_407_PROXY_AUTHENTICATION_REQUIRED) {
authenticateHeaderName = new SipHeaderFieldName(
RFC3261.HDR_PROXY_AUTHENTICATE);
} else {
return;
}
authenticate = responseHeaders.get(authenticateHeaderName);
if (authenticate == null) {
return;
}
if (!authenticate.getValue().startsWith(RFC2617.SCHEME_DIGEST)) {
Logger.info("unsupported challenge scheme in header: "
+ authenticate);
return;
}
String headerValue = authenticate.getValue();
realm = getParameter(RFC2617.PARAM_REALM, headerValue);
nonce = getParameter(RFC2617.PARAM_NONCE, headerValue);
opaque = getParameter(RFC2617.PARAM_OPAQUE, headerValue);
qop = getParameter(RFC2617.PARAM_QOP, headerValue);
if( "auth".equals(qop)) {
nonceCountHex = String.format("%08X", nonceCount++);
}
String method = sipRequest.getMethod();
requestUri = sipRequest.getRequestUri().toString();
cnonce = UUID.randomUUID().toString();
digest = getRequestDigest(method);
// FIXME message should be copied "as is" not created anew from scratch
// and this technique is not clean
String callId = responseHeaders.get(
new SipHeaderFieldName(RFC3261.HDR_CALLID)).getValue();
Dialog dialog = dialogManager.getDialog(callId);
if (dialog != null) {
midDialogRequestManager.generateMidDialogRequest(
dialog, RFC3261.METHOD_BYE, this);
} else {
SipHeaderFieldValue from = requestHeaders.get(
new SipHeaderFieldName(RFC3261.HDR_FROM));
String fromTag = from.getParam(new SipHeaderParamName(
RFC3261.PARAM_TAG));
try {
initialRequestManager.createInitialRequest(
requestUri, method, profileUri, callId, fromTag, this);
} catch (SipUriSyntaxException e) {
Logger.error("syntax error", e);
}
}
}
private String getRequestDigest(String method) {
StringBuffer buf = new StringBuffer();
buf.append(username);
buf.append(RFC2617.DIGEST_SEPARATOR);
buf.append(realm);
buf.append(RFC2617.DIGEST_SEPARATOR);
buf.append(password);
String ha1 = md5hash(buf.toString());
buf = new StringBuffer();
buf.append(method);
buf.append(RFC2617.DIGEST_SEPARATOR);
buf.append(requestUri);
String ha2 = md5hash(buf.toString());
buf = new StringBuffer();
buf.append(ha1);
buf.append(RFC2617.DIGEST_SEPARATOR);
buf.append(nonce);
buf.append(RFC2617.DIGEST_SEPARATOR);
if("auth".equals(qop)) {
buf.append(nonceCountHex);
buf.append(RFC2617.DIGEST_SEPARATOR);
buf.append(cnonce);
buf.append(RFC2617.DIGEST_SEPARATOR);
buf.append(qop);
buf.append(RFC2617.DIGEST_SEPARATOR);
}
buf.append(ha2);
return md5hash(buf.toString());
}
private String getParameter(String paramName, String header) {
int paramPos = header.indexOf(paramName);
if (paramPos < 0) {
return null;
}
int paramNameLength = paramName.length();
if (paramPos + paramNameLength + 3 > header.length()) {
Logger.info("Malformed " + RFC3261.HDR_WWW_AUTHENTICATE + " header");
return null;
}
if (header.charAt(paramPos + paramNameLength) !=
RFC2617.PARAM_VALUE_SEPARATOR) {
Logger.info("Malformed " + RFC3261.HDR_WWW_AUTHENTICATE + " header");
return null;
}
if (header.charAt(paramPos + paramNameLength + 1) !=
RFC2617.PARAM_VALUE_DELIMITER) {
Logger.info("Malformed " + RFC3261.HDR_WWW_AUTHENTICATE + " header");
return null;
}
header = header.substring(paramPos + paramNameLength + 2);
int endDelimiter = header.indexOf(RFC2617.PARAM_VALUE_DELIMITER);
if (endDelimiter < 0) {
Logger.info("Malformed " + RFC3261.HDR_WWW_AUTHENTICATE + " header");
return null;
}
return header.substring(0, endDelimiter);
}
/** add xxxAuthorization header */
public void postProcess(SipMessage sipMessage) {
if (realm == null || nonce == null || digest == null) {
return;
}
SipHeaders sipHeaders = sipMessage.getSipHeaders();
String cseq = sipHeaders.get(
new SipHeaderFieldName(RFC3261.HDR_CSEQ)).getValue();
String method = cseq.substring(cseq.trim().lastIndexOf(' ') + 1);
digest = getRequestDigest(method);
StringBuffer buf = new StringBuffer();
buf.append(RFC2617.SCHEME_DIGEST).append(" ");
appendParameter(buf, RFC2617.PARAM_USERNAME, username);
buf.append(RFC2617.PARAM_SEPARATOR).append(" ");
appendParameter(buf, RFC2617.PARAM_REALM, realm);
buf.append(RFC2617.PARAM_SEPARATOR).append(" ");
appendParameter(buf, RFC2617.PARAM_NONCE, nonce);
buf.append(RFC2617.PARAM_SEPARATOR).append(" ");
appendParameter(buf, RFC2617.PARAM_URI, requestUri);
buf.append(RFC2617.PARAM_SEPARATOR).append(" ");
appendParameter(buf, RFC2617.PARAM_RESPONSE, digest);
if("auth".equals(qop)) {
buf.append(RFC2617.PARAM_SEPARATOR).append(" ");
appendParameter(buf, RFC2617.PARAM_NC, nonceCountHex);
buf.append(RFC2617.PARAM_SEPARATOR).append(" ");
appendParameter(buf, RFC2617.PARAM_CNONCE, cnonce);
buf.append(RFC2617.PARAM_SEPARATOR).append(" ");
appendParameter(buf, RFC2617.PARAM_QOP, qop);
}
if (opaque != null) {
buf.append(RFC2617.PARAM_SEPARATOR).append(" ");
appendParameter(buf, RFC2617.PARAM_OPAQUE, opaque);
}
SipHeaderFieldName authorizationName;
if (statusCode == RFC3261.CODE_401_UNAUTHORIZED) {
authorizationName = new SipHeaderFieldName(
RFC3261.HDR_AUTHORIZATION);
} else if (statusCode == RFC3261.CODE_407_PROXY_AUTHENTICATION_REQUIRED) {
authorizationName = new SipHeaderFieldName(
RFC3261.HDR_PROXY_AUTHORIZATION);
} else {
return;
}
sipHeaders.add(authorizationName,
new SipHeaderFieldValue(buf.toString()));
// manage authentication on unregister challenge...
if (contact != null) {
SipHeaderParamName expiresName =
new SipHeaderParamName(RFC3261.PARAM_EXPIRES);
String expiresString = contact.getParam(expiresName);
if (expiresString != null && Integer.parseInt(expiresString) == 0) {
SipHeaderFieldValue requestContact =
sipHeaders.get(new SipHeaderFieldName(RFC3261.HDR_CONTACT));
requestContact.addParam(expiresName, expiresString);
}
}
}
private void appendParameter(StringBuffer buf, String name, String value) {
buf.append(name);
buf.append(RFC2617.PARAM_VALUE_SEPARATOR);
buf.append(RFC2617.PARAM_VALUE_DELIMITER);
buf.append(value);
buf.append(RFC2617.PARAM_VALUE_DELIMITER);
}
}

View File

@@ -0,0 +1,312 @@
/*
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, 2011 Yohann Martineau
*/
package peers.sip.core.useragent;
import org.pmw.tinylog.Logger;
import peers.sip.RFC3261;
import peers.sip.Utils;
import peers.sip.core.useragent.handlers.ByeHandler;
import peers.sip.core.useragent.handlers.CancelHandler;
import peers.sip.core.useragent.handlers.InviteHandler;
import peers.sip.core.useragent.handlers.OptionsHandler;
import peers.sip.core.useragent.handlers.RegisterHandler;
import peers.sip.syntaxencoding.NameAddress;
import peers.sip.syntaxencoding.SipHeaderFieldName;
import peers.sip.syntaxencoding.SipHeaderFieldValue;
import peers.sip.syntaxencoding.SipHeaderParamName;
import peers.sip.syntaxencoding.SipHeaders;
import peers.sip.syntaxencoding.SipURI;
import peers.sip.syntaxencoding.SipUriSyntaxException;
import peers.sip.transaction.ClientTransaction;
import peers.sip.transaction.ServerTransaction;
import peers.sip.transaction.ServerTransactionUser;
import peers.sip.transaction.TransactionManager;
import peers.sip.transactionuser.DialogManager;
import peers.sip.transport.SipRequest;
import peers.sip.transport.SipResponse;
import peers.sip.transport.TransportManager;
public class InitialRequestManager extends RequestManager
implements ServerTransactionUser {
public InitialRequestManager(UserAgent userAgent,
InviteHandler inviteHandler,
CancelHandler cancelHandler,
ByeHandler byeHandler,
OptionsHandler optionsHandler,
RegisterHandler registerHandler,
DialogManager dialogManager,
TransactionManager transactionManager,
TransportManager transportManager
) {
super(userAgent,
inviteHandler,
cancelHandler,
byeHandler,
optionsHandler,
registerHandler,
dialogManager,
transactionManager,
transportManager);
registerHandler.setInitialRequestManager(this);
}
/**
* gives a new request outside of a dialog
*
* @param requestUri
* @param method
* @return
* @throws SipUriSyntaxException
*/
public SipRequest getGenericRequest(String requestUri, String method,
String profileUri, String callId, String fromTag)
throws SipUriSyntaxException {
//8.1.1
SipRequest request = new SipRequest(method, new SipURI(requestUri));
SipHeaders headers = request.getSipHeaders();
//String hostAddress = utils.getMyAddress().getHostAddress();
//Via
//TODO no Via should be added directly by UAC, Via is normally added by Transaction layer
// StringBuffer viaBuf = new StringBuffer();
// viaBuf.append(RFC3261.DEFAULT_SIP_VERSION);
// // TODO choose real transport
// viaBuf.append("/UDP ");
// viaBuf.append(hostAddress);
// SipHeaderFieldValue via = new SipHeaderFieldValue(viaBuf.toString());
// via.addParam(new SipHeaderParamName(RFC3261.PARAM_BRANCHID),
// utils.generateBranchId());
// headers.add(new SipHeaderFieldName(RFC3261.HDR_VIA), via);
Utils.addCommonHeaders(headers);
//To
NameAddress to = new NameAddress(requestUri);
headers.add(new SipHeaderFieldName(RFC3261.HDR_TO),
new SipHeaderFieldValue(to.toString()));
//From
NameAddress fromNA = new NameAddress(profileUri);
SipHeaderFieldValue from = new SipHeaderFieldValue(fromNA.toString());
String localFromTag;
if (fromTag != null) {
localFromTag = fromTag;
} else {
localFromTag = Utils.generateTag();
}
from.addParam(new SipHeaderParamName(RFC3261.PARAM_TAG), localFromTag);
headers.add(new SipHeaderFieldName(RFC3261.HDR_FROM), from);
//Call-ID
SipHeaderFieldName callIdName =
new SipHeaderFieldName(RFC3261.HDR_CALLID);
String localCallId;
if (callId != null) {
localCallId = callId;
} else {
localCallId = Utils.generateCallID(
userAgent.getConfig().getLocalInetAddress());
}
headers.add(callIdName, new SipHeaderFieldValue(localCallId));
//CSeq
headers.add(new SipHeaderFieldName(RFC3261.HDR_CSEQ),
new SipHeaderFieldValue(userAgent.generateCSeq(method)));
return request;
}
public SipRequest createInitialRequest(String requestUri, String method,
String profileUri) throws SipUriSyntaxException {
return createInitialRequest(requestUri, method, profileUri, null);
}
public SipRequest createInitialRequest(String requestUri, String method,
String profileUri, String callId) throws SipUriSyntaxException {
return createInitialRequest(requestUri, method, profileUri, callId,
null, null);
}
public SipRequest createInitialRequest(String requestUri, String method,
String profileUri, String callId, String fromTag,
MessageInterceptor messageInterceptor)
throws SipUriSyntaxException {
SipRequest sipRequest = getGenericRequest(requestUri, method,
profileUri, callId, fromTag);
// TODO add route header for outbound proxy give it to xxxHandler to create
// clientTransaction
SipURI outboundProxy = userAgent.getOutboundProxy();
if (outboundProxy != null) {
NameAddress outboundProxyNameAddress =
new NameAddress(outboundProxy.toString());
sipRequest.getSipHeaders().add(new SipHeaderFieldName(RFC3261.HDR_ROUTE),
new SipHeaderFieldValue(outboundProxyNameAddress.toString()), 0);
}
ClientTransaction clientTransaction = null;
if (RFC3261.METHOD_INVITE.equals(method)) {
clientTransaction = inviteHandler.preProcessInvite(sipRequest);
} else if (RFC3261.METHOD_REGISTER.equals(method)) {
clientTransaction = registerHandler.preProcessRegister(sipRequest);
}
createInitialRequestEnd(sipRequest, clientTransaction, profileUri,
messageInterceptor, true);
return sipRequest;
}
private void createInitialRequestEnd(SipRequest sipRequest,
ClientTransaction clientTransaction, String profileUri,
MessageInterceptor messageInterceptor, boolean addContact) {
if (clientTransaction == null) {
Logger.error("method not supported");
return;
}
if (addContact) {
addContact(sipRequest, clientTransaction.getContact(), profileUri);
}
if (messageInterceptor != null) {
messageInterceptor.postProcess(sipRequest);
}
// TODO create message receiver on client transport port
clientTransaction.start();
}
public void createCancel(SipRequest inviteRequest,
MidDialogRequestManager midDialogRequestManager, String profileUri) {
SipHeaders inviteHeaders = inviteRequest.getSipHeaders();
SipHeaderFieldValue callId = inviteHeaders.get(
new SipHeaderFieldName(RFC3261.HDR_CALLID));
SipRequest sipRequest;
try {
sipRequest = getGenericRequest(
inviteRequest.getRequestUri().toString(), RFC3261.METHOD_CANCEL,
profileUri, callId.getValue(), null);
} catch (SipUriSyntaxException e) {
Logger.error("syntax error", e);
return;
}
ClientTransaction clientTransaction = null;
clientTransaction = cancelHandler.preProcessCancel(sipRequest,
inviteRequest, midDialogRequestManager);
if (clientTransaction != null) {
createInitialRequestEnd(sipRequest, clientTransaction, profileUri,
null, false);
}
}
public void manageInitialRequest(SipRequest sipRequest) {
SipHeaders headers = sipRequest.getSipHeaders();
// TODO authentication
//method inspection
SipResponse sipResponse = null;
if (!UAS.SUPPORTED_METHODS.contains(sipRequest.getMethod())) {
//TODO generate 405 (using 8.2.6 &) with Allow header
//(20.5) and send it
sipResponse = generateResponse(sipRequest, null,
RFC3261.CODE_405_METHOD_NOT_ALLOWED,
RFC3261.REASON_405_METHOD_NOT_ALLOWED);
SipHeaders sipHeaders = sipResponse.getSipHeaders();
sipHeaders.add(new SipHeaderFieldName(RFC3261.HDR_ALLOW),
new SipHeaderFieldValue(Utils.generateAllowHeader()));
}
SipHeaderFieldValue contentType =
headers.get(new SipHeaderFieldName(RFC3261.HDR_CONTENT_TYPE));
if (contentType != null) {
if (!RFC3261.CONTENT_TYPE_SDP.equals(contentType.getValue())) {
//TODO generate 415 with a Accept header listing supported content types
//8.2.3
}
}
//etc.
if (sipResponse != null) {
ServerTransaction serverTransaction =
transactionManager.createServerTransaction(
sipResponse, userAgent.getSipPort(), RFC3261.TRANSPORT_UDP,
this, sipRequest);
serverTransaction.start();
serverTransaction.receivedRequest(sipRequest);
serverTransaction.sendReponse(sipResponse);
}
//TODO create server transaction
String method = sipRequest.getMethod();
if (RFC3261.METHOD_INVITE.equals(method)) {
inviteHandler.handleInitialInvite(sipRequest);
} else if (RFC3261.METHOD_CANCEL.equals(method)) {
cancelHandler.handleCancel(sipRequest);
} else if (RFC3261.METHOD_OPTIONS.equals(method)) {
optionsHandler.handleOptions(sipRequest);
}
}
public void addContact(SipRequest sipRequest, String contactEnd,
String profileUri) {
SipHeaders sipHeaders = sipRequest.getSipHeaders();
//Contact
StringBuffer contactBuf = new StringBuffer();
contactBuf.append(RFC3261.SIP_SCHEME);
contactBuf.append(RFC3261.SCHEME_SEPARATOR);
String userPart = Utils.getUserPart(profileUri);
contactBuf.append(userPart);
contactBuf.append(RFC3261.AT);
contactBuf.append(contactEnd);
NameAddress contactNA = new NameAddress(contactBuf.toString());
SipHeaderFieldValue contact =
new SipHeaderFieldValue(contactNA.toString());
sipHeaders.add(new SipHeaderFieldName(RFC3261.HDR_CONTACT),
new SipHeaderFieldValue(contact.toString()));
}
///////////////////////////////////////////////////////////
// ServerTransactionUser methods
///////////////////////////////////////////////////////////
@Override
public void transactionFailure() {
// TODO Auto-generated method stub
}
}

View File

@@ -0,0 +1,27 @@
/*
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 Yohann Martineau
*/
package peers.sip.core.useragent;
import peers.sip.transport.SipMessage;
public interface MessageInterceptor {
public void postProcess(SipMessage sipMessage);
}

View File

@@ -0,0 +1,251 @@
/*
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 peers.sip.core.useragent;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Hashtable;
import org.pmw.tinylog.Logger;
import peers.sip.RFC3261;
import peers.sip.core.useragent.handlers.ByeHandler;
import peers.sip.core.useragent.handlers.CancelHandler;
import peers.sip.core.useragent.handlers.InviteHandler;
import peers.sip.core.useragent.handlers.OptionsHandler;
import peers.sip.core.useragent.handlers.RegisterHandler;
import peers.sip.syntaxencoding.SipHeaderFieldName;
import peers.sip.syntaxencoding.SipHeaderFieldValue;
import peers.sip.syntaxencoding.SipHeaders;
import peers.sip.syntaxencoding.SipURI;
import peers.sip.transaction.ClientTransaction;
import peers.sip.transaction.ClientTransactionUser;
import peers.sip.transaction.ServerTransaction;
import peers.sip.transaction.ServerTransactionUser;
import peers.sip.transaction.Transaction;
import peers.sip.transaction.TransactionManager;
import peers.sip.transactionuser.Dialog;
import peers.sip.transactionuser.DialogManager;
import peers.sip.transport.SipRequest;
import peers.sip.transport.SipResponse;
import peers.sip.transport.TransportManager;
public class MidDialogRequestManager extends RequestManager
implements ClientTransactionUser, ServerTransactionUser {
public MidDialogRequestManager(UserAgent userAgent,
InviteHandler inviteHandler,
CancelHandler cancelHandler,
ByeHandler byeHandler,
OptionsHandler optionsHandler,
RegisterHandler registerHandler,
DialogManager dialogManager,
TransactionManager transactionManager,
TransportManager transportManager) {
super(userAgent,
inviteHandler,
cancelHandler,
byeHandler,
optionsHandler,
registerHandler,
dialogManager,
transactionManager,
transportManager);
}
////////////////////////////////////////////////
// methods for UAC
////////////////////////////////////////////////
public void generateMidDialogRequest(Dialog dialog,
String method, MessageInterceptor messageInterceptor) {
SipRequest sipRequest = dialog.buildSubsequentRequest(RFC3261.METHOD_BYE);
if (RFC3261.METHOD_BYE.equals(method)) {
byeHandler.preprocessBye(sipRequest, dialog);
}
//TODO check that subsequent request is supported before client
//transaction creation
if (!RFC3261.METHOD_INVITE.equals(method)) {
ClientTransaction clientTransaction = createNonInviteClientTransaction(
sipRequest, null, byeHandler);
if (messageInterceptor != null) {
messageInterceptor.postProcess(sipRequest);
}
if (clientTransaction != null) {
clientTransaction.start();
}
} else {
//TODO client transaction user is managed by invite handler directly
}
}
public ClientTransaction createNonInviteClientTransaction(
SipRequest sipRequest, String branchId,
ClientTransactionUser clientTransactionUser) {
//8.1.2
SipURI destinationUri = RequestManager.getDestinationUri(sipRequest);
//TODO if header route is present, addrspec = toproute.nameaddress.addrspec
String transport = RFC3261.TRANSPORT_UDP;
Hashtable<String, String> params = destinationUri.getUriParameters();
if (params != null) {
String reqUriTransport = params.get(RFC3261.PARAM_TRANSPORT);
if (reqUriTransport != null) {
transport = reqUriTransport;
}
}
int port = destinationUri.getPort();
if (port == SipURI.DEFAULT_PORT) {
port = RFC3261.TRANSPORT_DEFAULT_PORT;
}
SipURI sipUri = userAgent.getConfig().getOutboundProxy();
if (sipUri == null) {
sipUri = destinationUri;
}
InetAddress inetAddress;
try {
inetAddress = InetAddress.getByName(sipUri.getHost());
} catch (UnknownHostException e) {
Logger.error("unknown host: " + sipUri.getHost(), e);
return null;
}
ClientTransaction clientTransaction = transactionManager
.createClientTransaction(sipRequest, inetAddress, port, transport,
branchId, clientTransactionUser);
return clientTransaction;
}
////////////////////////////////////////////////
// methods for UAS
////////////////////////////////////////////////
public void manageMidDialogRequest(SipRequest sipRequest, Dialog dialog) {
SipHeaders sipHeaders = sipRequest.getSipHeaders();
SipHeaderFieldValue cseq =
sipHeaders.get(new SipHeaderFieldName(RFC3261.HDR_CSEQ));
String cseqStr = cseq.getValue();
int pos = cseqStr.indexOf(' ');
if (pos < 0) {
pos = cseqStr.indexOf('\t');
}
int newCseq = Integer.parseInt(cseqStr.substring(0, pos));
int oldCseq = dialog.getRemoteCSeq();
if (oldCseq == Dialog.EMPTY_CSEQ) {
dialog.setRemoteCSeq(newCseq);
} else if (newCseq < oldCseq) {
// out of order
// RFC3261 12.2.2 p77
// TODO test out of order in-dialog-requests
SipResponse sipResponse = generateResponse(sipRequest, dialog,
RFC3261.CODE_500_SERVER_INTERNAL_ERROR,
RFC3261.REASON_500_SERVER_INTERNAL_ERROR);
ServerTransaction serverTransaction =
transactionManager.createServerTransaction(
sipResponse,
userAgent.getSipPort(),
RFC3261.TRANSPORT_UDP,
this, sipRequest);
serverTransaction.start();
serverTransaction.receivedRequest(sipRequest);
serverTransaction.sendReponse(sipResponse);
} else {
dialog.setRemoteCSeq(newCseq);
}
String method = sipRequest.getMethod();
if (RFC3261.METHOD_BYE.equals(method)) {
byeHandler.handleBye(sipRequest, dialog);
} else if (RFC3261.METHOD_INVITE.equals(method)) {
inviteHandler.handleReInvite(sipRequest, dialog);
} else if (RFC3261.METHOD_ACK.equals(method)) {
inviteHandler.handleAck(sipRequest, dialog);
} else if (RFC3261.METHOD_OPTIONS.equals(method)) {
optionsHandler.handleOptions(sipRequest);
}
}
///////////////////////////////////////
// ServerTransactionUser methods
///////////////////////////////////////
@Override
public void transactionFailure() {
// TODO Auto-generated method stub
}
///////////////////////////////////////
// ClientTransactionUser methods
///////////////////////////////////////
// callbacks employed for cancel responses (ignored)
@Override
public void transactionTimeout(ClientTransaction clientTransaction) {
// TODO Auto-generated method stub
}
@Override
public void provResponseReceived(SipResponse sipResponse,
Transaction transaction) {
// TODO Auto-generated method stub
}
@Override
public void errResponseReceived(SipResponse sipResponse) {
// TODO Auto-generated method stub
}
@Override
public void successResponseReceived(SipResponse sipResponse,
Transaction transaction) {
// TODO Auto-generated method stub
}
@Override
public void transactionTransportError() {
// TODO Auto-generated method stub
}
}

View File

@@ -0,0 +1,136 @@
/*
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 peers.sip.core.useragent;
import org.pmw.tinylog.Logger;
import peers.sip.RFC3261;
import peers.sip.core.useragent.handlers.ByeHandler;
import peers.sip.core.useragent.handlers.CancelHandler;
import peers.sip.core.useragent.handlers.InviteHandler;
import peers.sip.core.useragent.handlers.OptionsHandler;
import peers.sip.core.useragent.handlers.RegisterHandler;
import peers.sip.syntaxencoding.NameAddress;
import peers.sip.syntaxencoding.SipHeaderFieldName;
import peers.sip.syntaxencoding.SipHeaderFieldValue;
import peers.sip.syntaxencoding.SipHeaderParamName;
import peers.sip.syntaxencoding.SipHeaders;
import peers.sip.syntaxencoding.SipURI;
import peers.sip.syntaxencoding.SipUriSyntaxException;
import peers.sip.transaction.TransactionManager;
import peers.sip.transactionuser.Dialog;
import peers.sip.transactionuser.DialogManager;
import peers.sip.transport.SipRequest;
import peers.sip.transport.SipResponse;
import peers.sip.transport.TransportManager;
public abstract class RequestManager {
public static SipURI getDestinationUri(SipRequest sipRequest
) {
SipHeaders requestHeaders = sipRequest.getSipHeaders();
SipURI destinationUri = null;
SipHeaderFieldValue route = requestHeaders.get(
new SipHeaderFieldName(RFC3261.HDR_ROUTE));
if (route != null) {
try {
destinationUri = new SipURI(
NameAddress.nameAddressToUri(route.toString()));
} catch (SipUriSyntaxException e) {
Logger.error("syntax error", e);
}
}
if (destinationUri == null) {
destinationUri = sipRequest.getRequestUri();
}
return destinationUri;
}
public static SipResponse generateResponse(SipRequest sipRequest,
Dialog dialog, int statusCode, String reasonPhrase) {
//8.2.6.2
SipResponse sipResponse = new SipResponse(statusCode, reasonPhrase);
SipHeaders requestHeaders = sipRequest.getSipHeaders();
SipHeaders responseHeaders = sipResponse.getSipHeaders();
SipHeaderFieldName fromName = new SipHeaderFieldName(RFC3261.HDR_FROM);
responseHeaders.add(fromName, requestHeaders.get(fromName));
SipHeaderFieldName callIdName = new SipHeaderFieldName(RFC3261.HDR_CALLID);
responseHeaders.add(callIdName, requestHeaders.get(callIdName));
SipHeaderFieldName cseqName = new SipHeaderFieldName(RFC3261.HDR_CSEQ);
responseHeaders.add(cseqName, requestHeaders.get(cseqName));
SipHeaderFieldName viaName = new SipHeaderFieldName(RFC3261.HDR_VIA);
responseHeaders.add(viaName, requestHeaders.get(viaName));//TODO check ordering
SipHeaderFieldName toName = new SipHeaderFieldName(RFC3261.HDR_TO);
SipHeaderFieldValue toValue = requestHeaders.get(toName);
SipHeaderParamName toTagParamName = new SipHeaderParamName(RFC3261.PARAM_TAG);
String toTag = toValue.getParam(toTagParamName);
if (toTag == null) {
if (dialog != null) {
toTag = dialog.getLocalTag();
toValue.addParam(toTagParamName, toTag);
}
}
responseHeaders.add(toName, toValue);
return sipResponse;
}
protected InviteHandler inviteHandler;
protected CancelHandler cancelHandler;
protected ByeHandler byeHandler;
protected OptionsHandler optionsHandler;
protected RegisterHandler registerHandler;
protected UserAgent userAgent;
protected TransactionManager transactionManager;
protected TransportManager transportManager;
public RequestManager(UserAgent userAgent,
InviteHandler inviteHandler,
CancelHandler cancelHandler,
ByeHandler byeHandler,
OptionsHandler optionsHandler,
RegisterHandler registerHandler,
DialogManager dialogManager,
TransactionManager transactionManager,
TransportManager transportManager) {
this.userAgent = userAgent;
this.inviteHandler = inviteHandler;
this.cancelHandler = cancelHandler;
this.byeHandler = byeHandler;
this.optionsHandler = optionsHandler;
this.registerHandler = registerHandler;
this.transactionManager = transactionManager;
this.transportManager = transportManager;
}
public InviteHandler getInviteHandler() {
return inviteHandler;
}
public ByeHandler getByeHandler() {
return byeHandler;
}
public RegisterHandler getRegisterHandler() {
return registerHandler;
}
}

View File

@@ -0,0 +1,46 @@
/*
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, 2011 Yohann Martineau
*/
package peers.sip.core.useragent;
import peers.sip.transport.SipMessage;
public class SipEvent {
public enum EventType {
ERROR, RINGING, INCOMING_CALL, CALLEE_PICKUP;
}
private EventType eventType;
private SipMessage sipMessage;
public SipEvent(EventType type, SipMessage sipMessage) {
this.eventType = type;
this.sipMessage = sipMessage;
}
public SipMessage getSipMessage() {
return sipMessage;
}
public EventType getEventType() {
return eventType;
}
}

View File

@@ -0,0 +1,44 @@
/*
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 peers.sip.core.useragent;
import peers.sip.transport.SipRequest;
import peers.sip.transport.SipResponse;
public interface SipListener {
public void registering(SipRequest sipRequest);
public void registerSuccessful(SipResponse sipResponse);
public void registerFailed(SipResponse sipResponse);
public void incomingCall(SipRequest sipRequest, SipResponse provResponse);
public void remoteHangup(SipRequest sipRequest);
public void ringing(SipResponse sipResponse);
public void calleePickup(SipResponse sipResponse);
public void error(SipResponse sipResponse);
}

View File

@@ -0,0 +1,227 @@
/*
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 peers.sip.core.useragent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.pmw.tinylog.Logger;
import peers.sip.RFC3261;
import peers.sip.Utils;
import peers.sip.syntaxencoding.SipHeaderFieldName;
import peers.sip.syntaxencoding.SipHeaderFieldValue;
import peers.sip.syntaxencoding.SipHeaderParamName;
import peers.sip.syntaxencoding.SipHeaders;
import peers.sip.syntaxencoding.SipUriSyntaxException;
import peers.sip.transaction.ClientTransaction;
import peers.sip.transaction.InviteClientTransaction;
import peers.sip.transaction.TransactionManager;
import peers.sip.transactionuser.Dialog;
import peers.sip.transactionuser.DialogManager;
import peers.sip.transactionuser.DialogState;
import peers.sip.transport.SipMessage;
import peers.sip.transport.SipRequest;
import peers.sip.transport.SipResponse;
import peers.sip.transport.TransportManager;
public class UAC {
private InitialRequestManager initialRequestManager;
private MidDialogRequestManager midDialogRequestManager;
private String registerCallID;
private String profileUri;
//FIXME
private UserAgent userAgent;
private TransactionManager transactionManager;
private DialogManager dialogManager;
private List<String> guiClosedCallIds;
/**
* should be instanciated only once, it was a singleton.
*/
public UAC(UserAgent userAgent,
InitialRequestManager initialRequestManager,
MidDialogRequestManager midDialogRequestManager,
DialogManager dialogManager,
TransactionManager transactionManager,
TransportManager transportManager) {
this.userAgent = userAgent;
this.initialRequestManager = initialRequestManager;
this.midDialogRequestManager = midDialogRequestManager;
this.dialogManager = dialogManager;
this.transactionManager = transactionManager;
guiClosedCallIds = Collections.synchronizedList(new ArrayList<String>());
profileUri = RFC3261.SIP_SCHEME + RFC3261.SCHEME_SEPARATOR
+ userAgent.getUserpart() + RFC3261.AT + userAgent.getDomain();
}
/**
* For the moment we consider that only one profile uri is used at a time.
* @throws SipUriSyntaxException
*/
SipRequest register() throws SipUriSyntaxException {
String domain = userAgent.getDomain();
String requestUri = RFC3261.SIP_SCHEME + RFC3261.SCHEME_SEPARATOR
+ domain;
SipListener sipListener = userAgent.getSipListener();
profileUri = RFC3261.SIP_SCHEME + RFC3261.SCHEME_SEPARATOR
+ userAgent.getUserpart() + RFC3261.AT + domain;
registerCallID = Utils.generateCallID(
userAgent.getConfig().getLocalInetAddress());
SipRequest sipRequest = initialRequestManager.createInitialRequest(
requestUri, RFC3261.METHOD_REGISTER, profileUri,
registerCallID);
if (sipListener != null) {
sipListener.registering(sipRequest);
}
return sipRequest;
}
void unregister() throws SipUriSyntaxException {
if (getInitialRequestManager().getRegisterHandler().isRegistered()) {
String requestUri = RFC3261.SIP_SCHEME + RFC3261.SCHEME_SEPARATOR
+ userAgent.getDomain();
MessageInterceptor messageInterceptor = new MessageInterceptor() {
@Override
public void postProcess(SipMessage sipMessage) {
initialRequestManager.registerHandler.unregister();
SipHeaders sipHeaders = sipMessage.getSipHeaders();
SipHeaderFieldValue contact = sipHeaders.get(
new SipHeaderFieldName(RFC3261.HDR_CONTACT));
contact.addParam(new SipHeaderParamName(RFC3261.PARAM_EXPIRES),
"0");
}
};
// for any reason, asterisk requires a new Call-ID to unregister
registerCallID = Utils.generateCallID(
userAgent.getConfig().getLocalInetAddress());
initialRequestManager.createInitialRequest(requestUri,
RFC3261.METHOD_REGISTER, profileUri, registerCallID, null,
messageInterceptor);
}
}
SipRequest invite(String requestUri, String callId)
throws SipUriSyntaxException {
return initialRequestManager.createInitialRequest(requestUri,
RFC3261.METHOD_INVITE, profileUri, callId);
}
private SipRequest getInviteWithAuth(String callId) {
List<ClientTransaction> clientTransactions =
transactionManager.getClientTransactionsFromCallId(callId,
RFC3261.METHOD_INVITE);
SipRequest sipRequestNoAuth = null;
for (ClientTransaction clientTransaction: clientTransactions) {
InviteClientTransaction inviteClientTransaction =
(InviteClientTransaction)clientTransaction;
SipRequest sipRequest = inviteClientTransaction.getRequest();
SipHeaders sipHeaders = sipRequest.getSipHeaders();
SipHeaderFieldName authorization = new SipHeaderFieldName(
RFC3261.HDR_AUTHORIZATION);
SipHeaderFieldValue value = sipHeaders.get(authorization);
if (value == null) {
SipHeaderFieldName proxyAuthorization = new SipHeaderFieldName(
RFC3261.HDR_PROXY_AUTHORIZATION);
value = sipHeaders.get(proxyAuthorization);
}
if (value != null) {
return sipRequest;
}
sipRequestNoAuth = sipRequest;
}
return sipRequestNoAuth;
}
void terminate(SipRequest sipRequest) {
String callId = Utils.getMessageCallId(sipRequest);
if (!guiClosedCallIds.contains(callId)) {
guiClosedCallIds.add(callId);
}
Dialog dialog = dialogManager.getDialog(callId);
SipRequest inviteWithAuth = getInviteWithAuth(callId);
if (dialog != null) {
SipRequest originatingRequest;
if (inviteWithAuth != null) {
originatingRequest = inviteWithAuth;
} else {
originatingRequest = sipRequest;
}
ClientTransaction clientTransaction =
transactionManager.getClientTransaction(originatingRequest);
if (clientTransaction != null) {
synchronized (clientTransaction) {
DialogState dialogState = dialog.getState();
if (dialog.EARLY.equals(dialogState)) {
initialRequestManager.createCancel(inviteWithAuth,
midDialogRequestManager, profileUri);
} else if (dialog.CONFIRMED.equals(dialogState)) {
// clientTransaction not yet removed
midDialogRequestManager.generateMidDialogRequest(
dialog, RFC3261.METHOD_BYE, null);
guiClosedCallIds.remove(callId);
}
}
} else {
// clientTransaction Terminated and removed
Logger.debug("clientTransaction null");
midDialogRequestManager.generateMidDialogRequest(
dialog, RFC3261.METHOD_BYE, null);
guiClosedCallIds.remove(callId);
}
} else {
InviteClientTransaction inviteClientTransaction =
(InviteClientTransaction)transactionManager
.getClientTransaction(inviteWithAuth);
if (inviteClientTransaction == null) {
Logger.error("cannot find invite client transaction" +
" for call " + callId);
} else {
SipResponse sipResponse =
inviteClientTransaction.getLastResponse();
if (sipResponse != null) {
int statusCode = sipResponse.getStatusCode();
if (statusCode < RFC3261.CODE_200_OK) {
initialRequestManager.createCancel(inviteWithAuth,
midDialogRequestManager, profileUri);
}
}
}
}
userAgent.getMediaManager().stopSession();
}
public InitialRequestManager getInitialRequestManager() {
return initialRequestManager;
}
public List<String> getGuiClosedCallIds() {
return guiClosedCallIds;
}
}

View File

@@ -0,0 +1,133 @@
/*
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 peers.sip.core.useragent;
import java.net.SocketException;
import java.util.ArrayList;
import peers.sip.RFC3261;
import peers.sip.syntaxencoding.SipHeaderFieldName;
import peers.sip.syntaxencoding.SipHeaderFieldValue;
import peers.sip.syntaxencoding.SipHeaderParamName;
import peers.sip.syntaxencoding.SipHeaders;
import peers.sip.transaction.TransactionManager;
import peers.sip.transactionuser.Dialog;
import peers.sip.transactionuser.DialogManager;
import peers.sip.transport.SipMessage;
import peers.sip.transport.SipRequest;
import peers.sip.transport.SipResponse;
import peers.sip.transport.SipServerTransportUser;
import peers.sip.transport.TransportManager;
public class UAS implements SipServerTransportUser {
public final static ArrayList<String> SUPPORTED_METHODS;
static {
SUPPORTED_METHODS = new ArrayList<String>();
SUPPORTED_METHODS.add(RFC3261.METHOD_INVITE);
SUPPORTED_METHODS.add(RFC3261.METHOD_ACK);
SUPPORTED_METHODS.add(RFC3261.METHOD_CANCEL);
SUPPORTED_METHODS.add(RFC3261.METHOD_OPTIONS);
SUPPORTED_METHODS.add(RFC3261.METHOD_BYE);
};
private InitialRequestManager initialRequestManager;
private MidDialogRequestManager midDialogRequestManager;
private DialogManager dialogManager;
/**
* should be instanciated only once, it was a singleton.
*/
public UAS(UserAgent userAgent,
InitialRequestManager initialRequestManager,
MidDialogRequestManager midDialogRequestManager,
DialogManager dialogManager,
TransactionManager transactionManager,
TransportManager transportManager) throws SocketException {
this.initialRequestManager = initialRequestManager;
this.midDialogRequestManager = midDialogRequestManager;
this.dialogManager = dialogManager;
transportManager.setSipServerTransportUser(this);
transportManager.createServerTransport(
RFC3261.TRANSPORT_UDP, userAgent.getConfig().getSipPort());
}
public void messageReceived(SipMessage sipMessage) {
if (sipMessage instanceof SipRequest) {
requestReceived((SipRequest) sipMessage);
} else if (sipMessage instanceof SipResponse) {
responseReceived((SipResponse) sipMessage);
} else {
throw new RuntimeException("unknown message type");
}
}
private void responseReceived(SipResponse sipResponse) {
}
private void requestReceived(SipRequest sipRequest) {
//TODO 8.2
//TODO JTA to make request processing atomic
SipHeaders headers = sipRequest.getSipHeaders();
//TODO find whether the request is within an existing dialog or not
SipHeaderFieldValue to =
headers.get(new SipHeaderFieldName(RFC3261.HDR_TO));
String toTag = to.getParam(new SipHeaderParamName(RFC3261.PARAM_TAG));
if (toTag != null) {
Dialog dialog = dialogManager.getDialog(sipRequest);
if (dialog != null) {
//this is a mid-dialog request
midDialogRequestManager.manageMidDialogRequest(sipRequest, dialog);
//TODO continue processing
} else {
//TODO reject the request with a 481 Call/Transaction Does Not Exist
}
} else {
initialRequestManager.manageInitialRequest(sipRequest);
}
}
void acceptCall(SipRequest sipRequest, Dialog dialog) {
initialRequestManager.getInviteHandler().acceptCall(sipRequest,
dialog);
}
void rejectCall(SipRequest sipRequest) {
initialRequestManager.getInviteHandler().rejectCall(sipRequest);
}
public InitialRequestManager getInitialRequestManager() {
return initialRequestManager;
}
public MidDialogRequestManager getMidDialogRequestManager() {
return midDialogRequestManager;
}
}

View File

@@ -0,0 +1,382 @@
/*
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 peers.sip.core.useragent;
import java.io.File;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import org.pmw.tinylog.Logger;
import peers.Config;
import peers.XmlConfig;
import peers.media.AbstractSoundManager;
import peers.media.Echo;
import peers.media.MediaManager;
import peers.media.MediaMode;
import peers.sdp.SDPManager;
import peers.sip.Utils;
import peers.sip.core.useragent.handlers.ByeHandler;
import peers.sip.core.useragent.handlers.CancelHandler;
import peers.sip.core.useragent.handlers.InviteHandler;
import peers.sip.core.useragent.handlers.OptionsHandler;
import peers.sip.core.useragent.handlers.RegisterHandler;
import peers.sip.syntaxencoding.SipURI;
import peers.sip.syntaxencoding.SipUriSyntaxException;
import peers.sip.transaction.Transaction;
import peers.sip.transaction.TransactionManager;
import peers.sip.transactionuser.Dialog;
import peers.sip.transactionuser.DialogManager;
import peers.sip.transport.SipMessage;
import peers.sip.transport.SipRequest;
import peers.sip.transport.SipResponse;
import peers.sip.transport.TransportManager;
@SuppressWarnings("unused")
public class UserAgent {
public final static String CONFIG_FILE = "conf" + File.separator + "peers.xml";
public final static int RTP_DEFAULT_PORT = 8000;
private String peersHome;
private Config config;
private List<String> peers;
//private List<Dialog> dialogs;
//TODO factorize echo and captureRtpSender
private Echo echo;
private UAC uac;
private UAS uas;
private ChallengeManager challengeManager;
private DialogManager dialogManager;
private TransactionManager transactionManager;
private TransportManager transportManager;
private int cseqCounter;
private SipListener sipListener;
private SDPManager sdpManager;
private AbstractSoundManager soundManager;
private MediaManager mediaManager;
public UserAgent(SipListener sipListener, String peersHome,
AbstractSoundManager soundManager)
throws SocketException {
this(sipListener, null, peersHome, soundManager);
}
public UserAgent(SipListener sipListener, Config config,
AbstractSoundManager soundManager)
throws SocketException {
this(sipListener, config, null, soundManager);
}
private UserAgent(SipListener sipListener, Config config, String peersHome,
AbstractSoundManager soundManager)
throws SocketException {
this.sipListener = sipListener;
if (peersHome == null) {
peersHome = Utils.DEFAULT_PEERS_HOME;
}
this.peersHome = peersHome;
if (config == null) {
config = new XmlConfig(this.peersHome + File.separator
+ CONFIG_FILE);
}
this.config = config;
cseqCounter = 1;
StringBuffer buf = new StringBuffer();
buf.append("starting user agent [");
buf.append("myAddress: ");
buf.append(config.getLocalInetAddress().getHostAddress()).append(", ");
buf.append("sipPort: ");
buf.append(config.getSipPort()).append(", ");
buf.append("userpart: ");
buf.append(config.getUserPart()).append(", ");
buf.append("domain: ");
buf.append(config.getDomain()).append("]");
Logger.info(buf.toString());
//transaction user
dialogManager = new DialogManager();
//transaction
transactionManager = new TransactionManager();
//transport
transportManager = new TransportManager(transactionManager,
config);
transactionManager.setTransportManager(transportManager);
//core
InviteHandler inviteHandler = new InviteHandler(this,
dialogManager,
transactionManager,
transportManager);
CancelHandler cancelHandler = new CancelHandler(this,
dialogManager,
transactionManager,
transportManager);
ByeHandler byeHandler = new ByeHandler(this,
dialogManager,
transactionManager,
transportManager);
OptionsHandler optionsHandler = new OptionsHandler(this,
transactionManager,
transportManager);
RegisterHandler registerHandler = new RegisterHandler(this,
transactionManager,
transportManager);
InitialRequestManager initialRequestManager =
new InitialRequestManager(
this,
inviteHandler,
cancelHandler,
byeHandler,
optionsHandler,
registerHandler,
dialogManager,
transactionManager,
transportManager);
MidDialogRequestManager midDialogRequestManager =
new MidDialogRequestManager(
this,
inviteHandler,
cancelHandler,
byeHandler,
optionsHandler,
registerHandler,
dialogManager,
transactionManager,
transportManager);
uas = new UAS(this,
initialRequestManager,
midDialogRequestManager,
dialogManager,
transactionManager,
transportManager);
uac = new UAC(this,
initialRequestManager,
midDialogRequestManager,
dialogManager,
transactionManager,
transportManager);
challengeManager = new ChallengeManager(config,
initialRequestManager,
midDialogRequestManager,
dialogManager);
registerHandler.setChallengeManager(challengeManager);
inviteHandler.setChallengeManager(challengeManager);
byeHandler.setChallengeManager(challengeManager);
peers = new ArrayList<String>();
//dialogs = new ArrayList<Dialog>();
sdpManager = new SDPManager(this);
inviteHandler.setSdpManager(sdpManager);
optionsHandler.setSdpManager(sdpManager);
this.soundManager = soundManager;
mediaManager = new MediaManager(this);
}
// client methods
public void close() {
transportManager.closeTransports();
config.setPublicInetAddress(null);
}
public SipRequest register() throws SipUriSyntaxException {
return uac.register();
}
public void unregister() throws SipUriSyntaxException {
uac.unregister();
}
public SipRequest invite(String requestUri, String callId)
throws SipUriSyntaxException {
return uac.invite(requestUri, callId);
}
public void terminate(SipRequest sipRequest) {
uac.terminate(sipRequest);
}
public void acceptCall(SipRequest sipRequest, Dialog dialog) {
uas.acceptCall(sipRequest, dialog);
}
public void rejectCall(SipRequest sipRequest) {
uas.rejectCall(sipRequest);
}
/**
* Gives the sipMessage if sipMessage is a SipRequest or
* the SipRequest corresponding to the SipResponse
* if sipMessage is a SipResponse
* @param sipMessage
* @return null if sipMessage is neither a SipRequest neither a SipResponse
*/
public SipRequest getSipRequest(SipMessage sipMessage) {
if (sipMessage instanceof SipRequest) {
return (SipRequest) sipMessage;
} else if (sipMessage instanceof SipResponse) {
SipResponse sipResponse = (SipResponse) sipMessage;
Transaction transaction = (Transaction)transactionManager
.getClientTransaction(sipResponse);
if (transaction == null) {
transaction = (Transaction)transactionManager
.getServerTransaction(sipResponse);
}
if (transaction == null) {
return null;
}
return transaction.getRequest();
} else {
return null;
}
}
// public List<Dialog> getDialogs() {
// return dialogs;
// }
public List<String> getPeers() {
return peers;
}
// public Dialog getDialog(String peer) {
// for (Dialog dialog : dialogs) {
// String remoteUri = dialog.getRemoteUri();
// if (remoteUri != null) {
// if (remoteUri.contains(peer)) {
// return dialog;
// }
// }
// }
// return null;
// }
public String generateCSeq(String method) {
StringBuffer buf = new StringBuffer();
buf.append(cseqCounter++);
buf.append(' ');
buf.append(method);
return buf.toString();
}
public boolean isRegistered() {
return uac.getInitialRequestManager().getRegisterHandler()
.isRegistered();
}
public UAS getUas() {
return uas;
}
public UAC getUac() {
return uac;
}
public DialogManager getDialogManager() {
return dialogManager;
}
public int getSipPort() {
return transportManager.getSipPort();
}
public int getRtpPort() {
return config.getRtpPort();
}
public String getDomain() {
return config.getDomain();
}
public String getUserpart() {
return config.getUserPart();
}
public MediaMode getMediaMode() {
return config.getMediaMode();
}
public boolean isMediaDebug() {
return config.isMediaDebug();
}
public SipURI getOutboundProxy() {
return config.getOutboundProxy();
}
public Echo getEcho() {
return echo;
}
public void setEcho(Echo echo) {
this.echo = echo;
}
public SipListener getSipListener() {
return sipListener;
}
public AbstractSoundManager getSoundManager() {
return soundManager;
}
public MediaManager getMediaManager() {
return mediaManager;
}
public Config getConfig() {
return config;
}
public String getPeersHome() {
return peersHome;
}
public TransportManager getTransportManager() {
return transportManager;
}
}

View File

@@ -0,0 +1,183 @@
/*
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 peers.sip.core.useragent.handlers;
import org.pmw.tinylog.Logger;
import peers.sip.RFC3261;
import peers.sip.core.useragent.RequestManager;
import peers.sip.core.useragent.SipListener;
import peers.sip.core.useragent.UserAgent;
import peers.sip.transaction.ClientTransaction;
import peers.sip.transaction.ClientTransactionUser;
import peers.sip.transaction.NonInviteClientTransaction;
import peers.sip.transaction.ServerTransaction;
import peers.sip.transaction.ServerTransactionUser;
import peers.sip.transaction.Transaction;
import peers.sip.transaction.TransactionManager;
import peers.sip.transactionuser.Dialog;
import peers.sip.transactionuser.DialogManager;
import peers.sip.transport.SipRequest;
import peers.sip.transport.SipResponse;
import peers.sip.transport.TransportManager;
public class ByeHandler extends DialogMethodHandler
implements ServerTransactionUser, ClientTransactionUser {
public ByeHandler(UserAgent userAgent, DialogManager dialogManager,
TransactionManager transactionManager,
TransportManager transportManager) {
super(userAgent, dialogManager, transactionManager, transportManager);
}
////////////////////////////////////////////////
// methods for UAC
////////////////////////////////////////////////
public void preprocessBye(SipRequest sipRequest, Dialog dialog) {
// 15.1.1
String addrSpec = sipRequest.getRequestUri().toString();
userAgent.getPeers().remove(addrSpec);
challengeManager.postProcess(sipRequest);
}
////////////////////////////////////////////////
// methods for UAS
////////////////////////////////////////////////
public void handleBye(SipRequest sipRequest, Dialog dialog) {
dialog.receivedOrSentBye();
//String remoteUri = dialog.getRemoteUri();
String addrSpec = sipRequest.getRequestUri().toString();
userAgent.getPeers().remove(addrSpec);
dialogManager.removeDialog(dialog.getId());
Logger.debug("removed dialog " + dialog.getId());
userAgent.getMediaManager().stopSession();
SipResponse sipResponse =
RequestManager.generateResponse(
sipRequest,
dialog,
RFC3261.CODE_200_OK,
RFC3261.REASON_200_OK);
// TODO determine port and transport for server transaction>transport
// from initial invite
// FIXME determine port and transport for server transaction>transport
ServerTransaction serverTransaction = transactionManager
.createServerTransaction(
sipResponse,
userAgent.getSipPort(),
RFC3261.TRANSPORT_UDP,
this,
sipRequest);
serverTransaction.start();
serverTransaction.receivedRequest(sipRequest);
serverTransaction.sendReponse(sipResponse);
dialogManager.removeDialog(dialog.getId());
SipListener sipListener = userAgent.getSipListener();
if (sipListener != null) {
sipListener.remoteHangup(sipRequest);
}
// setChanged();
// notifyObservers(sipRequest);
}
///////////////////////////////////////
//ServerTransactionUser methods
///////////////////////////////////////
public void transactionFailure() {
// TODO Auto-generated method stub
}
///////////////////////////////////////
//ClientTransactionUser methods
///////////////////////////////////////
@Override
public void transactionTimeout(ClientTransaction clientTransaction) {
// TODO Auto-generated method stub
}
@Override
public void provResponseReceived(SipResponse sipResponse,
Transaction transaction) {
// TODO Auto-generated method stub
}
@Override
public void errResponseReceived(SipResponse sipResponse) {
int statusCode = sipResponse.getStatusCode();
if (statusCode == RFC3261.CODE_401_UNAUTHORIZED
|| statusCode == RFC3261.CODE_407_PROXY_AUTHENTICATION_REQUIRED
&& !challenged) {
NonInviteClientTransaction nonInviteClientTransaction =
(NonInviteClientTransaction)
transactionManager.getClientTransaction(sipResponse);
SipRequest sipRequest = nonInviteClientTransaction.getRequest();
String password = userAgent.getConfig().getPassword();
if (password != null && !"".equals(password.trim())) {
challengeManager.handleChallenge(sipRequest,
sipResponse);
}
challenged = true;
} else {
challenged = false;
}
}
@Override
public void successResponseReceived(SipResponse sipResponse,
Transaction transaction) {
Dialog dialog = dialogManager.getDialog(sipResponse);
if (dialog == null) {
return;
}
dialog.receivedOrSentBye();
dialogManager.removeDialog(dialog.getId());
Logger.debug("removed dialog " + dialog.getId());
}
@Override
public void transactionTransportError() {
// TODO Auto-generated method stub
}
}

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