diff options
author | Benny Prijono <bennylp@teluu.com> | 2009-03-12 18:11:37 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2009-03-12 18:11:37 +0000 |
commit | 1dacdee696b7591a6dcc0b3c1d0f41573e473168 (patch) | |
tree | 302b09dcd989c0c05cf09f6aebaa63d870b421b9 | |
parent | ba9d8ca28eb209571c0bd6a080a8bb03d0fa2d33 (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
100 files changed, 14101 insertions, 6613 deletions
@@ -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, ¶m); + \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(¶m, &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*)¶m->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, ¶m->rec_id); + make_global_index(f->sys.drv_idx, ¶m->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(¶m, 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, ¶m, 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, ¶m->rec_id); + make_global_index(strm->sys.drv_idx, ¶m->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, + ¶m->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, + ¶m->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, ¶m); 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, ¶m); + } else { + status = pjmedia_aud_dev_default_param(play_id, ¶m); + } + 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(¶m, &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, ¶m); + 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(¶m_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(¶m_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, ¶m); + 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, ¶m, 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, ¶m); + 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, ¶m, 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, ¶m); + 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, ¶m, 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, ¶m); + } else { + status = pjmedia_aud_dev_default_param(play_id, ¶m); + } + + 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(¶m, &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, ¶m); + 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(¶m, &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, ¶m); + 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(¶m, 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(¶m, 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(¶m); + 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, ¶m); + 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, ¶m->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, ¶m); + + 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(¶m, 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(¶m); + 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. */ |