Initial sources of APS-direct.
git-svn-id: https://svn.pjsip.org/repos/pjproject/branches/projects/aps-direct@2434 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/build.symbian/bld.inf b/build.symbian/bld.inf
index b624905..4c9c13a 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
@@ -25,13 +22,7 @@
libspeexcodec.mmp
/* Sound device impl */
-#if SND_USE_NULL
- null_audio.mmp
-#elif SND_USE_APS
- symbian_audio_aps.mmp
-#else
- symbian_audio.mmp
-#endif
+symbian_audio.mmp
/* Applications */
//pjlib_test.mmp
diff --git a/build.symbian/pjmedia.mmp b/build.symbian/pjmedia.mmp
index ce2d279..6c48b6c 100644
--- a/build.symbian/pjmedia.mmp
+++ b/build.symbian/pjmedia.mmp
@@ -40,6 +40,7 @@
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/symbian_audio.mmp b/build.symbian/symbian_audio.mmp
index 571b4b0..e53f4e8 100644
--- a/build.symbian/symbian_audio.mmp
+++ b/build.symbian/symbian_audio.mmp
@@ -24,22 +24,23 @@
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_ua.mmp b/build.symbian/symbian_ua.mmp
index 938b84d..5f7f1f8 100644
--- a/build.symbian/symbian_ua.mmp
+++ b/build.symbian/symbian_ua.mmp
@@ -1,63 +1,61 @@
-#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_USE_APS 1
+#define SND_USE_VAS 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
+STATICLIBRARY symbian_audio.lib
+
+#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
+ 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
+
diff --git a/build.symbian/symsndtest.mmp b/build.symbian/symsndtest.mmp
index a08f1d8..470de9d 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/pjmedia/build/pjmedia.vcproj b/pjmedia/build/pjmedia.vcproj
index d51f300..2add52d 100644
--- a/pjmedia/build/pjmedia.vcproj
+++ b/pjmedia/build/pjmedia.vcproj
@@ -95,78 +95,6 @@
/>
</Configuration>
<Configuration
- Name="Release|Windows Mobile 6 Standard SDK (ARMV4I)"
- OutputDirectory="output\$(ProjectName)-$(PlatformName)-$(ConfigurationName)"
- IntermediateDirectory="$(OutDir)"
- ConfigurationType="4"
- CharacterSet="1"
- >
- <Tool
- Name="VCPreBuildEventTool"
- />
- <Tool
- Name="VCCustomBuildTool"
- />
- <Tool
- Name="VCXMLDataGeneratorTool"
- />
- <Tool
- Name="VCWebServiceProxyGeneratorTool"
- />
- <Tool
- Name="VCMIDLTool"
- />
- <Tool
- Name="VCCLCompilerTool"
- ExecutionBucket="7"
- Optimization="2"
- FavorSizeOrSpeed="2"
- AdditionalIncludeDirectories="../include,../../pjlib/include,../../pjlib-util/include,../../pjnath/include,../../third_party/portaudio/include,../../third_party/speex/include,../../third_party/build/srtp,../../third_party/srtp/include,../../third_party/srtp/crypto/include;../.."
- PreprocessorDefinitions="NDEBUG;_WIN32_WCE=$(CEVER);UNDER_CE;$(PLATFORMDEFINES);WINCE;$(ARCHFAM);$(_ARCHFAM_);SMARTPHONE2003_UI_MODEL;SMARTPHONE2003_UI_MODEL"
- ExceptionHandling="0"
- RuntimeLibrary="0"
- WarningLevel="3"
- DebugInformationFormat="0"
- CompileAs="0"
- />
- <Tool
- Name="VCManagedResourceCompilerTool"
- />
- <Tool
- Name="VCResourceCompilerTool"
- />
- <Tool
- Name="VCPreLinkEventTool"
- />
- <Tool
- Name="VCLibrarianTool"
- AdditionalOptions=""
- />
- <Tool
- Name="VCALinkTool"
- />
- <Tool
- Name="VCXDCMakeTool"
- />
- <Tool
- Name="VCBscMakeTool"
- />
- <Tool
- Name="VCCodeSignTool"
- />
- <Tool
- Name="VCPostBuildEventTool"
- />
- <DeploymentTool
- ForceDirty="-1"
- RemoteDirectory=""
- RegisterOutput="0"
- AdditionalFiles=""
- />
- <DebuggerTool
- />
- </Configuration>
- <Configuration
Name="Debug|Win32"
OutputDirectory=".\output\pjmedia-i386-win32-vc8-debug"
IntermediateDirectory=".\output\pjmedia-i386-win32-vc8-debug"
@@ -243,6 +171,78 @@
/>
</Configuration>
<Configuration
+ Name="Release|Windows Mobile 6 Standard SDK (ARMV4I)"
+ OutputDirectory="output\$(ProjectName)-$(PlatformName)-$(ConfigurationName)"
+ IntermediateDirectory="$(OutDir)"
+ ConfigurationType="4"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ ExecutionBucket="7"
+ Optimization="2"
+ FavorSizeOrSpeed="2"
+ AdditionalIncludeDirectories="../include,../../pjlib/include,../../pjlib-util/include,../../pjnath/include,../../third_party/portaudio/include,../../third_party/speex/include,../../third_party/build/srtp,../../third_party/srtp/include,../../third_party/srtp/crypto/include;../.."
+ PreprocessorDefinitions="NDEBUG;_WIN32_WCE=$(CEVER);UNDER_CE;$(PLATFORMDEFINES);WINCE;$(ARCHFAM);$(_ARCHFAM_);SMARTPHONE2003_UI_MODEL;SMARTPHONE2003_UI_MODEL"
+ ExceptionHandling="0"
+ RuntimeLibrary="0"
+ WarningLevel="3"
+ DebugInformationFormat="0"
+ CompileAs="0"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ AdditionalOptions=""
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCCodeSignTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ <DeploymentTool
+ ForceDirty="-1"
+ RemoteDirectory=""
+ RegisterOutput="0"
+ AdditionalFiles=""
+ />
+ <DebuggerTool
+ />
+ </Configuration>
+ <Configuration
Name="Debug|Windows Mobile 6 Standard SDK (ARMV4I)"
OutputDirectory="output\$(ProjectName)-$(PlatformName)-$(ConfigurationName)"
IntermediateDirectory="$(OutDir)"
@@ -434,6 +434,10 @@
</FileConfiguration>
</File>
<File
+ RelativePath="..\src\pjmedia\conf_switch.c"
+ >
+ </File>
+ <File
RelativePath="..\src\pjmedia\conference.c"
>
<FileConfiguration
diff --git a/pjmedia/include/pjmedia-codec/config.h b/pjmedia/include/pjmedia-codec/config.h
index e50185d..40376d1 100644
--- a/pjmedia/include/pjmedia-codec/config.h
+++ b/pjmedia/include/pjmedia-codec/config.h
@@ -194,5 +194,13 @@
# 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
#endif /* __PJMEDIA_CODEC_CONFIG_H__ */
diff --git a/pjmedia/include/pjmedia/conference.h b/pjmedia/include/pjmedia/conference.h
index bc14c54..a6ffb3d 100644
--- a/pjmedia/include/pjmedia/conference.h
+++ b/pjmedia/include/pjmedia/conference.h
@@ -56,6 +56,7 @@
{
unsigned slot; /**< Slot number. */
pj_str_t name; /**< Port name. */
+ pjmedia_fourcc format; /**< Format (FourCC identifier) */
pjmedia_port_op tx_setting; /**< Transmit settings. */
pjmedia_port_op rx_setting; /**< Receive settings. */
unsigned listener_cnt; /**< Number of listeners. */
diff --git a/pjmedia/include/pjmedia/config.h b/pjmedia/include/pjmedia/config.h
index 735192e..de6e680 100644
--- a/pjmedia/include/pjmedia/config.h
+++ b/pjmedia/include/pjmedia/config.h
@@ -44,6 +44,22 @@
# 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.
*/
@@ -60,6 +76,16 @@
/** Constant for Win32 MME sound backend. */
#define PJMEDIA_SOUND_WIN32_MME_SOUND 3
+/** Constant for Symbian Multimedia Audio Stream backend. */
+#define PJMEDIA_SOUND_SYMB_MDA_SOUND 4
+
+/** Constant for Symbian APS backend. */
+#define PJMEDIA_SOUND_SYMB_APS_SOUND 5
+
+/** Constant for Symbian VAS backend. */
+#define PJMEDIA_SOUND_SYMB_VAS_SOUND 6
+
+
/** 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.
diff --git a/pjmedia/include/pjmedia/port.h b/pjmedia/include/pjmedia/port.h
index 9d7c86c..245ef00 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 @@
pj_bool_t has_info; /**< Has info? */
pj_bool_t need_info; /**< Need info on connect? */
unsigned pt; /**< Payload type (can be dynamic). */
+ pjmedia_fourcc format; /**< Format (FourCC identifier) */
pj_str_t encoding_name; /**< Encoding name. */
unsigned clock_rate; /**< Sampling rate. */
unsigned channel_count; /**< Number of channels. */
@@ -226,7 +228,8 @@
typedef enum pjmedia_frame_type
{
PJMEDIA_FRAME_TYPE_NONE, /**< No frame. */
- PJMEDIA_FRAME_TYPE_AUDIO /**< Normal audio frame. */
+ PJMEDIA_FRAME_TYPE_AUDIO, /**< Normal audio frame. */
+ PJMEDIA_FRAME_TYPE_EXTENDED /**< Extended audio frame. */
} pjmedia_frame_type;
@@ -249,6 +252,88 @@
/**
+ * 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.
+ */
+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;
+
+/**
+ * This structure represents the individual subframes in the
+ * pjmedia_frame_ext structure.
+ */
+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;
+
+
+/* Append one subframe to the frame_ext */
+PJ_INLINE(void) pjmedia_frame_ext_append_subframe(pjmedia_frame_ext *frm,
+ const void *src,
+ pj_uint16_t bitlen,
+ pj_uint16_t samples_cnt)
+{
+ pj_uint8_t *p;
+ unsigned i, tmp;
+
+ p = (pj_uint8_t*)frm + sizeof(pjmedia_frame_ext);
+ for (i = 0; i < frm->subframe_cnt; ++i) {
+ pjmedia_frame_ext_subframe *fsub;
+ fsub = (pjmedia_frame_ext_subframe*) p;
+ p += fsub->bitlen / 8;
+ if (fsub->bitlen % 8)
+ ++p;
+ }
+
+ tmp = bitlen / 8;
+ if (bitlen % 8) ++tmp;
+ pj_memcpy(p, &bitlen, sizeof(bitlen));
+ pj_memcpy(p + sizeof(bitlen), src, tmp);
+ frm->subframe_cnt++;
+ frm->samples_cnt = frm->samples_cnt + samples_cnt;
+}
+
+/* Get the pointer and length of the n-th subframe */
+PJ_INLINE(pjmedia_frame_ext_subframe*)
+ pjmedia_frame_ext_get_subframe(const pjmedia_frame_ext *frm,
+ unsigned n)
+{
+ pj_uint8_t *p;
+ unsigned i;
+ pjmedia_frame_ext_subframe *tmp;
+
+ pj_assert(n < frm->subframe_cnt);
+
+ p = (pj_uint8_t*)frm + sizeof(pjmedia_frame_ext);
+ for (i = 0; i < n; ++i) {
+ tmp = (pjmedia_frame_ext_subframe*) p;
+ p += tmp->bitlen / 8;
+ if (tmp->bitlen % 8)
+ ++p;
+ }
+
+ tmp = (pjmedia_frame_ext_subframe*) p;
+ return tmp;
+}
+
+/**
* Port interface.
*/
typedef struct pjmedia_port
diff --git a/pjmedia/include/pjmedia/symbian_sound_aps.h b/pjmedia/include/pjmedia/symbian_sound_aps.h
index 3208b41..4d7c8ee 100644
--- a/pjmedia/include/pjmedia/symbian_sound_aps.h
+++ b/pjmedia/include/pjmedia/symbian_sound_aps.h
@@ -31,6 +31,24 @@
PJ_BEGIN_DECL
/**
+ * Declaration of APS sound setting.
+ */
+typedef struct pjmedia_snd_aps_setting
+{
+ pjmedia_fourcc format; /**< Format (FourCC ID). */
+ pj_uint32_t bitrate; /**< Bitrate (bps). */
+ pj_uint32_t mode; /**< Mode, currently only used
+ for specifying iLBC mode,
+ 20ms or 30ms frame size. */
+ pj_bool_t plc; /**< PLC enabled/disabled. */
+ pj_bool_t vad; /**< VAD enabled/disabled. */
+ pj_bool_t cng; /**< CNG enabled/disabled. */
+ pj_bool_t loudspk; /**< Audio routed to loudspeaker.*/
+
+} pjmedia_snd_aps_setting;
+
+
+/**
* Activate/deactivate loudspeaker, when loudspeaker is inactive, audio
* will be routed to earpiece.
*
@@ -47,6 +65,17 @@
pj_bool_t active);
+/**
+ * Set a codec and its settings to be used on the next sound device session.
+ *
+ * @param setting APS sound device setting, see @pjmedia_snd_aps_setting.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_snd_aps_modify_setting(
+ const pjmedia_snd_aps_setting *setting);
+
+
PJ_END_DECL
diff --git a/pjmedia/include/pjmedia/types.h b/pjmedia/include/pjmedia/types.h
index e456e34..14786ca 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 @@
/** 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 @@
} pjmedia_type;
-/**
- * Media transport protocol.
+/**
+ * Media transport protocol.
*/
typedef enum pjmedia_tp_proto
{
@@ -92,8 +92,8 @@
} pjmedia_tp_proto;
-/**
- * Media direction.
+/**
+ * Media direction.
*/
typedef enum pjmedia_dir
{
@@ -138,8 +138,8 @@
(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_stream pjmedia_stream;
-/**
+/**
* Media socket info is used to describe the underlying sockets
* to be used as media transport.
*/
@@ -178,10 +178,34 @@
} pjmedia_sock_info;
+/**
+ * Declaration of FourCC type.
+ */
+typedef union pjmedia_fourcc {
+ pj_uint32_t u32;
+ char c[4];
+} pjmedia_fourcc;
+
+
+/**
+ * FourCC packing macro.
+ */
+#define PJMEDIA_FOURCC_PACK(C1, C2, C3, C4) ( C1<<24 | C2<<16 | C3<<8 | C4 )
+
+/**
+ * FourCC identifier definitions.
+ */
+#define PJMEDIA_FOURCC_L16 PJMEDIA_FOURCC_PACK(' ', 'L', '1', '6')
+#define PJMEDIA_FOURCC_G711A PJMEDIA_FOURCC_PACK('G', '7', '1', '1')
+#define PJMEDIA_FOURCC_G711U PJMEDIA_FOURCC_PACK('U', 'L', 'A', 'W')
+#define PJMEDIA_FOURCC_AMR PJMEDIA_FOURCC_PACK(' ', 'A', 'M', 'R')
+#define PJMEDIA_FOURCC_G729 PJMEDIA_FOURCC_PACK('G', '7', '2', '9')
+#define PJMEDIA_FOURCC_ILBC PJMEDIA_FOURCC_PACK('i', 'L', 'B', 'C')
+
/**
* 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 +229,7 @@
/**
* 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 +244,7 @@
#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 +252,7 @@
/**
* 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,7 +267,7 @@
#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
}
diff --git a/pjmedia/src/pjmedia/conf_switch.c b/pjmedia/src/pjmedia/conf_switch.c
new file mode 100644
index 0000000..5534076
--- /dev/null
+++ b/pjmedia/src/pjmedia/conf_switch.c
@@ -0,0 +1,1240 @@
+/* $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/errno.h>
+#include <pjmedia/port.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
+
+
+/* REC_FILE macro enables recording of the samples written to the sound
+ * device. The file contains RAW PCM data with no header, and has the
+ * same settings (clock rate etc) as the conference bridge.
+ * This should only be enabled when debugging audio quality *only*.
+ */
+//#define REC_FILE "confrec.pcm"
+#ifdef REC_FILE
+static FILE *fhnd_rec;
+#endif
+
+
+#define THIS_FILE "conf_switch.c"
+
+#define SIGNATURE PJMEDIA_PORT_SIGNATURE('S', 'W', 'T', 'C')
+#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
+
+/*
+ * 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
+{
+ 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. */
+ unsigned clock_rate; /**< Port's clock rate. */
+ unsigned samples_per_frame; /**< Port's samples per frame. */
+ unsigned channel_count; /**< Port's channel count. */
+
+ /* 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;
+
+ /* 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. */
+
+ /* When the port is not receiving signal from any other ports (e.g. when
+ * no other ports is transmitting to this port), the bridge periodically
+ * transmit NULL frame to the port to keep the port "alive" (for example,
+ * a stream port needs this heart-beat to periodically transmit silence
+ * frame to keep NAT binding alive).
+ *
+ * This NULL frame should be sent to the port at the port's ptime rate.
+ * So if the port's ptime is greater than the bridge's ptime, the bridge
+ * needs to delay the NULL frame until it's the right time to do so.
+ *
+ * This variable keeps track of how many pending NULL samples are being
+ * "held" for this port. Once this value reaches samples_per_frame
+ * value of the port, a NULL frame is sent. The samples value on this
+ * variable is clocked at the port's clock rate.
+ */
+ unsigned tx_heart_beat;
+};
+
+
+/*
+ * 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_snd_port *snd_dev_port; /**< Sound device port. */
+ 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. */
+ unsigned clock_rate; /**< Sampling rate. */
+ unsigned channel_count;/**< Number of channels (1=mono). */
+ unsigned samples_per_frame; /**< Samples per frame. */
+ unsigned bits_per_sample; /**< Bits per sample. */
+ 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;
+
+ /* 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;
+
+ /* 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. */
+ if (port) {
+ conf_port->port = port;
+ conf_port->clock_rate = port->info.clock_rate;
+ conf_port->samples_per_frame = port->info.samples_per_frame;
+ conf_port->channel_count = port->info.channel_count;
+ } else {
+ conf_port->port = NULL;
+ conf_port->clock_rate = conf->clock_rate;
+ conf_port->samples_per_frame = conf->samples_per_frame;
+ conf_port->channel_count = conf->channel_count;
+ }
+
+ /* Init pjmedia_frame structure in the TX buffer. */
+ f = (pjmedia_frame*)conf_port->tx_buf;
+ f->buf = conf_port->tx_buf + sizeof(pjmedia_frame);
+ f->size = 0;
+
+ /* 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, NULL, &name, &conf_port);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /* Create sound device port: */
+
+ if ((conf->options & PJMEDIA_CONF_NO_DEVICE) == 0) {
+ pjmedia_snd_stream *strm;
+ pjmedia_snd_stream_info si;
+
+ /*
+ * If capture is disabled then create player only port.
+ * Otherwise create bidirectional sound device port.
+ */
+ if (conf->options & PJMEDIA_CONF_NO_MIC) {
+ status = pjmedia_snd_port_create_player(pool, -1, conf->clock_rate,
+ conf->channel_count,
+ conf->samples_per_frame,
+ conf->bits_per_sample,
+ 0, /* options */
+ &conf->snd_dev_port);
+
+ } else {
+ status = pjmedia_snd_port_create( pool, -1, -1, conf->clock_rate,
+ conf->channel_count,
+ conf->samples_per_frame,
+ conf->bits_per_sample,
+ 0, /* Options */
+ &conf->snd_dev_port);
+
+ }
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ strm = pjmedia_snd_port_get_snd_stream(conf->snd_dev_port);
+ status = pjmedia_snd_stream_get_info(strm, &si);
+ if (status == PJ_SUCCESS) {
+ const pjmedia_snd_dev_info *snd_dev_info;
+ if (conf->options & PJMEDIA_CONF_NO_MIC)
+ snd_dev_info = pjmedia_snd_get_dev_info(si.play_id);
+ else
+ snd_dev_info = pjmedia_snd_get_dev_info(si.rec_id);
+ pj_strdup2_with_null(pool, &conf_port->name, snd_dev_info->name);
+ }
+ }
+
+
+ /* Add the port to the bridge */
+ 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;
+ conf->clock_rate = clock_rate;
+ conf->channel_count = channel_count;
+ conf->samples_per_frame = samples_per_frame;
+ conf->bits_per_sample = bits_per_sample;
+
+
+ /* 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;
+
+ /* If sound device was created, connect sound device to the
+ * master port.
+ */
+ if (conf->snd_dev_port) {
+ status = pjmedia_snd_port_connect( conf->snd_dev_port,
+ conf->master_port );
+ if (status != PJ_SUCCESS) {
+ pjmedia_conf_destroy(conf);
+ 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 sound device port. */
+ if (conf->snd_dev_port) {
+ pjmedia_snd_port_destroy(conf->snd_dev_port);
+ conf->snd_dev_port = NULL;
+ }
+
+ /* 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;
+
+ if (conf->master_port)
+ 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);
+ 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->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];
+
+ /* Check if source and sink has compatible format */
+ if (src_slot != 0 && sink_slot != 0 &&
+ src_port->port->info.format.u32 != dst_port->port->info.format.u32)
+ {
+ pj_mutex_unlock(conf->mutex);
+ return PJMEDIA_ENOTCOMPATIBLE;
+ }
+
+ /* 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) {
+ 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;
+
+ 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;
+
+ 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;
+ }
+
+ /* 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->clock_rate = conf_port->clock_rate;
+ info->channel_count = conf_port->channel_count;
+ info->samples_per_frame = conf_port->samples_per_frame;
+ info->bits_per_sample = conf->bits_per_sample;
+ info->format = slot? conf_port->port->info.format :
+ conf->master_port->info.format;
+
+ 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];
+
+ /* 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];
+
+ return PJ_SUCCESS;
+}
+
+/* Deliver frm_src to a conference port (via frm_dst), 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 deliver_frame(struct conf_port *cport_dst,
+ pjmedia_frame *frm_dst,
+ const pjmedia_frame *frm_src)
+{
+ PJ_TODO(MAKE_SURE_DEST_FRAME_HAS_ENOUGH_SPACE);
+
+ 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;
+
+ /* Copy frame to listener's TX buffer. */
+ for (i = 0; i < f_src->subframe_cnt; ++i) {
+ pjmedia_frame_ext_subframe *sf;
+
+ 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->samples_per_frame)
+ {
+ f_dst->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ if (cport_dst->port) {
+ pjmedia_port_put_frame(cport_dst->port, (pjmedia_frame*)f_dst);
+
+ /* Reset TX buffer. */
+ f_dst->subframe_cnt = 0;
+ f_dst->samples_cnt = 0;
+ }
+ }
+ }
+
+ } else {
+
+ pjmedia_frame *f_dst = (pjmedia_frame*)frm_dst;
+ 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;
+
+ nsamples_to_copy = f_end - f_start;
+ nsamples_req = cport_dst->samples_per_frame - (f_dst->size >> 1);
+ if (nsamples_to_copy > nsamples_req)
+ nsamples_to_copy = nsamples_req;
+ pjmedia_copy_samples((pj_int16_t*)f_dst->buf + (f_dst->size >> 1),
+ f_start,
+ nsamples_to_copy);
+ f_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 ((f_dst->size >> 1) == cport_dst->samples_per_frame)
+ {
+ f_dst->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ if (cport_dst->port) {
+ pjmedia_port_put_frame(cport_dst->port, f_dst);
+
+ /* Reset TX buffer. */
+ f_dst->size = 0;
+ }
+ }
+ }
+
+ }
+
+ 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;
+
+ PJ_TODO(ADJUST_AND_CALC_RX_TX_LEVEL_FOR_PCM_FRAMES);
+
+ TRACE_((THIS_FILE, "- clock -"));
+
+ /* 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;
+
+ /* Skip if we're not allowed to receive from this port. */
+ if (cport->rx_setting == PJMEDIA_PORT_DISABLE) {
+ cport->rx_level = 0;
+ continue;
+ }
+
+ /* Also skip if this port doesn't have listeners. */
+ if (cport->listener_cnt == 0) {
+ cport->rx_level = 0;
+ continue;
+ }
+
+ pj_add_timestamp32(&cport->ts_clock, conf->samples_per_frame);
+
+ /* This loop will make sure the ptime between port & conf port
+ * are 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_add_timestamp32(&cport->ts_rx, cport->samples_per_frame);
+
+ f->buf = &conf->buf[sizeof(pjmedia_frame)];
+ f->size = BUFFER_SIZE - sizeof(pjmedia_frame);
+
+ /* Get frame from port. */
+ status = pjmedia_port_get_frame(cport->port, f);
+ if (status != PJ_SUCCESS)
+ continue;
+
+ if (f->type == PJMEDIA_FRAME_TYPE_NONE) {
+ if (cport->port->info.format.u32 == PJMEDIA_FOURCC_L16) {
+ pjmedia_zero_samples((pj_int16_t*)f->buf,
+ cport->samples_per_frame);
+ f->size = cport->samples_per_frame << 1;
+ f->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ } else {
+ /* Handle DTX */
+ PJ_TODO(HANDLE_DTX);
+ }
+ }
+
+ /* Put the frame to all listeners. */
+ for (j=0; j < cport->listener_cnt; ++j)
+ {
+ struct conf_port *listener;
+ pjmedia_frame *frm_dst;
+
+ listener = conf->ports[cport->listener_slots[j]];
+
+ /* Skip if this listener doesn't want to receive audio */
+ if (listener->tx_setting != PJMEDIA_PORT_ENABLE)
+ continue;
+
+ if (listener->port)
+ frm_dst = frame;
+ else
+ frm_dst = (pjmedia_frame*)listener->tx_buf;
+
+ status = deliver_frame(listener, frm_dst, f);
+ if (status != PJ_SUCCESS)
+ continue;
+ }
+ }
+
+ /* Keep alive mechanism. */
+ PJ_TODO(SEND_KEEP_ALIVE_WHEN_NEEDED);
+ }
+
+ /* 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 *frame)
+{
+ pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
+ struct conf_port *port = conf->ports[this_port->port_data.ldata];
+ unsigned j;
+
+ /* Check for correct size. */
+ PJ_ASSERT_RETURN( frame->size == conf->samples_per_frame *
+ conf->bits_per_sample / 8,
+ PJMEDIA_ENCSAMPLESPFRAME);
+
+ /* Skip if this port is muted/disabled. */
+ if (port->rx_setting != PJMEDIA_PORT_ENABLE) {
+ return PJ_SUCCESS;
+ }
+
+ /* Skip if no port is listening to the microphone */
+ if (port->listener_cnt == 0) {
+ return PJ_SUCCESS;
+ }
+
+ if (frame->type == PJMEDIA_FRAME_TYPE_NONE) {
+ if (this_port->info.format.u32 == PJMEDIA_FOURCC_L16) {
+ pjmedia_frame *f = (pjmedia_frame*)port->tx_buf;
+
+ pjmedia_zero_samples((pj_int16_t*)f->buf,
+ port->samples_per_frame);
+ f->size = port->samples_per_frame << 1;
+ f->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame = f;
+ } else {
+ /* Handle DTX */
+ PJ_TODO(HANDLE_DTX);
+ }
+ }
+
+ /* Put the frame to all listeners. */
+ for (j=0; j < port->listener_cnt; ++j)
+ {
+ struct conf_port *listener;
+ pjmedia_frame *frm_dst;
+ pj_status_t status;
+
+ listener = conf->ports[port->listener_slots[j]];
+
+ /* Skip if this listener doesn't want to receive audio */
+ if (listener->tx_setting != PJMEDIA_PORT_ENABLE)
+ continue;
+
+ /* Skip loopback for now. */
+ if (listener == port)
+ continue;
+
+ frm_dst = (pjmedia_frame*)listener->tx_buf;
+
+ status = deliver_frame(listener, frm_dst, frame);
+ if (status != PJ_SUCCESS)
+ continue;
+ }
+
+ return PJ_SUCCESS;
+}
+
+#endif
diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c
index 9cae6a7..c71ef2e 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).
@@ -1987,3 +1988,4 @@
return status;
}
+#endif
diff --git a/pjmedia/src/pjmedia/symbian_sound.cpp b/pjmedia/src/pjmedia/symbian_sound.cpp
index b7c92f9..e6b8651 100644
--- a/pjmedia/src/pjmedia/symbian_sound.cpp
+++ b/pjmedia/src/pjmedia/symbian_sound.cpp
@@ -23,6 +23,7 @@
#include <pj/log.h>
#include <pj/os.h>
+#if PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_SYMB_MDA_SOUND
/*
* This file provides sound implementation for Symbian Audio Streaming
@@ -942,3 +943,5 @@
PJ_UNUSED_ARG(output_latency);
return PJ_SUCCESS;
}
+
+#endif
diff --git a/pjmedia/src/pjmedia/symbian_sound_aps.cpp b/pjmedia/src/pjmedia/symbian_sound_aps.cpp
index 2e9bf2f..687815c 100644
--- a/pjmedia/src/pjmedia/symbian_sound_aps.cpp
+++ b/pjmedia/src/pjmedia/symbian_sound_aps.cpp
@@ -17,6 +17,7 @@
* 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/symbian_sound_aps.h>
#include <pjmedia/sound.h>
#include <pjmedia/alaw_ulaw.h>
#include <pjmedia/errno.h>
@@ -25,6 +26,8 @@
#include <pj/math.h>
#include <pj/os.h>
+#if PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_SYMB_APS_SOUND
+
#include <e32msgqueue.h>
#include <sounddevice.h>
#include <APSClientSession.h>
@@ -45,7 +48,7 @@
# define TRACE_(st)
#endif
-static pjmedia_snd_dev_info symbian_snd_dev_info =
+static pjmedia_snd_dev_info symbian_snd_dev_info =
{
"Symbian Sound Device (APS)",
1,
@@ -56,56 +59,81 @@
/* 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;
+/* Default setting */
+static pjmedia_snd_aps_setting def_setting;
+
+/* APS G.711 frame length */
+static pj_uint8_t aps_g711_frame_len;
+
+/* Pool factory */
+static pj_pool_factory *snd_pool_factory;
/* Forward declaration of CPjAudioEngine */
class CPjAudioEngine;
-/*
- * PJMEDIA Sound Stream instance
+/*
+ * PJMEDIA Sound Stream instance
*/
struct pjmedia_snd_stream
{
// Pool
- pj_pool_t *pool;
+ pj_pool_t *pool;
// Common settings.
- pjmedia_dir dir;
- unsigned clock_rate;
- unsigned channel_count;
- unsigned samples_per_frame;
+ pjmedia_dir dir;
+ unsigned clock_rate;
+ unsigned channel_count;
+ unsigned samples_per_frame;
+ pjmedia_snd_rec_cb rec_cb;
+ pjmedia_snd_play_cb play_cb;
+ void *user_data;
// Audio engine
- CPjAudioEngine *engine;
-};
+ CPjAudioEngine *engine;
-static pj_pool_factory *snd_pool_factory;
+ pj_timestamp ts_play;
+ pj_timestamp ts_rec;
+
+ 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;
+};
/*
* Utility: print sound device error
*/
-static void snd_perror(const char *title, TInt rc)
+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;
- virtual void RecCb(TAPSCommBuffer &buffer) = 0;
- virtual void PlayCb(TAPSCommBuffer &buffer) = 0;
+public:
+ PjAudioCallback RecCb;
+ PjAudioCallback PlayCb;
+ void *UserData;
};
/**
@@ -132,11 +160,13 @@
EAPSRecorderInitComplete = 6
};
- static CQueueHandler* NewL(MQueueHandlerObserver* aObserver,
- RMsgQueue<TAPSCommBuffer>* aQ,
+ static CQueueHandler* NewL(MQueueHandlerObserver* aObserver,
+ RMsgQueue<TAPSCommBuffer>* aQ,
+ RMsgQueue<TAPSCommBuffer>* aWriteQ,
TQueueHandlerType aType)
{
- CQueueHandler* self = new (ELeave) CQueueHandler(aObserver, aQ, aType);
+ CQueueHandler* self = new (ELeave) CQueueHandler(aObserver, aQ, aWriteQ,
+ aType);
CleanupStack::PushL(self);
self->ConstructL();
CleanupStack::Pop(self);
@@ -154,11 +184,12 @@
private:
// Constructor
- CQueueHandler(MQueueHandlerObserver* aObserver,
- RMsgQueue<TAPSCommBuffer>* aQ,
- TQueueHandlerType aType)
+ CQueueHandler(MQueueHandlerObserver* aObserver,
+ RMsgQueue<TAPSCommBuffer>* aQ,
+ RMsgQueue<TAPSCommBuffer>* aWriteQ,
+ TQueueHandlerType aType)
: CActive(CActive::EPriorityHigh),
- iQ(aQ), iObserver(aObserver), iType(aType)
+ iQ(aQ), iWriteQ(aWriteQ), iObserver(aObserver), iType(aType)
{
CActiveScheduler::Add(this);
@@ -177,7 +208,7 @@
if (iStatus != KErrNone) {
iObserver->NotifyError(iStatus.Int());
return;
- }
+ }
TAPSCommBuffer buffer;
TInt ret = iQ->Receive(buffer);
@@ -190,7 +221,9 @@
switch (iType) {
case ERecordQueue:
if (buffer.iCommand == EAPSRecordData) {
- iObserver->RecCb(buffer);
+ iObserver->RecCb(buffer, iObserver->UserData);
+ } else {
+ iObserver->NotifyError(buffer.iStatus);
}
break;
@@ -199,7 +232,8 @@
switch (buffer.iCommand) {
case EAPSPlayData:
if (buffer.iStatus == KErrUnderflow) {
- iObserver->PlayCb(buffer);
+ iObserver->PlayCb(buffer, iObserver->UserData);
+ iWriteQ->Send(buffer);
}
break;
case EAPSPlayerInitialize:
@@ -224,14 +258,9 @@
// 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:
+ iObserver->NotifyError(buffer.iStatus);
break;
}
break;
@@ -245,12 +274,30 @@
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.
@@ -268,9 +315,10 @@
~CPjAudioEngine();
static CPjAudioEngine *NewL(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_rec_cb rec_cb,
- pjmedia_snd_play_cb play_cb,
- void *user_data);
+ PjAudioCallback rec_cb,
+ PjAudioCallback play_cb,
+ void *user_data,
+ const CPjAudioSetting &setting);
TInt StartL();
void Stop();
@@ -279,33 +327,29 @@
private:
CPjAudioEngine(pjmedia_snd_stream *parent_strm,
- pjmedia_snd_rec_cb rec_cb,
- pjmedia_snd_play_cb play_cb,
- void *user_data);
+ 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);
- 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_;
+ CPjAudioSetting setting_;
RAPSSession iSession;
- TAPSInitSettings iSettings;
+ TAPSInitSettings iPlaySettings;
+ TAPSInitSettings iRecSettings;
+
RMsgQueue<TAPSCommBuffer> iReadQ;
RMsgQueue<TAPSCommBuffer> iReadCommQ;
RMsgQueue<TAPSCommBuffer> iWriteQ;
@@ -314,28 +358,19 @@
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)
+ 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);
+ user_data,
+ setting);
CleanupStack::PushL(self);
self->ConstructL();
CleanupStack::Pop(self);
@@ -343,14 +378,14 @@
}
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),
+ 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),
- recCb_(rec_cb),
- playCb_(play_cb),
- userData_(user_data),
+ setting_(setting),
iPlayCommHandler(0),
iRecCommHandler(0),
iRecHandler(0)
@@ -361,10 +396,21 @@
{
Stop();
+ delete iRecHandler;
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 };
+ start.UniversalTime();
+ now.UniversalTime();
+ while (now.MicroSecondsFrom(start) < APS_CLOSE_WAIT_TIME * 1000) {
+ pj_symbianos_poll(-1, APS_CLOSE_WAIT_TIME);
+ now.UniversalTime();
+ }
iSession.Close();
@@ -383,16 +429,16 @@
if (state_ == STATE_STREAMING || state_ == STATE_READY)
return 0;
- TInt err = iSession.InitializePlayer(iSettings);
+ 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 = iSettings.iGlobal;
+ TBuf<128> buf2 = iPlaySettings.iGlobal;
buf2.Append(_L("PlayQueue"));
- TBuf<128> buf3 = iSettings.iGlobal;
+ TBuf<128> buf3 = iPlaySettings.iGlobal;
buf3.Append(_L("PlayCommQueue"));
while (iWriteQ.OpenGlobal(buf2))
@@ -401,8 +447,7 @@
User::After(10);
// Construct message queue handler
- iPlayCommHandler = CQueueHandler::NewL(this,
- &iWriteCommQ,
+ iPlayCommHandler = CQueueHandler::NewL(this, &iWriteCommQ, &iWriteQ,
CQueueHandler::EPlayCommQueue);
// Start observing APS callbacks on output stream message queue
@@ -417,15 +462,15 @@
return 0;
// Initialize input stream device
- TInt err = iSession.InitializeRecorder(iSettings);
- if (err != KErrNone) {
+ TInt err = iSession.InitializeRecorder(iRecSettings);
+ if (err != KErrNone && err != KErrAlreadyExists) {
snd_perror("Failed to initialize recorder", err);
return err;
}
- TBuf<128> buf1 = iSettings.iGlobal;
+ TBuf<128> buf1 = iRecSettings.iGlobal;
buf1.Append(_L("RecordQueue"));
- TBuf<128> buf4 = iSettings.iGlobal;
+ TBuf<128> buf4 = iRecSettings.iGlobal;
buf4.Append(_L("RecordCommQueue"));
// Must wait for APS thread to finish creating message queues
@@ -436,60 +481,57 @@
User::After(10);
// Construct message queue handlers
- iRecCommHandler = CQueueHandler::NewL(this,
- &iReadCommQ,
+ 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()
{
- 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
+ // 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;
+ if (state_ == STATE_STREAMING) {
+ iSession.Stop();
+ state_ = STATE_READY;
+ TRACE_((THIS_FILE, "Streaming stopped"));
+ }
}
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;
+ // 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()
@@ -497,26 +539,23 @@
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"));
- }
+ 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_->dir != PJMEDIA_DIR_CAPTURE) {
iSession.Write();
- TRACE_((THIS_FILE, "APS player started"));
+ TRACE_((THIS_FILE, "Player streaming started"));
+ }
+
+ // Not only playback
+ if (parentStrm_->dir != PJMEDIA_DIR_PLAYBACK) {
+ iSession.Read();
+ TRACE_((THIS_FILE, "Recorder streaming started"));
}
state_ = STATE_STREAMING;
@@ -556,90 +595,6 @@
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
/////////////////////////////////////////////////////////////
@@ -649,6 +604,7 @@
{
if (state_ == STATE_READY || state_ == STATE_STREAMING) {
iSession.ActivateLoudspeaker(active);
+ TRACE_((THIS_FILE, "Loudspeaker on/off: %d", active));
return KErrNone;
}
return KErrNotReady;
@@ -657,12 +613,110 @@
//
+static void RecCbPcm(TAPSCommBuffer &buf, void *user_data)
+{
+ pjmedia_snd_stream *strm = (pjmedia_snd_stream*) user_data;
+
+ 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->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->samples_per_frame);
+
+ if (strm->rec_buf_len == strm->samples_per_frame) {
+ strm->rec_cb(strm->user_data, 0, strm->rec_buf,
+ strm->rec_buf_len << 1);
+ strm->rec_buf_len = 0;
+ }
+ }
+}
+
+static void PlayCbPcm(TAPSCommBuffer &buf, void *user_data)
+{
+ pjmedia_snd_stream *strm = (pjmedia_snd_stream*) user_data;
+ unsigned g711_frame_len = aps_g711_frame_len;
+
+ 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) {
+ strm->play_cb(strm->user_data, 0, strm->play_buf,
+ strm->samples_per_frame<<1);
+ strm->play_buf_len = strm->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;
+ }
+}
+
+static void RecCb(TAPSCommBuffer &buf, void *user_data)
+{
+}
+
+static void PlayCb(TAPSCommBuffer &buf, void *user_data)
+{
+}
+
/*
* Initialize sound subsystem.
*/
PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
{
snd_pool_factory = factory;
+
+ def_setting.format.u32 = PJMEDIA_FOURCC_L16;
+ def_setting.mode = 0;
+ def_setting.bitrate = 128000;
+ def_setting.plc = PJ_FALSE;
+ def_setting.vad = PJ_FALSE;
+ def_setting.cng = PJ_FALSE;
+ def_setting.loudspk = PJ_FALSE;
+
return PJ_SUCCESS;
}
@@ -700,21 +754,24 @@
{
pj_pool_t *pool;
pjmedia_snd_stream *strm;
+ CPjAudioSetting setting;
+ PjAudioCallback aps_rec_cb;
+ PjAudioCallback aps_play_cb;
PJ_ASSERT_RETURN(p_snd_strm, PJ_EINVAL);
- PJ_ASSERT_RETURN(clock_rate == 8000 && channel_count == 1 &&
+ 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,
+ 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,
+ strm = (pjmedia_snd_stream*) pj_pool_zalloc(pool,
sizeof(pjmedia_snd_stream));
strm->dir = dir;
strm->pool = pool;
@@ -722,14 +779,68 @@
strm->channel_count = channel_count;
strm->samples_per_frame = samples_per_frame;
+ /* Set audio engine settings. */
+ if (def_setting.format.u32 == PJMEDIA_FOURCC_G711U ||
+ def_setting.format.u32 == PJMEDIA_FOURCC_L16)
+ {
+ setting.fourcc = TFourCC(KMCPFourCCIdG711);
+ } else {
+ setting.fourcc = TFourCC(def_setting.format.u32);
+ }
+
+ if (def_setting.format.u32 == PJMEDIA_FOURCC_AMR)
+ {
+ setting.mode = (TAPSCodecMode)def_setting.bitrate;
+ } else if (def_setting.format.u32 == PJMEDIA_FOURCC_G711U ||
+ def_setting.format.u32 == PJMEDIA_FOURCC_L16 ||
+ (def_setting.format.u32 == PJMEDIA_FOURCC_ILBC &&
+ def_setting.mode == 30))
+ {
+ setting.mode = EULawOr30ms;
+ } else {
+ setting.mode = EALawOr20ms;
+ }
+
+ setting.vad = def_setting.vad;
+ setting.plc = def_setting.plc;
+ setting.cng = def_setting.cng;
+ setting.loudspk = def_setting.loudspk;
+
+ if (def_setting.format.u32 == PJMEDIA_FOURCC_AMR ||
+ def_setting.format.u32 == PJMEDIA_FOURCC_G711A ||
+ def_setting.format.u32 == PJMEDIA_FOURCC_G711U ||
+ def_setting.format.u32 == PJMEDIA_FOURCC_G729 ||
+ def_setting.format.u32 == PJMEDIA_FOURCC_ILBC)
+ {
+ aps_play_cb = &PlayCb;
+ aps_rec_cb = &RecCb;
+ } else {
+ aps_play_cb = &PlayCbPcm;
+ aps_rec_cb = &RecCbPcm;
+ }
+
// Create the audio engine.
- TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm, rec_cb, play_cb,
- user_data));
+ TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm,
+ aps_rec_cb, aps_play_cb,
+ strm, setting));
if (err != KErrNone) {
- pj_pool_release(pool);
+ pj_pool_release(pool);
return PJ_RETURN_OS_ERROR(err);
}
+ 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_alloc(pool, 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_alloc(pool, samples_per_frame << 1);
+ strm->rec_buf_len = 0;
+
// Done.
*p_snd_strm = strm;
return PJ_SUCCESS;
@@ -752,7 +863,7 @@
if (index < 0) index = 0;
PJ_ASSERT_RETURN(index == 0, PJ_EINVAL);
- return sound_open(PJMEDIA_DIR_CAPTURE, clock_rate, channel_count,
+ return sound_open(PJMEDIA_DIR_CAPTURE, clock_rate, channel_count,
samples_per_frame, bits_per_sample, rec_cb, NULL,
user_data, p_snd_strm);
}
@@ -769,7 +880,7 @@
if (index < 0) index = 0;
PJ_ASSERT_RETURN(index == 0, PJ_EINVAL);
- return sound_open(PJMEDIA_DIR_PLAYBACK, clock_rate, channel_count,
+ return sound_open(PJMEDIA_DIR_PLAYBACK, clock_rate, channel_count,
samples_per_frame, bits_per_sample, NULL, play_cb,
user_data, p_snd_strm);
}
@@ -789,7 +900,7 @@
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,
+ 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);
}
@@ -821,13 +932,13 @@
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;
}
@@ -835,11 +946,11 @@
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;
}
@@ -847,20 +958,18 @@
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;
- }
+
+ delete stream->engine;
+ stream->engine = NULL;
pool = stream->pool;
- if (pool) {
+ if (pool) {
stream->pool = NULL;
pj_pool_release(pool);
}
-
+
return PJ_SUCCESS;
}
@@ -875,7 +984,7 @@
/*
* Set sound latency.
*/
-PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency,
+PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency,
unsigned output_latency)
{
/* Nothing to do */
@@ -889,11 +998,11 @@
* Activate/deactivate loudspeaker.
*/
PJ_DEF(pj_status_t) pjmedia_snd_aps_activate_loudspeaker(
- pjmedia_snd_stream *stream,
+ pjmedia_snd_stream *stream,
pj_bool_t active)
{
if (stream == NULL) {
- act_loudspeaker = active;
+ def_setting.loudspk = active;
} else {
if (stream->engine == NULL)
return PJ_EINVAL;
@@ -905,3 +1014,18 @@
return PJ_SUCCESS;
}
+
+/**
+ * Set a codec and its settings to be used on the next sound device session.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_aps_modify_setting(
+ const pjmedia_snd_aps_setting *setting)
+{
+ PJ_ASSERT_RETURN(setting, PJ_EINVAL);
+
+ def_setting = *setting;
+
+ return PJ_SUCCESS;
+}
+
+#endif // PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_SYMB_APS_SOUND
diff --git a/pjsip-apps/src/symsndtest/app_main.cpp b/pjsip-apps/src/symsndtest/app_main.cpp
index 597e711..056cde1 100644
--- a/pjsip-apps/src/symsndtest/app_main.cpp
+++ b/pjsip-apps/src/symsndtest/app_main.cpp
@@ -49,7 +49,7 @@
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 @@
}
/* 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 @@
}
pj_caching_pool_init(&cp, NULL, 0);
-
+
/* Init sound subsystem */
status = pjmedia_snd_init(&cp.factory);
if (status != PJ_SUCCESS) {
@@ -92,16 +92,16 @@
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 @@
}
/* 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 @@
//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 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 @@
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 @@
}
/* 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 @@
}
/* 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 @@
*/
#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 @@
}
// 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 @@
}
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 bc160bd..a711f09 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 @@
CleanupStack::PushL(scheduler);
CActiveScheduler::Install(scheduler);
- CActiveSchedulerWait *asw = new CActiveSchedulerWait;
- CleanupStack::PushL(asw);
-
- MyTask *task = MyTask::NewL(asw);
- task->Start();
+ app_main();
- asw->Start();
-
- delete task;
-
- CleanupStack::Pop(asw);
- delete asw;
-
CActiveScheduler::Install(NULL);
CleanupStack::Pop(scheduler);
delete scheduler;
@@ -142,13 +68,13 @@
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;