adaptation of old eclipse code to Intellij
This commit is contained in:
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal 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
8
.idea/compiler.xml
generated
Normal 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>
|
||||||
16
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
16
.idea/inspectionProfiles/Project_Default.xml
generated
Normal 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
11
.idea/libraries/google_code_gson.xml
generated
Normal 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
32
.idea/libraries/io_javalin.xml
generated
Normal 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
10
.idea/libraries/net_java_dev_jna.xml
generated
Normal 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
10
.idea/libraries/projectlombok_lombok.xml
generated
Normal 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
10
.idea/libraries/tinylog.xml
generated
Normal 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
50
.idea/misc.xml
generated
Normal 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
8
.idea/modules.xml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal 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
17
SIPIntercom.iml
Normal 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>
|
||||||
BIN
libs/linux-aarch64/libbass.so
Normal file
BIN
libs/linux-aarch64/libbass.so
Normal file
Binary file not shown.
BIN
libs/linux-armhf/libbass.so
Normal file
BIN
libs/linux-armhf/libbass.so
Normal file
Binary file not shown.
BIN
libs/linux-x86-64/libbass.so
Normal file
BIN
libs/linux-x86-64/libbass.so
Normal file
Binary file not shown.
BIN
libs/linux-x86/libbass.so
Normal file
BIN
libs/linux-x86/libbass.so
Normal file
Binary file not shown.
BIN
libs/win32-arm64/bass.dll
Normal file
BIN
libs/win32-arm64/bass.dll
Normal file
Binary file not shown.
BIN
libs/win32-x86-64/bass.dll
Normal file
BIN
libs/win32-x86-64/bass.dll
Normal file
Binary file not shown.
BIN
libs/win32-x86/bass.dll
Normal file
BIN
libs/win32-x86/bass.dll
Normal file
Binary file not shown.
7
src/Audio/AudioEvent1.java
Normal file
7
src/Audio/AudioEvent1.java
Normal 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
1092
src/Audio/Bass.java
Normal file
File diff suppressed because it is too large
Load Diff
45
src/Audio/BassAAC.java
Normal file
45
src/Audio/BassAAC.java
Normal 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
173
src/Audio/BassEnc.java
Normal 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
22
src/Audio/BassEncAAC.java
Normal 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
23
src/Audio/BassEncMP3.java
Normal 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
28
src/Audio/BassEncOGG.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/Audio/BassEncOPUS.java
Normal file
28
src/Audio/BassEncOPUS.java
Normal 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);
|
||||||
|
}
|
||||||
140
src/Audio/BassFileReader.java
Normal file
140
src/Audio/BassFileReader.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
14
src/Audio/BassFileReaderListener.java
Normal file
14
src/Audio/BassFileReaderListener.java
Normal 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
133
src/Audio/BassMix.java
Normal 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
24
src/Audio/BassOPUS.java
Normal 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);
|
||||||
|
}
|
||||||
162
src/Audio/CompressInputStream.java
Normal file
162
src/Audio/CompressInputStream.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
118
src/Audio/ConvertInputStream.java
Normal file
118
src/Audio/ConvertInputStream.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
138
src/Audio/DecompressInputStream.java
Normal file
138
src/Audio/DecompressInputStream.java
Normal 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
222
src/Audio/G711.java
Normal 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
873
src/Audio/JSIPAudio.java
Normal 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;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
311
src/Audio/basicfunctions.java
Normal file
311
src/Audio/basicfunctions.java
Normal 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
6
src/Main.java
Normal 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
53
src/peers/Config.java
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package 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
156
src/peers/JavaConfig.java
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2012 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package 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
355
src/peers/XmlConfig.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
214
src/peers/javaxsound/JavaxSoundManager.java
Normal file
214
src/peers/javaxsound/JavaxSoundManager.java
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010, 2011, 2012 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
28
src/peers/media/AbstractSoundManager.java
Normal file
28
src/peers/media/AbstractSoundManager.java
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2013 Yohann Martineau
|
||||||
|
*/
|
||||||
|
package 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);
|
||||||
|
}
|
||||||
80
src/peers/media/Capture.java
Normal file
80
src/peers/media/Capture.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
130
src/peers/media/CaptureRtpSender.java
Normal file
130
src/peers/media/CaptureRtpSender.java
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
26
src/peers/media/Decoder.java
Normal file
26
src/peers/media/Decoder.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.media;
|
||||||
|
|
||||||
|
public abstract class Decoder {
|
||||||
|
|
||||||
|
public abstract byte[] process(byte[] media);
|
||||||
|
|
||||||
|
}
|
||||||
95
src/peers/media/DtmfFactory.java
Normal file
95
src/peers/media/DtmfFactory.java
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package 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
80
src/peers/media/Echo.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
149
src/peers/media/Encoder.java
Normal file
149
src/peers/media/Encoder.java
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
96
src/peers/media/FileReader.java
Normal file
96
src/peers/media/FileReader.java
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2012 Yohann Martineau
|
||||||
|
*/
|
||||||
|
package 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
69
src/peers/media/IncomingRtpReader.java
Normal file
69
src/peers/media/IncomingRtpReader.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
335
src/peers/media/MediaManager.java
Normal file
335
src/peers/media/MediaManager.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/peers/media/MediaMode.java
Normal file
24
src/peers/media/MediaMode.java
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.media;
|
||||||
|
|
||||||
|
public enum MediaMode {
|
||||||
|
none, captureAndPlayback, echo, file
|
||||||
|
}
|
||||||
71
src/peers/media/PcmaDecoder.java
Normal file
71
src/peers/media/PcmaDecoder.java
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Oleg Kulikov, Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.media;
|
||||||
|
|
||||||
|
public class PcmaDecoder extends Decoder {
|
||||||
|
|
||||||
|
private static short aLawDecompressTable[] = new short[]{
|
||||||
|
-5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
|
||||||
|
-7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
|
||||||
|
-2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
|
||||||
|
-3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
|
||||||
|
-22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944,
|
||||||
|
-30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136,
|
||||||
|
-11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472,
|
||||||
|
-15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568,
|
||||||
|
-344, -328, -376, -360, -280, -264, -312, -296,
|
||||||
|
-472, -456, -504, -488, -408, -392, -440, -424,
|
||||||
|
-88, -72, -120, -104, -24, -8, -56, -40,
|
||||||
|
-216, -200, -248, -232, -152, -136, -184, -168,
|
||||||
|
-1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
|
||||||
|
-1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
|
||||||
|
-688, -656, -752, -720, -560, -528, -624, -592,
|
||||||
|
-944, -912, -1008, -976, -816, -784, -880, -848,
|
||||||
|
5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736,
|
||||||
|
7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784,
|
||||||
|
2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368,
|
||||||
|
3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392,
|
||||||
|
22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
|
||||||
|
30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
|
||||||
|
11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472,
|
||||||
|
15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
|
||||||
|
344, 328, 376, 360, 280, 264, 312, 296,
|
||||||
|
472, 456, 504, 488, 408, 392, 440, 424,
|
||||||
|
88, 72, 120, 104, 24, 8, 56, 40,
|
||||||
|
216, 200, 248, 232, 152, 136, 184, 168,
|
||||||
|
1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184,
|
||||||
|
1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696,
|
||||||
|
688, 656, 752, 720, 560, 528, 624, 592,
|
||||||
|
944, 912, 1008, 976, 816, 784, 880, 848
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] process(byte[] media) {
|
||||||
|
byte[] res = new byte[media.length * 2];
|
||||||
|
int j = 0;
|
||||||
|
for (int i = 0; i < media.length; i++) {
|
||||||
|
short s = aLawDecompressTable[media[i] & 0xff];
|
||||||
|
res[j++] = (byte) s;
|
||||||
|
res[j++] = (byte) (s >> 8);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
98
src/peers/media/PcmaEncoder.java
Normal file
98
src/peers/media/PcmaEncoder.java
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010, 2011 Oleg Kulikov, Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
103
src/peers/media/PcmuDecoder.java
Normal file
103
src/peers/media/PcmuDecoder.java
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Oleg Kulikov, Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.media;
|
||||||
|
|
||||||
|
public class PcmuDecoder extends Decoder {
|
||||||
|
|
||||||
|
// private final static int cBias = 0x84;
|
||||||
|
// private int QUANT_MASK = 0xf;
|
||||||
|
// private final static int SEG_SHIFT = 4;
|
||||||
|
// private final static int SEG_MASK = 0x70;
|
||||||
|
// private final static int SIGN_BIT = 0x80;
|
||||||
|
private static short muLawDecompressTable[] = new short[]{
|
||||||
|
-32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956,
|
||||||
|
-23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764,
|
||||||
|
-15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412,
|
||||||
|
-11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316,
|
||||||
|
-7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
|
||||||
|
-5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
|
||||||
|
-3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
|
||||||
|
-2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
|
||||||
|
-1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
|
||||||
|
-1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
|
||||||
|
-876, -844, -812, -780, -748, -716, -684, -652,
|
||||||
|
-620, -588, -556, -524, -492, -460, -428, -396,
|
||||||
|
-372, -356, -340, -324, -308, -292, -276, -260,
|
||||||
|
-244, -228, -212, -196, -180, -164, -148, -132,
|
||||||
|
-120, -112, -104, -96, -88, -80, -72, -64,
|
||||||
|
-56, -48, -40, -32, -24, -16, -8, 0,
|
||||||
|
32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
|
||||||
|
23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
|
||||||
|
15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
|
||||||
|
11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
|
||||||
|
7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
|
||||||
|
5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
|
||||||
|
3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
|
||||||
|
2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
|
||||||
|
1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
|
||||||
|
1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
|
||||||
|
876, 844, 812, 780, 748, 716, 684, 652,
|
||||||
|
620, 588, 556, 524, 492, 460, 428, 396,
|
||||||
|
372, 356, 340, 324, 308, 292, 276, 260,
|
||||||
|
244, 228, 212, 196, 180, 164, 148, 132,
|
||||||
|
120, 112, 104, 96, 88, 80, 72, 64,
|
||||||
|
56, 48, 40, 32, 24, 16, 8, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] process(byte[] media) {
|
||||||
|
byte[] res = new byte[media.length * 2];
|
||||||
|
int j = 0;
|
||||||
|
for (int i = 0; i < media.length; i++) {
|
||||||
|
short s = muLawDecompressTable[media[i] & 0xff];
|
||||||
|
res[j++] = (byte) s;
|
||||||
|
res[j++] = (byte) (s >> 8);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO compare what's the fastest: table lookup or real conversion
|
||||||
|
/*
|
||||||
|
* ulaw2linear() - Convert a u-law value to 16-bit linear PCM
|
||||||
|
*
|
||||||
|
* First, a biased linear code is derived from the code word. An unbiased
|
||||||
|
* output can then be obtained by subtracting 33 from the biased code.
|
||||||
|
*
|
||||||
|
* Note that this function expects to be passed the complement of the
|
||||||
|
* original code word. This is in keeping with ISDN conventions.
|
||||||
|
*/
|
||||||
|
// private short ulaw2linear(byte u_val) {
|
||||||
|
// int t;
|
||||||
|
//
|
||||||
|
// /* Complement to obtain normal u-law value. */
|
||||||
|
// u_val = (byte) ~u_val;
|
||||||
|
//
|
||||||
|
// /*
|
||||||
|
// * Extract and bias the quantization bits. Then
|
||||||
|
// * shift up by the segment number and subtract out the bias.
|
||||||
|
// */
|
||||||
|
// t = ((u_val & QUANT_MASK) << 3) + cBias;
|
||||||
|
// t <<= (u_val & SEG_MASK) >> SEG_SHIFT;
|
||||||
|
//
|
||||||
|
// boolean s = (u_val & SIGN_BIT) == SIGN_BIT;
|
||||||
|
// return (short) (s ? (cBias - t) : (t - cBias));
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
130
src/peers/media/PcmuEncoder.java
Normal file
130
src/peers/media/PcmuEncoder.java
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010, 2011 Oleg Kulikov, Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
201
src/peers/media/RtpSender.java
Normal file
201
src/peers/media/RtpSender.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
30
src/peers/media/SoundSource.java
Normal file
30
src/peers/media/SoundSource.java
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2012 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package 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
64
src/peers/nat/Client.java
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.nat;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
|
public class Client {
|
||||||
|
|
||||||
|
private Server server;
|
||||||
|
//private String email;
|
||||||
|
private PeerManager peerManager;
|
||||||
|
|
||||||
|
public Client(String email, String localInetAddress, int localPort) {
|
||||||
|
//this.email = email;
|
||||||
|
// TODO automatic global access interface discovery
|
||||||
|
try {
|
||||||
|
InetAddress localAddress = InetAddress.getByName(localInetAddress);
|
||||||
|
server = new Server(localAddress, localPort);
|
||||||
|
peerManager = new PeerManager(localAddress, localPort);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
server.update(email);
|
||||||
|
Document document = server.getPeers(email);
|
||||||
|
peerManager.setDocument(document);
|
||||||
|
peerManager.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param args
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
if (args.length != 3) {
|
||||||
|
System.err.println("usage: java ... <email> <localAddress>" +
|
||||||
|
" <localPort>");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
new Client(args[0], args[1], Integer.parseInt(args[2]));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
117
src/peers/nat/PeerManager.java
Normal file
117
src/peers/nat/PeerManager.java
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package 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
175
src/peers/nat/Server.java
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.nat;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
public class Server {
|
||||||
|
|
||||||
|
public static final String SERVER_HOST = "peers.sourceforge.net";
|
||||||
|
public static final String PREFIX = "/peers";
|
||||||
|
//public static final int SOCKET_TIMEOUT = 30000;//millis
|
||||||
|
|
||||||
|
//private InetAddress localAddress;
|
||||||
|
//private int localPort;
|
||||||
|
private InetAddress remoteAddress;
|
||||||
|
private int remotePort;
|
||||||
|
|
||||||
|
private Socket socket;
|
||||||
|
|
||||||
|
//TODO constructor without parameters
|
||||||
|
public Server(InetAddress localAddress, int localPort) throws IOException {
|
||||||
|
super();
|
||||||
|
//this.localAddress = localAddress;
|
||||||
|
//this.localPort = localPort;
|
||||||
|
this.remoteAddress = InetAddress.getByName(SERVER_HOST);
|
||||||
|
this.remotePort = 80;
|
||||||
|
socket = new Socket(remoteAddress, remotePort, localAddress, localPort);
|
||||||
|
//socket.setSoTimeout(SOCKET_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will update public address on the web server.
|
||||||
|
* @param email user identifier
|
||||||
|
*/
|
||||||
|
public void update(String email) {
|
||||||
|
String encodedEmail;
|
||||||
|
try {
|
||||||
|
encodedEmail = URLEncoder.encode(email, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StringBuffer urlEnd = new StringBuffer();
|
||||||
|
urlEnd.append("update2.php?email=");
|
||||||
|
urlEnd.append(encodedEmail);
|
||||||
|
get(urlEnd.toString());
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document getPeers(String email) {
|
||||||
|
String encodedEmail;
|
||||||
|
try {
|
||||||
|
encodedEmail = URLEncoder.encode(email, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
StringBuffer urlBuf = new StringBuffer();
|
||||||
|
urlBuf.append("http://");
|
||||||
|
urlBuf.append(SERVER_HOST);
|
||||||
|
urlBuf.append(PREFIX);
|
||||||
|
urlBuf.append("/getassocasxml.php?email=");
|
||||||
|
urlBuf.append(encodedEmail);
|
||||||
|
URL url;
|
||||||
|
try {
|
||||||
|
url = new URL(urlBuf.toString());
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
System.out.println("retrieved peers");
|
||||||
|
DocumentBuilderFactory documentBuilderFactory
|
||||||
|
= DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder documentBuilder;
|
||||||
|
try {
|
||||||
|
documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||||
|
} catch (ParserConfigurationException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
URLConnection urlConnection = url.openConnection();
|
||||||
|
InputStream inputStream = urlConnection.getInputStream();
|
||||||
|
return documentBuilder.parse(inputStream);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (SAXException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String get(String urlEnd) {
|
||||||
|
StringBuffer get = new StringBuffer();
|
||||||
|
get.append("GET ");
|
||||||
|
get.append(PREFIX);
|
||||||
|
get.append('/');
|
||||||
|
get.append(urlEnd);
|
||||||
|
get.append(" HTTP/1.1\r\n");
|
||||||
|
get.append("Host: ");
|
||||||
|
get.append(SERVER_HOST);
|
||||||
|
get.append("\r\n");
|
||||||
|
get.append("\r\n");
|
||||||
|
|
||||||
|
try {
|
||||||
|
socket.getOutputStream().write(get.toString().getBytes());
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
System.out.println("> sent:\n" + get.toString());
|
||||||
|
|
||||||
|
StringBuffer result = new StringBuffer();
|
||||||
|
try {
|
||||||
|
byte[] buf = new byte[256];
|
||||||
|
int read = 0;
|
||||||
|
while ((read = socket.getInputStream().read(buf)) > -1) {
|
||||||
|
byte[] exactBuf = new byte[read];
|
||||||
|
System.arraycopy(buf, 0, exactBuf, 0, read);
|
||||||
|
result.append(new String(exactBuf));
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
System.out.println("socket timeout");
|
||||||
|
return null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
System.out.println("< received:\n" + result.toString());
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
if (socket != null) {
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/peers/nat/UDPReceiver.java
Normal file
49
src/peers/nat/UDPReceiver.java
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.nat;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
|
||||||
|
public class UDPReceiver extends Thread {
|
||||||
|
|
||||||
|
private DatagramSocket datagramSocket;
|
||||||
|
|
||||||
|
public UDPReceiver(DatagramSocket datagramSocket) {
|
||||||
|
super();
|
||||||
|
this.datagramSocket = datagramSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
byte[] buf = new byte[1024];
|
||||||
|
DatagramPacket packet = new DatagramPacket(buf, buf.length);
|
||||||
|
datagramSocket.receive(packet);
|
||||||
|
System.out.println("< received:\n"
|
||||||
|
+ new String(packet.getData()));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/peers/nat/api/DataReceiver.java
Normal file
26
src/peers/nat/api/DataReceiver.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.nat.api;
|
||||||
|
|
||||||
|
|
||||||
|
public interface DataReceiver {
|
||||||
|
|
||||||
|
public void dataReceived(byte[] data, String peerId);
|
||||||
|
}
|
||||||
47
src/peers/nat/api/PeersClient.java
Normal file
47
src/peers/nat/api/PeersClient.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.nat.api;
|
||||||
|
|
||||||
|
public abstract class PeersClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a new peers client
|
||||||
|
* @param myId the string identifier corresponding
|
||||||
|
* to the computer or to a person (email).
|
||||||
|
* @param dataReceiver object that will receive incoming traffic.
|
||||||
|
*/
|
||||||
|
public PeersClient(String myId, DataReceiver dataReceiver) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a UDP connection to a peer.
|
||||||
|
* @param peerId unique peer identifier (email for example).
|
||||||
|
* @return an object that allows to send data to the peer.
|
||||||
|
*/
|
||||||
|
public abstract UDPTransport createUDPTransport(String peerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a TCP connection to a peer.
|
||||||
|
* @param peerId unique peer identifier (email for example).
|
||||||
|
* @return an object that allows to send data to the peer.
|
||||||
|
*/
|
||||||
|
public abstract TCPTransport createTCPTransport(String peerId);
|
||||||
|
}
|
||||||
24
src/peers/nat/api/TCPTransport.java
Normal file
24
src/peers/nat/api/TCPTransport.java
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.nat.api;
|
||||||
|
|
||||||
|
public interface TCPTransport extends Transport {
|
||||||
|
|
||||||
|
}
|
||||||
25
src/peers/nat/api/Transport.java
Normal file
25
src/peers/nat/api/Transport.java
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.nat.api;
|
||||||
|
|
||||||
|
public interface Transport {
|
||||||
|
|
||||||
|
public void sendData(byte[] data);
|
||||||
|
}
|
||||||
24
src/peers/nat/api/UDPTransport.java
Normal file
24
src/peers/nat/api/UDPTransport.java
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.nat.api;
|
||||||
|
|
||||||
|
public interface UDPTransport extends Transport {
|
||||||
|
|
||||||
|
}
|
||||||
34
src/peers/rtp/RFC3551.java
Normal file
34
src/peers/rtp/RFC3551.java
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.rtp;
|
||||||
|
|
||||||
|
public class RFC3551 {
|
||||||
|
|
||||||
|
// payload types
|
||||||
|
|
||||||
|
public static final int PAYLOAD_TYPE_PCMU = 0;
|
||||||
|
public static final int PAYLOAD_TYPE_PCMA = 8;
|
||||||
|
|
||||||
|
// encoding names
|
||||||
|
|
||||||
|
public static final String PCMU = "PCMU";
|
||||||
|
public static final String PCMA = "PCMA";
|
||||||
|
|
||||||
|
}
|
||||||
32
src/peers/rtp/RFC4733.java
Normal file
32
src/peers/rtp/RFC4733.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.rtp;
|
||||||
|
|
||||||
|
public class RFC4733 {
|
||||||
|
|
||||||
|
// payload types
|
||||||
|
|
||||||
|
public static final int PAYLOAD_TYPE_TELEPHONE_EVENT = 101;
|
||||||
|
|
||||||
|
// encoding names
|
||||||
|
|
||||||
|
public static final String TELEPHONE_EVENT = "telephone-event";
|
||||||
|
|
||||||
|
}
|
||||||
26
src/peers/rtp/RtpListener.java
Normal file
26
src/peers/rtp/RtpListener.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.rtp;
|
||||||
|
|
||||||
|
public interface RtpListener {
|
||||||
|
|
||||||
|
void receivedRtpPacket(RtpPacket rtpPacket);
|
||||||
|
|
||||||
|
}
|
||||||
124
src/peers/rtp/RtpPacket.java
Normal file
124
src/peers/rtp/RtpPacket.java
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
119
src/peers/rtp/RtpParser.java
Normal file
119
src/peers/rtp/RtpParser.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
264
src/peers/rtp/RtpSession.java
Normal file
264
src/peers/rtp/RtpSession.java
Normal 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
67
src/peers/sdp/Codec.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
118
src/peers/sdp/MediaDescription.java
Normal file
118
src/peers/sdp/MediaDescription.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
52
src/peers/sdp/MediaDestination.java
Normal file
52
src/peers/sdp/MediaDestination.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
26
src/peers/sdp/NoCodecException.java
Normal file
26
src/peers/sdp/NoCodecException.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.sdp;
|
||||||
|
|
||||||
|
public class NoCodecException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
}
|
||||||
49
src/peers/sdp/RFC4566.java
Normal file
49
src/peers/sdp/RFC4566.java
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package 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";
|
||||||
|
}
|
||||||
150
src/peers/sdp/SDPManager.java
Normal file
150
src/peers/sdp/SDPManager.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
25
src/peers/sdp/SDPMessage.java
Normal file
25
src/peers/sdp/SDPMessage.java
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package peers.sdp;
|
||||||
|
|
||||||
|
public class SDPMessage {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
38
src/peers/sdp/SdpLine.java
Normal file
38
src/peers/sdp/SdpLine.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
236
src/peers/sdp/SdpParser.java
Normal file
236
src/peers/sdp/SdpParser.java
Normal 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
129
src/peers/sdp/SessionDescription.java
Normal file
129
src/peers/sdp/SessionDescription.java
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/peers/sip/AbstractState.java
Normal file
43
src/peers/sip/AbstractState.java
Normal 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
29
src/peers/sip/JavaUtils.java
Normal file
29
src/peers/sip/JavaUtils.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
47
src/peers/sip/RFC2617.java
Normal file
47
src/peers/sip/RFC2617.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 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
169
src/peers/sip/RFC3261.java
Normal 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
128
src/peers/sip/Utils.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
300
src/peers/sip/core/useragent/ChallengeManager.java
Normal file
300
src/peers/sip/core/useragent/ChallengeManager.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
312
src/peers/sip/core/useragent/InitialRequestManager.java
Normal file
312
src/peers/sip/core/useragent/InitialRequestManager.java
Normal 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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
27
src/peers/sip/core/useragent/MessageInterceptor.java
Normal file
27
src/peers/sip/core/useragent/MessageInterceptor.java
Normal 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);
|
||||||
|
}
|
||||||
251
src/peers/sip/core/useragent/MidDialogRequestManager.java
Normal file
251
src/peers/sip/core/useragent/MidDialogRequestManager.java
Normal 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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
136
src/peers/sip/core/useragent/RequestManager.java
Normal file
136
src/peers/sip/core/useragent/RequestManager.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
46
src/peers/sip/core/useragent/SipEvent.java
Normal file
46
src/peers/sip/core/useragent/SipEvent.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
44
src/peers/sip/core/useragent/SipListener.java
Normal file
44
src/peers/sip/core/useragent/SipListener.java
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
227
src/peers/sip/core/useragent/UAC.java
Normal file
227
src/peers/sip/core/useragent/UAC.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
133
src/peers/sip/core/useragent/UAS.java
Normal file
133
src/peers/sip/core/useragent/UAS.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
382
src/peers/sip/core/useragent/UserAgent.java
Normal file
382
src/peers/sip/core/useragent/UserAgent.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
183
src/peers/sip/core/useragent/handlers/ByeHandler.java
Normal file
183
src/peers/sip/core/useragent/handlers/ByeHandler.java
Normal 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
Reference in New Issue
Block a user