diff options
Diffstat (limited to 'pjmedia')
54 files changed, 12084 insertions, 4971 deletions
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 */ - |