初始化

This commit is contained in:
SmallMain 2022-06-25 00:23:03 +08:00
commit ef0589e8e5
2264 changed files with 617829 additions and 0 deletions

36
.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
#/////////////////////////////////////////////////////////////////////////////
# npm files
#/////////////////////////////////////////////////////////////////////////////
npm-debug.log
node_modules/
package-lock.json
#/////////////////////////////////////////////////////////////////////////////
# OS generated files
#/////////////////////////////////////////////////////////////////////////////
.DS_Store
ehthumbs.db
Thumbs.db
#/////////////////////////////////////////////////////////////////////////////
# WebStorm files
#/////////////////////////////////////////////////////////////////////////////
.idea/
#/////////////////////////////////////////////////////////////////////////////
# VS Code files
#/////////////////////////////////////////////////////////////////////////////
.vscode/*
!.vscode/settings.json
!.vscode/launch.json
!.vscode/**/*.code-snippets
#/////////////////////////////////////////////////////////////////////////////
# Temp files
#/////////////////////////////////////////////////////////////////////////////
temp

3
README.md Normal file
View File

@ -0,0 +1,3 @@
Cocos Service Pack v1.0.0 引擎源码
适配 v2.4.9 引擎

169
cocos2d-x/.gitignore vendored Normal file
View File

@ -0,0 +1,169 @@
# Ignore thumbnails created by windows
Thumbs.db
# Ignore npm files
/node_modules
/tools/make-package/cocos2d-x.zip
/simulator_*.zip
/prebuilt_*.zip
/prebuilt_mk
/prebuilt_mk*.zip
/simulator.json
# Ignore files build by Visual Studio
*.obj
*.exe
*.pdb
*.aps
*.vcproj.*.user
*.vcxproj.user
*.csproj.user
*.vspscc
*_i.c
*.i
*.icf
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
[Bb]in
[Dd]ebug/
[Dd]ebug.win32/
*.sbr
*.sdf
obj/
[Rr]elease/
[Rr]elease.win32/
_ReSharper*/
[Tt]est[Rr]esult*
ipch/
*.opensdf
*.opendb
SubmissionInfo
Generated Files
AppPackages
BundleArtifacts
.vs/
# Ignore files build by ndk and eclipse
bin/
obj/
gen/
assets/
local.properties
# Ignore python compiled files
*.pyc
# Ignore files build by airplay and marmalade
build_*_xcode/
build_*_vc10/
# Ignore files build by xcode
*.mode*v*
*.pbxuser
*.xcbkptlist
*.xcworkspacedata
*.xcuserstate
*.xccheckout
xcschememanagement.plist
.DS_Store
._.*
xcuserdata/
DerivedData/
# Ignore files built by AppCode
.idea/
# Ignore files built by bada
.Simulator-Debug/
.Target-Debug/
.Target-Release/
# Ignore files built by blackberry
/simulator/
Device-Debug/
Device-Release/
# Ignore vim swaps
*.swp
*.swo
# Ignore files created by create_project.py
projects/
# Ignore config files in javascript bindings generator
tools/tojs/user.cfg
# ... userconf.ini generated if running from tools/tojs
tools/tojs/userconf.ini
tools/tolua/userconf.ini
# ... userconf.ini generated if running from tools/jenkins_scripts/mac/android/
tools/jenkins_scripts/mac/android/userconf.ini
# CTags
tags
# ignore files, created with make-all-linux-project script
/lib
/build/linux-build
# Cmake files
CMakeCache.txt
CMakeFiles
Makefile
cmake_install.cmake
CMakeLists.txt.user
# Ignore files generated by console
build/build/
**/proj.ios_mac/build/
**/tests/*/runtime/
**/tests/*/publish/
**/proj.android-studio/app/libs/
**/proj.android-studio/app/build.xml
**/proj.android-studio/app/proguard-project.txt
# Android
project.properties
*.iml
# Ignore prebuilt libraries folder
/external/*
!/external/config.json
/templates/lua-template-runtime/runtime
/v*-deps-*.zip
/new-renderer-*.zip
/v*-lua-runtime-*.zip
/v*-console-*.zip
/tools/fbx-conv/
tests/cpp-tests/Resources/audio
/tests/lua-empty-test/src/cocos/
/tests/lua-game-controller-test/src/cocos/
/tests/lua-tests/src/cocos/
/tests/js-tests/res/
/tools/framework-compile/bin/proj_modifier/plutil-win32/
!/tools/framework-compile/bin/
# generated by framework-compile
/prebuilt/
/*/prebuilt-mk/Android.mk
/*/*/prebuilt-mk/Android.mk
/*/*/*/prebuilt-mk/Android.mk
/*/*/*/*/prebuilt-mk/Android.mk
*.xcscmblueprint
# generated by vscode
.vscode/
# generated by "gulp gen-simulator"
/tools/simulator/runtime/win32
/tools/simulator/simulator
cocos2d-x-lite-external-*
*.zip

6
cocos2d-x/.gitmodules vendored Normal file
View File

@ -0,0 +1,6 @@
[submodule "tools/cocos2d-console"]
path = tools/cocos2d-console
url = https://github.com/cocos-creator/cocos2d-console.git
[submodule "tools/bindings-generator"]
path = tools/bindings-generator
url = https://github.com/cocos-creator/bindings-generator.git

19
cocos2d-x/.travis.yml Normal file
View File

@ -0,0 +1,19 @@
language: cpp
os:
- linux
script:
- tools/travis-scripts/run-script.sh
sudo: required
before_install:
- tools/travis-scripts/before-install.sh
# blocklist
# branches:
# except:
# - develop
env:
global:
- secure: bUrk522Qw5Mc3SfDXHZtJ/aF9QbLXYDnUVSOJXt1wUB1d8xAQphTcZ1ARNYu7I5m3ZXqRfzTS4POOT58qeS3sYPpJ8urWW18hgX3I/dmsL1WJXmwnOV+0J3xLt6AT3ttqj7/emaf0xxuBvZ0szyRz0rU4wprtWftWMFiUrsF8G1an2Qa/g/f5LhilnDiKAqFdHMkn0ogaggL0PbgliHeHo7SEOfSF2gQaSIBta7jzE/BEZZ3R4AuReB3oNDwxr+1E4BpPaGO1lMLzdr8MAjWBGVLBRlpVDihgFTTmdka8UYasjEWDs6GbSjJRsTZQkluOo+1UylSiLn5o1tnWMqINo5DDaayBSKWRV/x+Sx+KMG221BwHLGFHrhEm8Jwc3g5IBiFrc9Xn/PAoCL3Zi/m0m8Y/TVIlA2nWuU0jeMZYXOb0ALCplAQMrG/Yt7euzgSjZ9iPvO7HO8BkaRkUxafcp6svWyMedNeDczG6liLSquJak+XIbOpWCOW+KVxc2S2Unz37MSOQR/aB9dJ8fKI/4e3LCdYrSKW9MSKAz9BSYA2IK2F8Fgr6zY3IPTANQCi6o8i/N54wsmpY17uvzqNDGuag5m/FkNpFWVQUy/TdONanXV6m1jvREaxtDHucSnP3olmvx6pi1awRspDfRmcILK08yvul+yLol0nQwenlx0=
- secure: g7YPNjmnfzksSu2OqIAjoU3wyogplkOOnACJF8jOsacs0wvZ/JieNBusnkbsOmWhxk1exdpbP01j+ApeJQiqSACPsEXyImrIP0MaErH8SfzBIYtxJKVGZLO292aKmES0BlJTUeLHBGv3kzLg1uOZAfMiLVFgH00oHWx0dr+T9qpRJ8brmJ6orosV8D55A12IHTz3tJLmvJRNuVidrCFxJar9xK5HSPwT6NgH2PXPQKdLCoRqZe6N/uMtrDXgwqGHzLzkR0w+O0iyai2iK6ACTXhNIk88yh7QkV+oa8KoYNLsajGu+Yab1YAQgKSqBM8FvRMGJdJ8Jk4z8btX9sN1AUyJxjgrVic/kqsWlIu6rsSAUbVQ4O1Rhw74r0GJLObQOXZsUUApYQAcYdwPV/Ln0k3GXLxZ8AdEUnDDIQpklJfPmmsLYDSyaj1qQjrECIX3piS4kFhiNak5izxgweLMScig6HOg/q1gWOSuAgmz0WJ/0IdP7q+CF6B70/sxp5jomERGEcfgtIn/N8He+X4swFVHvYiFWyF5jJ+GpFbZQZbhn3dtFcj9/Yr5UPoR9q2RJD1ayO+3yuN4fbq3VO4ZlXmHY9CAH4b1NIAGdmF1vdIgW9QSX+suc8dBex7wCu3+74N54CnFZWl4KbLX3dq86IIjihimUJtUmSjHPOH8m20=
- secure: 0OmLJ6Y5zfybz3fnQWb9GI5z4Mljlg9q1JGwN5j6IDdsXc8QKBXoBQSIFMQmDaDvb91NQl/gWRXcG409t4ysKSImOIxtsSE4A5ZLYu2pI7c09xXbGP6pvP9pGKnqvIVH6N7DERYi0R8kZdjCyttCLaLsMgX6DU+OoqlszetSOwct6uQ+L8jhU2xRzVB6oTgZqLUI8PDNMOXH0ftrroWc7vjtTinJQ54xTFKCat5FEXCMK6XX4lfeayJuS1m2dg8vabhW347PDa2pOa/gnVEeoqUM9qSDWUmA2LRUO5Z6vgI/1I3H4xd2uRmYsLaZHmpddS3IG8QvcHP8yTKbEE6USaEBsM8PbKg5Zy8fE301Bmv+Sr7rWFmBJMRjMiiowAQiN08DfUziDLY66uOucXY4W7sGxBi5383KYqzC3/R+yOmsaeCEWHcymcqvsGuuMq4eIPgYIK0G4TG9ubB0oJ+cJYiePdrAMK1PzJ9MqK4GsJBz4W1Lh1c0ofkr8sC0kLZbWJSyB4DArcVwYPgrsMelgwySO5EGbi9OUm5ONHS+7QaNVqczfbwrrR6tmQZruoG2u6HRpYxcqWnYl/TusodnZxBGT+POYaUcKwNBt6lSSq1sJTFb5ysGRBme/1+KUDLjqslX3yx6isC+n65GlEUFxguNeuokoLc9vnL7+npukHk=

64
cocos2d-x/README.md Normal file
View File

@ -0,0 +1,64 @@
Cocos2d-x, compact version
==========================
<a href="https://travis-ci.org/cocos-creator/cocos2d-x-lite"><img src="https://travis-ci.org/cocos-creator/cocos2d-x-lite.svg?branch=develop " alt="Build Status"></a>
It is based on [cocos2d-x](https://github.com/cocos2d/cocos2d-x)[version 3.9], but remove 3D and other features. It works on iOS, Android, macOS and Windows.
------------------------------------------------
The major change:
- Remove 3D features
- Sprite3D
- Skybox
- Terrain
- Light
- Navmesh
- Physics3D
- BillBoard
- Animate3D
- Bundle3D
- MeshSkin
- etc..
- Only support iOS, macOS, Android and Windows.
- Remove support for LUA script
- Remove deprecated classes and functions
- Remove Camera
- Remove Physics integration
- Using FastTileMap instead of TileMap
- Remove C++ implementations of CocoStudio parser
- Remove C++ implementations of CocosBuilder parser
- Remove AssetsManager, AssetsManagerEX
- Remove Allocator
- Remove AutoPolygon
- Remove support for WebP, S3TC, ATITC
- Remove support for game controller
- Improved robustness and many bugs have been fixed
Git user attention
-----------------------
1. Clone the repo from GitHub.
$ git clone https://github.com/cocos-creator/cocos2d-x-lite.git
$ cd cocos2d-x-lite
$ npm install
2. After cloning the repo, please execute `gulp init` to download and install dependencies.
$ gulp init
3. Build simulator
$ gulp gen-simulator
$ gulp update-simulator-config
If you need to debug the simulator on macOS, you should sign the "./simulator/mac/simulator.app" by using `codesign` after build, or manually build the simulator project ("./tools/simulator/frameworks/runtime-src/proj.ios_mac/simulator.xcodeproj") in Xcode and enable Signing.
![](https://user-images.githubusercontent.com/1503156/32046986-3ab1f0b6-ba0a-11e7-9c7f-7fe0a385d338.png)
Contributing to the Project
--------------------------------
cocos2d-x-lite is licensed under the [MIT License](https://opensource.org/licenses/MIT). We welcome participation!

View File

@ -0,0 +1,3 @@
git pull fireball v1.4
git submodule update --init
gulp make-cocos2d-x

5
cocos2d-x/auto-build.bat Normal file
View File

@ -0,0 +1,5 @@
git checkout -- .
git pull fireball v1.4
git submodule update --init
gulp make-simulator
gulp make-prebuilt

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros">
<EngineRoot>$(MSBuildThisFileDirectory)..\</EngineRoot>
</PropertyGroup>
<PropertyGroup />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(EngineRoot)cocos;$(EngineRoot)external\win32\include;$(EngineRoot)cocos\platform;$(EngineRoot)external\sources;$(EngineRoot)external\win32\include\zlib;$(EngineRoot)external\win32\include\freetype;$(EngineRoot)external\sources\unzip</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_VARIADIC_MAX=10;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<ProjectReference>
<UseLibraryDependencyInputs>false</UseLibraryDependencyInputs>
</ProjectReference>
</ItemDefinitionGroup>
<ItemGroup>
<BuildMacro Include="EngineRoot">
<Value>$(EngineRoot)</Value>
<EnvironmentVariable>true</EnvironmentVariable>
</BuildMacro>
</ItemGroup>
</Project>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup>
<Link>
<AdditionalDependencies>libcocos2d.lib;opengl32.lib;glew32.lib;libzlib.lib;libiconv.lib;freetype.lib;winmm.lib;ws2_32.lib;Psapi.lib;Iphlpapi.lib;Userenv.lib;libcurl.lib;websockets.lib;libwebp.lib;sqlite3.lib;OpenAL32.lib;libmpg123.lib;libvorbisfile.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
</Link>
<ClCompile />
<ProjectReference>
<UseLibraryDependencyInputs>false</UseLibraryDependencyInputs>
</ProjectReference>
<ClCompile>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup />
</Project>

View File

@ -0,0 +1,840 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\cocos\2d\CCFontAtlas.cpp" />
<ClCompile Include="..\cocos\2d\CCFontFreetype.cpp" />
<ClCompile Include="..\cocos\2d\CCLabelLayout.cpp" />
<ClCompile Include="..\cocos\2d\CCTTFLabelAtlasCache.cpp" />
<ClCompile Include="..\cocos\2d\CCTTFLabelRenderer.cpp" />
<ClCompile Include="..\cocos\2d\CCTTFTypes.cpp" />
<ClCompile Include="..\cocos\audio\AudioEngine.cpp" />
<ClCompile Include="..\cocos\audio\win32\AudioCache.cpp" />
<ClCompile Include="..\cocos\audio\win32\AudioDecoder.cpp" />
<ClCompile Include="..\cocos\audio\win32\AudioDecoderManager.cpp" />
<ClCompile Include="..\cocos\audio\win32\AudioDecoderMp3.cpp" />
<ClCompile Include="..\cocos\audio\win32\AudioDecoderOgg.cpp" />
<ClCompile Include="..\cocos\audio\win32\AudioEngine-win32.cpp" />
<ClCompile Include="..\cocos\audio\win32\AudioPlayer.cpp" />
<ClCompile Include="..\cocos\base\base64.cpp" />
<ClCompile Include="..\cocos\base\CCAutoreleasePool.cpp" />
<ClCompile Include="..\cocos\base\ccCArray.cpp" />
<ClCompile Include="..\cocos\base\CCConfiguration.cpp" />
<ClCompile Include="..\cocos\base\CCData.cpp" />
<ClCompile Include="..\cocos\base\CCGLUtils.cpp" />
<ClCompile Include="..\cocos\base\CCLog.cpp" />
<ClCompile Include="..\cocos\base\ccRandom.cpp" />
<ClCompile Include="..\cocos\base\CCRef.cpp" />
<ClCompile Include="..\cocos\base\CCRenderTexture.cpp" />
<ClCompile Include="..\cocos\base\CCScheduler.cpp" />
<ClCompile Include="..\cocos\base\CCThreadPool.cpp" />
<ClCompile Include="..\cocos\base\ccTypes.cpp" />
<ClCompile Include="..\cocos\base\ccUTF8.cpp" />
<ClCompile Include="..\cocos\base\ccUtils.cpp" />
<ClCompile Include="..\cocos\base\CCValue.cpp" />
<ClCompile Include="..\cocos\base\csscolorparser.cpp" />
<ClCompile Include="..\cocos\base\etc1.cpp" />
<ClCompile Include="..\cocos\base\etc2.cpp" />
<ClCompile Include="..\cocos\base\pvr.cpp" />
<ClCompile Include="..\cocos\base\TGAlib.cpp" />
<ClCompile Include="..\cocos\base\ZipUtils.cpp" />
<ClCompile Include="..\cocos\cocos2d.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones-creator-support\ArmatureCache.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones-creator-support\ArmatureCacheMgr.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones-creator-support\AttachUtil.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones-creator-support\CCArmatureCacheDisplay.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones-creator-support\CCArmatureDisplay.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones-creator-support\CCFactory.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones-creator-support\CCSlot.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones-creator-support\CCTextureAtlasData.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\animation\Animation.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\animation\AnimationState.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\animation\BaseTimelineState.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\animation\TimelineState.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\animation\WorldClock.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\armature\Armature.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\armature\Bone.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\armature\Constraint.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\armature\DeformVertices.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\armature\Slot.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\armature\TransformObject.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\core\BaseObject.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\core\DragonBones.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\event\EventObject.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\factory\BaseFactory.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\geom\Point.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\geom\Transform.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\model\AnimationConfig.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\model\AnimationData.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\model\ArmatureData.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\model\BoundingBoxData.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\model\CanvasData.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\model\ConstraintData.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\model\DisplayData.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\model\DragonBonesData.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\model\SkinData.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\model\TextureAtlasData.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\model\UserData.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\parser\BinaryDataParser.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\parser\DataParser.cpp" />
<ClCompile Include="..\cocos\editor-support\dragonbones\parser\JSONDataParser.cpp" />
<ClCompile Include="..\cocos\editor-support\MeshBuffer.cpp" />
<ClCompile Include="..\cocos\editor-support\middleware-adapter.cpp" />
<ClCompile Include="..\cocos\editor-support\MiddlewareManager.cpp" />
<ClCompile Include="..\cocos\editor-support\IOBuffer.cpp" />
<ClCompile Include="..\cocos\editor-support\IOTypedArray.cpp" />
<ClCompile Include="..\cocos\editor-support\particle\ParticleSimulator.cpp" />
<ClCompile Include="..\cocos\editor-support\spine-creator-support\AttachmentVertices.cpp" />
<ClCompile Include="..\cocos\editor-support\spine-creator-support\AttachUtil.cpp" />
<ClCompile Include="..\cocos\editor-support\spine-creator-support\SkeletonAnimation.cpp" />
<ClCompile Include="..\cocos\editor-support\spine-creator-support\SkeletonCache.cpp" />
<ClCompile Include="..\cocos\editor-support\spine-creator-support\SkeletonCacheAnimation.cpp" />
<ClCompile Include="..\cocos\editor-support\spine-creator-support\SkeletonCacheMgr.cpp" />
<ClCompile Include="..\cocos\editor-support\spine-creator-support\SkeletonDataMgr.cpp" />
<ClCompile Include="..\cocos\editor-support\spine-creator-support\SkeletonRenderer.cpp" />
<ClCompile Include="..\cocos\editor-support\spine-creator-support\spine-cocos2dx.cpp" />
<ClCompile Include="..\cocos\editor-support\spine-creator-support\VertexEffectDelegate.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\Animation.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\AnimationState.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\AnimationStateData.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\Atlas.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\AtlasAttachmentLoader.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\Attachment.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\AttachmentLoader.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\AttachmentTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\Bone.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\BoneData.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\BoundingBoxAttachment.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\ClippingAttachment.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\ColorTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\Constraint.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\ConstraintData.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\CurveTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\DeformTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\DrawOrderTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\Event.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\EventData.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\EventTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\Extension.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\IkConstraint.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\IkConstraintData.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\IkConstraintTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\Json.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\LinkedMesh.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\MathUtil.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\MeshAttachment.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\PathAttachment.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\PathConstraint.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\PathConstraintData.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\PathConstraintMixTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\PathConstraintPositionTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\PathConstraintSpacingTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\PointAttachment.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\RegionAttachment.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\RotateTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\RTTI.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\ScaleTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\ShearTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\Skeleton.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\SkeletonBinary.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\SkeletonBounds.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\SkeletonClipping.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\SkeletonData.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\SkeletonJson.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\Skin.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\Slot.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\SlotData.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\SpineObject.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\TextureLoader.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\Timeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\TransformConstraint.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\TransformConstraintData.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\TransformConstraintTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\TranslateTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\Triangulator.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\TwoColorTimeline.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\Updatable.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\VertexAttachment.cpp" />
<ClCompile Include="..\cocos\editor-support\spine\VertexEffect.cpp" />
<ClCompile Include="..\cocos\editor-support\TypedArrayPool.cpp" />
<ClCompile Include="..\cocos\math\CCGeometry.cpp" />
<ClCompile Include="..\cocos\math\CCVertex.cpp" />
<ClCompile Include="..\cocos\math\Mat4.cpp" />
<ClCompile Include="..\cocos\math\MathUtil.cpp" />
<ClCompile Include="..\cocos\math\Quaternion.cpp" />
<ClCompile Include="..\cocos\math\Vec2.cpp" />
<ClCompile Include="..\cocos\math\Vec3.cpp" />
<ClCompile Include="..\cocos\math\Vec4.cpp" />
<ClCompile Include="..\cocos\network\CCDownloader-curl.cpp" />
<ClCompile Include="..\cocos\network\CCDownloader.cpp" />
<ClCompile Include="..\cocos\network\HttpClient.cpp" />
<ClCompile Include="..\cocos\network\HttpCookie.cpp" />
<ClCompile Include="..\cocos\network\SocketIO.cpp" />
<ClCompile Include="..\cocos\network\Uri.cpp" />
<ClCompile Include="..\cocos\network\WebSocket-libwebsockets.cpp" />
<ClCompile Include="..\cocos\network\WebSocketServer.cpp" />
<ClCompile Include="..\cocos\platform\CCFileUtils.cpp" />
<ClCompile Include="..\cocos\platform\CCImage.cpp" />
<ClCompile Include="..\cocos\platform\CCSAXParser.cpp" />
<ClCompile Include="..\cocos\platform\desktop\CCGLView-desktop.cpp" />
<ClCompile Include="..\cocos\platform\win32\CCApplication-win32.cpp" />
<ClCompile Include="..\cocos\platform\win32\CCCanvasRenderingContext2D-win32.cpp" />
<ClCompile Include="..\cocos\platform\win32\CCDevice-win32.cpp" />
<ClCompile Include="..\cocos\platform\win32\CCFileUtils-win32.cpp" />
<ClCompile Include="..\cocos\platform\win32\CCUtils-win32.cpp" />
<ClCompile Include="..\cocos\renderer\gfx\DeviceGraphics.cpp" />
<ClCompile Include="..\cocos\renderer\gfx\FrameBuffer.cpp" />
<ClCompile Include="..\cocos\renderer\gfx\GFX.cpp" />
<ClCompile Include="..\cocos\renderer\gfx\GFXUtils.cpp" />
<ClCompile Include="..\cocos\renderer\gfx\GraphicsHandle.cpp" />
<ClCompile Include="..\cocos\renderer\gfx\IndexBuffer.cpp" />
<ClCompile Include="..\cocos\renderer\gfx\Program.cpp" />
<ClCompile Include="..\cocos\renderer\gfx\RenderBuffer.cpp" />
<ClCompile Include="..\cocos\renderer\gfx\RenderTarget.cpp" />
<ClCompile Include="..\cocos\renderer\gfx\State.cpp" />
<ClCompile Include="..\cocos\renderer\gfx\Texture.cpp" />
<ClCompile Include="..\cocos\renderer\gfx\Texture2D.cpp" />
<ClCompile Include="..\cocos\renderer\gfx\VertexBuffer.cpp" />
<ClCompile Include="..\cocos\renderer\gfx\VertexFormat.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\BaseRenderer.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\Camera.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\Config.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\EffectVariant.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\Effect.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\EffectBase.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\ForwardRenderer.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\InputAssembler.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\Light.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\Model.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\Pass.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\ProgramLib.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\RendererUtils.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\Scene.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\Technique.cpp" />
<ClCompile Include="..\cocos\renderer\renderer\View.cpp" />
<ClCompile Include="..\cocos\renderer\scene\assembler\Assembler.cpp" />
<ClCompile Include="..\cocos\renderer\scene\assembler\AssemblerBase.cpp" />
<ClCompile Include="..\cocos\renderer\scene\assembler\AssemblerSprite.cpp" />
<ClCompile Include="..\cocos\renderer\scene\assembler\CustomAssembler.cpp" />
<ClCompile Include="..\cocos\renderer\scene\assembler\MaskAssembler.cpp" />
<ClCompile Include="..\cocos\renderer\scene\assembler\RenderData.cpp" />
<ClCompile Include="..\cocos\renderer\scene\assembler\RenderDataList.cpp" />
<ClCompile Include="..\cocos\renderer\scene\assembler\SimpleSprite2D.cpp" />
<ClCompile Include="..\cocos\renderer\scene\assembler\SlicedSprite2D.cpp" />
<ClCompile Include="..\cocos\renderer\scene\assembler\SimpleSprite3D.cpp" />
<ClCompile Include="..\cocos\renderer\scene\assembler\SlicedSprite3D.cpp" />
<ClCompile Include="..\cocos\renderer\scene\assembler\TiledMapAssembler.cpp" />
<ClCompile Include="..\cocos\renderer\scene\assembler\MeshAssembler.cpp" />
<ClCompile Include="..\cocos\renderer\scene\assembler\Particle3DAssembler.cpp" />
<ClCompile Include="..\cocos\renderer\scene\MemPool.cpp" />
<ClCompile Include="..\cocos\renderer\scene\MeshBuffer.cpp" />
<ClCompile Include="..\cocos\renderer\scene\ModelBatcher.cpp" />
<ClCompile Include="..\cocos\renderer\scene\NodeMemPool.cpp" />
<ClCompile Include="..\cocos\renderer\scene\NodeProxy.cpp" />
<ClCompile Include="..\cocos\renderer\scene\ParallelTask.cpp" />
<ClCompile Include="..\cocos\renderer\scene\RenderFlow.cpp" />
<ClCompile Include="..\cocos\renderer\scene\StencilManager.cpp" />
<ClCompile Include="..\cocos\renderer\Types.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_audioengine_auto.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_auto.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_dragonbones_auto.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_editor_support_auto.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_extension_auto.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_network_auto.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_particle_auto.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_spine_auto.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\auto\jsb_gfx_auto.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\auto\jsb_renderer_auto.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\event\EventDispatcher.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\config.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\HandleObject.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\MappingUtils.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\RefCounter.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\State.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\v8\Class.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\env.cc" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\http_parser.c" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\inspector_agent.cc" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\inspector_io.cc" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\inspector_socket.cc" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\inspector_socket_server.cc" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\node.cc" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\node_debug_options.cc" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\SHA1.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\util.cc" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\v8\Object.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\v8\ObjectWrap.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\v8\ScriptEngine.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\v8\Utils.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\jswrapper\Value.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_classtype.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_cocos2dx_manual.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_cocos2dx_network_manual.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_conversions.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_dragonbones_manual.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_gfx_manual.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_global.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_helper.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_opengl_manual.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_opengl_utils.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_platfrom_win32.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_renderer_manual.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_socketio.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_spine_manual.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_websocket.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_websocket_server.cpp" />
<ClCompile Include="..\cocos\scripting\js-bindings\manual\jsb_xmlhttprequest.cpp" />
<ClCompile Include="..\cocos\storage\local-storage\LocalStorage.cpp" />
<ClCompile Include="..\cocos\ui\edit-box\EditBox-win32.cpp" />
<ClCompile Include="..\extensions\assets-manager\AssetsManagerEx.cpp" />
<ClCompile Include="..\extensions\assets-manager\CCAsyncTaskPool.cpp" />
<ClCompile Include="..\extensions\assets-manager\CCEventAssetsManagerEx.cpp" />
<ClCompile Include="..\extensions\assets-manager\Manifest.cpp" />
<ClCompile Include="..\external\sources\ConvertUTF\ConvertUTF.c" />
<ClCompile Include="..\external\sources\ConvertUTF\ConvertUTFWrapper.cpp" />
<ClCompile Include="..\external\sources\edtaa3func\edtaa3func.cpp" />
<ClCompile Include="..\external\sources\tinyxml2\tinyxml2.cpp" />
<ClCompile Include="..\external\sources\unzip\ioapi.cpp" />
<ClCompile Include="..\external\sources\unzip\ioapi_mem.cpp" />
<ClCompile Include="..\external\sources\unzip\unzip.cpp" />
<ClCompile Include="..\external\sources\xxtea\xxtea.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\cocos\2d\CCFontAtlas.h" />
<ClInclude Include="..\cocos\2d\CCFontFreetype.h" />
<ClInclude Include="..\cocos\2d\CCLabelLayout.h" />
<ClInclude Include="..\cocos\2d\CCTTFLabelAtlasCache.h" />
<ClInclude Include="..\cocos\2d\CCTTFLabelRenderer.h" />
<ClInclude Include="..\cocos\2d\CCTTFTypes.h" />
<ClInclude Include="..\cocos\audio\include\AudioEngine.h" />
<ClInclude Include="..\cocos\audio\include\Export.h" />
<ClInclude Include="..\cocos\audio\win32\AudioCache.h" />
<ClInclude Include="..\cocos\audio\win32\AudioDecoder.h" />
<ClInclude Include="..\cocos\audio\win32\AudioDecoderManager.h" />
<ClInclude Include="..\cocos\audio\win32\AudioDecoderMp3.h" />
<ClInclude Include="..\cocos\audio\win32\AudioDecoderOgg.h" />
<ClInclude Include="..\cocos\audio\win32\AudioEngine-win32.h" />
<ClInclude Include="..\cocos\audio\win32\AudioMacros.h" />
<ClInclude Include="..\cocos\audio\win32\AudioPlayer.h" />
<ClInclude Include="..\cocos\base\base64.h" />
<ClInclude Include="..\cocos\base\CCAutoreleasePool.h" />
<ClInclude Include="..\cocos\base\ccCArray.h" />
<ClInclude Include="..\cocos\base\ccConfig.h" />
<ClInclude Include="..\cocos\base\CCConfiguration.h" />
<ClInclude Include="..\cocos\base\CCData.h" />
<ClInclude Include="..\cocos\base\CCGLUtils.h" />
<ClInclude Include="..\cocos\base\CCLog.h" />
<ClInclude Include="..\cocos\base\ccMacros.h" />
<ClInclude Include="..\cocos\base\CCMap.h" />
<ClInclude Include="..\cocos\base\ccRandom.h" />
<ClInclude Include="..\cocos\base\CCRef.h" />
<ClInclude Include="..\cocos\base\CCRefPtr.h" />
<ClInclude Include="..\cocos\base\CCRenderTexture.h" />
<ClInclude Include="..\cocos\base\CCScheduler.h" />
<ClInclude Include="..\cocos\base\CCThreadPool.h" />
<ClInclude Include="..\cocos\base\ccTypes.h" />
<ClInclude Include="..\cocos\base\ccUTF8.h" />
<ClInclude Include="..\cocos\base\ccUtils.h" />
<ClInclude Include="..\cocos\base\CCValue.h" />
<ClInclude Include="..\cocos\base\CCVector.h" />
<ClInclude Include="..\cocos\base\csscolorparser.hpp" />
<ClInclude Include="..\cocos\base\etc1.h" />
<ClInclude Include="..\cocos\base\etc2.h" />
<ClInclude Include="..\cocos\base\pvr.h" />
<ClInclude Include="..\cocos\base\TGAlib.h" />
<ClInclude Include="..\cocos\base\uthash.h" />
<ClInclude Include="..\cocos\base\utlist.h" />
<ClInclude Include="..\cocos\base\ZipUtils.h" />
<ClInclude Include="..\cocos\cocos2d.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones-creator-support\ArmatureCache.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones-creator-support\ArmatureCacheMgr.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones-creator-support\AttachUtil.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones-creator-support\CCArmatureCacheDisplay.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones-creator-support\CCArmatureDisplay.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones-creator-support\CCDragonBonesHeaders.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones-creator-support\CCFactory.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones-creator-support\CCSlot.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones-creator-support\CCTextureAtlasData.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\animation\Animation.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\animation\AnimationState.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\animation\BaseTimelineState.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\animation\IAnimatable.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\animation\TimelineState.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\animation\WorldClock.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\armature\Armature.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\armature\Bone.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\armature\Constraint.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\armature\DeformVertices.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\armature\IArmatureProxy.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\armature\Slot.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\armature\TransformObject.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\core\BaseObject.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\core\DragonBones.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\DragonBonesHeaders.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\event\EventObject.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\event\IEventDispatcher.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\factory\BaseFactory.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\geom\ColorTransform.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\geom\Matrix.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\geom\Point.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\geom\Rectangle.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\geom\Transform.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\model\AnimationConfig.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\model\AnimationData.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\model\ArmatureData.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\model\BoundingBoxData.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\model\CanvasData.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\model\ConstraintData.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\model\DisplayData.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\model\DragonBonesData.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\model\SkinData.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\model\TextureAtlasData.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\model\UserData.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\parser\BinaryDataParser.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\parser\DataParser.h" />
<ClInclude Include="..\cocos\editor-support\dragonbones\parser\JSONDataParser.h" />
<ClInclude Include="..\cocos\editor-support\IOBuffer.h" />
<ClInclude Include="..\cocos\editor-support\MeshBuffer.h" />
<ClInclude Include="..\cocos\editor-support\middleware-adapter.h" />
<ClInclude Include="..\cocos\editor-support\MiddlewareMacro.h" />
<ClInclude Include="..\cocos\editor-support\MiddlewareManager.h" />
<ClInclude Include="..\cocos\editor-support\IOTypedArray.h" />
<ClInclude Include="..\cocos\editor-support\particle\ParticleSimulator.h" />
<ClInclude Include="..\cocos\editor-support\spine-creator-support\AttachmentVertices.h" />
<ClInclude Include="..\cocos\editor-support\spine-creator-support\AttachUtil.h" />
<ClInclude Include="..\cocos\editor-support\spine-creator-support\SkeletonAnimation.h" />
<ClInclude Include="..\cocos\editor-support\spine-creator-support\SkeletonCache.h" />
<ClInclude Include="..\cocos\editor-support\spine-creator-support\SkeletonCacheAnimation.h" />
<ClInclude Include="..\cocos\editor-support\spine-creator-support\SkeletonCacheMgr.h" />
<ClInclude Include="..\cocos\editor-support\spine-creator-support\SkeletonDataMgr.h" />
<ClInclude Include="..\cocos\editor-support\spine-creator-support\SkeletonRenderer.h" />
<ClInclude Include="..\cocos\editor-support\spine-creator-support\spine-cocos2dx.h" />
<ClInclude Include="..\cocos\editor-support\spine-creator-support\VertexEffectDelegate.h" />
<ClInclude Include="..\cocos\editor-support\spine\Attachment.h" />
<ClInclude Include="..\cocos\editor-support\spine\Animation.h" />
<ClInclude Include="..\cocos\editor-support\spine\AnimationState.h" />
<ClInclude Include="..\cocos\editor-support\spine\AnimationStateData.h" />
<ClInclude Include="..\cocos\editor-support\spine\Atlas.h" />
<ClInclude Include="..\cocos\editor-support\spine\AtlasAttachmentLoader.h" />
<ClInclude Include="..\cocos\editor-support\spine\AttachmentLoader.h" />
<ClInclude Include="..\cocos\editor-support\spine\AttachmentTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\AttachmentType.h" />
<ClInclude Include="..\cocos\editor-support\spine\BlendMode.h" />
<ClInclude Include="..\cocos\editor-support\spine\Bone.h" />
<ClInclude Include="..\cocos\editor-support\spine\BoneData.h" />
<ClInclude Include="..\cocos\editor-support\spine\BoundingBoxAttachment.h" />
<ClInclude Include="..\cocos\editor-support\spine\ClippingAttachment.h" />
<ClInclude Include="..\cocos\editor-support\spine\Color.h" />
<ClInclude Include="..\cocos\editor-support\spine\ColorTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\Constraint.h" />
<ClInclude Include="..\cocos\editor-support\spine\ConstraintData.h" />
<ClInclude Include="..\cocos\editor-support\spine\ContainerUtil.h" />
<ClInclude Include="..\cocos\editor-support\spine\CurveTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\Debug.h" />
<ClInclude Include="..\cocos\editor-support\spine\DeformTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\dll.h" />
<ClInclude Include="..\cocos\editor-support\spine\DrawOrderTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\Event.h" />
<ClInclude Include="..\cocos\editor-support\spine\EventData.h" />
<ClInclude Include="..\cocos\editor-support\spine\EventTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\Extension.h" />
<ClInclude Include="..\cocos\editor-support\spine\HashMap.h" />
<ClInclude Include="..\cocos\editor-support\spine\HasRendererObject.h" />
<ClInclude Include="..\cocos\editor-support\spine\IkConstraint.h" />
<ClInclude Include="..\cocos\editor-support\spine\IkConstraintData.h" />
<ClInclude Include="..\cocos\editor-support\spine\IkConstraintTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\Json.h" />
<ClInclude Include="..\cocos\editor-support\spine\LinkedMesh.h" />
<ClInclude Include="..\cocos\editor-support\spine\MathUtil.h" />
<ClInclude Include="..\cocos\editor-support\spine\MeshAttachment.h" />
<ClInclude Include="..\cocos\editor-support\spine\MixBlend.h" />
<ClInclude Include="..\cocos\editor-support\spine\MixDirection.h" />
<ClInclude Include="..\cocos\editor-support\spine\PathAttachment.h" />
<ClInclude Include="..\cocos\editor-support\spine\PathConstraint.h" />
<ClInclude Include="..\cocos\editor-support\spine\PathConstraintData.h" />
<ClInclude Include="..\cocos\editor-support\spine\PathConstraintMixTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\PathConstraintPositionTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\PathConstraintSpacingTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\PointAttachment.h" />
<ClInclude Include="..\cocos\editor-support\spine\Pool.h" />
<ClInclude Include="..\cocos\editor-support\spine\PositionMode.h" />
<ClInclude Include="..\cocos\editor-support\spine\RegionAttachment.h" />
<ClInclude Include="..\cocos\editor-support\spine\RotateMode.h" />
<ClInclude Include="..\cocos\editor-support\spine\RotateTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\RTTI.h" />
<ClInclude Include="..\cocos\editor-support\spine\ScaleTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\ShearTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\Skeleton.h" />
<ClInclude Include="..\cocos\editor-support\spine\SkeletonBinary.h" />
<ClInclude Include="..\cocos\editor-support\spine\SkeletonBounds.h" />
<ClInclude Include="..\cocos\editor-support\spine\SkeletonClipping.h" />
<ClInclude Include="..\cocos\editor-support\spine\SkeletonData.h" />
<ClInclude Include="..\cocos\editor-support\spine\SkeletonJson.h" />
<ClInclude Include="..\cocos\editor-support\spine\Skin.h" />
<ClInclude Include="..\cocos\editor-support\spine\Slot.h" />
<ClInclude Include="..\cocos\editor-support\spine\SlotData.h" />
<ClInclude Include="..\cocos\editor-support\spine\SpacingMode.h" />
<ClInclude Include="..\cocos\editor-support\spine\spine.h" />
<ClInclude Include="..\cocos\editor-support\spine\SpineObject.h" />
<ClInclude Include="..\cocos\editor-support\spine\SpineString.h" />
<ClInclude Include="..\cocos\editor-support\spine\TextureLoader.h" />
<ClInclude Include="..\cocos\editor-support\spine\Timeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\TimelineType.h" />
<ClInclude Include="..\cocos\editor-support\spine\TransformConstraint.h" />
<ClInclude Include="..\cocos\editor-support\spine\TransformConstraintData.h" />
<ClInclude Include="..\cocos\editor-support\spine\TransformConstraintTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\TransformMode.h" />
<ClInclude Include="..\cocos\editor-support\spine\TranslateTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\Triangulator.h" />
<ClInclude Include="..\cocos\editor-support\spine\TwoColorTimeline.h" />
<ClInclude Include="..\cocos\editor-support\spine\Updatable.h" />
<ClInclude Include="..\cocos\editor-support\spine\Vector.h" />
<ClInclude Include="..\cocos\editor-support\spine\VertexAttachment.h" />
<ClInclude Include="..\cocos\editor-support\spine\VertexEffect.h" />
<ClInclude Include="..\cocos\editor-support\spine\Vertices.h" />
<ClInclude Include="..\cocos\editor-support\TypedArrayPool.h" />
<ClInclude Include="..\cocos\math\CCGeometry.h" />
<ClInclude Include="..\cocos\math\CCMath.h" />
<ClInclude Include="..\cocos\math\CCMathBase.h" />
<ClInclude Include="..\cocos\math\CCVertex.h" />
<ClInclude Include="..\cocos\math\Mat4.h" />
<ClInclude Include="..\cocos\math\MathUtil.h" />
<ClInclude Include="..\cocos\math\Quaternion.h" />
<ClInclude Include="..\cocos\math\Vec2.h" />
<ClInclude Include="..\cocos\math\Vec3.h" />
<ClInclude Include="..\cocos\math\Vec4.h" />
<ClInclude Include="..\cocos\network\CCDownloader-curl.h" />
<ClInclude Include="..\cocos\network\CCDownloader.h" />
<ClInclude Include="..\cocos\network\CCIDownloaderImpl.h" />
<ClInclude Include="..\cocos\network\HttpClient.h" />
<ClInclude Include="..\cocos\network\HttpCookie.h" />
<ClInclude Include="..\cocos\network\HttpRequest.h" />
<ClInclude Include="..\cocos\network\HttpResponse.h" />
<ClInclude Include="..\cocos\network\SocketIO.h" />
<ClInclude Include="..\cocos\network\Uri.h" />
<ClInclude Include="..\cocos\network\WebSocket.h" />
<ClInclude Include="..\cocos\network\WebSocketServer.h" />
<ClInclude Include="..\cocos\platform\CCApplication.h" />
<ClInclude Include="..\cocos\platform\CCCanvasRenderingContext2D.h" />
<ClInclude Include="..\cocos\platform\CCDevice.h" />
<ClInclude Include="..\cocos\platform\CCFileUtils.h" />
<ClInclude Include="..\cocos\platform\CCGL.h" />
<ClInclude Include="..\cocos\platform\CCImage.h" />
<ClInclude Include="..\cocos\platform\CCPlatformConfig.h" />
<ClInclude Include="..\cocos\platform\CCPlatformDefine.h" />
<ClInclude Include="..\cocos\platform\CCSAXParser.h" />
<ClInclude Include="..\cocos\platform\CCStdC.h" />
<ClInclude Include="..\cocos\platform\desktop\CCGLView-desktop.h" />
<ClInclude Include="..\cocos\platform\win32\CCFileUtils-win32.h" />
<ClInclude Include="..\cocos\platform\win32\CCGL-win32.h" />
<ClInclude Include="..\cocos\platform\win32\CCPlatformDefine-win32.h" />
<ClInclude Include="..\cocos\platform\win32\CCUtils-win32.h" />
<ClInclude Include="..\cocos\platform\win32\compat\stdint.h" />
<ClInclude Include="..\cocos\renderer\gfx\DeviceGraphics.h" />
<ClInclude Include="..\cocos\renderer\gfx\FrameBuffer.h" />
<ClInclude Include="..\cocos\renderer\gfx\GFX.h" />
<ClInclude Include="..\cocos\renderer\gfx\GFXUtils.h" />
<ClInclude Include="..\cocos\renderer\gfx\GraphicsHandle.h" />
<ClInclude Include="..\cocos\renderer\gfx\IndexBuffer.h" />
<ClInclude Include="..\cocos\renderer\gfx\Program.h" />
<ClInclude Include="..\cocos\renderer\gfx\RenderBuffer.h" />
<ClInclude Include="..\cocos\renderer\gfx\RenderTarget.h" />
<ClInclude Include="..\cocos\renderer\gfx\State.h" />
<ClInclude Include="..\cocos\renderer\gfx\Texture.h" />
<ClInclude Include="..\cocos\renderer\gfx\Texture2D.h" />
<ClInclude Include="..\cocos\renderer\gfx\VertexBuffer.h" />
<ClInclude Include="..\cocos\renderer\gfx\VertexFormat.h" />
<ClInclude Include="..\cocos\renderer\Macro.h" />
<ClInclude Include="..\cocos\renderer\memop\RecyclePool.hpp" />
<ClInclude Include="..\cocos\renderer\renderer\BaseRenderer.h" />
<ClInclude Include="..\cocos\renderer\renderer\Camera.h" />
<ClInclude Include="..\cocos\renderer\renderer\Config.h" />
<ClInclude Include="..\cocos\renderer\renderer\EffectVariant.hpp" />
<ClInclude Include="..\cocos\renderer\renderer\Effect.h" />
<ClInclude Include="..\cocos\renderer\renderer\EffectBase.h" />
<ClInclude Include="..\cocos\renderer\renderer\ForwardRenderer.h" />
<ClInclude Include="..\cocos\renderer\renderer\INode.h" />
<ClInclude Include="..\cocos\renderer\renderer\InputAssembler.h" />
<ClInclude Include="..\cocos\renderer\renderer\Light.h" />
<ClInclude Include="..\cocos\renderer\renderer\Model.h" />
<ClInclude Include="..\cocos\renderer\renderer\Pass.h" />
<ClInclude Include="..\cocos\renderer\renderer\ProgramLib.h" />
<ClInclude Include="..\cocos\renderer\renderer\Renderer.h" />
<ClInclude Include="..\cocos\renderer\renderer\RendererUtils.h" />
<ClInclude Include="..\cocos\renderer\renderer\Scene.h" />
<ClInclude Include="..\cocos\renderer\renderer\Technique.h" />
<ClInclude Include="..\cocos\renderer\renderer\View.h" />
<ClInclude Include="..\cocos\renderer\scene\assembler\Assembler.hpp" />
<ClInclude Include="..\cocos\renderer\scene\assembler\AssemblerBase.hpp" />
<ClInclude Include="..\cocos\renderer\scene\assembler\AssemblerSprite.hpp" />
<ClInclude Include="..\cocos\renderer\scene\assembler\CustomAssembler.hpp" />
<ClInclude Include="..\cocos\renderer\scene\assembler\MaskAssembler.hpp" />
<ClInclude Include="..\cocos\renderer\scene\assembler\RenderData.hpp" />
<ClInclude Include="..\cocos\renderer\scene\assembler\RenderDataList.hpp" />
<ClInclude Include="..\cocos\renderer\scene\assembler\SimpleSprite2D.hpp" />
<ClInclude Include="..\cocos\renderer\scene\assembler\SlicedSprite2D.hpp" />
<ClInclude Include="..\cocos\renderer\scene\assembler\SimpleSprite3D.hpp" />
<ClInclude Include="..\cocos\renderer\scene\assembler\SlicedSprite3D.hpp" />
<ClInclude Include="..\cocos\renderer\scene\assembler\TiledMapAssembler.hpp" />
<ClInclude Include="..\cocos\renderer\scene\assembler\MeshAssembler.hpp" />
<ClInclude Include="..\cocos\renderer\scene\assembler\Particle3DAssembler.hpp" />
<ClInclude Include="..\cocos\renderer\scene\MemPool.hpp" />
<ClInclude Include="..\cocos\renderer\scene\MeshBuffer.hpp" />
<ClInclude Include="..\cocos\renderer\scene\ModelBatcher.hpp" />
<ClInclude Include="..\cocos\renderer\scene\NodeMemPool.hpp" />
<ClInclude Include="..\cocos\renderer\scene\NodeProxy.hpp" />
<ClInclude Include="..\cocos\renderer\scene\ParallelTask.hpp" />
<ClInclude Include="..\cocos\renderer\scene\RenderFlow.hpp" />
<ClInclude Include="..\cocos\renderer\scene\scene-bindings.h" />
<ClInclude Include="..\cocos\renderer\scene\StencilManager.hpp" />
<ClInclude Include="..\cocos\renderer\Types.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_audioengine_auto.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_auto.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_dragonbones_auto.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_editor_support_auto.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_extension_auto.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_network_auto.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_particle_auto.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\auto\jsb_cocos2dx_spine_auto.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\auto\jsb_gfx_auto.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\auto\jsb_renderer_auto.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\event\CustomEventTypes.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\event\EventDispatcher.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\config.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\HandleObject.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\MappingUtils.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\Object.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\RefCounter.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\SeApi.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\State.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\Base.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\Class.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\base64.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\env.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\http_parser.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\inspector_agent.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\inspector_io.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\inspector_socket.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\inspector_socket_server.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\node.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\node_debug_options.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\node_mutex.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\SHA1.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\util-inl.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\util.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\debugger\v8_inspector_protocol_json.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\HelperMacros.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\Object.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\ObjectWrap.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\ScriptEngine.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\SeApi.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\v8\Utils.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\jswrapper\Value.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_classtype.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_cocos2dx_manual.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_cocos2dx_network_manual.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_conversions.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_dragonbones_manual.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_gfx_manual.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_global.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_helper.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_opengl_manual.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_opengl_utils.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_platform.h" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_renderer_manual.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_socketio.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_spine_manual.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_websocket.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_websocket_server.hpp" />
<ClInclude Include="..\cocos\scripting\js-bindings\manual\jsb_xmlhttprequest.hpp" />
<ClInclude Include="..\cocos\storage\local-storage\LocalStorage.h" />
<ClInclude Include="..\cocos\ui\edit-box\EditBox.h" />
<ClInclude Include="..\extensions\assets-manager\AssetsManagerEx.h" />
<ClInclude Include="..\extensions\assets-manager\CCAsyncTaskPool.h" />
<ClInclude Include="..\extensions\assets-manager\CCEventAssetsManagerEx.h" />
<ClInclude Include="..\extensions\assets-manager\Manifest.h" />
<ClInclude Include="..\extensions\cocos-ext.h" />
<ClInclude Include="..\extensions\ExtensionExport.h" />
<ClInclude Include="..\extensions\ExtensionMacros.h" />
<ClInclude Include="..\external\sources\ConvertUTF\ConvertUTF.h" />
<ClInclude Include="..\external\sources\edtaa3func\edtaa3func.h" />
<ClInclude Include="..\external\sources\tinyxml2\tinyxml2.h" />
<ClInclude Include="..\external\sources\unzip\crypt.h" />
<ClInclude Include="..\external\sources\unzip\ioapi.h" />
<ClInclude Include="..\external\sources\unzip\ioapi_mem.h" />
<ClInclude Include="..\external\sources\unzip\unzip.h" />
<ClInclude Include="..\external\sources\xxtea\xxtea.h" />
</ItemGroup>
<ItemGroup>
<None Include="..\cocos\math\Mat4.inl" />
<None Include="..\cocos\math\MathUtil.inl" />
<None Include="..\cocos\math\MathUtilNeon.inl" />
<None Include="..\cocos\math\MathUtilNeon64.inl" />
<None Include="..\cocos\math\MathUtilSSE.inl" />
<None Include="..\cocos\math\Quaternion.inl" />
<None Include="..\cocos\math\Vec2.inl" />
<None Include="..\cocos\math\Vec3.inl" />
<None Include="..\cocos\math\Vec4.inl" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectName>libcocos2d</ProjectName>
<ProjectGuid>{98A51BA8-FC3A-415B-AC8F-8C7BD464E93E}</ProjectGuid>
<RootNamespace>cocos2d-x.win32</RootNamespace>
<Keyword>Win32Proj</Keyword>
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '12.0'">v120</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '12.0' and exists('$(MSBuildProgramFiles32)\Microsoft SDKs\Windows\v7.1A')">v120_xp</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '14.0'">v140</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '14.0' and exists('$(MSBuildProgramFiles32)\Microsoft SDKs\Windows\v7.1A')">v140_xp</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0' and exists('$(MSBuildProgramFiles32)\Microsoft SDKs\Windows\v7.1A')">v140_xp</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '12.0'">v120</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '12.0' and exists('$(MSBuildProgramFiles32)\Microsoft SDKs\Windows\v7.1A')">v120_xp</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '14.0'">v140</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '14.0' and exists('$(MSBuildProgramFiles32)\Microsoft SDKs\Windows\v7.1A')">v140_xp</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0' and exists('$(MSBuildProgramFiles32)\Microsoft SDKs\Windows\v7.1A')">v140_xp</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="cocos2d_headers.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="cocos2d_headers.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>12.0.21005.1</_ProjectFileVersion>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)$(Configuration).win32\</OutDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Configuration).win32\</IntDir>
<LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</LinkIncremental>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Configuration).win32\</OutDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Configuration).win32\</IntDir>
<LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
<CodeAnalysisRuleSet Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">AllRules.ruleset</CodeAnalysisRuleSet>
<CodeAnalysisRules Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" />
<CodeAnalysisRuleAssemblies Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" />
<CodeAnalysisRuleSet Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">AllRules.ruleset</CodeAnalysisRuleSet>
<CodeAnalysisRules Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" />
<CodeAnalysisRuleAssemblies Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LibraryPath>$(EngineRoot)external\win32\libs;$(MSBuildProgramFiles32)\Microsoft SDKs\Windows\v7.1A\lib;$(LibraryPath)</LibraryPath>
<TargetName>$(ProjectName)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LibraryPath>$(EngineRoot)external\win32\libs;$(MSBuildProgramFiles32)\Microsoft SDKs\Windows\v7.1A\lib;$(LibraryPath)</LibraryPath>
<TargetName>$(ProjectName)</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<PreBuildEvent>
<Command>
</Command>
</PreBuildEvent>
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>$(ProjectDir)..\cocos;$(ProjectDir)..\external\sources;$(ProjectDir)..\cocos\renderer\gfx;$(ProjectDir)..\cocos\renderer;$(ProjectDir)..\cocos\platform;$(ProjectDir)..\external\win32\include\zlib;$(ProjectDir)..\external\win32\include\v8;$(ProjectDir)..\external\sources\firefox;$(projectDir)..;$(ProjectDir)..\external\win32\include;$(ProjectDir)..\external\win32\include\uv;$(ProjectDir)..\cocos\editor-support;$(ProjectDir)..\external\win32\include\freetype</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;CC_STATIC;_USRDLL;_LIB;COCOS2D_DEBUG=1;JS_HAVE____INTN;JS_INTPTR_TYPE=int;XP_WIN;_CRT_SECURE_NO_WARNINGS;GLFW_EXPOSE_NATIVE_WIN32;GLFW_EXPOSE_NATIVE_WGL;_WINDOWS;_WIN32;__MWERKS__;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>false</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<DisableSpecificWarnings>4138;4267;4251;4244;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<CompileAs>Default</CompileAs>
<ObjectFileName>$(Configuration).win32\$(IntDir)%(RelativeDir)</ObjectFileName>
</ClCompile>
<PreLinkEvent>
<Command>if not exist "$(OutDir)" mkdir "$(OutDir)"
xcopy /Y /Q "$(ProjectDir)..\external\win32\libs\*.*" "$(OutDir)"
xcopy /Y /Q "$(ProjectDir)..\external\win32\libs\Debug\*.*" "$(OutDir)"</Command>
</PreLinkEvent>
<Link>
<OutputFile>$(OutDir)$(ProjectName).dll</OutputFile>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
<IgnoreSpecificDefaultLibraries>libcmtd.lib;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<ImportLibrary>$(TargetDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalDependencies>sqlite3.lib;libcrypto.lib;libssl.lib;libcurl.lib;websockets.lib;libmpg123.lib;libogg.lib;libvorbis.lib;libvorbisfile.lib;OpenAL32.lib;version.lib;libwebp.lib;libuv.lib;v8_libplatform.dll.lib;v8_libbase.dll.lib;opengl32.lib;glew32.lib;libzlib.lib;Psapi.lib;Iphlpapi.lib;userenv.lib;ws2_32.lib;libiconv.lib;v8.dll.lib;winmm.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<PreBuildEvent>
<Command>
</Command>
</PreBuildEvent>
<ClCompile>
<AdditionalIncludeDirectories>$(ProjectDir)..\cocos;$(ProjectDir)..\external\sources;$(ProjectDir)..\cocos\renderer\gfx;$(ProjectDir)..\cocos\renderer;$(ProjectDir)..\cocos\platform;$(ProjectDir)..\external\win32\include\zlib;$(ProjectDir)..\external\win32\include\v8;$(ProjectDir)..\external\sources\firefox;$(projectDir)..;$(ProjectDir)..\external\win32\include;$(ProjectDir)..\external\win32\include\uv;$(ProjectDir)..\cocos\editor-support;$(ProjectDir)..\external\win32\include\freetype</AdditionalIncludeDirectories>
<PreprocessorDefinitions>NDEBUG;WIN32;_WINDOWS;CC_STATIC;_USRDLL;_LIB;JS_HAVE____INTN;JS_INTPTR_TYPE=int;XP_WIN;_CRT_SECURE_NO_WARNINGS;GLFW_EXPOSE_NATIVE_WIN32;GLFW_EXPOSE_NATIVE_WGL;_WINDOWS;_WIN32;__MWERKS__;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>None</DebugInformationFormat>
<DisableSpecificWarnings>4267;4251;4244;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<CompileAs>Default</CompileAs>
<WholeProgramOptimization>false</WholeProgramOptimization>
<Optimization>MinSpace</Optimization>
<ObjectFileName>$(Configuration).win32\$(IntDir)%(RelativeDir)</ObjectFileName>
</ClCompile>
<PreLinkEvent>
<Command>
if not exist "$(OutDir)" mkdir "$(OutDir)"
xcopy /Y /Q "$(ProjectDir)..\external\win32\libs\*.*" "$(OutDir)"
xcopy /Y /Q "$(ProjectDir)..\external\win32\libs\Debug\*.*" "$(OutDir)"</Command>
</PreLinkEvent>
<Link>
<AdditionalDependencies>sqlite3.lib;libcrypto.lib;libssl.lib;libcurl.lib;websockets.lib;libmpg123.lib;libogg.lib;libvorbis.lib;libvorbisfile.lib;OpenAL32.lib;version.lib;libwebp.lib;libuv.lib;v8_libplatform.dll.lib;v8_libbase.dll.lib;opengl32.lib;glew32.lib;libzlib.lib;Psapi.lib;Iphlpapi.lib;userenv.lib;ws2_32.lib;libiconv.lib;v8.dll.lib;winmm.lib;%(AdditionalDependencies)</AdditionalDependencies>
<OutputFile>$(OutDir)$(ProjectName).dll</OutputFile>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<IgnoreSpecificDefaultLibraries>libcmtd.lib;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<ImportLibrary>$(TargetDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
</Link>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,361 @@
/****************************************************************************
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "CCFontAtlas.h"
#include "renderer/gfx/Texture2D.h"
#include "renderer/gfx/DeviceGraphics.h"
#include "base/ccConfig.h"
#include <cassert>
#if CC_ENABLE_TTF_LABEL_RENDERER
static const int PIXEL_PADDING = 2;
namespace cocos2d {
FontAtlasFrame::FontAtlasFrame()
{
_currentRowX = PIXEL_PADDING;
_currentRowY = PIXEL_PADDING;
}
FontAtlasFrame::FontAtlasFrame(FontAtlasFrame&& o)
{
#if CC_ENABLE_CACHE_TTF_FONT_TEXTURE
// move buffer instead of copy
std::swap(_buffer, o._buffer);
_dirtyFlag = o._dirtyFlag;
_dirtyRegion = o._dirtyRegion;
#endif
_WIDTH = o._WIDTH;
_HEIGHT = o._HEIGHT;
_currentRowX = o._currentRowX;
_currentRowY = o._currentRowY;
_currRowHeight = o._currRowHeight;
_pixelMode = o._pixelMode;
_texture = o._texture;
o._texture = nullptr;
}
FontAtlasFrame::~FontAtlasFrame()
{
CC_SAFE_RELEASE(_texture);
}
void FontAtlasFrame::reinit(PixelMode pixelMode, int width, int height)
{
_pixelMode = pixelMode;
_WIDTH = width;
_HEIGHT = height;
_currentRowY = PIXEL_PADDING;
_currentRowY = PIXEL_PADDING;
_currRowHeight = 0;
#if CC_ENABLE_CACHE_TTF_FONT_TEXTURE
_buffer.resize(PixelModeSize(pixelMode) * width * height);
std::fill(_buffer.begin(), _buffer.end(), 0);
_dirtyFlag = 0;
#endif
getTexture(); // init texture
}
FontAtlasFrame::FrameResult FontAtlasFrame::append(int width, int height, std::vector<uint8_t> &data, Rect &out)
{
#if CC_ENABLE_CACHE_TTF_FONT_TEXTURE
assert(_buffer.size() > 0);
assert(width <= _WIDTH && height <= _HEIGHT);
#endif
if (!hasSpace(width, height)) {
return FrameResult::E_FULL;
}
#if CC_ENABLE_CACHE_TTF_FONT_TEXTURE
//update texture-data in CPU memory
const int pixelSize = PixelModeSize(_pixelMode);
uint8_t* dst = _buffer.data();
uint8_t* src = data.data();
uint8_t* dstOrigin = pixelSize * (_currentRowY * _WIDTH + _currentRowX) + dst;
const int BytesEachRow = pixelSize * width;
for (int i = 0; i < height; i++)
{
memcpy(dstOrigin + i * _WIDTH * pixelSize, src + i * BytesEachRow, BytesEachRow);
}
if (_dirtyFlag == 0)
{
_dirtyFlag |= DIRTY_RECT;
_dirtyRegion = Rect(_currentRowX, _currentRowY, width, height);
}
else
{
_dirtyRegion.merge(Rect(_currentRowX, _currentRowY, width, height));
}
#else
//update GPU texture immediately
renderer::Texture::SubImageOption opt;
opt.imageData = data.data();
opt.x = _currentRowX;
opt.y = _currentRowY;
opt.width = width;
opt.height = height;
opt.imageDataLength = data.size();
_texture->updateSubImage(opt);
#endif
out.origin.set(_currentRowX, _currentRowY);
out.size.width = width;
out.size.height = height;
// move cursor
moveToNextCursor(width, height);
return FrameResult::SUCCESS;
}
bool FontAtlasFrame::hasSpace(int width, int height)
{
if (hasRowXSpace(width) && hasYSpace(height)) {
return true;
}
if (hasNextRowXSpace(width) && hasNextRowYSpace(height))
{
moveToNextRow();
return true;
}
return false;
}
int FontAtlasFrame::remainRowXSpace() const
{
return _WIDTH - _currentRowX;
}
int FontAtlasFrame::remainYSpace() const
{
return _HEIGHT - _currentRowY;
}
bool FontAtlasFrame::hasRowXSpace(int x) const
{
return x + PIXEL_PADDING <= remainRowXSpace();
}
bool FontAtlasFrame::hasYSpace(int y) const
{
return y + PIXEL_PADDING <= remainYSpace();
}
bool FontAtlasFrame::hasNextRowXSpace(int x) const
{
return x + PIXEL_PADDING <= _WIDTH;
}
bool FontAtlasFrame::hasNextRowYSpace(int y) const
{
return y + PIXEL_PADDING <= remainYSpace() - _currRowHeight;
}
void FontAtlasFrame::moveToNextRow()
{
_currentRowY += _currRowHeight + PIXEL_PADDING;
_currentRowX = PIXEL_PADDING;
_currRowHeight = 0;
}
void FontAtlasFrame::moveToNextCursor(int width, int height)
{
_currRowHeight = std::max(_currRowHeight, height);
_currentRowX += width + PIXEL_PADDING;
}
renderer::Texture2D * FontAtlasFrame::getTexture()
{
if (!_texture)
{
auto* device = renderer::DeviceGraphics::getInstance();
_texture = new cocos2d::renderer::Texture2D();
cocos2d::renderer::Texture::Options option;
option.width = _WIDTH;
option.height = _HEIGHT;
// alpha only
option.glFormat = GL_ALPHA;
option.glInternalFormat = GL_ALPHA;
option.glType = GL_UNSIGNED_BYTE;
option.bpp = 8 * PixelModeSize(_pixelMode);
renderer::Texture::Image img;
#if CC_ENABLE_CACHE_TTF_FONT_TEXTURE
img.data = _buffer.data();
img.length = _buffer.size();
#else
std::vector<uint8_t> buffer(_WIDTH * _HEIGHT * PixelModeSize(_pixelMode), 0);
img.length = buffer.size();
img.data = buffer.data();
#endif
option.images.push_back(img);
_texture->init(device, option);
}
#if CC_ENABLE_CACHE_TTF_FONT_TEXTURE
if (_dirtyFlag & DIRTY_ALL)
{
renderer::Texture::SubImageOption opt;
opt.imageData = _buffer.data();
opt.x = 0;
opt.y = 0;
opt.width = _WIDTH;
opt.height = _HEIGHT;
opt.imageDataLength = (uint32_t)_buffer.size();
_texture->updateSubImage(opt);
}
else if (_dirtyFlag & DIRTY_RECT)
{
int yMin = _dirtyRegion.getMinY();
int yHeight = _dirtyRegion.size.height;
renderer::Texture::SubImageOption opt;
opt.imageData = _buffer.data() + PixelModeSize(_pixelMode) * _WIDTH * yMin;
opt.x = 0;
opt.y = yMin;
opt.width = _WIDTH;
opt.height = yHeight;
opt.imageDataLength = PixelModeSize(_pixelMode) * _WIDTH * yHeight;
_texture->updateSubImage(opt);
}
_dirtyFlag = 0;
#endif
return _texture;
}
FontAtlas::FontAtlas(PixelMode pixelMode, int width, int height, bool hasoutline)
:_pixelMode(pixelMode), _width(width), _height(height), _useSDF(hasoutline)
{
}
FontAtlas::~FontAtlas()
{
}
bool FontAtlas::init()
{
_textureFrame.reinit(_pixelMode, _width, _height);
_letterMap.clear();
return true;
}
bool FontAtlas::prepareLetter(unsigned long ch, std::shared_ptr<GlyphBitmap> bitmap)
{
if (_letterMap.find(ch) != _letterMap.end())
{
return true;
}
Rect rect;
FontAtlasFrame::FrameResult ret = _textureFrame.append(bitmap->getWidth(), bitmap->getHeight(), bitmap->getData(), rect);
switch (ret) {
case FontAtlasFrame::FrameResult::E_ERROR:
//TODO: ERROR LOG
assert(false);
return false;
case FontAtlasFrame::FrameResult::E_FULL:
// Allocate a new frame & add bitmap the frame
_buffers.push_back(std::move(_textureFrame));
_textureBufferIndex += 1;
_textureFrame.reinit(_pixelMode, _width, _height);
return prepareLetter(ch, bitmap);
case FontAtlasFrame::FrameResult::SUCCESS:
addLetterDef(ch, bitmap, rect);
return true;
default:
//TODO: LOG
assert(false);
}
return false;
}
void FontAtlas::addLetterDef(unsigned long ch, std::shared_ptr<GlyphBitmap> bitmap, const Rect& rect)
{
assert(bitmap->getPixelMode() == _pixelMode);
auto& def = _letterMap[ch];
def.validate = true;
def.textureID = _textureBufferIndex;
def.xAdvance = bitmap->getXAdvance();
def.rect = bitmap->getRect();
def.texX = (rect.origin.x - 0.5f) / _textureFrame.getWidth();
def.texY = (rect.origin.y -0.5f)/ _textureFrame.getHeight();
def.texWidth = (rect.size.width + 1.0f) / _textureFrame.getWidth();
def.texHeight = (rect.size.height + 1.0f) / _textureFrame.getHeight();
def.outline = bitmap->getOutline();
}
bool FontAtlas::prepareLetters(const std::u32string &text, cocos2d::FontFreeType *font)
{
bool ok = true;
for (int i = 0; i < text.length(); i++)
{
auto it = _letterMap.find(text[i]);
if(it == _letterMap.end())
{
auto glyph = font->getGlyphBitmap(text[i], _useSDF);
ok &= prepareLetter(text[i], glyph);
}
}
return ok;
}
FontLetterDefinition* FontAtlas::getOrLoad(unsigned long ch, cocos2d::FontFreeType* font)
{
auto it = _letterMap.find(ch);
if (it != _letterMap.end()) return &it->second;
if (font) {
auto bitmap = font->getGlyphBitmap(ch, _useSDF);
if (bitmap) {
if (prepareLetter(ch, bitmap)) {
return getOrLoad(ch, nullptr);
}
}
}
return nullptr;
}
FontAtlasFrame& FontAtlas::frameAt(int idx)
{
return idx == _textureBufferIndex ? _textureFrame : _buffers.at(idx);
}
}
#endif

View File

@ -0,0 +1,147 @@
/****************************************************************************
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "CCFontFreetype.h"
#include <unordered_map>
#include <algorithm>
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
namespace cocos2d {
namespace renderer {
class Texture2D;
}
class FontAtlas;
struct FontLetterDefinition
{
float texX = 0, texY = 0;
float texWidth = 0, texHeight = 0;
Rect rect;
int textureID = -1;
float xAdvance = 0;
int outline = 0;
bool validate = false;
};
class FontAtlasFrame
{
public:
enum class FrameResult {
SUCCESS,
E_FULL,
E_ERROR
};
FontAtlasFrame();
FontAtlasFrame(FontAtlasFrame&&); //move
virtual ~FontAtlasFrame();
void reinit(PixelMode mode, int width, int height);
FrameResult append(int width, int height, std::vector<uint8_t> &, Rect &out);
float getWidth() const { return _WIDTH; }
float getHeight() const { return _HEIGHT; }
renderer::Texture2D* getTexture();
private:
enum DirtyType {
DIRTY_RECT = 1,
DIRTY_ALL= 2,
};
bool hasSpace(int width, int height);
int remainRowXSpace() const;
int remainYSpace() const;
bool hasRowXSpace(int x) const;
bool hasYSpace(int y) const;
bool hasNextRowXSpace(int x) const;
bool hasNextRowYSpace(int y) const;
void moveToNextRow();
void moveToNextCursor(int width, int height);
#if CC_ENABLE_CACHE_TTF_FONT_TEXTURE
mutable std::vector<uint8_t> _buffer;
int _dirtyFlag = 0;
Rect _dirtyRegion;
#endif
int _WIDTH = 0;
int _HEIGHT = 0;
int _currentRowY = 0;
int _currentRowX = 0;
int _currRowHeight = 0;
PixelMode _pixelMode = PixelMode::A8;
renderer::Texture2D *_texture = nullptr;
friend class FontAtlas;
};
class FontAtlas {
public:
FontAtlas(PixelMode mode, int width, int height, bool hasoutline);
virtual ~FontAtlas();
bool init();
bool prepareLetter(unsigned long ch, std::shared_ptr<GlyphBitmap> bitmap);
bool prepareLetters(const std::u32string &text, cocos2d::FontFreeType *font);
FontLetterDefinition* getOrLoad(unsigned long ch, FontFreeType* font);
FontAtlasFrame& frameAt(int idx);
private:
void addLetterDef(unsigned long ch, std::shared_ptr<GlyphBitmap> bitmap, const Rect& rect);
std::unordered_map<uint64_t, FontLetterDefinition> _letterMap;
FontAtlasFrame _textureFrame;
std::vector<FontAtlasFrame> _buffers;
int _textureBufferIndex = 0;
int _width = 0;
int _height = 0;
PixelMode _pixelMode = PixelMode::A8;
bool _useSDF = false;
};
}
#endif

View File

@ -0,0 +1,415 @@
/****************************************************************************
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "CCFontFreetype.h"
#include <cassert>
#include "platform/CCFileUtils.h"
#include "platform/CCDevice.h"
#include "external/sources/edtaa3func/edtaa3func.h"
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
/**
* By enable FFT_USE_SCREEN_DPI, text will be much more clear in mobile devices,
* and more memory will be consumed.
*/
#define FFT_USE_SCREEN_DPI 0
#if FFT_USE_SCREEN_DPI
#define SCALE_BY_DPI(x) (int)((x) * 72 / _dpi)
#else
#define SCALE_BY_DPI(x) (int)(x)
#endif
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
// `thread_local` can not compile on iOS 9.0 below device
# define FFT_SDF_TMP_VECTOR (__IPHONE_OS_VERSION_MIN_REQUIRED >= 90000)
#else
# define FFT_SDF_TMP_VECTOR 1
#endif // CC_TARGET_PLATFORM == CC_PLATFORM_IOS
#if FFT_SDF_TMP_VECTOR
namespace {
//cache vector in thread
thread_local std::vector<short> xdistV;
thread_local std::vector<short> ydistV;
thread_local std::vector<double> gxV;
thread_local std::vector<double> gyV;
thread_local std::vector<double> dataV;
thread_local std::vector<double> outsideV;
thread_local std::vector<double> insideV;
}
#endif
namespace cocos2d {
class FontFreeTypeLibrary {
public:
FontFreeTypeLibrary() {
memset(&_library, 0, sizeof(FT_Library));
FT_Init_FreeType(&_library);
}
~FontFreeTypeLibrary()
{
_faceCache.clear();
FT_Done_FreeType(_library);
}
FT_Library * get() { return &_library; }
std::unordered_map<std::string, std::shared_ptr<Data>> &getFaceCache() {
return _faceCache;
}
private:
std::unordered_map<std::string, std::shared_ptr<Data>> _faceCache;
FT_Library _library;
};
namespace {
std::shared_ptr<FontFreeTypeLibrary> _sFTLibrary;
PixelMode FTtoPixelModel(FT_Pixel_Mode mode)
{
switch (mode)
{
case FT_PIXEL_MODE_GRAY:
return PixelMode::A8;
case FT_PIXEL_MODE_LCD:
return PixelMode::RGB888;
case FT_PIXEL_MODE_BGRA:
return PixelMode::BGRA8888;
default:
assert(false); //invalidate pixelmode
return PixelMode::INVAL;
}
}
std::vector<uint8_t> makeDistanceMap(unsigned char *img, long width, long height, int distanceMapSpread)
{
long pixelAmount = (width + 2 * distanceMapSpread) * (height + 2 * distanceMapSpread);
#if FFT_SDF_TMP_VECTOR
xdistV.resize(pixelAmount);
ydistV.resize(pixelAmount);
gxV.resize(pixelAmount);
gyV.resize(pixelAmount);
dataV.resize(pixelAmount);
outsideV.resize(pixelAmount);
insideV.resize(pixelAmount);
std::fill(gxV.begin(), gxV.end(), 0.0);
std::fill(gyV.begin(), gyV.end(), 0.0);
std::fill(dataV.begin(), dataV.end(), 0.0);
std::fill(outsideV.begin(), outsideV.end(), 0.0);
std::fill(insideV.begin(), insideV.end(), 0.0);
short * xdist = xdistV.data();
short * ydist = ydistV.data();
double * gx = gxV.data();
double * gy = gyV.data();
double * data = dataV.data();
double * outside = outsideV.data();
double * inside = insideV.data();
#else
short * xdist = (short *)malloc(pixelAmount * sizeof(short));
short * ydist = (short *)malloc(pixelAmount * sizeof(short));
double * gx = (double *)calloc(pixelAmount, sizeof(double));
double * gy = (double *)calloc(pixelAmount, sizeof(double));
double * data = (double *)calloc(pixelAmount, sizeof(double));
double * outside = (double *)calloc(pixelAmount, sizeof(double));
double * inside = (double *)calloc(pixelAmount, sizeof(double));
#endif
long i, j;
// Convert img into double (data) rescale image levels between 0 and 1
long outWidth = width + 2 * distanceMapSpread;
for (i = 0; i < width; ++i)
{
for (j = 0; j < height; ++j)
{
data[(j + distanceMapSpread) * outWidth + distanceMapSpread + i] = img[j * width + i] / 255.0;
}
}
width += 2 * distanceMapSpread;
height += 2 * distanceMapSpread;
// Transform background (outside contour, in areas of 0's)
computegradient(data, (int)width, (int)height, gx, gy);
edtaa3(data, gx, gy, (int)width, (int)height, xdist, ydist, outside);
for (i = 0; i < pixelAmount; i++)
if (outside[i] < 0.0)
outside[i] = 0.0;
// Transform foreground (inside contour, in areas of 1's)
for (i = 0; i < pixelAmount; i++)
data[i] = 1 - data[i];
computegradient(data, (int)width, (int)height, gx, gy);
edtaa3(data, gx, gy, (int)width, (int)height, xdist, ydist, inside);
for (i = 0; i < pixelAmount; i++)
if (inside[i] < 0.0)
inside[i] = 0.0;
// The bipolar distance field is now outside-inside
double dist;
/* Single channel 8-bit output (bad precision and range, but simple) */
std::vector<uint8_t> out;
out.resize(pixelAmount);
for (i = 0; i < pixelAmount; i++)
{
dist = outside[i] - inside[i];
dist = 128.0 - dist * 16;
if (dist < 0) dist = 0;
if (dist > 255) dist = 255;
out[i] = (unsigned char)dist;
}
/* Dual channel 16-bit output (more complicated, but good precision and range) */
/*unsigned char *out = (unsigned char *) malloc( pixelAmount * 3 * sizeof(unsigned char) );
for( i=0; i< pixelAmount; i++)
{
dist = outside[i] - inside[i];
dist = 128.0 - dist*16;
if( dist < 0.0 ) dist = 0.0;
if( dist >= 256.0 ) dist = 255.999;
// R channel is a copy of the original grayscale image
out[3*i] = img[i];
// G channel is fraction
out[3*i + 1] = (unsigned char) ( 256 - (dist - floor(dist)* 256.0 ));
// B channel is truncated integer part
out[3*i + 2] = (unsigned char)dist;
}*/
#if !FFT_SDF_TMP_VECTOR
free(xdist);
free(ydist);
free(gx);
free(gy);
free(data);
free(outside);
free(inside);
#endif
return out;
}
}
FontFreeType::FontFreeType(const std::string& fontName, float fontSize, LabelLayoutInfo *info)
{
if (!_sFTLibrary)
{
_sFTLibrary = std::make_shared< FontFreeTypeLibrary>();
}
_fontName = fontName;
_fontSize = fontSize;
_info = info;
#if FFT_USE_SCREEN_DPI
_dpi = Device::getDPI();
#else
_dpi = 72;
#endif
}
FontFreeType::~FontFreeType()
{
//CCLOG("~FontFreeType");
if (_stroker) FT_Stroker_Done(_stroker);
if (_face) FT_Done_Face(_face);
}
FT_Library& FontFreeType::getFTLibrary()
{
return *(_sFTLibrary->get());
}
bool FontFreeType::loadFont()
{
std::shared_ptr<Data> faceData;
auto itr = _sFTLibrary->getFaceCache().find(_fontName);
if (itr == _sFTLibrary->getFaceCache().end()) {
faceData = std::make_shared<Data>(FileUtils::getInstance()->getDataFromFile(_fontName));
_sFTLibrary->getFaceCache()[_fontName] = faceData;
} else {
faceData = itr->second;
}
if (FT_New_Memory_Face(getFTLibrary(), faceData->getBytes(), faceData->getSize(), 0, &_face))
{
cocos2d::log("[error] failed to parse font %s", _fontName.c_str());
return false;
}
_fontFaceData = faceData;
if (FT_Select_Charmap(_face, _encoding))
{
int foundIndex = -1;
for (int charmapIndex = 0; charmapIndex < _face->num_charmaps; charmapIndex++)
{
if (_face->charmaps[charmapIndex]->encoding != FT_ENCODING_NONE)
{
foundIndex = charmapIndex;
break;
}
}
if (foundIndex == -1)
{
return false;
}
_encoding = _face->charmaps[foundIndex]->encoding;
if (FT_Select_Charmap(_face, _encoding))
{
return false;
}
}
int fontSizeInPoints = (int)(64.0f * _fontSize);
if (FT_Set_Char_Size(_face, fontSizeInPoints, fontSizeInPoints, _dpi, _dpi))
{
return false;
}
_lineHeight = SCALE_BY_DPI((_face->size->metrics.ascender - _face->size->metrics.descender) >> 6);
return true;
}
int FontFreeType::getHorizontalKerningForChars(uint64_t a, uint64_t b) const
{
auto idx1 = FT_Get_Char_Index(_face, static_cast<FT_ULong>(a));
if (!idx1)
return 0;
auto idx2 = FT_Get_Char_Index(_face, static_cast<FT_ULong>(b));
if (!idx2)
return 0;
FT_Vector kerning;
if (FT_Get_Kerning(_face, idx1, idx2, FT_KERNING_DEFAULT, &kerning))
return 0;
return SCALE_BY_DPI(kerning.x >> 6);
}
std::unique_ptr<std::vector<int>> FontFreeType::getHorizontalKerningForUTF32Text(const std::u32string& text) const
{
if (!_face) return nullptr;
if (FT_HAS_KERNING(_face) == 0) return nullptr;
const auto letterNum = text.length();
std::vector<int>* sizes = new std::vector<int>(letterNum, 0);
for (int i = 1; i < letterNum; i++)
{
(*sizes)[i] = SCALE_BY_DPI(getHorizontalKerningForChars(text[i - 1], text[i]));
}
return std::unique_ptr<std::vector<int>>(sizes);
}
int FontFreeType::getFontAscender() const
{
return SCALE_BY_DPI(_face->size->metrics.ascender >> 6);
}
const char* FontFreeType::getFontFamily() const
{
if (!_face) return nullptr;
return _face->family_name;
}
std::shared_ptr<GlyphBitmap> FontFreeType::getGlyphBitmap(unsigned long ch, bool hasOutline)
{
return hasOutline ? getSDFGlyphBitmap(ch) : getNormalGlyphBitmap(ch);
}
std::shared_ptr<GlyphBitmap> FontFreeType::getNormalGlyphBitmap(unsigned long ch)
{
if (!_face) return nullptr;
const auto load_char_flag = FT_LOAD_RENDER | FT_LOAD_NO_AUTOHINT;
if (FT_Load_Char(_face, static_cast<FT_ULong>(ch), load_char_flag))
{
return nullptr;
}
auto& metrics = _face->glyph->metrics;
int x = SCALE_BY_DPI(metrics.horiBearingX >> 6);
int y = SCALE_BY_DPI(-(metrics.horiBearingY >> 6));
int w = SCALE_BY_DPI(metrics.width >> 6);
int h = SCALE_BY_DPI(metrics.height >> 6);
int adv = SCALE_BY_DPI(metrics.horiAdvance >> 6);
auto& bitmap = _face->glyph->bitmap;
int bmWidth = bitmap.width;
int bmHeight = bitmap.rows;
PixelMode mode = FTtoPixelModel(static_cast<FT_Pixel_Mode>(bitmap.pixel_mode));
int size = PixelModeSize(mode) * bmWidth * bmHeight;
std::vector<uint8_t> data((uint8_t*)bitmap.buffer, (uint8_t*)bitmap.buffer + size);
auto* ret = new GlyphBitmap(data, bmWidth, bmHeight, Rect(x, y, w, h), adv, mode, 0);
return std::shared_ptr<GlyphBitmap>(ret);
}
std::shared_ptr<GlyphBitmap> FontFreeType::getSDFGlyphBitmap(unsigned long ch)
{
if (!_face) return nullptr;
const auto load_char_flag = FT_LOAD_RENDER | FT_LOAD_NO_AUTOHINT;
if (FT_Load_Char(_face, static_cast<FT_ULong>(ch), load_char_flag))
{
return nullptr;
}
auto& metrics = _face->glyph->metrics;
int x = SCALE_BY_DPI(metrics.horiBearingX >> 6);
int y = SCALE_BY_DPI(-(metrics.horiBearingY >> 6));
int w = SCALE_BY_DPI(metrics.width >> 6);
int h = SCALE_BY_DPI(metrics.height >> 6);
int adv = SCALE_BY_DPI(metrics.horiAdvance >> 6);
auto& bitmap = _face->glyph->bitmap;
int bmWidth = bitmap.width;
int bmHeight = bitmap.rows;
PixelMode mode = FTtoPixelModel(static_cast<FT_Pixel_Mode>(bitmap.pixel_mode));
assert(mode == PixelMode::A8);
int dms = std::max(3, (int)std::max(0.2 * bmWidth, 0.2 * bmHeight));
// int size = PixelModeSize(mode) * bmWidth * bmHeight;
std::vector<uint8_t> data = makeDistanceMap(bitmap.buffer, bmWidth, bmHeight, dms);
auto* ret = new GlyphBitmap(data, bmWidth + 2 * dms, bmHeight + 2 * dms, Rect(x, y, w + 2 * dms, h + 2 * dms), adv, mode, dms);
return std::shared_ptr<GlyphBitmap>(ret);
}
}
#endif

View File

@ -0,0 +1,93 @@
/****************************************************************************
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
//#include "freetype2/ft2build.h"
#include "freetype/ft2build.h"
#include FT_FREETYPE_H
#include FT_STROKER_H
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include "CCTTFTypes.h"
#include "base/CCData.h"
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
namespace cocos2d {
class FontFreeTypeLibrary;
struct LabelLayoutInfo;
class FontFreeType
{
public:
//const static int DistanceMapSpread;
FontFreeType(const std::string& fontName, float fontSize, LabelLayoutInfo* info);
virtual ~FontFreeType();
FT_Library& getFTLibrary();
bool loadFont();
int getHorizontalKerningForChars(uint64_t a, uint64_t b) const;
std::unique_ptr<std::vector<int>> getHorizontalKerningForUTF32Text(const std::u32string &text) const;
int getFontAscender() const;
const char* getFontFamily() const;
std::shared_ptr<GlyphBitmap> getGlyphBitmap(unsigned long ch, bool hasOutline = false);
private:
std::shared_ptr<GlyphBitmap> getNormalGlyphBitmap(unsigned long ch);
std::shared_ptr<GlyphBitmap> getSDFGlyphBitmap(unsigned long ch);
//weak reference
LabelLayoutInfo *_info = nullptr;
float _fontSize = 0.0f;
float _lineHeight = 0.0f;
std::string _fontName;
std::shared_ptr<Data> _fontFaceData;
FT_Face _face = { 0 };
FT_Stroker _stroker = { 0 };
FT_Encoding _encoding = FT_ENCODING_UNICODE;
int _dpi = 72;
};
}
#endif

View File

@ -0,0 +1,920 @@
/****************************************************************************
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "2d/CCLabelLayout.h"
#include "2d/CCTTFLabelAtlasCache.h"
#include "2d/CCTTFLabelRenderer.h"
#include "base/ccUTF8.h"
#include "MiddlewareMacro.h"
#include "renderer/renderer/Pass.h"
#include "renderer/renderer/Effect.h"
#include "renderer/renderer/Technique.h"
#include "renderer/scene/assembler/CustomAssembler.hpp"
#include "cocos/editor-support/MeshBuffer.h"
#include "cocos/editor-support/IOBuffer.h"
#include "cocos/editor-support/middleware-adapter.h"
#include "cocos/editor-support/MiddlewareManager.h"
#include "renderer/gfx/DeviceGraphics.h"
#include "renderer/gfx/Texture2D.h"
#include <cassert>
#include <algorithm>
#include <fstream>
#include <unordered_map>
#include <chrono>
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
#define INIT_VERTEX_SIZE 32
namespace {
const std::string textureKey = "texture";
const std::string shadowColor = "shadowColor";
const std::string outlineSizeKey = "outlineSize";
const std::string outlineColorKey= "outlineColor";
}
using namespace cocos2d::renderer;
using namespace cocos2d::middleware;
namespace cocos2d {
namespace {
void recalculateUV(const Rect &oldRect, const Rect &oldUV, const Rect &newRect, Rect &newUV)
{
auto offsetOrigin = newRect.origin - oldRect.origin;
float uvWidth = newRect.size.width / oldRect.size.width * oldUV.size.width;
float uvHeight = newRect.size.height / oldRect.size.height * oldUV.size.height;
float uvOriginX = offsetOrigin.x / oldRect.size.width * oldUV.size.width + oldUV.origin.x;
float uvOriginY = (oldUV.size.height - uvHeight - offsetOrigin.y / oldRect.size.height * oldUV.size.height) + oldUV.origin.y;
newUV.setRect(uvOriginX, uvOriginY, uvWidth, uvHeight);
}
inline void find_2nd_3rd(float min1, float max1, float min2, float max2, float &second, float &third)
{
assert(max1 >= min1 && max2 >= min2);
if (max1 < max2)
{
second = min1 < min2 ? min2 : min1;
third = max1;
}
else
{
second = min2 < min1 ? min1 : min2;
third = max2;
}
}
void rectUnion(const Rect &a, const Rect &b, Rect &out)
{
float xMax, yMax;
find_2nd_3rd(a.getMinX(), a.getMaxX(), b.getMinX(), b.getMaxX(), out.origin.x, xMax);
find_2nd_3rd(a.getMinY(), a.getMaxY(), b.getMinY(), b.getMaxY(), out.origin.y, yMax);
out.size.setSize(xMax - out.origin.x, yMax - out.origin.y);
}
void subRect(const Rect &rect, const Rect &uv, float offsetX, float width, Rect &out1, Rect &out2)
{
out1.origin.set(offsetX + rect.origin.x, rect.origin.y);
out1.size.setSize(width, rect.size.height);
out2.origin.set(uv.origin.x + uv.size.width * offsetX / rect.size.width, uv.origin.y);
out2.size.setSize(uv.size.width * width / rect.size.width, uv.size.height);
}
void splitRectIntoThreeParts(int texId, const Rect &defRect, const Rect &targetRect, const Rect &uv, cocos2d::TextRowSpace &space)
{
constexpr float LEFT = 0.3f;
constexpr float MIDDLE = 0.4f;
constexpr float RIGHT = 0.3f;
Rect out1, out2;
float cursorX = 0.0f;
//part left
subRect(targetRect, uv, 0.0f, targetRect.size.width * LEFT, out1, out2);
cursorX = defRect.size.width *LEFT;
out1.size.width = cursorX;
space.fillRect(texId, out1, out2);
//part middle
subRect(targetRect, uv, targetRect.size.width * LEFT, targetRect.size.width * MIDDLE, out1, out2);
out1.origin.x = targetRect.origin.x + cursorX;
out1.size.width = targetRect.size.width - defRect.size.width * (LEFT + RIGHT);
cursorX = targetRect.size.width - defRect.size.width * RIGHT;
space.fillRect(texId, out1, out2);
//part right
subRect(targetRect, uv, targetRect.size.width * (LEFT + MIDDLE) , targetRect.size.width *RIGHT, out1, out2);
out1.origin.x = targetRect.origin.x + cursorX;
out1.size.width = defRect.size.width * RIGHT;
space.fillRect(texId, out1, out2);
}
}
class TextRenderGroupItem {
public:
enum DirtyFlag {
VERTEX_DIRTY = 1,
INDEXES_DIRTY = 2
};
TextRenderGroupItem(renderer::Texture *);
virtual ~TextRenderGroupItem();
void reset();
void addRect(const Rect &vert, const Rect &uv, const Color4B &color, bool italics);
void upload();
inline int getRectSize() const { return _rectSize; }
middleware::MeshBuffer* getBuffer() { return _buffer; }
private:
void addIndexes();
middleware::MeshBuffer * _buffer = nullptr;
renderer::Texture *_texture = nullptr;
int _rectSize = 0;
int _indexSize = 0;
int _dirtyFlags = -1;
};
class TextRenderGroup {
public:
void reset();
void addRect(renderer::Texture*, const Rect &vert, const Rect &uv, const Color4B &color, bool italics);
int fill(CustomAssembler *assembler, int, LabelLayout *layout, EffectVariant *);
private:
std::unordered_map<renderer::Texture*, std::shared_ptr<TextRenderGroupItem>> _buffers;
};
TextRenderGroupItem::TextRenderGroupItem(renderer::Texture* tex)
{
_texture = tex;
_buffer = new middleware::MeshBuffer(VF_XYUVC, sizeof(uint16_t) * 6 * INIT_VERTEX_SIZE, INIT_VERTEX_SIZE * 4);
}
TextRenderGroupItem::~TextRenderGroupItem()
{
delete _buffer;
}
void TextRenderGroupItem::reset() {
_buffer->reset();
_rectSize = 0;
_dirtyFlags = -1;
}
void TextRenderGroupItem::addRect(const Rect &rect, const Rect &uv, const Color4B &color, bool italics)
{
middleware::IOBuffer &vertexBuffer = _buffer->getVB();
const int minSize = sizeof(V2F_T2F_C4B) * 4;
vertexBuffer.checkSpace(minSize << 1, true);
V2F_T2F_C4B *verts = ((V2F_T2F_C4B*)vertexBuffer.getBuffer()) + 4 * _rectSize;
float height = rect.size.height;
float offsetX = 0.0f;
const float factor = 0.21255f;
if (italics) {
offsetX = height * factor;
}
verts[0].vertex = { rect.getMinX() + offsetX, rect.getMaxY() };
verts[1].vertex = { rect.getMaxX() + offsetX, rect.getMaxY() };
verts[2].vertex = { rect.getMinX() - offsetX, rect.getMinY() };
verts[3].vertex = { rect.getMaxX() - offsetX, rect.getMinY() };
verts[0].texCoord = { uv.getMinX(), uv.getMinY() };
verts[1].texCoord = { uv.getMaxX(), uv.getMinY() };
verts[2].texCoord = { uv.getMinX(), uv.getMaxY() };
verts[3].texCoord = { uv.getMaxX(), uv.getMaxY() };
verts[0].color = color;
verts[1].color = color;
verts[2].color = color;
verts[3].color = color;
vertexBuffer.move(4 * sizeof(V2F_T2F_C4B));
_rectSize += 1;
_dirtyFlags |= DirtyFlag::VERTEX_DIRTY;
}
void TextRenderGroupItem::addIndexes()
{
middleware::IOBuffer &indexBuffer = _buffer->getIB();
int indexSize = sizeof(uint16_t) * 6 * (_rectSize - _indexSize);
indexBuffer.checkSpace(indexSize << 1, true);
uint16_t *indices = (uint16_t*)indexBuffer.getBuffer();
for (int i = _indexSize; i < _rectSize; i += 1)
{
indices[i * 6 + 0] = 0 + 4 * i;
indices[i * 6 + 1] = 1 + 4 * i;
indices[i * 6 + 2] = 2 + 4 * i;
indices[i * 6 + 3] = 1 + 4 * i;
indices[i * 6 + 4] = 3 + 4 * i;
indices[i * 6 + 5] = 2 + 4 * i;
}
indexBuffer.move(sizeof(uint16_t) * 6 * (_rectSize - _indexSize));
if (_indexSize < _rectSize)
{
_indexSize = _rectSize;
_dirtyFlags |= DirtyFlag::INDEXES_DIRTY;
}
}
void TextRenderGroupItem::upload()
{
addIndexes();
middleware::IOBuffer &vertexBuffer = _buffer->getVB();
middleware::IOBuffer &indexBuffer = _buffer->getIB();
vertexBuffer.move(_rectSize * 4 * sizeof(V2F_T2F_C4B));
indexBuffer.move(_rectSize * 6 * sizeof(uint16_t));
if (_dirtyFlags | DirtyFlag::INDEXES_DIRTY)
{
_buffer->uploadIB();
}
if (_dirtyFlags | DirtyFlag::VERTEX_DIRTY)
{
_buffer->uploadVB();
}
_dirtyFlags = 0;
}
void TextRenderGroup::addRect(renderer::Texture* tex, const Rect &vert, const Rect &uv, const Color4B &color, bool italics)
{
auto &itemPtr = _buffers[tex];
if (!itemPtr) {
itemPtr = std::make_shared<TextRenderGroupItem>(tex);
}
itemPtr->addRect(vert, uv, color, italics);
}
void TextRenderGroup::reset()
{
for (auto &it : _buffers)
{
it.second->reset();
}
_buffers.clear();
}
int TextRenderGroup::fill(CustomAssembler *assembler, int startIndex, LabelLayout *layout, EffectVariant *templateEffect)
{
int groupIndex = startIndex;
for (auto &it : _buffers)
{
auto &item = it.second;
if (item->getRectSize() > 0)
{
item->upload();
assembler->updateIABuffer(groupIndex, item->getBuffer()->getGLVB(), item->getBuffer()->getGLIB());
assembler->updateIARange(groupIndex, 0, item->getRectSize() * 6);
auto *effect = assembler->getEffect(groupIndex);
if (!effect && templateEffect) {
effect = new cocos2d::renderer::EffectVariant();
effect->autorelease();
effect->copy(templateEffect);
assembler->updateEffect(groupIndex, effect);
}
if(effect->getPasses().at(0)->getDefinesHash() != templateEffect->getPasses().at(0)->getDefinesHash()){
effect->copy(templateEffect);
}
if (effect) {
effect->setProperty(textureKey, it.first);
}
groupIndex += 1;
}
}
return groupIndex;
}
struct TextSpaceArray {
void addSpace(TextRowSpace& space)
{
if (space.size() > 0)
{
_maxWidth = std::max(_maxWidth, space.getWidth());
}
_data.emplace_back(std::move(space));
}
float _maxWidth = FLT_MIN;
std::vector<TextRowSpace> _data;
};
TextRowSpace::GlyphBlock & TextRowSpace::operator[](size_t i)
{
return _data[i];
}
TextRowSpace::GlyphBlock & TextRowSpace::appendBlock()
{
_data.resize(_data.size() + 1);
return _data[_data.size() - 1];
}
TextRowSpace::TextRowSpace(TextRowSpace &&other)
{
_left = other._left;
_bottom = other._bottom;
_right = other._right;
_top = other._top;
_x = other._x;
_y = other._y;
_data = std::move(other._data);
_ignored = other._ignored;
other.reset();
}
void TextRowSpace::reset()
{
_left = FLT_MAX;
_bottom = FLT_MAX;
_right = FLT_MIN;
_top = FLT_MIN;
_x = 0.0f;
_y = 0.0f;
_data.clear();
_ignored = false;
}
void TextRowSpace::fillRect(int texId, Rect &rect, Rect &uv)
{
GlyphBlock &block = appendBlock();
_left = std::min(_left, rect.getMinX());
_right = std::max(_right, rect.getMaxX());
_bottom = std::min(_bottom, rect.getMinY());
_top = std::max(_top, rect.getMaxY());
block.area = rect;
block.uv = uv;
block.ignored = false;
block.texId = texId;
}
void TextRowSpace::translate(float x, float y)
{
_x += x;
_y += y;
}
Vec2 TextRowSpace::center() const
{
Vec2 ret((_left + _right) / 2.0f, (_bottom + _top) / 2.0f);
return ret;
}
float TextRowSpace::getExtentedWidth(float left, float right) const
{
if (_data.size() == 0)
{
return right - left;
}
return std::max(_right, right) - std::min(_left, left);
}
void TextRowSpace::clip(const Rect &rect)
{
Rect contentRect(_x + _left, _y + _bottom, _right - _left, _top - _bottom);
if (!rect.intersectsRect(contentRect)) {
_ignored = true;
return;
}
const auto size = _data.size();
const auto offset = getOffset();
Rect letter;
Rect unionRect;
for (int i = 0; i < size; i++)
{
letter.size = _data[i].area.size;
letter.origin = offset + _data[i].area.origin;
if (!rect.intersectsRect(letter))
{
_data[i].ignored = true;
}
else if (rect.containsPoint(letter.origin) && rect.containsPoint(Vec2(letter.getMaxX(), letter.getMaxY())))
{
_data[i].area.origin = letter.origin;
}
else
{
//Rect unionRect = letter.unionWithRect(rect); // incorrect result
rectUnion(letter, rect, unionRect);
_data[i].area = unionRect;
recalculateUV(letter, _data[i].uv, unionRect, _data[i].uv);
}
}
//ignore translate
_x = 0.0f;
_y = 0.0f;
}
Rect TextRowSpace::asRect() const {
return Rect(_left + _x, _bottom + _y, _right - _left, _top - _bottom);
}
bool LabelLayout::init(const std::string& font, const std::string& text, float fontSize, float retinaFontSize, LabelLayoutInfo *info)
{
_inited = true;
_layoutInfo = info;
_retinaFontSize = std::max(fontSize, retinaFontSize);
_fontAtlas = TTFLabelAtlasCache::getInstance()->load(font, _retinaFontSize, info);
if(!_fontAtlas) {
return false;
}
_fontScale = fontSize / _fontAtlas->getFontSize();
_groups = std::make_shared<TextRenderGroup>();
if (info->shadowBlur >= 0)
{
_shadowGroups = std::make_shared<TextRenderGroup>();
}
_string = text;
_font = font;
_fontSize = fontSize;
cocos2d::StringUtils::UTF8ToUTF32(text.c_str(), _u32string);
_textSpace.clear();
updateContent();
return true;
}
LabelLayout::~LabelLayout()
{
}
void LabelLayout::setString(const std::string &txt, bool forceUpdate)
{
if (_string != txt) {
_string = txt;
cocos2d::StringUtils::UTF8ToUTF32(txt.c_str(), _u32string);
updateContent();
}
else if (forceUpdate)
{
updateContent();
}
}
bool LabelLayout::updateContent()
{
if(!_fontAtlas)
{
return false;
}
auto *atlas = _fontAtlas->getFontAtlas();
auto *ttf = _fontAtlas->getTTF();
FontLetterDefinition* letterDef = nullptr;
Rect row;
const float LineHeight = _layoutInfo->lineHeight;
const float SpaceX = _layoutInfo->spaceX;
const LabelOverflow OverFlow = _layoutInfo->overflow;
const bool ClampAndWrap = _layoutInfo->wrap && OverFlow == LabelOverflow::CLAMP;
const bool ResizeHeight = OverFlow == LabelOverflow::RESIZE_HEIGHT;
const int ContentWidth = _layoutInfo->width;
const int ContentHeight = _layoutInfo->height;
const LabelAlignmentH HAlign = _layoutInfo->halign;
const LabelAlignmentV VAlign = _layoutInfo->valign;
const float AnchorX = _layoutInfo->anchorX;
const float AnchorY = _layoutInfo->anchorY;
const bool Underline = _layoutInfo->underline;
//LabelCursor cursor;
float cursorX = 0;
TextSpaceArray textSpaces;
TextRowSpace rowSpace;
Rect letterRect;
std::unique_ptr<std::vector<int> > kerning = nullptr;
if (_enableKerning) {
kerning = ttf->getHorizontalKerningForUTF32Text(_u32string);
}
atlas->prepareLetters(_u32string, ttf);
for (int i = 0; i < _u32string.size(); i++)
{
auto ch = _u32string[i];
if (ch == u'\r')
{
cursorX = 0;
continue;
}
if (ch == u'\n')
{
textSpaces.addSpace(rowSpace);
cursorX = 0;
continue;
}
letterDef = atlas->getOrLoad(ch, ttf);
if (!letterDef) continue;
letterRect = letterDef->rect;
letterRect.origin *= _fontScale;
letterRect.size = Size(letterRect.size.width * _fontScale, letterRect.size.height * _fontScale);
if (_enableKerning && kerning) {
auto const kerningX = kerning->at(i) * _fontScale;
cursorX += kerningX;
}
float left = cursorX - letterDef->outline * _fontScale + letterRect.getMinX();
float bottom = letterDef->outline * _fontScale - letterRect.getMaxY();
if ((ResizeHeight || ClampAndWrap) && rowSpace.getExtentedWidth(left, left + letterRect.size.width) > ContentWidth)
{
// RESIZE_HEIGHT or (CLAMP & Enable Wrap)
textSpaces.addSpace(rowSpace);
cursorX = 0;
left = cursorX - letterDef->outline * _fontScale + letterRect.getMinX();
}
float width = letterRect.size.width;
float height = letterRect.size.height;
Rect letterRectInline(left, bottom, width, height);
Rect letterTexture(letterDef->texX, letterDef->texY, letterDef->texWidth, letterDef->texHeight);
rowSpace.fillRect(letterDef->textureID, letterRectInline, letterTexture);
cursorX += SpaceX + letterDef->xAdvance * _fontScale;
}
textSpaces.addSpace(rowSpace);
auto& list = textSpaces._data;
//add underline
if(Underline){
auto underline = atlas->getOrLoad('_', ttf);
for (auto &space : list) {
Rect letterRect = underline->rect;
letterRect.origin *= _fontScale;
letterRect.size = Size(letterRect.size.width * _fontScale, letterRect.size.height * _fontScale);
float bottom = underline->outline * _fontScale - letterRect.getMaxY();
float left = -letterDef->outline * _fontScale + space.getLeft();
Rect letterRectInline(left, bottom, space.getWidth(), letterRect.size.height);
Rect letterTexture(underline->texX, underline->texY, underline->texWidth, underline->texHeight);
splitRectIntoThreeParts(underline->textureID, letterRect, letterRectInline, letterTexture, space);
}
}
Vec2 newCenter;
Vec2 delta;
// default value in LabelOverflow::None mode
float maxLineWidth = textSpaces._maxWidth;
const float TotalTextHeight = textSpaces._data.size() * LineHeight;
float topMost = (TotalTextHeight - LineHeight) * (1.0f - AnchorY);
float leftMost = - AnchorX * maxLineWidth;
float rightMost = (1.0f - AnchorX) * maxLineWidth;
float centerX = (0.5f - AnchorX) * ContentWidth;
Rect textArea(0, 0, 0, 0);
for (int i = 0; i < list.size(); i++)
{
auto& s = list[i];
if (!s.validate())
{
continue;
}
if (HAlign == LabelAlignmentH::CENTER)
{
newCenter.set(centerX, topMost - i * LineHeight);
}
else if (HAlign == LabelAlignmentH::LEFT)
{
newCenter.set(leftMost + s.getWidth() * 0.5, topMost - i * LineHeight);
}
else // right
{
newCenter.set(rightMost - s.getWidth() * 0.5f, topMost - i * LineHeight);
}
delta = newCenter - s.center();
s.translate(delta.x, delta.y);
textArea.merge(s.asRect());
}
//no resize
if (OverFlow == LabelOverflow::NONE || OverFlow == LabelOverflow::CLAMP || OverFlow == LabelOverflow::RESIZE_HEIGHT)
{
Vec2 centerArea(textArea.origin.x + textArea.size.width / 2.0, textArea.origin.y + textArea.size.height / 2.0);
float tPosX, tPosY;
float halfAreaWidth = textArea.size.width * 0.5f;
float halfAreaHeight = textArea.size.height * 0.5f;
float contentWidth = ContentWidth;
float contentHeight = ContentHeight;
if (OverFlow == LabelOverflow::NONE)
{
contentWidth = textArea.size.width;
contentHeight = textArea.size.height;
}
switch (HAlign)
{
case LabelAlignmentH::LEFT:
tPosX = halfAreaWidth - AnchorX * contentWidth;
break;
case LabelAlignmentH::CENTER:
tPosX = (0.5f - AnchorX) * contentWidth;
break;
case LabelAlignmentH::RIGHT:
tPosX = (1.0f - AnchorX) * contentWidth - halfAreaWidth;
break;
default:
assert(false);
}
switch (VAlign)
{
case LabelAlignmentV::TOP:
tPosY = (1.0f - AnchorY) * contentHeight - halfAreaHeight;
break;
case LabelAlignmentV::CENTER:
tPosY = (0.5f - AnchorY) * ContentHeight;
break;
case LabelAlignmentV::BOTTOM:
tPosY = halfAreaHeight - AnchorY * contentHeight;
break;
default:
assert(false);
}
delta = Vec2(tPosX, tPosY) - centerArea;
for (auto &textSpace : textSpaces._data) {
textSpace.translate(delta.x, delta.y);
}
if (OverFlow == LabelOverflow::CLAMP)
{
// clipping
Rect displayRegion(-ContentWidth * AnchorX, -ContentHeight * AnchorY, ContentWidth, ContentHeight);
for (auto &textSpace : textSpaces._data) {
textSpace.clip(displayRegion);
}
}
}
else if (OverFlow == LabelOverflow::SHRINK)
{
float scale = std::min(ContentWidth / textArea.size.width, ContentHeight / textArea.size.height);
scale = std::min(1.0f, scale);
_scale = scale;
Vec2 centerArea(textArea.origin.x + textArea.size.width * 0.5f, textArea.origin.y + textArea.size.height * 0.5f);
float tPosXScale, tPosYScale;
float halfTextWidth = textArea.size.width * 0.5f * _scale;
float halfTextHeight = textArea.size.height * 0.5f * _scale;
switch (HAlign)
{
case LabelAlignmentH::LEFT:
tPosXScale = halfTextWidth - AnchorX * ContentWidth;
break;
case LabelAlignmentH::CENTER:
tPosXScale = (0.5f - AnchorX) * ContentWidth;
break;
case LabelAlignmentH::RIGHT:
tPosXScale = (1.0f - AnchorX) * ContentWidth - halfTextWidth;
break;
default:
assert(false);
}
switch (VAlign)
{
case LabelAlignmentV::TOP:
tPosYScale = (1.0f - AnchorY) * ContentHeight - halfTextHeight;
break;
case LabelAlignmentV::CENTER:
tPosYScale = (0.5f - AnchorY) * ContentHeight;
break;
case LabelAlignmentV::BOTTOM:
tPosYScale = halfTextHeight -AnchorY * ContentHeight;
break;
default:
assert(false);
}
delta = Vec2(tPosXScale/ _scale, tPosYScale/_scale) - centerArea;
for (auto &textSpace : textSpaces._data) {
textSpace.translate(delta.x, delta.y);
}
}
se::Object *comp = _renderer->getJsComponent();
if ((OverFlow == LabelOverflow::NONE || OverFlow == LabelOverflow::RESIZE_HEIGHT) && comp) {
se::Value funcVal;
se::Value nodeVal;
if(comp->getProperty("node", &nodeVal) && nodeVal.isObject() &&
nodeVal.toObject()->getProperty("setContentSize", &funcVal)) {
se::ValueArray args;
args.push_back(se::Value( OverFlow == LabelOverflow::RESIZE_HEIGHT ? ContentWidth: maxLineWidth));
args.push_back(se::Value(TotalTextHeight));
funcVal.toObject()->call(args, nodeVal.toObject());
}
}
_textSpace = std::move(textSpaces._data);
return true;
}
void LabelLayout::fillAssembler(renderer::CustomAssembler *assembler, EffectVariant *templateEffect)
{
assembler->reset();
if(!_groups) {
return;
}
_groups->reset();
int groupIndex = 0;
if (_textSpace.empty())
return;
if (_shadowGroups)
{
_shadowGroups->reset();
}
assembler->setUseModel(true);
auto *fontAtals = _fontAtlas->getFontAtlas();
Color4B textColor = _layoutInfo->color;
// apply transform to all rectangles
Rect rect;
float scale = _scale;
const bool Italics = _layoutInfo->italic;
if (_shadowGroups && _layoutInfo->shadowBlur >= 0)
{
Vec2 offset(_layoutInfo->shadowX, _layoutInfo->shadowY);
Color4B shadowColor = _layoutInfo->shadowColor;
for (auto &textSpace : _textSpace) {
if (textSpace.isIgnored()) continue;
Vec2 shadowOffset = textSpace.getOffset();
auto size = textSpace.size();
for (int i = 0; i < size; i++)
{
TextRowSpace::GlyphBlock item = textSpace[i];
if (textSpace.isIgnored(i)) continue;
item.area.size = item.area.size * scale;
item.area.origin = (offset + item.area.origin + shadowOffset) * scale;
_shadowGroups->addRect(fontAtals->frameAt(item.texId).getTexture(), item.area, item.uv, shadowColor, Italics);
}
}
groupIndex = _shadowGroups->fill(assembler, groupIndex, this, templateEffect);
Technique::Parameter outlineSizeP(outlineSizeKey, Technique::Parameter::Type::FLOAT, (float*)&(_layoutInfo->outlineSize));
if (_layoutInfo->outlineSize > 0.0f)
{
// use shadow color to replace outline color
Color4F outlineColor(_layoutInfo->shadowColor);
Technique::Parameter outlineColorP(outlineColorKey, Technique::Parameter::Type::COLOR4, (float*)&outlineColor);
for (auto i = 0; i < groupIndex; i++)
{
auto *e = assembler->getEffect(i);
e->setProperty(outlineColorKey, outlineColorP);
e->setProperty(outlineSizeKey, outlineSizeP);
}
}
else {
for (auto i = 0; i < groupIndex; i++)
{
auto *e = assembler->getEffect(i);
e->setProperty(outlineSizeKey, outlineSizeP);
}
}
}
for (auto &textSpace : _textSpace) {
if (textSpace.isIgnored()) continue;
Vec2 offset = textSpace.getOffset();
auto size = textSpace.size();
for (int i = 0; i < size; i++)
{
auto &item = textSpace[i];
if (textSpace.isIgnored(i)) continue;
item.area.size = item.area.size * scale;
item.area.origin = (offset + item.area.origin) * scale;
_groups->addRect(fontAtals->frameAt(item.texId).getTexture(), item.area, item.uv, textColor, Italics);
}
}
int textStartIndex = groupIndex;
groupIndex = _groups->fill(assembler, groupIndex, this, templateEffect);
if (_layoutInfo->outlineSize > 0.0f)
{
Color4F outlineColor(_layoutInfo->outlineColor);
Technique::Parameter outlineColorP(outlineColorKey, Technique::Parameter::Type::COLOR4, (float*)&outlineColor);
Technique::Parameter outlineSizeP(outlineSizeKey, Technique::Parameter::Type::FLOAT, (float*)&(_layoutInfo->outlineSize));
for (auto i = textStartIndex; i < groupIndex; i++)
{
auto *e = assembler->getEffect(i);
e->setProperty(outlineColorKey, outlineColorP);
e->setProperty(outlineSizeKey, outlineSizeP);
}
}
else {
Color4F outlineColor(_layoutInfo->outlineColor);
Technique::Parameter outlineSizeP(outlineSizeKey, Technique::Parameter::Type::FLOAT, (float*)&(_layoutInfo->outlineSize));
for (auto i = textStartIndex; i < groupIndex; i++)
{
auto *e = assembler->getEffect(i);
e->setProperty(outlineSizeKey, outlineSizeP);
}
}
}
}
#endif

View File

@ -0,0 +1,168 @@
/****************************************************************************
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include <string>
#include "2d/CCTTFLabelAtlasCache.h"
#include "math/Vec3.h"
#include "math/Vec4.h"
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
namespace cocos2d {
namespace renderer {
class CustomAssembler;
class Texture2D;
class MeshBuffer;
class Effect;
class EffectVariant;
}
namespace middleware {
class MeshBuffer;
}
class TextRenderGroup;
class LabelRenderer;
class TextRowSpace {
/**
* Y
* ^
* |
* |
* +------> X
*/
public:
struct GlyphBlock {
Rect area;
Rect uv;
int texId = 0;
bool ignored = false;
};
TextRowSpace() = default;
TextRowSpace(TextRowSpace &&other);
void fillRect(int texId, Rect &rect, Rect &uv);
void translate(float x, float y);
Vec2 center() const;
float getWidth() const { return _data.size() > 0 ? _right - _left : 0; }
float getHeight() const { return _data.size() > 0 ? _top - _bottom : 0; }
float getExtentedWidth(float left, float right) const;
Vec2 getOffset() const { return Vec2(_x, _y); }
GlyphBlock & operator[] (size_t i);
bool isIgnored() const { return _ignored; }
bool isIgnored(int i) const { return _data[i].ignored; }
void clip(const Rect &rec);
size_t size() const { return _data.size(); }
bool validate() const { return _data.size() > 0; }
Rect asRect() const;
float getLeft() const { return _left; }
float getRight() const { return _right; }
float getTop() const { return _top; }
float getBottom() const { return _bottom; }
private:
void reset();
GlyphBlock & appendBlock();
private:
float _left = FLT_MAX;
float _bottom = FLT_MAX;
float _right = FLT_MIN;
float _top = FLT_MIN;
float _x = 0.0f;
float _y = 0.0f;
std::vector<GlyphBlock> _data;
bool _ignored = false;
};
class LabelLayout {
public:
LabelLayout(LabelRenderer *r): _renderer(r){}
bool init(const std::string& font, const std::string& text, float fontSize, float retinaFontSize, LabelLayoutInfo *info);
virtual ~LabelLayout();
void setString(const std::string &txt, bool forceUpdate);
bool isInited() const { return _inited; }
void fillAssembler(renderer::CustomAssembler *assembeler, renderer::EffectVariant *effect);
private:
bool updateContent();
private:
std::string _string;
std::u32string _u32string;
std::string _font;
float _fontSize = 0.0f;
float _retinaFontSize = 0.0f;
float _fontScale = 1.0f;
float _scale = 1.0f;
//weak reference
LabelLayoutInfo *_layoutInfo = nullptr;
std::shared_ptr<TTFLabelAtlas> _fontAtlas;
bool _enableKerning = true;
bool _inited = false;
std::vector<TextRowSpace> _textSpace;
std::shared_ptr<TextRenderGroup> _groups;
std::shared_ptr<TextRenderGroup> _shadowGroups;
LabelRenderer *_renderer = nullptr;
};
}
#endif

View File

@ -0,0 +1,155 @@
/****************************************************************************
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "CCTTFLabelAtlasCache.h"
#include "platform/CCFileUtils.h"
#include <cmath>
#include <cassert>
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
#define FTT_MAP_FONT_SIZE 0
#define FTT_USE_FONT_MAP_SIZE_POWEROFTWO 0
#define FTT_TEXTURE_SIZE 1024
namespace cocos2d {
namespace {
TTFLabelAtlasCache *_instance = nullptr;
/**
* Labels of different size can share a same FontAtlas by mapping font size.
*/
#if FTT_MAP_FONT_SIZE
inline int mapFontSize(float x) {
#if FTT_USE_FONT_MAP_SIZE_POWEROFTWO
// round to power of 2, which waste lots of memory?
return 1 << (int)(::ceil(::log(x) / ::log(2.0)));
#else
if (x <= 8.0f) return 8;
else if (x <= 16.0f) return 16;
else if (x <= 32.0f) return 32;
else if (x <= 48.0f) return 48;
return (int)x;
#endif
}
#else
#define mapFontSize(f) (int)(f)
#endif
}
TTFLabelAtlas::TTFLabelAtlas(const std::string &fontPath, float fontSize, LabelLayoutInfo *info)
:_fontName(fontPath), _fontSize(fontSize), _info(info)
{
}
bool TTFLabelAtlas::init()
{
assert(FileUtils::getInstance()->isFileExist(_fontName));
_ttfFont = std::make_shared<FontFreeType>(_fontName, _fontSize, _info);
if(!_ttfFont->loadFont()){
return false;
}
_fontAtlas = std::make_shared<FontAtlas>(PixelMode::A8, FTT_TEXTURE_SIZE, FTT_TEXTURE_SIZE, _info->outlineSize > 0 || _info->bold);
_fontAtlas->init();
return true;
}
TTFLabelAtlasCache * TTFLabelAtlasCache::getInstance()
{
if (!_instance)
{
_instance = new TTFLabelAtlasCache();
}
return _instance;
}
void TTFLabelAtlasCache::destroyInstance()
{
delete _instance;
_instance = nullptr;
}
void TTFLabelAtlasCache::reset()
{
_cache.clear();
}
std::shared_ptr<TTFLabelAtlas> TTFLabelAtlasCache::load(const std::string &font, float fontSizeF, LabelLayoutInfo *info)
{
int fontSize = mapFontSize(fontSizeF);
std::string keybuffer = cacheKeyFor(font, fontSize, info);
#if CC_TTF_LABELATLAS_ENABLE_GC
std::weak_ptr<TTFLabelAtlas> &atlasWeak= _cache[keybuffer];
std::shared_ptr<TTFLabelAtlas> atlas = atlasWeak.lock();
if (!atlas)
{
atlas = std::make_shared<TTFLabelAtlas>(font, fontSize, info);
if(!atlas->init())
{
return nullptr;
}
atlasWeak = atlas;
}
#else
std::shared_ptr<TTFLabelAtals> &atlas = _cache[keybuffer];
if (!atlas)
{
atlas = std::make_shared<TTFLabelAtals>(font, fontSize, info);
if(!atlas->init())
{
return nullptr;
}
}
#endif
return atlas;
}
void TTFLabelAtlasCache::unload(TTFLabelAtlas *atlas)
{
int fontSize = mapFontSize(atlas->_fontSize);
std::string key = cacheKeyFor(atlas->_fontName, fontSize, atlas->_info);
_cache.erase(key);
}
std::string TTFLabelAtlasCache::cacheKeyFor(const std::string &font, int fontSize, LabelLayoutInfo * info)
{
char keybuffer[512] = { 0 };
std::string fullPath = FileUtils::getInstance()->fullPathForFilename(font);
//NOTICE: fontpath may be too long
snprintf(keybuffer, 511, "s:%d/sdf:%d/p:%s", fontSize, info->outlineSize > 0 || info->bold, fullPath.c_str());
return keybuffer;
}
}
#endif

View File

@ -0,0 +1,96 @@
/****************************************************************************
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "2d/CCFontFreetype.h"
#include "2d/CCFontAtlas.h"
#include <unordered_map>
#include <memory>
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
#define CC_TTF_LABELATLAS_ENABLE_GC 1
namespace cocos2d {
class TTFLabelAtlasCache;
struct LabelLayoutInfo;
// font atlas of specific size font
class TTFLabelAtlas {
public:
TTFLabelAtlas(const std::string &, float, LabelLayoutInfo *info);
inline FontAtlas * getFontAtlas() const { return _fontAtlas.get(); }
inline FontFreeType * getTTF() const { return _ttfFont.get(); }
float getFontSize() const { return _fontSize; }
bool init();
private:
std::string _fontName;
float _fontSize = 0.f;
//weak reference
LabelLayoutInfo *_info = nullptr;
std::shared_ptr<FontAtlas> _fontAtlas;
std::shared_ptr<FontFreeType> _ttfFont;
friend class TTFLabelAtlasCache;
};
class TTFLabelAtlasCache {
public:
static TTFLabelAtlasCache* getInstance();
static void destroyInstance();
void reset();
std::shared_ptr<TTFLabelAtlas> load(const std::string &font, float fontSize, LabelLayoutInfo* info);
void unload(TTFLabelAtlas *);
protected:
std::string cacheKeyFor(const std::string &font, int fontSize, LabelLayoutInfo *info);
TTFLabelAtlasCache() {}
private:
#if CC_TTF_LABELATLAS_ENABLE_GC
std::unordered_map< std::string, std::weak_ptr< TTFLabelAtlas>> _cache;
#else
std::unordered_map< std::string, std::shared_ptr< TTFLabelAtals>> _cache;
#endif
};
}
#endif

View File

@ -0,0 +1,167 @@
/****************************************************************************
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "cocos/2d/CCTTFLabelRenderer.h"
#include "MiddlewareMacro.h"
#include "renderer/renderer/Pass.h"
#include "renderer/renderer/Technique.h"
#include "renderer/scene/RenderFlow.hpp"
#include "renderer/scene/assembler/CustomAssembler.hpp"
#include "cocos/editor-support/IOBuffer.h"
#include "cocos/editor-support/middleware-adapter.h"
#include "cocos/editor-support/MiddlewareManager.h"
#include "cocos/platform/CCApplication.h"
#include "cocos/platform/CCFileUtils.h"
#include "scripting/js-bindings/jswrapper/SeApi.h"
#include "CCLabelLayout.h"
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
USING_NS_CC;
USING_NS_MW;
using namespace renderer;
namespace cocos2d {
LabelRenderer::LabelRenderer()
{
}
LabelRenderer::~LabelRenderer()
{
CC_SAFE_RELEASE_NULL(_effect);
CC_SAFE_RELEASE_NULL(_nodeProxy);
if(_componentObj) _componentObj->decRef();
}
void LabelRenderer::bindNodeProxy(cocos2d::renderer::NodeProxy* node) {
if (node == _nodeProxy) return;
CC_SAFE_RELEASE(_nodeProxy);
_nodeProxy = node;
CC_SAFE_RETAIN(_nodeProxy);
}
void LabelRenderer::setEffect(cocos2d::renderer::EffectVariant* arg) {
if (_effect == arg) return;
CC_SAFE_RELEASE(_effect);
_effect = arg;
CC_SAFE_RETAIN(arg);
_cfg->updateFlags |= UPDATE_EFFECT;
}
void LabelRenderer::bindSharedBlock(se::Object * selfObj, void *cfg, void *layout)
{
_selfObj = selfObj;
_cfg = static_cast<decltype(_cfg)>(cfg);
_layoutInfo = static_cast<decltype(_layoutInfo)>(layout);
}
void LabelRenderer::genStringLayout()
{
std::string fontPath = getFontPath();
std::string text = getString();
if (!fontPath.empty() && !text.empty() && !_stringLayout)
{
_stringLayout.reset(new LabelLayout(this));
_stringLayout->init(fontPath, text, _cfg->fontSize, _cfg->fontSizeRetina, _layoutInfo);
}
}
void LabelRenderer::render()
{
std::string text = getString();
std::string fontPath = getFontPath();
if (!_effect || text.empty() || fontPath.empty()) return;
if (!_stringLayout) {
genStringLayout();
_cfg->updateFlags &= ~(UPDATE_FONT | UPDATE_EFFECT);
}
renderIfChange();
}
void LabelRenderer::renderIfChange()
{
if (!_stringLayout) return;
if (_cfg->updateFlags & UPDATE_FONT || _cfg->updateFlags & UPDATE_EFFECT)
{
// update font & string
_stringLayout.reset();
genStringLayout();
doRender();
}
else if (_cfg->updateFlags & UPDATE_CONTENT)
{
std::string text = getString(); // update content only
if (_stringLayout->isInited())
{
_stringLayout->setString(text, true);
doRender();
}
}
_cfg->updateFlags = 0;
}
void LabelRenderer::doRender()
{
if (_stringLayout && _effect && _nodeProxy && _nodeProxy->getAssembler())
{
auto *assembler = (CustomAssembler*)_nodeProxy->getAssembler();
_stringLayout->fillAssembler(assembler, _effect);
}
}
std::string LabelRenderer::getString() {
se::Value str;
assert(_selfObj);
_selfObj->getProperty("string", &str);
return str.toString();
}
std::string LabelRenderer::getFontPath() {
se::Value str;
assert(_selfObj);
_selfObj->getProperty("fontPath", &str);
return str.toString();
}
void LabelRenderer::setJsComponent(se::Object *component)
{
if(_componentObj == component) return;
if(_componentObj) _componentObj->decRef();
if(component) component->incRef();
_componentObj = component;
}
}
#endif

View File

@ -0,0 +1,106 @@
/****************************************************************************
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "base/ccMacros.h"
#include "2d/CCTTFTypes.h"
#include "base/ccConfig.h"
#include <memory>
#if CC_ENABLE_TTF_LABEL_RENDERER
namespace se {
class Object;
}
namespace cocos2d {
namespace renderer {
class Texture2D;
class Effect;
class EffectVariant;
class NodeProxy;
}
class LabelLayout;
class LabelRenderer : public cocos2d::Ref {
private:
enum UpdateFlags {
UPDATE_CONTENT = 1 << 0,
UPDATE_FONT = 1 << 1,
UPDATE_EFFECT = 1 << 2
};
public:
struct LabelRendererConfig {
uint32_t updateFlags = 0xFFFFFFFF;
float fontSize = 20.0f;
float fontSizeRetina = 0.0f;
};
LabelRenderer();
virtual ~LabelRenderer();
void bindNodeProxy(cocos2d::renderer::NodeProxy* node);
void setEffect(cocos2d::renderer::EffectVariant* effect);
void render();
void bindSharedBlock(se::Object *selfObj, void *cfg, void *layout);
void setJsComponent(se::Object *component);
se::Object *getJsComponent() const {return _componentObj;}
private:
void genStringLayout();
void renderIfChange();
void doRender();
std::string getString();
std::string getFontPath();
std::unique_ptr<LabelLayout> _stringLayout;
se::Object *_selfObj = nullptr;
se::Object *_componentObj = nullptr;
//export arraybuffer to js
LabelRendererConfig *_cfg = nullptr;
LabelLayoutInfo *_layoutInfo = nullptr;
cocos2d::renderer::NodeProxy* _nodeProxy = nullptr;
cocos2d::renderer::EffectVariant * _effect = nullptr;
};
}
#endif

View File

@ -0,0 +1,69 @@
/****************************************************************************
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "CCTTFTypes.h"
#include <cassert>
#include <cstdarg>
#include <iostream>
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
namespace cocos2d {
GlyphBitmap::GlyphBitmap(std::vector<uint8_t>& data, int width, int height, Rect rect, int adv, PixelMode mode, int outline)
: _data(std::move(data)), _width(width), _height(height), _rect(rect), _xAdvance(adv), _pixelMode(mode), _outline(outline)
{
}
GlyphBitmap::GlyphBitmap(GlyphBitmap&& other) noexcept
{
_data = std::move(other._data);
_rect = other._rect;
_width = other._width;
_height = other._height;
_xAdvance = other._xAdvance;
_pixelMode = other._pixelMode;
_outline = other._outline;
}
int PixelModeSize(PixelMode mode)
{
switch (mode)
{
case PixelMode::AI88:
return 2;
case PixelMode::A8:
return 1;
case PixelMode::RGB888:
return 3;
case PixelMode::BGRA8888:
return 4;
default:
assert(false); // invalidate pixel mode
}
return 0;
}
}
#endif

View File

@ -0,0 +1,114 @@
/****************************************************************************
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include <memory>
#include <vector>
#include <iosfwd>
#include <cstdint>
#include "math/Vec2.h"
#include "math/CCGeometry.h"
#include "base/ccTypes.h"
#include "base/ccConfig.h"
#if CC_ENABLE_TTF_LABEL_RENDERER
namespace cocos2d {
// ref CCMacro.js
enum class LabelAlignmentH : uint8_t
{
LEFT, CENTER, RIGHT
};
// ref CCMacro.js
enum class LabelAlignmentV : uint8_t {
TOP, CENTER, BOTTOM
};
// ref CCLabel.js
enum class LabelOverflow : uint8_t {
NONE, CLAMP, SHRINK, RESIZE_HEIGHT
};
struct LabelLayoutInfo {
float lineHeight = 0.0f;
float outlineSize = 0.0f;
float spaceX = 0.0f;
float width = 0.0f;
float height = 0.0f;
float anchorX = 0.5f;
float anchorY = 0.5f;
float shadowX = 0.0f;
float shadowY = 0.0f;
int32_t shadowBlur = -1;
Color4B shadowColor;
Color4B color;
Color4B outlineColor;
bool wrap = false;
bool bold = false;
bool italic = false;
bool underline = false;
LabelAlignmentV valign = LabelAlignmentV::CENTER;
LabelAlignmentH halign = LabelAlignmentH::CENTER;
LabelOverflow overflow = LabelOverflow::NONE;
};
enum class PixelMode {
AI88,
A8,
RGB888,
BGRA8888,
INVAL,
};
int PixelModeSize(PixelMode mode);
class GlyphBitmap {
public:
GlyphBitmap(std::vector<uint8_t>& data, int width, int height, Rect rect, int xAdvance, PixelMode mode, int outline);
GlyphBitmap(GlyphBitmap&& other) noexcept;
int getWidth() const { return _width; }
int getHeight() const { return _height; }
Rect getRect() const { return _rect; }
int getXAdvance() const { return _xAdvance; }
int getOutline() const { return _outline; }
PixelMode getPixelMode() const { return _pixelMode; }
std::vector<uint8_t>& getData() { return _data; }
private:
int _width = 0;
int _height = 0;
int _outline = 0;
std::vector<uint8_t> _data;
Rect _rect;
int _xAdvance = 0;
PixelMode _pixelMode;
};
}
#endif

275
cocos2d-x/cocos/Android.mk Normal file
View File

@ -0,0 +1,275 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := cocos2dx_static
LOCAL_MODULE_FILENAME := libcocos2d
LOCAL_ARM_MODE := arm
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
MATHNEONFILE := math/MathUtil.cpp.neon
else
MATHNEONFILE := math/MathUtil.cpp
endif
LOCAL_SRC_FILES := \
cocos2d.cpp \
platform/CCFileUtils.cpp \
platform/CCImage.cpp \
platform/CCSAXParser.cpp \
$(MATHNEONFILE) \
math/CCGeometry.cpp \
math/CCVertex.cpp \
math/Mat4.cpp \
math/Quaternion.cpp \
math/Vec2.cpp \
math/Vec3.cpp \
math/Vec4.cpp \
math/Mat3.cpp \
base/CCAutoreleasePool.cpp \
base/CCConfiguration.cpp \
base/CCData.cpp \
base/CCRef.cpp \
base/CCValue.cpp \
base/CCThreadPool.cpp \
base/TGAlib.cpp \
base/ZipUtils.cpp \
base/base64.cpp \
base/ccCArray.cpp \
base/ccRandom.cpp \
base/ccTypes.cpp \
base/ccUTF8.cpp \
base/ccUtils.cpp \
base/etc1.cpp \
base/etc2.cpp \
base/pvr.cpp \
base/CCLog.cpp \
base/CCScheduler.cpp \
base/csscolorparser.cpp \
base/CCGLUtils.cpp \
base/CCRenderTexture.cpp \
storage/local-storage/LocalStorage-android.cpp \
network/CCDownloader.cpp \
network/CCDownloader-android.cpp \
network/Uri.cpp \
network/HttpClient-android.cpp \
scripting/js-bindings/auto/jsb_cocos2dx_auto.cpp \
scripting/js-bindings/auto/jsb_cocos2dx_extension_auto.cpp \
scripting/js-bindings/auto/jsb_cocos2dx_network_auto.cpp \
scripting/js-bindings/manual/JavaScriptJavaBridge.cpp \
scripting/js-bindings/manual/jsb_opengl_manual.cpp \
scripting/js-bindings/manual/jsb_opengl_utils.cpp \
scripting/js-bindings/manual/jsb_classtype.cpp \
scripting/js-bindings/manual/jsb_conversions.cpp \
scripting/js-bindings/manual/jsb_cocos2dx_manual.cpp \
scripting/js-bindings/manual/jsb_global.cpp \
scripting/js-bindings/manual/jsb_xmlhttprequest.cpp \
scripting/js-bindings/manual/jsb_cocos2dx_network_manual.cpp \
scripting/js-bindings/manual/jsb_platform_android.cpp \
scripting/js-bindings/jswrapper/config.cpp \
scripting/js-bindings/jswrapper/HandleObject.cpp \
scripting/js-bindings/jswrapper/MappingUtils.cpp \
scripting/js-bindings/jswrapper/RefCounter.cpp \
scripting/js-bindings/jswrapper/Value.cpp \
scripting/js-bindings/jswrapper/State.cpp \
scripting/js-bindings/jswrapper/v8/Class.cpp \
scripting/js-bindings/jswrapper/v8/Object.cpp \
scripting/js-bindings/jswrapper/v8/ObjectWrap.cpp \
scripting/js-bindings/jswrapper/v8/ScriptEngine.cpp \
scripting/js-bindings/jswrapper/v8/Utils.cpp \
scripting/js-bindings/event/EventDispatcher.cpp \
../external/sources/xxtea/xxtea.cpp \
../external/sources/tinyxml2/tinyxml2.cpp \
../external/sources/unzip/ioapi_mem.cpp \
../external/sources/unzip/ioapi.cpp \
../external/sources/unzip/unzip.cpp \
../external/sources/ConvertUTF/ConvertUTFWrapper.cpp \
../external/sources/ConvertUTF/ConvertUTF.c \
../external/sources/edtaa3func/edtaa3func.cpp \
../external/sources/edtaa3func/edtaa3func.h \
ui/edit-box/EditBox-android.cpp \
2d/CCFontAtlas.cpp \
2d/CCFontFreetype.cpp \
2d/CCLabelLayout.cpp \
2d/CCTTFLabelAtlasCache.cpp \
2d/CCTTFLabelRenderer.cpp \
2d/CCTTFTypes.cpp
# v8 debugger source files, always enable it
LOCAL_SRC_FILES += \
scripting/js-bindings/jswrapper/v8/debugger/SHA1.cpp \
scripting/js-bindings/jswrapper/v8/debugger/util.cc \
scripting/js-bindings/jswrapper/v8/debugger/env.cc \
scripting/js-bindings/jswrapper/v8/debugger/inspector_agent.cc \
scripting/js-bindings/jswrapper/v8/debugger/inspector_io.cc \
scripting/js-bindings/jswrapper/v8/debugger/inspector_socket.cc \
scripting/js-bindings/jswrapper/v8/debugger/inspector_socket_server.cc \
scripting/js-bindings/jswrapper/v8/debugger/node.cc \
scripting/js-bindings/jswrapper/v8/debugger/node_debug_options.cc \
scripting/js-bindings/jswrapper/v8/debugger/http_parser.c
# uv_static only used in v8 debugger
LOCAL_STATIC_LIBRARIES += uv_static
LOCAL_STATIC_LIBRARIES += v8_inspector
LOCAL_STATIC_LIBRARIES += cocos_extension_static
# opengl bindings depend on GFXUtils "_JSB_GL_CHECK"
LOCAL_SRC_FILES += \
renderer/gfx/GFXUtils.cpp
ifeq ($(USE_GFX_RENDERER),1)
LOCAL_SRC_FILES += \
renderer/Types.cpp \
renderer/gfx/DeviceGraphics.cpp \
renderer/gfx/FrameBuffer.cpp \
renderer/gfx/GFX.cpp \
renderer/gfx/GraphicsHandle.cpp \
renderer/gfx/IndexBuffer.cpp \
renderer/gfx/Program.cpp \
renderer/gfx/RenderBuffer.cpp \
renderer/gfx/RenderTarget.cpp \
renderer/gfx/State.cpp \
renderer/gfx/Texture.cpp \
renderer/gfx/Texture2D.cpp \
renderer/gfx/VertexBuffer.cpp \
renderer/gfx/VertexFormat.cpp \
renderer/renderer/BaseRenderer.cpp \
renderer/renderer/Camera.cpp \
renderer/renderer/Config.cpp \
renderer/renderer/Effect.cpp \
renderer/renderer/InputAssembler.cpp \
renderer/renderer/Light.cpp \
renderer/renderer/Model.cpp \
renderer/renderer/Pass.cpp \
renderer/renderer/ProgramLib.cpp \
renderer/renderer/Scene.cpp \
renderer/renderer/Technique.cpp \
renderer/renderer/View.cpp \
renderer/renderer/ForwardRenderer.cpp \
renderer/scene/assembler/Assembler.cpp \
renderer/scene/assembler/AssemblerBase.cpp \
renderer/scene/assembler/CustomAssembler.cpp \
renderer/scene/assembler/MaskAssembler.cpp \
renderer/scene/assembler/RenderData.cpp \
renderer/scene/assembler/RenderDataList.cpp \
renderer/scene/assembler/TiledMapAssembler.cpp \
renderer/scene/assembler/AssemblerSprite.cpp \
renderer/scene/assembler/SimpleSprite2D.cpp \
renderer/scene/assembler/SlicedSprite2D.cpp \
renderer/scene/assembler/SimpleSprite3D.cpp \
renderer/scene/assembler/SlicedSprite3D.cpp \
renderer/scene/assembler/MeshAssembler.cpp \
renderer/scene/assembler/Particle3DAssembler.cpp \
renderer/scene/MeshBuffer.cpp \
renderer/scene/ModelBatcher.cpp \
renderer/scene/NodeProxy.cpp \
renderer/scene/RenderFlow.cpp \
renderer/scene/StencilManager.cpp \
renderer/scene/MemPool.cpp \
renderer/scene/NodeMemPool.cpp \
renderer/scene/ParallelTask.cpp \
renderer/memop/RecyclePool.hpp \
renderer/renderer/EffectVariant.cpp \
renderer/renderer/EffectBase.cpp \
scripting/js-bindings/auto/jsb_gfx_auto.cpp \
scripting/js-bindings/auto/jsb_renderer_auto.cpp \
scripting/js-bindings/manual/jsb_renderer_manual.cpp \
scripting/js-bindings/manual/jsb_gfx_manual.cpp
endif # USE_GFX_RENDERER
ifeq ($(USE_VIDEO),1)
LOCAL_SRC_FILES += \
ui/videoplayer/VideoPlayer-android.cpp \
scripting/js-bindings/auto/jsb_video_auto.cpp
endif # USE_VIDEO
ifeq ($(USE_WEB_VIEW),1)
LOCAL_SRC_FILES += \
ui/webview/WebViewImpl-android.cpp \
scripting/js-bindings/auto/jsb_webview_auto.cpp
endif # USE_WEB_VIEW
ifeq ($(USE_AUDIO),1)
LOCAL_SRC_FILES += \
scripting/js-bindings/auto/jsb_cocos2dx_audioengine_auto.cpp
LOCAL_STATIC_LIBRARIES += audioengine_static
endif # USE_AUDIO
ifeq ($(USE_SOCKET),1)
LOCAL_SRC_FILES += \
network/SocketIO.cpp \
network/WebSocket-libwebsockets.cpp \
network/WebSocketServer.cpp \
scripting/js-bindings/manual/jsb_socketio.cpp \
scripting/js-bindings/manual/jsb_websocket.cpp \
scripting/js-bindings/manual/jsb_websocket_server.cpp
LOCAL_STATIC_LIBRARIES += libwebsockets_static
LOCAL_STATIC_LIBRARIES += cocos_ssl_static
LOCAL_STATIC_LIBRARIES += cocos_crypto_static
LOCAL_STATIC_LIBRARIES += uv_static
endif # USE_SOCKET
ifneq ($(USE_MIDDLEWARE),0)
LOCAL_STATIC_LIBRARIES += editor_support_static
endif # USE_MIDDLEWARE
LOCAL_C_INCLUDES := $(LOCAL_PATH) \
$(LOCAL_PATH)/.. \
$(LOCAL_PATH)/platform \
$(LOCAL_PATH)/editor-support \
$(LOCAL_PATH)/../external/android/$(TARGET_ARCH_ABI)/include \
$(LOCAL_PATH)/../external/sources \
$(LOCAL_PATH)/renderer \
$(LOCAL_PATH)/scripting/js-bindings/manual \
$(LOCAL_PATH)/scripting/js-bindings/manual/platform/android \
$(LOCAL_PATH)/scripting/js-bindings/auto \
$(LOCAL_PATH)/renderer/gfx
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) \
$(LOCAL_PATH)/.. \
$(LOCAL_PATH)/platform \
$(LOCAL_PATH)/base \
$(LOCAL_PATH)/network \
$(LOCAL_PATH)/../external/android/$(TARGET_ARCH_ABI)/include \
$(LOCAL_PATH)/../external/sources \
$(LOCAL_PATH)/renderer
LOCAL_STATIC_LIBRARIES += cocos_png_static
LOCAL_STATIC_LIBRARIES += cocos_jpeg_static
ifeq ($(USE_TIFF),1)
LOCAL_STATIC_LIBRARIES += cocos_tiff_static
endif
LOCAL_STATIC_LIBRARIES += cocos_webp_static
LOCAL_STATIC_LIBRARIES += cocos_zlib_static
LOCAL_STATIC_LIBRARIES += v8_static
LOCAL_STATIC_LIBRARIES += cocos_freetype_static
LOCAL_WHOLE_STATIC_LIBRARIES := cocos2dxandroid_static
LOCAL_WHOLE_STATIC_LIBRARIES += cpufeatures
# define the macro to compile through support/zip_support/ioapi.c
LOCAL_CFLAGS := -DUSE_FILE32API -fexceptions
# Issues #9968
#ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
# LOCAL_CFLAGS += -DHAVE_NEON=1
#endif
LOCAL_CPPFLAGS := -Wno-deprecated-declarations
LOCAL_EXPORT_CFLAGS := -DUSE_FILE32API
LOCAL_EXPORT_CPPFLAGS := -Wno-deprecated-declarations
include $(BUILD_STATIC_LIBRARY)
#==============================================================
#$(call import-module,.)
$(call import-module,android)
$(call import-module,editor-support)
$(call import-module,platform/android)
$(call import-module,audio/android)
$(call import-module,extensions)
$(call import-module,android/cpufeatures)

View File

@ -0,0 +1,655 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "platform/CCPlatformConfig.h"
#include "audio/include/AudioEngine.h"
#include "platform/CCFileUtils.h"
#include "base/ccUtils.h"
#include <condition_variable>
#include <queue>
#include <thread>
#include <mutex>
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
#include "audio/android/AudioEngine-inl.h"
#elif CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC
#include "audio/apple/AudioEngine-inl.h"
#elif CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
#include "audio/win32/AudioEngine-win32.h"
#elif CC_TARGET_PLATFORM == CC_PLATFORM_WINRT
#include "audio/winrt/AudioEngine-winrt.h"
#elif CC_TARGET_PLATFORM == CC_PLATFORM_LINUX
#include "audio/linux/AudioEngine-linux.h"
#elif CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN
#include "audio/tizen/AudioEngine-tizen.h"
#endif
#define TIME_DELAY_PRECISION 0.0001
#ifdef ERROR
#undef ERROR
#endif // ERROR
using namespace cocos2d;
const int AudioEngine::INVALID_AUDIO_ID = -1;
const float AudioEngine::TIME_UNKNOWN = -1.0f;
//audio file path,audio IDs
std::unordered_map<std::string,std::list<int>> AudioEngine::_audioPathIDMap;
//profileName,ProfileHelper
std::unordered_map<std::string, AudioEngine::ProfileHelper> AudioEngine::_audioPathProfileHelperMap;
unsigned int AudioEngine::_maxInstances = MAX_AUDIOINSTANCES;
AudioEngine::ProfileHelper* AudioEngine::_defaultProfileHelper = nullptr;
std::unordered_map<int, AudioEngine::AudioInfo> AudioEngine::_audioIDInfoMap;
AudioEngineImpl* AudioEngine::_audioEngineImpl = nullptr;
uint32_t AudioEngine::_onPauseListenerID = 0;
uint32_t AudioEngine::_onResumeListenerID = 0;
std::vector<int> AudioEngine::_breakAudioID;
AudioEngine::AudioEngineThreadPool* AudioEngine::s_threadPool = nullptr;
bool AudioEngine::_isEnabled = true;
AudioEngine::AudioInfo::AudioInfo()
: filePath(nullptr)
, profileHelper(nullptr)
, volume(1.0f)
, loop(false)
, duration(TIME_UNKNOWN)
, state(AudioState::INITIALIZING)
{
}
AudioEngine::AudioInfo::~AudioInfo()
{
}
class AudioEngine::AudioEngineThreadPool
{
public:
AudioEngineThreadPool(int threads = 4)
: _stop(false)
{
for (int index = 0; index < threads; ++index)
{
_workers.emplace_back(std::thread(std::bind(&AudioEngineThreadPool::threadFunc, this)));
}
}
void addTask(const std::function<void()> &task){
std::unique_lock<std::mutex> lk(_queueMutex);
_taskQueue.emplace(task);
_taskCondition.notify_one();
}
~AudioEngineThreadPool()
{
{
std::unique_lock<std::mutex> lk(_queueMutex);
_stop = true;
_taskCondition.notify_all();
}
for (auto&& worker : _workers) {
worker.join();
}
}
private:
void threadFunc()
{
while (true) {
std::function<void()> task = nullptr;
{
std::unique_lock<std::mutex> lk(_queueMutex);
if (_stop)
{
break;
}
if (!_taskQueue.empty())
{
task = std::move(_taskQueue.front());
_taskQueue.pop();
}
else
{
_taskCondition.wait(lk);
continue;
}
}
task();
}
}
std::vector<std::thread> _workers;
std::queue< std::function<void()> > _taskQueue;
std::mutex _queueMutex;
std::condition_variable _taskCondition;
bool _stop;
};
void AudioEngine::end()
{
stopAll();
if (s_threadPool)
{
delete s_threadPool;
s_threadPool = nullptr;
}
delete _audioEngineImpl;
_audioEngineImpl = nullptr;
delete _defaultProfileHelper;
_defaultProfileHelper = nullptr;
if (_onPauseListenerID != 0)
{
EventDispatcher::removeCustomEventListener(EVENT_ON_PAUSE, _onPauseListenerID);
_onPauseListenerID = 0;
}
if (_onResumeListenerID != 0)
{
EventDispatcher::removeCustomEventListener(EVENT_ON_RESUME, _onResumeListenerID);
_onResumeListenerID = 0;
}
}
bool AudioEngine::lazyInit()
{
if (_audioEngineImpl == nullptr)
{
_audioEngineImpl = new (std::nothrow) AudioEngineImpl();
if(!_audioEngineImpl || !_audioEngineImpl->init() ){
delete _audioEngineImpl;
_audioEngineImpl = nullptr;
return false;
}
_onPauseListenerID = EventDispatcher::addCustomEventListener(EVENT_ON_PAUSE, AudioEngine::onPause);
_onResumeListenerID = EventDispatcher::addCustomEventListener(EVENT_ON_RESUME, AudioEngine::onResume);
}
#if (CC_TARGET_PLATFORM != CC_PLATFORM_ANDROID)
if (_audioEngineImpl && s_threadPool == nullptr)
{
s_threadPool = new (std::nothrow) AudioEngineThreadPool();
}
#endif
return true;
}
int AudioEngine::play2d(const std::string& filePath, bool loop, float volume, const AudioProfile *profile)
{
int ret = AudioEngine::INVALID_AUDIO_ID;
do {
if (!isEnabled())
{
break;
}
if ( !lazyInit() ){
break;
}
if ( !FileUtils::getInstance()->isFileExist(filePath)){
break;
}
auto profileHelper = _defaultProfileHelper;
if (profile && profile != &profileHelper->profile){
CC_ASSERT(!profile->name.empty());
profileHelper = &_audioPathProfileHelperMap[profile->name];
profileHelper->profile = *profile;
}
if (_audioIDInfoMap.size() >= _maxInstances) {
log("Fail to play %s cause by limited max instance of AudioEngine",filePath.c_str());
break;
}
if (profileHelper)
{
if(profileHelper->profile.maxInstances != 0 && profileHelper->audioIDs.size() >= profileHelper->profile.maxInstances){
log("Fail to play %s cause by limited max instance of AudioProfile",filePath.c_str());
break;
}
if (profileHelper->profile.minDelay > TIME_DELAY_PRECISION) {
auto currTime = utils::gettime();
if (profileHelper->lastPlayTime > TIME_DELAY_PRECISION && currTime - profileHelper->lastPlayTime <= profileHelper->profile.minDelay) {
log("Fail to play %s cause by limited minimum delay",filePath.c_str());
break;
}
}
}
if (volume < 0.0f) {
volume = 0.0f;
}
else if (volume > 1.0f){
volume = 1.0f;
}
ret = _audioEngineImpl->play2d(filePath, loop, volume);
if (ret != INVALID_AUDIO_ID)
{
_audioPathIDMap[filePath].push_back(ret);
auto it = _audioPathIDMap.find(filePath);
auto& audioRef = _audioIDInfoMap[ret];
audioRef.volume = volume;
audioRef.loop = loop;
audioRef.filePath = &it->first;
if (profileHelper) {
profileHelper->lastPlayTime = utils::gettime();
profileHelper->audioIDs.push_back(ret);
}
audioRef.profileHelper = profileHelper;
}
} while (0);
return ret;
}
void AudioEngine::setLoop(int audioID, bool loop)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end() && it->second.loop != loop){
_audioEngineImpl->setLoop(audioID, loop);
it->second.loop = loop;
}
}
void AudioEngine::setVolume(int audioID, float volume)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end()){
if (volume < 0.0f) {
volume = 0.0f;
}
else if (volume > 1.0f){
volume = 1.0f;
}
if (it->second.volume != volume){
_audioEngineImpl->setVolume(audioID, volume);
it->second.volume = volume;
}
}
}
void AudioEngine::pause(int audioID)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end() && it->second.state == AudioState::PLAYING){
_audioEngineImpl->pause(audioID);
it->second.state = AudioState::PAUSED;
}
}
void AudioEngine::pauseAll()
{
auto itEnd = _audioIDInfoMap.end();
for (auto it = _audioIDInfoMap.begin(); it != itEnd; ++it)
{
if (it->second.state == AudioState::PLAYING)
{
_audioEngineImpl->pause(it->first);
it->second.state = AudioState::PAUSED;
}
}
}
void AudioEngine::resume(int audioID)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end() && it->second.state == AudioState::PAUSED){
_audioEngineImpl->resume(audioID);
it->second.state = AudioState::PLAYING;
}
}
void AudioEngine::resumeAll()
{
auto itEnd = _audioIDInfoMap.end();
for (auto it = _audioIDInfoMap.begin(); it != itEnd; ++it)
{
if (it->second.state == AudioState::PAUSED)
{
_audioEngineImpl->resume(it->first);
it->second.state = AudioState::PLAYING;
}
}
}
void AudioEngine::onPause(const CustomEvent &event) {
auto itEnd = _audioIDInfoMap.end();
for (auto it = _audioIDInfoMap.begin(); it != itEnd; ++it)
{
if (it->second.state == AudioState::PLAYING)
{
_audioEngineImpl->pause(it->first);
_breakAudioID.push_back(it->first);
}
}
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
if (_audioEngineImpl) {
_audioEngineImpl->onPause();
}
#endif
}
void AudioEngine::onResume(const CustomEvent &event) {
auto itEnd = _breakAudioID.end();
for (auto it = _breakAudioID.begin(); it != itEnd; ++it) {
_audioEngineImpl->resume(*it);
}
_breakAudioID.clear();
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
if (_audioEngineImpl) {
_audioEngineImpl->onResume();
}
#endif
}
void AudioEngine::stop(int audioID)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end()){
_audioEngineImpl->stop(audioID);
remove(audioID);
}
}
void AudioEngine::remove(int audioID)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end()){
if (it->second.profileHelper) {
it->second.profileHelper->audioIDs.remove(audioID);
}
_audioPathIDMap[*it->second.filePath].remove(audioID);
_audioIDInfoMap.erase(audioID);
}
}
void AudioEngine::stopAll()
{
if(!_audioEngineImpl){
return;
}
_audioEngineImpl->stopAll();
auto itEnd = _audioIDInfoMap.end();
for (auto it = _audioIDInfoMap.begin(); it != itEnd; ++it)
{
if (it->second.profileHelper){
it->second.profileHelper->audioIDs.remove(it->first);
}
}
_audioPathIDMap.clear();
_audioIDInfoMap.clear();
}
void AudioEngine::uncache(const std::string &filePath)
{
auto audioIDsIter = _audioPathIDMap.find(filePath);
if (audioIDsIter != _audioPathIDMap.end())
{
//@Note: For safely iterating elements from the audioID list, we need to copy the list
// since 'AudioEngine::remove' may be invoked in '_audioEngineImpl->stop' synchronously.
// If this happens, it will break the iteration, and crash will appear on some devices.
std::list<int> copiedIDs(audioIDsIter->second);
for (int audioID : copiedIDs)
{
_audioEngineImpl->stop(audioID);
auto itInfo = _audioIDInfoMap.find(audioID);
if (itInfo != _audioIDInfoMap.end())
{
if (itInfo->second.profileHelper)
{
itInfo->second.profileHelper->audioIDs.remove(audioID);
}
_audioIDInfoMap.erase(audioID);
}
}
_audioPathIDMap.erase(filePath);
}
if (_audioEngineImpl)
{
_audioEngineImpl->uncache(filePath);
}
}
void AudioEngine::uncacheAll()
{
if(!_audioEngineImpl){
return;
}
stopAll();
_audioEngineImpl->uncacheAll();
}
float AudioEngine::getDuration(int audioID)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end() && it->second.state != AudioState::INITIALIZING)
{
if (it->second.duration == TIME_UNKNOWN)
{
it->second.duration = _audioEngineImpl->getDuration(audioID);
}
return it->second.duration;
}
return TIME_UNKNOWN;
}
float AudioEngine::getDurationFromFile(const std::string& filePath)
{
lazyInit();
if (_audioEngineImpl)
{
return _audioEngineImpl->getDurationFromFile(filePath);
}
return TIME_UNKNOWN;
}
bool AudioEngine::setCurrentTime(int audioID, float time)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end() && it->second.state != AudioState::INITIALIZING) {
return _audioEngineImpl->setCurrentTime(audioID, time);
}
return false;
}
float AudioEngine::getCurrentTime(int audioID)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end() && it->second.state != AudioState::INITIALIZING) {
return _audioEngineImpl->getCurrentTime(audioID);
}
return 0.0f;
}
void AudioEngine::setFinishCallback(int audioID, const std::function<void (int, const std::string &)> &callback)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end()){
_audioEngineImpl->setFinishCallback(audioID, callback);
}
}
bool AudioEngine::setMaxAudioInstance(int maxInstances)
{
if (maxInstances > 0 && maxInstances <= MAX_AUDIOINSTANCES) {
_maxInstances = maxInstances;
return true;
}
return false;
}
bool AudioEngine::isLoop(int audioID)
{
auto tmpIterator = _audioIDInfoMap.find(audioID);
if (tmpIterator != _audioIDInfoMap.end())
{
return tmpIterator->second.loop;
}
log("AudioEngine::isLoop-->The audio instance %d is non-existent", audioID);
return false;
}
float AudioEngine::getVolume(int audioID)
{
auto tmpIterator = _audioIDInfoMap.find(audioID);
if (tmpIterator != _audioIDInfoMap.end())
{
return tmpIterator->second.volume;
}
log("AudioEngine::getVolume-->The audio instance %d is non-existent", audioID);
return 0.0f;
}
AudioEngine::AudioState AudioEngine::getState(int audioID)
{
auto tmpIterator = _audioIDInfoMap.find(audioID);
if (tmpIterator != _audioIDInfoMap.end())
{
return tmpIterator->second.state;
}
return AudioState::ERROR;
}
AudioProfile* AudioEngine::getProfile(int audioID)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end())
{
return &it->second.profileHelper->profile;
}
return nullptr;
}
AudioProfile* AudioEngine::getDefaultProfile()
{
if (_defaultProfileHelper == nullptr)
{
_defaultProfileHelper = new (std::nothrow) ProfileHelper();
}
return &_defaultProfileHelper->profile;
}
AudioProfile* AudioEngine::getProfile(const std::string &name)
{
auto it = _audioPathProfileHelperMap.find(name);
if (it != _audioPathProfileHelperMap.end()) {
return &it->second.profile;
} else {
return nullptr;
}
}
void AudioEngine::preload(const std::string& filePath, std::function<void(bool isSuccess)> callback)
{
if (!isEnabled())
{
callback(false);
return;
}
lazyInit();
if (_audioEngineImpl)
{
if (!FileUtils::getInstance()->isFileExist(filePath)){
if (callback)
{
callback(false);
}
return;
}
_audioEngineImpl->preload(filePath, callback);
}
}
void AudioEngine::addTask(const std::function<void()>& task)
{
lazyInit();
if (_audioEngineImpl && s_threadPool)
{
s_threadPool->addTask(task);
}
}
int AudioEngine::getPlayingAudioCount()
{
return static_cast<int>(_audioIDInfoMap.size());
}
void AudioEngine::setEnabled(bool isEnabled)
{
if (_isEnabled != isEnabled)
{
_isEnabled = isEnabled;
if (!_isEnabled)
{
stopAll();
}
}
}
bool AudioEngine::isEnabled()
{
return _isEnabled;
}

View File

@ -0,0 +1,53 @@
LOCAL_PATH := $(call my-dir)
#New AudioEngine
include $(CLEAR_VARS)
LOCAL_MODULE := audioengine_static
LOCAL_MODULE_FILENAME := libaudioengine
LOCAL_SRC_FILES := AudioEngine-inl.cpp \
../AudioEngine.cpp \
AssetFd.cpp \
AudioDecoder.cpp \
AudioDecoderProvider.cpp \
AudioDecoderSLES.cpp \
AudioDecoderOgg.cpp \
AudioDecoderMp3.cpp \
AudioDecoderWav.cpp \
AudioPlayerProvider.cpp \
AudioResampler.cpp \
AudioResamplerCubic.cpp \
PcmBufferProvider.cpp \
PcmAudioPlayer.cpp \
UrlAudioPlayer.cpp \
PcmData.cpp \
AudioMixerController.cpp \
AudioMixer.cpp \
PcmAudioService.cpp \
Track.cpp \
audio_utils/format.c \
audio_utils/minifloat.cpp \
audio_utils/primitives.c \
utils/Utils.cpp \
mp3reader.cpp \
tinysndfile.cpp
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../include
LOCAL_EXPORT_LDLIBS := -lOpenSLES
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../include \
$(LOCAL_PATH)/../.. \
$(LOCAL_PATH)/../../platform/android \
$(LOCAL_PATH)/../../../external \
$(LOCAL_PATH)/../../../external/sources
LOCAL_STATIC_LIBRARIES += libvorbisidec libpvmp3dec
include $(BUILD_STATIC_LIBRARY)
$(call import-module,sources/tremolo)
$(call import-module,sources/pvmp3dec)

View File

@ -0,0 +1,48 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AssetFd"
#include "audio/android/cutils/log.h"
#include "audio/android/AssetFd.h"
namespace cocos2d {
AssetFd::AssetFd(int assetFd)
: _assetFd(assetFd)
{
}
AssetFd::~AssetFd()
{
ALOGV("~AssetFd: %d", _assetFd);
if (_assetFd > 0)
{
::close(_assetFd);
_assetFd = 0;
}
};
} // namespace cocos2d {

View File

@ -0,0 +1,43 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include <unistd.h>
namespace cocos2d {
class AssetFd
{
public:
AssetFd(int assetFd);
~AssetFd();
inline int getFd() const { return _assetFd; };
private:
int _assetFd;
};
} // namespace cocos2d {

View File

@ -0,0 +1,80 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "audio/android/utils/Errors.h"
namespace cocos2d {
// ----------------------------------------------------------------------------
class AudioBufferProvider
{
public:
// IDEA: merge with AudioTrackShared::Buffer, AudioTrack::Buffer, and AudioRecord::Buffer
// and rename getNextBuffer() to obtainBuffer()
struct Buffer {
Buffer() : raw(NULL), frameCount(0) { }
union {
void* raw;
short* i16;
int8_t* i8;
};
size_t frameCount;
};
virtual ~AudioBufferProvider() {}
// value representing an invalid presentation timestamp
static const int64_t kInvalidPTS = 0x7FFFFFFFFFFFFFFFLL; // <stdint.h> is too painful
// pts is the local time when the next sample yielded by getNextBuffer
// will be rendered.
// Pass kInvalidPTS if the PTS is unknown or not applicable.
// On entry:
// buffer != NULL
// buffer->raw unused
// buffer->frameCount maximum number of desired frames
// On successful return:
// status NO_ERROR
// buffer->raw non-NULL pointer to buffer->frameCount contiguous available frames
// buffer->frameCount number of contiguous available frames at buffer->raw,
// 0 < buffer->frameCount <= entry value
// On error return:
// status != NO_ERROR
// buffer->raw NULL
// buffer->frameCount 0
virtual status_t getNextBuffer(Buffer* buffer, int64_t pts = kInvalidPTS) = 0;
// Release (a portion of) the buffer previously obtained by getNextBuffer().
// It is permissible to call releaseBuffer() multiple times per getNextBuffer().
// On entry:
// buffer->frameCount number of frames to release, must be <= number of frames
// obtained but not yet released
// buffer->raw unused
// On return:
// buffer->frameCount 0; implementation MUST set to zero
// buffer->raw undefined; implementation is PERMITTED to set to any value,
// so if caller needs to continue using this buffer it must
// keep track of the pointer itself
virtual void releaseBuffer(Buffer* buffer) = 0;
};
// ----------------------------------------------------------------------------
} // namespace cocos2d {

View File

@ -0,0 +1,293 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioDecoder"
#include "audio/android/AudioDecoder.h"
#include "audio/android/AudioResampler.h"
#include "audio/android/PcmBufferProvider.h"
#include "audio/android/AudioResampler.h"
#include <thread>
#include <chrono>
namespace cocos2d {
size_t AudioDecoder::fileRead(void* ptr, size_t size, size_t nmemb, void* datasource)
{
AudioDecoder* thiz = (AudioDecoder*)datasource;
ssize_t toReadBytes = std::min((ssize_t)(thiz->_fileData.getSize() - thiz->_fileCurrPos), (ssize_t)(nmemb * size));
if (toReadBytes > 0)
{
memcpy(ptr, (unsigned char*) thiz->_fileData.getBytes() + thiz->_fileCurrPos, toReadBytes);
thiz->_fileCurrPos += toReadBytes;
}
// ALOGD("File size: %d, After fileRead _fileCurrPos %d", (int)thiz->_fileData.getSize(), thiz->_fileCurrPos);
return toReadBytes;
}
int AudioDecoder::fileSeek(void* datasource, int64_t offset, int whence)
{
AudioDecoder* thiz = (AudioDecoder*)datasource;
if (whence == SEEK_SET)
thiz->_fileCurrPos = offset;
else if (whence == SEEK_CUR)
thiz->_fileCurrPos = thiz->_fileCurrPos + offset;
else if (whence == SEEK_END)
thiz->_fileCurrPos = thiz->_fileData.getSize();
return 0;
}
int AudioDecoder::fileClose(void* datasource)
{
return 0;
}
long AudioDecoder::fileTell(void* datasource)
{
AudioDecoder* thiz = (AudioDecoder*)datasource;
return (long) thiz->_fileCurrPos;
}
AudioDecoder::AudioDecoder()
: _fileCurrPos(0), _sampleRate(-1)
{
auto pcmBuffer = std::make_shared<std::vector<char>>();
pcmBuffer->reserve(4096);
_result.pcmBuffer = pcmBuffer;
}
AudioDecoder::~AudioDecoder()
{
ALOGV("~AudioDecoder() %p", this);
}
bool AudioDecoder::init(const std::string &url, int sampleRate)
{
_url = url;
_sampleRate = sampleRate;
return true;
}
bool AudioDecoder::start()
{
auto oldTime = clockNow();
auto nowTime = oldTime;
bool ret;
do
{
ret = decodeToPcm();
if (!ret)
{
ALOGE("decodeToPcm (%s) failed!", _url.c_str());
break;
}
nowTime = clockNow();
ALOGD("Decoding (%s) to pcm data wasted %fms", _url.c_str(), intervalInMS(oldTime, nowTime));
oldTime = nowTime;
ret = resample();
if (!ret)
{
ALOGE("resample (%s) failed!", _url.c_str());
break;
}
nowTime = clockNow();
ALOGD("Resampling (%s) wasted %fms", _url.c_str(), intervalInMS(oldTime, nowTime));
oldTime = nowTime;
ret = interleave();
if (!ret)
{
ALOGE("interleave (%s) failed!", _url.c_str());
break;
}
nowTime = clockNow();
ALOGD("Interleave (%s) wasted %fms", _url.c_str(), intervalInMS(oldTime, nowTime));
} while(false);
ALOGV_IF(!ret, "%s returns false, decode (%s)", __FUNCTION__, _url.c_str());
return ret;
}
bool AudioDecoder::resample()
{
if (_result.sampleRate == _sampleRate)
{
ALOGI("No need to resample since the sample rate (%d) of the decoded pcm data is the same as the device output sample rate",
_sampleRate);
return true;
}
ALOGV("Resample: %d --> %d", _result.sampleRate, _sampleRate);
auto r = _result;
PcmBufferProvider provider;
provider.init(r.pcmBuffer->data(), r.numFrames, r.pcmBuffer->size() / r.numFrames);
const int outFrameRate = _sampleRate;
int outputChannels = 2;
size_t outputFrameSize = outputChannels * sizeof(int32_t);
size_t outputFrames = ((int64_t) r.numFrames * outFrameRate) / r.sampleRate;
size_t outputSize = outputFrames * outputFrameSize;
void *outputVAddr = malloc(outputSize);
auto resampler = AudioResampler::create(AUDIO_FORMAT_PCM_16_BIT, r.numChannels, outFrameRate,
AudioResampler::MED_QUALITY);
resampler->setSampleRate(r.sampleRate);
resampler->setVolume(AudioResampler::UNITY_GAIN_FLOAT, AudioResampler::UNITY_GAIN_FLOAT);
memset(outputVAddr, 0, outputSize);
ALOGV("resample() %zu output frames", outputFrames);
std::vector<int> Ovalues;
if (Ovalues.empty())
{
Ovalues.push_back(outputFrames);
}
for (size_t i = 0, j = 0; i < outputFrames;)
{
size_t thisFrames = Ovalues[j++];
if (j >= Ovalues.size())
{
j = 0;
}
if (thisFrames == 0 || thisFrames > outputFrames - i)
{
thisFrames = outputFrames - i;
}
int outFrames = resampler->resample((int *) outputVAddr + outputChannels * i, thisFrames,
&provider);
ALOGV("outFrames: %d", outFrames);
i += thisFrames;
}
ALOGV("resample() complete");
resampler->reset();
ALOGV("reset() complete");
delete resampler;
resampler = nullptr;
// mono takes left channel only (out of stereo output pair)
// stereo and multichannel preserve all channels.
int channels = r.numChannels;
int32_t *out = (int32_t *) outputVAddr;
int16_t *convert = (int16_t *) malloc(outputFrames * channels * sizeof(int16_t));
const int volumeShift = 12; // shift requirement for Q4.27 to Q.15
// round to half towards zero and saturate at int16 (non-dithered)
const int roundVal = (1 << (volumeShift - 1)) - 1; // volumePrecision > 0
for (size_t i = 0; i < outputFrames; i++)
{
for (int j = 0; j < channels; j++)
{
int32_t s = out[i * outputChannels + j] + roundVal; // add offset here
if (s < 0)
{
s = (s + 1) >> volumeShift; // round to 0
if (s < -32768)
{
s = -32768;
}
} else
{
s = s >> volumeShift;
if (s > 32767)
{
s = 32767;
}
}
convert[i * channels + j] = int16_t(s);
}
}
// Reset result
_result.numFrames = outputFrames;
_result.sampleRate = outFrameRate;
auto buffer = std::make_shared<std::vector<char>>();
buffer->reserve(_result.numFrames * _result.bitsPerSample / 8);
buffer->insert(buffer->end(), (char *) convert,
(char *) convert + outputFrames * channels * sizeof(int16_t));
_result.pcmBuffer = buffer;
ALOGV("pcm buffer size: %d", (int)_result.pcmBuffer->size());
free(convert);
free(outputVAddr);
return true;
}
//-----------------------------------------------------------------
bool AudioDecoder::interleave()
{
if (_result.numChannels == 2)
{
ALOGI("Audio channel count is 2, no need to interleave");
return true;
}
else if (_result.numChannels == 1)
{
// If it's a mono audio, try to compose a fake stereo buffer
size_t newBufferSize = _result.pcmBuffer->size() * 2;
auto newBuffer = std::make_shared<std::vector<char>>();
newBuffer->reserve(newBufferSize);
size_t totalFrameSizeInBytes = (size_t) (_result.numFrames * _result.bitsPerSample / 8);
for (size_t i = 0; i < totalFrameSizeInBytes; i += 2)
{
// get one short value
char byte1 = _result.pcmBuffer->at(i);
char byte2 = _result.pcmBuffer->at(i + 1);
// push two short value
for (int j = 0; j < 2; ++j)
{
newBuffer->push_back(byte1);
newBuffer->push_back(byte2);
}
}
_result.numChannels = 2;
_result.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
_result.pcmBuffer = newBuffer;
return true;
}
ALOGE("Audio channel count (%d) is wrong, interleave only supports converting mono to stereo!", _result.numChannels);
return false;
}
} // namespace cocos2d {

View File

@ -0,0 +1,64 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/OpenSLHelper.h"
#include "audio/android/PcmData.h"
#include "base/CCData.h"
namespace cocos2d {
class AudioDecoder
{
public:
AudioDecoder();
virtual ~AudioDecoder();
virtual bool init(const std::string &url, int sampleRate);
bool start();
inline PcmData getResult()
{ return _result; };
protected:
virtual bool decodeToPcm() = 0;
bool resample();
bool interleave();
static size_t fileRead(void* ptr, size_t size, size_t nmemb, void* datasource);
static int fileSeek(void* datasource, int64_t offset, int whence);
static int fileClose(void* datasource);
static long fileTell(void* datasource);
std::string _url;
PcmData _result;
int _sampleRate;
Data _fileData;
size_t _fileCurrPos;
};
} // namespace cocos2d {

View File

@ -0,0 +1,83 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioDecoderMp3"
#include "audio/android/AudioDecoderMp3.h"
#include "audio/android/mp3reader.h"
#include "platform/CCFileUtils.h"
namespace cocos2d {
AudioDecoderMp3::AudioDecoderMp3()
{
ALOGV("Create AudioDecoderMp3");
}
AudioDecoderMp3::~AudioDecoderMp3()
{
}
bool AudioDecoderMp3::decodeToPcm()
{
_fileData = FileUtils::getInstance()->getDataFromFile(_url);
if (_fileData.isNull())
{
return false;
}
mp3_callbacks callbacks;
callbacks.read = AudioDecoder::fileRead;
callbacks.seek = AudioDecoder::fileSeek;
callbacks.close = AudioDecoder::fileClose;
callbacks.tell = AudioDecoder::fileTell;
int numChannels = 0;
int sampleRate = 0;
int numFrames = 0;
if (EXIT_SUCCESS == decodeMP3(&callbacks, this, *_result.pcmBuffer, &numChannels, &sampleRate, &numFrames)
&& numChannels > 0 && sampleRate > 0 && numFrames > 0)
{
_result.numChannels = numChannels;
_result.sampleRate = sampleRate;
_result.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
_result.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
_result.channelMask = numChannels == 1 ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT);
_result.endianness = SL_BYTEORDER_LITTLEENDIAN;
_result.numFrames = numFrames;
_result.duration = 1.0f * numFrames / sampleRate;
std::string info = _result.toString();
ALOGI("Original audio info: %s, total size: %d", info.c_str(), (int)_result.pcmBuffer->size());
return true;
}
ALOGE("Decode MP3 (%s) failed, channels: %d, rate: %d, frames: %d", _url.c_str(), numChannels, sampleRate, numFrames);
return false;
}
} // namespace cocos2d {

View File

@ -0,0 +1,43 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/AudioDecoder.h"
namespace cocos2d {
class AudioDecoderMp3 : public AudioDecoder
{
protected:
AudioDecoderMp3();
virtual ~AudioDecoderMp3();
virtual bool decodeToPcm() override;
friend class AudioDecoderProvider;
};
} // namespace cocos2d {

View File

@ -0,0 +1,113 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioDecoderOgg"
#include "audio/android/AudioDecoderOgg.h"
#include "platform/CCFileUtils.h"
namespace cocos2d {
AudioDecoderOgg::AudioDecoderOgg()
{
ALOGV("Create AudioDecoderOgg");
}
AudioDecoderOgg::~AudioDecoderOgg()
{
}
int AudioDecoderOgg::fseek64Wrap(void* datasource, ogg_int64_t off, int whence)
{
return AudioDecoder::fileSeek(datasource, (long)off, whence);
}
bool AudioDecoderOgg::decodeToPcm()
{
_fileData = FileUtils::getInstance()->getDataFromFile(_url);
if (_fileData.isNull())
{
return false;
}
ov_callbacks callbacks;
callbacks.read_func = AudioDecoder::fileRead;
callbacks.seek_func = AudioDecoderOgg::fseek64Wrap;
callbacks.close_func = AudioDecoder::fileClose;
callbacks.tell_func = AudioDecoder::fileTell;
_fileCurrPos = 0;
OggVorbis_File vf;
int ret = ov_open_callbacks(this, &vf, NULL, 0, callbacks);
if (ret != 0)
{
ALOGE("Open file error, file: %s, ov_open_callbacks return %d", _url.c_str(), ret);
return false;
}
// header
auto vi = ov_info(&vf, -1);
uint32_t pcmSamples = (uint32_t) ov_pcm_total(&vf, -1);
uint32_t bufferSize = pcmSamples * vi->channels * sizeof(short);
char* pcmBuffer = (char*)malloc(bufferSize);
memset(pcmBuffer, 0, bufferSize);
int currentSection = 0;
long curPos = 0;
long readBytes = 0;
do
{
readBytes = ov_read(&vf, pcmBuffer + curPos, 4096, &currentSection);
curPos += readBytes;
} while (readBytes > 0);
if (curPos > 0)
{
_result.pcmBuffer->insert(_result.pcmBuffer->end(), pcmBuffer, pcmBuffer + bufferSize);
_result.numChannels = vi->channels;
_result.sampleRate = vi->rate;
_result.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
_result.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
_result.channelMask = vi->channels == 1 ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT);
_result.endianness = SL_BYTEORDER_LITTLEENDIAN;
_result.numFrames = pcmSamples;
_result.duration = 1.0f * pcmSamples / vi->rate;
}
else
{
ALOGE("ov_read returns 0 byte!");
}
ov_clear(&vf);
free(pcmBuffer);
return (curPos > 0);
}
} // namespace cocos2d {

View File

@ -0,0 +1,46 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/AudioDecoder.h"
#include "tremolo/Tremolo/ivorbisfile.h"
namespace cocos2d {
class AudioDecoderOgg : public AudioDecoder
{
protected:
AudioDecoderOgg();
virtual ~AudioDecoderOgg();
static int fseek64Wrap(void* datasource, ogg_int64_t off, int whence);
virtual bool decodeToPcm() override;
friend class AudioDecoderProvider;
};
} // namespace cocos2d {

View File

@ -0,0 +1,94 @@
/****************************************************************************
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioDecoderProvider"
#include "audio/android/AudioDecoderProvider.h"
#include "audio/android/AudioDecoderSLES.h"
#include "audio/android/AudioDecoderOgg.h"
#include "audio/android/AudioDecoderMp3.h"
#include "audio/android/AudioDecoderWav.h"
#include "platform/CCFileUtils.h"
namespace cocos2d {
AudioDecoder* AudioDecoderProvider::createAudioDecoder(SLEngineItf engineItf, const std::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback)
{
AudioDecoder* decoder = nullptr;
std::string extension = FileUtils::getInstance()->getFileExtension(url);
ALOGV("url:%s, extension:%s", url.c_str(), extension.c_str());
if (extension == ".ogg")
{
decoder = new AudioDecoderOgg();
if (!decoder->init(url, sampleRate))
{
delete decoder;
decoder = nullptr;
}
}
else if (extension == ".mp3")
{
decoder = new AudioDecoderMp3();
if (!decoder->init(url, sampleRate))
{
delete decoder;
decoder = nullptr;
}
}
else if (extension == ".wav")
{
decoder = new AudioDecoderWav();
if (!decoder->init(url, sampleRate))
{
delete decoder;
decoder = nullptr;
}
}
else
{
auto slesDecoder = new AudioDecoderSLES();
if (slesDecoder->init(engineItf, url, bufferSizeInFrames, sampleRate, fdGetterCallback))
{
decoder = slesDecoder;
}
else
{
delete slesDecoder;
}
}
return decoder;
}
void AudioDecoderProvider::destroyAudioDecoder(AudioDecoder** decoder)
{
if (decoder != nullptr && *decoder != nullptr)
{
delete (*decoder);
(*decoder) = nullptr;
}
}
} // namespace cocos2d {

View File

@ -0,0 +1,41 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/OpenSLHelper.h"
namespace cocos2d {
class AudioDecoder;
class AudioDecoderProvider
{
public:
static AudioDecoder* createAudioDecoder(SLEngineItf engineItf, const std::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback);
static void destroyAudioDecoder(AudioDecoder** decoder);
};
} // namespace cocos2d {

View File

@ -0,0 +1,649 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioDecoderSLES"
#include "audio/android/AudioDecoderSLES.h"
#include "platform/CCFileUtils.h"
#include <thread>
#include <mutex>
namespace cocos2d {
/* Explicitly requesting SL_IID_ANDROIDSIMPLEBUFFERQUEUE and SL_IID_PREFETCHSTATUS
* on the UrlAudioPlayer object for decoding, SL_IID_METADATAEXTRACTION for retrieving the
* format of the decoded audio */
#define NUM_EXPLICIT_INTERFACES_FOR_PLAYER 3
/* Size of the decode buffer queue */
#define NB_BUFFERS_IN_QUEUE 4
/* size of the struct to retrieve the PCM format metadata values: the values we're interested in
* are SLuint32, but it is saved in the data field of a SLMetadataInfo, hence the larger size.
* Nate that this size is queried and displayed at l.452 for demonstration/test purposes.
* */
#define PCM_METADATA_VALUE_SIZE 32
/* used to detect errors likely to have occurred when the OpenSL ES framework fails to open
* a resource, for instance because a file URI is invalid, or an HTTP server doesn't respond.
*/
#define PREFETCHEVENT_ERROR_CANDIDATE (SL_PREFETCHEVENT_STATUSCHANGE | SL_PREFETCHEVENT_FILLLEVELCHANGE)
//-----------------------------------------------------------------
static std::mutex __SLPlayerMutex;
static int toBufferSizeInBytes(int bufferSizeInFrames, int sampleSize, int channelCount)
{
return bufferSizeInFrames * sampleSize * channelCount;
}
static int BUFFER_SIZE_IN_BYTES = 0;
static void checkMetaData(int index, const char *key)
{
if (index != -1)
{
ALOGV("Key %s is at index %d", key, index);
}
else
{
ALOGE("Unable to find key %s", key);
}
}
class SLAudioDecoderCallbackProxy
{
public:
//-----------------------------------------------------------------
/* Callback for "prefetch" events, here used to detect audio resource opening errors */
static void prefetchEventCallback(SLPrefetchStatusItf caller, void *context, SLuint32 event)
{
AudioDecoderSLES *thiz = reinterpret_cast<AudioDecoderSLES *>(context);
thiz->prefetchCallback(caller, event);
}
static void decPlayCallback(SLAndroidSimpleBufferQueueItf queueItf, void *context)
{
AudioDecoderSLES *thiz = reinterpret_cast<AudioDecoderSLES *>(context);
thiz->decodeToPcmCallback(queueItf);
}
static void decProgressCallback(SLPlayItf caller, void *context, SLuint32 event)
{
AudioDecoderSLES *thiz = reinterpret_cast<AudioDecoderSLES *>(context);
thiz->decodeProgressCallback(caller, event);
}
};
AudioDecoderSLES::AudioDecoderSLES()
: _engineItf(nullptr), _playObj(nullptr), _formatQueried(false),
_prefetchError(false), _counter(0), _numChannelsKeyIndex(-1), _sampleRateKeyIndex(-1),
_bitsPerSampleKeyIndex(-1), _containerSizeKeyIndex(-1), _channelMaskKeyIndex(-1),
_endiannessKeyIndex(-1), _eos(false), _bufferSizeInFrames(-1),
_assetFd(0), _fdGetterCallback(nullptr), _isDecodingCallbackInvoked(false)
{
ALOGV("Create AudioDecoderSLES");
}
AudioDecoderSLES::~AudioDecoderSLES()
{
{
std::lock_guard<std::mutex> lk(__SLPlayerMutex);
SL_DESTROY_OBJ(_playObj);
}
ALOGV("After destroying SL play object");
if (_assetFd > 0)
{
ALOGV("Closing assetFd: %d", _assetFd);
::close(_assetFd);
_assetFd = 0;
}
free(_pcmData);
}
bool AudioDecoderSLES::init(SLEngineItf engineItf, const std::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback)
{
if (AudioDecoder::init(url, sampleRate))
{
_engineItf = engineItf;
_bufferSizeInFrames = bufferSizeInFrames;
_fdGetterCallback = fdGetterCallback;
BUFFER_SIZE_IN_BYTES = toBufferSizeInBytes(bufferSizeInFrames, 2, 2);
_pcmData = (char*) malloc(NB_BUFFERS_IN_QUEUE * BUFFER_SIZE_IN_BYTES);
memset(_pcmData, 0x00, NB_BUFFERS_IN_QUEUE * BUFFER_SIZE_IN_BYTES);
return true;
}
return false;
}
bool AudioDecoderSLES::decodeToPcm()
{
SLresult result;
/* Objects this application uses: one audio player */
SLObjectItf player;
/* Interfaces for the audio player */
SLAndroidSimpleBufferQueueItf decBuffQueueItf;
SLPrefetchStatusItf prefetchItf;
SLPlayItf playItf;
SLMetadataExtractionItf mdExtrItf;
/* Source of audio data for the decoding */
SLDataSource decSource;
// decUri & locFd should be defined here
SLDataLocator_URI decUri;
SLDataLocator_AndroidFD locFd;
/* Data sink for decoded audio */
SLDataSink decDest;
SLDataLocator_AndroidSimpleBufferQueue decBuffQueue;
SLDataFormat_PCM pcm;
SLboolean required[NUM_EXPLICIT_INTERFACES_FOR_PLAYER];
SLInterfaceID iidArray[NUM_EXPLICIT_INTERFACES_FOR_PLAYER];
/* Initialize arrays required[] and iidArray[] */
for (int i = 0; i < NUM_EXPLICIT_INTERFACES_FOR_PLAYER; i++)
{
required[i] = SL_BOOLEAN_FALSE;
iidArray[i] = SL_IID_NULL;
}
/* ------------------------------------------------------ */
/* Configuration of the player */
/* Request the AndroidSimpleBufferQueue interface */
required[0] = SL_BOOLEAN_TRUE;
iidArray[0] = SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
/* Request the PrefetchStatus interface */
required[1] = SL_BOOLEAN_TRUE;
iidArray[1] = SL_IID_PREFETCHSTATUS;
/* Request the PrefetchStatus interface */
required[2] = SL_BOOLEAN_TRUE;
iidArray[2] = SL_IID_METADATAEXTRACTION;
SLDataFormat_MIME formatMime = {SL_DATAFORMAT_MIME, nullptr, SL_CONTAINERTYPE_UNSPECIFIED};
decSource.pFormat = &formatMime;
if (_url[0] != '/')
{
off_t start = 0, length = 0;
std::string relativePath;
size_t position = _url.find("@assets/");
if (0 == position)
{
// "@assets/" is at the beginning of the path and we don't want it
relativePath = _url.substr(strlen("@assets/"));
} else
{
relativePath = _url;
}
_assetFd = _fdGetterCallback(relativePath, &start, &length);
if (_assetFd <= 0)
{
ALOGE("Failed to open file descriptor for '%s'", _url.c_str());
return false;
}
// configure audio source
locFd = {SL_DATALOCATOR_ANDROIDFD, _assetFd, start, length};
decSource.pLocator = &locFd;
}
else
{
decUri = {SL_DATALOCATOR_URI, (SLchar *) _url.c_str()};
decSource.pLocator = &decUri;
}
/* Setup the data sink */
decBuffQueue.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
decBuffQueue.numBuffers = NB_BUFFERS_IN_QUEUE;
/* set up the format of the data in the buffer queue */
pcm.formatType = SL_DATAFORMAT_PCM;
// IDEA: valid value required but currently ignored
pcm.numChannels = 2;
pcm.samplesPerSec = SL_SAMPLINGRATE_44_1;
pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
pcm.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
decDest.pLocator = (void *) &decBuffQueue;
decDest.pFormat = (void *) &pcm;
{
std::lock_guard<std::mutex> lk(__SLPlayerMutex);
/* Create the audio player */
result = (*_engineItf)->CreateAudioPlayer(_engineItf, &player, &decSource, &decDest,
NUM_EXPLICIT_INTERFACES_FOR_PLAYER, iidArray,
required);
SL_RETURN_VAL_IF_FAILED(result, false, "CreateAudioPlayer failed");
_playObj = player;
/* Realize the player in synchronous mode. */
result = (*player)->Realize(player, SL_BOOLEAN_FALSE);
SL_RETURN_VAL_IF_FAILED(result, false, "Realize failed");
}
/* Get the play interface which is implicit */
result = (*player)->GetInterface(player, SL_IID_PLAY, (void *) &playItf);
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_PLAY failed");
/* Set up the player callback to get events during the decoding */
// IDEA: currently ignored
result = (*playItf)->SetMarkerPosition(playItf, 2000);
SL_RETURN_VAL_IF_FAILED(result, false, "SetMarkerPosition failed");
result = (*playItf)->SetPositionUpdatePeriod(playItf, 500);
SL_RETURN_VAL_IF_FAILED(result, false, "SetPositionUpdatePeriod failed");
result = (*playItf)->SetCallbackEventsMask(playItf,
SL_PLAYEVENT_HEADATMARKER |
SL_PLAYEVENT_HEADATNEWPOS | SL_PLAYEVENT_HEADATEND);
SL_RETURN_VAL_IF_FAILED(result, false, "SetCallbackEventsMask failed");
result = (*playItf)->RegisterCallback(playItf, SLAudioDecoderCallbackProxy::decProgressCallback,
this);
SL_RETURN_VAL_IF_FAILED(result, false, "RegisterCallback failed");
ALOGV("Play callback registered");
/* Get the buffer queue interface which was explicitly requested */
result = (*player)->GetInterface(player, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
(void *) &decBuffQueueItf);
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE failed");
/* Get the prefetch status interface which was explicitly requested */
result = (*player)->GetInterface(player, SL_IID_PREFETCHSTATUS, (void *) &prefetchItf);
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_PREFETCHSTATUS failed");
/* Get the metadata extraction interface which was explicitly requested */
result = (*player)->GetInterface(player, SL_IID_METADATAEXTRACTION, (void *) &mdExtrItf);
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_METADATAEXTRACTION failed");
/* ------------------------------------------------------ */
/* Initialize the callback and its context for the decoding buffer queue */
_decContext.playItf = playItf;
_decContext.metaItf = mdExtrItf;
_decContext.pDataBase = (int8_t *) _pcmData;
_decContext.pData = _decContext.pDataBase;
_decContext.size = NB_BUFFERS_IN_QUEUE * BUFFER_SIZE_IN_BYTES;
result = (*decBuffQueueItf)->RegisterCallback(decBuffQueueItf,
SLAudioDecoderCallbackProxy::decPlayCallback,
this);
SL_RETURN_VAL_IF_FAILED(result, false, "decBuffQueueItf RegisterCallback failed");
/* Enqueue buffers to map the region of memory allocated to store the decoded data */
// ALOGV("Enqueueing buffer ");
for (int i = 0; i < NB_BUFFERS_IN_QUEUE; i++)
{
result = (*decBuffQueueItf)->Enqueue(decBuffQueueItf, _decContext.pData,
BUFFER_SIZE_IN_BYTES);
SL_RETURN_VAL_IF_FAILED(result, false, "Enqueue failed");
_decContext.pData += BUFFER_SIZE_IN_BYTES;
}
_decContext.pData = _decContext.pDataBase;
/* ------------------------------------------------------ */
/* Initialize the callback for prefetch errors, if we can't open the resource to decode */
result = (*prefetchItf)->RegisterCallback(prefetchItf,
SLAudioDecoderCallbackProxy::prefetchEventCallback,
this);
SL_RETURN_VAL_IF_FAILED(result, false, "prefetchItf RegisterCallback failed");
result = (*prefetchItf)->SetCallbackEventsMask(prefetchItf, PREFETCHEVENT_ERROR_CANDIDATE);
SL_RETURN_VAL_IF_FAILED(result, false, "prefetchItf SetCallbackEventsMask failed");
/* ------------------------------------------------------ */
/* Prefetch the data so we can get information about the format before starting to decode */
/* 1/ cause the player to prefetch the data */
result = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PAUSED);
SL_RETURN_VAL_IF_FAILED(result, false, "SetPlayState SL_PLAYSTATE_PAUSED failed");
/* 2/ block until data has been prefetched */
SLuint32 prefetchStatus = SL_PREFETCHSTATUS_UNDERFLOW;
SLuint32 timeOutIndex = 1000; //cjh time out prefetching after 2s
while ((prefetchStatus != SL_PREFETCHSTATUS_SUFFICIENTDATA) && (timeOutIndex > 0) &&
!_prefetchError)
{
std::this_thread::sleep_for(std::chrono::milliseconds(2));
(*prefetchItf)->GetPrefetchStatus(prefetchItf, &prefetchStatus);
timeOutIndex--;
}
if (timeOutIndex == 0 || _prefetchError)
{
ALOGE("Failure to prefetch data in time, exiting");
SL_RETURN_VAL_IF_FAILED(SL_RESULT_CONTENT_NOT_FOUND, false,
"Failure to prefetch data in time");
}
/* ------------------------------------------------------ */
/* Display duration */
SLmillisecond durationInMsec = SL_TIME_UNKNOWN;
result = (*playItf)->GetDuration(playItf, &durationInMsec);
SL_RETURN_VAL_IF_FAILED(result, false, "GetDuration failed");
if (durationInMsec == SL_TIME_UNKNOWN)
{
ALOGV("Content duration is unknown");
} else
{
ALOGV("Content duration is %dms", (int)durationInMsec);
}
/* ------------------------------------------------------ */
/* Display the metadata obtained from the decoder */
// This is for test / demonstration purposes only where we discover the key and value sizes
// of a PCM decoder. An application that would want to directly get access to those values
// can make assumptions about the size of the keys and their matching values (all SLuint32)
SLuint32 itemCount;
result = (*mdExtrItf)->GetItemCount(mdExtrItf, &itemCount);
SLuint32 i, keySize, valueSize;
SLMetadataInfo *keyInfo, *value;
for (i = 0; i < itemCount; i++)
{
keyInfo = nullptr;
keySize = 0;
value = nullptr;
valueSize = 0;
result = (*mdExtrItf)->GetKeySize(mdExtrItf, i, &keySize);
SL_RETURN_VAL_IF_FAILED(result, false, "GetKeySize(%d) failed", (int)i);
result = (*mdExtrItf)->GetValueSize(mdExtrItf, i, &valueSize);
SL_RETURN_VAL_IF_FAILED(result, false, "GetValueSize(%d) failed", (int)i);
keyInfo = (SLMetadataInfo *) malloc(keySize);
if (nullptr != keyInfo)
{
result = (*mdExtrItf)->GetKey(mdExtrItf, i, keySize, keyInfo);
SL_RETURN_VAL_IF_FAILED(result, false, "GetKey(%d) failed", (int)i);
ALOGV("key[%d] size=%d, name=%s, value size=%d",
(int)i, (int)keyInfo->size, keyInfo->data, (int)valueSize);
/* find out the key index of the metadata we're interested in */
if (!strcmp((char *) keyInfo->data, ANDROID_KEY_PCMFORMAT_NUMCHANNELS))
{
_numChannelsKeyIndex = i;
} else if (!strcmp((char *) keyInfo->data, ANDROID_KEY_PCMFORMAT_SAMPLERATE))
{
_sampleRateKeyIndex = i;
} else if (!strcmp((char *) keyInfo->data, ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE))
{
_bitsPerSampleKeyIndex = i;
} else if (!strcmp((char *) keyInfo->data, ANDROID_KEY_PCMFORMAT_CONTAINERSIZE))
{
_containerSizeKeyIndex = i;
} else if (!strcmp((char *) keyInfo->data, ANDROID_KEY_PCMFORMAT_CHANNELMASK))
{
_channelMaskKeyIndex = i;
} else if (!strcmp((char *) keyInfo->data, ANDROID_KEY_PCMFORMAT_ENDIANNESS))
{
_endiannessKeyIndex = i;
}
free(keyInfo);
}
}
checkMetaData(_numChannelsKeyIndex, ANDROID_KEY_PCMFORMAT_NUMCHANNELS);
checkMetaData(_sampleRateKeyIndex, ANDROID_KEY_PCMFORMAT_SAMPLERATE);
checkMetaData(_bitsPerSampleKeyIndex, ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE);
checkMetaData(_containerSizeKeyIndex, ANDROID_KEY_PCMFORMAT_CONTAINERSIZE);
checkMetaData(_channelMaskKeyIndex, ANDROID_KEY_PCMFORMAT_CHANNELMASK);
checkMetaData(_endiannessKeyIndex, ANDROID_KEY_PCMFORMAT_ENDIANNESS);
/* ------------------------------------------------------ */
/* Start decoding */
result = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PLAYING);
SL_RETURN_VAL_IF_FAILED(result, false, "SetPlayState SL_PLAYSTATE_PLAYING failed");
ALOGV("Starting to decode");
/* Decode until the end of the stream is reached */
{
std::unique_lock<std::mutex> autoLock(_eosLock);
while (!_eos)
{
_eosCondition.wait(autoLock);
}
}
ALOGV("EOS signaled");
/* ------------------------------------------------------ */
/* End of decoding */
/* Stop decoding */
result = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_STOPPED);
SL_RETURN_VAL_IF_FAILED(result, false, "SetPlayState SL_PLAYSTATE_STOPPED failed");
ALOGV("Stopped decoding");
/* Destroy the UrlAudioPlayer object */
{
std::lock_guard<std::mutex> lk(__SLPlayerMutex);
SL_DESTROY_OBJ(_playObj);
}
ALOGV("After destroy player ...");
_result.numFrames =
_result.pcmBuffer->size() / _result.numChannels / (_result.bitsPerSample / 8);
std::string info = _result.toString();
ALOGI("Original audio info: %s, total size: %d", info.c_str(), (int)_result.pcmBuffer->size());
return true;
}
//-----------------------------------------------------------------
void AudioDecoderSLES::signalEos()
{
std::unique_lock<std::mutex> autoLock(_eosLock);
_eos = true;
_eosCondition.notify_one();
}
void AudioDecoderSLES::queryAudioInfo()
{
if (_formatQueried)
{
return;
}
SLresult result;
/* Get duration in callback where we use the callback context for the SLPlayItf*/
SLmillisecond durationInMsec = SL_TIME_UNKNOWN;
result = (*_decContext.playItf)->GetDuration(_decContext.playItf, &durationInMsec);
SL_RETURN_IF_FAILED(result, "decodeProgressCallback,GetDuration failed");
if (durationInMsec == SL_TIME_UNKNOWN)
{
ALOGV("Content duration is unknown (in dec callback)");
} else
{
ALOGV("Content duration is %dms (in dec callback)", (int)durationInMsec);
_result.duration = durationInMsec / 1000.0f;
}
/* used to query metadata values */
SLMetadataInfo pcmMetaData;
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _sampleRateKeyIndex,
PCM_METADATA_VALUE_SIZE, &pcmMetaData);
SL_RETURN_IF_FAILED(result, "%s GetValue _sampleRateKeyIndex failed", __FUNCTION__);
// Note: here we could verify the following:
// pcmMetaData->encoding == SL_CHARACTERENCODING_BINARY
// pcmMetaData->size == sizeof(SLuint32)
// but the call was successful for the PCM format keys, so those conditions are implied
_result.sampleRate = *((SLuint32 *) pcmMetaData.data);
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _numChannelsKeyIndex,
PCM_METADATA_VALUE_SIZE, &pcmMetaData);
SL_RETURN_IF_FAILED(result, "%s GetValue _numChannelsKeyIndex failed", __FUNCTION__);
_result.numChannels = *((SLuint32 *) pcmMetaData.data);
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _bitsPerSampleKeyIndex,
PCM_METADATA_VALUE_SIZE, &pcmMetaData);
SL_RETURN_IF_FAILED(result, "%s GetValue _bitsPerSampleKeyIndex failed", __FUNCTION__)
_result.bitsPerSample = *((SLuint32 *) pcmMetaData.data);
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _containerSizeKeyIndex,
PCM_METADATA_VALUE_SIZE, &pcmMetaData);
SL_RETURN_IF_FAILED(result, "%s GetValue _containerSizeKeyIndex failed", __FUNCTION__)
_result.containerSize = *((SLuint32 *) pcmMetaData.data);
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _channelMaskKeyIndex,
PCM_METADATA_VALUE_SIZE, &pcmMetaData);
SL_RETURN_IF_FAILED(result, "%s GetValue _channelMaskKeyIndex failed", __FUNCTION__)
_result.channelMask = *((SLuint32 *) pcmMetaData.data);
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _endiannessKeyIndex,
PCM_METADATA_VALUE_SIZE, &pcmMetaData);
SL_RETURN_IF_FAILED(result, "%s GetValue _endiannessKeyIndex failed", __FUNCTION__)
_result.endianness = *((SLuint32 *) pcmMetaData.data);
_formatQueried = true;
}
void AudioDecoderSLES::prefetchCallback(SLPrefetchStatusItf caller, SLuint32 event)
{
SLpermille level = 0;
SLresult result;
result = (*caller)->GetFillLevel(caller, &level);
SL_RETURN_IF_FAILED(result, "GetFillLevel failed");
SLuint32 status;
//ALOGV("PrefetchEventCallback: received event %u", event);
result = (*caller)->GetPrefetchStatus(caller, &status);
SL_RETURN_IF_FAILED(result, "GetPrefetchStatus failed");
if ((PREFETCHEVENT_ERROR_CANDIDATE == (event & PREFETCHEVENT_ERROR_CANDIDATE))
&& (level == 0) && (status == SL_PREFETCHSTATUS_UNDERFLOW))
{
ALOGV("PrefetchEventCallback: Error while prefetching data, exiting");
_prefetchError = true;
signalEos();
}
}
/* Callback for "playback" events, i.e. event happening during decoding */
void AudioDecoderSLES::decodeProgressCallback(SLPlayItf caller, SLuint32 event)
{
if (SL_PLAYEVENT_HEADATEND & event)
{
ALOGV("SL_PLAYEVENT_HEADATEND");
if (!_isDecodingCallbackInvoked)
{
queryAudioInfo();
for (int i = 0; i < NB_BUFFERS_IN_QUEUE; ++i)
{
_result.pcmBuffer->insert(_result.pcmBuffer->end(), _decContext.pData,
_decContext.pData + BUFFER_SIZE_IN_BYTES);
/* Increase data pointer by buffer size */
_decContext.pData += BUFFER_SIZE_IN_BYTES;
}
}
signalEos();
}
}
//-----------------------------------------------------------------
/* Callback for decoding buffer queue events */
void AudioDecoderSLES::decodeToPcmCallback(SLAndroidSimpleBufferQueueItf queueItf)
{
_isDecodingCallbackInvoked = true;
ALOGV("%s ...", __FUNCTION__);
_counter++;
SLresult result;
// IDEA: ??
if (_counter % 1000 == 0)
{
SLmillisecond msec;
result = (*_decContext.playItf)->GetPosition(_decContext.playItf, &msec);
SL_RETURN_IF_FAILED(result, "%s, GetPosition failed", __FUNCTION__);
ALOGV("%s called (iteration %d): current position=%d ms", __FUNCTION__, _counter, (int)msec);
}
_result.pcmBuffer->insert(_result.pcmBuffer->end(), _decContext.pData,
_decContext.pData + BUFFER_SIZE_IN_BYTES);
result = (*queueItf)->Enqueue(queueItf, _decContext.pData, BUFFER_SIZE_IN_BYTES);
SL_RETURN_IF_FAILED(result, "%s, Enqueue failed", __FUNCTION__);
/* Increase data pointer by buffer size */
_decContext.pData += BUFFER_SIZE_IN_BYTES;
if (_decContext.pData >= _decContext.pDataBase + (NB_BUFFERS_IN_QUEUE * BUFFER_SIZE_IN_BYTES))
{
_decContext.pData = _decContext.pDataBase;
}
// Note: adding a sleep here or any sync point is a way to slow down the decoding, or
// synchronize it with some other event, as the OpenSL ES framework will block until the
// buffer queue callback return to proceed with the decoding.
#if 0
/* Example: buffer queue state display */
SLAndroidSimpleBufferQueueState decQueueState;
result =(*queueItf)->GetState(queueItf, &decQueueState);
SL_RETURN_IF_FAILED(result, "decQueueState.GetState failed");
ALOGV("DecBufferQueueCallback now has _decContext.pData=%p, _decContext.pDataBase=%p, queue: "
"count=%u playIndex=%u, count: %d",
_decContext.pData, _decContext.pDataBase, decQueueState.count, decQueueState.index, _counter);
#endif
#if 0
/* Example: display position in callback where we use the callback context for the SLPlayItf*/
SLmillisecond posMsec = SL_TIME_UNKNOWN;
result = (*_decContext.playItf)->GetPosition(_decContext.playItf, &posMsec);
SL_RETURN_IF_FAILED(result, "decodeToPcmCallback,GetPosition2 failed");
if (posMsec == SL_TIME_UNKNOWN) {
ALOGV("Content position is unknown (in dec callback)");
} else {
ALOGV("Content position is %ums (in dec callback)",
posMsec);
}
#endif
queryAudioInfo();
}
} // namespace cocos2d {

View File

@ -0,0 +1,97 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/AudioDecoder.h"
#include <mutex>
#include <condition_variable>
namespace cocos2d {
class AudioDecoderSLES : public AudioDecoder
{
protected:
AudioDecoderSLES();
virtual ~AudioDecoderSLES();
bool init(SLEngineItf engineItf, const std::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback);
virtual bool decodeToPcm() override;
private:
void queryAudioInfo();
void signalEos();
void decodeToPcmCallback(SLAndroidSimpleBufferQueueItf queueItf);
void prefetchCallback(SLPrefetchStatusItf caller, SLuint32 event);
void decodeProgressCallback(SLPlayItf caller, SLuint32 event);
SLEngineItf _engineItf;
SLObjectItf _playObj;
/* Local storage for decoded audio data */
char* _pcmData;
/* we only want to query / display the PCM format once */
bool _formatQueried;
/* Used to signal prefetching failures */
bool _prefetchError;
/* to display the number of decode iterations */
int _counter;
/* metadata key index for the PCM format information we want to retrieve */
int _numChannelsKeyIndex;
int _sampleRateKeyIndex;
int _bitsPerSampleKeyIndex;
int _containerSizeKeyIndex;
int _channelMaskKeyIndex;
int _endiannessKeyIndex;
/* to signal to the test app the end of the stream to decode has been reached */
bool _eos;
std::mutex _eosLock;
std::condition_variable _eosCondition;
/* Structure for passing information to callback function */
typedef struct CallbackCntxt_
{
SLPlayItf playItf;
SLMetadataExtractionItf metaItf;
SLuint32 size;
SLint8 *pDataBase; // Base address of local audio data storage
SLint8 *pData; // Current address of local audio data storage
} CallbackCntxt;
CallbackCntxt _decContext;
int _bufferSizeInFrames;
int _assetFd;
FdGetterCallback _fdGetterCallback;
bool _isDecodingCallbackInvoked;
friend class SLAudioDecoderCallbackProxy;
friend class AudioDecoderProvider;
};
} // namespace cocos2d {

View File

@ -0,0 +1,115 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioDecoderWav"
#include "audio/android/AudioDecoderWav.h"
#include "audio/android/tinysndfile.h"
#include "platform/CCFileUtils.h"
#include <assert.h>
namespace cocos2d {
AudioDecoderWav::AudioDecoderWav()
{
ALOGV("Create AudioDecoderWav");
}
AudioDecoderWav::~AudioDecoderWav()
{
}
void* AudioDecoderWav::onWavOpen(const char* path, void* user)
{
return user;
}
int AudioDecoderWav::onWavSeek(void* datasource, long offset, int whence)
{
return AudioDecoder::fileSeek(datasource, (int64_t) offset, whence);
}
int AudioDecoderWav::onWavClose(void* datasource)
{
return 0;
}
bool AudioDecoderWav::decodeToPcm()
{
_fileData = FileUtils::getInstance()->getDataFromFile(_url);
if (_fileData.isNull())
{
return false;
}
SF_INFO info;
snd_callbacks cb;
cb.open = onWavOpen;
cb.read = AudioDecoder::fileRead;
cb.seek = onWavSeek;
cb.close = onWavClose;
cb.tell = AudioDecoder::fileTell;
SNDFILE* handle = NULL;
bool ret = false;
do
{
handle = sf_open_read(_url.c_str(), &info, &cb, this);
if (handle == nullptr)
break;
if (info.frames == 0)
break;
ALOGD("wav info: frames: %d, samplerate: %d, channels: %d, format: %d", info.frames, info.samplerate, info.channels, info.format);
size_t bufSize = sizeof(short) * info.frames * info.channels;
unsigned char* buf = (unsigned char*)malloc(bufSize);
sf_count_t readFrames = sf_readf_short(handle, (short*)buf, info.frames);
assert(readFrames == info.frames);
_result.pcmBuffer->insert(_result.pcmBuffer->end(), buf, buf + bufSize);
_result.numChannels = info.channels;
_result.sampleRate = info.samplerate;
_result.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
_result.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
_result.channelMask = _result.numChannels == 1 ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT);
_result.endianness = SL_BYTEORDER_LITTLEENDIAN;
_result.numFrames = info.frames;
_result.duration = 1.0f * info.frames / _result.sampleRate;
free(buf);
ret = true;
} while (false);
if (handle != NULL)
sf_close(handle);
return ret;
}
} // namespace cocos2d {

View File

@ -0,0 +1,47 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/AudioDecoder.h"
namespace cocos2d {
class AudioDecoderWav : public AudioDecoder
{
protected:
AudioDecoderWav();
virtual ~AudioDecoderWav();
virtual bool decodeToPcm() override;
static void* onWavOpen(const char* path, void* user);
static int onWavSeek(void* datasource, long offset, int whence);
static int onWavClose(void* datasource);
friend class AudioDecoderProvider;
};
} // namespace cocos2d {

View File

@ -0,0 +1,466 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
#define LOG_TAG "AudioEngineImpl"
#include "audio/android/AudioEngine-inl.h"
#include <unistd.h>
// for native asset manager
#include <sys/types.h>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
#include <unordered_map>
#include <android/log.h>
#include <thread>
#include <mutex>
#include "audio/include/AudioEngine.h"
#include "platform/CCApplication.h"
#include "base/CCScheduler.h"
#include "base/ccUTF8.h"
#include "platform/android/CCFileUtils-android.h"
#include "platform/android/jni/JniImp.h"
#include "platform/android/jni/JniHelper.h"
#include "audio/android/IAudioPlayer.h"
#include "audio/android/ICallerThreadUtils.h"
#include "audio/android/AudioPlayerProvider.h"
#include "audio/android/cutils/log.h"
#include "audio/android/UrlAudioPlayer.h"
#include "scripting/js-bindings/event/EventDispatcher.h"
#include "scripting/js-bindings/event/CustomEventTypes.h"
using namespace cocos2d;
// Audio focus values synchronized with which in cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxActivity.java
static const int AUDIOFOCUS_GAIN = 0;
static const int AUDIOFOCUS_LOST = 1;
static const int AUDIOFOCUS_LOST_TRANSIENT = 2;
static const int AUDIOFOCUS_LOST_TRANSIENT_CAN_DUCK = 3;
static int __currentAudioFocus = AUDIOFOCUS_GAIN;
static AudioEngineImpl* __impl = nullptr;
class CallerThreadUtils : public ICallerThreadUtils
{
public:
virtual void performFunctionInCallerThread(const std::function<void()>& func)
{
Application::getInstance()->getScheduler()->performFunctionInCocosThread(func);
};
virtual std::thread::id getCallerThreadId()
{
return _tid;
};
void setCallerThreadId(std::thread::id tid)
{
_tid = tid;
};
private:
std::thread::id _tid;
};
static CallerThreadUtils __callerThreadUtils;
static int fdGetter(const std::string& url, off_t* start, off_t* length)
{
int fd = -1;
if (cocos2d::FileUtilsAndroid::getObbFile() != nullptr)
{
fd = getObbAssetFileDescriptorJNI(url.c_str(), start, length);
}
if (fd <= 0)
{
auto asset = AAssetManager_open(cocos2d::FileUtilsAndroid::getAssetManager(), url.c_str(), AASSET_MODE_UNKNOWN);
// open asset as file descriptor
fd = AAsset_openFileDescriptor(asset, start, length);
AAsset_close(asset);
}
if (fd <= 0)
{
ALOGE("Failed to open file descriptor for '%s'", url.c_str());
}
return fd;
};
//====================================================
AudioEngineImpl::AudioEngineImpl()
: _engineObject(nullptr)
, _engineEngine(nullptr)
, _outputMixObject(nullptr)
, _audioPlayerProvider(nullptr)
, _audioIDIndex(0)
, _lazyInitLoop(true)
{
__callerThreadUtils.setCallerThreadId(std::this_thread::get_id());
__impl = this;
}
AudioEngineImpl::~AudioEngineImpl()
{
if (_audioPlayerProvider != nullptr)
{
delete _audioPlayerProvider;
_audioPlayerProvider = nullptr;
}
if (_outputMixObject)
{
(*_outputMixObject)->Destroy(_outputMixObject);
}
if (_engineObject)
{
(*_engineObject)->Destroy(_engineObject);
}
__impl = nullptr;
}
bool AudioEngineImpl::init()
{
bool ret = false;
do{
// create engine
auto result = slCreateEngine(&_engineObject, 0, nullptr, 0, nullptr, nullptr);
if(SL_RESULT_SUCCESS != result){ ERRORLOG("create opensl engine fail"); break; }
// realize the engine
result = (*_engineObject)->Realize(_engineObject, SL_BOOLEAN_FALSE);
if(SL_RESULT_SUCCESS != result){ ERRORLOG("realize the engine fail"); break; }
// get the engine interface, which is needed in order to create other objects
result = (*_engineObject)->GetInterface(_engineObject, SL_IID_ENGINE, &_engineEngine);
if(SL_RESULT_SUCCESS != result){ ERRORLOG("get the engine interface fail"); break; }
// create output mix
const SLInterfaceID outputMixIIDs[] = {};
const SLboolean outputMixReqs[] = {};
result = (*_engineEngine)->CreateOutputMix(_engineEngine, &_outputMixObject, 0, outputMixIIDs, outputMixReqs);
if(SL_RESULT_SUCCESS != result){ ERRORLOG("create output mix fail"); break; }
// realize the output mix
result = (*_outputMixObject)->Realize(_outputMixObject, SL_BOOLEAN_FALSE);
if(SL_RESULT_SUCCESS != result){ ERRORLOG("realize the output mix fail"); break; }
_audioPlayerProvider = new AudioPlayerProvider(_engineEngine, _outputMixObject, getDeviceSampleRateJNI(), getDeviceAudioBufferSizeInFramesJNI(), fdGetter, &__callerThreadUtils);
ret = true;
}while (false);
return ret;
}
void AudioEngineImpl::setAudioFocusForAllPlayers(bool isFocus)
{
for (const auto& e : _audioPlayers)
{
e.second->setAudioFocus(isFocus);
}
}
int AudioEngineImpl::play2d(const std::string &filePath ,bool loop ,float volume)
{
ALOGV("play2d, _audioPlayers.size=%d", (int)_audioPlayers.size());
auto audioId = AudioEngine::INVALID_AUDIO_ID;
do
{
if (_engineEngine == nullptr || _audioPlayerProvider == nullptr)
break;
auto fullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
audioId = _audioIDIndex++;
auto player = _audioPlayerProvider->getAudioPlayer(fullPath);
if (player != nullptr)
{
player->setId(audioId);
_audioPlayers.insert(std::make_pair(audioId, player));
player->setPlayEventCallback([this, player, filePath](IAudioPlayer::State state){
if (state != IAudioPlayer::State::OVER && state != IAudioPlayer::State::STOPPED)
{
ALOGV("Ignore state: %d", static_cast<int>(state));
return;
}
int id = player->getId();
ALOGV("Removing player id=%d, state:%d", id, (int)state);
AudioEngine::remove(id);
if (_audioPlayers.find(id) != _audioPlayers.end())
{
_audioPlayers.erase(id);
}
if (_urlAudioPlayersNeedResume.find(id) != _urlAudioPlayersNeedResume.end())
{
_urlAudioPlayersNeedResume.erase(id);
}
auto iter = _callbackMap.find(id);
if (iter != _callbackMap.end())
{
if (state == IAudioPlayer::State::OVER)
{
iter->second(id, filePath);
}
_callbackMap.erase(iter);
}
});
player->setLoop(loop);
player->setVolume(volume);
player->setAudioFocus(__currentAudioFocus == AUDIOFOCUS_GAIN);
player->play();
}
else
{
ALOGE("Oops, player is null ...");
return AudioEngine::INVALID_AUDIO_ID;
}
AudioEngine::_audioIDInfoMap[audioId].state = AudioEngine::AudioState::PLAYING;
} while (0);
return audioId;
}
void AudioEngineImpl::setVolume(int audioID,float volume)
{
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end())
{
auto player = iter->second;
player->setVolume(volume);
}
}
void AudioEngineImpl::setLoop(int audioID, bool loop)
{
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end())
{
auto player = iter->second;
player->setLoop(loop);
}
}
void AudioEngineImpl::pause(int audioID)
{
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end())
{
auto player = iter->second;
player->pause();
}
}
void AudioEngineImpl::resume(int audioID)
{
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end())
{
auto player = iter->second;
player->resume();
}
}
void AudioEngineImpl::stop(int audioID)
{
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end())
{
auto player = iter->second;
player->stop();
}
}
void AudioEngineImpl::stopAll()
{
if (_audioPlayers.empty())
{
return;
}
// Create a temporary vector for storing all players since
// p->stop() will trigger _audioPlayers.erase,
// and it will cause a crash as it's already in for loop
std::vector<IAudioPlayer*> players;
players.reserve(_audioPlayers.size());
for (const auto& e : _audioPlayers)
{
players.push_back(e.second);
}
for (auto p : players)
{
p->stop();
}
}
float AudioEngineImpl::getDuration(int audioID)
{
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end())
{
auto player = iter->second;
return player->getDuration();
}
return 0.0f;
}
float AudioEngineImpl::getDurationFromFile(const std::string &filePath)
{
if (_audioPlayerProvider != nullptr)
{
auto fullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
return _audioPlayerProvider->getDurationFromFile(fullPath);
}
return 0;
}
float AudioEngineImpl::getCurrentTime(int audioID)
{
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end())
{
auto player = iter->second;
return player->getPosition();
}
return 0.0f;
}
bool AudioEngineImpl::setCurrentTime(int audioID, float time)
{
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end())
{
auto player = iter->second;
return player->setPosition(time);
}
return false;
}
void AudioEngineImpl::setFinishCallback(int audioID, const std::function<void (int, const std::string &)> &callback)
{
_callbackMap[audioID] = callback;
}
void AudioEngineImpl::preload(const std::string& filePath, const std::function<void(bool)>& callback)
{
if (_audioPlayerProvider != nullptr)
{
std::string fullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
_audioPlayerProvider->preloadEffect(fullPath, [callback](bool succeed, PcmData data){
if (callback != nullptr)
{
callback(succeed);
}
});
}
else
{
if (callback != nullptr)
{
callback(false);
}
}
}
void AudioEngineImpl::uncache(const std::string& filePath)
{
if (_audioPlayerProvider != nullptr)
{
std::string fullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
_audioPlayerProvider->clearPcmCache(fullPath);
}
}
void AudioEngineImpl::uncacheAll()
{
if (_audioPlayerProvider != nullptr)
{
_audioPlayerProvider->clearAllPcmCaches();
}
}
void AudioEngineImpl::onPause()
{
if (_audioPlayerProvider != nullptr)
{
_audioPlayerProvider->pause();
}
}
void AudioEngineImpl::onResume()
{
if (_audioPlayerProvider != nullptr)
{
_audioPlayerProvider->resume();
}
}
// It's invoked from javaactivity-android.cpp
void cocos_audioengine_focus_change(int focusChange)
{
if (focusChange < AUDIOFOCUS_GAIN || focusChange > AUDIOFOCUS_LOST_TRANSIENT_CAN_DUCK)
{
CCLOGERROR("cocos_audioengine_focus_change: unknown value: %d", focusChange);
return;
}
CCLOG("cocos_audioengine_focus_change: %d", focusChange);
__currentAudioFocus = focusChange;
if (__impl == nullptr)
{
CCLOGWARN("cocos_audioengine_focus_change: AudioEngineImpl isn't ready!");
return;
}
if (__currentAudioFocus == AUDIOFOCUS_GAIN)
{
__impl->setAudioFocusForAllPlayers(true);
}
else
{
__impl->setAudioFocusForAllPlayers(false);
}
}
#endif

View File

@ -0,0 +1,105 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
#ifndef __AUDIO_ENGINE_INL_H_
#define __AUDIO_ENGINE_INL_H_
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <string>
#include <unordered_map>
#include <functional>
#include "base/CCRef.h"
#include "base/ccUtils.h"
#define MAX_AUDIOINSTANCES 13
#define ERRORLOG(msg) log("fun:%s,line:%d,msg:%s",__func__,__LINE__,#msg)
NS_CC_BEGIN
struct CustomEvent;
class IAudioPlayer;
class AudioPlayerProvider;
class AudioEngineImpl;
class AudioEngineImpl : public cocos2d::Ref
{
public:
AudioEngineImpl();
~AudioEngineImpl();
bool init();
int play2d(const std::string &fileFullPath ,bool loop ,float volume);
void setVolume(int audioID,float volume);
void setLoop(int audioID, bool loop);
void pause(int audioID);
void resume(int audioID);
void stop(int audioID);
void stopAll();
float getDuration(int audioID);
float getDurationFromFile(const std::string &fileFullPath);
float getCurrentTime(int audioID);
bool setCurrentTime(int audioID, float time);
void setFinishCallback(int audioID, const std::function<void (int, const std::string &)> &callback);
void uncache(const std::string& filePath);
void uncacheAll();
void preload(const std::string& filePath, const std::function<void(bool)>& callback);
void onResume();
void onPause();
void setAudioFocusForAllPlayers(bool isFocus);
private:
// engine interfaces
SLObjectItf _engineObject;
SLEngineItf _engineEngine;
// output mix interfaces
SLObjectItf _outputMixObject;
//audioID,AudioInfo
std::unordered_map<int, IAudioPlayer*> _audioPlayers;
std::unordered_map<int, std::function<void (int, const std::string &)>> _callbackMap;
// UrlAudioPlayers which need to resumed while entering foreground
std::unordered_map<int, IAudioPlayer*> _urlAudioPlayersNeedResume;
AudioPlayerProvider* _audioPlayerProvider;
int _audioIDIndex;
bool _lazyInitLoop;
};
#endif // __AUDIO_ENGINE_INL_H_
NS_CC_END
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,389 @@
/*
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <sys/types.h>
#include <pthread.h>
#include "audio/android/AudioBufferProvider.h"
#include "audio/android/AudioResamplerPublic.h"
#include "audio/android/AudioResampler.h"
#include "audio/android/audio.h"
// IDEA: This is actually unity gain, which might not be max in future, expressed in U.12
#define MAX_GAIN_INT AudioMixer::UNITY_GAIN_INT
namespace cocos2d {
// ----------------------------------------------------------------------------
class AudioMixer
{
public:
AudioMixer(size_t frameCount, uint32_t sampleRate,
uint32_t maxNumTracks = MAX_NUM_TRACKS);
/*virtual*/ ~AudioMixer(); // non-virtual saves a v-table, restore if sub-classed
// This mixer has a hard-coded upper limit of 32 active track inputs.
// Adding support for > 32 tracks would require more than simply changing this value.
static const uint32_t MAX_NUM_TRACKS = 32;
// maximum number of channels supported by the mixer
// This mixer has a hard-coded upper limit of 8 channels for output.
static const uint32_t MAX_NUM_CHANNELS = 8;
static const uint32_t MAX_NUM_VOLUMES = 2; // stereo volume only
// maximum number of channels supported for the content
static const uint32_t MAX_NUM_CHANNELS_TO_DOWNMIX = AUDIO_CHANNEL_COUNT_MAX;
static const uint16_t UNITY_GAIN_INT = 0x1000;
static const CONSTEXPR float UNITY_GAIN_FLOAT = 1.0f;
enum { // names
// track names (MAX_NUM_TRACKS units)
TRACK0 = 0x1000,
// 0x2000 is unused
// setParameter targets
TRACK = 0x3000,
RESAMPLE = 0x3001,
RAMP_VOLUME = 0x3002, // ramp to new volume
VOLUME = 0x3003, // don't ramp
TIMESTRETCH = 0x3004,
// set Parameter names
// for target TRACK
CHANNEL_MASK = 0x4000,
FORMAT = 0x4001,
MAIN_BUFFER = 0x4002,
AUX_BUFFER = 0x4003,
DOWNMIX_TYPE = 0X4004,
MIXER_FORMAT = 0x4005, // AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
MIXER_CHANNEL_MASK = 0x4006, // Channel mask for mixer output
// for target RESAMPLE
SAMPLE_RATE = 0x4100, // Configure sample rate conversion on this track name;
// parameter 'value' is the new sample rate in Hz.
// Only creates a sample rate converter the first time that
// the track sample rate is different from the mix sample rate.
// If the new sample rate is the same as the mix sample rate,
// and a sample rate converter already exists,
// then the sample rate converter remains present but is a no-op.
RESET = 0x4101, // Reset sample rate converter without changing sample rate.
// This clears out the resampler's input buffer.
REMOVE = 0x4102, // Remove the sample rate converter on this track name;
// the track is restored to the mix sample rate.
// for target RAMP_VOLUME and VOLUME (8 channels max)
// IDEA: use float for these 3 to improve the dynamic range
VOLUME0 = 0x4200,
VOLUME1 = 0x4201,
AUXLEVEL = 0x4210,
// for target TIMESTRETCH
PLAYBACK_RATE = 0x4300, // Configure timestretch on this track name;
// parameter 'value' is a pointer to the new playback rate.
};
// For all APIs with "name": TRACK0 <= name < TRACK0 + MAX_NUM_TRACKS
// Allocate a track name. Returns new track name if successful, -1 on failure.
// The failure could be because of an invalid channelMask or format, or that
// the track capacity of the mixer is exceeded.
int getTrackName(audio_channel_mask_t channelMask,
audio_format_t format, int sessionId);
// Free an allocated track by name
void deleteTrackName(int name);
// Enable or disable an allocated track by name
void enable(int name);
void disable(int name);
void setParameter(int name, int target, int param, void *value);
void setBufferProvider(int name, AudioBufferProvider* bufferProvider);
void process(int64_t pts);
uint32_t trackNames() const { return mTrackNames; }
size_t getUnreleasedFrames(int name) const;
static inline bool isValidPcmTrackFormat(audio_format_t format) {
switch (format) {
case AUDIO_FORMAT_PCM_8_BIT:
case AUDIO_FORMAT_PCM_16_BIT:
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
case AUDIO_FORMAT_PCM_32_BIT:
case AUDIO_FORMAT_PCM_FLOAT:
return true;
default:
return false;
}
}
private:
enum {
// IDEA: this representation permits up to 8 channels
NEEDS_CHANNEL_COUNT__MASK = 0x00000007,
};
enum {
NEEDS_CHANNEL_1 = 0x00000000, // mono
NEEDS_CHANNEL_2 = 0x00000001, // stereo
// sample format is not explicitly specified, and is assumed to be AUDIO_FORMAT_PCM_16_BIT
NEEDS_MUTE = 0x00000100,
NEEDS_RESAMPLE = 0x00001000,
NEEDS_AUX = 0x00010000,
};
struct state_t;
struct track_t;
typedef void (*hook_t)(track_t* t, int32_t* output, size_t numOutFrames, int32_t* temp,
int32_t* aux);
static const int BLOCKSIZE = 16; // 4 cache lines
struct track_t {
uint32_t needs;
// REFINE: Eventually remove legacy integer volume settings
union {
int16_t volume[MAX_NUM_VOLUMES]; // U4.12 fixed point (top bit should be zero)
int32_t volumeRL;
};
int32_t prevVolume[MAX_NUM_VOLUMES];
// 16-byte boundary
int32_t volumeInc[MAX_NUM_VOLUMES];
int32_t auxInc;
int32_t prevAuxLevel;
// 16-byte boundary
int16_t auxLevel; // 0 <= auxLevel <= MAX_GAIN_INT, but signed for mul performance
uint16_t frameCount;
uint8_t channelCount; // 1 or 2, redundant with (needs & NEEDS_CHANNEL_COUNT__MASK)
uint8_t unused_padding; // formerly format, was always 16
uint16_t enabled; // actually bool
audio_channel_mask_t channelMask;
// actual buffer provider used by the track hooks, see DownmixerBufferProvider below
// for how the Track buffer provider is wrapped by another one when dowmixing is required
AudioBufferProvider* bufferProvider;
// 16-byte boundary
mutable AudioBufferProvider::Buffer buffer; // 8 bytes
hook_t hook;
const void* in; // current location in buffer
// 16-byte boundary
AudioResampler* resampler;
uint32_t sampleRate;
int32_t* mainBuffer;
int32_t* auxBuffer;
// 16-byte boundary
/* Buffer providers are constructed to translate the track input data as needed.
*
* REFINE: perhaps make a single PlaybackConverterProvider class to move
* all pre-mixer track buffer conversions outside the AudioMixer class.
*
* 1) mInputBufferProvider: The AudioTrack buffer provider.
* 2) mReformatBufferProvider: If not NULL, performs the audio reformat to
* match either mMixerInFormat or mDownmixRequiresFormat, if the downmixer
* requires reformat. For example, it may convert floating point input to
* PCM_16_bit if that's required by the downmixer.
* 3) downmixerBufferProvider: If not NULL, performs the channel remixing to match
* the number of channels required by the mixer sink.
* 4) mPostDownmixReformatBufferProvider: If not NULL, performs reformatting from
* the downmixer requirements to the mixer engine input requirements.
* 5) mTimestretchBufferProvider: Adds timestretching for playback rate
*/
AudioBufferProvider* mInputBufferProvider; // externally provided buffer provider.
//cjh PassthruBufferProvider* mReformatBufferProvider; // provider wrapper for reformatting.
// PassthruBufferProvider* downmixerBufferProvider; // wrapper for channel conversion.
// PassthruBufferProvider* mPostDownmixReformatBufferProvider;
// PassthruBufferProvider* mTimestretchBufferProvider;
int32_t sessionId;
audio_format_t mMixerFormat; // output mix format: AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
audio_format_t mFormat; // input track format
audio_format_t mMixerInFormat; // mix internal format AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
// each track must be converted to this format.
audio_format_t mDownmixRequiresFormat; // required downmixer format
// AUDIO_FORMAT_PCM_16_BIT if 16 bit necessary
// AUDIO_FORMAT_INVALID if no required format
float mVolume[MAX_NUM_VOLUMES]; // floating point set volume
float mPrevVolume[MAX_NUM_VOLUMES]; // floating point previous volume
float mVolumeInc[MAX_NUM_VOLUMES]; // floating point volume increment
float mAuxLevel; // floating point set aux level
float mPrevAuxLevel; // floating point prev aux level
float mAuxInc; // floating point aux increment
audio_channel_mask_t mMixerChannelMask;
uint32_t mMixerChannelCount;
AudioPlaybackRate mPlaybackRate;
bool needsRamp() { return (volumeInc[0] | volumeInc[1] | auxInc) != 0; }
bool setResampler(uint32_t trackSampleRate, uint32_t devSampleRate);
bool doesResample() const { return resampler != NULL; }
void resetResampler() { if (resampler != NULL) resampler->reset(); }
void adjustVolumeRamp(bool aux, bool useFloat = false);
size_t getUnreleasedFrames() const { return resampler != NULL ?
resampler->getUnreleasedFrames() : 0; };
status_t prepareForDownmix();
void unprepareForDownmix();
status_t prepareForReformat();
void unprepareForReformat();
bool setPlaybackRate(const AudioPlaybackRate &playbackRate);
void reconfigureBufferProviders();
};
typedef void (*process_hook_t)(state_t* state, int64_t pts);
// pad to 32-bytes to fill cache line
struct state_t {
uint32_t enabledTracks;
uint32_t needsChanged;
size_t frameCount;
process_hook_t hook; // one of process__*, never NULL
int32_t *outputTemp;
int32_t *resampleTemp;
//cjh NBLog::Writer* mLog;
int32_t reserved[1];
// IDEA: allocate dynamically to save some memory when maxNumTracks < MAX_NUM_TRACKS
track_t tracks[MAX_NUM_TRACKS] __attribute__((aligned(32)));
};
// bitmask of allocated track names, where bit 0 corresponds to TRACK0 etc.
uint32_t mTrackNames;
// bitmask of configured track names; ~0 if maxNumTracks == MAX_NUM_TRACKS,
// but will have fewer bits set if maxNumTracks < MAX_NUM_TRACKS
const uint32_t mConfiguredNames;
const uint32_t mSampleRate;
//cjh NBLog::Writer mDummyLog;
public:
//cjh void setLog(NBLog::Writer* log);
private:
state_t mState __attribute__((aligned(32)));
// Call after changing either the enabled status of a track, or parameters of an enabled track.
// OK to call more often than that, but unnecessary.
void invalidateState(uint32_t mask);
bool setChannelMasks(int name,
audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask);
static void track__genericResample(track_t* t, int32_t* out, size_t numFrames, int32_t* temp,
int32_t* aux);
static void track__nop(track_t* t, int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux);
static void track__16BitsStereo(track_t* t, int32_t* out, size_t numFrames, int32_t* temp,
int32_t* aux);
static void track__16BitsMono(track_t* t, int32_t* out, size_t numFrames, int32_t* temp,
int32_t* aux);
static void volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp,
int32_t* aux);
static void volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp,
int32_t* aux);
static void process__validate(state_t* state, int64_t pts);
static void process__nop(state_t* state, int64_t pts);
static void process__genericNoResampling(state_t* state, int64_t pts);
static void process__genericResampling(state_t* state, int64_t pts);
static void process__OneTrack16BitsStereoNoResampling(state_t* state,
int64_t pts);
static int64_t calculateOutputPTS(const track_t& t, int64_t basePTS,
int outputFrameIndex);
static uint64_t sLocalTimeFreq;
static pthread_once_t sOnceControl;
static void sInitRoutine();
/* multi-format volume mixing function (calls template functions
* in AudioMixerOps.h). The template parameters are as follows:
*
* MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration)
* USEFLOATVOL (set to true if float volume is used)
* ADJUSTVOL (set to true if volume ramp parameters needs adjustment afterwards)
* TO: int32_t (Q4.27) or float
* TI: int32_t (Q4.27) or int16_t (Q0.15) or float
* TA: int32_t (Q4.27)
*/
template <int MIXTYPE, bool USEFLOATVOL, bool ADJUSTVOL,
typename TO, typename TI, typename TA>
static void volumeMix(TO *out, size_t outFrames,
const TI *in, TA *aux, bool ramp, AudioMixer::track_t *t);
// multi-format process hooks
template <int MIXTYPE, typename TO, typename TI, typename TA>
static void process_NoResampleOneTrack(state_t* state, int64_t pts);
// multi-format track hooks
template <int MIXTYPE, typename TO, typename TI, typename TA>
static void track__Resample(track_t* t, TO* out, size_t frameCount,
TO* temp __unused, TA* aux);
template <int MIXTYPE, typename TO, typename TI, typename TA>
static void track__NoResample(track_t* t, TO* out, size_t frameCount,
TO* temp __unused, TA* aux);
static void convertMixerFormat(void *out, audio_format_t mixerOutFormat,
void *in, audio_format_t mixerInFormat, size_t sampleCount);
// hook types
enum {
PROCESSTYPE_NORESAMPLEONETRACK,
};
enum {
TRACKTYPE_NOP,
TRACKTYPE_RESAMPLE,
TRACKTYPE_NORESAMPLE,
TRACKTYPE_NORESAMPLEMONO,
};
// functions for determining the proper process and track hooks.
static process_hook_t getProcessHook(int processType, uint32_t channelCount,
audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
static hook_t getTrackHook(int trackType, uint32_t channelCount,
audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
};
// ----------------------------------------------------------------------------
} // namespace cocos2d {

View File

@ -0,0 +1,350 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioMixerController"
#include "audio/android/AudioMixerController.h"
#include "audio/android/AudioMixer.h"
#include "audio/android/Track.h"
#include "audio/android/OpenSLHelper.h"
#include <algorithm>
namespace cocos2d {
AudioMixerController::AudioMixerController(int bufferSizeInFrames, int sampleRate, int channelCount)
: _bufferSizeInFrames(bufferSizeInFrames)
, _sampleRate(sampleRate)
, _channelCount(channelCount)
, _mixer(nullptr)
, _isPaused(false)
, _isMixingFrame(false)
{
ALOGV("In the constructor of AudioMixerController!");
_mixingBuffer.size = (size_t) bufferSizeInFrames * 2 * channelCount;
// Don't use posix_memalign since it was added from API 16, it will crash on Android 2.3
// Therefore, for a workaround, we uses memalign here.
_mixingBuffer.buf = memalign(32, _mixingBuffer.size);
memset(_mixingBuffer.buf, 0, _mixingBuffer.size);
}
AudioMixerController::~AudioMixerController()
{
destroy();
if (_mixer != nullptr)
{
delete _mixer;
_mixer = nullptr;
}
free(_mixingBuffer.buf);
}
bool AudioMixerController::init()
{
_mixer = new (std::nothrow) AudioMixer(_bufferSizeInFrames, _sampleRate);
return _mixer != nullptr;
}
bool AudioMixerController::addTrack(Track* track)
{
ALOG_ASSERT(track != nullptr, "Shouldn't pass nullptr to addTrack");
bool ret = false;
std::lock_guard<std::mutex> lk(_activeTracksMutex);
auto iter = std::find(_activeTracks.begin(), _activeTracks.end(), track);
if (iter == _activeTracks.end())
{
_activeTracks.push_back(track);
ret = true;
}
return ret;
}
template <typename T>
static void removeItemFromVector(std::vector<T>& v, T item)
{
auto iter = std::find(v.begin(), v.end(), item);
if (iter != v.end())
{
v.erase(iter);
}
}
void AudioMixerController::initTrack(Track* track, std::vector<Track*>& tracksToRemove)
{
if (track->isInitialized())
return;
uint32_t channelMask = audio_channel_out_mask_from_count(2);
int32_t name = _mixer->getTrackName(channelMask, AUDIO_FORMAT_PCM_16_BIT,
AUDIO_SESSION_OUTPUT_MIX);
if (name < 0)
{
// If we could not get the track name, it means that there're MAX_NUM_TRACKS tracks
// So ignore the new track.
tracksToRemove.push_back(track);
}
else
{
_mixer->setBufferProvider(name, track);
_mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
_mixingBuffer.buf);
_mixer->setParameter(
name,
AudioMixer::TRACK,
AudioMixer::MIXER_FORMAT,
(void *) (uintptr_t) AUDIO_FORMAT_PCM_16_BIT);
_mixer->setParameter(
name,
AudioMixer::TRACK,
AudioMixer::FORMAT,
(void *) (uintptr_t) AUDIO_FORMAT_PCM_16_BIT);
_mixer->setParameter(
name,
AudioMixer::TRACK,
AudioMixer::MIXER_CHANNEL_MASK,
(void *) (uintptr_t) channelMask);
_mixer->setParameter(
name,
AudioMixer::TRACK,
AudioMixer::CHANNEL_MASK,
(void *) (uintptr_t) channelMask);
track->setName(name);
_mixer->enable(name);
std::lock_guard<std::mutex> lk(track->_volumeDirtyMutex);
gain_minifloat_packed_t volume = track->getVolumeLR();
float lVolume = float_from_gain(gain_minifloat_unpack_left(volume));
float rVolume = float_from_gain(gain_minifloat_unpack_right(volume));
_mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &lVolume);
_mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &rVolume);
track->setVolumeDirty(false);
track->setInitialized(true);
}
}
void AudioMixerController::mixOneFrame()
{
_isMixingFrame = true;
_activeTracksMutex.lock();
auto mixStart = clockNow();
std::vector<Track*> tracksToRemove;
tracksToRemove.reserve(_activeTracks.size());
// FOR TESTING BEGIN
// Track* track = _activeTracks[0];
//
// AudioBufferProvider::Buffer buffer;
// buffer.frameCount = _bufferSizeInFrames;
// status_t r = track->getNextBuffer(&buffer);
//// ALOG_ASSERT(buffer.frameCount == _mixing->size / 2, "buffer.frameCount:%d, _mixing->size/2:%d", buffer.frameCount, _mixing->size/2);
// if (r == NO_ERROR)
// {
// ALOGV("getNextBuffer succeed ...");
// memcpy(_mixing->buf, buffer.raw, _mixing->size);
// }
// if (buffer.raw == nullptr)
// {
// ALOGV("Play over ...");
// tracksToRemove.push_back(track);
// }
// else
// {
// track->releaseBuffer(&buffer);
// }
//
// _mixing->state = BufferState::FULL;
// _activeTracksMutex.unlock();
// FOR TESTING END
Track::State state;
// set up the tracks.
for (auto&& track : _activeTracks)
{
state = track->getState();
if (state == Track::State::PLAYING)
{
initTrack(track, tracksToRemove);
int name = track->getName();
ALOG_ASSERT(name >= 0);
std::lock_guard<std::mutex> lk(track->_volumeDirtyMutex);
if (track->isVolumeDirty())
{
gain_minifloat_packed_t volume = track->getVolumeLR();
float lVolume = float_from_gain(gain_minifloat_unpack_left(volume));
float rVolume = float_from_gain(gain_minifloat_unpack_right(volume));
ALOGV("Track (name: %d)'s volume is dirty, update volume to L: %f, R: %f", name, lVolume, rVolume);
_mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &lVolume);
_mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &rVolume);
track->setVolumeDirty(false);
}
}
else if (state == Track::State::RESUMED)
{
initTrack(track, tracksToRemove);
if (track->getPrevState() == Track::State::PAUSED)
{
_mixer->enable(track->getName());
track->setState(Track::State::PLAYING);
}
else
{
ALOGW("Previous state (%d) isn't PAUSED, couldn't resume!", static_cast<int>(track->getPrevState()));
}
}
else if (state == Track::State::PAUSED)
{
initTrack(track, tracksToRemove);
if (track->getPrevState() == Track::State::PLAYING || track->getPrevState() == Track::State::RESUMED)
{
_mixer->disable(track->getName());
}
else
{
ALOGW("Previous state (%d) isn't PLAYING, couldn't pause!", static_cast<int>(track->getPrevState()));
}
}
else if (state == Track::State::STOPPED)
{
if (track->isInitialized())
{
_mixer->deleteTrackName(track->getName());
}
else
{
ALOGV("Track (%p) hasn't been initialized yet!", track);
}
tracksToRemove.push_back(track);
}
if (track->isPlayOver())
{
if (track->isLoop())
{
track->reset();
}
else
{
ALOGV("Play over ...");
_mixer->deleteTrackName(track->getName());
tracksToRemove.push_back(track);
track->setState(Track::State::OVER);
}
}
}
bool hasAvailableTracks = _activeTracks.size() - tracksToRemove.size() > 0;
if (hasAvailableTracks)
{
ALOGV_IF(_activeTracks.size() > 8, "More than 8 active tracks: %d", (int) _activeTracks.size());
_mixer->process(AudioBufferProvider::kInvalidPTS);
}
else
{
ALOGV("Doesn't have enough tracks: %d, %d", (int) _activeTracks.size(), (int) tracksToRemove.size());
}
// Remove stopped or playover tracks for active tracks container
for (auto&& track : tracksToRemove)
{
removeItemFromVector(_activeTracks, track);
if (track != nullptr && track->onStateChanged != nullptr)
{
track->onStateChanged(Track::State::DESTROYED);
}
else
{
ALOGE("track (%p) was released ...", track);
}
}
_activeTracksMutex.unlock();
auto mixEnd = clockNow();
float mixInterval = intervalInMS(mixStart, mixEnd);
ALOGV_IF(mixInterval > 1.0f, "Mix a frame waste: %fms", mixInterval);
_isMixingFrame = false;
}
void AudioMixerController::destroy()
{
while (_isMixingFrame)
{
usleep(10);
}
usleep(2000); // Wait for more 2ms
}
void AudioMixerController::pause()
{
_isPaused = true;
}
void AudioMixerController::resume()
{
_isPaused = false;
}
bool AudioMixerController::hasPlayingTacks()
{
std::lock_guard<std::mutex> lk (_activeTracksMutex);
if (_activeTracks.empty())
return false;
for (auto&& track : _activeTracks)
{
Track::State state = track->getState();
if (state == Track::State::IDLE || state == Track::State::PLAYING || state == Track::State::RESUMED)
{
return true;
}
}
return false;
}
} // namespace cocos2d {

View File

@ -0,0 +1,88 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/utils/Errors.h"
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <vector>
namespace cocos2d {
class Track;
class AudioMixer;
class AudioMixerController
{
public:
struct OutputBuffer
{
void* buf;
size_t size;
};
AudioMixerController(int bufferSizeInFrames, int sampleRate, int channelCount);
~AudioMixerController();
bool init();
bool addTrack(Track* track);
bool hasPlayingTacks();
void pause();
void resume();
inline bool isPaused() const { return _isPaused; };
void mixOneFrame();
inline OutputBuffer* current() { return &_mixingBuffer; }
private:
void destroy();
void initTrack(Track* track, std::vector<Track*>& tracksToRemove);
private:
int _bufferSizeInFrames;
int _sampleRate;
int _channelCount;
AudioMixer* _mixer;
std::mutex _activeTracksMutex;
std::vector<Track*> _activeTracks;
OutputBuffer _mixingBuffer;
std::atomic_bool _isPaused;
std::atomic_bool _isMixingFrame;
};
} // namespace cocos2d {

View File

@ -0,0 +1,450 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "audio/android/cutils/log.h"
namespace cocos2d {
/* Behavior of is_same<>::value is true if the types are identical,
* false otherwise. Identical to the STL std::is_same.
*/
template<typename T, typename U>
struct is_same
{
static const bool value = false;
};
template<typename T>
struct is_same<T, T> // partial specialization
{
static const bool value = true;
};
/* MixMul is a multiplication operator to scale an audio input signal
* by a volume gain, with the formula:
*
* O(utput) = I(nput) * V(olume)
*
* The output, input, and volume may have different types.
* There are 27 variants, of which 14 are actually defined in an
* explicitly templated class.
*
* The following type variables and the underlying meaning:
*
* Output type TO: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1]
* Input signal type TI: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1]
* Volume type TV: int32_t (U4.28) or int16_t (U4.12) or float [-1,1]
*
* For high precision audio, only the <TO, TI, TV> = <float, float, float>
* needs to be accelerated. This is perhaps the easiest form to do quickly as well.
*
* A generic version is NOT defined to catch any mistake of using it.
*/
template <typename TO, typename TI, typename TV>
TO MixMul(TI value, TV volume);
template <>
inline int32_t MixMul<int32_t, int16_t, int16_t>(int16_t value, int16_t volume) {
return value * volume;
}
template <>
inline int32_t MixMul<int32_t, int32_t, int16_t>(int32_t value, int16_t volume) {
return (value >> 12) * volume;
}
template <>
inline int32_t MixMul<int32_t, int16_t, int32_t>(int16_t value, int32_t volume) {
return value * (volume >> 16);
}
template <>
inline int32_t MixMul<int32_t, int32_t, int32_t>(int32_t value, int32_t volume) {
return (value >> 12) * (volume >> 16);
}
template <>
inline float MixMul<float, float, int16_t>(float value, int16_t volume) {
static const float norm = 1. / (1 << 12);
return value * volume * norm;
}
template <>
inline float MixMul<float, float, int32_t>(float value, int32_t volume) {
static const float norm = 1. / (1 << 28);
return value * volume * norm;
}
template <>
inline int16_t MixMul<int16_t, float, int16_t>(float value, int16_t volume) {
return clamp16_from_float(MixMul<float, float, int16_t>(value, volume));
}
template <>
inline int16_t MixMul<int16_t, float, int32_t>(float value, int32_t volume) {
return clamp16_from_float(MixMul<float, float, int32_t>(value, volume));
}
template <>
inline float MixMul<float, int16_t, int16_t>(int16_t value, int16_t volume) {
static const float norm = 1. / (1 << (15 + 12));
return static_cast<float>(value) * static_cast<float>(volume) * norm;
}
template <>
inline float MixMul<float, int16_t, int32_t>(int16_t value, int32_t volume) {
static const float norm = 1. / (1ULL << (15 + 28));
return static_cast<float>(value) * static_cast<float>(volume) * norm;
}
template <>
inline int16_t MixMul<int16_t, int16_t, int16_t>(int16_t value, int16_t volume) {
return clamp16(MixMul<int32_t, int16_t, int16_t>(value, volume) >> 12);
}
template <>
inline int16_t MixMul<int16_t, int32_t, int16_t>(int32_t value, int16_t volume) {
return clamp16(MixMul<int32_t, int32_t, int16_t>(value, volume) >> 12);
}
template <>
inline int16_t MixMul<int16_t, int16_t, int32_t>(int16_t value, int32_t volume) {
return clamp16(MixMul<int32_t, int16_t, int32_t>(value, volume) >> 12);
}
template <>
inline int16_t MixMul<int16_t, int32_t, int32_t>(int32_t value, int32_t volume) {
return clamp16(MixMul<int32_t, int32_t, int32_t>(value, volume) >> 12);
}
/* Required for floating point volume. Some are needed for compilation but
* are not needed in execution and should be removed from the final build by
* an optimizing compiler.
*/
template <>
inline float MixMul<float, float, float>(float value, float volume) {
return value * volume;
}
template <>
inline float MixMul<float, int16_t, float>(int16_t value, float volume) {
static const float float_from_q_15 = 1. / (1 << 15);
return value * volume * float_from_q_15;
}
template <>
inline int32_t MixMul<int32_t, int32_t, float>(int32_t value, float volume) {
LOG_ALWAYS_FATAL("MixMul<int32_t, int32_t, float> Runtime Should not be here");
return value * volume;
}
template <>
inline int32_t MixMul<int32_t, int16_t, float>(int16_t value, float volume) {
LOG_ALWAYS_FATAL("MixMul<int32_t, int16_t, float> Runtime Should not be here");
static const float u4_12_from_float = (1 << 12);
return value * volume * u4_12_from_float;
}
template <>
inline int16_t MixMul<int16_t, int16_t, float>(int16_t value, float volume) {
LOG_ALWAYS_FATAL("MixMul<int16_t, int16_t, float> Runtime Should not be here");
return clamp16_from_float(MixMul<float, int16_t, float>(value, volume));
}
template <>
inline int16_t MixMul<int16_t, float, float>(float value, float volume) {
return clamp16_from_float(value * volume);
}
/*
* MixAccum is used to add into an accumulator register of a possibly different
* type. The TO and TI types are the same as MixMul.
*/
template <typename TO, typename TI>
inline void MixAccum(TO *auxaccum, TI value) {
if (!is_same<TO, TI>::value) {
LOG_ALWAYS_FATAL("MixAccum type not properly specialized: %zu %zu\n",
sizeof(TO), sizeof(TI));
}
*auxaccum += value;
}
template<>
inline void MixAccum<float, int16_t>(float *auxaccum, int16_t value) {
static const float norm = 1. / (1 << 15);
*auxaccum += norm * value;
}
template<>
inline void MixAccum<float, int32_t>(float *auxaccum, int32_t value) {
static const float norm = 1. / (1 << 27);
*auxaccum += norm * value;
}
template<>
inline void MixAccum<int32_t, int16_t>(int32_t *auxaccum, int16_t value) {
*auxaccum += value << 12;
}
template<>
inline void MixAccum<int32_t, float>(int32_t *auxaccum, float value) {
*auxaccum += clampq4_27_from_float(value);
}
/* MixMulAux is just like MixMul except it combines with
* an accumulator operation MixAccum.
*/
template <typename TO, typename TI, typename TV, typename TA>
inline TO MixMulAux(TI value, TV volume, TA *auxaccum) {
MixAccum<TA, TI>(auxaccum, value);
return MixMul<TO, TI, TV>(value, volume);
}
/* MIXTYPE is used to determine how the samples in the input frame
* are mixed with volume gain into the output frame.
* See the volumeRampMulti functions below for more details.
*/
enum {
MIXTYPE_MULTI,
MIXTYPE_MONOEXPAND,
MIXTYPE_MULTI_SAVEONLY,
MIXTYPE_MULTI_MONOVOL,
MIXTYPE_MULTI_SAVEONLY_MONOVOL,
};
/*
* The volumeRampMulti and volumeRamp functions take a MIXTYPE
* which indicates the per-frame mixing and accumulation strategy.
*
* MIXTYPE_MULTI:
* NCHAN represents number of input and output channels.
* TO: int32_t (Q4.27) or float
* TI: int32_t (Q4.27) or int16_t (Q0.15) or float
* TV: int32_t (U4.28) or int16_t (U4.12) or float
* vol: represents a volume array.
*
* This accumulates into the out pointer.
*
* MIXTYPE_MONOEXPAND:
* Single input channel. NCHAN represents number of output channels.
* TO: int32_t (Q4.27) or float
* TI: int32_t (Q4.27) or int16_t (Q0.15) or float
* TV: int32_t (U4.28) or int16_t (U4.12) or float
* Input channel count is 1.
* vol: represents volume array.
*
* This accumulates into the out pointer.
*
* MIXTYPE_MULTI_SAVEONLY:
* NCHAN represents number of input and output channels.
* TO: int16_t (Q.15) or float
* TI: int32_t (Q4.27) or int16_t (Q0.15) or float
* TV: int32_t (U4.28) or int16_t (U4.12) or float
* vol: represents a volume array.
*
* MIXTYPE_MULTI_SAVEONLY does not accumulate into the out pointer.
*
* MIXTYPE_MULTI_MONOVOL:
* Same as MIXTYPE_MULTI, but uses only volume[0].
*
* MIXTYPE_MULTI_SAVEONLY_MONOVOL:
* Same as MIXTYPE_MULTI_SAVEONLY, but uses only volume[0].
*
*/
template <int MIXTYPE, int NCHAN,
typename TO, typename TI, typename TV, typename TA, typename TAV>
inline void volumeRampMulti(TO* out, size_t frameCount,
const TI* in, TA* aux, TV *vol, const TV *volinc, TAV *vola, TAV volainc)
{
#ifdef ALOGVV
ALOGVV("volumeRampMulti, MIXTYPE:%d\n", MIXTYPE);
#endif
if (aux != NULL) {
do {
TA auxaccum = 0;
switch (MIXTYPE) {
case MIXTYPE_MULTI:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
vol[i] += volinc[i];
}
break;
case MIXTYPE_MONOEXPAND:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
vol[i] += volinc[i];
}
in++;
break;
case MIXTYPE_MULTI_SAVEONLY:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
vol[i] += volinc[i];
}
break;
case MIXTYPE_MULTI_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
}
vol[0] += volinc[0];
break;
case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
}
vol[0] += volinc[0];
break;
default:
LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
break;
}
auxaccum /= NCHAN;
*aux++ += MixMul<TA, TA, TAV>(auxaccum, *vola);
vola[0] += volainc;
} while (--frameCount);
} else {
do {
switch (MIXTYPE) {
case MIXTYPE_MULTI:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
vol[i] += volinc[i];
}
break;
case MIXTYPE_MONOEXPAND:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMul<TO, TI, TV>(*in, vol[i]);
vol[i] += volinc[i];
}
in++;
break;
case MIXTYPE_MULTI_SAVEONLY:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
vol[i] += volinc[i];
}
break;
case MIXTYPE_MULTI_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMul<TO, TI, TV>(*in++, vol[0]);
}
vol[0] += volinc[0];
break;
case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMul<TO, TI, TV>(*in++, vol[0]);
}
vol[0] += volinc[0];
break;
default:
LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
break;
}
} while (--frameCount);
}
}
template <int MIXTYPE, int NCHAN,
typename TO, typename TI, typename TV, typename TA, typename TAV>
inline void volumeMulti(TO* out, size_t frameCount,
const TI* in, TA* aux, const TV *vol, TAV vola)
{
#ifdef ALOGVV
ALOGVV("volumeMulti MIXTYPE:%d\n", MIXTYPE);
#endif
if (aux != NULL) {
do {
TA auxaccum = 0;
switch (MIXTYPE) {
case MIXTYPE_MULTI:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
}
break;
case MIXTYPE_MONOEXPAND:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
}
in++;
break;
case MIXTYPE_MULTI_SAVEONLY:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
}
break;
case MIXTYPE_MULTI_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
}
break;
case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
}
break;
default:
LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
break;
}
auxaccum /= NCHAN;
*aux++ += MixMul<TA, TA, TAV>(auxaccum, vola);
} while (--frameCount);
} else {
do {
switch (MIXTYPE) {
case MIXTYPE_MULTI:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
}
break;
case MIXTYPE_MONOEXPAND:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMul<TO, TI, TV>(*in, vol[i]);
}
in++;
break;
case MIXTYPE_MULTI_SAVEONLY:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
}
break;
case MIXTYPE_MULTI_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMul<TO, TI, TV>(*in++, vol[0]);
}
break;
case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMul<TO, TI, TV>(*in++, vol[0]);
}
break;
default:
LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
break;
}
} while (--frameCount);
}
}
} // namespace cocos2d {

View File

@ -0,0 +1,529 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioPlayerProvider"
#include "base/CCThreadPool.h"
#include "audio/android/AudioPlayerProvider.h"
#include "audio/android/UrlAudioPlayer.h"
#include "audio/android/PcmAudioPlayer.h"
#include "audio/android/AudioDecoder.h"
#include "audio/android/AudioDecoderProvider.h"
#include "audio/android/AudioMixerController.h"
#include "audio/android/PcmAudioService.h"
#include "audio/android/ICallerThreadUtils.h"
#include "audio/android/utils/Utils.h"
#include <sys/system_properties.h>
#include <stdlib.h>
#include <algorithm> // for std::find_if
namespace cocos2d {
static int getSystemAPILevel()
{
static int __systemApiLevel = -1;
if (__systemApiLevel > 0)
{
return __systemApiLevel;
}
int apiLevel = getSDKVersion();
if (apiLevel > 0)
{
ALOGD("Android API level: %d", apiLevel);
}
else
{
ALOGE("Fail to get Android API level!");
}
__systemApiLevel = apiLevel;
return apiLevel;
}
struct AudioFileIndicator
{
std::string extension;
int smallSizeIndicator;
};
static AudioFileIndicator __audioFileIndicator[] = {
{"default", 128000}, // If we could not handle the audio format, return default value, the position should be first.
{".wav", 1024000},
{".ogg", 128000},
{".mp3", 160000}
};
AudioPlayerProvider::AudioPlayerProvider(SLEngineItf engineItf, SLObjectItf outputMixObject,
int deviceSampleRate, int bufferSizeInFrames,
const FdGetterCallback &fdGetterCallback,
ICallerThreadUtils* callerThreadUtils)
: _engineItf(engineItf), _outputMixObject(outputMixObject),
_deviceSampleRate(deviceSampleRate), _bufferSizeInFrames(bufferSizeInFrames),
_fdGetterCallback(fdGetterCallback), _callerThreadUtils(callerThreadUtils),
_pcmAudioService(nullptr), _mixController(nullptr),
_threadPool(ThreadPool::newCachedThreadPool(1, 8, 5, 2, 2))
{
ALOGI("deviceSampleRate: %d, bufferSizeInFrames: %d", _deviceSampleRate, _bufferSizeInFrames);
if (getSystemAPILevel() >= 17)
{
_mixController = new (std::nothrow) AudioMixerController(_bufferSizeInFrames, _deviceSampleRate, 2);
_mixController->init();
_pcmAudioService = new (std::nothrow) PcmAudioService(engineItf, outputMixObject);
_pcmAudioService->init(_mixController, 2, deviceSampleRate, bufferSizeInFrames * 2);
}
ALOG_ASSERT(callerThreadUtils != nullptr, "Caller thread utils parameter should not be nullptr!");
}
AudioPlayerProvider::~AudioPlayerProvider()
{
ALOGV("~AudioPlayerProvider()");
UrlAudioPlayer::stopAll();
SL_SAFE_DELETE(_pcmAudioService);
SL_SAFE_DELETE(_mixController);
SL_SAFE_DELETE(_threadPool);
}
IAudioPlayer *AudioPlayerProvider::getAudioPlayer(const std::string &audioFilePath)
{
// Pcm data decoding by OpenSLES API only supports in API level 17 and later.
if (getSystemAPILevel() < 17)
{
AudioFileInfo info = getFileInfo(audioFilePath);
if (info.isValid())
{
return createUrlAudioPlayer(info);
}
return nullptr;
}
IAudioPlayer *player = nullptr;
_pcmCacheMutex.lock();
auto iter = _pcmCache.find(audioFilePath);
if (iter != _pcmCache.end())
{// Found pcm cache means it was used to be a PcmAudioService
PcmData pcmData = iter->second;
_pcmCacheMutex.unlock();
player = obtainPcmAudioPlayer(audioFilePath, pcmData);
ALOGV_IF(player == nullptr, "%s, %d: player is nullptr, path: %s", __FUNCTION__, __LINE__, audioFilePath.c_str());
}
else
{
_pcmCacheMutex.unlock();
// Check audio file size to determine to use a PcmAudioService or UrlAudioPlayer,
// generally PcmAudioService is used for playing short audio like game effects while
// playing background music uses UrlAudioPlayer
AudioFileInfo info = getFileInfo(audioFilePath);
if (info.isValid())
{
if (isSmallFile(info))
{
// Put an empty lambda to preloadEffect since we only want the future object to get PcmData
auto pcmData = std::make_shared<PcmData>();
auto isSucceed = std::make_shared<bool>(false);
auto isReturnFromCache = std::make_shared<bool>(false);
auto isPreloadFinished = std::make_shared<bool>(false);
std::thread::id threadId = std::this_thread::get_id();
void* infoPtr = &info;
std::string url = info.url;
preloadEffect(info, [infoPtr, url, threadId, pcmData, isSucceed, isReturnFromCache, isPreloadFinished](bool succeed, PcmData data){
// If the callback is in the same thread as caller's, it means that we found it
// in the cache
*isReturnFromCache = std::this_thread::get_id() == threadId;
*pcmData = data;
*isSucceed = succeed;
*isPreloadFinished = true;
ALOGV("FileInfo (%p), Set isSucceed flag: %d, path: %s", infoPtr, succeed, url.c_str());
}, true);
if (!*isReturnFromCache && !*isPreloadFinished)
{
std::unique_lock<std::mutex> lk(_preloadWaitMutex);
// Wait for 2 seconds for the decoding in sub thread finishes.
ALOGV("FileInfo (%p), Waiting preload (%s) to finish ...", &info, audioFilePath.c_str());
_preloadWaitCond.wait_for(lk, std::chrono::seconds(2));
ALOGV("FileInfo (%p), Waitup preload (%s) ...", &info, audioFilePath.c_str());
}
if (*isSucceed)
{
if (pcmData->isValid())
{
player = obtainPcmAudioPlayer(info.url, *pcmData);
ALOGV_IF(player == nullptr, "%s, %d: player is nullptr, path: %s", __FUNCTION__, __LINE__, audioFilePath.c_str());
}
else
{
ALOGE("pcm data is invalid, path: %s", audioFilePath.c_str());
}
}
else
{
ALOGE("FileInfo (%p), preloadEffect (%s) failed", &info, audioFilePath.c_str());
}
}
else
{
player = createUrlAudioPlayer(info);
ALOGV_IF(player == nullptr, "%s, %d: player is nullptr, path: %s", __FUNCTION__, __LINE__, audioFilePath.c_str());
}
}
else
{
ALOGE("File info is invalid, path: %s", audioFilePath.c_str());
}
}
ALOGV_IF(player == nullptr, "%s, %d return nullptr", __FUNCTION__, __LINE__);
return player;
}
void AudioPlayerProvider::preloadEffect(const std::string &audioFilePath, const PreloadCallback& cb)
{
// Pcm data decoding by OpenSLES API only supports in API level 17 and later.
if (getSystemAPILevel() < 17)
{
PcmData data;
cb(true, data);
return;
}
_pcmCacheMutex.lock();
auto&& iter = _pcmCache.find(audioFilePath);
if (iter != _pcmCache.end())
{
ALOGV("preload return from cache: (%s)", audioFilePath.c_str());
_pcmCacheMutex.unlock();
cb(true, iter->second);
return;
}
_pcmCacheMutex.unlock();
auto info = getFileInfo(audioFilePath);
preloadEffect(info, [this, cb, audioFilePath](bool succeed, PcmData data){
_callerThreadUtils->performFunctionInCallerThread([this, succeed, data, cb](){
cb(succeed, data);
});
}, false);
}
// Used internally
void AudioPlayerProvider::preloadEffect(const AudioFileInfo &info, const PreloadCallback& cb, bool isPreloadInPlay2d)
{
PcmData pcmData;
if (!info.isValid())
{
cb(false, pcmData);
return;
}
if (isSmallFile(info))
{
std::string audioFilePath = info.url;
// 1. First time check, if it wasn't in the cache, goto 2 step
_pcmCacheMutex.lock();
auto&& iter = _pcmCache.find(audioFilePath);
if (iter != _pcmCache.end())
{
ALOGV("1. Return pcm data from cache, url: %s", info.url.c_str());
_pcmCacheMutex.unlock();
cb(true, iter->second);
return;
}
_pcmCacheMutex.unlock();
{
// 2. Check whether the audio file is being preloaded, if it has been removed from map just now,
// goto step 3
std::lock_guard<std::mutex> lk(_preloadCallbackMutex);
auto&& preloadIter = _preloadCallbackMap.find(audioFilePath);
if (preloadIter != _preloadCallbackMap.end())
{
ALOGV("audio (%s) is being preloaded, add to callback vector!", audioFilePath.c_str());
PreloadCallbackParam param;
param.callback = cb;
param.isPreloadInPlay2d = isPreloadInPlay2d;
preloadIter->second.push_back(std::move(param));
return;
}
// 3. Check it in cache again. If it has been removed from map just now, the file is in
// the cache absolutely.
_pcmCacheMutex.lock();
auto&& iter = _pcmCache.find(audioFilePath);
if (iter != _pcmCache.end())
{
ALOGV("2. Return pcm data from cache, url: %s", info.url.c_str());
_pcmCacheMutex.unlock();
cb(true, iter->second);
return;
}
_pcmCacheMutex.unlock();
PreloadCallbackParam param;
param.callback = cb;
param.isPreloadInPlay2d = isPreloadInPlay2d;
std::vector<PreloadCallbackParam> callbacks;
callbacks.push_back(std::move(param));
_preloadCallbackMap.insert(std::make_pair(audioFilePath, std::move(callbacks)));
}
_threadPool->pushTask([this, audioFilePath](int tid) {
ALOGV("AudioPlayerProvider::preloadEffect: (%s)", audioFilePath.c_str());
PcmData d;
AudioDecoder* decoder = AudioDecoderProvider::createAudioDecoder(_engineItf, audioFilePath, _bufferSizeInFrames, _deviceSampleRate, _fdGetterCallback);
bool ret = decoder != nullptr && decoder->start();
if (ret)
{
d = decoder->getResult();
std::lock_guard<std::mutex> lk(_pcmCacheMutex);
_pcmCache.insert(std::make_pair(audioFilePath, d));
}
else
{
ALOGE("decode (%s) failed!", audioFilePath.c_str());
}
ALOGV("decode %s", (ret ? "succeed" : "failed"));
std::lock_guard<std::mutex> lk(_preloadCallbackMutex);
auto&& preloadIter = _preloadCallbackMap.find(audioFilePath);
if (preloadIter != _preloadCallbackMap.end())
{
auto&& params = preloadIter->second;
ALOGV("preload (%s) callback count: %d", audioFilePath.c_str(), (int)params.size());
PcmData result = decoder->getResult();
for (auto&& param : params)
{
param.callback(ret, result);
if (param.isPreloadInPlay2d)
{
_preloadWaitCond.notify_one();
}
}
_preloadCallbackMap.erase(preloadIter);
}
AudioDecoderProvider::destroyAudioDecoder(&decoder);
});
}
else
{
ALOGV("File (%s) is too large, ignore preload!", info.url.c_str());
cb(true, pcmData);
}
}
AudioPlayerProvider::AudioFileInfo AudioPlayerProvider::getFileInfo(
const std::string &audioFilePath)
{
AudioFileInfo info;
long fileSize = 0;
off_t start = 0, length = 0;
int assetFd = -1;
if (audioFilePath[0] != '/')
{
std::string relativePath;
size_t position = audioFilePath.find("@assets/");
if (0 == position)
{
// "@assets/" is at the beginning of the path and we don't want it
relativePath = audioFilePath.substr(strlen("@assets/"));
}
else
{
relativePath = audioFilePath;
}
assetFd = _fdGetterCallback(relativePath, &start, &length);
if (assetFd <= 0)
{
ALOGE("Failed to open file descriptor for '%s'", audioFilePath.c_str());
return info;
}
fileSize = length;
}
else
{
FILE *fp = fopen(audioFilePath.c_str(), "rb");
if (fp != nullptr)
{
fseek(fp, 0, SEEK_END);
fileSize = ftell(fp);
fclose(fp);
}
else
{
return info;
}
}
info.url = audioFilePath;
info.assetFd = std::make_shared<AssetFd>(assetFd);
info.start = start;
info.length = fileSize;
ALOGV("(%s) file size: %ld", audioFilePath.c_str(), fileSize);
return info;
}
bool AudioPlayerProvider::isSmallFile(const AudioFileInfo &info)
{
//REFINE: If file size is smaller than 100k, we think it's a small file. This value should be set by developers.
AudioFileInfo &audioFileInfo = const_cast<AudioFileInfo &>(info);
size_t judgeCount = sizeof(__audioFileIndicator) / sizeof(__audioFileIndicator[0]);
size_t pos = audioFileInfo.url.rfind(".");
std::string extension;
if (pos != std::string::npos)
{
extension = audioFileInfo.url.substr(pos);
}
auto iter = std::find_if(std::begin(__audioFileIndicator), std::end(__audioFileIndicator),
[&extension](const AudioFileIndicator &judge) -> bool {
return judge.extension == extension;
});
if (iter != std::end(__audioFileIndicator))
{
// ALOGV("isSmallFile: found: %s: ", iter->extension.c_str());
return info.length < iter->smallSizeIndicator;
}
// ALOGV("isSmallFile: not found return default value");
return info.length < __audioFileIndicator[0].smallSizeIndicator;
}
float AudioPlayerProvider::getDurationFromFile(const std::string &filePath)
{
std::lock_guard<std::mutex> lk(_pcmCacheMutex);
auto iter = _pcmCache.find(filePath);
if (iter != _pcmCache.end()){
return iter->second.duration;
}
return 0;
}
void AudioPlayerProvider::clearPcmCache(const std::string &audioFilePath)
{
std::lock_guard<std::mutex> lk(_pcmCacheMutex);
auto iter = _pcmCache.find(audioFilePath);
if (iter != _pcmCache.end())
{
ALOGV("clear pcm cache: (%s)", audioFilePath.c_str());
_pcmCache.erase(iter);
}
else
{
ALOGW("Couldn't find the pcm cache: (%s)", audioFilePath.c_str());
}
}
void AudioPlayerProvider::clearAllPcmCaches()
{
std::lock_guard<std::mutex> lk(_pcmCacheMutex);
_pcmCache.clear();
}
PcmAudioPlayer *AudioPlayerProvider::obtainPcmAudioPlayer(const std::string &url,
const PcmData &pcmData)
{
PcmAudioPlayer *pcmPlayer = nullptr;
if (pcmData.isValid())
{
pcmPlayer = new(std::nothrow) PcmAudioPlayer(_mixController, _callerThreadUtils);
if (pcmPlayer != nullptr)
{
pcmPlayer->prepare(url, pcmData);
}
}
else
{
ALOGE("obtainPcmAudioPlayer failed, pcmData isn't valid!");
}
return pcmPlayer;
}
UrlAudioPlayer *AudioPlayerProvider::createUrlAudioPlayer(
const AudioPlayerProvider::AudioFileInfo &info)
{
if (info.url.empty())
{
ALOGE("createUrlAudioPlayer failed, url is empty!");
return nullptr;
}
SLuint32 locatorType = info.assetFd->getFd() > 0 ? SL_DATALOCATOR_ANDROIDFD : SL_DATALOCATOR_URI;
auto urlPlayer = new (std::nothrow) UrlAudioPlayer(_engineItf, _outputMixObject, _callerThreadUtils);
bool ret = urlPlayer->prepare(info.url, locatorType, info.assetFd, info.start, info.length);
if (!ret)
{
SL_SAFE_DELETE(urlPlayer);
}
return urlPlayer;
}
void AudioPlayerProvider::pause()
{
if (_mixController != nullptr)
{
_mixController->pause();
}
if (_pcmAudioService != nullptr)
{
_pcmAudioService->pause();
}
}
void AudioPlayerProvider::resume()
{
if (_mixController != nullptr)
{
_mixController->resume();
}
if (_pcmAudioService != nullptr)
{
_pcmAudioService->resume();
}
}
} // namespace cocos2d {

View File

@ -0,0 +1,129 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/IAudioPlayer.h"
#include "audio/android/OpenSLHelper.h"
#include "audio/android/PcmData.h"
#include <unordered_map>
#include <memory>
#include <condition_variable>
namespace cocos2d {
// Manage PcmAudioPlayer& UrlAudioPlayer
class PcmAudioPlayer;
class PcmAudioService;
class UrlAudioPlayer;
class AudioMixerController;
class ICallerThreadUtils;
class AssetFd;
class ThreadPool;
class AudioPlayerProvider
{
public:
AudioPlayerProvider(SLEngineItf engineItf, SLObjectItf outputMixObject, int deviceSampleRate,
int bufferSizeInFrames, const FdGetterCallback &fdGetterCallback,
ICallerThreadUtils* callerThreadUtils);
virtual ~AudioPlayerProvider();
IAudioPlayer *getAudioPlayer(const std::string &audioFilePath);
typedef std::function<void(bool/* succeed */, PcmData /* data */)> PreloadCallback;
void preloadEffect(const std::string &audioFilePath, const PreloadCallback& cb);
float getDurationFromFile(const std::string &filePath);
void clearPcmCache(const std::string &audioFilePath);
void clearAllPcmCaches();
void pause();
void resume();
private:
struct AudioFileInfo
{
std::string url;
std::shared_ptr<AssetFd> assetFd;
off_t start;
off_t length;
AudioFileInfo()
: assetFd(nullptr), start(0), length(0)
{ };
inline bool isValid() const
{
return !url.empty() && length > 0;
}
};
PcmAudioPlayer *obtainPcmAudioPlayer(const std::string &url, const PcmData &pcmData);
UrlAudioPlayer *createUrlAudioPlayer(const AudioFileInfo &info);
void preloadEffect(const AudioFileInfo &info, const PreloadCallback& cb, bool isPreloadInPlay2d);
AudioFileInfo getFileInfo(const std::string &audioFilePath);
bool isSmallFile(const AudioFileInfo &info);
private:
SLEngineItf _engineItf;
SLObjectItf _outputMixObject;
int _deviceSampleRate;
int _bufferSizeInFrames;
FdGetterCallback _fdGetterCallback;
ICallerThreadUtils* _callerThreadUtils;
std::unordered_map<std::string, PcmData> _pcmCache;
std::mutex _pcmCacheMutex;
struct PreloadCallbackParam
{
PreloadCallback callback;
bool isPreloadInPlay2d;
};
std::unordered_map<std::string, std::vector<PreloadCallbackParam>> _preloadCallbackMap;
std::mutex _preloadCallbackMutex;
std::mutex _preloadWaitMutex;
std::condition_variable _preloadWaitCond;
PcmAudioService* _pcmAudioService;
AudioMixerController *_mixController;
ThreadPool* _threadPool;
};
} // namespace cocos2d {

View File

@ -0,0 +1,788 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "AudioResampler"
//#define LOG_NDEBUG 0
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pthread.h>
#include <new>
#include "audio/android/cutils/log.h"
#include "audio/android/utils/Utils.h"
//#include <cutils/properties.h>
#include "audio/android/audio_utils/include/audio_utils/primitives.h"
#include "audio/android/AudioResampler.h"
//#include "audio/android/AudioResamplerSinc.h"
#include "audio/android/AudioResamplerCubic.h"
//#include "AudioResamplerDyn.h"
//cjh #ifdef __arm__
// #define ASM_ARM_RESAMP1 // enable asm optimisation for ResamplerOrder1
//#endif
namespace cocos2d {
// ----------------------------------------------------------------------------
class AudioResamplerOrder1 : public AudioResampler {
public:
AudioResamplerOrder1(int inChannelCount, int32_t sampleRate) :
AudioResampler(inChannelCount, sampleRate, LOW_QUALITY), mX0L(0), mX0R(0) {
}
virtual size_t resample(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider);
private:
// number of bits used in interpolation multiply - 15 bits avoids overflow
static const int kNumInterpBits = 15;
// bits to shift the phase fraction down to avoid overflow
static const int kPreInterpShift = kNumPhaseBits - kNumInterpBits;
void init() {}
size_t resampleMono16(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider);
size_t resampleStereo16(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider);
#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1
void AsmMono16Loop(int16_t *in, int32_t* maxOutPt, int32_t maxInIdx,
size_t &outputIndex, int32_t* out, size_t &inputIndex, int32_t vl, int32_t vr,
uint32_t &phaseFraction, uint32_t phaseIncrement);
void AsmStereo16Loop(int16_t *in, int32_t* maxOutPt, int32_t maxInIdx,
size_t &outputIndex, int32_t* out, size_t &inputIndex, int32_t vl, int32_t vr,
uint32_t &phaseFraction, uint32_t phaseIncrement);
#endif // ASM_ARM_RESAMP1
static inline int32_t Interp(int32_t x0, int32_t x1, uint32_t f) {
return x0 + (((x1 - x0) * (int32_t)(f >> kPreInterpShift)) >> kNumInterpBits);
}
static inline void Advance(size_t* index, uint32_t* frac, uint32_t inc) {
*frac += inc;
*index += (size_t)(*frac >> kNumPhaseBits);
*frac &= kPhaseMask;
}
int mX0L;
int mX0R;
};
/*static*/
const double AudioResampler::kPhaseMultiplier = 1L << AudioResampler::kNumPhaseBits;
bool AudioResampler::qualityIsSupported(src_quality quality)
{
switch (quality) {
case DEFAULT_QUALITY:
case LOW_QUALITY:
case MED_QUALITY:
case HIGH_QUALITY:
case VERY_HIGH_QUALITY:
return true;
default:
return false;
}
}
// ----------------------------------------------------------------------------
static pthread_once_t once_control = PTHREAD_ONCE_INIT;
static AudioResampler::src_quality defaultQuality = AudioResampler::DEFAULT_QUALITY;
void AudioResampler::init_routine()
{
// int resamplerQuality = getSystemProperty("af.resampler.quality");
// if (resamplerQuality > 0) {
// defaultQuality = (src_quality) resamplerQuality;
// ALOGD("forcing AudioResampler quality to %d", defaultQuality);
// if (defaultQuality < DEFAULT_QUALITY || defaultQuality > VERY_HIGH_QUALITY) {
// defaultQuality = DEFAULT_QUALITY;
// }
// }
}
uint32_t AudioResampler::qualityMHz(src_quality quality)
{
switch (quality) {
default:
case DEFAULT_QUALITY:
case LOW_QUALITY:
return 3;
case MED_QUALITY:
return 6;
case HIGH_QUALITY:
return 20;
case VERY_HIGH_QUALITY:
return 34;
// case DYN_LOW_QUALITY:
// return 4;
// case DYN_MED_QUALITY:
// return 6;
// case DYN_HIGH_QUALITY:
// return 12;
}
}
static const uint32_t maxMHz = 130; // an arbitrary number that permits 3 VHQ, should be tunable
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static uint32_t currentMHz = 0;
AudioResampler* AudioResampler::create(audio_format_t format, int inChannelCount,
int32_t sampleRate, src_quality quality) {
bool atFinalQuality;
if (quality == DEFAULT_QUALITY) {
// read the resampler default quality property the first time it is needed
int ok = pthread_once(&once_control, init_routine);
if (ok != 0) {
ALOGE("%s pthread_once failed: %d", __func__, ok);
}
quality = defaultQuality;
atFinalQuality = false;
} else {
atFinalQuality = true;
}
/* if the caller requests DEFAULT_QUALITY and af.resampler.property
* has not been set, the target resampler quality is set to DYN_MED_QUALITY,
* and allowed to "throttle" down to DYN_LOW_QUALITY if necessary
* due to estimated CPU load of having too many active resamplers
* (the code below the if).
*/
if (quality == DEFAULT_QUALITY) {
//cjh quality = DYN_MED_QUALITY;
}
// naive implementation of CPU load throttling doesn't account for whether resampler is active
pthread_mutex_lock(&mutex);
for (;;) {
uint32_t deltaMHz = qualityMHz(quality);
uint32_t newMHz = currentMHz + deltaMHz;
if ((qualityIsSupported(quality) && newMHz <= maxMHz) || atFinalQuality) {
ALOGV("resampler load %u -> %u MHz due to delta +%u MHz from quality %d",
currentMHz, newMHz, deltaMHz, quality);
currentMHz = newMHz;
break;
}
// not enough CPU available for proposed quality level, so try next lowest level
switch (quality) {
default:
case LOW_QUALITY:
atFinalQuality = true;
break;
case MED_QUALITY:
quality = LOW_QUALITY;
break;
case HIGH_QUALITY:
quality = MED_QUALITY;
break;
case VERY_HIGH_QUALITY:
quality = HIGH_QUALITY;
break;
// case DYN_LOW_QUALITY:
// atFinalQuality = true;
// break;
// case DYN_MED_QUALITY:
// quality = DYN_LOW_QUALITY;
// break;
// case DYN_HIGH_QUALITY:
// quality = DYN_MED_QUALITY;
// break;
}
}
pthread_mutex_unlock(&mutex);
AudioResampler* resampler;
switch (quality) {
default:
case LOW_QUALITY:
ALOGV("Create linear Resampler");
LOG_ALWAYS_FATAL_IF(format != AUDIO_FORMAT_PCM_16_BIT, "invalid pcm format");
resampler = new (std::nothrow) AudioResamplerOrder1(inChannelCount, sampleRate);
break;
case MED_QUALITY:
ALOGV("Create cubic Resampler");
LOG_ALWAYS_FATAL_IF(format != AUDIO_FORMAT_PCM_16_BIT, "invalid pcm format");
resampler = new (std::nothrow) AudioResamplerCubic(inChannelCount, sampleRate);
break;
case HIGH_QUALITY:
ALOGV("Create HIGH_QUALITY sinc Resampler");
LOG_ALWAYS_FATAL_IF(format != AUDIO_FORMAT_PCM_16_BIT, "invalid pcm format");
ALOG_ASSERT(false, "HIGH_QUALITY isn't supported");
// Cocos2d-x only uses MED_QUALITY, so we could remove Sinc relative files
// resampler = new (std::nothrow) AudioResamplerSinc(inChannelCount, sampleRate);
break;
case VERY_HIGH_QUALITY:
ALOGV("Create VERY_HIGH_QUALITY sinc Resampler = %d", quality);
LOG_ALWAYS_FATAL_IF(format != AUDIO_FORMAT_PCM_16_BIT, "invalid pcm format");
// Cocos2d-x only uses MED_QUALITY, so we could remove Sinc relative files
// resampler = new (std::nothrow) AudioResamplerSinc(inChannelCount, sampleRate, quality);
ALOG_ASSERT(false, "VERY_HIGH_QUALITY isn't supported");
break;
}
// initialize resampler
resampler->init();
return resampler;
}
AudioResampler::AudioResampler(int inChannelCount,
int32_t sampleRate, src_quality quality) :
mChannelCount(inChannelCount),
mSampleRate(sampleRate), mInSampleRate(sampleRate), mInputIndex(0),
mPhaseFraction(0), mLocalTimeFreq(0),
mPTS(AudioBufferProvider::kInvalidPTS), mQuality(quality) {
const int maxChannels = 2;//cjh quality < DYN_LOW_QUALITY ? 2 : 8;
if (inChannelCount < 1
|| inChannelCount > maxChannels) {
LOG_ALWAYS_FATAL("Unsupported sample format %d quality %d channels",
quality, inChannelCount);
}
if (sampleRate <= 0) {
LOG_ALWAYS_FATAL("Unsupported sample rate %d Hz", sampleRate);
}
// initialize common members
mVolume[0] = mVolume[1] = 0;
mBuffer.frameCount = 0;
}
AudioResampler::~AudioResampler() {
pthread_mutex_lock(&mutex);
src_quality quality = getQuality();
uint32_t deltaMHz = qualityMHz(quality);
int32_t newMHz = currentMHz - deltaMHz;
ALOGV("resampler load %u -> %d MHz due to delta -%u MHz from quality %d",
currentMHz, newMHz, deltaMHz, quality);
LOG_ALWAYS_FATAL_IF(newMHz < 0, "negative resampler load %d MHz", newMHz);
currentMHz = newMHz;
pthread_mutex_unlock(&mutex);
}
void AudioResampler::setSampleRate(int32_t inSampleRate) {
mInSampleRate = inSampleRate;
mPhaseIncrement = (uint32_t)((kPhaseMultiplier * inSampleRate) / mSampleRate);
}
void AudioResampler::setVolume(float left, float right) {
// REFINE: Implement anti-zipper filter
// convert to U4.12 for internal integer use (round down)
// integer volume values are clamped to 0 to UNITY_GAIN.
mVolume[0] = u4_12_from_float(clampFloatVol(left));
mVolume[1] = u4_12_from_float(clampFloatVol(right));
}
void AudioResampler::setLocalTimeFreq(uint64_t freq) {
mLocalTimeFreq = freq;
}
void AudioResampler::setPTS(int64_t pts) {
mPTS = pts;
}
int64_t AudioResampler::calculateOutputPTS(int outputFrameIndex) {
if (mPTS == AudioBufferProvider::kInvalidPTS) {
return AudioBufferProvider::kInvalidPTS;
} else {
return mPTS + ((outputFrameIndex * mLocalTimeFreq) / mSampleRate);
}
}
void AudioResampler::reset() {
mInputIndex = 0;
mPhaseFraction = 0;
mBuffer.frameCount = 0;
}
// ----------------------------------------------------------------------------
size_t AudioResamplerOrder1::resample(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider) {
// should never happen, but we overflow if it does
// ALOG_ASSERT(outFrameCount < 32767);
// select the appropriate resampler
switch (mChannelCount) {
case 1:
return resampleMono16(out, outFrameCount, provider);
case 2:
return resampleStereo16(out, outFrameCount, provider);
default:
LOG_ALWAYS_FATAL("invalid channel count: %d", mChannelCount);
return 0;
}
}
size_t AudioResamplerOrder1::resampleStereo16(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider) {
int32_t vl = mVolume[0];
int32_t vr = mVolume[1];
size_t inputIndex = mInputIndex;
uint32_t phaseFraction = mPhaseFraction;
uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
size_t outputSampleCount = outFrameCount * 2;
size_t inFrameCount = getInFrameCountRequired(outFrameCount);
// ALOGE("starting resample %d frames, inputIndex=%d, phaseFraction=%d, phaseIncrement=%d",
// outFrameCount, inputIndex, phaseFraction, phaseIncrement);
while (outputIndex < outputSampleCount) {
// buffer is empty, fetch a new one
while (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
provider->getNextBuffer(&mBuffer,
calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
goto resampleStereo16_exit;
}
// ALOGE("New buffer fetched: %d frames", mBuffer.frameCount);
if (mBuffer.frameCount > inputIndex) break;
inputIndex -= mBuffer.frameCount;
mX0L = mBuffer.i16[mBuffer.frameCount*2-2];
mX0R = mBuffer.i16[mBuffer.frameCount*2-1];
provider->releaseBuffer(&mBuffer);
// mBuffer.frameCount == 0 now so we reload a new buffer
}
int16_t *in = mBuffer.i16;
// handle boundary case
while (inputIndex == 0) {
// ALOGE("boundary case");
out[outputIndex++] += vl * Interp(mX0L, in[0], phaseFraction);
out[outputIndex++] += vr * Interp(mX0R, in[1], phaseFraction);
Advance(&inputIndex, &phaseFraction, phaseIncrement);
if (outputIndex == outputSampleCount) {
break;
}
}
// process input samples
// ALOGE("general case");
#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1
if (inputIndex + 2 < mBuffer.frameCount) {
int32_t* maxOutPt;
int32_t maxInIdx;
maxOutPt = out + (outputSampleCount - 2); // 2 because 2 frames per loop
maxInIdx = mBuffer.frameCount - 2;
AsmStereo16Loop(in, maxOutPt, maxInIdx, outputIndex, out, inputIndex, vl, vr,
phaseFraction, phaseIncrement);
}
#endif // ASM_ARM_RESAMP1
while (outputIndex < outputSampleCount && inputIndex < mBuffer.frameCount) {
out[outputIndex++] += vl * Interp(in[inputIndex*2-2],
in[inputIndex*2], phaseFraction);
out[outputIndex++] += vr * Interp(in[inputIndex*2-1],
in[inputIndex*2+1], phaseFraction);
Advance(&inputIndex, &phaseFraction, phaseIncrement);
}
// ALOGE("loop done - outputIndex=%d, inputIndex=%d", outputIndex, inputIndex);
// if done with buffer, save samples
if (inputIndex >= mBuffer.frameCount) {
inputIndex -= mBuffer.frameCount;
// ALOGE("buffer done, new input index %d", inputIndex);
mX0L = mBuffer.i16[mBuffer.frameCount*2-2];
mX0R = mBuffer.i16[mBuffer.frameCount*2-1];
provider->releaseBuffer(&mBuffer);
// verify that the releaseBuffer resets the buffer frameCount
// ALOG_ASSERT(mBuffer.frameCount == 0);
}
}
// ALOGE("output buffer full - outputIndex=%d, inputIndex=%d", outputIndex, inputIndex);
resampleStereo16_exit:
// save state
mInputIndex = inputIndex;
mPhaseFraction = phaseFraction;
return outputIndex / 2 /* channels for stereo */;
}
size_t AudioResamplerOrder1::resampleMono16(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider) {
int32_t vl = mVolume[0];
int32_t vr = mVolume[1];
size_t inputIndex = mInputIndex;
uint32_t phaseFraction = mPhaseFraction;
uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
size_t outputSampleCount = outFrameCount * 2;
size_t inFrameCount = getInFrameCountRequired(outFrameCount);
// ALOGE("starting resample %d frames, inputIndex=%d, phaseFraction=%d, phaseIncrement=%d",
// outFrameCount, inputIndex, phaseFraction, phaseIncrement);
while (outputIndex < outputSampleCount) {
// buffer is empty, fetch a new one
while (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
provider->getNextBuffer(&mBuffer,
calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
mInputIndex = inputIndex;
mPhaseFraction = phaseFraction;
goto resampleMono16_exit;
}
// ALOGE("New buffer fetched: %d frames", mBuffer.frameCount);
if (mBuffer.frameCount > inputIndex) break;
inputIndex -= mBuffer.frameCount;
mX0L = mBuffer.i16[mBuffer.frameCount-1];
provider->releaseBuffer(&mBuffer);
// mBuffer.frameCount == 0 now so we reload a new buffer
}
int16_t *in = mBuffer.i16;
// handle boundary case
while (inputIndex == 0) {
// ALOGE("boundary case");
int32_t sample = Interp(mX0L, in[0], phaseFraction);
out[outputIndex++] += vl * sample;
out[outputIndex++] += vr * sample;
Advance(&inputIndex, &phaseFraction, phaseIncrement);
if (outputIndex == outputSampleCount) {
break;
}
}
// process input samples
// ALOGE("general case");
#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1
if (inputIndex + 2 < mBuffer.frameCount) {
int32_t* maxOutPt;
int32_t maxInIdx;
maxOutPt = out + (outputSampleCount - 2);
maxInIdx = (int32_t)mBuffer.frameCount - 2;
AsmMono16Loop(in, maxOutPt, maxInIdx, outputIndex, out, inputIndex, vl, vr,
phaseFraction, phaseIncrement);
}
#endif // ASM_ARM_RESAMP1
while (outputIndex < outputSampleCount && inputIndex < mBuffer.frameCount) {
int32_t sample = Interp(in[inputIndex-1], in[inputIndex],
phaseFraction);
out[outputIndex++] += vl * sample;
out[outputIndex++] += vr * sample;
Advance(&inputIndex, &phaseFraction, phaseIncrement);
}
// ALOGE("loop done - outputIndex=%d, inputIndex=%d", outputIndex, inputIndex);
// if done with buffer, save samples
if (inputIndex >= mBuffer.frameCount) {
inputIndex -= mBuffer.frameCount;
// ALOGE("buffer done, new input index %d", inputIndex);
mX0L = mBuffer.i16[mBuffer.frameCount-1];
provider->releaseBuffer(&mBuffer);
// verify that the releaseBuffer resets the buffer frameCount
// ALOG_ASSERT(mBuffer.frameCount == 0);
}
}
// ALOGE("output buffer full - outputIndex=%d, inputIndex=%d", outputIndex, inputIndex);
resampleMono16_exit:
// save state
mInputIndex = inputIndex;
mPhaseFraction = phaseFraction;
return outputIndex;
}
#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1
/*******************************************************************
*
* AsmMono16Loop
* asm optimized monotonic loop version; one loop is 2 frames
* Input:
* in : pointer on input samples
* maxOutPt : pointer on first not filled
* maxInIdx : index on first not used
* outputIndex : pointer on current output index
* out : pointer on output buffer
* inputIndex : pointer on current input index
* vl, vr : left and right gain
* phaseFraction : pointer on current phase fraction
* phaseIncrement
* Output:
* outputIndex :
* out : updated buffer
* inputIndex : index of next to use
* phaseFraction : phase fraction for next interpolation
*
*******************************************************************/
__attribute__((noinline))
void AudioResamplerOrder1::AsmMono16Loop(int16_t *in, int32_t* maxOutPt, int32_t maxInIdx,
size_t &outputIndex, int32_t* out, size_t &inputIndex, int32_t vl, int32_t vr,
uint32_t &phaseFraction, uint32_t phaseIncrement)
{
(void)maxOutPt; // remove unused parameter warnings
(void)maxInIdx;
(void)outputIndex;
(void)out;
(void)inputIndex;
(void)vl;
(void)vr;
(void)phaseFraction;
(void)phaseIncrement;
(void)in;
#define MO_PARAM5 "36" // offset of parameter 5 (outputIndex)
asm(
"stmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, lr}\n"
// get parameters
" ldr r6, [sp, #" MO_PARAM5 " + 20]\n" // &phaseFraction
" ldr r6, [r6]\n" // phaseFraction
" ldr r7, [sp, #" MO_PARAM5 " + 8]\n" // &inputIndex
" ldr r7, [r7]\n" // inputIndex
" ldr r8, [sp, #" MO_PARAM5 " + 4]\n" // out
" ldr r0, [sp, #" MO_PARAM5 " + 0]\n" // &outputIndex
" ldr r0, [r0]\n" // outputIndex
" add r8, r8, r0, asl #2\n" // curOut
" ldr r9, [sp, #" MO_PARAM5 " + 24]\n" // phaseIncrement
" ldr r10, [sp, #" MO_PARAM5 " + 12]\n" // vl
" ldr r11, [sp, #" MO_PARAM5 " + 16]\n" // vr
// r0 pin, x0, Samp
// r1 in
// r2 maxOutPt
// r3 maxInIdx
// r4 x1, i1, i3, Out1
// r5 out0
// r6 frac
// r7 inputIndex
// r8 curOut
// r9 inc
// r10 vl
// r11 vr
// r12
// r13 sp
// r14
// the following loop works on 2 frames
"1:\n"
" cmp r8, r2\n" // curOut - maxCurOut
" bcs 2f\n"
#define MO_ONE_FRAME \
" add r0, r1, r7, asl #1\n" /* in + inputIndex */\
" ldrsh r4, [r0]\n" /* in[inputIndex] */\
" ldr r5, [r8]\n" /* out[outputIndex] */\
" ldrsh r0, [r0, #-2]\n" /* in[inputIndex-1] */\
" bic r6, r6, #0xC0000000\n" /* phaseFraction & ... */\
" sub r4, r4, r0\n" /* in[inputIndex] - in[inputIndex-1] */\
" mov r4, r4, lsl #2\n" /* <<2 */\
" smulwt r4, r4, r6\n" /* (x1-x0)*.. */\
" add r6, r6, r9\n" /* phaseFraction + phaseIncrement */\
" add r0, r0, r4\n" /* x0 - (..) */\
" mla r5, r0, r10, r5\n" /* vl*interp + out[] */\
" ldr r4, [r8, #4]\n" /* out[outputIndex+1] */\
" str r5, [r8], #4\n" /* out[outputIndex++] = ... */\
" mla r4, r0, r11, r4\n" /* vr*interp + out[] */\
" add r7, r7, r6, lsr #30\n" /* inputIndex + phaseFraction>>30 */\
" str r4, [r8], #4\n" /* out[outputIndex++] = ... */
MO_ONE_FRAME // frame 1
MO_ONE_FRAME // frame 2
" cmp r7, r3\n" // inputIndex - maxInIdx
" bcc 1b\n"
"2:\n"
" bic r6, r6, #0xC0000000\n" // phaseFraction & ...
// save modified values
" ldr r0, [sp, #" MO_PARAM5 " + 20]\n" // &phaseFraction
" str r6, [r0]\n" // phaseFraction
" ldr r0, [sp, #" MO_PARAM5 " + 8]\n" // &inputIndex
" str r7, [r0]\n" // inputIndex
" ldr r0, [sp, #" MO_PARAM5 " + 4]\n" // out
" sub r8, r0\n" // curOut - out
" asr r8, #2\n" // new outputIndex
" ldr r0, [sp, #" MO_PARAM5 " + 0]\n" // &outputIndex
" str r8, [r0]\n" // save outputIndex
" ldmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, pc}\n"
);
}
/*******************************************************************
*
* AsmStereo16Loop
* asm optimized stereo loop version; one loop is 2 frames
* Input:
* in : pointer on input samples
* maxOutPt : pointer on first not filled
* maxInIdx : index on first not used
* outputIndex : pointer on current output index
* out : pointer on output buffer
* inputIndex : pointer on current input index
* vl, vr : left and right gain
* phaseFraction : pointer on current phase fraction
* phaseIncrement
* Output:
* outputIndex :
* out : updated buffer
* inputIndex : index of next to use
* phaseFraction : phase fraction for next interpolation
*
*******************************************************************/
__attribute__((noinline))
void AudioResamplerOrder1::AsmStereo16Loop(int16_t *in, int32_t* maxOutPt, int32_t maxInIdx,
size_t &outputIndex, int32_t* out, size_t &inputIndex, int32_t vl, int32_t vr,
uint32_t &phaseFraction, uint32_t phaseIncrement)
{
(void)maxOutPt; // remove unused parameter warnings
(void)maxInIdx;
(void)outputIndex;
(void)out;
(void)inputIndex;
(void)vl;
(void)vr;
(void)phaseFraction;
(void)phaseIncrement;
(void)in;
#define ST_PARAM5 "40" // offset of parameter 5 (outputIndex)
asm(
"stmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, r12, lr}\n"
// get parameters
" ldr r6, [sp, #" ST_PARAM5 " + 20]\n" // &phaseFraction
" ldr r6, [r6]\n" // phaseFraction
" ldr r7, [sp, #" ST_PARAM5 " + 8]\n" // &inputIndex
" ldr r7, [r7]\n" // inputIndex
" ldr r8, [sp, #" ST_PARAM5 " + 4]\n" // out
" ldr r0, [sp, #" ST_PARAM5 " + 0]\n" // &outputIndex
" ldr r0, [r0]\n" // outputIndex
" add r8, r8, r0, asl #2\n" // curOut
" ldr r9, [sp, #" ST_PARAM5 " + 24]\n" // phaseIncrement
" ldr r10, [sp, #" ST_PARAM5 " + 12]\n" // vl
" ldr r11, [sp, #" ST_PARAM5 " + 16]\n" // vr
// r0 pin, x0, Samp
// r1 in
// r2 maxOutPt
// r3 maxInIdx
// r4 x1, i1, i3, out1
// r5 out0
// r6 frac
// r7 inputIndex
// r8 curOut
// r9 inc
// r10 vl
// r11 vr
// r12 temporary
// r13 sp
// r14
"3:\n"
" cmp r8, r2\n" // curOut - maxCurOut
" bcs 4f\n"
#define ST_ONE_FRAME \
" bic r6, r6, #0xC0000000\n" /* phaseFraction & ... */\
\
" add r0, r1, r7, asl #2\n" /* in + 2*inputIndex */\
\
" ldrsh r4, [r0]\n" /* in[2*inputIndex] */\
" ldr r5, [r8]\n" /* out[outputIndex] */\
" ldrsh r12, [r0, #-4]\n" /* in[2*inputIndex-2] */\
" sub r4, r4, r12\n" /* in[2*InputIndex] - in[2*InputIndex-2] */\
" mov r4, r4, lsl #2\n" /* <<2 */\
" smulwt r4, r4, r6\n" /* (x1-x0)*.. */\
" add r12, r12, r4\n" /* x0 - (..) */\
" mla r5, r12, r10, r5\n" /* vl*interp + out[] */\
" ldr r4, [r8, #4]\n" /* out[outputIndex+1] */\
" str r5, [r8], #4\n" /* out[outputIndex++] = ... */\
\
" ldrsh r12, [r0, #+2]\n" /* in[2*inputIndex+1] */\
" ldrsh r0, [r0, #-2]\n" /* in[2*inputIndex-1] */\
" sub r12, r12, r0\n" /* in[2*InputIndex] - in[2*InputIndex-2] */\
" mov r12, r12, lsl #2\n" /* <<2 */\
" smulwt r12, r12, r6\n" /* (x1-x0)*.. */\
" add r12, r0, r12\n" /* x0 - (..) */\
" mla r4, r12, r11, r4\n" /* vr*interp + out[] */\
" str r4, [r8], #4\n" /* out[outputIndex++] = ... */\
\
" add r6, r6, r9\n" /* phaseFraction + phaseIncrement */\
" add r7, r7, r6, lsr #30\n" /* inputIndex + phaseFraction>>30 */
ST_ONE_FRAME // frame 1
ST_ONE_FRAME // frame 1
" cmp r7, r3\n" // inputIndex - maxInIdx
" bcc 3b\n"
"4:\n"
" bic r6, r6, #0xC0000000\n" // phaseFraction & ...
// save modified values
" ldr r0, [sp, #" ST_PARAM5 " + 20]\n" // &phaseFraction
" str r6, [r0]\n" // phaseFraction
" ldr r0, [sp, #" ST_PARAM5 " + 8]\n" // &inputIndex
" str r7, [r0]\n" // inputIndex
" ldr r0, [sp, #" ST_PARAM5 " + 4]\n" // out
" sub r8, r0\n" // curOut - out
" asr r8, #2\n" // new outputIndex
" ldr r0, [sp, #" ST_PARAM5 " + 0]\n" // &outputIndex
" str r8, [r0]\n" // save outputIndex
" ldmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, r12, pc}\n"
);
}
#endif // ASM_ARM_RESAMP1
// ----------------------------------------------------------------------------
} // namespace cocos2d {

View File

@ -0,0 +1,181 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <sys/types.h>
#include <android/log.h>
#include <sys/system_properties.h>
#include "audio/android/AudioBufferProvider.h"
//#include <cutils/compiler.h>
//#include <utils/Compat.h>
//#include <media/AudioBufferProvider.h>
//#include <system/audio.h>
#include <assert.h>
#include "audio/android/audio.h"
namespace cocos2d {
class AudioResampler {
public:
// Determines quality of SRC.
// LOW_QUALITY: linear interpolator (1st order)
// MED_QUALITY: cubic interpolator (3rd order)
// HIGH_QUALITY: fixed multi-tap FIR (e.g. 48KHz->44.1KHz)
// NOTE: high quality SRC will only be supported for
// certain fixed rate conversions. Sample rate cannot be
// changed dynamically.
enum src_quality {
DEFAULT_QUALITY=0,
LOW_QUALITY=1,
MED_QUALITY=2,
HIGH_QUALITY=3,
VERY_HIGH_QUALITY=4,
};
static const CONSTEXPR float UNITY_GAIN_FLOAT = 1.0f;
static AudioResampler* create(audio_format_t format, int inChannelCount,
int32_t sampleRate, src_quality quality=DEFAULT_QUALITY);
virtual ~AudioResampler();
virtual void init() = 0;
virtual void setSampleRate(int32_t inSampleRate);
virtual void setVolume(float left, float right);
virtual void setLocalTimeFreq(uint64_t freq);
// set the PTS of the next buffer output by the resampler
virtual void setPTS(int64_t pts);
// Resample int16_t samples from provider and accumulate into 'out'.
// A mono provider delivers a sequence of samples.
// A stereo provider delivers a sequence of interleaved pairs of samples.
//
// In either case, 'out' holds interleaved pairs of fixed-point Q4.27.
// That is, for a mono provider, there is an implicit up-channeling.
// Since this method accumulates, the caller is responsible for clearing 'out' initially.
//
// For a float resampler, 'out' holds interleaved pairs of float samples.
//
// Multichannel interleaved frames for n > 2 is supported for quality DYN_LOW_QUALITY,
// DYN_MED_QUALITY, and DYN_HIGH_QUALITY.
//
// Returns the number of frames resampled into the out buffer.
virtual size_t resample(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider) = 0;
virtual void reset();
virtual size_t getUnreleasedFrames() const { return mInputIndex; }
// called from destructor, so must not be virtual
src_quality getQuality() const { return mQuality; }
protected:
// number of bits for phase fraction - 30 bits allows nearly 2x downsampling
static const int kNumPhaseBits = 30;
// phase mask for fraction
static const uint32_t kPhaseMask = (1LU<<kNumPhaseBits)-1;
// multiplier to calculate fixed point phase increment
static const double kPhaseMultiplier;
AudioResampler(int inChannelCount, int32_t sampleRate, src_quality quality);
// prevent copying
AudioResampler(const AudioResampler&);
AudioResampler& operator=(const AudioResampler&);
int64_t calculateOutputPTS(int outputFrameIndex);
const int32_t mChannelCount;
const int32_t mSampleRate;
int32_t mInSampleRate;
AudioBufferProvider::Buffer mBuffer;
union {
int16_t mVolume[2];
uint32_t mVolumeRL;
};
int16_t mTargetVolume[2];
size_t mInputIndex;
int32_t mPhaseIncrement;
uint32_t mPhaseFraction;
uint64_t mLocalTimeFreq;
int64_t mPTS;
// returns the inFrameCount required to generate outFrameCount frames.
//
// Placed here to be a consistent for all resamplers.
//
// Right now, we use the upper bound without regards to the current state of the
// input buffer using integer arithmetic, as follows:
//
// (static_cast<uint64_t>(outFrameCount)*mInSampleRate + (mSampleRate - 1))/mSampleRate;
//
// The double precision equivalent (float may not be precise enough):
// ceil(static_cast<double>(outFrameCount) * mInSampleRate / mSampleRate);
//
// this relies on the fact that the mPhaseIncrement is rounded down from
// #phases * mInSampleRate/mSampleRate and the fact that Sum(Floor(x)) <= Floor(Sum(x)).
// http://www.proofwiki.org/wiki/Sum_of_Floors_Not_Greater_Than_Floor_of_Sums
//
// (so long as double precision is computed accurately enough to be considered
// greater than or equal to the Floor(x) value in int32_t arithmetic; thus this
// will not necessarily hold for floats).
//
// REFINE:
// Greater accuracy and a tight bound is obtained by:
// 1) subtract and adjust for the current state of the AudioBufferProvider buffer.
// 2) using the exact integer formula where (ignoring 64b casting)
// inFrameCount = (mPhaseIncrement * (outFrameCount - 1) + mPhaseFraction) / phaseWrapLimit;
// phaseWrapLimit is the wraparound (1 << kNumPhaseBits), if not specified explicitly.
//
inline size_t getInFrameCountRequired(size_t outFrameCount) {
return (static_cast<uint64_t>(outFrameCount)*mInSampleRate
+ (mSampleRate - 1))/mSampleRate;
}
inline float clampFloatVol(float volume) {
if (volume > UNITY_GAIN_FLOAT) {
return UNITY_GAIN_FLOAT;
} else if (volume >= 0.) {
return volume;
}
return 0.; // NaN or negative volume maps to 0.
}
private:
const src_quality mQuality;
// Return 'true' if the quality level is supported without explicit request
static bool qualityIsSupported(src_quality quality);
// For pthread_once()
static void init_routine();
// Return the estimated CPU load for specific resampler in MHz.
// The absolute number is irrelevant, it's the relative values that matter.
static uint32_t qualityMHz(src_quality quality);
};
// ----------------------------------------------------------------------------
} // namespace cocos2d {

View File

@ -0,0 +1,191 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "AudioResamplerCubic"
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include "audio/android/cutils/log.h"
#include "audio/android/AudioResampler.h"
#include "audio/android/AudioResamplerCubic.h"
namespace cocos2d {
// ----------------------------------------------------------------------------
void AudioResamplerCubic::init() {
memset(&left, 0, sizeof(state));
memset(&right, 0, sizeof(state));
}
size_t AudioResamplerCubic::resample(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider) {
// should never happen, but we overflow if it does
// ALOG_ASSERT(outFrameCount < 32767);
// select the appropriate resampler
switch (mChannelCount) {
case 1:
return resampleMono16(out, outFrameCount, provider);
case 2:
return resampleStereo16(out, outFrameCount, provider);
default:
LOG_ALWAYS_FATAL("invalid channel count: %d", mChannelCount);
return 0;
}
}
size_t AudioResamplerCubic::resampleStereo16(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider) {
int32_t vl = mVolume[0];
int32_t vr = mVolume[1];
size_t inputIndex = mInputIndex;
uint32_t phaseFraction = mPhaseFraction;
uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
size_t outputSampleCount = outFrameCount * 2;
size_t inFrameCount = getInFrameCountRequired(outFrameCount);
// fetch first buffer
if (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
provider->getNextBuffer(&mBuffer, mPTS);
if (mBuffer.raw == NULL) {
return 0;
}
// ALOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount);
}
int16_t *in = mBuffer.i16;
while (outputIndex < outputSampleCount) {
int32_t sample;
int32_t x;
// calculate output sample
x = phaseFraction >> kPreInterpShift;
out[outputIndex++] += vl * interp(&left, x);
out[outputIndex++] += vr * interp(&right, x);
// out[outputIndex++] += vr * in[inputIndex*2];
// increment phase
phaseFraction += phaseIncrement;
uint32_t indexIncrement = (phaseFraction >> kNumPhaseBits);
phaseFraction &= kPhaseMask;
// time to fetch another sample
while (indexIncrement--) {
inputIndex++;
if (inputIndex == mBuffer.frameCount) {
inputIndex = 0;
provider->releaseBuffer(&mBuffer);
mBuffer.frameCount = inFrameCount;
provider->getNextBuffer(&mBuffer,
calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
goto save_state; // ugly, but efficient
}
in = mBuffer.i16;
// ALOGW("New buffer: offset=%p, frames=%d", mBuffer.raw, mBuffer.frameCount);
}
// advance sample state
advance(&left, in[inputIndex*2]);
advance(&right, in[inputIndex*2+1]);
}
}
save_state:
// ALOGW("Done: index=%d, fraction=%u", inputIndex, phaseFraction);
mInputIndex = inputIndex;
mPhaseFraction = phaseFraction;
return outputIndex / 2 /* channels for stereo */;
}
size_t AudioResamplerCubic::resampleMono16(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider) {
int32_t vl = mVolume[0];
int32_t vr = mVolume[1];
size_t inputIndex = mInputIndex;
uint32_t phaseFraction = mPhaseFraction;
uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
size_t outputSampleCount = outFrameCount * 2;
size_t inFrameCount = getInFrameCountRequired(outFrameCount);
// fetch first buffer
if (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
provider->getNextBuffer(&mBuffer, mPTS);
if (mBuffer.raw == NULL) {
return 0;
}
// ALOGW("New buffer: offset=%p, frames=%d", mBuffer.raw, mBuffer.frameCount);
}
int16_t *in = mBuffer.i16;
while (outputIndex < outputSampleCount) {
int32_t sample;
int32_t x;
// calculate output sample
x = phaseFraction >> kPreInterpShift;
sample = interp(&left, x);
out[outputIndex++] += vl * sample;
out[outputIndex++] += vr * sample;
// increment phase
phaseFraction += phaseIncrement;
uint32_t indexIncrement = (phaseFraction >> kNumPhaseBits);
phaseFraction &= kPhaseMask;
// time to fetch another sample
while (indexIncrement--) {
inputIndex++;
if (inputIndex == mBuffer.frameCount) {
inputIndex = 0;
provider->releaseBuffer(&mBuffer);
mBuffer.frameCount = inFrameCount;
provider->getNextBuffer(&mBuffer,
calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
goto save_state; // ugly, but efficient
}
// ALOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount);
in = mBuffer.i16;
}
// advance sample state
advance(&left, in[inputIndex]);
}
}
save_state:
// ALOGW("Done: index=%d, fraction=%u", inputIndex, phaseFraction);
mInputIndex = inputIndex;
mPhaseFraction = phaseFraction;
return outputIndex;
}
// ----------------------------------------------------------------------------
} // namespace cocos2d {

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <sys/types.h>
#include "audio/android/AudioResampler.h"
#include "audio/android/AudioBufferProvider.h"
namespace cocos2d {
// ----------------------------------------------------------------------------
class AudioResamplerCubic : public AudioResampler {
public:
AudioResamplerCubic(int inChannelCount, int32_t sampleRate) :
AudioResampler(inChannelCount, sampleRate, MED_QUALITY) {
}
virtual size_t resample(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider);
private:
// number of bits used in interpolation multiply - 14 bits avoids overflow
static const int kNumInterpBits = 14;
// bits to shift the phase fraction down to avoid overflow
static const int kPreInterpShift = kNumPhaseBits - kNumInterpBits;
typedef struct {
int32_t a, b, c, y0, y1, y2, y3;
} state;
void init();
size_t resampleMono16(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider);
size_t resampleStereo16(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider);
static inline int32_t interp(state* p, int32_t x) {
return (((((p->a * x >> 14) + p->b) * x >> 14) + p->c) * x >> 14) + p->y1;
}
static inline void advance(state* p, int16_t in) {
p->y0 = p->y1;
p->y1 = p->y2;
p->y2 = p->y3;
p->y3 = in;
p->a = (3 * (p->y1 - p->y2) - p->y0 + p->y3) >> 1;
p->b = (p->y2 << 1) + p->y0 - (((5 * p->y1 + p->y3)) >> 1);
p->c = (p->y2 - p->y0) >> 1;
}
state left, right;
};
// ----------------------------------------------------------------------------
} // namespace cocos2d {

View File

@ -0,0 +1,181 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <math.h>
#include <stdint.h>
namespace cocos2d {
// AUDIO_RESAMPLER_DOWN_RATIO_MAX is the maximum ratio between the original
// audio sample rate and the target rate when downsampling,
// as permitted in the audio framework, e.g. AudioTrack and AudioFlinger.
// In practice, it is not recommended to downsample more than 6:1
// for best audio quality, even though the audio framework permits a larger
// downsampling ratio.
// REFINE: replace with an API
#define AUDIO_RESAMPLER_DOWN_RATIO_MAX 256
// AUDIO_RESAMPLER_UP_RATIO_MAX is the maximum suggested ratio between the
// original audio sample rate and the target rate when upsampling. It is
// loosely enforced by the system. One issue with large upsampling ratios is the
// approximation by an int32_t of the phase increments, making the resulting
// sample rate inexact.
#define AUDIO_RESAMPLER_UP_RATIO_MAX 65536
// AUDIO_TIMESTRETCH_SPEED_MIN and AUDIO_TIMESTRETCH_SPEED_MAX define the min
// and max time stretch speeds supported by the system. These are enforced by
// the system and values outside this range will result in a runtime error.
// Depending on the AudioPlaybackRate::mStretchMode, the effective limits might
// be narrower than the ones specified here AUDIO_TIMESTRETCH_SPEED_MIN_DELTA is
// the minimum absolute speed difference that might trigger a parameter update
#define AUDIO_TIMESTRETCH_SPEED_MIN 0.01f
#define AUDIO_TIMESTRETCH_SPEED_MAX 20.0f
#define AUDIO_TIMESTRETCH_SPEED_NORMAL 1.0f
#define AUDIO_TIMESTRETCH_SPEED_MIN_DELTA 0.0001f
// AUDIO_TIMESTRETCH_PITCH_MIN and AUDIO_TIMESTRETCH_PITCH_MAX define the min
// and max time stretch pitch shifting supported by the system. These are not
// enforced by the system and values outside this range might result in a pitch
// different than the one requested. Depending on the
// AudioPlaybackRate::mStretchMode, the effective limits might be narrower than
// the ones specified here.
// AUDIO_TIMESTRETCH_PITCH_MIN_DELTA is the minimum absolute pitch difference
// that might trigger a parameter update
#define AUDIO_TIMESTRETCH_PITCH_MIN 0.25f
#define AUDIO_TIMESTRETCH_PITCH_MAX 4.0f
#define AUDIO_TIMESTRETCH_PITCH_NORMAL 1.0f
#define AUDIO_TIMESTRETCH_PITCH_MIN_DELTA 0.0001f
// Determines the current algorithm used for stretching
enum AudioTimestretchStretchMode : int32_t {
AUDIO_TIMESTRETCH_STRETCH_DEFAULT = 0,
AUDIO_TIMESTRETCH_STRETCH_SPEECH = 1,
// REFINE: add more stretch modes/algorithms
};
// Limits for AUDIO_TIMESTRETCH_STRETCH_SPEECH mode
#define TIMESTRETCH_SONIC_SPEED_MIN 0.1f
#define TIMESTRETCH_SONIC_SPEED_MAX 6.0f
// Determines behavior of Timestretch if current algorithm can't perform
// with current parameters.
// FALLBACK_CUT_REPEAT: (internal only) for speed <1.0 will truncate frames
// for speed > 1.0 will repeat frames
// FALLBACK_MUTE: will set all processed frames to zero
// FALLBACK_FAIL: will stop program execution and log a fatal error
enum AudioTimestretchFallbackMode : int32_t {
AUDIO_TIMESTRETCH_FALLBACK_CUT_REPEAT = -1,
AUDIO_TIMESTRETCH_FALLBACK_DEFAULT = 0,
AUDIO_TIMESTRETCH_FALLBACK_MUTE = 1,
AUDIO_TIMESTRETCH_FALLBACK_FAIL = 2,
};
struct AudioPlaybackRate {
float mSpeed;
float mPitch;
enum AudioTimestretchStretchMode mStretchMode;
enum AudioTimestretchFallbackMode mFallbackMode;
};
static const AudioPlaybackRate AUDIO_PLAYBACK_RATE_DEFAULT = {
AUDIO_TIMESTRETCH_SPEED_NORMAL, AUDIO_TIMESTRETCH_PITCH_NORMAL,
AUDIO_TIMESTRETCH_STRETCH_DEFAULT, AUDIO_TIMESTRETCH_FALLBACK_DEFAULT};
static inline bool isAudioPlaybackRateEqual(const AudioPlaybackRate &pr1,
const AudioPlaybackRate &pr2) {
return fabs(pr1.mSpeed - pr2.mSpeed) < AUDIO_TIMESTRETCH_SPEED_MIN_DELTA &&
fabs(pr1.mPitch - pr2.mPitch) < AUDIO_TIMESTRETCH_PITCH_MIN_DELTA &&
pr1.mStretchMode == pr2.mStretchMode &&
pr1.mFallbackMode == pr2.mFallbackMode;
}
static inline bool
isAudioPlaybackRateValid(const AudioPlaybackRate &playbackRate) {
if (playbackRate.mFallbackMode == AUDIO_TIMESTRETCH_FALLBACK_FAIL &&
(playbackRate.mStretchMode == AUDIO_TIMESTRETCH_STRETCH_SPEECH ||
playbackRate.mStretchMode == AUDIO_TIMESTRETCH_STRETCH_DEFAULT)) {
// test sonic specific constraints
return playbackRate.mSpeed >= TIMESTRETCH_SONIC_SPEED_MIN &&
playbackRate.mSpeed <= TIMESTRETCH_SONIC_SPEED_MAX &&
playbackRate.mPitch >= AUDIO_TIMESTRETCH_PITCH_MIN &&
playbackRate.mPitch <= AUDIO_TIMESTRETCH_PITCH_MAX;
} else {
return playbackRate.mSpeed >= AUDIO_TIMESTRETCH_SPEED_MIN &&
playbackRate.mSpeed <= AUDIO_TIMESTRETCH_SPEED_MAX &&
playbackRate.mPitch >= AUDIO_TIMESTRETCH_PITCH_MIN &&
playbackRate.mPitch <= AUDIO_TIMESTRETCH_PITCH_MAX;
}
}
// REFINE: Consider putting these inlines into a class scope
// Returns the source frames needed to resample to destination frames. This is
// not a precise value and depends on the resampler (and possibly how it handles
// rounding internally). Nevertheless, this should be an upper bound on the
// requirements of the resampler. If srcSampleRate and dstSampleRate are equal,
// then it returns destination frames, which may not be true if the resampler is
// asynchronous.
static inline size_t sourceFramesNeeded(uint32_t srcSampleRate,
size_t dstFramesRequired,
uint32_t dstSampleRate) {
// +1 for rounding - always do this even if matched ratio (resampler may use
// phases not ratio) +1 for additional sample needed for interpolation
return srcSampleRate == dstSampleRate
? dstFramesRequired
: size_t((uint64_t)dstFramesRequired * srcSampleRate /
dstSampleRate +
1 + 1);
}
// An upper bound for the number of destination frames possible from srcFrames
// after sample rate conversion. This may be used for buffer sizing.
static inline size_t destinationFramesPossible(size_t srcFrames,
uint32_t srcSampleRate,
uint32_t dstSampleRate) {
if (srcSampleRate == dstSampleRate) {
return srcFrames;
}
uint64_t dstFrames = (uint64_t)srcFrames * dstSampleRate / srcSampleRate;
return dstFrames > 2 ? dstFrames - 2 : 0;
}
static inline size_t sourceFramesNeededWithTimestretch(uint32_t srcSampleRate,
size_t dstFramesRequired,
uint32_t dstSampleRate,
float speed) {
// required is the number of input frames the resampler needs
size_t required =
sourceFramesNeeded(srcSampleRate, dstFramesRequired, dstSampleRate);
// to deliver this, the time stretcher requires:
return required * (double)speed + 1 +
1; // accounting for rounding dependencies
}
// Identifies sample rates that we associate with music
// and thus eligible for better resampling and fast capture.
// This is somewhat less than 44100 to allow for pitch correction
// involving resampling as well as asynchronous resampling.
#define AUDIO_PROCESSING_MUSIC_RATE 40000
static inline bool isMusicRate(uint32_t sampleRate) {
return sampleRate >= AUDIO_PROCESSING_MUSIC_RATE;
}
} // namespace cocos2d
// ---------------------------------------------------------------------------

View File

@ -0,0 +1,89 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include <functional>
namespace cocos2d {
class IAudioPlayer
{
public:
enum class State
{
INVALID = 0,
INITIALIZED,
PLAYING,
PAUSED,
STOPPED,
OVER
};
using PlayEventCallback = std::function<void(State)>;
virtual ~IAudioPlayer()
{ };
virtual int getId() const = 0;
virtual void setId(int id) = 0;
virtual std::string getUrl() const = 0;
virtual State getState() const = 0;
virtual void play() = 0;
virtual void pause() = 0;
virtual void resume() = 0;
virtual void stop() = 0;
virtual void rewind() = 0;
virtual void setVolume(float volume) = 0;
virtual float getVolume() const = 0;
virtual void setAudioFocus(bool isFocus) = 0;
virtual void setLoop(bool isLoop) = 0;
virtual bool isLoop() const = 0;
virtual float getDuration() const = 0;
virtual float getPosition() const = 0;
virtual bool setPosition(float pos) = 0;
// @note: STOPPED event is invoked in main thread
// OVER event is invoked in sub thread
virtual void setPlayEventCallback(const PlayEventCallback &playEventCallback) = 0;
};
} // namespace cocos2d {

View File

@ -0,0 +1,42 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include <functional>
#include <thread>
namespace cocos2d {
class ICallerThreadUtils
{
public:
virtual ~ICallerThreadUtils()
{ };
virtual void performFunctionInCallerThread(const std::function<void()>& func) = 0;
virtual std::thread::id getCallerThreadId() = 0;
};
} // namespace cocos2d {

View File

@ -0,0 +1,45 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/audio_utils/include/audio_utils/minifloat.h"
namespace cocos2d {
class IVolumeProvider
{
public:
// The provider implementation is responsible for validating that the return value is in range.
virtual gain_minifloat_packed_t getVolumeLR() = 0;
protected:
IVolumeProvider()
{ }
virtual ~IVolumeProvider()
{ }
};
} // namespace cocos2d {

View File

@ -0,0 +1,100 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/cutils/log.h"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <functional>
#include <string>
#define SL_SAFE_DELETE(obj) \
if ((obj) != nullptr) { delete (obj); (obj) = nullptr; }
#define SL_DESTROY_OBJ(OBJ) \
if ((OBJ) != nullptr) { \
(*(OBJ))->Destroy(OBJ); \
(OBJ) = nullptr; \
}
#define SL_RETURN_VAL_IF_FAILED(r, rval, ...) \
if (r != SL_RESULT_SUCCESS) {\
ALOGE(__VA_ARGS__); \
return rval; \
}
#define SL_RETURN_IF_FAILED(r, ...) \
if (r != SL_RESULT_SUCCESS) {\
ALOGE(__VA_ARGS__); \
return; \
}
#define SL_PRINT_ERROR_IF_FAILED(r, ...) \
if (r != SL_RESULT_SUCCESS) {\
ALOGE(__VA_ARGS__); \
}
typedef std::function<int(const std::string&, off_t* start, off_t* length)> FdGetterCallback;
// Copied from OpenSLES_AndroidMetadata.h in android-21
// It's because android-10 doesn't contain this header file
/**
* Additional metadata keys to be used in SLMetadataExtractionItf:
* the ANDROID_KEY_PCMFORMAT_* keys follow the fields of the SLDataFormat_PCM struct, and as such
* all values corresponding to these keys are of SLuint32 type, and are defined as the fields
* of the same name in SLDataFormat_PCM. The exception is that sample rate is expressed here
* in Hz units, rather than in milliHz units.
*/
#ifndef ANDROID_KEY_PCMFORMAT_NUMCHANNELS
#define ANDROID_KEY_PCMFORMAT_NUMCHANNELS "AndroidPcmFormatNumChannels"
#endif
#ifndef ANDROID_KEY_PCMFORMAT_SAMPLERATE
#define ANDROID_KEY_PCMFORMAT_SAMPLERATE "AndroidPcmFormatSampleRate"
#endif
#ifndef ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE
#define ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE "AndroidPcmFormatBitsPerSample"
#endif
#ifndef ANDROID_KEY_PCMFORMAT_CONTAINERSIZE
#define ANDROID_KEY_PCMFORMAT_CONTAINERSIZE "AndroidPcmFormatContainerSize"
#endif
#ifndef ANDROID_KEY_PCMFORMAT_CHANNELMASK
#define ANDROID_KEY_PCMFORMAT_CHANNELMASK "AndroidPcmFormatChannelMask"
#endif
#ifndef ANDROID_KEY_PCMFORMAT_ENDIANNESS
#define ANDROID_KEY_PCMFORMAT_ENDIANNESS "AndroidPcmFormatEndianness"
#endif
#define clockNow() std::chrono::high_resolution_clock::now()
#define intervalInMS(oldTime, newTime) (static_cast<long>(std::chrono::duration_cast<std::chrono::microseconds>((newTime) - (oldTime)).count()) / 1000.f)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))

View File

@ -0,0 +1,226 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "PcmAudioPlayer"
#include "audio/android/cutils/log.h"
#include "audio/android/PcmAudioPlayer.h"
#include "audio/android/AudioMixerController.h"
#include "audio/android/ICallerThreadUtils.h"
namespace cocos2d {
PcmAudioPlayer::PcmAudioPlayer(AudioMixerController * controller, ICallerThreadUtils* callerThreadUtils)
: _id(-1)
, _track(nullptr)
, _playEventCallback(nullptr)
, _controller(controller)
, _callerThreadUtils(callerThreadUtils)
{
ALOGV("PcmAudioPlayer constructor: %p", this);
}
PcmAudioPlayer::~PcmAudioPlayer()
{
ALOGV("In the destructor of PcmAudioPlayer (%p)", this);
delete _track;
}
bool PcmAudioPlayer::prepare(const std::string &url, const PcmData &decResult)
{
_url = url;
_decResult = decResult;
_track = new (std::nothrow) Track(_decResult);
std::thread::id callerThreadId = _callerThreadUtils->getCallerThreadId();
// @note The logic may cause this issue https://github.com/cocos2d/cocos2d-x/issues/17707
// Assume that AudioEngine::stop(id) is invoked and the audio is played over meanwhile.
// Since State::OVER and State::DESTROYED are triggered in the audio mixing thread, it will
// call 'performFunctionInCallerThread' to post events to cocos's message queue.
// Therefore, the sequence in cocos's thread will be |STOP|OVER|DESTROYED|.
// Although, we remove the audio id in |STOPPED| callback, because it's asynchronous operation,
// |OVER| and |DESTROYED| callbacks will still be invoked in cocos's thread.
// HOW TO FIX: If the previous state is |STOPPED| and the current state
// is |OVER|, just skip to invoke |OVER| callback.
_track->onStateChanged = [this, callerThreadId](Track::State state) {
// It maybe in sub thread
Track::State prevState = _track->getPrevState();
auto func = [this, state, prevState](){
// It's in caller's thread
if (state == Track::State::OVER && prevState != Track::State::STOPPED)
{
if (_playEventCallback != nullptr)
{
_playEventCallback(State::OVER);
}
}
else if (state == Track::State::STOPPED)
{
if (_playEventCallback != nullptr)
{
_playEventCallback(State::STOPPED);
}
}
else if (state == Track::State::DESTROYED)
{
delete this;
}
};
if (callerThreadId == std::this_thread::get_id())
{ // onStateChanged(Track::State::STOPPED) is in caller's (Cocos's) thread.
func();
}
else
{ // onStateChanged(Track::State::OVER) or onStateChanged(Track::State::DESTROYED) are in audio mixing thread.
_callerThreadUtils->performFunctionInCallerThread(func);
}
};
setVolume(1.0f);
return true;
}
void PcmAudioPlayer::rewind()
{
ALOGW("PcmAudioPlayer::rewind isn't supported!");
}
void PcmAudioPlayer::setVolume(float volume)
{
_track->setVolume(volume);
}
float PcmAudioPlayer::getVolume() const
{
return _track->getVolume();
}
void PcmAudioPlayer::setAudioFocus(bool isFocus)
{
_track->setAudioFocus(isFocus);
}
void PcmAudioPlayer::setLoop(bool isLoop)
{
_track->setLoop(isLoop);
}
bool PcmAudioPlayer::isLoop() const
{
return _track->isLoop();
}
float PcmAudioPlayer::getDuration() const
{
return _decResult.duration;
}
float PcmAudioPlayer::getPosition() const
{
return _track->getPosition();
}
bool PcmAudioPlayer::setPosition(float pos)
{
return _track->setPosition(pos);
}
void PcmAudioPlayer::setPlayEventCallback(const PlayEventCallback &playEventCallback)
{
_playEventCallback = playEventCallback;
}
void PcmAudioPlayer::play()
{
// put track to AudioMixerController
ALOGV("PcmAudioPlayer (%p) play, url: %s", this, _url.c_str());
_controller->addTrack(_track);
_track->setState(Track::State::PLAYING);
}
void PcmAudioPlayer::pause()
{
ALOGV("PcmAudioPlayer (%p) pause, url: %s", this, _url.c_str());
_track->setState(Track::State::PAUSED);
}
void PcmAudioPlayer::resume()
{
ALOGV("PcmAudioPlayer (%p) resume, url: %s", this, _url.c_str());
_track->setState(Track::State::RESUMED);
}
void PcmAudioPlayer::stop()
{
ALOGV("PcmAudioPlayer (%p) stop, url: %s", this, _url.c_str());
_track->setState(Track::State::STOPPED);
}
IAudioPlayer::State PcmAudioPlayer::getState() const
{
IAudioPlayer::State state = State::INVALID;
if (_track != nullptr)
{
switch (_track->getState())
{
case Track::State::IDLE:
state = State::INITIALIZED;
break;
case Track::State::PLAYING:
state = State::PLAYING;
break;
case Track::State::RESUMED:
state = State::PLAYING;
break;
case Track::State::PAUSED:
state = State::PAUSED;
break;
case Track::State::STOPPED:
state = State::STOPPED;
break;
case Track::State::OVER:
state = State::OVER;
break;
default:
state = State::INVALID;
break;
}
}
return state;
}
} // namespace cocos2d {

View File

@ -0,0 +1,98 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include <mutex>
#include "audio/android/IAudioPlayer.h"
#include "audio/android/PcmData.h"
#include "audio/android/Track.h"
namespace cocos2d {
class ICallerThreadUtils;
class AudioMixerController;
class PcmAudioPlayer : public IAudioPlayer
{
public:
bool prepare(const std::string &url, const PcmData &decResult);
// Override Functions Begin
virtual int getId() const override { return _id; };
virtual void setId(int id) override { _id = id; };
virtual std::string getUrl() const override { return _url; };
virtual State getState() const override;
virtual void play() override;
virtual void pause() override;
virtual void resume() override;
virtual void stop() override;
virtual void rewind() override;
virtual void setVolume(float volume) override;
virtual float getVolume() const override;
virtual void setAudioFocus(bool isFocus) override;
virtual void setLoop(bool isLoop) override;
virtual bool isLoop() const override;
virtual float getDuration() const override;
virtual float getPosition() const override;
virtual bool setPosition(float pos) override;
virtual void setPlayEventCallback(const PlayEventCallback &playEventCallback) override;
// Override Functions End
private:
PcmAudioPlayer(AudioMixerController * controller, ICallerThreadUtils* callerThreadUtils);
virtual ~PcmAudioPlayer();
private:
int _id;
std::string _url;
PcmData _decResult;
Track* _track;
PlayEventCallback _playEventCallback;
AudioMixerController * _controller;
ICallerThreadUtils* _callerThreadUtils;
friend class AudioPlayerProvider;
};
} // namespace cocos2d {

View File

@ -0,0 +1,193 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "PcmAudioService"
#include "audio/android/PcmAudioService.h"
#include "audio/android/AudioMixerController.h"
namespace cocos2d {
static std::vector<char> __silenceData;
#define AUDIO_PLAYER_BUFFER_COUNT (2)
class SLPcmAudioPlayerCallbackProxy
{
public:
static void samplePlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
{
PcmAudioService *thiz = reinterpret_cast<PcmAudioService *>(context);
thiz->bqFetchBufferCallback(bq);
}
};
PcmAudioService::PcmAudioService(SLEngineItf engineItf, SLObjectItf outputMixObject)
: _engineItf(engineItf), _outputMixObj(outputMixObject), _playObj(nullptr),
_playItf(nullptr), _volumeItf(nullptr), _bufferQueueItf(nullptr), _numChannels(-1),
_sampleRate(-1), _bufferSizeInBytes(0), _controller(nullptr)
{
}
PcmAudioService::~PcmAudioService()
{
ALOGV("PcmAudioServicee() (%p), before destroy play object", this);
SL_DESTROY_OBJ(_playObj);
ALOGV("PcmAudioServicee() end");
}
bool PcmAudioService::enqueue()
{
if (_controller->hasPlayingTacks())
{
if (_controller->isPaused())
{
SLresult r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, __silenceData.data(), __silenceData.size());
SL_RETURN_VAL_IF_FAILED(r, false, "enqueue silent data failed!");
}
else
{
_controller->mixOneFrame();
auto current = _controller->current();
ALOG_ASSERT(current != nullptr, "current buffer is nullptr ...");
SLresult r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, current->buf, current->size);
SL_RETURN_VAL_IF_FAILED(r, false, "enqueue failed!");
}
}
else
{
SLresult r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, __silenceData.data(), __silenceData.size());
SL_RETURN_VAL_IF_FAILED(r, false, "enqueue silent data failed!");
}
return true;
}
void PcmAudioService::bqFetchBufferCallback(SLAndroidSimpleBufferQueueItf bq)
{
// IDEA: PcmAudioService instance may be destroyed, we need to find a way to wait...
// It's in sub thread
enqueue();
}
bool PcmAudioService::init(AudioMixerController* controller, int numChannels, int sampleRate, int bufferSizeInBytes)
{
_controller = controller;
_numChannels = numChannels;
_sampleRate = sampleRate;
_bufferSizeInBytes = bufferSizeInBytes;
SLuint32 channelMask = SL_SPEAKER_FRONT_CENTER;
if (numChannels > 1)
{
channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
}
SLDataFormat_PCM formatPcm = {
SL_DATAFORMAT_PCM,
(SLuint32) numChannels,
(SLuint32) sampleRate * 1000,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_PCMSAMPLEFORMAT_FIXED_16,
channelMask,
SL_BYTEORDER_LITTLEENDIAN
};
SLDataLocator_AndroidSimpleBufferQueue locBufQueue = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
AUDIO_PLAYER_BUFFER_COUNT
};
SLDataSource source = {&locBufQueue, &formatPcm};
SLDataLocator_OutputMix locOutmix = {
SL_DATALOCATOR_OUTPUTMIX,
_outputMixObj
};
SLDataSink sink = {&locOutmix, nullptr};
const SLInterfaceID ids[] = {
SL_IID_PLAY,
SL_IID_VOLUME,
SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
};
const SLboolean req[] = {
SL_BOOLEAN_TRUE,
SL_BOOLEAN_TRUE,
SL_BOOLEAN_TRUE,
};
SLresult r;
r = (*_engineItf)->CreateAudioPlayer(_engineItf, &_playObj, &source, &sink,
sizeof(ids) / sizeof(ids[0]), ids, req);
SL_RETURN_VAL_IF_FAILED(r, false, "CreateAudioPlayer failed");
r = (*_playObj)->Realize(_playObj, SL_BOOLEAN_FALSE);
SL_RETURN_VAL_IF_FAILED(r, false, "Realize failed");
r = (*_playObj)->GetInterface(_playObj, SL_IID_PLAY, &_playItf);
SL_RETURN_VAL_IF_FAILED(r, false, "GetInterface SL_IID_PLAY failed");
r = (*_playObj)->GetInterface(_playObj, SL_IID_VOLUME, &_volumeItf);
SL_RETURN_VAL_IF_FAILED(r, false, "GetInterface SL_IID_VOLUME failed");
r = (*_playObj)->GetInterface(_playObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &_bufferQueueItf);
SL_RETURN_VAL_IF_FAILED(r, false, "GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE failed");
r = (*_bufferQueueItf)->RegisterCallback(_bufferQueueItf,
SLPcmAudioPlayerCallbackProxy::samplePlayerCallback,
this);
SL_RETURN_VAL_IF_FAILED(r, false, "_bufferQueueItf RegisterCallback failed");
if (__silenceData.empty())
{
__silenceData.resize(_numChannels * _bufferSizeInBytes, 0x00);
}
r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, __silenceData.data(), __silenceData.size());
SL_RETURN_VAL_IF_FAILED(r, false, "_bufferQueueItf Enqueue failed");
r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING);
SL_RETURN_VAL_IF_FAILED(r, false, "SetPlayState failed");
return true;
}
void PcmAudioService::pause()
{
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PAUSED);
SL_RETURN_IF_FAILED(r, "PcmAudioService::pause failed");
}
void PcmAudioService::resume()
{
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING);
SL_RETURN_IF_FAILED(r, "PcmAudioService::resume failed");
}
} // namespace cocos2d {

View File

@ -0,0 +1,81 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/IAudioPlayer.h"
#include "audio/android/OpenSLHelper.h"
#include "audio/android/PcmData.h"
#include <mutex>
#include <condition_variable>
namespace cocos2d {
class AudioMixerController;
class PcmAudioService
{
public:
inline int getChannelCount() const
{ return _numChannels; };
inline int getSampleRate() const
{ return _sampleRate; };
private:
PcmAudioService(SLEngineItf engineItf, SLObjectItf outputMixObject);
virtual ~PcmAudioService();
bool init(AudioMixerController* controller, int numChannels, int sampleRate, int bufferSizeInBytes);
bool enqueue();
void bqFetchBufferCallback(SLAndroidSimpleBufferQueueItf bq);
void pause();
void resume();
private:
SLEngineItf _engineItf;
SLObjectItf _outputMixObj;
SLObjectItf _playObj;
SLPlayItf _playItf;
SLVolumeItf _volumeItf;
SLAndroidSimpleBufferQueueItf _bufferQueueItf;
int _numChannels;
int _sampleRate;
int _bufferSizeInBytes;
AudioMixerController* _controller;
friend class SLPcmAudioPlayerCallbackProxy;
friend class AudioPlayerProvider;
};
} // namespace cocos2d {

View File

@ -0,0 +1,102 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "PcmBufferProvider"
#include "audio/android/cutils/log.h"
#include "audio/android/PcmBufferProvider.h"
//#define VERY_VERY_VERBOSE_LOGGING
#ifdef VERY_VERY_VERBOSE_LOGGING
#define ALOGVV ALOGV
#else
#define ALOGVV(a...) do { } while (0)
#endif
namespace cocos2d {
PcmBufferProvider::PcmBufferProvider()
: _addr(nullptr)
, _numFrames(0)
, _frameSize(0)
, _nextFrame(0)
, _unrel(0)
{
}
bool PcmBufferProvider::init(const void *addr, size_t frames, size_t frameSize)
{
_addr = addr;
_numFrames = frames;
_frameSize = frameSize;
_nextFrame = 0;
_unrel = 0;
return true;
}
status_t PcmBufferProvider::getNextBuffer(Buffer *buffer,
int64_t pts/* = kInvalidPTS*/) {
(void) pts; // suppress warning
size_t requestedFrames = buffer->frameCount;
if (requestedFrames > _numFrames - _nextFrame) {
buffer->frameCount = _numFrames - _nextFrame;
}
ALOGVV("getNextBuffer() requested %zu frames out of %zu frames available,"
" and returned %zu frames",
requestedFrames, (size_t) (_numFrames - _nextFrame), buffer->frameCount);
_unrel = buffer->frameCount;
if (buffer->frameCount > 0) {
buffer->raw = (char *) _addr + _frameSize * _nextFrame;
return NO_ERROR;
} else {
buffer->raw = NULL;
return NOT_ENOUGH_DATA;
}
}
void PcmBufferProvider::releaseBuffer(Buffer *buffer) {
if (buffer->frameCount > _unrel) {
ALOGVV("ERROR releaseBuffer() released %zu frames but only %zu available "
"to release", buffer->frameCount, _unrel);
_nextFrame += _unrel;
_unrel = 0;
} else {
ALOGVV("releaseBuffer() released %zu frames out of %zu frames available "
"to release", buffer->frameCount, _unrel);
_nextFrame += buffer->frameCount;
_unrel -= buffer->frameCount;
}
buffer->frameCount = 0;
buffer->raw = NULL;
}
void PcmBufferProvider::reset() {
_nextFrame = 0;
}
} // namespace cocos2d {

View File

@ -0,0 +1,52 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/AudioBufferProvider.h"
#include <stddef.h>
#include <stdio.h>
namespace cocos2d {
class PcmBufferProvider : public AudioBufferProvider
{
public:
PcmBufferProvider();
bool init(const void *addr, size_t frames, size_t frameSize);
virtual status_t getNextBuffer(Buffer *buffer, int64_t pts = kInvalidPTS) override ;
virtual void releaseBuffer(Buffer *buffer) override ;
void reset();
protected:
const void *_addr; // base address
size_t _numFrames; // total frames
size_t _frameSize; // size of each frame in bytes
size_t _nextFrame; // index of next frame to provide
size_t _unrel; // number of frames not yet released
};
} // namespace cocos2d {

View File

@ -0,0 +1,139 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "PcmData"
#include "audio/android/OpenSLHelper.h"
#include "audio/android/PcmData.h"
namespace cocos2d {
PcmData::PcmData()
{
// ALOGV("In the constructor of PcmData (%p)", this);
reset();
}
PcmData::~PcmData()
{
// ALOGV("In the destructor of PcmData (%p)", this);
}
PcmData::PcmData(const PcmData &o)
{
// ALOGV("In the copy constructor of PcmData (%p)", this);
numChannels = o.numChannels;
sampleRate = o.sampleRate;
bitsPerSample = o.bitsPerSample;
containerSize = o.containerSize;
channelMask = o.channelMask;
endianness = o.endianness;
numFrames = o.numFrames;
duration = o.duration;
pcmBuffer = std::move(o.pcmBuffer);
}
PcmData::PcmData(PcmData &&o)
{
// ALOGV("In the move constructor of PcmData (%p)", this);
numChannels = o.numChannels;
sampleRate = o.sampleRate;
bitsPerSample = o.bitsPerSample;
containerSize = o.containerSize;
channelMask = o.channelMask;
endianness = o.endianness;
numFrames = o.numFrames;
duration = o.duration;
pcmBuffer = std::move(o.pcmBuffer);
o.reset();
}
PcmData &PcmData::operator=(const PcmData &o)
{
// ALOGV("In the copy assignment of PcmData");
numChannels = o.numChannels;
sampleRate = o.sampleRate;
bitsPerSample = o.bitsPerSample;
containerSize = o.containerSize;
channelMask = o.channelMask;
endianness = o.endianness;
numFrames = o.numFrames;
duration = o.duration;
pcmBuffer = o.pcmBuffer;
return *this;
}
PcmData &PcmData::operator=(PcmData &&o)
{
// ALOGV("In the move assignment of PcmData");
numChannels = o.numChannels;
sampleRate = o.sampleRate;
bitsPerSample = o.bitsPerSample;
containerSize = o.containerSize;
channelMask = o.channelMask;
endianness = o.endianness;
numFrames = o.numFrames;
duration = o.duration;
pcmBuffer = std::move(o.pcmBuffer);
o.reset();
return *this;
}
void PcmData::reset()
{
numChannels = -1;
sampleRate = -1;
bitsPerSample = -1;
containerSize = -1;
channelMask = -1;
endianness = -1;
numFrames = -1;
duration = -1.0f;
pcmBuffer = nullptr;
}
bool PcmData::isValid() const
{
return numChannels > 0 && sampleRate > 0 && bitsPerSample > 0 && containerSize > 0
&& numFrames > 0 && duration > 0 && pcmBuffer != nullptr;
}
std::string PcmData::toString() const
{
std::string ret;
char buf[256] = {0};
snprintf(buf, sizeof(buf),
"numChannels: %d, sampleRate: %d, bitPerSample: %d, containerSize: %d, "
"channelMask: %d, endianness: %d, numFrames: %d, duration: %f",
numChannels, sampleRate, bitsPerSample, containerSize, channelMask, endianness,
numFrames, duration
);
ret = buf;
return ret;
}
} // namespace cocos2d {

View File

@ -0,0 +1,66 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include <stdio.h>
#include <string>
#include <vector>
#include <memory>
namespace cocos2d {
struct PcmData
{
std::shared_ptr<std::vector<char>> pcmBuffer;
int numChannels;
int sampleRate;
int bitsPerSample;
int containerSize;
int channelMask;
int endianness;
int numFrames;
float duration; // in seconds
PcmData();
~PcmData();
PcmData(const PcmData &o);
PcmData(PcmData &&o);
PcmData &operator=(const PcmData &o);
PcmData &operator=(PcmData &&o);
void reset();
bool isValid() const;
std::string toString() const;
};
} // namespace cocos2d {

View File

@ -0,0 +1,106 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "Track"
#include "audio/android/cutils/log.h"
#include "audio/android/Track.h"
#include <math.h>
namespace cocos2d {
Track::Track(const PcmData &pcmData)
: onStateChanged(nullptr)
, _pcmData(pcmData)
, _prevState(State::IDLE)
, _state(State::IDLE)
, _name(-1)
, _volume(1.0f)
, _isVolumeDirty(true)
, _isLoop(false)
, _isInitialized(false)
, _isAudioFocus(true)
{
init(_pcmData.pcmBuffer->data(), _pcmData.numFrames, _pcmData.bitsPerSample / 8 * _pcmData.numChannels);
}
Track::~Track()
{
ALOGV("~Track(): %p", this);
}
gain_minifloat_packed_t Track::getVolumeLR()
{
float volume = _isAudioFocus ? _volume : 0.0f;
gain_minifloat_t v = gain_from_float(volume);
return gain_minifloat_pack(v, v);
}
bool Track::setPosition(float pos)
{
_nextFrame = (size_t) (pos * _numFrames / _pcmData.duration);
_unrel = 0;
return true;
}
float Track::getPosition() const
{
return _nextFrame * _pcmData.duration / _numFrames;
}
void Track::setVolume(float volume)
{
std::lock_guard<std::mutex> lk(_volumeDirtyMutex);
if (fabs(_volume - volume) > 0.00001)
{
_volume = volume;
setVolumeDirty(true);
}
}
float Track::getVolume() const
{
return _volume;
}
void Track::setAudioFocus(bool isFocus)
{
_isAudioFocus = isFocus;
setVolumeDirty(true);
}
void Track::setState(State state)
{
std::lock_guard<std::mutex> lk(_stateMutex);
if (_state != state)
{
_prevState = _state;
_state = state;
onStateChanged(_state);
}
};
} // namespace cocos2d {

View File

@ -0,0 +1,107 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/PcmData.h"
#include "audio/android/IVolumeProvider.h"
#include "audio/android/PcmBufferProvider.h"
#include <functional>
#include <mutex>
namespace cocos2d {
class Track : public PcmBufferProvider, public IVolumeProvider
{
public:
enum class State
{
IDLE,
PLAYING,
RESUMED,
PAUSED,
STOPPED,
OVER,
DESTROYED
};
Track(const PcmData &pcmData);
virtual ~Track();
inline State getState() const { return _state; };
void setState(State state);
inline State getPrevState() const { return _prevState; };
inline bool isPlayOver() const { return _state == State::PLAYING && _nextFrame >= _numFrames;};
inline void setName(int name) { _name = name; };
inline int getName() const { return _name; };
void setVolume(float volume);
float getVolume() const;
void setAudioFocus(bool isFocus);
bool setPosition(float pos);
float getPosition() const;
virtual gain_minifloat_packed_t getVolumeLR() override ;
inline void setLoop(bool isLoop) { _isLoop = isLoop; };
inline bool isLoop() const { return _isLoop; };
std::function<void(State)> onStateChanged;
private:
inline bool isVolumeDirty() const
{ return _isVolumeDirty; };
inline void setVolumeDirty(bool isDirty)
{ _isVolumeDirty = isDirty; };
inline bool isInitialized() const
{ return _isInitialized; };
inline void setInitialized(bool isInitialized)
{ _isInitialized = isInitialized; };
private:
PcmData _pcmData;
State _prevState;
State _state;
std::mutex _stateMutex;
int _name;
float _volume;
bool _isVolumeDirty;
std::mutex _volumeDirtyMutex;
bool _isLoop;
bool _isInitialized;
bool _isAudioFocus;
friend class AudioMixerController;
};
} // namespace cocos2d {

View File

@ -0,0 +1,424 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "UrlAudioPlayer"
#include "audio/android/UrlAudioPlayer.h"
#include "audio/android/ICallerThreadUtils.h"
#include <math.h>
#include <algorithm> // for std::find
namespace {
std::mutex __playerContainerMutex;
std::vector<cocos2d::UrlAudioPlayer*> __playerContainer;
std::once_flag __onceFlag;
}
namespace cocos2d {
class SLUrlAudioPlayerCallbackProxy
{
public:
static void playEventCallback(SLPlayItf caller, void *context, SLuint32 playEvent)
{
UrlAudioPlayer *thiz = (UrlAudioPlayer *) context;
// We must use a mutex for the whole block of the following function invocation.
std::lock_guard<std::mutex> lk(__playerContainerMutex);
auto iter = std::find(__playerContainer.begin(), __playerContainer.end(), thiz);
if (iter != __playerContainer.end())
{
thiz->playEventCallback(caller, playEvent);
}
}
};
UrlAudioPlayer::UrlAudioPlayer(SLEngineItf engineItf, SLObjectItf outputMixObject, ICallerThreadUtils* callerThreadUtils)
: _engineItf(engineItf), _outputMixObj(outputMixObject),
_callerThreadUtils(callerThreadUtils), _id(-1), _assetFd(nullptr),
_playObj(nullptr), _playItf(nullptr), _seekItf(nullptr), _volumeItf(nullptr),
_volume(0.0f), _duration(0.0f), _isLoop(false), _isAudioFocus(true), _state(State::INVALID),
_playEventCallback(nullptr), _isDestroyed(std::make_shared<bool>(false))
{
std::call_once(__onceFlag, [](){
__playerContainer.reserve(10);
});
__playerContainerMutex.lock();
__playerContainer.push_back(this);
ALOGV("Current UrlAudioPlayer instance count: %d", (int)__playerContainer.size());
__playerContainerMutex.unlock();
_callerThreadId = callerThreadUtils->getCallerThreadId();
}
UrlAudioPlayer::~UrlAudioPlayer()
{
ALOGV("~UrlAudioPlayer(): %p", this);
__playerContainerMutex.lock();
auto iter = std::find(__playerContainer.begin(), __playerContainer.end(), this);
if (iter != __playerContainer.end())
{
__playerContainer.erase(iter);
}
__playerContainerMutex.unlock();
}
void UrlAudioPlayer::playEventCallback(SLPlayItf caller, SLuint32 playEvent)
{
// Note that it's on sub thread, please don't invoke OpenSLES API on sub thread
if (playEvent == SL_PLAYEVENT_HEADATEND)
{
std::shared_ptr<bool> isDestroyed = _isDestroyed;
auto func = [this, isDestroyed](){
// If it was destroyed, just return.
if (*isDestroyed)
{
ALOGV("The UrlAudioPlayer (%p) was destroyed!", this);
return;
}
//Note that It's in the caller's thread (Cocos Thread)
// If state is already stopped, ignore the play over event.
if (_state == State::STOPPED)
{
return;
}
//fix issue#8965:AudioEngine can't looping audio on Android 2.3.x
if (isLoop())
{
play();
}
else
{
setState(State::OVER);
if (_playEventCallback != nullptr)
{
_playEventCallback(State::OVER);
}
ALOGV("UrlAudioPlayer (%p) played over, destroy self ...", this);
destroy();
delete this;
}
};
if (_callerThreadId == std::this_thread::get_id())
{
func();
}
else
{
_callerThreadUtils->performFunctionInCallerThread(func);
}
}
}
void UrlAudioPlayer::setPlayEventCallback(const PlayEventCallback &playEventCallback)
{
_playEventCallback = playEventCallback;
}
void UrlAudioPlayer::stop()
{
ALOGV("UrlAudioPlayer::stop (%p, %d)", this, getId());
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_STOPPED);
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::stop failed");
if (_state == State::PLAYING || _state == State::PAUSED)
{
setLoop(false);
setState(State::STOPPED);
if (_playEventCallback != nullptr)
{
_playEventCallback(State::STOPPED);
}
destroy();
delete this;
}
else
{
ALOGW("UrlAudioPlayer (%p, state:%d) isn't playing or paused, could not invoke stop!", this, static_cast<int>(_state));
}
}
void UrlAudioPlayer::pause()
{
if (_state == State::PLAYING)
{
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PAUSED);
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::pause failed");
setState(State::PAUSED);
}
else
{
ALOGW("UrlAudioPlayer (%p, state:%d) isn't playing, could not invoke pause!", this, static_cast<int>(_state));
}
}
void UrlAudioPlayer::resume()
{
if (_state == State::PAUSED)
{
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING);
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::resume failed");
setState(State::PLAYING);
}
else
{
ALOGW("UrlAudioPlayer (%p, state:%d) isn't paused, could not invoke resume!", this, static_cast<int>(_state));
}
}
void UrlAudioPlayer::play()
{
if (_state == State::INITIALIZED || _state == State::PAUSED)
{
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING);
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::play failed");
setState(State::PLAYING);
}
else
{
ALOGW("UrlAudioPlayer (%p, state:%d) isn't paused or initialized, could not invoke play!", this, static_cast<int>(_state));
}
}
void UrlAudioPlayer::setVolumeToSLPlayer(float volume)
{
int dbVolume = 2000 * log10(volume);
if (dbVolume < SL_MILLIBEL_MIN)
{
dbVolume = SL_MILLIBEL_MIN;
}
SLresult r = (*_volumeItf)->SetVolumeLevel(_volumeItf, dbVolume);
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::setVolumeToSLPlayer %d failed", dbVolume);
}
void UrlAudioPlayer::setVolume(float volume)
{
_volume = volume;
if (_isAudioFocus)
{
setVolumeToSLPlayer(_volume);
}
}
float UrlAudioPlayer::getVolume() const
{
return _volume;
}
void UrlAudioPlayer::setAudioFocus(bool isFocus)
{
_isAudioFocus = isFocus;
float volume = _isAudioFocus ? _volume : 0.0f;
setVolumeToSLPlayer(volume);
}
float UrlAudioPlayer::getDuration() const
{
if (_duration > 0)
{
return _duration;
}
SLmillisecond duration;
SLresult r = (*_playItf)->GetDuration(_playItf, &duration);
SL_RETURN_VAL_IF_FAILED(r, 0.0f, "UrlAudioPlayer::getDuration failed");
if (duration == SL_TIME_UNKNOWN)
{
return -1.0f;
}
else
{
const_cast<UrlAudioPlayer *>(this)->_duration = duration / 1000.0f;
if (_duration <= 0)
{
return -1.0f;
}
}
return _duration;
}
float UrlAudioPlayer::getPosition() const
{
SLmillisecond millisecond;
SLresult r = (*_playItf)->GetPosition(_playItf, &millisecond);
SL_RETURN_VAL_IF_FAILED(r, 0.0f, "UrlAudioPlayer::getPosition failed");
return millisecond / 1000.0f;
}
bool UrlAudioPlayer::setPosition(float pos)
{
SLmillisecond millisecond = 1000.0f * pos;
SLresult r = (*_seekItf)->SetPosition(_seekItf, millisecond, SL_SEEKMODE_ACCURATE);
SL_RETURN_VAL_IF_FAILED(r, false, "UrlAudioPlayer::setPosition %f failed", pos);
return true;
}
bool UrlAudioPlayer::prepare(const std::string &url, SLuint32 locatorType, std::shared_ptr<AssetFd> assetFd, int start,
int length)
{
_url = url;
_assetFd = assetFd;
const char* locatorTypeStr= "UNKNOWN";
if (locatorType == SL_DATALOCATOR_ANDROIDFD)
locatorTypeStr = "SL_DATALOCATOR_ANDROIDFD";
else if (locatorType == SL_DATALOCATOR_URI)
locatorTypeStr = "SL_DATALOCATOR_URI";
else
{
ALOGE("Oops, invalid locatorType: %d", (int)locatorType);
return false;
}
ALOGV("UrlAudioPlayer::prepare: %s, %s, %d, %d, %d", _url.c_str(), locatorTypeStr, _assetFd->getFd(), start,
length);
SLDataSource audioSrc;
SLDataFormat_MIME formatMime = {SL_DATAFORMAT_MIME, nullptr, SL_CONTAINERTYPE_UNSPECIFIED};
audioSrc.pFormat = &formatMime;
//Note: locFd & locUri should be outside of the following if/else block
// Although locFd & locUri are only used inside if/else block, its lifecycle
// will be destroyed right after '}' block. And since we pass a pointer to
// 'audioSrc.pLocator=&locFd/&locUri', pLocator will point to an invalid address
// while invoking Engine::createAudioPlayer interface. So be care of change the position
// of these two variables.
SLDataLocator_AndroidFD locFd;
SLDataLocator_URI locUri;
if (locatorType == SL_DATALOCATOR_ANDROIDFD)
{
locFd = {locatorType, _assetFd->getFd(), start, length};
audioSrc.pLocator = &locFd;
}
else if (locatorType == SL_DATALOCATOR_URI)
{
locUri = {locatorType, (SLchar *) _url.c_str()};
audioSrc.pLocator = &locUri;
ALOGV("locUri: locatorType: %d", (int)locUri.locatorType);
}
// configure audio sink
SLDataLocator_OutputMix locOutmix = {SL_DATALOCATOR_OUTPUTMIX, _outputMixObj};
SLDataSink audioSnk = {&locOutmix, nullptr};
// create audio player
const SLInterfaceID ids[3] = {SL_IID_SEEK, SL_IID_PREFETCHSTATUS, SL_IID_VOLUME};
const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
SLresult result = (*_engineItf)->CreateAudioPlayer(_engineItf, &_playObj, &audioSrc, &audioSnk,
3, ids, req);
SL_RETURN_VAL_IF_FAILED(result, false, "CreateAudioPlayer failed");
// realize the player
result = (*_playObj)->Realize(_playObj, SL_BOOLEAN_FALSE);
SL_RETURN_VAL_IF_FAILED(result, false, "Realize failed");
// get the play interface
result = (*_playObj)->GetInterface(_playObj, SL_IID_PLAY, &_playItf);
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_PLAY failed");
// get the seek interface
result = (*_playObj)->GetInterface(_playObj, SL_IID_SEEK, &_seekItf);
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_SEEK failed");
// get the volume interface
result = (*_playObj)->GetInterface(_playObj, SL_IID_VOLUME, &_volumeItf);
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_VOLUME failed");
result = (*_playItf)->RegisterCallback(_playItf,
SLUrlAudioPlayerCallbackProxy::playEventCallback, this);
SL_RETURN_VAL_IF_FAILED(result, false, "RegisterCallback failed");
result = (*_playItf)->SetCallbackEventsMask(_playItf, SL_PLAYEVENT_HEADATEND);
SL_RETURN_VAL_IF_FAILED(result, false, "SetCallbackEventsMask SL_PLAYEVENT_HEADATEND failed");
setState(State::INITIALIZED);
setVolume(1.0f);
return true;
}
void UrlAudioPlayer::rewind()
{
// Not supported currently. since cocos audio engine will new -> prepare -> play again.
}
void UrlAudioPlayer::setLoop(bool isLoop)
{
_isLoop = isLoop;
SLboolean loopEnable = _isLoop ? SL_BOOLEAN_TRUE : SL_BOOLEAN_FALSE;
SLresult r = (*_seekItf)->SetLoop(_seekItf, loopEnable, 0, SL_TIME_UNKNOWN);
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::setLoop %d failed", _isLoop ? 1 : 0);
}
bool UrlAudioPlayer::isLoop() const
{
return _isLoop;
}
void UrlAudioPlayer::stopAll()
{
// To avoid break the for loop, we need to copy a new map
__playerContainerMutex.lock();
auto temp = __playerContainer;
__playerContainerMutex.unlock();
for (auto&& player : temp)
{
player->stop();
}
}
void UrlAudioPlayer::destroy()
{
if (!*_isDestroyed)
{
*_isDestroyed = true;
ALOGV("UrlAudioPlayer::destroy() %p", this);
SL_DESTROY_OBJ(_playObj);
ALOGV("UrlAudioPlayer::destroy end");
}
}
} // namespace cocos2d {

View File

@ -0,0 +1,135 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/IAudioPlayer.h"
#include "audio/android/OpenSLHelper.h"
#include "audio/android/AssetFd.h"
#include <mutex>
#include <vector>
#include <memory>
#include <thread>
namespace cocos2d {
class ICallerThreadUtils;
class AssetFd;
class UrlAudioPlayer : public IAudioPlayer
{
public:
// Override Functions Begin
virtual int getId() const override
{ return _id; };
virtual void setId(int id) override
{ _id = id; };
virtual std::string getUrl() const override
{ return _url; };
virtual State getState() const override
{ return _state; };
virtual void play() override;
virtual void pause() override;
virtual void resume() override;
virtual void stop() override;
virtual void rewind() override;
virtual void setVolume(float volume) override;
virtual float getVolume() const override;
virtual void setAudioFocus(bool isFocus) override;
virtual void setLoop(bool isLoop) override;
virtual bool isLoop() const override;
virtual float getDuration() const override;
virtual float getPosition() const override;
virtual bool setPosition(float pos) override;
virtual void setPlayEventCallback(const PlayEventCallback &playEventCallback) override;
// Override Functions EndOv
private:
UrlAudioPlayer(SLEngineItf engineItf, SLObjectItf outputMixObject, ICallerThreadUtils* callerThreadUtils);
virtual ~UrlAudioPlayer();
bool prepare(const std::string &url, SLuint32 locatorType, std::shared_ptr<AssetFd> assetFd, int start, int length);
static void stopAll();
void destroy();
inline void setState(State state)
{ _state = state; };
void playEventCallback(SLPlayItf caller, SLuint32 playEvent);
void setVolumeToSLPlayer(float volume);
private:
SLEngineItf _engineItf;
SLObjectItf _outputMixObj;
ICallerThreadUtils* _callerThreadUtils;
int _id;
std::string _url;
std::shared_ptr<AssetFd> _assetFd;
SLObjectItf _playObj;
SLPlayItf _playItf;
SLSeekItf _seekItf;
SLVolumeItf _volumeItf;
float _volume;
float _duration;
bool _isLoop;
bool _isAudioFocus;
State _state;
PlayEventCallback _playEventCallback;
std::thread::id _callerThreadId;
std::shared_ptr<bool> _isDestroyed;
friend class SLUrlAudioPlayerCallbackProxy;
friend class AudioPlayerProvider;
};
} // namespace cocos2d {

View File

@ -0,0 +1,504 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
// ----------------------------------------------------------------------------
#include <stdint.h>
#include <android/log.h>
#include "audio/android/cutils/bitops.h"
#define PROPERTY_VALUE_MAX 256
#define CONSTEXPR constexpr
#ifdef __cplusplus
# define CC_LIKELY( exp ) (__builtin_expect( !!(exp), true ))
# define CC_UNLIKELY( exp ) (__builtin_expect( !!(exp), false ))
#else
# define CC_LIKELY( exp ) (__builtin_expect( !!(exp), 1 ))
# define CC_UNLIKELY( exp ) (__builtin_expect( !!(exp), 0 ))
#endif
/* special audio session values
* (XXX: should this be living in the audio effects land?)
*/
typedef enum {
/* session for effects attached to a particular output stream
* (value must be less than 0)
*/
AUDIO_SESSION_OUTPUT_STAGE = -1,
/* session for effects applied to output mix. These effects can
* be moved by audio policy manager to another output stream
* (value must be 0)
*/
AUDIO_SESSION_OUTPUT_MIX = 0,
/* application does not specify an explicit session ID to be used,
* and requests a new session ID to be allocated
* REFINE: use unique values for AUDIO_SESSION_OUTPUT_MIX and AUDIO_SESSION_ALLOCATE,
* after all uses have been updated from 0 to the appropriate symbol, and have been tested.
*/
AUDIO_SESSION_ALLOCATE = 0,
} audio_session_t;
/* Audio sub formats (see enum audio_format). */
/* PCM sub formats */
typedef enum {
/* All of these are in native byte order */
AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1, /* DO NOT CHANGE - PCM signed 16 bits */
AUDIO_FORMAT_PCM_SUB_8_BIT = 0x2, /* DO NOT CHANGE - PCM unsigned 8 bits */
AUDIO_FORMAT_PCM_SUB_32_BIT = 0x3, /* PCM signed .31 fixed point */
AUDIO_FORMAT_PCM_SUB_8_24_BIT = 0x4, /* PCM signed 8.23 fixed point */
AUDIO_FORMAT_PCM_SUB_FLOAT = 0x5, /* PCM single-precision floating point */
AUDIO_FORMAT_PCM_SUB_24_BIT_PACKED = 0x6, /* PCM signed .23 fixed point packed in 3 bytes */
} audio_format_pcm_sub_fmt_t;
/* The audio_format_*_sub_fmt_t declarations are not currently used */
/* MP3 sub format field definition : can use 11 LSBs in the same way as MP3
* frame header to specify bit rate, stereo mode, version...
*/
typedef enum {
AUDIO_FORMAT_MP3_SUB_NONE = 0x0,
} audio_format_mp3_sub_fmt_t;
/* AMR NB/WB sub format field definition: specify frame block interleaving,
* bandwidth efficient or octet aligned, encoding mode for recording...
*/
typedef enum {
AUDIO_FORMAT_AMR_SUB_NONE = 0x0,
} audio_format_amr_sub_fmt_t;
/* AAC sub format field definition: specify profile or bitrate for recording... */
typedef enum {
AUDIO_FORMAT_AAC_SUB_MAIN = 0x1,
AUDIO_FORMAT_AAC_SUB_LC = 0x2,
AUDIO_FORMAT_AAC_SUB_SSR = 0x4,
AUDIO_FORMAT_AAC_SUB_LTP = 0x8,
AUDIO_FORMAT_AAC_SUB_HE_V1 = 0x10,
AUDIO_FORMAT_AAC_SUB_SCALABLE = 0x20,
AUDIO_FORMAT_AAC_SUB_ERLC = 0x40,
AUDIO_FORMAT_AAC_SUB_LD = 0x80,
AUDIO_FORMAT_AAC_SUB_HE_V2 = 0x100,
AUDIO_FORMAT_AAC_SUB_ELD = 0x200,
} audio_format_aac_sub_fmt_t;
/* VORBIS sub format field definition: specify quality for recording... */
typedef enum {
AUDIO_FORMAT_VORBIS_SUB_NONE = 0x0,
} audio_format_vorbis_sub_fmt_t;
/* Audio format consists of a main format field (upper 8 bits) and a sub format
* field (lower 24 bits).
*
* The main format indicates the main codec type. The sub format field
* indicates options and parameters for each format. The sub format is mainly
* used for record to indicate for instance the requested bitrate or profile.
* It can also be used for certain formats to give informations not present in
* the encoded audio stream (e.g. octet alignment for AMR).
*/
typedef enum {
AUDIO_FORMAT_INVALID = 0xFFFFFFFFUL,
AUDIO_FORMAT_DEFAULT = 0,
AUDIO_FORMAT_PCM = 0x00000000UL, /* DO NOT CHANGE */
AUDIO_FORMAT_MP3 = 0x01000000UL,
AUDIO_FORMAT_AMR_NB = 0x02000000UL,
AUDIO_FORMAT_AMR_WB = 0x03000000UL,
AUDIO_FORMAT_AAC = 0x04000000UL,
AUDIO_FORMAT_HE_AAC_V1 = 0x05000000UL, /* Deprecated, Use AUDIO_FORMAT_AAC_HE_V1*/
AUDIO_FORMAT_HE_AAC_V2 = 0x06000000UL, /* Deprecated, Use AUDIO_FORMAT_AAC_HE_V2*/
AUDIO_FORMAT_VORBIS = 0x07000000UL,
AUDIO_FORMAT_OPUS = 0x08000000UL,
AUDIO_FORMAT_AC3 = 0x09000000UL,
AUDIO_FORMAT_E_AC3 = 0x0A000000UL,
AUDIO_FORMAT_DTS = 0x0B000000UL,
AUDIO_FORMAT_DTS_HD = 0x0C000000UL,
AUDIO_FORMAT_MAIN_MASK = 0xFF000000UL,
AUDIO_FORMAT_SUB_MASK = 0x00FFFFFFUL,
/* Aliases */
/* note != AudioFormat.ENCODING_PCM_16BIT */
AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM |
AUDIO_FORMAT_PCM_SUB_16_BIT),
/* note != AudioFormat.ENCODING_PCM_8BIT */
AUDIO_FORMAT_PCM_8_BIT = (AUDIO_FORMAT_PCM |
AUDIO_FORMAT_PCM_SUB_8_BIT),
AUDIO_FORMAT_PCM_32_BIT = (AUDIO_FORMAT_PCM |
AUDIO_FORMAT_PCM_SUB_32_BIT),
AUDIO_FORMAT_PCM_8_24_BIT = (AUDIO_FORMAT_PCM |
AUDIO_FORMAT_PCM_SUB_8_24_BIT),
AUDIO_FORMAT_PCM_FLOAT = (AUDIO_FORMAT_PCM |
AUDIO_FORMAT_PCM_SUB_FLOAT),
AUDIO_FORMAT_PCM_24_BIT_PACKED = (AUDIO_FORMAT_PCM |
AUDIO_FORMAT_PCM_SUB_24_BIT_PACKED),
AUDIO_FORMAT_AAC_MAIN = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_MAIN),
AUDIO_FORMAT_AAC_LC = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_LC),
AUDIO_FORMAT_AAC_SSR = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_SSR),
AUDIO_FORMAT_AAC_LTP = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_LTP),
AUDIO_FORMAT_AAC_HE_V1 = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_HE_V1),
AUDIO_FORMAT_AAC_SCALABLE = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_SCALABLE),
AUDIO_FORMAT_AAC_ERLC = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_ERLC),
AUDIO_FORMAT_AAC_LD = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_LD),
AUDIO_FORMAT_AAC_HE_V2 = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_HE_V2),
AUDIO_FORMAT_AAC_ELD = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_ELD),
} audio_format_t;
/* For the channel mask for position assignment representation */
enum {
/* These can be a complete audio_channel_mask_t. */
AUDIO_CHANNEL_NONE = 0x0,
AUDIO_CHANNEL_INVALID = 0xC0000000,
/* These can be the bits portion of an audio_channel_mask_t
* with representation AUDIO_CHANNEL_REPRESENTATION_POSITION.
* Using these bits as a complete audio_channel_mask_t is deprecated.
*/
/* output channels */
AUDIO_CHANNEL_OUT_FRONT_LEFT = 0x1,
AUDIO_CHANNEL_OUT_FRONT_RIGHT = 0x2,
AUDIO_CHANNEL_OUT_FRONT_CENTER = 0x4,
AUDIO_CHANNEL_OUT_LOW_FREQUENCY = 0x8,
AUDIO_CHANNEL_OUT_BACK_LEFT = 0x10,
AUDIO_CHANNEL_OUT_BACK_RIGHT = 0x20,
AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x40,
AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x80,
AUDIO_CHANNEL_OUT_BACK_CENTER = 0x100,
AUDIO_CHANNEL_OUT_SIDE_LEFT = 0x200,
AUDIO_CHANNEL_OUT_SIDE_RIGHT = 0x400,
AUDIO_CHANNEL_OUT_TOP_CENTER = 0x800,
AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT = 0x1000,
AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER = 0x2000,
AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT = 0x4000,
AUDIO_CHANNEL_OUT_TOP_BACK_LEFT = 0x8000,
AUDIO_CHANNEL_OUT_TOP_BACK_CENTER = 0x10000,
AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT = 0x20000,
/* REFINE: should these be considered complete channel masks, or only bits? */
AUDIO_CHANNEL_OUT_MONO = AUDIO_CHANNEL_OUT_FRONT_LEFT,
AUDIO_CHANNEL_OUT_STEREO = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
AUDIO_CHANNEL_OUT_FRONT_RIGHT),
AUDIO_CHANNEL_OUT_QUAD = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
AUDIO_CHANNEL_OUT_BACK_LEFT |
AUDIO_CHANNEL_OUT_BACK_RIGHT),
AUDIO_CHANNEL_OUT_QUAD_BACK = AUDIO_CHANNEL_OUT_QUAD,
/* like AUDIO_CHANNEL_OUT_QUAD_BACK with *_SIDE_* instead of *_BACK_* */
AUDIO_CHANNEL_OUT_QUAD_SIDE = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
AUDIO_CHANNEL_OUT_SIDE_LEFT |
AUDIO_CHANNEL_OUT_SIDE_RIGHT),
AUDIO_CHANNEL_OUT_5POINT1 = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
AUDIO_CHANNEL_OUT_FRONT_CENTER |
AUDIO_CHANNEL_OUT_LOW_FREQUENCY |
AUDIO_CHANNEL_OUT_BACK_LEFT |
AUDIO_CHANNEL_OUT_BACK_RIGHT),
AUDIO_CHANNEL_OUT_5POINT1_BACK = AUDIO_CHANNEL_OUT_5POINT1,
/* like AUDIO_CHANNEL_OUT_5POINT1_BACK with *_SIDE_* instead of *_BACK_* */
AUDIO_CHANNEL_OUT_5POINT1_SIDE = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
AUDIO_CHANNEL_OUT_FRONT_CENTER |
AUDIO_CHANNEL_OUT_LOW_FREQUENCY |
AUDIO_CHANNEL_OUT_SIDE_LEFT |
AUDIO_CHANNEL_OUT_SIDE_RIGHT),
// matches the correct AudioFormat.CHANNEL_OUT_7POINT1_SURROUND definition for 7.1
AUDIO_CHANNEL_OUT_7POINT1 = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
AUDIO_CHANNEL_OUT_FRONT_CENTER |
AUDIO_CHANNEL_OUT_LOW_FREQUENCY |
AUDIO_CHANNEL_OUT_BACK_LEFT |
AUDIO_CHANNEL_OUT_BACK_RIGHT |
AUDIO_CHANNEL_OUT_SIDE_LEFT |
AUDIO_CHANNEL_OUT_SIDE_RIGHT),
AUDIO_CHANNEL_OUT_ALL = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
AUDIO_CHANNEL_OUT_FRONT_CENTER |
AUDIO_CHANNEL_OUT_LOW_FREQUENCY |
AUDIO_CHANNEL_OUT_BACK_LEFT |
AUDIO_CHANNEL_OUT_BACK_RIGHT |
AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER |
AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER |
AUDIO_CHANNEL_OUT_BACK_CENTER|
AUDIO_CHANNEL_OUT_SIDE_LEFT|
AUDIO_CHANNEL_OUT_SIDE_RIGHT|
AUDIO_CHANNEL_OUT_TOP_CENTER|
AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT|
AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER|
AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT|
AUDIO_CHANNEL_OUT_TOP_BACK_LEFT|
AUDIO_CHANNEL_OUT_TOP_BACK_CENTER|
AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT),
/* These are bits only, not complete values */
/* input channels */
AUDIO_CHANNEL_IN_LEFT = 0x4,
AUDIO_CHANNEL_IN_RIGHT = 0x8,
AUDIO_CHANNEL_IN_FRONT = 0x10,
AUDIO_CHANNEL_IN_BACK = 0x20,
AUDIO_CHANNEL_IN_LEFT_PROCESSED = 0x40,
AUDIO_CHANNEL_IN_RIGHT_PROCESSED = 0x80,
AUDIO_CHANNEL_IN_FRONT_PROCESSED = 0x100,
AUDIO_CHANNEL_IN_BACK_PROCESSED = 0x200,
AUDIO_CHANNEL_IN_PRESSURE = 0x400,
AUDIO_CHANNEL_IN_X_AXIS = 0x800,
AUDIO_CHANNEL_IN_Y_AXIS = 0x1000,
AUDIO_CHANNEL_IN_Z_AXIS = 0x2000,
AUDIO_CHANNEL_IN_VOICE_UPLINK = 0x4000,
AUDIO_CHANNEL_IN_VOICE_DNLINK = 0x8000,
/* REFINE: should these be considered complete channel masks, or only bits, or deprecated? */
AUDIO_CHANNEL_IN_MONO = AUDIO_CHANNEL_IN_FRONT,
AUDIO_CHANNEL_IN_STEREO = (AUDIO_CHANNEL_IN_LEFT | AUDIO_CHANNEL_IN_RIGHT),
AUDIO_CHANNEL_IN_FRONT_BACK = (AUDIO_CHANNEL_IN_FRONT | AUDIO_CHANNEL_IN_BACK),
AUDIO_CHANNEL_IN_ALL = (AUDIO_CHANNEL_IN_LEFT |
AUDIO_CHANNEL_IN_RIGHT |
AUDIO_CHANNEL_IN_FRONT |
AUDIO_CHANNEL_IN_BACK|
AUDIO_CHANNEL_IN_LEFT_PROCESSED |
AUDIO_CHANNEL_IN_RIGHT_PROCESSED |
AUDIO_CHANNEL_IN_FRONT_PROCESSED |
AUDIO_CHANNEL_IN_BACK_PROCESSED|
AUDIO_CHANNEL_IN_PRESSURE |
AUDIO_CHANNEL_IN_X_AXIS |
AUDIO_CHANNEL_IN_Y_AXIS |
AUDIO_CHANNEL_IN_Z_AXIS |
AUDIO_CHANNEL_IN_VOICE_UPLINK |
AUDIO_CHANNEL_IN_VOICE_DNLINK),
};
/* A channel mask per se only defines the presence or absence of a channel, not the order.
* But see AUDIO_INTERLEAVE_* below for the platform convention of order.
*
* audio_channel_mask_t is an opaque type and its internal layout should not
* be assumed as it may change in the future.
* Instead, always use the functions declared in this header to examine.
*
* These are the current representations:
*
* AUDIO_CHANNEL_REPRESENTATION_POSITION
* is a channel mask representation for position assignment.
* Each low-order bit corresponds to the spatial position of a transducer (output),
* or interpretation of channel (input).
* The user of a channel mask needs to know the context of whether it is for output or input.
* The constants AUDIO_CHANNEL_OUT_* or AUDIO_CHANNEL_IN_* apply to the bits portion.
* It is not permitted for no bits to be set.
*
* AUDIO_CHANNEL_REPRESENTATION_INDEX
* is a channel mask representation for index assignment.
* Each low-order bit corresponds to a selected channel.
* There is no platform interpretation of the various bits.
* There is no concept of output or input.
* It is not permitted for no bits to be set.
*
* All other representations are reserved for future use.
*
* Warning: current representation distinguishes between input and output, but this will not the be
* case in future revisions of the platform. Wherever there is an ambiguity between input and output
* that is currently resolved by checking the channel mask, the implementer should look for ways to
* fix it with additional information outside of the mask.
*/
typedef uint32_t audio_channel_mask_t;
/* Maximum number of channels for all representations */
#define AUDIO_CHANNEL_COUNT_MAX 30
/* log(2) of maximum number of representations, not part of public API */
#define AUDIO_CHANNEL_REPRESENTATION_LOG2 2
/* Representations */
typedef enum {
AUDIO_CHANNEL_REPRESENTATION_POSITION = 0, // must be zero for compatibility
// 1 is reserved for future use
AUDIO_CHANNEL_REPRESENTATION_INDEX = 2,
// 3 is reserved for future use
} audio_channel_representation_t;
/* The return value is undefined if the channel mask is invalid. */
static inline uint32_t audio_channel_mask_get_bits(audio_channel_mask_t channel)
{
return channel & ((1 << AUDIO_CHANNEL_COUNT_MAX) - 1);
}
/* The return value is undefined if the channel mask is invalid. */
static inline audio_channel_representation_t audio_channel_mask_get_representation(
audio_channel_mask_t channel)
{
// The right shift should be sufficient, but also "and" for safety in case mask is not 32 bits
return (audio_channel_representation_t)
((channel >> AUDIO_CHANNEL_COUNT_MAX) & ((1 << AUDIO_CHANNEL_REPRESENTATION_LOG2) - 1));
}
/* Returns the number of channels from an output channel mask,
* used in the context of audio output or playback.
* If a channel bit is set which could _not_ correspond to an output channel,
* it is excluded from the count.
* Returns zero if the representation is invalid.
*/
static inline uint32_t audio_channel_count_from_out_mask(audio_channel_mask_t channel)
{
uint32_t bits = audio_channel_mask_get_bits(channel);
switch (audio_channel_mask_get_representation(channel)) {
case AUDIO_CHANNEL_REPRESENTATION_POSITION:
// REFINE: We can now merge with from_in_mask and remove anding
bits &= AUDIO_CHANNEL_OUT_ALL;
// fall through
case AUDIO_CHANNEL_REPRESENTATION_INDEX:
return popcount(bits);
default:
return 0;
}
}
static inline bool audio_is_valid_format(audio_format_t format)
{
switch (format & AUDIO_FORMAT_MAIN_MASK) {
case AUDIO_FORMAT_PCM:
switch (format) {
case AUDIO_FORMAT_PCM_16_BIT:
case AUDIO_FORMAT_PCM_8_BIT:
case AUDIO_FORMAT_PCM_32_BIT:
case AUDIO_FORMAT_PCM_8_24_BIT:
case AUDIO_FORMAT_PCM_FLOAT:
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
return true;
default:
return false;
}
/* not reached */
case AUDIO_FORMAT_MP3:
case AUDIO_FORMAT_AMR_NB:
case AUDIO_FORMAT_AMR_WB:
case AUDIO_FORMAT_AAC:
case AUDIO_FORMAT_HE_AAC_V1:
case AUDIO_FORMAT_HE_AAC_V2:
case AUDIO_FORMAT_VORBIS:
case AUDIO_FORMAT_OPUS:
case AUDIO_FORMAT_AC3:
case AUDIO_FORMAT_E_AC3:
case AUDIO_FORMAT_DTS:
case AUDIO_FORMAT_DTS_HD:
return true;
default:
return false;
}
}
static inline bool audio_is_linear_pcm(audio_format_t format)
{
return ((format & AUDIO_FORMAT_MAIN_MASK) == AUDIO_FORMAT_PCM);
}
static inline size_t audio_bytes_per_sample(audio_format_t format)
{
size_t size = 0;
switch (format) {
case AUDIO_FORMAT_PCM_32_BIT:
case AUDIO_FORMAT_PCM_8_24_BIT:
size = sizeof(int32_t);
break;
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
size = sizeof(uint8_t) * 3;
break;
case AUDIO_FORMAT_PCM_16_BIT:
size = sizeof(int16_t);
break;
case AUDIO_FORMAT_PCM_8_BIT:
size = sizeof(uint8_t);
break;
case AUDIO_FORMAT_PCM_FLOAT:
size = sizeof(float);
break;
default:
break;
}
return size;
}
/* Not part of public API */
static inline audio_channel_mask_t audio_channel_mask_from_representation_and_bits(
audio_channel_representation_t representation, uint32_t bits)
{
return (audio_channel_mask_t) ((representation << AUDIO_CHANNEL_COUNT_MAX) | bits);
}
/* Derive an output channel mask for position assignment from a channel count.
* This is to be used when the content channel mask is unknown. The 1, 2, 4, 5, 6, 7 and 8 channel
* cases are mapped to the standard game/home-theater layouts, but note that 4 is mapped to quad,
* and not stereo + FC + mono surround. A channel count of 3 is arbitrarily mapped to stereo + FC
* for continuity with stereo.
* Returns the matching channel mask,
* or AUDIO_CHANNEL_NONE if the channel count is zero,
* or AUDIO_CHANNEL_INVALID if the channel count exceeds that of the
* configurations for which a default output channel mask is defined.
*/
static inline audio_channel_mask_t audio_channel_out_mask_from_count(uint32_t channel_count)
{
uint32_t bits;
switch (channel_count) {
case 0:
return AUDIO_CHANNEL_NONE;
case 1:
bits = AUDIO_CHANNEL_OUT_MONO;
break;
case 2:
bits = AUDIO_CHANNEL_OUT_STEREO;
break;
case 3:
bits = AUDIO_CHANNEL_OUT_STEREO | AUDIO_CHANNEL_OUT_FRONT_CENTER;
break;
case 4: // 4.0
bits = AUDIO_CHANNEL_OUT_QUAD;
break;
case 5: // 5.0
bits = AUDIO_CHANNEL_OUT_QUAD | AUDIO_CHANNEL_OUT_FRONT_CENTER;
break;
case 6: // 5.1
bits = AUDIO_CHANNEL_OUT_5POINT1;
break;
case 7: // 6.1
bits = AUDIO_CHANNEL_OUT_5POINT1 | AUDIO_CHANNEL_OUT_BACK_CENTER;
break;
case 8:
bits = AUDIO_CHANNEL_OUT_7POINT1;
break;
// IDEA: FCC_8
default:
return AUDIO_CHANNEL_INVALID;
}
return audio_channel_mask_from_representation_and_bits(
AUDIO_CHANNEL_REPRESENTATION_POSITION, bits);
}

View File

@ -0,0 +1,183 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* #define LOG_NDEBUG 0 */
#define LOG_TAG "audio_utils_format"
#include "audio/android/cutils/log.h"
#include "audio/android/audio_utils/include/audio_utils/primitives.h"
#include "audio/android/audio_utils/include/audio_utils/format.h"
#include "audio/android/audio.h"
void memcpy_by_audio_format(void *dst, audio_format_t dst_format,
const void *src, audio_format_t src_format, size_t count)
{
/* default cases for error falls through to fatal log below. */
if (dst_format == src_format) {
switch (dst_format) {
case AUDIO_FORMAT_PCM_16_BIT:
case AUDIO_FORMAT_PCM_FLOAT:
case AUDIO_FORMAT_PCM_8_BIT:
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
case AUDIO_FORMAT_PCM_32_BIT:
case AUDIO_FORMAT_PCM_8_24_BIT:
memcpy(dst, src, count * audio_bytes_per_sample(dst_format));
return;
default:
break;
}
}
switch (dst_format) {
case AUDIO_FORMAT_PCM_16_BIT:
switch (src_format) {
case AUDIO_FORMAT_PCM_FLOAT:
memcpy_to_i16_from_float((int16_t*)dst, (float*)src, count);
return;
case AUDIO_FORMAT_PCM_8_BIT:
memcpy_to_i16_from_u8((int16_t*)dst, (uint8_t*)src, count);
return;
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
memcpy_to_i16_from_p24((int16_t*)dst, (uint8_t*)src, count);
return;
case AUDIO_FORMAT_PCM_32_BIT:
memcpy_to_i16_from_i32((int16_t*)dst, (int32_t*)src, count);
return;
case AUDIO_FORMAT_PCM_8_24_BIT:
memcpy_to_i16_from_q8_23((int16_t*)dst, (int32_t*)src, count);
return;
default:
break;
}
break;
case AUDIO_FORMAT_PCM_FLOAT:
switch (src_format) {
case AUDIO_FORMAT_PCM_16_BIT:
memcpy_to_float_from_i16((float*)dst, (int16_t*)src, count);
return;
case AUDIO_FORMAT_PCM_8_BIT:
memcpy_to_float_from_u8((float*)dst, (uint8_t*)src, count);
return;
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
memcpy_to_float_from_p24((float*)dst, (uint8_t*)src, count);
return;
case AUDIO_FORMAT_PCM_32_BIT:
memcpy_to_float_from_i32((float*)dst, (int32_t*)src, count);
return;
case AUDIO_FORMAT_PCM_8_24_BIT:
memcpy_to_float_from_q8_23((float*)dst, (int32_t*)src, count);
return;
default:
break;
}
break;
case AUDIO_FORMAT_PCM_8_BIT:
switch (src_format) {
case AUDIO_FORMAT_PCM_16_BIT:
memcpy_to_u8_from_i16((uint8_t*)dst, (int16_t*)src, count);
return;
case AUDIO_FORMAT_PCM_FLOAT:
memcpy_to_u8_from_float((uint8_t*)dst, (float*)src, count);
return;
default:
break;
}
break;
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
switch (src_format) {
case AUDIO_FORMAT_PCM_16_BIT:
memcpy_to_p24_from_i16((uint8_t*)dst, (int16_t*)src, count);
return;
case AUDIO_FORMAT_PCM_FLOAT:
memcpy_to_p24_from_float((uint8_t*)dst, (float*)src, count);
return;
default:
break;
}
break;
case AUDIO_FORMAT_PCM_32_BIT:
switch (src_format) {
case AUDIO_FORMAT_PCM_16_BIT:
memcpy_to_i32_from_i16((int32_t*)dst, (int16_t*)src, count);
return;
case AUDIO_FORMAT_PCM_FLOAT:
memcpy_to_i32_from_float((int32_t*)dst, (float*)src, count);
return;
default:
break;
}
break;
case AUDIO_FORMAT_PCM_8_24_BIT:
switch (src_format) {
case AUDIO_FORMAT_PCM_16_BIT:
memcpy_to_q8_23_from_i16((int32_t*)dst, (int16_t*)src, count);
return;
case AUDIO_FORMAT_PCM_FLOAT:
memcpy_to_q8_23_from_float_with_clamp((int32_t*)dst, (float*)src, count);
return;
case AUDIO_FORMAT_PCM_24_BIT_PACKED: {
memcpy_to_q8_23_from_p24((int32_t *)dst, (uint8_t *)src, count);
return;
}
default:
break;
}
break;
default:
break;
}
LOG_ALWAYS_FATAL("invalid src format %#x for dst format %#x",
src_format, dst_format);
}
size_t memcpy_by_index_array_initialization_from_channel_mask(int8_t *idxary, size_t arysize,
audio_channel_mask_t dst_channel_mask, audio_channel_mask_t src_channel_mask)
{
const audio_channel_representation_t src_representation =
audio_channel_mask_get_representation(src_channel_mask);
const audio_channel_representation_t dst_representation =
audio_channel_mask_get_representation(dst_channel_mask);
const uint32_t src_bits = audio_channel_mask_get_bits(src_channel_mask);
const uint32_t dst_bits = audio_channel_mask_get_bits(dst_channel_mask);
switch (src_representation) {
case AUDIO_CHANNEL_REPRESENTATION_POSITION:
switch (dst_representation) {
case AUDIO_CHANNEL_REPRESENTATION_POSITION:
return memcpy_by_index_array_initialization(idxary, arysize,
dst_bits, src_bits);
case AUDIO_CHANNEL_REPRESENTATION_INDEX:
return memcpy_by_index_array_initialization_dst_index(idxary, arysize,
dst_bits, src_bits);
default:
return 0;
}
break;
case AUDIO_CHANNEL_REPRESENTATION_INDEX:
switch (dst_representation) {
case AUDIO_CHANNEL_REPRESENTATION_POSITION:
return memcpy_by_index_array_initialization_src_index(idxary, arysize,
dst_bits, src_bits);
case AUDIO_CHANNEL_REPRESENTATION_INDEX:
return memcpy_by_index_array_initialization(idxary, arysize,
dst_bits, src_bits);
default:
return 0;
}
break;
default:
return 0;
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef COCOS_AUDIO_FORMAT_H
#define COCOS_AUDIO_FORMAT_H
#include <stdint.h>
#include <sys/cdefs.h>
#include "audio/android/audio.h"
__BEGIN_DECLS
/* Copy buffers with conversion between buffer sample formats.
*
* dst Destination buffer
* dst_format Destination buffer format
* src Source buffer
* src_format Source buffer format
* count Number of samples to copy
*
* Allowed format conversions are given by either case 1 or 2 below:
*
* 1) One of src_format or dst_format is AUDIO_FORMAT_PCM_16_BIT or
* AUDIO_FORMAT_PCM_FLOAT, and the other format type is one of:
*
* AUDIO_FORMAT_PCM_16_BIT
* AUDIO_FORMAT_PCM_FLOAT
* AUDIO_FORMAT_PCM_8_BIT
* AUDIO_FORMAT_PCM_24_BIT_PACKED
* AUDIO_FORMAT_PCM_32_BIT
* AUDIO_FORMAT_PCM_8_24_BIT
*
* 2) Both dst_format and src_format are identical and of the list given
* in (1). This is a straight copy.
*
* The destination and source buffers must be completely separate if the destination
* format size is larger than the source format size. These routines call functions
* in primitives.h, so descriptions of detailed behavior can be reviewed there.
*
* Logs a fatal error if dst or src format is not allowed by the conversion rules above.
*/
void memcpy_by_audio_format(void *dst, audio_format_t dst_format,
const void *src, audio_format_t src_format, size_t count);
/* This function creates an index array for converting audio data with different
* channel position and index masks, used by memcpy_by_index_array().
* Returns the number of array elements required.
* This may be greater than idxcount, so the return value should be checked
* if idxary size is less than 32. Returns zero if the input masks are unrecognized.
*
* Note that idxary is a caller allocated array
* of at least as many channels as present in the dst_mask.
*
* Parameters:
* idxary Updated array of indices of channels in the src frame for the dst frame
* idxcount Number of caller allocated elements in idxary
* dst_mask Bit mask corresponding to destination channels present
* src_mask Bit mask corresponding to source channels present
*/
size_t memcpy_by_index_array_initialization_from_channel_mask(int8_t *idxary, size_t arysize,
audio_channel_mask_t dst_channel_mask, audio_channel_mask_t src_channel_mask);
__END_DECLS
#endif // COCOS_AUDIO_FORMAT_H

View File

@ -0,0 +1,81 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef COCOS_AUDIO_MINIFLOAT_H
#define COCOS_AUDIO_MINIFLOAT_H
#include <stdint.h>
#include <sys/cdefs.h>
__BEGIN_DECLS
/* A single gain expressed as minifloat */
typedef uint16_t gain_minifloat_t;
/* A pair of gain_minifloat_t packed into a single word */
typedef uint32_t gain_minifloat_packed_t;
/* The nominal range of a gain, expressed as a float */
#define GAIN_FLOAT_ZERO 0.0f
#define GAIN_FLOAT_UNITY 1.0f
/* Unity gain expressed as a minifloat */
#define GAIN_MINIFLOAT_UNITY 0xE000
/* Pack a pair of gain_mini_float_t into a combined gain_minifloat_packed_t */
static inline gain_minifloat_packed_t gain_minifloat_pack(gain_minifloat_t left,
gain_minifloat_t right)
{
return (right << 16) | left;
}
/* Unpack a gain_minifloat_packed_t into the two gain_minifloat_t components */
static inline gain_minifloat_t gain_minifloat_unpack_left(gain_minifloat_packed_t packed)
{
return packed & 0xFFFF;
}
static inline gain_minifloat_t gain_minifloat_unpack_right(gain_minifloat_packed_t packed)
{
return packed >> 16;
}
/* A pair of unity gains expressed as a gain_minifloat_packed_t */
#define GAIN_MINIFLOAT_PACKED_UNITY gain_minifloat_pack(GAIN_MINIFLOAT_UNITY, GAIN_MINIFLOAT_UNITY)
/* Convert a float to the internal representation used for gains.
* The nominal range [0.0, 1.0], but the hard range is [0.0, 2.0).
* Negative and underflow values are converted to 0.0,
* and values larger than the hard maximum are truncated to the hard maximum.
*
* Minifloats are ordered, and standard comparisons may be used between them
* in the gain_minifloat_t representation.
*
* Details on internal representation of gains, based on mini-floats:
* The nominal maximum is 1.0 and the hard maximum is 1 ULP less than 2.0, or +6 dB.
* The minimum non-zero value is approximately 1.9e-6 or -114 dB.
* Negative numbers, infinity, and NaN are not supported.
* There are 13 significand bits specified, 1 implied hidden bit, 3 exponent bits,
* and no sign bit. Denormals are supported.
*/
gain_minifloat_t gain_from_float(float f);
/* Convert the internal representation used for gains to float */
float float_from_gain(gain_minifloat_t gain);
__END_DECLS
#endif // COCOS_AUDIO_MINIFLOAT_H

View File

@ -0,0 +1,942 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef COCOS_AUDIO_PRIMITIVES_H
#define COCOS_AUDIO_PRIMITIVES_H
#include <stdint.h>
#include <stdlib.h>
#include <sys/cdefs.h>
__BEGIN_DECLS
/* The memcpy_* conversion routines are designed to work in-place on same dst as
* src buffers only if the types shrink on copy, with the exception of
* memcpy_to_i16_from_u8(). This allows the loops to go upwards for faster cache
* access (and may be more flexible for future optimization later).
*/
/**
* Dither and clamp pairs of 32-bit input samples (sums) to 16-bit output
* samples (out). Each 32-bit input sample can be viewed as a signed fixed-point
* Q19.12 of which the .12 fraction bits are dithered and the 19 integer bits
* are clamped to signed 16 bits. Alternatively the input can be viewed as
* Q4.27, of which the lowest .12 of the fraction is dithered and the remaining
* fraction is converted to the output Q.15, with clamping on the 4 integer
* guard bits.
*
* For interleaved stereo, c is the number of sample pairs,
* and out is an array of interleaved pairs of 16-bit samples per channel.
* For mono, c is the number of samples / 2, and out is an array of 16-bit
* samples. The name "dither" is a misnomer; the current implementation does not
* actually dither but uses truncation. This may change. The out and sums
* buffers must either be completely separate (non-overlapping), or they must
* both start at the same address. Partially overlapping buffers are not
* supported.
*/
void ditherAndClamp(int32_t *out, const int32_t *sums, size_t c);
/* Expand and copy samples from unsigned 8-bit offset by 0x80 to signed 16-bit.
* Parameters:
* dst Destination buffer
* src Source buffer
* count Number of samples to copy
* The destination and source buffers must either be completely separate
* (non-overlapping), or they must both start at the same address. Partially
* overlapping buffers are not supported.
*/
void memcpy_to_i16_from_u8(int16_t *dst, const uint8_t *src, size_t count);
/* Shrink and copy samples from signed 16-bit to unsigned 8-bit offset by 0x80.
* Parameters:
* dst Destination buffer
* src Source buffer
* count Number of samples to copy
* The destination and source buffers must either be completely separate
* (non-overlapping), or they must both start at the same address. Partially
* overlapping buffers are not supported. The conversion is done by truncation,
* without dithering, so it loses resolution.
*/
void memcpy_to_u8_from_i16(uint8_t *dst, const int16_t *src, size_t count);
/* Copy samples from float to unsigned 8-bit offset by 0x80.
* Parameters:
* dst Destination buffer
* src Source buffer
* count Number of samples to copy
* The destination and source buffers must either be completely separate
* (non-overlapping), or they must both start at the same address. Partially
* overlapping buffers are not supported. The conversion is done by truncation,
* without dithering, so it loses resolution.
*/
void memcpy_to_u8_from_float(uint8_t *dst, const float *src, size_t count);
/* Shrink and copy samples from signed 32-bit fixed-point Q0.31 to signed 16-bit
* Q0.15. Parameters: dst Destination buffer src Source buffer count
* Number of samples to copy The destination and source buffers must either be
* completely separate (non-overlapping), or they must both start at the same
* address. Partially overlapping buffers are not supported. The conversion is
* done by truncation, without dithering, so it loses resolution.
*/
void memcpy_to_i16_from_i32(int16_t *dst, const int32_t *src, size_t count);
/* Shrink and copy samples from single-precision floating-point to signed
* 16-bit. Each float should be in the range -1.0 to 1.0. Values outside that
* range are clamped, refer to clamp16_from_float(). Parameters: dst Destination
* buffer src Source buffer count Number of samples to copy The
* destination and source buffers must either be completely separate
* (non-overlapping), or they must both start at the same address. Partially
* overlapping buffers are not supported. The conversion is done by truncation,
* without dithering, so it loses resolution.
*/
void memcpy_to_i16_from_float(int16_t *dst, const float *src, size_t count);
/* Copy samples from signed fixed-point 32-bit Q4.27 to single-precision
* floating-point. The nominal output float range is [-1.0, 1.0] if the
* fixed-point range is [0xf8000000, 0x07ffffff]. The full float range is
* [-16.0, 16.0]. Note the closed range at 1.0 and 16.0 is due to rounding on
* conversion to float. See float_from_q4_27() for details. Parameters: dst
* Destination buffer src Source buffer count Number of samples to copy
* The destination and source buffers must either be completely separate
* (non-overlapping), or they must both start at the same address. Partially
* overlapping buffers are not supported.
*/
void memcpy_to_float_from_q4_27(float *dst, const int32_t *src, size_t count);
/* Copy samples from signed fixed-point 16 bit Q0.15 to single-precision
* floating-point. The output float range is [-1.0, 1.0) for the fixed-point
* range [0x8000, 0x7fff]. No rounding is needed as the representation is exact.
* Parameters:
* dst Destination buffer
* src Source buffer
* count Number of samples to copy
* The destination and source buffers must be completely separate.
*/
void memcpy_to_float_from_i16(float *dst, const int16_t *src, size_t count);
/* Copy samples from unsigned fixed-point 8 bit to single-precision
* floating-point. The output float range is [-1.0, 1.0) for the fixed-point
* range [0x00, 0xFF]. No rounding is needed as the representation is exact.
* Parameters:
* dst Destination buffer
* src Source buffer
* count Number of samples to copy
* The destination and source buffers must be completely separate.
*/
void memcpy_to_float_from_u8(float *dst, const uint8_t *src, size_t count);
/* Copy samples from signed fixed-point packed 24 bit Q0.23 to single-precision
* floating-point. The packed 24 bit input is stored in native endian format in
* a uint8_t byte array. The output float range is [-1.0, 1.0) for the
* fixed-point range [0x800000, 0x7fffff]. No rounding is needed as the
* representation is exact. Parameters: dst Destination buffer src Source
* buffer count Number of samples to copy The destination and source buffers
* must be completely separate.
*/
void memcpy_to_float_from_p24(float *dst, const uint8_t *src, size_t count);
/* Copy samples from signed fixed-point packed 24 bit Q0.23 to signed fixed
* point 16 bit Q0.15. The packed 24 bit output is stored in native endian
* format in a uint8_t byte array. The data is truncated without rounding.
* Parameters:
* dst Destination buffer
* src Source buffer
* count Number of samples to copy
* The destination and source buffers must either be completely separate
* (non-overlapping), or they must both start at the same address. Partially
* overlapping buffers are not supported.
*/
void memcpy_to_i16_from_p24(int16_t *dst, const uint8_t *src, size_t count);
/* Copy samples from signed fixed-point packed 24 bit Q0.23 to signed
* fixed-point 32-bit Q0.31. The packed 24 bit input is stored in native endian
* format in a uint8_t byte array. The output data range is [0x80000000,
* 0x7fffff00] at intervals of 0x100. Parameters: dst Destination buffer src
* Source buffer count Number of samples to copy The destination and source
* buffers must be completely separate.
*/
void memcpy_to_i32_from_p24(int32_t *dst, const uint8_t *src, size_t count);
/* Copy samples from signed fixed point 16 bit Q0.15 to signed fixed-point
* packed 24 bit Q0.23. The packed 24 bit output is assumed to be a
* native-endian uint8_t byte array. The output data range is [0x800000,
* 0x7fff00] (not full). Nevertheless there is no DC offset on the output, if
* the input has no DC offset. Parameters: dst Destination buffer src Source
* buffer count Number of samples to copy The destination and source buffers
* must be completely separate.
*/
void memcpy_to_p24_from_i16(uint8_t *dst, const int16_t *src, size_t count);
/* Copy samples from single-precision floating-point to signed fixed-point
* packed 24 bit Q0.23. The packed 24 bit output is assumed to be a
* native-endian uint8_t byte array. The data is clamped and rounded to nearest,
* ties away from zero. See clamp24_from_float() for details. Parameters: dst
* Destination buffer src Source buffer count Number of samples to copy
* The destination and source buffers must either be completely separate
* (non-overlapping), or they must both start at the same address. Partially
* overlapping buffers are not supported.
*/
void memcpy_to_p24_from_float(uint8_t *dst, const float *src, size_t count);
/* Copy samples from signed fixed-point 32-bit Q8.23 to signed fixed-point
* packed 24 bit Q0.23. The packed 24 bit output is assumed to be a
* native-endian uint8_t byte array. The data is clamped to the range is
* [0x800000, 0x7fffff]. Parameters: dst Destination buffer src Source
* buffer count Number of samples to copy The destination and source buffers
* must be completely separate.
*/
void memcpy_to_p24_from_q8_23(uint8_t *dst, const int32_t *src, size_t count);
/* Shrink and copy samples from signed 32-bit fixed-point Q0.31
* to signed fixed-point packed 24 bit Q0.23.
* The packed 24 bit output is assumed to be a native-endian uint8_t byte array.
* Parameters:
* dst Destination buffer
* src Source buffer
* count Number of samples to copy
* The destination and source buffers must either be completely separate
* (non-overlapping), or they must both start at the same address. Partially
* overlapping buffers are not supported. The conversion is done by truncation,
* without dithering, so it loses resolution.
*/
void memcpy_to_p24_from_i32(uint8_t *dst, const int32_t *src, size_t count);
/* Copy samples from signed fixed point 16-bit Q0.15 to signed fixed-point
* 32-bit Q8.23. The output data range is [0xff800000, 0x007fff00] at intervals
* of 0x100. Parameters: dst Destination buffer src Source buffer count
* Number of samples to copy The destination and source buffers must be
* completely separate.
*/
void memcpy_to_q8_23_from_i16(int32_t *dst, const int16_t *src, size_t count);
/* Copy samples from single-precision floating-point to signed fixed-point
* 32-bit Q8.23. This copy will clamp the Q8.23 representation to [0xff800000,
* 0x007fffff] even though there are guard bits available. Fractional lsb is
* rounded to nearest, ties away from zero. See clamp24_from_float() for
* details. Parameters: dst Destination buffer src Source buffer count
* Number of samples to copy The destination and source buffers must either be
* completely separate (non-overlapping), or they must both start at the same
* address. Partially overlapping buffers are not supported.
*/
void memcpy_to_q8_23_from_float_with_clamp(int32_t *dst, const float *src,
size_t count);
/* Copy samples from signed fixed point packed 24-bit Q0.23 to signed
* fixed-point 32-bit Q8.23. The output data range is [0xff800000, 0x007fffff].
* Parameters:
* dst Destination buffer
* src Source buffer
* count Number of samples to copy
* The destination and source buffers must be completely separate.
*/
void memcpy_to_q8_23_from_p24(int32_t *dst, const uint8_t *src, size_t count);
/* Copy samples from single-precision floating-point to signed fixed-point
* 32-bit Q4.27. The conversion will use the full available Q4.27 range,
* including guard bits. Fractional lsb is rounded to nearest, ties away from
* zero. See clampq4_27_from_float() for details. Parameters: dst Destination
* buffer src Source buffer count Number of samples to copy The
* destination and source buffers must either be completely separate
* (non-overlapping), or they must both start at the same address. Partially
* overlapping buffers are not supported.
*/
void memcpy_to_q4_27_from_float(int32_t *dst, const float *src, size_t count);
/* Copy samples from signed fixed-point 32-bit Q8.23 to signed fixed point
* 16-bit Q0.15. The data is clamped, and truncated without rounding.
* Parameters:
* dst Destination buffer
* src Source buffer
* count Number of samples to copy
* The destination and source buffers must either be completely separate
* (non-overlapping), or they must both start at the same address. Partially
* overlapping buffers are not supported.
*/
void memcpy_to_i16_from_q8_23(int16_t *dst, const int32_t *src, size_t count);
/* Copy samples from signed fixed-point 32-bit Q8.23 to single-precision
* floating-point. The nominal output float range is [-1.0, 1.0) for the
* fixed-point range [0xff800000, 0x007fffff]. The maximum output float range is
* [-256.0, 256.0). No rounding is needed as the representation is exact for
* nominal values. Rounding for overflow values is to nearest, ties to even.
* Parameters:
* dst Destination buffer
* src Source buffer
* count Number of samples to copy
* The destination and source buffers must either be completely separate
* (non-overlapping), or they must both start at the same address. Partially
* overlapping buffers are not supported.
*/
void memcpy_to_float_from_q8_23(float *dst, const int32_t *src, size_t count);
/* Copy samples from signed fixed point 16-bit Q0.15 to signed fixed-point
* 32-bit Q0.31. The output data range is [0x80000000, 0x7fff0000] at intervals
* of 0x10000. Parameters: dst Destination buffer src Source buffer
* count Number of samples to copy
* The destination and source buffers must be completely separate.
*/
void memcpy_to_i32_from_i16(int32_t *dst, const int16_t *src, size_t count);
/* Copy samples from single-precision floating-point to signed fixed-point
* 32-bit Q0.31. If rounding is needed on truncation, the fractional lsb is
* rounded to nearest, ties away from zero. See clamp32_from_float() for
* details. Parameters: dst Destination buffer src Source buffer count
* Number of samples to copy The destination and source buffers must either be
* completely separate (non-overlapping), or they must both start at the same
* address. Partially overlapping buffers are not supported.
*/
void memcpy_to_i32_from_float(int32_t *dst, const float *src, size_t count);
/* Copy samples from signed fixed-point 32-bit Q0.31 to single-precision
* floating-point. The float range is [-1.0, 1.0] for the fixed-point range
* [0x80000000, 0x7fffffff]. Rounding is done according to float_from_i32().
* Parameters:
* dst Destination buffer
* src Source buffer
* count Number of samples to copy
* The destination and source buffers must either be completely separate
* (non-overlapping), or they must both start at the same address. Partially
* overlapping buffers are not supported.
*/
void memcpy_to_float_from_i32(float *dst, const int32_t *src, size_t count);
/* Downmix pairs of interleaved stereo input 16-bit samples to mono output
* 16-bit samples. Parameters: dst Destination buffer src Source buffer
* count Number of stereo frames to downmix
* The destination and source buffers must be completely separate
* (non-overlapping). The current implementation truncates the mean rather than
* dither, but this may change.
*/
void downmix_to_mono_i16_from_stereo_i16(int16_t *dst, const int16_t *src,
size_t count);
/* Upmix mono input 16-bit samples to pairs of interleaved stereo output 16-bit
* samples by duplicating. Parameters: dst Destination buffer src Source
* buffer count Number of mono samples to upmix The destination and source
* buffers must be completely separate (non-overlapping).
*/
void upmix_to_stereo_i16_from_mono_i16(int16_t *dst, const int16_t *src,
size_t count);
/* Downmix pairs of interleaved stereo input float samples to mono output float
* samples by averaging the stereo pair together. Parameters: dst Destination
* buffer src Source buffer count Number of stereo frames to downmix The
* destination and source buffers must be completely separate (non-overlapping),
* or they must both start at the same address.
*/
void downmix_to_mono_float_from_stereo_float(float *dst, const float *src,
size_t count);
/* Upmix mono input float samples to pairs of interleaved stereo output float
* samples by duplicating. Parameters: dst Destination buffer src Source
* buffer count Number of mono samples to upmix The destination and source
* buffers must be completely separate (non-overlapping).
*/
void upmix_to_stereo_float_from_mono_float(float *dst, const float *src,
size_t count);
/* Return the total number of non-zero 32-bit samples */
size_t nonZeroMono32(const int32_t *samples, size_t count);
/* Return the total number of non-zero 16-bit samples */
size_t nonZeroMono16(const int16_t *samples, size_t count);
/* Return the total number of non-zero stereo frames, where a frame is
* considered non-zero if either of its constituent 32-bit samples is non-zero
*/
size_t nonZeroStereo32(const int32_t *frames, size_t count);
/* Return the total number of non-zero stereo frames, where a frame is
* considered non-zero if either of its constituent 16-bit samples is non-zero
*/
size_t nonZeroStereo16(const int16_t *frames, size_t count);
/* Copy frames, selecting source samples based on a source channel mask to fit
* the destination channel mask. Unmatched channels in the destination channel
* mask are zero filled. Unmatched channels in the source channel mask are
* dropped. Channels present in the channel mask are represented by set bits in
* the uint32_t value and are matched without further interpretation.
* Parameters:
* dst Destination buffer
* dst_mask Bit mask corresponding to destination channels present
* src Source buffer
* src_mask Bit mask corresponding to source channels present
* sample_size Size of each sample in bytes. Must be 1, 2, 3, or 4.
* count Number of frames to copy
* The destination and source buffers must be completely separate
* (non-overlapping). If the sample size is not in range, the function will
* abort.
*/
void memcpy_by_channel_mask(void *dst, uint32_t dst_mask, const void *src,
uint32_t src_mask, size_t sample_size,
size_t count);
/* Copy frames, selecting source samples based on an index array (idxary).
* The idxary[] consists of dst_channels number of elements.
* The ith element if idxary[] corresponds the ith destination channel.
* A non-negative value is the channel index in the source frame.
* A negative index (-1) represents filling with 0.
*
* Example: Swapping L and R channels for stereo streams
* idxary[0] = 1;
* idxary[1] = 0;
*
* Example: Copying a mono source to the front center 5.1 channel
* idxary[0] = -1;
* idxary[1] = -1;
* idxary[2] = 0;
* idxary[3] = -1;
* idxary[4] = -1;
* idxary[5] = -1;
*
* This copy allows swizzling of channels or replication of channels.
*
* Parameters:
* dst Destination buffer
* dst_channels Number of destination channels per frame
* src Source buffer
* src_channels Number of source channels per frame
* idxary Array of indices representing channels in the source frame
* sample_size Size of each sample in bytes. Must be 1, 2, 3, or 4.
* count Number of frames to copy
* The destination and source buffers must be completely separate
* (non-overlapping). If the sample size is not in range, the function will
* abort.
*/
void memcpy_by_index_array(void *dst, uint32_t dst_channels, const void *src,
uint32_t src_channels, const int8_t *idxary,
size_t sample_size, size_t count);
/* Prepares an index array (idxary) from channel masks, which can be later
* used by memcpy_by_index_array(). Returns the number of array elements
* required. This may be greater than idxcount, so the return value should be
* checked if idxary size is less than 32. Note that idxary is a caller
* allocated array of at least as many channels as present in the dst_mask.
* Channels present in the channel mask are represented by set bits in the
* uint32_t value and are matched without further interpretation.
*
* This function is typically used for converting audio data with different
* channel position masks.
*
* Parameters:
* idxary Updated array of indices of channels in the src frame for the
* dst frame idxcount Number of caller allocated elements in idxary dst_mask
* Bit mask corresponding to destination channels present src_mask Bit mask
* corresponding to source channels present
*/
size_t memcpy_by_index_array_initialization(int8_t *idxary, size_t idxcount,
uint32_t dst_mask,
uint32_t src_mask);
/* Prepares an index array (idxary) from channel masks, which can be later
* used by memcpy_by_index_array(). Returns the number of array elements
* required.
*
* For a source channel index mask, the source channels will map to the
* destination channels as if counting the set bits in dst_mask in order from
* lsb to msb (zero bits are ignored). The ith bit of the src_mask corresponds
* to the ith SET bit of dst_mask and the ith destination channel. Hence, a
* zero ith bit of the src_mask indicates that the ith destination channel plays
* silence.
*
* Parameters:
* idxary Updated array of indices of channels in the src frame for the
* dst frame idxcount Number of caller allocated elements in idxary dst_mask
* Bit mask corresponding to destination channels present src_mask Bit mask
* corresponding to source channels present
*/
size_t memcpy_by_index_array_initialization_src_index(int8_t *idxary,
size_t idxcount,
uint32_t dst_mask,
uint32_t src_mask);
/* Prepares an index array (idxary) from channel mask bits, which can be later
* used by memcpy_by_index_array(). Returns the number of array elements
* required.
*
* This initialization is for a destination channel index mask from a positional
* source mask.
*
* For an destination channel index mask, the input channels will map
* to the destination channels, with the ith SET bit in the source bits
* corresponding to the ith bit in the destination bits. If there is a zero bit
* in the middle of set destination bits (unlikely), the corresponding source
* channel will be dropped.
*
* Parameters:
* idxary Updated array of indices of channels in the src frame for the
* dst frame idxcount Number of caller allocated elements in idxary dst_mask
* Bit mask corresponding to destination channels present src_mask Bit mask
* corresponding to source channels present
*/
size_t memcpy_by_index_array_initialization_dst_index(int8_t *idxary,
size_t idxcount,
uint32_t dst_mask,
uint32_t src_mask);
/**
* Clamp (aka hard limit or clip) a signed 32-bit sample to 16-bit range.
*/
static inline int16_t clamp16(int32_t sample) {
if ((sample >> 15) ^ (sample >> 31))
sample = 0x7FFF ^ (sample >> 31);
return sample;
}
/*
* Convert a IEEE 754 single precision float [-1.0, 1.0) to int16_t [-32768,
* 32767] with clamping. Note the open bound at 1.0, values within 1/65536
* of 1.0 map to 32767 instead of 32768 (early clamping due to the smaller
* positive integer subrange).
*
* Values outside the range [-1.0, 1.0) are properly clamped to -32768 and
* 32767, including -Inf and +Inf. NaN will generally be treated either as
* -32768 or 32767, depending on the sign bit inside NaN (whose representation
* is not unique). Nevertheless, strictly speaking, NaN behavior should be
* considered undefined.
*
* Rounding of 0.5 lsb is to even (default for IEEE 754).
*/
static inline int16_t clamp16_from_float(float f) {
/* Offset is used to expand the valid range of [-1.0, 1.0) into the 16 lsbs of
* the floating point significand. The normal shift is 3<<22, but the -15
* offset is used to multiply by 32768.
*/
static const float offset = (float)(3 << (22 - 15));
/* zero = (0x10f << 22) = 0x43c00000 (not directly used) */
static const int32_t limneg = (0x10f << 22) /*zero*/ - 32768; /* 0x43bf8000 */
static const int32_t limpos = (0x10f << 22) /*zero*/ + 32767; /* 0x43c07fff */
union {
float f;
int32_t i;
} u;
u.f = f + offset; /* recenter valid range */
/* Now the valid range is represented as integers between [limneg, limpos].
* Clamp using the fact that float representation (as an integer) is an
* ordered set.
*/
if (u.i < limneg)
u.i = -32768;
else if (u.i > limpos)
u.i = 32767;
return u
.i; /* Return lower 16 bits, the part of interest in the significand. */
}
/*
* Convert a IEEE 754 single precision float [-1.0, 1.0) to uint8_t [0, 0xff]
* with clamping. Note the open bound at 1.0, values within 1/128 of 1.0 map
* to 255 instead of 256 (early clamping due to the smaller positive integer
* subrange).
*
* Values outside the range [-1.0, 1.0) are properly clamped to 0 and 255,
* including -Inf and +Inf. NaN will generally be treated either as 0 or 255,
* depending on the sign bit inside NaN (whose representation is not unique).
* Nevertheless, strictly speaking, NaN behavior should be considered undefined.
*
* Rounding of 0.5 lsb is to even (default for IEEE 754).
*/
static inline uint8_t clamp8_from_float(float f) {
/* Offset is used to expand the valid range of [-1.0, 1.0) into the 16 lsbs of
* the floating point significand. The normal shift is 3<<22, but the -7
* offset is used to multiply by 128.
*/
static const float offset = (float)((3 << (22 - 7)) + 1 /* to cancel -1.0 */);
/* zero = (0x11f << 22) = 0x47c00000 */
static const int32_t limneg = (0x11f << 22) /*zero*/;
static const int32_t limpos = (0x11f << 22) /*zero*/ + 255; /* 0x47c000ff */
union {
float f;
int32_t i;
} u;
u.f = f + offset; /* recenter valid range */
/* Now the valid range is represented as integers between [limneg, limpos].
* Clamp using the fact that float representation (as an integer) is an
* ordered set.
*/
if (u.i < limneg)
return 0;
if (u.i > limpos)
return 255;
return u
.i; /* Return lower 8 bits, the part of interest in the significand. */
}
/* Convert a single-precision floating point value to a Q0.23 integer value,
* stored in a 32 bit signed integer (technically stored as Q8.23, but clamped
* to Q0.23).
*
* Rounds to nearest, ties away from 0.
*
* Values outside the range [-1.0, 1.0) are properly clamped to -8388608 and
* 8388607, including -Inf and +Inf. NaN values are considered undefined, and
* behavior may change depending on hardware and future implementation of this
* function.
*/
static inline int32_t clamp24_from_float(float f) {
static const float scale = (float)(1 << 23);
static const float limpos = 0x7fffff / (float)(1 << 23);
static const float limneg = -0x800000 / (float)(1 << 23);
if (f <= limneg) {
return -0x800000;
} else if (f >= limpos) {
return 0x7fffff;
}
f *= scale;
/* integer conversion is through truncation (though int to float is not).
* ensure that we round to nearest, ties away from 0.
*/
return f > 0 ? f + 0.5 : f - 0.5;
}
/* Convert a signed fixed-point 32-bit Q8.23 value to a Q0.23 integer value,
* stored in a 32-bit signed integer (technically stored as Q8.23, but clamped
* to Q0.23).
*
* Values outside the range [-0x800000, 0x7fffff] are clamped to that range.
*/
static inline int32_t clamp24_from_q8_23(int32_t ival) {
static const int32_t limpos = 0x7fffff;
static const int32_t limneg = -0x800000;
if (ival < limneg) {
return limneg;
} else if (ival > limpos) {
return limpos;
} else {
return ival;
}
}
/* Convert a single-precision floating point value to a Q4.27 integer value.
* Rounds to nearest, ties away from 0.
*
* Values outside the range [-16.0, 16.0) are properly clamped to -2147483648
* and 2147483647, including -Inf and +Inf. NaN values are considered undefined,
* and behavior may change depending on hardware and future implementation of
* this function.
*/
static inline int32_t clampq4_27_from_float(float f) {
static const float scale = (float)(1UL << 27);
static const float limpos = 16.;
static const float limneg = -16.;
if (f <= limneg) {
return -0x80000000; /* or 0x80000000 */
} else if (f >= limpos) {
return 0x7fffffff;
}
f *= scale;
/* integer conversion is through truncation (though int to float is not).
* ensure that we round to nearest, ties away from 0.
*/
return f > 0 ? f + 0.5 : f - 0.5;
}
/* Convert a single-precision floating point value to a Q0.31 integer value.
* Rounds to nearest, ties away from 0.
*
* Values outside the range [-1.0, 1.0) are properly clamped to -2147483648 and
* 2147483647, including -Inf and +Inf. NaN values are considered undefined, and
* behavior may change depending on hardware and future implementation of this
* function.
*/
static inline int32_t clamp32_from_float(float f) {
static const float scale = (float)(1UL << 31);
static const float limpos = 1.;
static const float limneg = -1.;
if (f <= limneg) {
return -0x80000000; /* or 0x80000000 */
} else if (f >= limpos) {
return 0x7fffffff;
}
f *= scale;
/* integer conversion is through truncation (though int to float is not).
* ensure that we round to nearest, ties away from 0.
*/
return f > 0 ? f + 0.5 : f - 0.5;
}
/* Convert a signed fixed-point 32-bit Q4.27 value to single-precision
* floating-point. The nominal output float range is [-1.0, 1.0] if the
* fixed-point range is [0xf8000000, 0x07ffffff]. The full float range is
* [-16.0, 16.0].
*
* Note the closed range at 1.0 and 16.0 is due to rounding on conversion to
* float. In more detail: if the fixed-point integer exceeds 24 bit significand
* of single precision floating point, the 0.5 lsb in the significand conversion
* will round towards even, as per IEEE 754 default.
*/
static inline float float_from_q4_27(int32_t ival) {
/* The scale factor is the reciprocal of the fractional bits.
*
* Since the scale factor is a power of 2, the scaling is exact, and there
* is no rounding due to the multiplication - the bit pattern is preserved.
* However, there may be rounding due to the fixed-point to float conversion,
* as described above.
*/
static const float scale = 1. / (float)(1UL << 27);
return ival * scale;
}
/* Convert an unsigned fixed-point 32-bit U4.28 value to single-precision
* floating-point. The nominal output float range is [0.0, 1.0] if the
* fixed-point range is [0x00000000, 0x10000000]. The full float range is
* [0.0, 16.0].
*
* Note the closed range at 1.0 and 16.0 is due to rounding on conversion to
* float. In more detail: if the fixed-point integer exceeds 24 bit significand
* of single precision floating point, the 0.5 lsb in the significand conversion
* will round towards even, as per IEEE 754 default.
*/
static inline float float_from_u4_28(uint32_t uval) {
static const float scale = 1. / (float)(1UL << 28);
return uval * scale;
}
/* Convert an unsigned fixed-point 16-bit U4.12 value to single-precision
* floating-point. The nominal output float range is [0.0, 1.0] if the
* fixed-point range is [0x0000, 0x1000]. The full float range is [0.0, 16.0).
*/
static inline float float_from_u4_12(uint16_t uval) {
static const float scale = 1. / (float)(1UL << 12);
return uval * scale;
}
/* Convert a single-precision floating point value to a U4.28 integer value.
* Rounds to nearest, ties away from 0.
*
* Values outside the range [0, 16.0] are properly clamped to [0, 4294967295]
* including -Inf and +Inf. NaN values are considered undefined, and behavior
* may change depending on hardware and future implementation of this function.
*/
static inline uint32_t u4_28_from_float(float f) {
static const float scale = (float)(1 << 28);
static const float limpos = (float)((double)0xffffffffUL / (double)(1 << 28));
if (f <= 0.) {
return 0;
} else if (f >= limpos) {
return 0xffffffff;
}
/* integer conversion is through truncation (though int to float is not).
* ensure that we round to nearest, ties away from 0.
*/
return f * scale + 0.5;
}
/* Convert a single-precision floating point value to a U4.12 integer value.
* Rounds to nearest, ties away from 0.
*
* Values outside the range [0, 16.0) are properly clamped to [0, 65535]
* including -Inf and +Inf. NaN values are considered undefined, and behavior
* may change depending on hardware and future implementation of this function.
*/
static inline uint16_t u4_12_from_float(float f) {
static const float scale = (float)(1 << 12);
static const float limpos = 0xffff / (float)(1 << 12);
if (f <= 0.) {
return 0;
} else if (f >= limpos) {
return 0xffff;
}
/* integer conversion is through truncation (though int to float is not).
* ensure that we round to nearest, ties away from 0.
*/
return f * scale + 0.5;
}
/* Convert a signed fixed-point 16-bit Q0.15 value to single-precision
* floating-point. The output float range is [-1.0, 1.0) for the fixed-point
* range [0x8000, 0x7fff].
*
* There is no rounding, the conversion and representation is exact.
*/
static inline float float_from_i16(int16_t ival) {
/* The scale factor is the reciprocal of the nominal 16 bit integer
* half-sided range (32768).
*
* Since the scale factor is a power of 2, the scaling is exact, and there
* is no rounding due to the multiplication - the bit pattern is preserved.
*/
static const float scale = 1. / (float)(1UL << 15);
return ival * scale;
}
/* Convert an unsigned fixed-point 8-bit U0.8 value to single-precision
* floating-point. The nominal output float range is [-1.0, 1.0) if the
* fixed-point range is [0x00, 0xff].
*/
static inline float float_from_u8(uint8_t uval) {
static const float scale = 1. / (float)(1UL << 7);
return ((int)uval - 128) * scale;
}
/* Convert a packed 24bit Q0.23 value stored native-endian in a uint8_t ptr
* to a signed fixed-point 32 bit integer Q0.31 value. The output Q0.31 range
* is [0x80000000, 0x7fffff00] for the fixed-point range [0x800000, 0x7fffff].
* Even though the output range is limited on the positive side, there is no
* DC offset on the output, if the input has no DC offset.
*
* Avoid relying on the limited output range, as future implementations may go
* to full range.
*/
static inline int32_t i32_from_p24(const uint8_t *packed24) {
/* convert to 32b */
return (packed24[0] << 8) | (packed24[1] << 16) | (packed24[2] << 24);
}
/* Convert a 32-bit Q0.31 value to single-precision floating-point.
* The output float range is [-1.0, 1.0] for the fixed-point range
* [0x80000000, 0x7fffffff].
*
* Rounding may occur in the least significant 8 bits for large fixed point
* values due to storage into the 24-bit floating-point significand.
* Rounding will be to nearest, ties to even.
*/
static inline float float_from_i32(int32_t ival) {
static const float scale = 1. / (float)(1UL << 31);
return ival * scale;
}
/* Convert a packed 24bit Q0.23 value stored native endian in a uint8_t ptr
* to single-precision floating-point. The output float range is [-1.0, 1.0)
* for the fixed-point range [0x800000, 0x7fffff].
*
* There is no rounding, the conversion and representation is exact.
*/
static inline float float_from_p24(const uint8_t *packed24) {
return float_from_i32(i32_from_p24(packed24));
}
/* Convert a 24-bit Q8.23 value to single-precision floating-point.
* The nominal output float range is [-1.0, 1.0) for the fixed-point
* range [0xff800000, 0x007fffff]. The maximum float range is [-256.0, 256.0).
*
* There is no rounding in the nominal range, the conversion and representation
* is exact. For values outside the nominal range, rounding is to nearest, ties
* to even.
*/
static inline float float_from_q8_23(int32_t ival) {
static const float scale = 1. / (float)(1UL << 23);
return ival * scale;
}
/**
* Multiply-accumulate 16-bit terms with 32-bit result: return a + in*v.
*/
static inline int32_t mulAdd(int16_t in, int16_t v, int32_t a) {
#if defined(__arm__) && !defined(__thumb__)
int32_t out;
asm("smlabb %[out], %[in], %[v], %[a] \n"
: [out] "=r"(out)
: [in] "%r"(in), [v] "r"(v), [a] "r"(a)
:);
return out;
#else
return a + in * (int32_t)v;
#endif
}
/**
* Multiply 16-bit terms with 32-bit result: return in*v.
*/
static inline int32_t mul(int16_t in, int16_t v) {
#if defined(__arm__) && !defined(__thumb__)
int32_t out;
asm("smulbb %[out], %[in], %[v] \n"
: [out] "=r"(out)
: [in] "%r"(in), [v] "r"(v)
:);
return out;
#else
return in * (int32_t)v;
#endif
}
/**
* Similar to mulAdd, but the 16-bit terms are extracted from a 32-bit
* interleaved stereo pair.
*/
static inline int32_t mulAddRL(int left, uint32_t inRL, uint32_t vRL,
int32_t a) {
#if defined(__arm__) && !defined(__thumb__)
int32_t out;
if (left) {
asm("smlabb %[out], %[inRL], %[vRL], %[a] \n"
: [out] "=r"(out)
: [inRL] "%r"(inRL), [vRL] "r"(vRL), [a] "r"(a)
:);
} else {
asm("smlatt %[out], %[inRL], %[vRL], %[a] \n"
: [out] "=r"(out)
: [inRL] "%r"(inRL), [vRL] "r"(vRL), [a] "r"(a)
:);
}
return out;
#else
if (left) {
return a + (int16_t)(inRL & 0xFFFF) * (int16_t)(vRL & 0xFFFF);
} else {
return a + (int16_t)(inRL >> 16) * (int16_t)(vRL >> 16);
}
#endif
}
/**
* Similar to mul, but the 16-bit terms are extracted from a 32-bit interleaved
* stereo pair.
*/
static inline int32_t mulRL(int left, uint32_t inRL, uint32_t vRL) {
#if defined(__arm__) && !defined(__thumb__)
int32_t out;
if (left) {
asm("smulbb %[out], %[inRL], %[vRL] \n"
: [out] "=r"(out)
: [inRL] "%r"(inRL), [vRL] "r"(vRL)
:);
} else {
asm("smultt %[out], %[inRL], %[vRL] \n"
: [out] "=r"(out)
: [inRL] "%r"(inRL), [vRL] "r"(vRL)
:);
}
return out;
#else
if (left) {
return (int16_t)(inRL & 0xFFFF) * (int16_t)(vRL & 0xFFFF);
} else {
return (int16_t)(inRL >> 16) * (int16_t)(vRL >> 16);
}
#endif
}
__END_DECLS
#endif // COCOS_AUDIO_PRIMITIVES_H

View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cmath>
#include "audio/android/audio_utils/include/audio_utils/minifloat.h"
#define EXPONENT_BITS 3
#define EXPONENT_MAX ((1 << EXPONENT_BITS) - 1)
#define EXCESS ((1 << EXPONENT_BITS) - 2)
#define MANTISSA_BITS 13
#define MANTISSA_MAX ((1 << MANTISSA_BITS) - 1)
#define HIDDEN_BIT (1 << MANTISSA_BITS)
#define ONE_FLOAT ((float) (1 << (MANTISSA_BITS + 1)))
#define MINIFLOAT_MAX ((EXPONENT_MAX << MANTISSA_BITS) | MANTISSA_MAX)
#if EXPONENT_BITS + MANTISSA_BITS != 16
#error EXPONENT_BITS and MANTISSA_BITS must sum to 16
#endif
extern "C" {
gain_minifloat_t gain_from_float(float v)
{
if (std::isnan(v) || v <= 0.0f)
{
return 0;
}
if (v >= 2.0f)
{
return MINIFLOAT_MAX;
}
int exp;
float r = frexpf(v, &exp);
if ((exp += EXCESS) > EXPONENT_MAX)
{
return MINIFLOAT_MAX;
}
if (-exp >= MANTISSA_BITS)
{
return 0;
}
int mantissa = (int) (r * ONE_FLOAT);
return exp > 0 ? (exp << MANTISSA_BITS) | (mantissa & ~HIDDEN_BIT) :
(mantissa >> (1 - exp)) & MANTISSA_MAX;
}
float float_from_gain(gain_minifloat_t a)
{
int mantissa = a & MANTISSA_MAX;
int exponent = (a >> MANTISSA_BITS) & EXPONENT_MAX;
return ldexpf((exponent > 0 ? HIDDEN_BIT | mantissa : mantissa << 1) / ONE_FLOAT,
exponent - EXCESS);
}
} // extern "C" {

View File

@ -0,0 +1,526 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "audio/android/cutils/bitops.h" /* for popcount() */
#include "audio/android/audio_utils/include/audio_utils/primitives.h"
#include "audio/android/audio_utils/private/private.h"
void ditherAndClamp(int32_t* out, const int32_t *sums, size_t c)
{
size_t i;
for (i=0 ; i<c ; i++) {
int32_t l = *sums++;
int32_t r = *sums++;
int32_t nl = l >> 12;
int32_t nr = r >> 12;
l = clamp16(nl);
r = clamp16(nr);
*out++ = (r<<16) | (l & 0xFFFF);
}
}
void memcpy_to_i16_from_u8(int16_t *dst, const uint8_t *src, size_t count)
{
dst += count;
src += count;
while (count--) {
*--dst = (int16_t)(*--src - 0x80) << 8;
}
}
void memcpy_to_u8_from_i16(uint8_t *dst, const int16_t *src, size_t count)
{
while (count--) {
*dst++ = (*src++ >> 8) + 0x80;
}
}
void memcpy_to_u8_from_float(uint8_t *dst, const float *src, size_t count)
{
while (count--) {
*dst++ = clamp8_from_float(*src++);
}
}
void memcpy_to_i16_from_i32(int16_t *dst, const int32_t *src, size_t count)
{
while (count--) {
*dst++ = *src++ >> 16;
}
}
void memcpy_to_i16_from_float(int16_t *dst, const float *src, size_t count)
{
while (count--) {
*dst++ = clamp16_from_float(*src++);
}
}
void memcpy_to_float_from_q4_27(float *dst, const int32_t *src, size_t count)
{
while (count--) {
*dst++ = float_from_q4_27(*src++);
}
}
void memcpy_to_float_from_i16(float *dst, const int16_t *src, size_t count)
{
while (count--) {
*dst++ = float_from_i16(*src++);
}
}
void memcpy_to_float_from_u8(float *dst, const uint8_t *src, size_t count)
{
while (count--) {
*dst++ = float_from_u8(*src++);
}
}
void memcpy_to_float_from_p24(float *dst, const uint8_t *src, size_t count)
{
while (count--) {
*dst++ = float_from_p24(src);
src += 3;
}
}
void memcpy_to_i16_from_p24(int16_t *dst, const uint8_t *src, size_t count)
{
while (count--) {
#ifdef HAVE_BIG_ENDIAN
*dst++ = src[1] | (src[0] << 8);
#else
*dst++ = src[1] | (src[2] << 8);
#endif
src += 3;
}
}
void memcpy_to_i32_from_p24(int32_t *dst, const uint8_t *src, size_t count)
{
while (count--) {
#ifdef HAVE_BIG_ENDIAN
*dst++ = (src[2] << 8) | (src[1] << 16) | (src[0] << 24);
#else
*dst++ = (src[0] << 8) | (src[1] << 16) | (src[2] << 24);
#endif
src += 3;
}
}
void memcpy_to_p24_from_i16(uint8_t *dst, const int16_t *src, size_t count)
{
while (count--) {
#ifdef HAVE_BIG_ENDIAN
*dst++ = *src >> 8;
*dst++ = *src++;
*dst++ = 0;
#else
*dst++ = 0;
*dst++ = *src;
*dst++ = *src++ >> 8;
#endif
}
}
void memcpy_to_p24_from_float(uint8_t *dst, const float *src, size_t count)
{
while (count--) {
int32_t ival = clamp24_from_float(*src++);
#ifdef HAVE_BIG_ENDIAN
*dst++ = ival >> 16;
*dst++ = ival >> 8;
*dst++ = ival;
#else
*dst++ = ival;
*dst++ = ival >> 8;
*dst++ = ival >> 16;
#endif
}
}
void memcpy_to_p24_from_q8_23(uint8_t *dst, const int32_t *src, size_t count)
{
while (count--) {
int32_t ival = clamp24_from_q8_23(*src++);
#ifdef HAVE_BIG_ENDIAN
*dst++ = ival >> 16;
*dst++ = ival >> 8;
*dst++ = ival;
#else
*dst++ = ival;
*dst++ = ival >> 8;
*dst++ = ival >> 16;
#endif
}
}
void memcpy_to_p24_from_i32(uint8_t *dst, const int32_t *src, size_t count)
{
while (count--) {
int32_t ival = *src++ >> 8;
#ifdef HAVE_BIG_ENDIAN
*dst++ = ival >> 16;
*dst++ = ival >> 8;
*dst++ = ival;
#else
*dst++ = ival;
*dst++ = ival >> 8;
*dst++ = ival >> 16;
#endif
}
}
void memcpy_to_q8_23_from_i16(int32_t *dst, const int16_t *src, size_t count)
{
while (count--) {
*dst++ = (int32_t)*src++ << 8;
}
}
void memcpy_to_q8_23_from_float_with_clamp(int32_t *dst, const float *src, size_t count)
{
while (count--) {
*dst++ = clamp24_from_float(*src++);
}
}
void memcpy_to_q8_23_from_p24(int32_t *dst, const uint8_t *src, size_t count)
{
while (count--) {
#ifdef HAVE_BIG_ENDIAN
*dst++ = (int8_t)src[0] << 16 | src[1] << 8 | src[2];
#else
*dst++ = (int8_t)src[2] << 16 | src[1] << 8 | src[0];
#endif
src += 3;
}
}
void memcpy_to_q4_27_from_float(int32_t *dst, const float *src, size_t count)
{
while (count--) {
*dst++ = clampq4_27_from_float(*src++);
}
}
void memcpy_to_i16_from_q8_23(int16_t *dst, const int32_t *src, size_t count)
{
while (count--) {
*dst++ = clamp16(*src++ >> 8);
}
}
void memcpy_to_float_from_q8_23(float *dst, const int32_t *src, size_t count)
{
while (count--) {
*dst++ = float_from_q8_23(*src++);
}
}
void memcpy_to_i32_from_i16(int32_t *dst, const int16_t *src, size_t count)
{
while (count--) {
*dst++ = (int32_t)*src++ << 16;
}
}
void memcpy_to_i32_from_float(int32_t *dst, const float *src, size_t count)
{
while (count--) {
*dst++ = clamp32_from_float(*src++);
}
}
void memcpy_to_float_from_i32(float *dst, const int32_t *src, size_t count)
{
while (count--) {
*dst++ = float_from_i32(*src++);
}
}
void downmix_to_mono_i16_from_stereo_i16(int16_t *dst, const int16_t *src, size_t count)
{
while (count--) {
*dst++ = (int16_t)(((int32_t)src[0] + (int32_t)src[1]) >> 1);
src += 2;
}
}
void upmix_to_stereo_i16_from_mono_i16(int16_t *dst, const int16_t *src, size_t count)
{
while (count--) {
int32_t temp = *src++;
dst[0] = temp;
dst[1] = temp;
dst += 2;
}
}
void downmix_to_mono_float_from_stereo_float(float *dst, const float *src, size_t frames)
{
while (frames--) {
*dst++ = (src[0] + src[1]) * 0.5;
src += 2;
}
}
void upmix_to_stereo_float_from_mono_float(float *dst, const float *src, size_t frames)
{
while (frames--) {
float temp = *src++;
dst[0] = temp;
dst[1] = temp;
dst += 2;
}
}
size_t nonZeroMono32(const int32_t *samples, size_t count)
{
size_t nonZero = 0;
while (count-- > 0) {
if (*samples++ != 0) {
nonZero++;
}
}
return nonZero;
}
size_t nonZeroMono16(const int16_t *samples, size_t count)
{
size_t nonZero = 0;
while (count-- > 0) {
if (*samples++ != 0) {
nonZero++;
}
}
return nonZero;
}
size_t nonZeroStereo32(const int32_t *frames, size_t count)
{
size_t nonZero = 0;
while (count-- > 0) {
if (frames[0] != 0 || frames[1] != 0) {
nonZero++;
}
frames += 2;
}
return nonZero;
}
size_t nonZeroStereo16(const int16_t *frames, size_t count)
{
size_t nonZero = 0;
while (count-- > 0) {
if (frames[0] != 0 || frames[1] != 0) {
nonZero++;
}
frames += 2;
}
return nonZero;
}
/*
* C macro to do channel mask copying independent of dst/src sample type.
* Don't pass in any expressions for the macro arguments here.
*/
#define copy_frame_by_mask(dst, dmask, src, smask, count, zero) \
{ \
uint32_t bit, ormask; \
while ((count)--) { \
ormask = (dmask) | (smask); \
while (ormask) { \
bit = ormask & -ormask; /* get lowest bit */ \
ormask ^= bit; /* remove lowest bit */ \
if ((dmask) & bit) { \
*(dst)++ = (smask) & bit ? *(src)++ : (zero); \
} else { /* source channel only */ \
++(src); \
} \
} \
} \
}
void memcpy_by_channel_mask(void *dst, uint32_t dst_mask,
const void *src, uint32_t src_mask, size_t sample_size, size_t count)
{
#if 0
/* alternate way of handling memcpy_by_channel_mask by using the idxary */
int8_t idxary[32];
uint32_t src_channels = popcount(src_mask);
uint32_t dst_channels =
memcpy_by_index_array_initialization(idxary, 32, dst_mask, src_mask);
memcpy_by_idxary(dst, dst_channels, src, src_channels, idxary, sample_size, count);
#else
if (dst_mask == src_mask) {
memcpy(dst, src, sample_size * popcount(dst_mask) * count);
return;
}
switch (sample_size) {
case 1: {
uint8_t *udst = (uint8_t*)dst;
const uint8_t *usrc = (const uint8_t*)src;
copy_frame_by_mask(udst, dst_mask, usrc, src_mask, count, 0);
} break;
case 2: {
uint16_t *udst = (uint16_t*)dst;
const uint16_t *usrc = (const uint16_t*)src;
copy_frame_by_mask(udst, dst_mask, usrc, src_mask, count, 0);
} break;
case 3: { /* could be slow. use a struct to represent 3 bytes of data. */
uint8x3_t *udst = (uint8x3_t*)dst;
const uint8x3_t *usrc = (const uint8x3_t*)src;
static const uint8x3_t zero; /* tricky - we use this to zero out a sample */
copy_frame_by_mask(udst, dst_mask, usrc, src_mask, count, zero);
} break;
case 4: {
uint32_t *udst = (uint32_t*)dst;
const uint32_t *usrc = (const uint32_t*)src;
copy_frame_by_mask(udst, dst_mask, usrc, src_mask, count, 0);
} break;
default:
abort(); /* illegal value */
break;
}
#endif
}
/*
* C macro to do copying by index array, to rearrange samples
* within a frame. This is independent of src/dst sample type.
* Don't pass in any expressions for the macro arguments here.
*/
#define copy_frame_by_idx(dst, dst_channels, src, src_channels, idxary, count, zero) \
{ \
unsigned i; \
int index; \
while ((count)--) { \
for (i = 0; i < (dst_channels); ++i) { \
index = (idxary)[i]; \
*(dst)++ = index < 0 ? (zero) : (src)[index]; \
} \
(src) += (src_channels); \
} \
}
void memcpy_by_index_array(void *dst, uint32_t dst_channels,
const void *src, uint32_t src_channels,
const int8_t *idxary, size_t sample_size, size_t count)
{
switch (sample_size) {
case 1: {
uint8_t *udst = (uint8_t*)dst;
const uint8_t *usrc = (const uint8_t*)src;
copy_frame_by_idx(udst, dst_channels, usrc, src_channels, idxary, count, 0);
} break;
case 2: {
uint16_t *udst = (uint16_t*)dst;
const uint16_t *usrc = (const uint16_t*)src;
copy_frame_by_idx(udst, dst_channels, usrc, src_channels, idxary, count, 0);
} break;
case 3: { /* could be slow. use a struct to represent 3 bytes of data. */
uint8x3_t *udst = (uint8x3_t*)dst;
const uint8x3_t *usrc = (const uint8x3_t*)src;
static const uint8x3_t zero;
copy_frame_by_idx(udst, dst_channels, usrc, src_channels, idxary, count, zero);
} break;
case 4: {
uint32_t *udst = (uint32_t*)dst;
const uint32_t *usrc = (const uint32_t*)src;
copy_frame_by_idx(udst, dst_channels, usrc, src_channels, idxary, count, 0);
} break;
default:
abort(); /* illegal value */
break;
}
}
size_t memcpy_by_index_array_initialization(int8_t *idxary, size_t idxcount,
uint32_t dst_mask, uint32_t src_mask)
{
size_t n = 0;
int srcidx = 0;
uint32_t bit, ormask = src_mask | dst_mask;
while (ormask && n < idxcount) {
bit = ormask & -ormask; /* get lowest bit */
ormask ^= bit; /* remove lowest bit */
if (src_mask & dst_mask & bit) { /* matching channel */
idxary[n++] = srcidx++;
} else if (src_mask & bit) { /* source channel only */
++srcidx;
} else { /* destination channel only */
idxary[n++] = -1;
}
}
return n + popcount(ormask & dst_mask);
}
size_t memcpy_by_index_array_initialization_src_index(int8_t *idxary, size_t idxcount,
uint32_t dst_mask, uint32_t src_mask) {
size_t dst_count = popcount(dst_mask);
if (idxcount == 0) {
return dst_count;
}
if (dst_count > idxcount) {
dst_count = idxcount;
}
size_t src_idx, dst_idx;
for (src_idx = 0, dst_idx = 0; dst_idx < dst_count; ++dst_idx) {
if (src_mask & 1) {
idxary[dst_idx] = src_idx++;
} else {
idxary[dst_idx] = -1;
}
src_mask >>= 1;
}
return dst_idx;
}
size_t memcpy_by_index_array_initialization_dst_index(int8_t *idxary, size_t idxcount,
uint32_t dst_mask, uint32_t src_mask) {
size_t src_idx, dst_idx;
size_t dst_count = __builtin_popcount(dst_mask);
size_t src_count = __builtin_popcount(src_mask);
if (idxcount == 0) {
return dst_count;
}
if (dst_count > idxcount) {
dst_count = idxcount;
}
for (src_idx = 0, dst_idx = 0; dst_idx < dst_count; ++src_idx) {
if (dst_mask & 1) {
idxary[dst_idx++] = src_idx < src_count ? (signed)src_idx : -1;
}
dst_mask >>= 1;
}
return dst_idx;
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_AUDIO_PRIVATE_H
#define ANDROID_AUDIO_PRIVATE_H
#include <stdint.h>
__BEGIN_DECLS
/* Defines not necessary for external use but kept here to be common
* to the audio_utils library.
*/
/* struct representation of 3 bytes for packed PCM 24 bit data.
* The naming follows the ARM NEON convention.
*/
typedef struct {uint8_t c[3];} __attribute__((__packed__)) uint8x3_t;
__END_DECLS
#endif /*ANDROID_AUDIO_PRIVATE_H*/

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef COCOS_CUTILS_BITOPS_H
#define COCOS_CUTILS_BITOPS_H
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <sys/cdefs.h>
__BEGIN_DECLS
static inline int popcount(unsigned int x)
{
return __builtin_popcount(x);
}
static inline int popcountl(unsigned long x)
{
return __builtin_popcountl(x);
}
static inline int popcountll(unsigned long long x)
{
return __builtin_popcountll(x);
}
__END_DECLS
#endif /* COCOS_CUTILS_BITOPS_H */

View File

@ -0,0 +1,568 @@
/*
* Copyright (C) 2005-2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// C/C++ logging functions. See the logging documentation for API details.
//
// We'd like these to be available from C code (in case we import some from
// somewhere), so this has a C interface.
//
// The output will be correct when the log file is shared between multiple
// threads and/or multiple processes so long as the operating system
// supports O_APPEND. These calls have mutex-protected data structures
// and so are NOT reentrant. Do not use LOG in a signal handler.
//
#ifndef COCOS_CUTILS_LOG_H
#define COCOS_CUTILS_LOG_H
#include <stdarg.h>
#include <stdio.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <android/log.h>
#ifdef __cplusplus
extern "C" {
#endif
// ---------------------------------------------------------------------
/*
* Normally we strip ALOGV (VERBOSE messages) from release builds.
* You can modify this (for example with "#define LOG_NDEBUG 0"
* at the top of your source file) to change that behavior.
*/
#ifndef LOG_NDEBUG
#if defined(COCOS2D_DEBUG) && COCOS2D_DEBUG > 0
#define LOG_NDEBUG 0
#else
#define LOG_NDEBUG 1
#endif
#endif
/*
* This is the local tag used for the following simplified
* logging macros. You can change this preprocessor definition
* before using the other macros to change the tag.
*/
#ifndef LOG_TAG
#define LOG_TAG NULL
#endif
// ---------------------------------------------------------------------
#ifndef __predict_false
#define __predict_false(exp) __builtin_expect((exp) != 0, 0)
#endif
/*
* -DLINT_RLOG in sources that you want to enforce that all logging
* goes to the radio log buffer. If any logging goes to any of the other
* log buffers, there will be a compile or link error to highlight the
* problem. This is not a replacement for a full audit of the code since
* this only catches compiled code, not ifdef'd debug code. Options to
* defining this, either temporarily to do a spot check, or permanently
* to enforce, in all the communications trees; We have hopes to ensure
* that by supplying just the radio log buffer that the communications
* teams will have their one-stop shop for triaging issues.
*/
#ifndef LINT_RLOG
/*
* Simplified macro to send a verbose log message using the current LOG_TAG.
*/
#ifndef ALOGV
#define __ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
#if LOG_NDEBUG
#define ALOGV(...) do { if (0) { __ALOGV(__VA_ARGS__); } } while (0)
#else
#define ALOGV(...) __ALOGV(__VA_ARGS__)
#endif
#endif
#ifndef ALOGV_IF
#if LOG_NDEBUG
#define ALOGV_IF(cond, ...) ((void)0)
#else
#define ALOGV_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
: (void)0 )
#endif
#endif
/*
* Simplified macro to send a debug log message using the current LOG_TAG.
*/
#ifndef ALOGD
#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#endif
#ifndef ALOGD_IF
#define ALOGD_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
: (void)0 )
#endif
/*
* Simplified macro to send an info log message using the current LOG_TAG.
*/
#ifndef ALOGI
#define ALOGI(...) ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
#endif
#ifndef ALOGI_IF
#define ALOGI_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \
: (void)0 )
#endif
/*
* Simplified macro to send a warning log message using the current LOG_TAG.
*/
#ifndef ALOGW
#define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
#endif
#ifndef ALOGW_IF
#define ALOGW_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \
: (void)0 )
#endif
/*
* Simplified macro to send an error log message using the current LOG_TAG.
*/
#ifndef ALOGE
#define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
#endif
#ifndef ALOGE_IF
#define ALOGE_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
: (void)0 )
#endif
// ---------------------------------------------------------------------
/*
* Conditional based on whether the current LOG_TAG is enabled at
* verbose priority.
*/
#ifndef IF_ALOGV
#if LOG_NDEBUG
#define IF_ALOGV() if (false)
#else
#define IF_ALOGV() IF_ALOG(LOG_VERBOSE, LOG_TAG)
#endif
#endif
/*
* Conditional based on whether the current LOG_TAG is enabled at
* debug priority.
*/
#ifndef IF_ALOGD
#define IF_ALOGD() IF_ALOG(LOG_DEBUG, LOG_TAG)
#endif
/*
* Conditional based on whether the current LOG_TAG is enabled at
* info priority.
*/
#ifndef IF_ALOGI
#define IF_ALOGI() IF_ALOG(LOG_INFO, LOG_TAG)
#endif
/*
* Conditional based on whether the current LOG_TAG is enabled at
* warn priority.
*/
#ifndef IF_ALOGW
#define IF_ALOGW() IF_ALOG(LOG_WARN, LOG_TAG)
#endif
/*
* Conditional based on whether the current LOG_TAG is enabled at
* error priority.
*/
#ifndef IF_ALOGE
#define IF_ALOGE() IF_ALOG(LOG_ERROR, LOG_TAG)
#endif
// ---------------------------------------------------------------------
/*
* Simplified macro to send a verbose system log message using the current LOG_TAG.
*/
#ifndef SLOGV
#define __SLOGV(...) \
((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
#if LOG_NDEBUG
#define SLOGV(...) do { if (0) { __SLOGV(__VA_ARGS__); } } while (0)
#else
#define SLOGV(...) __SLOGV(__VA_ARGS__)
#endif
#endif
#ifndef SLOGV_IF
#if LOG_NDEBUG
#define SLOGV_IF(cond, ...) ((void)0)
#else
#define SLOGV_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
: (void)0 )
#endif
#endif
/*
* Simplified macro to send a debug system log message using the current LOG_TAG.
*/
#ifndef SLOGD
#define SLOGD(...) \
((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#endif
#ifndef SLOGD_IF
#define SLOGD_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
: (void)0 )
#endif
/*
* Simplified macro to send an info system log message using the current LOG_TAG.
*/
#ifndef SLOGI
#define SLOGI(...) \
((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
#endif
#ifndef SLOGI_IF
#define SLOGI_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) \
: (void)0 )
#endif
/*
* Simplified macro to send a warning system log message using the current LOG_TAG.
*/
#ifndef SLOGW
#define SLOGW(...) \
((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
#endif
#ifndef SLOGW_IF
#define SLOGW_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
: (void)0 )
#endif
/*
* Simplified macro to send an error system log message using the current LOG_TAG.
*/
#ifndef SLOGE
#define SLOGE(...) \
((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
#endif
#ifndef SLOGE_IF
#define SLOGE_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
: (void)0 )
#endif
#endif /* !LINT_RLOG */
// ---------------------------------------------------------------------
/*
* Simplified macro to send a verbose radio log message using the current LOG_TAG.
*/
#ifndef RLOGV
#define __RLOGV(...) \
((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
#if LOG_NDEBUG
#define RLOGV(...) do { if (0) { __RLOGV(__VA_ARGS__); } } while (0)
#else
#define RLOGV(...) __RLOGV(__VA_ARGS__)
#endif
#endif
#ifndef RLOGV_IF
#if LOG_NDEBUG
#define RLOGV_IF(cond, ...) ((void)0)
#else
#define RLOGV_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
: (void)0 )
#endif
#endif
/*
* Simplified macro to send a debug radio log message using the current LOG_TAG.
*/
#ifndef RLOGD
#define RLOGD(...) \
((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#endif
#ifndef RLOGD_IF
#define RLOGD_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
: (void)0 )
#endif
/*
* Simplified macro to send an info radio log message using the current LOG_TAG.
*/
#ifndef RLOGI
#define RLOGI(...) \
((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
#endif
#ifndef RLOGI_IF
#define RLOGI_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) \
: (void)0 )
#endif
/*
* Simplified macro to send a warning radio log message using the current LOG_TAG.
*/
#ifndef RLOGW
#define RLOGW(...) \
((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
#endif
#ifndef RLOGW_IF
#define RLOGW_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
: (void)0 )
#endif
/*
* Simplified macro to send an error radio log message using the current LOG_TAG.
*/
#ifndef RLOGE
#define RLOGE(...) \
((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
#endif
#ifndef RLOGE_IF
#define RLOGE_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
: (void)0 )
#endif
// ---------------------------------------------------------------------
/*
* Log a fatal error. If the given condition fails, this stops program
* execution like a normal assertion, but also generating the given message.
* It is NOT stripped from release builds. Note that the condition test
* is -inverted- from the normal assert() semantics.
*/
#ifndef LOG_ALWAYS_FATAL_IF
#define LOG_ALWAYS_FATAL_IF(cond, ...) \
( (__predict_false(cond)) \
? ((void)android_printAssert(#cond, LOG_TAG, ## __VA_ARGS__)) \
: (void)0 )
#endif
#ifndef LOG_ALWAYS_FATAL
#define LOG_ALWAYS_FATAL(...) \
( ((void)android_printAssert(NULL, LOG_TAG, ## __VA_ARGS__)) )
#endif
/*
* Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
* are stripped out of release builds.
*/
#if LOG_NDEBUG
#ifndef LOG_FATAL_IF
#define LOG_FATAL_IF(cond, ...) ((void)0)
#endif
#ifndef LOG_FATAL
#define LOG_FATAL(...) ((void)0)
#endif
#else
#ifndef LOG_FATAL_IF
#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ## __VA_ARGS__)
#endif
#ifndef LOG_FATAL
#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
#endif
#endif
/*
* Assertion that generates a log message when the assertion fails.
* Stripped out of release builds. Uses the current LOG_TAG.
*/
#ifndef ALOG_ASSERT
#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ## __VA_ARGS__)
//#define ALOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond)
#endif
// ---------------------------------------------------------------------
/*
* Basic log message macro.
*
* Example:
* ALOG(LOG_WARN, NULL, "Failed with error %d", errno);
*
* The second argument may be NULL or "" to indicate the "global" tag.
*/
#ifndef ALOG
#define ALOG(priority, tag, ...) \
LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
#endif
/*
* Log macro that allows you to specify a number for the priority.
*/
#ifndef LOG_PRI
#define LOG_PRI(priority, tag, ...) \
android_printLog(priority, tag, __VA_ARGS__)
#endif
/*
* Log macro that allows you to pass in a varargs ("args" is a va_list).
*/
#ifndef LOG_PRI_VA
#define LOG_PRI_VA(priority, tag, fmt, args) \
android_vprintLog(priority, NULL, tag, fmt, args)
#endif
/*
* Conditional given a desired logging priority and tag.
*/
#ifndef IF_ALOG
#define IF_ALOG(priority, tag) \
if (android_testLog(ANDROID_##priority, tag))
#endif
// ---------------------------------------------------------------------
/*
* ===========================================================================
*
* The stuff in the rest of this file should not be used directly.
*/
#define android_printLog(prio, tag, ...) \
__android_log_print(prio, tag, __VA_ARGS__)
#define android_vprintLog(prio, cond, tag, ...) \
__android_log_vprint(prio, tag, __VA_ARGS__)
/* XXX Macros to work around syntax errors in places where format string
* arg is not passed to ALOG_ASSERT, LOG_ALWAYS_FATAL or LOG_ALWAYS_FATAL_IF
* (happens only in debug builds).
*/
/* Returns 2nd arg. Used to substitute default value if caller's vararg list
* is empty.
*/
#define __android_second(dummy, second, ...) second
/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise
* returns nothing.
*/
#define __android_rest(first, ...) , ## __VA_ARGS__
#define android_printAssert(cond, tag, ...) \
__android_log_assert(cond, tag, \
__android_second(0, ## __VA_ARGS__, NULL) __android_rest(__VA_ARGS__))
#define android_writeLog(prio, tag, text) \
__android_log_write(prio, tag, text)
#define android_bWriteLog(tag, payload, len) \
__android_log_bwrite(tag, payload, len)
#define android_btWriteLog(tag, type, payload, len) \
__android_log_btwrite(tag, type, payload, len)
#define android_errorWriteLog(tag, subTag) \
__android_log_error_write(tag, subTag, -1, NULL, 0)
#define android_errorWriteWithInfoLog(tag, subTag, uid, data, dataLen) \
__android_log_error_write(tag, subTag, uid, data, dataLen)
/*
* IF_ALOG uses android_testLog, but IF_ALOG can be overridden.
* android_testLog will remain constant in its purpose as a wrapper
* for Android logging filter policy, and can be subject to
* change. It can be reused by the developers that override
* IF_ALOG as a convenient means to reimplement their policy
* over Android.
*/
#if LOG_NDEBUG /* Production */
#define android_testLog(prio, tag) \
(__android_log_is_loggable(prio, tag, ANDROID_LOG_DEBUG) != 0)
#else
#define android_testLog(prio, tag) \
(__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE) != 0)
#endif
/*
* Use the per-tag properties "log.tag.<tagname>" to generate a runtime
* result of non-zero to expose a log. prio is ANDROID_LOG_VERBOSE to
* ANDROID_LOG_FATAL. default_prio if no property. Undefined behavior if
* any other value.
*/
int __android_log_is_loggable(int prio, const char *tag, int default_prio);
int __android_log_security(); /* Device Owner is present */
int __android_log_error_write(int tag, const char *subTag, int32_t uid, const char *data,
uint32_t dataLen);
/*
* Send a simple string to the log.
*/
int __android_log_buf_write(int bufID, int prio, const char *tag, const char *text);
int __android_log_buf_print(int bufID, int prio, const char *tag, const char *fmt, ...)
#if defined(__GNUC__)
__attribute__((__format__(printf, 4, 5)))
#endif
;
#ifdef __cplusplus
}
#endif
#endif /* COCOS_CUTILS_LOG_H */

View File

@ -0,0 +1,540 @@
/*
* Copyright (C) 2014 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#define LOG_TAG "mp3reader"
#include <stdlib.h>
#include <assert.h>
#include <stdint.h>
#include <string.h> // Resolves that memset, memcpy aren't found while APP_PLATFORM >= 22 on Android
#include <vector>
#include "audio/android/cutils/log.h"
#include "pvmp3decoder_api.h"
#include "audio/android/mp3reader.h"
static uint32_t U32_AT(const uint8_t *ptr) {
return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
}
static bool parseHeader(
uint32_t header, size_t *frame_size,
uint32_t *out_sampling_rate = NULL, uint32_t *out_channels = NULL ,
uint32_t *out_bitrate = NULL, uint32_t *out_num_samples = NULL) {
*frame_size = 0;
if (out_sampling_rate) {
*out_sampling_rate = 0;
}
if (out_channels) {
*out_channels = 0;
}
if (out_bitrate) {
*out_bitrate = 0;
}
if (out_num_samples) {
*out_num_samples = 1152;
}
if ((header & 0xffe00000) != 0xffe00000) {
return false;
}
unsigned version = (header >> 19) & 3;
if (version == 0x01) {
return false;
}
unsigned layer = (header >> 17) & 3;
if (layer == 0x00) {
return false;
}
unsigned bitrate_index = (header >> 12) & 0x0f;
if (bitrate_index == 0 || bitrate_index == 0x0f) {
// Disallow "free" bitrate.
return false;
}
unsigned sampling_rate_index = (header >> 10) & 3;
if (sampling_rate_index == 3) {
return false;
}
static const int kSamplingRateV1[] = { 44100, 48000, 32000 };
int sampling_rate = kSamplingRateV1[sampling_rate_index];
if (version == 2 /* V2 */) {
sampling_rate /= 2;
} else if (version == 0 /* V2.5 */) {
sampling_rate /= 4;
}
unsigned padding = (header >> 9) & 1;
if (layer == 3) {
// layer I
static const int kBitrateV1[] = {
32, 64, 96, 128, 160, 192, 224, 256,
288, 320, 352, 384, 416, 448
};
static const int kBitrateV2[] = {
32, 48, 56, 64, 80, 96, 112, 128,
144, 160, 176, 192, 224, 256
};
int bitrate =
(version == 3 /* V1 */)
? kBitrateV1[bitrate_index - 1]
: kBitrateV2[bitrate_index - 1];
if (out_bitrate) {
*out_bitrate = bitrate;
}
*frame_size = (12000 * bitrate / sampling_rate + padding) * 4;
if (out_num_samples) {
*out_num_samples = 384;
}
} else {
// layer II or III
static const int kBitrateV1L2[] = {
32, 48, 56, 64, 80, 96, 112, 128,
160, 192, 224, 256, 320, 384
};
static const int kBitrateV1L3[] = {
32, 40, 48, 56, 64, 80, 96, 112,
128, 160, 192, 224, 256, 320
};
static const int kBitrateV2[] = {
8, 16, 24, 32, 40, 48, 56, 64,
80, 96, 112, 128, 144, 160
};
int bitrate;
if (version == 3 /* V1 */) {
bitrate = (layer == 2 /* L2 */)
? kBitrateV1L2[bitrate_index - 1]
: kBitrateV1L3[bitrate_index - 1];
if (out_num_samples) {
*out_num_samples = 1152;
}
} else {
// V2 (or 2.5)
bitrate = kBitrateV2[bitrate_index - 1];
if (out_num_samples) {
*out_num_samples = (layer == 1 /* L3 */) ? 576 : 1152;
}
}
if (out_bitrate) {
*out_bitrate = bitrate;
}
if (version == 3 /* V1 */) {
*frame_size = 144000 * bitrate / sampling_rate + padding;
} else {
// V2 or V2.5
size_t tmp = (layer == 1 /* L3 */) ? 72000 : 144000;
*frame_size = tmp * bitrate / sampling_rate + padding;
}
}
if (out_sampling_rate) {
*out_sampling_rate = sampling_rate;
}
if (out_channels) {
int channel_mode = (header >> 6) & 3;
*out_channels = (channel_mode == 3) ? 1 : 2;
}
return true;
}
// Mask to extract the version, layer, sampling rate parts of the MP3 header,
// which should be same for all MP3 frames.
static const uint32_t kMask = 0xfffe0c00;
static ssize_t sourceReadAt(mp3_callbacks *callback, void* source, off64_t offset, void *data, size_t size) {
int retVal = callback->seek(source, offset, SEEK_SET);
if (retVal != EXIT_SUCCESS) {
return 0;
} else {
return callback->read(data, 1, size, source);
}
}
// Resync to next valid MP3 frame in the file.
static bool resync(
mp3_callbacks *callback, void* source, uint32_t match_header,
off64_t *inout_pos, uint32_t *out_header) {
if (*inout_pos == 0) {
// Skip an optional ID3 header if syncing at the very beginning
// of the datasource.
for (;;) {
uint8_t id3header[10];
int retVal = sourceReadAt(callback, source, *inout_pos, id3header,
sizeof(id3header));
if (retVal < (ssize_t)sizeof(id3header)) {
// If we can't even read these 10 bytes, we might as well bail
// out, even if there _were_ 10 bytes of valid mp3 audio data...
return false;
}
if (memcmp("ID3", id3header, 3)) {
break;
}
// Skip the ID3v2 header.
size_t len =
((id3header[6] & 0x7f) << 21)
| ((id3header[7] & 0x7f) << 14)
| ((id3header[8] & 0x7f) << 7)
| (id3header[9] & 0x7f);
len += 10;
*inout_pos += len;
ALOGV("skipped ID3 tag, new starting offset is %lld (0x%016llx)",
(long long)*inout_pos, (long long)*inout_pos);
}
}
off64_t pos = *inout_pos;
bool valid = false;
const int32_t kMaxReadBytes = 1024;
const int32_t kMaxBytesChecked = 128 * 1024;
uint8_t buf[kMaxReadBytes];
ssize_t bytesToRead = kMaxReadBytes;
ssize_t totalBytesRead = 0;
ssize_t remainingBytes = 0;
bool reachEOS = false;
uint8_t *tmp = buf;
do {
if (pos >= (off64_t)(*inout_pos + kMaxBytesChecked)) {
// Don't scan forever.
ALOGV("giving up at offset %lld", (long long)pos);
break;
}
if (remainingBytes < 4) {
if (reachEOS) {
break;
} else {
memcpy(buf, tmp, remainingBytes);
bytesToRead = kMaxReadBytes - remainingBytes;
/*
* The next read position should start from the end of
* the last buffer, and thus should include the remaining
* bytes in the buffer.
*/
totalBytesRead = sourceReadAt(callback, source, pos + remainingBytes,
buf + remainingBytes, bytesToRead);
if (totalBytesRead <= 0) {
break;
}
reachEOS = (totalBytesRead != bytesToRead);
remainingBytes += totalBytesRead;
tmp = buf;
continue;
}
}
uint32_t header = U32_AT(tmp);
if (match_header != 0 && (header & kMask) != (match_header & kMask)) {
++pos;
++tmp;
--remainingBytes;
continue;
}
size_t frame_size;
uint32_t sample_rate, num_channels, bitrate;
if (!parseHeader(
header, &frame_size,
&sample_rate, &num_channels, &bitrate)) {
++pos;
++tmp;
--remainingBytes;
continue;
}
// ALOGV("found possible 1st frame at %lld (header = 0x%08x)", (long long)pos, header);
// We found what looks like a valid frame,
// now find its successors.
off64_t test_pos = pos + frame_size;
valid = true;
const int FRAME_MATCH_REQUIRED = 3;
for (int j = 0; j < FRAME_MATCH_REQUIRED; ++j) {
uint8_t tmp[4];
ssize_t retval = sourceReadAt(callback, source, test_pos, tmp, sizeof(tmp));
if (retval < (ssize_t)sizeof(tmp)) {
valid = false;
break;
}
uint32_t test_header = U32_AT(tmp);
ALOGV("subsequent header is %08x", test_header);
if ((test_header & kMask) != (header & kMask)) {
valid = false;
break;
}
size_t test_frame_size;
if (!parseHeader(test_header, &test_frame_size)) {
valid = false;
break;
}
ALOGV("found subsequent frame #%d at %lld", j + 2, (long long)test_pos);
test_pos += test_frame_size;
}
if (valid) {
*inout_pos = pos;
if (out_header != NULL) {
*out_header = header;
}
} else {
ALOGV("no dice, no valid sequence of frames found.");
}
++pos;
++tmp;
--remainingBytes;
} while (!valid);
return valid;
}
Mp3Reader::Mp3Reader() : mSource(NULL), mCallback(NULL) {
}
// Initialize the MP3 reader.
bool Mp3Reader::init(mp3_callbacks *callback, void* source) {
mSource = source;
mCallback = callback;
// Open the file.
// mFp = fopen(file, "rb");
// if (mFp == NULL) return false;
// Sync to the first valid frame.
off64_t pos = 0;
uint32_t header;
bool success = resync(callback, source, 0 /*match_header*/, &pos, &header);
if (!success)
{
ALOGE("%s, resync failed", __FUNCTION__);
return false;
}
mCurrentPos = pos;
mFixedHeader = header;
size_t frame_size;
return parseHeader(header, &frame_size, &mSampleRate,
&mNumChannels, &mBitrate);
}
// Get the next valid MP3 frame.
bool Mp3Reader::getFrame(void *buffer, uint32_t *size) {
size_t frame_size;
uint32_t bitrate;
uint32_t num_samples;
uint32_t sample_rate;
for (;;) {
ssize_t n = sourceReadAt(mCallback, mSource, mCurrentPos, buffer, 4);
if (n < 4) {
return false;
}
uint32_t header = U32_AT((const uint8_t *)buffer);
if ((header & kMask) == (mFixedHeader & kMask)
&& parseHeader(
header, &frame_size, &sample_rate, NULL /*out_channels*/,
&bitrate, &num_samples)) {
break;
}
// Lost sync.
off64_t pos = mCurrentPos;
if (!resync(mCallback, mSource, mFixedHeader, &pos, NULL /*out_header*/)) {
// Unable to resync. Signalling end of stream.
return false;
}
mCurrentPos = pos;
// Try again with the new position.
}
ssize_t n = sourceReadAt(mCallback, mSource, mCurrentPos, buffer, frame_size);
if (n < (ssize_t)frame_size) {
return false;
}
*size = frame_size;
mCurrentPos += frame_size;
return true;
}
// Close the MP3 reader.
void Mp3Reader::close() {
assert(mCallback != NULL);
mCallback->close(mSource);
}
Mp3Reader::~Mp3Reader() {
}
enum {
kInputBufferSize = 10 * 1024,
kOutputBufferSize = 4608 * 2,
};
int decodeMP3(mp3_callbacks* cb, void* source, std::vector<char>& pcmBuffer, int* numChannels, int* sampleRate, int* numFrames)
{
// Initialize the config.
tPVMP3DecoderExternal config;
config.equalizerType = flat;
config.crcEnabled = false;
// Allocate the decoder memory.
uint32_t memRequirements = pvmp3_decoderMemRequirements();
void *decoderBuf = malloc(memRequirements);
assert(decoderBuf != NULL);
// Initialize the decoder.
pvmp3_InitDecoder(&config, decoderBuf);
// Open the input file.
Mp3Reader mp3Reader;
bool success = mp3Reader.init(cb, source);
if (!success) {
ALOGE("mp3Reader.init: Encountered error reading\n");
free(decoderBuf);
return EXIT_FAILURE;
}
// Open the output file.
// SF_INFO sfInfo;
// memset(&sfInfo, 0, sizeof(SF_INFO));
// sfInfo.channels = mp3Reader.getNumChannels();
// sfInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
// sfInfo.samplerate = mp3Reader.getSampleRate();
// SNDFILE *handle = sf_open(argv[2], SFM_WRITE, &sfInfo);
// if (handle == NULL) {
// ALOGE("Encountered error writing %s\n", argv[2]);
// mp3Reader.close();
// free(decoderBuf);
// return EXIT_FAILURE;
// }
// Allocate input buffer.
uint8_t *inputBuf = static_cast<uint8_t*>(malloc(kInputBufferSize));
assert(inputBuf != NULL);
// Allocate output buffer.
int16_t *outputBuf = static_cast<int16_t*>(malloc(kOutputBufferSize));
assert(outputBuf != NULL);
// Decode loop.
int retVal = EXIT_SUCCESS;
while (1) {
// Read input from the file.
uint32_t bytesRead;
bool success = mp3Reader.getFrame(inputBuf, &bytesRead);
if (!success) break;
*numChannels = mp3Reader.getNumChannels();
*sampleRate = mp3Reader.getSampleRate();
// Set the input config.
config.inputBufferCurrentLength = bytesRead;
config.inputBufferMaxLength = 0;
config.inputBufferUsedLength = 0;
config.pInputBuffer = inputBuf;
config.pOutputBuffer = outputBuf;
config.outputFrameSize = kOutputBufferSize / sizeof(int16_t);
ERROR_CODE decoderErr;
decoderErr = pvmp3_framedecoder(&config, decoderBuf);
if (decoderErr != NO_DECODING_ERROR) {
ALOGE("Decoder encountered error=%d", decoderErr);
retVal = EXIT_FAILURE;
break;
}
pcmBuffer.insert(pcmBuffer.end(), (char*)outputBuf, ((char*)outputBuf) + config.outputFrameSize * 2);
*numFrames += config.outputFrameSize / mp3Reader.getNumChannels();
}
// Close input reader and output writer.
mp3Reader.close();
// sf_close(handle);
// Free allocated memory.
free(inputBuf);
free(outputBuf);
free(decoderBuf);
return retVal;
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2014 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef MP3READER_H_
#define MP3READER_H_
typedef struct {
size_t (*read) (void *ptr, size_t size, size_t nmemb, void *datasource);
int (*seek) (void *datasource, int64_t offset, int whence);
int (*close) (void *datasource);
long (*tell) (void *datasource);
} mp3_callbacks;
class Mp3Reader {
public:
Mp3Reader();
bool init(mp3_callbacks *callback, void* source);
bool getFrame(void *buffer, uint32_t *size);
uint32_t getSampleRate() { return mSampleRate;}
uint32_t getNumChannels() { return mNumChannels;}
void close();
~Mp3Reader();
private:
void *mSource;
mp3_callbacks* mCallback;
uint32_t mFixedHeader;
off64_t mCurrentPos;
uint32_t mSampleRate;
uint32_t mNumChannels;
uint32_t mBitrate;
};
int decodeMP3(mp3_callbacks* cb, void* source, std::vector<char>& pcmBuffer, int* numChannels, int* sampleRate, int* numFrames);
#endif /* MP3READER_H_ */

View File

@ -0,0 +1,518 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "tinysndfile"
#include "audio/android/tinysndfile.h"
#include "audio/android/audio_utils/include/audio_utils/primitives.h"
#include "audio/android/cutils/log.h"
// #ifdef HAVE_STDERR
// #include <stdio.h>
// #endif
#include <string.h>
#include <errno.h>
#ifndef HAVE_STDERR
#define HAVE_STDERR
#endif
#define WAVE_FORMAT_PCM 1
#define WAVE_FORMAT_IEEE_FLOAT 3
#define WAVE_FORMAT_EXTENSIBLE 0xFFFE
static snd_callbacks __defaultCallback;
static int __inited = 0;
struct SNDFILE_ {
uint8_t *temp; // realloc buffer used for shrinking 16 bits to 8 bits and byte-swapping
void *stream;
size_t bytesPerFrame;
size_t remaining; // frames unread for SFM_READ, frames written for SFM_WRITE
SF_INFO info;
snd_callbacks callback;
};
static unsigned little2u(unsigned char *ptr)
{
return (ptr[1] << 8) + ptr[0];
}
static unsigned little4u(unsigned char *ptr)
{
return (ptr[3] << 24) + (ptr[2] << 16) + (ptr[1] << 8) + ptr[0];
}
static int isLittleEndian(void)
{
static const short one = 1;
return *((const char *) &one) == 1;
}
// "swab" conflicts with OS X <string.h>
static void my_swab(short *ptr, size_t numToSwap)
{
while (numToSwap > 0) {
*ptr = little2u((unsigned char *) ptr);
--numToSwap;
++ptr;
}
}
static void* open_func(const char* path, void* user)
{
return fopen(path, "rb");
}
static size_t read_func(void *ptr, size_t size, size_t nmemb, void* datasource)
{
return fread(ptr, size, nmemb, (FILE*)datasource);
}
static int seek_func(void* datasource, long offset, int whence)
{
return fseek((FILE*)datasource, offset, whence);
}
static int close_func(void* datasource)
{
return fclose((FILE*)datasource);
}
static long tell_func(void* datasource)
{
return ftell((FILE*)datasource);
}
static void lazyInit()
{
if (__inited == 0)
{
__defaultCallback.open = open_func;
__defaultCallback.read = read_func;
__defaultCallback.seek = seek_func;
__defaultCallback.close = close_func;
__defaultCallback.tell = tell_func;
__inited = 1;
}
}
SNDFILE *sf_open_read(const char *path, SF_INFO *info, snd_callbacks* cb, void* user)
{
lazyInit();
if (path == NULL || info == NULL) {
#ifdef HAVE_STDERR
ALOGE("path=%p info=%p\n", path, info);
#endif
return NULL;
}
SNDFILE *handle = (SNDFILE *) malloc(sizeof(SNDFILE));
handle->temp = NULL;
handle->info.format = SF_FORMAT_WAV;
if (cb != NULL) {
handle->callback = *cb;
} else {
handle->callback = __defaultCallback;
}
void* stream = handle->callback.open(path, user);
if (stream == NULL) {
#ifdef HAVE_STDERR
ALOGE("fopen %s failed errno %d\n", path, errno);
#endif
free(handle);
return NULL;
}
handle->stream = stream;
// don't attempt to parse all valid forms, just the most common ones
unsigned char wav[12];
size_t actual;
unsigned riffSize;
size_t remaining;
int hadFmt = 0;
int hadData = 0;
long dataTell = 0L;
actual = handle->callback.read(wav, sizeof(char), sizeof(wav), stream);
if (actual < 12) {
#ifdef HAVE_STDERR
ALOGE("actual %zu < 44\n", actual);
#endif
goto close;
}
if (memcmp(wav, "RIFF", 4)) {
#ifdef HAVE_STDERR
ALOGE("wav != RIFF\n");
#endif
goto close;
}
riffSize = little4u(&wav[4]);
if (riffSize < 4) {
#ifdef HAVE_STDERR
ALOGE("riffSize %u < 4\n", riffSize);
#endif
goto close;
}
if (memcmp(&wav[8], "WAVE", 4)) {
#ifdef HAVE_STDERR
ALOGE("missing WAVE\n");
#endif
goto close;
}
remaining = riffSize - 4;
while (remaining >= 8) {
unsigned char chunk[8];
actual = handle->callback.read(chunk, sizeof(char), sizeof(chunk), stream);
if (actual != sizeof(chunk)) {
#ifdef HAVE_STDERR
ALOGE("actual %zu != %zu\n", actual, sizeof(chunk));
#endif
goto close;
}
remaining -= 8;
unsigned chunkSize = little4u(&chunk[4]);
if (chunkSize > remaining) {
#ifdef HAVE_STDERR
ALOGE("chunkSize %u > remaining %zu\n", chunkSize, remaining);
#endif
goto close;
}
if (!memcmp(&chunk[0], "fmt ", 4)) {
if (hadFmt) {
#ifdef HAVE_STDERR
ALOGE("multiple fmt\n");
#endif
goto close;
}
if (chunkSize < 2) {
#ifdef HAVE_STDERR
ALOGE("chunkSize %u < 2\n", chunkSize);
#endif
goto close;
}
unsigned char fmt[40];
actual = handle->callback.read(fmt, sizeof(char), 2, stream);
if (actual != 2) {
#ifdef HAVE_STDERR
ALOGE("actual %zu != 2\n", actual);
#endif
goto close;
}
unsigned format = little2u(&fmt[0]);
size_t minSize = 0;
switch (format) {
case WAVE_FORMAT_PCM:
case WAVE_FORMAT_IEEE_FLOAT:
minSize = 16;
break;
case WAVE_FORMAT_EXTENSIBLE:
minSize = 40;
break;
default:
#ifdef HAVE_STDERR
ALOGE("unsupported format %u\n", format);
#endif
goto close;
}
if (chunkSize < minSize) {
#ifdef HAVE_STDERR
ALOGE("chunkSize %u < minSize %zu\n", chunkSize, minSize);
#endif
goto close;
}
actual = handle->callback.read(&fmt[2], sizeof(char), minSize - 2, stream);
if (actual != minSize - 2) {
#ifdef HAVE_STDERR
ALOGE("actual %zu != %zu\n", actual, minSize - 16);
#endif
goto close;
}
if (chunkSize > minSize) {
handle->callback.seek(stream, (long) (chunkSize - minSize), SEEK_CUR);
}
unsigned channels = little2u(&fmt[2]);
// IDEA: FCC_8
if (channels != 1 && channels != 2 && channels != 4 && channels != 6 && channels != 8) {
#ifdef HAVE_STDERR
ALOGE("unsupported channels %u\n", channels);
#endif
goto close;
}
unsigned samplerate = little4u(&fmt[4]);
if (samplerate == 0) {
#ifdef HAVE_STDERR
ALOGE("samplerate %u == 0\n", samplerate);
#endif
goto close;
}
// ignore byte rate
// ignore block alignment
unsigned bitsPerSample = little2u(&fmt[14]);
if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 24 &&
bitsPerSample != 32) {
#ifdef HAVE_STDERR
ALOGE("bitsPerSample %u != 8 or 16 or 24 or 32\n", bitsPerSample);
#endif
goto close;
}
unsigned bytesPerFrame = (bitsPerSample >> 3) * channels;
handle->bytesPerFrame = bytesPerFrame;
handle->info.samplerate = samplerate;
handle->info.channels = channels;
switch (bitsPerSample) {
case 8:
handle->info.format |= SF_FORMAT_PCM_U8;
break;
case 16:
handle->info.format |= SF_FORMAT_PCM_16;
break;
case 24:
handle->info.format |= SF_FORMAT_PCM_24;
break;
case 32:
if (format == WAVE_FORMAT_IEEE_FLOAT)
handle->info.format |= SF_FORMAT_FLOAT;
else
handle->info.format |= SF_FORMAT_PCM_32;
break;
}
hadFmt = 1;
} else if (!memcmp(&chunk[0], "data", 4)) {
if (!hadFmt) {
#ifdef HAVE_STDERR
ALOGE("data not preceded by fmt\n");
#endif
goto close;
}
if (hadData) {
#ifdef HAVE_STDERR
ALOGE("multiple data\n");
#endif
goto close;
}
handle->remaining = chunkSize / handle->bytesPerFrame;
handle->info.frames = handle->remaining;
dataTell = handle->callback.tell(stream);
if (chunkSize > 0) {
handle->callback.seek(stream, (long) chunkSize, SEEK_CUR);
}
hadData = 1;
} else if (!memcmp(&chunk[0], "fact", 4)) {
// ignore fact
if (chunkSize > 0) {
handle->callback.seek(stream, (long) chunkSize, SEEK_CUR);
}
} else {
// ignore unknown chunk
#ifdef HAVE_STDERR
ALOGE("ignoring unknown chunk %c%c%c%c\n",
chunk[0], chunk[1], chunk[2], chunk[3]);
#endif
if (chunkSize > 0) {
handle->callback.seek(stream, (long) chunkSize, SEEK_CUR);
}
}
remaining -= chunkSize;
}
if (remaining > 0) {
#ifdef HAVE_STDERR
ALOGE("partial chunk at end of RIFF, remaining %zu\n", remaining);
#endif
goto close;
}
if (!hadData) {
#ifdef HAVE_STDERR
ALOGE("missing data\n");
#endif
goto close;
}
(void) handle->callback.seek(stream, dataTell, SEEK_SET);
*info = handle->info;
return handle;
close:
free(handle);
handle->callback.close(stream);
return NULL;
}
void sf_close(SNDFILE *handle)
{
if (handle == NULL)
return;
free(handle->temp);
(void) handle->callback.close(handle->stream);
free(handle);
}
sf_count_t sf_readf_short(SNDFILE *handle, short *ptr, sf_count_t desiredFrames)
{
if (handle == NULL || ptr == NULL || !handle->remaining ||
desiredFrames <= 0) {
return 0;
}
if (handle->remaining < (size_t) desiredFrames) {
desiredFrames = handle->remaining;
}
// does not check for numeric overflow
size_t desiredBytes = desiredFrames * handle->bytesPerFrame;
size_t actualBytes;
void *temp = NULL;
unsigned format = handle->info.format & SF_FORMAT_SUBMASK;
if (format == SF_FORMAT_PCM_32 || format == SF_FORMAT_FLOAT || format == SF_FORMAT_PCM_24) {
temp = malloc(desiredBytes);
actualBytes = handle->callback.read(temp, sizeof(char), desiredBytes, handle->stream);
} else {
actualBytes = handle->callback.read(ptr, sizeof(char), desiredBytes, handle->stream);
}
size_t actualFrames = actualBytes / handle->bytesPerFrame;
handle->remaining -= actualFrames;
switch (format) {
case SF_FORMAT_PCM_U8:
memcpy_to_i16_from_u8(ptr, (unsigned char *) ptr, actualFrames * handle->info.channels);
break;
case SF_FORMAT_PCM_16:
if (!isLittleEndian())
my_swab(ptr, actualFrames * handle->info.channels);
break;
case SF_FORMAT_PCM_32:
memcpy_to_i16_from_i32(ptr, (const int *) temp, actualFrames * handle->info.channels);
free(temp);
break;
case SF_FORMAT_FLOAT:
memcpy_to_i16_from_float(ptr, (const float *) temp, actualFrames * handle->info.channels);
free(temp);
break;
case SF_FORMAT_PCM_24:
memcpy_to_i16_from_p24(ptr, (const uint8_t *) temp, actualFrames * handle->info.channels);
free(temp);
break;
default:
memset(ptr, 0, actualFrames * handle->info.channels * sizeof(short));
break;
}
return actualFrames;
}
/*
sf_count_t sf_readf_float(SNDFILE *handle, float *ptr, sf_count_t desiredFrames)
{
if (handle == NULL || ptr == NULL || !handle->remaining ||
desiredFrames <= 0) {
return 0;
}
if (handle->remaining < (size_t) desiredFrames) {
desiredFrames = handle->remaining;
}
// does not check for numeric overflow
size_t desiredBytes = desiredFrames * handle->bytesPerFrame;
size_t actualBytes;
void *temp = NULL;
unsigned format = handle->info.format & SF_FORMAT_SUBMASK;
if (format == SF_FORMAT_PCM_16 || format == SF_FORMAT_PCM_U8 || format == SF_FORMAT_PCM_24) {
temp = malloc(desiredBytes);
actualBytes = handle->callback.read(temp, sizeof(char), desiredBytes, handle->stream);
} else {
actualBytes = handle->callback.read(ptr, sizeof(char), desiredBytes, handle->stream);
}
size_t actualFrames = actualBytes / handle->bytesPerFrame;
handle->remaining -= actualFrames;
switch (format) {
case SF_FORMAT_PCM_U8:
#if 0
// REFINE: - implement
memcpy_to_float_from_u8(ptr, (const unsigned char *) temp,
actualFrames * handle->info.channels);
#endif
free(temp);
break;
case SF_FORMAT_PCM_16:
memcpy_to_float_from_i16(ptr, (const short *) temp, actualFrames * handle->info.channels);
free(temp);
break;
case SF_FORMAT_PCM_32:
memcpy_to_float_from_i32(ptr, (const int *) ptr, actualFrames * handle->info.channels);
break;
case SF_FORMAT_FLOAT:
break;
case SF_FORMAT_PCM_24:
memcpy_to_float_from_p24(ptr, (const uint8_t *) temp, actualFrames * handle->info.channels);
free(temp);
break;
default:
memset(ptr, 0, actualFrames * handle->info.channels * sizeof(float));
break;
}
return actualFrames;
}
sf_count_t sf_readf_int(SNDFILE *handle, int *ptr, sf_count_t desiredFrames)
{
if (handle == NULL || ptr == NULL || !handle->remaining ||
desiredFrames <= 0) {
return 0;
}
if (handle->remaining < (size_t) desiredFrames) {
desiredFrames = handle->remaining;
}
// does not check for numeric overflow
size_t desiredBytes = desiredFrames * handle->bytesPerFrame;
void *temp = NULL;
unsigned format = handle->info.format & SF_FORMAT_SUBMASK;
size_t actualBytes;
if (format == SF_FORMAT_PCM_16 || format == SF_FORMAT_PCM_U8 || format == SF_FORMAT_PCM_24) {
temp = malloc(desiredBytes);
actualBytes = handle->callback.read(temp, sizeof(char), desiredBytes, handle->stream);
} else {
actualBytes = handle->callback.read(ptr, sizeof(char), desiredBytes, handle->stream);
}
size_t actualFrames = actualBytes / handle->bytesPerFrame;
handle->remaining -= actualFrames;
switch (format) {
case SF_FORMAT_PCM_U8:
#if 0
// REFINE: - implement
memcpy_to_i32_from_u8(ptr, (const unsigned char *) temp,
actualFrames * handle->info.channels);
#endif
free(temp);
break;
case SF_FORMAT_PCM_16:
memcpy_to_i32_from_i16(ptr, (const short *) temp, actualFrames * handle->info.channels);
free(temp);
break;
case SF_FORMAT_PCM_32:
break;
case SF_FORMAT_FLOAT:
memcpy_to_i32_from_float(ptr, (const float *) ptr, actualFrames * handle->info.channels);
break;
case SF_FORMAT_PCM_24:
memcpy_to_i32_from_p24(ptr, (const uint8_t *) temp, actualFrames * handle->info.channels);
free(temp);
break;
default:
memset(ptr, 0, actualFrames * handle->info.channels * sizeof(int));
break;
}
return actualFrames;
}
*/

View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
// This is a C library for reading and writing PCM .wav files. It is
// influenced by other libraries such as libsndfile and audiofile, except is
// much smaller and has an Apache 2.0 license.
// The API should be familiar to clients of similar libraries, but there is
// no guarantee that it will stay exactly source-code compatible with other libraries.
#include <stdio.h>
#include <sys/cdefs.h>
__BEGIN_DECLS
// visible to clients
typedef int sf_count_t;
typedef struct {
sf_count_t frames;
int samplerate;
int channels;
int format;
} SF_INFO;
// opaque to clients
typedef struct SNDFILE_ SNDFILE;
// Format
#define SF_FORMAT_TYPEMASK 1
#define SF_FORMAT_WAV 1
#define SF_FORMAT_SUBMASK 14
#define SF_FORMAT_PCM_16 2
#define SF_FORMAT_PCM_U8 4
#define SF_FORMAT_FLOAT 6
#define SF_FORMAT_PCM_32 8
#define SF_FORMAT_PCM_24 10
typedef struct {
void* (*open)(const char* path, void* user);
size_t (*read) (void* ptr, size_t size, size_t nmemb, void* datasource);
int (*seek) (void* datasource, long offset, int whence);
int (*close) (void* datasource);
long (*tell) (void* datasource);
} snd_callbacks;
// Open stream
SNDFILE *sf_open_read(const char *path, SF_INFO *info, snd_callbacks* cb, void* user);
// Close stream
void sf_close(SNDFILE *handle);
// Read interleaved frames and return actual number of frames read
sf_count_t sf_readf_short(SNDFILE *handle, short *ptr, sf_count_t desired);
/*
sf_count_t sf_readf_float(SNDFILE *handle, float *ptr, sf_count_t desired);
sf_count_t sf_readf_int(SNDFILE *handle, int *ptr, sf_count_t desired);
*/
__END_DECLS

View File

@ -0,0 +1,88 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef COCOS_LIB_UTILS_COMPAT_H
#define COCOS_LIB_UTILS_COMPAT_H
#include <unistd.h>
#if defined(__APPLE__)
/* Mac OS has always had a 64-bit off_t, so it doesn't have off64_t. */
typedef off_t off64_t;
static inline off64_t lseek64(int fd, off64_t offset, int whence) {
return lseek(fd, offset, whence);
}
static inline ssize_t pread64(int fd, void* buf, size_t nbytes, off64_t offset) {
return pread(fd, buf, nbytes, offset);
}
static inline ssize_t pwrite64(int fd, const void* buf, size_t nbytes, off64_t offset) {
return pwrite(fd, buf, nbytes, offset);
}
#endif /* __APPLE__ */
#if defined(_WIN32)
#define O_CLOEXEC O_NOINHERIT
#define O_NOFOLLOW 0
#define DEFFILEMODE 0666
#endif /* _WIN32 */
#if defined(_WIN32)
#define ZD "%ld"
#define ZD_TYPE long
#else
#define ZD "%zd"
#define ZD_TYPE ssize_t
#endif
/*
* Needed for cases where something should be constexpr if possible, but not
* being constexpr is fine if in pre-C++11 code (such as a const static float
* member variable).
*/
#if __cplusplus >= 201103L
#define CONSTEXPR constexpr
#else
#define CONSTEXPR
#endif
/*
* TEMP_FAILURE_RETRY is defined by some, but not all, versions of
* <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
* not already defined, then define it here.
*/
#ifndef TEMP_FAILURE_RETRY
/* Used to retry syscalls that can return EINTR. */
#define TEMP_FAILURE_RETRY(exp) ({ \
typeof (exp) _rc; \
do { \
_rc = (exp); \
} while (_rc == -1 && errno == EINTR); \
_rc; })
#endif
#if defined(_WIN32)
#define OS_PATH_SEPARATOR '\\'
#else
#define OS_PATH_SEPARATOR '/'
#endif
#endif /* COCOS_LIB_UTILS_COMPAT_H */

View File

@ -0,0 +1,88 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef COCOS_ERRORS_H
#define COCOS_ERRORS_H
#include <sys/types.h>
#include <errno.h>
namespace cocos2d {
// use this type to return error codes
#ifdef _WIN32
typedef int status_t;
#else
typedef int32_t status_t;
#endif
/* the MS C runtime lacks a few error codes */
/*
* Error codes.
* All error codes are negative values.
*/
// Win32 #defines NO_ERROR as well. It has the same value, so there's no
// real conflict, though it's a bit awkward.
#ifdef _WIN32
# undef NO_ERROR
#endif
enum {
OK = 0, // Everything's swell.
NO_ERROR = 0, // No errors.
UNKNOWN_ERROR = (-2147483647-1), // INT32_MIN value
NO_MEMORY = -ENOMEM,
INVALID_OPERATION = -ENOSYS,
BAD_VALUE = -EINVAL,
BAD_TYPE = (UNKNOWN_ERROR + 1),
NAME_NOT_FOUND = -ENOENT,
PERMISSION_DENIED = -EPERM,
NO_INIT = -ENODEV,
ALREADY_EXISTS = -EEXIST,
DEAD_OBJECT = -EPIPE,
FAILED_TRANSACTION = (UNKNOWN_ERROR + 2),
#if !defined(_WIN32)
BAD_INDEX = -EOVERFLOW,
NOT_ENOUGH_DATA = -ENODATA,
WOULD_BLOCK = -EWOULDBLOCK,
TIMED_OUT = -ETIMEDOUT,
UNKNOWN_TRANSACTION = -EBADMSG,
#else
BAD_INDEX = -E2BIG,
NOT_ENOUGH_DATA = (UNKNOWN_ERROR + 3),
WOULD_BLOCK = (UNKNOWN_ERROR + 4),
TIMED_OUT = (UNKNOWN_ERROR + 5),
UNKNOWN_TRANSACTION = (UNKNOWN_ERROR + 6),
#endif
FDS_NOT_ALLOWED = (UNKNOWN_ERROR + 7),
UNEXPECTED_NULL = (UNKNOWN_ERROR + 8),
};
// Restore define; enumeration is in "android" namespace, so the value defined
// there won't work for Win32 code in a different namespace.
#ifdef _WIN32
# define NO_ERROR 0L
#endif
} // namespace cocos2d {
// ---------------------------------------------------------------------------
#endif // COCOS_ERRORS_H

View File

@ -0,0 +1,39 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "audio/android/utils/Utils.h"
#include "platform/android/jni/JniHelper.h"
#ifndef JCLS_HELPER
#define JCLS_HELPER "org/cocos2dx/lib/Cocos2dxHelper"
#endif
namespace cocos2d {
int getSDKVersion()
{
return JniHelper::callStaticIntMethod(JCLS_HELPER, "getSDKVersion");
}
} // end of namespace cocos2d

View File

@ -0,0 +1,30 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include <string>
namespace cocos2d {
extern int getSDKVersion();
}

View File

@ -0,0 +1,113 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC
#import <OpenAL/al.h>
#include <string>
#include <mutex>
#include <vector>
#include "base/ccMacros.h"
#include "audio/apple/AudioMacros.h"
NS_CC_BEGIN
class AudioEngineImpl;
class AudioPlayer;
class AudioCache
{
public:
enum class State
{
INITIAL,
LOADING,
READY,
FAILED
};
AudioCache();
~AudioCache();
void addPlayCallback(const std::function<void()>& callback);
void addLoadCallback(const std::function<void(bool)>& callback);
protected:
void setSkipReadDataTask(bool isSkip) { _isSkipReadDataTask = isSkip; };
void readDataTask(unsigned int selfId);
void invokingPlayCallbacks();
void invokingLoadCallbacks();
//pcm data related stuff
ALenum _format;
ALsizei _sampleRate;
float _duration;
uint32_t _totalFrames;
uint32_t _framesRead;
/*Cache related stuff;
* Cache pcm data when sizeInBytes less than PCMDATA_CACHEMAXSIZE
*/
ALuint _alBufferId;
char* _pcmData;
/*Queue buffer related stuff
* Streaming in openal when sizeInBytes greater then PCMDATA_CACHEMAXSIZE
*/
char* _queBuffers[QUEUEBUFFER_NUM];
ALsizei _queBufferSize[QUEUEBUFFER_NUM];
uint32_t _queBufferFrames;
std::mutex _playCallbackMutex;
std::vector< std::function<void()> > _playCallbacks;
// loadCallbacks doesn't need mutex since it's invoked only in Cocos thread.
std::vector< std::function<void(bool)> > _loadCallbacks;
std::mutex _readDataTaskMutex;
State _state;
std::shared_ptr<bool> _isDestroyed;
std::string _fileFullPath;
unsigned int _id;
bool _isLoadingFinished;
bool _isSkipReadDataTask;
friend class AudioEngineImpl;
friend class AudioPlayer;
};
NS_CC_END
#endif

View File

@ -0,0 +1,408 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioCache"
#include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC
#include "audio/apple/AudioCache.h"
#import <Foundation/Foundation.h>
#import <OpenAL/alc.h>
#include <thread>
#include "platform/CCApplication.h"
#include "base/CCScheduler.h"
#include "audio/apple/AudioDecoder.h"
#ifdef VERY_VERY_VERBOSE_LOGGING
#define ALOGVV ALOGV
#else
#define ALOGVV(...) do{} while(false)
#endif
namespace {
unsigned int __idIndex = 0;
}
#define INVALID_AL_BUFFER_ID 0xFFFFFFFF
#define PCMDATA_CACHEMAXSIZE 1048576
@interface NSTimerWrapper : NSObject
{
std::function<void()> _timeoutCallback;
}
@end
@implementation NSTimerWrapper
-(id) initWithTimeInterval:(double) seconds callback:(const std::function<void()>&) cb
{
if (self = [super init])
{
_timeoutCallback = cb;
NSTimer* timer = [NSTimer timerWithTimeInterval:seconds target: self selector:@selector(onTimeoutCallback:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
return self;
}
-(void) onTimeoutCallback: (NSTimer*) timer
{
if (_timeoutCallback != nullptr)
{
_timeoutCallback();
_timeoutCallback = nullptr;
}
}
-(void) dealloc
{
[super dealloc];
}
@end
using namespace cocos2d;
AudioCache::AudioCache()
: _format(-1)
, _duration(0.0f)
, _totalFrames(0)
, _framesRead(0)
, _alBufferId(INVALID_AL_BUFFER_ID)
, _pcmData(nullptr)
, _queBufferFrames(0)
, _state(State::INITIAL)
, _isDestroyed(std::make_shared<bool>(false))
, _id(++__idIndex)
, _isLoadingFinished(false)
, _isSkipReadDataTask(false)
{
ALOGVV("AudioCache() %p, id=%u", this, _id);
for (int i = 0; i < QUEUEBUFFER_NUM; ++i)
{
_queBuffers[i] = nullptr;
_queBufferSize[i] = 0;
}
}
AudioCache::~AudioCache()
{
ALOGVV("~AudioCache() %p, id=%u, begin", this, _id);
*_isDestroyed = true;
while (!_isLoadingFinished)
{
if (_isSkipReadDataTask)
{
ALOGV("id=%u, Skip read data task, don't continue to wait!", _id);
break;
}
ALOGVV("id=%u, waiting readData thread to finish ...", _id);
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
//wait for the 'readDataTask' task to exit
_readDataTaskMutex.lock();
if (_state == State::READY)
{
if (_alBufferId != INVALID_AL_BUFFER_ID && alIsBuffer(_alBufferId))
{
ALOGV("~AudioCache(id=%u), delete buffer: %u", _id, _alBufferId);
alDeleteBuffers(1, &_alBufferId);
_alBufferId = INVALID_AL_BUFFER_ID;
}
}
else
{
ALOGW("AudioCache (%p), id=%u, buffer isn't ready, state=%d", this, _id, _state);
}
if (_queBufferFrames > 0)
{
for (int index = 0; index < QUEUEBUFFER_NUM; ++index)
{
free(_queBuffers[index]);
}
}
ALOGVV("~AudioCache() %p, id=%u, end", this, _id);
_readDataTaskMutex.unlock();
}
void AudioCache::readDataTask(unsigned int selfId)
{
//Note: It's in sub thread
ALOGVV("readDataTask, cache id=%u", selfId);
_readDataTaskMutex.lock();
_state = State::LOADING;
AudioDecoder decoder;
do
{
if (!decoder.open(_fileFullPath.c_str()))
break;
const uint32_t originalTotalFrames = decoder.getTotalFrames();
const uint32_t bytesPerFrame = decoder.getBytesPerFrame();
const uint32_t sampleRate = decoder.getSampleRate();
const uint32_t channelCount = decoder.getChannelCount();
uint32_t totalFrames = originalTotalFrames;
uint32_t dataSize = totalFrames * bytesPerFrame;
uint32_t remainingFrames = totalFrames;
uint32_t adjustFrames = 0;
_format = channelCount > 1 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
_sampleRate = (ALsizei)sampleRate;
_duration = 1.0f * totalFrames / sampleRate;
_totalFrames = totalFrames;
if (dataSize <= PCMDATA_CACHEMAXSIZE)
{
uint32_t framesRead = 0;
const uint32_t framesToReadOnce = std::min(totalFrames, static_cast<uint32_t>(sampleRate * QUEUEBUFFER_TIME_STEP * QUEUEBUFFER_NUM));
BREAK_IF_ERR_LOG(!decoder.seek(totalFrames), "AudioDecoder::seek(%u) error", totalFrames);
char* tmpBuf = (char*)malloc(framesToReadOnce * bytesPerFrame);
std::vector<char> adjustFrameBuf;
adjustFrameBuf.reserve(framesToReadOnce * bytesPerFrame);
// Adjust total frames by setting position to the end of frames and try to read more data.
// This is a workaround for https://github.com/cocos2d/cocos2d-x/issues/16938
do
{
framesRead = decoder.read(framesToReadOnce, tmpBuf);
if (framesRead > 0)
{
adjustFrames += framesRead;
adjustFrameBuf.insert(adjustFrameBuf.end(), tmpBuf, tmpBuf + framesRead * bytesPerFrame);
}
} while (framesRead > 0);
if (adjustFrames > 0)
{
ALOGV("Orignal total frames: %u, adjust frames: %u, current total frames: %u", totalFrames, adjustFrames, totalFrames + adjustFrames);
totalFrames += adjustFrames;
_totalFrames = remainingFrames = totalFrames;
}
// Reset dataSize
dataSize = totalFrames * bytesPerFrame;
free(tmpBuf);
// Reset to frame 0
BREAK_IF_ERR_LOG(!decoder.seek(0), "AudioDecoder::seek(0) failed!");
_pcmData = (char*)malloc(dataSize);
memset(_pcmData, 0x00, dataSize);
ALOGV(" id=%u _pcmData alloc: %p", selfId, _pcmData);
if (adjustFrames > 0)
{
memcpy(_pcmData + (dataSize - adjustFrameBuf.size()), adjustFrameBuf.data(), adjustFrameBuf.size());
}
if (*_isDestroyed)
break;
framesRead = decoder.readFixedFrames(std::min(framesToReadOnce, remainingFrames), _pcmData + _framesRead * bytesPerFrame);
_framesRead += framesRead;
remainingFrames -= framesRead;
if (*_isDestroyed)
break;
uint32_t frames = 0;
while (!*_isDestroyed && _framesRead < originalTotalFrames)
{
frames = std::min(framesToReadOnce, remainingFrames);
if (_framesRead + frames > originalTotalFrames)
{
frames = originalTotalFrames - _framesRead;
}
framesRead = decoder.read(frames, _pcmData + _framesRead * bytesPerFrame);
if (framesRead == 0)
break;
_framesRead += framesRead;
remainingFrames -= framesRead;
}
if (_framesRead < originalTotalFrames)
{
memset(_pcmData + _framesRead * bytesPerFrame, 0x00, (totalFrames - _framesRead) * bytesPerFrame);
}
ALOGV("pcm buffer was loaded successfully, total frames: %u, total read frames: %u, adjust frames: %u, remainingFrames: %u", totalFrames, _framesRead, adjustFrames, remainingFrames);
_framesRead += adjustFrames;
alGenBuffers(1, &_alBufferId);
auto alError = alGetError();
if (alError != AL_NO_ERROR) {
ALOGE("%s: attaching audio to buffer fail: %x", __PRETTY_FUNCTION__, alError);
break;
}
ALOGV(" id=%u generated alGenBuffers: %u for _pcmData: %p", selfId, _alBufferId, _pcmData);
ALOGV(" id=%u _pcmData alBufferData: %p", selfId, _pcmData);
alBufferData(_alBufferId, _format, _pcmData, (ALsizei)dataSize, (ALsizei)sampleRate);
_state = State::READY;
invokingPlayCallbacks();
}
else
{
_queBufferFrames = sampleRate * QUEUEBUFFER_TIME_STEP;
BREAK_IF_ERR_LOG(_queBufferFrames == 0, "_queBufferFrames == 0");
const uint32_t queBufferBytes = _queBufferFrames * bytesPerFrame;
for (int index = 0; index < QUEUEBUFFER_NUM; ++index)
{
_queBuffers[index] = (char*)malloc(queBufferBytes);
_queBufferSize[index] = queBufferBytes;
decoder.readFixedFrames(_queBufferFrames, _queBuffers[index]);
}
_state = State::READY;
}
} while (false);
if (_pcmData != nullptr){
CC_SAFE_FREE(_pcmData);
}
decoder.close();
//IDEA: Why to invoke play callback first? Should it be after 'load' callback?
invokingPlayCallbacks();
invokingLoadCallbacks();
_isLoadingFinished = true;
if (_state != State::READY)
{
_state = State::FAILED;
if (_alBufferId != INVALID_AL_BUFFER_ID && alIsBuffer(_alBufferId))
{
ALOGV(" id=%u readDataTask failed, delete buffer: %u", selfId, _alBufferId);
alDeleteBuffers(1, &_alBufferId);
_alBufferId = INVALID_AL_BUFFER_ID;
}
}
_readDataTaskMutex.unlock();
}
void AudioCache::addPlayCallback(const std::function<void()>& callback)
{
std::lock_guard<std::mutex> lk(_playCallbackMutex);
switch (_state)
{
case State::INITIAL:
case State::LOADING:
_playCallbacks.push_back(callback);
break;
case State::READY:
// If state is failure, we still need to invoke the callback
// since the callback will set the 'AudioPlayer::_removeByAudioEngine' flag to true.
case State::FAILED:
callback();
break;
default:
ALOGE("Invalid state: %d", _state);
break;
}
}
void AudioCache::invokingPlayCallbacks()
{
std::lock_guard<std::mutex> lk(_playCallbackMutex);
for (auto&& cb : _playCallbacks)
{
cb();
}
_playCallbacks.clear();
}
void AudioCache::addLoadCallback(const std::function<void(bool)>& callback)
{
switch (_state)
{
case State::INITIAL:
case State::LOADING:
_loadCallbacks.push_back(callback);
break;
case State::READY:
callback(true);
break;
case State::FAILED:
callback(false);
break;
default:
ALOGE("Invalid state: %d", _state);
break;
}
}
void AudioCache::invokingLoadCallbacks()
{
if (*_isDestroyed)
{
ALOGV("AudioCache (%p) was destroyed, don't invoke preload callback ...", this);
return;
}
auto isDestroyed = _isDestroyed;
auto scheduler = Application::getInstance()->getScheduler();
scheduler->performFunctionInCocosThread([&, isDestroyed](){
if (*isDestroyed)
{
ALOGV("invokingLoadCallbacks perform in cocos thread, AudioCache (%p) was destroyed!", this);
return;
}
for (auto&& cb : _loadCallbacks)
{
cb(_state == State::READY);
}
_loadCallbacks.clear();
});
}
#endif

View File

@ -0,0 +1,120 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include <stdint.h>
#import <AudioToolbox/ExtendedAudioFile.h>
namespace cocos2d {
/**
* @brief The class for decoding compressed audio file to PCM buffer.
*/
class AudioDecoder
{
public:
static const uint32_t INVALID_FRAME_INDEX = UINT32_MAX;
AudioDecoder();
~AudioDecoder();
/**
* @brief Opens an audio file specified by a file path.
* @return true if succeed, otherwise false.
*/
bool open(const char* path);
/**
* @brief Checks whether decoder has opened file successfully.
* @return true if succeed, otherwise false.
*/
bool isOpened() const;
/**
* @brief Closes opened audio file.
* @note The method will also be automatically invoked in the destructor.
*/
void close();
/**
* @brief Reads audio frames of PCM format.
* @param framesToRead The number of frames excepted to be read.
* @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame.
* @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end of file.
*/
uint32_t read(uint32_t framesToRead, char* pcmBuf);
/**
* @brief Reads fixed audio frames of PCM format.
* @param framesToRead The number of frames excepted to be read.
* @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame.
* @return The number of frames actually read, it's probably less than |framesToRead|. Returns 0 means reach the end of file.
* @note The different between |read| and |readFixedFrames| is |readFixedFrames| will do multiple reading operations if |framesToRead| frames
* isn't filled entirely, while |read| just does reading operation once whatever |framesToRead| is or isn't filled entirely.
* If current position reaches the end of frames, the return value may smaller than |framesToRead| and the remaining
* buffer in |pcmBuf| will be set with silence data (0x00).
*/
uint32_t readFixedFrames(uint32_t framesToRead, char* pcmBuf);
/**
* @brief Sets frame offest to be read.
* @param frameOffset The frame offest to be set.
* @return true if succeed, otherwise false
*/
bool seek(uint32_t frameOffset);
/**
* @brief Tells the current frame offset.
* @return The current frame offset.
*/
uint32_t tell() const;
/** Gets total frames of current audio.*/
uint32_t getTotalFrames() const;
/** Gets bytes per frame of current audio.*/
uint32_t getBytesPerFrame() const;
/** Gets sample rate of current audio.*/
uint32_t getSampleRate() const;
/** Gets the channel count of current audio.
* @note Currently we only support 1 or 2 channels.
*/
uint32_t getChannelCount() const;
private:
bool _isOpened;
ExtAudioFileRef _extRef;
uint32_t _totalFrames;
uint32_t _bytesPerFrame;
uint32_t _sampleRate;
uint32_t _channelCount;
AudioStreamBasicDescription _outputFormat;
};
} // namespace cocos2d {

View File

@ -0,0 +1,229 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "audio/apple/AudioDecoder.h"
#include "audio/apple/AudioMacros.h"
#import <Foundation/Foundation.h>
#define LOG_TAG "AudioDecoder"
namespace cocos2d {
AudioDecoder::AudioDecoder()
: _isOpened(false)
, _extRef(nullptr)
, _totalFrames(0)
, _bytesPerFrame(0)
, _sampleRate(0)
, _channelCount(0)
{
memset(&_outputFormat, 0, sizeof(_outputFormat));
}
AudioDecoder::~AudioDecoder()
{
close();
}
bool AudioDecoder::open(const char* path)
{
bool ret = false;
CFURLRef fileURL = nil;
do
{
BREAK_IF_ERR_LOG(path == nullptr || strlen(path) == 0, "Invalid path!");
NSString *fileFullPath = [[NSString alloc] initWithCString:path encoding:NSUTF8StringEncoding];
fileURL = (CFURLRef)[[NSURL alloc] initFileURLWithPath:fileFullPath];
[fileFullPath release];
BREAK_IF_ERR_LOG(fileURL == nil, "Converting path to CFURLRef failed!");
OSStatus status = ExtAudioFileOpenURL(fileURL, &_extRef);
BREAK_IF_ERR_LOG(status != noErr, "ExtAudioFileOpenURL FAILED, Error = %ld", (long)ret);
AudioStreamBasicDescription fileFormat;
UInt32 propertySize = sizeof(fileFormat);
// Get the audio data format
ret = ExtAudioFileGetProperty(_extRef, kExtAudioFileProperty_FileDataFormat, &propertySize, &fileFormat);
BREAK_IF_ERR_LOG(status != noErr, "ExtAudioFileGetProperty(kExtAudioFileProperty_FileDataFormat) FAILED, Error = %ld", (long)ret);
BREAK_IF_ERR_LOG(fileFormat.mChannelsPerFrame > 2, "Unsupported Format, channel count is greater than stereo!");
// Set the client format to 16 bit signed integer (native-endian) data
// Maintain the channel count and sample rate of the original source format
_outputFormat.mSampleRate = fileFormat.mSampleRate;
_outputFormat.mChannelsPerFrame = fileFormat.mChannelsPerFrame;
_outputFormat.mFormatID = kAudioFormatLinearPCM;
_outputFormat.mFramesPerPacket = 1;
_outputFormat.mBitsPerChannel = 16;
_outputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
_sampleRate = _outputFormat.mSampleRate;
_channelCount = _outputFormat.mChannelsPerFrame;
_bytesPerFrame = 2 * _outputFormat.mChannelsPerFrame;
_outputFormat.mBytesPerPacket = _bytesPerFrame;
_outputFormat.mBytesPerFrame = _bytesPerFrame;
ret = ExtAudioFileSetProperty(_extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(_outputFormat), &_outputFormat);
BREAK_IF_ERR_LOG(status != noErr, "ExtAudioFileSetProperty FAILED, Error = %ld", (long)ret);
// Get the total frame count
SInt64 totalFrames = 0;
propertySize = sizeof(totalFrames);
ret = ExtAudioFileGetProperty(_extRef, kExtAudioFileProperty_FileLengthFrames, &propertySize, &totalFrames);
BREAK_IF_ERR_LOG(status != noErr, "ExtAudioFileGetProperty(kExtAudioFileProperty_FileLengthFrames) FAILED, Error = %ld", (long)ret);
BREAK_IF_ERR_LOG(totalFrames <= 0, "Total frames is 0, it's an invalid audio file: %s", path);
_totalFrames = static_cast<uint32_t>(totalFrames);
_isOpened = true;
ret = true;
} while (false);
if (fileURL != nil)
CFRelease(fileURL);
if (!ret)
{
close();
}
return ret;
}
void AudioDecoder::close()
{
if (_extRef != nullptr)
{
ExtAudioFileDispose(_extRef);
_extRef = nullptr;
_totalFrames = 0;
_bytesPerFrame = 0;
_sampleRate = 0;
_channelCount = 0;
}
}
uint32_t AudioDecoder::read(uint32_t framesToRead, char* pcmBuf)
{
uint32_t ret = 0;
do
{
BREAK_IF_ERR_LOG(!isOpened(), "decoder isn't openned");
BREAK_IF_ERR_LOG(framesToRead == INVALID_FRAME_INDEX, "frameToRead is INVALID_FRAME_INDEX");
BREAK_IF_ERR_LOG(framesToRead == 0, "frameToRead is 0");
BREAK_IF_ERR_LOG(pcmBuf == nullptr, "pcmBuf is nullptr");
AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mDataByteSize = framesToRead * _bytesPerFrame;
bufferList.mBuffers[0].mNumberChannels = _outputFormat.mChannelsPerFrame;
bufferList.mBuffers[0].mData = pcmBuf;
UInt32 frames = framesToRead;
OSStatus status = ExtAudioFileRead(_extRef, &frames, &bufferList);
BREAK_IF(status != noErr);
ret = frames;
} while (false);
return ret;
}
uint32_t AudioDecoder::readFixedFrames(uint32_t framesToRead, char* pcmBuf)
{
uint32_t framesRead = 0;
uint32_t framesReadOnce = 0;
do
{
framesReadOnce = read(framesToRead - framesRead, pcmBuf + framesRead * _bytesPerFrame);
framesRead += framesReadOnce;
} while (framesReadOnce != 0 && framesRead < framesToRead);
if (framesRead < framesToRead)
{
memset(pcmBuf + framesRead * _bytesPerFrame, 0x00, (framesToRead - framesRead) * _bytesPerFrame);
}
return framesRead;
}
bool AudioDecoder::seek(uint32_t frameOffset)
{
bool ret = false;
do
{
BREAK_IF_ERR_LOG(!isOpened(), "decoder isn't openned");
BREAK_IF_ERR_LOG(frameOffset == INVALID_FRAME_INDEX, "frameIndex is INVALID_FRAME_INDEX");
OSStatus status = ExtAudioFileSeek(_extRef, frameOffset);
BREAK_IF(status != noErr);
ret = true;
} while(false);
return ret;
}
uint32_t AudioDecoder::tell() const
{
uint32_t ret = INVALID_FRAME_INDEX;
do
{
BREAK_IF_ERR_LOG(!isOpened(), "decoder isn't openned");
SInt64 frameIndex = INVALID_FRAME_INDEX;
OSStatus status = ExtAudioFileTell(_extRef, &frameIndex);
BREAK_IF(status != noErr);
ret = static_cast<uint32_t>(frameIndex);
} while(false);
return ret;
}
uint32_t AudioDecoder::getTotalFrames() const
{
return _totalFrames;
}
uint32_t AudioDecoder::getBytesPerFrame() const
{
return _bytesPerFrame;
}
uint32_t AudioDecoder::getSampleRate() const
{
return _sampleRate;
}
uint32_t AudioDecoder::getChannelCount() const
{
return _channelCount;
}
bool AudioDecoder::isOpened() const
{
return _isOpened;
}
} // namespace cocos2d {

View File

@ -0,0 +1,94 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC
#ifndef __AUDIO_ENGINE_INL_H_
#define __AUDIO_ENGINE_INL_H_
#include <unordered_map>
#include "base/CCRef.h"
#include "audio/apple/AudioCache.h"
#include "audio/apple/AudioPlayer.h"
NS_CC_BEGIN
class Scheduler;
#define MAX_AUDIOINSTANCES 24
class AudioEngineImpl : public cocos2d::Ref
{
public:
AudioEngineImpl();
~AudioEngineImpl();
bool init();
int play2d(const std::string &fileFullPath ,bool loop ,float volume);
void setVolume(int audioID,float volume);
void setLoop(int audioID, bool loop);
bool pause(int audioID);
bool resume(int audioID);
void stop(int audioID);
void stopAll();
float getDuration(int audioID);
float getDurationFromFile(const std::string &fileFullPath);
float getCurrentTime(int audioID);
bool setCurrentTime(int audioID, float time);
void setFinishCallback(int audioID, const std::function<void (int, const std::string &)> &callback);
void uncache(const std::string& filePath);
void uncacheAll();
AudioCache* preload(const std::string& filePath, std::function<void(bool)> callback);
void update(float dt);
private:
bool _checkAudioIdValid(int audioID);
void _play2d(AudioCache *cache, int audioID);
ALuint findValidSource();
static ALvoid myAlSourceNotificationCallback(ALuint sid, ALuint notificationID, ALvoid* userData);
ALuint _alSources[MAX_AUDIOINSTANCES];
//source,used
std::list<ALuint> _unusedSourcesPool;
//filePath,bufferInfo
std::unordered_map<std::string, AudioCache> _audioCaches;
//audioID,AudioInfo
std::unordered_map<int, AudioPlayer*> _audioPlayers;
std::mutex _threadMutex;
bool _lazyInitLoop;
int _currentAudioID;
std::weak_ptr<Scheduler> _scheduler;
};
NS_CC_END
#endif // __AUDIO_ENGINE_INL_H_
#endif

View File

@ -0,0 +1,742 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioEngine-inl.mm"
#include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC
#include "audio/apple/AudioEngine-inl.h"
#import <OpenAL/alc.h>
#import <AVFoundation/AVFoundation.h>
#include "audio/include/AudioEngine.h"
#include "platform/CCFileUtils.h"
#include "platform/CCApplication.h"
#include "base/CCScheduler.h"
#include "base/ccUtils.h"
using namespace cocos2d;
static ALCdevice* s_ALDevice = nullptr;
static ALCcontext* s_ALContext = nullptr;
static AudioEngineImpl* s_instance = nullptr;
typedef ALvoid (*alSourceNotificationProc)(ALuint sid, ALuint notificationID, ALvoid* userData);
typedef ALenum (*alSourceAddNotificationProcPtr)(ALuint sid, ALuint notificationID, alSourceNotificationProc notifyProc, ALvoid* userData);
static ALenum alSourceAddNotificationExt(ALuint sid, ALuint notificationID, alSourceNotificationProc notifyProc, ALvoid* userData)
{
static alSourceAddNotificationProcPtr proc = nullptr;
if (proc == nullptr)
{
proc = (alSourceAddNotificationProcPtr)alcGetProcAddress(nullptr, "alSourceAddNotification");
}
if (proc)
{
return proc(sid, notificationID, notifyProc, userData);
}
return AL_INVALID_VALUE;
}
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
@interface AudioEngineSessionHandler : NSObject
{
}
-(id) init;
-(void)handleInterruption:(NSNotification*)notification;
@end
@implementation AudioEngineSessionHandler
// only enable it on iOS. Disable it on tvOS
#if !defined(CC_TARGET_OS_TVOS)
void AudioEngineInterruptionListenerCallback(void* user_data, UInt32 interruption_state)
{
if (kAudioSessionBeginInterruption == interruption_state)
{
alcMakeContextCurrent(nullptr);
}
else if (kAudioSessionEndInterruption == interruption_state)
{
OSStatus result = AudioSessionSetActive(true);
if (result) NSLog(@"Error setting audio session active! %d\n", static_cast<int>(result));
alcMakeContextCurrent(s_ALContext);
}
}
#endif
-(id) init
{
if (self = [super init])
{
if ([[[UIDevice currentDevice] systemVersion] intValue] > 5) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:UIApplicationWillResignActiveNotification object:nil];
}
// only enable it on iOS. Disable it on tvOS
// AudioSessionInitialize removed from tvOS
#if !defined(CC_TARGET_OS_TVOS)
else {
AudioSessionInitialize(NULL, NULL, AudioEngineInterruptionListenerCallback, self);
}
#endif
BOOL success = [[AVAudioSession sharedInstance]
setCategory: AVAudioSessionCategoryAmbient
error: nil];
if (!success)
ALOGE("Fail to set audio session.");
}
return self;
}
-(void)handleInterruption:(NSNotification*)notification
{
static bool isAudioSessionInterrupted = false;
static bool resumeOnBecomingActive = false;
static bool pauseOnResignActive = false;
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification])
{
NSInteger reason = [[[notification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey] integerValue];
if (reason == AVAudioSessionInterruptionTypeBegan)
{
isAudioSessionInterrupted = true;
alcMakeContextCurrent(nullptr);
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive)
{
ALOGD("AVAudioSessionInterruptionTypeBegan, application == UIApplicationStateActive, pauseOnResignActive = true");
pauseOnResignActive = true;
}
}
if (reason == AVAudioSessionInterruptionTypeEnded)
{
isAudioSessionInterrupted = false;
NSError *error = nil;
[[AVAudioSession sharedInstance] setActive:YES error:&error];
alcMakeContextCurrent(s_ALContext);
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive)
{
ALOGD("AVAudioSessionInterruptionTypeEnded, application != UIApplicationStateActive, resumeOnBecomingActive = true");
resumeOnBecomingActive = true;
}
}
}
else if ([notification.name isEqualToString:UIApplicationWillResignActiveNotification])
{
ALOGD("UIApplicationWillResignActiveNotification");
if (pauseOnResignActive)
{
pauseOnResignActive = false;
ALOGD("UIApplicationWillResignActiveNotification, alcMakeContextCurrent(nullptr)");
alcMakeContextCurrent(nullptr);
}
}
else if ([notification.name isEqualToString:UIApplicationDidBecomeActiveNotification])
{
ALOGD("UIApplicationDidBecomeActiveNotification");
if (resumeOnBecomingActive)
{
resumeOnBecomingActive = false;
ALOGD("UIApplicationDidBecomeActiveNotification, alcMakeContextCurrent(s_ALContext)");
NSError *error = nil;
BOOL success = [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryAmbient error: &error];
if (!success) {
ALOGE("Fail to set audio session.");
return;
}
[[AVAudioSession sharedInstance] setActive:YES error:&error];
alcMakeContextCurrent(s_ALContext);
}
else if (isAudioSessionInterrupted)
{
ALOGD("Audio session is still interrupted, pause director!");
//IDEA: Director::getInstance()->pause();
}
}
}
-(void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionInterruptionNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
[super dealloc];
}
@end
static id s_AudioEngineSessionHandler = nullptr;
#endif
ALvoid AudioEngineImpl::myAlSourceNotificationCallback(ALuint sid, ALuint notificationID, ALvoid* userData)
{
// Currently, we only care about AL_BUFFERS_PROCESSED event
if (notificationID != AL_BUFFERS_PROCESSED)
return;
AudioPlayer* player = nullptr;
s_instance->_threadMutex.lock();
for (const auto& e : s_instance->_audioPlayers)
{
player = e.second;
if (player->_alSource == sid && player->_streamingSource)
{
player->wakeupRotateThread();
}
}
s_instance->_threadMutex.unlock();
}
AudioEngineImpl::AudioEngineImpl()
: _lazyInitLoop(true)
, _currentAudioID(0)
{
s_instance = this;
}
AudioEngineImpl::~AudioEngineImpl()
{
if (auto sche = _scheduler.lock())
{
sche->unschedule("AudioEngine", this);
}
if (s_ALContext) {
alDeleteSources(MAX_AUDIOINSTANCES, _alSources);
_audioCaches.clear();
alcMakeContextCurrent(nullptr);
alcDestroyContext(s_ALContext);
}
if (s_ALDevice) {
alcCloseDevice(s_ALDevice);
}
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
[s_AudioEngineSessionHandler release];
#endif
s_instance = nullptr;
}
bool AudioEngineImpl::init()
{
bool ret = false;
do{
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
s_AudioEngineSessionHandler = [[AudioEngineSessionHandler alloc] init];
#endif
s_ALDevice = alcOpenDevice(nullptr);
if (s_ALDevice) {
s_ALContext = alcCreateContext(s_ALDevice, nullptr);
alcMakeContextCurrent(s_ALContext);
alGenSources(MAX_AUDIOINSTANCES, _alSources);
auto alError = alGetError();
if(alError != AL_NO_ERROR)
{
ALOGE("%s:generating sources failed! error = %x", __PRETTY_FUNCTION__, alError);
break;
}
for (int i = 0; i < MAX_AUDIOINSTANCES; ++i) {
_unusedSourcesPool.push_back(_alSources[i]);
alSourceAddNotificationExt(_alSources[i], AL_BUFFERS_PROCESSED, myAlSourceNotificationCallback, nullptr);
}
// fixed #16170: Random crash in alGenBuffers(AudioCache::readDataTask) at startup
// Please note that, as we know the OpenAL operation is atomic (threadsafe),
// 'alGenBuffers' may be invoked by different threads. But in current implementation of 'alGenBuffers',
// When the first time it's invoked, application may crash!!!
// Why? OpenAL is opensource by Apple and could be found at
// http://opensource.apple.com/source/OpenAL/OpenAL-48.7/Source/OpenAL/oalImp.cpp .
/*
void InitializeBufferMap()
{
if (gOALBufferMap == NULL) // Position 1
{
gOALBufferMap = new OALBufferMap (); // Position 2
// Position Gap
gBufferMapLock = new CAGuard("OAL:BufferMapLock"); // Position 3
gDeadOALBufferMap = new OALBufferMap ();
OALBuffer *newBuffer = new OALBuffer (AL_NONE);
gOALBufferMap->Add(AL_NONE, &newBuffer);
}
}
AL_API ALvoid AL_APIENTRY alGenBuffers(ALsizei n, ALuint *bids)
{
...
try {
if (n < 0)
throw ((OSStatus) AL_INVALID_VALUE);
InitializeBufferMap();
if (gOALBufferMap == NULL)
throw ((OSStatus) AL_INVALID_OPERATION);
CAGuard::Locker locked(*gBufferMapLock); // Position 4
...
...
}
*/
// 'gBufferMapLock' will be initialized in the 'InitializeBufferMap' function,
// that's the problem. It means that 'InitializeBufferMap' may be invoked in different threads.
// It will be very dangerous in multi-threads environment.
// Imagine there're two threads (Thread A, Thread B), they call 'alGenBuffers' simultaneously.
// While A goto 'Position Gap', 'gOALBufferMap' was assigned, then B goto 'Position 1' and find
// that 'gOALBufferMap' isn't NULL, B just jump over 'InitialBufferMap' and goto 'Position 4'.
// Meanwhile, A is still at 'Position Gap', B will crash at '*gBufferMapLock' since 'gBufferMapLock'
// is still a null pointer. Oops, how could Apple implemented this method in this fucking way?
// Workaround is do an unused invocation in the mainthread right after OpenAL is initialized successfully
// as bellow.
// ================ Workaround begin ================ //
ALuint unusedAlBufferId = 0;
alGenBuffers(1, &unusedAlBufferId);
alDeleteBuffers(1, &unusedAlBufferId);
// ================ Workaround end ================ //
_scheduler = Application::getInstance()->getScheduler();
ret = true;
ALOGI("OpenAL was initialized successfully!");
}
}while (false);
return ret;
}
AudioCache* AudioEngineImpl::preload(const std::string& filePath, std::function<void(bool)> callback)
{
AudioCache* audioCache = nullptr;
auto it = _audioCaches.find(filePath);
if (it == _audioCaches.end()) {
audioCache = &_audioCaches[filePath];
audioCache->_fileFullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
unsigned int cacheId = audioCache->_id;
auto isCacheDestroyed = audioCache->_isDestroyed;
AudioEngine::addTask([audioCache, cacheId, isCacheDestroyed](){
if (*isCacheDestroyed)
{
ALOGV("AudioCache (id=%u) was destroyed, no need to launch readDataTask.", cacheId);
audioCache->setSkipReadDataTask(true);
return;
}
audioCache->readDataTask(cacheId);
});
}
else {
audioCache = &it->second;
}
if (audioCache && callback)
{
audioCache->addLoadCallback(callback);
}
return audioCache;
}
int AudioEngineImpl::play2d(const std::string &filePath ,bool loop ,float volume)
{
if (s_ALDevice == nullptr) {
return AudioEngine::INVALID_AUDIO_ID;
}
ALuint alSource = findValidSource();
if (alSource == AL_INVALID)
{
return AudioEngine::INVALID_AUDIO_ID;
}
auto player = new (std::nothrow) AudioPlayer;
if (player == nullptr) {
return AudioEngine::INVALID_AUDIO_ID;
}
player->_alSource = alSource;
player->_loop = loop;
player->_volume = volume;
auto audioCache = preload(filePath, nullptr);
if (audioCache == nullptr) {
delete player;
return AudioEngine::INVALID_AUDIO_ID;
}
player->setCache(audioCache);
_threadMutex.lock();
_audioPlayers[_currentAudioID] = player;
_threadMutex.unlock();
audioCache->addPlayCallback(std::bind(&AudioEngineImpl::_play2d,this,audioCache,_currentAudioID));
if (_lazyInitLoop) {
_lazyInitLoop = false;
if(auto sche = _scheduler.lock())
{
sche->schedule(CC_CALLBACK_1(AudioEngineImpl::update, this), this, 0.05f, false, "AudioEngine");
}
}
return _currentAudioID++;
}
void AudioEngineImpl::_play2d(AudioCache *cache, int audioID)
{
//Note: It may bn in sub thread or main thread :(
if (!*cache->_isDestroyed && cache->_state == AudioCache::State::READY)
{
_threadMutex.lock();
auto playerIt = _audioPlayers.find(audioID);
if (playerIt != _audioPlayers.end() && playerIt->second->play2d()) {
if(auto sche = _scheduler.lock()){
sche->performFunctionInCocosThread([audioID](){
if (AudioEngine::_audioIDInfoMap.find(audioID) != AudioEngine::_audioIDInfoMap.end()) {
AudioEngine::_audioIDInfoMap[audioID].state = AudioEngine::AudioState::PLAYING;
}
});
}
}
_threadMutex.unlock();
}
else
{
ALOGD("AudioEngineImpl::_play2d, cache was destroyed or not ready!");
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end())
{
iter->second->_removeByAudioEngine = true;
}
}
}
ALuint AudioEngineImpl::findValidSource()
{
ALuint sourceId = AL_INVALID;
if (!_unusedSourcesPool.empty())
{
sourceId = _unusedSourcesPool.front();
_unusedSourcesPool.pop_front();
}
return sourceId;
}
void AudioEngineImpl::setVolume(int audioID,float volume)
{
if (!_checkAudioIdValid(audioID)) {
return;
}
auto player = _audioPlayers[audioID];
player->_volume = volume;
if (player->_ready) {
alSourcef(_audioPlayers[audioID]->_alSource, AL_GAIN, volume);
auto error = alGetError();
if (error != AL_NO_ERROR) {
ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__,audioID,error);
}
}
}
void AudioEngineImpl::setLoop(int audioID, bool loop)
{
if (!_checkAudioIdValid(audioID)) {
return;
}
auto player = _audioPlayers[audioID];
if (player->_ready) {
if (player->_streamingSource) {
player->setLoop(loop);
} else {
if (loop) {
alSourcei(player->_alSource, AL_LOOPING, AL_TRUE);
} else {
alSourcei(player->_alSource, AL_LOOPING, AL_FALSE);
}
auto error = alGetError();
if (error != AL_NO_ERROR) {
ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__,audioID,error);
}
}
}
else {
player->_loop = loop;
}
}
bool AudioEngineImpl::pause(int audioID)
{
if (!_checkAudioIdValid(audioID)) {
return false;
}
bool ret = true;
alSourcePause(_audioPlayers[audioID]->_alSource);
auto error = alGetError();
if (error != AL_NO_ERROR) {
ret = false;
ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__,audioID,error);
}
return ret;
}
bool AudioEngineImpl::resume(int audioID)
{
if (!_checkAudioIdValid(audioID)) {
return false;
}
bool ret = true;
alSourcePlay(_audioPlayers[audioID]->_alSource);
auto error = alGetError();
if (error != AL_NO_ERROR) {
ret = false;
ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__,audioID,error);
}
return ret;
}
void AudioEngineImpl::stop(int audioID)
{
if (!_checkAudioIdValid(audioID)) {
return;
}
auto player = _audioPlayers[audioID];
player->destroy();
// Call 'update' method to cleanup immediately since the schedule may be cancelled without any notification.
update(0.0f);
}
void AudioEngineImpl::stopAll()
{
for(auto&& player : _audioPlayers)
{
player.second->destroy();
}
// Call 'update' method to cleanup immediately since the schedule may be cancelled without any notification.
update(0.0f);
}
float AudioEngineImpl::getDuration(int audioID)
{
if (!_checkAudioIdValid(audioID)) {
return AudioEngine::TIME_UNKNOWN;
}
auto player = _audioPlayers[audioID];
if(player->_ready){
return player->_audioCache->_duration;
} else {
return AudioEngine::TIME_UNKNOWN;
}
}
float AudioEngineImpl::getDurationFromFile(const std::string &filePath)
{
auto it = _audioCaches.find(filePath);
if (it == _audioCaches.end()) {
this->preload(filePath, nullptr);
return AudioEngine::TIME_UNKNOWN;
}
return it->second._duration;
}
float AudioEngineImpl::getCurrentTime(int audioID)
{
float ret = 0.0f;
if (!_checkAudioIdValid(audioID)) {
return ret;
}
auto player = _audioPlayers[audioID];
if(player->_ready){
if (player->_streamingSource) {
ret = player->getTime();
} else {
alGetSourcef(player->_alSource, AL_SEC_OFFSET, &ret);
auto error = alGetError();
if (error != AL_NO_ERROR) {
ALOGE("%s, audio id:%d,error code:%x", __PRETTY_FUNCTION__,audioID,error);
}
}
}
return ret;
}
bool AudioEngineImpl::setCurrentTime(int audioID, float time)
{
if (!_checkAudioIdValid(audioID)) {
return false;
}
bool ret = false;
auto player = _audioPlayers[audioID];
do {
if (!player->_ready) {
break;
}
if (player->_streamingSource) {
ret = player->setTime(time);
break;
}
else {
if (player->_audioCache->_framesRead != player->_audioCache->_totalFrames &&
(time * player->_audioCache->_sampleRate) > player->_audioCache->_framesRead) {
ALOGE("%s: audio id = %d", __PRETTY_FUNCTION__,audioID);
break;
}
alSourcef(player->_alSource, AL_SEC_OFFSET, time);
auto error = alGetError();
if (error != AL_NO_ERROR) {
ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__,audioID,error);
}
ret = true;
}
} while (0);
return ret;
}
void AudioEngineImpl::setFinishCallback(int audioID, const std::function<void (int, const std::string &)> &callback)
{
if (!_checkAudioIdValid(audioID)) {
return;
}
_audioPlayers[audioID]->_finishCallbak = callback;
}
void AudioEngineImpl::update(float dt)
{
ALint sourceState;
int audioID;
AudioPlayer* player;
ALuint alSource;
// ALOGV("AudioPlayer count: %d", (int)_audioPlayers.size());
for (auto it = _audioPlayers.begin(); it != _audioPlayers.end(); ) {
audioID = it->first;
player = it->second;
alSource = player->_alSource;
alGetSourcei(alSource, AL_SOURCE_STATE, &sourceState);
if (player->_removeByAudioEngine)
{
AudioEngine::remove(audioID);
_threadMutex.lock();
it = _audioPlayers.erase(it);
_threadMutex.unlock();
delete player;
_unusedSourcesPool.push_back(alSource);
}
else if (player->_ready && sourceState == AL_STOPPED) {
std::string filePath;
if (player->_finishCallbak) {
auto& audioInfo = AudioEngine::_audioIDInfoMap[audioID];
filePath = *audioInfo.filePath;
}
AudioEngine::remove(audioID);
_threadMutex.lock();
it = _audioPlayers.erase(it);
_threadMutex.unlock();
if(auto sche = _scheduler.lock()) {
if (player->_finishCallbak) {
auto cb = player->_finishCallbak;
sche->performFunctionInCocosThread([audioID, cb, filePath](){
cb(audioID, filePath); //IDEA: callback will delay 50ms
});
}
}
delete player;
_unusedSourcesPool.push_back(alSource);
}
else{
++it;
}
}
if(_audioPlayers.empty()){
_lazyInitLoop = true;
if(auto sche = _scheduler.lock()) {
sche->unschedule("AudioEngine", this);
}
}
}
void AudioEngineImpl::uncache(const std::string &filePath)
{
_audioCaches.erase(filePath);
}
void AudioEngineImpl::uncacheAll()
{
_audioCaches.clear();
}
bool AudioEngineImpl::_checkAudioIdValid(int audioID) {
return _audioPlayers.find(audioID) != _audioPlayers.end();
}
#endif

View File

@ -0,0 +1,65 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#define QUEUEBUFFER_NUM (3)
#define QUEUEBUFFER_TIME_STEP (0.1f)
#define QUOTEME_(x) #x
#define QUOTEME(x) QUOTEME_(x)
#if defined(COCOS2D_DEBUG) && COCOS2D_DEBUG > 0
#define ALOGV(fmt, ...) printf("V/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__)
#else
#define ALOGV(fmt, ...) do {} while(false)
#endif
#define ALOGD(fmt, ...) printf("D/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__)
#define ALOGI(fmt, ...) printf("I/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__)
#define ALOGW(fmt, ...) printf("W/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__)
#define ALOGE(fmt, ...) printf("E/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__)
#if defined(COCOS2D_DEBUG) && COCOS2D_DEBUG > 0
#define CHECK_AL_ERROR_DEBUG() \
do { \
GLenum __error = alGetError(); \
if (__error) { \
ALOGE("OpenAL error 0x%04X in %s %s %d\n", __error, __FILE__, __FUNCTION__, __LINE__); \
} \
} while (false)
#else
#define CHECK_AL_ERROR_DEBUG()
#endif
#define BREAK_IF(condition) \
if (!!(condition)) { \
break; \
}
#define BREAK_IF_ERR_LOG(condition, fmt, ...) \
if (!!(condition)) { \
ALOGE("(" QUOTEME(condition) ") failed, message: " fmt, ##__VA_ARGS__); \
break; \
}

View File

@ -0,0 +1,94 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC
#include "base/ccMacros.h"
#include "audio/apple/AudioMacros.h"
#include <condition_variable>
#include <mutex>
#include <string>
#include <thread>
#include <OpenAL/al.h>
NS_CC_BEGIN
class AudioCache;
class AudioEngineImpl;
class AudioPlayer
{
public:
AudioPlayer();
~AudioPlayer();
void destroy();
//queue buffer related stuff
bool setTime(float time);
float getTime() { return _currTime;}
bool setLoop(bool loop);
protected:
void setCache(AudioCache* cache);
void rotateBufferThread(int offsetFrame);
bool play2d();
void wakeupRotateThread();
AudioCache* _audioCache;
float _volume;
bool _loop;
std::function<void (int, const std::string &)> _finishCallbak;
bool _isDestroyed;
bool _removeByAudioEngine;
bool _ready;
ALuint _alSource;
//play by circular buffer
float _currTime;
bool _streamingSource;
ALuint _bufferIds[QUEUEBUFFER_NUM];
std::thread* _rotateBufferThread;
std::condition_variable _sleepCondition;
std::mutex _sleepMutex;
bool _timeDirty;
bool _isRotateThreadExited;
std::atomic_bool _needWakeupRotateThread;
std::mutex _play2dMutex;
unsigned int _id;
friend class AudioEngineImpl;
};
NS_CC_END
#endif

View File

@ -0,0 +1,349 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioPlayer"
#include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC
#import <Foundation/Foundation.h>
#include "audio/apple/AudioPlayer.h"
#include "audio/apple/AudioCache.h"
#include "platform/CCFileUtils.h"
#include "audio/apple/AudioDecoder.h"
#ifdef VERY_VERY_VERBOSE_LOGGING
#define ALOGVV ALOGV
#else
#define ALOGVV(...) do{} while(false)
#endif
using namespace cocos2d;
namespace {
unsigned int __idIndex = 0;
}
AudioPlayer::AudioPlayer()
: _audioCache(nullptr)
, _finishCallbak(nullptr)
, _isDestroyed(false)
, _removeByAudioEngine(false)
, _ready(false)
, _currTime(0.0f)
, _streamingSource(false)
, _rotateBufferThread(nullptr)
, _timeDirty(false)
, _isRotateThreadExited(false)
, _needWakeupRotateThread(false)
, _id(++__idIndex)
{
memset(_bufferIds, 0, sizeof(_bufferIds));
}
AudioPlayer::~AudioPlayer()
{
ALOGVV("~AudioPlayer() (%p), id=%u", this, _id);
destroy();
if (_streamingSource)
{
alDeleteBuffers(QUEUEBUFFER_NUM, _bufferIds);
}
}
void AudioPlayer::destroy()
{
if (_isDestroyed)
return;
ALOGVV("AudioPlayer::destroy begin, id=%u", _id);
_isDestroyed = true;
do
{
if (_audioCache != nullptr)
{
if (_audioCache->_state == AudioCache::State::INITIAL)
{
ALOGV("AudioPlayer::destroy, id=%u, cache isn't ready!", _id);
break;
}
while (!_audioCache->_isLoadingFinished)
{
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
}
// Wait for play2d to be finished.
_play2dMutex.lock();
_play2dMutex.unlock();
if (_streamingSource)
{
if (_rotateBufferThread != nullptr)
{
while (!_isRotateThreadExited)
{
_sleepCondition.notify_one();
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
if (_rotateBufferThread->joinable()) {
_rotateBufferThread->join();
}
delete _rotateBufferThread;
_rotateBufferThread = nullptr;
ALOGVV("rotateBufferThread exited!");
}
}
} while(false);
ALOGVV("Before alSourceStop");
alSourceStop(_alSource); CHECK_AL_ERROR_DEBUG();
ALOGVV("Before alSourcei");
alSourcei(_alSource, AL_BUFFER, 0); CHECK_AL_ERROR_DEBUG();
_removeByAudioEngine = true;
_ready = false;
ALOGVV("AudioPlayer::destroy end, id=%u", _id);
}
void AudioPlayer::setCache(AudioCache* cache)
{
_audioCache = cache;
}
bool AudioPlayer::play2d()
{
_play2dMutex.lock();
ALOGVV("AudioPlayer::play2d, _alSource: %u", _alSource);
/*********************************************************************/
/* Note that it may be in sub thread or in main thread. **/
/*********************************************************************/
bool ret = false;
do
{
if (_audioCache->_state != AudioCache::State::READY)
{
ALOGE("alBuffer isn't ready for play!");
break;
}
alSourcei(_alSource, AL_BUFFER, 0);CHECK_AL_ERROR_DEBUG();
alSourcef(_alSource, AL_PITCH, 1.0f);CHECK_AL_ERROR_DEBUG();
alSourcef(_alSource, AL_GAIN, _volume);CHECK_AL_ERROR_DEBUG();
alSourcei(_alSource, AL_LOOPING, AL_FALSE);CHECK_AL_ERROR_DEBUG();
if (_audioCache->_queBufferFrames == 0)
{
if (_loop) {
alSourcei(_alSource, AL_LOOPING, AL_TRUE);
CHECK_AL_ERROR_DEBUG();
}
}
else
{
alGenBuffers(QUEUEBUFFER_NUM, _bufferIds);
auto alError = alGetError();
if (alError == AL_NO_ERROR)
{
for (int index = 0; index < QUEUEBUFFER_NUM; ++index)
{
alBufferData(_bufferIds[index], _audioCache->_format, _audioCache->_queBuffers[index], _audioCache->_queBufferSize[index], _audioCache->_sampleRate);
}
CHECK_AL_ERROR_DEBUG();
}
else
{
ALOGE("%s:alGenBuffers error code:%x", __PRETTY_FUNCTION__,alError);
break;
}
_streamingSource = true;
}
{
std::unique_lock<std::mutex> lk(_sleepMutex);
if (_isDestroyed)
break;
if (_streamingSource)
{
alSourceQueueBuffers(_alSource, QUEUEBUFFER_NUM, _bufferIds);
CHECK_AL_ERROR_DEBUG();
_rotateBufferThread = new std::thread(&AudioPlayer::rotateBufferThread, this, _audioCache->_queBufferFrames * QUEUEBUFFER_NUM + 1);
}
else
{
alSourcei(_alSource, AL_BUFFER, _audioCache->_alBufferId);
CHECK_AL_ERROR_DEBUG();
}
alSourcePlay(_alSource);
}
auto alError = alGetError();
if (alError != AL_NO_ERROR)
{
ALOGE("%s:alSourcePlay error code:%x", __PRETTY_FUNCTION__,alError);
break;
}
ALint state;
alGetSourcei(_alSource, AL_SOURCE_STATE, &state);
assert(state == AL_PLAYING);
_ready = true;
ret = true;
} while (false);
if (!ret)
{
_removeByAudioEngine = true;
}
_play2dMutex.unlock();
return ret;
}
void AudioPlayer::rotateBufferThread(int offsetFrame)
{
char* tmpBuffer = nullptr;
AudioDecoder decoder;
do
{
BREAK_IF(!decoder.open(_audioCache->_fileFullPath.c_str()));
uint32_t framesRead = 0;
const uint32_t framesToRead = _audioCache->_queBufferFrames;
const uint32_t bufferSize = framesToRead * decoder.getBytesPerFrame();
tmpBuffer = (char*)malloc(bufferSize);
memset(tmpBuffer, 0, bufferSize);
if (offsetFrame != 0) {
decoder.seek(offsetFrame);
}
ALint sourceState;
ALint bufferProcessed = 0;
bool needToExitThread = false;
while (!_isDestroyed) {
alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState);
if (sourceState == AL_PLAYING) {
alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed);
while (bufferProcessed > 0) {
bufferProcessed--;
if (_timeDirty) {
_timeDirty = false;
offsetFrame = _currTime * decoder.getSampleRate();
decoder.seek(offsetFrame);
}
else {
_currTime += QUEUEBUFFER_TIME_STEP;
if (_currTime > _audioCache->_duration) {
if (_loop) {
_currTime = 0.0f;
} else {
_currTime = _audioCache->_duration;
}
}
}
framesRead = decoder.readFixedFrames(framesToRead, tmpBuffer);
if (framesRead == 0) {
if (_loop) {
decoder.seek(0);
framesRead = decoder.readFixedFrames(framesToRead, tmpBuffer);
} else {
needToExitThread = true;
break;
}
}
ALuint bid;
alSourceUnqueueBuffers(_alSource, 1, &bid);
alBufferData(bid, _audioCache->_format, tmpBuffer, framesRead * decoder.getBytesPerFrame(), decoder.getSampleRate());
alSourceQueueBuffers(_alSource, 1, &bid);
}
}
std::unique_lock<std::mutex> lk(_sleepMutex);
if (_isDestroyed || needToExitThread) {
break;
}
if (!_needWakeupRotateThread)
{
_sleepCondition.wait_for(lk,std::chrono::milliseconds(75));
}
_needWakeupRotateThread = false;
}
} while(false);
ALOGV("Exit rotate buffer thread ...");
decoder.close();
free(tmpBuffer);
_isRotateThreadExited = true;
}
void AudioPlayer::wakeupRotateThread()
{
_needWakeupRotateThread = true;
_sleepCondition.notify_all();
}
bool AudioPlayer::setLoop(bool loop)
{
if (!_isDestroyed ) {
_loop = loop;
return true;
}
return false;
}
bool AudioPlayer::setTime(float time)
{
if (!_isDestroyed && time >= 0.0f && time < _audioCache->_duration) {
_currTime = time;
_timeDirty = true;
return true;
}
return false;
}
#endif

Some files were not shown because too many files have changed in this diff Show More