mirror of
https://github.com/smallmain/cocos-enhance-kit.git
synced 2025-06-25 22:54:00 +00:00
初始化
This commit is contained in:
commit
ef0589e8e5
36
.gitignore
vendored
Normal file
36
.gitignore
vendored
Normal 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
|
169
cocos2d-x/.gitignore
vendored
Normal file
169
cocos2d-x/.gitignore
vendored
Normal 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
6
cocos2d-x/.gitmodules
vendored
Normal 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
19
cocos2d-x/.travis.yml
Normal 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
64
cocos2d-x/README.md
Normal 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.
|
||||

|
||||
|
||||
Contributing to the Project
|
||||
--------------------------------
|
||||
|
||||
cocos2d-x-lite is licensed under the [MIT License](https://opensource.org/licenses/MIT). We welcome participation!
|
3
cocos2d-x/auto-build-src.bat
Normal file
3
cocos2d-x/auto-build-src.bat
Normal 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
5
cocos2d-x/auto-build.bat
Normal file
@ -0,0 +1,5 @@
|
||||
git checkout -- .
|
||||
git pull fireball v1.4
|
||||
git submodule update --init
|
||||
gulp make-simulator
|
||||
gulp make-prebuilt
|
23
cocos2d-x/build/cocos2d_headers.props
Normal file
23
cocos2d-x/build/cocos2d_headers.props
Normal 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>
|
5628
cocos2d-x/build/cocos2d_libs.xcodeproj/project.pbxproj
Normal file
5628
cocos2d-x/build/cocos2d_libs.xcodeproj/project.pbxproj
Normal file
File diff suppressed because it is too large
Load Diff
23
cocos2d-x/build/cocos2dx.props
Normal file
23
cocos2d-x/build/cocos2dx.props
Normal 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>
|
840
cocos2d-x/build/libcocos2d.vcxproj
Normal file
840
cocos2d-x/build/libcocos2d.vcxproj
Normal 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>
|
2006
cocos2d-x/build/libcocos2d.vcxproj.filters
Normal file
2006
cocos2d-x/build/libcocos2d.vcxproj.filters
Normal file
File diff suppressed because it is too large
Load Diff
361
cocos2d-x/cocos/2d/CCFontAtlas.cpp
Normal file
361
cocos2d-x/cocos/2d/CCFontAtlas.cpp
Normal 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
|
147
cocos2d-x/cocos/2d/CCFontAtlas.h
Normal file
147
cocos2d-x/cocos/2d/CCFontAtlas.h
Normal 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
|
415
cocos2d-x/cocos/2d/CCFontFreetype.cpp
Normal file
415
cocos2d-x/cocos/2d/CCFontFreetype.cpp
Normal 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
|
93
cocos2d-x/cocos/2d/CCFontFreetype.h
Normal file
93
cocos2d-x/cocos/2d/CCFontFreetype.h
Normal 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
|
920
cocos2d-x/cocos/2d/CCLabelLayout.cpp
Normal file
920
cocos2d-x/cocos/2d/CCLabelLayout.cpp
Normal 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
|
168
cocos2d-x/cocos/2d/CCLabelLayout.h
Normal file
168
cocos2d-x/cocos/2d/CCLabelLayout.h
Normal 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
|
155
cocos2d-x/cocos/2d/CCTTFLabelAtlasCache.cpp
Normal file
155
cocos2d-x/cocos/2d/CCTTFLabelAtlasCache.cpp
Normal 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
|
96
cocos2d-x/cocos/2d/CCTTFLabelAtlasCache.h
Normal file
96
cocos2d-x/cocos/2d/CCTTFLabelAtlasCache.h
Normal 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
|
167
cocos2d-x/cocos/2d/CCTTFLabelRenderer.cpp
Normal file
167
cocos2d-x/cocos/2d/CCTTFLabelRenderer.cpp
Normal 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
|
106
cocos2d-x/cocos/2d/CCTTFLabelRenderer.h
Normal file
106
cocos2d-x/cocos/2d/CCTTFLabelRenderer.h
Normal 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
|
69
cocos2d-x/cocos/2d/CCTTFTypes.cpp
Normal file
69
cocos2d-x/cocos/2d/CCTTFTypes.cpp
Normal 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
|
114
cocos2d-x/cocos/2d/CCTTFTypes.h
Normal file
114
cocos2d-x/cocos/2d/CCTTFTypes.h
Normal 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
275
cocos2d-x/cocos/Android.mk
Normal 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)
|
655
cocos2d-x/cocos/audio/AudioEngine.cpp
Normal file
655
cocos2d-x/cocos/audio/AudioEngine.cpp
Normal 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;
|
||||
}
|
||||
|
53
cocos2d-x/cocos/audio/android/Android.mk
Normal file
53
cocos2d-x/cocos/audio/android/Android.mk
Normal 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)
|
48
cocos2d-x/cocos/audio/android/AssetFd.cpp
Normal file
48
cocos2d-x/cocos/audio/android/AssetFd.cpp
Normal 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 {
|
43
cocos2d-x/cocos/audio/android/AssetFd.h
Normal file
43
cocos2d-x/cocos/audio/android/AssetFd.h
Normal 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 {
|
||||
|
80
cocos2d-x/cocos/audio/android/AudioBufferProvider.h
Normal file
80
cocos2d-x/cocos/audio/android/AudioBufferProvider.h
Normal 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 {
|
293
cocos2d-x/cocos/audio/android/AudioDecoder.cpp
Normal file
293
cocos2d-x/cocos/audio/android/AudioDecoder.cpp
Normal 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 {
|
64
cocos2d-x/cocos/audio/android/AudioDecoder.h
Normal file
64
cocos2d-x/cocos/audio/android/AudioDecoder.h
Normal 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 {
|
83
cocos2d-x/cocos/audio/android/AudioDecoderMp3.cpp
Normal file
83
cocos2d-x/cocos/audio/android/AudioDecoderMp3.cpp
Normal 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 {
|
43
cocos2d-x/cocos/audio/android/AudioDecoderMp3.h
Normal file
43
cocos2d-x/cocos/audio/android/AudioDecoderMp3.h
Normal 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 {
|
113
cocos2d-x/cocos/audio/android/AudioDecoderOgg.cpp
Normal file
113
cocos2d-x/cocos/audio/android/AudioDecoderOgg.cpp
Normal 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, ¤tSection);
|
||||
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 {
|
46
cocos2d-x/cocos/audio/android/AudioDecoderOgg.h
Normal file
46
cocos2d-x/cocos/audio/android/AudioDecoderOgg.h
Normal 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 {
|
94
cocos2d-x/cocos/audio/android/AudioDecoderProvider.cpp
Normal file
94
cocos2d-x/cocos/audio/android/AudioDecoderProvider.cpp
Normal 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 {
|
41
cocos2d-x/cocos/audio/android/AudioDecoderProvider.h
Normal file
41
cocos2d-x/cocos/audio/android/AudioDecoderProvider.h
Normal 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 {
|
649
cocos2d-x/cocos/audio/android/AudioDecoderSLES.cpp
Normal file
649
cocos2d-x/cocos/audio/android/AudioDecoderSLES.cpp
Normal 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 {
|
97
cocos2d-x/cocos/audio/android/AudioDecoderSLES.h
Normal file
97
cocos2d-x/cocos/audio/android/AudioDecoderSLES.h
Normal 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 {
|
115
cocos2d-x/cocos/audio/android/AudioDecoderWav.cpp
Normal file
115
cocos2d-x/cocos/audio/android/AudioDecoderWav.cpp
Normal 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 {
|
47
cocos2d-x/cocos/audio/android/AudioDecoderWav.h
Normal file
47
cocos2d-x/cocos/audio/android/AudioDecoderWav.h
Normal 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 {
|
466
cocos2d-x/cocos/audio/android/AudioEngine-inl.cpp
Normal file
466
cocos2d-x/cocos/audio/android/AudioEngine-inl.cpp
Normal 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
|
105
cocos2d-x/cocos/audio/android/AudioEngine-inl.h
Normal file
105
cocos2d-x/cocos/audio/android/AudioEngine-inl.h
Normal 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
|
2101
cocos2d-x/cocos/audio/android/AudioMixer.cpp
Normal file
2101
cocos2d-x/cocos/audio/android/AudioMixer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
389
cocos2d-x/cocos/audio/android/AudioMixer.h
Normal file
389
cocos2d-x/cocos/audio/android/AudioMixer.h
Normal 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 {
|
350
cocos2d-x/cocos/audio/android/AudioMixerController.cpp
Normal file
350
cocos2d-x/cocos/audio/android/AudioMixerController.cpp
Normal 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 {
|
88
cocos2d-x/cocos/audio/android/AudioMixerController.h
Normal file
88
cocos2d-x/cocos/audio/android/AudioMixerController.h
Normal 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 {
|
450
cocos2d-x/cocos/audio/android/AudioMixerOps.h
Normal file
450
cocos2d-x/cocos/audio/android/AudioMixerOps.h
Normal 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 {
|
||||
|
529
cocos2d-x/cocos/audio/android/AudioPlayerProvider.cpp
Normal file
529
cocos2d-x/cocos/audio/android/AudioPlayerProvider.cpp
Normal 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 {
|
129
cocos2d-x/cocos/audio/android/AudioPlayerProvider.h
Normal file
129
cocos2d-x/cocos/audio/android/AudioPlayerProvider.h
Normal 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 {
|
||||
|
788
cocos2d-x/cocos/audio/android/AudioResampler.cpp
Normal file
788
cocos2d-x/cocos/audio/android/AudioResampler.cpp
Normal 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 {
|
181
cocos2d-x/cocos/audio/android/AudioResampler.h
Normal file
181
cocos2d-x/cocos/audio/android/AudioResampler.h
Normal 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 {
|
191
cocos2d-x/cocos/audio/android/AudioResamplerCubic.cpp
Normal file
191
cocos2d-x/cocos/audio/android/AudioResamplerCubic.cpp
Normal 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 {
|
65
cocos2d-x/cocos/audio/android/AudioResamplerCubic.h
Normal file
65
cocos2d-x/cocos/audio/android/AudioResamplerCubic.h
Normal 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 {
|
181
cocos2d-x/cocos/audio/android/AudioResamplerPublic.h
Normal file
181
cocos2d-x/cocos/audio/android/AudioResamplerPublic.h
Normal 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
|
||||
|
||||
// ---------------------------------------------------------------------------
|
89
cocos2d-x/cocos/audio/android/IAudioPlayer.h
Normal file
89
cocos2d-x/cocos/audio/android/IAudioPlayer.h
Normal 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 {
|
42
cocos2d-x/cocos/audio/android/ICallerThreadUtils.h
Normal file
42
cocos2d-x/cocos/audio/android/ICallerThreadUtils.h
Normal 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 {
|
45
cocos2d-x/cocos/audio/android/IVolumeProvider.h
Normal file
45
cocos2d-x/cocos/audio/android/IVolumeProvider.h
Normal 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 {
|
100
cocos2d-x/cocos/audio/android/OpenSLHelper.h
Normal file
100
cocos2d-x/cocos/audio/android/OpenSLHelper.h
Normal 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]))
|
226
cocos2d-x/cocos/audio/android/PcmAudioPlayer.cpp
Normal file
226
cocos2d-x/cocos/audio/android/PcmAudioPlayer.cpp
Normal 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 {
|
98
cocos2d-x/cocos/audio/android/PcmAudioPlayer.h
Normal file
98
cocos2d-x/cocos/audio/android/PcmAudioPlayer.h
Normal 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 {
|
193
cocos2d-x/cocos/audio/android/PcmAudioService.cpp
Normal file
193
cocos2d-x/cocos/audio/android/PcmAudioService.cpp
Normal 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 {
|
81
cocos2d-x/cocos/audio/android/PcmAudioService.h
Normal file
81
cocos2d-x/cocos/audio/android/PcmAudioService.h
Normal 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 {
|
102
cocos2d-x/cocos/audio/android/PcmBufferProvider.cpp
Normal file
102
cocos2d-x/cocos/audio/android/PcmBufferProvider.cpp
Normal 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 {
|
52
cocos2d-x/cocos/audio/android/PcmBufferProvider.h
Normal file
52
cocos2d-x/cocos/audio/android/PcmBufferProvider.h
Normal 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 {
|
139
cocos2d-x/cocos/audio/android/PcmData.cpp
Normal file
139
cocos2d-x/cocos/audio/android/PcmData.cpp
Normal 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 {
|
66
cocos2d-x/cocos/audio/android/PcmData.h
Normal file
66
cocos2d-x/cocos/audio/android/PcmData.h
Normal 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 {
|
106
cocos2d-x/cocos/audio/android/Track.cpp
Normal file
106
cocos2d-x/cocos/audio/android/Track.cpp
Normal 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 {
|
107
cocos2d-x/cocos/audio/android/Track.h
Normal file
107
cocos2d-x/cocos/audio/android/Track.h
Normal 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 {
|
424
cocos2d-x/cocos/audio/android/UrlAudioPlayer.cpp
Normal file
424
cocos2d-x/cocos/audio/android/UrlAudioPlayer.cpp
Normal 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 {
|
135
cocos2d-x/cocos/audio/android/UrlAudioPlayer.h
Normal file
135
cocos2d-x/cocos/audio/android/UrlAudioPlayer.h
Normal 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 {
|
504
cocos2d-x/cocos/audio/android/audio.h
Normal file
504
cocos2d-x/cocos/audio/android/audio.h
Normal 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);
|
||||
}
|
||||
|
183
cocos2d-x/cocos/audio/android/audio_utils/format.c
Normal file
183
cocos2d-x/cocos/audio/android/audio_utils/format.c
Normal 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;
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
@ -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
|
70
cocos2d-x/cocos/audio/android/audio_utils/minifloat.cpp
Normal file
70
cocos2d-x/cocos/audio/android/audio_utils/minifloat.cpp
Normal 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" {
|
526
cocos2d-x/cocos/audio/android/audio_utils/primitives.c
Normal file
526
cocos2d-x/cocos/audio/android/audio_utils/primitives.c
Normal 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;
|
||||
}
|
35
cocos2d-x/cocos/audio/android/audio_utils/private/private.h
Normal file
35
cocos2d-x/cocos/audio/android/audio_utils/private/private.h
Normal 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*/
|
44
cocos2d-x/cocos/audio/android/cutils/bitops.h
Normal file
44
cocos2d-x/cocos/audio/android/cutils/bitops.h
Normal 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 */
|
568
cocos2d-x/cocos/audio/android/cutils/log.h
Normal file
568
cocos2d-x/cocos/audio/android/cutils/log.h
Normal 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 */
|
540
cocos2d-x/cocos/audio/android/mp3reader.cpp
Normal file
540
cocos2d-x/cocos/audio/android/mp3reader.cpp
Normal 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;
|
||||
}
|
61
cocos2d-x/cocos/audio/android/mp3reader.h
Normal file
61
cocos2d-x/cocos/audio/android/mp3reader.h
Normal 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_ */
|
518
cocos2d-x/cocos/audio/android/tinysndfile.cpp
Normal file
518
cocos2d-x/cocos/audio/android/tinysndfile.cpp
Normal 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;
|
||||
}
|
||||
*/
|
74
cocos2d-x/cocos/audio/android/tinysndfile.h
Normal file
74
cocos2d-x/cocos/audio/android/tinysndfile.h
Normal 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
|
88
cocos2d-x/cocos/audio/android/utils/Compat.h
Normal file
88
cocos2d-x/cocos/audio/android/utils/Compat.h
Normal 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 */
|
88
cocos2d-x/cocos/audio/android/utils/Errors.h
Normal file
88
cocos2d-x/cocos/audio/android/utils/Errors.h
Normal 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
|
39
cocos2d-x/cocos/audio/android/utils/Utils.cpp
Normal file
39
cocos2d-x/cocos/audio/android/utils/Utils.cpp
Normal 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
|
30
cocos2d-x/cocos/audio/android/utils/Utils.h
Normal file
30
cocos2d-x/cocos/audio/android/utils/Utils.h
Normal 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();
|
||||
}
|
113
cocos2d-x/cocos/audio/apple/AudioCache.h
Normal file
113
cocos2d-x/cocos/audio/apple/AudioCache.h
Normal 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
|
408
cocos2d-x/cocos/audio/apple/AudioCache.mm
Normal file
408
cocos2d-x/cocos/audio/apple/AudioCache.mm
Normal 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
|
120
cocos2d-x/cocos/audio/apple/AudioDecoder.h
Normal file
120
cocos2d-x/cocos/audio/apple/AudioDecoder.h
Normal 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 {
|
229
cocos2d-x/cocos/audio/apple/AudioDecoder.mm
Normal file
229
cocos2d-x/cocos/audio/apple/AudioDecoder.mm
Normal 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 {
|
94
cocos2d-x/cocos/audio/apple/AudioEngine-inl.h
Normal file
94
cocos2d-x/cocos/audio/apple/AudioEngine-inl.h
Normal 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
|
742
cocos2d-x/cocos/audio/apple/AudioEngine-inl.mm
Normal file
742
cocos2d-x/cocos/audio/apple/AudioEngine-inl.mm
Normal 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
|
65
cocos2d-x/cocos/audio/apple/AudioMacros.h
Normal file
65
cocos2d-x/cocos/audio/apple/AudioMacros.h
Normal 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; \
|
||||
}
|
94
cocos2d-x/cocos/audio/apple/AudioPlayer.h
Normal file
94
cocos2d-x/cocos/audio/apple/AudioPlayer.h
Normal 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
|
349
cocos2d-x/cocos/audio/apple/AudioPlayer.mm
Normal file
349
cocos2d-x/cocos/audio/apple/AudioPlayer.mm
Normal 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
Loading…
x
Reference in New Issue
Block a user