summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2009-03-12 18:11:37 +0000
committerBenny Prijono <bennylp@teluu.com>2009-03-12 18:11:37 +0000
commit1dacdee696b7591a6dcc0b3c1d0f41573e473168 (patch)
tree302b09dcd989c0c05cf09f6aebaa63d870b421b9
parentba9d8ca28eb209571c0bd6a080a8bb03d0fa2d33 (diff)
(Major) Task #737 and #738: integration of APS-Direct and Audiodev from aps-direct branch to trunk.
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2506 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r--Makefile1
-rw-r--r--build.mak.in1
-rw-r--r--build.symbian/bld.inf14
-rw-r--r--build.symbian/libpassthroughcodec.mmp26
-rw-r--r--build.symbian/pjlib.mmp4
-rw-r--r--build.symbian/pjlib_test.mmp4
-rw-r--r--build.symbian/pjlib_util.mmp4
-rw-r--r--build.symbian/pjmedia.mmp1
-rw-r--r--build.symbian/pjmedia_audiodev.mmp30
-rw-r--r--build.symbian/pjnath.mmp4
-rw-r--r--build.symbian/pjsdp.mmp4
-rw-r--r--build.symbian/pjsip.mmp4
-rw-r--r--build.symbian/pjsip_simple.mmp5
-rw-r--r--build.symbian/pjstun_client.mmp4
-rw-r--r--build.symbian/symbian_audio.mmp9
-rw-r--r--build.symbian/symbian_audio_aps.mmp20
-rw-r--r--build.symbian/symbian_ua.mmp133
-rw-r--r--build.symbian/symbian_ua.pkg18
-rw-r--r--build.symbian/symbian_ua_udeb.pkg11
-rw-r--r--build.symbian/symbian_ua_urel.pkg5
-rw-r--r--build.symbian/symsndtest.mmp64
-rw-r--r--pjlib/include/pj/compat/os_symbian.h2
-rw-r--r--pjlib/include/pj/config_site_sample.h292
-rw-r--r--pjlib/include/pj/errno.h1
-rw-r--r--pjmedia/build/Makefile33
-rw-r--r--pjmedia/build/os-auto.mak.in18
-rw-r--r--pjmedia/build/pjmedia.dsp26
-rw-r--r--pjmedia/build/pjmedia.vcproj78
-rw-r--r--pjmedia/build/pjmedia_audiodev.dsp150
-rw-r--r--pjmedia/build/pjmedia_audiodev.vcproj386
-rw-r--r--pjmedia/build/pjmedia_codec.dsp8
-rw-r--r--pjmedia/build/pjmedia_codec.vcproj4
-rw-r--r--pjmedia/include/pjmedia-audiodev/audiodev.h667
-rw-r--r--pjmedia/include/pjmedia-audiodev/audiodev_imp.h181
-rw-r--r--pjmedia/include/pjmedia-audiodev/audiotest.h116
-rw-r--r--pjmedia/include/pjmedia-audiodev/config.h368
-rw-r--r--pjmedia/include/pjmedia-audiodev/errno.h198
-rw-r--r--pjmedia/include/pjmedia-codec.h1
-rw-r--r--pjmedia/include/pjmedia-codec/amr_helper.h31
-rw-r--r--pjmedia/include/pjmedia-codec/config.h30
-rw-r--r--pjmedia/include/pjmedia-codec/passthrough.h78
-rw-r--r--pjmedia/include/pjmedia/alaw_ulaw.h16
-rw-r--r--pjmedia/include/pjmedia/codec.h3
-rw-r--r--pjmedia/include/pjmedia/conference.h14
-rw-r--r--pjmedia/include/pjmedia/config.h92
-rw-r--r--pjmedia/include/pjmedia/endpoint.h1
-rw-r--r--pjmedia/include/pjmedia/port.h30
-rw-r--r--pjmedia/include/pjmedia/sound.h13
-rw-r--r--pjmedia/include/pjmedia/sound_port.h40
-rw-r--r--pjmedia/include/pjmedia/stream.h1
-rw-r--r--pjmedia/include/pjmedia/symbian_sound_aps.h15
-rw-r--r--pjmedia/include/pjmedia/types.h306
-rw-r--r--pjmedia/include/pjmedia_audiodev.h33
-rw-r--r--pjmedia/src/pjmedia-audiodev/audiodev.c697
-rw-r--r--pjmedia/src/pjmedia-audiodev/audiotest.c269
-rw-r--r--pjmedia/src/pjmedia-audiodev/errno.c190
-rw-r--r--pjmedia/src/pjmedia-audiodev/legacy_dev.c459
-rw-r--r--pjmedia/src/pjmedia-audiodev/pa_dev.c (renamed from pjmedia/src/pjmedia/pasound.c)732
-rw-r--r--pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h171
-rw-r--r--pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp1611
-rw-r--r--pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp1110
-rw-r--r--pjmedia/src/pjmedia-audiodev/wmme_dev.c1311
-rw-r--r--pjmedia/src/pjmedia-codec/passthrough.c850
-rw-r--r--pjmedia/src/pjmedia/conf_switch.c1466
-rw-r--r--pjmedia/src/pjmedia/conference.c19
-rw-r--r--pjmedia/src/pjmedia/dsound.c1113
-rw-r--r--pjmedia/src/pjmedia/endpoint.c7
-rw-r--r--pjmedia/src/pjmedia/errno.c6
-rw-r--r--pjmedia/src/pjmedia/nullsound.c197
-rw-r--r--pjmedia/src/pjmedia/sound_legacy.c284
-rw-r--r--pjmedia/src/pjmedia/sound_port.c624
-rw-r--r--pjmedia/src/pjmedia/stream.c127
-rw-r--r--pjmedia/src/pjmedia/symbian_sound.cpp944
-rw-r--r--pjmedia/src/pjmedia/symbian_sound_aps.cpp920
-rw-r--r--pjmedia/src/pjmedia/transport_ice.c1
-rw-r--r--pjmedia/src/pjmedia/transport_loop.c1
-rw-r--r--pjmedia/src/pjmedia/transport_udp.c1
-rw-r--r--pjmedia/src/pjmedia/wmme_sound.c1008
-rw-r--r--pjproject-vs8.sln137
-rw-r--r--pjproject.dsw71
-rw-r--r--pjsip-apps/build/Makefile1
-rw-r--r--pjsip-apps/build/Samples-vc.mak10
-rw-r--r--pjsip-apps/build/Samples.mak5
-rw-r--r--pjsip-apps/build/sample_debug.dsp7
-rw-r--r--pjsip-apps/build/samples.dsp4
-rw-r--r--pjsip-apps/src/pjsua/pjsua_app.c9
-rw-r--r--pjsip-apps/src/samples/auddemo.c544
-rw-r--r--pjsip-apps/src/samples/debug.c2
-rw-r--r--pjsip-apps/src/samples/sndinfo.c295
-rw-r--r--pjsip-apps/src/samples/sndtest.c621
-rw-r--r--pjsip-apps/src/symbian_ua/main_symbian.cpp2
-rw-r--r--pjsip-apps/src/symbian_ua/ua.cpp281
-rw-r--r--pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp7
-rw-r--r--pjsip-apps/src/symsndtest/app_main.cpp110
-rw-r--r--pjsip-apps/src/symsndtest/main_symbian.cpp88
-rw-r--r--pjsip/include/pjsua-lib/pjsua.h118
-rw-r--r--pjsip/include/pjsua-lib/pjsua_internal.h11
-rw-r--r--pjsip/src/pjsua-lib/pjsua_call.c10
-rw-r--r--pjsip/src/pjsua-lib/pjsua_core.c7
-rw-r--r--pjsip/src/pjsua-lib/pjsua_media.c658
100 files changed, 14101 insertions, 6613 deletions
diff --git a/Makefile b/Makefile
index 507bb682..288f6eab 100644
--- a/Makefile
+++ b/Makefile
@@ -33,6 +33,7 @@ LIBS = pjlib/lib/libpj-$(TARGET_NAME).a \
pjlib-util/lib/libpjlib-util-$(TARGET_NAME).a \
pjnath/lib/libpjnath-$(TARGET_NAME).a \
pjmedia/lib/libpjmedia-$(TARGET_NAME).a \
+ pjmedia/lib/libpjmedia-audiodev-$(TARGET_NAME).a \
pjmedia/lib/libpjmedia-codec-$(TARGET_NAME).a \
pjsip/lib/libpjsip-$(TARGET_NAME).a \
pjsip/lib/libpjsip-ua-$(TARGET_NAME).a \
diff --git a/build.mak.in b/build.mak.in
index d0fea159..82f6a11b 100644
--- a/build.mak.in
+++ b/build.mak.in
@@ -60,6 +60,7 @@ export APP_LDLIBS := -lpjsua-$(TARGET_NAME)\
-lpjsip-$(TARGET_NAME)\
-lpjmedia-codec-$(TARGET_NAME)\
-lpjmedia-$(TARGET_NAME)\
+ -lpjmedia-audiodev-$(TARGET_NAME)\
-lpjnath-$(TARGET_NAME)\
-lpjlib-util-$(TARGET_NAME)\
$(APP_THIRD_PARTY_LIBS)\
diff --git a/build.symbian/bld.inf b/build.symbian/bld.inf
index b624905e..1f0ddbc6 100644
--- a/build.symbian/bld.inf
+++ b/build.symbian/bld.inf
@@ -1,6 +1,3 @@
-#define SND_USE_NULL 0
-#define SND_USE_APS 0
-
prj_platforms
winscw
//armv5
@@ -23,15 +20,10 @@ libsrtp.mmp
/* Codecs */
libgsmcodec.mmp
libspeexcodec.mmp
+libpassthroughcodec.mmp
-/* Sound device impl */
-#if SND_USE_NULL
- null_audio.mmp
-#elif SND_USE_APS
- symbian_audio_aps.mmp
-#else
- symbian_audio.mmp
-#endif
+/* Audio device. */
+pjmedia_audiodev.mmp
/* Applications */
//pjlib_test.mmp
diff --git a/build.symbian/libpassthroughcodec.mmp b/build.symbian/libpassthroughcodec.mmp
new file mode 100644
index 00000000..c4fb2775
--- /dev/null
+++ b/build.symbian/libpassthroughcodec.mmp
@@ -0,0 +1,26 @@
+TARGET libpassthroughcodec.lib
+TARGETTYPE lib
+
+MACRO HAVE_CONFIG_H
+MACRO PJ_M_I386=1
+MACRO PJ_SYMBIAN=1
+
+//
+// GCCE optimization setting
+//
+OPTION GCCE -O2 -fno-unit-at-a-time
+
+//
+// Passthrough codecs wrapper for pjmedia-codec
+//
+SOURCEPATH ..\pjmedia\src\pjmedia-codec
+SOURCE passthrough.c
+
+//
+// Header files
+//
+SYSTEMINCLUDE ..\pjmedia\include
+SYSTEMINCLUDE ..\pjlib\include
+
+SYSTEMINCLUDE \epoc32\include
+SYSTEMINCLUDE \epoc32\include\libc
diff --git a/build.symbian/pjlib.mmp b/build.symbian/pjlib.mmp
index db396672..3b2e9cf8 100644
--- a/build.symbian/pjlib.mmp
+++ b/build.symbian/pjlib.mmp
@@ -19,10 +19,6 @@ TARGETTYPE lib
SOURCEPATH ..\pjlib\src\pj
-OPTION CW -lang c++
-OPTION ARMCC --cpp
-OPTION GCC -x c++
-
MACRO PJ_M_I386=1
MACRO PJ_SYMBIAN=1
diff --git a/build.symbian/pjlib_test.mmp b/build.symbian/pjlib_test.mmp
index 37c5c53c..13783c2f 100644
--- a/build.symbian/pjlib_test.mmp
+++ b/build.symbian/pjlib_test.mmp
@@ -15,10 +15,6 @@ LIBRARY pjlib.lib
STATICLIBRARY pjlib.lib
#endif
-OPTION CW -lang c++
-OPTION ARMCC --cpp
-OPTION GCC -x c++
-
// Test files
SOURCE activesock.c
diff --git a/build.symbian/pjlib_util.mmp b/build.symbian/pjlib_util.mmp
index efbb41cf..c2d6ebec 100644
--- a/build.symbian/pjlib_util.mmp
+++ b/build.symbian/pjlib_util.mmp
@@ -22,10 +22,6 @@ SOURCEPATH ..\pjlib-util\src\pjlib-util
MACRO PJ_M_I386=1
MACRO PJ_SYMBIAN=1
-OPTION CW -lang c++
-OPTION ARMCC --cpp
-OPTION GCC -x c++
-
//
// PJLIB-UTIL files
//
diff --git a/build.symbian/pjmedia.mmp b/build.symbian/pjmedia.mmp
index ce2d279f..6c48b6c4 100644
--- a/build.symbian/pjmedia.mmp
+++ b/build.symbian/pjmedia.mmp
@@ -40,6 +40,7 @@ SOURCE bidirectional.c
SOURCE clock_thread.c
SOURCE codec.c
SOURCE conference.c
+SOURCE conf_switch.c
SOURCE echo_common.c
SOURCE echo_port.c
SOURCE echo_suppress.c
diff --git a/build.symbian/pjmedia_audiodev.mmp b/build.symbian/pjmedia_audiodev.mmp
new file mode 100644
index 00000000..ccabc2fc
--- /dev/null
+++ b/build.symbian/pjmedia_audiodev.mmp
@@ -0,0 +1,30 @@
+TARGET pjmedia_audiodev.lib
+TARGETTYPE lib
+
+SOURCEPATH ..\pjmedia\src\pjmedia-audiodev
+
+//
+// GCCE optimization setting
+//
+//OPTION GCCE -O2 -fno-unit-at-a-time
+
+MACRO PJ_M_I386=1
+MACRO PJ_SYMBIAN=1
+
+SOURCE audiodev.c
+SOURCE errno.c
+SOURCE symb_aps_dev.cpp
+SOURCE symb_mda_dev.cpp
+
+SYSTEMINCLUDE ..\pjmedia\include
+SYSTEMINCLUDE ..\pjlib\include
+SYSTEMINCLUDE ..\pjlib-util\include
+
+SYSTEMINCLUDE \epoc32\include
+SYSTEMINCLUDE \epoc32\include\libc
+SYSTEMINCLUDE \epoc32\include\mmf\server
+SYSTEMINCLUDE \epoc32\include\mmf\common
+SYSTEMINCLUDE \epoc32\include\mda\common
+SYSTEMINCLUDE \epoc32\include\mmf\plugin
+
+
diff --git a/build.symbian/pjnath.mmp b/build.symbian/pjnath.mmp
index 3060c4c7..7168313d 100644
--- a/build.symbian/pjnath.mmp
+++ b/build.symbian/pjnath.mmp
@@ -22,10 +22,6 @@ SOURCEPATH ..\pjnath\src\pjnath
MACRO PJ_M_I386=1
MACRO PJ_SYMBIAN=1
-OPTION CW -lang c++
-OPTION ARMCC --cpp
-OPTION GCC -x c++
-
//
// PJNATH files
//
diff --git a/build.symbian/pjsdp.mmp b/build.symbian/pjsdp.mmp
index 23c8bd3d..e8b41b84 100644
--- a/build.symbian/pjsdp.mmp
+++ b/build.symbian/pjsdp.mmp
@@ -19,10 +19,6 @@ TARGETTYPE lib
SOURCEPATH ..\pjmedia\src\pjmedia
-OPTION CW -lang c++
-OPTION ARMCC --cpp
-OPTION GCC -x c++
-
MACRO PJ_M_I386=1
MACRO PJ_SYMBIAN=1
diff --git a/build.symbian/pjsip.mmp b/build.symbian/pjsip.mmp
index 8fb7d085..e3e2bc29 100644
--- a/build.symbian/pjsip.mmp
+++ b/build.symbian/pjsip.mmp
@@ -25,10 +25,6 @@ SOURCEPATH ..\pjsip\src\pjsip
MACRO PJ_M_I386=1
MACRO PJ_SYMBIAN=1
-OPTION CW -lang c++
-OPTION ARMCC --cpp
-OPTION GCC -x c++
-
// PJSIP-CORE files
//SOURCE sip_auth_aka.c
diff --git a/build.symbian/pjsip_simple.mmp b/build.symbian/pjsip_simple.mmp
index d44b0cad..b02d1423 100644
--- a/build.symbian/pjsip_simple.mmp
+++ b/build.symbian/pjsip_simple.mmp
@@ -25,11 +25,6 @@ SOURCEPATH ..\pjsip\src\pjsip-simple
MACRO PJ_M_I386=1
MACRO PJ_SYMBIAN=1
-OPTION CW -lang c++
-OPTION ARMCC --cpp
-OPTION GCC -x c++
-
-
// PJSIP-SIMPLE files
SOURCE errno.c
diff --git a/build.symbian/pjstun_client.mmp b/build.symbian/pjstun_client.mmp
index 37b855da..fedb7a25 100644
--- a/build.symbian/pjstun_client.mmp
+++ b/build.symbian/pjstun_client.mmp
@@ -7,10 +7,6 @@ SOURCEPATH ..\pjnath\src\pjstun-client
MACRO PJ_M_I386=1
MACRO PJ_SYMBIAN=1
-OPTION CW -lang c++
-OPTION ARMCC --cpp
-OPTION GCC -x c++
-
// PJSTUN-CLIENT files
SOURCE client_main.c
diff --git a/build.symbian/symbian_audio.mmp b/build.symbian/symbian_audio.mmp
index 571b4b0c..e53f4e89 100644
--- a/build.symbian/symbian_audio.mmp
+++ b/build.symbian/symbian_audio.mmp
@@ -24,22 +24,23 @@ TARGETTYPE lib
SOURCEPATH ..\pjmedia\src\pjmedia
OPTION CW -lang c++
-
-//
-// GCCE optimization setting
-//
OPTION GCCE -O2 -fno-unit-at-a-time
MACRO PJ_M_I386=1
MACRO PJ_SYMBIAN=1
+SOURCE nullsound.c
SOURCE symbian_sound.cpp
+SOURCE symbian_sound_aps.cpp
SYSTEMINCLUDE ..\pjlib\include
SYSTEMINCLUDE ..\pjmedia\include
SYSTEMINCLUDE \epoc32\include
SYSTEMINCLUDE \epoc32\include\libc
+SYSTEMINCLUDE \epoc32\include\mmf\server
+SYSTEMINCLUDE \epoc32\include\mmf\common
+SYSTEMINCLUDE \epoc32\include\mda\common
SYSTEMINCLUDE \epoc32\include\mmf\plugin
diff --git a/build.symbian/symbian_audio_aps.mmp b/build.symbian/symbian_audio_aps.mmp
deleted file mode 100644
index 85cb1e84..00000000
--- a/build.symbian/symbian_audio_aps.mmp
+++ /dev/null
@@ -1,20 +0,0 @@
-TARGET symbian_audio_aps.lib
-TARGETTYPE lib
-
-OPTION CW -lang c++
-OPTION GCCE -O2 -fno-unit-at-a-time
-
-MACRO PJ_M_I386=1
-MACRO PJ_SYMBIAN=1
-
-SYSTEMINCLUDE ..\pjmedia\include
-SYSTEMINCLUDE ..\pjlib\include
-
-SOURCEPATH ..\pjmedia\src\pjmedia
-SOURCE symbian_sound_aps.cpp
-
-SYSTEMINCLUDE \epoc32\include\mmf\server
-SYSTEMINCLUDE \epoc32\include\mmf\common
-SYSTEMINCLUDE \epoc32\include\mda\common
-SYSTEMINCLUDE \epoc32\include\libc
-SYSTEMINCLUDE \epoc32\include
diff --git a/build.symbian/symbian_ua.mmp b/build.symbian/symbian_ua.mmp
index 938b84d4..a96da6ac 100644
--- a/build.symbian/symbian_ua.mmp
+++ b/build.symbian/symbian_ua.mmp
@@ -1,63 +1,70 @@
-#define SND_USE_NULL 0
-#define SND_USE_APS 0
-
-TARGET symbian_ua.exe
-TARGETTYPE exe
-UID 0x0 0xA000000D
-
-SOURCEPATH ..\pjsip-apps\src\symbian_ua
-
-MACRO PJ_M_I386=1
-MACRO PJ_SYMBIAN=1
-
-// Source files
-
-SOURCE ua.cpp
-SOURCE main_symbian.cpp
-
-DOCUMENT ua.h
-
-START RESOURCE symbian_ua_reg.rss
- TARGETPATH \private\10003a3f\apps
-END
-
-SYSTEMINCLUDE ..\pjlib\include
-SYSTEMINCLUDE ..\pjlib-util\include
-SYSTEMINCLUDE ..\pjnath\include
-SYSTEMINCLUDE ..\pjmedia\include
-SYSTEMINCLUDE ..\pjsip\include
-
-SYSTEMINCLUDE \epoc32\include
-SYSTEMINCLUDE \epoc32\include\libc
-
-STATICLIBRARY pjsua_lib.lib pjsip_ua.lib
-STATICLIBRARY pjsip_simple.lib pjsip.lib pjsdp.lib pjmedia.lib
-STATICLIBRARY pjnath.lib pjlib_util.lib pjlib.lib
-STATICLIBRARY libsrtp.lib
-STATICLIBRARY libgsmcodec.lib libspeexcodec.lib
-
-#if SND_USE_NULL
- STATICLIBRARY null_audio.lib
- CAPABILITY NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment
-#elif SND_USE_APS
- STATICLIBRARY symbian_audio_aps.lib
- LIBRARY APSSession2.lib
- CAPABILITY NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
- MACRO PJMEDIA_SYM_SND_USE_APS=1
-#else
- STATICLIBRARY symbian_audio.lib
- LIBRARY mediaclientaudiostream.lib
- LIBRARY mediaclientaudioinputstream.lib
- CAPABILITY NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment
-#endif
-
-#ifdef WINSCW
- STATICLIBRARY eexe.lib ecrt0.lib
-#endif
-
-LIBRARY esock.lib insock.lib charconv.lib euser.lib estlib.lib commdb.lib apengine.lib
-
-// The default 8KB seems to be insufficient with all bells and
-// whistles turned on
-EPOCSTACKSIZE 12288
-
+#define SND_HAS_APS 0
+#define SND_HAS_VAS 0
+#define SND_HAS_MDA 1
+
+TARGET symbian_ua.exe
+TARGETTYPE exe
+UID 0x0 0x200235D3
+
+SOURCEPATH ..\pjsip-apps\src\symbian_ua
+
+MACRO PJ_M_I386=1
+MACRO PJ_SYMBIAN=1
+
+// Source files
+
+SOURCE ua.cpp
+SOURCE main_symbian.cpp
+
+DOCUMENT ua.h
+
+START RESOURCE symbian_ua_reg.rss
+ TARGETPATH \private\10003a3f\apps
+END
+
+SYSTEMINCLUDE ..\pjlib\include
+SYSTEMINCLUDE ..\pjlib-util\include
+SYSTEMINCLUDE ..\pjnath\include
+SYSTEMINCLUDE ..\pjmedia\include
+SYSTEMINCLUDE ..\pjsip\include
+
+SYSTEMINCLUDE \epoc32\include
+SYSTEMINCLUDE \epoc32\include\libc
+
+STATICLIBRARY pjsua_lib.lib pjsip_ua.lib
+STATICLIBRARY pjsip_simple.lib pjsip.lib pjsdp.lib pjmedia.lib
+STATICLIBRARY pjnath.lib pjlib_util.lib pjlib.lib
+STATICLIBRARY libsrtp.lib
+STATICLIBRARY libgsmcodec.lib libspeexcodec.lib
+STATICLIBRARY libpassthroughcodec.lib
+STATICLIBRARY pjmedia_audiodev.lib
+
+#if SND_HAS_APS
+ LIBRARY APSSession2.lib
+#endif
+
+#if SND_HAS_VAS
+// LIBRARY
+#endif
+
+#if SND_HAS_MDA
+ LIBRARY mediaclientaudiostream.lib
+ LIBRARY mediaclientaudioinputstream.lib
+#endif
+
+#if SND_HAS_APS || SND_HAS_VAS
+ CAPABILITY NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
+#else
+ CAPABILITY NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment
+#endif
+
+#ifdef WINSCW
+ STATICLIBRARY eexe.lib ecrt0.lib
+#endif
+
+LIBRARY esock.lib insock.lib charconv.lib euser.lib estlib.lib commdb.lib apengine.lib
+
+// The default 8KB seems to be insufficient with all bells and
+// whistles turned on
+EPOCSTACKSIZE 12288
+
diff --git a/build.symbian/symbian_ua.pkg b/build.symbian/symbian_ua.pkg
new file mode 100644
index 00000000..c60a62bb
--- /dev/null
+++ b/build.symbian/symbian_ua.pkg
@@ -0,0 +1,18 @@
+; symbian_ua.pkg
+
+; Languages
+&EN
+
+; Header
+#{"symbian_ua"},(0x200235D3), 0, 1, 1
+
+; Platform compatibility
+[0x101F7961], *, *, *,{"Series60ProductID"}
+
+; vendor
+%{"PJSIP"}
+:"PJSIP"
+
+; Target
+"$(EPOCROOT)Epoc32\release\$(PLATFORM)\$(TARGET)\symbian_ua.exe"-"!:\sys\bin\symbian_ua.exe"
+"$(EPOCROOT)Epoc32\data\z\private\10003a3f\apps\symbian_ua_reg.rSC"-"!:\private\10003a3f\import\apps\symbian_ua_reg.rSC"
diff --git a/build.symbian/symbian_ua_udeb.pkg b/build.symbian/symbian_ua_udeb.pkg
index 4f6c6694..5c255bf0 100644
--- a/build.symbian/symbian_ua_udeb.pkg
+++ b/build.symbian/symbian_ua_udeb.pkg
@@ -1,10 +1,13 @@
-; symbian_ua.pkg
+; symbian_ua_udeb.pkg
+
+; for Carbide development, please use symbian_ua.pkg for comfortness
+; (epoc32 path, platform, and target are set automatically using env vars)
; Languages
&EN
; Header
-#{"symbian_ua"},(0xA000000D), 0, 1, 1
+#{"symbian_ua"},(0x200235D3), 0, 1, 1
; Platform compatibility
[0x101F7961], *, *, *,{"Series60ProductID"}
@@ -14,5 +17,5 @@
:"PJSIP"
; Target
-"C:\Symbian\9.1\S60_3rd_MR\epoc32\release\gcce\UDEB\symbian_ua.exe"-"!:\sys\bin\symbian_ua.exe"
-"C:\Symbian\9.1\S60_3rd_MR\epoc32\data\z\private\10003a3f\apps\symbian_ua_reg.rSC"-"!:\private\10003a3f\import\apps\symbian_ua_reg.rSC"
+"C:\Symbian\9.2\S60_3rd_FP1\epoc32\release\gcce\UDEB\symbian_ua.exe"-"!:\sys\bin\symbian_ua.exe"
+"C:\Symbian\9.2\S60_3rd_FP1\epoc32\data\z\private\10003a3f\apps\symbian_ua_reg.rSC"-"!:\private\10003a3f\import\apps\symbian_ua_reg.rSC"
diff --git a/build.symbian/symbian_ua_urel.pkg b/build.symbian/symbian_ua_urel.pkg
index 731276d2..3bd60982 100644
--- a/build.symbian/symbian_ua_urel.pkg
+++ b/build.symbian/symbian_ua_urel.pkg
@@ -1,10 +1,13 @@
; symbian_ua.pkg
+; for Carbide development, please use symbian_ua.pkg for comfortness
+; (epoc32 path, platform, and target are set automatically using env vars)
+
; Languages
&EN
; Header
-#{"symbian_ua"},(0xA000000D), 0, 1, 1
+#{"symbian_ua"},(0x200235D3), 0, 1, 1
; Platform compatibility
[0x101F7961], *, *, *,{"Series60ProductID"}
diff --git a/build.symbian/symsndtest.mmp b/build.symbian/symsndtest.mmp
index a08f1d8d..470de9d0 100644
--- a/build.symbian/symsndtest.mmp
+++ b/build.symbian/symsndtest.mmp
@@ -1,53 +1,45 @@
-#define SND_USE_NULL 0
-#define SND_USE_APS 0
+#define SND_USE_APS 1
+#define SND_USE_VAS 0
-TARGET symsndtest.exe
-TARGETTYPE exe
-UID 0x0 0xA000000E
+TARGET symsndtest.exe
+TARGETTYPE exe
+UID 0x0 0xA000000E
-SOURCEPATH ..\pjsip-apps\src\symsndtest
+SOURCEPATH ..\pjsip-apps\src\symsndtest
-MACRO PJ_M_I386=1
-MACRO PJ_SYMBIAN=1
+MACRO PJ_M_I386=1
+MACRO PJ_SYMBIAN=1
// Test files
-SOURCE app_main.cpp
-SOURCE main_symbian.cpp
+SOURCE app_main.cpp
+SOURCE main_symbian.cpp
-START RESOURCE symsndtest_reg.rss
+START RESOURCE symsndtest_reg.rss
TARGETPATH \private\10003a3f\apps
END
-SYSTEMINCLUDE ..\pjlib\include
-SYSTEMINCLUDE ..\pjmedia\include
+SYSTEMINCLUDE ..\pjlib\include
+SYSTEMINCLUDE ..\pjmedia\include
-SYSTEMINCLUDE \epoc32\include
-SYSTEMINCLUDE \epoc32\include\libc
+SYSTEMINCLUDE \epoc32\include
+SYSTEMINCLUDE \epoc32\include\libc
-LIBRARY charconv.lib euser.lib estlib.lib
-LIBRARY esock.lib insock.lib
-STATICLIBRARY pjlib.lib pjmedia.lib
+LIBRARY charconv.lib euser.lib estlib.lib
+LIBRARY esock.lib insock.lib
+STATICLIBRARY pjlib.lib pjmedia.lib
+STATICLIBRARY symbian_audio.lib
-#if SND_USE_NULL
- STATICLIBRARY null_audio.lib
- CAPABILITY NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment
-#elif SND_USE_APS
- SOURCEPATH ..\pjmedia\src\pjmedia
- SOURCE symbian_sound_aps.cpp
-
- SYSTEMINCLUDE \epoc32\include\mmf\server
- SYSTEMINCLUDE \epoc32\include\mmf\common
- SYSTEMINCLUDE \epoc32\include\mda\common
-
- //STATICLIBRARY symbian_audio_aps.lib
- LIBRARY APSSession2.lib
- CAPABILITY NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
+#if SND_USE_APS
+ LIBRARY APSSession2.lib
+ CAPABILITY NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
+#elif SND_USE_VAS
+// LIBRARY
+ CAPABILITY NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
#else
- STATICLIBRARY symbian_audio.lib
- LIBRARY mediaclientaudiostream.lib
- LIBRARY mediaclientaudioinputstream.lib
- CAPABILITY NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment
+ LIBRARY mediaclientaudiostream.lib
+ LIBRARY mediaclientaudioinputstream.lib
+ CAPABILITY NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment
#endif
#ifdef WINSCW
diff --git a/pjlib/include/pj/compat/os_symbian.h b/pjlib/include/pj/compat/os_symbian.h
index a586b5cc..4879d3d2 100644
--- a/pjlib/include/pj/compat/os_symbian.h
+++ b/pjlib/include/pj/compat/os_symbian.h
@@ -114,7 +114,7 @@
/*
* Override features.
*/
-#define PJ_HAS_FLOATING_POINT 1
+#define PJ_HAS_FLOATING_POINT 0
#define PJ_HAS_MALLOC 0
#define PJ_HAS_SEMAPHORE 1
#define PJ_HAS_EVENT_OBJ 0
diff --git a/pjlib/include/pj/config_site_sample.h b/pjlib/include/pj/config_site_sample.h
index 223a1da6..9336221e 100644
--- a/pjlib/include/pj/config_site_sample.h
+++ b/pjlib/include/pj/config_site_sample.h
@@ -1,12 +1,22 @@
-
-
-//#define PJ_CONFIG_MINIMAL_SIZE
-//#define PJ_CONFIG_MAXIMUM_SPEED
-
-
/*
- * This file (config_site_sample.h) contains various configuration
- * settings that I use for certain settings.
+ * This file contains several sample settings especially for Windows
+ * Mobile and Symbian targets. You can include this file in your
+ * <pj/config_site.h> file.
+ *
+ * The Windows Mobile and Symbian settings will be activated
+ * automatically if you include this file.
+ *
+ * In addition, you may specify one of these macros (before including
+ * this file) to activate additional settings:
+ *
+ * #define PJ_CONFIG_NOKIA_APS_DIRECT
+ * Use this macro to activate the APS-Direct feature. Please see
+ * http://trac.pjsip.org/repos/wiki/Nokia_APS_VAS_Direct for more
+ * info.
+ *
+ * #define PJ_CONFIG_WIN32_WMME_DIRECT
+ * Configuration to activate "APS-Direct" media mode on Windows or
+ * Windows Mobile, useful for testing purposes only.
*/
@@ -14,26 +24,63 @@
* Typical configuration for WinCE target.
*/
#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0
-# define PJ_HAS_FLOATING_POINT 0
-
-# define PJMEDIA_HAS_G711_PLC 0
-//# define PJMEDIA_HAS_SMALL_FILTER 1
-//# define PJMEDIA_HAS_LARGE_FILTER 0
-# define PJMEDIA_HAS_L16_CODEC 0
-/*# define PJMEDIA_HAS_GSM_CODEC 0*/
-/*# define PJMEDIA_HAS_ILBC_CODEC 0*/
-/*# define PJMEDIA_HAS_SPEEX_CODEC 0*/
-# define PJMEDIA_HAS_SPEEX_AEC 0
-# undef PJMEDIA_RESAMPLE_IMP
-# define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_LIBRESAMPLE
-# define PJMEDIA_WSOLA_IMP PJMEDIA_WSOLA_IMP_WSOLA_LITE
-
- /* Speex default quality settings */
-# define PJSUA_DEFAULT_CODEC_QUALITY 5
-# define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY 5
-
-# define PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER 0
-#endif
+
+ /*
+ * PJLIB settings.
+ */
+
+ /* Disable floating point support */
+ #define PJ_HAS_FLOATING_POINT 0
+
+ /*
+ * PJMEDIA settings
+ */
+
+ /* Select codecs to disable */
+ #define PJMEDIA_HAS_L16_CODEC 0
+ #define PJMEDIA_HAS_ILBC_CODEC 0
+
+ /* We probably need more buffers on WM, so increase the limit */
+ #define PJMEDIA_SOUND_BUFFER_COUNT 32
+
+ /* Fine tune Speex's default settings for best performance/quality */
+ #define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY 5
+
+ /* For CPU reason, disable speex AEC and use the echo suppressor. */
+ #define PJMEDIA_HAS_SPEEX_AEC 0
+
+ /* Shouldn't use resampling for performance reason too. */
+ #define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_NONE
+
+ /* Use the lighter WSOLA implementation */
+ #define PJMEDIA_WSOLA_IMP PJMEDIA_WSOLA_IMP_WSOLA_LITE
+
+ /*
+ * PJSIP settings.
+ */
+
+ /* Set maximum number of dialog/transaction/calls to minimum to reduce
+ * memory usage
+ */
+ #define PJSIP_MAX_TSX_COUNT 31
+ #define PJSIP_MAX_DIALOG_COUNT 31
+ #define PJSUA_MAX_CALLS 4
+
+ /*
+ * PJSUA settings
+ */
+
+ /* Default codec (Speex) quality */
+ #define PJSUA_DEFAULT_CODEC_QUALITY 5
+
+ /* Set maximum number of objects to minimum to reduce memory usage */
+ #define PJSUA_MAX_ACC 4
+ #define PJSUA_MAX_PLAYERS 4
+ #define PJSUA_MAX_RECORDERS 4
+ #define PJSUA_MAX_CONF_PORTS (PJSUA_MAX_CALLS+2*PJSUA_MAX_PLAYERS)
+ #define PJSUA_MAX_BUDDIES 32
+
+#endif /* PJ_WIN32_WINCE */
/*
@@ -41,81 +88,158 @@
*/
#if defined(PJ_SYMBIAN) && PJ_SYMBIAN!=0
- /* We don't want to use float, for now */
-# undef PJ_HAS_FLOATING_POINT
-# define PJ_HAS_FLOATING_POINT 0
+ /*
+ * PJLIB settings.
+ */
-# define PJMEDIA_SOUND_IMPLEMENTATION PJMEDIA_SOUND_NULL_SOUND
+ /* Disable floating point support */
+ #define PJ_HAS_FLOATING_POINT 0
/* Misc PJLIB setting */
-# define PJ_MAXPATH 80
+ #define PJ_MAXPATH 80
- /* SRTP has not been ported to Symbian yet */
-# define PJMEDIA_HAS_SRTP 1
+ /* This is important for Symbian. Symbian lacks vsnprintf(), so
+ * if the log buffer is not long enough it's possible that
+ * large incoming packet will corrupt memory when the log tries
+ * to log the packet.
+ */
+ #define PJ_LOG_MAX_SIZE (PJSIP_MAX_PKT_LEN+500)
- /* Disable these */
-# define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_NONE
-# define PJMEDIA_HAS_SPEEX_AEC 0
-# define PJMEDIA_WSOLA_IMP PJMEDIA_WSOLA_IMP_WSOLA_LITE
+ /* Since we don't have threads, log buffer can use static buffer
+ * rather than stack
+ */
+ #define PJ_LOG_USE_STACK_BUFFER 0
- /* Disable all codecs but G.711 and GSM, for now */
-# define PJMEDIA_HAS_GSM_CODEC 1
-# define PJMEDIA_HAS_L16_CODEC 0
-# define PJMEDIA_HAS_ILBC_CODEC 0
-# define PJMEDIA_HAS_SPEEX_CODEC 1
-# define PJMEDIA_HAS_G722_CODEC 0
+ /* Disable check stack since it increases footprint */
+ #define PJ_OS_HAS_CHECK_STACK 0
- /* Need larger sound buffers */
-# define PJMEDIA_SOUND_BUFFER_COUNT 16
- /* Disable safe module access */
-# define PJSIP_SAFE_MODULE 0
+ /*
+ * PJMEDIA settings
+ */
-# define PJSIP_MAX_PKT_LEN 2000
+ /* Disable non-Symbian audio devices */
+ #define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0
+ #define PJMEDIA_AUDIO_DEV_HAS_WMME 0
- /* This is important for Symbian. Symbian lacks vsnprintf(), so
- * if the log buffer is not long enough it's possible that
- * large incoming packet will corrupt memory when the log tries
- * to log the packet.
+ /* Select codecs to disable */
+ #define PJMEDIA_HAS_L16_CODEC 0
+ #define PJMEDIA_HAS_ILBC_CODEC 0
+ #define PJMEDIA_HAS_G722_CODEC 0
+
+ /* Fine tune Speex's default settings for best performance/quality */
+ #define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY 5
+
+ /* For CPU reason, disable speex AEC and use the echo suppressor. */
+ #define PJMEDIA_HAS_SPEEX_AEC 0
+
+ /* Shouldn't use resampling for performance reason too. */
+ #define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_NONE
+
+ /* Use the lighter WSOLA implementation */
+ #define PJMEDIA_WSOLA_IMP PJMEDIA_WSOLA_IMP_WSOLA_LITE
+
+ /* We probably need more buffers especially if MDA audio backend
+ * is used, so increase the limit
*/
-# define PJ_LOG_MAX_SIZE (PJSIP_MAX_PKT_LEN+500)
+ #define PJMEDIA_SOUND_BUFFER_COUNT 32
- /* Since we don't have threads, log buffer can use static buffer */
-# define PJ_LOG_USE_STACK_BUFFER 0
+ /*
+ * PJSIP settings.
+ */
- /* Disable check stack since it increases footprint */
-# undef PJ_OS_HAS_CHECK_STACK
-# define PJ_OS_HAS_CHECK_STACK 0
+ /* Disable safe module access, since we don't use multithreading */
+ #define PJSIP_SAFE_MODULE 0
+
+ /* Increase allowable packet size, just in case */
+ #define PJSIP_MAX_PKT_LEN 2000
/* Symbian has problem with too many large blocks */
-# define PJSIP_POOL_LEN_ENDPT 1000
-# define PJSIP_POOL_INC_ENDPT 1000
-# define PJSIP_POOL_RDATA_LEN 2000
-# define PJSIP_POOL_RDATA_INC 2000
-# define PJSIP_POOL_LEN_TDATA 2000
-# define PJSIP_POOL_INC_TDATA 512
-# define PJSIP_POOL_LEN_UA 2000
-# define PJSIP_POOL_INC_UA 1000
-# define PJSIP_POOL_TSX_LAYER_LEN 256
-# define PJSIP_POOL_TSX_LAYER_INC 256
-# define PJSIP_POOL_TSX_LEN 512
-# define PJSIP_POOL_TSX_INC 128
+ #define PJSIP_POOL_LEN_ENDPT 1000
+ #define PJSIP_POOL_INC_ENDPT 1000
+ #define PJSIP_POOL_RDATA_LEN 2000
+ #define PJSIP_POOL_RDATA_INC 2000
+ #define PJSIP_POOL_LEN_TDATA 2000
+ #define PJSIP_POOL_INC_TDATA 512
+ #define PJSIP_POOL_LEN_UA 2000
+ #define PJSIP_POOL_INC_UA 1000
+ #define PJSIP_POOL_TSX_LAYER_LEN 256
+ #define PJSIP_POOL_TSX_LAYER_INC 256
+ #define PJSIP_POOL_TSX_LEN 512
+ #define PJSIP_POOL_TSX_INC 128
+
+ /*
+ * PJSUA settings.
+ */
+
+ /* Default codec quality */
+ #define PJSUA_DEFAULT_CODEC_QUALITY 5
/* Set maximum number of dialog/transaction/calls to minimum */
-# define PJSIP_MAX_TSX_COUNT 31
-# define PJSIP_MAX_DIALOG_COUNT 31
-# define PJSUA_MAX_CALLS 4
+ #define PJSIP_MAX_TSX_COUNT 31
+ #define PJSIP_MAX_DIALOG_COUNT 31
+ #define PJSUA_MAX_CALLS 4
/* Other pjsua settings */
-# define PJSUA_MAX_ACC 4
-# define PJSUA_MAX_PLAYERS 4
-# define PJSUA_MAX_RECORDERS 4
-# define PJSUA_MAX_CONF_PORTS (PJSUA_MAX_CALLS+2*PJSUA_MAX_PLAYERS)
-# define PJSUA_MAX_BUDDIES 32
-
- /* Speex default quality settings */
-# define PJSUA_DEFAULT_CODEC_QUALITY 5
-# define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY 5
+ #define PJSUA_MAX_ACC 4
+ #define PJSUA_MAX_PLAYERS 4
+ #define PJSUA_MAX_RECORDERS 4
+ #define PJSUA_MAX_CONF_PORTS (PJSUA_MAX_CALLS+2*PJSUA_MAX_PLAYERS)
+ #define PJSUA_MAX_BUDDIES 32
+#endif
+
+
+/*
+ * Additional configuration to activate APS-Direct feature for
+ * Nokia S60 target
+ *
+ * Please see http://trac.pjsip.org/repos/wiki/Nokia_APS_VAS_Direct
+ */
+#ifdef PJ_CONFIG_NOKIA_APS_DIRECT
+
+ /* MUST use switchboard rather than the conference bridge */
+ #define PJMEDIA_CONF_USE_SWITCH_BOARD 1
+
+ /* Enable APS sound device backend and disable MDA */
+ #define PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA 0
+ #define PJMEDIA_AUDIO_DEV_HAS_SYMB_APS 1
+
+ /* Enable passthrough codec framework */
+ #define PJMEDIA_HAS_PASSTHROUGH_CODECS 1
+
+ /* And selectively enable which codecs are supported by the handset */
+ #define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU 1
+ #define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA 1
+ #define PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR 1
+ #define PJMEDIA_HAS_PASSTHROUGH_CODEC_G729 1
+ #define PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC 1
+
+#endif
+
+
+/*
+ * Configuration to activate "APS-Direct" media mode on Windows,
+ * useful for testing purposes only.
+ */
+#ifdef PJ_CONFIG_WIN32_WMME_DIRECT
+
+ /* MUST use switchboard rather than the conference bridge */
+ #define PJMEDIA_CONF_USE_SWITCH_BOARD 1
+
+ /* Only WMME supports the "direct" feature */
+ #define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0
+ #define PJMEDIA_AUDIO_DEV_HAS_WMME 1
+
+ /* Enable passthrough codec framework */
+ #define PJMEDIA_HAS_PASSTHROUGH_CODECS 1
+
+ /* Only PCMA and PCMU are supported by WMME-direct */
+ #define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU 1
+ #define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA 1
+ #define PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR 0
+ #define PJMEDIA_HAS_PASSTHROUGH_CODEC_G729 0
+ #define PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC 0
+
#endif
diff --git a/pjlib/include/pj/errno.h b/pjlib/include/pj/errno.h
index 59b21cf4..07cdcf8a 100644
--- a/pjlib/include/pj/errno.h
+++ b/pjlib/include/pj/errno.h
@@ -375,6 +375,7 @@ PJ_DECL(pj_status_t) pj_register_strerror(pj_status_t start_code,
* - PJSIP_SIMPLE_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*2)
* - PJLIB_UTIL_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*3)
* - PJNATH_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*4)
+ * - PJMEDIA_AUDIODEV_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*5)
*/
/* Internal */
diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile
index 291339a7..e1254167 100644
--- a/pjmedia/build/Makefile
+++ b/pjmedia/build/Makefile
@@ -18,8 +18,8 @@ PJNATH_LIB:=$(PJDIR)/pjnath/lib/libpjnath-$(TARGET_NAME)$(LIBEXT)
export PJMEDIA_LIB:=../lib/libpjmedia-$(TARGET_NAME)$(LIBEXT)
export PJMEDIA_CODEC_LIB:=../lib/libpjmedia-codec-$(TARGET_NAME)$(LIBEXT)
export PJSDP_LIB:=../lib/libpjsdp-$(TARGET_NAME)$(LIBEXT)
+export PJMEDIA_AUDIODEV_LIB:=../lib/libpjmedia-audiodev-$(TARGET_NAME)$(LIBEXT)
-NULLSOUND_OBJS := nullsound.o
###############################################################################
# Gather all flags.
@@ -34,6 +34,7 @@ export _CFLAGS := $(CC_CFLAGS) $(OS_CFLAGS) $(HOST_CFLAGS) $(M_CFLAGS) \
export _CXXFLAGS:= $(_CFLAGS) $(CC_CXXFLAGS) $(OS_CXXFLAGS) $(M_CXXFLAGS) \
$(HOST_CXXFLAGS) $(CXXFLAGS)
export _LDFLAGS := $(subst /,$(HOST_PSEP),$(PJMEDIA_LIB)) \
+ $(subst /,$(HOST_PSEP),$(PJMEDIA_AUDIODEV_LIB)) \
$(subst /,$(HOST_PSEP),$(PJMEDIA_CODEC_LIB)) \
$(subst /,$(HOST_PSEP),$(PJLIB_LIB)) \
$(subst /,$(HOST_PSEP),$(PJLIB_UTIL_LIB)) \
@@ -49,24 +50,33 @@ export _LDFLAGS := $(subst /,$(HOST_PSEP),$(PJMEDIA_LIB)) \
export PJMEDIA_SRCDIR = ../src/pjmedia
export PJMEDIA_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
alaw_ulaw.o alaw_ulaw_table.o clock_thread.o codec.o \
- conference.o delaybuf.o echo_common.o echo_port.o \
- echo_suppress.o endpoint.o errno.o \
+ conference.o conf_switch.o delaybuf.o echo_common.o \
+ echo_port.o echo_suppress.o endpoint.o errno.o \
g711.o jbuf.o master_port.o mem_capture.o mem_player.o \
null_port.o plc_common.o port.o splitcomb.o \
resample_resample.o resample_libsamplerate.o \
resample_port.o rtcp.o rtcp_xr.o rtp.o \
- sdp.o sdp_cmp.o sdp_neg.o \
- session.o silencedet.o sound_port.o stereo_port.o \
+ sdp.o sdp_cmp.o sdp_neg.o session.o silencedet.o \
+ sound_legacy.o sound_port.o stereo_port.o \
stream.o tonegen.o transport_adapter_sample.o \
transport_ice.o transport_loop.o \
transport_srtp.o transport_udp.o \
wav_player.o wav_playlist.o wav_writer.o wave.o \
- wsola.o $(SOUND_OBJS) $(NULLSOUND_OBJS)
+ wsola.o
export PJMEDIA_CFLAGS += $(_CFLAGS)
###############################################################################
+# Defines for building PJMEDIA-AUDIODEV library
+#
+export PJMEDIA_AUDIODEV_SRCDIR = ../src/pjmedia-audiodev
+export PJMEDIA_AUDIODEV_OBJS += audiodev.o audiotest.o errno.o legacy_dev.o pa_dev.o \
+ wmme_dev.o
+export PJMEDIA_AUDIODEV_CFLAGS += $(_CFLAGS)
+
+
+###############################################################################
# Defines for building PJSDP library
# Note that SDP functionality is already INCLUDED in PJMEDIA.
# The PJSDP library should only be used for applications that want SDP
@@ -106,7 +116,7 @@ export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT
#
# $(TARGET) is defined in os-$(OS_NAME).mak file in current directory.
#
-TARGETS := pjmedia pjmedia-codec pjsdp pjmedia-test
+TARGETS := pjmedia pjmedia-audiodev pjmedia-codec pjsdp pjmedia-test
all: $(TARGETS)
@@ -121,7 +131,7 @@ doc:
dep: depend
distclean: realclean
-.PHONY: dep depend pjmedia pjmedia-codec pjmedia-test clean realclean distclean
+.PHONY: dep depend pjmedia pjmedia-codec pjmedia-audiodev pjmedia-test clean realclean distclean
pjmedia:
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $(PJMEDIA_LIB)
@@ -129,6 +139,9 @@ pjmedia:
pjmedia-codec:
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $(PJMEDIA_CODEC_LIB)
+pjmedia-audiodev:
+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_AUDIODEV app=pjmedia-audiodev $(PJMEDIA_AUDIODEV_LIB)
+
pjsdp:
$(MAKE) -f $(RULES_MAK) APP=PJSDP app=pjsdp $(PJSDP_LIB)
@@ -152,22 +165,26 @@ pjmedia-test: $(PJMEDIA_LIB)
clean:
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $@
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $@
+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_AUDIODEV app=pjmedia-audiodev $@
$(MAKE) -f $(RULES_MAK) APP=PJSDP app=pjsdp $@
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_TEST app=pjmedia-test $@
realclean:
$(subst @@,$(subst /,$(HOST_PSEP),.pjmedia-$(TARGET_NAME).depend),$(HOST_RMR))
+ $(subst @@,$(subst /,$(HOST_PSEP),.pjmedia-audiodev-$(TARGET_NAME).depend),$(HOST_RMR))
$(subst @@,$(subst /,$(HOST_PSEP),.pjmedia-codec-$(TARGET_NAME).depend),$(HOST_RMR))
$(subst @@,$(subst /,$(HOST_PSEP),.pjmedia-test-$(TARGET_NAME).depend),$(HOST_RMR))
$(subst @@,$(subst /,$(HOST_PSEP),.pjsdp-$(TARGET_NAME).depend),$(HOST_RMR))
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $@
+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia-audiodev $@
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $@
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_TEST app=pjmedia-test $@
$(MAKE) -f $(RULES_MAK) APP=PJSDP app=pjsdp $@
depend:
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $@
+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia-audiodev $@
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $@
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_TEST app=pjmedia-test $@
$(MAKE) -f $(RULES_MAK) APP=PJSDP app=pjsdp $@
diff --git a/pjmedia/build/os-auto.mak.in b/pjmedia/build/os-auto.mak.in
index 656e3d58..74911aea 100644
--- a/pjmedia/build/os-auto.mak.in
+++ b/pjmedia/build/os-auto.mak.in
@@ -78,23 +78,24 @@ endif
# PortAudio
#
ifneq ($(findstring pa,$(AC_PJMEDIA_SND)),)
-export CFLAGS += -I$(THIRD_PARTY)/build/portaudio -I$(THIRD_PARTY)/portaudio/include -DPJMEDIA_SOUND_IMPLEMENTATION=PJMEDIA_SOUND_PORTAUDIO_SOUND
-export SOUND_OBJS = pasound.o
+export CFLAGS += -I$(THIRD_PARTY)/build/portaudio -I$(THIRD_PARTY)/portaudio/include -DPJMEDIA_AUDIO_DEV_HAS_PORTAUDIO=1
endif
#
-# Win32 DirectSound
+# Windows specific
#
-ifeq ($(AC_PJMEDIA_SND),ds)
-export SOUND_OBJS = dsound.o
-export CFLAGS += -DPJMEDIA_SOUND_IMPLEMENTATION=PJMEDIA_SOUND_WIN32_DIRECT_SOUND
+ifneq ($(findstring win32,$(AC_PJMEDIA_SND)),)
+export CFLAGS += -DPJMEDIA_AUDIO_DEV_HAS_WMME=1
+else
+export CFLAGS += -DPJMEDIA_AUDIO_DEV_HAS_WMME=0
endif
#
# Null sound device
#
ifeq ($(AC_PJMEDIA_SND),null)
-export SOUND_OBJS = nullsound.o
+# ***** Error ******
+# This will not work either with the new Audiodev
export CFLAGS += -DPJMEDIA_SOUND_IMPLEMENTATION=PJMEDIA_SOUND_NULL_SOUND
endif
@@ -102,8 +103,7 @@ endif
# External sound device
#
ifeq ($(AC_PJMEDIA_SND),external)
-export SOUND_OBJS =
-export CFLAGS += -DPJMEDIA_SOUND_IMPLEMENTATION=PJMEDIA_SOUND_EXTERNAL
+export CFLAGS += -DPJMEDIA_AUDIO_DEV_HAS_PORTAUDIO=0 -DPJMEDIA_AUDIO_DEV_HAS_WMME=0
endif
diff --git a/pjmedia/build/pjmedia.dsp b/pjmedia/build/pjmedia.dsp
index 844938f3..aa127e77 100644
--- a/pjmedia/build/pjmedia.dsp
+++ b/pjmedia/build/pjmedia.dsp
@@ -109,15 +109,15 @@ SOURCE=..\src\pjmedia\codec.c
# End Source File
# Begin Source File
-SOURCE=..\src\pjmedia\conference.c
+SOURCE=..\src\pjmedia\conf_switch.c
# End Source File
# Begin Source File
-SOURCE=..\src\pjmedia\delaybuf.c
+SOURCE=..\src\pjmedia\conference.c
# End Source File
# Begin Source File
-SOURCE=..\src\pjmedia\dsound.c
+SOURCE=..\src\pjmedia\delaybuf.c
# End Source File
# Begin Source File
@@ -173,14 +173,6 @@ SOURCE=..\src\pjmedia\null_port.c
# End Source File
# Begin Source File
-SOURCE=..\src\pjmedia\nullsound.c
-# End Source File
-# Begin Source File
-
-SOURCE=..\src\pjmedia\pasound.c
-# End Source File
-# Begin Source File
-
SOURCE=..\src\pjmedia\plc_common.c
# End Source File
# Begin Source File
@@ -237,6 +229,10 @@ SOURCE=..\src\pjmedia\silencedet.c
# End Source File
# Begin Source File
+SOURCE=..\src\pjmedia\sound_legacy.c
+# End Source File
+# Begin Source File
+
SOURCE=..\src\pjmedia\sound_port.c
# End Source File
# Begin Source File
@@ -293,10 +289,6 @@ SOURCE=..\src\pjmedia\wave.c
# End Source File
# Begin Source File
-SOURCE=..\src\pjmedia\wmme_sound.c
-# End Source File
-# Begin Source File
-
SOURCE=..\src\pjmedia\wsola.c
# End Source File
# End Group
@@ -309,6 +301,10 @@ SOURCE=..\include\pjmedia\alaw_ulaw.h
# End Source File
# Begin Source File
+SOURCE=..\include\pjmedia\audio_dev.h
+# End Source File
+# Begin Source File
+
SOURCE=..\include\pjmedia\bidirectional.h
# End Source File
# Begin Source File
diff --git a/pjmedia/build/pjmedia.vcproj b/pjmedia/build/pjmedia.vcproj
index d51f3008..13d80a68 100644
--- a/pjmedia/build/pjmedia.vcproj
+++ b/pjmedia/build/pjmedia.vcproj
@@ -434,6 +434,10 @@
</FileConfiguration>
</File>
<File
+ RelativePath="..\src\pjmedia\conf_switch.c"
+ >
+ </File>
+ <File
RelativePath="..\src\pjmedia\conference.c"
>
<FileConfiguration
@@ -460,28 +464,6 @@
>
</File>
<File
- RelativePath="..\src\pjmedia\dsound.c"
- >
- <FileConfiguration
- Name="Release|Win32"
- >
- <Tool
- Name="VCCLCompilerTool"
- AdditionalIncludeDirectories=""
- PreprocessorDefinitions=""
- />
- </FileConfiguration>
- <FileConfiguration
- Name="Debug|Win32"
- >
- <Tool
- Name="VCCLCompilerTool"
- AdditionalIncludeDirectories=""
- PreprocessorDefinitions=""
- />
- </FileConfiguration>
- </File>
- <File
RelativePath="..\src\pjmedia\echo_common.c"
>
<FileConfiguration
@@ -746,50 +728,6 @@
</FileConfiguration>
</File>
<File
- RelativePath="..\src\pjmedia\nullsound.c"
- >
- <FileConfiguration
- Name="Release|Win32"
- >
- <Tool
- Name="VCCLCompilerTool"
- AdditionalIncludeDirectories=""
- PreprocessorDefinitions=""
- />
- </FileConfiguration>
- <FileConfiguration
- Name="Debug|Win32"
- >
- <Tool
- Name="VCCLCompilerTool"
- AdditionalIncludeDirectories=""
- PreprocessorDefinitions=""
- />
- </FileConfiguration>
- </File>
- <File
- RelativePath="..\src\pjmedia\pasound.c"
- >
- <FileConfiguration
- Name="Release|Win32"
- >
- <Tool
- Name="VCCLCompilerTool"
- AdditionalIncludeDirectories=""
- PreprocessorDefinitions=""
- />
- </FileConfiguration>
- <FileConfiguration
- Name="Debug|Win32"
- >
- <Tool
- Name="VCCLCompilerTool"
- AdditionalIncludeDirectories=""
- PreprocessorDefinitions=""
- />
- </FileConfiguration>
- </File>
- <File
RelativePath="..\src\pjmedia\plc_common.c"
>
<FileConfiguration
@@ -1026,6 +964,10 @@
</FileConfiguration>
</File>
<File
+ RelativePath="..\src\pjmedia\sound_legacy.c"
+ >
+ </File>
+ <File
RelativePath="..\src\pjmedia\sound_port.c"
>
<FileConfiguration
@@ -1244,10 +1186,6 @@
</FileConfiguration>
</File>
<File
- RelativePath="..\src\pjmedia\wmme_sound.c"
- >
- </File>
- <File
RelativePath="..\src\pjmedia\wsola.c"
>
</File>
diff --git a/pjmedia/build/pjmedia_audiodev.dsp b/pjmedia/build/pjmedia_audiodev.dsp
new file mode 100644
index 00000000..10217689
--- /dev/null
+++ b/pjmedia/build/pjmedia_audiodev.dsp
@@ -0,0 +1,150 @@
+# Microsoft Developer Studio Project File - Name="pjmedia_audiodev" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Static Library" 0x0104
+
+CFG=pjmedia_audiodev - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "pjmedia_audiodev.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "pjmedia_audiodev.mak" CFG="pjmedia_audiodev - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "pjmedia_audiodev - Win32 Release" (based on "Win32 (x86) Static Library")
+!MESSAGE "pjmedia_audiodev - Win32 Debug" (based on "Win32 (x86) Static Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "pjmedia_audiodev - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir ".\output\pjmedia-audiodev-i386-win32-vc6-release"
+# PROP BASE Intermediate_Dir ".\output\pjmedia-audiodev-i386-win32-vc6-release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir ".\output\pjmedia-audiodev-i386-win32-vc6-release"
+# PROP Intermediate_Dir ".\output\pjmedia-audiodev-i386-win32-vc6-release"
+# PROP Target_Dir ""
+F90=df.exe
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
+# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjnath/include" /I "../../third_party/portaudio/include" /I "../../third_party/speex/include" /I "../../third_party/build/srtp" /I "../../third_party/srtp/crypto/include" /I "../../third_party/srtp/include" /I "../.." /D "NDEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /c
+# SUBTRACT CPP /YX
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LIB32=link.exe -lib
+# ADD BASE LIB32 /nologo
+# ADD LIB32 /nologo /out:"../lib/pjmedia-audiodev-i386-win32-vc6-release.lib"
+
+!ELSEIF "$(CFG)" == "pjmedia_audiodev - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir ".\output\pjmedia_audiodev-i386-win32-vc6-debug"
+# PROP BASE Intermediate_Dir ".\output\pjmedia_audiodev-i386-win32-vc6-debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir ".\output\pjmedia-audiodev-i386-win32-vc6-debug"
+# PROP Intermediate_Dir ".\output\pjmedia-audiodev-i386-win32-vc6-debug"
+# PROP Target_Dir ""
+F90=df.exe
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjnath/include" /I "../../third_party/portaudio/include" /I "../../third_party/speex/include" /I "../../third_party/build/srtp" /I "../../third_party/srtp/crypto/include" /I "../../third_party/srtp/include" /I "../.." /D "_DEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /GZ /c
+# SUBTRACT CPP /YX
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LIB32=link.exe -lib
+# ADD BASE LIB32 /nologo
+# ADD LIB32 /nologo /out:"../lib/pjmedia-audiodev-i386-win32-vc6-debug.lib"
+
+!ENDIF
+
+# Begin Target
+
+# Name "pjmedia_audiodev - Win32 Release"
+# Name "pjmedia_audiodev - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\audiodev.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\audiotest.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\errno.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\legacy_dev.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\pa_dev.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\symb_aps_dev.cpp"
+# PROP Exclude_From_Build 1
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\symb_mda_dev.cpp"
+# PROP Exclude_From_Build 1
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\wmme_dev.c"
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE="..\include\pjmedia-audiodev\audiodev.h"
+# End Source File
+# Begin Source File
+
+SOURCE="..\include\pjmedia-audiodev\audiodev_imp.h"
+# End Source File
+# Begin Source File
+
+SOURCE="..\include\pjmedia-audiodev\audiotest.h"
+# End Source File
+# Begin Source File
+
+SOURCE="..\include\pjmedia-audiodev\config.h"
+# End Source File
+# Begin Source File
+
+SOURCE="..\include\pjmedia-audiodev\errno.h"
+# End Source File
+# End Group
+# End Target
+# End Project
diff --git a/pjmedia/build/pjmedia_audiodev.vcproj b/pjmedia/build/pjmedia_audiodev.vcproj
new file mode 100644
index 00000000..ab432a66
--- /dev/null
+++ b/pjmedia/build/pjmedia_audiodev.vcproj
@@ -0,0 +1,386 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="pjmedia_audiodev"
+ ProjectGUID="{4281CA5E-1D48-45D4-A991-2718A454B4BA}"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory=".\output\pjmedia-audiodev-i386-win32-vc8-debug"
+ IntermediateDirectory=".\output\pjmedia-audiodev-i386-win32-vc8-debug"
+ ConfigurationType="4"
+ InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops"
+ UseOfMFC="0"
+ ATLMinimizesCRunTimeLibraryUsage="false"
+ CharacterSet="2"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories="../include,../../pjlib/include,../../pjlib-util/include,../../pjnath/include,../../third_party/portaudio/include,../../third_party/speex/include,../../third_party/build/srtp,../../third_party/srtp/crypto/include,../../third_party/srtp/include,../.."
+ PreprocessorDefinitions="_DEBUG;WIN32;_LIB;PJ_WIN32=1;PJ_M_I386=1"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="1"
+ PrecompiledHeaderFile=".\output\pjmedia-audiodev-i386-win32-vc8-debug/pjmedia_audiodev.pch"
+ AssemblerListingLocation=".\output\pjmedia-audiodev-i386-win32-vc8-debug/"
+ ObjectFile=".\output\pjmedia-audiodev-i386-win32-vc8-debug/"
+ ProgramDataBaseFileName=".\output\pjmedia-audiodev-i386-win32-vc8-debug/"
+ BrowseInformation="1"
+ WarningLevel="4"
+ SuppressStartupBanner="true"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ PreprocessorDefinitions="_DEBUG"
+ Culture="1033"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ OutputFile="../lib/pjmedia-audiodev-i386-win32-vc8-debug.lib"
+ SuppressStartupBanner="true"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ SuppressStartupBanner="true"
+ OutputFile=".\output\pjmedia-audiodev-i386-win32-vc8-debug/pjmedia_audiodev.bsc"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory=".\output\pjmedia-audiodev-i386-win32-vc8-release"
+ IntermediateDirectory=".\output\pjmedia-audiodev-i386-win32-vc8-release"
+ ConfigurationType="4"
+ InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops"
+ UseOfMFC="0"
+ ATLMinimizesCRunTimeLibraryUsage="false"
+ CharacterSet="2"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ InlineFunctionExpansion="1"
+ AdditionalIncludeDirectories="../include,../../pjlib/include,../../pjlib-util/include,../../pjnath/include,../../third_party/portaudio/include,../../third_party/speex/include,../../third_party/build/srtp,../../third_party/srtp/crypto/include,../../third_party/srtp/include,../.."
+ PreprocessorDefinitions="NDEBUG;WIN32;_LIB;PJ_WIN32=1;PJ_M_I386=1"
+ StringPooling="true"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ PrecompiledHeaderFile=".\output\pjmedia-audiodev-i386-win32-vc8-release/pjmedia_audiodev.pch"
+ AssemblerListingLocation=".\output\pjmedia-audiodev-i386-win32-vc8-release/"
+ ObjectFile=".\output\pjmedia-audiodev-i386-win32-vc8-release/"
+ ProgramDataBaseFileName=".\output\pjmedia-audiodev-i386-win32-vc8-release/"
+ BrowseInformation="1"
+ WarningLevel="4"
+ SuppressStartupBanner="true"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ PreprocessorDefinitions="NDEBUG"
+ Culture="1033"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ OutputFile="../lib/pjmedia-audiodev-i386-win32-vc8-release.lib"
+ SuppressStartupBanner="true"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ SuppressStartupBanner="true"
+ OutputFile=".\output\pjmedia-audiodev-i386-win32-vc8-release/pjmedia_audiodev.bsc"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+ >
+ <File
+ RelativePath="..\src\pjmedia-audiodev\audiodev.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\src\pjmedia-audiodev\audiotest.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\src\pjmedia-audiodev\errno.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\src\pjmedia-audiodev\legacy_dev.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\src\pjmedia-audiodev\pa_dev.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\src\pjmedia-audiodev\symb_aps_dev.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ ExcludedFromBuild="true"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ ExcludedFromBuild="true"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\src\pjmedia-audiodev\symb_mda_dev.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ ExcludedFromBuild="true"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ ExcludedFromBuild="true"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\src\pjmedia-audiodev\wmme_dev.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl"
+ >
+ <File
+ RelativePath="..\include\pjmedia-audiodev\audiodev.h"
+ >
+ </File>
+ <File
+ RelativePath="..\include\pjmedia-audiodev\audiodev_imp.h"
+ >
+ </File>
+ <File
+ RelativePath="..\include\pjmedia-audiodev\audiotest.h"
+ >
+ </File>
+ <File
+ RelativePath="..\include\pjmedia-audiodev\config.h"
+ >
+ </File>
+ <File
+ RelativePath="..\include\pjmedia-audiodev\errno.h"
+ >
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/pjmedia/build/pjmedia_codec.dsp b/pjmedia/build/pjmedia_codec.dsp
index 8df43c26..ee56f483 100644
--- a/pjmedia/build/pjmedia_codec.dsp
+++ b/pjmedia/build/pjmedia_codec.dsp
@@ -147,6 +147,10 @@ SOURCE="..\src\pjmedia-codec\l16.c"
# End Source File
# Begin Source File
+SOURCE="..\src\pjmedia-codec\passthrough.c"
+# End Source File
+# Begin Source File
+
SOURCE="..\src\pjmedia-codec\speex_codec.c"
!IF "$(CFG)" == "pjmedia_codec - Win32 Release"
@@ -192,6 +196,10 @@ SOURCE="..\include\pjmedia-codec\l16.h"
# End Source File
# Begin Source File
+SOURCE="..\include\pjmedia-codec\passthrough.h"
+# End Source File
+# Begin Source File
+
SOURCE="..\include\pjmedia-codec.h"
# End Source File
# Begin Source File
diff --git a/pjmedia/build/pjmedia_codec.vcproj b/pjmedia/build/pjmedia_codec.vcproj
index 7c30da63..822fe106 100644
--- a/pjmedia/build/pjmedia_codec.vcproj
+++ b/pjmedia/build/pjmedia_codec.vcproj
@@ -398,6 +398,10 @@
</FileConfiguration>
</File>
<File
+ RelativePath="..\src\pjmedia-codec\passthrough.c"
+ >
+ </File>
+ <File
RelativePath="..\src\pjmedia-codec\speex_codec.c"
>
<FileConfiguration
diff --git a/pjmedia/include/pjmedia-audiodev/audiodev.h b/pjmedia/include/pjmedia-audiodev/audiodev.h
new file mode 100644
index 00000000..8f2ab783
--- /dev/null
+++ b/pjmedia/include/pjmedia-audiodev/audiodev.h
@@ -0,0 +1,667 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __PJMEDIA_AUDIODEV_AUDIODEV_H__
+#define __PJMEDIA_AUDIODEV_AUDIODEV_H__
+
+/**
+ * @file audiodev.h
+ * @brief Audio device API.
+ */
+#include <pjmedia-audiodev/config.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pjmedia/types.h>
+#include <pj/pool.h>
+
+
+PJ_BEGIN_DECL
+
+/**
+ * @defgroup s2_audio_device_reference Audio Device API Reference
+ * @ingroup audio_device_api
+ * @brief API Reference
+ * @{
+ */
+
+/**
+ * Type for device index.
+ */
+typedef pj_int32_t pjmedia_aud_dev_index;
+
+/**
+ * Device index constants.
+ */
+enum
+{
+ /**
+ * Constant to denote default capture device
+ */
+ PJMEDIA_AUD_DEFAULT_CAPTURE_DEV = -1,
+
+ /**
+ * Constant to denote default playback device
+ */
+ PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV = -2,
+
+ /**
+ * Constant to denote invalid device index.
+ */
+ PJMEDIA_AUD_INVALID_DEV = -3
+};
+
+
+/**
+ * This enumeration identifies various audio device capabilities. These audio
+ * capabilities indicates what features are supported by the underlying
+ * audio device implementation.
+ *
+ * Applications get these capabilities in the #pjmedia_aud_dev_info structure.
+ *
+ * Application can also set the specific features/capabilities when opening
+ * the audio stream by setting the \a flags member of #pjmedia_aud_param
+ * structure.
+ *
+ * Once audio stream is running, application can also retrieve or set some
+ * specific audio capability, by using #pjmedia_aud_stream_get_cap() and
+ * #pjmedia_aud_stream_set_cap() and specifying the desired capability. The
+ * value of the capability is specified as pointer, and application needs to
+ * supply the pointer with the correct value, according to the documentation
+ * of each of the capability.
+ */
+typedef enum pjmedia_aud_dev_cap
+{
+ /**
+ * Support for audio formats other than PCM. The value of this capability
+ * is represented by #pjmedia_format structure.
+ */
+ PJMEDIA_AUD_DEV_CAP_EXT_FORMAT = 1,
+
+ /**
+ * Support for audio input latency control or query. The value of this
+ * capability is an unsigned integer containing milliseconds value of
+ * the latency.
+ */
+ PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY = 2,
+
+ /**
+ * Support for audio output latency control or query. The value of this
+ * capability is an unsigned integer containing milliseconds value of
+ * the latency.
+ */
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY = 4,
+
+ /**
+ * Support for setting/retrieving the audio input device volume level.
+ * The value of this capability is an unsigned integer representing
+ * the input audio volume setting in percent.
+ */
+ PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING = 8,
+
+ /**
+ * Support for setting/retrieving the audio output device volume level.
+ * The value of this capability is an unsigned integer representing
+ * the output audio volume setting in percent.
+ */
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING = 16,
+
+ /**
+ * Support for monitoring the current audio input signal volume.
+ * The value of this capability is an unsigned integer representing
+ * the audio volume in percent.
+ */
+ PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER = 32,
+
+ /**
+ * Support for monitoring the current audio output signal volume.
+ * The value of this capability is an unsigned integer representing
+ * the audio volume in percent.
+ */
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER = 64,
+
+ /**
+ * Support for audio input routing. The value of this capability is an
+ * integer containing #pjmedia_aud_dev_route enumeration.
+ */
+ PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE = 128,
+
+ /**
+ * Support for audio output routing (e.g. loudspeaker vs earpiece). The
+ * value of this capability is an integer containing #pjmedia_aud_dev_route
+ * enumeration.
+ */
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE = 256,
+
+ /**
+ * The audio device has echo cancellation feature. The value of this
+ * capability is a pj_bool_t containing boolean PJ_TRUE or PJ_FALSE.
+ */
+ PJMEDIA_AUD_DEV_CAP_EC = 512,
+
+ /**
+ * The audio device supports setting echo cancellation fail length. The
+ * value of this capability is an unsigned integer representing the
+ * echo tail in milliseconds.
+ */
+ PJMEDIA_AUD_DEV_CAP_EC_TAIL = 1024,
+
+ /**
+ * The audio device has voice activity detection feature. The value
+ * of this capability is a pj_bool_t containing boolean PJ_TRUE or
+ * PJ_FALSE.
+ */
+ PJMEDIA_AUD_DEV_CAP_VAD = 2048,
+
+ /**
+ * The audio device has comfort noise generation feature. The value
+ * of this capability is a pj_bool_t containing boolean PJ_TRUE or
+ * PJ_FALSE.
+ */
+ PJMEDIA_AUD_DEV_CAP_CNG = 4096,
+
+ /**
+ * The audio device has packet loss concealment feature. The value
+ * of this capability is a pj_bool_t containing boolean PJ_TRUE or
+ * PJ_FALSE.
+ */
+ PJMEDIA_AUD_DEV_CAP_PLC = 8192,
+
+ /**
+ * End of capability
+ */
+ PJMEDIA_AUD_DEV_CAP_MAX = 16384
+
+} pjmedia_aud_dev_cap;
+
+
+/**
+ * This enumeration describes audio routing setting.
+ */
+typedef enum pjmedia_aud_dev_route
+{
+ /** Default route. */
+ PJMEDIA_AUD_DEV_ROUTE_DEFAULT = 0,
+
+ /** Route to loudspeaker */
+ PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER = 1,
+
+ /** Route to earpiece */
+ PJMEDIA_AUD_DEV_ROUTE_EARPIECE = 2
+
+} pjmedia_aud_dev_route;
+
+
+/**
+ * Device information structure returned by #pjmedia_aud_dev_get_info().
+ */
+typedef struct pjmedia_aud_dev_info
+{
+ /**
+ * The device name
+ */
+ char name[64];
+
+ /**
+ * Maximum number of input channels supported by this device. If the
+ * value is zero, the device does not support input operation (i.e.
+ * it is a playback only device).
+ */
+ unsigned input_count;
+
+ /**
+ * Maximum number of output channels supported by this device. If the
+ * value is zero, the device does not support output operation (i.e.
+ * it is an input only device).
+ */
+ unsigned output_count;
+
+ /**
+ * Default sampling rate.
+ */
+ unsigned default_samples_per_sec;
+
+ /**
+ * The underlying driver name
+ */
+ char driver[32];
+
+ /**
+ * Device capabilities, as bitmask combination of #pjmedia_aud_dev_cap.
+ */
+ unsigned caps;
+
+ /**
+ * Supported audio device routes, as bitmask combination of
+ * #pjmedia_aud_dev_route. The value may be zero if the device
+ * does not support audio routing.
+ */
+ unsigned routes;
+
+ /**
+ * Number of audio formats supported by this device. The value may be
+ * zero if the device does not support non-PCM format.
+ */
+ unsigned ext_fmt_cnt;
+
+ /**
+ * Array of supported extended audio formats
+ */
+ pjmedia_format ext_fmt[8];
+
+
+} pjmedia_aud_dev_info;
+
+
+/**
+ * This callback is called by player stream when it needs additional data
+ * to be played by the device. Application must fill in the whole of output
+ * buffer with audio samples.
+ *
+ * The frame argument contains the following values:
+ * - timestamp Playback timestamp, in samples.
+ * - buf Buffer to be filled out by application.
+ * - size The size requested in bytes, which will be equal to
+ * the size of one whole packet.
+ *
+ * @param user_data User data associated with the stream.
+ * @param frame Audio frame, which buffer is to be filled in by
+ * the application.
+ *
+ * @return Returning non-PJ_SUCCESS will cause the audio stream
+ * to stop
+ */
+typedef pj_status_t (*pjmedia_aud_play_cb)(void *user_data,
+ pjmedia_frame *frame);
+
+/**
+ * This callback is called by recorder stream when it has captured the whole
+ * packet worth of audio samples.
+ *
+ * @param user_data User data associated with the stream.
+ * @param frame Captured frame.
+ *
+ * @return Returning non-PJ_SUCCESS will cause the audio stream
+ * to stop
+ */
+typedef pj_status_t (*pjmedia_aud_rec_cb)(void *user_data,
+ pjmedia_frame *frame);
+
+/**
+ * This structure specifies the parameters to open the audio stream.
+ */
+typedef struct pjmedia_aud_param
+{
+ /**
+ * The audio direction. This setting is mandatory.
+ */
+ pjmedia_dir dir;
+
+ /**
+ * The audio recorder device ID. This setting is mandatory if the audio
+ * direction includes input/capture direction.
+ */
+ pjmedia_aud_dev_index rec_id;
+
+ /**
+ * The audio playback device ID. This setting is mandatory if the audio
+ * direction includes output/playback direction.
+ */
+ pjmedia_aud_dev_index play_id;
+
+ /**
+ * Clock rate/sampling rate. This setting is mandatory.
+ */
+ unsigned clock_rate;
+
+ /**
+ * Number of channels. This setting is mandatory.
+ */
+ unsigned channel_count;
+
+ /**
+ * Number of samples per frame. This setting is mandatory.
+ */
+ unsigned samples_per_frame;
+
+ /**
+ * Number of bits per sample. This setting is mandatory.
+ */
+ unsigned bits_per_sample;
+
+ /**
+ * This flags specifies which of the optional settings are valid in this
+ * structure. The flags is bitmask combination of pjmedia_aud_dev_cap.
+ */
+ unsigned flags;
+
+ /**
+ * Set the audio format. This setting is optional, and will only be used
+ * if PJMEDIA_AUD_DEV_CAP_EXT_FORMAT is set in the flags.
+ */
+ pjmedia_format ext_fmt;
+
+ /**
+ * Input latency, in milliseconds. This setting is optional, and will
+ * only be used if PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY is set in the flags.
+ */
+ unsigned input_latency_ms;
+
+ /**
+ * Input latency, in milliseconds. This setting is optional, and will
+ * only be used if PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY is set in the flags.
+ */
+ unsigned output_latency_ms;
+
+ /**
+ * Input volume setting, in percent. This setting is optional, and will
+ * only be used if PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING is set in
+ * the flags.
+ */
+ unsigned input_vol;
+
+ /**
+ * Output volume setting, in percent. This setting is optional, and will
+ * only be used if PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING is set in
+ * the flags.
+ */
+ unsigned output_vol;
+
+ /**
+ * Set the audio input route. This setting is optional, and will only be
+ * used if PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE is set in the flags.
+ */
+ pjmedia_aud_dev_route input_route;
+
+ /**
+ * Set the audio output route. This setting is optional, and will only be
+ * used if PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE is set in the flags.
+ */
+ pjmedia_aud_dev_route output_route;
+
+ /**
+ * Enable/disable echo canceller, if the device supports it. This setting
+ * is optional, and will only be used if PJMEDIA_AUD_DEV_CAP_EC is set in
+ * the flags.
+ */
+ pj_bool_t ec_enabled;
+
+ /**
+ * Set echo canceller tail length in milliseconds, if the device supports
+ * it. This setting is optional, and will only be used if
+ * PJMEDIA_AUD_DEV_CAP_EC_TAIL is set in the flags.
+ */
+ unsigned ec_tail_ms;
+
+ /**
+ * Enable/disable PLC. This setting is optional, and will only be used
+ * if PJMEDIA_AUD_DEV_CAP_PLC is set in the flags.
+ */
+ pj_bool_t plc_enabled;
+
+ /**
+ * Enable/disable CNG. This setting is optional, and will only be used
+ * if PJMEDIA_AUD_DEV_CAP_CNG is set in the flags.
+ */
+ pj_bool_t cng_enabled;
+
+} pjmedia_aud_param;
+
+
+/** Forward declaration for pjmedia_aud_stream */
+typedef struct pjmedia_aud_stream pjmedia_aud_stream;
+
+/** Forward declaration for audio device factory */
+typedef struct pjmedia_aud_dev_factory pjmedia_aud_dev_factory;
+
+/**
+ * Get string info for the specified capability.
+ *
+ * @param cap The capability ID.
+ * @param p_desc Optional pointer which will be filled with longer
+ * description about the capability.
+ *
+ * @return Capability name.
+ */
+PJ_DECL(const char*) pjmedia_aud_dev_cap_name(pjmedia_aud_dev_cap cap,
+ const char **p_desc);
+
+
+/**
+ * Set a capability field value in #pjmedia_aud_param structure. This will
+ * also set the flags field for the specified capability in the structure.
+ *
+ * @param param The structure.
+ * @param cap The audio capability which value is to be set.
+ * @param value Pointer to value. Please see the type of value to
+ * be supplied in the pjmedia_aud_dev_cap documentation.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_param_set_cap(pjmedia_aud_param *param,
+ pjmedia_aud_dev_cap cap,
+ const void *pval);
+
+
+/**
+ * Get a capability field value from #pjmedia_aud_param structure. This
+ * function will return PJMEDIA_EAUD_INVCAP error if the flag for that
+ * capability is not set in the flags field in the structure.
+ *
+ * @param param The structure.
+ * @param cap The audio capability which value is to be retrieved.
+ * @param value Pointer to value. Please see the type of value to
+ * be supplied in the pjmedia_aud_dev_cap documentation.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_param_get_cap(const pjmedia_aud_param *param,
+ pjmedia_aud_dev_cap cap,
+ void *pval);
+
+/**
+ * Initialize the audio subsystem. This will register all supported audio
+ * device factories to the audio subsystem. This function may be called
+ * more than once, but each call to this function must have the
+ * corresponding #pjmedia_aud_subsys_shutdown() call.
+ *
+ * @param pf The pool factory.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_subsys_init(pj_pool_factory *pf);
+
+
+/**
+ * Get the pool factory registered to the audio subsystem.
+ *
+ * @return The pool factory.
+ */
+PJ_DECL(pj_pool_factory*) pjmedia_aud_subsys_get_pool_factory(void);
+
+
+/**
+ * Shutdown the audio subsystem. This will destroy all audio device factories
+ * registered in the audio subsystem. Note that currently opened audio streams
+ * may or may not be closed, depending on the implementation of the audio
+ * device factories.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_subsys_shutdown(void);
+
+
+/**
+ * Get the number of sound devices installed in the system.
+ *
+ * @return The number of sound devices installed in the system.
+ */
+PJ_DECL(unsigned) pjmedia_aud_dev_count(void);
+
+
+/**
+ * Get device information.
+ *
+ * @param id The audio device ID.
+ * @param info The device information which will be filled in by this
+ * function once it returns successfully.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_dev_get_info(pjmedia_aud_dev_index id,
+ pjmedia_aud_dev_info *info);
+
+
+/**
+ * Lookup device index based on the driver and device name.
+ *
+ * @param drv_name The driver name.
+ * @param dev_name The device name.
+ * @param id Pointer to store the returned device ID.
+ *
+ * @return PJ_SUCCESS if the device can be found.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_dev_lookup(const char *drv_name,
+ const char *dev_name,
+ pjmedia_aud_dev_index *id);
+
+
+/**
+ * Initialize the audio device parameters with default values for the
+ * specified device.
+ *
+ * @param id The audio device ID.
+ * @param param The audio device parameters which will be initialized
+ * by this function once it returns successfully.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_dev_default_param(pjmedia_aud_dev_index id,
+ pjmedia_aud_param *param);
+
+
+/**
+ * Open audio stream object using the specified parameters.
+ *
+ * @param param Sound device parameters to be used for the stream.
+ * @param rec_cb Callback to be called on every input frame captured.
+ * @param play_cb Callback to be called everytime the sound device needs
+ * audio frames to be played back.
+ * @param user_data Arbitrary user data, which will be given back in the
+ * callbacks.
+ * @param p_strm Pointer to receive the audio stream.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_stream_create(const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_strm);
+
+/**
+ * Get the running parameters for the specified audio stream.
+ *
+ * @param strm The audio stream.
+ * @param param Audio stream parameters to be filled in by this
+ * function once it returns successfully.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+
+/**
+ * Get the value of a specific capability of the audio stream.
+ *
+ * @param strm The audio stream.
+ * @param cap The audio capability which value is to be retrieved.
+ * @param value Pointer to value to be filled in by this function
+ * once it returns successfully. Please see the type
+ * of value to be supplied in the pjmedia_aud_dev_cap
+ * documentation.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+
+/**
+ * Set the value of a specific capability of the audio stream.
+ *
+ * @param strm The audio stream.
+ * @param cap The audio capability which value is to be set.
+ * @param value Pointer to value. Please see the type of value to
+ * be supplied in the pjmedia_aud_dev_cap documentation.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+
+/**
+ * Start the stream.
+ *
+ * @param strm The audio stream.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_stream_start(pjmedia_aud_stream *strm);
+
+/**
+ * Stop the stream.
+ *
+ * @param strm The audio stream.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_stream_stop(pjmedia_aud_stream *strm);
+
+/**
+ * Destroy the stream.
+ *
+ * @param strm The audio stream.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_stream_destroy(pjmedia_aud_stream *strm);
+
+
+/**
+ * @}
+ */
+
+PJ_END_DECL
+
+
+#endif /* __PJMEDIA_AUDIODEV_AUDIODEV_H__ */
+
diff --git a/pjmedia/include/pjmedia-audiodev/audiodev_imp.h b/pjmedia/include/pjmedia-audiodev/audiodev_imp.h
new file mode 100644
index 00000000..e12c0f21
--- /dev/null
+++ b/pjmedia/include/pjmedia-audiodev/audiodev_imp.h
@@ -0,0 +1,181 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __AUDIODEV_IMP_H__
+#define __AUDIODEV_IMP_H__
+
+#include <pjmedia-audiodev/audiodev.h>
+
+/**
+ * @defgroup s8_audio_device_implementors_api Audio Device Implementors API
+ * @ingroup audio_device_api
+ * @brief API for audio device implementors
+ * @{
+ */
+
+/**
+ * Sound device factory operations.
+ */
+typedef struct pjmedia_aud_dev_factory_op
+{
+ /**
+ * Initialize the audio device factory.
+ *
+ * @param f The audio device factory.
+ */
+ pj_status_t (*init)(pjmedia_aud_dev_factory *f);
+
+ /**
+ * Close this audio device factory and release all resources back to the
+ * operating system.
+ *
+ * @param f The audio device factory.
+ */
+ pj_status_t (*destroy)(pjmedia_aud_dev_factory *f);
+
+ /**
+ * Get the number of audio devices installed in the system.
+ *
+ * @param f The audio device factory.
+ */
+ unsigned (*get_dev_count)(pjmedia_aud_dev_factory *f);
+
+ /**
+ * Get the audio device information and capabilities.
+ *
+ * @param f The audio device factory.
+ * @param index Device index.
+ * @param info The audio device information structure which will be
+ * initialized by this function once it returns
+ * successfully.
+ */
+ pj_status_t (*get_dev_info)(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+
+ /**
+ * Initialize the specified audio device parameter with the default
+ * values for the specified device.
+ *
+ * @param f The audio device factory.
+ * @param index Device index.
+ * @param param The audio device parameter.
+ */
+ pj_status_t (*default_param)(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+
+ /**
+ * Open the audio device and create audio stream. See
+ * #pjmedia_aud_stream_create()
+ */
+ pj_status_t (*create_stream)(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm);
+
+} pjmedia_aud_dev_factory_op;
+
+
+/**
+ * This structure describes an audio device factory.
+ */
+struct pjmedia_aud_dev_factory
+{
+ /** Internal data to be initialized by audio subsystem. */
+ struct {
+ /** Driver index */
+ unsigned drv_idx;
+ } sys;
+
+ /** Operations */
+ pjmedia_aud_dev_factory_op *op;
+};
+
+
+/**
+ * Sound stream operations.
+ */
+typedef struct pjmedia_aud_stream_op
+{
+ /**
+ * See #pjmedia_aud_stream_get_param()
+ */
+ pj_status_t (*get_param)(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+
+ /**
+ * See #pjmedia_aud_stream_get_cap()
+ */
+ pj_status_t (*get_cap)(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+
+ /**
+ * See #pjmedia_aud_stream_set_cap()
+ */
+ pj_status_t (*set_cap)(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+
+ /**
+ * See #pjmedia_aud_stream_start()
+ */
+ pj_status_t (*start)(pjmedia_aud_stream *strm);
+
+ /**
+ * See #pjmedia_aud_stream_stop().
+ */
+ pj_status_t (*stop)(pjmedia_aud_stream *strm);
+
+ /**
+ * See #pjmedia_aud_stream_destroy().
+ */
+ pj_status_t (*destroy)(pjmedia_aud_stream *strm);
+
+} pjmedia_aud_stream_op;
+
+
+/**
+ * This structure describes the audio device stream.
+ */
+struct pjmedia_aud_stream
+{
+ /** Internal data to be initialized by audio subsystem */
+ struct {
+ /** Driver index */
+ unsigned drv_idx;
+ } sys;
+
+ /** Operations */
+ pjmedia_aud_stream_op *op;
+};
+
+
+
+
+/**
+ * @}
+ */
+
+
+
+#endif /* __AUDIODEV_IMP_H__ */
diff --git a/pjmedia/include/pjmedia-audiodev/audiotest.h b/pjmedia/include/pjmedia-audiodev/audiotest.h
new file mode 100644
index 00000000..8da3cc14
--- /dev/null
+++ b/pjmedia/include/pjmedia-audiodev/audiotest.h
@@ -0,0 +1,116 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __PJMEDIA_AUDIODEV_AUDIOTEST_H__
+#define __PJMEDIA_AUDIODEV_AUDIOTEST_H__
+
+/**
+ * @file audiotest.h
+ * @brief Audio test utility.
+ */
+#include <pjmedia-audiodev/audiodev.h>
+
+
+PJ_BEGIN_DECL
+
+/**
+ * @defgroup s30_audio_test_utility Audio tests utility.
+ * @ingroup audio_device_api
+ * @brief Audio test utility.
+ * @{
+ */
+
+/**
+ * Statistic for each direction.
+ */
+typedef struct pjmedia_aud_test_stat
+{
+ /**
+ * Number of frames processed during the test.
+ */
+ unsigned frame_cnt;
+
+ /**
+ * Minimum inter-frame arrival time, in milliseconds
+ */
+ unsigned min_interval;
+
+ /**
+ * Maximum inter-frame arrival time, in milliseconds
+ */
+ unsigned max_interval;
+
+ /**
+ * Average inter-frame arrival time, in milliseconds
+ */
+ unsigned avg_interval;
+
+ /**
+ * Standard deviation of inter-frame arrival time, in milliseconds
+ */
+ unsigned dev_interval;
+
+ /**
+ * Maximum number of frame burst
+ */
+ unsigned max_burst;
+
+} pjmedia_aud_test_stat;
+
+
+/**
+ * Test results.
+ */
+typedef struct pjmedia_aud_test_results
+{
+ /**
+ * Recording statistic.
+ */
+ pjmedia_aud_test_stat rec;
+
+ /**
+ * Playback statistic.
+ */
+ pjmedia_aud_test_stat play;
+
+ /**
+ * Clock drifts per second, in samples. Positive number indicates rec
+ * device is running faster than playback device.
+ */
+ pj_int32_t rec_drift_per_sec;
+
+} pjmedia_aud_test_results;
+
+
+/**
+ * Perform audio device testing.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_test(const pjmedia_aud_param *param,
+ pjmedia_aud_test_results *result);
+
+/**
+ * @}
+ */
+
+PJ_END_DECL
+
+
+#endif /* __PJMEDIA_AUDIODEV_AUDIOTEST_H__ */
+
+
diff --git a/pjmedia/include/pjmedia-audiodev/config.h b/pjmedia/include/pjmedia-audiodev/config.h
new file mode 100644
index 00000000..b4603c15
--- /dev/null
+++ b/pjmedia/include/pjmedia-audiodev/config.h
@@ -0,0 +1,368 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __PJMEDIA_AUDIODEV_CONFIG_H__
+#define __PJMEDIA_AUDIODEV_CONFIG_H__
+
+/**
+ * @file audiodev.h
+ * @brief Audio device API.
+ */
+#include <pjmedia/types.h>
+#include <pj/pool.h>
+
+
+PJ_BEGIN_DECL
+
+/**
+ * @defgroup audio_device_api Audio Device API
+ * @brief PJMEDIA audio device abstraction API.
+ */
+
+/**
+ * @defgroup s1_audio_device_config Compile time configurations
+ * @ingroup audio_device_api
+ * @brief Compile time configurations
+ * @{
+ */
+
+/**
+ * This setting controls whether PortAudio support should be included.
+ */
+#ifndef PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+# define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 1
+#endif
+
+
+/**
+ * This setting controls whether WMME support should be included.
+ */
+#ifndef PJMEDIA_AUDIO_DEV_HAS_WMME
+# define PJMEDIA_AUDIO_DEV_HAS_WMME 1
+#endif
+
+
+/**
+ * This setting controls whether Symbian APS support should be included.
+ */
+#ifndef PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
+# define PJMEDIA_AUDIO_DEV_HAS_SYMB_APS 0
+#endif
+
+
+/**
+ * This setting controls whether Symbian audio (using built-in multimedia
+ * framework) support should be included.
+ */
+#ifndef PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA
+# define PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA PJ_SYMBIAN
+#endif
+
+
+/**
+ * This setting controls whether the Audio Device API should support
+ * device implementation that is based on the old sound device API
+ * (sound.h).
+ *
+ * Enable this API if:
+ * - you have implemented your own sound device using the old sound
+ * device API (sound.h), and
+ * - you wish to be able to use your sound device implementation
+ * using the new Audio Device API.
+ *
+ * Please see http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more
+ * info.
+ */
+#ifndef PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE
+# define PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE 0
+#endif
+
+
+/**
+ * @}
+ */
+
+PJ_END_DECL
+
+
+#endif /* __PJMEDIA_AUDIODEV_CONFIG_H__ */
+
+/*
+ --------------------- DOCUMENTATION FOLLOWS ---------------------------
+ */
+
+/**
+ * @addtogroup audio_device_api Audio Device API
+ * @{
+
+PJMEDIA Audio Device API is a cross-platform audio API appropriate for use with
+VoIP applications and many other types of audio streaming applications.
+
+The API abstracts many different audio API's on various platforms, such as:
+ - PortAudio back-end for Win32, Windows Mobile, Linux, Unix, dan MacOS X.
+ - native WMME audio for Win32 and Windows Mobile devices
+ - native Symbian audio streaming/multimedia framework (MMF) implementation
+ - native Nokia Audio Proxy Server (APS) implementation
+ - null-audio implementation
+ - and more to be implemented in the future
+
+The Audio Device API/library is an evolution from PJMEDIA @ref PJMED_SND and
+contains many enhancements:
+
+ - Forward compatibility:
+\n
+ The new API has been designed to be extensible, it will support new API's as
+ well as new features that may be introduced in the future without breaking
+ compatibility with applications that use this API as well as compatibility
+ with existing device implementations.
+
+ - Device capabilities:
+\n
+ At the heart of the API is device capabilities management, where all possible
+ audio capabilities of audio devices should be able to be handled in a generic
+ manner. With this framework, new capabilities that may be discovered in the
+ future can be handled in manner without breaking existing applications.
+
+ - Built-in features:
+\n
+ The device capabilities framework enables applications to use and control
+ audio features built-in in the device, such as:
+ - echo cancellation,
+ - built-in codecs,
+ - audio routing (e.g. to earpiece or loudspeaker),
+ - volume control,
+ - etc.
+
+ - Codec support:
+\n
+ Some audio devices such as Nokia/Symbian Audio Proxy Server (APS) and Nokia
+ VoIP Audio Services (VAS) support built-in hardware audio codecs (e.g. G.729,
+ iLBC, and AMR), and application can use the sound device in encoded mode to
+ make use of these hardware codecs.
+
+ - Multiple backends:
+\n
+ The new API supports multiple audio backends (called factories or drivers in
+ the code) to be active simultaneously, and audio backends may be added or
+ removed during run-time.
+
+
+@section using Overview on using the API
+
+@subsection getting_started Getting started
+
+ -# <b>Configure the application's project settings</b>.\n
+ Add the following
+ include:
+ \code
+ #include <pjmedia_audiodev.h>\endcode\n
+ And add <b>pjmedia-audiodev</b> library to your application link
+ specifications.\n
+ -# <b>Compile time settings</b>.\n
+ Use the compile time settings to enable or
+ disable specific audio drivers. For more information, please see
+ \ref s1_audio_device_config.
+ -# <b>API initialization and cleaning up</b>.\n
+ Before anything else, application must initialize the API by calling:
+ \code
+ pjmedia_aud_subsys_init(pf);\endcode\n
+ And add this in the application cleanup sequence
+ \code
+ pjmedia_aud_subsys_shutdown();\endcode
+
+@subsection devices Working with devices
+
+ -# The following code prints the list of audio devices detected
+ in the system.
+ \code
+ int dev_count;
+ pjmedia_aud_dev_index dev_idx;
+ pj_status_t status;
+
+ dev_count = pjmedia_aud_dev_count();
+ printf("Got %d audio devices\n", dev_count);
+
+ for (dev_idx=0; dev_idx<dev_count; ++i) {
+ pjmedia_aud_dev_info info;
+
+ status = pjmedia_aud_dev_get_info(dev_idx, &info);
+ printf("%d. %s (in=%d, out=%d)\n",
+ dev_idx, info.name,
+ info.input_count, info.output_count);
+ }
+ \endcode\n
+ -# Info: The #PJMEDIA_AUD_DEFAULT_CAPTURE_DEV and #PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV
+ constants are used to denote default capture and playback devices
+ respectively.
+ -# Info: You may save the device and driver's name in your application
+ setting, for example to specify the prefered devices to be
+ used by your application. You can then retrieve the device index
+ for the device by calling:
+ \code
+ const char *drv_name = "WMME";
+ const char *dev_name = "Wave mapper";
+ pjmedia_aud_dev_index dev_idx;
+
+ status = pjmedia_aud_dev_lookup(drv_name, dev_name, &dev_idx);
+ if (status==PJ_SUCCESS)
+ printf("Device index is %d\n", dev_idx);
+ \endcode
+
+@subsection caps Device capabilities
+
+Capabilities are encoded as #pjmedia_aud_dev_cap enumeration. Please see
+#pjmedia_aud_dev_cap enumeration for more information.
+
+ -# The following snippet prints the capabilities supported by the device:
+ \code
+ pjmedia_aud_dev_info info;
+ pj_status_t status;
+
+ status = pjmedia_aud_dev_get_info(PJMEDIA_AUD_DEFAULT_CAPTURE_DEV, &info);
+ if (status == PJ_SUCCESS) {
+ unsigned i;
+ // Enumerate capability bits
+ printf("Device capabilities: ");
+ for (i=0; i<32; ++i) {
+ if (info.caps & (1 << i))
+ printf("%s ", pjmedia_aud_dev_cap_name(1 << i, NULL));
+ }
+ }
+ \endcode\n
+ -# Info: You can set the device settings when opening audio stream by setting
+ the flags and the appropriate setting in #pjmedia_aud_param when calling
+ #pjmedia_aud_stream_create()\n
+ -# Info: Once the audio stream is running, you can retrieve or change the stream
+ setting by specifying the capability in #pjmedia_aud_stream_get_cap()
+ and #pjmedia_aud_stream_set_cap() respectively.
+
+
+@subsection creating_stream Creating audio streams
+
+The audio stream enables audio streaming to capture device, playback device,
+or both.
+
+ -# It is recommended to initialize the #pjmedia_aud_param with its default
+ values before using it:
+ \code
+ pjmedia_aud_param param;
+ pjmedia_aud_dev_index dev_idx;
+ pj_status_t status;
+
+ dev_idx = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV;
+ status = pjmedia_aud_dev_default_param(dev_idx, &param);
+ \endcode\n
+ -# Configure the mandatory parameters:
+ \code
+ param.dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param.rec_id = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV;
+ param.play_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
+ param.clock_rate = 8000;
+ param.channel_count = 1;
+ param.samples_per_frame = 160;
+ param.bits_per_sample = 16;
+ \endcode\n
+ -# If you want the audio stream to use the device's built-in codec, specify
+ the codec in the #pjmedia_aud_param. You must make sure that the codec
+ is supported by the device, by looking at its supported format list in
+ the #pjmedia_aud_dev_info.\n
+ The snippet below sets the audio stream to use G.711 ULAW encoding:
+ \code
+ unsigned i;
+
+ // Make sure Ulaw is supported
+ if ((info.caps & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) == 0)
+ error("Device does not support extended formats");
+ for (i = 0; i < info.ext_fmt_cnt; ++i) {
+ if (info.ext_fmt[i].id == PJMEDIA_FORMAT_ULAW)
+ break;
+ }
+ if (i == info.ext_fmt_cnt)
+ error("Device does not support Ulaw format");
+
+ // Set Ulaw format
+ param.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
+ param.ext_fmt.id = PJMEDIA_FORMAT_ULAW;
+ param.ext_fmt.bitrate = 64000;
+ param.ext_fmt.vad = PJ_FALSE;
+ \endcode\n
+ -# Note that if non-PCM format is configured on the audio stream, the
+ capture and/or playback functions (#pjmedia_aud_rec_cb and
+ #pjmedia_aud_play_cb respectively) will report the audio frame as
+ #pjmedia_frame_ext structure instead of the #pjmedia_frame.
+ -# Optionally configure other device's capabilities. The following snippet
+ shows how to enable echo cancellation on the device (note that this
+ snippet may not be necessary since the setting may have been enabled
+ when calling #pjmedia_aud_dev_default_param() above):
+ \code
+ if (info.caps & PJMEDIA_AUD_DEV_CAP_EC) {
+ param.flags |= PJMEDIA_AUD_DEV_CAP_EC;
+ param.ec_enabled = PJ_TRUE;
+ }
+ \endcode
+ -# Open the audio stream, specifying the capture and/or playback callback
+ functions:
+ \code
+ pjmedia_aud_stream *stream;
+
+ status = pjmedia_aud_stream_create(&param, &rec_cb, &play_cb,
+ user_data, &stream);
+ \endcode
+
+@subsection working_with_stream Working with audio streams
+
+ -# To start the audio stream:
+ \code
+ status = pjmedia_aud_stream_start(stream);
+ \endcode\n
+ To stop the stream:
+ \code
+ status = pjmedia_aud_stream_stop(stream);
+ \endcode\n
+ And to destroy the stream:
+ \code
+ status = pjmedia_aud_stream_destroy(stream);
+ \endcode\n
+ -# Info: The following shows how to retrieve the capability value of the
+ stream (in this case, the current output volume setting).
+ \code
+ // Volume setting is an unsigned integer showing the level in percent.
+ unsigned vol;
+ status = pjmedia_aud_stream_get_cap(stream,
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &vol);
+ \endcode
+ -# Info: And following shows how to modify the capability value of the
+ stream (in this case, the current output volume setting).
+ \code
+ // Volume setting is an unsigned integer showing the level in percent.
+ unsigned vol = 50;
+ status = pjmedia_aud_stream_set_cap(stream,
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &vol);
+ \endcode
+
+
+*/
+
+
+/**
+ * @}
+ */
+
diff --git a/pjmedia/include/pjmedia-audiodev/errno.h b/pjmedia/include/pjmedia-audiodev/errno.h
new file mode 100644
index 00000000..12830354
--- /dev/null
+++ b/pjmedia/include/pjmedia-audiodev/errno.h
@@ -0,0 +1,198 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __PJMEDIA_AUDIODEV_AUDIODEV_ERRNO_H__
+#define __PJMEDIA_AUDIODEV_AUDIODEV_ERRNO_H__
+
+/**
+ * @file errno.h Error Codes
+ * @brief Audiodev specific error codes.
+ */
+
+#include <pjmedia-audiodev/config.h>
+#include <pj/errno.h>
+
+/**
+ * @defgroup error_codes Error Codes
+ * @ingroup audio_device_api
+ * @brief Audio devive library specific error codes.
+ * @{
+ */
+
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Start of error code relative to PJ_ERRNO_START_USER.
+ * This value is 420000.
+ */
+#define PJMEDIA_AUDIODEV_ERRNO_START \
+ (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*5)
+#define PJMEDIA_AUDIODEV_ERRNO_END \
+ (PJMEDIA_AUDIODEV_ERRNO_START + PJ_ERRNO_SPACE_SIZE - 1)
+
+
+/**
+ * Mapping from PortAudio error codes to pjmedia error space.
+ */
+#define PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START \
+ (PJMEDIA_AUDIODEV_ERRNO_END-10000)
+#define PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_END \
+ (PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START + 10000 -1)
+/**
+ * Convert PortAudio error code to PJLIB error code.
+ * PortAudio error code range: 0 >= err >= -10000
+ */
+#define PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) \
+ ((int)PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START-err)
+
+/**
+ * Mapping from Windows multimedia WaveIn error codes.
+ */
+#define PJMEDIA_AUDIODEV_WMME_IN_ERROR_START \
+ (PJMEDIA_AUDIODEV_ERRNO_START + 30000)
+#define PJMEDIA_AUDIODEV_WMME_IN_ERROR_END \
+ (PJMEDIA_AUDIODEV_WMME_IN_ERROR_START + 1000 - 1)
+/**
+ * Convert WaveIn operation error codes to PJLIB error space.
+ */
+#define PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(err) \
+ ((int)PJMEDIA_AUDIODEV_WMME_IN_ERROR_START+err)
+
+
+/**
+ * Mapping from Windows multimedia WaveOut error codes.
+ */
+#define PJMEDIA_AUDIODEV_WMME_OUT_ERROR_START \
+ (PJMEDIA_AUDIODEV_WMME_IN_ERROR_END + 1000)
+#define PJMEDIA_AUDIODEV_WMME_OUT_ERROR_END \
+ (PJMEDIA_AUDIODEV_WMME_OUT_ERROR_START + 1000)
+/**
+ * Convert WaveOut operation error codes to PJLIB error space.
+ */
+#define PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(err) \
+ ((int)PJMEDIA_AUDIODEV_WMME_OUT_ERROR_START+err)
+
+
+/************************************************************
+ * Audio Device API error codes
+ ***********************************************************/
+/**
+ * @hideinitializer
+ * General/unknown error.
+ */
+#define PJMEDIA_EAUD_ERR (PJMEDIA_AUDIODEV_ERRNO_START+1) /* 420001 */
+
+/**
+ * @hideinitializer
+ * Unknown error from audio driver
+ */
+#define PJMEDIA_EAUD_SYSERR (PJMEDIA_AUDIODEV_ERRNO_START+2) /* 420002 */
+
+/**
+ * @hideinitializer
+ * Audio subsystem not initialized
+ */
+#define PJMEDIA_EAUD_INIT (PJMEDIA_AUDIODEV_ERRNO_START+3) /* 420003 */
+
+/**
+ * @hideinitializer
+ * Invalid audio device
+ */
+#define PJMEDIA_EAUD_INVDEV (PJMEDIA_AUDIODEV_ERRNO_START+4) /* 420004 */
+
+/**
+ * @hideinitializer
+ * Found no devices
+ */
+#define PJMEDIA_EAUD_NODEV (PJMEDIA_AUDIODEV_ERRNO_START+5) /* 420005 */
+
+/**
+ * @hideinitializer
+ * Unable to find default device
+ */
+#define PJMEDIA_EAUD_NODEFDEV (PJMEDIA_AUDIODEV_ERRNO_START+6) /* 420006 */
+
+/**
+ * @hideinitializer
+ * Device not ready
+ */
+#define PJMEDIA_EAUD_NOTREADY (PJMEDIA_AUDIODEV_ERRNO_START+7) /* 420007 */
+
+/**
+ * @hideinitializer
+ * The audio capability is invalid or not supported
+ */
+#define PJMEDIA_EAUD_INVCAP (PJMEDIA_AUDIODEV_ERRNO_START+8) /* 420008 */
+
+/**
+ * @hideinitializer
+ * The operation is invalid or not supported
+ */
+#define PJMEDIA_EAUD_INVOP (PJMEDIA_AUDIODEV_ERRNO_START+9) /* 420009 */
+
+/**
+ * @hideinitializer
+ * Bad or invalid audio device format
+ */
+#define PJMEDIA_EAUD_BADFORMAT (PJMEDIA_AUDIODEV_ERRNO_START+10) /* 4200010 */
+
+/**
+ * @hideinitializer
+ * Invalid audio device sample format
+ */
+#define PJMEDIA_EAUD_SAMPFORMAT (PJMEDIA_AUDIODEV_ERRNO_START+11) /* 4200011 */
+
+/**
+ * @hideinitializer
+ * Bad latency setting
+ */
+#define PJMEDIA_EAUD_BADLATENCY (PJMEDIA_AUDIODEV_ERRNO_START+12) /* 4200012 */
+
+
+
+
+
+/**
+ * Get error message for the specified error code. Note that this
+ * function is only able to decode PJMEDIA Audiodev specific error code.
+ * Application should use pj_strerror(), which should be able to
+ * decode all error codes belonging to all subsystems (e.g. pjlib,
+ * pjmedia, pjsip, etc).
+ *
+ * @param status The error code.
+ * @param buffer The buffer where to put the error message.
+ * @param bufsize Size of the buffer.
+ *
+ * @return The error message as NULL terminated string,
+ * wrapped with pj_str_t.
+ */
+PJ_DECL(pj_str_t) pjmedia_audiodev_strerror(pj_status_t status, char *buffer,
+ pj_size_t bufsize);
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+
+#endif /* __PJMEDIA_AUDIODEV_AUDIODEV_ERRNO_H__ */
+
diff --git a/pjmedia/include/pjmedia-codec.h b/pjmedia/include/pjmedia-codec.h
index 3db581a6..8b7c3f64 100644
--- a/pjmedia/include/pjmedia-codec.h
+++ b/pjmedia/include/pjmedia-codec.h
@@ -31,6 +31,7 @@
#include <pjmedia-codec/ilbc.h>
#include <pjmedia-codec/g722.h>
#include <pjmedia-codec/ipp_codecs.h>
+#include <pjmedia-codec/passthrough.h>
#endif /* __PJMEDIA_CODEC_PJMEDIA_CODEC_H__ */
diff --git a/pjmedia/include/pjmedia-codec/amr_helper.h b/pjmedia/include/pjmedia-codec/amr_helper.h
index cf900165..79d324af 100644
--- a/pjmedia/include/pjmedia-codec/amr_helper.h
+++ b/pjmedia/include/pjmedia-codec/amr_helper.h
@@ -632,7 +632,6 @@ typedef struct pjmedia_codec_amr_pack_setting {
*
* @return AMR mode.
*/
-
PJ_INLINE(pj_int8_t) pjmedia_codec_amr_get_mode(unsigned bitrate)
{
pj_int8_t mode = -1;
@@ -678,6 +677,35 @@ PJ_INLINE(pj_int8_t) pjmedia_codec_amr_get_mode(unsigned bitrate)
}
/**
+ * Get AMR mode based on frame length.
+ *
+ * @param amrnb Set to PJ_TRUE for AMR-NB domain or PJ_FALSE for AMR-WB.
+ * @param frame_len The frame length.
+ *
+ * @return AMR mode.
+ */
+
+PJ_INLINE(pj_int8_t) pjmedia_codec_amr_get_mode2(pj_bool_t amrnb,
+ unsigned frame_len)
+{
+ int i;
+
+ if (amrnb) {
+ for (i = 0; i < 9; ++i)
+ if (frame_len == pjmedia_codec_amrnb_framelen[i])
+ return (pj_int8_t)i;
+ } else {
+ for (i = 0; i < 10; ++i) {
+ if (frame_len == pjmedia_codec_amrwb_framelen[i])
+ return (pj_int8_t)i;
+ }
+ }
+
+ pj_assert(!"Invalid AMR frame length");
+ return -1;
+}
+
+/**
* Prepare a frame before pass it to decoder. This function will do:
* - reorder AMR bitstream from descending sensitivity order into
* encoder bits order. This can be enabled/disabled via param
@@ -794,7 +822,6 @@ PJ_INLINE(pj_status_t) pjmedia_codec_amr_predecode(
out_info->mode = FT_;
out->size = 5;
- PJ_ASSERT_RETURN(out->size <= in->size, PJMEDIA_CODEC_EFRMINLEN);
pj_bzero(out->buf, out->size);
for(i = 0; i < framelenbit_tbl[SID_FT]; ++i) {
diff --git a/pjmedia/include/pjmedia-codec/config.h b/pjmedia/include/pjmedia-codec/config.h
index e50185d1..49ac3214 100644
--- a/pjmedia/include/pjmedia-codec/config.h
+++ b/pjmedia/include/pjmedia-codec/config.h
@@ -194,5 +194,35 @@
# define PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1 1
#endif
+/**
+ * Enable Passthrough codecs.
+ *
+ * Default: 0
+ */
+#ifndef PJMEDIA_HAS_PASSTHROUGH_CODECS
+# define PJMEDIA_HAS_PASSTHROUGH_CODECS 0
+#endif
+
+#ifndef PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+# define PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR 1
+#endif
+
+#ifndef PJMEDIA_HAS_PASSTHROUGH_CODEC_G729
+# define PJMEDIA_HAS_PASSTHROUGH_CODEC_G729 1
+#endif
+
+#ifndef PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC
+# define PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC 1
+#endif
+
+#ifndef PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU
+# define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU 1
+# undef PJMEDIA_HAS_G711_CODEC
+#endif
+
+#ifndef PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA
+# define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA 1
+# undef PJMEDIA_HAS_G711_CODEC
+#endif
#endif /* __PJMEDIA_CODEC_CONFIG_H__ */
diff --git a/pjmedia/include/pjmedia-codec/passthrough.h b/pjmedia/include/pjmedia-codec/passthrough.h
new file mode 100644
index 00000000..0705690a
--- /dev/null
+++ b/pjmedia/include/pjmedia-codec/passthrough.h
@@ -0,0 +1,78 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __PJMEDIA_CODECS_PASSTHROUGH_H__
+#define __PJMEDIA_CODECS_PASSTHROUGH_H__
+
+/**
+ * @file pjmedia-codec/passthrough.h
+ * @brief Passthrough codecs.
+ */
+
+#include <pjmedia-codec/types.h>
+
+/**
+ * @defgroup PJMED_PASSTHROUGH_CODEC Passthrough Codecs
+ * @ingroup PJMEDIA_CODEC
+ * @brief Implementation of passthrough codecs
+ * @{
+ *
+ * This section describes functions to register and register passthrough
+ * codecs factory to the codec manager. After the codec factory has been
+ * registered, application can use @ref PJMEDIA_CODEC API to manipulate
+ * the codec. This codec factory contains various codecs, e.g: G.729, iLBC,
+ * AMR, and G.711.
+ *
+ * Passthrough codecs are codecs wrapper that does not perform encoding
+ * or decoding, it just pack and parse encoded audio data from/into RTP
+ * payload. This will accomodate pjmedia ports which work with encoded
+ * audio data, e.g: encoded audio files, sound device with capability
+ * of playing/recording encoded audio data.
+ */
+
+PJ_BEGIN_DECL
+
+/**
+ * Initialize and register passthrough codecs factory to pjmedia endpoint.
+ *
+ * @param endpt The pjmedia endpoint.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_codec_passthrough_init( pjmedia_endpt *endpt );
+
+
+
+/**
+ * Unregister passthrough codecs factory from pjmedia endpoint.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_codec_passthrough_deinit(void);
+
+
+PJ_END_DECL
+
+
+/**
+ * @}
+ */
+
+#endif /* __PJMEDIA_CODECS_PASSTHROUGH_H__ */
+
diff --git a/pjmedia/include/pjmedia/alaw_ulaw.h b/pjmedia/include/pjmedia/alaw_ulaw.h
index 3b97ad3d..a6aae811 100644
--- a/pjmedia/include/pjmedia/alaw_ulaw.h
+++ b/pjmedia/include/pjmedia/alaw_ulaw.h
@@ -144,12 +144,12 @@ PJ_DECL(unsigned char) pjmedia_ulaw2alaw(unsigned char uval);
*
* @param dst Destination buffer for 8-bit U-Law data.
* @param src Source, 16-bit linear PCM data.
- * @param len Number of samples.
+ * @param count Number of samples.
*/
PJ_INLINE(void) pjmedia_ulaw_encode(pj_uint8_t *dst, const pj_int16_t *src,
- pj_size_t len)
+ pj_size_t count)
{
- const pj_int16_t *end = src + len;
+ const pj_int16_t *end = src + count;
while (src < end) {
*dst++ = pjmedia_linear2ulaw(*src++);
@@ -161,12 +161,12 @@ PJ_INLINE(void) pjmedia_ulaw_encode(pj_uint8_t *dst, const pj_int16_t *src,
*
* @param dst Destination buffer for 8-bit A-Law data.
* @param src Source, 16-bit linear PCM data.
- * @param len Number of samples.
+ * @param count Number of samples.
*/
PJ_INLINE(void) pjmedia_alaw_encode(pj_uint8_t *dst, const pj_int16_t *src,
- pj_size_t len)
+ pj_size_t count)
{
- const pj_int16_t *end = src + len;
+ const pj_int16_t *end = src + count;
while (src < end) {
*dst++ = pjmedia_linear2alaw(*src++);
@@ -178,7 +178,7 @@ PJ_INLINE(void) pjmedia_alaw_encode(pj_uint8_t *dst, const pj_int16_t *src,
*
* @param dst Destination buffer for 16-bit PCM data.
* @param src Source, 8-bit U-Law data.
- * @param len Number of samples.
+ * @param len Encoded frame/source length in bytes.
*/
PJ_INLINE(void) pjmedia_ulaw_decode(pj_int16_t *dst, const pj_uint8_t *src,
pj_size_t len)
@@ -195,7 +195,7 @@ PJ_INLINE(void) pjmedia_ulaw_decode(pj_int16_t *dst, const pj_uint8_t *src,
*
* @param dst Destination buffer for 16-bit PCM data.
* @param src Source, 8-bit A-Law data.
- * @param len Number of samples.
+ * @param len Encoded frame/source length in bytes.
*/
PJ_INLINE(void) pjmedia_alaw_decode(pj_int16_t *dst, const pj_uint8_t *src,
pj_size_t len)
diff --git a/pjmedia/include/pjmedia/codec.h b/pjmedia/include/pjmedia/codec.h
index 199e3989..dcaae626 100644
--- a/pjmedia/include/pjmedia/codec.h
+++ b/pjmedia/include/pjmedia/codec.h
@@ -275,6 +275,9 @@ typedef struct pjmedia_codec_param
equal to decoder ptime. */
pj_uint8_t pcm_bits_per_sample; /**< Bits/sample in the PCM side */
pj_uint8_t pt; /**< Payload type. */
+ pjmedia_format_id fmt_id; /**< Source format, it's format of
+ encoder input and decoder
+ output. */
} info;
/**
diff --git a/pjmedia/include/pjmedia/conference.h b/pjmedia/include/pjmedia/conference.h
index bc14c543..584e56ce 100644
--- a/pjmedia/include/pjmedia/conference.h
+++ b/pjmedia/include/pjmedia/conference.h
@@ -43,6 +43,18 @@
PJ_BEGIN_DECL
+/**
+ * The conference bridge signature in pjmedia_port_info.
+ */
+#define PJMEDIA_CONF_BRIDGE_SIGNATURE \
+ PJMEDIA_PORT_SIGNATURE('C', 'O', 'N', 'F')
+
+/**
+ * The audio switchboard signature in pjmedia_port_info.
+ */
+#define PJMEDIA_CONF_SWITCH_SIGNATURE \
+ PJMEDIA_PORT_SIGNATURE('A', 'S', 'W', 'I')
+
/**
* Opaque type for conference bridge.
@@ -56,10 +68,12 @@ typedef struct pjmedia_conf_port_info
{
unsigned slot; /**< Slot number. */
pj_str_t name; /**< Port name. */
+ pjmedia_format format; /**< Format. */
pjmedia_port_op tx_setting; /**< Transmit settings. */
pjmedia_port_op rx_setting; /**< Receive settings. */
unsigned listener_cnt; /**< Number of listeners. */
unsigned *listener_slots; /**< Array of listeners. */
+ unsigned transmitter_cnt; /**< Number of transmitter. */
unsigned clock_rate; /**< Clock rate of the port. */
unsigned channel_count; /**< Number of channels. */
unsigned samples_per_frame; /**< Samples per frame */
diff --git a/pjmedia/include/pjmedia/config.h b/pjmedia/include/pjmedia/config.h
index 735192ef..9a3eea9b 100644
--- a/pjmedia/include/pjmedia/config.h
+++ b/pjmedia/include/pjmedia/config.h
@@ -44,79 +44,69 @@
# include <pjmedia/config_auto.h>
#endif
+/**
+ * Specify whether we prefer to use audio switch board rather than
+ * conference bridge.
+ *
+ * Audio switch board is a kind of simplified version of conference
+ * bridge, but not really the subset of conference bridge. It has
+ * stricter rules on audio routing among the pjmedia ports and has
+ * no audio mixing capability. The power of it is it could work with
+ * encoded audio frames where conference brigde couldn't.
+ *
+ * Default: 0
+ */
+#ifndef PJMEDIA_CONF_USE_SWITCH_BOARD
+# define PJMEDIA_CONF_USE_SWITCH_BOARD 0
+#endif
+
/*
* Types of sound stream backends.
*/
-/** Constant for NULL sound backend. */
-#define PJMEDIA_SOUND_NULL_SOUND 0
-
-/** Constant for PortAudio sound backend. */
-#define PJMEDIA_SOUND_PORTAUDIO_SOUND 1
-
-/** Constant for Win32 DirectSound sound backend. */
-#define PJMEDIA_SOUND_WIN32_DIRECT_SOUND 2
-
-/** Constant for Win32 MME sound backend. */
-#define PJMEDIA_SOUND_WIN32_MME_SOUND 3
-
-/** When this is set, pjmedia will not provide any sound device backend.
- * Application will have to provide its own sound device backend
- * and link the application with it.
+/**
+ * This macro has been deprecated in releasee 1.1. Please see
+ * http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more information.
*/
-#define PJMEDIA_SOUND_EXTERNAL 255
-
+#if defined(PJMEDIA_SOUND_IMPLEMENTATION)
+# error PJMEDIA_SOUND_IMPLEMENTATION has been deprecated
+#endif
/**
- * Unless specified otherwise, sound device uses PortAudio implementation
- * by default.
+ * This macro has been deprecated in releasee 1.1. Please see
+ * http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more information.
*/
-#ifndef PJMEDIA_SOUND_IMPLEMENTATION
-# if defined(PJ_WIN32) && PJ_WIN32!=0
-/*# define PJMEDIA_SOUND_IMPLEMENTATION PJMEDIA_SOUND_WIN32_DIRECT_SOUND*/
-/*# define PJMEDIA_SOUND_IMPLEMENTATION PJMEDIA_SOUND_WIN32_MME_SOUND*/
-# define PJMEDIA_SOUND_IMPLEMENTATION PJMEDIA_SOUND_PORTAUDIO_SOUND
-# else
-# define PJMEDIA_SOUND_IMPLEMENTATION PJMEDIA_SOUND_PORTAUDIO_SOUND
-# endif
+#if defined(PJMEDIA_PREFER_DIRECT_SOUND)
+# error PJMEDIA_PREFER_DIRECT_SOUND has been deprecated
#endif
-
/**
- * Specify whether we prefer to use DirectSound on Windows.
+ * This macro controls whether the legacy sound device API is to be
+ * implemented, for applications that still use the old sound device
+ * API (sound.h). If this macro is set to non-zero, the sound_legacy.c
+ * will be included in the compilation. The sound_legacy.c is an
+ * implementation of old sound device (sound.h) using the new Audio
+ * Device API.
*
- * Default: 0
+ * Please see http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more
+ * info.
*/
-#ifndef PJMEDIA_PREFER_DIRECT_SOUND
-# define PJMEDIA_PREFER_DIRECT_SOUND 0
+#ifndef PJMEDIA_HAS_LEGACY_SOUND_API
+# define PJMEDIA_HAS_LEGACY_SOUND_API 1
#endif
-
/**
- * Specify sound device latency default, in milisecond.
+ * Specify default sound device latency, in milisecond.
*/
#ifndef PJMEDIA_SND_DEFAULT_REC_LATENCY
# define PJMEDIA_SND_DEFAULT_REC_LATENCY 100
#endif
-#ifndef PJMEDIA_SND_DEFAULT_PLAY_LATENCY
-# define PJMEDIA_SND_DEFAULT_PLAY_LATENCY 100
-#endif
-
-
/**
- * Specify whether delay buffer is used for sound device.
- * When delay buffer is enabled, the sound device callback
- * will be called one after another evenly.
- * The delay buffer also performs the best delay calculation
- * for the sound device, and will try to limit the delay caused
- * by uneven callback calls to this delay.
- *
- * When this setting is enabled, the PJMEDIA_SOUND_BUFFER_COUNT
- * macro will specify the maximum size of the delay buffer.
+ * Specify default sound device latency, in milisecond.
*/
-#ifndef PJMEDIA_SOUND_USE_DELAYBUF
-# define PJMEDIA_SOUND_USE_DELAYBUF 0
+#ifndef PJMEDIA_SND_DEFAULT_PLAY_LATENCY
+# define PJMEDIA_SND_DEFAULT_PLAY_LATENCY 100
#endif
@@ -285,7 +275,7 @@
* Default file player/writer buffer size.
*/
#ifndef PJMEDIA_FILE_PORT_BUFSIZE
-# define PJMEDIA_FILE_PORT_BUFSIZE 4000
+# define PJMEDIA_FILE_PORT_BUFSIZE 4000
#endif
diff --git a/pjmedia/include/pjmedia/endpoint.h b/pjmedia/include/pjmedia/endpoint.h
index 0d8dd39d..2cc386f5 100644
--- a/pjmedia/include/pjmedia/endpoint.h
+++ b/pjmedia/include/pjmedia/endpoint.h
@@ -37,7 +37,6 @@
* to create a media session (#pjmedia_session_create()).
*/
-#include <pjmedia/sound.h>
#include <pjmedia/codec.h>
#include <pjmedia/sdp.h>
diff --git a/pjmedia/include/pjmedia/port.h b/pjmedia/include/pjmedia/port.h
index 9d7c86c6..447c429d 100644
--- a/pjmedia/include/pjmedia/port.h
+++ b/pjmedia/include/pjmedia/port.h
@@ -25,6 +25,7 @@
* @brief Port interface declaration
*/
#include <pjmedia/types.h>
+#include <pj/assert.h>
#include <pj/os.h>
@@ -211,6 +212,7 @@ typedef struct pjmedia_port_info
pj_bool_t has_info; /**< Has info? */
pj_bool_t need_info; /**< Need info on connect? */
unsigned pt; /**< Payload type (can be dynamic). */
+ pjmedia_format format; /**< Format. */
pj_str_t encoding_name; /**< Encoding name. */
unsigned clock_rate; /**< Sampling rate. */
unsigned channel_count; /**< Number of channels. */
@@ -220,34 +222,6 @@ typedef struct pjmedia_port_info
} pjmedia_port_info;
-/**
- * Types of media frame.
- */
-typedef enum pjmedia_frame_type
-{
- PJMEDIA_FRAME_TYPE_NONE, /**< No frame. */
- PJMEDIA_FRAME_TYPE_AUDIO /**< Normal audio frame. */
-
-} pjmedia_frame_type;
-
-
-/**
- * This structure describes a media frame.
- */
-typedef struct pjmedia_frame
-{
- pjmedia_frame_type type; /**< Frame type. */
- void *buf; /**< Pointer to buffer. */
- pj_size_t size; /**< Frame size in bytes. */
- pj_timestamp timestamp; /**< Frame timestamp. */
- pj_uint32_t bit_info; /**< Bit info of the frame, sample case:
- a frame may not exactly start and end
- at the octet boundary, so this field
- may be used for specifying start &
- end bit offset. */
-} pjmedia_frame;
-
-
/**
* Port interface.
*/
diff --git a/pjmedia/include/pjmedia/sound.h b/pjmedia/include/pjmedia/sound.h
index 962bfe96..dca9905b 100644
--- a/pjmedia/include/pjmedia/sound.h
+++ b/pjmedia/include/pjmedia/sound.h
@@ -23,10 +23,11 @@
/**
* @file sound.h
- * @brief Sound player and recorder device framework.
+ * @brief Legacy sound device API
*/
+#include <pjmedia-audiodev/audiodev.h>
#include <pjmedia/types.h>
-#include <pj/pool.h>
+
PJ_BEGIN_DECL
@@ -36,6 +37,11 @@ PJ_BEGIN_DECL
* @brief PJMEDIA abstraction for sound device hardware
* @{
*
+ * <strong>Warning: this sound device API has been deprecated
+ * and replaced by PJMEDIA Audio Device API. Please see
+ * http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more
+ * information.</strong>
+ *
* This section describes lower level abstraction for sound device
* hardware. Application normally uses the higher layer @ref
* PJMED_SND_PORT abstraction since it works seamlessly with
@@ -61,7 +67,7 @@ PJ_BEGIN_DECL
* frames from/to the sound device.
*/
-/** Opaque data type for audio stream. */
+/** Opaque declaration for pjmedia_snd_stream. */
typedef struct pjmedia_snd_stream pjmedia_snd_stream;
/**
@@ -92,7 +98,6 @@ typedef struct pjmedia_snd_stream_info
unsigned play_latency; /**< Playback latency, in samples. */
} pjmedia_snd_stream_info;
-
/**
* This callback is called by player stream when it needs additional data
* to be played by the device. Application must fill in the whole of output
diff --git a/pjmedia/include/pjmedia/sound_port.h b/pjmedia/include/pjmedia/sound_port.h
index a41357c8..7293a4d7 100644
--- a/pjmedia/include/pjmedia/sound_port.h
+++ b/pjmedia/include/pjmedia/sound_port.h
@@ -24,7 +24,7 @@
* @file sound_port.h
* @brief Media port connection abstraction to sound device.
*/
-#include <pjmedia/sound.h>
+#include <pjmedia-audiodev/audiodev.h>
#include <pjmedia/port.h>
PJ_BEGIN_DECL
@@ -159,7 +159,22 @@ PJ_DECL(pj_status_t) pjmedia_snd_port_create_player(pj_pool_t *pool,
unsigned bits_per_sample,
unsigned options,
pjmedia_snd_port **p_port);
-
+
+
+/**
+ * Create sound device port according to the specified parameters.
+ *
+ * @param pool Pool to allocate sound port structure.
+ * @param prm Sound device settings.
+ * @param p_port Pointer to receive the sound device port instance.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error
+ * code.
+ */
+PJ_DECL(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool,
+ const pjmedia_aud_param *prm,
+ pjmedia_snd_port **p_port);
+
/**
* Destroy sound device port.
@@ -179,19 +194,23 @@ PJ_DECL(pj_status_t) pjmedia_snd_port_destroy(pjmedia_snd_port *snd_port);
*
* @return The sound stream instance.
*/
-PJ_DECL(pjmedia_snd_stream*) pjmedia_snd_port_get_snd_stream(
+PJ_DECL(pjmedia_aud_stream*) pjmedia_snd_port_get_snd_stream(
pjmedia_snd_port *snd_port);
/**
- * Configure the echo cancellation tail length. By default, echo canceller
- * is enabled in the sound device with the default tail length. After the
- * sound port is created, application can query the current echo canceller
- * tail length by calling #pjmedia_snd_port_get_ec_tail.
+ * Change the echo cancellation settings. The echo cancellation settings
+ * should have been specified when this sound port was created, by setting
+ * the appropriate fields in the pjmedia_aud_param, because not all sound
+ * device implementation supports changing the EC setting once the device
+ * has been opened.
*
- * Note that you should only change the EC settings when the sound port
- * is not connected to any downstream ports, otherwise race condition may
- * occur.
+ * The behavior of this function depends on whether device or software AEC
+ * is being used. If the device supports AEC, this function will forward
+ * the change request to the device and it will be up to the device whether
+ * to support the request. If software AEC is being used (the software EC
+ * will be used if the device does not support AEC), this function will
+ * change the software EC settings.
*
* @param snd_port The sound device port.
* @param pool Pool to re-create the echo canceller if necessary.
@@ -199,6 +218,7 @@ PJ_DECL(pjmedia_snd_stream*) pjmedia_snd_port_get_snd_stream(
* miliseconds. If zero is specified, the EC would
* be disabled.
* @param options The options to be passed to #pjmedia_echo_create().
+ * This is only used if software EC is being used.
*
* @return PJ_SUCCESS on success.
*/
diff --git a/pjmedia/include/pjmedia/stream.h b/pjmedia/include/pjmedia/stream.h
index cf5ef47f..0227f645 100644
--- a/pjmedia/include/pjmedia/stream.h
+++ b/pjmedia/include/pjmedia/stream.h
@@ -26,7 +26,6 @@
* @brief Media Stream.
*/
-#include <pjmedia/sound.h>
#include <pjmedia/codec.h>
#include <pjmedia/endpoint.h>
#include <pjmedia/port.h>
diff --git a/pjmedia/include/pjmedia/symbian_sound_aps.h b/pjmedia/include/pjmedia/symbian_sound_aps.h
index 3208b416..bd9e0439 100644
--- a/pjmedia/include/pjmedia/symbian_sound_aps.h
+++ b/pjmedia/include/pjmedia/symbian_sound_aps.h
@@ -31,21 +31,16 @@
PJ_BEGIN_DECL
/**
- * Activate/deactivate loudspeaker, when loudspeaker is inactive, audio
- * will be routed to earpiece.
+ * Set audio routing for APS sound device.
*
* @param stream The sound device stream, the stream should be started
- * before calling this function. This param can be NULL
- * to set the behaviour of next opened stream.
- * @param active Specify PJ_TRUE to activate loudspeaker, and PJ_FALSE
- * otherwise.
+ * before calling this function.
+ * @param route Audio routing to be set.
*
* @return PJ_SUCCESS on success.
*/
-PJ_DECL(pj_status_t) pjmedia_snd_aps_activate_loudspeaker(
- pjmedia_snd_stream *stream,
- pj_bool_t active);
-
+PJ_DECL(pj_status_t) pjmedia_snd_aps_set_route( pjmedia_snd_stream *stream,
+ pjmedia_snd_route route);
PJ_END_DECL
diff --git a/pjmedia/include/pjmedia/types.h b/pjmedia/include/pjmedia/types.h
index e456e34e..a4c71743 100644
--- a/pjmedia/include/pjmedia/types.h
+++ b/pjmedia/include/pjmedia/types.h
@@ -47,8 +47,8 @@
* @{
*/
-/**
- * Top most media type.
+/**
+ * Top most media type.
*/
typedef enum pjmedia_type
{
@@ -61,7 +61,7 @@ typedef enum pjmedia_type
/** The media is video. */
PJMEDIA_TYPE_VIDEO = 2,
- /** Unknown media type, in this case the name will be specified in
+ /** Unknown media type, in this case the name will be specified in
* encoding_name.
*/
PJMEDIA_TYPE_UNKNOWN = 3,
@@ -72,8 +72,8 @@ typedef enum pjmedia_type
} pjmedia_type;
-/**
- * Media transport protocol.
+/**
+ * Media transport protocol.
*/
typedef enum pjmedia_tp_proto
{
@@ -92,8 +92,8 @@ typedef enum pjmedia_tp_proto
} pjmedia_tp_proto;
-/**
- * Media direction.
+/**
+ * Media direction.
*/
typedef enum pjmedia_dir
{
@@ -138,8 +138,8 @@ typedef enum pjmedia_dir
(a<<24 | b<<16 | c<<8 | d)
-/**
- * Opague declaration of media endpoint.
+/**
+ * Opaque declaration of media endpoint.
*/
typedef struct pjmedia_endpt pjmedia_endpt;
@@ -150,7 +150,7 @@ typedef struct pjmedia_endpt pjmedia_endpt;
typedef struct pjmedia_stream pjmedia_stream;
-/**
+/**
* Media socket info is used to describe the underlying sockets
* to be used as media transport.
*/
@@ -180,8 +180,84 @@ typedef struct pjmedia_sock_info
/**
+ * Macro for packing format.
+ */
+#define PJMEDIA_FORMAT_PACK(C1, C2, C3, C4) ( C4<<24 | C3<<16 | C2<<8 | C1 )
+
+/**
+ * This enumeration describes format ID.
+ */
+typedef enum pjmedia_format_id
+{
+ /**
+ * 16bit linear
+ */
+ PJMEDIA_FORMAT_L16 = 0,
+
+ /**
+ * Alias for PJMEDIA_FORMAT_L16
+ */
+ PJMEDIA_FORMAT_PCM = PJMEDIA_FORMAT_L16,
+
+ /**
+ * G.711 ALAW
+ */
+ PJMEDIA_FORMAT_PCMA = PJMEDIA_FORMAT_PACK('A', 'L', 'A', 'W'),
+
+ /**
+ * Alias for PJMEDIA_FORMAT_PCMA
+ */
+ PJMEDIA_FORMAT_ALAW = PJMEDIA_FORMAT_PCMA,
+
+ /**
+ * G.711 ULAW
+ */
+ PJMEDIA_FORMAT_PCMU = PJMEDIA_FORMAT_PACK('u', 'L', 'A', 'W'),
+
+ /**
+ * Aliaw for PJMEDIA_FORMAT_PCMU
+ */
+ PJMEDIA_FORMAT_ULAW = PJMEDIA_FORMAT_PCMU,
+
+ /**
+ * AMR narrowband
+ */
+ PJMEDIA_FORMAT_AMR = PJMEDIA_FORMAT_PACK(' ', 'A', 'M', 'R'),
+
+ /**
+ * ITU G.729
+ */
+ PJMEDIA_FORMAT_G729 = PJMEDIA_FORMAT_PACK('G', '7', '2', '9'),
+
+ /**
+ * Internet Low Bit-Rate Codec (ILBC)
+ */
+ PJMEDIA_FORMAT_ILBC = PJMEDIA_FORMAT_PACK('I', 'L', 'B', 'C')
+
+} pjmedia_format_id;
+
+
+/**
+ * Media format information.
+ */
+typedef struct pjmedia_format
+{
+ /** Format ID */
+ pjmedia_format_id id;
+
+ /** Bitrate. */
+ pj_uint32_t bitrate;
+
+ /** Flag to indicate whether VAD is enabled */
+ pj_bool_t vad;
+
+} pjmedia_format;
+
+
+
+/**
* This is a general purpose function set PCM samples to zero.
- * Since this function is needed by many parts of the library,
+ * Since this function is needed by many parts of the library,
* by putting this functionality in one place, it enables some.
* clever people to optimize this function.
*
@@ -205,7 +281,7 @@ PJ_INLINE(void) pjmedia_zero_samples(pj_int16_t *samples, unsigned count)
/**
* This is a general purpose function to copy samples from/to buffers with
- * equal size. Since this function is needed by many parts of the library,
+ * equal size. Since this function is needed by many parts of the library,
* by putting this functionality in one place, it enables some.
* clever people to optimize this function.
*/
@@ -220,7 +296,7 @@ PJ_INLINE(void) pjmedia_copy_samples(pj_int16_t *dst, const pj_int16_t *src,
#else
unsigned i;
count >>= 1;
- for (i=0; i<count; ++i)
+ for (i=0; i<count; ++i)
((pj_int32_t*)dst)[i] = ((pj_int32_t*)src)[i];
#endif
}
@@ -228,7 +304,7 @@ PJ_INLINE(void) pjmedia_copy_samples(pj_int16_t *dst, const pj_int16_t *src,
/**
* This is a general purpose function to copy samples from/to buffers with
- * equal size. Since this function is needed by many parts of the library,
+ * equal size. Since this function is needed by many parts of the library,
* by putting this functionality in one place, it enables some.
* clever people to optimize this function.
*/
@@ -243,11 +319,211 @@ PJ_INLINE(void) pjmedia_move_samples(pj_int16_t *dst, const pj_int16_t *src,
#else
unsigned i;
count >>= 1;
- for (i=0; i<count; ++i)
+ for (i=0; i<count; ++i)
((pj_int32_t*)dst)[i] = ((pj_int32_t*)src)[i];
#endif
}
+/**
+ * Types of media frame.
+ */
+typedef enum pjmedia_frame_type
+{
+ PJMEDIA_FRAME_TYPE_NONE, /**< No frame. */
+ PJMEDIA_FRAME_TYPE_AUDIO, /**< Normal audio frame. */
+ PJMEDIA_FRAME_TYPE_EXTENDED /**< Extended audio frame. */
+
+} pjmedia_frame_type;
+
+
+/**
+ * This structure describes a media frame.
+ */
+typedef struct pjmedia_frame
+{
+ pjmedia_frame_type type; /**< Frame type. */
+ void *buf; /**< Pointer to buffer. */
+ pj_size_t size; /**< Frame size in bytes. */
+ pj_timestamp timestamp; /**< Frame timestamp. */
+ pj_uint32_t bit_info; /**< Bit info of the frame, sample case:
+ a frame may not exactly start and end
+ at the octet boundary, so this field
+ may be used for specifying start &
+ end bit offset. */
+} pjmedia_frame;
+
+
+/**
+ * The pjmedia_frame_ext is used to carry a more complex audio frames than
+ * the typical PCM audio frames, and it is signaled by setting the "type"
+ * field of a pjmedia_frame to PJMEDIA_FRAME_TYPE_EXTENDED. With this set,
+ * application may typecast pjmedia_frame to pjmedia_frame_ext.
+ *
+ * This structure may contain more than one audio frames, which subsequently
+ * will be called subframes in this structure. The subframes section
+ * immediately follows the end of this structure, and each subframe is
+ * represented by pjmedia_frame_ext_subframe structure. Every next
+ * subframe immediately follows the previous subframe, and all subframes
+ * are byte-aligned although its payload may not be byte-aligned.
+ */
+
+#pragma pack(1)
+typedef struct pjmedia_frame_ext {
+ pjmedia_frame base; /**< Base frame info */
+ pj_uint16_t samples_cnt; /**< Number of samples in this frame */
+ pj_uint16_t subframe_cnt; /**< Number of (sub)frames in this frame */
+
+ /* Zero or more (sub)frames follows immediately after this,
+ * each will be represented by pjmedia_frame_ext_subframe
+ */
+} pjmedia_frame_ext;
+#pragma pack()
+
+/**
+ * This structure represents the individual subframes in the
+ * pjmedia_frame_ext structure.
+ */
+#pragma pack(1)
+typedef struct pjmedia_frame_ext_subframe {
+ pj_uint16_t bitlen; /**< Number of bits in the data */
+ pj_uint8_t data[1]; /**< Start of encoded data */
+} pjmedia_frame_ext_subframe;
+
+#pragma pack()
+
+
+/**
+ * Append one subframe to #pjmedia_frame_ext.
+ *
+ * @param frm The #pjmedia_frame_ext.
+ * @param src Subframe data.
+ * @param bitlen Lenght of subframe, in bits.
+ * @param samples_cnt Number of audio samples in subframe.
+ */
+PJ_INLINE(void) pjmedia_frame_ext_append_subframe(pjmedia_frame_ext *frm,
+ const void *src,
+ unsigned bitlen,
+ unsigned samples_cnt)
+{
+ pjmedia_frame_ext_subframe *fsub;
+ pj_uint8_t *p;
+ unsigned i;
+
+ p = (pj_uint8_t*)frm + sizeof(pjmedia_frame_ext);
+ for (i = 0; i < frm->subframe_cnt; ++i) {
+ fsub = (pjmedia_frame_ext_subframe*) p;
+ p += sizeof(fsub->bitlen) + ((fsub->bitlen+7) >> 3);
+ }
+
+ fsub = (pjmedia_frame_ext_subframe*) p;
+ fsub->bitlen = (pj_uint16_t)bitlen;
+ if (bitlen)
+ pj_memcpy(fsub->data, src, (bitlen+7) >> 3);
+
+ frm->subframe_cnt++;
+ frm->samples_cnt = (pj_uint16_t)(frm->samples_cnt + samples_cnt);
+}
+
+/**
+ * Get a subframe from #pjmedia_frame_ext.
+ *
+ * @param frm The #pjmedia_frame_ext.
+ * @param n Subframe index, zero based.
+ *
+ * @return The n-th subframe, or NULL if n is out-of-range.
+ */
+PJ_INLINE(pjmedia_frame_ext_subframe*)
+pjmedia_frame_ext_get_subframe(const pjmedia_frame_ext *frm, unsigned n)
+{
+ pjmedia_frame_ext_subframe *sf = NULL;
+
+ if (n < frm->subframe_cnt) {
+ pj_uint8_t *p;
+ unsigned i;
+
+ p = (pj_uint8_t*)frm + sizeof(pjmedia_frame_ext);
+ for (i = 0; i < n; ++i) {
+ sf = (pjmedia_frame_ext_subframe*) p;
+ p += sizeof(sf->bitlen) + ((sf->bitlen+7) >> 3);
+ }
+
+ sf = (pjmedia_frame_ext_subframe*) p;
+ }
+
+ return sf;
+}
+
+/**
+ * Extract all frame payload to the specified buffer.
+ *
+ * @param frm The frame.
+ * @param dst Destination buffer.
+ * @param maxsize Maximum size to copy (i.e. the size of the
+ * destination buffer).
+ *
+ * @return Total size of payload copied.
+ */
+PJ_INLINE(unsigned)
+pjmedia_frame_ext_copy_payload(const pjmedia_frame_ext *frm,
+ void *dst,
+ unsigned maxlen)
+{
+ unsigned i, copied=0;
+ for (i=0; i<frm->subframe_cnt; ++i) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned sz;
+
+ sf = pjmedia_frame_ext_get_subframe(frm, i);
+ if (!sf)
+ continue;
+
+ sz = ((sf->bitlen + 7) >> 3);
+ if (sz + copied > maxlen)
+ break;
+
+ pj_memcpy(((pj_uint8_t*)dst) + copied, sf->data, sz);
+ copied += sz;
+ }
+ return copied;
+}
+
+
+/**
+ * Pop out first n subframes from #pjmedia_frame_ext.
+ *
+ * @param frm The #pjmedia_frame_ext.
+ * @param n Number of first subframes to be popped out.
+ *
+ * @return PJ_SUCCESS when successful.
+ */
+PJ_INLINE(pj_status_t)
+pjmedia_frame_ext_pop_subframes(pjmedia_frame_ext *frm, unsigned n)
+{
+ pjmedia_frame_ext_subframe *sf;
+ pj_uint8_t *move_src;
+ unsigned move_len;
+
+ if (frm->subframe_cnt <= n) {
+ frm->subframe_cnt = 0;
+ frm->samples_cnt = 0;
+ return PJ_SUCCESS;
+ }
+
+ move_src = (pj_uint8_t*)pjmedia_frame_ext_get_subframe(frm, n);
+ sf = pjmedia_frame_ext_get_subframe(frm, frm->subframe_cnt-1);
+ move_len = (pj_uint8_t*)sf - move_src + sizeof(sf->bitlen) +
+ ((sf->bitlen+7) >> 3);
+ pj_memmove((pj_uint8_t*)frm+sizeof(pjmedia_frame_ext),
+ move_src, move_len);
+
+ frm->samples_cnt = (pj_uint16_t)
+ (frm->samples_cnt - n*frm->samples_cnt/frm->subframe_cnt);
+ frm->subframe_cnt = (pj_uint16_t) (frm->subframe_cnt - n);
+
+ return PJ_SUCCESS;
+}
+
+
/**
* @}
*/
diff --git a/pjmedia/include/pjmedia_audiodev.h b/pjmedia/include/pjmedia_audiodev.h
new file mode 100644
index 00000000..e011d1b9
--- /dev/null
+++ b/pjmedia/include/pjmedia_audiodev.h
@@ -0,0 +1,33 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __PJMEDIA_AUDIODEV_H__
+#define __PJMEDIA_AUDIODEV_H__
+
+/**
+ * @file pjmedia_audiodev.h
+ * @brief PJMEDIA main header file.
+ */
+
+#include <pjmedia-audiodev/audiodev.h>
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia-audiodev/audiotest.h>
+
+#endif /* __PJMEDIA_AUDIODEV_H__ */
+
diff --git a/pjmedia/src/pjmedia-audiodev/audiodev.c b/pjmedia/src/pjmedia-audiodev/audiodev.c
new file mode 100644
index 00000000..956b5a97
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/audiodev.c
@@ -0,0 +1,697 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#define THIS_FILE "audiodev.c"
+
+#define DEFINE_CAP(name, info) {name, info}
+
+/* Capability names */
+static struct cap_info
+{
+ const char *name;
+ const char *info;
+} cap_infos[] =
+{
+ DEFINE_CAP("ext-fmt", "Extended/non-PCM format"),
+ DEFINE_CAP("latency-in", "Input latency/buffer size setting"),
+ DEFINE_CAP("latency-out", "Output latency/buffer size setting"),
+ DEFINE_CAP("vol-in", "Input volume setting"),
+ DEFINE_CAP("vol-out", "Output volume setting"),
+ DEFINE_CAP("meter-in", "Input meter"),
+ DEFINE_CAP("meter-out", "Output meter"),
+ DEFINE_CAP("route-in", "Input routing"),
+ DEFINE_CAP("route-out", "Output routing"),
+ DEFINE_CAP("aec", "Accoustic echo cancellation"),
+ DEFINE_CAP("aec-tail", "Tail length setting for AEC"),
+ DEFINE_CAP("vad", "Voice activity detection"),
+ DEFINE_CAP("cng", "Comfort noise generation"),
+ DEFINE_CAP("plg", "Packet loss concealment")
+};
+
+
+/*
+ * The device index seen by application and driver is different.
+ *
+ * At application level, device index is index to global list of device.
+ * At driver level, device index is index to device list on that particular
+ * factory only.
+ */
+#define MAKE_DEV_ID(f_id, index) (((f_id & 0xFFFF) << 16) | (index & 0xFFFF))
+#define GET_INDEX(dev_id) ((dev_id) & 0xFFFF)
+#define GET_FID(dev_id) ((dev_id) >> 16)
+#define DEFAULT_DEV_ID 0
+
+
+/* extern functions to create factories */
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+pjmedia_aud_dev_factory* pjmedia_pa_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+pjmedia_aud_dev_factory* pjmedia_wmme_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
+pjmedia_aud_dev_factory* pjmedia_aps_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA
+pjmedia_aud_dev_factory* pjmedia_symb_mda_factory(pj_pool_factory *pf);
+#endif
+
+#define MAX_DRIVERS 16
+#define MAX_DEVS 64
+
+/* typedef for factory creation function */
+typedef pjmedia_aud_dev_factory* (*create_func_ptr)(pj_pool_factory*);
+
+/* driver structure */
+struct driver
+{
+ create_func_ptr create; /* Creation function. */
+ pjmedia_aud_dev_factory *f; /* Factory instance. */
+ char name[32]; /* Driver name */
+ unsigned dev_cnt; /* Number of devices */
+ unsigned start_idx; /* Start index in global list */
+ int rec_dev_idx;/* Default capture device. */
+ int play_dev_idx;/* Default playback device */
+ int dev_idx; /* Default device. */
+};
+
+/* The audio subsystem */
+static struct aud_subsys
+{
+ unsigned init_count; /* How many times init() is called */
+ pj_pool_factory *pf; /* The pool factory. */
+
+ unsigned drv_cnt; /* Number of drivers. */
+ struct driver drv[MAX_DRIVERS]; /* Array of drivers. */
+
+ unsigned dev_cnt; /* Total number of devices. */
+ pj_uint32_t dev_list[MAX_DEVS];/* Array of device IDs. */
+
+} aud_subsys;
+
+/* API: get capability name/info */
+PJ_DEF(const char*) pjmedia_aud_dev_cap_name(pjmedia_aud_dev_cap cap,
+ const char **p_desc)
+{
+ const char *desc;
+ unsigned i;
+
+ if (p_desc==NULL) p_desc = &desc;
+
+ for (i=0; i<PJ_ARRAY_SIZE(cap_infos); ++i) {
+ if ((1 << i)==cap)
+ break;
+ }
+
+ if (i==32) {
+ *p_desc = "??";
+ return "??";
+ }
+
+ *p_desc = cap_infos[i].info;
+ return cap_infos[i].name;
+}
+
+static pj_status_t get_cap_pointer(const pjmedia_aud_param *param,
+ pjmedia_aud_dev_cap cap,
+ void **ptr,
+ unsigned *size)
+{
+#define FIELD_INFO(name) *ptr = (void*)&param->name; \
+ *size = sizeof(param->name)
+
+ switch (cap) {
+ case PJMEDIA_AUD_DEV_CAP_EXT_FORMAT:
+ FIELD_INFO(ext_fmt);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY:
+ FIELD_INFO(input_latency_ms);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY:
+ FIELD_INFO(output_latency_ms);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+ FIELD_INFO(input_vol);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+ FIELD_INFO(output_vol);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE:
+ FIELD_INFO(input_route);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
+ FIELD_INFO(output_route);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_EC:
+ FIELD_INFO(ec_enabled);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_EC_TAIL:
+ FIELD_INFO(ec_tail_ms);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_VAD:
+ FIELD_INFO(ext_fmt.vad);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_CNG:
+ FIELD_INFO(cng_enabled);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_PLC:
+ FIELD_INFO(plc_enabled);
+ break;
+ default:
+ return PJMEDIA_EAUD_INVCAP;
+ }
+
+#undef FIELD_INFO
+
+ return PJ_SUCCESS;
+}
+
+/* API: set cap value to param */
+PJ_DEF(pj_status_t) pjmedia_aud_param_set_cap( pjmedia_aud_param *param,
+ pjmedia_aud_dev_cap cap,
+ const void *pval)
+{
+ void *cap_ptr;
+ unsigned cap_size;
+ pj_status_t status;
+
+ status = get_cap_pointer(param, cap, &cap_ptr, &cap_size);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_memcpy(cap_ptr, pval, cap_size);
+ param->flags |= cap;
+
+ return PJ_SUCCESS;
+}
+
+/* API: get cap value from param */
+PJ_DEF(pj_status_t) pjmedia_aud_param_get_cap( const pjmedia_aud_param *param,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ void *cap_ptr;
+ unsigned cap_size;
+ pj_status_t status;
+
+ status = get_cap_pointer(param, cap, &cap_ptr, &cap_size);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if ((param->flags & cap) == 0) {
+ pj_bzero(cap_ptr, cap_size);
+ return PJMEDIA_EAUD_INVCAP;
+ }
+
+ pj_memcpy(pval, cap_ptr, cap_size);
+ return PJ_SUCCESS;
+}
+
+/* Internal: init driver */
+static pj_status_t init_driver(unsigned drv_idx)
+{
+ struct driver *drv = &aud_subsys.drv[drv_idx];
+ pjmedia_aud_dev_factory *f;
+ unsigned i, dev_cnt;
+ pj_status_t status;
+
+ /* Create the factory */
+ f = (*drv->create)(aud_subsys.pf);
+ if (!f)
+ return PJ_EUNKNOWN;
+
+ /* Call factory->init() */
+ status = f->op->init(f);
+ if (status != PJ_SUCCESS) {
+ f->op->destroy(f);
+ return status;
+ }
+
+ /* Get number of devices */
+ dev_cnt = f->op->get_dev_count(f);
+ if (dev_cnt + aud_subsys.dev_cnt > MAX_DEVS) {
+ PJ_LOG(4,(THIS_FILE, "%d device(s) cannot be registered because"
+ " there are too many devices",
+ aud_subsys.dev_cnt + dev_cnt - MAX_DEVS));
+ dev_cnt = MAX_DEVS - aud_subsys.dev_cnt;
+ }
+ if (dev_cnt == 0) {
+ f->op->destroy(f);
+ return PJMEDIA_EAUD_NODEV;
+ }
+
+ /* Fill in default devices */
+ drv->play_dev_idx = drv->rec_dev_idx = drv->dev_idx = -1;
+ for (i=0; i<dev_cnt; ++i) {
+ pjmedia_aud_dev_info info;
+
+ status = f->op->get_dev_info(f, i, &info);
+ if (status != PJ_SUCCESS) {
+ f->op->destroy(f);
+ return status;
+ }
+
+ if (drv->name[0]=='\0') {
+ /* Set driver name */
+ pj_ansi_strncpy(drv->name, info.driver, sizeof(drv->name));
+ drv->name[sizeof(drv->name)-1] = '\0';
+ }
+
+ if (drv->play_dev_idx < 0 && info.output_count) {
+ /* Set default playback device */
+ drv->play_dev_idx = i;
+ }
+ if (drv->rec_dev_idx < 0 && info.input_count) {
+ /* Set default capture device */
+ drv->rec_dev_idx = i;
+ }
+ if (drv->dev_idx < 0 && info.input_count &&
+ info.output_count)
+ {
+ /* Set default capture and playback device */
+ drv->dev_idx = i;
+ }
+
+ if (drv->play_dev_idx >= 0 && drv->rec_dev_idx >= 0 &&
+ drv->dev_idx >= 0)
+ {
+ /* Done. */
+ break;
+ }
+ }
+
+ /* Register the factory */
+ drv->f = f;
+ drv->f->sys.drv_idx = drv_idx;
+ drv->start_idx = aud_subsys.dev_cnt;
+ drv->dev_cnt = dev_cnt;
+
+ /* Register devices to global list */
+ for (i=0; i<dev_cnt; ++i) {
+ aud_subsys.dev_list[aud_subsys.dev_cnt++] = MAKE_DEV_ID(drv_idx, i);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* Internal: deinit driver */
+static void deinit_driver(unsigned drv_idx)
+{
+ struct driver *drv = &aud_subsys.drv[drv_idx];
+
+ if (drv->f) {
+ drv->f->op->destroy(drv->f);
+ drv->f = NULL;
+ }
+
+ drv->dev_cnt = 0;
+ drv->play_dev_idx = drv->rec_dev_idx = drv->dev_idx = -1;
+}
+
+/* API: Initialize the audio subsystem. */
+PJ_DEF(pj_status_t) pjmedia_aud_subsys_init(pj_pool_factory *pf)
+{
+ unsigned i;
+ pj_status_t status = PJ_ENOMEM;
+
+ /* Allow init() to be called multiple times as long as there is matching
+ * number of shutdown().
+ */
+ if (aud_subsys.init_count++ != 0) {
+ return PJ_SUCCESS;
+ }
+
+ /* Register error subsystem */
+ pj_register_strerror(PJMEDIA_AUDIODEV_ERRNO_START,
+ PJ_ERRNO_SPACE_SIZE,
+ &pjmedia_audiodev_strerror);
+
+ /* Init */
+ aud_subsys.pf = pf;
+ aud_subsys.drv_cnt = 0;
+ aud_subsys.dev_cnt = 0;
+
+ /* Register creation functions */
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+ aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_pa_factory;
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+ aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_wmme_factory;
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
+ aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_aps_factory;
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA
+ aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_symb_mda_factory;
+#endif
+
+ /* Initialize each factory and build the device ID list */
+ for (i=0; i<aud_subsys.drv_cnt; ++i) {
+ status = init_driver(i);
+ if (status != PJ_SUCCESS) {
+ deinit_driver(i);
+ continue;
+ }
+ }
+
+ return aud_subsys.dev_cnt ? PJ_SUCCESS : status;
+}
+
+/* API: get the pool factory registered to the audio subsystem. */
+PJ_DEF(pj_pool_factory*) pjmedia_aud_subsys_get_pool_factory(void)
+{
+ return aud_subsys.pf;
+}
+
+/* API: Shutdown the audio subsystem. */
+PJ_DEF(pj_status_t) pjmedia_aud_subsys_shutdown(void)
+{
+ unsigned i;
+
+ /* Allow shutdown() to be called multiple times as long as there is matching
+ * number of init().
+ */
+ if (aud_subsys.init_count == 0) {
+ return PJ_SUCCESS;
+ }
+ --aud_subsys.init_count;
+
+ for (i=0; i<aud_subsys.drv_cnt; ++i) {
+ deinit_driver(i);
+ }
+
+ aud_subsys.pf = NULL;
+ return PJ_SUCCESS;
+}
+
+/* API: Get the number of sound devices installed in the system. */
+PJ_DEF(unsigned) pjmedia_aud_dev_count(void)
+{
+ return aud_subsys.dev_cnt;
+}
+
+/* Internal: convert local index to global device index */
+static pj_status_t make_global_index(unsigned drv_idx,
+ pjmedia_aud_dev_index *id)
+{
+ if (*id < 0) {
+ return PJ_SUCCESS;
+ }
+
+ /* Check that factory still exists */
+ PJ_ASSERT_RETURN(aud_subsys.drv[drv_idx].f, PJ_EBUG);
+
+ /* Check that device index is valid */
+ PJ_ASSERT_RETURN(*id>=0 && *id<(int)aud_subsys.drv[drv_idx].dev_cnt,
+ PJ_EBUG);
+
+ *id += aud_subsys.drv[drv_idx].start_idx;
+ return PJ_SUCCESS;
+}
+
+/* Internal: lookup device id */
+static pj_status_t lookup_dev(pjmedia_aud_dev_index id,
+ pjmedia_aud_dev_factory **p_f,
+ unsigned *p_local_index)
+{
+ int f_id, index;
+
+ if (id < 0) {
+ unsigned i;
+
+ if (id == PJMEDIA_AUD_INVALID_DEV)
+ return PJMEDIA_EAUD_INVDEV;
+
+ for (i=0; i<aud_subsys.drv_cnt; ++i) {
+ struct driver *drv = &aud_subsys.drv[i];
+ if (drv->dev_idx >= 0) {
+ id = drv->dev_idx;
+ make_global_index(i, &id);
+ break;
+ } else if (id==PJMEDIA_AUD_DEFAULT_CAPTURE_DEV &&
+ drv->rec_dev_idx >= 0)
+ {
+ id = drv->rec_dev_idx;
+ make_global_index(i, &id);
+ break;
+ } else if (id==PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV &&
+ drv->play_dev_idx >= 0)
+ {
+ id = drv->play_dev_idx;
+ make_global_index(i, &id);
+ break;
+ }
+ }
+
+ if (id < 0) {
+ return PJMEDIA_EAUD_NODEFDEV;
+ }
+ }
+
+ f_id = GET_FID(aud_subsys.dev_list[id]);
+ index = GET_INDEX(aud_subsys.dev_list[id]);
+
+ if (f_id < 0 || f_id >= (int)aud_subsys.drv_cnt)
+ return PJMEDIA_EAUD_INVDEV;
+
+ if (index < 0 || index >= (int)aud_subsys.drv[f_id].dev_cnt)
+ return PJMEDIA_EAUD_INVDEV;
+
+ *p_f = aud_subsys.drv[f_id].f;
+ *p_local_index = (unsigned)index;
+
+ return PJ_SUCCESS;
+
+}
+
+/* API: Get device information. */
+PJ_DEF(pj_status_t) pjmedia_aud_dev_get_info(pjmedia_aud_dev_index id,
+ pjmedia_aud_dev_info *info)
+{
+ pjmedia_aud_dev_factory *f;
+ unsigned index;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(info && id!=PJMEDIA_AUD_INVALID_DEV, PJ_EINVAL);
+ PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
+
+ status = lookup_dev(id, &f, &index);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return f->op->get_dev_info(f, index, info);
+}
+
+/* API: find device */
+PJ_DEF(pj_status_t) pjmedia_aud_dev_lookup( const char *drv_name,
+ const char *dev_name,
+ pjmedia_aud_dev_index *id)
+{
+ pjmedia_aud_dev_factory *f = NULL;
+ unsigned drv_idx, dev_idx;
+
+ PJ_ASSERT_RETURN(drv_name && dev_name && id, PJ_EINVAL);
+ PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
+
+ for (drv_idx=0; drv_idx<aud_subsys.drv_cnt; ++drv_idx) {
+ if (!pj_ansi_stricmp(drv_name, aud_subsys.drv[drv_idx].name)) {
+ f = aud_subsys.drv[drv_idx].f;
+ break;
+ }
+ }
+
+ if (!f)
+ return PJ_ENOTFOUND;
+
+ for (dev_idx=0; dev_idx<aud_subsys.drv[drv_idx].dev_cnt; ++dev_idx) {
+ pjmedia_aud_dev_info info;
+ pj_status_t status;
+
+ status = f->op->get_dev_info(f, dev_idx, &info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (!pj_ansi_stricmp(dev_name, info.name))
+ break;
+ }
+
+ if (dev_idx==aud_subsys.drv[drv_idx].dev_cnt)
+ return PJ_ENOTFOUND;
+
+ *id = dev_idx;
+ make_global_index(drv_idx, id);
+
+ return PJ_SUCCESS;
+}
+
+/* API: Initialize the audio device parameters with default values for the
+ * specified device.
+ */
+PJ_DEF(pj_status_t) pjmedia_aud_dev_default_param(pjmedia_aud_dev_index id,
+ pjmedia_aud_param *param)
+{
+ pjmedia_aud_dev_factory *f;
+ unsigned index;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(param && id!=PJMEDIA_AUD_INVALID_DEV, PJ_EINVAL);
+ PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
+
+ status = lookup_dev(id, &f, &index);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = f->op->default_param(f, index, param);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Normalize device IDs */
+ make_global_index(f->sys.drv_idx, &param->rec_id);
+ make_global_index(f->sys.drv_idx, &param->play_id);
+
+ return PJ_SUCCESS;
+}
+
+/* API: Open audio stream object using the specified parameters. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_create(const pjmedia_aud_param *prm,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ pjmedia_aud_dev_factory *rec_f=NULL, *play_f=NULL, *f=NULL;
+ pjmedia_aud_param param;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(prm && prm->dir && p_aud_strm, PJ_EINVAL);
+ PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
+
+ /* Must make copy of param because we're changing device ID */
+ pj_memcpy(&param, prm, sizeof(param));
+
+ /* Normalize rec_id */
+ if (param.dir & PJMEDIA_DIR_CAPTURE) {
+ unsigned index;
+
+ if (param.rec_id < 0)
+ param.rec_id = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV;
+
+ status = lookup_dev(param.rec_id, &rec_f, &index);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ param.rec_id = index;
+ f = rec_f;
+ }
+
+ /* Normalize play_id */
+ if (param.dir & PJMEDIA_DIR_PLAYBACK) {
+ unsigned index;
+
+ if (param.play_id < 0)
+ param.play_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
+
+ status = lookup_dev(param.play_id, &play_f, &index);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ param.play_id = index;
+ f = play_f;
+
+ /* For now, rec_id and play_id must belong to the same factory */
+ PJ_ASSERT_RETURN(rec_f == play_f, PJMEDIA_EAUD_INVDEV);
+ }
+
+
+ /* Create the stream */
+ status = f->op->create_stream(f, &param, rec_cb, play_cb,
+ user_data, p_aud_strm);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Assign factory id to the stream */
+ (*p_aud_strm)->sys.drv_idx = f->sys.drv_idx;
+ return PJ_SUCCESS;
+}
+
+/* API: Get the running parameters for the specified audio stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(strm && param, PJ_EINVAL);
+ PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
+
+ status = strm->op->get_param(strm, param);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Normalize device id's */
+ make_global_index(strm->sys.drv_idx, &param->rec_id);
+ make_global_index(strm->sys.drv_idx, &param->play_id);
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get the value of a specific capability of the audio stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value)
+{
+ return strm->op->get_cap(strm, cap, value);
+}
+
+/* API: Set the value of a specific capability of the audio stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value)
+{
+ return strm->op->set_cap(strm, cap, value);
+}
+
+/* API: Start the stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_start(pjmedia_aud_stream *strm)
+{
+ return strm->op->start(strm);
+}
+
+/* API: Stop the stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_stop(pjmedia_aud_stream *strm)
+{
+ return strm->op->stop(strm);
+}
+
+/* API: Destroy the stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_destroy(pjmedia_aud_stream *strm)
+{
+ return strm->op->destroy(strm);
+}
+
+
diff --git a/pjmedia/src/pjmedia-audiodev/audiotest.c b/pjmedia/src/pjmedia-audiodev/audiotest.c
new file mode 100644
index 00000000..bf0ac1f2
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/audiotest.c
@@ -0,0 +1,269 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjmedia-audiodev/audiotest.h>
+#include <pjmedia-audiodev/audiodev.h>
+#include <pjlib.h>
+#include <pjlib-util.h>
+
+#define THIS_FILE "audiotest.c"
+
+/* Test duration in msec */
+#define DURATION 10000
+
+/* Skip the first msec from the calculation */
+#define SKIP_DURATION 1000
+
+/* Division helper */
+#define DIV_ROUND_UP(a,b) (((a) + ((b) - 1)) / (b))
+#define DIV_ROUND(a,b) (((a) + ((b)/2 - 1)) / (b))
+
+struct stream_data
+{
+ pj_uint32_t first_timestamp;
+ pj_uint32_t last_timestamp;
+ pj_timestamp last_called;
+ pj_math_stat delay;
+};
+
+struct test_data
+{
+ pj_pool_t *pool;
+ const pjmedia_aud_param *param;
+ pjmedia_aud_test_results *result;
+ pj_bool_t running;
+ pj_bool_t has_error;
+ pj_mutex_t *mutex;
+
+ struct stream_data capture_data;
+ struct stream_data playback_data;
+};
+
+static pj_status_t play_cb(void *user_data, pjmedia_frame *frame)
+{
+ struct test_data *test_data = (struct test_data *)user_data;
+ struct stream_data *strm_data = &test_data->playback_data;
+
+ pj_mutex_lock(test_data->mutex);
+
+ /* Skip frames when test is not started or test has finished */
+ if (!test_data->running) {
+ pj_bzero(frame->buf, frame->size);
+ pj_mutex_unlock(test_data->mutex);
+ return PJ_SUCCESS;
+ }
+
+ /* Save last timestamp seen (to calculate drift) */
+ strm_data->last_timestamp = frame->timestamp.u32.lo;
+
+ if (strm_data->last_called.u64 == 0) {
+ /* Init vars. */
+ pj_get_timestamp(&strm_data->last_called);
+ pj_math_stat_init(&strm_data->delay);
+ strm_data->first_timestamp = frame->timestamp.u32.lo;
+ } else {
+ pj_timestamp now;
+ unsigned delay;
+
+ /* Calculate frame interval */
+ pj_get_timestamp(&now);
+ delay = pj_elapsed_usec(&strm_data->last_called, &now);
+ strm_data->last_called = now;
+
+ /* Update frame interval statistic */
+ pj_math_stat_update(&strm_data->delay, delay);
+ }
+
+ pj_bzero(frame->buf, frame->size);
+
+ pj_mutex_unlock(test_data->mutex);
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame)
+{
+ struct test_data *test_data = (struct test_data*)user_data;
+ struct stream_data *strm_data = &test_data->capture_data;
+
+ pj_mutex_lock(test_data->mutex);
+
+ /* Skip frames when test is not started or test has finished */
+ if (!test_data->running) {
+ pj_mutex_unlock(test_data->mutex);
+ return PJ_SUCCESS;
+ }
+
+ /* Save last timestamp seen (to calculate drift) */
+ strm_data->last_timestamp = frame->timestamp.u32.lo;
+
+ if (strm_data->last_called.u64 == 0) {
+ /* Init vars. */
+ pj_get_timestamp(&strm_data->last_called);
+ pj_math_stat_init(&strm_data->delay);
+ strm_data->first_timestamp = frame->timestamp.u32.lo;
+ } else {
+ pj_timestamp now;
+ unsigned delay;
+
+ /* Calculate frame interval */
+ pj_get_timestamp(&now);
+ delay = pj_elapsed_usec(&strm_data->last_called, &now);
+ strm_data->last_called = now;
+
+ /* Update frame interval statistic */
+ pj_math_stat_update(&strm_data->delay, delay);
+ }
+
+ pj_mutex_unlock(test_data->mutex);
+ return PJ_SUCCESS;
+}
+
+static void app_perror(const char *title, pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ printf( "%s: %s (err=%d)\n",
+ title, errmsg, status);
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_aud_test( const pjmedia_aud_param *param,
+ pjmedia_aud_test_results *result)
+{
+ pj_status_t status = PJ_SUCCESS;
+ pjmedia_aud_stream *strm;
+ struct test_data test_data;
+ unsigned ptime, tmp;
+
+ /*
+ * Init test parameters
+ */
+ pj_bzero(&test_data, sizeof(test_data));
+ test_data.param = param;
+ test_data.result = result;
+
+ test_data.pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(),
+ "audtest", 1000, 1000, NULL);
+ pj_mutex_create_simple(test_data.pool, "sndtest", &test_data.mutex);
+
+ /*
+ * Open device.
+ */
+ status = pjmedia_aud_stream_create(test_data.param, &rec_cb, &play_cb,
+ &test_data, &strm);
+ if (status != PJ_SUCCESS) {
+ app_perror("Unable to open device", status);
+ pj_pool_release(test_data.pool);
+ return status;
+ }
+
+
+ /* Sleep for a while to let sound device "settles" */
+ pj_thread_sleep(200);
+
+ /*
+ * Start the stream.
+ */
+ status = pjmedia_aud_stream_start(strm);
+ if (status != PJ_SUCCESS) {
+ app_perror("Unable to start capture stream", status);
+ pjmedia_aud_stream_destroy(strm);
+ pj_pool_release(test_data.pool);
+ return status;
+ }
+
+ PJ_LOG(3,(THIS_FILE,
+ " Please wait while test is in progress (~%d secs)..",
+ (DURATION+SKIP_DURATION)/1000));
+
+ /* Let the stream runs for few msec/sec to get stable result.
+ * (capture normally begins with frames available simultaneously).
+ */
+ pj_thread_sleep(SKIP_DURATION);
+
+
+ /* Begin gather data */
+ test_data.running = 1;
+
+ /*
+ * Let the test runs for a while.
+ */
+ pj_thread_sleep(DURATION);
+
+
+ /*
+ * Close stream.
+ */
+ test_data.running = 0;
+ pjmedia_aud_stream_destroy(strm);
+ pj_pool_release(test_data.pool);
+
+
+ /*
+ * Gather results
+ */
+ ptime = param->samples_per_frame * 1000 / param->clock_rate;
+
+ tmp = pj_math_stat_get_stddev(&test_data.capture_data.delay);
+ result->rec.frame_cnt = test_data.capture_data.delay.n;
+ result->rec.min_interval = DIV_ROUND(test_data.capture_data.delay.min, 1000);
+ result->rec.max_interval = DIV_ROUND(test_data.capture_data.delay.max, 1000);
+ result->rec.avg_interval = DIV_ROUND(test_data.capture_data.delay.mean, 1000);
+ result->rec.dev_interval = DIV_ROUND(tmp, 1000);
+ result->rec.max_burst = DIV_ROUND_UP(result->rec.max_interval, ptime);
+
+ tmp = pj_math_stat_get_stddev(&test_data.playback_data.delay);
+ result->play.frame_cnt = test_data.playback_data.delay.n;
+ result->play.min_interval = DIV_ROUND(test_data.playback_data.delay.min, 1000);
+ result->play.max_interval = DIV_ROUND(test_data.playback_data.delay.max, 1000);
+ result->play.avg_interval = DIV_ROUND(test_data.capture_data.delay.mean, 1000);
+ result->play.dev_interval = DIV_ROUND(tmp, 1000);
+ result->play.max_burst = DIV_ROUND_UP(result->play.max_interval, ptime);
+
+ /* Check drifting */
+ if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+ int end_diff, start_diff, drift;
+
+ end_diff = test_data.capture_data.last_timestamp -
+ test_data.playback_data.last_timestamp;
+ start_diff = test_data.capture_data.first_timestamp-
+ test_data.playback_data.first_timestamp;
+ drift = end_diff > start_diff? end_diff - start_diff :
+ start_diff - end_diff;
+
+ /* Allow one frame tolerance for clock drift detection */
+ if (drift < (int)param->samples_per_frame) {
+ result->rec_drift_per_sec = 0;
+ } else {
+ unsigned msec_dur;
+
+ msec_dur = (test_data.capture_data.last_timestamp -
+ test_data.capture_data.first_timestamp) * 1000 /
+ test_data.param->clock_rate;
+
+ result->rec_drift_per_sec = drift * 1000 / msec_dur;
+
+ }
+ }
+
+ return test_data.has_error? PJ_EUNKNOWN : PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia-audiodev/errno.c b/pjmedia/src/pjmedia-audiodev/errno.c
new file mode 100644
index 00000000..28522490
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/errno.c
@@ -0,0 +1,190 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjmedia-audiodev/errno.h>
+#include <pj/string.h>
+#include <pj/unicode.h>
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+# include <portaudio.h>
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+# ifdef _MSC_VER
+# pragma warning(push, 3)
+# endif
+# include <windows.h>
+# include <mmsystem.h>
+# ifdef _MSC_VER
+# pragma warning(pop)
+# endif
+#endif
+
+/* PJMEDIA-Audiodev's own error codes/messages
+ * MUST KEEP THIS ARRAY SORTED!!
+ * Message must be limited to 64 chars!
+ */
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+static const struct
+{
+ int code;
+ const char *msg;
+} err_str[] =
+{
+ PJ_BUILD_ERR( PJMEDIA_EAUD_ERR, "Unspecified audio device error" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_SYSERR, "Unknown error from audio driver" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_INIT, "Audio subsystem not initialized" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_INVDEV, "Invalid audio device" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_NODEV, "Found no audio devices" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_NODEFDEV, "Unable to find default audio device" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_NOTREADY, "Audio device not ready" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_INVCAP, "Invalid or unsupported audio capability" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_INVOP, "Invalid or unsupported audio device operation" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_BADFORMAT, "Bad or invalid audio device format" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_SAMPFORMAT, "Invalid audio device sample format"),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_BADLATENCY, "Bad audio latency setting")
+
+};
+
+#endif /* PJ_HAS_ERROR_STRING */
+
+
+
+/*
+ * pjmedia_audiodev_strerror()
+ */
+PJ_DEF(pj_str_t) pjmedia_audiodev_strerror(pj_status_t statcode,
+ char *buf, pj_size_t bufsize )
+{
+ pj_str_t errstr;
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+ /* See if the error comes from PortAudio. */
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+ if (statcode >= PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START &&
+ statcode <= PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_END)
+ {
+
+ //int pa_err = statcode - PJMEDIA_ERRNO_FROM_PORTAUDIO(0);
+ int pa_err = PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START - statcode;
+ pj_str_t msg;
+
+ msg.ptr = (char*)Pa_GetErrorText(pa_err);
+ msg.slen = pj_ansi_strlen(msg.ptr);
+
+ errstr.ptr = buf;
+ pj_strncpy_with_null(&errstr, &msg, bufsize);
+ return errstr;
+
+ } else
+#endif /* PJMEDIA_SOUND_IMPLEMENTATION */
+
+ /* See if the error comes from WMME */
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+ if ((statcode >= PJMEDIA_AUDIODEV_WMME_IN_ERROR_START &&
+ statcode < PJMEDIA_AUDIODEV_WMME_IN_ERROR_END) ||
+ (statcode >= PJMEDIA_AUDIODEV_WMME_OUT_ERROR_START &&
+ statcode < PJMEDIA_AUDIODEV_WMME_OUT_ERROR_END))
+ {
+ MMRESULT native_err, mr;
+ MMRESULT (WINAPI *waveGetErrText)(UINT mmrError, LPTSTR pszText, UINT cchText);
+ PJ_DECL_UNICODE_TEMP_BUF(wbuf, 80)
+
+ if (statcode >= PJMEDIA_AUDIODEV_WMME_IN_ERROR_START &&
+ statcode <= PJMEDIA_AUDIODEV_WMME_IN_ERROR_END)
+ {
+ native_err = statcode - PJMEDIA_AUDIODEV_WMME_IN_ERROR_START;
+ waveGetErrText = &waveInGetErrorText;
+ } else {
+ native_err = statcode - PJMEDIA_AUDIODEV_WMME_OUT_ERROR_START;
+ waveGetErrText = &waveOutGetErrorText;
+ }
+
+#if PJ_NATIVE_STRING_IS_UNICODE
+ mr = (*waveGetErrText)(native_err, wbuf, PJ_ARRAY_SIZE(wbuf));
+ if (mr == MMSYSERR_NOERROR) {
+ int len = wcslen(wbuf);
+ pj_unicode_to_ansi(wbuf, len, buf, bufsize);
+ }
+#else
+ mr = (*waveGetErrText)(native_err, buf, bufsize);
+#endif
+
+ if (mr==MMSYSERR_NOERROR) {
+ errstr.ptr = buf;
+ errstr.slen = pj_ansi_strlen(buf);
+ return errstr;
+ } else {
+ pj_ansi_snprintf(buf, bufsize, "MMSYSTEM native error %d",
+ native_err);
+ return pj_str(buf);
+ }
+
+ } else
+#endif
+
+ /* Audiodev error */
+ if (statcode >= PJMEDIA_AUDIODEV_ERRNO_START &&
+ statcode < PJMEDIA_AUDIODEV_ERRNO_END)
+ {
+ /* Find the error in the table.
+ * Use binary search!
+ */
+ int first = 0;
+ int n = PJ_ARRAY_SIZE(err_str);
+
+ while (n > 0) {
+ int half = n/2;
+ int mid = first + half;
+
+ if (err_str[mid].code < statcode) {
+ first = mid+1;
+ n -= (half+1);
+ } else if (err_str[mid].code > statcode) {
+ n = half;
+ } else {
+ first = mid;
+ break;
+ }
+ }
+
+
+ if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
+ pj_str_t msg;
+
+ msg.ptr = (char*)err_str[first].msg;
+ msg.slen = pj_ansi_strlen(err_str[first].msg);
+
+ errstr.ptr = buf;
+ pj_strncpy_with_null(&errstr, &msg, bufsize);
+ return errstr;
+
+ }
+ }
+#endif /* PJ_HAS_ERROR_STRING */
+
+ /* Error not found. */
+ errstr.ptr = buf;
+ errstr.slen = pj_ansi_snprintf(buf, bufsize,
+ "Unknown pjmedia-audiodev error %d",
+ statcode);
+
+ return errstr;
+}
+
diff --git a/pjmedia/src/pjmedia-audiodev/legacy_dev.c b/pjmedia/src/pjmedia-audiodev/legacy_dev.c
new file mode 100644
index 00000000..66fad9b0
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/legacy_dev.c
@@ -0,0 +1,459 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia/sound.h>
+#include <pj/assert.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE
+
+#define THIS_FILE "legacy_dev.c"
+
+/* Legacy devices factory */
+struct legacy_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+};
+
+
+struct legacy_stream
+{
+ pjmedia_aud_stream base;
+
+ pj_pool_t *pool;
+ pjmedia_aud_param param;
+ pjmedia_snd_stream *snd_strm;
+ pjmedia_aud_play_cb user_play_cb;
+ pjmedia_aud_rec_cb user_rec_cb;
+ void *user_user_data;
+ unsigned input_latency;
+ unsigned output_latency;
+};
+
+
+/* Prototypes */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t stream_start(pjmedia_aud_stream *strm);
+static pj_status_t stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
+
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+ &factory_init,
+ &factory_destroy,
+ &factory_get_dev_count,
+ &factory_get_dev_info,
+ &factory_default_param,
+ &factory_create_stream
+};
+
+static pjmedia_aud_stream_op stream_op =
+{
+ &stream_get_param,
+ &stream_get_cap,
+ &stream_set_cap,
+ &stream_start,
+ &stream_stop,
+ &stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+
+/*
+ * Init legacy audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_legacy_factory(pj_pool_factory *pf)
+{
+ struct legacy_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "legacy-snd", 512, 512, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct legacy_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
+{
+ struct legacy_factory *wf = (struct legacy_factory*)f;
+
+ return pjmedia_snd_init(wf->pf);
+}
+
+/* API: destroy factory */
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct legacy_factory *wf = (struct legacy_factory*)f;
+ pj_status_t status;
+
+ status = pjmedia_snd_deinit();
+
+ if (status == PJ_SUCCESS) {
+ pj_pool_t *pool = wf->pool;
+ wf->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return status;
+}
+
+/* API: get number of devices */
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return pjmedia_snd_get_dev_count();
+}
+
+/* API: get device info */
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
+{
+ const pjmedia_snd_dev_info *si =
+ pjmedia_snd_get_dev_info(index);;
+
+ PJ_UNUSED_ARG(f);
+
+ if (si == NULL)
+ return PJMEDIA_EAUD_INVDEV;
+
+ pj_bzero(info, sizeof(*info));
+ pj_ansi_strncpy(info->name, si->name, sizeof(info->name));
+ info->name[sizeof(info->name)-1] = '\0';
+ info->input_count = si->input_count;
+ info->output_count = si->output_count;
+ info->default_samples_per_sec = si->default_samples_per_sec;
+ pj_ansi_strcpy(info->driver, "legacy");
+ info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ pjmedia_aud_dev_info di;
+ pj_status_t status;
+
+ status = factory_get_dev_info(f, index, &di);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_bzero(param, sizeof(*param));
+ if (di.input_count && di.output_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ } else if (di.input_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->rec_id = index;
+ param->play_id = PJMEDIA_AUD_INVALID_DEV;
+ } else if (di.output_count) {
+ param->dir = PJMEDIA_DIR_PLAYBACK;
+ param->play_id = index;
+ param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+ } else {
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ param->clock_rate = di.default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = di.default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = 16;
+ param->flags = di.caps;
+ param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+ return PJ_SUCCESS;
+}
+
+/* Callback from legacy sound device */
+static pj_status_t snd_play_cb(/* in */ void *user_data,
+ /* in */ pj_uint32_t timestamp,
+ /* out */ void *output,
+ /* out */ unsigned size)
+{
+ struct legacy_stream *strm = (struct legacy_stream*)user_data;
+ pjmedia_frame frame;
+ pj_status_t status;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = output;
+ frame.size = size;
+ frame.timestamp.u64 = timestamp;
+
+ status = strm->user_play_cb(strm->user_user_data, &frame);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+ pj_bzero(output, size);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* Callback from legacy sound device */
+static pj_status_t snd_rec_cb( /* in */ void *user_data,
+ /* in */ pj_uint32_t timestamp,
+ /* in */ void *input,
+ /* in*/ unsigned size)
+{
+ struct legacy_stream *strm = (struct legacy_stream*)user_data;
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = input;
+ frame.size = size;
+ frame.timestamp.u64 = timestamp;
+
+ return strm->user_rec_cb(strm->user_user_data, &frame);
+}
+
+/* API: create stream */
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ struct legacy_factory *wf = (struct legacy_factory*)f;
+ pj_pool_t *pool;
+ struct legacy_stream *strm;
+ pj_status_t status;
+
+ /* Initialize our stream data */
+ pool = pj_pool_create(wf->pf, "legacy-snd", 512, 512, NULL);
+ strm = PJ_POOL_ZALLOC_T(pool, struct legacy_stream);
+ strm->pool = pool;
+ strm->user_rec_cb = rec_cb;
+ strm->user_play_cb = play_cb;
+ strm->user_user_data = user_data;
+ pj_memcpy(&strm->param, param, sizeof(*param));
+
+ /* Set the latency if wanted */
+ if (param->dir==PJMEDIA_DIR_CAPTURE_PLAYBACK &&
+ param->flags & (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY))
+ {
+ PJ_ASSERT_RETURN(param->input_latency_ms &&
+ param->output_latency_ms,
+ PJMEDIA_EAUD_BADLATENCY);
+
+ strm->input_latency = param->input_latency_ms;
+ strm->output_latency = param->output_latency_ms;
+
+ status = pjmedia_snd_set_latency(param->input_latency_ms,
+ param->output_latency_ms);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return status;
+ }
+ }
+
+ /* Open the stream */
+ if (param->dir == PJMEDIA_DIR_CAPTURE) {
+ status = pjmedia_snd_open_rec(param->rec_id,
+ param->clock_rate,
+ param->channel_count,
+ param->samples_per_frame,
+ param->bits_per_sample,
+ &snd_rec_cb,
+ strm,
+ &strm->snd_strm);
+ } else if (param->dir == PJMEDIA_DIR_PLAYBACK) {
+ status = pjmedia_snd_open_player(param->play_id,
+ param->clock_rate,
+ param->channel_count,
+ param->samples_per_frame,
+ param->bits_per_sample,
+ &snd_play_cb,
+ strm,
+ &strm->snd_strm);
+
+ } else if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+ status = pjmedia_snd_open(param->rec_id,
+ param->play_id,
+ param->clock_rate,
+ param->channel_count,
+ param->samples_per_frame,
+ param->bits_per_sample,
+ &snd_rec_cb,
+ &snd_play_cb,
+ strm,
+ &strm->snd_strm);
+ } else {
+ pj_assert(!"Invalid direction!");
+ return PJ_EINVAL;
+ }
+
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return status;
+ }
+
+ *p_aud_strm = &strm->base;
+ return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t stream_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
+{
+ struct legacy_stream *strm = (struct legacy_stream*)s;
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ if (strm->input_latency) {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+ pi->input_latency_ms = strm->input_latency;
+ } else {
+ pi->flags &= ~PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+ }
+
+ if (strm->output_latency) {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ pi->output_latency_ms = strm->output_latency;
+ } else {
+ pi->flags &= ~PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct legacy_stream *strm = (struct legacy_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_CAPTURE))
+ {
+ /* Recording latency */
+ if (strm->input_latency) {
+ *(unsigned*)pval = strm->input_latency;
+ return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+ /* Playback latency */
+ if (strm->output_latency) {
+ *(unsigned*)pval = strm->output_latency;
+ return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+ } else {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+}
+
+/* API: set capability */
+static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ const void *pval)
+{
+ PJ_UNUSED_ARG(s);
+ PJ_UNUSED_ARG(cap);
+ PJ_UNUSED_ARG(pval);
+ return PJMEDIA_EAUD_INVCAP;
+}
+
+/* API: Start stream. */
+static pj_status_t stream_start(pjmedia_aud_stream *s)
+{
+ struct legacy_stream *strm = (struct legacy_stream*)s;
+ return pjmedia_snd_stream_start(strm->snd_strm);
+}
+
+/* API: Stop stream. */
+static pj_status_t stream_stop(pjmedia_aud_stream *s)
+{
+ struct legacy_stream *strm = (struct legacy_stream*)s;
+ return pjmedia_snd_stream_stop(strm->snd_strm);
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t stream_destroy(pjmedia_aud_stream *s)
+{
+ struct legacy_stream *strm = (struct legacy_stream*)s;
+ pj_status_t status;
+
+ status = pjmedia_snd_stream_close(strm->snd_strm);
+
+ if (status == PJ_SUCCESS) {
+ pj_pool_t *pool = strm->pool;
+
+ strm->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return status;
+}
+
+#endif /* PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE */
+
diff --git a/pjmedia/src/pjmedia/pasound.c b/pjmedia/src/pjmedia-audiodev/pa_dev.c
index a004008d..5644e1f1 100644
--- a/pjmedia/src/pjmedia/pasound.c
+++ b/pjmedia/src/pjmedia-audiodev/pa_dev.c
@@ -17,36 +17,36 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
-#include <pjmedia/sound.h>
-#include <pjmedia/errno.h>
+#include <pjmedia-audiodev/audiodev_imp.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/string.h>
#include <portaudio.h>
-#if PJMEDIA_SOUND_IMPLEMENTATION==PJMEDIA_SOUND_PORTAUDIO_SOUND
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
-#define THIS_FILE "pasound.c"
-static int snd_init_count;
+#define THIS_FILE "pa_dev.c"
+#define DRIVER_NAME "PA"
-/* Latency settings */
-static unsigned snd_input_latency = PJMEDIA_SND_DEFAULT_REC_LATENCY;
-static unsigned snd_output_latency = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
-
-static struct snd_mgr
+struct pa_aud_factory
{
- pj_pool_factory *factory;
-} snd_mgr;
+ pjmedia_aud_dev_factory base;
+ pj_pool_factory *pf;
+ pj_pool_t *pool;
+};
+
/*
* Sound stream descriptor.
* This struct may be used for both unidirectional or bidirectional sound
* streams.
*/
-struct pjmedia_snd_stream
+struct pa_aud_stream
{
+ pjmedia_aud_stream base;
+
pj_pool_t *pool;
pj_str_t name;
pjmedia_dir dir;
@@ -61,11 +61,11 @@ struct pjmedia_snd_stream
PaStream *play_strm;
void *user_data;
- pjmedia_snd_rec_cb rec_cb;
- pjmedia_snd_play_cb play_cb;
+ pjmedia_aud_rec_cb rec_cb;
+ pjmedia_aud_play_cb play_cb;
- pj_uint32_t play_timestamp;
- pj_uint32_t rec_timestamp;
+ pj_timestamp play_timestamp;
+ pj_timestamp rec_timestamp;
pj_uint32_t underflow;
pj_uint32_t overflow;
@@ -98,6 +98,59 @@ struct pjmedia_snd_stream
};
+/* Factory prototypes */
+static pj_status_t pa_init(pjmedia_aud_dev_factory *f);
+static pj_status_t pa_destroy(pjmedia_aud_dev_factory *f);
+static unsigned pa_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t pa_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t pa_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t pa_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm);
+
+/* Stream prototypes */
+static pj_status_t strm_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t strm_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t strm_start(pjmedia_aud_stream *strm);
+static pj_status_t strm_stop(pjmedia_aud_stream *strm);
+static pj_status_t strm_destroy(pjmedia_aud_stream *strm);
+
+
+static pjmedia_aud_dev_factory_op pa_op =
+{
+ &pa_init,
+ &pa_destroy,
+ &pa_get_dev_count,
+ &pa_get_dev_info,
+ &pa_default_param,
+ &pa_create_stream
+};
+
+static pjmedia_aud_stream_op pa_strm_op =
+{
+ &strm_get_param,
+ &strm_get_cap,
+ &strm_set_cap,
+ &strm_start,
+ &strm_stop,
+ &strm_destroy
+};
+
+
+
static int PaRecorderCallback(const void *input,
void *output,
unsigned long frameCount,
@@ -105,7 +158,7 @@ static int PaRecorderCallback(const void *input,
PaStreamCallbackFlags statusFlags,
void *userData )
{
- pjmedia_snd_stream *stream = (pjmedia_snd_stream*) userData;
+ struct pa_aud_stream *stream = (struct pa_aud_stream*) userData;
pj_status_t status = 0;
unsigned nsamples;
@@ -138,8 +191,6 @@ static int PaRecorderCallback(const void *input,
if (statusFlags & paInputOverflow)
++stream->overflow;
- stream->rec_timestamp += frameCount;
-
/* Calculate number of samples we've got */
nsamples = frameCount * stream->channel_count + stream->rec_buf_count;
@@ -150,30 +201,43 @@ static int PaRecorderCallback(const void *input,
*/
if (stream->rec_buf_count) {
unsigned chunk_count = 0;
+ pjmedia_frame frame;
chunk_count = stream->samples_per_frame - stream->rec_buf_count;
pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count,
(pj_int16_t*)input, chunk_count);
- status = (*stream->rec_cb)(stream->user_data,
- stream->rec_timestamp,
- (void*) stream->rec_buf,
- stream->samples_per_frame *
- stream->bytes_per_sample);
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = (void*) stream->rec_buf;
+ frame.size = stream->samples_per_frame * stream->bytes_per_sample;
+ frame.timestamp.u64 = stream->rec_timestamp.u64;
+ frame.bit_info = 0;
+
+ status = (*stream->rec_cb)(stream->user_data, &frame);
input = (pj_int16_t*) input + chunk_count;
nsamples -= stream->samples_per_frame;
stream->rec_buf_count = 0;
+ stream->rec_timestamp.u64 += stream->samples_per_frame /
+ stream->channel_count;
}
/* Give all frames we have */
while (nsamples >= stream->samples_per_frame && status == 0) {
- status = (*stream->rec_cb)(stream->user_data,
- stream->rec_timestamp,
- (void*) input,
- stream->samples_per_frame *
- stream->bytes_per_sample);
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = (void*) input;
+ frame.size = stream->samples_per_frame * stream->bytes_per_sample;
+ frame.timestamp.u64 = stream->rec_timestamp.u64;
+ frame.bit_info = 0;
+
+ status = (*stream->rec_cb)(stream->user_data, &frame);
+
input = (pj_int16_t*) input + stream->samples_per_frame;
nsamples -= stream->samples_per_frame;
+ stream->rec_timestamp.u64 += stream->samples_per_frame /
+ stream->channel_count;
}
/* Store the remaining samples into the buffer */
@@ -206,7 +270,7 @@ static int PaPlayerCallback( const void *input,
PaStreamCallbackFlags statusFlags,
void *userData )
{
- pjmedia_snd_stream *stream = (pjmedia_snd_stream*) userData;
+ struct pa_aud_stream *stream = (struct pa_aud_stream*) userData;
pj_status_t status = 0;
unsigned nsamples_req = frameCount * stream->channel_count;
@@ -239,7 +303,6 @@ static int PaPlayerCallback( const void *input,
if (statusFlags & paOutputOverflow)
++stream->overflow;
- stream->play_timestamp += frameCount;
/* Check if any buffered samples */
if (stream->play_buf_count) {
@@ -267,19 +330,39 @@ static int PaPlayerCallback( const void *input,
/* Fill output buffer as requested */
while (nsamples_req && status == 0) {
if (nsamples_req >= stream->samples_per_frame) {
- status = (*stream->play_cb)(stream->user_data,
- stream->play_timestamp,
- output,
- stream->samples_per_frame *
- stream->bytes_per_sample);
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = output;
+ frame.size = stream->samples_per_frame * stream->bytes_per_sample;
+ frame.timestamp.u64 = stream->play_timestamp.u64;
+ frame.bit_info = 0;
+
+ status = (*stream->play_cb)(stream->user_data, &frame);
+ if (status != PJ_SUCCESS)
+ goto on_break;
+
+ if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ pj_bzero(frame.buf, frame.size);
+
nsamples_req -= stream->samples_per_frame;
output = (pj_int16_t*)output + stream->samples_per_frame;
} else {
- status = (*stream->play_cb)(stream->user_data,
- stream->play_timestamp,
- stream->play_buf,
- stream->samples_per_frame *
- stream->bytes_per_sample);
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = stream->play_buf;
+ frame.size = stream->samples_per_frame * stream->bytes_per_sample;
+ frame.timestamp.u64 = stream->play_timestamp.u64;
+ frame.bit_info = 0;
+
+ status = (*stream->play_cb)(stream->user_data, &frame);
+ if (status != PJ_SUCCESS)
+ goto on_break;
+
+ if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ pj_bzero(frame.buf, frame.size);
+
pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
nsamples_req);
stream->play_buf_count = stream->samples_per_frame - nsamples_req;
@@ -288,6 +371,9 @@ static int PaPlayerCallback( const void *input,
stream->play_buf_count);
nsamples_req = 0;
}
+
+ stream->play_timestamp.u64 += stream->samples_per_frame /
+ stream->channel_count;
}
if (status==0)
@@ -330,67 +416,147 @@ static void pa_log_cb(const char *log)
typedef void (*PaUtilLogCallback ) (const char *log);
void PaUtil_SetDebugPrintFunction(PaUtilLogCallback cb);
+
/*
- * Init sound library.
+ * Init PortAudio audio driver.
*/
-PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
+pjmedia_aud_dev_factory* pjmedia_pa_factory(pj_pool_factory *pf)
{
- if (++snd_init_count == 1) {
- int err;
+ struct pa_aud_factory *f;
+ pj_pool_t *pool;
- PaUtil_SetDebugPrintFunction(&pa_log_cb);
+ pool = pj_pool_create(pf, "portaudio", 64, 64, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct pa_aud_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &pa_op;
- snd_mgr.factory = factory;
- err = Pa_Initialize();
+ return &f->base;
+}
- PJ_LOG(4,(THIS_FILE,
- "PortAudio sound library initialized, status=%d", err));
- PJ_LOG(4,(THIS_FILE, "PortAudio host api count=%d",
- Pa_GetHostApiCount()));
- PJ_LOG(4,(THIS_FILE, "Sound device count=%d",
- pjmedia_snd_get_dev_count()));
- return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
- } else {
- return PJ_SUCCESS;
- }
+/* API: Init factory */
+static pj_status_t pa_init(pjmedia_aud_dev_factory *f)
+{
+ int err;
+
+ PJ_UNUSED_ARG(f);
+
+ PaUtil_SetDebugPrintFunction(&pa_log_cb);
+
+ err = Pa_Initialize();
+
+ PJ_LOG(4,(THIS_FILE,
+ "PortAudio sound library initialized, status=%d", err));
+ PJ_LOG(4,(THIS_FILE, "PortAudio host api count=%d",
+ Pa_GetHostApiCount()));
+ PJ_LOG(4,(THIS_FILE, "Sound device count=%d",
+ pa_get_dev_count(f)));
+
+ return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
}
-/*
- * Get device count.
- */
-PJ_DEF(int) pjmedia_snd_get_dev_count(void)
+/* API: Destroy factory */
+static pj_status_t pa_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct pa_aud_factory *pa = (struct pa_aud_factory*)f;
+ pj_pool_t *pool;
+ int err;
+
+ PJ_LOG(4,(THIS_FILE, "PortAudio sound library shutting down.."));
+
+ err = Pa_Terminate();
+
+ pool = pa->pool;
+ pa->pool = NULL;
+ pj_pool_release(pool);
+
+ return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+
+/* API: Get device count. */
+static unsigned pa_get_dev_count(pjmedia_aud_dev_factory *f)
{
int count = Pa_GetDeviceCount();
+ PJ_UNUSED_ARG(f);
return count < 0 ? 0 : count;
}
-/*
- * Get device info.
- */
-PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
+/* API: Get device info. */
+static pj_status_t pa_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
{
- static pjmedia_snd_dev_info info;
const PaDeviceInfo *pa_info;
+ PJ_UNUSED_ARG(f);
+
pa_info = Pa_GetDeviceInfo(index);
if (!pa_info)
- return NULL;
+ return PJMEDIA_EAUD_INVDEV;
+
+ pj_bzero(info, sizeof(*info));
+ strncpy(info->name, pa_info->name, sizeof(info->name));
+ info->name[sizeof(info->name)-1] = '\0';
+ info->input_count = pa_info->maxInputChannels;
+ info->output_count = pa_info->maxOutputChannels;
+ info->default_samples_per_sec = (unsigned)pa_info->defaultSampleRate;
+ strncpy(info->driver, DRIVER_NAME, sizeof(info->driver));
+ info->driver[sizeof(info->driver)-1] = '\0';
+ info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+
+ return PJ_SUCCESS;
+}
- pj_bzero(&info, sizeof(info));
- strncpy(info.name, pa_info->name, sizeof(info.name));
- info.name[sizeof(info.name)-1] = '\0';
- info.input_count = pa_info->maxInputChannels;
- info.output_count = pa_info->maxOutputChannels;
- info.default_samples_per_sec = (unsigned)pa_info->defaultSampleRate;
- return &info;
+/* API: fill in with default parameter. */
+static pj_status_t pa_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ pjmedia_aud_dev_info adi;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(f);
+
+ status = pa_get_dev_info(f, index, &adi);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_bzero(param, sizeof(*param));
+ if (adi.input_count && adi.output_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ } else if (adi.input_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->rec_id = index;
+ param->play_id = PJMEDIA_AUD_INVALID_DEV;
+ } else if (adi.output_count) {
+ param->dir = PJMEDIA_DIR_PLAYBACK;
+ param->play_id = index;
+ param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+ } else {
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ param->clock_rate = adi.default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = adi.default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = 16;
+ param->flags = adi.caps;
+ param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+ return PJ_SUCCESS;
}
-/* Get PortAudio default input device ID */
+/* Internal: Get PortAudio default input device ID */
static int pa_get_default_input_dev(int channel_count)
{
int i, count;
@@ -434,7 +600,7 @@ static int pa_get_default_input_dev(int channel_count)
}
/* If still no device is found, enumerate all devices */
- count = pjmedia_snd_get_dev_count();
+ count = Pa_GetDeviceCount();
for (i=0; i<count; ++i) {
const PaDeviceInfo *paDevInfo;
@@ -446,7 +612,7 @@ static int pa_get_default_input_dev(int channel_count)
return -1;
}
-/* Get PortAudio default output device ID */
+/* Internal: Get PortAudio default output device ID */
static int pa_get_default_output_dev(int channel_count)
{
int i, count;
@@ -490,7 +656,7 @@ static int pa_get_default_output_dev(int channel_count)
}
/* If still no device is found, enumerate all devices */
- count = pjmedia_snd_get_dev_count();
+ count = Pa_GetDeviceCount();
for (i=0; i<count; ++i) {
const PaDeviceInfo *paDevInfo;
@@ -503,20 +669,16 @@ static int pa_get_default_output_dev(int channel_count)
}
-/*
- * Open stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_rec_cb rec_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
+/* Internal: create capture/recorder stream */
+static pj_status_t create_rec_stream( struct pa_aud_factory *pa,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_snd_strm)
{
pj_pool_t *pool;
- pjmedia_snd_stream *stream;
+ pjmedia_aud_dev_index rec_id;
+ struct pa_aud_stream *stream;
PaStreamParameters inputParam;
int sampleFormat;
const PaDeviceInfo *paDevInfo = NULL;
@@ -525,44 +687,47 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
const PaStreamInfo *paSI;
PaError err;
- if (index < 0) {
- index = pa_get_default_input_dev(channel_count);
- if (index < 0) {
+ PJ_ASSERT_RETURN(rec_cb && p_snd_strm, PJ_EINVAL);
+
+ rec_id = param->rec_id;
+ if (rec_id < 0) {
+ rec_id = pa_get_default_input_dev(param->channel_count);
+ if (rec_id < 0) {
/* No such device. */
- return PJMEDIA_ENOSNDREC;
+ return PJMEDIA_EAUD_NODEFDEV;
}
}
- paDevInfo = Pa_GetDeviceInfo(index);
+ paDevInfo = Pa_GetDeviceInfo(rec_id);
if (!paDevInfo) {
/* Assumed it is "No such device" error. */
- return PJMEDIA_ESNDINDEVID;
+ return PJMEDIA_EAUD_INVDEV;
}
- if (bits_per_sample == 8)
+ if (param->bits_per_sample == 8)
sampleFormat = paUInt8;
- else if (bits_per_sample == 16)
+ else if (param->bits_per_sample == 16)
sampleFormat = paInt16;
- else if (bits_per_sample == 32)
+ else if (param->bits_per_sample == 32)
sampleFormat = paInt32;
else
- return PJMEDIA_ESNDINSAMPLEFMT;
+ return PJMEDIA_EAUD_SAMPFORMAT;
- pool = pj_pool_create( snd_mgr.factory, "sndstream", 1024, 1024, NULL);
+ pool = pj_pool_create(pa->pf, "recstrm", 1024, 1024, NULL);
if (!pool)
return PJ_ENOMEM;
- stream = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_stream);
+ stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
stream->pool = pool;
pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
stream->dir = PJMEDIA_DIR_CAPTURE;
- stream->rec_id = index;
+ stream->rec_id = rec_id;
stream->play_id = -1;
stream->user_data = user_data;
- stream->samples_per_sec = clock_rate;
- stream->samples_per_frame = samples_per_frame;
- stream->bytes_per_sample = bits_per_sample / 8;
- stream->channel_count = channel_count;
+ stream->samples_per_sec = param->clock_rate;
+ stream->samples_per_frame = param->samples_per_frame;
+ stream->bytes_per_sample = param->bits_per_sample / 8;
+ stream->channel_count = param->channel_count;
stream->rec_cb = rec_cb;
stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool,
@@ -570,23 +735,26 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
stream->rec_buf_count = 0;
pj_bzero(&inputParam, sizeof(inputParam));
- inputParam.device = index;
- inputParam.channelCount = channel_count;
+ inputParam.device = rec_id;
+ inputParam.channelCount = param->channel_count;
inputParam.hostApiSpecificStreamInfo = NULL;
inputParam.sampleFormat = sampleFormat;
- inputParam.suggestedLatency = snd_input_latency / 1000.0;
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
+ inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
+ else
+ inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
/* Frames in PortAudio is number of samples in a single channel */
- paFrames = samples_per_frame / channel_count;
+ paFrames = param->samples_per_frame / param->channel_count;
err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
- clock_rate, paFrames,
+ param->clock_rate, paFrames,
paClipOff, &PaRecorderCallback, stream );
if (err != paNoError) {
pj_pool_release(pool);
- return PJMEDIA_ERRNO_FROM_PORTAUDIO(err);
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
}
paSI = Pa_GetStreamInfo(stream->rec_strm);
@@ -597,26 +765,25 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
"rate=%d, ch=%d, "
"bits=%d, %d samples per frame, latency=%d ms",
paDevInfo->name, paHostApiInfo->name,
- paRate, channel_count,
- bits_per_sample, samples_per_frame,
+ paRate, param->channel_count,
+ param->bits_per_sample, param->samples_per_frame,
paLatency));
- *p_snd_strm = stream;
+ *p_snd_strm = &stream->base;
return PJ_SUCCESS;
}
-PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_play_cb play_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
+/* Internal: create playback stream */
+static pj_status_t create_play_stream(struct pa_aud_factory *pa,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_snd_strm)
{
pj_pool_t *pool;
- pjmedia_snd_stream *stream;
+ pjmedia_aud_dev_index play_id;
+ struct pa_aud_stream *stream;
PaStreamParameters outputParam;
int sampleFormat;
const PaDeviceInfo *paDevInfo = NULL;
@@ -625,68 +792,75 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
unsigned paFrames, paRate, paLatency;
PaError err;
- if (index < 0) {
- index = pa_get_default_output_dev(channel_count);
- if (index < 0) {
+ PJ_ASSERT_RETURN(play_cb && p_snd_strm, PJ_EINVAL);
+
+ play_id = param->play_id;
+ if (play_id < 0) {
+ play_id = pa_get_default_output_dev(param->channel_count);
+ if (play_id < 0) {
/* No such device. */
- return PJMEDIA_ENOSNDPLAY;
+ return PJMEDIA_EAUD_NODEFDEV;
}
}
- paDevInfo = Pa_GetDeviceInfo(index);
+ paDevInfo = Pa_GetDeviceInfo(play_id);
if (!paDevInfo) {
/* Assumed it is "No such device" error. */
- return PJMEDIA_ESNDINDEVID;
+ return PJMEDIA_EAUD_INVDEV;
}
- if (bits_per_sample == 8)
+ if (param->bits_per_sample == 8)
sampleFormat = paUInt8;
- else if (bits_per_sample == 16)
+ else if (param->bits_per_sample == 16)
sampleFormat = paInt16;
- else if (bits_per_sample == 32)
+ else if (param->bits_per_sample == 32)
sampleFormat = paInt32;
else
- return PJMEDIA_ESNDINSAMPLEFMT;
+ return PJMEDIA_EAUD_SAMPFORMAT;
- pool = pj_pool_create( snd_mgr.factory, "sndstream", 1024, 1024, NULL);
+ pool = pj_pool_create(pa->pf, "playstrm", 1024, 1024, NULL);
if (!pool)
return PJ_ENOMEM;
- stream = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_stream);
+ stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
stream->pool = pool;
pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
stream->dir = PJMEDIA_DIR_PLAYBACK;
- stream->play_id = index;
+ stream->play_id = play_id;
stream->rec_id = -1;
stream->user_data = user_data;
- stream->samples_per_sec = clock_rate;
- stream->samples_per_frame = samples_per_frame;
- stream->bytes_per_sample = bits_per_sample / 8;
- stream->channel_count = channel_count;
+ stream->samples_per_sec = param->clock_rate;
+ stream->samples_per_frame = param->samples_per_frame;
+ stream->bytes_per_sample = param->bits_per_sample / 8;
+ stream->channel_count = param->channel_count;
stream->play_cb = play_cb;
stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool,
- stream->samples_per_frame * stream->bytes_per_sample);
+ stream->samples_per_frame *
+ stream->bytes_per_sample);
stream->play_buf_count = 0;
pj_bzero(&outputParam, sizeof(outputParam));
- outputParam.device = index;
- outputParam.channelCount = channel_count;
+ outputParam.device = play_id;
+ outputParam.channelCount = param->channel_count;
outputParam.hostApiSpecificStreamInfo = NULL;
outputParam.sampleFormat = sampleFormat;
- outputParam.suggestedLatency = snd_output_latency / 1000.0;
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
+ outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
+ else
+ outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
/* Frames in PortAudio is number of samples in a single channel */
- paFrames = samples_per_frame / channel_count;
+ paFrames = param->samples_per_frame / param->channel_count;
err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
- clock_rate, paFrames,
+ param->clock_rate, paFrames,
paClipOff, &PaPlayerCallback, stream );
if (err != paNoError) {
pj_pool_release(pool);
- return PJMEDIA_ERRNO_FROM_PORTAUDIO(err);
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
}
paSI = Pa_GetStreamInfo(stream->play_strm);
@@ -696,32 +870,28 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
PJ_LOG(5,(THIS_FILE, "Opened device %d: %s(%s) for playing, sample rate=%d"
", ch=%d, "
"bits=%d, %d samples per frame, latency=%d ms",
- index, paDevInfo->name, paHostApiInfo->name,
- paRate, channel_count,
- bits_per_sample, samples_per_frame, paLatency));
+ play_id, paDevInfo->name, paHostApiInfo->name,
+ paRate, param->channel_count,
+ param->bits_per_sample, param->samples_per_frame,
+ paLatency));
- *p_snd_strm = stream;
+ *p_snd_strm = &stream->base;
return PJ_SUCCESS;
}
-/*
- * Open both player and recorder.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id,
- int play_id,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_rec_cb rec_cb,
- pjmedia_snd_play_cb play_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
+/* Internal: Create both player and recorder stream */
+static pj_status_t create_bidir_stream(struct pa_aud_factory *pa,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_snd_strm)
{
pj_pool_t *pool;
- pjmedia_snd_stream *stream;
+ pjmedia_aud_dev_index rec_id, play_id;
+ struct pa_aud_stream *stream;
PaStream *paStream = NULL;
PaStreamParameters inputParam;
PaStreamParameters outputParam;
@@ -734,59 +904,63 @@ PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id,
unsigned paFrames, paRate, paInputLatency, paOutputLatency;
PaError err;
+ PJ_ASSERT_RETURN(play_cb && rec_cb && p_snd_strm, PJ_EINVAL);
+
+ rec_id = param->rec_id;
if (rec_id < 0) {
- rec_id = pa_get_default_input_dev(channel_count);
+ rec_id = pa_get_default_input_dev(param->channel_count);
if (rec_id < 0) {
/* No such device. */
- return PJMEDIA_ENOSNDREC;
+ return PJMEDIA_EAUD_NODEFDEV;
}
}
paRecDevInfo = Pa_GetDeviceInfo(rec_id);
if (!paRecDevInfo) {
/* Assumed it is "No such device" error. */
- return PJMEDIA_ESNDINDEVID;
+ return PJMEDIA_EAUD_INVDEV;
}
+ play_id = param->play_id;
if (play_id < 0) {
- play_id = pa_get_default_output_dev(channel_count);
+ play_id = pa_get_default_output_dev(param->channel_count);
if (play_id < 0) {
/* No such device. */
- return PJMEDIA_ENOSNDPLAY;
+ return PJMEDIA_EAUD_NODEFDEV;
}
}
paPlayDevInfo = Pa_GetDeviceInfo(play_id);
if (!paPlayDevInfo) {
/* Assumed it is "No such device" error. */
- return PJMEDIA_ESNDINDEVID;
+ return PJMEDIA_EAUD_INVDEV;
}
- if (bits_per_sample == 8)
+ if (param->bits_per_sample == 8)
sampleFormat = paUInt8;
- else if (bits_per_sample == 16)
+ else if (param->bits_per_sample == 16)
sampleFormat = paInt16;
- else if (bits_per_sample == 32)
+ else if (param->bits_per_sample == 32)
sampleFormat = paInt32;
else
- return PJMEDIA_ESNDINSAMPLEFMT;
+ return PJMEDIA_EAUD_SAMPFORMAT;
- pool = pj_pool_create( snd_mgr.factory, "sndstream", 1024, 1024, NULL);
+ pool = pj_pool_create(pa->pf, "sndstream", 1024, 1024, NULL);
if (!pool)
return PJ_ENOMEM;
- stream = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_stream);
+ stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
stream->pool = pool;
pj_strdup2_with_null(pool, &stream->name, paRecDevInfo->name);
stream->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
stream->play_id = play_id;
stream->rec_id = rec_id;
stream->user_data = user_data;
- stream->samples_per_sec = clock_rate;
- stream->samples_per_frame = samples_per_frame;
- stream->bytes_per_sample = bits_per_sample / 8;
- stream->channel_count = channel_count;
+ stream->samples_per_sec = param->clock_rate;
+ stream->samples_per_frame = param->samples_per_frame;
+ stream->bytes_per_sample = param->bits_per_sample / 8;
+ stream->channel_count = param->channel_count;
stream->rec_cb = rec_cb;
stream->play_cb = play_cb;
@@ -800,31 +974,37 @@ PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id,
pj_bzero(&inputParam, sizeof(inputParam));
inputParam.device = rec_id;
- inputParam.channelCount = channel_count;
+ inputParam.channelCount = param->channel_count;
inputParam.hostApiSpecificStreamInfo = NULL;
inputParam.sampleFormat = sampleFormat;
- inputParam.suggestedLatency = snd_input_latency / 1000.0;
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
+ inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
+ else
+ inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
paRecHostApiInfo = Pa_GetHostApiInfo(paRecDevInfo->hostApi);
pj_bzero(&outputParam, sizeof(outputParam));
outputParam.device = play_id;
- outputParam.channelCount = channel_count;
+ outputParam.channelCount = param->channel_count;
outputParam.hostApiSpecificStreamInfo = NULL;
outputParam.sampleFormat = sampleFormat;
- outputParam.suggestedLatency = snd_output_latency / 1000.0;
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
+ outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
+ else
+ outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
paPlayHostApiInfo = Pa_GetHostApiInfo(paPlayDevInfo->hostApi);
/* Frames in PortAudio is number of samples in a single channel */
- paFrames = samples_per_frame / channel_count;
+ paFrames = param->samples_per_frame / param->channel_count;
/* If both input and output are on the same device, open a single stream
* for both input and output.
*/
if (rec_id == play_id) {
err = Pa_OpenStream( &paStream, &inputParam, &outputParam,
- clock_rate, paFrames,
+ param->clock_rate, paFrames,
paClipOff, &PaRecorderPlayerCallback, stream );
if (err == paNoError) {
/* Set play stream and record stream to the same stream */
@@ -841,12 +1021,12 @@ PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id,
if (paStream == NULL) {
/* Open input stream */
err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
- clock_rate, paFrames,
+ param->clock_rate, paFrames,
paClipOff, &PaRecorderCallback, stream );
if (err == paNoError) {
/* Open output stream */
err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
- clock_rate, paFrames,
+ param->clock_rate, paFrames,
paClipOff, &PaPlayerCallback, stream );
if (err != paNoError)
Pa_CloseStream(stream->rec_strm);
@@ -855,7 +1035,7 @@ PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id,
if (err != paNoError) {
pj_pool_release(pool);
- return PJMEDIA_ERRNO_FROM_PORTAUDIO(err);
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
}
paSI = Pa_GetStreamInfo(stream->rec_strm);
@@ -870,23 +1050,52 @@ PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id,
"output latency=%d ms",
paRecDevInfo->name, paRecHostApiInfo->name,
paPlayDevInfo->name, paPlayHostApiInfo->name,
- paRate, channel_count,
- bits_per_sample, samples_per_frame,
+ paRate, param->channel_count,
+ param->bits_per_sample, param->samples_per_frame,
paInputLatency, paOutputLatency));
- *p_snd_strm = stream;
+ *p_snd_strm = &stream->base;
+
+ return PJ_SUCCESS;
+}
+/* API: create stream */
+static pj_status_t pa_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ struct pa_aud_factory *pa = (struct pa_aud_factory*)f;
+ pj_status_t status;
+
+ if (param->dir == PJMEDIA_DIR_CAPTURE) {
+ status = create_rec_stream(pa, param, rec_cb, user_data, p_aud_strm);
+ } else if (param->dir == PJMEDIA_DIR_PLAYBACK) {
+ status = create_play_stream(pa, param, play_cb, user_data, p_aud_strm);
+ } else if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+ status = create_bidir_stream(pa, param, rec_cb, play_cb, user_data,
+ p_aud_strm);
+ } else {
+ return PJ_EINVAL;
+ }
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ (*p_aud_strm)->op = &pa_strm_op;
+
return PJ_SUCCESS;
}
-/*
- * Get stream info.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
- pjmedia_snd_stream_info *pi)
+/* API: Get stream parameters */
+static pj_status_t strm_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
{
+ struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
const PaStreamInfo *paPlaySI = NULL, *paRecSI = NULL;
PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
@@ -908,20 +1117,68 @@ PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
pi->channel_count = strm->channel_count;
pi->samples_per_frame = strm->samples_per_frame;
pi->bits_per_sample = strm->bytes_per_sample * 8;
- pi->rec_latency = (unsigned)(paRecSI ? paRecSI->inputLatency *
- paRecSI->sampleRate : 0);
- pi->play_latency = (unsigned)(paPlaySI ? paPlaySI->outputLatency *
- paPlaySI->sampleRate : 0);
+ if (paRecSI) {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+ pi->input_latency_ms = (unsigned)(paRecSI ? paRecSI->inputLatency *
+ 1000 : 0);
+ }
+ if (paPlaySI) {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ pi->output_latency_ms = (unsigned)(paPlaySI? paPlaySI->outputLatency *
+ 1000 : 0);
+ }
return PJ_SUCCESS;
}
-/*
- * Start stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
+/* API: get capability */
+static pj_status_t strm_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && strm->rec_strm) {
+ const PaStreamInfo *si = Pa_GetStreamInfo(strm->rec_strm);
+ if (!si)
+ return PJMEDIA_EAUD_SYSERR;
+
+ *(unsigned*)pval = (unsigned)(si->inputLatency * 1000);
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && strm->play_strm) {
+ const PaStreamInfo *si = Pa_GetStreamInfo(strm->play_strm);
+ if (!si)
+ return PJMEDIA_EAUD_SYSERR;
+
+ *(unsigned*)pval = (unsigned)(si->outputLatency * 1000);
+ return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+}
+
+
+/* API: set capability */
+static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value)
+{
+ PJ_UNUSED_ARG(strm);
+ PJ_UNUSED_ARG(cap);
+ PJ_UNUSED_ARG(value);
+
+ /* Nothing is supported */
+ return PJMEDIA_EAUD_INVCAP;
+}
+
+
+/* API: start stream. */
+static pj_status_t strm_start(pjmedia_aud_stream *s)
{
+ struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
int err = 0;
PJ_LOG(5,(THIS_FILE, "Starting %s stream..", stream->name.ptr));
@@ -937,14 +1194,14 @@ PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
- return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+ return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
}
-/*
- * Stop stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
+
+/* API: stop stream. */
+static pj_status_t strm_stop(pjmedia_aud_stream *s)
{
+ struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
int i, err = 0;
stream->quit_flag = 1;
@@ -968,14 +1225,14 @@ PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
- return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+ return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
}
-/*
- * Destroy stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
+
+/* API: destroy stream. */
+static pj_status_t strm_destroy(pjmedia_aud_stream *s)
{
+ struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
int i, err = 0;
stream->quit_flag = 1;
@@ -999,39 +1256,8 @@ PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
pj_pool_release(stream->pool);
- return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+ return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
}
-/*
- * Deinitialize sound library.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
-{
- if (--snd_init_count == 0) {
- int err;
-
- PJ_LOG(4,(THIS_FILE, "PortAudio sound library shutting down.."));
-
- err = Pa_Terminate();
-
- return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
- } else {
- return PJ_SUCCESS;
- }
-}
-
-/*
- * Set sound latency.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency,
- unsigned output_latency)
-{
- snd_input_latency = (input_latency == 0)?
- PJMEDIA_SND_DEFAULT_REC_LATENCY : input_latency;
- snd_output_latency = (output_latency == 0)?
- PJMEDIA_SND_DEFAULT_PLAY_LATENCY : output_latency;
-
- return PJ_SUCCESS;
-}
+#endif /* PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO */
-#endif /* PJMEDIA_SOUND_IMPLEMENTATION */
diff --git a/pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h b/pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h
new file mode 100644
index 00000000..ae13bb11
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h
@@ -0,0 +1,171 @@
+#ifndef __BITSTREAM_H_
+#define __BITSTREAM_H_
+
+#define KPackedFrameLen 10
+#define KUnpackedFrameLen 22
+
+// Below values are taken from the APS design document
+const TUint8 KG729FullPayloadBits[] = { 8, 10, 8, 1, 13, 4, 7, 5, 13, 4, 7 };
+const TUint KNumFullFrameParams = 11;
+const TUint8 KG729SIDPayloadBits[] = { 1, 5, 4, 5 };
+const TUint KNumSIDFrameParams = 4;
+
+/*!
+ @class TBitStream
+
+ @discussion Provides compression from 16-bit-word-aligned G.729 audio frames
+ (used in S60 G.729 DSP codec) to 8-bit stream, and vice versa.
+ */
+class TBitStream
+ {
+public:
+ /*!
+ @function TBitStream
+
+ @discussion Constructor
+ */
+ TBitStream():iDes(iData,KUnpackedFrameLen){}
+ /*!
+ @function CompressG729Frame
+
+ @discussion Compress either a 22-byte G.729 full rate frame to 10 bytes
+ or a 8-byte G.729 Annex.B SID frame to 2 bytes.
+ @param aSrc Reference to the uncompressed source frame data
+ @param aIsSIDFrame True if the source is a SID frame
+ @result a reference to the compressed frame
+ */
+ const TDesC8& CompressG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame = EFalse );
+
+ /*!
+ @function ExpandG729Frame
+
+ @discussion Expand a 10-byte G.729 full rate frame to 22 bytes
+ or a 2-byte G.729 Annex.B SID frame to 8(22) bytes.
+ @param aSrc Reference to the compressed source frame data
+ @param aIsSIDFrame True if the source is a SID frame
+ @result a reference to a descriptor representing the uncompressed frame.
+ Note that SID frames are zero-padded to 22 bytes as well.
+ */
+ const TDesC8& ExpandG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame = EFalse );
+
+private:
+ void Compress( TUint8 aValue, TUint8 aNumOfBits );
+ void Expand( const TUint8* aSrc, TInt aDstIdx, TUint8 aNumOfBits );
+
+private:
+ TUint8 iData[KUnpackedFrameLen];
+ TPtr8 iDes;
+ TInt iIdx;
+ TInt iBitOffset;
+ };
+
+
+const TDesC8& TBitStream::CompressG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame )
+ {
+ // reset data
+ iDes.FillZ(iDes.MaxLength());
+ iIdx = iBitOffset = 0;
+
+ TInt numParams = (aIsSIDFrame) ? KNumSIDFrameParams : KNumFullFrameParams;
+ const TUint8* p = const_cast<TUint8*>(aSrc.Ptr());
+
+ for(TInt i = 0, pIdx = 0; i < numParams; i++, pIdx += 2)
+ {
+ TUint8 paramBits = (aIsSIDFrame) ? KG729SIDPayloadBits[i] : KG729FullPayloadBits[i];
+ if(paramBits > 8)
+ {
+ Compress(p[pIdx+1], paramBits - 8); // msb
+ paramBits = 8;
+ }
+ Compress(p[pIdx], paramBits); // lsb
+ }
+
+ if( iBitOffset )
+ iIdx++;
+
+ iDes.SetLength(iIdx);
+ return iDes;
+ }
+
+
+const TDesC8& TBitStream::ExpandG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame )
+ {
+ // reset data
+ iDes.FillZ(iDes.MaxLength());
+ iIdx = iBitOffset = 0;
+
+ TInt numParams = (aIsSIDFrame) ? KNumSIDFrameParams : KNumFullFrameParams;
+ const TUint8* p = const_cast<TUint8*>(aSrc.Ptr());
+
+ for(TInt i = 0, dIdx = 0; i < numParams; i++, dIdx += 2)
+ {
+ TUint8 paramBits = (aIsSIDFrame) ? KG729SIDPayloadBits[i] : KG729FullPayloadBits[i];
+ if(paramBits > 8)
+ {
+ Expand(p, dIdx+1, paramBits - 8); // msb
+ paramBits = 8;
+ }
+ Expand(p, dIdx, paramBits); // lsb
+ }
+
+ iDes.SetLength(KUnpackedFrameLen);
+ return iDes;
+ }
+
+
+void TBitStream::Compress( TUint8 aValue, TUint8 aNumOfBits )
+ {
+ // clear bits that will be discarded
+ aValue &= (0xff >> (8 - aNumOfBits));
+
+ // calculate required bitwise left shift
+ TInt shl = 8 - (iBitOffset + aNumOfBits);
+
+ if (shl == 0) // no shift required
+ {
+ iData[iIdx++] |= aValue;
+ iBitOffset = 0;
+ }
+ else if (shl > 0) // bits fit into current byte
+ {
+ iData[iIdx] |= (aValue << shl);
+ iBitOffset += aNumOfBits;
+ }
+ else
+ {
+ iBitOffset = -shl;
+ iData[iIdx] |= (aValue >> iBitOffset); // right shift
+ iData[++iIdx] |= (aValue << (8-iBitOffset)); // push remaining bits to next byte
+ }
+ }
+
+
+void TBitStream::Expand( const TUint8* aSrc, TInt aDstIdx, TUint8 aNumOfBits )
+ {
+ TUint8 aValue = aSrc[iIdx] & (0xff >> iBitOffset);
+
+ // calculate required bitwise right shift
+ TInt shr = 8 - (iBitOffset + aNumOfBits);
+
+ if (shr == 0) // no shift required
+ {
+ iData[aDstIdx] = aValue;
+ iIdx++;
+ iBitOffset = 0;
+ }
+ else if (shr > 0) // right shift
+ {
+ iData[aDstIdx] = (aValue >> shr);
+ iBitOffset += aNumOfBits;
+ }
+ else // shift left and take remaining bits from the next src byte
+ {
+ iBitOffset = -shr;
+ iData[aDstIdx] = aValue << iBitOffset;
+ iData[aDstIdx] |= aSrc[++iIdx] >> (8 - iBitOffset);
+ }
+ }
+
+#endif // __BITSTREAM_H_
+
+// eof
diff --git a/pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp b/pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp
new file mode 100644
index 00000000..bfdec1e5
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp
@@ -0,0 +1,1611 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
+
+#include <e32msgqueue.h>
+#include <sounddevice.h>
+#include <APSClientSession.h>
+#include <pjmedia-codec/amr_helper.h>
+
+/* Pack/unpack G.729 frame of S60 DSP codec, taken from:
+ * http://wiki.forum.nokia.com/index.php/TSS000776_-_Payload_conversion_for_G.729_audio_format
+ */
+#include "s60_g729_bitstream.h"
+
+
+#define THIS_FILE "symb_aps_dev.c"
+#define BITS_PER_SAMPLE 16
+
+#if 1
+# define TRACE_(st) PJ_LOG(3, st)
+#else
+# define TRACE_(st)
+#endif
+
+
+/* App UID to open global APS queues to communicate with the APS server. */
+extern TPtrC APP_UID;
+
+/* APS G.711 frame length */
+static pj_uint8_t aps_g711_frame_len;
+
+
+/* APS factory */
+struct aps_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+ pjmedia_aud_dev_info dev_info;
+};
+
+
+/* Forward declaration of CPjAudioEngine */
+class CPjAudioEngine;
+
+
+/* APS stream. */
+struct aps_stream
+{
+ // Base
+ pjmedia_aud_stream base; /**< Base class. */
+
+ // Pool
+ pj_pool_t *pool; /**< Memory pool. */
+
+ // Common settings.
+ pjmedia_aud_param param; /**< Stream param. */
+ pjmedia_aud_rec_cb rec_cb; /**< Record callback. */
+ pjmedia_aud_play_cb play_cb; /**< Playback callback. */
+ void *user_data; /**< Application data. */
+
+ // Audio engine
+ CPjAudioEngine *engine; /**< Internal engine. */
+
+ pj_timestamp ts_play; /**< Playback timestamp.*/
+ pj_timestamp ts_rec; /**< Record timestamp. */
+
+ pj_int16_t *play_buf; /**< Playback buffer. */
+ pj_uint16_t play_buf_len; /**< Playback buffer length. */
+ pj_uint16_t play_buf_start; /**< Playback buffer start index. */
+ pj_int16_t *rec_buf; /**< Record buffer. */
+ pj_uint16_t rec_buf_len; /**< Record buffer length. */
+ void *strm_data; /**< Stream data. */
+};
+
+
+/* Prototypes */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t stream_start(pjmedia_aud_stream *strm);
+static pj_status_t stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
+
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+ &factory_init,
+ &factory_destroy,
+ &factory_get_dev_count,
+ &factory_get_dev_info,
+ &factory_default_param,
+ &factory_create_stream
+};
+
+static pjmedia_aud_stream_op stream_op =
+{
+ &stream_get_param,
+ &stream_get_cap,
+ &stream_set_cap,
+ &stream_start,
+ &stream_stop,
+ &stream_destroy
+};
+
+
+/****************************************************************************
+ * Internal APS Engine
+ */
+
+/*
+ * Utility: print sound device error
+ */
+static void snd_perror(const char *title, TInt rc)
+{
+ PJ_LOG(1,(THIS_FILE, "%s (error code=%d)", title, rc));
+}
+
+typedef void(*PjAudioCallback)(TAPSCommBuffer &buf, void *user_data);
+
+/**
+ * Abstract class for handler of callbacks from APS client.
+ */
+class MQueueHandlerObserver
+{
+public:
+ MQueueHandlerObserver(PjAudioCallback RecCb_, PjAudioCallback PlayCb_,
+ void *UserData_)
+ : RecCb(RecCb_), PlayCb(PlayCb_), UserData(UserData_)
+ {}
+
+ virtual void InputStreamInitialized(const TInt aStatus) = 0;
+ virtual void OutputStreamInitialized(const TInt aStatus) = 0;
+ virtual void NotifyError(const TInt aError) = 0;
+
+public:
+ PjAudioCallback RecCb;
+ PjAudioCallback PlayCb;
+ void *UserData;
+};
+
+/**
+ * Handler for communication and data queue.
+ */
+class CQueueHandler : public CActive
+{
+public:
+ // Types of queue handler
+ enum TQueueHandlerType {
+ ERecordCommQueue,
+ EPlayCommQueue,
+ ERecordQueue,
+ EPlayQueue
+ };
+
+ // The order corresponds to the APS Server state, do not change!
+ enum TState {
+ EAPSPlayerInitialize = 1,
+ EAPSRecorderInitialize = 2,
+ EAPSPlayData = 3,
+ EAPSRecordData = 4,
+ EAPSPlayerInitComplete = 5,
+ EAPSRecorderInitComplete = 6
+ };
+
+ static CQueueHandler* NewL(MQueueHandlerObserver* aObserver,
+ RMsgQueue<TAPSCommBuffer>* aQ,
+ RMsgQueue<TAPSCommBuffer>* aWriteQ,
+ TQueueHandlerType aType)
+ {
+ CQueueHandler* self = new (ELeave) CQueueHandler(aObserver, aQ, aWriteQ,
+ aType);
+ CleanupStack::PushL(self);
+ self->ConstructL();
+ CleanupStack::Pop(self);
+ return self;
+ }
+
+ // Destructor
+ ~CQueueHandler() { Cancel(); }
+
+ // Start listening queue event
+ void Start() {
+ iQ->NotifyDataAvailable(iStatus);
+ SetActive();
+ }
+
+private:
+ // Constructor
+ CQueueHandler(MQueueHandlerObserver* aObserver,
+ RMsgQueue<TAPSCommBuffer>* aQ,
+ RMsgQueue<TAPSCommBuffer>* aWriteQ,
+ TQueueHandlerType aType)
+ : CActive(CActive::EPriorityHigh),
+ iQ(aQ), iWriteQ(aWriteQ), iObserver(aObserver), iType(aType)
+ {
+ CActiveScheduler::Add(this);
+
+ // use lower priority for comm queues
+ if ((iType == ERecordCommQueue) || (iType == EPlayCommQueue))
+ SetPriority(CActive::EPriorityStandard);
+ }
+
+ // Second phase constructor
+ void ConstructL() {}
+
+ // Inherited from CActive
+ void DoCancel() { iQ->CancelDataAvailable(); }
+
+ void RunL() {
+ if (iStatus != KErrNone) {
+ iObserver->NotifyError(iStatus.Int());
+ return;
+ }
+
+ TAPSCommBuffer buffer;
+ TInt ret = iQ->Receive(buffer);
+
+ if (ret != KErrNone) {
+ iObserver->NotifyError(ret);
+ return;
+ }
+
+ switch (iType) {
+ case ERecordQueue:
+ if (buffer.iCommand == EAPSRecordData) {
+ iObserver->RecCb(buffer, iObserver->UserData);
+ } else {
+ iObserver->NotifyError(buffer.iStatus);
+ }
+ break;
+
+ // Callbacks from the APS main thread
+ case EPlayCommQueue:
+ switch (buffer.iCommand) {
+ case EAPSPlayData:
+ if (buffer.iStatus == KErrUnderflow) {
+ iObserver->PlayCb(buffer, iObserver->UserData);
+ iWriteQ->Send(buffer);
+ }
+ break;
+ case EAPSPlayerInitialize:
+ iObserver->NotifyError(buffer.iStatus);
+ break;
+ case EAPSPlayerInitComplete:
+ iObserver->OutputStreamInitialized(buffer.iStatus);
+ break;
+ case EAPSRecorderInitComplete:
+ iObserver->InputStreamInitialized(buffer.iStatus);
+ break;
+ default:
+ iObserver->NotifyError(buffer.iStatus);
+ break;
+ }
+ break;
+
+ // Callbacks from the APS recorder thread
+ case ERecordCommQueue:
+ switch (buffer.iCommand) {
+ // The APS recorder thread will only report errors
+ // through this handler. All other callbacks will be
+ // sent from the APS main thread through EPlayCommQueue
+ case EAPSRecorderInitialize:
+ case EAPSRecordData:
+ default:
+ iObserver->NotifyError(buffer.iStatus);
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // issue next request
+ iQ->NotifyDataAvailable(iStatus);
+ SetActive();
+ }
+
+ TInt RunError(TInt) {
+ return 0;
+ }
+
+ // Data
+ RMsgQueue<TAPSCommBuffer> *iQ; // (not owned)
+ RMsgQueue<TAPSCommBuffer> *iWriteQ; // (not owned)
+ MQueueHandlerObserver *iObserver; // (not owned)
+ TQueueHandlerType iType;
+};
+
+/*
+ * Audio setting for CPjAudioEngine.
+ */
+class CPjAudioSetting
+{
+public:
+ TFourCC fourcc;
+ TAPSCodecMode mode;
+ TBool plc;
+ TBool vad;
+ TBool cng;
+ TBool loudspk;
+};
+
+/*
+ * Implementation: Symbian Input & Output Stream.
+ */
+class CPjAudioEngine : public CBase, MQueueHandlerObserver
+{
+public:
+ enum State
+ {
+ STATE_NULL,
+ STATE_INITIALIZING,
+ STATE_READY,
+ STATE_STREAMING,
+ STATE_PENDING_STOP
+ };
+
+ ~CPjAudioEngine();
+
+ static CPjAudioEngine *NewL(struct aps_stream *parent_strm,
+ PjAudioCallback rec_cb,
+ PjAudioCallback play_cb,
+ void *user_data,
+ const CPjAudioSetting &setting);
+
+ TInt StartL();
+ void Stop();
+
+ TInt ActivateSpeaker(TBool active);
+
+ TInt SetVolume(TInt vol) { return iSession.SetVolume(vol); }
+ TInt GetVolume() { return iSession.Volume(); }
+ TInt GetMaxVolume() { return iSession.MaxVolume(); }
+
+ TInt SetGain(TInt gain) { return iSession.SetGain(gain); }
+ TInt GetGain() { return iSession.Gain(); }
+ TInt GetMaxGain() { return iSession.MaxGain(); }
+
+private:
+ CPjAudioEngine(struct aps_stream *parent_strm,
+ PjAudioCallback rec_cb,
+ PjAudioCallback play_cb,
+ void *user_data,
+ const CPjAudioSetting &setting);
+ void ConstructL();
+
+ TInt InitPlayL();
+ TInt InitRecL();
+ TInt StartStreamL();
+
+ // Inherited from MQueueHandlerObserver
+ virtual void InputStreamInitialized(const TInt aStatus);
+ virtual void OutputStreamInitialized(const TInt aStatus);
+ virtual void NotifyError(const TInt aError);
+
+ State state_;
+ struct aps_stream *parentStrm_;
+ CPjAudioSetting setting_;
+
+ RAPSSession iSession;
+ TAPSInitSettings iPlaySettings;
+ TAPSInitSettings iRecSettings;
+
+ RMsgQueue<TAPSCommBuffer> iReadQ;
+ RMsgQueue<TAPSCommBuffer> iReadCommQ;
+ RMsgQueue<TAPSCommBuffer> iWriteQ;
+ RMsgQueue<TAPSCommBuffer> iWriteCommQ;
+
+ CQueueHandler *iPlayCommHandler;
+ CQueueHandler *iRecCommHandler;
+ CQueueHandler *iRecHandler;
+};
+
+
+CPjAudioEngine* CPjAudioEngine::NewL(struct aps_stream *parent_strm,
+ PjAudioCallback rec_cb,
+ PjAudioCallback play_cb,
+ void *user_data,
+ const CPjAudioSetting &setting)
+{
+ CPjAudioEngine* self = new (ELeave) CPjAudioEngine(parent_strm,
+ rec_cb, play_cb,
+ user_data,
+ setting);
+ CleanupStack::PushL(self);
+ self->ConstructL();
+ CleanupStack::Pop(self);
+ return self;
+}
+
+CPjAudioEngine::CPjAudioEngine(struct aps_stream *parent_strm,
+ PjAudioCallback rec_cb,
+ PjAudioCallback play_cb,
+ void *user_data,
+ const CPjAudioSetting &setting)
+ : MQueueHandlerObserver(rec_cb, play_cb, user_data),
+ state_(STATE_NULL),
+ parentStrm_(parent_strm),
+ setting_(setting),
+ iPlayCommHandler(0),
+ iRecCommHandler(0),
+ iRecHandler(0)
+{
+}
+
+CPjAudioEngine::~CPjAudioEngine()
+{
+ Stop();
+
+ delete iRecHandler;
+ delete iPlayCommHandler;
+ delete iRecCommHandler;
+
+ // On some devices, immediate closing after stopping may cause APS server
+ // panic KERN-EXEC 0, so let's wait for sometime before really closing
+ // the client session.
+ TTime start, now;
+ enum { APS_CLOSE_WAIT_TIME = 200 }; /* in msecs */
+
+ start.UniversalTime();
+ do {
+ pj_symbianos_poll(-1, APS_CLOSE_WAIT_TIME);
+ now.UniversalTime();
+ } while (now.MicroSecondsFrom(start) < APS_CLOSE_WAIT_TIME * 1000);
+
+ iSession.Close();
+
+ if (state_ == STATE_READY) {
+ if (parentStrm_->param.dir != PJMEDIA_DIR_PLAYBACK) {
+ iReadQ.Close();
+ iReadCommQ.Close();
+ }
+ iWriteQ.Close();
+ iWriteCommQ.Close();
+ }
+
+ TRACE_((THIS_FILE, "Sound device destroyed"));
+}
+
+TInt CPjAudioEngine::InitPlayL()
+{
+ TInt err = iSession.InitializePlayer(iPlaySettings);
+ if (err != KErrNone) {
+ snd_perror("Failed to initialize player", err);
+ return err;
+ }
+
+ // Open message queues for the output stream
+ TBuf<128> buf2 = iPlaySettings.iGlobal;
+ buf2.Append(_L("PlayQueue"));
+ TBuf<128> buf3 = iPlaySettings.iGlobal;
+ buf3.Append(_L("PlayCommQueue"));
+
+ while (iWriteQ.OpenGlobal(buf2))
+ User::After(10);
+ while (iWriteCommQ.OpenGlobal(buf3))
+ User::After(10);
+
+ // Construct message queue handler
+ iPlayCommHandler = CQueueHandler::NewL(this, &iWriteCommQ, &iWriteQ,
+ CQueueHandler::EPlayCommQueue);
+
+ // Start observing APS callbacks on output stream message queue
+ iPlayCommHandler->Start();
+
+ return 0;
+}
+
+TInt CPjAudioEngine::InitRecL()
+{
+ // Initialize input stream device
+ TInt err = iSession.InitializeRecorder(iRecSettings);
+ if (err != KErrNone && err != KErrAlreadyExists) {
+ snd_perror("Failed to initialize recorder", err);
+ return err;
+ }
+
+ TBuf<128> buf1 = iRecSettings.iGlobal;
+ buf1.Append(_L("RecordQueue"));
+ TBuf<128> buf4 = iRecSettings.iGlobal;
+ buf4.Append(_L("RecordCommQueue"));
+
+ // Must wait for APS thread to finish creating message queues
+ // before we can open and use them.
+ while (iReadQ.OpenGlobal(buf1))
+ User::After(10);
+ while (iReadCommQ.OpenGlobal(buf4))
+ User::After(10);
+
+ // Construct message queue handlers
+ iRecHandler = CQueueHandler::NewL(this, &iReadQ, NULL,
+ CQueueHandler::ERecordQueue);
+ iRecCommHandler = CQueueHandler::NewL(this, &iReadCommQ, NULL,
+ CQueueHandler::ERecordCommQueue);
+
+ // Start observing APS callbacks from on input stream message queue
+ iRecHandler->Start();
+ iRecCommHandler->Start();
+
+ return 0;
+}
+
+TInt CPjAudioEngine::StartL()
+{
+ if (state_ == STATE_READY)
+ return StartStreamL();
+
+ PJ_ASSERT_RETURN(state_ == STATE_NULL, PJMEDIA_EAUD_INVOP);
+
+ // Even if only capturer are opened, playback thread of APS Server need
+ // to be run(?). Since some messages will be delivered via play comm queue.
+ state_ = STATE_INITIALIZING;
+
+ return InitPlayL();
+}
+
+void CPjAudioEngine::Stop()
+{
+ if (state_ == STATE_STREAMING) {
+ iSession.Stop();
+ state_ = STATE_READY;
+ TRACE_((THIS_FILE, "Sound device stopped"));
+ } else if (state_ == STATE_INITIALIZING) {
+ // Initialization is on progress, so let's set the state to
+ // STATE_PENDING_STOP to prevent it starting the stream.
+ state_ = STATE_PENDING_STOP;
+
+ // Then wait until initialization done.
+ while (state_ != STATE_READY)
+ pj_symbianos_poll(-1, 100);
+ }
+}
+
+void CPjAudioEngine::ConstructL()
+{
+ // Recorder settings
+ iRecSettings.iFourCC = setting_.fourcc;
+ iRecSettings.iGlobal = APP_UID;
+ iRecSettings.iPriority = TMdaPriority(100);
+ iRecSettings.iPreference = TMdaPriorityPreference(0x05210001);
+ iRecSettings.iSettings.iChannels = EMMFMono;
+ iRecSettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
+
+ // Player settings
+ iPlaySettings.iFourCC = setting_.fourcc;
+ iPlaySettings.iGlobal = APP_UID;
+ iPlaySettings.iPriority = TMdaPriority(100);
+ iPlaySettings.iPreference = TMdaPriorityPreference(0x05220001);
+ iPlaySettings.iSettings.iChannels = EMMFMono;
+ iPlaySettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
+ iPlaySettings.iSettings.iVolume = 0;
+
+ User::LeaveIfError(iSession.Connect());
+}
+
+TInt CPjAudioEngine::StartStreamL()
+{
+ pj_assert(state_==STATE_READY || state_==STATE_INITIALIZING);
+
+ iSession.SetCng(setting_.cng);
+ iSession.SetVadMode(setting_.vad);
+ iSession.SetPlc(setting_.plc);
+ iSession.SetEncoderMode(setting_.mode);
+ iSession.SetDecoderMode(setting_.mode);
+ iSession.ActivateLoudspeaker(setting_.loudspk);
+
+ // Not only capture
+ if (parentStrm_->param.dir != PJMEDIA_DIR_CAPTURE) {
+ iSession.Write();
+ TRACE_((THIS_FILE, "Player started"));
+ }
+
+ // Not only playback
+ if (parentStrm_->param.dir != PJMEDIA_DIR_PLAYBACK) {
+ iSession.Read();
+ TRACE_((THIS_FILE, "Recorder started"));
+ }
+
+ state_ = STATE_STREAMING;
+
+ return 0;
+}
+
+void CPjAudioEngine::InputStreamInitialized(const TInt aStatus)
+{
+ TRACE_((THIS_FILE, "Recorder initialized, err=%d", aStatus));
+
+ if (aStatus == KErrNone) {
+ // Don't start the stream since Stop() has been requested.
+ if (state_ != STATE_PENDING_STOP) {
+ StartStreamL();
+ } else {
+ state_ = STATE_READY;
+ }
+ }
+}
+
+void CPjAudioEngine::OutputStreamInitialized(const TInt aStatus)
+{
+ TRACE_((THIS_FILE, "Player initialized, err=%d", aStatus));
+
+ if (aStatus == KErrNone) {
+ if (parentStrm_->param.dir == PJMEDIA_DIR_PLAYBACK) {
+ // Don't start the stream since Stop() has been requested.
+ if (state_ != STATE_PENDING_STOP) {
+ StartStreamL();
+ } else {
+ state_ = STATE_READY;
+ }
+ } else
+ InitRecL();
+ }
+}
+
+void CPjAudioEngine::NotifyError(const TInt aError)
+{
+ snd_perror("Error from CQueueHandler", aError);
+}
+
+TInt CPjAudioEngine::ActivateSpeaker(TBool active)
+{
+ if (state_ == STATE_READY || state_ == STATE_STREAMING) {
+ iSession.ActivateLoudspeaker(active);
+ TRACE_((THIS_FILE, "Loudspeaker turned %s", (active? "on":"off")));
+ return KErrNone;
+ }
+ return KErrNotReady;
+}
+
+
+
+static void RecCbPcm(TAPSCommBuffer &buf, void *user_data)
+{
+ struct aps_stream *strm = (struct aps_stream*) user_data;
+
+ /* Buffer has to contain normal speech. */
+ pj_assert(buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0);
+
+ /* Detect the recorder G.711 frame size, player frame size will follow
+ * this recorder frame size.
+ */
+ if (aps_g711_frame_len == 0) {
+ aps_g711_frame_len = buf.iBuffer.Length() < 160? 80 : 160;
+ TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples",
+ aps_g711_frame_len));
+ }
+
+ /* Decode APS buffer (coded in G.711) and put the PCM result into rec_buf.
+ * Whenever rec_buf is full, call parent stream callback.
+ */
+ unsigned dec_len = 0;
+
+ while (dec_len < aps_g711_frame_len) {
+ unsigned tmp;
+
+ tmp = PJ_MIN(strm->param.samples_per_frame - strm->rec_buf_len,
+ aps_g711_frame_len - dec_len);
+ pjmedia_ulaw_decode(&strm->rec_buf[strm->rec_buf_len],
+ buf.iBuffer.Ptr() + 2 + dec_len,
+ tmp);
+ strm->rec_buf_len += tmp;
+ dec_len += tmp;
+
+ pj_assert(strm->rec_buf_len <= strm->param.samples_per_frame);
+
+ if (strm->rec_buf_len == strm->param.samples_per_frame) {
+ pjmedia_frame f;
+
+ f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ f.buf = strm->rec_buf;
+ f.size = strm->rec_buf_len << 1;
+
+ strm->rec_cb(strm->user_data, &f);
+ strm->rec_buf_len = 0;
+ }
+ }
+}
+
+static void PlayCbPcm(TAPSCommBuffer &buf, void *user_data)
+{
+ struct aps_stream *strm = (struct aps_stream*) user_data;
+ unsigned g711_frame_len = aps_g711_frame_len;
+
+ /* Init buffer attributes and header. */
+ buf.iCommand = CQueueHandler::EAPSPlayData;
+ buf.iStatus = 0;
+ buf.iBuffer.Zero();
+ buf.iBuffer.Append(1);
+ buf.iBuffer.Append(0);
+
+ /* Assume frame size is 10ms if frame size hasn't been known. */
+ if (g711_frame_len == 0)
+ g711_frame_len = 80;
+
+ /* Call parent stream callback to get PCM samples to play,
+ * encode the PCM samples into G.711 and put it into APS buffer.
+ */
+ unsigned enc_len = 0;
+ while (enc_len < g711_frame_len) {
+ if (strm->play_buf_len == 0) {
+ pjmedia_frame f;
+
+ f.buf = strm->play_buf;
+ f.size = strm->param.samples_per_frame << 1;
+
+ strm->play_cb(strm->user_data, &f);
+ if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+ pjmedia_zero_samples(strm->play_buf,
+ strm->param.samples_per_frame);
+ }
+
+ strm->play_buf_len = strm->param.samples_per_frame;
+ strm->play_buf_start = 0;
+ }
+
+ unsigned tmp;
+
+ tmp = PJ_MIN(strm->play_buf_len, g711_frame_len - enc_len);
+ pjmedia_ulaw_encode((pj_uint8_t*)&strm->play_buf[strm->play_buf_start],
+ &strm->play_buf[strm->play_buf_start],
+ tmp);
+ buf.iBuffer.Append((TUint8*)&strm->play_buf[strm->play_buf_start], tmp);
+ enc_len += tmp;
+ strm->play_buf_len -= tmp;
+ strm->play_buf_start += tmp;
+ }
+}
+
+/****************************************************************************
+ * Internal APS callbacks
+ */
+
+static void RecCb(TAPSCommBuffer &buf, void *user_data)
+{
+ struct aps_stream *strm = (struct aps_stream*) user_data;
+ pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->rec_buf;
+
+ switch(strm->param.ext_fmt.id) {
+ case PJMEDIA_FORMAT_AMR:
+ {
+ const pj_uint8_t *p = (const pj_uint8_t*)buf.iBuffer.Ptr() + 1;
+ unsigned len = buf.iBuffer.Length() - 1;
+
+ pjmedia_frame_ext_append_subframe(frame, p, len << 3, 160);
+ if (frame->samples_cnt == strm->param.samples_per_frame) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_G729:
+ {
+ /* Check if we got a normal or SID frame. */
+ if (buf.iBuffer[0] != 0 || buf.iBuffer[1] != 0) {
+ enum { NORMAL_LEN = 22, SID_LEN = 8 };
+ TBitStream *bitstream = (TBitStream*)strm->strm_data;
+ unsigned src_len = buf.iBuffer.Length()- 2;
+
+ pj_assert(src_len == NORMAL_LEN || src_len == SID_LEN);
+
+ const TDesC8& p = bitstream->CompressG729Frame(
+ buf.iBuffer.Right(src_len),
+ src_len == SID_LEN);
+
+ pjmedia_frame_ext_append_subframe(frame, p.Ptr(),
+ p.Length() << 3, 80);
+ } else { /* We got null frame. */
+ pjmedia_frame_ext_append_subframe(frame, NULL, 0, 80);
+ }
+
+ if (frame->samples_cnt == strm->param.samples_per_frame) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_ILBC:
+ {
+ unsigned samples_got;
+
+ samples_got = strm->param.ext_fmt.bitrate == 15200? 160 : 240;
+
+ /* Check if we got a normal frame. */
+ if (buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0) {
+ const pj_uint8_t *p = (const pj_uint8_t*)buf.iBuffer.Ptr() + 2;
+ unsigned len = buf.iBuffer.Length() - 2;
+
+ pjmedia_frame_ext_append_subframe(frame, p, len << 3,
+ samples_got);
+ } else { /* We got null frame. */
+ pjmedia_frame_ext_append_subframe(frame, NULL, 0, samples_got);
+ }
+
+ if (frame->samples_cnt == strm->param.samples_per_frame) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_PCMU:
+ case PJMEDIA_FORMAT_PCMA:
+ {
+ unsigned samples_processed = 0;
+
+ /* Make sure it is normal frame. */
+ pj_assert(buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0);
+
+ /* Detect the recorder G.711 frame size, player frame size will
+ * follow this recorder frame size.
+ */
+ if (aps_g711_frame_len == 0) {
+ aps_g711_frame_len = buf.iBuffer.Length() < 160? 80 : 160;
+ TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples",
+ aps_g711_frame_len));
+ }
+
+ /* Convert APS buffer format into pjmedia_frame_ext. Whenever
+ * samples count in the frame is equal to stream's samples per
+ * frame, call parent stream callback.
+ */
+ while (samples_processed < aps_g711_frame_len) {
+ unsigned tmp;
+ const pj_uint8_t *pb = (const pj_uint8_t*)buf.iBuffer.Ptr() +
+ 2 + samples_processed;
+
+ tmp = PJ_MIN(strm->param.samples_per_frame - frame->samples_cnt,
+ aps_g711_frame_len - samples_processed);
+
+ pjmedia_frame_ext_append_subframe(frame, pb, tmp << 3, tmp);
+ samples_processed += tmp;
+
+ if (frame->samples_cnt == strm->param.samples_per_frame) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void PlayCb(TAPSCommBuffer &buf, void *user_data)
+{
+ struct aps_stream *strm = (struct aps_stream*) user_data;
+ pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->play_buf;
+
+ /* Init buffer attributes and header. */
+ buf.iCommand = CQueueHandler::EAPSPlayData;
+ buf.iStatus = 0;
+ buf.iBuffer.Zero();
+
+ switch(strm->param.ext_fmt.id) {
+ case PJMEDIA_FORMAT_AMR:
+ {
+ if (frame->samples_cnt == 0) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+ pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+ frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+ }
+
+ if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned samples_cnt;
+
+ sf = pjmedia_frame_ext_get_subframe(frame, 0);
+ samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+
+ if (sf->data && sf->bitlen) {
+ /* AMR header for APS is one byte, the format (may be!):
+ * 0xxxxy00, where xxxx:frame type, y:not sure.
+ */
+ unsigned len = (sf->bitlen+7)>>3;
+ enum {SID_FT = 8 };
+ pj_uint8_t amr_header = 4, ft = SID_FT;
+
+ if (len >= pjmedia_codec_amrnb_framelen[0])
+ ft = pjmedia_codec_amr_get_mode2(PJ_TRUE, len);
+
+ amr_header |= ft << 3;
+ buf.iBuffer.Append(amr_header);
+
+ buf.iBuffer.Append((TUint8*)sf->data, len);
+ } else {
+ buf.iBuffer.Append(0);
+ }
+
+ pjmedia_frame_ext_pop_subframes(frame, 1);
+
+ } else { /* PJMEDIA_FRAME_TYPE_NONE */
+ buf.iBuffer.Append(0);
+
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_G729:
+ {
+ if (frame->samples_cnt == 0) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+ pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+ frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+ }
+
+ if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned samples_cnt;
+
+ sf = pjmedia_frame_ext_get_subframe(frame, 0);
+ samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+
+ if (sf->data && sf->bitlen) {
+ enum { NORMAL_LEN = 10, SID_LEN = 2 };
+ pj_bool_t sid_frame = ((sf->bitlen >> 3) == SID_LEN);
+ TBitStream *bitstream = (TBitStream*)strm->strm_data;
+ const TPtrC8 src(sf->data, sf->bitlen>>3);
+ const TDesC8 &dst = bitstream->ExpandG729Frame(src,
+ sid_frame);
+ if (sid_frame) {
+ buf.iBuffer.Append(0);
+ buf.iBuffer.Append(1);
+ } else {
+ buf.iBuffer.Append(1);
+ buf.iBuffer.Append(0);
+ }
+ buf.iBuffer.Append(dst);
+ } else {
+ buf.iBuffer.Append(0);
+ buf.iBuffer.Append(0);
+ }
+
+ pjmedia_frame_ext_pop_subframes(frame, 1);
+
+ } else { /* PJMEDIA_FRAME_TYPE_NONE */
+ buf.iBuffer.Append(0);
+ buf.iBuffer.Append(0);
+
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_ILBC:
+ {
+ if (frame->samples_cnt == 0) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+ pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+ frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+ }
+
+ if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned samples_cnt;
+
+ sf = pjmedia_frame_ext_get_subframe(frame, 0);
+ samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+
+ pj_assert((strm->param.ext_fmt.bitrate == 15200 &&
+ samples_cnt == 160) ||
+ (strm->param.ext_fmt.bitrate != 15200 &&
+ samples_cnt == 240));
+
+ if (sf->data && sf->bitlen) {
+ buf.iBuffer.Append(1);
+ buf.iBuffer.Append(0);
+ buf.iBuffer.Append((TUint8*)sf->data, sf->bitlen>>3);
+ } else {
+ buf.iBuffer.Append(0);
+ buf.iBuffer.Append(0);
+ }
+
+ pjmedia_frame_ext_pop_subframes(frame, 1);
+
+ } else { /* PJMEDIA_FRAME_TYPE_NONE */
+ buf.iBuffer.Append(0);
+ buf.iBuffer.Append(0);
+
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_PCMU:
+ case PJMEDIA_FORMAT_PCMA:
+ {
+ unsigned samples_ready = 0;
+ unsigned samples_req = aps_g711_frame_len;
+
+ /* Assume frame size is 10ms if frame size hasn't been known. */
+ if (samples_req == 0)
+ samples_req = 80;
+
+ buf.iBuffer.Append(1);
+ buf.iBuffer.Append(0);
+
+ /* Call parent stream callback to get samples to play. */
+ while (samples_ready < samples_req) {
+ if (frame->samples_cnt == 0) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+ pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+ frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+ }
+
+ if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned samples_cnt;
+
+ sf = pjmedia_frame_ext_get_subframe(frame, 0);
+ samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+ if (sf->data && sf->bitlen) {
+ buf.iBuffer.Append((TUint8*)sf->data, sf->bitlen>>3);
+ } else {
+ pj_uint8_t silc;
+ silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
+ pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
+ buf.iBuffer.AppendFill(silc, samples_cnt);
+ }
+ samples_ready += samples_cnt;
+
+ pjmedia_frame_ext_pop_subframes(frame, 1);
+
+ } else { /* PJMEDIA_FRAME_TYPE_NONE */
+ pj_uint8_t silc;
+
+ silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
+ pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
+ buf.iBuffer.AppendFill(silc, samples_req - samples_ready);
+
+ samples_ready = samples_req;
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+/****************************************************************************
+ * Factory operations
+ */
+
+/*
+ * C compatible declaration of APS factory.
+ */
+PJ_BEGIN_DECL
+PJ_DECL(pjmedia_aud_dev_factory*) pjmedia_aps_factory(pj_pool_factory *pf);
+PJ_END_DECL
+
+/*
+ * Init APS audio driver.
+ */
+PJ_DEF(pjmedia_aud_dev_factory*) pjmedia_aps_factory(pj_pool_factory *pf)
+{
+ struct aps_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "APS", 1000, 1000, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct aps_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+/* API: init factory */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
+{
+ struct aps_factory *af = (struct aps_factory*)f;
+
+ pj_ansi_strcpy(af->dev_info.name, "S60 APS");
+ af->dev_info.default_samples_per_sec = 8000;
+ af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
+ //PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE |
+ PJMEDIA_AUD_DEV_CAP_VAD |
+ PJMEDIA_AUD_DEV_CAP_CNG;
+ af->dev_info.routes = PJMEDIA_AUD_DEV_ROUTE_EARPIECE |
+ PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
+ af->dev_info.input_count = 1;
+ af->dev_info.output_count = 1;
+
+ af->dev_info.ext_fmt_cnt = 5;
+
+ af->dev_info.ext_fmt[0].id = PJMEDIA_FORMAT_AMR;
+ af->dev_info.ext_fmt[0].bitrate = 7400;
+ af->dev_info.ext_fmt[0].vad = PJ_TRUE;
+
+ af->dev_info.ext_fmt[1].id = PJMEDIA_FORMAT_G729;
+ af->dev_info.ext_fmt[1].bitrate = 8000;
+ af->dev_info.ext_fmt[1].vad = PJ_FALSE;
+
+ af->dev_info.ext_fmt[2].id = PJMEDIA_FORMAT_ILBC;
+ af->dev_info.ext_fmt[2].bitrate = 13333;
+ af->dev_info.ext_fmt[2].vad = PJ_TRUE;
+
+ af->dev_info.ext_fmt[3].id = PJMEDIA_FORMAT_PCMU;
+ af->dev_info.ext_fmt[3].bitrate = 64000;
+ af->dev_info.ext_fmt[3].vad = PJ_FALSE;
+
+ af->dev_info.ext_fmt[4].id = PJMEDIA_FORMAT_PCMA;
+ af->dev_info.ext_fmt[4].bitrate = 64000;
+ af->dev_info.ext_fmt[4].vad = PJ_FALSE;
+
+ PJ_LOG(4, (THIS_FILE, "APS initialized"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct aps_factory *af = (struct aps_factory*)f;
+ pj_pool_t *pool = af->pool;
+
+ af->pool = NULL;
+ pj_pool_release(pool);
+
+ PJ_LOG(4, (THIS_FILE, "APS destroyed"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return 1;
+}
+
+/* API: get device info */
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
+{
+ struct aps_factory *af = (struct aps_factory*)f;
+
+ PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+ pj_memcpy(info, &af->dev_info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ struct aps_factory *af = (struct aps_factory*)f;
+
+ PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+ pj_bzero(param, sizeof(*param));
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ param->clock_rate = af->dev_info.default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = BITS_PER_SAMPLE;
+ param->flags = PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE;
+ param->output_route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: create stream */
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ struct aps_factory *af = (struct aps_factory*)f;
+ pj_pool_t *pool;
+ struct aps_stream *strm;
+
+ CPjAudioSetting aps_setting;
+ PjAudioCallback aps_rec_cb;
+ PjAudioCallback aps_play_cb;
+
+ /* Can only support 16bits per sample */
+ PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(af->pf, "aps-dev", 1000, 1000, NULL);
+ PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct aps_stream);
+ strm->pool = pool;
+ strm->param = *param;
+
+ if (strm->param.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT == 0)
+ strm->param.ext_fmt.id = PJMEDIA_FORMAT_L16;
+
+ /* Set audio engine fourcc. */
+ switch(strm->param.ext_fmt.id) {
+ case PJMEDIA_FORMAT_L16:
+ case PJMEDIA_FORMAT_PCMU:
+ case PJMEDIA_FORMAT_PCMA:
+ aps_setting.fourcc = TFourCC(KMCPFourCCIdG711);
+ break;
+ case PJMEDIA_FORMAT_AMR:
+ aps_setting.fourcc = TFourCC(KMCPFourCCIdAMRNB);
+ break;
+ case PJMEDIA_FORMAT_G729:
+ aps_setting.fourcc = TFourCC(KMCPFourCCIdG729);
+ break;
+ case PJMEDIA_FORMAT_ILBC:
+ aps_setting.fourcc = TFourCC(KMCPFourCCIdILBC);
+ break;
+ default:
+ aps_setting.fourcc = 0;
+ break;
+ }
+
+ /* Set audio engine mode. */
+ if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_AMR)
+ {
+ aps_setting.mode = (TAPSCodecMode)strm->param.ext_fmt.bitrate;
+ }
+ else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU ||
+ strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 ||
+ (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC &&
+ strm->param.ext_fmt.bitrate != 15200))
+ {
+ aps_setting.mode = EULawOr30ms;
+ }
+ else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
+ (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC &&
+ strm->param.ext_fmt.bitrate == 15200))
+ {
+ aps_setting.mode = EALawOr20ms;
+ }
+
+ /* Disable VAD on L16, G711, and also G729 (G729's VAD potentially
+ * causes noise?).
+ */
+ if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU ||
+ strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
+ strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 ||
+ strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729)
+ {
+ aps_setting.vad = EFalse;
+ } else {
+ aps_setting.vad = strm->param.ext_fmt.vad;
+ }
+
+ /* Set other audio engine attributes. */
+ aps_setting.plc = strm->param.plc_enabled;
+ aps_setting.cng = aps_setting.vad;
+ aps_setting.loudspk =
+ strm->param.output_route==PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
+
+ /* Set audio engine callbacks. */
+ if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16) {
+ aps_play_cb = &PlayCbPcm;
+ aps_rec_cb = &RecCbPcm;
+ } else {
+ aps_play_cb = &PlayCb;
+ aps_rec_cb = &RecCb;
+ }
+
+ /* Create the audio engine. */
+ TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm,
+ aps_rec_cb, aps_play_cb,
+ strm, aps_setting));
+ if (err != KErrNone) {
+ pj_pool_release(pool);
+ return PJ_RETURN_OS_ERROR(err);
+ }
+
+ /* Apply output volume setting if specified */
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
+ stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &param->output_vol);
+ }
+
+ strm->rec_cb = rec_cb;
+ strm->play_cb = play_cb;
+ strm->user_data = user_data;
+
+ /* play_buf size is samples per frame. */
+ strm->play_buf = (pj_int16_t*)pj_pool_zalloc(pool,
+ strm->param.samples_per_frame << 1);
+ strm->play_buf_len = 0;
+ strm->play_buf_start = 0;
+
+ /* rec_buf size is samples per frame. */
+ strm->rec_buf = (pj_int16_t*)pj_pool_zalloc(pool,
+ strm->param.samples_per_frame << 1);
+ strm->rec_buf_len = 0;
+
+ if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
+ TBitStream *g729_bitstream = new TBitStream;
+
+ PJ_ASSERT_RETURN(g729_bitstream, PJ_ENOMEM);
+ strm->strm_data = (void*)g729_bitstream;
+ }
+
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_aud_strm = &strm->base;
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t stream_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
+{
+ struct aps_stream *strm = (struct aps_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ /* Update the output volume setting */
+ if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &pi->output_vol) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct aps_stream *strm = (struct aps_stream*)s;
+ pj_status_t status = PJ_ENOTSUP;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ switch (cap) {
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ *(pjmedia_aud_dev_route*)pval = strm->param.output_route;
+ status = PJ_SUCCESS;
+ }
+ break;
+
+ /* There is a case that GetMaxGain() stucks, e.g: in N95. */
+ /*
+ case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+ PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+
+ TInt max_gain = strm->engine->GetMaxGain();
+ TInt gain = strm->engine->GetGain();
+
+ if (max_gain > 0 && gain >= 0) {
+ *(unsigned*)pval = gain * 100 / max_gain;
+ status = PJ_SUCCESS;
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ }
+ break;
+ */
+
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+
+ TInt max_vol = strm->engine->GetMaxVolume();
+ TInt vol = strm->engine->GetVolume();
+
+ if (max_vol > 0 && vol >= 0) {
+ *(unsigned*)pval = vol * 100 / max_vol;
+ status = PJ_SUCCESS;
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return status;
+}
+
+/* API: set capability */
+static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ const void *pval)
+{
+ struct aps_stream *strm = (struct aps_stream*)s;
+ pj_status_t status = PJ_ENOTSUP;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ switch (cap) {
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ pjmedia_aud_dev_route r = *(const pjmedia_aud_dev_route*)pval;
+ TInt err;
+
+ PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+
+ switch (r) {
+ case PJMEDIA_AUD_DEV_ROUTE_DEFAULT:
+ case PJMEDIA_AUD_DEV_ROUTE_EARPIECE:
+ err = strm->engine->ActivateSpeaker(EFalse);
+ status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+ break;
+ case PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER:
+ err = strm->engine->ActivateSpeaker(ETrue);
+ status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+ break;
+ default:
+ status = PJ_EINVAL;
+ break;
+ }
+ if (status == PJ_SUCCESS)
+ strm->param.output_route = r;
+ }
+ break;
+
+ /* There is a case that GetMaxGain() stucks, e.g: in N95. */
+ /*
+ case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+ PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+
+ TInt max_gain = strm->engine->GetMaxGain();
+ if (max_gain > 0) {
+ TInt gain, err;
+
+ gain = *(unsigned*)pval * max_gain / 100;
+ err = strm->engine->SetGain(gain);
+ status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ if (status == PJ_SUCCESS)
+ strm->param.input_vol = *(unsigned*)pval;
+ }
+ break;
+ */
+
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+
+ TInt max_vol = strm->engine->GetMaxVolume();
+ if (max_vol > 0) {
+ TInt vol, err;
+
+ vol = *(unsigned*)pval * max_vol / 100;
+ err = strm->engine->SetVolume(vol);
+ status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ if (status == PJ_SUCCESS)
+ strm->param.output_vol = *(unsigned*)pval;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return status;
+}
+
+/* API: Start stream. */
+static pj_status_t stream_start(pjmedia_aud_stream *strm)
+{
+ struct aps_stream *stream = (struct aps_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if (stream->engine) {
+ TInt err = stream->engine->StartL();
+ if (err != KErrNone)
+ return PJ_RETURN_OS_ERROR(err);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t stream_stop(pjmedia_aud_stream *strm)
+{
+ struct aps_stream *stream = (struct aps_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if (stream->engine) {
+ stream->engine->Stop();
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
+{
+ struct aps_stream *stream = (struct aps_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ stream_stop(strm);
+
+ delete stream->engine;
+ stream->engine = NULL;
+
+ if (stream->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
+ TBitStream *g729_bitstream = (TBitStream*)stream->strm_data;
+ stream->strm_data = NULL;
+ delete g729_bitstream;
+ }
+
+ pj_pool_t *pool;
+ pool = stream->pool;
+ if (pool) {
+ stream->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+#endif // PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
+
diff --git a/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp b/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp
new file mode 100644
index 00000000..f9437e55
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp
@@ -0,0 +1,1110 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA
+
+/*
+ * This file provides sound implementation for Symbian Audio Streaming
+ * device. Application using this sound abstraction must link with:
+ * - mediaclientaudiostream.lib, and
+ * - mediaclientaudioinputstream.lib
+ */
+#include <mda/common/audio.h>
+#include <mdaaudiooutputstream.h>
+#include <mdaaudioinputstream.h>
+
+
+#define THIS_FILE "symb_mda_dev.c"
+#define BITS_PER_SAMPLE 16
+#define BYTES_PER_SAMPLE (BITS_PER_SAMPLE/8)
+
+
+#if 1
+# define TRACE_(st) PJ_LOG(3, st)
+#else
+# define TRACE_(st)
+#endif
+
+
+/* MDA factory */
+struct mda_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+ pjmedia_aud_dev_info dev_info;
+};
+
+/* Forward declaration of internal engine. */
+class CPjAudioInputEngine;
+class CPjAudioOutputEngine;
+
+/* MDA stream. */
+struct mda_stream
+{
+ // Base
+ pjmedia_aud_stream base; /**< Base class. */
+
+ // Pool
+ pj_pool_t *pool; /**< Memory pool. */
+
+ // Common settings.
+ pjmedia_aud_param param; /**< Stream param. */
+
+ // Audio engine
+ CPjAudioInputEngine *in_engine; /**< Record engine. */
+ CPjAudioOutputEngine *out_engine; /**< Playback engine. */
+};
+
+
+/* Prototypes */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t stream_start(pjmedia_aud_stream *strm);
+static pj_status_t stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
+
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+ &factory_init,
+ &factory_destroy,
+ &factory_get_dev_count,
+ &factory_get_dev_info,
+ &factory_default_param,
+ &factory_create_stream
+};
+
+static pjmedia_aud_stream_op stream_op =
+{
+ &stream_get_param,
+ &stream_get_cap,
+ &stream_set_cap,
+ &stream_start,
+ &stream_stop,
+ &stream_destroy
+};
+
+
+/*
+ * Convert clock rate to Symbian's TMdaAudioDataSettings capability.
+ */
+static TInt get_clock_rate_cap(unsigned clock_rate)
+{
+ switch (clock_rate) {
+ case 8000: return TMdaAudioDataSettings::ESampleRate8000Hz;
+ case 11025: return TMdaAudioDataSettings::ESampleRate11025Hz;
+ case 12000: return TMdaAudioDataSettings::ESampleRate12000Hz;
+ case 16000: return TMdaAudioDataSettings::ESampleRate16000Hz;
+ case 22050: return TMdaAudioDataSettings::ESampleRate22050Hz;
+ case 24000: return TMdaAudioDataSettings::ESampleRate24000Hz;
+ case 32000: return TMdaAudioDataSettings::ESampleRate32000Hz;
+ case 44100: return TMdaAudioDataSettings::ESampleRate44100Hz;
+ case 48000: return TMdaAudioDataSettings::ESampleRate48000Hz;
+ case 64000: return TMdaAudioDataSettings::ESampleRate64000Hz;
+ case 96000: return TMdaAudioDataSettings::ESampleRate96000Hz;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Convert number of channels into Symbian's TMdaAudioDataSettings capability.
+ */
+static TInt get_channel_cap(unsigned channel_count)
+{
+ switch (channel_count) {
+ case 1: return TMdaAudioDataSettings::EChannelsMono;
+ case 2: return TMdaAudioDataSettings::EChannelsStereo;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Utility: print sound device error
+ */
+static void snd_perror(const char *title, TInt rc)
+{
+ PJ_LOG(1,(THIS_FILE, "%s: error code %d", title, rc));
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+
+/*
+ * Implementation: Symbian Input Stream.
+ */
+class CPjAudioInputEngine : public CBase, MMdaAudioInputStreamCallback
+{
+public:
+ enum State
+ {
+ STATE_INACTIVE,
+ STATE_ACTIVE,
+ };
+
+ ~CPjAudioInputEngine();
+
+ static CPjAudioInputEngine *NewL(struct mda_stream *parent_strm,
+ pjmedia_aud_rec_cb rec_cb,
+ void *user_data);
+
+ static CPjAudioInputEngine *NewLC(struct mda_stream *parent_strm,
+ pjmedia_aud_rec_cb rec_cb,
+ void *user_data);
+
+ pj_status_t StartRecord();
+ void Stop();
+
+ pj_status_t SetGain(TInt gain) {
+ if (iInputStream_) {
+ iInputStream_->SetGain(gain);
+ return PJ_SUCCESS;
+ } else
+ return PJ_EINVALIDOP;
+ }
+
+ TInt GetGain() {
+ if (iInputStream_) {
+ return iInputStream_->Gain();
+ } else
+ return PJ_EINVALIDOP;
+ }
+
+ TInt GetMaxGain() {
+ if (iInputStream_) {
+ return iInputStream_->MaxGain();
+ } else
+ return PJ_EINVALIDOP;
+ }
+
+private:
+ State state_;
+ struct mda_stream *parentStrm_;
+ pjmedia_aud_rec_cb recCb_;
+ void *userData_;
+ CMdaAudioInputStream *iInputStream_;
+ HBufC8 *iStreamBuffer_;
+ TPtr8 iFramePtr_;
+ TInt lastError_;
+ pj_uint32_t timeStamp_;
+
+ // cache variable
+ // to avoid calculating frame length repeatedly
+ TInt frameLen_;
+
+ // sometimes recorded size != requested framesize, so let's
+ // provide a buffer to make sure the rec callback returning
+ // framesize as requested.
+ TUint8 *frameRecBuf_;
+ TInt frameRecBufLen_;
+
+ CPjAudioInputEngine(struct mda_stream *parent_strm,
+ pjmedia_aud_rec_cb rec_cb,
+ void *user_data);
+ void ConstructL();
+ TPtr8 & GetFrame();
+
+public:
+ virtual void MaiscOpenComplete(TInt aError);
+ virtual void MaiscBufferCopied(TInt aError, const TDesC8 &aBuffer);
+ virtual void MaiscRecordComplete(TInt aError);
+
+};
+
+
+CPjAudioInputEngine::CPjAudioInputEngine(struct mda_stream *parent_strm,
+ pjmedia_aud_rec_cb rec_cb,
+ void *user_data)
+ : state_(STATE_INACTIVE), parentStrm_(parent_strm),
+ recCb_(rec_cb), userData_(user_data),
+ iInputStream_(NULL), iStreamBuffer_(NULL), iFramePtr_(0, 0),
+ lastError_(KErrNone), timeStamp_(0),
+ frameLen_(parent_strm->param.samples_per_frame *
+ parent_strm->param.channel_count *
+ BYTES_PER_SAMPLE),
+ frameRecBuf_(NULL), frameRecBufLen_(0)
+{
+}
+
+CPjAudioInputEngine::~CPjAudioInputEngine()
+{
+ Stop();
+
+ delete iStreamBuffer_;
+ iStreamBuffer_ = NULL;
+
+ delete [] frameRecBuf_;
+ frameRecBuf_ = NULL;
+ frameRecBufLen_ = 0;
+}
+
+void CPjAudioInputEngine::ConstructL()
+{
+ iStreamBuffer_ = HBufC8::NewL(frameLen_);
+ CleanupStack::PushL(iStreamBuffer_);
+
+ frameRecBuf_ = new TUint8[frameLen_*2];
+ CleanupStack::PushL(frameRecBuf_);
+}
+
+CPjAudioInputEngine *CPjAudioInputEngine::NewLC(struct mda_stream *parent,
+ pjmedia_aud_rec_cb rec_cb,
+ void *user_data)
+{
+ CPjAudioInputEngine* self = new (ELeave) CPjAudioInputEngine(parent,
+ rec_cb,
+ user_data);
+ CleanupStack::PushL(self);
+ self->ConstructL();
+ return self;
+}
+
+CPjAudioInputEngine *CPjAudioInputEngine::NewL(struct mda_stream *parent,
+ pjmedia_aud_rec_cb rec_cb,
+ void *user_data)
+{
+ CPjAudioInputEngine *self = NewLC(parent, rec_cb, user_data);
+ CleanupStack::Pop(self->frameRecBuf_);
+ CleanupStack::Pop(self->iStreamBuffer_);
+ CleanupStack::Pop(self);
+ return self;
+}
+
+
+pj_status_t CPjAudioInputEngine::StartRecord()
+{
+
+ // Ignore command if recording is in progress.
+ if (state_ == STATE_ACTIVE)
+ return PJ_SUCCESS;
+
+ // According to Nokia's AudioStream example, some 2nd Edition, FP2 devices
+ // (such as Nokia 6630) require the stream to be reconstructed each time
+ // before calling Open() - otherwise the callback never gets called.
+ // For uniform behavior, lets just delete/re-create the stream for all
+ // devices.
+
+ // Destroy existing stream.
+ if (iInputStream_) delete iInputStream_;
+ iInputStream_ = NULL;
+
+ // Create the stream.
+ TRAPD(err, iInputStream_ = CMdaAudioInputStream::NewL(*this));
+ if (err != KErrNone)
+ return PJ_RETURN_OS_ERROR(err);
+
+ // Initialize settings.
+ TMdaAudioDataSettings iStreamSettings;
+ iStreamSettings.iChannels =
+ get_channel_cap(parentStrm_->param.channel_count);
+ iStreamSettings.iSampleRate =
+ get_clock_rate_cap(parentStrm_->param.clock_rate);
+
+ pj_assert(iStreamSettings.iChannels != 0 &&
+ iStreamSettings.iSampleRate != 0);
+
+ PJ_LOG(4,(THIS_FILE, "Opening sound device for capture, "
+ "clock rate=%d, channel count=%d..",
+ parentStrm_->param.clock_rate,
+ parentStrm_->param.channel_count));
+
+ // Open stream.
+ lastError_ = KRequestPending;
+ iInputStream_->Open(&iStreamSettings);
+
+ // Success
+ PJ_LOG(4,(THIS_FILE, "Sound capture started."));
+ return PJ_SUCCESS;
+}
+
+
+void CPjAudioInputEngine::Stop()
+{
+ // If capture is in progress, stop it.
+ if (iInputStream_ && state_ == STATE_ACTIVE) {
+ lastError_ = KRequestPending;
+ iInputStream_->Stop();
+
+ // Wait until it's actually stopped
+ while (lastError_ == KRequestPending)
+ pj_symbianos_poll(-1, 100);
+ }
+
+ if (iInputStream_) {
+ delete iInputStream_;
+ iInputStream_ = NULL;
+ }
+
+ state_ = STATE_INACTIVE;
+}
+
+
+TPtr8 & CPjAudioInputEngine::GetFrame()
+{
+ //iStreamBuffer_->Des().FillZ(frameLen_);
+ iFramePtr_.Set((TUint8*)(iStreamBuffer_->Ptr()), frameLen_, frameLen_);
+ return iFramePtr_;
+}
+
+void CPjAudioInputEngine::MaiscOpenComplete(TInt aError)
+{
+ lastError_ = aError;
+ if (aError != KErrNone) {
+ snd_perror("Error in MaiscOpenComplete()", aError);
+ return;
+ }
+
+ // set stream priority to normal and time sensitive
+ iInputStream_->SetPriority(EPriorityNormal,
+ EMdaPriorityPreferenceTime);
+
+ // Read the first frame.
+ TPtr8 & frm = GetFrame();
+ TRAPD(err2, iInputStream_->ReadL(frm));
+ if (err2) {
+ PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()"));
+ }
+}
+
+void CPjAudioInputEngine::MaiscBufferCopied(TInt aError,
+ const TDesC8 &aBuffer)
+{
+ lastError_ = aError;
+ if (aError != KErrNone) {
+ snd_perror("Error in MaiscBufferCopied()", aError);
+ return;
+ }
+
+ if (frameRecBufLen_ || aBuffer.Size() < frameLen_) {
+ pj_memcpy(frameRecBuf_ + frameRecBufLen_, (void*) aBuffer.Ptr(), aBuffer.Size());
+ frameRecBufLen_ += aBuffer.Size();
+ }
+
+ if (frameRecBufLen_) {
+ while (frameRecBufLen_ >= frameLen_) {
+ pjmedia_frame f;
+
+ f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ f.buf = frameRecBuf_;
+ f.size = frameLen_;
+ f.timestamp.u32.lo = timeStamp_;
+ f.bit_info = 0;
+
+ // Call the callback.
+ recCb_(userData_, &f);
+ // Increment timestamp.
+ timeStamp_ += parentStrm_->param.samples_per_frame;
+
+ frameRecBufLen_ -= frameLen_;
+ pj_memmove(frameRecBuf_, frameRecBuf_+frameLen_, frameRecBufLen_);
+ }
+ } else {
+ pjmedia_frame f;
+
+ f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ f.buf = (void*)aBuffer.Ptr();
+ f.size = aBuffer.Size();
+ f.timestamp.u32.lo = timeStamp_;
+ f.bit_info = 0;
+
+ // Call the callback.
+ recCb_(userData_, &f);
+
+ // Increment timestamp.
+ timeStamp_ += parentStrm_->param.samples_per_frame;
+ }
+
+ // Record next frame
+ TPtr8 & frm = GetFrame();
+ TRAPD(err2, iInputStream_->ReadL(frm));
+ if (err2) {
+ PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()"));
+ }
+}
+
+
+void CPjAudioInputEngine::MaiscRecordComplete(TInt aError)
+{
+ lastError_ = aError;
+ state_ = STATE_INACTIVE;
+ if (aError != KErrNone) {
+ snd_perror("Error in MaiscRecordComplete()", aError);
+ }
+}
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+
+/*
+ * Implementation: Symbian Output Stream.
+ */
+
+class CPjAudioOutputEngine : public CBase, MMdaAudioOutputStreamCallback
+{
+public:
+ enum State
+ {
+ STATE_INACTIVE,
+ STATE_ACTIVE,
+ };
+
+ ~CPjAudioOutputEngine();
+
+ static CPjAudioOutputEngine *NewL(struct mda_stream *parent_strm,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data);
+
+ static CPjAudioOutputEngine *NewLC(struct mda_stream *parent_strm,
+ pjmedia_aud_play_cb rec_cb,
+ void *user_data);
+
+ pj_status_t StartPlay();
+ void Stop();
+
+ pj_status_t SetVolume(TInt vol) {
+ if (iOutputStream_) {
+ iOutputStream_->SetVolume(vol);
+ return PJ_SUCCESS;
+ } else
+ return PJ_EINVALIDOP;
+ }
+
+ TInt GetVolume() {
+ if (iOutputStream_) {
+ return iOutputStream_->Volume();
+ } else
+ return PJ_EINVALIDOP;
+ }
+
+ TInt GetMaxVolume() {
+ if (iOutputStream_) {
+ return iOutputStream_->MaxVolume();
+ } else
+ return PJ_EINVALIDOP;
+ }
+
+private:
+ State state_;
+ struct mda_stream *parentStrm_;
+ pjmedia_aud_play_cb playCb_;
+ void *userData_;
+ CMdaAudioOutputStream *iOutputStream_;
+ TUint8 *frameBuf_;
+ unsigned frameBufSize_;
+ TPtrC8 frame_;
+ TInt lastError_;
+ unsigned timestamp_;
+
+ CPjAudioOutputEngine(struct mda_stream *parent_strm,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data);
+ void ConstructL();
+
+ virtual void MaoscOpenComplete(TInt aError);
+ virtual void MaoscBufferCopied(TInt aError, const TDesC8& aBuffer);
+ virtual void MaoscPlayComplete(TInt aError);
+};
+
+
+CPjAudioOutputEngine::CPjAudioOutputEngine(struct mda_stream *parent_strm,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data)
+: state_(STATE_INACTIVE), parentStrm_(parent_strm), playCb_(play_cb),
+ userData_(user_data), iOutputStream_(NULL), frameBuf_(NULL),
+ lastError_(KErrNone), timestamp_(0)
+{
+}
+
+
+void CPjAudioOutputEngine::ConstructL()
+{
+ frameBufSize_ = parentStrm_->param.samples_per_frame *
+ parentStrm_->param.channel_count *
+ BYTES_PER_SAMPLE;
+ frameBuf_ = new TUint8[frameBufSize_];
+}
+
+CPjAudioOutputEngine::~CPjAudioOutputEngine()
+{
+ Stop();
+ delete [] frameBuf_;
+}
+
+CPjAudioOutputEngine *
+CPjAudioOutputEngine::NewLC(struct mda_stream *parent_strm,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data)
+{
+ CPjAudioOutputEngine* self = new (ELeave) CPjAudioOutputEngine(parent_strm,
+ play_cb,
+ user_data);
+ CleanupStack::PushL(self);
+ self->ConstructL();
+ return self;
+}
+
+CPjAudioOutputEngine *
+CPjAudioOutputEngine::NewL(struct mda_stream *parent_strm,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data)
+{
+ CPjAudioOutputEngine *self = NewLC(parent_strm, play_cb, user_data);
+ CleanupStack::Pop(self);
+ return self;
+}
+
+pj_status_t CPjAudioOutputEngine::StartPlay()
+{
+ // Ignore command if playing is in progress.
+ if (state_ == STATE_ACTIVE)
+ return PJ_SUCCESS;
+
+ // Destroy existing stream.
+ if (iOutputStream_) delete iOutputStream_;
+ iOutputStream_ = NULL;
+
+ // Create the stream
+ TRAPD(err, iOutputStream_ = CMdaAudioOutputStream::NewL(*this));
+ if (err != KErrNone)
+ return PJ_RETURN_OS_ERROR(err);
+
+ // Initialize settings.
+ TMdaAudioDataSettings iStreamSettings;
+ iStreamSettings.iChannels =
+ get_channel_cap(parentStrm_->param.channel_count);
+ iStreamSettings.iSampleRate =
+ get_clock_rate_cap(parentStrm_->param.clock_rate);
+
+ pj_assert(iStreamSettings.iChannels != 0 &&
+ iStreamSettings.iSampleRate != 0);
+
+ PJ_LOG(4,(THIS_FILE, "Opening sound device for playback, "
+ "clock rate=%d, channel count=%d..",
+ parentStrm_->param.clock_rate,
+ parentStrm_->param.channel_count));
+
+ // Open stream.
+ lastError_ = KRequestPending;
+ iOutputStream_->Open(&iStreamSettings);
+
+ // Success
+ PJ_LOG(4,(THIS_FILE, "Sound playback started"));
+ return PJ_SUCCESS;
+
+}
+
+void CPjAudioOutputEngine::Stop()
+{
+ // Stop stream if it's playing
+ if (iOutputStream_ && state_ != STATE_INACTIVE) {
+ lastError_ = KRequestPending;
+ iOutputStream_->Stop();
+
+ // Wait until it's actually stopped
+ while (lastError_ == KRequestPending)
+ pj_symbianos_poll(-1, 100);
+ }
+
+ if (iOutputStream_) {
+ delete iOutputStream_;
+ iOutputStream_ = NULL;
+ }
+
+ state_ = STATE_INACTIVE;
+}
+
+void CPjAudioOutputEngine::MaoscOpenComplete(TInt aError)
+{
+ lastError_ = aError;
+
+ if (aError==KErrNone) {
+ // output stream opened succesfully, set status to Active
+ state_ = STATE_ACTIVE;
+
+ // set stream properties, 16bit 8KHz mono
+ TMdaAudioDataSettings iSettings;
+ iSettings.iChannels =
+ get_channel_cap(parentStrm_->param.channel_count);
+ iSettings.iSampleRate =
+ get_clock_rate_cap(parentStrm_->param.clock_rate);
+
+ iOutputStream_->SetAudioPropertiesL(iSettings.iSampleRate,
+ iSettings.iChannels);
+
+ // set volume to 1/2th of stream max volume
+ iOutputStream_->SetVolume(iOutputStream_->MaxVolume()/2);
+
+ // set stream priority to normal and time sensitive
+ iOutputStream_->SetPriority(EPriorityNormal,
+ EMdaPriorityPreferenceTime);
+
+ // Call callback to retrieve frame from upstream.
+ pjmedia_frame f;
+ pj_status_t status;
+
+ f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ f.buf = frameBuf_;
+ f.size = frameBufSize_;
+ f.timestamp.u32.lo = timestamp_;
+ f.bit_info = 0;
+
+ status = playCb_(this->userData_, &f);
+ if (status != PJ_SUCCESS) {
+ this->Stop();
+ return;
+ }
+
+ // Increment timestamp.
+ timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE);
+
+ // issue WriteL() to write the first audio data block,
+ // subsequent calls to WriteL() will be issued in
+ // MMdaAudioOutputStreamCallback::MaoscBufferCopied()
+ // until whole data buffer is written.
+ frame_.Set(frameBuf_, frameBufSize_);
+ iOutputStream_->WriteL(frame_);
+ } else {
+ snd_perror("Error in MaoscOpenComplete()", aError);
+ }
+}
+
+void CPjAudioOutputEngine::MaoscBufferCopied(TInt aError,
+ const TDesC8& aBuffer)
+{
+ PJ_UNUSED_ARG(aBuffer);
+
+ if (aError==KErrNone) {
+ // Buffer successfully written, feed another one.
+
+ // Call callback to retrieve frame from upstream.
+ pjmedia_frame f;
+ pj_status_t status;
+
+ f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ f.buf = frameBuf_;
+ f.size = frameBufSize_;
+ f.timestamp.u32.lo = timestamp_;
+ f.bit_info = 0;
+
+ status = playCb_(this->userData_, &f);
+ if (status != PJ_SUCCESS) {
+ this->Stop();
+ return;
+ }
+
+ // Increment timestamp.
+ timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE);
+
+ // Write to playback stream.
+ frame_.Set(frameBuf_, frameBufSize_);
+ iOutputStream_->WriteL(frame_);
+
+ } else if (aError==KErrAbort) {
+ // playing was aborted, due to call to CMdaAudioOutputStream::Stop()
+ state_ = STATE_INACTIVE;
+ } else {
+ // error writing data to output
+ lastError_ = aError;
+ state_ = STATE_INACTIVE;
+ snd_perror("Error in MaoscBufferCopied()", aError);
+ }
+}
+
+void CPjAudioOutputEngine::MaoscPlayComplete(TInt aError)
+{
+ lastError_ = aError;
+ state_ = STATE_INACTIVE;
+ if (aError != KErrNone) {
+ snd_perror("Error in MaoscPlayComplete()", aError);
+ }
+}
+
+/****************************************************************************
+ * Factory operations
+ */
+
+/*
+ * C compatible declaration of MDA factory.
+ */
+PJ_BEGIN_DECL
+PJ_DECL(pjmedia_aud_dev_factory*) pjmedia_symb_mda_factory(pj_pool_factory *pf);
+PJ_END_DECL
+
+/*
+ * Init Symbian audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_symb_mda_factory(pj_pool_factory *pf)
+{
+ struct mda_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "symb_aud", 1000, 1000, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct mda_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+/* API: init factory */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
+{
+ struct mda_factory *af = (struct mda_factory*)f;
+
+ pj_ansi_strcpy(af->dev_info.name, "Symbian Audio");
+ af->dev_info.default_samples_per_sec = 8000;
+ af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+ af->dev_info.input_count = 1;
+ af->dev_info.output_count = 1;
+
+ PJ_LOG(4, (THIS_FILE, "Symb Mda initialized"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct mda_factory *af = (struct mda_factory*)f;
+ pj_pool_t *pool = af->pool;
+
+ af->pool = NULL;
+ pj_pool_release(pool);
+
+ PJ_LOG(4, (THIS_FILE, "Symbian Mda destroyed"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return 1;
+}
+
+/* API: get device info */
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
+{
+ struct mda_factory *af = (struct mda_factory*)f;
+
+ PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+ pj_memcpy(info, &af->dev_info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ struct mda_factory *af = (struct mda_factory*)f;
+
+ PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+ pj_bzero(param, sizeof(*param));
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ param->clock_rate = af->dev_info.default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = BITS_PER_SAMPLE;
+ param->flags = af->dev_info.caps;
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: create stream */
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ struct mda_factory *mf = (struct mda_factory*)f;
+ pj_pool_t *pool;
+ struct mda_stream *strm;
+
+ /* Can only support 16bits per sample raw PCM format. */
+ PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
+ PJ_ASSERT_RETURN((param->flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT)==0 ||
+ param->ext_fmt.id == PJMEDIA_FORMAT_L16,
+ PJ_ENOTSUP);
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(mf->pf, "symb_aud_dev", 1000, 1000, NULL);
+ PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct mda_stream);
+ strm->pool = pool;
+ strm->param = *param;
+
+ // Create the output stream.
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ TRAPD(err, strm->out_engine = CPjAudioOutputEngine::NewL(strm, play_cb,
+ user_data));
+ if (err != KErrNone) {
+ pj_pool_release(pool);
+ return PJ_RETURN_OS_ERROR(err);
+ }
+ }
+
+ // Create the input stream.
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+ TRAPD(err, strm->in_engine = CPjAudioInputEngine::NewL(strm, rec_cb,
+ user_data));
+ if (err != KErrNone) {
+ strm->in_engine = NULL;
+ delete strm->out_engine;
+ strm->out_engine = NULL;
+ pj_pool_release(pool);
+ return PJ_RETURN_OS_ERROR(err);
+ }
+ }
+
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_aud_strm = &strm->base;
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t stream_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
+{
+ struct mda_stream *strm = (struct mda_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct mda_stream *strm = (struct mda_stream*)s;
+ pj_status_t status = PJ_ENOTSUP;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ switch (cap) {
+ case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+ PJ_ASSERT_RETURN(strm->in_engine, PJ_EINVAL);
+
+ TInt max_gain = strm->in_engine->GetMaxGain();
+ TInt gain = strm->in_engine->GetGain();
+
+ if (max_gain > 0 && gain >= 0) {
+ *(unsigned*)pval = gain * 100 / max_gain;
+ status = PJ_SUCCESS;
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ }
+ break;
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ PJ_ASSERT_RETURN(strm->out_engine, PJ_EINVAL);
+
+ TInt max_vol = strm->out_engine->GetMaxVolume();
+ TInt vol = strm->out_engine->GetVolume();
+
+ if (max_vol > 0 && vol >= 0) {
+ *(unsigned*)pval = vol * 100 / max_vol;
+ status = PJ_SUCCESS;
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return status;
+}
+
+/* API: set capability */
+static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ const void *pval)
+{
+ struct mda_stream *strm = (struct mda_stream*)s;
+ pj_status_t status = PJ_ENOTSUP;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ switch (cap) {
+ case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+ PJ_ASSERT_RETURN(strm->in_engine, PJ_EINVAL);
+
+ TInt max_gain = strm->in_engine->GetMaxGain();
+ if (max_gain > 0) {
+ TInt gain;
+
+ gain = *(unsigned*)pval * max_gain / 100;
+ status = strm->in_engine->SetGain(gain);
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ }
+ break;
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+ PJ_ASSERT_RETURN(strm->out_engine, PJ_EINVAL);
+
+ TInt max_vol = strm->out_engine->GetMaxVolume();
+ if (max_vol > 0) {
+ TInt vol;
+
+ vol = *(unsigned*)pval * max_vol / 100;
+ status = strm->out_engine->SetVolume(vol);
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return status;
+}
+
+/* API: Start stream. */
+static pj_status_t stream_start(pjmedia_aud_stream *strm)
+{
+ struct mda_stream *stream = (struct mda_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if (stream->out_engine) {
+ pj_status_t status;
+ status = stream->out_engine->StartPlay();
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ if (stream->in_engine) {
+ pj_status_t status;
+ status = stream->in_engine->StartRecord();
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t stream_stop(pjmedia_aud_stream *strm)
+{
+ struct mda_stream *stream = (struct mda_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if (stream->in_engine) {
+ stream->in_engine->Stop();
+ }
+
+ if (stream->out_engine) {
+ stream->out_engine->Stop();
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
+{
+ struct mda_stream *stream = (struct mda_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ stream_stop(strm);
+
+ delete stream->in_engine;
+ stream->in_engine = NULL;
+
+ delete stream->out_engine;
+ stream->out_engine = NULL;
+
+ pj_pool_t *pool;
+ pool = stream->pool;
+ if (pool) {
+ stream->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA */
diff --git a/pjmedia/src/pjmedia-audiodev/wmme_dev.c b/pjmedia/src/pjmedia-audiodev/wmme_dev.c
new file mode 100644
index 00000000..4c690eb7
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/wmme_dev.c
@@ -0,0 +1,1311 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/string.h>
+#include <pj/unicode.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+
+#ifdef _MSC_VER
+# pragma warning(push, 3)
+#endif
+
+#include <windows.h>
+#include <mmsystem.h>
+#include <mmreg.h>
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+/* mingw lacks WAVE_FORMAT_ALAW/MULAW */
+#ifndef WAVE_FORMAT_ALAW
+# define WAVE_FORMAT_ALAW 0x0006
+#endif
+#ifndef WAVE_FORMAT_MULAW
+# define WAVE_FORMAT_MULAW 0x0007
+#endif
+
+#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0
+# pragma comment(lib, "Coredll.lib")
+#elif defined(_MSC_VER)
+# pragma comment(lib, "winmm.lib")
+#endif
+
+
+#define THIS_FILE "wmme_dev.c"
+
+/* WMME device info */
+struct wmme_dev_info
+{
+ pjmedia_aud_dev_info info;
+ unsigned deviceId;
+};
+
+/* WMME factory */
+struct wmme_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+
+ unsigned dev_count;
+ struct wmme_dev_info *dev_info;
+};
+
+
+/* Individual WMME capture/playback stream descriptor */
+struct wmme_channel
+{
+ union
+ {
+ HWAVEIN In;
+ HWAVEOUT Out;
+ } hWave;
+
+ WAVEHDR *WaveHdr;
+ HANDLE hEvent;
+ DWORD dwBufIdx;
+ DWORD dwMaxBufIdx;
+ pj_timestamp timestamp;
+};
+
+
+/* Sound stream. */
+struct wmme_stream
+{
+ pjmedia_aud_stream base; /**< Base stream */
+ pjmedia_aud_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool. */
+
+ pjmedia_aud_rec_cb rec_cb; /**< Capture callback. */
+ pjmedia_aud_play_cb play_cb; /**< Playback callback. */
+ void *user_data; /**< Application data. */
+
+ struct wmme_channel play_strm; /**< Playback stream. */
+ struct wmme_channel rec_strm; /**< Capture stream. */
+
+ void *buffer; /**< Temp. frame buffer. */
+ pjmedia_format_id fmt_id; /**< Frame format */
+ pj_uint8_t silence_char; /**< Silence pattern */
+ unsigned bytes_per_frame; /**< Bytes per frame */
+
+ pjmedia_frame_ext *xfrm; /**< Extended frame buffer */
+ unsigned xfrm_size; /**< Total ext frm size */
+
+ pj_thread_t *thread; /**< Thread handle. */
+ HANDLE thread_quit_event; /**< Quit signal to thread */
+};
+
+
+/* Prototypes */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t stream_start(pjmedia_aud_stream *strm);
+static pj_status_t stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
+
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+ &factory_init,
+ &factory_destroy,
+ &factory_get_dev_count,
+ &factory_get_dev_info,
+ &factory_default_param,
+ &factory_create_stream
+};
+
+static pjmedia_aud_stream_op stream_op =
+{
+ &stream_get_param,
+ &stream_get_cap,
+ &stream_set_cap,
+ &stream_start,
+ &stream_stop,
+ &stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init WMME audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_wmme_factory(pj_pool_factory *pf)
+{
+ struct wmme_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "WMME", 1000, 1000, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct wmme_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+
+/* Internal: build device info from WAVEINCAPS/WAVEOUTCAPS */
+static void build_dev_info(UINT deviceId, struct wmme_dev_info *wdi,
+ const WAVEINCAPS *wic, const WAVEOUTCAPS *woc)
+{
+#define WIC_WOC(wic,woc,field) (wic? wic->field : woc->field)
+
+ pj_bzero(wdi, sizeof(*wdi));
+ wdi->deviceId = deviceId;
+
+ /* Device Name */
+ if (deviceId==WAVE_MAPPER) {
+ strncpy(wdi->info.name, "Wave mapper", sizeof(wdi->info.name));
+ wdi->info.name[sizeof(wdi->info.name)-1] = '\0';
+ } else {
+ const pj_char_t *szPname = WIC_WOC(wic, woc, szPname);
+ PJ_DECL_ANSI_TEMP_BUF(wTmp, sizeof(wdi->info.name));
+
+ strncpy(wdi->info.name,
+ PJ_NATIVE_TO_STRING(szPname, wTmp, PJ_ARRAY_SIZE(wTmp)),
+ sizeof(wdi->info.name));
+ wdi->info.name[sizeof(wdi->info.name)-1] = '\0';
+ }
+
+ wdi->info.default_samples_per_sec = 16000;
+ strcpy(wdi->info.driver, "WMME");
+
+ if (wic) {
+ wdi->info.input_count = wic->wChannels;
+ wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+
+ /* Sometimes a device can return a rediculously large number of
+ * channels. This happened with an SBLive card on a Windows ME box.
+ * It also happens on Win XP!
+ */
+ if (wdi->info.input_count<1 || wdi->info.input_count>256) {
+ wdi->info.input_count = 2;
+ }
+ }
+
+ if (woc) {
+ wdi->info.output_count = woc->wChannels;
+ wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+
+ if (woc->dwSupport & WAVECAPS_VOLUME) {
+ wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+ }
+
+ /* Sometimes a device can return a rediculously large number of
+ * channels. This happened with an SBLive card on a Windows ME box.
+ * It also happens on Win XP!
+ */
+ if (wdi->info.output_count<1 || wdi->info.output_count>256) {
+ wdi->info.output_count = 2;
+ }
+ }
+
+ /* Extended formats */
+ wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
+ wdi->info.ext_fmt_cnt = 2;
+ wdi->info.ext_fmt[0].id = PJMEDIA_FORMAT_PCMU;
+ wdi->info.ext_fmt[0].bitrate = 64000;
+ wdi->info.ext_fmt[0].vad = 0;
+ wdi->info.ext_fmt[1].id = PJMEDIA_FORMAT_PCMA;
+ wdi->info.ext_fmt[1].bitrate = 64000;
+ wdi->info.ext_fmt[1].vad = 0;
+}
+
+/* API: init factory */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
+{
+ struct wmme_factory *wf = (struct wmme_factory*)f;
+ unsigned c;
+ int i;
+ int inputDeviceCount, outputDeviceCount, devCount=0;
+ pj_bool_t waveMapperAdded = PJ_FALSE;
+
+ /* Enumerate sound devices */
+ wf->dev_count = 0;
+
+ inputDeviceCount = waveInGetNumDevs();
+ devCount += inputDeviceCount;
+
+ outputDeviceCount = waveOutGetNumDevs();
+ devCount += outputDeviceCount;
+
+ if (devCount) {
+ /* Assume there is WAVE_MAPPER */
+ devCount += 2;
+ }
+
+ if (devCount==0) {
+ PJ_LOG(4,(THIS_FILE, "WMME found no sound devices"));
+ return PJMEDIA_EAUD_NODEV;
+ }
+
+ wf->dev_info = (struct wmme_dev_info*)
+ pj_pool_calloc(wf->pool, devCount,
+ sizeof(struct wmme_dev_info));
+
+ if (inputDeviceCount && outputDeviceCount) {
+ /* Attempt to add WAVE_MAPPER as input and output device */
+ WAVEINCAPS wic;
+ MMRESULT mr;
+
+ pj_bzero(&wic, sizeof(WAVEINCAPS));
+ mr = waveInGetDevCaps(WAVE_MAPPER, &wic, sizeof(WAVEINCAPS));
+
+ if (mr == MMSYSERR_NOERROR) {
+ WAVEOUTCAPS woc;
+
+ pj_bzero(&woc, sizeof(WAVEOUTCAPS));
+ mr = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS));
+ if (mr == MMSYSERR_NOERROR) {
+ build_dev_info(WAVE_MAPPER, &wf->dev_info[wf->dev_count],
+ &wic, &woc);
+ ++wf->dev_count;
+ waveMapperAdded = PJ_TRUE;
+ }
+ }
+
+ }
+
+ if (inputDeviceCount > 0) {
+ /* -1 is the WAVE_MAPPER */
+ for (i = (waveMapperAdded? 0 : -1); i < inputDeviceCount; ++i) {
+ UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i);
+ WAVEINCAPS wic;
+ MMRESULT mr;
+
+ pj_bzero(&wic, sizeof(WAVEINCAPS));
+
+ mr = waveInGetDevCaps(uDeviceID, &wic, sizeof(WAVEINCAPS));
+
+ if (mr == MMSYSERR_NOMEM)
+ return PJ_ENOMEM;
+
+ if (mr != MMSYSERR_NOERROR)
+ continue;
+
+ build_dev_info(uDeviceID, &wf->dev_info[wf->dev_count],
+ &wic, NULL);
+ ++wf->dev_count;
+ }
+ }
+
+ if( outputDeviceCount > 0 )
+ {
+ /* -1 is the WAVE_MAPPER */
+ for (i = (waveMapperAdded? 0 : -1); i < outputDeviceCount; ++i) {
+ UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i);
+ WAVEOUTCAPS woc;
+ MMRESULT mr;
+
+ pj_bzero(&woc, sizeof(WAVEOUTCAPS));
+
+ mr = waveOutGetDevCaps(uDeviceID, &woc, sizeof(WAVEOUTCAPS));
+
+ if (mr == MMSYSERR_NOMEM)
+ return PJ_ENOMEM;
+
+ if (mr != MMSYSERR_NOERROR)
+ continue;
+
+ build_dev_info(uDeviceID, &wf->dev_info[wf->dev_count],
+ NULL, &woc);
+ ++wf->dev_count;
+ }
+ }
+
+ PJ_LOG(4, (THIS_FILE, "WMME initialized, found %d devices:",
+ wf->dev_count));
+ for (c = 0; c < wf->dev_count; ++c) {
+ PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d)",
+ c,
+ wf->dev_info[c].info.name,
+ wf->dev_info[c].info.input_count,
+ wf->dev_info[c].info.output_count));
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct wmme_factory *wf = (struct wmme_factory*)f;
+ pj_pool_t *pool = wf->pool;
+
+ wf->pool = NULL;
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ struct wmme_factory *wf = (struct wmme_factory*)f;
+ return wf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
+{
+ struct wmme_factory *wf = (struct wmme_factory*)f;
+
+ PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV);
+
+ pj_memcpy(info, &wf->dev_info[index].info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ struct wmme_factory *wf = (struct wmme_factory*)f;
+ struct wmme_dev_info *di = &wf->dev_info[index];
+
+ PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV);
+
+ pj_bzero(param, sizeof(*param));
+ if (di->info.input_count && di->info.output_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ } else if (di->info.input_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->rec_id = index;
+ param->play_id = PJMEDIA_AUD_INVALID_DEV;
+ } else if (di->info.output_count) {
+ param->dir = PJMEDIA_DIR_PLAYBACK;
+ param->play_id = index;
+ param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+ } else {
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ param->clock_rate = di->info.default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = di->info.default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = 16;
+ param->flags = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+ return PJ_SUCCESS;
+}
+
+/* Internal: init WAVEFORMATEX */
+static pj_status_t init_waveformatex(LPWAVEFORMATEX wfx,
+ const pjmedia_aud_param *prm)
+{
+
+ pj_bzero(wfx, sizeof(PCMWAVEFORMAT));
+ if (prm->ext_fmt.id == PJMEDIA_FORMAT_L16) {
+ enum { BYTES_PER_SAMPLE = 2 };
+ wfx->wFormatTag = WAVE_FORMAT_PCM;
+ wfx->nChannels = (pj_uint16_t)prm->channel_count;
+ wfx->nSamplesPerSec = prm->clock_rate;
+ wfx->nBlockAlign = (pj_uint16_t)(prm->channel_count *
+ BYTES_PER_SAMPLE);
+ wfx->nAvgBytesPerSec = prm->clock_rate * prm->channel_count *
+ BYTES_PER_SAMPLE;
+ wfx->wBitsPerSample = 16;
+
+ return PJ_SUCCESS;
+
+ } else if ((prm->flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) &&
+ (prm->ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
+ prm->ext_fmt.id == PJMEDIA_FORMAT_PCMU))
+ {
+ unsigned ptime;
+
+ ptime = prm->samples_per_frame * 1000 /
+ (prm->clock_rate * prm->channel_count);
+ wfx->wFormatTag = (pj_uint16_t)
+ ((prm->ext_fmt.id==PJMEDIA_FORMAT_PCMA) ?
+ WAVE_FORMAT_ALAW : WAVE_FORMAT_MULAW);
+ wfx->nChannels = (pj_uint16_t)prm->channel_count;
+ wfx->nSamplesPerSec = prm->clock_rate;
+ wfx->nAvgBytesPerSec = prm->clock_rate * prm->channel_count;
+ wfx->nBlockAlign = (pj_uint16_t)(wfx->nAvgBytesPerSec * ptime /
+ 1000);
+ wfx->wBitsPerSample = 8;
+ wfx->cbSize = 0;
+
+ return PJ_SUCCESS;
+
+ } else {
+
+ return PJMEDIA_EAUD_BADFORMAT;
+
+ }
+}
+
+/* Get format name */
+static const char *get_fmt_name(pj_uint32_t id)
+{
+ static char name[8];
+
+ if (id == PJMEDIA_FORMAT_L16)
+ return "PCM";
+ pj_memcpy(name, &id, 4);
+ name[4] = '\0';
+ return name;
+}
+
+/* Internal: create WMME player device. */
+static pj_status_t init_player_stream( struct wmme_factory *wf,
+ pj_pool_t *pool,
+ struct wmme_stream *parent,
+ struct wmme_channel *wmme_strm,
+ const pjmedia_aud_param *prm,
+ unsigned buffer_count)
+{
+ MMRESULT mr;
+ WAVEFORMATEX wfx;
+ unsigned i, ptime;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(prm->play_id < (int)wf->dev_count, PJ_EINVAL);
+
+ /*
+ * Create a wait event.
+ */
+ wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (NULL == wmme_strm->hEvent)
+ return pj_get_os_error();
+
+ /*
+ * Set up wave format structure for opening the device.
+ */
+ status = init_waveformatex(&wfx, prm);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ ptime = prm->samples_per_frame * 1000 /
+ (prm->clock_rate * prm->channel_count);
+ parent->bytes_per_frame = wfx.nAvgBytesPerSec * ptime / 1000;
+
+ /*
+ * Open wave device.
+ */
+ mr = waveOutOpen(&wmme_strm->hWave.Out,
+ wf->dev_info[prm->play_id].deviceId,
+ &wfx, (DWORD)wmme_strm->hEvent, 0, CALLBACK_EVENT);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ }
+
+ /* Pause the wave out device */
+ mr = waveOutPause(wmme_strm->hWave.Out);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ }
+
+ /*
+ * Create the buffers.
+ */
+ wmme_strm->WaveHdr = (WAVEHDR*)
+ pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count);
+ for (i = 0; i < buffer_count; ++i) {
+ wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool,
+ parent->bytes_per_frame);
+ wmme_strm->WaveHdr[i].dwBufferLength = parent->bytes_per_frame;
+ mr = waveOutPrepareHeader(wmme_strm->hWave.Out,
+ &(wmme_strm->WaveHdr[i]),
+ sizeof(WAVEHDR));
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ }
+ mr = waveOutWrite(wmme_strm->hWave.Out, &(wmme_strm->WaveHdr[i]),
+ sizeof(WAVEHDR));
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ }
+ }
+
+ wmme_strm->dwBufIdx = 0;
+ wmme_strm->dwMaxBufIdx = buffer_count;
+ wmme_strm->timestamp.u64 = 0;
+
+ /* Done setting up play device. */
+ PJ_LOG(4, (THIS_FILE,
+ " WaveAPI Sound player \"%s\" initialized ("
+ "format=%s, clock_rate=%d, "
+ "channel_count=%d, samples_per_frame=%d (%dms))",
+ wf->dev_info[prm->play_id].info.name,
+ get_fmt_name(prm->ext_fmt.id),
+ prm->clock_rate, prm->channel_count, prm->samples_per_frame,
+ prm->samples_per_frame * 1000 / prm->clock_rate));
+
+ return PJ_SUCCESS;
+}
+
+
+/* Internal: create Windows Multimedia recorder device */
+static pj_status_t init_capture_stream( struct wmme_factory *wf,
+ pj_pool_t *pool,
+ struct wmme_stream *parent,
+ struct wmme_channel *wmme_strm,
+ const pjmedia_aud_param *prm,
+ unsigned buffer_count)
+{
+ MMRESULT mr;
+ WAVEFORMATEX wfx;
+ unsigned i, ptime;
+
+ PJ_ASSERT_RETURN(prm->rec_id < (int)wf->dev_count, PJ_EINVAL);
+
+ /*
+ * Create a wait event.
+ */
+ wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (NULL == wmme_strm->hEvent) {
+ return pj_get_os_error();
+ }
+
+ /*
+ * Set up wave format structure for opening the device.
+ */
+ init_waveformatex(&wfx, prm);
+ ptime = prm->samples_per_frame * 1000 /
+ (prm->clock_rate * prm->channel_count);
+ parent->bytes_per_frame = wfx.nAvgBytesPerSec * ptime / 1000;
+
+ /*
+ * Open wave device.
+ */
+ mr = waveInOpen(&wmme_strm->hWave.In,
+ wf->dev_info[prm->rec_id].deviceId,
+ &wfx, (DWORD)wmme_strm->hEvent, 0, CALLBACK_EVENT);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+ }
+
+ /*
+ * Create the buffers.
+ */
+ wmme_strm->WaveHdr = (WAVEHDR*)
+ pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count);
+ for (i = 0; i < buffer_count; ++i) {
+ wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool,
+ parent->bytes_per_frame);
+ wmme_strm->WaveHdr[i].dwBufferLength = parent->bytes_per_frame;
+ mr = waveInPrepareHeader(wmme_strm->hWave.In,
+ &(wmme_strm->WaveHdr[i]),
+ sizeof(WAVEHDR));
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+ }
+ mr = waveInAddBuffer(wmme_strm->hWave.In, &(wmme_strm->WaveHdr[i]),
+ sizeof(WAVEHDR));
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+ }
+ }
+
+ wmme_strm->dwBufIdx = 0;
+ wmme_strm->dwMaxBufIdx = buffer_count;
+ wmme_strm->timestamp.u64 = 0;
+
+ /* Done setting up play device. */
+ PJ_LOG(4,(THIS_FILE,
+ " WaveAPI Sound recorder \"%s\" initialized "
+ "(format=%s, clock_rate=%d, "
+ "channel_count=%d, samples_per_frame=%d (%dms))",
+ wf->dev_info[prm->rec_id].info.name,
+ get_fmt_name(prm->ext_fmt.id),
+ prm->clock_rate, prm->channel_count, prm->samples_per_frame,
+ prm->samples_per_frame * 1000 / prm->clock_rate));
+
+ return PJ_SUCCESS;
+}
+
+
+/* WMME capture and playback thread. */
+static int PJ_THREAD_FUNC wmme_dev_thread(void *arg)
+{
+ struct wmme_stream *strm = (struct wmme_stream*)arg;
+ HANDLE events[3];
+ unsigned eventCount;
+ pj_status_t status = PJ_SUCCESS;
+ static unsigned rec_cnt, play_cnt;
+ enum { MAX_BURST = 1 };
+
+ rec_cnt = play_cnt = 0;
+
+ eventCount = 0;
+ events[eventCount++] = strm->thread_quit_event;
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK)
+ events[eventCount++] = strm->play_strm.hEvent;
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE)
+ events[eventCount++] = strm->rec_strm.hEvent;
+
+
+ /* Raise self priority. We don't want the audio to be distorted by
+ * system activity.
+ */
+#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK)
+ CeSetThreadPriority(GetCurrentThread(), 153);
+ else
+ CeSetThreadPriority(GetCurrentThread(), 247);
+#else
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
+#endif
+
+ /*
+ * Loop while not signalled to quit, wait for event objects to be
+ * signalled by WMME capture and play buffer.
+ */
+ while (status == PJ_SUCCESS)
+ {
+
+ DWORD rc;
+ pjmedia_dir signalled_dir;
+
+ /* Swap hWaveIn and hWaveOut to get equal opportunity for both */
+ if (eventCount==3) {
+ HANDLE hTemp = events[2];
+ events[2] = events[1];
+ events[1] = hTemp;
+ }
+
+ rc = WaitForMultipleObjects(eventCount, events, FALSE, INFINITE);
+ if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + eventCount)
+ continue;
+
+ if (rc == WAIT_OBJECT_0)
+ break;
+
+ if (rc == (WAIT_OBJECT_0 + 1))
+ {
+ if (events[1] == strm->play_strm.hEvent)
+ signalled_dir = PJMEDIA_DIR_PLAYBACK;
+ else
+ signalled_dir = PJMEDIA_DIR_CAPTURE;
+ }
+ else
+ {
+ if (events[2] == strm->play_strm.hEvent)
+ signalled_dir = PJMEDIA_DIR_PLAYBACK;
+ else
+ signalled_dir = PJMEDIA_DIR_CAPTURE;
+ }
+
+
+ if (signalled_dir == PJMEDIA_DIR_PLAYBACK)
+ {
+ struct wmme_channel *wmme_strm = &strm->play_strm;
+ unsigned burst;
+
+ status = PJ_SUCCESS;
+
+ /*
+ * Windows Multimedia has requested us to feed some frames to
+ * playback buffer.
+ */
+
+ for (burst=0; burst<MAX_BURST &&
+ (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE);
+ ++burst)
+ {
+ void *buffer = wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
+ pjmedia_frame pcm_frame, *frame;
+ MMRESULT mr = MMSYSERR_NOERROR;
+
+ //PJ_LOG(5,(THIS_FILE, "Finished writing buffer %d",
+ // wmme_strm->dwBufIdx));
+
+ if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
+ /* PCM mode */
+ frame = &pcm_frame;
+
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame->size = strm->bytes_per_frame;
+ frame->buf = buffer;
+ frame->timestamp.u64 = wmme_strm->timestamp.u64;
+ frame->bit_info = 0;
+ } else {
+ /* Codec mode */
+ frame = &strm->xfrm->base;
+
+ strm->xfrm->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->xfrm->base.size = strm->bytes_per_frame;
+ strm->xfrm->base.buf = NULL;
+ strm->xfrm->base.timestamp.u64 = wmme_strm->timestamp.u64;
+ strm->xfrm->base.bit_info = 0;
+ }
+
+ /* Get frame from application. */
+ //PJ_LOG(5,(THIS_FILE, "xxx %u play_cb", play_cnt++));
+ status = (*strm->play_cb)(strm->user_data, frame);
+
+ if (status != PJ_SUCCESS)
+ break;
+
+ if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
+ /* PCM mode */
+ if (frame->type == PJMEDIA_FRAME_TYPE_NONE) {
+ pj_bzero(buffer, strm->bytes_per_frame);
+ } else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pj_assert(!"Frame type not supported");
+ } else if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+ /* Nothing to do */
+ } else {
+ pj_assert(!"Frame type not supported");
+ }
+ } else {
+ /* Codec mode */
+ if (frame->type == PJMEDIA_FRAME_TYPE_NONE) {
+ pj_memset(buffer, strm->silence_char,
+ strm->bytes_per_frame);
+ } else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ unsigned sz;
+ sz = pjmedia_frame_ext_copy_payload(strm->xfrm,
+ buffer,
+ strm->bytes_per_frame);
+ if (sz < strm->bytes_per_frame) {
+ pj_memset((char*)buffer+sz,
+ strm->silence_char,
+ strm->bytes_per_frame - sz);
+ }
+ } else {
+ pj_assert(!"Frame type not supported");
+ }
+ }
+
+ /* Write to the device. */
+ mr = waveOutWrite(wmme_strm->hWave.Out,
+ &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]),
+ sizeof(WAVEHDR));
+ if (mr != MMSYSERR_NOERROR) {
+ status = PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ break;
+ }
+
+ /* Increment position. */
+ if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx)
+ wmme_strm->dwBufIdx = 0;
+ wmme_strm->timestamp.u64 += strm->param.samples_per_frame /
+ strm->param.channel_count;
+ } /* for */
+ }
+ else
+ {
+ struct wmme_channel *wmme_strm = &strm->rec_strm;
+ unsigned burst;
+ MMRESULT mr = MMSYSERR_NOERROR;
+ status = PJ_SUCCESS;
+
+ /*
+ * Windows Multimedia has indicated that it has some frames ready
+ * in the capture buffer. Get as much frames as possible to
+ * prevent overflows.
+ */
+#if 0
+ {
+ static DWORD tc = 0;
+ DWORD now = GetTickCount();
+ DWORD i = 0;
+ DWORD bits = 0;
+
+ if (tc == 0) tc = now;
+
+ for (i = 0; i < wmme_strm->dwMaxBufIdx; ++i)
+ {
+ bits = bits << 4;
+ bits |= wmme_strm->WaveHdr[i].dwFlags & WHDR_DONE;
+ }
+ PJ_LOG(5,(THIS_FILE, "Record Signal> Index: %d, Delta: %4.4d, "
+ "Flags: %6.6x\n",
+ wmme_strm->dwBufIdx,
+ now - tc,
+ bits));
+ tc = now;
+ }
+#endif
+
+ for (burst=0; burst<MAX_BURST &&
+ (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE);
+ ++burst)
+ {
+ char* buffer = (char*)
+ wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
+ unsigned cap_len =
+ wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwBytesRecorded;
+ pjmedia_frame pcm_frame, *frame;
+
+ /*
+ PJ_LOG(5,(THIS_FILE, "Read %d bytes from buffer %d", cap_len,
+ wmme_strm->dwBufIdx));
+ */
+
+ if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
+ /* PCM mode */
+ if (cap_len < strm->bytes_per_frame)
+ pj_bzero(buffer + cap_len,
+ strm->bytes_per_frame - cap_len);
+
+ /* Copy the audio data out of the wave buffer. */
+ pj_memcpy(strm->buffer, buffer, strm->bytes_per_frame);
+
+ /* Prepare frame */
+ frame = &pcm_frame;
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame->buf = strm->buffer;
+ frame->size = strm->bytes_per_frame;
+ frame->timestamp.u64 = wmme_strm->timestamp.u64;
+ frame->bit_info = 0;
+
+ } else {
+ /* Codec mode */
+ frame = &strm->xfrm->base;
+
+ frame->type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ frame->buf = NULL;
+ frame->size = strm->bytes_per_frame;
+ frame->timestamp.u64 = wmme_strm->timestamp.u64;
+ frame->bit_info = 0;
+
+ strm->xfrm->samples_cnt = 0;
+ strm->xfrm->subframe_cnt = 0;
+ pjmedia_frame_ext_append_subframe(
+ strm->xfrm, buffer,
+ strm->bytes_per_frame *8,
+ strm->param.samples_per_frame
+ );
+ }
+
+ /* Re-add the buffer to the device. */
+ mr = waveInAddBuffer(wmme_strm->hWave.In,
+ &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]),
+ sizeof(WAVEHDR));
+ if (mr != MMSYSERR_NOERROR) {
+ status = PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+ break;
+ }
+
+
+ /* Call callback */
+ //PJ_LOG(5,(THIS_FILE, "xxx %u rec_cb", rec_cnt++));
+ status = (*strm->rec_cb)(strm->user_data, frame);
+ if (status != PJ_SUCCESS)
+ break;
+
+ /* Increment position. */
+ if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx)
+ wmme_strm->dwBufIdx = 0;
+ wmme_strm->timestamp.u64 += strm->param.samples_per_frame /
+ strm->param.channel_count;
+ } /* for */
+ }
+ }
+
+ PJ_LOG(5,(THIS_FILE, "WMME: thread stopping.."));
+ return 0;
+}
+
+
+/* API: create stream */
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ struct wmme_factory *wf = (struct wmme_factory*)f;
+ pj_pool_t *pool;
+ struct wmme_stream *strm;
+ pj_uint8_t silence_char;
+ pj_status_t status;
+
+ switch (param->ext_fmt.id) {
+ case PJMEDIA_FORMAT_L16:
+ silence_char = '\0';
+ break;
+ case PJMEDIA_FORMAT_ALAW:
+ silence_char = (pj_uint8_t)'\xd5';
+ break;
+ case PJMEDIA_FORMAT_ULAW:
+ silence_char = (pj_uint8_t)'\xff';
+ break;
+ default:
+ return PJMEDIA_EAUD_BADFORMAT;
+ }
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(wf->pf, "wmme-dev", 1000, 1000, NULL);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct wmme_stream);
+ pj_memcpy(&strm->param, param, sizeof(*param));
+ strm->pool = pool;
+ strm->rec_cb = rec_cb;
+ strm->play_cb = play_cb;
+ strm->user_data = user_data;
+ strm->fmt_id = param->ext_fmt.id;
+ strm->silence_char = silence_char;
+
+ /* Create player stream */
+ if (param->dir & PJMEDIA_DIR_PLAYBACK) {
+ unsigned buf_count;
+
+ if ((param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)==0) {
+ strm->param.flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ strm->param.output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+ }
+
+ buf_count = strm->param.output_latency_ms * param->clock_rate *
+ param->channel_count / param->samples_per_frame / 1000;
+
+ status = init_player_stream(wf, strm->pool,
+ strm,
+ &strm->play_strm,
+ param,
+ buf_count);
+
+ if (status != PJ_SUCCESS) {
+ stream_destroy(&strm->base);
+ return status;
+ }
+ }
+
+ /* Create capture stream */
+ if (param->dir & PJMEDIA_DIR_CAPTURE) {
+ unsigned buf_count;
+
+ if ((param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)==0) {
+ strm->param.flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+ strm->param.input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ }
+
+ buf_count = strm->param.input_latency_ms * param->clock_rate *
+ param->channel_count / param->samples_per_frame / 1000;
+
+ status = init_capture_stream(wf, strm->pool,
+ strm,
+ &strm->rec_strm,
+ param,
+ buf_count);
+
+ if (status != PJ_SUCCESS) {
+ stream_destroy(&strm->base);
+ return status;
+ }
+ }
+
+ strm->buffer = pj_pool_alloc(pool, strm->bytes_per_frame);
+ if (!strm->buffer) {
+ pj_pool_release(pool);
+ return PJ_ENOMEM;
+ }
+
+ /* If format is extended, must create buffer for the extended frame. */
+ if (strm->fmt_id != PJMEDIA_FORMAT_L16) {
+ strm->xfrm_size = sizeof(pjmedia_frame_ext) +
+ 32 * sizeof(pjmedia_frame_ext_subframe) +
+ strm->bytes_per_frame + 4;
+ strm->xfrm = (pjmedia_frame_ext*)
+ pj_pool_alloc(pool, strm->xfrm_size);
+ }
+
+ /* Create the stop event */
+ strm->thread_quit_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (strm->thread_quit_event == NULL) {
+ status = pj_get_os_error();
+ stream_destroy(&strm->base);
+ return status;
+ }
+
+ /* Create and start the thread */
+ status = pj_thread_create(pool, "wmme", &wmme_dev_thread, strm, 0, 0,
+ &strm->thread);
+ if (status != PJ_SUCCESS) {
+ stream_destroy(&strm->base);
+ return status;
+ }
+
+ /* Apply the remaining settings */
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
+ stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &param->output_vol);
+ }
+
+
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_aud_strm = &strm->base;
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t stream_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
+{
+ struct wmme_stream *strm = (struct wmme_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ /* Update the volume setting */
+ if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &pi->output_vol) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct wmme_stream *strm = (struct wmme_stream*)s;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_CAPTURE))
+ {
+ /* Recording latency */
+ *(unsigned*)pval = strm->param.input_latency_ms;
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+ /* Playback latency */
+ *(unsigned*)pval = strm->param.output_latency_ms;
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
+ strm->play_strm.hWave.Out)
+ {
+ /* Output volume setting */
+ DWORD waveVol;
+ MMRESULT mr;
+
+ mr = waveOutGetVolume(strm->play_strm.hWave.Out, &waveVol);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ }
+
+ waveVol &= 0xFFFF;
+ *(unsigned*)pval = (waveVol * 100) / 0xFFFF;
+ return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+}
+
+/* API: set capability */
+static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ const void *pval)
+{
+ struct wmme_stream *strm = (struct wmme_stream*)s;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
+ strm->play_strm.hWave.Out)
+ {
+ /* Output volume setting */
+ unsigned vol = *(unsigned*)pval;
+ DWORD waveVol;
+ MMRESULT mr;
+ pj_status_t status;
+
+ if (vol > 100)
+ vol = 100;
+
+ waveVol = (vol * 0xFFFF) / 100;
+ waveVol |= (waveVol << 16);
+
+ mr = waveOutSetVolume(strm->play_strm.hWave.Out, waveVol);
+ status = (mr==MMSYSERR_NOERROR)? PJ_SUCCESS :
+ PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ if (status == PJ_SUCCESS) {
+ strm->param.output_vol = *(unsigned*)pval;
+ }
+ return status;
+ }
+
+ return PJMEDIA_EAUD_INVCAP;
+}
+
+/* API: Start stream. */
+static pj_status_t stream_start(pjmedia_aud_stream *strm)
+{
+ struct wmme_stream *stream = (struct wmme_stream*)strm;
+ MMRESULT mr;
+
+ if (stream->play_strm.hWave.Out != NULL)
+ {
+ mr = waveOutRestart(stream->play_strm.hWave.Out);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ }
+ PJ_LOG(4,(THIS_FILE, "WMME playback stream started"));
+ }
+
+ if (stream->rec_strm.hWave.In != NULL)
+ {
+ mr = waveInStart(stream->rec_strm.hWave.In);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+ }
+ PJ_LOG(4,(THIS_FILE, "WMME capture stream started"));
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t stream_stop(pjmedia_aud_stream *strm)
+{
+ struct wmme_stream *stream = (struct wmme_stream*)strm;
+ MMRESULT mr;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ if (stream->play_strm.hWave.Out != NULL)
+ {
+ mr = waveOutPause(stream->play_strm.hWave.Out);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ }
+ PJ_LOG(4,(THIS_FILE, "Stopped WMME playback stream"));
+ }
+
+ if (stream->rec_strm.hWave.In != NULL)
+ {
+ mr = waveInStop(stream->rec_strm.hWave.In);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+ }
+ PJ_LOG(4,(THIS_FILE, "Stopped WMME capture stream"));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
+{
+ struct wmme_stream *stream = (struct wmme_stream*)strm;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ stream_stop(strm);
+
+ if (stream->thread)
+ {
+ SetEvent(stream->thread_quit_event);
+ pj_thread_join(stream->thread);
+ pj_thread_destroy(stream->thread);
+ stream->thread = NULL;
+ }
+
+ /* Unprepare the headers and close the play device */
+ if (stream->play_strm.hWave.Out)
+ {
+ waveOutReset(stream->play_strm.hWave.Out);
+ for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i)
+ waveOutUnprepareHeader(stream->play_strm.hWave.Out,
+ &(stream->play_strm.WaveHdr[i]),
+ sizeof(WAVEHDR));
+ waveOutClose(stream->play_strm.hWave.Out);
+ stream->play_strm.hWave.Out = NULL;
+ }
+
+ /* Close the play event */
+ if (stream->play_strm.hEvent)
+ {
+ CloseHandle(stream->play_strm.hEvent);
+ stream->play_strm.hEvent = NULL;
+ }
+
+ /* Unprepare the headers and close the record device */
+ if (stream->rec_strm.hWave.In)
+ {
+ waveInReset(stream->rec_strm.hWave.In);
+ for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i)
+ waveInUnprepareHeader(stream->rec_strm.hWave.In,
+ &(stream->rec_strm.WaveHdr[i]),
+ sizeof(WAVEHDR));
+ waveInClose(stream->rec_strm.hWave.In);
+ stream->rec_strm.hWave.In = NULL;
+ }
+
+ /* Close the record event */
+ if (stream->rec_strm.hEvent)
+ {
+ CloseHandle(stream->rec_strm.hEvent);
+ stream->rec_strm.hEvent = NULL;
+ }
+
+ pj_pool_release(stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_AUDIO_DEV_HAS_WMME */
+
diff --git a/pjmedia/src/pjmedia-codec/passthrough.c b/pjmedia/src/pjmedia-codec/passthrough.c
new file mode 100644
index 00000000..0374bc00
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/passthrough.c
@@ -0,0 +1,850 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjmedia-codec/passthrough.h>
+#include <pjmedia/codec.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/endpoint.h>
+#include <pjmedia/port.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/os.h>
+
+/*
+ * Only build this file if PJMEDIA_HAS_PASSTHROUGH_CODECS != 0
+ */
+#if defined(PJMEDIA_HAS_PASSTHROUGH_CODECS) && PJMEDIA_HAS_PASSTHROUGH_CODECS!=0
+
+#define THIS_FILE "passthrough.c"
+
+
+/* Prototypes for passthrough codecs factory */
+static pj_status_t test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id );
+static pj_status_t default_attr( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr );
+static pj_status_t enum_codecs( pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[]);
+static pj_status_t alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec);
+static pj_status_t dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec );
+
+/* Prototypes for passthrough codecs implementation. */
+static pj_status_t codec_init( pjmedia_codec *codec,
+ pj_pool_t *pool );
+static pj_status_t codec_open( pjmedia_codec *codec,
+ pjmedia_codec_param *attr );
+static pj_status_t codec_close( pjmedia_codec *codec );
+static pj_status_t codec_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr );
+static pj_status_t codec_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[]);
+static pj_status_t codec_encode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t codec_decode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t codec_recover( pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+
+/* Definition for passthrough codecs operations. */
+static pjmedia_codec_op codec_op =
+{
+ &codec_init,
+ &codec_open,
+ &codec_close,
+ &codec_modify,
+ &codec_parse,
+ &codec_encode,
+ &codec_decode,
+ &codec_recover
+};
+
+/* Definition for passthrough codecs factory operations. */
+static pjmedia_codec_factory_op codec_factory_op =
+{
+ &test_alloc,
+ &default_attr,
+ &enum_codecs,
+ &alloc_codec,
+ &dealloc_codec
+};
+
+/* Passthrough codecs factory */
+static struct codec_factory {
+ pjmedia_codec_factory base;
+ pjmedia_endpt *endpt;
+ pj_pool_t *pool;
+ pj_mutex_t *mutex;
+} codec_factory;
+
+/* Passthrough codecs private data. */
+typedef struct codec_private {
+ pj_pool_t *pool; /**< Pool for each instance. */
+ int codec_idx; /**< Codec index. */
+ void *codec_setting; /**< Specific codec setting. */
+ pj_uint16_t avg_frame_size; /**< Average of frame size. */
+} codec_private_t;
+
+
+
+/* CUSTOM CALLBACKS */
+
+/* Parse frames from a packet. Default behaviour of frame parsing is
+ * just separating frames based on calculating frame length derived
+ * from bitrate. Implement this callback when the default behaviour is
+ * unapplicable.
+ */
+typedef pj_status_t (*parse_cb)(codec_private_t *codec_data, void *pkt,
+ pj_size_t pkt_size, const pj_timestamp *ts,
+ unsigned *frame_cnt, pjmedia_frame frames[]);
+
+/* Pack frames into a packet. Default behaviour of packing frames is
+ * just stacking the frames with octet aligned without adding any
+ * payload header. Implement this callback when the default behaviour is
+ * unapplicable.
+ */
+typedef pj_status_t (*pack_cb)(codec_private_t *codec_data,
+ const struct pjmedia_frame_ext *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+
+
+/* Custom callback implementations. */
+static pj_status_t parse_amr( codec_private_t *codec_data, void *pkt,
+ pj_size_t pkt_size, const pj_timestamp *ts,
+ unsigned *frame_cnt, pjmedia_frame frames[]);
+static pj_status_t pack_amr ( codec_private_t *codec_data,
+ const struct pjmedia_frame_ext *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+
+
+/* Passthrough codec implementation descriptions. */
+static struct codec_desc {
+ int enabled; /* Is this codec enabled? */
+ const char *name; /* Codec name. */
+ pj_uint8_t pt; /* Payload type. */
+ pjmedia_format_id fmt_id; /* Source format. */
+ unsigned clock_rate; /* Codec's clock rate. */
+ unsigned channel_count; /* Codec's channel count. */
+ unsigned samples_per_frame; /* Codec's samples count. */
+ unsigned def_bitrate; /* Default bitrate of this codec. */
+ unsigned max_bitrate; /* Maximum bitrate of this codec. */
+ pj_uint8_t frm_per_pkt; /* Default num of frames per packet.*/
+ pj_uint8_t vad; /* VAD enabled/disabled. */
+ pj_uint8_t plc; /* PLC enabled/disabled. */
+ parse_cb parse; /* Callback to parse bitstream. */
+ pack_cb pack; /* Callback to pack bitstream. */
+ pjmedia_codec_fmtp dec_fmtp; /* Decoder's fmtp params. */
+}
+
+codec_desc[] =
+{
+# if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+ {1, "AMR", PJMEDIA_RTP_PT_AMR, PJMEDIA_FORMAT_AMR,
+ 8000, 1, 160,
+ 7400, 12200, 2, 1, 1,
+ &parse_amr, &pack_amr
+ /*, {1, {{{"octet-align", 11}, {"1", 1}}} } */
+ },
+# endif
+
+# if PJMEDIA_HAS_PASSTHROUGH_CODEC_G729
+ {1, "G729", PJMEDIA_RTP_PT_G729, PJMEDIA_FORMAT_G729,
+ 8000, 1, 80,
+ 8000, 8000, 2, 1, 1
+ },
+# endif
+
+# if PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC
+ {1, "iLBC", PJMEDIA_RTP_PT_ILBC, PJMEDIA_FORMAT_ILBC,
+ 8000, 1, 240,
+ 13333, 15200, 1, 1, 1,
+ NULL, NULL,
+ {1, {{{"mode", 4}, {"30", 2}}} }
+ },
+# endif
+
+# if PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU
+ {1, "PCMU", PJMEDIA_RTP_PT_PCMU, PJMEDIA_FORMAT_PCMU,
+ 8000, 1, 80,
+ 64000, 64000, 2, 1, 1
+ },
+# endif
+
+# if PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA
+ {1, "PCMA", PJMEDIA_RTP_PT_PCMA, PJMEDIA_FORMAT_PCMA,
+ 8000, 1, 80,
+ 64000, 64000, 2, 1, 1
+ },
+# endif
+};
+
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+
+#include <pjmedia-codec/amr_helper.h>
+
+typedef struct amr_settings_t {
+ pjmedia_codec_amr_pack_setting enc_setting;
+ pjmedia_codec_amr_pack_setting dec_setting;
+ pj_int8_t enc_mode;
+} amr_settings_t;
+
+
+/* Pack AMR payload */
+static pj_status_t pack_amr ( codec_private_t *codec_data,
+ const struct pjmedia_frame_ext *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ enum {MAX_FRAMES_PER_PACKET = 8};
+
+ pjmedia_frame frames[MAX_FRAMES_PER_PACKET];
+ amr_settings_t* setting = (amr_settings_t*)codec_data->codec_setting;
+ pjmedia_codec_amr_pack_setting *enc_setting = &setting->enc_setting;
+ pj_uint8_t SID_FT;
+ unsigned i;
+
+ pj_assert(input->subframe_cnt <= MAX_FRAMES_PER_PACKET);
+
+ SID_FT = (pj_uint8_t)(enc_setting->amr_nb? 8 : 9);
+
+ /* Get frames */
+ for (i = 0; i < input->subframe_cnt; ++i) {
+ pjmedia_frame_ext_subframe *sf;
+ pjmedia_codec_amr_bit_info *info;
+ unsigned len;
+
+ sf = pjmedia_frame_ext_get_subframe(input, i);
+ len = (sf->bitlen + 7) >> 3;
+
+ info = (pjmedia_codec_amr_bit_info*) &frames[i].bit_info;
+ pj_bzero(info, sizeof(*info));
+
+ if (len == 0) {
+ info->frame_type = (pj_uint8_t)(enc_setting->amr_nb? 14 : 15);
+ } else {
+ info->frame_type = pjmedia_codec_amr_get_mode2(enc_setting->amr_nb,
+ len);
+ }
+ info->good_quality = 1;
+ info->mode = setting->enc_mode;
+
+ frames[i].buf = sf->data;
+ frames[i].size = len;
+ }
+
+ output->size = output_buf_len;
+
+ return pjmedia_codec_amr_pack(frames, input->subframe_cnt, enc_setting,
+ output->buf, &output->size);
+}
+
+
+/* Parse AMR payload into frames. */
+static pj_status_t parse_amr(codec_private_t *codec_data, void *pkt,
+ pj_size_t pkt_size, const pj_timestamp *ts,
+ unsigned *frame_cnt, pjmedia_frame frames[])
+{
+ amr_settings_t* s = (amr_settings_t*)codec_data->codec_setting;
+ pjmedia_codec_amr_pack_setting *setting;
+ pj_status_t status;
+ pj_uint8_t cmr;
+
+ setting = &s->dec_setting;
+
+ status = pjmedia_codec_amr_parse(pkt, pkt_size, ts, setting, frames,
+ frame_cnt, &cmr);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ // CMR is not supported for now.
+ /* Check Change Mode Request. */
+ //if ((setting->amr_nb && cmr <= 7) || (!setting->amr_nb && cmr <= 8)) {
+ // s->enc_mode = cmr;
+ //}
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_HAS_PASSTROUGH_CODEC_AMR */
+
+
+/*
+ * Initialize and register passthrough codec factory to pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_passthrough_init( pjmedia_endpt *endpt )
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+ if (codec_factory.pool != NULL) {
+ /* Already initialized. */
+ return PJ_SUCCESS;
+ }
+
+ /* Create passthrough codec factory. */
+ codec_factory.base.op = &codec_factory_op;
+ codec_factory.base.factory_data = NULL;
+ codec_factory.endpt = endpt;
+
+ codec_factory.pool = pjmedia_endpt_create_pool(endpt, "Passthrough codecs",
+ 4000, 4000);
+ if (!codec_factory.pool)
+ return PJ_ENOMEM;
+
+ /* Create mutex. */
+ status = pj_mutex_create_simple(codec_factory.pool, "Passthrough codecs",
+ &codec_factory.mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
+ if (!codec_mgr) {
+ status = PJ_EINVALIDOP;
+ goto on_error;
+ }
+
+ /* Register codec factory to endpoint. */
+ status = pjmedia_codec_mgr_register_factory(codec_mgr,
+ &codec_factory.base);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Done. */
+ return PJ_SUCCESS;
+
+on_error:
+ pj_pool_release(codec_factory.pool);
+ codec_factory.pool = NULL;
+ return status;
+}
+
+/*
+ * Unregister passthrough codecs factory from pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_passthrough_deinit(void)
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+ if (codec_factory.pool == NULL) {
+ /* Already deinitialized */
+ return PJ_SUCCESS;
+ }
+
+ pj_mutex_lock(codec_factory.mutex);
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(codec_factory.endpt);
+ if (!codec_mgr) {
+ pj_pool_release(codec_factory.pool);
+ codec_factory.pool = NULL;
+ return PJ_EINVALIDOP;
+ }
+
+ /* Unregister passthrough codecs factory. */
+ status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
+ &codec_factory.base);
+
+ /* Destroy mutex. */
+ pj_mutex_destroy(codec_factory.mutex);
+
+ /* Destroy pool. */
+ pj_pool_release(codec_factory.pool);
+ codec_factory.pool = NULL;
+
+ return status;
+}
+
+/*
+ * Check if factory can allocate the specified codec.
+ */
+static pj_status_t test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *info )
+{
+ unsigned i;
+
+ PJ_UNUSED_ARG(factory);
+
+ /* Type MUST be audio. */
+ if (info->type != PJMEDIA_TYPE_AUDIO)
+ return PJMEDIA_CODEC_EUNSUP;
+
+ for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
+ pj_str_t name = pj_str((char*)codec_desc[i].name);
+ if ((pj_stricmp(&info->encoding_name, &name) == 0) &&
+ (info->clock_rate == (unsigned)codec_desc[i].clock_rate) &&
+ (info->channel_cnt == (unsigned)codec_desc[i].channel_count) &&
+ (codec_desc[i].enabled))
+ {
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* Unsupported, or mode is disabled. */
+ return PJMEDIA_CODEC_EUNSUP;
+}
+
+/*
+ * Generate default attribute.
+ */
+static pj_status_t default_attr ( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr )
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(factory==&codec_factory.base, PJ_EINVAL);
+
+ pj_bzero(attr, sizeof(pjmedia_codec_param));
+
+ for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
+ pj_str_t name = pj_str((char*)codec_desc[i].name);
+ if ((pj_stricmp(&id->encoding_name, &name) == 0) &&
+ (id->clock_rate == (unsigned)codec_desc[i].clock_rate) &&
+ (id->channel_cnt == (unsigned)codec_desc[i].channel_count) &&
+ (id->pt == (unsigned)codec_desc[i].pt))
+ {
+ attr->info.pt = (pj_uint8_t)id->pt;
+ attr->info.channel_cnt = codec_desc[i].channel_count;
+ attr->info.clock_rate = codec_desc[i].clock_rate;
+ attr->info.avg_bps = codec_desc[i].def_bitrate;
+ attr->info.max_bps = codec_desc[i].max_bitrate;
+ attr->info.pcm_bits_per_sample = 16;
+ attr->info.frm_ptime = (pj_uint16_t)
+ (codec_desc[i].samples_per_frame * 1000 /
+ codec_desc[i].channel_count /
+ codec_desc[i].clock_rate);
+ attr->info.fmt_id = codec_desc[i].fmt_id;
+
+ /* Default flags. */
+ attr->setting.frm_per_pkt = codec_desc[i].frm_per_pkt;
+ attr->setting.plc = codec_desc[i].plc;
+ attr->setting.penh= 0;
+ attr->setting.vad = codec_desc[i].vad;
+ attr->setting.cng = attr->setting.vad;
+ attr->setting.dec_fmtp = codec_desc[i].dec_fmtp;
+
+ if (attr->setting.vad == 0) {
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_G729
+ if (id->pt == PJMEDIA_RTP_PT_G729) {
+ /* Signal G729 Annex B is being disabled */
+ attr->setting.dec_fmtp.cnt = 1;
+ pj_strset2(&attr->setting.dec_fmtp.param[0].name, "annexb");
+ pj_strset2(&attr->setting.dec_fmtp.param[0].val, "no");
+ }
+#endif
+ }
+
+ return PJ_SUCCESS;
+ }
+ }
+
+ return PJMEDIA_CODEC_EUNSUP;
+}
+
+/*
+ * Enum codecs supported by this factory.
+ */
+static pj_status_t enum_codecs( pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[])
+{
+ unsigned max;
+ unsigned i;
+
+ PJ_UNUSED_ARG(factory);
+ PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);
+
+ max = *count;
+
+ for (i = 0, *count = 0; i < PJ_ARRAY_SIZE(codec_desc) && *count < max; ++i)
+ {
+ if (!codec_desc[i].enabled)
+ continue;
+
+ pj_bzero(&codecs[*count], sizeof(pjmedia_codec_info));
+ codecs[*count].encoding_name = pj_str((char*)codec_desc[i].name);
+ codecs[*count].pt = codec_desc[i].pt;
+ codecs[*count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[*count].clock_rate = codec_desc[i].clock_rate;
+ codecs[*count].channel_cnt = codec_desc[i].channel_count;
+
+ ++*count;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Allocate a new codec instance.
+ */
+static pj_status_t alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec)
+{
+ codec_private_t *codec_data;
+ pjmedia_codec *codec;
+ int idx;
+ pj_pool_t *pool;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &codec_factory.base, PJ_EINVAL);
+
+ pj_mutex_lock(codec_factory.mutex);
+
+ /* Find codec's index */
+ idx = -1;
+ for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
+ pj_str_t name = pj_str((char*)codec_desc[i].name);
+ if ((pj_stricmp(&id->encoding_name, &name) == 0) &&
+ (id->clock_rate == (unsigned)codec_desc[i].clock_rate) &&
+ (id->channel_cnt == (unsigned)codec_desc[i].channel_count) &&
+ (codec_desc[i].enabled))
+ {
+ idx = i;
+ break;
+ }
+ }
+ if (idx == -1) {
+ *p_codec = NULL;
+ return PJMEDIA_CODEC_EUNSUP;
+ }
+
+ /* Create pool for codec instance */
+ pool = pjmedia_endpt_create_pool(codec_factory.endpt, "passthroughcodec",
+ 512, 512);
+ codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec);
+ codec->op = &codec_op;
+ codec->factory = factory;
+ codec->codec_data = PJ_POOL_ZALLOC_T(pool, codec_private_t);
+ codec_data = (codec_private_t*) codec->codec_data;
+ codec_data->pool = pool;
+ codec_data->codec_idx = idx;
+
+ pj_mutex_unlock(codec_factory.mutex);
+
+ *p_codec = codec;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Free codec.
+ */
+static pj_status_t dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec )
+{
+ codec_private_t *codec_data;
+
+ PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &codec_factory.base, PJ_EINVAL);
+
+ /* Close codec, if it's not closed. */
+ codec_data = (codec_private_t*) codec->codec_data;
+ codec_close(codec);
+
+ pj_pool_release(codec_data->pool);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Init codec.
+ */
+static pj_status_t codec_init( pjmedia_codec *codec,
+ pj_pool_t *pool )
+{
+ PJ_UNUSED_ARG(codec);
+ PJ_UNUSED_ARG(pool);
+ return PJ_SUCCESS;
+}
+
+/*
+ * Open codec.
+ */
+static pj_status_t codec_open( pjmedia_codec *codec,
+ pjmedia_codec_param *attr )
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+ struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
+ pj_pool_t *pool;
+ int i, j;
+
+ pool = codec_data->pool;
+
+ /* Get bitstream size */
+ i = attr->info.avg_bps * desc->samples_per_frame;
+ j = desc->clock_rate << 3;
+ codec_data->avg_frame_size = (pj_uint16_t)(i / j);
+ if (i % j) ++codec_data->avg_frame_size;
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+ /* Init AMR settings */
+ if (desc->pt == PJMEDIA_RTP_PT_AMR || desc->pt == PJMEDIA_RTP_PT_AMRWB) {
+ amr_settings_t *s;
+ pj_uint8_t octet_align = 0;
+ const pj_str_t STR_FMTP_OCTET_ALIGN = {"octet-align", 11};
+
+ /* Fetch octet-align setting. It should be fine to fetch only
+ * the decoder, since encoder & decoder must use the same setting
+ * (RFC 4867 section 8.3.1).
+ */
+ for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) {
+ if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name,
+ &STR_FMTP_OCTET_ALIGN) == 0)
+ {
+ octet_align=(pj_uint8_t)
+ (pj_strtoul(&attr->setting.dec_fmtp.param[i].val));
+ break;
+ }
+ }
+
+ s = PJ_POOL_ZALLOC_T(pool, amr_settings_t);
+ codec_data->codec_setting = s;
+
+ s->enc_mode = pjmedia_codec_amr_get_mode(desc->def_bitrate);
+ if (s->enc_mode < 0)
+ return PJMEDIA_CODEC_EINMODE;
+
+ s->enc_setting.amr_nb = (pj_uint8_t)(desc->pt == PJMEDIA_RTP_PT_AMR);
+ s->enc_setting.octet_aligned = octet_align;
+ s->enc_setting.reorder = PJ_FALSE; /* Note this! passthrough codec
+ doesn't do sensitivity bits
+ reordering */
+ s->enc_setting.cmr = 15;
+
+ s->dec_setting.amr_nb = (pj_uint8_t)(desc->pt == PJMEDIA_RTP_PT_AMR);
+ s->dec_setting.octet_aligned = octet_align;
+ s->dec_setting.reorder = PJ_FALSE; /* Note this! passthrough codec
+ doesn't do sensitivity bits
+ reordering */
+ }
+#endif
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Close codec.
+ */
+static pj_status_t codec_close( pjmedia_codec *codec )
+{
+ PJ_UNUSED_ARG(codec);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Modify codec settings.
+ */
+static pj_status_t codec_modify( pjmedia_codec *codec,
+ const pjmedia_codec_param *attr )
+{
+ /* Not supported yet. */
+ PJ_UNUSED_ARG(codec);
+ PJ_UNUSED_ARG(attr);
+
+ return PJ_ENOTSUP;
+}
+
+/*
+ * Get frames in the packet.
+ */
+static pj_status_t codec_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[])
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+ struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
+ unsigned count = 0;
+
+ PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL);
+
+ if (desc->parse != NULL) {
+ return desc->parse(codec_data, pkt, pkt_size, ts, frame_cnt, frames);
+ }
+
+ while (pkt_size >= codec_data->avg_frame_size && count < *frame_cnt) {
+ frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frames[count].buf = pkt;
+ frames[count].size = codec_data->avg_frame_size;
+ frames[count].timestamp.u64 = ts->u64 + count*desc->samples_per_frame;
+
+ pkt = (pj_uint8_t*)pkt + codec_data->avg_frame_size;
+ pkt_size -= codec_data->avg_frame_size;
+
+ ++count;
+ }
+
+ if (pkt_size && count < *frame_cnt) {
+ frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frames[count].buf = pkt;
+ frames[count].size = pkt_size;
+ frames[count].timestamp.u64 = ts->u64 + count*desc->samples_per_frame;
+ ++count;
+ }
+
+ *frame_cnt = count;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Encode frames.
+ */
+static pj_status_t codec_encode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+ struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
+ const pjmedia_frame_ext *input_ = (const pjmedia_frame_ext*) input;
+
+ pj_assert(input && input->type == PJMEDIA_FRAME_TYPE_EXTENDED);
+
+ if (desc->pack != NULL) {
+ desc->pack(codec_data, input_, output_buf_len, output);
+ } else {
+ if (input_->subframe_cnt == 0) {
+ /* DTX */
+ output->buf = NULL;
+ output->size = 0;
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ } else {
+ unsigned i;
+ pj_uint8_t *p = output->buf;
+
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->size = 0;
+
+ for (i = 0; i < input_->subframe_cnt; ++i) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned sf_len;
+
+ sf = pjmedia_frame_ext_get_subframe(input_, i);
+ pj_assert(sf);
+
+ sf_len = (sf->bitlen + 7) >> 3;
+
+ pj_memcpy(p, sf->data, sf_len);
+ p += sf_len;
+ output->size += sf_len;
+
+ /* If there is SID or DTX frame, break the loop. */
+ if (desc->pt == PJMEDIA_RTP_PT_G729 &&
+ sf_len < codec_data->avg_frame_size)
+ {
+ break;
+ }
+
+ }
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Decode frame.
+ */
+static pj_status_t codec_decode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+ struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
+ pjmedia_frame_ext *output_ = (pjmedia_frame_ext*) output;
+
+ pj_assert(input);
+ PJ_UNUSED_ARG(output_buf_len);
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+ /* Need to rearrange the AMR bitstream, since the bitstream may not be
+ * started from bit 0 or may need to be reordered from sensitivity order
+ * into encoder bits order.
+ */
+ if (desc->pt == PJMEDIA_RTP_PT_AMR || desc->pt == PJMEDIA_RTP_PT_AMRWB) {
+ pjmedia_frame input_;
+ pjmedia_codec_amr_pack_setting *setting;
+
+ setting = &((amr_settings_t*)codec_data->codec_setting)->dec_setting;
+
+ input_ = *input;
+ pjmedia_codec_amr_predecode(input, setting, &input_);
+
+ pjmedia_frame_ext_append_subframe(output_, input_.buf,
+ (pj_uint16_t)(input_.size << 3),
+ (pj_uint16_t)desc->samples_per_frame);
+
+ return PJ_SUCCESS;
+ }
+#endif
+
+ pjmedia_frame_ext_append_subframe(output_, input->buf,
+ (pj_uint16_t)(input->size << 3),
+ (pj_uint16_t)desc->samples_per_frame);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Recover lost frame.
+ */
+static pj_status_t codec_recover( pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+ struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
+ pjmedia_frame_ext *output_ = (pjmedia_frame_ext*) output;
+
+ PJ_UNUSED_ARG(output_buf_len);
+
+ pjmedia_frame_ext_append_subframe(output_, NULL, 0,
+ (pj_uint16_t)desc->samples_per_frame);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
+
diff --git a/pjmedia/src/pjmedia/conf_switch.c b/pjmedia/src/pjmedia/conf_switch.c
new file mode 100644
index 00000000..bf8ca504
--- /dev/null
+++ b/pjmedia/src/pjmedia/conf_switch.c
@@ -0,0 +1,1466 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjmedia/conference.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/port.h>
+#include <pjmedia/silencedet.h>
+#include <pjmedia/sound_port.h>
+#include <pjmedia/stream.h>
+#include <pj/array.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#if defined(PJMEDIA_CONF_USE_SWITCH_BOARD) && PJMEDIA_CONF_USE_SWITCH_BOARD!=0
+
+/* CONF_DEBUG enables detailed operation of the conference bridge.
+ * Beware that it prints large amounts of logs (several lines per frame).
+ */
+//#define CONF_DEBUG
+#ifdef CONF_DEBUG
+# include <stdio.h>
+# define TRACE_(x) PJ_LOG(5,x)
+#else
+# define TRACE_(x)
+#endif
+
+#define THIS_FILE "conf_switch.c"
+
+#define SIGNATURE PJMEDIA_CONF_SWITCH_SIGNATURE
+#define SIGNATURE_PORT PJMEDIA_PORT_SIGNATURE('S', 'W', 'T', 'P')
+#define NORMAL_LEVEL 128
+#define SLOT_TYPE unsigned
+#define INVALID_SLOT ((SLOT_TYPE)-1)
+#define BUFFER_SIZE PJMEDIA_MAX_MTU
+#define MAX_LEVEL (32767)
+#define MIN_LEVEL (-32768)
+
+/*
+ * DON'T GET CONFUSED WITH TX/RX!!
+ *
+ * TX and RX directions are always viewed from the conference bridge's point
+ * of view, and NOT from the port's point of view. So TX means the bridge
+ * is transmitting to the port, RX means the bridge is receiving from the
+ * port.
+ */
+
+
+/**
+ * This is a port connected to conference bridge.
+ */
+struct conf_port
+{
+ SLOT_TYPE slot; /**< Array of listeners. */
+ pj_str_t name; /**< Port name. */
+ pjmedia_port *port; /**< get_frame() and put_frame() */
+ pjmedia_port_op rx_setting; /**< Can we receive from this port */
+ pjmedia_port_op tx_setting; /**< Can we transmit to this port */
+ unsigned listener_cnt; /**< Number of listeners. */
+ SLOT_TYPE *listener_slots;/**< Array of listeners. */
+ unsigned transmitter_cnt;/**<Number of transmitters. */
+
+ /* Shortcut for port info. */
+ pjmedia_port_info *info;
+
+ /* Calculated signal levels: */
+ unsigned tx_level; /**< Last tx level to this port. */
+ unsigned rx_level; /**< Last rx level from this port. */
+
+ /* The normalized signal level adjustment.
+ * A value of 128 (NORMAL_LEVEL) means there's no adjustment.
+ */
+ unsigned tx_adj_level; /**< Adjustment for TX. */
+ unsigned rx_adj_level; /**< Adjustment for RX. */
+
+ pj_timestamp ts_clock;
+ pj_timestamp ts_rx;
+ pj_timestamp ts_tx;
+
+ /* Tx buffer is a temporary buffer to be used when there's mismatch
+ * between port's ptime with conference's ptime. This buffer is used as
+ * the source to buffer the samples until there are enough samples to
+ * fulfill a complete frame to be transmitted to the port.
+ */
+ pj_uint8_t tx_buf[BUFFER_SIZE]; /**< Tx buffer. */
+};
+
+
+/*
+ * Conference bridge.
+ */
+struct pjmedia_conf
+{
+ unsigned options; /**< Bitmask options. */
+ unsigned max_ports; /**< Maximum ports. */
+ unsigned port_cnt; /**< Current number of ports. */
+ unsigned connect_cnt; /**< Total number of connections */
+ pjmedia_port *master_port; /**< Port zero's port. */
+ char master_name_buf[80]; /**< Port0 name buffer. */
+ pj_mutex_t *mutex; /**< Conference mutex. */
+ struct conf_port **ports; /**< Array of ports. */
+ pj_uint8_t buf[BUFFER_SIZE]; /**< Common buffer. */
+};
+
+
+/* Prototypes */
+static pj_status_t put_frame(pjmedia_port *this_port,
+ const pjmedia_frame *frame);
+static pj_status_t get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t destroy_port(pjmedia_port *this_port);
+
+
+/*
+ * Create port.
+ */
+static pj_status_t create_conf_port( pj_pool_t *pool,
+ pjmedia_conf *conf,
+ pjmedia_port *port,
+ const pj_str_t *name,
+ struct conf_port **p_conf_port)
+{
+ struct conf_port *conf_port;
+ pjmedia_frame *f;
+
+ PJ_ASSERT_RETURN(pool && conf && port && name && p_conf_port, PJ_EINVAL);
+
+ /* Create port. */
+ conf_port = PJ_POOL_ZALLOC_T(pool, struct conf_port);
+
+ /* Set name */
+ pj_strdup_with_null(pool, &conf_port->name, name);
+
+ /* Default has tx and rx enabled. */
+ conf_port->rx_setting = PJMEDIA_PORT_ENABLE;
+ conf_port->tx_setting = PJMEDIA_PORT_ENABLE;
+
+ /* Default level adjustment is 128 (which means no adjustment) */
+ conf_port->tx_adj_level = NORMAL_LEVEL;
+ conf_port->rx_adj_level = NORMAL_LEVEL;
+
+ /* Create transmit flag array */
+ conf_port->listener_slots = (SLOT_TYPE*)
+ pj_pool_zalloc(pool,
+ conf->max_ports * sizeof(SLOT_TYPE));
+ PJ_ASSERT_RETURN(conf_port->listener_slots, PJ_ENOMEM);
+
+ /* Save some port's infos, for convenience. */
+ conf_port->port = port;
+ conf_port->info = &port->info;
+
+ /* Init pjmedia_frame structure in the TX buffer. */
+ f = (pjmedia_frame*)conf_port->tx_buf;
+ f->buf = conf_port->tx_buf + sizeof(pjmedia_frame);
+
+ /* Done */
+ *p_conf_port = conf_port;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Create port zero for the sound device.
+ */
+static pj_status_t create_sound_port( pj_pool_t *pool,
+ pjmedia_conf *conf )
+{
+ struct conf_port *conf_port;
+ pj_str_t name = { "Master/sound", 12 };
+ pj_status_t status;
+
+ status = create_conf_port(pool, conf, conf->master_port, &name, &conf_port);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add the port to the bridge */
+ conf_port->slot = 0;
+ conf->ports[0] = conf_port;
+ conf->port_cnt++;
+
+ PJ_LOG(5,(THIS_FILE, "Sound device successfully created for port 0"));
+ return PJ_SUCCESS;
+}
+
+/*
+ * Create conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool,
+ unsigned max_ports,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ unsigned options,
+ pjmedia_conf **p_conf )
+{
+ pjmedia_conf *conf;
+ const pj_str_t name = { "Conf", 4 };
+ pj_status_t status;
+
+ /* Can only accept 16bits per sample, for now.. */
+ PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
+
+ PJ_LOG(5,(THIS_FILE, "Creating conference bridge with %d ports",
+ max_ports));
+
+ /* Create and init conf structure. */
+ conf = PJ_POOL_ZALLOC_T(pool, pjmedia_conf);
+ PJ_ASSERT_RETURN(conf, PJ_ENOMEM);
+
+ conf->ports = (struct conf_port**)
+ pj_pool_zalloc(pool, max_ports*sizeof(void*));
+ PJ_ASSERT_RETURN(conf->ports, PJ_ENOMEM);
+
+ conf->options = options;
+ conf->max_ports = max_ports;
+
+ /* Create and initialize the master port interface. */
+ conf->master_port = PJ_POOL_ZALLOC_T(pool, pjmedia_port);
+ PJ_ASSERT_RETURN(conf->master_port, PJ_ENOMEM);
+
+ pjmedia_port_info_init(&conf->master_port->info, &name, SIGNATURE,
+ clock_rate, channel_count, bits_per_sample,
+ samples_per_frame);
+
+ conf->master_port->port_data.pdata = conf;
+ conf->master_port->port_data.ldata = 0;
+
+ conf->master_port->get_frame = &get_frame;
+ conf->master_port->put_frame = &put_frame;
+ conf->master_port->on_destroy = &destroy_port;
+
+
+ /* Create port zero for sound device. */
+ status = create_sound_port(pool, conf);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create mutex. */
+ status = pj_mutex_create_recursive(pool, "conf", &conf->mutex);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Done */
+
+ *p_conf = conf;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Pause sound device.
+ */
+static pj_status_t pause_sound( pjmedia_conf *conf )
+{
+ /* Do nothing. */
+ PJ_UNUSED_ARG(conf);
+ return PJ_SUCCESS;
+}
+
+/*
+ * Resume sound device.
+ */
+static pj_status_t resume_sound( pjmedia_conf *conf )
+{
+ /* Do nothing. */
+ PJ_UNUSED_ARG(conf);
+ return PJ_SUCCESS;
+}
+
+
+/**
+ * Destroy conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf )
+{
+ PJ_ASSERT_RETURN(conf != NULL, PJ_EINVAL);
+
+ /* Destroy mutex */
+ pj_mutex_destroy(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy the master port (will destroy the conference)
+ */
+static pj_status_t destroy_port(pjmedia_port *this_port)
+{
+ pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
+ return pjmedia_conf_destroy(conf);
+}
+
+/*
+ * Get port zero interface.
+ */
+PJ_DEF(pjmedia_port*) pjmedia_conf_get_master_port(pjmedia_conf *conf)
+{
+ /* Sanity check. */
+ PJ_ASSERT_RETURN(conf != NULL, NULL);
+
+ /* Can only return port interface when PJMEDIA_CONF_NO_DEVICE was
+ * present in the option.
+ */
+ PJ_ASSERT_RETURN((conf->options & PJMEDIA_CONF_NO_DEVICE) != 0, NULL);
+
+ return conf->master_port;
+}
+
+
+/*
+ * Set master port name.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_set_port0_name(pjmedia_conf *conf,
+ const pj_str_t *name)
+{
+ unsigned len;
+
+ /* Sanity check. */
+ PJ_ASSERT_RETURN(conf != NULL && name != NULL, PJ_EINVAL);
+
+ len = name->slen;
+ if (len > sizeof(conf->master_name_buf))
+ len = sizeof(conf->master_name_buf);
+
+ if (len > 0) pj_memcpy(conf->master_name_buf, name->ptr, len);
+
+ conf->ports[0]->name.ptr = conf->master_name_buf;
+ conf->ports[0]->name.slen = len;
+
+ conf->master_port->info.name = conf->ports[0]->name;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Add stream port to the conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf,
+ pj_pool_t *pool,
+ pjmedia_port *strm_port,
+ const pj_str_t *port_name,
+ unsigned *p_port )
+{
+ struct conf_port *conf_port;
+ unsigned index;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(conf && pool && strm_port, PJ_EINVAL);
+ /*
+ PJ_ASSERT_RETURN(conf->clock_rate == strm_port->info.clock_rate,
+ PJMEDIA_ENCCLOCKRATE);
+ PJ_ASSERT_RETURN(conf->channel_count == strm_port->info.channel_count,
+ PJMEDIA_ENCCHANNEL);
+ PJ_ASSERT_RETURN(conf->bits_per_sample == strm_port->info.bits_per_sample,
+ PJMEDIA_ENCBITS);
+ */
+
+ /* Port's samples per frame should be equal to or multiplication of
+ * conference's samples per frame.
+ */
+ /*
+ Not sure if this is needed!
+ PJ_ASSERT_RETURN((conf->samples_per_frame %
+ strm_port->info.samples_per_frame==0) ||
+ (strm_port->info.samples_per_frame %
+ conf->samples_per_frame==0),
+ PJMEDIA_ENCSAMPLESPFRAME);
+ */
+
+ /* If port_name is not specified, use the port's name */
+ if (!port_name)
+ port_name = &strm_port->info.name;
+
+ pj_mutex_lock(conf->mutex);
+
+ if (conf->port_cnt >= conf->max_ports) {
+ pj_assert(!"Too many ports");
+ pj_mutex_unlock(conf->mutex);
+ return PJ_ETOOMANY;
+ }
+
+ /* Find empty port in the conference bridge. */
+ for (index=0; index < conf->max_ports; ++index) {
+ if (conf->ports[index] == NULL)
+ break;
+ }
+
+ pj_assert(index != conf->max_ports);
+
+ /* Create conf port structure. */
+ status = create_conf_port(pool, conf, strm_port, port_name, &conf_port);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_unlock(conf->mutex);
+ return status;
+ }
+
+ /* Put the port. */
+ conf_port->slot = index;
+ conf->ports[index] = conf_port;
+ conf->port_cnt++;
+
+ /* Done. */
+ if (p_port) {
+ *p_port = index;
+ }
+
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Add passive port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_add_passive_port( pjmedia_conf *conf,
+ pj_pool_t *pool,
+ const pj_str_t *name,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ unsigned options,
+ unsigned *p_slot,
+ pjmedia_port **p_port )
+{
+ PJ_UNUSED_ARG(conf);
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(name);
+ PJ_UNUSED_ARG(clock_rate);
+ PJ_UNUSED_ARG(channel_count);
+ PJ_UNUSED_ARG(samples_per_frame);
+ PJ_UNUSED_ARG(bits_per_sample);
+ PJ_UNUSED_ARG(options);
+ PJ_UNUSED_ARG(p_slot);
+ PJ_UNUSED_ARG(p_port);
+
+ return PJ_ENOTSUP;
+}
+
+
+
+/*
+ * Change TX and RX settings for the port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_configure_port( pjmedia_conf *conf,
+ unsigned slot,
+ pjmedia_port_op tx,
+ pjmedia_port_op rx)
+{
+ struct conf_port *conf_port;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+ /* Port must be valid. */
+ PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL);
+
+ conf_port = conf->ports[slot];
+
+ if (tx != PJMEDIA_PORT_NO_CHANGE)
+ conf_port->tx_setting = tx;
+
+ if (rx != PJMEDIA_PORT_NO_CHANGE)
+ conf_port->rx_setting = rx;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Connect port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_connect_port( pjmedia_conf *conf,
+ unsigned src_slot,
+ unsigned sink_slot,
+ int level )
+{
+ struct conf_port *src_port, *dst_port;
+ pj_bool_t start_sound = PJ_FALSE;
+ unsigned i;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && src_slot<conf->max_ports &&
+ sink_slot<conf->max_ports, PJ_EINVAL);
+
+ /* Ports must be valid. */
+ PJ_ASSERT_RETURN(conf->ports[src_slot] != NULL, PJ_EINVAL);
+ PJ_ASSERT_RETURN(conf->ports[sink_slot] != NULL, PJ_EINVAL);
+
+ /* For now, level MUST be zero. */
+ PJ_ASSERT_RETURN(level == 0, PJ_EINVAL);
+
+ pj_mutex_lock(conf->mutex);
+
+ src_port = conf->ports[src_slot];
+ dst_port = conf->ports[sink_slot];
+
+ /* Format must match. */
+ if (src_port->info->format.id != dst_port->info->format.id ||
+ src_port->info->format.bitrate != dst_port->info->format.bitrate)
+ {
+ pj_mutex_unlock(conf->mutex);
+ return PJMEDIA_ENOTCOMPATIBLE;
+ }
+
+ /* Clock rate must match. */
+ if (src_port->info->clock_rate != dst_port->info->clock_rate) {
+ pj_mutex_unlock(conf->mutex);
+ return PJMEDIA_ENCCLOCKRATE;
+ }
+
+ /* Channel count must match. */
+ if (src_port->info->channel_count != dst_port->info->channel_count) {
+ pj_mutex_unlock(conf->mutex);
+ return PJMEDIA_ENCCLOCKRATE;
+ }
+
+ /* Source and sink ptime must be equal or a multiplication factor. */
+ if ((src_port->info->samples_per_frame %
+ dst_port->info->samples_per_frame != 0) &&
+ (dst_port->info->samples_per_frame %
+ src_port->info->samples_per_frame != 0))
+ {
+ pj_mutex_unlock(conf->mutex);
+ return PJMEDIA_ENCSAMPLESPFRAME;
+ }
+
+ /* Check if sink is listening to other ports */
+ if (dst_port->transmitter_cnt > 0) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_ETOOMANYCONN;
+ }
+
+ /* Check if connection has been made */
+ for (i=0; i<src_port->listener_cnt; ++i) {
+ if (src_port->listener_slots[i] == sink_slot)
+ break;
+ }
+
+ if (i == src_port->listener_cnt) {
+ src_port->listener_slots[src_port->listener_cnt] = sink_slot;
+ ++conf->connect_cnt;
+ ++src_port->listener_cnt;
+ ++dst_port->transmitter_cnt;
+
+ if (conf->connect_cnt == 1)
+ start_sound = 1;
+
+ PJ_LOG(4,(THIS_FILE,"Port %d (%.*s) transmitting to port %d (%.*s)",
+ src_slot,
+ (int)src_port->name.slen,
+ src_port->name.ptr,
+ sink_slot,
+ (int)dst_port->name.slen,
+ dst_port->name.ptr));
+ }
+
+ pj_mutex_unlock(conf->mutex);
+
+ /* Sound device must be started without mutex, otherwise the
+ * sound thread will deadlock (?)
+ */
+ if (start_sound)
+ resume_sound(conf);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Disconnect port
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_disconnect_port( pjmedia_conf *conf,
+ unsigned src_slot,
+ unsigned sink_slot )
+{
+ struct conf_port *src_port, *dst_port;
+ unsigned i;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && src_slot<conf->max_ports &&
+ sink_slot<conf->max_ports, PJ_EINVAL);
+
+ /* Ports must be valid. */
+ PJ_ASSERT_RETURN(conf->ports[src_slot] != NULL, PJ_EINVAL);
+ PJ_ASSERT_RETURN(conf->ports[sink_slot] != NULL, PJ_EINVAL);
+
+ pj_mutex_lock(conf->mutex);
+
+ src_port = conf->ports[src_slot];
+ dst_port = conf->ports[sink_slot];
+
+ /* Check if connection has been made */
+ for (i=0; i<src_port->listener_cnt; ++i) {
+ if (src_port->listener_slots[i] == sink_slot)
+ break;
+ }
+
+ if (i != src_port->listener_cnt) {
+ pjmedia_frame_ext *f;
+
+ pj_assert(src_port->listener_cnt > 0 &&
+ src_port->listener_cnt < conf->max_ports);
+ pj_assert(dst_port->transmitter_cnt > 0 &&
+ dst_port->transmitter_cnt < conf->max_ports);
+ pj_array_erase(src_port->listener_slots, sizeof(SLOT_TYPE),
+ src_port->listener_cnt, i);
+ --conf->connect_cnt;
+ --src_port->listener_cnt;
+ --dst_port->transmitter_cnt;
+
+ /* Cleanup listener TX buffer. */
+ f = (pjmedia_frame_ext*)dst_port->tx_buf;
+ f->base.type = PJMEDIA_FRAME_TYPE_NONE;
+ f->base.size = 0;
+ f->samples_cnt = 0;
+ f->subframe_cnt = 0;
+
+ PJ_LOG(4,(THIS_FILE,
+ "Port %d (%.*s) stop transmitting to port %d (%.*s)",
+ src_slot,
+ (int)src_port->name.slen,
+ src_port->name.ptr,
+ sink_slot,
+ (int)dst_port->name.slen,
+ dst_port->name.ptr));
+ }
+
+ pj_mutex_unlock(conf->mutex);
+
+ if (conf->connect_cnt == 0) {
+ pause_sound(conf);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get number of ports currently registered to the conference bridge.
+ */
+PJ_DEF(unsigned) pjmedia_conf_get_port_count(pjmedia_conf *conf)
+{
+ return conf->port_cnt;
+}
+
+/*
+ * Get total number of ports connections currently set up in the bridge.
+ */
+PJ_DEF(unsigned) pjmedia_conf_get_connect_count(pjmedia_conf *conf)
+{
+ return conf->connect_cnt;
+}
+
+
+/*
+ * Remove the specified port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_remove_port( pjmedia_conf *conf,
+ unsigned port )
+{
+ struct conf_port *conf_port;
+ unsigned i;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && port < conf->max_ports, PJ_EINVAL);
+
+ /* Port must be valid. */
+ PJ_ASSERT_RETURN(conf->ports[port] != NULL, PJ_EINVAL);
+
+ /* Suspend the sound devices.
+ * Don't want to remove port while port is being accessed by sound
+ * device's threads!
+ */
+
+ pj_mutex_lock(conf->mutex);
+
+ conf_port = conf->ports[port];
+ conf_port->tx_setting = PJMEDIA_PORT_DISABLE;
+ conf_port->rx_setting = PJMEDIA_PORT_DISABLE;
+
+ /* Remove this port from transmit array of other ports. */
+ for (i=0; i<conf->max_ports; ++i) {
+ unsigned j;
+ struct conf_port *src_port;
+
+ src_port = conf->ports[i];
+
+ if (!src_port)
+ continue;
+
+ if (src_port->listener_cnt == 0)
+ continue;
+
+ for (j=0; j<src_port->listener_cnt; ++j) {
+ if (src_port->listener_slots[j] == port) {
+ pj_array_erase(src_port->listener_slots, sizeof(SLOT_TYPE),
+ src_port->listener_cnt, j);
+ pj_assert(conf->connect_cnt > 0);
+ --conf->connect_cnt;
+ --src_port->listener_cnt;
+ break;
+ }
+ }
+ }
+
+ /* Update transmitter_cnt of ports we're transmitting to */
+ while (conf_port->listener_cnt) {
+ unsigned dst_slot;
+ struct conf_port *dst_port;
+ pjmedia_frame_ext *f;
+
+ dst_slot = conf_port->listener_slots[conf_port->listener_cnt-1];
+ dst_port = conf->ports[dst_slot];
+ --dst_port->transmitter_cnt;
+ --conf_port->listener_cnt;
+ pj_assert(conf->connect_cnt > 0);
+ --conf->connect_cnt;
+
+ /* Cleanup & reinit listener TX buffer. */
+ f = (pjmedia_frame_ext*)dst_port->tx_buf;
+ f->base.type = PJMEDIA_FRAME_TYPE_NONE;
+ f->base.size = 0;
+ f->samples_cnt = 0;
+ f->subframe_cnt = 0;
+ }
+
+ /* Remove the port. */
+ conf->ports[port] = NULL;
+ --conf->port_cnt;
+
+ pj_mutex_unlock(conf->mutex);
+
+
+ /* Stop sound if there's no connection. */
+ if (conf->connect_cnt == 0) {
+ pause_sound(conf);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Enum ports.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_enum_ports( pjmedia_conf *conf,
+ unsigned ports[],
+ unsigned *p_count )
+{
+ unsigned i, count=0;
+
+ PJ_ASSERT_RETURN(conf && p_count && ports, PJ_EINVAL);
+
+ for (i=0; i<conf->max_ports && count<*p_count; ++i) {
+ if (!conf->ports[i])
+ continue;
+
+ ports[count++] = i;
+ }
+
+ *p_count = count;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get port info
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf,
+ unsigned slot,
+ pjmedia_conf_port_info *info)
+{
+ struct conf_port *conf_port;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+ /* Port must be valid. */
+ PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL);
+
+ conf_port = conf->ports[slot];
+
+ pj_bzero(info, sizeof(pjmedia_conf_port_info));
+
+ info->slot = slot;
+ info->name = conf_port->name;
+ info->tx_setting = conf_port->tx_setting;
+ info->rx_setting = conf_port->rx_setting;
+ info->listener_cnt = conf_port->listener_cnt;
+ info->listener_slots = conf_port->listener_slots;
+ info->transmitter_cnt = conf_port->transmitter_cnt;
+ info->clock_rate = conf_port->info->clock_rate;
+ info->channel_count = conf_port->info->channel_count;
+ info->samples_per_frame = conf_port->info->samples_per_frame;
+ info->bits_per_sample = conf_port->info->bits_per_sample;
+ info->format = conf_port->port->info.format;
+ info->tx_adj_level = conf_port->tx_adj_level - NORMAL_LEVEL;
+ info->rx_adj_level = conf_port->rx_adj_level - NORMAL_LEVEL;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_conf_get_ports_info(pjmedia_conf *conf,
+ unsigned *size,
+ pjmedia_conf_port_info info[])
+{
+ unsigned i, count=0;
+
+ PJ_ASSERT_RETURN(conf && size && info, PJ_EINVAL);
+
+ for (i=0; i<conf->max_ports && count<*size; ++i) {
+ if (!conf->ports[i])
+ continue;
+
+ pjmedia_conf_get_port_info(conf, i, &info[count]);
+ ++count;
+ }
+
+ *size = count;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get signal level.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_get_signal_level( pjmedia_conf *conf,
+ unsigned slot,
+ unsigned *tx_level,
+ unsigned *rx_level)
+{
+ struct conf_port *conf_port;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+ /* Port must be valid. */
+ PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL);
+
+ conf_port = conf->ports[slot];
+
+ if (tx_level != NULL) {
+ *tx_level = conf_port->tx_level;
+ }
+
+ if (rx_level != NULL)
+ *rx_level = conf_port->rx_level;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Adjust RX level of individual port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_adjust_rx_level( pjmedia_conf *conf,
+ unsigned slot,
+ int adj_level )
+{
+ struct conf_port *conf_port;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+ /* Port must be valid. */
+ PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL);
+
+ /* Value must be from -128 to +127 */
+ /* Disabled, you can put more than +127, at your own risk:
+ PJ_ASSERT_RETURN(adj_level >= -128 && adj_level <= 127, PJ_EINVAL);
+ */
+ PJ_ASSERT_RETURN(adj_level >= -128, PJ_EINVAL);
+
+ conf_port = conf->ports[slot];
+
+ /* Level adjustment is applicable only for ports that work with raw PCM. */
+ PJ_ASSERT_RETURN(conf_port->info->format.id == PJMEDIA_FORMAT_L16,
+ PJ_EIGNORED);
+
+ /* Set normalized adjustment level. */
+ conf_port->rx_adj_level = adj_level + NORMAL_LEVEL;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Adjust TX level of individual port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_adjust_tx_level( pjmedia_conf *conf,
+ unsigned slot,
+ int adj_level )
+{
+ struct conf_port *conf_port;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+ /* Port must be valid. */
+ PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL);
+
+ /* Value must be from -128 to +127 */
+ /* Disabled, you can put more than +127,, at your own risk:
+ PJ_ASSERT_RETURN(adj_level >= -128 && adj_level <= 127, PJ_EINVAL);
+ */
+ PJ_ASSERT_RETURN(adj_level >= -128, PJ_EINVAL);
+
+ conf_port = conf->ports[slot];
+
+ /* Level adjustment is applicable only for ports that work with raw PCM. */
+ PJ_ASSERT_RETURN(conf_port->info->format.id == PJMEDIA_FORMAT_L16,
+ PJ_EIGNORED);
+
+ /* Set normalized adjustment level. */
+ conf_port->tx_adj_level = adj_level + NORMAL_LEVEL;
+
+ return PJ_SUCCESS;
+}
+
+/* Deliver frm_src to a listener port, eventually call port's put_frame()
+ * when samples count in the frm_dst are equal to port's samples_per_frame.
+ */
+static pj_status_t write_frame(struct conf_port *cport_dst,
+ const pjmedia_frame *frm_src)
+{
+ pjmedia_frame *frm_dst = (pjmedia_frame*)cport_dst->tx_buf;
+
+ PJ_TODO(MAKE_SURE_DEST_FRAME_HAS_ENOUGH_SPACE);
+
+ frm_dst->type = frm_src->type;
+ frm_dst->timestamp = cport_dst->ts_tx;
+
+ if (frm_src->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+
+ pjmedia_frame_ext *f_src = (pjmedia_frame_ext*)frm_src;
+ pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frm_dst;
+ unsigned i;
+
+ for (i = 0; i < f_src->subframe_cnt; ++i) {
+ pjmedia_frame_ext_subframe *sf;
+
+ /* Copy frame to listener's TX buffer. */
+ sf = pjmedia_frame_ext_get_subframe(f_src, i);
+ pjmedia_frame_ext_append_subframe(f_dst, sf->data, sf->bitlen,
+ f_src->samples_cnt /
+ f_src->subframe_cnt);
+
+ /* Check if it's time to deliver the TX buffer to listener,
+ * i.e: samples count in TX buffer equal to listener's
+ * samples per frame.
+ */
+ if (f_dst->samples_cnt >= cport_dst->info->samples_per_frame)
+ {
+ if (cport_dst->slot) {
+ pjmedia_port_put_frame(cport_dst->port,
+ (pjmedia_frame*)f_dst);
+
+ /* Reset TX buffer. */
+ f_dst->subframe_cnt = 0;
+ f_dst->samples_cnt = 0;
+ }
+
+ /* Update TX timestamp. */
+ pj_add_timestamp32(&cport_dst->ts_tx,
+ cport_dst->info->samples_per_frame);
+ }
+ }
+
+ } else if (frm_src->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+
+ pj_int16_t *f_start, *f_end;
+
+ f_start = (pj_int16_t*)frm_src->buf;
+ f_end = f_start + (frm_src->size >> 1);
+
+ while (f_start < f_end) {
+ unsigned nsamples_to_copy, nsamples_req;
+
+ /* Copy frame to listener's TX buffer. */
+ nsamples_to_copy = f_end - f_start;
+ nsamples_req = cport_dst->info->samples_per_frame -
+ (frm_dst->size>>1);
+ if (nsamples_to_copy > nsamples_req)
+ nsamples_to_copy = nsamples_req;
+
+ /* Adjust TX level. */
+ if (cport_dst->tx_adj_level != NORMAL_LEVEL) {
+ pj_int16_t *p, *p_end;
+
+ p = f_start;
+ p_end = p + nsamples_to_copy;
+ while (p < p_end) {
+ pj_int32_t itemp = *p;
+
+ /* Adjust the level */
+ itemp = (itemp * cport_dst->tx_adj_level) >> 7;
+
+ /* Clip the signal if it's too loud */
+ if (itemp > MAX_LEVEL) itemp = MAX_LEVEL;
+ else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL;
+
+ /* Put back in the buffer. */
+ *p = (pj_int16_t)itemp;
+ ++p;
+ }
+ }
+
+ pjmedia_copy_samples((pj_int16_t*)frm_dst->buf + (frm_dst->size>>1),
+ f_start,
+ nsamples_to_copy);
+ frm_dst->size += nsamples_to_copy << 1;
+ f_start += nsamples_to_copy;
+
+ /* Check if it's time to deliver the TX buffer to listener,
+ * i.e: samples count in TX buffer equal to listener's
+ * samples per frame.
+ */
+ if ((frm_dst->size >> 1) == cport_dst->info->samples_per_frame)
+ {
+ if (cport_dst->slot) {
+ pjmedia_port_put_frame(cport_dst->port, frm_dst);
+
+ /* Reset TX buffer. */
+ frm_dst->size = 0;
+ }
+
+ /* Update TX timestamp. */
+ pj_add_timestamp32(&cport_dst->ts_tx,
+ cport_dst->info->samples_per_frame);
+ }
+ }
+
+ } else if (frm_src->type == PJMEDIA_FRAME_TYPE_NONE) {
+
+ /* Check port format. */
+ if (cport_dst->port &&
+ cport_dst->port->info.format.id == PJMEDIA_FORMAT_L16)
+ {
+ /* When there is already some samples in listener's TX buffer,
+ * pad the buffer with "zero samples".
+ */
+ if (frm_dst->size != 0) {
+ pjmedia_zero_samples((pj_int16_t*)frm_dst->buf,
+ cport_dst->info->samples_per_frame -
+ (frm_dst->size>>1));
+
+ frm_dst->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frm_dst->size = cport_dst->info->samples_per_frame << 1;
+ if (cport_dst->slot) {
+ pjmedia_port_put_frame(cport_dst->port, frm_dst);
+
+ /* Reset TX buffer. */
+ frm_dst->size = 0;
+ }
+
+ /* Update TX timestamp. */
+ pj_add_timestamp32(&cport_dst->ts_tx,
+ cport_dst->info->samples_per_frame);
+ }
+ } else {
+ pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frm_dst;
+
+ if (f_dst->samples_cnt != 0) {
+ frm_dst->type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ pjmedia_frame_ext_append_subframe(f_dst, NULL, 0, (pj_uint16_t)
+ (cport_dst->info->samples_per_frame - f_dst->samples_cnt));
+ if (cport_dst->slot) {
+ pjmedia_port_put_frame(cport_dst->port, frm_dst);
+
+ /* Reset TX buffer. */
+ f_dst->subframe_cnt = 0;
+ f_dst->samples_cnt = 0;
+ }
+
+ /* Update TX timestamp. */
+ pj_add_timestamp32(&cport_dst->ts_tx,
+ cport_dst->info->samples_per_frame);
+ }
+ }
+
+ /* Synchronize clock. */
+ while (pj_cmp_timestamp(&cport_dst->ts_clock,
+ &cport_dst->ts_tx) > 0)
+ {
+ frm_dst->type = PJMEDIA_FRAME_TYPE_NONE;
+ frm_dst->timestamp = cport_dst->ts_tx;
+ if (cport_dst->slot)
+ pjmedia_port_put_frame(cport_dst->port, frm_dst);
+
+ /* Update TX timestamp. */
+ pj_add_timestamp32(&cport_dst->ts_tx, cport_dst->info->samples_per_frame);
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Player callback.
+ */
+static pj_status_t get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
+ unsigned ci, i;
+
+ /* Must lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ /* Call get_frame() from all ports (except port 0) that has
+ * receiver and distribute the frame (put the frame to the destination
+ * port's buffer to accommodate different ptime, and ultimately call
+ * put_frame() of that port) to ports that are receiving from this port.
+ */
+ for (i=1, ci=1; i<conf->max_ports && ci<conf->port_cnt; ++i) {
+ struct conf_port *cport = conf->ports[i];
+
+ /* Skip empty port. */
+ if (!cport)
+ continue;
+
+ /* Var "ci" is to count how many ports have been visited so far. */
+ ++ci;
+
+ /* Update clock of the port. */
+ pj_add_timestamp32(&cport->ts_clock,
+ conf->master_port->info.samples_per_frame);
+
+ /* Skip if we're not allowed to receive from this port or
+ * the port doesn't have listeners.
+ */
+ if (cport->rx_setting == PJMEDIA_PORT_DISABLE ||
+ cport->listener_cnt == 0)
+ {
+ cport->rx_level = 0;
+ pj_add_timestamp32(&cport->ts_rx,
+ conf->master_port->info.samples_per_frame);
+ continue;
+ }
+
+ /* Get frame from each port, put it to the listener TX buffer,
+ * and eventually call put_frame() of the listener. This loop
+ * will also make sure the ptime between conf & port synchronized.
+ */
+ while (pj_cmp_timestamp(&cport->ts_clock, &cport->ts_rx) > 0) {
+ pjmedia_frame *f = (pjmedia_frame*)conf->buf;
+ pj_status_t status;
+ unsigned j;
+ pj_int32_t level = 0;
+
+ pj_add_timestamp32(&cport->ts_rx, cport->info->samples_per_frame);
+
+ f->buf = &conf->buf[sizeof(pjmedia_frame)];
+ f->size = cport->info->samples_per_frame<<1;
+
+ /* Get frame from port. */
+ status = pjmedia_port_get_frame(cport->port, f);
+ if (status != PJ_SUCCESS)
+ continue;
+
+ /* Calculate & adjust RX level. */
+ if (f->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+ if (cport->rx_adj_level != NORMAL_LEVEL) {
+ pj_int16_t *p = (pj_int16_t*)f->buf;
+ pj_int16_t *end;
+
+ end = p + (f->size >> 1);
+ while (p < end) {
+ pj_int32_t itemp = *p;
+
+ /* Adjust the level */
+ itemp = (itemp * cport->rx_adj_level) >> 7;
+
+ /* Clip the signal if it's too loud */
+ if (itemp > MAX_LEVEL) itemp = MAX_LEVEL;
+ else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL;
+
+ level += PJ_ABS(itemp);
+
+ /* Put back in the buffer. */
+ *p = (pj_int16_t)itemp;
+ ++p;
+ }
+ level /= (f->size >> 1);
+ } else {
+ level = pjmedia_calc_avg_signal((const pj_int16_t*)f->buf,
+ f->size >> 1);
+ }
+ } else if (f->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ /* For extended frame, level is unknown, so we just set
+ * it to NORMAL_LEVEL.
+ */
+ level = NORMAL_LEVEL;
+ }
+
+ cport->rx_level = pjmedia_linear2ulaw(level) ^ 0xff;
+
+ /* Put the frame to all listeners. */
+ for (j=0; j < cport->listener_cnt; ++j)
+ {
+ struct conf_port *listener;
+
+ listener = conf->ports[cport->listener_slots[j]];
+
+ /* Skip if this listener doesn't want to receive audio */
+ if (listener->tx_setting == PJMEDIA_PORT_DISABLE) {
+ pj_add_timestamp32(&listener->ts_tx,
+ listener->info->samples_per_frame);
+ listener->tx_level = 0;
+ continue;
+ }
+
+ status = write_frame(listener, f);
+ if (status != PJ_SUCCESS) {
+ listener->tx_level = 0;
+ continue;
+ }
+
+ /* Set listener TX level based on transmitter RX level &
+ * listener TX level.
+ */
+ listener->tx_level = (cport->rx_level * listener->tx_adj_level)
+ >> 8;
+ }
+ }
+ }
+
+ /* Keep alive. Update TX timestamp and send frame type NONE to all
+ * underflow ports at their own clock.
+ */
+ for (i=1, ci=1; i<conf->max_ports && ci<conf->port_cnt; ++i) {
+ struct conf_port *cport = conf->ports[i];
+
+ /* Skip empty port. */
+ if (!cport)
+ continue;
+
+ /* Var "ci" is to count how many ports have been visited so far. */
+ ++ci;
+
+ if (cport->tx_setting==PJMEDIA_PORT_MUTE || cport->transmitter_cnt==0)
+ {
+ pjmedia_frame_ext *f;
+
+ /* Clear left-over samples in tx_buffer, if any, so that it won't
+ * be transmitted next time we have audio signal.
+ */
+ f = (pjmedia_frame_ext*)cport->tx_buf;
+ f->base.type = PJMEDIA_FRAME_TYPE_NONE;
+ f->base.size = 0;
+ f->samples_cnt = 0;
+ f->subframe_cnt = 0;
+
+ cport->tx_level = 0;
+
+ while (pj_cmp_timestamp(&cport->ts_clock, &cport->ts_tx) > 0)
+ {
+ if (cport->tx_setting == PJMEDIA_PORT_ENABLE) {
+ pjmedia_frame tmp_f;
+
+ tmp_f.timestamp = cport->ts_tx;
+ tmp_f.type = PJMEDIA_FRAME_TYPE_NONE;
+ tmp_f.buf = NULL;
+ tmp_f.size = 0;
+
+ pjmedia_port_put_frame(cport->port, &tmp_f);
+ pj_add_timestamp32(&cport->ts_tx, cport->info->samples_per_frame);
+ }
+ }
+ }
+ }
+
+ /* Return sound playback frame. */
+ do {
+ struct conf_port *this_cport = conf->ports[this_port->port_data.ldata];
+ pjmedia_frame *f_src = (pjmedia_frame*) this_cport->tx_buf;
+
+ frame->type = f_src->type;
+
+ if (f_src->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pjmedia_frame_ext *f_src_ = (pjmedia_frame_ext*)f_src;
+ pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frame;
+ pjmedia_frame_ext_subframe *sf;
+ unsigned samples_per_subframe;
+
+ if (f_src_->samples_cnt < this_cport->info->samples_per_frame) {
+ f_dst->base.type = PJMEDIA_FRAME_TYPE_NONE;
+ f_dst->samples_cnt = 0;
+ f_dst->subframe_cnt = 0;
+ break;
+ }
+
+ f_dst->samples_cnt = 0;
+ f_dst->subframe_cnt = 0;
+ i = 0;
+ samples_per_subframe = f_src_->samples_cnt / f_src_->subframe_cnt;
+
+
+ while (f_dst->samples_cnt < this_cport->info->samples_per_frame) {
+ sf = pjmedia_frame_ext_get_subframe(f_src_, i++);
+ pj_assert(sf);
+ pjmedia_frame_ext_append_subframe(f_dst, sf->data, sf->bitlen,
+ samples_per_subframe);
+ }
+
+ /* Shift left TX buffer. */
+ pjmedia_frame_ext_pop_subframes(f_src_, i);
+
+ } else if (f_src->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+ if ((f_src->size>>1) < this_cport->info->samples_per_frame) {
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ frame->size = 0;
+ break;
+ }
+
+ pjmedia_copy_samples((pj_int16_t*)frame->buf,
+ (pj_int16_t*)f_src->buf,
+ this_cport->info->samples_per_frame);
+ frame->size = this_cport->info->samples_per_frame << 1;
+
+ /* Shift left TX buffer. */
+ f_src->size -= frame->size;
+ if (f_src->size)
+ pjmedia_move_samples((pj_int16_t*)f_src->buf,
+ (pj_int16_t*)f_src->buf +
+ this_cport->info->samples_per_frame,
+ f_src->size >> 1);
+ } else { /* PJMEDIA_FRAME_TYPE_NONE */
+ pjmedia_frame_ext *f_src_ = (pjmedia_frame_ext*)f_src;
+
+ /* Reset source/TX buffer */
+ f_src_->base.size = 0;
+ f_src_->samples_cnt = 0;
+ f_src_->subframe_cnt = 0;
+ }
+ } while (0);
+
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Recorder callback.
+ */
+static pj_status_t put_frame(pjmedia_port *this_port,
+ const pjmedia_frame *f)
+{
+ pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
+ struct conf_port *cport = conf->ports[this_port->port_data.ldata];
+ unsigned j;
+ pj_int32_t level = 0;
+
+ pj_add_timestamp32(&cport->ts_rx, cport->info->samples_per_frame);
+
+ /* Skip if this port is muted/disabled. */
+ if (cport->rx_setting == PJMEDIA_PORT_DISABLE) {
+ cport->rx_level = 0;
+ return PJ_SUCCESS;
+ }
+
+ /* Skip if no port is listening to the microphone */
+ if (cport->listener_cnt == 0) {
+ cport->rx_level = 0;
+ return PJ_SUCCESS;
+ }
+
+ /* Calculate & adjust RX level. */
+ if (f->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+ if (cport->rx_adj_level != NORMAL_LEVEL) {
+ pj_int16_t *p = (pj_int16_t*)f->buf;
+ pj_int16_t *end;
+
+ end = p + (f->size >> 1);
+ while (p < end) {
+ pj_int32_t itemp = *p;
+
+ /* Adjust the level */
+ itemp = (itemp * cport->rx_adj_level) >> 7;
+
+ /* Clip the signal if it's too loud */
+ if (itemp > MAX_LEVEL) itemp = MAX_LEVEL;
+ else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL;
+
+ level += PJ_ABS(itemp);
+
+ /* Put back in the buffer. */
+ *p = (pj_int16_t)itemp;
+ ++p;
+ }
+ level /= (f->size >> 1);
+ } else {
+ level = pjmedia_calc_avg_signal((const pj_int16_t*)f->buf,
+ f->size >> 1);
+ }
+ } else if (f->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ /* For extended frame, level is unknown, so we just set
+ * it to NORMAL_LEVEL.
+ */
+ level = NORMAL_LEVEL;
+ }
+
+ cport->rx_level = pjmedia_linear2ulaw(level) ^ 0xff;
+
+ /* Put the frame to all listeners. */
+ for (j=0; j < cport->listener_cnt; ++j)
+ {
+ struct conf_port *listener;
+ pj_status_t status;
+
+ listener = conf->ports[cport->listener_slots[j]];
+
+ /* Skip if this listener doesn't want to receive audio */
+ if (listener->tx_setting == PJMEDIA_PORT_DISABLE) {
+ pj_add_timestamp32(&listener->ts_tx,
+ listener->info->samples_per_frame);
+ listener->tx_level = 0;
+ continue;
+ }
+
+ /* Skip loopback for now. */
+ if (listener == cport) {
+ pj_add_timestamp32(&listener->ts_tx,
+ listener->info->samples_per_frame);
+ listener->tx_level = 0;
+ continue;
+ }
+
+ status = write_frame(listener, f);
+ if (status != PJ_SUCCESS) {
+ listener->tx_level = 0;
+ continue;
+ }
+
+ /* Set listener TX level based on transmitter RX level & listener
+ * TX level.
+ */
+ listener->tx_level = (cport->rx_level * listener->tx_adj_level) >> 8;
+ }
+
+ return PJ_SUCCESS;
+}
+
+#endif
diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c
index 9cae6a74..c39ec036 100644
--- a/pjmedia/src/pjmedia/conference.c
+++ b/pjmedia/src/pjmedia/conference.c
@@ -33,6 +33,7 @@
#include <pj/pool.h>
#include <pj/string.h>
+#if !defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0
/* CONF_DEBUG enables detailed operation of the conference bridge.
* Beware that it prints large amounts of logs (several lines per frame).
@@ -63,7 +64,7 @@ static FILE *fhnd_rec;
#define BYTES_PER_SAMPLE 2
-#define SIGNATURE PJMEDIA_PORT_SIGNATURE('C', 'O', 'N', 'F')
+#define SIGNATURE PJMEDIA_CONF_BRIDGE_SIGNATURE
#define SIGNATURE_PORT PJMEDIA_PORT_SIGNATURE('C', 'O', 'N', 'P')
/* Normal level is hardcodec to 128 in all over places */
#define NORMAL_LEVEL 128
@@ -464,8 +465,8 @@ static pj_status_t create_sound_port( pj_pool_t *pool,
/* Create sound device port: */
if ((conf->options & PJMEDIA_CONF_NO_DEVICE) == 0) {
- pjmedia_snd_stream *strm;
- pjmedia_snd_stream_info si;
+ pjmedia_aud_stream *strm;
+ pjmedia_aud_param param;
/*
* If capture is disabled then create player only port.
@@ -493,14 +494,14 @@ static pj_status_t create_sound_port( pj_pool_t *pool,
return status;
strm = pjmedia_snd_port_get_snd_stream(conf->snd_dev_port);
- status = pjmedia_snd_stream_get_info(strm, &si);
+ status = pjmedia_aud_stream_get_param(strm, &param);
if (status == PJ_SUCCESS) {
- const pjmedia_snd_dev_info *snd_dev_info;
+ pjmedia_aud_dev_info snd_dev_info;
if (conf->options & PJMEDIA_CONF_NO_MIC)
- snd_dev_info = pjmedia_snd_get_dev_info(si.play_id);
+ pjmedia_aud_dev_get_info(param.play_id, &snd_dev_info);
else
- snd_dev_info = pjmedia_snd_get_dev_info(si.rec_id);
- pj_strdup2_with_null(pool, &conf_port->name, snd_dev_info->name);
+ pjmedia_aud_dev_get_info(param.rec_id, &snd_dev_info);
+ pj_strdup2_with_null(pool, &conf_port->name, snd_dev_info.name);
}
}
@@ -1170,6 +1171,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf,
info->rx_setting = conf_port->rx_setting;
info->listener_cnt = conf_port->listener_cnt;
info->listener_slots = conf_port->listener_slots;
+ info->transmitter_cnt = conf_port->transmitter_cnt;
info->clock_rate = conf_port->clock_rate;
info->channel_count = conf_port->channel_count;
info->samples_per_frame = conf_port->samples_per_frame;
@@ -1987,3 +1989,4 @@ static pj_status_t put_frame(pjmedia_port *this_port,
return status;
}
+#endif
diff --git a/pjmedia/src/pjmedia/dsound.c b/pjmedia/src/pjmedia/dsound.c
deleted file mode 100644
index 367a6974..00000000
--- a/pjmedia/src/pjmedia/dsound.c
+++ /dev/null
@@ -1,1113 +0,0 @@
-/* $Id$ */
-/*
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
- * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
- *
- * 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 2 of the License, or
- * (at your option) 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-#include <pjmedia/sound.h>
-#include <pjmedia/errno.h>
-#include <pj/assert.h>
-#include <pj/log.h>
-#include <pj/os.h>
-#include <pj/string.h>
-
-#if PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_WIN32_DIRECT_SOUND
-
-#define PJ_MIN(x, y) ((x < y) ? (x) : (y))
-
-#ifdef _MSC_VER
-# pragma warning(push, 3)
-#endif
-
-#include <windows.h>
-#include <mmsystem.h>
-#include <dsound.h>
-
-#ifdef _MSC_VER
-# pragma warning(pop)
-#endif
-
-
-#define THIS_FILE "dsound.c"
-#define BITS_PER_SAMPLE 16
-#define BYTES_PER_SAMPLE (BITS_PER_SAMPLE/8)
-
-#define MAX_PACKET_BUFFER_COUNT 50
-#define MIN_PACKET_BUFFER_COUNT 2
-
-#define MAX_HARDWARE 16
-
-struct dsound_dev_info
-{
- pjmedia_snd_dev_info info;
- LPGUID lpGuid;
- GUID guid;
-};
-
-static unsigned dev_count;
-static struct dsound_dev_info dev_info[MAX_HARDWARE];
-static int snd_init_count;
-
-/* Latency settings */
-static unsigned snd_input_latency = PJMEDIA_SND_DEFAULT_REC_LATENCY;
-static unsigned snd_output_latency = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
-
-
-/* Individual DirectSound capture/playback stream descriptor */
-struct dsound_stream
-{
- union
- {
- struct
- {
- LPDIRECTSOUND lpDs;
- LPDIRECTSOUNDBUFFER lpDsBuffer;
- } play;
-
- struct
- {
- LPDIRECTSOUNDCAPTURE lpDs;
- LPDIRECTSOUNDCAPTUREBUFFER lpDsBuffer;
- } capture;
- } ds;
-
- HANDLE hEvent;
- LPDIRECTSOUNDNOTIFY lpDsNotify;
- DWORD dwBytePos;
- DWORD dwDsBufferSize;
- pj_timestamp timestamp;
- unsigned latency;
-};
-
-
-/* Sound stream. */
-struct pjmedia_snd_stream
-{
- pjmedia_dir dir; /**< Sound direction. */
- int play_id; /**< Playback dev id. */
- int rec_id; /**< Recording dev id. */
- pj_pool_t *pool; /**< Memory pool. */
-
- pjmedia_snd_rec_cb rec_cb; /**< Capture callback. */
- pjmedia_snd_play_cb play_cb; /**< Playback callback. */
- void *user_data; /**< Application data. */
-
- struct dsound_stream play_strm; /**< Playback stream. */
- struct dsound_stream rec_strm; /**< Capture stream. */
-
- void *buffer; /**< Temp. frame buffer. */
- unsigned clock_rate; /**< Clock rate. */
- unsigned samples_per_frame; /**< Samples per frame. */
- unsigned bits_per_sample; /**< Bits per sample. */
- unsigned channel_count; /**< Channel count. */
-
- pj_thread_t *thread; /**< Thread handle. */
- HANDLE thread_quit_event; /**< Quit signal to thread */
-};
-
-
-static pj_pool_factory *pool_factory;
-
-
-static void init_waveformatex (PCMWAVEFORMAT *pcmwf,
- unsigned clock_rate,
- unsigned channel_count)
-{
- pj_bzero(pcmwf, sizeof(PCMWAVEFORMAT));
- pcmwf->wf.wFormatTag = WAVE_FORMAT_PCM;
- pcmwf->wf.nChannels = (pj_uint16_t)channel_count;
- pcmwf->wf.nSamplesPerSec = clock_rate;
- pcmwf->wf.nBlockAlign = (pj_uint16_t)(channel_count * BYTES_PER_SAMPLE);
- pcmwf->wf.nAvgBytesPerSec = clock_rate * channel_count * BYTES_PER_SAMPLE;
- pcmwf->wBitsPerSample = BITS_PER_SAMPLE;
-}
-
-
-/*
- * Initialize DirectSound player device.
- */
-static pj_status_t init_player_stream( struct dsound_stream *ds_strm,
- int dev_id,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned buffer_count)
-{
- HRESULT hr;
- HWND hwnd;
- PCMWAVEFORMAT pcmwf;
- DSBUFFERDESC dsbdesc;
- DSBPOSITIONNOTIFY dsPosNotify[MAX_PACKET_BUFFER_COUNT];
- unsigned bytes_per_frame;
- unsigned max_latency;
- unsigned i;
-
-
- PJ_ASSERT_RETURN(buffer_count <= MAX_PACKET_BUFFER_COUNT, PJ_EINVAL);
-
- /* Check device ID */
- if (dev_id == -1)
- dev_id = 0;
-
- PJ_ASSERT_RETURN(dev_id>=0 && dev_id < (int)dev_count, PJ_EINVAL);
-
- /*
- * Create DirectSound device.
- */
- hr = DirectSoundCreate(dev_info[dev_id].lpGuid, &ds_strm->ds.play.lpDs,
- NULL);
- if (FAILED(hr))
- return PJ_RETURN_OS_ERROR(hr);
-
- hwnd = GetForegroundWindow();
- if (hwnd == NULL) {
- hwnd = GetDesktopWindow();
- }
- hr = IDirectSound_SetCooperativeLevel( ds_strm->ds.play.lpDs, hwnd,
- DSSCL_PRIORITY);
- if FAILED(hr)
- return PJ_RETURN_OS_ERROR(hr);
-
- /*
- * Set up wave format structure for initialize DirectSound play
- * buffer.
- */
- init_waveformatex(&pcmwf, clock_rate, channel_count);
- bytes_per_frame = samples_per_frame * BYTES_PER_SAMPLE;
-
- /* Set up DSBUFFERDESC structure. */
- pj_bzero(&dsbdesc, sizeof(DSBUFFERDESC));
- dsbdesc.dwSize = sizeof(DSBUFFERDESC);
- dsbdesc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLPOSITIONNOTIFY |
- DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
-
- dsbdesc.dwBufferBytes = buffer_count * bytes_per_frame;
- dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf;
-
- /*
- * Create DirectSound playback buffer.
- */
- hr = IDirectSound_CreateSoundBuffer(ds_strm->ds.play.lpDs, &dsbdesc,
- &ds_strm->ds.play.lpDsBuffer, NULL);
- if (FAILED(hr) )
- return PJ_RETURN_OS_ERROR(hr);
-
- /*
- * Create event for play notification.
- */
- ds_strm->hEvent = CreateEvent( NULL, FALSE, FALSE, NULL);
- if (ds_strm->hEvent == NULL)
- return pj_get_os_error();
-
- /*
- * Setup notification for play.
- */
- hr = IDirectSoundBuffer_QueryInterface( ds_strm->ds.play.lpDsBuffer,
- &IID_IDirectSoundNotify,
- (LPVOID *)&ds_strm->lpDsNotify);
- if (FAILED(hr))
- return PJ_RETURN_OS_ERROR(hr);
-
-
- for (i=0; i<buffer_count; ++i) {
- dsPosNotify[i].dwOffset = i * bytes_per_frame;
- dsPosNotify[i].hEventNotify = ds_strm->hEvent;
- }
-
- hr = IDirectSoundNotify_SetNotificationPositions( ds_strm->lpDsNotify,
- buffer_count,
- dsPosNotify);
- if (FAILED(hr))
- return PJ_RETURN_OS_ERROR(hr);
-
-
- hr = IDirectSoundBuffer_SetCurrentPosition(ds_strm->ds.play.lpDsBuffer, 0);
- if (FAILED(hr))
- return PJ_RETURN_OS_ERROR(hr);
-
-
- ds_strm->dwBytePos = 0;
- ds_strm->dwDsBufferSize = buffer_count * bytes_per_frame;
- ds_strm->timestamp.u64 = 0;
- /*
- * Play latency does not need to be on a frame boundry, it is just how far
- * ahead of the read pointer we set the write pointer. So we should just
- * use the user configured latency. However, if the latency measured in
- * bytes causes more buffers than we are allowed, we must cap the latency
- * at the time contained in 1-buffer_count.
- */
- max_latency = (1 - buffer_count) * samples_per_frame * 1000 / clock_rate /
- channel_count;
- ds_strm->latency = PJ_MIN(max_latency, snd_output_latency);
-
- /* Done setting up play device. */
- PJ_LOG(5,(THIS_FILE,
- " DirectSound player \"%s\" initialized (clock_rate=%d, "
- "channel_count=%d, samples_per_frame=%d (%dms))",
- dev_info[dev_id].info.name,
- clock_rate, channel_count, samples_per_frame,
- samples_per_frame * 1000 / clock_rate));
-
- return PJ_SUCCESS;
-}
-
-
-/*
- * Initialize DirectSound recorder device
- */
-static pj_status_t init_capture_stream( struct dsound_stream *ds_strm,
- int dev_id,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned buffer_count)
-{
- HRESULT hr;
- PCMWAVEFORMAT pcmwf;
- DSCBUFFERDESC dscbdesc;
- DSBPOSITIONNOTIFY dsPosNotify[MAX_PACKET_BUFFER_COUNT];
- unsigned bytes_per_frame;
- unsigned i;
-
-
- PJ_ASSERT_RETURN(buffer_count <= MAX_PACKET_BUFFER_COUNT, PJ_EINVAL);
-
-
- /* Check device id */
- if (dev_id == -1)
- dev_id = 0;
-
- PJ_ASSERT_RETURN(dev_id>=0 && dev_id < (int)dev_count, PJ_EINVAL);
-
- /*
- * Creating recorder device.
- */
- hr = DirectSoundCaptureCreate(dev_info[dev_id].lpGuid,
- &ds_strm->ds.capture.lpDs, NULL);
- if (FAILED(hr))
- return PJ_RETURN_OS_ERROR(hr);
-
-
- /* Init wave format to initialize buffer */
- init_waveformatex( &pcmwf, clock_rate, channel_count);
- bytes_per_frame = samples_per_frame * BYTES_PER_SAMPLE;
-
- /*
- * Setup capture buffer using sound buffer structure that was passed
- * to play buffer creation earlier.
- */
- pj_bzero(&dscbdesc, sizeof(DSCBUFFERDESC));
- dscbdesc.dwSize = sizeof(DSCBUFFERDESC);
- dscbdesc.dwFlags = DSCBCAPS_WAVEMAPPED ;
- dscbdesc.dwBufferBytes = buffer_count * bytes_per_frame;
- dscbdesc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf;
-
- hr = IDirectSoundCapture_CreateCaptureBuffer( ds_strm->ds.capture.lpDs,
- &dscbdesc,
- &ds_strm->ds.capture.lpDsBuffer,
- NULL);
- if (FAILED(hr))
- return PJ_RETURN_OS_ERROR(hr);
-
- /*
- * Create event for play notification.
- */
- ds_strm->hEvent = CreateEvent( NULL, FALSE, FALSE, NULL);
- if (ds_strm->hEvent == NULL)
- return pj_get_os_error();
-
- /*
- * Setup notifications for recording.
- */
- hr = IDirectSoundCaptureBuffer_QueryInterface( ds_strm->ds.capture.lpDsBuffer,
- &IID_IDirectSoundNotify,
- (LPVOID *)&ds_strm->lpDsNotify);
- if (FAILED(hr))
- return PJ_RETURN_OS_ERROR(hr);
-
-
- for (i=0; i<buffer_count; ++i) {
- dsPosNotify[i].dwOffset = i * bytes_per_frame;
- dsPosNotify[i].hEventNotify = ds_strm->hEvent;
- }
-
- hr = IDirectSoundNotify_SetNotificationPositions( ds_strm->lpDsNotify,
- buffer_count,
- dsPosNotify);
- if (FAILED(hr))
- return PJ_RETURN_OS_ERROR(hr);
-
- hr = IDirectSoundCaptureBuffer_GetCurrentPosition( ds_strm->ds.capture.lpDsBuffer,
- NULL, &ds_strm->dwBytePos );
- if (FAILED(hr))
- return PJ_RETURN_OS_ERROR(hr);
-
- ds_strm->timestamp.u64 = 0;
- ds_strm->dwDsBufferSize = buffer_count * bytes_per_frame;
- /*
- * Capture latency must always be on a frame boundry,
- * so compute it based off the calculated buffer_count.
- */
- ds_strm->latency = buffer_count * samples_per_frame * 1000 / clock_rate /
- channel_count;
-
- /* Done setting up recorder device. */
- PJ_LOG(5,(THIS_FILE,
- " DirectSound capture \"%s\" initialized (clock_rate=%d, "
- "channel_count=%d, samples_per_frame=%d (%dms))",
- dev_info[dev_id].info.name,
- clock_rate, channel_count, samples_per_frame,
- samples_per_frame * 1000 / clock_rate));
-
- return PJ_SUCCESS;
-}
-
-
-
-static BOOL AppReadDataFromBuffer(LPDIRECTSOUNDCAPTUREBUFFER lpDsb, // The buffer.
- DWORD dwOffset, // Our own write cursor.
- LPBYTE lpbSoundData, // Start of our data.
- DWORD dwSoundBytes) // Size of block to copy.
-{
- LPVOID lpvPtr1;
- DWORD dwBytes1;
- LPVOID lpvPtr2;
- DWORD dwBytes2;
- HRESULT hr;
-
- // Obtain memory address of write block. This will be in two parts
- // if the block wraps around.
-
- hr = IDirectSoundCaptureBuffer_Lock( lpDsb, dwOffset, dwSoundBytes, &lpvPtr1,
- &dwBytes1, &lpvPtr2, &dwBytes2, 0);
-
- if SUCCEEDED(hr) {
- // Read from pointers.
- pj_memcpy(lpbSoundData, lpvPtr1, dwBytes1);
- if (lpvPtr2 != NULL)
- pj_memcpy(lpbSoundData+dwBytes1, lpvPtr2, dwBytes2);
-
- // Release the data back to DirectSound.
- hr = IDirectSoundCaptureBuffer_Unlock(lpDsb, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);
- if SUCCEEDED(hr)
- return TRUE;
- }
-
- // Lock, Unlock, or Restore failed.
- return FALSE;
-}
-
-
-static BOOL AppWriteDataToBuffer(LPDIRECTSOUNDBUFFER lpDsb, // The buffer.
- DWORD dwOffset, // Our own write cursor.
- LPBYTE lpbSoundData, // Start of our data.
- DWORD dwSoundBytes) // Size of block to copy.
-{
- LPVOID lpvPtr1;
- DWORD dwBytes1;
- LPVOID lpvPtr2;
- DWORD dwBytes2;
- HRESULT hr;
-
- // Obtain memory address of write block. This will be in two parts
- // if the block wraps around.
-
- hr = IDirectSoundBuffer_Lock( lpDsb, dwOffset, dwSoundBytes, &lpvPtr1,
- &dwBytes1, &lpvPtr2, &dwBytes2, 0);
-
- // If the buffer was lost, restore and retry lock.
- if (DSERR_BUFFERLOST == hr) {
- IDirectSoundBuffer_Restore(lpDsb);
- hr = IDirectSoundBuffer_Lock( lpDsb, dwOffset, dwSoundBytes,
- &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);
- }
- if SUCCEEDED(hr) {
- pj_memcpy(lpvPtr1, lpbSoundData, dwBytes1);
- if (NULL != lpvPtr2)
- pj_memcpy(lpvPtr2, lpbSoundData+dwBytes1, dwBytes2);
-
- hr = IDirectSoundBuffer_Unlock(lpDsb, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);
- if SUCCEEDED(hr)
- return TRUE;
- }
-
- return FALSE;
-}
-
-/*
- * Check if there is space in playing buffer.
- */
-static unsigned dsound_play_empty_size(struct dsound_stream *dsound_strm)
-{
- HRESULT hr;
- long size_available;
- DWORD writePos, readPos;
-
- hr = IDirectSoundBuffer_GetCurrentPosition(dsound_strm->ds.play.lpDsBuffer,
- &readPos, &writePos);
- if FAILED(hr)
- return PJ_FALSE;
-
- if (readPos < dsound_strm->dwBytePos)
- size_available = readPos + dsound_strm->dwDsBufferSize -
- dsound_strm->dwBytePos;
- else
- size_available = readPos - dsound_strm->dwBytePos;
-
- return size_available;
-}
-
-
-/*
- * Check if there are captured frames in DirectSound capture buffer.
- */
-static unsigned dsound_captured_size(struct dsound_stream *dsound_strm)
-{
- HRESULT hr;
- long size_available;
- DWORD writePos, readPos;
-
- hr = IDirectSoundCaptureBuffer_GetCurrentPosition(
- dsound_strm->ds.capture.lpDsBuffer,
- &writePos, &readPos);
- if FAILED(hr)
- return PJ_FALSE;
-
- if (readPos < dsound_strm->dwBytePos)
- size_available = readPos +
- (dsound_strm->dwDsBufferSize) - dsound_strm->dwBytePos;
- else
- size_available = readPos - dsound_strm->dwBytePos;
-
- return size_available;
-}
-
-/*
- * DirectSound capture and playback thread.
- */
-static int dsound_dev_thread(void *arg)
-{
- pjmedia_snd_stream *strm = arg;
- HANDLE events[3];
- unsigned eventCount;
- unsigned bytes_per_frame;
- pj_status_t status;
-
-
- eventCount = 0;
- events[eventCount++] = strm->thread_quit_event;
- if (strm->dir & PJMEDIA_DIR_PLAYBACK)
- events[eventCount++] = strm->play_strm.hEvent;
- if (strm->dir & PJMEDIA_DIR_CAPTURE)
- events[eventCount++] = strm->rec_strm.hEvent;
-
-
- /* Raise self priority. We don't want the audio to be distorted by
- * system activity.
- */
- SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
-
- /* Calculate bytes per frame */
- bytes_per_frame = strm->samples_per_frame * BYTES_PER_SAMPLE;
-
- /*
- * Loop while not signalled to quit, wait for event objects to be
- * signalled by DirectSound capture and play buffer.
- */
- while (PJ_TRUE) {
-
- DWORD rc;
- pjmedia_dir signalled_dir;
-
- rc = WaitForMultipleObjects(eventCount, events, FALSE, INFINITE);
- if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0+eventCount)
- continue;
-
-
- if (rc == WAIT_OBJECT_0)
- break;
- if (rc == (WAIT_OBJECT_0 + 1)) {
- if (events[1] == strm->play_strm.hEvent)
- signalled_dir = PJMEDIA_DIR_PLAYBACK;
- else
- signalled_dir = PJMEDIA_DIR_CAPTURE;
- } else {
- if (events[2] == strm->play_strm.hEvent)
- signalled_dir = PJMEDIA_DIR_PLAYBACK;
- else
- signalled_dir = PJMEDIA_DIR_CAPTURE;
- }
-
-
- if (signalled_dir == PJMEDIA_DIR_PLAYBACK) {
-
- struct dsound_stream *dsound_strm;
-
- /*
- * DirectSound has requested us to feed some frames to
- * playback buffer.
- */
-
- dsound_strm = &strm->play_strm;
- status = PJ_SUCCESS;
-
- while (dsound_play_empty_size(dsound_strm) > bytes_per_frame) {
- /* Get frame from application. */
- status = (*strm->play_cb)(strm->user_data,
- dsound_strm->timestamp.u32.lo,
- strm->buffer,
- bytes_per_frame);
- if (status != PJ_SUCCESS)
- break;
-
- /* Write to DirectSound buffer. */
- AppWriteDataToBuffer( dsound_strm->ds.play.lpDsBuffer,
- dsound_strm->dwBytePos,
- (LPBYTE)strm->buffer,
- bytes_per_frame);
-
- /* Increment position. */
- dsound_strm->dwBytePos += bytes_per_frame;
- if (dsound_strm->dwBytePos >= dsound_strm->dwDsBufferSize)
- dsound_strm->dwBytePos -= dsound_strm->dwDsBufferSize;
- dsound_strm->timestamp.u64 += strm->samples_per_frame;
- }
-
- } else {
- /*
- * DirectSound has indicated that it has some frames ready
- * in the capture buffer. Get as much frames as possible to
- * prevent overflows.
- */
- struct dsound_stream *dsound_strm;
- BOOL rc;
-
- dsound_strm = &strm->rec_strm;
-
- /* Fetch while we have more than 1 frame */
- while (dsound_captured_size(dsound_strm) > bytes_per_frame) {
-
- /* Capture from DirectSound buffer. */
- rc = AppReadDataFromBuffer(dsound_strm->ds.capture.lpDsBuffer,
- dsound_strm->dwBytePos,
- (LPBYTE)strm->buffer,
- bytes_per_frame);
-
- if (!rc) {
- pj_bzero(strm->buffer, bytes_per_frame);
- }
-
- /* Call callback */
- status = (*strm->rec_cb)(strm->user_data,
- dsound_strm->timestamp.u32.lo,
- strm->buffer,
- bytes_per_frame);
-
- /* Quit thread on error. */
- if (status != PJ_SUCCESS)
- goto on_error;
-
-
- /* Increment position. */
- dsound_strm->dwBytePos += bytes_per_frame;
- if (dsound_strm->dwBytePos >= dsound_strm->dwDsBufferSize)
- dsound_strm->dwBytePos -= dsound_strm->dwDsBufferSize;
- dsound_strm->timestamp.u64 += strm->samples_per_frame;
- }
- }
- }
-
-
-on_error:
- PJ_LOG(5,(THIS_FILE, "DirectSound: thread stopping.."));
- return 0;
-}
-
-
-/* DirectSound enum device callback */
-static BOOL CALLBACK DSEnumCallback( LPGUID lpGuid, LPCTSTR lpcstrDescription,
- LPCTSTR lpcstrModule, LPVOID lpContext)
-{
- unsigned index, max = sizeof(dev_info[index].info.name);
- pj_bool_t is_capture_device = (lpContext != NULL);
-
-
- PJ_UNUSED_ARG(lpcstrModule);
-
-
- /* Put the capture and playback of the same devices to the same
- * dev_info item, by looking at the GUID.
- */
- for (index=0; index<dev_count; ++index) {
- if ((dev_info[index].lpGuid==NULL && lpGuid==NULL) ||
- pj_memcmp(&dev_info[index].guid, lpGuid, sizeof(GUID))==0)
- {
- break;
- }
- }
-
- if (index == dev_count)
- ++dev_count;
- else if (dev_count >= MAX_HARDWARE) {
- pj_assert(!"Too many DirectSound hardware found");
- PJ_LOG(4,(THIS_FILE, "Too many hardware found, some devices will "
- "not be listed"));
- return FALSE;
- }
-
-#ifdef UNICODE
- WideCharToMultiByte(CP_ACP, 0, lpcstrDescription, wcslen(lpcstrDescription),
- dev_info[index].info.name, max, NULL, NULL);
-#else
- strncpy(dev_info[index].info.name, lpcstrDescription, max);
-#endif
-
- dev_info[index].info.name[max-1] = '\0';
- if (lpGuid == NULL) {
- dev_info[index].lpGuid = NULL;
- } else {
- pj_memcpy(&dev_info[index].guid, lpGuid, sizeof(GUID));
- dev_info[index].lpGuid = &dev_info[index].guid;
- }
- dev_info[index].info.default_samples_per_sec = 44100;
-
- /* Just assumed that device supports stereo capture/playback */
- if (is_capture_device)
- dev_info[index].info.input_count+=2;
- else
- dev_info[index].info.output_count+=2;
-
- return TRUE;
-}
-
-
-/*
- * Init sound library.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
-{
- HRESULT hr;
- unsigned i;
-
- if (++snd_init_count != 1)
- return PJ_SUCCESS;
-
- pool_factory = factory;
-
- /* Enumerate sound playback devices */
- hr = DirectSoundEnumerate(&DSEnumCallback, NULL);
- if (FAILED(hr))
- return PJ_RETURN_OS_ERROR(hr);
-
- /* Enumerate sound capture devices */
- hr = DirectSoundCaptureEnumerate(&DSEnumCallback, (void*)1);
- if (FAILED(hr))
- return PJ_RETURN_OS_ERROR(hr);
-
- PJ_LOG(4,(THIS_FILE, "DirectSound initialized, found %d devices:",
- dev_count));
- for (i=0; i<dev_count; ++i) {
- PJ_LOG(4,(THIS_FILE, " dev_id %d: %s (in=%d, out=%d)",
- i, dev_info[i].info.name,
- dev_info[i].info.input_count,
- dev_info[i].info.output_count));
- }
-
- return PJ_SUCCESS;
-}
-
-/*
- * Deinitialize sound library.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
-{
- --snd_init_count;
- return PJ_SUCCESS;
-}
-
-/*
- * Get device count.
- */
-PJ_DEF(int) pjmedia_snd_get_dev_count(void)
-{
- return dev_count;
-}
-
-/*
- * Get device info.
- */
-PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
-{
- if (index == (unsigned)-1)
- index = 0;
-
- PJ_ASSERT_RETURN(index < dev_count, NULL);
-
- return &dev_info[index].info;
-}
-
-
-/*
- * Open stream.
- */
-static pj_status_t open_stream( pjmedia_dir dir,
- int rec_id,
- int play_id,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_rec_cb rec_cb,
- pjmedia_snd_play_cb play_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
-{
- pj_pool_t *pool;
- pjmedia_snd_stream *strm;
- pj_status_t status;
-
- /* Make sure sound subsystem has been initialized with
- * pjmedia_snd_init()
- */
- PJ_ASSERT_RETURN( pool_factory != NULL, PJ_EINVALIDOP );
-
-
- /* Can only support 16bits per sample */
- PJ_ASSERT_RETURN(bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
-
- /* Create and Initialize stream descriptor */
- pool = pj_pool_create(pool_factory, "dsound-dev", 1000, 1000, NULL);
- PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
-
- strm = pj_pool_zalloc(pool, sizeof(pjmedia_snd_stream));
- strm->dir = dir;
- strm->play_id = play_id;
- strm->rec_id = rec_id;
- strm->pool = pool;
- strm->rec_cb = rec_cb;
- strm->play_cb = play_cb;
- strm->user_data = user_data;
- strm->clock_rate = clock_rate;
- strm->samples_per_frame = samples_per_frame;
- strm->bits_per_sample = bits_per_sample;
- strm->channel_count = channel_count;
- strm->buffer = pj_pool_alloc(pool, samples_per_frame * BYTES_PER_SAMPLE);
- if (!strm->buffer) {
- pj_pool_release(pool);
- return PJ_ENOMEM;
- }
-
- /*
- * Create event for stopping the worker thread.
- */
- strm->thread_quit_event = CreateEvent(NULL, FALSE, FALSE, NULL);
- if (strm->thread_quit_event == NULL) {
- status = pj_get_os_error();
- pj_pool_release(pool);
- return status;
- }
-
- /* Create player stream */
- if (dir & PJMEDIA_DIR_PLAYBACK) {
- unsigned buffer_count;
-
- /* Calculate buffer count, in frame unit */
- buffer_count = clock_rate * snd_output_latency / samples_per_frame /
- 1000;
- /* There must always be one more buffer than required for the latency */
- buffer_count += 1;
- if (buffer_count < MIN_PACKET_BUFFER_COUNT)
- buffer_count = MIN_PACKET_BUFFER_COUNT;
- if (buffer_count > MAX_PACKET_BUFFER_COUNT)
- buffer_count = MAX_PACKET_BUFFER_COUNT;
-
- status = init_player_stream( &strm->play_strm, play_id, clock_rate,
- channel_count, samples_per_frame,
- buffer_count );
- if (status != PJ_SUCCESS) {
- pjmedia_snd_stream_close(strm);
- return status;
- }
- }
-
- /* Create capture stream */
- if (dir & PJMEDIA_DIR_CAPTURE) {
- unsigned buffer_count;
-
- /* Calculate buffer count, in frame unit */
- buffer_count = clock_rate * snd_input_latency / samples_per_frame /
- 1000;
- if (buffer_count < MIN_PACKET_BUFFER_COUNT)
- buffer_count = MIN_PACKET_BUFFER_COUNT;
- if (buffer_count > MAX_PACKET_BUFFER_COUNT)
- buffer_count = MAX_PACKET_BUFFER_COUNT;
-
- status = init_capture_stream( &strm->rec_strm, rec_id, clock_rate,
- channel_count, samples_per_frame,
- buffer_count);
- if (status != PJ_SUCCESS) {
- pjmedia_snd_stream_close(strm);
- return status;
- }
- }
-
-
- /* Create and start the thread */
- status = pj_thread_create(pool, "dsound", &dsound_dev_thread, strm,
- 0, 0, &strm->thread);
- if (status != PJ_SUCCESS) {
- pjmedia_snd_stream_close(strm);
- return status;
- }
-
- *p_snd_strm = strm;
-
- return PJ_SUCCESS;
-}
-
-/*
- * Open stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_rec_cb rec_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
-{
- PJ_ASSERT_RETURN(rec_cb && p_snd_strm, PJ_EINVAL);
-
- return open_stream( PJMEDIA_DIR_CAPTURE, index, -1,
- clock_rate, channel_count, samples_per_frame,
- bits_per_sample, rec_cb, NULL, user_data,
- p_snd_strm);
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_play_cb play_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
-{
- PJ_ASSERT_RETURN(play_cb && p_snd_strm, PJ_EINVAL);
-
- return open_stream( PJMEDIA_DIR_PLAYBACK, -1, index,
- clock_rate, channel_count, samples_per_frame,
- bits_per_sample, NULL, play_cb, user_data,
- p_snd_strm);
-}
-
-/*
- * Open both player and recorder.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id,
- int play_id,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_rec_cb rec_cb,
- pjmedia_snd_play_cb play_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
-{
- PJ_ASSERT_RETURN(rec_cb && play_cb && p_snd_strm, PJ_EINVAL);
-
- return open_stream( PJMEDIA_DIR_CAPTURE_PLAYBACK, rec_id, play_id,
- clock_rate, channel_count, samples_per_frame,
- bits_per_sample, rec_cb, play_cb, user_data,
- p_snd_strm );
-}
-
-/*
- * Get stream info.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
- pjmedia_snd_stream_info *pi)
-{
-
- PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
-
- pj_bzero(pi, sizeof(*pi));
- pi->dir = strm->dir;
- pi->play_id = strm->play_id;
- pi->rec_id = strm->rec_id;
- pi->clock_rate = strm->clock_rate;
- pi->channel_count = strm->channel_count;
- pi->samples_per_frame = strm->samples_per_frame;
- pi->bits_per_sample = strm->bits_per_sample;
- pi->rec_latency = strm->rec_strm.latency * strm->clock_rate *
- strm->channel_count/ 1000;
- pi->play_latency = strm->play_strm.latency * strm->clock_rate *
- strm->channel_count/ 1000;
-
- return PJ_SUCCESS;
-}
-
-
-/*
- * Start stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
-{
- HRESULT hr;
-
- if (stream->play_strm.ds.play.lpDsBuffer) {
- hr = IDirectSoundBuffer_SetCurrentPosition(
- stream->play_strm.ds.play.lpDsBuffer, 0);
- if (FAILED(hr))
- return PJ_RETURN_OS_ERROR(hr);
-
- /* Set the write pointer ahead of the read pointer by latency bytes */
- stream->play_strm.dwBytePos = BYTES_PER_SAMPLE * stream->clock_rate *
- stream->channel_count * stream->play_strm.latency / 1000;
-
- hr = IDirectSoundBuffer_Play(stream->play_strm.ds.play.lpDsBuffer,
- 0, 0, DSBPLAY_LOOPING);
- if (FAILED(hr))
- return PJ_RETURN_OS_ERROR(hr);
- PJ_LOG(5,(THIS_FILE, "DirectSound playback stream started"));
- }
-
- if (stream->rec_strm.ds.capture.lpDsBuffer) {
- hr = IDirectSoundCaptureBuffer_GetCurrentPosition(
- stream->rec_strm.ds.capture.lpDsBuffer,
- NULL, &stream->rec_strm.dwBytePos );
- if (FAILED(hr))
- return PJ_RETURN_OS_ERROR(hr);
-
- hr = IDirectSoundCaptureBuffer_Start(
- stream->rec_strm.ds.capture.lpDsBuffer,
- DSCBSTART_LOOPING );
- if (FAILED(hr))
- return PJ_RETURN_OS_ERROR(hr);
- PJ_LOG(5,(THIS_FILE, "DirectSound capture stream started"));
- }
-
- return PJ_SUCCESS;
-}
-
-/*
- * Stop stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
-{
- PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-
- if (stream->play_strm.ds.play.lpDsBuffer) {
- PJ_LOG(5,(THIS_FILE, "Stopping DirectSound playback stream"));
- IDirectSoundBuffer_Stop( stream->play_strm.ds.play.lpDsBuffer );
- }
-
- if (stream->rec_strm.ds.capture.lpDsBuffer) {
- PJ_LOG(5,(THIS_FILE, "Stopping DirectSound capture stream"));
- IDirectSoundCaptureBuffer_Stop(stream->rec_strm.ds.capture.lpDsBuffer);
- }
-
- return PJ_SUCCESS;
-}
-
-
-/*
- * Destroy stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
-{
- PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-
- pjmedia_snd_stream_stop(stream);
-
- if (stream->thread) {
- pj_assert(stream->thread_quit_event);
- SetEvent(stream->thread_quit_event);
- pj_thread_join(stream->thread);
- pj_thread_destroy(stream->thread);
- stream->thread = NULL;
- }
-
- if (stream->thread_quit_event) {
- CloseHandle(stream->thread_quit_event);
- stream->thread_quit_event = NULL;
- }
-
- if (stream->play_strm.lpDsNotify) {
- IDirectSoundNotify_Release( stream->play_strm.lpDsNotify );
- stream->play_strm.lpDsNotify = NULL;
- }
-
- if (stream->play_strm.hEvent) {
- CloseHandle(stream->play_strm.hEvent);
- stream->play_strm.hEvent = NULL;
- }
-
- if (stream->play_strm.ds.play.lpDsBuffer) {
- IDirectSoundBuffer_Release( stream->play_strm.ds.play.lpDsBuffer );
- stream->play_strm.ds.play.lpDsBuffer = NULL;
- }
-
- if (stream->play_strm.ds.play.lpDs) {
- IDirectSound_Release( stream->play_strm.ds.play.lpDs );
- stream->play_strm.ds.play.lpDs = NULL;
- }
-
- if (stream->rec_strm.lpDsNotify) {
- IDirectSoundNotify_Release( stream->rec_strm.lpDsNotify );
- stream->rec_strm.lpDsNotify = NULL;
- }
-
- if (stream->rec_strm.hEvent) {
- CloseHandle(stream->rec_strm.hEvent);
- stream->rec_strm.hEvent = NULL;
- }
-
- if (stream->rec_strm.ds.capture.lpDsBuffer) {
- IDirectSoundCaptureBuffer_Release( stream->rec_strm.ds.capture.lpDsBuffer );
- stream->rec_strm.ds.capture.lpDsBuffer = NULL;
- }
-
- if (stream->rec_strm.ds.capture.lpDs) {
- IDirectSoundCapture_Release( stream->rec_strm.ds.capture.lpDs );
- stream->rec_strm.ds.capture.lpDs = NULL;
- }
-
-
- pj_pool_release(stream->pool);
-
- return PJ_SUCCESS;
-}
-
-
-/*
- * Set sound latency.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency,
- unsigned output_latency)
-{
- snd_input_latency = (input_latency == 0)?
- PJMEDIA_SND_DEFAULT_REC_LATENCY : input_latency;
- snd_output_latency = (output_latency == 0)?
- PJMEDIA_SND_DEFAULT_PLAY_LATENCY : output_latency;
-
- return PJ_SUCCESS;
-}
-
-#endif /* PJMEDIA_SOUND_IMPLEMENTATION */
-
diff --git a/pjmedia/src/pjmedia/endpoint.c b/pjmedia/src/pjmedia/endpoint.c
index fb66a813..a3e988d5 100644
--- a/pjmedia/src/pjmedia/endpoint.c
+++ b/pjmedia/src/pjmedia/endpoint.c
@@ -20,6 +20,7 @@
#include <pjmedia/endpoint.h>
#include <pjmedia/errno.h>
#include <pjmedia/sdp.h>
+#include <pjmedia-audiodev/audiodev.h>
#include <pj/assert.h>
#include <pj/ioqueue.h>
#include <pj/log.h>
@@ -121,7 +122,7 @@ PJ_DEF(pj_status_t) pjmedia_endpt_create(pj_pool_factory *pf,
endpt->thread_cnt = worker_cnt;
/* Sound */
- status = pjmedia_snd_init(pf);
+ status = pjmedia_aud_subsys_init(pf);
if (status != PJ_SUCCESS)
goto on_error;
@@ -171,7 +172,7 @@ on_error:
if (endpt->ioqueue && endpt->own_ioqueue)
pj_ioqueue_destroy(endpt->ioqueue);
- pjmedia_snd_deinit();
+ pjmedia_aud_subsys_shutdown();
pj_pool_release(pool);
return status;
}
@@ -212,7 +213,7 @@ PJ_DEF(pj_status_t) pjmedia_endpt_destroy (pjmedia_endpt *endpt)
endpt->pf = NULL;
- pjmedia_snd_deinit();
+ pjmedia_aud_subsys_shutdown();
pj_pool_release (endpt->pool);
return PJ_SUCCESS;
diff --git a/pjmedia/src/pjmedia/errno.c b/pjmedia/src/pjmedia/errno.c
index f92e888c..d86eb585 100644
--- a/pjmedia/src/pjmedia/errno.c
+++ b/pjmedia/src/pjmedia/errno.c
@@ -20,7 +20,8 @@
#include <pjmedia/errno.h>
#include <pjmedia/types.h>
#include <pj/string.h>
-#if PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_PORTAUDIO_SOUND
+#if defined(PJMEDIA_SOUND_IMPLEMENTATION) && \
+ PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_PORTAUDIO_SOUND
# include <portaudio.h>
#endif
@@ -179,7 +180,8 @@ PJ_DEF(pj_str_t) pjmedia_strerror( pj_status_t statcode,
#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
/* See if the error comes from PortAudio. */
-#if PJMEDIA_SOUND_IMPLEMENTATION==PJMEDIA_SOUND_PORTAUDIO_SOUND
+#if defined(PJMEDIA_SOUND_IMPLEMENTATION) && \
+ PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_PORTAUDIO_SOUND
if (statcode >= PJMEDIA_PORTAUDIO_ERRNO_START &&
statcode <= PJMEDIA_PORTAUDIO_ERRNO_END)
{
diff --git a/pjmedia/src/pjmedia/nullsound.c b/pjmedia/src/pjmedia/nullsound.c
deleted file mode 100644
index a3ee54a1..00000000
--- a/pjmedia/src/pjmedia/nullsound.c
+++ /dev/null
@@ -1,197 +0,0 @@
-/* $Id$ */
-/*
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
- * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
- *
- * 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 2 of the License, or
- * (at your option) 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-#include <pjmedia/sound.h>
-#include <pjmedia/errno.h>
-#include <pj/assert.h>
-#include <pj/pool.h>
-
-#if PJMEDIA_SOUND_IMPLEMENTATION==PJMEDIA_SOUND_NULL_SOUND
-
-static pjmedia_snd_dev_info null_info =
-{
- "Null Device",
- 1,
- 1,
- 8000
-};
-
-static pj_pool_factory *pool_factory;
-
-struct pjmedia_snd_stream
-{
- pj_pool_t *pool;
- pjmedia_dir dir;
- int rec_id;
- int play_id;
- unsigned clock_rate;
- unsigned channel_count;
- unsigned samples_per_frame;
- unsigned bits_per_sample;
- pjmedia_snd_rec_cb rec_cb;
- pjmedia_snd_play_cb play_cb;
- void *user_data;
-};
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
-{
- pool_factory = factory;
- return PJ_SUCCESS;
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
-{
- return PJ_SUCCESS;
-}
-
-PJ_DEF(int) pjmedia_snd_get_dev_count(void)
-{
- return 1;
-}
-
-PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
-{
- PJ_ASSERT_RETURN(index==0 || index==(unsigned)-1, NULL);
- return &null_info;
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_rec_cb rec_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
-{
- return pjmedia_snd_open(index, -2, clock_rate, channel_count,
- samples_per_frame, bits_per_sample,
- rec_cb, NULL, user_data, p_snd_strm);
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_play_cb play_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm )
-{
- return pjmedia_snd_open(-2, index, clock_rate, channel_count,
- samples_per_frame, bits_per_sample,
- NULL, play_cb, user_data, p_snd_strm);
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id,
- int play_id,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_rec_cb rec_cb,
- pjmedia_snd_play_cb play_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
-{
- pj_pool_t *pool;
- pjmedia_snd_stream *snd_strm;
-
- pool = pj_pool_create(pool_factory, NULL, 128, 128, NULL);
- snd_strm = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_stream);
-
- snd_strm->pool = pool;
-
- if (rec_id == -1) rec_id = 0;
- if (play_id == -1) play_id = 0;
-
- if (rec_id != -2 && play_id != -2)
- snd_strm->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
- else if (rec_id != -2)
- snd_strm->dir = PJMEDIA_DIR_CAPTURE;
- else if (play_id != -2)
- snd_strm->dir = PJMEDIA_DIR_PLAYBACK;
-
- snd_strm->rec_id = rec_id;
- snd_strm->play_id = play_id;
- snd_strm->clock_rate = clock_rate;
- snd_strm->channel_count = channel_count;
- snd_strm->samples_per_frame = samples_per_frame;
- snd_strm->bits_per_sample = bits_per_sample;
- snd_strm->rec_cb = rec_cb;
- snd_strm->play_cb = play_cb;
- snd_strm->user_data = user_data;
-
- *p_snd_strm = snd_strm;
-
- return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
-{
- PJ_UNUSED_ARG(stream);
- return PJ_SUCCESS;
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
-{
- PJ_UNUSED_ARG(stream);
- return PJ_SUCCESS;
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
- pjmedia_snd_stream_info *pi)
-{
-
- pj_bzero(pi, sizeof(pjmedia_snd_stream_info));
- pi->dir = strm->dir;
- pi->play_id = strm->play_id;
- pi->rec_id = strm->rec_id;
- pi->clock_rate = strm->clock_rate;
- pi->channel_count = strm->channel_count;
- pi->samples_per_frame = strm->samples_per_frame;
- pi->bits_per_sample = strm->bits_per_sample;
- pi->rec_latency = 0;
- pi->play_latency = 0;
-
- return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
-{
- pj_pool_release(stream->pool);
- return PJ_SUCCESS;
-}
-
-/*
- * Set sound latency.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency,
- unsigned output_latency)
-{
- /* Nothing to do */
- PJ_UNUSED_ARG(input_latency);
- PJ_UNUSED_ARG(output_latency);
- return PJ_SUCCESS;
-}
-
-#endif /* PJMEDIA_SOUND_IMPLEMENTATION */
diff --git a/pjmedia/src/pjmedia/sound_legacy.c b/pjmedia/src/pjmedia/sound_legacy.c
new file mode 100644
index 00000000..3500588c
--- /dev/null
+++ b/pjmedia/src/pjmedia/sound_legacy.c
@@ -0,0 +1,284 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This is implementation of legacy sound device API, for applications
+ * that still use the old/deprecated sound device API. This implementation
+ * uses the new Audio Device API.
+ *
+ * Please see http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more
+ * information.
+ */
+
+#include <pjmedia/sound.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pj/assert.h>
+
+#if PJMEDIA_HAS_LEGACY_SOUND_API
+
+static struct legacy_subsys
+{
+ pjmedia_snd_dev_info info[4];
+ unsigned info_counter;
+ unsigned user_rec_latency;
+ unsigned user_play_latency;
+} g_sys;
+
+struct pjmedia_snd_stream
+{
+ pj_pool_t *pool;
+ pjmedia_aud_stream *aud_strm;
+ pjmedia_snd_rec_cb user_rec_cb;
+ pjmedia_snd_play_cb user_play_cb;
+ void *user_user_data;
+};
+
+PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
+{
+ return pjmedia_aud_subsys_init(factory);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
+{
+ return pjmedia_aud_subsys_shutdown();
+}
+
+PJ_DEF(int) pjmedia_snd_get_dev_count(void)
+{
+ return pjmedia_aud_dev_count();
+}
+
+PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
+{
+ pjmedia_snd_dev_info *oi = &g_sys.info[g_sys.info_counter];
+ pjmedia_aud_dev_info di;
+
+ g_sys.info_counter = (g_sys.info_counter+1) % PJ_ARRAY_SIZE(g_sys.info);
+
+ if (pjmedia_aud_dev_get_info(index, &di) != PJ_SUCCESS)
+ return NULL;
+
+ pj_bzero(oi, sizeof(*oi));
+ pj_ansi_strncpy(oi->name, di.name, sizeof(oi->name));
+ oi->name[sizeof(oi->name)-1] = '\0';
+ oi->input_count = di.input_count;
+ oi->output_count = di.output_count;
+ oi->default_samples_per_sec = di.default_samples_per_sec;
+
+ return oi;
+}
+
+
+static pj_status_t snd_play_cb(void *user_data,
+ pjmedia_frame *frame)
+{
+ pjmedia_snd_stream *strm = (pjmedia_snd_stream*)user_data;
+
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ return strm->user_play_cb(strm->user_user_data,
+ frame->timestamp.u32.lo,
+ frame->buf,
+ frame->size);
+}
+
+static pj_status_t snd_rec_cb(void *user_data,
+ pjmedia_frame *frame)
+{
+ pjmedia_snd_stream *strm = (pjmedia_snd_stream*)user_data;
+ return strm->user_rec_cb(strm->user_user_data,
+ frame->timestamp.u32.lo,
+ frame->buf,
+ frame->size);
+}
+
+static pj_status_t open_stream( pjmedia_dir dir,
+ int rec_id,
+ int play_id,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ pjmedia_snd_rec_cb rec_cb,
+ pjmedia_snd_play_cb play_cb,
+ void *user_data,
+ pjmedia_snd_stream **p_snd_strm)
+{
+ pj_pool_t *pool;
+ pjmedia_snd_stream *snd_strm;
+ pjmedia_aud_param param;
+ pj_status_t status;
+
+ /* Initialize parameters */
+ if (dir & PJMEDIA_DIR_CAPTURE) {
+ status = pjmedia_aud_dev_default_param(rec_id, &param);
+ } else {
+ status = pjmedia_aud_dev_default_param(play_id, &param);
+ }
+ if (status != PJ_SUCCESS)
+ return status;
+
+ param.dir = dir;
+ param.rec_id = rec_id;
+ param.play_id = play_id;
+ param.clock_rate = clock_rate;
+ param.channel_count = channel_count;
+ param.samples_per_frame = samples_per_frame;
+ param.bits_per_sample = bits_per_sample;
+
+ /* Latencies setting */
+ if ((dir & PJMEDIA_DIR_CAPTURE) && g_sys.user_rec_latency) {
+ param.flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+ param.input_latency_ms = g_sys.user_rec_latency;
+ }
+ if ((dir & PJMEDIA_DIR_PLAYBACK) && g_sys.user_play_latency) {
+ param.flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ param.output_latency_ms = g_sys.user_play_latency;
+ }
+
+ /* Create sound wrapper */
+ pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(),
+ "legacy-snd", 512, 512, NULL);
+ snd_strm = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_stream);
+ snd_strm->pool = pool;
+ snd_strm->user_rec_cb = rec_cb;
+ snd_strm->user_play_cb = play_cb;
+ snd_strm->user_user_data = user_data;
+
+ /* Create the stream */
+ status = pjmedia_aud_stream_create(&param, &snd_rec_cb,
+ &snd_play_cb, snd_strm,
+ &snd_strm->aud_strm);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return status;
+ }
+
+ *p_snd_strm = snd_strm;
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ pjmedia_snd_rec_cb rec_cb,
+ void *user_data,
+ pjmedia_snd_stream **p_snd_strm)
+{
+ return open_stream(PJMEDIA_DIR_CAPTURE, index, PJMEDIA_AUD_INVALID_DEV,
+ clock_rate, channel_count, samples_per_frame,
+ bits_per_sample, rec_cb, NULL,
+ user_data, p_snd_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ pjmedia_snd_play_cb play_cb,
+ void *user_data,
+ pjmedia_snd_stream **p_snd_strm )
+{
+ return open_stream(PJMEDIA_DIR_PLAYBACK, PJMEDIA_AUD_INVALID_DEV, index,
+ clock_rate, channel_count, samples_per_frame,
+ bits_per_sample, NULL, play_cb,
+ user_data, p_snd_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id,
+ int play_id,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ pjmedia_snd_rec_cb rec_cb,
+ pjmedia_snd_play_cb play_cb,
+ void *user_data,
+ pjmedia_snd_stream **p_snd_strm)
+{
+ return open_stream(PJMEDIA_DIR_CAPTURE_PLAYBACK, rec_id, play_id,
+ clock_rate, channel_count, samples_per_frame,
+ bits_per_sample, rec_cb, play_cb,
+ user_data, p_snd_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
+{
+ return pjmedia_aud_stream_start(stream->aud_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
+{
+ return pjmedia_aud_stream_stop(stream->aud_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
+ pjmedia_snd_stream_info *pi)
+{
+ pjmedia_aud_param param;
+ pj_status_t status;
+
+ status = pjmedia_aud_stream_get_param(strm->aud_strm, &param);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_bzero(pi, sizeof(*pi));
+ pi->dir = param.dir;
+ pi->play_id = param.play_id;
+ pi->rec_id = param.rec_id;
+ pi->clock_rate = param.clock_rate;
+ pi->channel_count = param.channel_count;
+ pi->samples_per_frame = param.samples_per_frame;
+ pi->bits_per_sample = param.bits_per_sample;
+
+ if (param.flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) {
+ pi->rec_latency = param.input_latency_ms;
+ }
+ if (param.flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) {
+ pi->play_latency = param.output_latency_ms;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
+{
+ pj_status_t status;
+
+ status = pjmedia_aud_stream_destroy(stream->aud_strm);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_pool_release(stream->pool);
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency,
+ unsigned output_latency)
+{
+ g_sys.user_rec_latency = input_latency;
+ g_sys.user_play_latency = output_latency;
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_HAS_LEGACY_SOUND_API */
+
diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c
index 3a21ee5d..7117c82b 100644
--- a/pjmedia/src/pjmedia/sound_port.c
+++ b/pjmedia/src/pjmedia/sound_port.c
@@ -18,16 +18,15 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjmedia/sound_port.h>
+#include <pjmedia/alaw_ulaw.h>
#include <pjmedia/delaybuf.h>
#include <pjmedia/echo.h>
#include <pjmedia/errno.h>
-#include <pjmedia/plc.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/rand.h>
#include <pj/string.h> /* pj_memset() */
-//#define SIMULATE_LOST_PCT 20
#define AEC_TAIL 128 /* default AEC length in ms */
#define AEC_SUSPEND_LIMIT 5 /* seconds of no activity */
@@ -35,119 +34,55 @@
//#define TEST_OVERFLOW_UNDERFLOW
-enum
-{
- PJMEDIA_PLC_ENABLED = 1,
-};
-
-//#define DEFAULT_OPTIONS PJMEDIA_PLC_ENABLED
-#define DEFAULT_OPTIONS 0
-
-
struct pjmedia_snd_port
{
int rec_id;
int play_id;
- pjmedia_snd_stream *snd_stream;
+ pj_uint32_t aud_caps;
+ pjmedia_aud_param aud_param;
+ pjmedia_aud_stream *aud_stream;
pjmedia_dir dir;
pjmedia_port *port;
- unsigned options;
-
- pjmedia_echo_state *ec_state;
- unsigned aec_tail_len;
-
- pj_bool_t ec_suspended;
- unsigned ec_suspend_count;
- unsigned ec_suspend_limit;
-
- pjmedia_plc *plc;
unsigned clock_rate;
unsigned channel_count;
unsigned samples_per_frame;
unsigned bits_per_sample;
-#if PJMEDIA_SOUND_USE_DELAYBUF
- pjmedia_delay_buf *delay_buf;
-#endif
+ /* software ec */
+ pjmedia_echo_state *ec_state;
+ unsigned ec_options;
+ unsigned ec_tail_len;
+ pj_bool_t ec_suspended;
+ unsigned ec_suspend_count;
+ unsigned ec_suspend_limit;
};
/*
* The callback called by sound player when it needs more samples to be
* played.
*/
-static pj_status_t play_cb(/* in */ void *user_data,
- /* in */ pj_uint32_t timestamp,
- /* out */ void *output,
- /* out */ unsigned size)
+static pj_status_t play_cb(void *user_data, pjmedia_frame *frame)
{
pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
pjmedia_port *port;
- pjmedia_frame frame;
+ unsigned required_size = frame->size;
pj_status_t status;
- /* We're risking accessing the port without holding any mutex.
- * It's possible that port is disconnected then destroyed while
- * we're trying to access it.
- * But in the name of performance, we'll try this approach until
- * someone complains when it crashes.
- */
port = snd_port->port;
if (port == NULL)
goto no_frame;
- frame.buf = output;
- frame.size = size;
- frame.timestamp.u32.hi = 0;
- frame.timestamp.u32.lo = timestamp;
-
-#if PJMEDIA_SOUND_USE_DELAYBUF
- if (snd_port->delay_buf) {
- status = pjmedia_delay_buf_get(snd_port->delay_buf, (pj_int16_t*)output);
- if (status != PJ_SUCCESS)
- pj_bzero(output, size);
-
- frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
- pjmedia_port_put_frame(port, &frame);
-
-#ifdef TEST_OVERFLOW_UNDERFLOW
- {
- static int count = 1;
- if (++count % 10 == 0) {
- status = pjmedia_delay_buf_get(snd_port->delay_buf,
- (pj_int16_t*)output);
- if (status != PJ_SUCCESS)
- pj_bzero(output, size);
-
- frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
- pjmedia_port_put_frame(port, &frame);
- }
- }
-#endif
-
- }
-#endif
-
- status = pjmedia_port_get_frame(port, &frame);
+ status = pjmedia_port_get_frame(port, frame);
if (status != PJ_SUCCESS)
goto no_frame;
- if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO)
goto no_frame;
/* Must supply the required samples */
- pj_assert(frame.size == size);
-
-#ifdef SIMULATE_LOST_PCT
- /* Simulate packet lost */
- if (pj_rand() % 100 < SIMULATE_LOST_PCT) {
- PJ_LOG(4,(THIS_FILE, "Frame dropped"));
- goto no_frame;
- }
-#endif
-
- if (snd_port->plc)
- pjmedia_plc_save(snd_port->plc, (pj_int16_t*) output);
+ PJ_UNUSED_ARG(required_size);
+ pj_assert(frame->size == required_size);
if (snd_port->ec_state) {
if (snd_port->ec_suspended) {
@@ -156,7 +91,7 @@ static pj_status_t play_cb(/* in */ void *user_data,
PJ_LOG(4,(THIS_FILE, "EC activated"));
}
snd_port->ec_suspend_count = 0;
- pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)output);
+ pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf);
}
@@ -172,22 +107,10 @@ no_frame:
}
if (snd_port->ec_state) {
/* To maintain correct delay in EC */
- pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)output);
+ pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf);
}
}
- /* Apply PLC */
- if (snd_port->plc) {
-
- pjmedia_plc_generate(snd_port->plc, (pj_int16_t*) output);
-#ifdef SIMULATE_LOST_PCT
- PJ_LOG(4,(THIS_FILE, "Lost frame generated"));
-#endif
- } else {
- pj_bzero(output, size);
- }
-
-
return PJ_SUCCESS;
}
@@ -196,49 +119,59 @@ no_frame:
* The callback called by sound recorder when it has finished capturing a
* frame.
*/
-static pj_status_t rec_cb(/* in */ void *user_data,
- /* in */ pj_uint32_t timestamp,
- /* in */ void *input,
- /* in*/ unsigned size)
+static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame)
{
pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
pjmedia_port *port;
- pjmedia_frame frame;
- /* We're risking accessing the port without holding any mutex.
- * It's possible that port is disconnected then destroyed while
- * we're trying to access it.
- * But in the name of performance, we'll try this approach until
- * someone complains when it crashes.
- */
port = snd_port->port;
if (port == NULL)
return PJ_SUCCESS;
/* Cancel echo */
if (snd_port->ec_state && !snd_port->ec_suspended) {
- pjmedia_echo_capture(snd_port->ec_state, (pj_int16_t*) input, 0);
+ pjmedia_echo_capture(snd_port->ec_state, (pj_int16_t*) frame->buf, 0);
}
-#if PJMEDIA_SOUND_USE_DELAYBUF
- if (snd_port->delay_buf) {
- pjmedia_delay_buf_put(snd_port->delay_buf, (pj_int16_t*)input);
- } else {
- frame.buf = (void*)input;
- frame.size = size;
- frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
- frame.timestamp.u32.lo = timestamp;
+ pjmedia_port_put_frame(port, frame);
+
+ return PJ_SUCCESS;
+}
- pjmedia_port_put_frame(port, &frame);
+/*
+ * The callback called by sound player when it needs more samples to be
+ * played. This version is for non-PCM data.
+ */
+static pj_status_t play_cb_ext(void *user_data, pjmedia_frame *frame)
+{
+ pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
+ pjmedia_port *port = snd_port->port;
+
+ if (port == NULL) {
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ return PJ_SUCCESS;
}
-#else
- frame.buf = (void*)input;
- frame.size = size;
- frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
- frame.timestamp.u32.lo = timestamp;
- pjmedia_port_put_frame(port, &frame);
-#endif
+ pjmedia_port_get_frame(port, frame);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * The callback called by sound recorder when it has finished capturing a
+ * frame. This version is for non-PCM data.
+ */
+static pj_status_t rec_cb_ext(void *user_data, pjmedia_frame *frame)
+{
+ pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
+ pjmedia_port *port;
+
+ port = snd_port->port;
+ if (port == NULL)
+ return PJ_SUCCESS;
+
+ pjmedia_port_put_frame(port, frame);
return PJ_SUCCESS;
}
@@ -250,84 +183,102 @@ static pj_status_t rec_cb(/* in */ void *user_data,
static pj_status_t start_sound_device( pj_pool_t *pool,
pjmedia_snd_port *snd_port )
{
+ pjmedia_aud_rec_cb snd_rec_cb;
+ pjmedia_aud_play_cb snd_play_cb;
+ pjmedia_aud_param param_copy;
pj_status_t status;
/* Check if sound has been started. */
- if (snd_port->snd_stream != NULL)
+ if (snd_port->aud_stream != NULL)
return PJ_SUCCESS;
- /* Open sound stream. */
- if (snd_port->dir == PJMEDIA_DIR_CAPTURE) {
- status = pjmedia_snd_open_rec( snd_port->rec_id,
- snd_port->clock_rate,
- snd_port->channel_count,
- snd_port->samples_per_frame,
- snd_port->bits_per_sample,
- &rec_cb,
- snd_port,
- &snd_port->snd_stream);
+ PJ_ASSERT_RETURN(snd_port->dir == PJMEDIA_DIR_CAPTURE ||
+ snd_port->dir == PJMEDIA_DIR_PLAYBACK ||
+ snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK,
+ PJ_EBUG);
- } else if (snd_port->dir == PJMEDIA_DIR_PLAYBACK) {
- status = pjmedia_snd_open_player( snd_port->play_id,
- snd_port->clock_rate,
- snd_port->channel_count,
- snd_port->samples_per_frame,
- snd_port->bits_per_sample,
- &play_cb,
- snd_port,
- &snd_port->snd_stream);
-
- } else if (snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
- status = pjmedia_snd_open( snd_port->rec_id,
- snd_port->play_id,
- snd_port->clock_rate,
- snd_port->channel_count,
- snd_port->samples_per_frame,
- snd_port->bits_per_sample,
- &rec_cb,
- &play_cb,
- snd_port,
- &snd_port->snd_stream);
+ /* Get device caps */
+ if (snd_port->aud_param.dir & PJMEDIA_DIR_CAPTURE) {
+ pjmedia_aud_dev_info dev_info;
+
+ status = pjmedia_aud_dev_get_info(snd_port->aud_param.rec_id,
+ &dev_info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ snd_port->aud_caps = dev_info.caps;
+ } else {
+ snd_port->aud_caps = 0;
+ }
+
+ /* Process EC settings */
+ pj_memcpy(&param_copy, &snd_port->aud_param, sizeof(param_copy));
+ if (param_copy.flags & PJMEDIA_AUD_DEV_CAP_EC) {
+ /* EC is wanted */
+ if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) {
+ /* Device supports EC */
+ /* Nothing to do */
+ } else {
+ /* Device doesn't support EC, remove EC settings from
+ * device parameters
+ */
+ param_copy.flags &= ~(PJMEDIA_AUD_DEV_CAP_EC |
+ PJMEDIA_AUD_DEV_CAP_EC_TAIL);
+ }
+ }
+
+ /* Use different callback if format is not PCM */
+ if (snd_port->aud_param.ext_fmt.id == PJMEDIA_FORMAT_L16) {
+ snd_rec_cb = &rec_cb;
+ snd_play_cb = &play_cb;
} else {
- pj_assert(!"Invalid dir");
- status = PJ_EBUG;
+ snd_rec_cb = &rec_cb_ext;
+ snd_play_cb = &play_cb_ext;
}
+ /* Open the device */
+ status = pjmedia_aud_stream_create(&param_copy,
+ snd_rec_cb,
+ snd_play_cb,
+ snd_port,
+ &snd_port->aud_stream);
+
if (status != PJ_SUCCESS)
return status;
+ /* Inactivity limit before EC is suspended. */
+ snd_port->ec_suspend_limit = AEC_SUSPEND_LIMIT *
+ (snd_port->clock_rate /
+ snd_port->samples_per_frame);
-#ifdef SIMULATE_LOST_PCT
- snd_port->options |= PJMEDIA_PLC_ENABLED;
-#endif
-
- /* If we have player components, allocate buffer to save the last
- * frame played to the speaker. The last frame is used for packet
- * lost concealment (PLC) algorithm.
+ /* Create software EC if parameter specifies EC but device
+ * doesn't support EC. Only do this if the format is PCM!
*/
- if ((snd_port->dir & PJMEDIA_DIR_PLAYBACK) &&
- (snd_port->options & PJMEDIA_PLC_ENABLED))
+ if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC) &&
+ (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)==0 &&
+ param_copy.ext_fmt.id == PJMEDIA_FORMAT_PCM)
{
- status = pjmedia_plc_create(pool, snd_port->clock_rate,
- snd_port->samples_per_frame *
- snd_port->channel_count,
- 0, &snd_port->plc);
+ if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) {
+ snd_port->aud_param.flags |= PJMEDIA_AUD_DEV_CAP_EC_TAIL;
+ snd_port->aud_param.ec_tail_ms = AEC_TAIL;
+ PJ_LOG(4,(THIS_FILE, "AEC tail is set to default %u ms",
+ snd_port->aud_param.ec_tail_ms));
+ }
+
+ status = pjmedia_snd_port_set_ec(snd_port, pool,
+ snd_port->aud_param.ec_tail_ms, 0);
if (status != PJ_SUCCESS) {
- PJ_LOG(4,(THIS_FILE, "Unable to create PLC"));
- snd_port->plc = NULL;
+ pjmedia_aud_stream_destroy(snd_port->aud_stream);
+ snd_port->aud_stream = NULL;
+ return status;
}
}
- /* Inactivity limit before EC is suspended. */
- snd_port->ec_suspend_limit = AEC_SUSPEND_LIMIT *
- (snd_port->clock_rate /
- snd_port->samples_per_frame);
-
/* Start sound stream. */
- status = pjmedia_snd_stream_start(snd_port->snd_stream);
+ status = pjmedia_aud_stream_start(snd_port->aud_stream);
if (status != PJ_SUCCESS) {
- pjmedia_snd_stream_close(snd_port->snd_stream);
- snd_port->snd_stream = NULL;
+ pjmedia_aud_stream_destroy(snd_port->aud_stream);
+ snd_port->aud_stream = NULL;
return status;
}
@@ -342,10 +293,10 @@ static pj_status_t start_sound_device( pj_pool_t *pool,
static pj_status_t stop_sound_device( pjmedia_snd_port *snd_port )
{
/* Check if we have sound stream device. */
- if (snd_port->snd_stream) {
- pjmedia_snd_stream_stop(snd_port->snd_stream);
- pjmedia_snd_stream_close(snd_port->snd_stream);
- snd_port->snd_stream = NULL;
+ if (snd_port->aud_stream) {
+ pjmedia_aud_stream_stop(snd_port->aud_stream);
+ pjmedia_aud_stream_destroy(snd_port->aud_stream);
+ snd_port->aud_stream = NULL;
}
/* Destroy AEC */
@@ -371,47 +322,24 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create( pj_pool_t *pool,
unsigned options,
pjmedia_snd_port **p_port)
{
- pjmedia_snd_port *snd_port;
-
- PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL);
-
- snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port);
- PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM);
+ pjmedia_aud_param param;
+ pj_status_t status;
- snd_port->rec_id = rec_id;
- snd_port->play_id = play_id;
- snd_port->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
- snd_port->options = options | DEFAULT_OPTIONS;
- snd_port->clock_rate = clock_rate;
- snd_port->channel_count = channel_count;
- snd_port->samples_per_frame = samples_per_frame;
- snd_port->bits_per_sample = bits_per_sample;
-
-#if PJMEDIA_SOUND_USE_DELAYBUF
- do {
- pj_status_t status;
- unsigned ptime;
-
- ptime = samples_per_frame * 1000 / (clock_rate * channel_count);
-
- status = pjmedia_delay_buf_create(pool, "snd_buff",
- clock_rate, samples_per_frame,
- channel_count,
- PJMEDIA_SOUND_BUFFER_COUNT * ptime,
- 0, &snd_port->delay_buf);
- PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
- } while (0);
-#endif
+ PJ_UNUSED_ARG(options);
- *p_port = snd_port;
+ status = pjmedia_aud_dev_default_param(rec_id, &param);
+ if (status != PJ_SUCCESS)
+ return status;
+ param.dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param.rec_id = rec_id;
+ param.play_id = play_id;
+ param.clock_rate = clock_rate;
+ param.channel_count = channel_count;
+ param.samples_per_frame = samples_per_frame;
+ param.bits_per_sample = bits_per_sample;
- /* Start sound device immediately.
- * If there's no port connected, the sound callback will return
- * empty signal.
- */
- return start_sound_device( pool, snd_port );
-
+ return pjmedia_snd_port_create2(pool, &param, p_port);
}
/*
@@ -426,28 +354,23 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create_rec( pj_pool_t *pool,
unsigned options,
pjmedia_snd_port **p_port)
{
- pjmedia_snd_port *snd_port;
-
- PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL);
+ pjmedia_aud_param param;
+ pj_status_t status;
- snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port);
- PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM);
+ PJ_UNUSED_ARG(options);
- snd_port->rec_id = dev_id;
- snd_port->dir = PJMEDIA_DIR_CAPTURE;
- snd_port->options = options | DEFAULT_OPTIONS;
- snd_port->clock_rate = clock_rate;
- snd_port->channel_count = channel_count;
- snd_port->samples_per_frame = samples_per_frame;
- snd_port->bits_per_sample = bits_per_sample;
+ status = pjmedia_aud_dev_default_param(dev_id, &param);
+ if (status != PJ_SUCCESS)
+ return status;
- *p_port = snd_port;
+ param.dir = PJMEDIA_DIR_CAPTURE;
+ param.rec_id = dev_id;
+ param.clock_rate = clock_rate;
+ param.channel_count = channel_count;
+ param.samples_per_frame = samples_per_frame;
+ param.bits_per_sample = bits_per_sample;
- /* Start sound device immediately.
- * If there's no port connected, the sound callback will return
- * empty signal.
- */
- return start_sound_device( pool, snd_port );
+ return pjmedia_snd_port_create2(pool, &param, p_port);
}
@@ -463,28 +386,63 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create_player( pj_pool_t *pool,
unsigned options,
pjmedia_snd_port **p_port)
{
+ pjmedia_aud_param param;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(options);
+
+ status = pjmedia_aud_dev_default_param(dev_id, &param);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ param.dir = PJMEDIA_DIR_PLAYBACK;
+ param.play_id = dev_id;
+ param.clock_rate = clock_rate;
+ param.channel_count = channel_count;
+ param.samples_per_frame = samples_per_frame;
+ param.bits_per_sample = bits_per_sample;
+
+ return pjmedia_snd_port_create2(pool, &param, p_port);
+}
+
+
+/*
+ * Create sound port.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool,
+ const pjmedia_aud_param *prm,
+ pjmedia_snd_port **p_port)
+{
pjmedia_snd_port *snd_port;
+ pj_status_t status;
- PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL);
+ PJ_ASSERT_RETURN(pool && prm && p_port, PJ_EINVAL);
snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port);
PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM);
- snd_port->play_id = dev_id;
- snd_port->dir = PJMEDIA_DIR_PLAYBACK;
- snd_port->options = options | DEFAULT_OPTIONS;
- snd_port->clock_rate = clock_rate;
- snd_port->channel_count = channel_count;
- snd_port->samples_per_frame = samples_per_frame;
- snd_port->bits_per_sample = bits_per_sample;
-
- *p_port = snd_port;
-
+ snd_port->dir = prm->dir;
+ snd_port->rec_id = prm->rec_id;
+ snd_port->play_id = prm->play_id;
+ snd_port->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ snd_port->clock_rate = prm->clock_rate;
+ snd_port->channel_count = prm->channel_count;
+ snd_port->samples_per_frame = prm->samples_per_frame;
+ snd_port->bits_per_sample = prm->bits_per_sample;
+ pj_memcpy(&snd_port->aud_param, prm, sizeof(*prm));
+
/* Start sound device immediately.
* If there's no port connected, the sound callback will return
* empty signal.
*/
- return start_sound_device( pool, snd_port );
+ status = start_sound_device( pool, snd_port );
+ if (status != PJ_SUCCESS) {
+ pjmedia_snd_port_destroy(snd_port);
+ return status;
+ }
+
+ *p_port = snd_port;
+ return PJ_SUCCESS;
}
@@ -502,23 +460,23 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_destroy(pjmedia_snd_port *snd_port)
/*
* Retrieve the sound stream associated by this sound device port.
*/
-PJ_DEF(pjmedia_snd_stream*) pjmedia_snd_port_get_snd_stream(
+PJ_DEF(pjmedia_aud_stream*) pjmedia_snd_port_get_snd_stream(
pjmedia_snd_port *snd_port)
{
PJ_ASSERT_RETURN(snd_port, NULL);
- return snd_port->snd_stream;
+ return snd_port->aud_stream;
}
/*
- * Enable AEC
+ * Change EC settings.
*/
PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port,
pj_pool_t *pool,
unsigned tail_ms,
unsigned options)
{
- pjmedia_snd_stream_info si;
+ pjmedia_aud_param prm;
pj_status_t status;
/* Sound must be opened in full-duplex mode */
@@ -526,43 +484,101 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port,
snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK,
PJ_EINVALIDOP);
- /* Sound port must have 16bits per sample */
- PJ_ASSERT_RETURN(snd_port->bits_per_sample == 16,
- PJ_EINVALIDOP);
+ /* Determine whether we use device or software EC */
+ if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) {
+ /* We use device EC */
+ pj_bool_t ec_enabled;
- /* Destroy AEC */
- if (snd_port->ec_state) {
- pjmedia_echo_destroy(snd_port->ec_state);
- snd_port->ec_state = NULL;
- }
+ /* Query EC status */
+ status = pjmedia_aud_stream_get_cap(snd_port->aud_stream,
+ PJMEDIA_AUD_DEV_CAP_EC,
+ &ec_enabled);
+ if (status != PJ_SUCCESS)
+ return status;
- snd_port->aec_tail_len = tail_ms;
+ if (tail_ms != 0) {
+ /* Change EC setting */
- if (tail_ms != 0) {
- unsigned delay_ms;
+ if (!ec_enabled) {
+ /* Enable EC first */
+ pj_bool_t value = PJ_TRUE;
+ status = pjmedia_aud_stream_set_cap(snd_port->aud_stream,
+ PJMEDIA_AUD_DEV_CAP_EC,
+ &value);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
- status = pjmedia_snd_stream_get_info(snd_port->snd_stream, &si);
- if (status != PJ_SUCCESS)
- si.rec_latency = si.play_latency = 0;
-
- //No need to add input latency in the latency calculation,
- //since actual input latency should be zero.
- //delay_ms = (si.rec_latency + si.play_latency) * 1000 /
- // snd_port->clock_rate;
- delay_ms = si.play_latency * 1000 / snd_port->clock_rate;
- status = pjmedia_echo_create2(pool, snd_port->clock_rate,
- snd_port->channel_count,
- snd_port->samples_per_frame,
- tail_ms, delay_ms,
- options, &snd_port->ec_state);
+ if ((snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) {
+ /* Device does not support setting EC tail */
+ return PJMEDIA_EAUD_INVCAP;
+ }
+
+ return pjmedia_aud_stream_set_cap(snd_port->aud_stream,
+ PJMEDIA_AUD_DEV_CAP_EC_TAIL,
+ &tail_ms);
+
+ } else if (ec_enabled) {
+ /* Disable EC */
+ pj_bool_t value = PJ_FALSE;
+ return pjmedia_aud_stream_set_cap(snd_port->aud_stream,
+ PJMEDIA_AUD_DEV_CAP_EC,
+ &value);
+ } else {
+ /* Request to disable EC but EC has been disabled */
+ /* Do nothing */
+ return PJ_SUCCESS;
+ }
+
+ } else {
+ /* We use software EC */
+
+ /* Check if there is change in parameters */
+ if (tail_ms==snd_port->ec_tail_len && options==snd_port->ec_options) {
+ PJ_LOG(5,(THIS_FILE, "pjmedia_snd_port_set_ec() ignored, no "
+ "change in settings"));
+ return PJ_SUCCESS;
+ }
+
+ status = pjmedia_aud_stream_get_param(snd_port->aud_stream, &prm);
if (status != PJ_SUCCESS)
+ return status;
+
+ /* Audio stream must be in PCM format */
+ PJ_ASSERT_RETURN(prm.ext_fmt.id == PJMEDIA_FORMAT_PCM,
+ PJ_EINVALIDOP);
+
+ /* Destroy AEC */
+ if (snd_port->ec_state) {
+ pjmedia_echo_destroy(snd_port->ec_state);
snd_port->ec_state = NULL;
- else
- snd_port->ec_suspended = PJ_FALSE;
- } else {
- PJ_LOG(4,(THIS_FILE, "Echo canceller is now disabled in the "
- "sound port"));
- status = PJ_SUCCESS;
+ }
+
+ if (tail_ms != 0) {
+ unsigned delay_ms;
+
+ //No need to add input latency in the latency calculation,
+ //since actual input latency should be zero.
+ //delay_ms = (si.rec_latency + si.play_latency) * 1000 /
+ // snd_port->clock_rate;
+ delay_ms = prm.output_latency_ms;
+ status = pjmedia_echo_create2(pool, snd_port->clock_rate,
+ snd_port->channel_count,
+ snd_port->samples_per_frame,
+ tail_ms, delay_ms,
+ options, &snd_port->ec_state);
+ if (status != PJ_SUCCESS)
+ snd_port->ec_state = NULL;
+ else
+ snd_port->ec_suspended = PJ_FALSE;
+ } else {
+ PJ_LOG(4,(THIS_FILE, "Echo canceller is now disabled in the "
+ "sound port"));
+ status = PJ_SUCCESS;
+ }
+
+ snd_port->ec_options = options;
+ snd_port->ec_tail_len = tail_ms;
}
return status;
@@ -574,12 +590,42 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_get_ec_tail( pjmedia_snd_port *snd_port,
unsigned *p_length)
{
PJ_ASSERT_RETURN(snd_port && p_length, PJ_EINVAL);
- *p_length = snd_port->ec_state ? snd_port->aec_tail_len : 0;
+
+ /* Determine whether we use device or software EC */
+ if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) {
+ /* We use device EC */
+ pj_bool_t ec_enabled;
+ pj_status_t status;
+
+ /* Query EC status */
+ status = pjmedia_aud_stream_get_cap(snd_port->aud_stream,
+ PJMEDIA_AUD_DEV_CAP_EC,
+ &ec_enabled);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (!ec_enabled) {
+ *p_length = 0;
+ } else if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL) {
+ /* Get device EC tail */
+ status = pjmedia_aud_stream_get_cap(snd_port->aud_stream,
+ PJMEDIA_AUD_DEV_CAP_EC_TAIL,
+ p_length);
+ if (status != PJ_SUCCESS)
+ return status;
+ } else {
+ /* Just use default */
+ *p_length = AEC_TAIL;
+ }
+
+ } else {
+ /* We use software EC */
+ *p_length = snd_port->ec_state ? snd_port->ec_tail_len : 0;
+ }
return PJ_SUCCESS;
}
-
/*
* Connect a port.
*/
diff --git a/pjmedia/src/pjmedia/stream.c b/pjmedia/src/pjmedia/stream.c
index f1d01c27..1e289e98 100644
--- a/pjmedia/src/pjmedia/stream.c
+++ b/pjmedia/src/pjmedia/stream.c
@@ -408,6 +408,116 @@ static pj_status_t get_frame( pjmedia_port *port, pjmedia_frame *frame)
}
+/* The other version of get_frame callback used when stream port format
+ * is non linear PCM.
+ */
+static pj_status_t get_frame_ext( pjmedia_port *port, pjmedia_frame *frame)
+{
+ pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
+ pjmedia_channel *channel = stream->dec;
+ pjmedia_frame_ext *f = (pjmedia_frame_ext*)frame;
+ unsigned samples_per_frame, samples_required;
+ pj_status_t status;
+
+ /* Return no frame if channel is paused */
+ if (channel->paused) {
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ return PJ_SUCCESS;
+ }
+
+ /* Repeat get frame from the jitter buffer and decode the frame
+ * until we have enough frames according to codec's ptime.
+ */
+
+ samples_required = stream->port.info.samples_per_frame;
+ samples_per_frame = stream->codec_param.info.frm_ptime *
+ stream->codec_param.info.clock_rate *
+ stream->codec_param.info.channel_cnt /
+ 1000;
+
+ pj_bzero(f, sizeof(pjmedia_frame_ext));
+ f->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+
+ while (f->samples_cnt < samples_required) {
+ char frame_type;
+ pj_size_t frame_size;
+ pj_uint32_t bit_info;
+
+ /* Lock jitter buffer mutex first */
+ pj_mutex_lock( stream->jb_mutex );
+
+ /* Get frame from jitter buffer. */
+ pjmedia_jbuf_get_frame2(stream->jb, channel->out_pkt, &frame_size,
+ &frame_type, &bit_info);
+
+ /* Unlock jitter buffer mutex. */
+ pj_mutex_unlock( stream->jb_mutex );
+
+ if (frame_type == PJMEDIA_JB_NORMAL_FRAME) {
+ /* Got "NORMAL" frame from jitter buffer */
+ pjmedia_frame frame_in;
+
+ /* Decode */
+ frame_in.buf = channel->out_pkt;
+ frame_in.size = frame_size;
+ frame_in.bit_info = bit_info;
+ frame_in.type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+ status = stream->codec->op->decode( stream->codec, &frame_in,
+ 0, frame);
+ if (status != PJ_SUCCESS) {
+ LOGERR_((port->info.name.ptr, "codec decode() error",
+ status));
+ pjmedia_frame_ext_append_subframe(f, NULL, 0,
+ (pj_uint16_t)samples_per_frame);
+ }
+ } else {
+ status = (*stream->codec->op->recover)(stream->codec,
+ 0, frame);
+ if (status != PJ_SUCCESS) {
+ pjmedia_frame_ext_append_subframe(f, NULL, 0,
+ (pj_uint16_t)samples_per_frame);
+ }
+
+ if (frame_type == PJMEDIA_JB_MISSING_FRAME) {
+ PJ_LOG(5,(stream->port.info.name.ptr, "Frame lost!"));
+ } else if (frame_type == PJMEDIA_JB_ZERO_EMPTY_FRAME) {
+ /* Jitter buffer is empty. Check if this is the first "empty"
+ * state.
+ */
+ if (frame_type != stream->jb_last_frm) {
+ pjmedia_jb_state jb_state;
+
+ /* Report the state of jitter buffer */
+ pjmedia_jbuf_get_state(stream->jb, &jb_state);
+ PJ_LOG(5,(stream->port.info.name.ptr,
+ "Jitter buffer empty (prefetch=%d)",
+ jb_state.prefetch));
+ }
+ } else {
+ pjmedia_jb_state jb_state;
+
+ /* It can only be PJMEDIA_JB_ZERO_PREFETCH frame */
+ pj_assert(frame_type == PJMEDIA_JB_ZERO_PREFETCH_FRAME);
+
+ /* Get the state of jitter buffer */
+ pjmedia_jbuf_get_state(stream->jb, &jb_state);
+
+ if (stream->jb_last_frm != frame_type) {
+ PJ_LOG(5,(stream->port.info.name.ptr,
+ "Jitter buffer is bufferring (prefetch=%d)",
+ jb_state.prefetch));
+ }
+ }
+ }
+
+ stream->jb_last_frm = frame_type;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
/*
* Transmit DTMF
*/
@@ -686,6 +796,9 @@ static pj_status_t put_frame_imp( pjmedia_port *port,
/* Number of samples in the frame */
if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO)
ts_len = (frame->size >> 1) / stream->codec_param.info.channel_cnt;
+ else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED)
+ ts_len = stream->port.info.samples_per_frame /
+ stream->port.info.channel_count;
else
ts_len = 0;
@@ -752,6 +865,7 @@ static pj_status_t put_frame_imp( pjmedia_port *port,
*/
} else if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO &&
frame->buf == NULL &&
+ stream->port.info.format.id == PJMEDIA_FORMAT_L16 &&
(stream->dir & PJMEDIA_DIR_ENCODING) &&
stream->codec_param.info.frm_ptime *
stream->codec_param.info.channel_cnt *
@@ -1483,9 +1597,18 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt,
pj_strdup(pool, &stream->port.info.encoding_name, &info->fmt.encoding_name);
stream->port.info.clock_rate = info->fmt.clock_rate;
stream->port.info.channel_count = info->fmt.channel_cnt;
+ stream->port.info.format.id = info->param->info.fmt_id;
stream->port.port_data.pdata = stream;
- stream->port.put_frame = &put_frame;
- stream->port.get_frame = &get_frame;
+ if (stream->port.info.format.id == PJMEDIA_FORMAT_L16) {
+ stream->port.put_frame = &put_frame;
+ stream->port.get_frame = &get_frame;
+ } else {
+ stream->port.info.format.bitrate = info->param->info.avg_bps;
+ stream->port.info.format.vad = (info->param->setting.vad != 0);
+
+ stream->port.put_frame = &put_frame;
+ stream->port.get_frame = &get_frame_ext;
+ }
/* Init stream: */
diff --git a/pjmedia/src/pjmedia/symbian_sound.cpp b/pjmedia/src/pjmedia/symbian_sound.cpp
deleted file mode 100644
index b7c92f9f..00000000
--- a/pjmedia/src/pjmedia/symbian_sound.cpp
+++ /dev/null
@@ -1,944 +0,0 @@
-/* $Id$ */
-/*
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
- * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
- *
- * 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 2 of the License, or
- * (at your option) 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-#include <pjmedia/sound.h>
-#include <pjmedia/errno.h>
-#include <pj/assert.h>
-#include <pj/log.h>
-#include <pj/os.h>
-
-
-/*
- * This file provides sound implementation for Symbian Audio Streaming
- * device. Application using this sound abstraction must link with:
- * - mediaclientaudiostream.lib, and
- * - mediaclientaudioinputstream.lib
- */
-#include <mda/common/audio.h>
-#include <mdaaudiooutputstream.h>
-#include <mdaaudioinputstream.h>
-
-
-
-//////////////////////////////////////////////////////////////////////////////
-//
-
-#define THIS_FILE "symbian_sound.cpp"
-#define BYTES_PER_SAMPLE 2
-#define POOL_NAME "SymbianSound"
-#define POOL_SIZE 512
-#define POOL_INC 512
-
-static pjmedia_snd_dev_info symbian_snd_dev_info =
-{
- "Symbian Sound Device",
- 1,
- 1,
- 8000
-};
-
-class CPjAudioInputEngine;
-class CPjAudioOutputEngine;
-
-/*
- * PJMEDIA Sound Stream instance
- */
-struct pjmedia_snd_stream
-{
- // Pool
- pj_pool_t *pool;
-
- // Common settings.
- pjmedia_dir dir;
- unsigned clock_rate;
- unsigned channel_count;
- unsigned samples_per_frame;
-
- // Input stream
- CPjAudioInputEngine *inEngine;
-
- // Output stream
- CPjAudioOutputEngine *outEngine;
-};
-
-static pj_pool_factory *snd_pool_factory;
-
-
-/*
- * Convert clock rate to Symbian's TMdaAudioDataSettings capability.
- */
-static TInt get_clock_rate_cap(unsigned clock_rate)
-{
- switch (clock_rate) {
- case 8000: return TMdaAudioDataSettings::ESampleRate8000Hz;
- case 11025: return TMdaAudioDataSettings::ESampleRate11025Hz;
- case 12000: return TMdaAudioDataSettings::ESampleRate12000Hz;
- case 16000: return TMdaAudioDataSettings::ESampleRate16000Hz;
- case 22050: return TMdaAudioDataSettings::ESampleRate22050Hz;
- case 24000: return TMdaAudioDataSettings::ESampleRate24000Hz;
- case 32000: return TMdaAudioDataSettings::ESampleRate32000Hz;
- case 44100: return TMdaAudioDataSettings::ESampleRate44100Hz;
- case 48000: return TMdaAudioDataSettings::ESampleRate48000Hz;
- case 64000: return TMdaAudioDataSettings::ESampleRate64000Hz;
- case 96000: return TMdaAudioDataSettings::ESampleRate96000Hz;
- default:
- return 0;
- }
-}
-
-
-/*
- * Convert number of channels into Symbian's TMdaAudioDataSettings capability.
- */
-static TInt get_channel_cap(unsigned channel_count)
-{
- switch (channel_count) {
- case 1: return TMdaAudioDataSettings::EChannelsMono;
- case 2: return TMdaAudioDataSettings::EChannelsStereo;
- default:
- return 0;
- }
-}
-
-
-/*
- * Utility: print sound device error
- */
-static void snd_perror(const char *title, TInt rc)
-{
- PJ_LOG(1,(THIS_FILE, "%s: error code %d", title, rc));
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//
-
-/*
- * Implementation: Symbian Input Stream.
- */
-class CPjAudioInputEngine : public CBase, MMdaAudioInputStreamCallback
-{
-public:
- enum State
- {
- STATE_INACTIVE,
- STATE_ACTIVE,
- };
-
- ~CPjAudioInputEngine();
-
- static CPjAudioInputEngine *NewL(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_rec_cb rec_cb,
- void *user_data);
-
- static CPjAudioInputEngine *NewLC(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_rec_cb rec_cb,
- void *user_data);
-
- pj_status_t StartRecord();
- void Stop();
-
-private:
- State state_;
- pjmedia_snd_stream *parentStrm_;
- pjmedia_snd_rec_cb recCb_;
- void *userData_;
- CMdaAudioInputStream *iInputStream_;
- HBufC8 *iStreamBuffer_;
- TPtr8 iFramePtr_;
- TInt lastError_;
- pj_uint32_t timeStamp_;
-
- // cache variable
- // to avoid calculating frame length repeatedly
- TInt frameLen_;
-
- // in some SymbianOS (e.g: OSv9.1), sometimes recorded size != requested framesize
- // so let's provide a buffer to make sure the rec callback returns framesize as requested.
- TUint8 *frameRecBuf_;
- TInt frameRecBufLen_;
-
- CPjAudioInputEngine(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_rec_cb rec_cb,
- void *user_data);
- void ConstructL();
- TPtr8 & GetFrame();
-
-public:
- virtual void MaiscOpenComplete(TInt aError);
- virtual void MaiscBufferCopied(TInt aError, const TDesC8 &aBuffer);
- virtual void MaiscRecordComplete(TInt aError);
-
-};
-
-
-CPjAudioInputEngine::CPjAudioInputEngine(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_rec_cb rec_cb,
- void *user_data)
- : state_(STATE_INACTIVE), parentStrm_(parent_strm),
- recCb_(rec_cb), userData_(user_data),
- iInputStream_(NULL), iStreamBuffer_(NULL), iFramePtr_(0, 0),
- lastError_(KErrNone), timeStamp_(0),
- frameLen_(parent_strm->samples_per_frame * parent_strm->channel_count * BYTES_PER_SAMPLE),
- frameRecBuf_(NULL), frameRecBufLen_(0)
-{
-}
-
-CPjAudioInputEngine::~CPjAudioInputEngine()
-{
- Stop();
-
- delete iStreamBuffer_;
- iStreamBuffer_ = NULL;
-
- delete [] frameRecBuf_;
- frameRecBuf_ = NULL;
- frameRecBufLen_ = 0;
-}
-
-void CPjAudioInputEngine::ConstructL()
-{
- iStreamBuffer_ = HBufC8::NewL(frameLen_);
- CleanupStack::PushL(iStreamBuffer_);
-
- frameRecBuf_ = new TUint8[frameLen_*2];
- CleanupStack::PushL(frameRecBuf_);
-}
-
-CPjAudioInputEngine *CPjAudioInputEngine::NewLC(pjmedia_snd_stream *parent,
- pjmedia_snd_rec_cb rec_cb,
- void *user_data)
-{
- CPjAudioInputEngine* self = new (ELeave) CPjAudioInputEngine(parent,
- rec_cb,
- user_data);
- CleanupStack::PushL(self);
- self->ConstructL();
- return self;
-}
-
-CPjAudioInputEngine *CPjAudioInputEngine::NewL(pjmedia_snd_stream *parent,
- pjmedia_snd_rec_cb rec_cb,
- void *user_data)
-{
- CPjAudioInputEngine *self = NewLC(parent, rec_cb, user_data);
- CleanupStack::Pop(self->frameRecBuf_);
- CleanupStack::Pop(self->iStreamBuffer_);
- CleanupStack::Pop(self);
- return self;
-}
-
-
-pj_status_t CPjAudioInputEngine::StartRecord()
-{
-
- // Ignore command if recording is in progress.
- if (state_ == STATE_ACTIVE)
- return PJ_SUCCESS;
-
- // According to Nokia's AudioStream example, some 2nd Edition, FP2 devices
- // (such as Nokia 6630) require the stream to be reconstructed each time
- // before calling Open() - otherwise the callback never gets called.
- // For uniform behavior, lets just delete/re-create the stream for all
- // devices.
-
- // Destroy existing stream.
- if (iInputStream_) delete iInputStream_;
- iInputStream_ = NULL;
-
- // Create the stream.
- TRAPD(err, iInputStream_ = CMdaAudioInputStream::NewL(*this));
- if (err != KErrNone)
- return PJ_RETURN_OS_ERROR(err);
-
- // Initialize settings.
- TMdaAudioDataSettings iStreamSettings;
- iStreamSettings.iChannels = get_channel_cap(parentStrm_->channel_count);
- iStreamSettings.iSampleRate = get_clock_rate_cap(parentStrm_->clock_rate);
-
- pj_assert(iStreamSettings.iChannels != 0 &&
- iStreamSettings.iSampleRate != 0);
-
- PJ_LOG(4,(THIS_FILE, "Opening sound device for capture, "
- "clock rate=%d, channel count=%d..",
- parentStrm_->clock_rate,
- parentStrm_->channel_count));
-
- // Open stream.
- lastError_ = KRequestPending;
- iInputStream_->Open(&iStreamSettings);
-
- // Success
- PJ_LOG(4,(THIS_FILE, "Sound capture started."));
- return PJ_SUCCESS;
-}
-
-
-void CPjAudioInputEngine::Stop()
-{
- // If capture is in progress, stop it.
- if (iInputStream_ && state_ == STATE_ACTIVE) {
- lastError_ = KRequestPending;
- iInputStream_->Stop();
-
- // Wait until it's actually stopped
- while (lastError_ == KRequestPending)
- pj_symbianos_poll(-1, 100);
- }
-
- if (iInputStream_) {
- delete iInputStream_;
- iInputStream_ = NULL;
- }
-
- state_ = STATE_INACTIVE;
-}
-
-
-TPtr8 & CPjAudioInputEngine::GetFrame()
-{
- //iStreamBuffer_->Des().FillZ(frameLen_);
- iFramePtr_.Set((TUint8*)(iStreamBuffer_->Ptr()), frameLen_, frameLen_);
- return iFramePtr_;
-}
-
-void CPjAudioInputEngine::MaiscOpenComplete(TInt aError)
-{
- lastError_ = aError;
- if (aError != KErrNone) {
- snd_perror("Error in MaiscOpenComplete()", aError);
- return;
- }
-
- // set stream priority to normal and time sensitive
- iInputStream_->SetPriority(EPriorityNormal,
- EMdaPriorityPreferenceTime);
-
- // Read the first frame.
- TPtr8 & frm = GetFrame();
- TRAPD(err2, iInputStream_->ReadL(frm));
- if (err2) {
- PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()"));
- }
-}
-
-void CPjAudioInputEngine::MaiscBufferCopied(TInt aError,
- const TDesC8 &aBuffer)
-{
- lastError_ = aError;
- if (aError != KErrNone) {
- snd_perror("Error in MaiscBufferCopied()", aError);
- return;
- }
-
- if (frameRecBufLen_ || aBuffer.Size() < frameLen_) {
- pj_memcpy(frameRecBuf_ + frameRecBufLen_, (void*) aBuffer.Ptr(), aBuffer.Size());
- frameRecBufLen_ += aBuffer.Size();
- }
-
- if (frameRecBufLen_) {
- while (frameRecBufLen_ >= frameLen_) {
- // Call the callback.
- recCb_(userData_, timeStamp_, frameRecBuf_, frameLen_);
- // Increment timestamp.
- timeStamp_ += parentStrm_->samples_per_frame;
-
- frameRecBufLen_ -= frameLen_;
- pj_memmove(frameRecBuf_, frameRecBuf_+frameLen_, frameRecBufLen_);
- }
- } else {
- // Call the callback.
- recCb_(userData_, timeStamp_, (void*) aBuffer.Ptr(), aBuffer.Size());
- // Increment timestamp.
- timeStamp_ += parentStrm_->samples_per_frame;
- }
-
- // Record next frame
- TPtr8 & frm = GetFrame();
- TRAPD(err2, iInputStream_->ReadL(frm));
- if (err2) {
- PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()"));
- }
-}
-
-
-void CPjAudioInputEngine::MaiscRecordComplete(TInt aError)
-{
- lastError_ = aError;
- state_ = STATE_INACTIVE;
- if (aError != KErrNone) {
- snd_perror("Error in MaiscRecordComplete()", aError);
- }
-}
-
-
-
-//////////////////////////////////////////////////////////////////////////////
-//
-
-/*
- * Implementation: Symbian Output Stream.
- */
-
-class CPjAudioOutputEngine : public CBase, MMdaAudioOutputStreamCallback
-{
-public:
- enum State
- {
- STATE_INACTIVE,
- STATE_ACTIVE,
- };
-
- ~CPjAudioOutputEngine();
-
- static CPjAudioOutputEngine *NewL(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_play_cb play_cb,
- void *user_data);
-
- static CPjAudioOutputEngine *NewLC(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_play_cb rec_cb,
- void *user_data);
-
- pj_status_t StartPlay();
- void Stop();
-
-private:
- State state_;
- pjmedia_snd_stream *parentStrm_;
- pjmedia_snd_play_cb playCb_;
- void *userData_;
- CMdaAudioOutputStream *iOutputStream_;
- TUint8 *frameBuf_;
- unsigned frameBufSize_;
- TPtrC8 frame_;
- TInt lastError_;
- unsigned timestamp_;
-
- CPjAudioOutputEngine(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_play_cb play_cb,
- void *user_data);
- void ConstructL();
-
- virtual void MaoscOpenComplete(TInt aError);
- virtual void MaoscBufferCopied(TInt aError, const TDesC8& aBuffer);
- virtual void MaoscPlayComplete(TInt aError);
-};
-
-
-CPjAudioOutputEngine::CPjAudioOutputEngine(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_play_cb play_cb,
- void *user_data)
-: state_(STATE_INACTIVE), parentStrm_(parent_strm), playCb_(play_cb),
- userData_(user_data), iOutputStream_(NULL), frameBuf_(NULL),
- lastError_(KErrNone), timestamp_(0)
-{
-}
-
-
-void CPjAudioOutputEngine::ConstructL()
-{
- frameBufSize_ = parentStrm_->samples_per_frame *
- parentStrm_->channel_count *
- BYTES_PER_SAMPLE;
- frameBuf_ = new TUint8[frameBufSize_];
-}
-
-CPjAudioOutputEngine::~CPjAudioOutputEngine()
-{
- Stop();
- delete [] frameBuf_;
-}
-
-CPjAudioOutputEngine *
-CPjAudioOutputEngine::NewLC(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_play_cb rec_cb,
- void *user_data)
-{
- CPjAudioOutputEngine* self = new (ELeave) CPjAudioOutputEngine(parent_strm,
- rec_cb,
- user_data);
- CleanupStack::PushL(self);
- self->ConstructL();
- return self;
-}
-
-CPjAudioOutputEngine *
-CPjAudioOutputEngine::NewL(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_play_cb play_cb,
- void *user_data)
-{
- CPjAudioOutputEngine *self = NewLC(parent_strm, play_cb, user_data);
- CleanupStack::Pop(self);
- return self;
-}
-
-pj_status_t CPjAudioOutputEngine::StartPlay()
-{
- // Ignore command if playing is in progress.
- if (state_ == STATE_ACTIVE)
- return PJ_SUCCESS;
-
- // Destroy existing stream.
- if (iOutputStream_) delete iOutputStream_;
- iOutputStream_ = NULL;
-
- // Create the stream
- TRAPD(err, iOutputStream_ = CMdaAudioOutputStream::NewL(*this));
- if (err != KErrNone)
- return PJ_RETURN_OS_ERROR(err);
-
- // Initialize settings.
- TMdaAudioDataSettings iStreamSettings;
- iStreamSettings.iChannels = get_channel_cap(parentStrm_->channel_count);
- iStreamSettings.iSampleRate = get_clock_rate_cap(parentStrm_->clock_rate);
-
- pj_assert(iStreamSettings.iChannels != 0 &&
- iStreamSettings.iSampleRate != 0);
-
- PJ_LOG(4,(THIS_FILE, "Opening sound device for playback, "
- "clock rate=%d, channel count=%d..",
- parentStrm_->clock_rate,
- parentStrm_->channel_count));
-
- // Open stream.
- lastError_ = KRequestPending;
- iOutputStream_->Open(&iStreamSettings);
-
- // Success
- PJ_LOG(4,(THIS_FILE, "Sound playback started"));
- return PJ_SUCCESS;
-
-}
-
-void CPjAudioOutputEngine::Stop()
-{
- // Stop stream if it's playing
- if (iOutputStream_ && state_ != STATE_INACTIVE) {
- lastError_ = KRequestPending;
- iOutputStream_->Stop();
-
- // Wait until it's actually stopped
- while (lastError_ == KRequestPending)
- pj_symbianos_poll(-1, 100);
- }
-
- if (iOutputStream_) {
- delete iOutputStream_;
- iOutputStream_ = NULL;
- }
-
- state_ = STATE_INACTIVE;
-}
-
-void CPjAudioOutputEngine::MaoscOpenComplete(TInt aError)
-{
- lastError_ = aError;
-
- if (aError==KErrNone) {
- // output stream opened succesfully, set status to Active
- state_ = STATE_ACTIVE;
-
- // set stream properties, 16bit 8KHz mono
- TMdaAudioDataSettings iSettings;
- iSettings.iChannels = get_channel_cap(parentStrm_->channel_count);
- iSettings.iSampleRate = get_clock_rate_cap(parentStrm_->clock_rate);
-
- iOutputStream_->SetAudioPropertiesL(iSettings.iSampleRate,
- iSettings.iChannels);
-
- // set volume to 1/2th of stream max volume
- iOutputStream_->SetVolume(iOutputStream_->MaxVolume()/2);
-
- // set stream priority to normal and time sensitive
- iOutputStream_->SetPriority(EPriorityNormal,
- EMdaPriorityPreferenceTime);
-
- // Call callback to retrieve frame from upstream.
- pj_status_t status;
- status = playCb_(this->userData_, timestamp_, frameBuf_,
- frameBufSize_);
- if (status != PJ_SUCCESS) {
- this->Stop();
- return;
- }
-
- // Increment timestamp.
- timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE);
-
- // issue WriteL() to write the first audio data block,
- // subsequent calls to WriteL() will be issued in
- // MMdaAudioOutputStreamCallback::MaoscBufferCopied()
- // until whole data buffer is written.
- frame_.Set(frameBuf_, frameBufSize_);
- iOutputStream_->WriteL(frame_);
- } else {
- snd_perror("Error in MaoscOpenComplete()", aError);
- }
-}
-
-void CPjAudioOutputEngine::MaoscBufferCopied(TInt aError,
- const TDesC8& aBuffer)
-{
- PJ_UNUSED_ARG(aBuffer);
-
- if (aError==KErrNone) {
- // Buffer successfully written, feed another one.
-
- // Call callback to retrieve frame from upstream.
- pj_status_t status;
- status = playCb_(this->userData_, timestamp_, frameBuf_,
- frameBufSize_);
- if (status != PJ_SUCCESS) {
- this->Stop();
- return;
- }
-
- // Increment timestamp.
- timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE);
-
- // Write to playback stream.
- frame_.Set(frameBuf_, frameBufSize_);
- iOutputStream_->WriteL(frame_);
-
- } else if (aError==KErrAbort) {
- // playing was aborted, due to call to CMdaAudioOutputStream::Stop()
- state_ = STATE_INACTIVE;
- } else {
- // error writing data to output
- lastError_ = aError;
- state_ = STATE_INACTIVE;
- snd_perror("Error in MaoscBufferCopied()", aError);
- }
-}
-
-void CPjAudioOutputEngine::MaoscPlayComplete(TInt aError)
-{
- lastError_ = aError;
- state_ = STATE_INACTIVE;
- if (aError != KErrNone) {
- snd_perror("Error in MaoscPlayComplete()", aError);
- }
-}
-
-
-//////////////////////////////////////////////////////////////////////////////
-//
-
-
-/*
- * Initialize sound subsystem.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
-{
- snd_pool_factory = factory;
- return PJ_SUCCESS;
-}
-
-/*
- * Get device count.
- */
-PJ_DEF(int) pjmedia_snd_get_dev_count(void)
-{
- /* Always return 1 */
- return 1;
-}
-
-/*
- * Get device info.
- */
-PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
-{
- /* Always return the default sound device */
- if (index == (unsigned)-1)
- index = 0;
-
- PJ_ASSERT_RETURN(index==0, NULL);
- return &symbian_snd_dev_info;
-}
-
-
-
-/*
- * Open sound recorder stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_rec_cb rec_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
-{
- pj_pool_t *pool;
- pjmedia_snd_stream *strm;
-
- if (index==-1) index = 0;
-
- PJ_ASSERT_RETURN(index == 0, PJ_EINVAL);
- PJ_ASSERT_RETURN(clock_rate && channel_count && samples_per_frame &&
- bits_per_sample && rec_cb && p_snd_strm, PJ_EINVAL);
-
- pool = pj_pool_create(snd_pool_factory, POOL_NAME, POOL_SIZE, POOL_INC,
- NULL);
- if (!pool)
- return PJ_ENOMEM;
-
- strm = (pjmedia_snd_stream*) pj_pool_zalloc(pool,
- sizeof(pjmedia_snd_stream));
- strm->dir = PJMEDIA_DIR_CAPTURE;
- strm->pool = pool;
- strm->clock_rate = clock_rate;
- strm->channel_count = channel_count;
- strm->samples_per_frame = samples_per_frame;
-
- PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
- PJ_ASSERT_RETURN(get_clock_rate_cap(clock_rate) != 0, PJ_EINVAL);
- PJ_ASSERT_RETURN(get_channel_cap(channel_count) != 0, PJ_EINVAL);
-
- // Create the input stream.
- TRAPD(err, strm->inEngine = CPjAudioInputEngine::NewL(strm, rec_cb,
- user_data));
- if (err != KErrNone) {
- pj_pool_release(pool);
- return PJ_RETURN_OS_ERROR(err);
- }
-
- // Done.
- *p_snd_strm = strm;
- return PJ_SUCCESS;
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_play_cb play_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm )
-{
- pj_pool_t *pool;
- pjmedia_snd_stream *strm;
-
- if (index == -1) index = 0;
-
- PJ_ASSERT_RETURN(index == 0, PJ_EINVAL);
- PJ_ASSERT_RETURN(clock_rate && channel_count && samples_per_frame &&
- bits_per_sample && play_cb && p_snd_strm, PJ_EINVAL);
-
- pool = pj_pool_create(snd_pool_factory, POOL_NAME, POOL_SIZE, POOL_INC,
- NULL);
- if (!pool)
- return PJ_ENOMEM;
-
- strm = (pjmedia_snd_stream*) pj_pool_zalloc(pool,
- sizeof(pjmedia_snd_stream));
- strm->dir = PJMEDIA_DIR_PLAYBACK;
- strm->pool = pool;
- strm->clock_rate = clock_rate;
- strm->channel_count = channel_count;
- strm->samples_per_frame = samples_per_frame;
-
- PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
- PJ_ASSERT_RETURN(get_clock_rate_cap(clock_rate) != 0, PJ_EINVAL);
- PJ_ASSERT_RETURN(get_channel_cap(channel_count) != 0, PJ_EINVAL);
-
- // Create the output stream.
- TRAPD(err, strm->outEngine = CPjAudioOutputEngine::NewL(strm, play_cb,
- user_data));
- if (err != KErrNone) {
- pj_pool_release(pool);
- return PJ_RETURN_OS_ERROR(err);
- }
-
- // Done.
- *p_snd_strm = strm;
- return PJ_SUCCESS;
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id,
- int play_id,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_rec_cb rec_cb,
- pjmedia_snd_play_cb play_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
-{
- pj_pool_t *pool;
- pjmedia_snd_stream *strm;
-
- if (rec_id == -1) rec_id = 0;
- if (play_id == -1) play_id = 0;
-
- PJ_ASSERT_RETURN(rec_id == 0 && play_id == 0, PJ_EINVAL);
- PJ_ASSERT_RETURN(clock_rate && channel_count && samples_per_frame &&
- bits_per_sample && rec_cb && play_cb && p_snd_strm,
- PJ_EINVAL);
-
- pool = pj_pool_create(snd_pool_factory, POOL_NAME, POOL_SIZE, POOL_INC,
- NULL);
- if (!pool)
- return PJ_ENOMEM;
-
- strm = (pjmedia_snd_stream*) pj_pool_zalloc(pool,
- sizeof(pjmedia_snd_stream));
- strm->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
- strm->pool = pool;
- strm->clock_rate = clock_rate;
- strm->channel_count = channel_count;
- strm->samples_per_frame = samples_per_frame;
-
- PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
- PJ_ASSERT_RETURN(get_clock_rate_cap(clock_rate) != 0, PJ_EINVAL);
- PJ_ASSERT_RETURN(get_channel_cap(channel_count) != 0, PJ_EINVAL);
-
- // Create the output stream.
- TRAPD(err, strm->outEngine = CPjAudioOutputEngine::NewL(strm, play_cb,
- user_data));
- if (err != KErrNone) {
- pj_pool_release(pool);
- return PJ_RETURN_OS_ERROR(err);
- }
-
- // Create the input stream.
- TRAPD(err1, strm->inEngine = CPjAudioInputEngine::NewL(strm, rec_cb,
- user_data));
- if (err1 != KErrNone) {
- strm->inEngine = NULL;
- delete strm->outEngine;
- strm->outEngine = NULL;
- pj_pool_release(pool);
- return PJ_RETURN_OS_ERROR(err1);
- }
-
- // Done.
- *p_snd_strm = strm;
- return PJ_SUCCESS;
-}
-
-/*
- * Get stream info.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
- pjmedia_snd_stream_info *pi)
-{
- PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
-
- pj_bzero(pi, sizeof(*pi));
- pi->dir = strm->dir;
- pi->play_id = 0;
- pi->rec_id = 0;
- pi->clock_rate = strm->clock_rate;
- pi->channel_count = strm->channel_count;
- pi->samples_per_frame = strm->samples_per_frame;
- pi->bits_per_sample = BYTES_PER_SAMPLE * 8;
- // Symbian uses 4096 bytes buffer (~2048 samples/256 ms) for PCM rec & play.
- // The latencies below are rounded up to be a multiplication of 80.
- pi->rec_latency = 2080;
- pi->play_latency = 2080;
-
- return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
-{
- pj_status_t status;
-
- PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-
- if (stream->outEngine) {
- status = stream->outEngine->StartPlay();
- if (status != PJ_SUCCESS)
- return status;
- }
-
- if (stream->inEngine) {
- status = stream->inEngine->StartRecord();
- if (status != PJ_SUCCESS)
- return status;
- }
-
- return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
-{
- PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-
- if (stream->inEngine) {
- stream->inEngine->Stop();
- }
-
- if (stream->outEngine) {
- stream->outEngine->Stop();
- }
-
- return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
-{
- pj_pool_t *pool;
-
- PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-
- if (stream->inEngine) {
- delete stream->inEngine;
- stream->inEngine = NULL;
- }
-
- if (stream->outEngine) {
- delete stream->outEngine;
- stream->outEngine = NULL;
- }
-
- pool = stream->pool;
- if (pool) {
- stream->pool = NULL;
- pj_pool_release(pool);
- }
-
- return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
-{
- /* Nothing to do */
- return PJ_SUCCESS;
-}
-
-
-/*
- * Set sound latency.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency,
- unsigned output_latency)
-{
- /* Nothing to do */
- PJ_UNUSED_ARG(input_latency);
- PJ_UNUSED_ARG(output_latency);
- return PJ_SUCCESS;
-}
diff --git a/pjmedia/src/pjmedia/symbian_sound_aps.cpp b/pjmedia/src/pjmedia/symbian_sound_aps.cpp
deleted file mode 100644
index ea419c51..00000000
--- a/pjmedia/src/pjmedia/symbian_sound_aps.cpp
+++ /dev/null
@@ -1,920 +0,0 @@
-/* $Id$ */
-/*
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
- * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
- *
- * 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 2 of the License, or
- * (at your option) 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-#include <pjmedia/sound.h>
-#include <pjmedia/alaw_ulaw.h>
-#include <pjmedia/errno.h>
-#include <pjmedia/symbian_sound_aps.h>
-#include <pj/assert.h>
-#include <pj/log.h>
-#include <pj/math.h>
-#include <pj/os.h>
-
-#include <e32msgqueue.h>
-#include <sounddevice.h>
-#include <APSClientSession.h>
-
-//////////////////////////////////////////////////////////////////////////////
-//
-
-#define THIS_FILE "symbian_sound_aps.cpp"
-
-#define BYTES_PER_SAMPLE 2
-#define POOL_NAME "SymbianSoundAps"
-#define POOL_SIZE 512
-#define POOL_INC 512
-
-#if 1
-# define TRACE_(st) PJ_LOG(3, st)
-#else
-# define TRACE_(st)
-#endif
-
-static pjmedia_snd_dev_info symbian_snd_dev_info =
-{
- "Symbian Sound Device (APS)",
- 1,
- 1,
- 8000
-};
-
-/* App UID to open global APS queues to communicate with the APS server. */
-extern TPtrC APP_UID;
-
-/* Default setting for loudspeaker */
-static pj_bool_t act_loudspeaker = PJ_FALSE;
-
-/* Forward declaration of CPjAudioEngine */
-class CPjAudioEngine;
-
-/*
- * PJMEDIA Sound Stream instance
- */
-struct pjmedia_snd_stream
-{
- // Pool
- pj_pool_t *pool;
-
- // Common settings.
- pjmedia_dir dir;
- unsigned clock_rate;
- unsigned channel_count;
- unsigned samples_per_frame;
-
- // Audio engine
- CPjAudioEngine *engine;
-};
-
-static pj_pool_factory *snd_pool_factory;
-
-
-/*
- * Utility: print sound device error
- */
-static void snd_perror(const char *title, TInt rc)
-{
- PJ_LOG(1,(THIS_FILE, "%s (error code=%d)", title, rc));
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//
-
-/**
- * Abstract class for handler of callbacks from APS client.
- */
-class MQueueHandlerObserver
-{
-public:
- virtual void InputStreamInitialized(const TInt aStatus) = 0;
- virtual void OutputStreamInitialized(const TInt aStatus) = 0;
- virtual void NotifyError(const TInt aError) = 0;
-
- virtual void RecCb(TAPSCommBuffer &buffer) = 0;
- virtual void PlayCb(TAPSCommBuffer &buffer) = 0;
-};
-
-/**
- * Handler for communication and data queue.
- */
-class CQueueHandler : public CActive
-{
-public:
- // Types of queue handler
- enum TQueueHandlerType {
- ERecordCommQueue,
- EPlayCommQueue,
- ERecordQueue,
- EPlayQueue
- };
-
- // The order corresponds to the APS Server state, do not change!
- enum TState {
- EAPSPlayerInitialize = 1,
- EAPSRecorderInitialize = 2,
- EAPSPlayData = 3,
- EAPSRecordData = 4,
- EAPSPlayerInitComplete = 5,
- EAPSRecorderInitComplete = 6
- };
-
- static CQueueHandler* NewL(MQueueHandlerObserver* aObserver,
- RMsgQueue<TAPSCommBuffer>* aQ,
- TQueueHandlerType aType)
- {
- CQueueHandler* self = new (ELeave) CQueueHandler(aObserver, aQ, aType);
- CleanupStack::PushL(self);
- self->ConstructL();
- CleanupStack::Pop(self);
- return self;
- }
-
- // Destructor
- ~CQueueHandler() { Cancel(); }
-
- // Start listening queue event
- void Start() {
- iQ->NotifyDataAvailable(iStatus);
- SetActive();
- }
-
-private:
- // Constructor
- CQueueHandler(MQueueHandlerObserver* aObserver,
- RMsgQueue<TAPSCommBuffer>* aQ,
- TQueueHandlerType aType)
- : CActive(CActive::EPriorityHigh),
- iQ(aQ), iObserver(aObserver), iType(aType)
- {
- CActiveScheduler::Add(this);
-
- // use lower priority for comm queues
- if ((iType == ERecordCommQueue) || (iType == EPlayCommQueue))
- SetPriority(CActive::EPriorityStandard);
- }
-
- // Second phase constructor
- void ConstructL() {}
-
- // Inherited from CActive
- void DoCancel() { iQ->CancelDataAvailable(); }
-
- void RunL() {
- if (iStatus != KErrNone) {
- iObserver->NotifyError(iStatus.Int());
- return;
- }
-
- TAPSCommBuffer buffer;
- TInt ret = iQ->Receive(buffer);
-
- if (ret != KErrNone) {
- iObserver->NotifyError(ret);
- return;
- }
-
- switch (iType) {
- case ERecordQueue:
- if (buffer.iCommand == EAPSRecordData) {
- iObserver->RecCb(buffer);
- }
- break;
-
- // Callbacks from the APS main thread
- case EPlayCommQueue:
- switch (buffer.iCommand) {
- case EAPSPlayData:
- if (buffer.iStatus == KErrUnderflow) {
- iObserver->PlayCb(buffer);
- }
- break;
- case EAPSPlayerInitialize:
- iObserver->NotifyError(buffer.iStatus);
- break;
- case EAPSPlayerInitComplete:
- iObserver->OutputStreamInitialized(buffer.iStatus);
- break;
- case EAPSRecorderInitComplete:
- iObserver->InputStreamInitialized(buffer.iStatus);
- break;
- default:
- iObserver->NotifyError(buffer.iStatus);
- break;
- }
- break;
-
- // Callbacks from the APS recorder thread
- case ERecordCommQueue:
- switch (buffer.iCommand) {
- // The APS recorder thread will only report errors
- // through this handler. All other callbacks will be
- // sent from the APS main thread through EPlayCommQueue
- case EAPSRecorderInitialize:
- if (buffer.iStatus == KErrNone) {
- iObserver->InputStreamInitialized(buffer.iStatus);
- break;
- }
- case EAPSRecordData:
- iObserver->NotifyError(buffer.iStatus);
- break;
- default:
- break;
- }
- break;
-
- default:
- break;
- }
-
- // issue next request
- iQ->NotifyDataAvailable(iStatus);
- SetActive();
- }
-
- // Data
- RMsgQueue<TAPSCommBuffer> *iQ; // (not owned)
- MQueueHandlerObserver *iObserver; // (not owned)
- TQueueHandlerType iType;
-};
-
-
-/*
- * Implementation: Symbian Input & Output Stream.
- */
-class CPjAudioEngine : public CBase, MQueueHandlerObserver
-{
-public:
- enum State
- {
- STATE_NULL,
- STATE_READY,
- STATE_STREAMING
- };
-
- ~CPjAudioEngine();
-
- static CPjAudioEngine *NewL(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_rec_cb rec_cb,
- pjmedia_snd_play_cb play_cb,
- void *user_data);
-
- TInt StartL();
- void Stop();
-
- TInt ActivateSpeaker(TBool active);
-
-private:
- CPjAudioEngine(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_rec_cb rec_cb,
- pjmedia_snd_play_cb play_cb,
- void *user_data);
- void ConstructL();
-
- TInt InitPlayL();
- TInt InitRecL();
- TInt StartStreamL();
-
- // Inherited from MQueueHandlerObserver
- virtual void InputStreamInitialized(const TInt aStatus);
- virtual void OutputStreamInitialized(const TInt aStatus);
- virtual void NotifyError(const TInt aError);
-
- virtual void RecCb(TAPSCommBuffer &buffer);
- virtual void PlayCb(TAPSCommBuffer &buffer);
-
- State state_;
- pjmedia_snd_stream *parentStrm_;
- pjmedia_snd_rec_cb recCb_;
- pjmedia_snd_play_cb playCb_;
- void *userData_;
- pj_uint32_t TsPlay_;
- pj_uint32_t TsRec_;
-
- RAPSSession iSession;
- TAPSInitSettings iSettings;
- RMsgQueue<TAPSCommBuffer> iReadQ;
- RMsgQueue<TAPSCommBuffer> iReadCommQ;
- RMsgQueue<TAPSCommBuffer> iWriteQ;
- RMsgQueue<TAPSCommBuffer> iWriteCommQ;
-
- CQueueHandler *iPlayCommHandler;
- CQueueHandler *iRecCommHandler;
- CQueueHandler *iRecHandler;
-
- static pj_uint8_t aps_samples_per_frame;
-
- pj_int16_t *play_buf;
- pj_uint16_t play_buf_len;
- pj_uint16_t play_buf_start;
- pj_int16_t *rec_buf;
- pj_uint16_t rec_buf_len;
-};
-
-
-pj_uint8_t CPjAudioEngine::aps_samples_per_frame = 0;
-
-
-CPjAudioEngine* CPjAudioEngine::NewL(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_rec_cb rec_cb,
- pjmedia_snd_play_cb play_cb,
- void *user_data)
-{
- CPjAudioEngine* self = new (ELeave) CPjAudioEngine(parent_strm,
- rec_cb, play_cb,
- user_data);
- CleanupStack::PushL(self);
- self->ConstructL();
- CleanupStack::Pop(self);
- return self;
-}
-
-CPjAudioEngine::CPjAudioEngine(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_rec_cb rec_cb,
- pjmedia_snd_play_cb play_cb,
- void *user_data)
- : state_(STATE_NULL),
- parentStrm_(parent_strm),
- recCb_(rec_cb),
- playCb_(play_cb),
- userData_(user_data),
- iPlayCommHandler(0),
- iRecCommHandler(0),
- iRecHandler(0)
-{
-}
-
-CPjAudioEngine::~CPjAudioEngine()
-{
- Stop();
-
- delete iPlayCommHandler;
- iPlayCommHandler = NULL;
- delete iRecCommHandler;
- iRecCommHandler = NULL;
-
- // On some devices, immediate closing after stopping may cause APS server
- // panic KERN-EXEC 0, so let's wait for sometime before really closing
- // the client session.
- TTime start, now;
- enum { APS_CLOSE_WAIT_TIME = 200 }; /* in msecs */
-
- start.UniversalTime();
- do {
- pj_symbianos_poll(-1, APS_CLOSE_WAIT_TIME);
- now.UniversalTime();
- } while (now.MicroSecondsFrom(start) < APS_CLOSE_WAIT_TIME * 1000);
-
- iSession.Close();
-
- if (state_ == STATE_READY) {
- if (parentStrm_->dir != PJMEDIA_DIR_PLAYBACK) {
- iReadQ.Close();
- iReadCommQ.Close();
- }
- iWriteQ.Close();
- iWriteCommQ.Close();
- }
-}
-
-TInt CPjAudioEngine::InitPlayL()
-{
- if (state_ == STATE_STREAMING || state_ == STATE_READY)
- return 0;
-
- TInt err = iSession.InitializePlayer(iSettings);
- if (err != KErrNone) {
- snd_perror("Failed to initialize player", err);
- return err;
- }
-
- // Open message queues for the output stream
- TBuf<128> buf2 = iSettings.iGlobal;
- buf2.Append(_L("PlayQueue"));
- TBuf<128> buf3 = iSettings.iGlobal;
- buf3.Append(_L("PlayCommQueue"));
-
- while (iWriteQ.OpenGlobal(buf2))
- User::After(10);
- while (iWriteCommQ.OpenGlobal(buf3))
- User::After(10);
-
- // Construct message queue handler
- iPlayCommHandler = CQueueHandler::NewL(this,
- &iWriteCommQ,
- CQueueHandler::EPlayCommQueue);
-
- // Start observing APS callbacks on output stream message queue
- iPlayCommHandler->Start();
-
- return 0;
-}
-
-TInt CPjAudioEngine::InitRecL()
-{
- if (state_ == STATE_STREAMING || state_ == STATE_READY)
- return 0;
-
- // Initialize input stream device
- TInt err = iSession.InitializeRecorder(iSettings);
- if (err != KErrNone) {
- snd_perror("Failed to initialize recorder", err);
- return err;
- }
-
- TBuf<128> buf1 = iSettings.iGlobal;
- buf1.Append(_L("RecordQueue"));
- TBuf<128> buf4 = iSettings.iGlobal;
- buf4.Append(_L("RecordCommQueue"));
-
- // Must wait for APS thread to finish creating message queues
- // before we can open and use them.
- while (iReadQ.OpenGlobal(buf1))
- User::After(10);
- while (iReadCommQ.OpenGlobal(buf4))
- User::After(10);
-
- // Construct message queue handlers
- iRecCommHandler = CQueueHandler::NewL(this,
- &iReadCommQ,
- CQueueHandler::ERecordCommQueue);
-
- // Start observing APS callbacks from on input stream message queue
- iRecCommHandler->Start();
-
- return 0;
-}
-
-TInt CPjAudioEngine::StartL()
-{
- TInt err = iSession.Connect();
- if (err != KErrNone && err != KErrAlreadyExists)
- return err;
-
- if (state_ == STATE_READY)
- return StartStreamL();
-
- // Even if only capturer are opened, playback thread of APS Server need
- // to be run(?). Since some messages will be delivered via play comm queue.
- return InitPlayL();
-}
-
-void CPjAudioEngine::Stop()
-{
- iSession.Stop();
-
- delete iRecHandler;
- iRecHandler = NULL;
-
- state_ = STATE_READY;
-}
-
-void CPjAudioEngine::ConstructL()
-{
- iSettings.iFourCC = TFourCC(KMCPFourCCIdG711);
- iSettings.iGlobal = APP_UID;
- iSettings.iPriority = TMdaPriority(100);
- iSettings.iPreference = TMdaPriorityPreference(0x05210001);
- iSettings.iSettings.iChannels = EMMFMono;
- iSettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
- iSettings.iSettings.iVolume = 0;
-
- /* play_buf size is samples per frame of parent stream. */
- play_buf = (pj_int16_t*)pj_pool_alloc(parentStrm_->pool,
- parentStrm_->samples_per_frame << 1);
- play_buf_len = 0;
- play_buf_start = 0;
-
- /* rec_buf size is samples per frame of parent stream. */
- rec_buf = (pj_int16_t*)pj_pool_alloc(parentStrm_->pool,
- parentStrm_->samples_per_frame << 1);
- rec_buf_len = 0;
-}
-
-TInt CPjAudioEngine::StartStreamL()
-{
- if (state_ == STATE_STREAMING)
- return 0;
-
- iSession.SetCng(EFalse);
- iSession.SetVadMode(EFalse);
- iSession.SetPlc(EFalse);
- iSession.SetEncoderMode(EULawOr30ms);
- iSession.SetDecoderMode(EULawOr30ms);
- iSession.ActivateLoudspeaker(act_loudspeaker);
-
- // Not only playback
- if (parentStrm_->dir != PJMEDIA_DIR_PLAYBACK) {
- iRecHandler = CQueueHandler::NewL(this, &iReadQ,
- CQueueHandler::ERecordQueue);
- iRecHandler->Start();
- iSession.Read();
- TRACE_((THIS_FILE, "APS recorder started"));
- }
-
- // Not only capture
- if (parentStrm_->dir != PJMEDIA_DIR_CAPTURE) {
- iSession.Write();
- TRACE_((THIS_FILE, "APS player started"));
- }
-
- state_ = STATE_STREAMING;
- return 0;
-}
-
-///////////////////////////////////////////////////////////
-// Inherited from MQueueHandlerObserver
-//
-
-void CPjAudioEngine::InputStreamInitialized(const TInt aStatus)
-{
- TRACE_((THIS_FILE, "InputStreamInitialized %d", aStatus));
-
- state_ = STATE_READY;
- if (aStatus == KErrNone) {
- StartStreamL();
- }
-}
-
-void CPjAudioEngine::OutputStreamInitialized(const TInt aStatus)
-{
- TRACE_((THIS_FILE, "OutputStreamInitialized %d", aStatus));
-
- if (aStatus == KErrNone) {
- if (parentStrm_->dir == PJMEDIA_DIR_PLAYBACK) {
- state_ = STATE_READY;
- // Only playback, start directly
- StartStreamL();
- } else
- InitRecL();
- }
-}
-
-void CPjAudioEngine::NotifyError(const TInt aError)
-{
- snd_perror("Error from CQueueHandler", aError);
-}
-
-void CPjAudioEngine::RecCb(TAPSCommBuffer &buffer)
-{
- pj_assert(buffer.iBuffer[0] == 1 && buffer.iBuffer[1] == 0);
-
- /* Detect the recorder G.711 frame size, player frame size will follow
- * this recorder frame size.
- */
- if (CPjAudioEngine::aps_samples_per_frame == 0) {
- CPjAudioEngine::aps_samples_per_frame = buffer.iBuffer.Length() < 160?
- 80 : 160;
- TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples",
- CPjAudioEngine::aps_samples_per_frame));
- }
-
- /* Decode APS buffer (coded in G.711) and put the PCM result into rec_buf.
- * Whenever rec_buf is full, call parent stream callback.
- */
- unsigned dec_len = 0;
-
- while (dec_len < CPjAudioEngine::aps_samples_per_frame) {
- unsigned tmp;
-
- tmp = PJ_MIN(parentStrm_->samples_per_frame - rec_buf_len,
- CPjAudioEngine::aps_samples_per_frame - dec_len);
- pjmedia_ulaw_decode(&rec_buf[rec_buf_len],
- buffer.iBuffer.Ptr() + 2 + dec_len,
- tmp);
- rec_buf_len += tmp;
- dec_len += tmp;
-
- pj_assert(rec_buf_len <= parentStrm_->samples_per_frame);
-
- if (rec_buf_len == parentStrm_->samples_per_frame) {
- recCb_(userData_, 0, rec_buf, rec_buf_len << 1);
- rec_buf_len = 0;
- }
- }
-}
-
-void CPjAudioEngine::PlayCb(TAPSCommBuffer &buffer)
-{
- buffer.iCommand = CQueueHandler::EAPSPlayData;
- buffer.iStatus = 0;
- buffer.iBuffer.Zero();
- buffer.iBuffer.Append(1);
- buffer.iBuffer.Append(0);
-
- /* Send 10ms silence frame if frame size hasn't been known. */
- if (CPjAudioEngine::aps_samples_per_frame == 0) {
- pjmedia_zero_samples(play_buf, 80);
- pjmedia_ulaw_encode((pj_uint8_t*)play_buf, play_buf, 80);
- buffer.iBuffer.Append((TUint8*)play_buf, 80);
- iWriteQ.Send(buffer);
- return;
- }
-
- unsigned enc_len = 0;
-
- /* Call parent stream callback to get PCM samples to play,
- * encode the PCM samples into G.711 and put it into APS buffer.
- */
- while (enc_len < CPjAudioEngine::aps_samples_per_frame) {
- if (play_buf_len == 0) {
- playCb_(userData_, 0, play_buf, parentStrm_->samples_per_frame<<1);
- play_buf_len = parentStrm_->samples_per_frame;
- play_buf_start = 0;
- }
-
- unsigned tmp;
-
- tmp = PJ_MIN(play_buf_len,
- CPjAudioEngine::aps_samples_per_frame - enc_len);
- pjmedia_ulaw_encode((pj_uint8_t*)&play_buf[play_buf_start],
- &play_buf[play_buf_start],
- tmp);
- buffer.iBuffer.Append((TUint8*)&play_buf[play_buf_start], tmp);
- enc_len += tmp;
- play_buf_len -= tmp;
- play_buf_start += tmp;
- }
-
- iWriteQ.Send(buffer);
-}
-
-//
-// End of inherited from MQueueHandlerObserver
-/////////////////////////////////////////////////////////////
-
-
-TInt CPjAudioEngine::ActivateSpeaker(TBool active)
-{
- if (state_ == STATE_READY || state_ == STATE_STREAMING) {
- iSession.ActivateLoudspeaker(active);
- return KErrNone;
- }
- return KErrNotReady;
-}
-//////////////////////////////////////////////////////////////////////////////
-//
-
-
-/*
- * Initialize sound subsystem.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
-{
- snd_pool_factory = factory;
- return PJ_SUCCESS;
-}
-
-/*
- * Get device count.
- */
-PJ_DEF(int) pjmedia_snd_get_dev_count(void)
-{
- /* Always return 1 */
- return 1;
-}
-
-/*
- * Get device info.
- */
-PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
-{
- /* Always return the default sound device */
- if (index == (unsigned)-1)
- index = 0;
-
- PJ_ASSERT_RETURN(index==0, NULL);
- return &symbian_snd_dev_info;
-}
-
-static pj_status_t sound_open(pjmedia_dir dir,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_rec_cb rec_cb,
- pjmedia_snd_play_cb play_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
-{
- pj_pool_t *pool;
- pjmedia_snd_stream *strm;
-
- PJ_ASSERT_RETURN(p_snd_strm, PJ_EINVAL);
- PJ_ASSERT_RETURN(clock_rate == 8000 && channel_count == 1 &&
- bits_per_sample == 16, PJ_ENOTSUP);
- PJ_ASSERT_RETURN((dir == PJMEDIA_DIR_CAPTURE_PLAYBACK && rec_cb && play_cb)
- || (dir == PJMEDIA_DIR_CAPTURE && rec_cb && !play_cb)
- || (dir == PJMEDIA_DIR_PLAYBACK && !rec_cb && play_cb),
- PJ_EINVAL);
-
- pool = pj_pool_create(snd_pool_factory, POOL_NAME, POOL_SIZE, POOL_INC,
- NULL);
- if (!pool)
- return PJ_ENOMEM;
-
- strm = (pjmedia_snd_stream*) pj_pool_zalloc(pool,
- sizeof(pjmedia_snd_stream));
- strm->dir = dir;
- strm->pool = pool;
- strm->clock_rate = clock_rate;
- strm->channel_count = channel_count;
- strm->samples_per_frame = samples_per_frame;
-
- // Create the audio engine.
- TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm, rec_cb, play_cb,
- user_data));
- if (err != KErrNone) {
- pj_pool_release(pool);
- return PJ_RETURN_OS_ERROR(err);
- }
-
- // Done.
- *p_snd_strm = strm;
- return PJ_SUCCESS;
-}
-
-
-
-/*
- * Open sound recorder stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_rec_cb rec_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
-{
- if (index < 0) index = 0;
- PJ_ASSERT_RETURN(index == 0, PJ_EINVAL);
-
- return sound_open(PJMEDIA_DIR_CAPTURE, clock_rate, channel_count,
- samples_per_frame, bits_per_sample, rec_cb, NULL,
- user_data, p_snd_strm);
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_play_cb play_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm )
-{
- if (index < 0) index = 0;
- PJ_ASSERT_RETURN(index == 0, PJ_EINVAL);
-
- return sound_open(PJMEDIA_DIR_PLAYBACK, clock_rate, channel_count,
- samples_per_frame, bits_per_sample, NULL, play_cb,
- user_data, p_snd_strm);
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id,
- int play_id,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_rec_cb rec_cb,
- pjmedia_snd_play_cb play_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
-{
- if (rec_id < 0) rec_id = 0;
- if (play_id < 0) play_id = 0;
- PJ_ASSERT_RETURN(play_id == 0 && rec_id == 0, PJ_EINVAL);
-
- return sound_open(PJMEDIA_DIR_CAPTURE_PLAYBACK, clock_rate, channel_count,
- samples_per_frame, bits_per_sample, rec_cb, play_cb,
- user_data, p_snd_strm);
-}
-
-/*
- * Get stream info.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
- pjmedia_snd_stream_info *pi)
-{
- PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
-
- pj_bzero(pi, sizeof(*pi));
- pi->dir = strm->dir;
- pi->play_id = 0;
- pi->rec_id = 0;
- pi->clock_rate = strm->clock_rate;
- pi->channel_count = strm->channel_count;
- pi->samples_per_frame = strm->samples_per_frame;
- pi->bits_per_sample = BYTES_PER_SAMPLE * 8;
- // latencies approximation (in samples)
- pi->rec_latency = strm->samples_per_frame * 2;
- pi->play_latency = strm->samples_per_frame * 2;
-
- return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
-{
- PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-
- if (stream->engine) {
- TInt err = stream->engine->StartL();
- if (err != KErrNone)
- return PJ_RETURN_OS_ERROR(err);
- }
-
- return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
-{
- PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-
- if (stream->engine) {
- stream->engine->Stop();
- }
-
- return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
-{
- pj_pool_t *pool;
-
- PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-
- if (stream->engine) {
- delete stream->engine;
- stream->engine = NULL;
- }
-
- pool = stream->pool;
- if (pool) {
- stream->pool = NULL;
- pj_pool_release(pool);
- }
-
- return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
-{
- /* Nothing to do */
- return PJ_SUCCESS;
-}
-
-
-/*
- * Set sound latency.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency,
- unsigned output_latency)
-{
- /* Nothing to do */
- PJ_UNUSED_ARG(input_latency);
- PJ_UNUSED_ARG(output_latency);
- return PJ_SUCCESS;
-}
-
-
-/*
- * Activate/deactivate loudspeaker.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_aps_activate_loudspeaker(
- pjmedia_snd_stream *stream,
- pj_bool_t active)
-{
- if (stream == NULL) {
- act_loudspeaker = active;
- } else {
- if (stream->engine == NULL)
- return PJ_EINVAL;
-
- TInt err = stream->engine->ActivateSpeaker(active);
- if (err != KErrNone)
- return PJ_RETURN_OS_ERROR(err);
- }
-
- return PJ_SUCCESS;
-}
diff --git a/pjmedia/src/pjmedia/transport_ice.c b/pjmedia/src/pjmedia/transport_ice.c
index 7e0b6b75..77a2e346 100644
--- a/pjmedia/src/pjmedia/transport_ice.c
+++ b/pjmedia/src/pjmedia/transport_ice.c
@@ -21,6 +21,7 @@
#include <pjnath/errno.h>
#include <pj/assert.h>
#include <pj/log.h>
+#include <pj/pool.h>
#include <pj/rand.h>
#define THIS_FILE "transport_ice.c"
diff --git a/pjmedia/src/pjmedia/transport_loop.c b/pjmedia/src/pjmedia/transport_loop.c
index d9742d26..418e04a6 100644
--- a/pjmedia/src/pjmedia/transport_loop.c
+++ b/pjmedia/src/pjmedia/transport_loop.c
@@ -22,6 +22,7 @@
#include <pj/errno.h>
#include <pj/ioqueue.h>
#include <pj/log.h>
+#include <pj/pool.h>
#include <pj/rand.h>
#include <pj/string.h>
diff --git a/pjmedia/src/pjmedia/transport_udp.c b/pjmedia/src/pjmedia/transport_udp.c
index 11702713..2236392c 100644
--- a/pjmedia/src/pjmedia/transport_udp.c
+++ b/pjmedia/src/pjmedia/transport_udp.c
@@ -23,6 +23,7 @@
#include <pj/errno.h>
#include <pj/ioqueue.h>
#include <pj/log.h>
+#include <pj/pool.h>
#include <pj/rand.h>
#include <pj/string.h>
diff --git a/pjmedia/src/pjmedia/wmme_sound.c b/pjmedia/src/pjmedia/wmme_sound.c
deleted file mode 100644
index 8f94660c..00000000
--- a/pjmedia/src/pjmedia/wmme_sound.c
+++ /dev/null
@@ -1,1008 +0,0 @@
-#include <pjmedia/sound.h>
-#include <pjmedia/errno.h>
-#include <pj/assert.h>
-#include <pj/log.h>
-#include <pj/os.h>
-#include <pj/string.h>
-
-#if PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_WIN32_MME_SOUND
-
-#ifdef _MSC_VER
-# pragma warning(push, 3)
-#endif
-
-#include <windows.h>
-#include <mmsystem.h>
-
-#ifdef _MSC_VER
-# pragma warning(pop)
-#endif
-
-#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0
-# pragma comment(lib, "Coredll.lib")
-#elif defined(_MSC_VER)
-# pragma comment(lib, "winmm.lib")
-#endif
-
-
-#define THIS_FILE "wmme_sound.c"
-#define BITS_PER_SAMPLE 16
-#define BYTES_PER_SAMPLE (BITS_PER_SAMPLE/8)
-
-#define MAX_PACKET_BUFFER_COUNT 32
-#define MAX_HARDWARE 16
-
-struct wmme_dev_info
-{
- pjmedia_snd_dev_info info;
- unsigned deviceId;
-};
-
-static unsigned dev_count;
-static struct wmme_dev_info dev_info[MAX_HARDWARE];
-static pj_bool_t snd_initialized = PJ_FALSE;
-
-/* Latency settings */
-static unsigned snd_input_latency = PJMEDIA_SND_DEFAULT_REC_LATENCY;
-static unsigned snd_output_latency = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
-
-
-/* Individual WMME capture/playback stream descriptor */
-struct wmme_stream
-{
- union
- {
- HWAVEIN In;
- HWAVEOUT Out;
- } hWave;
-
- WAVEHDR *WaveHdr;
- HANDLE hEvent;
- DWORD dwBufIdx;
- DWORD dwMaxBufIdx;
- pj_timestamp timestamp;
-};
-
-
-/* Sound stream. */
-struct pjmedia_snd_stream
-{
- pjmedia_dir dir; /**< Sound direction. */
- int play_id; /**< Playback dev id. */
- int rec_id; /**< Recording dev id. */
- pj_pool_t *pool; /**< Memory pool. */
-
- pjmedia_snd_rec_cb rec_cb; /**< Capture callback. */
- pjmedia_snd_play_cb play_cb; /**< Playback callback. */
- void *user_data; /**< Application data. */
-
- struct wmme_stream play_strm; /**< Playback stream. */
- struct wmme_stream rec_strm; /**< Capture stream. */
-
- void *buffer; /**< Temp. frame buffer. */
- unsigned clock_rate; /**< Clock rate. */
- unsigned samples_per_frame; /**< Samples per frame. */
- unsigned bits_per_sample; /**< Bits per sample. */
- unsigned channel_count; /**< Channel count. */
-
- pj_thread_t *thread; /**< Thread handle. */
- HANDLE thread_quit_event; /**< Quit signal to thread */
-};
-
-
-static pj_pool_factory *pool_factory;
-
-static void init_waveformatex (LPWAVEFORMATEX pcmwf,
- unsigned clock_rate,
- unsigned channel_count)
-{
- pj_bzero(pcmwf, sizeof(PCMWAVEFORMAT));
- pcmwf->wFormatTag = WAVE_FORMAT_PCM;
- pcmwf->nChannels = (pj_uint16_t)channel_count;
- pcmwf->nSamplesPerSec = clock_rate;
- pcmwf->nBlockAlign = (pj_uint16_t)(channel_count * BYTES_PER_SAMPLE);
- pcmwf->nAvgBytesPerSec = clock_rate * channel_count * BYTES_PER_SAMPLE;
- pcmwf->wBitsPerSample = BITS_PER_SAMPLE;
-}
-
-
-/*
- * Initialize WMME player device.
- */
-static pj_status_t init_player_stream( pj_pool_t *pool,
- struct wmme_stream *wmme_strm,
- int dev_id,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned buffer_count)
-{
- MMRESULT mr;
- WAVEFORMATEX pcmwf;
- unsigned bytes_per_frame;
- unsigned i;
-
- PJ_ASSERT_RETURN(buffer_count <= MAX_PACKET_BUFFER_COUNT, PJ_EINVAL);
-
- /* Check device ID */
- if (dev_id == -1)
- dev_id = 0;
-
- PJ_ASSERT_RETURN(dev_id >= 0 && dev_id < (int)dev_count, PJ_EINVAL);
-
- /*
- * Create a wait event.
- */
- wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
- if (NULL == wmme_strm->hEvent)
- return pj_get_os_error();
-
- /*
- * Set up wave format structure for opening the device.
- */
- init_waveformatex(&pcmwf, clock_rate, channel_count);
- bytes_per_frame = samples_per_frame * BYTES_PER_SAMPLE;
-
- /*
- * Open wave device.
- */
- mr = waveOutOpen(&wmme_strm->hWave.Out, dev_info[dev_id].deviceId, &pcmwf,
- (DWORD)wmme_strm->hEvent, 0, CALLBACK_EVENT);
- if (mr != MMSYSERR_NOERROR)
- /* TODO: This is for HRESULT/GetLastError() */
- PJ_RETURN_OS_ERROR(mr);
-
- /* Pause the wave out device */
- mr = waveOutPause(wmme_strm->hWave.Out);
- if (mr != MMSYSERR_NOERROR)
- /* TODO: This is for HRESULT/GetLastError() */
- PJ_RETURN_OS_ERROR(mr);
-
- /*
- * Create the buffers.
- */
- wmme_strm->WaveHdr = pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count);
- for (i = 0; i < buffer_count; ++i)
- {
- wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool, bytes_per_frame);
- wmme_strm->WaveHdr[i].dwBufferLength = bytes_per_frame;
- mr = waveOutPrepareHeader(wmme_strm->hWave.Out,
- &(wmme_strm->WaveHdr[i]),
- sizeof(WAVEHDR));
- if (mr != MMSYSERR_NOERROR)
- /* TODO: This is for HRESULT/GetLastError() */
- PJ_RETURN_OS_ERROR(mr);
- mr = waveOutWrite(wmme_strm->hWave.Out, &(wmme_strm->WaveHdr[i]),
- sizeof(WAVEHDR));
- if (mr != MMSYSERR_NOERROR)
- /* TODO: This is for HRESULT/GetLastError() */
- PJ_RETURN_OS_ERROR(mr);
- }
-
- wmme_strm->dwBufIdx = 0;
- wmme_strm->dwMaxBufIdx = buffer_count;
- wmme_strm->timestamp.u64 = 0;
-
- /* Done setting up play device. */
- PJ_LOG(5, (THIS_FILE,
- " WaveAPI Sound player \"%s\" initialized (clock_rate=%d, "
- "channel_count=%d, samples_per_frame=%d (%dms))",
- dev_info[dev_id].info.name,
- clock_rate, channel_count, samples_per_frame,
- samples_per_frame * 1000 / clock_rate));
-
- return PJ_SUCCESS;
-}
-
-
-/*
- * Initialize Windows Multimedia recorder device
- */
-static pj_status_t init_capture_stream( pj_pool_t *pool,
- struct wmme_stream *wmme_strm,
- int dev_id,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned buffer_count)
-{
- MMRESULT mr;
- WAVEFORMATEX pcmwf;
- unsigned bytes_per_frame;
- unsigned i;
-
- PJ_ASSERT_RETURN(buffer_count <= MAX_PACKET_BUFFER_COUNT, PJ_EINVAL);
-
- /* Check device ID */
- if (dev_id == -1)
- dev_id = 0;
-
- PJ_ASSERT_RETURN(dev_id >= 0 && dev_id < (int)dev_count, PJ_EINVAL);
-
- /*
- * Create a wait event.
- */
- wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
- if (NULL == wmme_strm->hEvent)
- return pj_get_os_error();
-
- /*
- * Set up wave format structure for opening the device.
- */
- init_waveformatex(&pcmwf, clock_rate, channel_count);
- bytes_per_frame = samples_per_frame * BYTES_PER_SAMPLE;
-
- /*
- * Open wave device.
- */
- mr = waveInOpen(&wmme_strm->hWave.In, dev_info[dev_id].deviceId, &pcmwf,
- (DWORD)wmme_strm->hEvent, 0, CALLBACK_EVENT);
- if (mr != MMSYSERR_NOERROR)
- /* TODO: This is for HRESULT/GetLastError() */
- PJ_RETURN_OS_ERROR(mr);
-
- /*
- * Create the buffers.
- */
- wmme_strm->WaveHdr = pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count);
- for (i = 0; i < buffer_count; ++i)
- {
- wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool, bytes_per_frame);
- wmme_strm->WaveHdr[i].dwBufferLength = bytes_per_frame;
- mr = waveInPrepareHeader(wmme_strm->hWave.In, &(wmme_strm->WaveHdr[i]),
- sizeof(WAVEHDR));
- if (mr != MMSYSERR_NOERROR)
- /* TODO: This is for HRESULT/GetLastError() */
- PJ_RETURN_OS_ERROR(mr);
- mr = waveInAddBuffer(wmme_strm->hWave.In, &(wmme_strm->WaveHdr[i]),
- sizeof(WAVEHDR));
- if (mr != MMSYSERR_NOERROR)
- /* TODO: This is for HRESULT/GetLastError() */
- PJ_RETURN_OS_ERROR(mr);
- }
-
- wmme_strm->dwBufIdx = 0;
- wmme_strm->dwMaxBufIdx = buffer_count;
- wmme_strm->timestamp.u64 = 0;
-
- /* Done setting up play device. */
- PJ_LOG(5,(THIS_FILE,
- " WaveAPI Sound recorder \"%s\" initialized (clock_rate=%d, "
- "channel_count=%d, samples_per_frame=%d (%dms))",
- dev_info[dev_id].info.name,
- clock_rate, channel_count, samples_per_frame,
- samples_per_frame * 1000 / clock_rate));
-
- return PJ_SUCCESS;
-}
-
-
-
-/*
-* WMME capture and playback thread.
-*/
-static int PJ_THREAD_FUNC wmme_dev_thread(void *arg)
-{
- pjmedia_snd_stream *strm = arg;
- HANDLE events[3];
- unsigned eventCount;
- unsigned bytes_per_frame;
- pj_status_t status = PJ_SUCCESS;
-
-
- eventCount = 0;
- events[eventCount++] = strm->thread_quit_event;
- if (strm->dir & PJMEDIA_DIR_PLAYBACK)
- events[eventCount++] = strm->play_strm.hEvent;
- if (strm->dir & PJMEDIA_DIR_CAPTURE)
- events[eventCount++] = strm->rec_strm.hEvent;
-
-
- /* Raise self priority. We don't want the audio to be distorted by
- * system activity.
- */
-#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0
- if (strm->dir & PJMEDIA_DIR_PLAYBACK)
- CeSetThreadPriority(GetCurrentThread(), 153);
- else
- CeSetThreadPriority(GetCurrentThread(), 247);
-#else
- SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
-#endif
-
- /* Calculate bytes per frame */
- bytes_per_frame = strm->samples_per_frame * BYTES_PER_SAMPLE;
-
- /*
- * Loop while not signalled to quit, wait for event objects to be
- * signalled by WMME capture and play buffer.
- */
- while (status == PJ_SUCCESS)
- {
-
- DWORD rc;
- pjmedia_dir signalled_dir;
-
- rc = WaitForMultipleObjects(eventCount, events, FALSE, INFINITE);
- if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + eventCount)
- continue;
-
- if (rc == WAIT_OBJECT_0)
- break;
-
- if (rc == (WAIT_OBJECT_0 + 1))
- {
- if (events[1] == strm->play_strm.hEvent)
- signalled_dir = PJMEDIA_DIR_PLAYBACK;
- else
- signalled_dir = PJMEDIA_DIR_CAPTURE;
- }
- else
- {
- if (events[2] == strm->play_strm.hEvent)
- signalled_dir = PJMEDIA_DIR_PLAYBACK;
- else
- signalled_dir = PJMEDIA_DIR_CAPTURE;
- }
-
-
- if (signalled_dir == PJMEDIA_DIR_PLAYBACK)
- {
- struct wmme_stream *wmme_strm = &strm->play_strm;
- MMRESULT mr = MMSYSERR_NOERROR;
- status = PJ_SUCCESS;
-
- /*
- * Windows Multimedia has requested us to feed some frames to
- * playback buffer.
- */
-
- while (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE)
- {
- void* buffer = wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
-
- PJ_LOG(5,(THIS_FILE, "Finished writing buffer %d",
- wmme_strm->dwBufIdx));
-
- /* Get frame from application. */
- status = (*strm->play_cb)(strm->user_data,
- wmme_strm->timestamp.u32.lo,
- buffer,
- bytes_per_frame);
-
- if (status != PJ_SUCCESS)
- break;
-
- /* Write to the device. */
- mr = waveOutWrite(wmme_strm->hWave.Out,
- &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]),
- sizeof(WAVEHDR));
- if (mr != MMSYSERR_NOERROR)
- {
- status = PJ_STATUS_FROM_OS(mr);
- break;
- }
-
- /* Increment position. */
- if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx)
- wmme_strm->dwBufIdx = 0;
- wmme_strm->timestamp.u64 += strm->samples_per_frame /
- strm->channel_count;
- }
- }
- else
- {
- struct wmme_stream *wmme_strm = &strm->rec_strm;
- MMRESULT mr = MMSYSERR_NOERROR;
- status = PJ_SUCCESS;
-
- /*
- * Windows Multimedia has indicated that it has some frames ready
- * in the capture buffer. Get as much frames as possible to
- * prevent overflows.
- */
-#if 0
- {
- static DWORD tc = 0;
- DWORD now = GetTickCount();
- DWORD i = 0;
- DWORD bits = 0;
-
- if (tc == 0) tc = now;
-
- for (i = 0; i < wmme_strm->dwMaxBufIdx; ++i)
- {
- bits = bits << 4;
- bits |= wmme_strm->WaveHdr[i].dwFlags & WHDR_DONE;
- }
- PJ_LOG(5,(THIS_FILE, "Record Signal> Index: %d, Delta: %4.4d, "
- "Flags: %6.6x\n",
- wmme_strm->dwBufIdx,
- now - tc,
- bits));
- tc = now;
- }
-#endif
-
- while (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE)
- {
- char* buffer = (char*)
- wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
- unsigned cap_len =
- wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwBytesRecorded;
-
- /*
- PJ_LOG(5,(THIS_FILE, "Read %d bytes from buffer %d", cap_len,
- wmme_strm->dwBufIdx));
- */
-
- if (cap_len < bytes_per_frame)
- pj_bzero(buffer + cap_len, bytes_per_frame - cap_len);
-
- /* Copy the audio data out of the wave buffer. */
- pj_memcpy(strm->buffer, buffer, bytes_per_frame);
-
- /* Re-add the buffer to the device. */
- mr = waveInAddBuffer(wmme_strm->hWave.In,
- &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]),
- sizeof(WAVEHDR));
- if (mr != MMSYSERR_NOERROR) {
- status = PJ_STATUS_FROM_OS(mr);
- break;
- }
-
- /* Call callback */
- status = (*strm->rec_cb)(strm->user_data,
- wmme_strm->timestamp.u32.lo,
- strm->buffer,
- bytes_per_frame);
-
- if (status != PJ_SUCCESS)
- break;
-
- /* Increment position. */
- if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx)
- wmme_strm->dwBufIdx = 0;
- wmme_strm->timestamp.u64 += strm->samples_per_frame /
- strm->channel_count;
- }
- }
- }
-
- PJ_LOG(5,(THIS_FILE, "WMME: thread stopping.."));
- return 0;
-}
-
-
-/*
-* Init sound library.
-*/
-PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
-{
- unsigned c;
- int i;
- int inputDeviceCount, outputDeviceCount, maximumPossibleDeviceCount;
-
- if (snd_initialized)
- return PJ_SUCCESS;
-
- pj_bzero(&dev_info, sizeof(dev_info));
-
- dev_count = 0;
- pool_factory = factory;
-
- /* Enumerate sound playback devices */
- maximumPossibleDeviceCount = 0;
-
- inputDeviceCount = waveInGetNumDevs();
- if (inputDeviceCount > 0)
- /* assume there is a WAVE_MAPPER */
- maximumPossibleDeviceCount += inputDeviceCount + 1;
-
- outputDeviceCount = waveOutGetNumDevs();
- if (outputDeviceCount > 0)
- /* assume there is a WAVE_MAPPER */
- maximumPossibleDeviceCount += outputDeviceCount + 1;
-
- if (maximumPossibleDeviceCount >= MAX_HARDWARE)
- {
- pj_assert(!"Too many hardware found");
- PJ_LOG(3,(THIS_FILE, "Too many hardware found, "
- "some devices will not be listed"));
- }
-
- if (inputDeviceCount > 0)
- {
- /* -1 is the WAVE_MAPPER */
- for (i = -1; i < inputDeviceCount && dev_count < MAX_HARDWARE; ++i)
- {
- UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i);
- WAVEINCAPS wic;
- MMRESULT mr;
-
- pj_bzero(&wic, sizeof(WAVEINCAPS));
-
- mr = waveInGetDevCaps(uDeviceID, &wic, sizeof(WAVEINCAPS));
-
- if (mr == MMSYSERR_NOMEM)
- return PJ_ENOMEM;
-
- if (mr != MMSYSERR_NOERROR)
- continue;
-
-#ifdef UNICODE
- WideCharToMultiByte(CP_ACP, 0, wic.szPname, wcslen(wic.szPname),
- dev_info[dev_count].info.name, 64, NULL, NULL);
-#else
- strncpy(dev_info[dev_count].info.name, wic.szPname, MAXPNAMELEN);
-#endif
- if (uDeviceID == WAVE_MAPPER)
- strcat(dev_info[dev_count].info.name, " - Input");
-
- dev_info[dev_count].info.input_count = wic.wChannels;
- dev_info[dev_count].info.output_count = 0;
- dev_info[dev_count].info.default_samples_per_sec = 44100;
- dev_info[dev_count].deviceId = uDeviceID;
-
- /* Sometimes a device can return a rediculously large number of
- * channels. This happened with an SBLive card on a Windows ME box.
- * It also happens on Win XP!
- */
- if ((dev_info[dev_count].info.input_count < 1) ||
- (dev_info[dev_count].info.input_count > 256))
- dev_info[dev_count].info.input_count = 2;
-
- ++dev_count;
- }
- }
-
- if( outputDeviceCount > 0 )
- {
- /* -1 is the WAVE_MAPPER */
- for (i = -1; i < outputDeviceCount && dev_count < MAX_HARDWARE; ++i)
- {
- UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i);
- WAVEOUTCAPS woc;
- MMRESULT mr;
-
- pj_bzero(&woc, sizeof(WAVEOUTCAPS));
-
- mr = waveOutGetDevCaps(uDeviceID, &woc, sizeof(WAVEOUTCAPS));
-
- if (mr == MMSYSERR_NOMEM)
- return PJ_ENOMEM;
-
- if (mr != MMSYSERR_NOERROR)
- continue;
-
-#ifdef UNICODE
- WideCharToMultiByte(CP_ACP, 0, woc.szPname, wcslen(woc.szPname),
- dev_info[dev_count].info.name, 64, NULL, NULL);
-#else
- strncpy(dev_info[dev_count].info.name, woc.szPname, MAXPNAMELEN);
-#endif
- if (uDeviceID == WAVE_MAPPER)
- strcat(dev_info[dev_count].info.name, " - Output");
-
- dev_info[dev_count].info.output_count = woc.wChannels;
- dev_info[dev_count].info.input_count = 0;
- dev_info[dev_count].deviceId = uDeviceID;
- /* TODO: Perform a search! */
- dev_info[dev_count].info.default_samples_per_sec = 44100;
-
- /* Sometimes a device can return a rediculously large number of channels.
- * This happened with an SBLive card on a Windows ME box.
- * It also happens on Win XP!
- */
- if ((dev_info[dev_count].info.output_count < 1) ||
- (dev_info[dev_count].info.output_count > 256))
- dev_info[dev_count].info.output_count = 2;
-
- ++dev_count;
- }
- }
-
- PJ_LOG(4, (THIS_FILE, "WMME initialized, found %d devices:", dev_count));
- for (c = 0; c < dev_count; ++c)
- {
- PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d)",
- c,
- dev_info[c].info.name,
- dev_info[c].info.input_count,
- dev_info[c].info.output_count));
- }
- return PJ_SUCCESS;
-}
-
-/*
- * Deinitialize sound library.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
-{
- snd_initialized = PJ_FALSE;
- return PJ_SUCCESS;
-}
-
-/*
- * Get device count.
- */
-PJ_DEF(int) pjmedia_snd_get_dev_count(void)
-{
- return dev_count;
-}
-
-/*
- * Get device info.
- */
-PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
-{
- if (index == (unsigned)-1)
- index = 0;
-
- PJ_ASSERT_RETURN(index < dev_count, NULL);
-
- return &dev_info[index].info;
-}
-
-
-/*
- * Open stream.
- */
-static pj_status_t open_stream(pjmedia_dir dir,
- int rec_id,
- int play_id,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_rec_cb rec_cb,
- pjmedia_snd_play_cb play_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
-{
- pj_pool_t *pool;
- pjmedia_snd_stream *strm;
- pj_status_t status;
-
-
- /* Make sure sound subsystem has been initialized with
- * pjmedia_snd_init()
- */
- PJ_ASSERT_RETURN(pool_factory != NULL, PJ_EINVALIDOP);
-
-
- /* Can only support 16bits per sample */
- PJ_ASSERT_RETURN(bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
-
- /* Create and Initialize stream descriptor */
- pool = pj_pool_create(pool_factory, "wmme-dev", 1000, 1000, NULL);
- PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
-
- strm = pj_pool_zalloc(pool, sizeof(pjmedia_snd_stream));
- strm->dir = dir;
- strm->play_id = play_id;
- strm->rec_id = rec_id;
- strm->pool = pool;
- strm->rec_cb = rec_cb;
- strm->play_cb = play_cb;
- strm->user_data = user_data;
- strm->clock_rate = clock_rate;
- strm->samples_per_frame = samples_per_frame;
- strm->bits_per_sample = bits_per_sample;
- strm->channel_count = channel_count;
- strm->buffer = pj_pool_alloc(pool, samples_per_frame * BYTES_PER_SAMPLE);
- if (!strm->buffer)
- {
- pj_pool_release(pool);
- return PJ_ENOMEM;
- }
-
- /* Create player stream */
- if (dir & PJMEDIA_DIR_PLAYBACK)
- {
- unsigned buf_count;
-
- buf_count = snd_output_latency * clock_rate * channel_count /
- samples_per_frame / 1000;
-
- status = init_player_stream(strm->pool,
- &strm->play_strm,
- play_id,
- clock_rate,
- channel_count,
- samples_per_frame,
- buf_count);
-
- if (status != PJ_SUCCESS)
- {
- pjmedia_snd_stream_close(strm);
- return status;
- }
- }
-
- /* Create capture stream */
- if (dir & PJMEDIA_DIR_CAPTURE)
- {
- unsigned buf_count;
-
- buf_count = snd_input_latency * clock_rate * channel_count /
- samples_per_frame / 1000;
-
- status = init_capture_stream(strm->pool,
- &strm->rec_strm,
- rec_id,
- clock_rate,
- channel_count,
- samples_per_frame,
- buf_count);
-
- if (status != PJ_SUCCESS)
- {
- pjmedia_snd_stream_close(strm);
- return status;
- }
- }
-
- /* Create the stop event */
- strm->thread_quit_event = CreateEvent(NULL, FALSE, FALSE, NULL);
- if (strm->thread_quit_event == NULL)
- return pj_get_os_error();
-
- /* Create and start the thread */
- status = pj_thread_create(pool, "wmme", &wmme_dev_thread, strm, 0, 0,
- &strm->thread);
- if (status != PJ_SUCCESS)
- {
- pjmedia_snd_stream_close(strm);
- return status;
- }
-
- *p_snd_strm = strm;
-
- return PJ_SUCCESS;
-}
-
-/*
- * Open stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_open_rec(int index,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_rec_cb rec_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
-{
- PJ_ASSERT_RETURN(rec_cb && p_snd_strm, PJ_EINVAL);
-
- return open_stream( PJMEDIA_DIR_CAPTURE,
- index,
- -1,
- clock_rate,
- channel_count,
- samples_per_frame,
- bits_per_sample,
- rec_cb,
- NULL,
- user_data,
- p_snd_strm);
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_open_player(int index,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_play_cb play_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
-{
- PJ_ASSERT_RETURN(play_cb && p_snd_strm, PJ_EINVAL);
-
- return open_stream( PJMEDIA_DIR_PLAYBACK,
- -1,
- index,
- clock_rate,
- channel_count,
- samples_per_frame,
- bits_per_sample,
- NULL,
- play_cb,
- user_data,
- p_snd_strm);
-}
-
-/*
- * Open both player and recorder.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_open(int rec_id,
- int play_id,
- unsigned clock_rate,
- unsigned channel_count,
- unsigned samples_per_frame,
- unsigned bits_per_sample,
- pjmedia_snd_rec_cb rec_cb,
- pjmedia_snd_play_cb play_cb,
- void *user_data,
- pjmedia_snd_stream **p_snd_strm)
-{
- PJ_ASSERT_RETURN(rec_cb && play_cb && p_snd_strm, PJ_EINVAL);
-
- return open_stream( PJMEDIA_DIR_CAPTURE_PLAYBACK,
- rec_id,
- play_id,
- clock_rate,
- channel_count,
- samples_per_frame,
- bits_per_sample,
- rec_cb,
- play_cb,
- user_data,
- p_snd_strm);
-}
-
-/*
- * Get stream info.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
- pjmedia_snd_stream_info *pi)
-{
- PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
-
- pj_bzero(pi, sizeof(*pi));
- pi->dir = strm->dir;
- pi->play_id = strm->play_id;
- pi->rec_id = strm->rec_id;
- pi->clock_rate = strm->clock_rate;
- pi->channel_count = strm->channel_count;
- pi->samples_per_frame = strm->samples_per_frame;
- pi->bits_per_sample = strm->bits_per_sample;
- pi->rec_latency = snd_input_latency * strm->clock_rate *
- strm->channel_count / 1000;
- pi->play_latency = snd_output_latency * strm->clock_rate *
- strm->channel_count / 1000;
-
- return PJ_SUCCESS;
-}
-
-
-/*
-* Start stream.
-*/
-PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
-{
- MMRESULT mr;
-
- PJ_UNUSED_ARG(stream);
-
- if (stream->play_strm.hWave.Out != NULL)
- {
- mr = waveOutRestart(stream->play_strm.hWave.Out);
- if (mr != MMSYSERR_NOERROR)
- /* TODO: This macro is supposed to be used for HRESULT, fix. */
- PJ_RETURN_OS_ERROR(mr);
- PJ_LOG(5,(THIS_FILE, "WMME playback stream started"));
- }
-
- if (stream->rec_strm.hWave.In != NULL)
- {
- mr = waveInStart(stream->rec_strm.hWave.In);
- if (mr != MMSYSERR_NOERROR)
- /* TODO: This macro is supposed to be used for HRESULT, fix. */
- PJ_RETURN_OS_ERROR(mr);
- PJ_LOG(5,(THIS_FILE, "WMME capture stream started"));
- }
-
- return PJ_SUCCESS;
-}
-
-/*
- * Stop stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
-{
- MMRESULT mr;
-
- PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-
- if (stream->play_strm.hWave.Out != NULL)
- {
- mr = waveOutPause(stream->play_strm.hWave.Out);
- if (mr != MMSYSERR_NOERROR)
- /* TODO: This macro is supposed to be used for HRESULT, fix. */
- PJ_RETURN_OS_ERROR(mr);
- PJ_LOG(5,(THIS_FILE, "Stopped WMME playback stream"));
- }
-
- if (stream->rec_strm.hWave.In != NULL)
- {
- mr = waveInStop(stream->rec_strm.hWave.In);
- if (mr != MMSYSERR_NOERROR)
- /* TODO: This macro is supposed to be used for HRESULT, fix. */
- PJ_RETURN_OS_ERROR(mr);
- PJ_LOG(5,(THIS_FILE, "Stopped WMME capture stream"));
- }
-
- return PJ_SUCCESS;
-}
-
-
-/*
- * Destroy stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
-{
- unsigned i;
-
- PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-
- pjmedia_snd_stream_stop(stream);
-
- if (stream->thread)
- {
- SetEvent(stream->thread_quit_event);
- pj_thread_join(stream->thread);
- pj_thread_destroy(stream->thread);
- stream->thread = NULL;
- }
-
- /* Unprepare the headers and close the play device */
- if (stream->play_strm.hWave.Out)
- {
- waveOutReset(stream->play_strm.hWave.Out);
- for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i)
- waveOutUnprepareHeader(stream->play_strm.hWave.Out,
- &(stream->play_strm.WaveHdr[i]),
- sizeof(WAVEHDR));
- waveOutClose(stream->play_strm.hWave.Out);
- stream->play_strm.hWave.Out = NULL;
- }
-
- /* Close the play event */
- if (stream->play_strm.hEvent)
- {
- CloseHandle(stream->play_strm.hEvent);
- stream->play_strm.hEvent = NULL;
- }
-
- /* Unprepare the headers and close the record device */
- if (stream->rec_strm.hWave.In)
- {
- waveInReset(stream->rec_strm.hWave.In);
- for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i)
- waveInUnprepareHeader(stream->rec_strm.hWave.In,
- &(stream->rec_strm.WaveHdr[i]),
- sizeof(WAVEHDR));
- waveInClose(stream->rec_strm.hWave.In);
- stream->rec_strm.hWave.In = NULL;
- }
-
- /* Close the record event */
- if (stream->rec_strm.hEvent)
- {
- CloseHandle(stream->rec_strm.hEvent);
- stream->rec_strm.hEvent = NULL;
- }
-
- pj_pool_release(stream->pool);
-
- return PJ_SUCCESS;
-}
-
-/*
- * Set sound latency.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency,
- unsigned output_latency)
-{
- snd_input_latency = (input_latency == 0)?
- PJMEDIA_SND_DEFAULT_REC_LATENCY : input_latency;
- snd_output_latency = (output_latency == 0)?
- PJMEDIA_SND_DEFAULT_PLAY_LATENCY : output_latency;
-
- return PJ_SUCCESS;
-}
-
-#endif /* PJMEDIA_SOUND_IMPLEMENTATION */
-
diff --git a/pjproject-vs8.sln b/pjproject-vs8.sln
index 04b97a89..fe05ab74 100644
--- a/pjproject-vs8.sln
+++ b/pjproject-vs8.sln
@@ -12,8 +12,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjlib_util", "pjlib-util\bu
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjlib_util_test", "pjlib-util\build\pjlib_util_test.vcproj", "{ED02BE13-8297-4770-8097-27DC2CCABF9A}"
ProjectSection(ProjectDependencies) = postProject
- {DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
{FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
+ {DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjmedia", "pjmedia\build\pjmedia.vcproj", "{7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}"
@@ -28,75 +28,77 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjsip_ua", "pjsip\build\pjs
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjsua", "pjsip-apps\build\pjsua.vcproj", "{8310649E-A25E-4AF0-91E8-9E3CC659BB89}"
ProjectSection(ProjectDependencies) = postProject
- {E53AA5FF-B737-40AA-BD13-387EFA99023D} = {E53AA5FF-B737-40AA-BD13-387EFA99023D}
- {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
- {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
- {DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
- {B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
- {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
- {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
- {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
- {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
- {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
- {6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
- {FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
- {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
- {4BF51C21-5A30-423B-82FE-1ED410E5769D} = {4BF51C21-5A30-423B-82FE-1ED410E5769D}
- {2BB84911-C1B4-4747-B93D-36AA82CC5031} = {2BB84911-C1B4-4747-B93D-36AA82CC5031}
+ {4281CA5E-1D48-45D4-A991-2718A454B4BA} = {4281CA5E-1D48-45D4-A991-2718A454B4BA}
{F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858} = {F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858}
+ {2BB84911-C1B4-4747-B93D-36AA82CC5031} = {2BB84911-C1B4-4747-B93D-36AA82CC5031}
+ {4BF51C21-5A30-423B-82FE-1ED410E5769D} = {4BF51C21-5A30-423B-82FE-1ED410E5769D}
+ {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
+ {FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
+ {6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
+ {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
+ {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
+ {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
+ {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
+ {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
+ {B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
+ {DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
+ {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
+ {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
+ {E53AA5FF-B737-40AA-BD13-387EFA99023D} = {E53AA5FF-B737-40AA-BD13-387EFA99023D}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjsua_lib", "pjsip\build\pjsua_lib.vcproj", "{9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample_debug", "pjsip-apps\build\sample_debug.vcproj", "{A0F1AA62-0F6F-420D-B09A-AC04B6862821}"
ProjectSection(ProjectDependencies) = postProject
- {E53AA5FF-B737-40AA-BD13-387EFA99023D} = {E53AA5FF-B737-40AA-BD13-387EFA99023D}
- {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
- {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
- {DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
- {B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
- {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
- {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
- {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
- {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
- {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
- {6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
- {FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
- {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
{2BB84911-C1B4-4747-B93D-36AA82CC5031} = {2BB84911-C1B4-4747-B93D-36AA82CC5031}
+ {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
+ {FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
+ {6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
+ {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
+ {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
+ {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
+ {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
+ {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
+ {B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
+ {DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
+ {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
+ {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
+ {E53AA5FF-B737-40AA-BD13-387EFA99023D} = {E53AA5FF-B737-40AA-BD13-387EFA99023D}
+ {4281CA5E-1D48-45D4-A991-2718A454B4BA} = {4281CA5E-1D48-45D4-A991-2718A454B4BA}
{F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858} = {F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "samples", "pjsip-apps\build\samples.vcproj", "{E378A1FC-0C9C-4462-860F-7E60BC1BF84E}"
ProjectSection(ProjectDependencies) = postProject
- {E53AA5FF-B737-40AA-BD13-387EFA99023D} = {E53AA5FF-B737-40AA-BD13-387EFA99023D}
- {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
- {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
- {DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
- {B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
- {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
- {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
- {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
- {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
- {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
- {6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
- {FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
- {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
- {4BF51C21-5A30-423B-82FE-1ED410E5769D} = {4BF51C21-5A30-423B-82FE-1ED410E5769D}
- {2BB84911-C1B4-4747-B93D-36AA82CC5031} = {2BB84911-C1B4-4747-B93D-36AA82CC5031}
{F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858} = {F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858}
+ {2BB84911-C1B4-4747-B93D-36AA82CC5031} = {2BB84911-C1B4-4747-B93D-36AA82CC5031}
+ {4BF51C21-5A30-423B-82FE-1ED410E5769D} = {4BF51C21-5A30-423B-82FE-1ED410E5769D}
+ {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
+ {FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
+ {6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
+ {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
+ {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
+ {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
+ {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
+ {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
+ {B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
+ {DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
+ {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
+ {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
+ {E53AA5FF-B737-40AA-BD13-387EFA99023D} = {E53AA5FF-B737-40AA-BD13-387EFA99023D}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_pjsip", "pjsip\build\test_pjsip.vcproj", "{B3F7D4E9-702F-4EB4-ADA8-098D0A83D770}"
ProjectSection(ProjectDependencies) = postProject
- {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
- {DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
- {B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
- {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
- {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
- {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
- {FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
{2BB84911-C1B4-4747-B93D-36AA82CC5031} = {2BB84911-C1B4-4747-B93D-36AA82CC5031}
+ {FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
+ {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
+ {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
+ {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
+ {B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
+ {DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
+ {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjnath", "pjnath\build\pjnath.vcproj", "{A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}"
@@ -119,26 +121,29 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsrtp", "third_party\buil
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjnath_test", "pjnath\build\pjnath_test.vcproj", "{553C094C-F581-4A80-9540-D5D7B398A2C6}"
ProjectSection(ProjectDependencies) = postProject
- {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
- {FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
+ {FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
+ {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjmedia_test", "pjmedia\build\pjmedia_test.vcproj", "{21C0CECF-69DD-4F70-BC2B-9B4DE7F15277}"
ProjectSection(ProjectDependencies) = postProject
- {E53AA5FF-B737-40AA-BD13-387EFA99023D} = {E53AA5FF-B737-40AA-BD13-387EFA99023D}
- {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
- {DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
- {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
- {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
- {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
- {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
- {6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
- {FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
- {4BF51C21-5A30-423B-82FE-1ED410E5769D} = {4BF51C21-5A30-423B-82FE-1ED410E5769D}
+ {4281CA5E-1D48-45D4-A991-2718A454B4BA} = {4281CA5E-1D48-45D4-A991-2718A454B4BA}
{F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858} = {F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858}
+ {4BF51C21-5A30-423B-82FE-1ED410E5769D} = {4BF51C21-5A30-423B-82FE-1ED410E5769D}
+ {FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
+ {6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
+ {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
+ {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
+ {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
+ {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
+ {DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
+ {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
+ {E53AA5FF-B737-40AA-BD13-387EFA99023D} = {E53AA5FF-B737-40AA-BD13-387EFA99023D}
EndProjectSection
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjmedia_audiodev", "pjmedia\build\pjmedia_audiodev.vcproj", "{4281CA5E-1D48-45D4-A991-2718A454B4BA}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
@@ -385,6 +390,12 @@ Global
{21C0CECF-69DD-4F70-BC2B-9B4DE7F15277}.Release|Windows Mobile 6 Standard SDK (ARMV4I).ActiveCfg = Release|Windows Mobile 6 Standard SDK (ARMV4I)
{21C0CECF-69DD-4F70-BC2B-9B4DE7F15277}.Release|Windows Mobile 6 Standard SDK (ARMV4I).Build.0 = Release|Windows Mobile 6 Standard SDK (ARMV4I)
{21C0CECF-69DD-4F70-BC2B-9B4DE7F15277}.Release|Windows Mobile 6 Standard SDK (ARMV4I).Deploy.0 = Release|Windows Mobile 6 Standard SDK (ARMV4I)
+ {4281CA5E-1D48-45D4-A991-2718A454B4BA}.Debug|Win32.ActiveCfg = Debug|Win32
+ {4281CA5E-1D48-45D4-A991-2718A454B4BA}.Debug|Win32.Build.0 = Debug|Win32
+ {4281CA5E-1D48-45D4-A991-2718A454B4BA}.Debug|Windows Mobile 6 Standard SDK (ARMV4I).ActiveCfg = Debug|Win32
+ {4281CA5E-1D48-45D4-A991-2718A454B4BA}.Release|Win32.ActiveCfg = Release|Win32
+ {4281CA5E-1D48-45D4-A991-2718A454B4BA}.Release|Win32.Build.0 = Release|Win32
+ {4281CA5E-1D48-45D4-A991-2718A454B4BA}.Release|Windows Mobile 6 Standard SDK (ARMV4I).ActiveCfg = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/pjproject.dsw b/pjproject.dsw
index fe54cff7..55fea29d 100644
--- a/pjproject.dsw
+++ b/pjproject.dsw
@@ -3,7 +3,7 @@ Microsoft Developer Studio Workspace File, Format Version 6.00
###############################################################################
-Project: "libgsmcodec"=.\THIRD_PARTY\BUILD\GSM\libgsmcodec.dsp - Package Owner=<4>
+Project: "libgsmcodec"=".\THIRD_PARTY\BUILD\GSM\libgsmcodec.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -15,7 +15,7 @@ Package=<4>
###############################################################################
-Project: "libilbccodec"=.\THIRD_PARTY\BUILD\ILBC\libilbccodec.dsp - Package Owner=<4>
+Project: "libilbccodec"=".\THIRD_PARTY\BUILD\ILBC\libilbccodec.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -27,7 +27,7 @@ Package=<4>
###############################################################################
-Project: "libmilenage"=.\third_party\build\milenage\libmilenage.dsp - Package Owner=<4>
+Project: "libmilenage"=".\third_party\build\milenage\libmilenage.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -39,7 +39,7 @@ Package=<4>
###############################################################################
-Project: "libportaudio"=.\THIRD_PARTY\BUILD\PORTAUDIO\libportaudio.dsp - Package Owner=<4>
+Project: "libportaudio"=".\THIRD_PARTY\BUILD\PORTAUDIO\libportaudio.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -51,7 +51,7 @@ Package=<4>
###############################################################################
-Project: "libresample"=.\THIRD_PARTY\BUILD\RESAMPLE\libresample.dsp - Package Owner=<4>
+Project: "libresample"=".\THIRD_PARTY\BUILD\RESAMPLE\libresample.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -63,7 +63,7 @@ Package=<4>
###############################################################################
-Project: "libresample_dll"=.\THIRD_PARTY\BUILD\RESAMPLE\libresample_dll.dsp - Package Owner=<4>
+Project: "libresample_dll"=".\THIRD_PARTY\BUILD\RESAMPLE\libresample_dll.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -75,7 +75,7 @@ Package=<4>
###############################################################################
-Project: "libspeex"=.\third_party\build\speex\libspeex.dsp - Package Owner=<4>
+Project: "libspeex"=".\third_party\build\speex\libspeex.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -87,7 +87,7 @@ Package=<4>
###############################################################################
-Project: "libsrtp"=.\third_party\build\srtp\libsrtp.dsp - Package Owner=<4>
+Project: "libsrtp"=".\third_party\build\srtp\libsrtp.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -99,7 +99,7 @@ Package=<4>
###############################################################################
-Project: "pjlib"=.\pjlib\build\pjlib.dsp - Package Owner=<4>
+Project: "pjlib"=".\pjlib\build\pjlib.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -111,7 +111,7 @@ Package=<4>
###############################################################################
-Project: "pjlib_test"=.\pjlib\build\pjlib_test.dsp - Package Owner=<4>
+Project: "pjlib_test"=".\pjlib\build\pjlib_test.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -156,7 +156,7 @@ Package=<4>
###############################################################################
-Project: "pjmedia"=.\pjmedia\build\pjmedia.dsp - Package Owner=<4>
+Project: "pjmedia"=".\pjmedia\build\pjmedia.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -168,7 +168,7 @@ Package=<4>
###############################################################################
-Project: "pjmedia_codec"=.\pjmedia\build\pjmedia_codec.dsp - Package Owner=<4>
+Project: "pjmedia_audiodev"=".\pjmedia\build\pjmedia_audiodev.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -180,7 +180,19 @@ Package=<4>
###############################################################################
-Project: "pjmedia_test"=.\pjmedia\build\pjmedia_test.dsp - Package Owner=<4>
+Project: "pjmedia_codec"=".\pjmedia\build\pjmedia_codec.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjmedia_test"=".\pjmedia\build\pjmedia_test.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -221,11 +233,14 @@ Package=<4>
Begin Project Dependency
Project_Dep_Name pjnath
End Project Dependency
+ Begin Project Dependency
+ Project_Dep_Name pjmedia_audiodev
+ End Project Dependency
}}}
###############################################################################
-Project: "pjnath"=.\pjnath\build\pjnath.dsp - Package Owner=<4>
+Project: "pjnath"=".\pjnath\build\pjnath.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -237,7 +252,7 @@ Package=<4>
###############################################################################
-Project: "pjnath_test"=.\pjnath\build\pjnath_test.dsp - Package Owner=<4>
+Project: "pjnath_test"=".\pjnath\build\pjnath_test.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -258,7 +273,7 @@ Package=<4>
###############################################################################
-Project: "pjsip_core"=.\pjsip\build\pjsip_core.dsp - Package Owner=<4>
+Project: "pjsip_core"=".\pjsip\build\pjsip_core.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -270,7 +285,7 @@ Package=<4>
###############################################################################
-Project: "pjsip_simple"=.\pjsip\build\pjsip_simple.dsp - Package Owner=<4>
+Project: "pjsip_simple"=".\pjsip\build\pjsip_simple.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -282,7 +297,7 @@ Package=<4>
###############################################################################
-Project: "pjsip_ua"=.\pjsip\build\pjsip_ua.dsp - Package Owner=<4>
+Project: "pjsip_ua"=".\pjsip\build\pjsip_ua.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -350,11 +365,14 @@ Package=<4>
Begin Project Dependency
Project_Dep_Name libsrtp
End Project Dependency
+ Begin Project Dependency
+ Project_Dep_Name pjmedia_audiodev
+ End Project Dependency
}}}
###############################################################################
-Project: "pjsua_lib"=.\pjsip\build\pjsua_lib.dsp - Package Owner=<4>
+Project: "pjsua_lib"=".\pjsip\build\pjsua_lib.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -366,7 +384,7 @@ Package=<4>
###############################################################################
-Project: "pjturn_client"=.\pjnath\build\pjturn_client.dsp - Package Owner=<4>
+Project: "pjturn_client"=".\pjnath\build\pjturn_client.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -387,7 +405,7 @@ Package=<4>
###############################################################################
-Project: "pjturn_srv"=.\pjnath\build\pjturn_srv.dsp - Package Owner=<4>
+Project: "pjturn_srv"=".\pjnath\build\pjturn_srv.dsp" - Package Owner=<4>
Package=<5>
{{{
@@ -438,9 +456,6 @@ Package=<4>
Project_Dep_Name pjsip_ua
End Project Dependency
Begin Project Dependency
- Project_Dep_Name pjsua_lib
- End Project Dependency
- Begin Project Dependency
Project_Dep_Name libgsmcodec
End Project Dependency
Begin Project Dependency
@@ -461,6 +476,9 @@ Package=<4>
Begin Project Dependency
Project_Dep_Name libsrtp
End Project Dependency
+ Begin Project Dependency
+ Project_Dep_Name pjmedia_audiodev
+ End Project Dependency
}}}
###############################################################################
@@ -518,11 +536,14 @@ Package=<4>
Begin Project Dependency
Project_Dep_Name libsrtp
End Project Dependency
+ Begin Project Dependency
+ Project_Dep_Name pjmedia_audiodev
+ End Project Dependency
}}}
###############################################################################
-Project: "test_pjsip"=.\pjsip\build\test_pjsip.dsp - Package Owner=<4>
+Project: "test_pjsip"=".\pjsip\build\test_pjsip.dsp" - Package Owner=<4>
Package=<5>
{{{
diff --git a/pjsip-apps/build/Makefile b/pjsip-apps/build/Makefile
index ac004c58..56e540fb 100644
--- a/pjsip-apps/build/Makefile
+++ b/pjsip-apps/build/Makefile
@@ -8,6 +8,7 @@ PJLIB_LIB:=../../pjlib/lib/libpj-$(TARGET_NAME)$(LIBEXT)
PJLIB_UTIL_LIB:=../../pjlib-util/lib/libpjlib-util-$(TARGET_NAME)$(LIBEXT)
PJNATH_LIB:=../../pjnath/lib/libpjnath-$(TARGET_NAME)$(LIBEXT)
PJMEDIA_LIB:=../../pjmedia/lib/libpjmedia-$(TARGET_NAME)$(LIBEXT)
+PJMEDIA_AUDIODEV_LIB:=../../pjmedia/lib/libpjmedia-audiodev-$(TARGET_NAME)$(LIBEXT)
PJMEDIA_CODEC_LIB:=../../pjmedia/lib/libpjmedia-codec-$(TARGET_NAME)$(LIBEXT)
PJSIP_LIB:=../../pjsip/lib/libpjsip-$(TARGET_NAME)$(LIBEXT)
PJSIP_UA_LIB:=../../pjsip/lib/libpjsip-ua-$(TARGET_NAME)$(LIBEXT)
diff --git a/pjsip-apps/build/Samples-vc.mak b/pjsip-apps/build/Samples-vc.mak
index 2eb861a9..f17d9925 100644
--- a/pjsip-apps/build/Samples-vc.mak
+++ b/pjsip-apps/build/Samples-vc.mak
@@ -13,6 +13,7 @@ PJLIB_UTIL_LIB = ..\..\pjlib-util\lib\pjlib-util-$(TARGET)$(LIBEXT)
PJNATH_LIB = ..\..\pjnath\lib\pjnath-$(TARGET)$(LIBEXT)
PJMEDIA_LIB = ..\..\pjmedia\lib\pjmedia-$(TARGET)$(LIBEXT)
PJMEDIA_CODEC_LIB = ..\..\pjmedia\lib\pjmedia-codec-$(TARGET)$(LIBEXT)
+PJMEDIA_AUDIODEV_LIB = ..\..\pjmedia\lib\pjmedia-audiodev-$(TARGET)$(LIBEXT)
PJSIP_LIB = ..\..\pjsip\lib\pjsip-core-$(TARGET)$(LIBEXT)
PJSIP_UA_LIB = ..\..\pjsip\lib\pjsip-ua-$(TARGET)$(LIBEXT)
PJSIP_SIMPLE_LIB = ..\..\pjsip\lib\pjsip-simple-$(TARGET)$(LIBEXT)
@@ -29,8 +30,8 @@ THIRD_PARTY_LIBS = $(GSM_LIB) $(ILBC_LIB) $(PORTAUDIO_LIB) $(RESAMPLE_LIB) \
$(SPEEX_LIB) $(SRTP_LIB)
LIBS = $(PJSUA_LIB_LIB) $(PJSIP_UA_LIB) $(PJSIP_SIMPLE_LIB) \
- $(PJSIP_LIB) $(PJMEDIA_CODEC_LIB) $(PJMEDIA_LIB) $(PJNATH_LIB) \
- $(PJLIB_UTIL_LIB) $(PJLIB_LIB) \
+ $(PJSIP_LIB) $(PJMEDIA_CODEC_LIB) $(PJMEDIA_AUDIODEV_LIB) \
+ $(PJMEDIA_LIB) $(PJNATH_LIB) $(PJLIB_UTIL_LIB) $(PJLIB_LIB) \
$(THIRD_PARTY_LIBS)
CFLAGS = /DPJ_WIN32=1 /DPJ_M_I386=1 \
@@ -49,7 +50,8 @@ OBJDIR = .\output\samples-$(TARGET)
BINDIR = ..\bin\samples
-SAMPLES = $(BINDIR)\confsample.exe \
+SAMPLES = $(BINDIR)\auddemo.exe \
+ $(BINDIR)\confsample.exe \
$(BINDIR)\confbench.exe \
$(BINDIR)\encdec.exe \
$(BINDIR)\latency.exe \
@@ -65,8 +67,6 @@ SAMPLES = $(BINDIR)\confsample.exe \
$(BINDIR)\simple_pjsua.exe \
$(BINDIR)\siprtp.exe \
$(BINDIR)\sipstateless.exe \
- $(BINDIR)\sndinfo.exe \
- $(BINDIR)\sndtest.exe \
$(BINDIR)\stateful_proxy.exe \
$(BINDIR)\stateless_proxy.exe \
$(BINDIR)\stereotest.exe \
diff --git a/pjsip-apps/build/Samples.mak b/pjsip-apps/build/Samples.mak
index e70eb278..7a33bfab 100644
--- a/pjsip-apps/build/Samples.mak
+++ b/pjsip-apps/build/Samples.mak
@@ -13,7 +13,8 @@ SRCDIR := ../src/samples
OBJDIR := ./output/samples-$(TARGET_NAME)
BINDIR := ../bin/samples
-SAMPLES := confsample \
+SAMPLES := auddemo \
+ confsample \
encdec \
latency \
level \
@@ -28,8 +29,6 @@ SAMPLES := confsample \
simple_pjsua \
siprtp \
sipstateless \
- sndinfo \
- sndtest \
stateful_proxy \
stateless_proxy \
stereotest \
diff --git a/pjsip-apps/build/sample_debug.dsp b/pjsip-apps/build/sample_debug.dsp
index c788f774..03c49d16 100644
--- a/pjsip-apps/build/sample_debug.dsp
+++ b/pjsip-apps/build/sample_debug.dsp
@@ -90,13 +90,6 @@ LINK32=link.exe
# Begin Source File
SOURCE=..\src\samples\debug.c
-
-!IF "$(CFG)" == "sample_debug - Win32 Release"
-
-!ELSEIF "$(CFG)" == "sample_debug - Win32 Debug"
-
-!ENDIF
-
# End Source File
# End Group
# Begin Group "Header Files"
diff --git a/pjsip-apps/build/samples.dsp b/pjsip-apps/build/samples.dsp
index 1305397e..d76f1a00 100644
--- a/pjsip-apps/build/samples.dsp
+++ b/pjsip-apps/build/samples.dsp
@@ -90,6 +90,10 @@ SOURCE=..\src\samples\aectest.c
# End Source File
# Begin Source File
+SOURCE=..\src\samples\auddemo.c
+# End Source File
+# Begin Source File
+
SOURCE=..\src\samples\confbench.c
# End Source File
# Begin Source File
diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c
index e7f3390e..5944043f 100644
--- a/pjsip-apps/src/pjsua/pjsua_app.c
+++ b/pjsip-apps/src/pjsua/pjsua_app.c
@@ -4158,6 +4158,12 @@ pj_status_t app_init(int argc, char *argv[])
app_config.cfg.cb.on_call_replaced = &on_call_replaced;
app_config.cfg.cb.on_nat_detect = &on_nat_detect;
+ /* Set sound device latency */
+ if (app_config.capture_lat > 0)
+ app_config.media_cfg.snd_rec_latency = app_config.capture_lat;
+ if (app_config.playback_lat)
+ app_config.media_cfg.snd_play_latency = app_config.playback_lat;
+
/* Initialize pjsua */
status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
&app_config.media_cfg);
@@ -4423,9 +4429,6 @@ pj_status_t app_init(int argc, char *argv[])
if (status != PJ_SUCCESS)
goto on_error;
- /* Set sound device latency */
- pjmedia_snd_set_latency(app_config.capture_lat, app_config.playback_lat);
-
/* Use null sound device? */
#ifndef STEREO_DEMO
if (app_config.null_audio) {
diff --git a/pjsip-apps/src/samples/auddemo.c b/pjsip-apps/src/samples/auddemo.c
new file mode 100644
index 00000000..e805f176
--- /dev/null
+++ b/pjsip-apps/src/samples/auddemo.c
@@ -0,0 +1,544 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjmedia-audiodev/audiodev.h>
+#include <pjmedia-audiodev/audiotest.h>
+#include <pjmedia.h>
+#include <pjlib.h>
+#include <pjlib-util.h>
+
+#define THIS_FILE "auddemo.c"
+#define MAX_DEVICES 64
+#define WAV_FILE "auddemo.wav"
+
+
+static unsigned dev_count;
+
+static void app_perror(const char *title, pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ printf( "%s: %s (err=%d)\n",
+ title, errmsg, status);
+}
+
+static void list_devices(void)
+{
+ unsigned i;
+ pj_status_t status;
+
+ dev_count = pjmedia_aud_dev_count();
+ if (dev_count == 0) {
+ PJ_LOG(3,(THIS_FILE, "No devices found"));
+ return;
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Found %d devices:", dev_count));
+
+ for (i=0; i<dev_count; ++i) {
+ pjmedia_aud_dev_info info;
+
+ status = pjmedia_aud_dev_get_info(i, &info);
+ if (status != PJ_SUCCESS)
+ continue;
+
+ PJ_LOG(3,(THIS_FILE," %2d: %s [%s] (%d/%d)",
+ i, info.driver, info.name, info.input_count, info.output_count));
+ }
+}
+
+static const char *decode_caps(unsigned caps)
+{
+ static char text[200];
+ unsigned i;
+
+ text[0] = '\0';
+
+ for (i=0; i<31; ++i) {
+ if ((1 << i) & caps) {
+ const char *capname;
+ capname = pjmedia_aud_dev_cap_name((pjmedia_aud_dev_cap)(1 << i),
+ NULL);
+ strcat(text, capname);
+ strcat(text, " ");
+ }
+ }
+
+ return text;
+}
+
+static void show_dev_info(unsigned index)
+{
+#define H "%-20s"
+ pjmedia_aud_dev_info info;
+ char formats[200];
+ pj_status_t status;
+
+ if (index >= dev_count) {
+ PJ_LOG(1,(THIS_FILE, "Error: invalid index %u", index));
+ return;
+ }
+
+ status = pjmedia_aud_dev_get_info(index, &info);
+ if (status != PJ_SUCCESS) {
+ app_perror("pjmedia_aud_dev_get_info() error", status);
+ return;
+ }
+
+ PJ_LOG(3, (THIS_FILE, "Device at index %u:", index));
+ PJ_LOG(3, (THIS_FILE, "-------------------------"));
+
+ PJ_LOG(3, (THIS_FILE, H": %u (0x%x)", "ID", index, index));
+ PJ_LOG(3, (THIS_FILE, H": %s", "Name", info.name));
+ PJ_LOG(3, (THIS_FILE, H": %s", "Driver", info.driver));
+ PJ_LOG(3, (THIS_FILE, H": %u", "Input channels", info.input_count));
+ PJ_LOG(3, (THIS_FILE, H": %u", "Output channels", info.output_count));
+ PJ_LOG(3, (THIS_FILE, H": %s", "Capabilities", decode_caps(info.caps)));
+
+ formats[0] = '\0';
+ if (info.caps & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {
+ unsigned i;
+
+ for (i=0; i<info.ext_fmt_cnt; ++i) {
+ char bitrate[32];
+
+ switch (info.ext_fmt[i].id) {
+ case PJMEDIA_FORMAT_L16:
+ strcat(formats, "L16/");
+ break;
+ case PJMEDIA_FORMAT_PCMA:
+ strcat(formats, "PCMA/");
+ break;
+ case PJMEDIA_FORMAT_PCMU:
+ strcat(formats, "PCMU/");
+ break;
+ case PJMEDIA_FORMAT_AMR:
+ strcat(formats, "AMR/");
+ break;
+ case PJMEDIA_FORMAT_G729:
+ strcat(formats, "G729/");
+ break;
+ case PJMEDIA_FORMAT_ILBC:
+ strcat(formats, "ILBC/");
+ break;
+ default:
+ strcat(formats, "unknown/");
+ break;
+ }
+ sprintf(bitrate, "%u", info.ext_fmt[i].bitrate);
+ strcat(formats, bitrate);
+ strcat(formats, " ");
+ }
+ }
+ PJ_LOG(3, (THIS_FILE, H": %s", "Extended formats", formats));
+
+#undef H
+}
+
+static void test_device(pjmedia_dir dir, unsigned rec_id, unsigned play_id,
+ unsigned clock_rate, unsigned ptime,
+ unsigned chnum)
+{
+ pjmedia_aud_param param;
+ pjmedia_aud_test_results result;
+ pj_status_t status;
+
+ if (dir & PJMEDIA_DIR_CAPTURE) {
+ status = pjmedia_aud_dev_default_param(rec_id, &param);
+ } else {
+ status = pjmedia_aud_dev_default_param(play_id, &param);
+ }
+
+ if (status != PJ_SUCCESS) {
+ app_perror("pjmedia_aud_dev_default_param()", status);
+ return;
+ }
+
+ param.dir = dir;
+ param.rec_id = rec_id;
+ param.play_id = play_id;
+ param.clock_rate = clock_rate;
+ param.channel_count = chnum;
+ param.samples_per_frame = clock_rate * chnum * ptime / 1000;
+
+ PJ_LOG(3,(THIS_FILE, "Performing test.."));
+
+ status = pjmedia_aud_test(&param, &result);
+ if (status != PJ_SUCCESS) {
+ app_perror("Test has completed with error", status);
+ return;
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Done. Result:"));
+
+ if (dir & PJMEDIA_DIR_CAPTURE) {
+ if (result.rec.frame_cnt==0) {
+ PJ_LOG(1,(THIS_FILE, "Error: no frames captured!"));
+ } else {
+ PJ_LOG(3,(THIS_FILE, " %-20s: interval (min/max/avg/dev)=%u/%u/%u/%u, burst=%u",
+ "Recording result",
+ result.rec.min_interval,
+ result.rec.max_interval,
+ result.rec.avg_interval,
+ result.rec.dev_interval,
+ result.rec.max_burst));
+ }
+ }
+
+ if (dir & PJMEDIA_DIR_PLAYBACK) {
+ if (result.play.frame_cnt==0) {
+ PJ_LOG(1,(THIS_FILE, "Error: no playback!"));
+ } else {
+ PJ_LOG(3,(THIS_FILE, " %-20s: interval (min/max/avg/dev)=%u/%u/%u/%u, burst=%u",
+ "Playback result",
+ result.play.min_interval,
+ result.play.max_interval,
+ result.play.avg_interval,
+ result.play.dev_interval,
+ result.play.max_burst));
+ }
+ }
+
+ if (dir==PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+ if (result.rec_drift_per_sec == 0) {
+ PJ_LOG(3,(THIS_FILE, " No clock drift detected"));
+ } else {
+ const char *which = result.rec_drift_per_sec>=0 ? "faster" : "slower";
+ unsigned drift = result.rec_drift_per_sec>=0 ?
+ result.rec_drift_per_sec :
+ -result.rec_drift_per_sec;
+
+ PJ_LOG(3,(THIS_FILE, " Clock drifts detected. Capture device "
+ "is running %d samples per second %s "
+ "than the playback device",
+ drift, which));
+ }
+ }
+}
+
+
+static pj_status_t wav_rec_cb(void *user_data, pjmedia_frame *frame)
+{
+ return pjmedia_port_put_frame((pjmedia_port*)user_data, frame);
+}
+
+static void record(unsigned rec_index, const char *filename)
+{
+ pj_pool_t *pool = NULL;
+ pjmedia_port *wav = NULL;
+ pjmedia_aud_param param;
+ pjmedia_aud_stream *strm = NULL;
+ char line[10];
+ pj_status_t status;
+
+ if (filename == NULL)
+ filename = WAV_FILE;
+
+ pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(), "wav",
+ 1000, 1000, NULL);
+
+ status = pjmedia_wav_writer_port_create(pool, filename, 16000,
+ 1, 320, 16, 0, 0, &wav);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error creating WAV file", status);
+ goto on_return;
+ }
+
+ status = pjmedia_aud_dev_default_param(rec_index, &param);
+ if (status != PJ_SUCCESS) {
+ app_perror("pjmedia_aud_dev_default_param()", status);
+ goto on_return;
+ }
+
+ param.dir = PJMEDIA_DIR_CAPTURE;
+ param.clock_rate = wav->info.clock_rate;
+ param.samples_per_frame = wav->info.samples_per_frame;
+ param.channel_count = wav->info.channel_count;
+ param.bits_per_sample = wav->info.bits_per_sample;
+
+ status = pjmedia_aud_stream_create(&param, &wav_rec_cb, NULL, wav,
+ &strm);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error opening the sound device", status);
+ goto on_return;
+ }
+
+ status = pjmedia_aud_stream_start(strm);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error starting the sound device", status);
+ goto on_return;
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Recording started, press ENTER to stop"));
+ fgets(line, sizeof(line), stdin);
+
+on_return:
+ if (strm) {
+ pjmedia_aud_stream_stop(strm);
+ pjmedia_aud_stream_destroy(strm);
+ }
+ if (wav)
+ pjmedia_port_destroy(wav);
+ if (pool)
+ pj_pool_release(pool);
+}
+
+
+static pj_status_t wav_play_cb(void *user_data, pjmedia_frame *frame)
+{
+ return pjmedia_port_get_frame((pjmedia_port*)user_data, frame);
+}
+
+
+static void play_file(unsigned play_index, const char *filename)
+{
+ pj_pool_t *pool = NULL;
+ pjmedia_port *wav = NULL;
+ pjmedia_aud_param param;
+ pjmedia_aud_stream *strm = NULL;
+ char line[10];
+ pj_status_t status;
+
+ if (filename == NULL)
+ filename = WAV_FILE;
+
+ pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(), "wav",
+ 1000, 1000, NULL);
+
+ status = pjmedia_wav_player_port_create(pool, filename, 20, 0, 0, &wav);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error opening WAV file", status);
+ goto on_return;
+ }
+
+ status = pjmedia_aud_dev_default_param(play_index, &param);
+ if (status != PJ_SUCCESS) {
+ app_perror("pjmedia_aud_dev_default_param()", status);
+ goto on_return;
+ }
+
+ param.dir = PJMEDIA_DIR_PLAYBACK;
+ param.clock_rate = wav->info.clock_rate;
+ param.samples_per_frame = wav->info.samples_per_frame;
+ param.channel_count = wav->info.channel_count;
+ param.bits_per_sample = wav->info.bits_per_sample;
+
+ status = pjmedia_aud_stream_create(&param, NULL, &wav_play_cb, wav,
+ &strm);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error opening the sound device", status);
+ goto on_return;
+ }
+
+ status = pjmedia_aud_stream_start(strm);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error starting the sound device", status);
+ goto on_return;
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Playback started, press ENTER to stop"));
+ fgets(line, sizeof(line), stdin);
+
+on_return:
+ if (strm) {
+ pjmedia_aud_stream_stop(strm);
+ pjmedia_aud_stream_destroy(strm);
+ }
+ if (wav)
+ pjmedia_port_destroy(wav);
+ if (pool)
+ pj_pool_release(pool);
+}
+
+
+static void print_menu(void)
+{
+ puts("");
+ puts("Audio demo menu:");
+ puts("-------------------------------");
+ puts(" l List devices");
+ puts(" i ID Show device info for device ID");
+ puts(" t RID PID CR PTIM [CH] Perform test on the device:");
+ puts(" RID: record device ID (-1 for no)");
+ puts(" PID: playback device ID (-1 for no)");
+ puts(" CR: clock rate");
+ puts(" PTIM: ptime in ms");
+ puts(" CH: # of channels");
+ puts(" r RID [FILE] Record capture device RID to WAV file");
+ puts(" p PID [FILE] Playback WAV file to device ID PID");
+ puts(" v Toggle log verbosity");
+ puts(" q Quit");
+ puts("");
+ printf("Enter selection: ");
+ fflush(stdout);
+}
+
+int main()
+{
+ pj_caching_pool cp;
+ pj_bool_t done = PJ_FALSE;
+ pj_status_t status;
+
+ /* Init pjlib */
+ status = pj_init();
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
+
+ pj_log_set_decor(PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_COLOR);
+
+ /* Must create a pool factory before we can allocate any memory. */
+ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+
+ status = pjmedia_aud_subsys_init(&cp.factory);
+ if (status != PJ_SUCCESS) {
+ app_perror("pjmedia_aud_subsys_init()", status);
+ pj_caching_pool_destroy(&cp);
+ pj_shutdown();
+ return 1;
+ }
+
+ list_devices();
+
+ while (!done) {
+ char line[80];
+
+ print_menu();
+
+ if (fgets(line, sizeof(line), stdin)==NULL)
+ break;
+
+ switch (line[0]) {
+ case 'l':
+ list_devices();
+ break;
+
+ case 'i':
+ {
+ unsigned dev_index;
+ if (sscanf(line+2, "%u", &dev_index) != 1) {
+ puts("error: device ID required");
+ break;
+ }
+ show_dev_info(dev_index);
+ }
+ break;
+
+ case 't':
+ {
+ pjmedia_dir dir;
+ int rec_id, play_id;
+ unsigned clock_rate, ptime, chnum;
+ int cnt;
+
+ cnt = sscanf(line+2, "%d %d %u %u %u", &rec_id, &play_id,
+ &clock_rate, &ptime, &chnum);
+ if (cnt < 4) {
+ puts("error: not enough parameters");
+ break;
+ }
+ if (clock_rate < 8000 || clock_rate > 128000) {
+ puts("error: invalid clock rate");
+ break;
+ }
+ if (ptime < 10 || ptime > 500) {
+ puts("error: invalid ptime");
+ break;
+ }
+ if (cnt==5) {
+ if (chnum < 1 || chnum > 4) {
+ puts("error: invalid number of channels");
+ break;
+ }
+ } else {
+ chnum = 1;
+ }
+
+ if (rec_id >= 0 && rec_id < (int)dev_count) {
+ if (play_id >= 0 && play_id < (int)dev_count)
+ dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ else
+ dir = PJMEDIA_DIR_CAPTURE;
+ } else if (play_id >= 0 && play_id < (int)dev_count) {
+ dir = PJMEDIA_DIR_PLAYBACK;
+ } else {
+ puts("error: at least one valid device index required");
+ break;
+ }
+
+ test_device(dir, rec_id, play_id, clock_rate, ptime, chnum);
+
+ }
+ break;
+
+ case 'r':
+ /* record */
+ {
+ int index;
+ char filename[80];
+ int count;
+
+ count = sscanf(line+2, "%d %s", &index, filename);
+ if (count==1)
+ record(index, NULL);
+ else if (count==2)
+ record(index, filename);
+ else
+ puts("error: invalid command syntax");
+ }
+ break;
+
+ case 'p':
+ /* playback */
+ {
+ int index;
+ char filename[80];
+ int count;
+
+ count = sscanf(line+2, "%d %s", &index, filename);
+ if (count==1)
+ play_file(index, NULL);
+ else if (count==2)
+ play_file(index, filename);
+ else
+ puts("error: invalid command syntax");
+ }
+ break;
+
+ case 'v':
+ if (pj_log_get_level() <= 3) {
+ pj_log_set_level(5);
+ puts("Logging set to detail");
+ } else {
+ pj_log_set_level(3);
+ puts("Logging set to quiet");
+ }
+ break;
+
+ case 'q':
+ done = PJ_TRUE;
+ break;
+ }
+ }
+
+ pj_caching_pool_destroy(&cp);
+ pj_shutdown();
+ return 0;
+}
+
+
diff --git a/pjsip-apps/src/samples/debug.c b/pjsip-apps/src/samples/debug.c
index c7453d93..1a145b9a 100644
--- a/pjsip-apps/src/samples/debug.c
+++ b/pjsip-apps/src/samples/debug.c
@@ -28,5 +28,5 @@
* E.g.:
* #include "playfile.c"
*/
-#include "aectest.c"
+#include "auddemo.c"
diff --git a/pjsip-apps/src/samples/sndinfo.c b/pjsip-apps/src/samples/sndinfo.c
deleted file mode 100644
index 9f282d1a..00000000
--- a/pjsip-apps/src/samples/sndinfo.c
+++ /dev/null
@@ -1,295 +0,0 @@
-/* $Id$ */
-/*
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
- * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
- *
- * 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 2 of the License, or
- * (at your option) 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-static const char *desc =
- " sndinfo.c \n"
- " \n"
- " PURPOSE: \n"
- " Print sound device info and test open device. \n"
- " \n"
- " USAGE: \n"
- " sndinfo [id rec/play/both clockrate nchan bits] \n"
- " \n"
- " DESCRIPTION: \n"
- " When invoked without any arguments, it displays information about all \n"
- " sound devices in the system. \n"
- " \n"
- " When invoked with arguments, the program tests if device can be opened \n"
- " with the specified arguments. All these arguments must be specified: \n"
- " - id The device ID (-1 for the first capable device) \n"
- " - rec/play/both Specify which streams to open. \n"
- " - clockrate Specify clock rate (e.g. 8000, 11025, etc.) \n"
- " - nchan Number of channels (1=mono, 2=stereo). \n"
- " - bits Number of bits per sample (normally 16). \n";
-
-#include <pjmedia.h>
-#include <pjlib.h>
-
-#include <stdlib.h> /* atoi() */
-#include <stdio.h>
-
-
-static void enum_devices(void)
-{
- int i, count;
-
- count = pjmedia_snd_get_dev_count();
- if (count == 0) {
- puts("No devices found");
- return;
- }
-
- for (i=0; i<count; ++i) {
- const pjmedia_snd_dev_info *info;
-
- info = pjmedia_snd_get_dev_info(i);
- pj_assert(info != NULL);
-
- printf( "Device #%02d: \n"
- " Name : %s\n"
- " # of input channels : %d\n"
- " # of output channels: %d\n"
- " Default clock rate : %d Hz\n\n",
- i, info->name, info->input_count, info->output_count,
- info->default_samples_per_sec);
- }
- puts("");
- puts("Run with -h to get more options");
-}
-
-static unsigned clock_rate;
-static unsigned play_counter;
-static unsigned rec_counter;
-static unsigned min_delay = 0xFFFF, max_delay;
-static char play_delays[1000];
-static pj_uint32_t last_play_timestamp, last_rec_timestamp;
-
-static pj_status_t play_cb(void *user_data, pj_uint32_t timestamp,
- void *output, unsigned size)
-{
- static pj_timestamp last_cb;
-
-
- PJ_UNUSED_ARG(user_data);
- PJ_UNUSED_ARG(output);
- PJ_UNUSED_ARG(size);
-
-
- ++play_counter;
- last_play_timestamp = timestamp;
-
- if (last_cb.u64 == 0) {
- pj_get_timestamp(&last_cb);
- } else if (play_counter <= PJ_ARRAY_SIZE(play_delays)) {
- pj_timestamp now;
- unsigned delay;
-
- pj_get_timestamp(&now);
-
- delay = pj_elapsed_msec(&last_cb, &now);
- if (delay < min_delay)
- min_delay = delay;
- if (delay > max_delay)
- max_delay = delay;
-
- last_cb = now;
-
- play_delays[play_counter-1] = (char)delay;
- }
-
- return PJ_SUCCESS;
-}
-
-static pj_status_t rec_cb(void *user_data, pj_uint32_t timestamp,
- void *input, unsigned size)
-{
-
- PJ_UNUSED_ARG(size);
- PJ_UNUSED_ARG(input);
- PJ_UNUSED_ARG(user_data);
-
-
- ++rec_counter;
-
- if (timestamp - last_rec_timestamp >= clock_rate && last_play_timestamp) {
- int diff;
- diff = last_play_timestamp - timestamp;
- printf("Play timestamp=%u, capture timestamp=%u, diff=%d\n",
- last_play_timestamp, timestamp, diff);
- last_rec_timestamp = timestamp;
- }
- return PJ_SUCCESS;
-}
-
-static void app_perror(const char *title, pj_status_t status)
-{
- char errmsg[PJ_ERR_MSG_SIZE];
-
- pj_strerror(status, errmsg, sizeof(errmsg));
- printf( "%s: %s (err=%d)\n",
- title, errmsg, status);
-}
-
-static int open_device(int dev_id, pjmedia_dir dir,
- int nchannel, int bits)
-{
- pj_status_t status = PJ_SUCCESS;
- unsigned nsamples;
- pjmedia_snd_stream *strm;
- const char *dirtype;
- char tmp[10];
- unsigned i;
-
- switch (dir) {
- case PJMEDIA_DIR_CAPTURE:
- dirtype = "capture"; break;
- case PJMEDIA_DIR_PLAYBACK:
- dirtype = "playback"; break;
- case PJMEDIA_DIR_CAPTURE_PLAYBACK:
- dirtype = "capture/playback"; break;
- default:
- return 1;
- }
-
- nsamples = clock_rate * 20 / 1000;
-
- printf( "Opening device %d for %s: clockrate=%d, nchannel=%d, "
- "bits=%d, nsamples=%d..\n",
- dev_id, dirtype, clock_rate, nchannel, bits, nsamples);
-
- if (dir == PJMEDIA_DIR_CAPTURE) {
- status = pjmedia_snd_open_rec( dev_id, clock_rate, nchannel,
- nsamples, bits, &rec_cb, NULL,
- &strm);
- } else if (dir == PJMEDIA_DIR_PLAYBACK) {
- status = pjmedia_snd_open_player( dev_id, clock_rate, nchannel,
- nsamples, bits, &play_cb, NULL,
- &strm);
- } else {
- status = pjmedia_snd_open( dev_id, dev_id, clock_rate, nchannel,
- nsamples, bits, &rec_cb, &play_cb, NULL,
- &strm);
- }
-
- if (status != PJ_SUCCESS) {
- app_perror("Unable to open device for capture", status);
- return 1;
- }
-
- status = pjmedia_snd_stream_start(strm);
- if (status != PJ_SUCCESS) {
- app_perror("Unable to start capture stream", status);
- return 1;
- }
-
- /* Let playback/capture runs for a while */
- //pj_thread_sleep(1000);
- puts("Press <ENTER> to stop");
- if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
- puts("EOF while reading stdin, will quit now..");
- }
-
- pjmedia_snd_stream_close(strm);
-
- if ((dir & PJMEDIA_DIR_CAPTURE) && rec_counter==0) {
- printf("Error: capture stream was not running\n");
- return 1;
- }
-
- if ((dir & PJMEDIA_DIR_PLAYBACK) && play_counter==0) {
- printf("Error: playback stream was not running\n");
- return 1;
- }
-
- puts("Success.");
-
- printf("Delay: ");
- for (i=0; i<play_counter; ++i)
- printf("%d ", play_delays[i]);
-
- puts("");
- if (dir & PJMEDIA_DIR_PLAYBACK) {
- printf("Callback interval: min interval=%d ms, max interval=%d ms\n",
- min_delay, max_delay);
- }
-
-
- return 0;
-}
-
-
-int main(int argc, char *argv[])
-{
- pj_caching_pool cp;
- pjmedia_endpt *med_endpt;
- pj_status_t status;
-
- /* Init pjlib */
- status = pj_init();
- PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
-
- /* Must create a pool factory before we can allocate any memory. */
- pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
-
- /*
- * Initialize media endpoint.
- * This will implicitly initialize PJMEDIA too.
- */
- status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
- PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
-
-
- if (argc == 1) {
- enum_devices();
- } else if (argc == 6) {
-
- int dev_id;
- pjmedia_dir dir = PJMEDIA_DIR_NONE;
- int nchannel;
- int bits;
-
- dev_id = atoi(argv[1]);
-
- if (strcmp(argv[2], "rec")==0)
- dir = PJMEDIA_DIR_CAPTURE;
- else if (strcmp(argv[2], "play")==0)
- dir = PJMEDIA_DIR_PLAYBACK;
- else if (strcmp(argv[2], "both")==0)
- dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
-
- clock_rate = atoi(argv[3]);
- nchannel = atoi(argv[4]);
- bits = atoi(argv[5]);
-
- return open_device(dev_id, dir, nchannel, bits);
-
- } else {
- puts("Error: invalid arguments");
- puts(desc);
- return 1;
- }
-
- /* Shutdown PJLIB */
- pj_shutdown();
-
- return 0;
-}
-
-
diff --git a/pjsip-apps/src/samples/sndtest.c b/pjsip-apps/src/samples/sndtest.c
deleted file mode 100644
index 007ed57d..00000000
--- a/pjsip-apps/src/samples/sndtest.c
+++ /dev/null
@@ -1,621 +0,0 @@
-/* $Id$ */
-/*
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
- * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
- *
- * 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 2 of the License, or
- * (at your option) 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-/**
- * \page page_pjmedia_samples_sndtest_c Samples: Sound Card Benchmark
- *
- * This example can be used to benchmark the quality of the sound card
- * installed in the system. At the end of the test, it will report
- * the jitter and clock drifts of the device.
- *
- * This file is pjsip-apps/src/samples/sndtest.c
- *
- * Screenshots on WinXP: \image html sndtest.jpg
- *
- * \includelineno sndtest.c
- */
-
-
-#include <pjmedia.h>
-#include <pjlib.h>
-#include <pjlib-util.h>
-
-#include <stdlib.h> /* atoi() */
-#include <stdio.h>
-
-
-
-#define THIS_FILE "sndtest.c"
-
-/* Warn (print log with yellow color) if frame jitter is larger than
- * this value (in usec).
- */
-#define WARN_JITTER_USEC 1000
-
-/* Test duration in msec */
-#define DURATION 10000
-
-/* Skip the first msec from the calculation */
-#define SKIP_DURATION 1000
-
-/* Max frames per sec (to calculate number of delays to keep). */
-#define MAX_FRAMES_PER_SEC 100
-
-/* Number of frame durations to keep */
-#define MAX_DELAY_COUNTER (((DURATION/1000)+1)*MAX_FRAMES_PER_SEC)
-
-
-struct stream_data
-{
- pj_uint32_t first_timestamp;
- pj_uint32_t last_timestamp;
- pj_timestamp last_called;
- unsigned counter;
- unsigned min_delay;
- unsigned max_delay;
- unsigned delay[MAX_DELAY_COUNTER];
-};
-
-struct test_data {
- pjmedia_dir dir;
- unsigned clock_rate;
- unsigned samples_per_frame;
- unsigned channel_count;
- pj_bool_t running;
- pj_bool_t has_error;
- pj_mutex_t *mutex;
-
- struct stream_data capture_data;
- struct stream_data playback_data;
-};
-
-
-
-static const char *desc =
- " sndtest.c \n"
- " \n"
- " PURPOSE: \n"
- " Test the performance of sound device. \n"
- " \n"
- " USAGE: \n"
- " sndtest --help \n"
- " sndtest [options] \n"
- " \n"
- " where options: \n"
- " --id=ID -i Use device ID (default is -1) \n"
- " --rate=HZ -r Set test clock rate (default=8000)\n"
- " --frame=SAMPLES -f Set number of samples per frame\n"
- " --channel=CH -n Set number of channels (default=1)\n"
- " --verbose -v Show verbose result \n"
- " --help -h Show this screen \n"
-;
-
-
-
-static void enum_devices(void)
-{
- int i, count;
-
- count = pjmedia_snd_get_dev_count();
- if (count == 0) {
- PJ_LOG(3,(THIS_FILE, "No devices found"));
- return;
- }
-
- PJ_LOG(3,(THIS_FILE, "Found %d devices:", count));
- for (i=0; i<count; ++i) {
- const pjmedia_snd_dev_info *info;
-
- info = pjmedia_snd_get_dev_info(i);
- pj_assert(info != NULL);
-
- PJ_LOG(3,(THIS_FILE," %d: %s (capture=%d, playback=%d)",
- i, info->name, info->input_count, info->output_count));
- }
-}
-
-
-static pj_status_t play_cb(void *user_data, pj_uint32_t timestamp,
- void *output, unsigned size)
-{
- struct test_data *test_data = user_data;
- struct stream_data *strm_data = &test_data->playback_data;
-
- pj_mutex_lock(test_data->mutex);
-
- /* Skip frames when test is not started or test has finished */
- if (!test_data->running) {
- pj_bzero(output, size);
-
- pj_mutex_unlock(test_data->mutex);
- return PJ_SUCCESS;
- }
-
- /* Save last timestamp seen (to calculate drift) */
- strm_data->last_timestamp = timestamp;
-
- if (strm_data->last_called.u64 == 0) {
- pj_get_timestamp(&strm_data->last_called);
- /* Init min_delay to one frame */
- strm_data->min_delay = test_data->samples_per_frame * 1000000 /
- test_data->clock_rate;
- strm_data->first_timestamp = timestamp;
-
- } else if (strm_data->counter <= MAX_DELAY_COUNTER) {
- pj_timestamp now;
- unsigned delay;
-
- pj_get_timestamp(&now);
-
- /* Calculate frame interval */
- delay = pj_elapsed_usec(&strm_data->last_called, &now);
- if (delay < strm_data->min_delay)
- strm_data->min_delay = delay;
- if (delay > strm_data->max_delay)
- strm_data->max_delay = delay;
-
- strm_data->last_called = now;
-
- /* Save the frame interval for later calculation */
- strm_data->delay[strm_data->counter] = delay;
- ++strm_data->counter;
-
- } else {
-
- /* No space, can't take anymore frames */
- test_data->running = 0;
-
- }
-
- pj_bzero(output, size);
-
- pj_mutex_unlock(test_data->mutex);
-
- return PJ_SUCCESS;
-}
-
-static pj_status_t rec_cb(void *user_data, pj_uint32_t timestamp,
- void *input, unsigned size)
-{
-
- struct test_data *test_data = user_data;
- struct stream_data *strm_data = &test_data->capture_data;
-
- pj_mutex_lock(test_data->mutex);
-
- PJ_UNUSED_ARG(input);
- PJ_UNUSED_ARG(size);
-
- /* Skip frames when test is not started or test has finished */
- if (!test_data->running) {
- pj_mutex_unlock(test_data->mutex);
- return PJ_SUCCESS;
- }
-
- /* Save last timestamp seen (to calculate drift) */
- strm_data->last_timestamp = timestamp;
-
- if (strm_data->last_called.u64 == 0) {
- pj_get_timestamp(&strm_data->last_called);
- /* Init min_delay to one frame */
- strm_data->min_delay = test_data->samples_per_frame * 1000000 /
- test_data->clock_rate;
- strm_data->first_timestamp = timestamp;
-
- } else if (strm_data->counter <= MAX_DELAY_COUNTER) {
- pj_timestamp now;
- unsigned delay;
-
- pj_get_timestamp(&now);
-
- /* Calculate frame interval */
- delay = pj_elapsed_usec(&strm_data->last_called, &now);
- if (delay < strm_data->min_delay)
- strm_data->min_delay = delay;
- if (delay > strm_data->max_delay)
- strm_data->max_delay = delay;
-
- strm_data->last_called = now;
-
- /* Save the frame interval for later calculation */
- strm_data->delay[strm_data->counter] = delay;
- ++strm_data->counter;
-
- } else {
-
- /* No space, can't take anymore frames */
- test_data->running = 0;
-
- }
-
- pj_mutex_unlock(test_data->mutex);
- return PJ_SUCCESS;
-}
-
-static void app_perror(const char *title, pj_status_t status)
-{
- char errmsg[PJ_ERR_MSG_SIZE];
-
- pj_strerror(status, errmsg, sizeof(errmsg));
- printf( "%s: %s (err=%d)\n",
- title, errmsg, status);
-}
-
-
-static void print_stream_data(const char *title,
- struct test_data *test_data,
- struct stream_data *strm_data,
- int verbose)
-{
- unsigned i, dur;
- int ptime;
- unsigned min_jitter, max_jitter, sum_jitter, avg_jitter=0;
-
- PJ_LOG(3,(THIS_FILE, " %s stream report:", title));
-
- /* Check that frames are captured/played */
- if (strm_data->counter == 0) {
- PJ_LOG(1,(THIS_FILE, " Error: no frames are captured/played!"));
- test_data->has_error = 1;
- return;
- }
-
- /* Duration */
- dur = (strm_data->counter+1) * test_data->samples_per_frame * 1000 /
- test_data->clock_rate;
- PJ_LOG(3,(THIS_FILE, " Duration: %ds.%03d",
- dur/1000, dur%1000));
-
- /* Frame interval */
- if (strm_data->max_delay - strm_data->min_delay < WARN_JITTER_USEC) {
- PJ_LOG(3,(THIS_FILE,
- " Frame interval: min=%d.%03dms, max=%d.%03dms",
- strm_data->min_delay/1000, strm_data->min_delay%1000,
- strm_data->max_delay/1000, strm_data->max_delay%1000));
- } else {
- test_data->has_error = 1;
- PJ_LOG(2,(THIS_FILE,
- " Frame interval: min=%d.%03dms, max=%d.%03dms",
- strm_data->min_delay/1000, strm_data->min_delay%1000,
- strm_data->max_delay/1000, strm_data->max_delay%1000));
- }
-
- if (verbose) {
- unsigned i;
- unsigned decor = pj_log_get_decor();
-
- PJ_LOG(3,(THIS_FILE, " Dumping frame delays:"));
-
- pj_log_set_decor(0);
- for (i=0; i<strm_data->counter; ++i)
- PJ_LOG(3,(THIS_FILE, " %d.%03d", strm_data->delay[i]/1000,
- strm_data->delay[i]%1000));
- PJ_LOG(3,(THIS_FILE, "\r\n"));
- pj_log_set_decor(decor);
- }
-
- /* Calculate frame ptime in usec */
- ptime = test_data->samples_per_frame * 1000000 /
- test_data->clock_rate;
-
- /* Calculate jitter */
- min_jitter = 0xFFFFF;
- max_jitter = 0;
- sum_jitter = 0;
-
- for (i=1; i<strm_data->counter; ++i) {
- int jitter1, jitter2, jitter;
-
- /* jitter1 is interarrival difference */
- jitter1 = strm_data->delay[i] - strm_data->delay[i-1];
- if (jitter1 < 0) jitter1 = -jitter1;
-
- /* jitter2 is difference between actual and scheduled arrival.
- * This is intended to capture situation when frames are coming
- * instantaneously, which will calculate as zero jitter with
- * jitter1 calculation.
- */
- jitter2 = ptime - strm_data->delay[i];
- if (jitter2 < 0) jitter2 = -jitter2;
-
- /* Set jitter as the maximum of the two jitter calculations.
- * This is intended to show the worst result.
- */
- jitter = (jitter1>jitter2) ? jitter1 : jitter2;
-
- /* Calculate min, max, avg jitter */
- if (jitter < (int)min_jitter) min_jitter = jitter;
- if (jitter > (int)max_jitter) max_jitter = jitter;
-
- sum_jitter += jitter;
- }
-
- avg_jitter = (sum_jitter) / (strm_data->counter - 1);
-
- if (max_jitter < WARN_JITTER_USEC) {
- PJ_LOG(3,(THIS_FILE,
- " Jitter: min=%d.%03dms, avg=%d.%03dms, max=%d.%03dms",
- min_jitter/1000, min_jitter%1000,
- avg_jitter/1000, avg_jitter%1000,
- max_jitter/1000, max_jitter%1000));
- } else {
- test_data->has_error = 1;
- PJ_LOG(2,(THIS_FILE,
- " Jitter: min=%d.%03dms, avg=%d.%03dms, max=%d.%03dms",
- min_jitter/1000, min_jitter%1000,
- avg_jitter/1000, avg_jitter%1000,
- max_jitter/1000, max_jitter%1000));
- }
-}
-
-
-static int perform_test(pj_pool_t *pool, int dev_id, pjmedia_dir dir,
- unsigned clock_rate, unsigned samples_per_frame,
- unsigned nchannel, int verbose)
-{
- pj_status_t status = PJ_SUCCESS;
- pjmedia_snd_stream *strm;
- struct test_data test_data;
- pjmedia_snd_stream_info si;
-
- /*
- * Init test parameters
- */
- pj_bzero(&test_data, sizeof(test_data));
- test_data.dir = dir;
- test_data.clock_rate = clock_rate;
- test_data.samples_per_frame = samples_per_frame;
- test_data.channel_count = nchannel;
-
- pj_mutex_create_simple(pool, "sndtest", &test_data.mutex);
-
- /*
- * Open device.
- */
- if (dir == PJMEDIA_DIR_CAPTURE) {
- status = pjmedia_snd_open_rec( dev_id, clock_rate, nchannel,
- samples_per_frame, 16, &rec_cb,
- &test_data, &strm);
- } else if (dir == PJMEDIA_DIR_PLAYBACK) {
- status = pjmedia_snd_open_player( dev_id, clock_rate, nchannel,
- samples_per_frame, 16, &play_cb,
- &test_data, &strm);
- } else {
- status = pjmedia_snd_open( dev_id, dev_id, clock_rate, nchannel,
- samples_per_frame, 16, &rec_cb, &play_cb,
- &test_data, &strm);
- }
-
- if (status != PJ_SUCCESS) {
- app_perror("Unable to open device for capture", status);
- return status;
- }
-
- pjmedia_snd_stream_get_info(strm, &si);
- if (si.play_id >= 0) {
- PJ_LOG(3,(THIS_FILE, "Testing playback device %s",
- pjmedia_snd_get_dev_info(si.play_id)->name));
- }
- if (si.rec_id >= 0) {
- PJ_LOG(3,(THIS_FILE, "Testing capture device %s",
- pjmedia_snd_get_dev_info(si.rec_id)->name));
- }
-
- /* Sleep for a while to let sound device "settles" */
- pj_thread_sleep(200);
-
-
- /*
- * Start the stream.
- */
- status = pjmedia_snd_stream_start(strm);
- if (status != PJ_SUCCESS) {
- app_perror("Unable to start capture stream", status);
- return status;
- }
-
- PJ_LOG(3,(THIS_FILE,
- " Please wait while test is in progress (~%d secs)..",
- (DURATION+SKIP_DURATION)/1000));
-
- /* Let the stream runs for few msec/sec to get stable result.
- * (capture normally begins with frames available simultaneously).
- */
- pj_thread_sleep(SKIP_DURATION);
-
-
- /* Begin gather data */
- test_data.running = 1;
-
- /*
- * Let the test runs for a while.
- */
- pj_thread_sleep(DURATION);
-
-
- /*
- * Close stream.
- */
- test_data.running = 0;
- pjmedia_snd_stream_close(strm);
-
-
- /*
- * Print results.
- */
- PJ_LOG(3,(THIS_FILE, " Dumping results:"));
-
- PJ_LOG(3,(THIS_FILE, " Parameters: clock rate=%dHz, %d samples/frame",
- clock_rate, samples_per_frame));
-
- if (dir & PJMEDIA_DIR_PLAYBACK)
- print_stream_data("Playback", &test_data, &test_data.playback_data,
- verbose);
- if (dir & PJMEDIA_DIR_CAPTURE)
- print_stream_data("Capture", &test_data, &test_data.capture_data,
- verbose);
-
- /* Check drifting */
- if (dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
- int end_diff, start_diff, drift;
-
- end_diff = test_data.capture_data.last_timestamp -
- test_data.playback_data.last_timestamp;
- start_diff = test_data.capture_data.first_timestamp-
- test_data.playback_data.first_timestamp;
- drift = end_diff > start_diff? end_diff - start_diff :
- start_diff - end_diff;
-
- PJ_LOG(3,(THIS_FILE, " Checking for clock drifts:"));
-
- /* Allow one frame tolerance for clock drift detection */
- if (drift < (int)samples_per_frame) {
- PJ_LOG(3,(THIS_FILE, " No clock drifts is detected"));
- } else {
- const char *which = (drift<0 ? "slower" : "faster");
- unsigned msec_dur;
-
- if (drift < 0) drift = -drift;
-
-
- msec_dur = (test_data.capture_data.last_timestamp -
- test_data.capture_data.first_timestamp) * 1000 /
- test_data.clock_rate;
-
- PJ_LOG(2,(THIS_FILE,
- " Sound capture is %d samples %s than playback "
- "at the end of the test (average is %d samples"
- " per second)",
- drift, which,
- drift * 1000 / msec_dur));
-
- }
- }
-
- if (test_data.has_error == 0) {
- PJ_LOG(3,(THIS_FILE, " Test completed, sound device looks okay."));
- return 0;
- } else {
- PJ_LOG(2,(THIS_FILE, " Test completed with some warnings"));
- return 1;
- }
-}
-
-
-int main(int argc, char *argv[])
-{
- pj_caching_pool cp;
- pj_pool_t *pool;
- pjmedia_endpt *med_endpt;
- int id = -1, verbose = 0;
- int clock_rate = 8000;
- int frame = -1;
- int channel = 1;
- struct pj_getopt_option long_options[] = {
- { "id", 1, 0, 'i' },
- { "rate", 1, 0, 'r' },
- { "frame", 1, 0, 'f' },
- { "channel", 1, 0, 'n' },
- { "verbose", 0, 0, 'v' },
- { "help", 0, 0, 'h' },
- { NULL, 0, 0, 0 }
- };
- int c, option_index;
-
-
- pj_status_t status;
-
- /* Init pjlib */
- status = pj_init();
- PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
-
- /* Must create a pool factory before we can allocate any memory. */
- pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
-
- /* Also create pool for misc purposes */
- pool = pj_pool_create(&cp.factory, "sndtest", 1000, 1000, NULL);
-
- /*
- * Initialize media endpoint.
- * This will implicitly initialize PJMEDIA too.
- */
- status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
- PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
-
- /* Print devices */
- enum_devices();
-
- /* Parse options */
- pj_optind = 0;
- while((c=pj_getopt_long(argc,argv, "i:r:f:n:vh",
- long_options, &option_index))!=-1)
- {
- switch (c) {
- case 'i':
- id = atoi(pj_optarg);
- break;
- case 'r':
- clock_rate = atoi(pj_optarg);
- break;
- case 'f':
- frame = atoi(pj_optarg);
- break;
- case 'n':
- channel = atoi(pj_optarg);
- break;
- case 'v':
- verbose = 1;
- break;
- case 'h':
- puts(desc);
- return 0;
- break;
- default:
- printf("Error: invalid options %s\n", argv[pj_optind-1]);
- puts(desc);
- return 1;
- }
- }
-
- if (pj_optind != argc) {
- printf("Error: invalid options\n");
- puts(desc);
- return 1;
- }
-
- if (!verbose)
- pj_log_set_level(3);
-
- if (frame == -1)
- frame = 10 * clock_rate / 1000;
-
-
- status = perform_test(pool, id, PJMEDIA_DIR_CAPTURE_PLAYBACK,
- clock_rate, frame, channel, verbose);
-
- pjmedia_endpt_destroy(med_endpt);
- pj_pool_release(pool);
- pj_caching_pool_destroy(&cp);
- pj_shutdown();
-
- return status == PJ_SUCCESS ? 0 : 1;
-}
-
-
diff --git a/pjsip-apps/src/symbian_ua/main_symbian.cpp b/pjsip-apps/src/symbian_ua/main_symbian.cpp
index b26b10bd..a0082a02 100644
--- a/pjsip-apps/src/symbian_ua/main_symbian.cpp
+++ b/pjsip-apps/src/symbian_ua/main_symbian.cpp
@@ -29,7 +29,7 @@
CConsoleBase* console;
// Needed by APS
-TPtrC APP_UID = _L("A000000D");
+TPtrC APP_UID = _L("200235D3");
////////////////////////////////////////////////////////////////////////////
diff --git a/pjsip-apps/src/symbian_ua/ua.cpp b/pjsip-apps/src/symbian_ua/ua.cpp
index 11adb154..033a2f2a 100644
--- a/pjsip-apps/src/symbian_ua/ua.cpp
+++ b/pjsip-apps/src/symbian_ua/ua.cpp
@@ -19,10 +19,12 @@
*/
#include <pjsua-lib/pjsua.h>
#include <pjsua-lib/pjsua_internal.h>
+//#include <pjmedia/symbian_sound_aps.h>
#include "ua.h"
#define THIS_FILE "symbian_ua.cpp"
-#define LOG_LEVEL 3
+#define CON_LOG_LEVEL 3 // console log level
+#define FILE_LOG_LEVEL 4 // logfile log level
//
// Basic config.
@@ -280,7 +282,7 @@ static pj_status_t app_startup()
pj_log_set_log_func(&log_writer);
/* Set log level */
- pj_log_set_level(LOG_LEVEL);
+ pj_log_set_level(CON_LOG_LEVEL);
/* Create pjsua first! */
status = pjsua_create();
@@ -328,23 +330,20 @@ static pj_status_t app_startup()
pjsua_logging_config_default(&log_cfg);
- log_cfg.level = LOG_LEVEL;
- log_cfg.console_level = LOG_LEVEL;
+ log_cfg.level = FILE_LOG_LEVEL;
+ log_cfg.console_level = CON_LOG_LEVEL;
log_cfg.cb = &log_writer;
- //log_cfg.log_filename = pj_str("C:\\data\\symbian_ua.log");
+ log_cfg.log_filename = pj_str("C:\\data\\symbian_ua.log");
pjsua_media_config_default(&med_cfg);
med_cfg.thread_cnt = 0; // Disable threading on Symbian
med_cfg.has_ioqueue = PJ_FALSE;
med_cfg.clock_rate = 8000;
-#if defined(PJMEDIA_SYM_SND_USE_APS) && (PJMEDIA_SYM_SND_USE_APS==1)
- med_cfg.audio_frame_ptime = 20;
-#else
med_cfg.audio_frame_ptime = 40;
-#endif
med_cfg.ec_tail_len = 0;
med_cfg.enable_ice = USE_ICE;
- med_cfg.snd_auto_close_time = 5; // wait for 5 seconds idle before sound dev get auto-closed
+ med_cfg.snd_auto_close_time = 0; // wait for 0 seconds idle before sound dev get auto-closed
+ //med_cfg.no_vad = PJ_TRUE;
status = pjsua_init(&cfg, &log_cfg, &med_cfg);
if (status != PJ_SUCCESS) {
@@ -491,87 +490,233 @@ void ConsoleUI::DoCancel()
con_->ReadCancel();
}
-static void PrintMenu()
+static void PrintMainMenu()
{
- PJ_LOG(3, (THIS_FILE, "\n\n"
- "Menu:\n"
- " d Dump states\n"
- " D Dump states detail\n"
- " P Dump pool factory\n"
- " l Start loopback audio device\n"
- " L Stop loopback audio device\n"
+ const char *menu =
+ "\n\n"
+ "Main Menu:\n"
+ " d Enable/disable codecs\n"
" m Call " SIP_DST_URI "\n"
" a Answer call\n"
" g Hangup all calls\n"
+ " t Toggle audio route\n"
+#if !defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0
+ " j Toggle loopback audio\n"
+#endif
+ "up/dn Increase/decrease output volume\n"
" s Subscribe " SIP_DST_URI "\n"
" S Unsubscribe presence\n"
" o Set account online\n"
" O Set account offline\n"
- " w Quit\n"));
+ " w Quit\n";
+
+ PJ_LOG(3, (THIS_FILE, menu));
}
-// Implementation: called when read has completed.
-void ConsoleUI::RunL()
+static void PrintCodecMenu()
{
- TKeyCode kc = con_->KeyCode();
- pj_bool_t reschedule = PJ_TRUE;
+ const char *menu =
+ "\n\n"
+ "Codec Menu:\n"
+ " a Enable all codecs\n"
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+ " d Enable only AMR\n"
+#endif
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_G729
+ " g Enable only G.729\n"
+#endif
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC
+ " j Enable only iLBC\n"
+#endif
+ " m Enable only Speex\n"
+ " p Enable only GSM\n"
+ " t Enable only PCMU\n"
+ " w Enable only PCMA\n";
+ PJ_LOG(3, (THIS_FILE, menu));
+}
+
+static void HandleMainMenu(TKeyCode kc) {
switch (kc) {
- case 'w':
- CActiveScheduler::Stop();
- reschedule = PJ_FALSE;
- break;
- case 'D':
- case 'd':
- pjsua_dump(kc == 'D');
- break;
- case 'p':
- case 'P':
- pj_pool_factory_dump(pjsua_get_pool_factory(), PJ_TRUE);
- break;
- case 'l':
- pjsua_conf_connect(0, 0);
- break;
- case 'L':
- pjsua_conf_disconnect(0, 0);
- break;
- case 'm':
- if (g_call_id != PJSUA_INVALID_ID) {
- PJ_LOG(3,(THIS_FILE, "Another call is active"));
- break;
- }
- if (pjsua_verify_sip_url(SIP_DST_URI) == PJ_SUCCESS) {
- pj_str_t dst = pj_str(SIP_DST_URI);
- pjsua_call_make_call(g_acc_id, &dst, 0, NULL,
- NULL, &g_call_id);
+ case EKeyUpArrow:
+ case EKeyDownArrow:
+ {
+ unsigned vol;
+ pj_status_t status;
+
+ status = pjsua_snd_get_setting(
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, &vol);
+ if (status == PJ_SUCCESS) {
+ if (kc == EKeyUpArrow)
+ vol = PJ_MIN(100, vol+10);
+ else
+ vol = (vol>=10 ? vol-10 : 0);
+ status = pjsua_snd_set_setting(
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &vol, PJ_TRUE);
+ }
+
+ if (status == PJ_SUCCESS) {
+ PJ_LOG(3,(THIS_FILE, "Output volume set to %d", vol));
} else {
- PJ_LOG(3,(THIS_FILE, "Invalid SIP URI"));
+ pjsua_perror(THIS_FILE, "Error setting volume", status);
}
- break;
+ }
+ break;
+
+ case 't':
+ {
+ pjmedia_aud_dev_route route;
+ pj_status_t status;
+
+ status = pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
+ &route);
+
+ if (status == PJ_SUCCESS) {
+ if (route == PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER)
+ route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
+ else
+ route = PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
+
+ status = pjsua_snd_set_setting(
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
+ &route, PJ_TRUE);
+ }
+
+ if (status != PJ_SUCCESS)
+ pjsua_perror(THIS_FILE, "Error switch audio route", status);
+ }
+ break;
+
+ case 'j':
+ {
+ static pj_bool_t loopback_active = PJ_FALSE;
+ if (!loopback_active)
+ pjsua_conf_connect(0, 0);
+ else
+ pjsua_conf_disconnect(0, 0);
+ loopback_active = !loopback_active;
+ }
+ break;
+
+ case 'm':
+ if (g_call_id != PJSUA_INVALID_ID) {
+ PJ_LOG(3,(THIS_FILE, "Another call is active"));
+ break;
+ }
+
+ if (pjsua_verify_sip_url(SIP_DST_URI) == PJ_SUCCESS) {
+ pj_str_t dst = pj_str(SIP_DST_URI);
+ pjsua_call_make_call(g_acc_id, &dst, 0, NULL,
+ NULL, &g_call_id);
+ } else {
+ PJ_LOG(3,(THIS_FILE, "Invalid SIP URI"));
+ }
+ break;
case 'a':
- if (g_call_id != PJSUA_INVALID_ID)
- pjsua_call_answer(g_call_id, 200, NULL, NULL);
- break;
+ if (g_call_id != PJSUA_INVALID_ID)
+ pjsua_call_answer(g_call_id, 200, NULL, NULL);
+ break;
case 'g':
- pjsua_call_hangup_all();
- break;
+ pjsua_call_hangup_all();
+ break;
case 's':
case 'S':
- if (g_buddy_id != PJSUA_INVALID_ID)
- pjsua_buddy_subscribe_pres(g_buddy_id, kc=='s');
- break;
+ if (g_buddy_id != PJSUA_INVALID_ID)
+ pjsua_buddy_subscribe_pres(g_buddy_id, kc=='s');
+ break;
case 'o':
case 'O':
- pjsua_acc_set_online_status(g_acc_id, kc=='o');
- break;
+ pjsua_acc_set_online_status(g_acc_id, kc=='o');
+ break;
+
default:
- PJ_LOG(3,(THIS_FILE, "Keycode '%c' (%d) is pressed",
- kc, kc));
+ PJ_LOG(3,(THIS_FILE, "Keycode '%c' (%d) is pressed", kc, kc));
+ break;
+ }
+
+ PrintMainMenu();
+}
+
+static void HandleCodecMenu(TKeyCode kc) {
+ const pj_str_t ID_ALL = {"*", 1};
+ pj_str_t codec = {NULL, 0};
+
+ if (kc == 'a') {
+ pjsua_codec_set_priority(&ID_ALL, PJMEDIA_CODEC_PRIO_NORMAL);
+ PJ_LOG(3,(THIS_FILE, "All codecs activated"));
+ } else {
+ switch (kc) {
+ case 'd':
+ codec = pj_str("AMR");
+ break;
+ case 'g':
+ codec = pj_str("G729");
+ break;
+ case 'j':
+ codec = pj_str("ILBC");
+ break;
+ case 'm':
+ codec = pj_str("SPEEX/8000");
+ break;
+ case 'p':
+ codec = pj_str("GSM");
+ break;
+ case 't':
+ codec = pj_str("PCMU");
+ break;
+ case 'w':
+ codec = pj_str("PCMA");
+ break;
+ default:
+ PJ_LOG(3,(THIS_FILE, "Keycode '%c' (%d) is pressed", kc, kc));
break;
+ }
+
+ if (codec.slen) {
+ pj_status_t status;
+
+ pjsua_codec_set_priority(&ID_ALL, PJMEDIA_CODEC_PRIO_DISABLED);
+
+ status = pjsua_codec_set_priority(&codec,
+ PJMEDIA_CODEC_PRIO_NORMAL);
+ if (status == PJ_SUCCESS)
+ PJ_LOG(3,(THIS_FILE, "%s activated", codec.ptr));
+ else
+ PJ_LOG(3,(THIS_FILE, "Failed activating %s, err=%d",
+ codec.ptr, status));
+ }
}
+}
- PrintMenu();
+// Implementation: called when read has completed.
+void ConsoleUI::RunL()
+{
+ enum {
+ MENU_TYPE_MAIN = 0,
+ MENU_TYPE_CODEC = 1
+ };
+ static int menu_type = MENU_TYPE_MAIN;
+ TKeyCode kc = con_->KeyCode();
+ pj_bool_t reschedule = PJ_TRUE;
+
+ if (menu_type == MENU_TYPE_MAIN) {
+ if (kc == 'w') {
+ CActiveScheduler::Stop();
+ reschedule = PJ_FALSE;
+ } else if (kc == 'd') {
+ menu_type = MENU_TYPE_CODEC;
+ PrintCodecMenu();
+ } else {
+ HandleMainMenu(kc);
+ }
+ } else {
+ HandleCodecMenu(kc);
+
+ menu_type = MENU_TYPE_MAIN;
+ PrintMainMenu();
+ }
if (reschedule)
Run();
@@ -815,7 +960,7 @@ private:
}
PJ_LOG(3, (THIS_FILE, "PJSUA restarted."));
- PrintMenu();
+ PrintMainMenu();
}
Start();
@@ -873,7 +1018,7 @@ int ua_main()
ConsoleUI *con = new ConsoleUI(console);
con->Run();
- PrintMenu();
+ PrintMainMenu();
// Init & start connection monitor
CConnMon *connmon = CConnMon::NewL(aConn, aSocketServer);
@@ -910,7 +1055,7 @@ int ua_main()
// Close connection and socket server
aConn.Close();
aSocketServer.Close();
-
+
return status;
}
diff --git a/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp b/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp
index 0a09fcb3..5128ca69 100644
--- a/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp
+++ b/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp
@@ -39,6 +39,7 @@ LIBRARY charconv.lib estlib.lib
STATICLIBRARY pjsua_lib.lib pjsip_ua.lib
STATICLIBRARY pjsip_simple.lib pjsip.lib pjsdp.lib pjmedia.lib
+STATICLIBRARY pjmedia_audiodev.lib
STATICLIBRARY pjnath.lib pjlib_util.lib pjlib.lib
STATICLIBRARY libsrtp.lib
STATICLIBRARY libgsmcodec.lib
@@ -49,12 +50,10 @@ STATICLIBRARY libspeexcodec.lib
STATICLIBRARY null_audio.lib
CAPABILITY NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment
#elif SND_USE_APS
- STATICLIBRARY symbian_audio_aps.lib
- LIBRARY APSSession2.lib
+ LIBRARY APSSession2.lib
CAPABILITY NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
- MACRO PJMEDIA_SYM_SND_USE_APS=1
+ MACRO PJMEDIA_SYM_SND_USE_APS=1
#else
- STATICLIBRARY symbian_audio.lib
LIBRARY mediaclientaudiostream.lib
LIBRARY mediaclientaudioinputstream.lib
CAPABILITY NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment
diff --git a/pjsip-apps/src/symsndtest/app_main.cpp b/pjsip-apps/src/symsndtest/app_main.cpp
index 597e7117..056cde10 100644
--- a/pjsip-apps/src/symsndtest/app_main.cpp
+++ b/pjsip-apps/src/symsndtest/app_main.cpp
@@ -49,7 +49,7 @@ static void log_writer(int level, const char *buf, unsigned len)
static wchar_t buf16[PJ_LOG_MAX_SIZE];
PJ_UNUSED_ARG(level);
-
+
pj_ansi_to_unicode(buf, len, buf16, PJ_ARRAY_SIZE(buf16));
TPtrC16 aBuf((const TUint16*)buf16, (TInt)len);
@@ -57,24 +57,24 @@ static void log_writer(int level, const char *buf, unsigned len)
}
/* perror util */
-static void app_perror(const char *title, pj_status_t status)
+static void app_perror(const char *title, pj_status_t status)
{
- char errmsg[PJ_ERR_MSG_SIZE];
+ char errmsg[PJ_ERR_MSG_SIZE];
pj_strerror(status, errmsg, sizeof(errmsg));
PJ_LOG(1,(THIS_FILE, "Error: %s: %s", title, errmsg));
}
/* Application init */
-static pj_status_t app_init()
+static pj_status_t app_init()
{
unsigned i, count;
pj_status_t status;
-
+
/* Redirect log */
pj_log_set_log_func((void (*)(int,const char*,int)) &log_writer);
pj_log_set_decor(PJ_LOG_HAS_NEWLINE);
pj_log_set_level(3);
-
+
/* Init pjlib */
status = pj_init();
if (status != PJ_SUCCESS) {
@@ -83,7 +83,7 @@ static pj_status_t app_init()
}
pj_caching_pool_init(&cp, NULL, 0);
-
+
/* Init sound subsystem */
status = pjmedia_snd_init(&cp.factory);
if (status != PJ_SUCCESS) {
@@ -92,16 +92,16 @@ static pj_status_t app_init()
pj_shutdown();
return status;
}
-
+
count = pjmedia_snd_get_dev_count();
PJ_LOG(3,(THIS_FILE, "Device count: %d", count));
for (i=0; i<count; ++i) {
const pjmedia_snd_dev_info *info;
-
+
info = pjmedia_snd_get_dev_info(i);
PJ_LOG(3, (THIS_FILE, "%d: %s %d/%d %dHz",
i, info->name, info->input_count, info->output_count,
- info->default_samples_per_sec));
+ info->default_samples_per_sec));
}
/* Create pool */
@@ -114,8 +114,8 @@ static pj_status_t app_init()
}
/* Init delay buffer */
- status = pjmedia_delay_buf_create(pool, THIS_FILE, CLOCK_RATE,
- SAMPLES_PER_FRAME, CHANNEL_COUNT,
+ status = pjmedia_delay_buf_create(pool, THIS_FILE, CLOCK_RATE,
+ SAMPLES_PER_FRAME, CHANNEL_COUNT,
0, 0, &delaybuf);
if (status != PJ_SUCCESS) {
app_perror("pjmedia_delay_buf_create()", status);
@@ -123,16 +123,16 @@ static pj_status_t app_init()
//pj_shutdown();
//return status;
}
-
+
return PJ_SUCCESS;
}
/* Sound capture callback */
-static pj_status_t rec_cb(void *user_data,
+static pj_status_t rec_cb(void *user_data,
pj_uint32_t timestamp,
void *input,
- unsigned size)
+ unsigned size)
{
PJ_UNUSED_ARG(user_data);
PJ_UNUSED_ARG(timestamp);
@@ -153,28 +153,28 @@ static pj_status_t rec_cb(void *user_data,
static pj_status_t play_cb(void *user_data,
pj_uint32_t timestamp,
void *output,
- unsigned size)
+ unsigned size)
{
PJ_UNUSED_ARG(user_data);
PJ_UNUSED_ARG(timestamp);
PJ_UNUSED_ARG(size);
-
+
pjmedia_delay_buf_get(delaybuf, (pj_int16_t*)output);
-
+
++play_cnt;
- return PJ_SUCCESS;
+ return PJ_SUCCESS;
}
/* Start sound */
-static pj_status_t snd_start(unsigned flag)
+static pj_status_t snd_start(unsigned flag)
{
pj_status_t status;
-
+
if (strm != NULL) {
app_perror("snd already open", PJ_EINVALIDOP);
return PJ_EINVALIDOP;
}
-
+
if (flag==PJMEDIA_DIR_CAPTURE_PLAYBACK)
status = pjmedia_snd_open(-1, -1, CLOCK_RATE, CHANNEL_COUNT,
SAMPLES_PER_FRAME, BITS_PER_SAMPLE,
@@ -187,7 +187,7 @@ static pj_status_t snd_start(unsigned flag)
status = pjmedia_snd_open_player(-1, CLOCK_RATE, CHANNEL_COUNT,
SAMPLES_PER_FRAME, BITS_PER_SAMPLE,
&play_cb, NULL, &strm);
-
+
if (status != PJ_SUCCESS) {
app_perror("snd open", status);
return status;
@@ -210,19 +210,23 @@ static pj_status_t snd_start(unsigned flag)
}
/* Stop sound */
-static pj_status_t snd_stop()
+static pj_status_t snd_stop()
{
pj_time_val now;
pj_status_t status;
-
+
if (strm == NULL) {
app_perror("snd not open", PJ_EINVALIDOP);
return PJ_EINVALIDOP;
}
-
+
+ status = pjmedia_snd_stream_stop(strm);
+ if (status != PJ_SUCCESS) {
+ app_perror("snd failed to stop", status);
+ }
status = pjmedia_snd_stream_close(strm);
strm = NULL;
-
+
pj_gettimeofday(&now);
PJ_TIME_VAL_SUB(now, t_start);
@@ -234,11 +238,11 @@ static pj_status_t snd_stop()
}
/* Shutdown application */
-static void app_fini()
+static void app_fini()
{
if (strm)
snd_stop();
-
+
pjmedia_snd_deinit();
pjmedia_delay_buf_destroy(delaybuf);
pj_pool_release(pool);
@@ -253,56 +257,55 @@ static void app_fini()
*/
#include <e32base.h>
-class ConsoleUI : public CActive
+class ConsoleUI : public CActive
{
public:
- ConsoleUI(CActiveSchedulerWait *asw, CConsoleBase *con);
+ ConsoleUI(CConsoleBase *con);
// Run console UI
void Run();
// Stop
void Stop();
-
+
protected:
// Cancel asynchronous read.
void DoCancel();
// Implementation: called when read has completed.
void RunL();
-
+
private:
- CActiveSchedulerWait *asw_;
CConsoleBase *con_;
};
-ConsoleUI::ConsoleUI(CActiveSchedulerWait *asw, CConsoleBase *con)
-: CActive(EPriorityHigh), asw_(asw), con_(con)
+ConsoleUI::ConsoleUI(CConsoleBase *con)
+: CActive(EPriorityUserInput), con_(con)
{
CActiveScheduler::Add(this);
}
// Run console UI
-void ConsoleUI::Run()
+void ConsoleUI::Run()
{
con_->Read(iStatus);
SetActive();
}
// Stop console UI
-void ConsoleUI::Stop()
+void ConsoleUI::Stop()
{
DoCancel();
}
// Cancel asynchronous read.
-void ConsoleUI::DoCancel()
+void ConsoleUI::DoCancel()
{
con_->ReadCancel();
}
-static void PrintMenu()
+static void PrintMenu()
{
PJ_LOG(3, (THIS_FILE, "\n\n"
"Menu:\n"
@@ -314,14 +317,15 @@ static void PrintMenu()
}
// Implementation: called when read has completed.
-void ConsoleUI::RunL()
+void ConsoleUI::RunL()
{
TKeyCode kc = con_->KeyCode();
pj_bool_t reschedule = PJ_TRUE;
-
+
switch (kc) {
case 'w':
- asw_->AsyncStop();
+ snd_stop();
+ CActiveScheduler::Stop();
reschedule = PJ_FALSE;
break;
case 'a':
@@ -343,30 +347,28 @@ void ConsoleUI::RunL()
}
PrintMenu();
-
+
if (reschedule)
Run();
}
////////////////////////////////////////////////////////////////////////////
-int app_main()
+int app_main()
{
if (app_init() != PJ_SUCCESS)
return -1;
-
+
// Run the UI
- CActiveSchedulerWait *asw = new CActiveSchedulerWait;
- ConsoleUI *con = new ConsoleUI(asw, console);
-
+ ConsoleUI *con = new ConsoleUI(console);
+
con->Run();
-
+
PrintMenu();
- asw->Start();
-
+ CActiveScheduler::Start();
+
delete con;
- delete asw;
-
+
app_fini();
return 0;
}
diff --git a/pjsip-apps/src/symsndtest/main_symbian.cpp b/pjsip-apps/src/symsndtest/main_symbian.cpp
index bc160bd6..a711f092 100644
--- a/pjsip-apps/src/symsndtest/main_symbian.cpp
+++ b/pjsip-apps/src/symsndtest/main_symbian.cpp
@@ -29,75 +29,12 @@
CConsoleBase* console;
// Needed by APS
-TPtrC APP_UID = _L("A000000D");
+TPtrC APP_UID = _L("A000000E");
int app_main();
////////////////////////////////////////////////////////////////////////////
-class MyTask : public CActive
-{
-public:
- static MyTask *NewL(CActiveSchedulerWait *asw);
- ~MyTask();
- void Start();
-
-protected:
- MyTask(CActiveSchedulerWait *asw);
- void ConstructL();
- virtual void RunL();
- virtual void DoCancel();
-
-private:
- RTimer timer_;
- CActiveSchedulerWait *asw_;
-};
-
-MyTask::MyTask(CActiveSchedulerWait *asw)
-: CActive(EPriorityNormal), asw_(asw)
-{
-}
-
-MyTask::~MyTask()
-{
- timer_.Close();
-}
-
-void MyTask::ConstructL()
-{
- timer_.CreateLocal();
- CActiveScheduler::Add(this);
-}
-
-MyTask *MyTask::NewL(CActiveSchedulerWait *asw)
-{
- MyTask *self = new (ELeave) MyTask(asw);
- CleanupStack::PushL(self);
-
- self->ConstructL();
-
- CleanupStack::Pop(self);
- return self;
-}
-
-void MyTask::Start()
-{
- timer_.After(iStatus, 0);
- SetActive();
-}
-
-void MyTask::RunL()
-{
- int rc = app_main();
- asw_->AsyncStop();
-}
-
-void MyTask::DoCancel()
-{
-
-}
-
-////////////////////////////////////////////////////////////////////////////
LOCAL_C void DoStartL()
{
@@ -105,19 +42,8 @@ LOCAL_C void DoStartL()
CleanupStack::PushL(scheduler);
CActiveScheduler::Install(scheduler);
- CActiveSchedulerWait *asw = new CActiveSchedulerWait;
- CleanupStack::PushL(asw);
-
- MyTask *task = MyTask::NewL(asw);
- task->Start();
-
- asw->Start();
-
- delete task;
-
- CleanupStack::Pop(asw);
- delete asw;
-
+ app_main();
+
CActiveScheduler::Install(NULL);
CleanupStack::Pop(scheduler);
delete scheduler;
@@ -142,13 +68,13 @@ GLDEF_C TInt E32Main()
TRAPD(startError, DoStartL());
- console->Printf(_L("[press any key to close]\n"));
- console->Getch();
-
+ //console->Printf(_L("[press any key to close]\n"));
+ //console->Getch();
+
delete console;
delete cleanup;
- CloseSTDLIB();
+ CloseSTDLIB();
// Mark end of heap usage, detect memory leaks
__UHEAP_MARKEND;
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index ca00bb18..99869344 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -4200,6 +4200,20 @@ struct pjsua_media_config
*/
unsigned ec_tail_len;
+ /**
+ * Audio capture buffer length, in milliseconds.
+ *
+ * Default: PJMEDIA_SND_DEFAULT_REC_LATENCY
+ */
+ unsigned snd_rec_latency;
+
+ /**
+ * Audio playback buffer length, in milliseconds.
+ *
+ * Default: PJMEDIA_SND_DEFAULT_PLAY_LATENCY
+ */
+ unsigned snd_play_latency;
+
/**
* Jitter buffer initial prefetch delay in msec. The value must be
* between jb_min_pre and jb_max_pre below.
@@ -4272,9 +4286,10 @@ struct pjsua_media_config
/**
* Specify idle time of sound device before it is automatically closed,
- * in seconds.
+ * in seconds. Use value -1 to disable the auto-close feature of sound
+ * device
*
- * Default : -1 (Disable the auto-close feature of sound device)
+ * Default : 1
*/
int snd_auto_close_time;
};
@@ -4787,7 +4802,20 @@ PJ_DECL(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id);
*/
/**
- * Enum all sound devices installed in the system.
+ * Enum all audio devices installed in the system.
+ *
+ * @param info Array of info to be initialized.
+ * @param count On input, specifies max elements in the array.
+ * On return, it contains actual number of elements
+ * that have been initialized.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_enum_aud_devs(pjmedia_aud_dev_info info[],
+ unsigned *count);
+
+/**
+ * Enum all sound devices installed in the system (old API).
*
* @param info Array of info to be initialized.
* @param count On input, specifies max elements in the array.
@@ -4807,8 +4835,6 @@ PJ_DECL(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id);
PJ_DECL(pj_status_t) pjsua_enum_snd_devs(pjmedia_snd_dev_info info[],
unsigned *count);
-
-
/**
* Get currently active sound devices. If sound devices has not been created
* (for example when pjsua_start() is not called), it is possible that
@@ -4879,7 +4905,22 @@ PJ_DECL(pjmedia_port*) pjsua_set_no_snd_dev(void);
/**
- * Configure the echo canceller tail length of the sound port.
+ * Change the echo cancellation settings.
+ *
+ * The behavior of this function depends on whether the sound device is
+ * currently active, and if it is, whether device or software AEC is
+ * being used.
+ *
+ * If the sound device is currently active, and if the device supports AEC,
+ * this function will forward the change request to the device and it will
+ * be up to the device on whether support the request. If software AEC is
+ * being used (the software EC will be used if the device does not support
+ * AEC), this function will change the software EC settings. In all cases,
+ * the setting will be saved for future opening of the sound device.
+ *
+ * If the sound device is not currently active, this will only change the
+ * default AEC settings and the setting will be applied next time the
+ * sound device is opened.
*
* @param tail_ms The tail length, in miliseconds. Set to zero to
* disable AEC.
@@ -4897,7 +4938,7 @@ PJ_DECL(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options);
/**
- * Get current echo canceller tail length.
+ * Get current echo canceller tail length.
*
* @param p_tail_ms Pointer to receive the tail length, in miliseconds.
* If AEC is disabled, the value will be zero.
@@ -4912,6 +4953,69 @@ PJ_DECL(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options);
PJ_DECL(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms);
+/**
+ * Check whether the sound device is currently active. The sound device
+ * may be inactive if the application has set the auto close feature to
+ * non-zero (the snd_auto_close_time setting in #pjsua_media_config), or
+ * if null sound device or no sound device has been configured via the
+ * #pjsua_set_no_snd_dev() function.
+ */
+PJ_DECL(pj_bool_t) pjsua_snd_is_active(void);
+
+
+/**
+ * Configure sound device setting to the sound device being used. If sound
+ * device is currently active, the function will forward the setting to the
+ * sound device instance to be applied immediately, if it supports it.
+ *
+ * The setting will be saved for future opening of the sound device, if the
+ * "keep" argument is set to non-zero. If the sound device is currently
+ * inactive, and the "keep" argument is false, this function will return
+ * error.
+ *
+ * Note that in case the setting is kept for future use, it will be applied
+ * to any devices, even when application has changed the sound device to be
+ * used.
+ *
+ * Note also that the echo cancellation setting should be set with
+ * #pjsua_set_ec() API instead.
+ *
+ * See also #pjmedia_aud_stream_set_cap() for more information about setting
+ * an audio device capability.
+ *
+ * @param cap The sound device setting to change.
+ * @param pval Pointer to value. Please see #pjmedia_aud_dev_cap
+ * documentation about the type of value to be
+ * supplied for each setting.
+ * @param keep Specify whether the setting is to be kept for future
+ * use.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_snd_set_setting(pjmedia_aud_dev_cap cap,
+ const void *pval,
+ pj_bool_t keep);
+
+/**
+ * Retrieve a sound device setting. If sound device is currently active,
+ * the function will forward the request to the sound device. If sound device
+ * is currently inactive, and if application had previously set the setting
+ * and mark the setting as kept, then that setting will be returned.
+ * Otherwise, this function will return error.
+ *
+ * Note that echo cancellation settings should be retrieved with
+ * #pjsua_get_ec_tail() API instead.
+ *
+ * @param cap The sound device setting to retrieve.
+ * @param pval Pointer to receive the value.
+ * Please see #pjmedia_aud_dev_cap documentation about
+ * the type of value to be supplied for each setting.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_snd_get_setting(pjmedia_aud_dev_cap cap,
+ void *pval);
+
/*****************************************************************************
* Codecs.
diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h
index a4af71bc..a144c97d 100644
--- a/pjsip/include/pjsua-lib/pjsua_internal.h
+++ b/pjsip/include/pjsua-lib/pjsua_internal.h
@@ -272,8 +272,15 @@ struct pjsua_data
pjmedia_endpt *med_endpt; /**< Media endpoint. */
pjsua_conf_setting mconf_cfg; /**< Additionan conf. bridge. param */
pjmedia_conf *mconf; /**< Conference bridge. */
- int cap_dev; /**< Capture device ID. */
- int play_dev; /**< Playback device ID. */
+ pj_bool_t is_mswitch;/**< Are we using audio switchboard
+ (a.k.a APS-Direct) */
+
+ /* Sound device */
+ pjmedia_aud_dev_index cap_dev; /**< Capture device ID. */
+ pjmedia_aud_dev_index play_dev; /**< Playback device ID. */
+ pj_uint32_t aud_svmask;/**< Which settings to save */
+ pjmedia_aud_param aud_param; /**< User settings to sound dev */
+ pj_bool_t aud_open_cnt;/**< How many # device is opened */
pj_bool_t no_snd; /**< No sound (app will manage it) */
pj_pool_t *snd_pool; /**< Sound's private pool. */
pjmedia_snd_port *snd_port; /**< Sound port. */
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c
index c2f9ae2d..87dcfdea 100644
--- a/pjsip/src/pjsua-lib/pjsua_call.c
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -370,9 +370,13 @@ PJ_DEF(pj_status_t) pjsua_call_make_call( pjsua_acc_id acc_id,
PJSUA_LOCK();
- /* Create sound port if none is instantiated */
- if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
- !pjsua_var.no_snd)
+ /* Create sound port if none is instantiated, to check if sound device
+ * can be used. But only do this with the conference bridge, as with
+ * audio switchboard (i.e. APS-Direct), we can only open the sound
+ * device once the correct format has been known
+ */
+ if (!pjsua_var.is_mswitch && pjsua_var.snd_port==NULL &&
+ pjsua_var.null_snd==NULL && !pjsua_var.no_snd)
{
pj_status_t status;
diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c
index 4b7c4baa..7430fc12 100644
--- a/pjsip/src/pjsua-lib/pjsua_core.c
+++ b/pjsip/src/pjsua-lib/pjsua_core.c
@@ -177,8 +177,10 @@ PJ_DEF(void) pjsua_media_config_default(pjsua_media_config *cfg)
cfg->quality = PJSUA_DEFAULT_CODEC_QUALITY;
cfg->ilbc_mode = PJSUA_DEFAULT_ILBC_MODE;
cfg->ec_tail_len = PJSUA_DEFAULT_EC_TAIL_LEN;
+ cfg->snd_rec_latency = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ cfg->snd_play_latency = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
cfg->jb_init = cfg->jb_min_pre = cfg->jb_max_pre = cfg->jb_max = -1;
- cfg->snd_auto_close_time = -1;
+ cfg->snd_auto_close_time = 1;
cfg->turn_conn_type = PJ_TURN_TP_UDP;
}
@@ -581,7 +583,8 @@ PJ_DEF(pj_status_t) pjsua_create(void)
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
/* Set default sound device ID */
- pjsua_var.cap_dev = pjsua_var.play_dev = -1;
+ pjsua_var.cap_dev = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV;
+ pjsua_var.play_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
/* Init caching pool. */
pj_caching_pool_init(&pjsua_var.cp, NULL, 0);
diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c
index 8d2d8a8a..f49214a9 100644
--- a/pjsip/src/pjsua-lib/pjsua_media.c
+++ b/pjsip/src/pjsua-lib/pjsua_media.c
@@ -35,8 +35,18 @@
/* Next RTP port to be used */
static pj_uint16_t next_rtp_port;
+/* Open sound dev */
+static pj_status_t open_snd_dev(pjmedia_aud_param *param);
/* Close existing sound device */
static void close_snd_dev(void);
+/* Create audio device param */
+static pj_status_t create_aud_param(pjmedia_aud_param *param,
+ pjmedia_aud_dev_index capture_dev,
+ pjmedia_aud_dev_index playback_dev,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample);
static void pjsua_media_config_dup(pj_pool_t *pool,
@@ -60,6 +70,16 @@ pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
/* To suppress warning about unused var when all codecs are disabled */
PJ_UNUSED_ARG(codec_id);
+ /* Specify which audio device settings are save-able */
+ pjsua_var.aud_svmask = 0xFFFFFFFF;
+ /* These are not-settable */
+ pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
+ PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER);
+ /* EC settings use different API */
+ pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EC |
+ PJMEDIA_AUD_DEV_CAP_EC_TAIL);
+
/* Copy configuration */
pjsua_media_config_dup(pjsua_var.pool, &pjsua_var.media_cfg, cfg);
@@ -172,6 +192,16 @@ pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
#endif /* PJMEDIA_HAS_INTEL_IPP */
+#if PJMEDIA_HAS_PASSTHROUGH_CODECS
+ /* Register passthrough codecs */
+ status = pjmedia_codec_passthrough_init(pjsua_var.med_endpt);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error initializing passthrough codecs",
+ status);
+ return status;
+ }
+#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
+
#if PJMEDIA_HAS_L16_CODEC
/* Register L16 family codecs, but disable all */
status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
@@ -226,6 +256,10 @@ pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
return status;
}
+ /* Are we using the audio switchboard (a.k.a APS-Direct)? */
+ pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
+ ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
+
/* Create null port just in case user wants to use null sound. */
status = pjmedia_null_port_create(pjsua_var.pool,
pjsua_var.media_cfg.clock_rate,
@@ -574,6 +608,10 @@ pj_status_t pjsua_media_subsys_destroy(void)
pjmedia_codec_ipp_deinit();
# endif /* PJMEDIA_HAS_INTEL_IPP */
+# if PJMEDIA_HAS_PASSTHROUGH_CODECS
+ pjmedia_codec_passthrough_deinit();
+# endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
+
# if PJMEDIA_HAS_L16_CODEC
pjmedia_codec_l16_deinit();
# endif /* PJMEDIA_HAS_L16_CODEC */
@@ -1495,7 +1533,6 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
return PJ_SUCCESS;
}
-
/*
* Get maxinum number of conference ports.
*/
@@ -1610,17 +1647,85 @@ PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
pjsua_var.snd_idle_timer.id = PJ_FALSE;
}
- /* Create sound port if none is instantiated */
- if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
- !pjsua_var.no_snd)
- {
+
+ /* For audio switchboard (i.e. APS-Direct):
+ * Check if sound device need to be reopened, i.e: its attributes
+ * (format, clock rate, channel count) must match to peer's.
+ * Note that sound device can be reopened only if it doesn't have
+ * any connection.
+ */
+ if (pjsua_var.is_mswitch) {
+ pjmedia_conf_port_info port0_info;
+ pjmedia_conf_port_info peer_info;
+ unsigned peer_id;
+ pj_bool_t need_reopen = PJ_FALSE;
pj_status_t status;
- status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Error opening sound device", status);
- return status;
+ peer_id = (source!=0)? source : sink;
+ status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
+ &peer_info);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
+ pj_assert(status == PJ_SUCCESS);
+
+ /* Check if sound device is instantiated. */
+ need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
+ !pjsua_var.no_snd);
+
+ /* Check if sound device need to reopen because it needs to modify
+ * settings to match its peer. Sound device must be idle in this case
+ * though.
+ */
+ if (!need_reopen &&
+ port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
+ {
+ need_reopen = (peer_info.format.id != port0_info.format.id ||
+ peer_info.format.bitrate != port0_info.format.bitrate ||
+ peer_info.clock_rate != port0_info.clock_rate ||
+ peer_info.channel_count != port0_info.channel_count);
+ }
+
+ if (need_reopen) {
+ pjmedia_aud_param param;
+
+ /* Create parameter based on peer info */
+ status = create_aud_param(&param, pjsua_var.cap_dev,
+ pjsua_var.play_dev,
+ peer_info.clock_rate,
+ peer_info.channel_count,
+ peer_info.samples_per_frame,
+ peer_info.bits_per_sample);
+
+ /* And peer format */
+ if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
+ param.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
+ param.ext_fmt = peer_info.format;
+ }
+
+ status = open_snd_dev(&param);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error opening sound device", status);
+ return status;
+ }
}
+
+ } else {
+ /* The bridge version */
+
+ /* Create sound port if none is instantiated */
+ if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
+ !pjsua_var.no_snd)
+ {
+ pj_status_t status;
+
+ status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error opening sound device", status);
+ return status;
+ }
+ }
+
}
return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
@@ -2097,20 +2202,22 @@ PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
/*
* Enum sound devices.
*/
-PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
+
+PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
unsigned *count)
{
unsigned i, dev_count;
- dev_count = pjmedia_snd_get_dev_count();
+ dev_count = pjmedia_aud_dev_count();
if (dev_count > *count) dev_count = *count;
for (i=0; i<dev_count; ++i) {
- const pjmedia_snd_dev_info *ci;
+ pj_status_t status;
- ci = pjmedia_snd_get_dev_info(i);
- pj_memcpy(&info[i], ci, sizeof(*ci));
+ status = pjmedia_aud_dev_get_info(i, &info[i]);
+ if (status != PJ_SUCCESS)
+ return status;
}
*count = dev_count;
@@ -2119,56 +2226,154 @@ PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
}
-/* Close existing sound device */
-static void close_snd_dev(void)
+PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
+ unsigned *count)
{
- /* Close sound device */
- if (pjsua_var.snd_port) {
- const pjmedia_snd_dev_info *cap_info, *play_info;
+ unsigned i, dev_count;
+
+ dev_count = pjmedia_aud_dev_count();
+
+ if (dev_count > *count) dev_count = *count;
+ pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
- cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
- play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
+ for (i=0; i<dev_count; ++i) {
+ pjmedia_aud_dev_info ai;
+ pj_status_t status;
- PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
- "%s sound capture device",
- play_info->name, cap_info->name));
+ status = pjmedia_aud_dev_get_info(i, &ai);
+ if (status != PJ_SUCCESS)
+ return status;
- pjmedia_snd_port_disconnect(pjsua_var.snd_port);
- pjmedia_snd_port_destroy(pjsua_var.snd_port);
- pjsua_var.snd_port = NULL;
+ strncpy(info[i].name, ai.name, sizeof(info[i].name));
+ info[i].name[sizeof(info[i].name)-1] = '\0';
+ info[i].input_count = ai.input_count;
+ info[i].output_count = ai.output_count;
+ info[i].default_samples_per_sec = ai.default_samples_per_sec;
}
- /* Close null sound device */
- if (pjsua_var.null_snd) {
- PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
- pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
- pjsua_var.null_snd = NULL;
+ *count = dev_count;
+
+ return PJ_SUCCESS;
+}
+
+/* Create audio device parameter to open the device */
+static pj_status_t create_aud_param(pjmedia_aud_param *param,
+ pjmedia_aud_dev_index capture_dev,
+ pjmedia_aud_dev_index playback_dev,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample)
+{
+ pj_status_t status;
+
+ /* Normalize device ID with new convention about default device ID */
+ if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
+ playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
+
+ /* Create default parameters for the device */
+ status = pjmedia_aud_dev_default_param(capture_dev, param);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error retrieving default audio "
+ "device parameters", status);
+ return status;
+ }
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = capture_dev;
+ param->play_id = playback_dev;
+ param->clock_rate = clock_rate;
+ param->channel_count = channel_count;
+ param->samples_per_frame = samples_per_frame;
+ param->bits_per_sample = bits_per_sample;
+
+ /* Update the setting with user preference */
+#define update_param(cap, field) \
+ if (pjsua_var.aud_param.flags & cap) { \
+ param->flags |= cap; \
+ param->field = pjsua_var.aud_param.field; \
+ }
+ update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
+ update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
+ update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
+ update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
+#undef update_param
+
+ /* Latency settings */
+ param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
+ param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
+ param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
+
+ /* EC settings */
+ if (pjsua_var.media_cfg.ec_tail_len) {
+ param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
+ param->ec_enabled = PJ_TRUE;
+ param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
+ } else {
+ param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
}
- if (pjsua_var.snd_pool)
- pj_pool_release(pjsua_var.snd_pool);
- pjsua_var.snd_pool = NULL;
+ return PJ_SUCCESS;
}
-/*
- * Select or change sound device. Application may call this function at
- * any time to replace current sound device.
+/* Internal: the first time the audio device is opened (during app
+ * startup), retrieve the audio settings such as volume level
+ * so that aud_get_settings() will work.
*/
-PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
- int playback_dev)
+static pj_status_t update_initial_aud_param()
+{
+ pjmedia_aud_stream *strm;
+ pjmedia_aud_param param;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
+
+ strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+
+ status = pjmedia_aud_stream_get_param(strm, &param);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error audio stream "
+ "device parameters", status);
+ return status;
+ }
+
+#define update_saved_param(cap, field) \
+ if (param.flags & cap) { \
+ pjsua_var.aud_param.flags |= cap; \
+ pjsua_var.aud_param.field = param.field; \
+ }
+
+ update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
+ update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
+ update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
+ update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
+#undef update_saved_param
+
+ return PJ_SUCCESS;
+}
+
+/* Get format name */
+static const char *get_fmt_name(pj_uint32_t id)
+{
+ static char name[8];
+
+ if (id == PJMEDIA_FORMAT_L16)
+ return "PCM";
+ pj_memcpy(name, &id, 4);
+ name[4] = '\0';
+ return name;
+}
+
+/* Open sound device with the setting. */
+static pj_status_t open_snd_dev(pjmedia_aud_param *param)
{
pjmedia_port *conf_port;
- const pjmedia_snd_dev_info *play_info;
- unsigned clock_rates[] = {0, 44100, 48000, 32000, 16000, 8000};
- unsigned selected_clock_rate = 0;
- unsigned i;
- pjmedia_snd_stream *strm;
- pjmedia_snd_stream_info si;
- pj_str_t tmp;
- pj_status_t status = -1;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(param, PJ_EINVAL);
/* Check if NULL sound device is used */
- if (NULL_SND_DEV_ID == capture_dev || NULL_SND_DEV_ID == playback_dev) {
+ if (NULL_SND_DEV_ID==param->rec_id || NULL_SND_DEV_ID==param->play_id) {
return pjsua_set_null_snd_dev();
}
@@ -2179,94 +2384,72 @@ PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
- /* Set default clock rate */
- clock_rates[0] = pjsua_var.media_cfg.snd_clock_rate;
- if (clock_rates[0] == 0)
- clock_rates[0] = pjsua_var.media_cfg.clock_rate;
+
+ PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
+ get_fmt_name(param->ext_fmt.id),
+ param->clock_rate, param->channel_count,
+ param->samples_per_frame / param->channel_count * 1000 /
+ param->clock_rate));
+
+ status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
+ param, &pjsua_var.snd_port);
+ if (status != PJ_SUCCESS)
+ return status;
/* Get the port0 of the conference bridge. */
conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
pj_assert(conf_port != NULL);
- /* Attempts to open the sound device with different clock rates */
- for (i=0; i<PJ_ARRAY_SIZE(clock_rates); ++i) {
- char errmsg[PJ_ERR_MSG_SIZE];
- unsigned samples_per_frame;
-
- PJ_LOG(4,(THIS_FILE,
- "pjsua_set_snd_dev(): attempting to open devices "
- "@%d Hz", clock_rates[i]));
-
- samples_per_frame = clock_rates[i] *
- pjsua_var.media_cfg.audio_frame_ptime *
- pjsua_var.media_cfg.channel_count / 1000;
-
- /* Create the sound device. Sound port will start immediately. */
- status = pjmedia_snd_port_create(pjsua_var.snd_pool, capture_dev,
- playback_dev,
- clock_rates[i],
- pjsua_var.media_cfg.channel_count,
- samples_per_frame,
- 16, 0, &pjsua_var.snd_port);
-
- if (status == PJ_SUCCESS) {
- selected_clock_rate = clock_rates[i];
-
- /* If there's mismatch between sound port and conference's port,
- * create a resample port to bridge them.
- */
- if (selected_clock_rate != pjsua_var.media_cfg.clock_rate) {
- pjmedia_port *resample_port;
- unsigned resample_opt = 0;
-
- if (pjsua_var.media_cfg.quality >= 3 &&
- pjsua_var.media_cfg.quality <= 4)
- {
- resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
- }
- else if (pjsua_var.media_cfg.quality < 3) {
- resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
- }
-
- status = pjmedia_resample_port_create(pjsua_var.snd_pool,
- conf_port,
- selected_clock_rate,
- resample_opt,
- &resample_port);
- if (status != PJ_SUCCESS) {
- pj_strerror(status, errmsg, sizeof(errmsg));
- PJ_LOG(4, (THIS_FILE,
- "Error creating resample port, trying next "
- "clock rate",
- errmsg));
- pjmedia_snd_port_destroy(pjsua_var.snd_port);
- pjsua_var.snd_port = NULL;
- continue;
- } else {
- conf_port = resample_port;
- break;
- }
+ /* For conference bridge, resample if necessary if the bridge's
+ * clock rate is different than the sound device's clock rate.
+ */
+ if (!pjsua_var.is_mswitch &&
+ param->ext_fmt.id == PJMEDIA_FORMAT_PCM &&
+ conf_port->info.clock_rate != param->clock_rate)
+ {
+ pjmedia_port *resample_port;
+ unsigned resample_opt = 0;
- } else {
- break;
- }
+ if (pjsua_var.media_cfg.quality >= 3 &&
+ pjsua_var.media_cfg.quality <= 4)
+ {
+ resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
}
-
- pj_strerror(status, errmsg, sizeof(errmsg));
- PJ_LOG(4, (THIS_FILE, "..failed: %s", errmsg));
+ else if (pjsua_var.media_cfg.quality < 3) {
+ resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
+ }
+
+ status = pjmedia_resample_port_create(pjsua_var.snd_pool,
+ conf_port,
+ param->clock_rate,
+ resample_opt,
+ &resample_port);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4, (THIS_FILE,
+ "Error creating resample port: %s",
+ errmsg));
+ close_snd_dev();
+ return status;
+ }
+
+ conf_port = resample_port;
}
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Unable to open sound device", status);
- return status;
+ /* Otherwise for audio switchboard, the switch's port0 setting is
+ * derived from the sound device setting, so update the setting.
+ */
+ if (pjsua_var.is_mswitch) {
+ pj_memcpy(&conf_port->info.format, &param->ext_fmt,
+ sizeof(conf_port->info.format));
+ conf_port->info.clock_rate = param->clock_rate;
+ conf_port->info.samples_per_frame = param->samples_per_frame;
+ conf_port->info.channel_count = param->channel_count;
+ conf_port->info.bits_per_sample = 16;
}
- /* Set AEC */
- pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.snd_pool,
- pjsua_var.media_cfg.ec_tail_len,
- pjsua_var.media_cfg.ec_options);
-
- /* Connect sound port to the bridge */
+ /* Connect sound port to the bridge */
status = pjmedia_snd_port_connect(pjsua_var.snd_port,
conf_port );
if (status != PJ_SUCCESS) {
@@ -2278,25 +2461,145 @@ PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
}
/* Save the device IDs */
- pjsua_var.cap_dev = capture_dev;
- pjsua_var.play_dev = playback_dev;
+ pjsua_var.cap_dev = param->rec_id;
+ pjsua_var.play_dev = param->play_id;
/* Update sound device name. */
- strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
- pjmedia_snd_stream_get_info(strm, &si);
- play_info = pjmedia_snd_get_dev_info(si.rec_id);
+ {
+ pjmedia_aud_dev_info rec_info;
+ pjmedia_aud_stream *strm;
+ pjmedia_aud_param si;
+ pj_str_t tmp;
+
+ strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+ status = pjmedia_aud_stream_get_param(strm, &si);
+ if (status == PJ_SUCCESS)
+ status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
+
+ if (status==PJ_SUCCESS) {
+ if (param->clock_rate != pjsua_var.media_cfg.clock_rate) {
+ char tmp_buf[128];
+ int tmp_buf_len = sizeof(tmp_buf);
+
+ tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
+ "%s (%dKHz)",
+ rec_info.name,
+ param->clock_rate/1000);
+ pj_strset(&tmp, tmp_buf, tmp_buf_len);
+ pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
+ } else {
+ pjmedia_conf_set_port0_name(pjsua_var.mconf,
+ pj_cstr(&tmp, rec_info.name));
+ }
+ }
+
+ /* Any error is not major, let it through */
+ status = PJ_SUCCESS;
+ };
+
+ /* If this is the first time the audio device is open, retrieve some
+ * settings from the device (such as volume settings) so that the
+ * pjsua_snd_get_setting() work.
+ */
+ if (pjsua_var.aud_open_cnt == 0) {
+ update_initial_aud_param();
+ ++pjsua_var.aud_open_cnt;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* Close existing sound device */
+static void close_snd_dev(void)
+{
+ /* Close sound device */
+ if (pjsua_var.snd_port) {
+ pjmedia_aud_dev_info cap_info, play_info;
+ pjmedia_aud_stream *strm;
+ pjmedia_aud_param param;
+
+ strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+ pjmedia_aud_stream_get_param(strm, &param);
+
+ if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
+ cap_info.name[0] = '\0';
+ if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
+ play_info.name[0] = '\0';
+
+ PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
+ "%s sound capture device",
+ play_info.name, cap_info.name));
+
+ pjmedia_snd_port_disconnect(pjsua_var.snd_port);
+ pjmedia_snd_port_destroy(pjsua_var.snd_port);
+ pjsua_var.snd_port = NULL;
+ }
+
+ /* Close null sound device */
+ if (pjsua_var.null_snd) {
+ PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
+ pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
+ pjsua_var.null_snd = NULL;
+ }
+
+ if (pjsua_var.snd_pool)
+ pj_pool_release(pjsua_var.snd_pool);
+ pjsua_var.snd_pool = NULL;
+}
+
+
+/*
+ * Select or change sound device. Application may call this function at
+ * any time to replace current sound device.
+ */
+PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
+ int playback_dev)
+{
+ unsigned alt_cr_cnt = 1;
+ unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
+ unsigned i;
+ pj_status_t status = -1;
- if (si.clock_rate != pjsua_var.media_cfg.clock_rate) {
- char tmp_buf[128];
- int tmp_buf_len = sizeof(tmp_buf);
+ /* Set default clock rate */
+ alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
+ if (alt_cr[0] == 0)
+ alt_cr[0] = pjsua_var.media_cfg.clock_rate;
- tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1, "%s (%dKHz)",
- play_info->name, si.clock_rate/1000);
- pj_strset(&tmp, tmp_buf, tmp_buf_len);
- pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
+ /* Allow retrying of different clock rate if we're using conference
+ * bridge (meaning audio format is always PCM), otherwise lock on
+ * to one clock rate.
+ */
+ if (pjsua_var.is_mswitch) {
+ alt_cr_cnt = 1;
} else {
- pjmedia_conf_set_port0_name(pjsua_var.mconf,
- pj_cstr(&tmp, play_info->name));
+ alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
+ }
+
+ /* Attempts to open the sound device with different clock rates */
+ for (i=0; i<alt_cr_cnt; ++i) {
+ pjmedia_aud_param param;
+ unsigned samples_per_frame;
+
+ /* Create the default audio param */
+ samples_per_frame = alt_cr[i] *
+ pjsua_var.media_cfg.audio_frame_ptime *
+ pjsua_var.media_cfg.channel_count / 1000;
+ status = create_aud_param(&param, capture_dev, playback_dev,
+ alt_cr[i], pjsua_var.media_cfg.channel_count,
+ samples_per_frame, 16);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Open! */
+ status = open_snd_dev(&param);
+ if (status == PJ_SUCCESS)
+ break;
+ }
+
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to open sound device", status);
+ return status;
}
return PJ_SUCCESS;
@@ -2404,6 +2707,81 @@ PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
}
+/*
+ * Check whether the sound device is currently active.
+ */
+PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
+{
+ return pjsua_var.snd_port != NULL;
+}
+
+
+/*
+ * Configure sound device setting to the sound device being used.
+ */
+PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
+ const void *pval,
+ pj_bool_t keep)
+{
+ pj_status_t status;
+
+ /* Check if we are allowed to set the cap */
+ if ((cap & pjsua_var.aud_svmask) == 0) {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+
+ /* If sound is active, set it immediately */
+ if (pjsua_snd_is_active()) {
+ pjmedia_aud_stream *strm;
+
+ strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+ status = pjmedia_aud_stream_set_cap(strm, cap, pval);
+ } else {
+ status = PJ_SUCCESS;
+ }
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Save in internal param for later device open */
+ if (keep) {
+ status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
+ cap, pval);
+ }
+
+ return status;
+}
+
+/*
+ * Retrieve a sound device setting.
+ */
+PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ /* If sound device has never been opened before, open it to
+ * retrieve the initial setting from the device (e.g. audio
+ * volume)
+ */
+ if (pjsua_var.aud_open_cnt==0) {
+ PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
+ pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
+ close_snd_dev();
+ }
+
+ if (pjsua_snd_is_active()) {
+ /* Sound is active, retrieve from device directly */
+ pjmedia_aud_stream *strm;
+
+ strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+ return pjmedia_aud_stream_get_cap(strm, cap, pval);
+ } else {
+ /* Otherwise retrieve from internal param */
+ return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
+ cap, pval);
+ }
+}
+
+
/*****************************************************************************
* Codecs.
*/