diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 000000000000..f3002d0ff4c2 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,37 @@ +version: 1.0.{build} +skip_tags: true +skip_branch_with_pr: true +image: + - Visual Studio 2015 +environment: + PYTHON: "C:\\Python27" + PYTHON_VERSION: "2.7.13" + PYTHON_ARCH: "32" + matrix: + # - build_type: windows32_cmake_test + # - build_type: windows32_sln_test +# - build_type: android_lua_tests +# - build_type: android_cocos_new_test +# - build_type: android_cpp_empty_test +# - build_type: android_gen_libs + + +platform: + - x86 + +configuration: + - Release + + +branches: + except: + - v1 + - v2 + - v4-develop + - v3-doc + - v3.11_backup + - v35-for-tizen + +clone_depth: 1 + +test: off diff --git a/CMakeLists.txt b/CMakeLists.txt index a62f7eeee873..3526062b02d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,9 +98,11 @@ include(BuildModules) BuildModules() # build cpp-empty-test +if(!OHOS) if(BUILD_CPP_EMPTY_TEST) add_subdirectory(tests/cpp-empty-test) endif(BUILD_CPP_EMPTY_TEST) +endif() # build cpp-tests if(BUILD_CPP_TESTS) @@ -113,13 +115,19 @@ if(BUILD_LUA_LIBS) # build lua tests if(BUILD_LUA_TESTS) + if(OHOS) + add_subdirectory(external/lua/luajit) + endif() add_subdirectory(tests/lua-tests/project) + if(!OHOS) add_subdirectory(tests/lua-empty-test/project) + endif() endif(BUILD_LUA_TESTS) endif(BUILD_LUA_LIBS) ## JS +if(!OHOS) if(BUILD_JS_LIBS) add_subdirectory(cocos/scripting/js-bindings) @@ -129,3 +137,4 @@ if(BUILD_JS_LIBS) endif(BUILD_JS_TESTS) endif(BUILD_JS_LIBS) +endif() \ No newline at end of file diff --git a/cmake/Modules/BuildModules.cmake b/cmake/Modules/BuildModules.cmake index 7033148b119c..40c203dcce6f 100644 --- a/cmake/Modules/BuildModules.cmake +++ b/cmake/Modules/BuildModules.cmake @@ -41,14 +41,14 @@ macro (BuildModules) # Chipmunk if(USE_CHIPMUNK) - if(USE_PREBUILT_LIBS) + if(USE_PREBUILT_LIBS OR OHOS) cocos_find_package(Chipmunk CHIPMUNK REQUIRED) endif() endif(USE_CHIPMUNK) # Box2d (not prebuilded, exists as source) if(USE_BOX2D) - if(USE_PREBUILT_LIBS OR USE_SOURCES_EXTERNAL) + if(USE_PREBUILT_LIBS OR USE_SOURCES_EXTERNAL OR OHOS) add_subdirectory(external/Box2D) set(Box2D_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/external/box2d/include) set(Box2D_LIBRARIES box2d) @@ -64,7 +64,7 @@ macro (BuildModules) # Bullet (not prebuilded, exists as source) if(USE_BULLET) - if(USE_PREBUILT_LIBS OR USE_SOURCES_EXTERNAL) + if(USE_PREBUILT_LIBS OR USE_SOURCES_EXTERNAL OR OHOS) add_subdirectory(external/bullet) set(BULLET_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/external/bullet) set(BULLET_LIBRARIES bullet) @@ -75,9 +75,20 @@ macro (BuildModules) message(STATUS "Bullet include dirs: ${BULLET_INCLUDE_DIRS}") endif(USE_BULLET) + if(OHOS) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/ohos-specific/pvmp3dec) + set(PVMP3DEC_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/external/ohos-specific/pvmp3dec/include) + set(PVMP3DEC_LIBRARIES pvmp3dec) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/ohos-specific/tremolo) + set(TREMOLO_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/external/ohos-specific/tremolo) + set(TREMOLO_LIBRARIES tremolo) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/openssl) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/websockets) + endif(OHOS) + # Recast (not prebuilded, exists as source) if(USE_RECAST) - if(USE_PREBUILT_LIBS OR USE_SOURCES_EXTERNAL) + if(USE_PREBUILT_LIBS OR USE_SOURCES_EXTERNAL OR OHOS) add_subdirectory(external/recast) set(RECAST_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/external/recast) set(RECAST_LIBRARIES recast) @@ -89,7 +100,7 @@ macro (BuildModules) endif(USE_RECAST) # Tinyxml2 (not prebuilded, exists as source) - if(USE_PREBUILT_LIBS OR USE_SOURCES_EXTERNAL) + if(USE_PREBUILT_LIBS OR USE_SOURCES_EXTERNAL OR OHOS) add_subdirectory(external/tinyxml2) set(TinyXML2_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/external/tinyxml2) set(TinyXML2_LIBRARIES tinyxml2) @@ -107,7 +118,7 @@ macro (BuildModules) # dists have packages from zlib, thats very old for us. # moreover our embedded version modified to quick provide # functionality needed by cocos. - if(USE_PREBUILT_LIBS OR NOT MINGW) + if(USE_PREBUILT_LIBS OR NOT MINGW OR OHOS) #TODO: hack! should be in external/unzip/CMakeLists.txt include_directories(${ZLIB_INCLUDE_DIRS}) add_subdirectory(external/unzip) @@ -131,7 +142,7 @@ macro (BuildModules) cocos_find_package(CURL CURL REQUIRED) # flatbuffers - if(USE_PREBUILT_LIBS OR USE_SOURCES_EXTERNAL) + if(USE_PREBUILT_LIBS OR USE_SOURCES_EXTERNAL OR OHOS) add_subdirectory(external/flatbuffers) set(FLATBUFFERS_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/external) message(STATUS "Flatbuffers include dirs: ${FLATBUFFERS_INCLUDE_DIRS}") @@ -140,7 +151,7 @@ macro (BuildModules) endif() # xxhash - if(USE_PREBUILT_LIBS OR USE_SOURCES_EXTERNAL) + if(USE_PREBUILT_LIBS OR USE_SOURCES_EXTERNAL OR OHOS) add_subdirectory(external/xxhash) set(XXHASH_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/external/xxhash) set(XXHASH_LIBRARIES xxhash) diff --git a/cmake/Modules/CocosBuildHelpers.cmake b/cmake/Modules/CocosBuildHelpers.cmake index 932d808b3d30..dc9c2c25b5da 100644 --- a/cmake/Modules/CocosBuildHelpers.cmake +++ b/cmake/Modules/CocosBuildHelpers.cmake @@ -114,6 +114,7 @@ endfunction() #IOS = iOS #MACOSX = MacOS X #LINUX = Linux +#OHOS = Ohos if (${CMAKE_SYSTEM_NAME} MATCHES "Windows") if(WINRT) @@ -139,6 +140,8 @@ elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") set(APPLE TRUE) set(SYSTEM_STRING "Mac OSX") endif() +elseif(OHOS) + set(SYSTEM_STRING "HarmonyOS Next") endif() if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") diff --git a/cmake/Modules/FindCURL.cmake b/cmake/Modules/FindCURL.cmake index e286d78712f4..90d7d7be5b63 100644 --- a/cmake/Modules/FindCURL.cmake +++ b/cmake/Modules/FindCURL.cmake @@ -58,6 +58,19 @@ if(NOT CURL_FOUND) # Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip): libcurl ) + if(OHOS) + # Active set path + if(${CURL_INCLUDE_DIR} STREQUAL "CURL_INCLUDE_DIR-NOTFOUND") + set(CURL_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/curl/include/ohos) + endif() + + if(${CURL_LIBRARY} STREQUAL "CURL_LIBRARY-NOTFOUND") + set(CURL_LIBRARY + ${CMAKE_CURRENT_SOURCE_DIR}/external/curl/prebuilt/ohos/libcurl.a + ) + endif() + endif() + mark_as_advanced(CURL_LIBRARY) if(CURL_INCLUDE_DIR) diff --git a/cmake/Modules/FindChipmunk.cmake b/cmake/Modules/FindChipmunk.cmake index 6fd591734dcd..08f562e0abcc 100644 --- a/cmake/Modules/FindChipmunk.cmake +++ b/cmake/Modules/FindChipmunk.cmake @@ -60,6 +60,19 @@ FIND_LIBRARY(CHIPMUNK_LIBRARY set(CHIPMUNK_INCLUDE_DIRS "${CHIPMUNK_INCLUDE_DIR}") +if(OHOS) + # Active set path + if(${CHIPMUNK_INCLUDE_DIR} STREQUAL "CHIPMUNK_INCLUDE_DIR-NOTFOUND") + set(CHIPMUNK_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/chipmunk/include) + endif() + + if(${CHIPMUNK_LIBRARY} STREQUAL "CHIPMUNK_LIBRARY-NOTFOUND") + set(CHIPMUNK_LIBRARY + ${CMAKE_CURRENT_SOURCE_DIR}/external/chipmunk/prebuilt/ohos/libchipmunk.a + ) + endif() +endif() + IF(CHIPMUNK_LIBRARY) # include the math library for Unix IF(UNIX AND NOT APPLE) diff --git a/cmake/Modules/FindFreetype.cmake b/cmake/Modules/FindFreetype.cmake index 7935a937901e..69eae4b89495 100644 --- a/cmake/Modules/FindFreetype.cmake +++ b/cmake/Modules/FindFreetype.cmake @@ -120,6 +120,23 @@ find_library(FREETYPE_LIBRARY [HKEY_LOCAL_MACHINE\\SOFTWARE\\gtkmm\\2.4;Path] ) +if(OHOS) + # Active set path + if(${FREETYPE_INCLUDE_DIR_ft2build} STREQUAL "FREETYPE_INCLUDE_DIR_ft2build-NOTFOUND") + set(FREETYPE_INCLUDE_DIR_ft2build ${CMAKE_CURRENT_SOURCE_DIR}/external/freetype2/include/ohos) + endif() + + if(${FREETYPE_INCLUDE_DIR_freetype2} STREQUAL "FREETYPE_INCLUDE_DIR_freetype2-NOTFOUND") + set(FREETYPE_INCLUDE_DIR_freetype2 ${CMAKE_CURRENT_SOURCE_DIR}/external/freetype2/include/ohos/freetype2) + endif() + + if(${FREETYPE_LIBRARY} STREQUAL "FREETYPE_LIBRARY-NOTFOUND") + set(FREETYPE_LIBRARY + ${CMAKE_CURRENT_SOURCE_DIR}/external/freetype2/prebuilt/ohos/libfreetype.a + ) + endif() +endif() + # set the user variables if(FREETYPE_INCLUDE_DIR_ft2build AND FREETYPE_INCLUDE_DIR_freetype2) set(FREETYPE_INCLUDE_DIRS "${FREETYPE_INCLUDE_DIR_ft2build};${FREETYPE_INCLUDE_DIR_freetype2}") diff --git a/cmake/Modules/FindJPEG.cmake b/cmake/Modules/FindJPEG.cmake index 54acdd9ae759..dc330c30d286 100644 --- a/cmake/Modules/FindJPEG.cmake +++ b/cmake/Modules/FindJPEG.cmake @@ -36,6 +36,18 @@ find_path(JPEG_INCLUDE_DIR jpeglib.h) set(JPEG_NAMES ${JPEG_NAMES} jpeg) find_library(JPEG_LIBRARY NAMES ${JPEG_NAMES} ) +if(OHOS) + # Active set path + if(${JPEG_INCLUDE_DIR} STREQUAL "JPEG_INCLUDE_DIR-NOTFOUND") + set(JPEG_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/jpeg/include/ohos) + endif() + if(${JPEG_LIBRARY} STREQUAL "JPEG_LIBRARY-NOTFOUND") + set(JPEG_LIBRARY + ${CMAKE_CURRENT_SOURCE_DIR}/external/jpeg/prebuilt/ohos/libjpeg.a + ) + endif() +endif() + # handle the QUIETLY and REQUIRED arguments and set JPEG_FOUND to TRUE if # all listed variables are TRUE include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) diff --git a/cmake/Modules/FindPNG.cmake b/cmake/Modules/FindPNG.cmake index a83d9e9ee373..5c8c8e8c6cd7 100644 --- a/cmake/Modules/FindPNG.cmake +++ b/cmake/Modules/FindPNG.cmake @@ -126,6 +126,19 @@ if(ZLIB_FOUND) # find_package_handle_standard_args() below. unset(PNG_FOUND) + if(OHOS) + # Active set path + if(${PNG_PNG_INCLUDE_DIR} STREQUAL "PNG_PNG_INCLUDE_DIR-NOTFOUND") + set(PNG_PNG_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/png/include/ohos) + endif() + + if(${PNG_LIBRARY} STREQUAL "PNG_LIBRARY-NOTFOUND") + set(PNG_LIBRARY + ${CMAKE_CURRENT_SOURCE_DIR}/external/png/prebuilt/ohos/libpng.a + ) + endif() + endif() + if (PNG_LIBRARY AND PNG_PNG_INCLUDE_DIR) # png.h includes zlib.h. Sigh. set(PNG_INCLUDE_DIRS ${PNG_PNG_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} ) diff --git a/cmake/Modules/FindTIFF.cmake b/cmake/Modules/FindTIFF.cmake index e5475a2701c9..d07351717be9 100644 --- a/cmake/Modules/FindTIFF.cmake +++ b/cmake/Modules/FindTIFF.cmake @@ -62,6 +62,19 @@ find_library(TIFF_LIBRARY /opt ) +if(OHOS) + # Active set path + if(${TIFF_INCLUDE_DIR} STREQUAL "TIFF_INCLUDE_DIR-NOTFOUND") + set(TIFF_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/tiff/include/ohos) + endif() + + if(${TIFF_LIBRARY} STREQUAL "TIFF_LIBRARY-NOTFOUND") + set(TIFF_LIBRARY + ${CMAKE_CURRENT_SOURCE_DIR}/external/tiff/prebuilt/ohos/libtiff.a + ) + endif() +endif() + if(TIFF_INCLUDE_DIR AND EXISTS "${TIFF_INCLUDE_DIR}/tiffvers.h") file(STRINGS "${TIFF_INCLUDE_DIR}/tiffvers.h" tiff_version_str REGEX "^#define[\t ]+TIFFLIB_VERSION_STR[\t ]+\"LIBTIFF, Version .*") diff --git a/cmake/Modules/FindWEBSOCKETS.cmake b/cmake/Modules/FindWEBSOCKETS.cmake index 475d16775331..283d2632a5ca 100644 --- a/cmake/Modules/FindWEBSOCKETS.cmake +++ b/cmake/Modules/FindWEBSOCKETS.cmake @@ -39,6 +39,19 @@ find_library(WEBSOCKETS_LIBRARY NAMES websockets libwebsockets /opt ) +if(OHOS) + # Active set path + if(${WEBSOCKETS_INCLUDE_DIR} STREQUAL "WEBSOCKETS_INCLUDE_DIR-NOTFOUND") + set(WEBSOCKETS_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/websockets/include/ohos) + endif() + + if(${WEBSOCKETS_LIBRARY} STREQUAL "WEBSOCKETS_LIBRARY-NOTFOUND") + set(WEBSOCKETS_LIBRARY + ${CMAKE_CURRENT_SOURCE_DIR}/external/websockets/prebuilt/ohos/libwebsockets.a + ) + endif() +endif() + set(WEBSOCKETS_INCLUDE_DIRS ${WEBSOCKETS_INCLUDE_DIR}) set(WEBSOCKETS_LIBRARIES ${WEBSOCKETS_LIBRARY}) diff --git a/cmake/Modules/FindWebP.cmake b/cmake/Modules/FindWebP.cmake index f7b140b979f7..1408cc3219d5 100644 --- a/cmake/Modules/FindWebP.cmake +++ b/cmake/Modules/FindWebP.cmake @@ -58,6 +58,19 @@ FIND_LIBRARY(WEBP_LIBRARY /opt ) +if(OHOS) + # Active set path + if(${WEBP_INCLUDE_DIR} STREQUAL "WEBP_INCLUDE_DIR-NOTFOUND") + set(WEBP_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/webp/include/ohos) + endif() + + if(${WEBP_LIBRARY} STREQUAL "WEBP_LIBRARY-NOTFOUND") + set(WEBP_LIBRARY + ${CMAKE_CURRENT_SOURCE_DIR}/external/webp/prebuilt/ohos/libwebp.a + ) + endif() +endif() + set(WEBP_INCLUDE_DIRS "${WEBP_INCLUDE_DIR}") set(WEBP_LIBRARIES "${WEBP_LIBRARY}") diff --git a/cmake/Modules/SetCompilerOptions.cmake b/cmake/Modules/SetCompilerOptions.cmake index 54c59d1c82eb..03be2b49a108 100644 --- a/cmake/Modules/SetCompilerOptions.cmake +++ b/cmake/Modules/SetCompilerOptions.cmake @@ -82,6 +82,23 @@ macro (SetCompilerOptions) set(PLATFORM_FOLDER android) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fexceptions") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsigned-char -latomic") + elseif(OHOS) + add_compile_options(-DUSE_FILE32API + -Wno-absolute-value + -Wno-extra + -Wno-implicit-int-float-conversion + -Wno-overloaded-virtual + -Wno-unused-function + -Wno-unused-private-field + -Wno-unused-parameter + -Wno-reorder-ctor + -Wno-unsequenced + -Wno-extra + -Wno-c++11-narrowing + -Wno-expansion-to-defined + -Wno-unused-command-line-argument + ) + set(PLATFORM_FOLDER ohos) else() message( FATAL_ERROR "Unsupported platform, CMake will exit" ) return() @@ -97,7 +114,7 @@ macro (SetCompilerOptions) endif() endif() - if (MINGW AND NOT USE_PREBUILT_LIBS) + if (MINGW AND NOT USE_PREBUILT_LIBS AND NOT OHOS) add_definitions(-DMINIZIP_FROM_SYSTEM) endif() diff --git a/cocos/CMakeLists.txt b/cocos/CMakeLists.txt index a5703f64d60c..44aa748a255e 100644 --- a/cocos/CMakeLists.txt +++ b/cocos/CMakeLists.txt @@ -89,6 +89,10 @@ set(COCOS_SRC cocos2d.cpp include_directories(../external/clipper) add_library(cocos2dInternal ${COCOS_SRC}) +if (OHOS) + + target_link_libraries(cocos2dInternal ${Drawing-lib} ${libace-lib} ${GLES-lib} ${libnapi-lib} ${libuv-lib} ${rawfile-lib} ${EGL-lib} ${hilog-lib} libohaudio.so libavplayer.so libnative_window.so libnative_buffer.so) +endif() set_target_properties(cocos2dInternal PROPERTIES @@ -142,6 +146,30 @@ elseif(ANDROID) get_filename_component(__module_path "${CMAKE_TOOLCHAIN_FILE}" DIRECTORY) include(${__module_path}/AndroidNdkModules.cmake) android_ndk_import_module_cpufeatures() + +elseif(OHOS) + FIND_LIBRARY(Drawing-lib native_drawing) + FIND_LIBRARY(libace-lib ace_ndk.z) + FIND_LIBRARY(libnapi-lib ace_napi.z) + FIND_LIBRARY(GLES-lib GLESv3) + FIND_LIBRARY(libuv-lib uv) + FIND_LIBRARY(rawfile-lib rawfile.z) + FIND_LIBRARY(EGL-lib EGL) + FIND_LIBRARY(OpenSl-lib OpenSLES) + find_library( hilog-lib hilog_ndk.z ) + + set(PLATFORM_SPECIFIC_LIBS + ${Drawing-lib} + ${libace-lib} + ${libnapi-lib} + ${GLES-lib} + ${libuv-lib} + ${rawfile-lib} + ${EGL-lib} + ${OpenSl-lib} + ${hilog-lib} + ) + else() message( FATAL_ERROR "Unsupported platform, CMake will exit" ) endif() @@ -179,6 +207,16 @@ if(USE_RECAST) cocos_use_pkg(cocos2dInternal RECAST) endif() +if(OHOS) + cocos_use_pkg(cocos2dInternal PVMP3DEC) + cocos_use_pkg(cocos2dInternal TREMOLO) + target_link_libraries(cocos2dInternal + ext_ssl + ext_crypto + ext_websockets + ) +endif() + ADD_LIBRARY(cocos2d cc_dummy.c) if(ANDROID) diff --git a/cocos/audio/AudioEngine.cpp b/cocos/audio/AudioEngine.cpp index 0f822147befe..1b47244a01fa 100644 --- a/cocos/audio/AudioEngine.cpp +++ b/cocos/audio/AudioEngine.cpp @@ -42,6 +42,8 @@ #include "audio/linux/AudioEngine-linux.h" #elif CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN #include "audio/tizen/AudioEngine-tizen.h" +#elif CC_TARGET_PLATFORM == CC_PLATFORM_OHOS +#include "ohos/AudioEngine-inl.h" #endif #define TIME_DELAY_PRECISION 0.0001 @@ -144,6 +146,9 @@ class AudioEngine::AudioEngineThreadPool void AudioEngine::end() { +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + stopAll(); +#endif if (s_threadPool) { delete s_threadPool; @@ -215,11 +220,22 @@ int AudioEngine::play2d(const std::string& filePath, bool loop, float volume, co break; } if (profileHelper->profile.minDelay > TIME_DELAY_PRECISION) { +#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS + auto currTime = std::chrono::high_resolution_clock::now(); + auto delay = static_cast(std::chrono::duration_cast( + currTime - profileHelper->lastPlayTime).count()) / 1000000.0; + if (profileHelper->lastPlayTime.time_since_epoch().count() != 0 && + delay <= profileHelper->profile.minDelay) { + log("Fail to play %s cause by limited minimum delay", filePath.c_str()); + break; + } +#else 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; } +#endif } } @@ -242,7 +258,11 @@ int AudioEngine::play2d(const std::string& filePath, bool loop, float volume, co audioRef.filePath = &it->first; if (profileHelper) { +#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS + profileHelper->lastPlayTime = std::chrono::high_resolution_clock::now(); +#else profileHelper->lastPlayTime = utils::gettime(); +#endif profileHelper->audioIDs.push_back(ret); } audioRef.profileHelper = profileHelper; @@ -325,6 +345,11 @@ void AudioEngine::resumeAll() void AudioEngine::stop(int audioID) { +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + if(!_audioEngineImpl){ + return; + } +#endif auto it = _audioIDInfoMap.find(audioID); if (it != _audioIDInfoMap.end()){ _audioEngineImpl->stop(audioID); @@ -365,6 +390,21 @@ void AudioEngine::stopAll() void AudioEngine::uncache(const std::string &filePath) { if(_audioPathIDMap.find(filePath) != _audioPathIDMap.end()){ +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + auto lst = _audioPathIDMap[filePath]; + for (auto it = lst.begin() ; it != lst.end(); ++it) { + auto audioID = *it; + _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); + } + } +#else auto itEnd = _audioPathIDMap[filePath].end(); for (auto it = _audioPathIDMap[filePath].begin() ; it != itEnd; ++it) { auto audioID = *it; @@ -378,6 +418,7 @@ void AudioEngine::uncache(const std::string &filePath) _audioIDInfoMap.erase(audioID); } } +#endif _audioPathIDMap.erase(filePath); } diff --git a/cocos/audio/CMakeLists.txt b/cocos/audio/CMakeLists.txt index 188bc2536113..fbe4be0b8656 100644 --- a/cocos/audio/CMakeLists.txt +++ b/cocos/audio/CMakeLists.txt @@ -20,6 +20,81 @@ ELSEIF(ANDROID) audio/android/cddSimpleAudioEngine.cpp audio/android/jni/cddandroidAndroidJavaEngine.cpp ) +elseif(OHOS) + set(COCOS_AUDIO_PLATFORM_HEADER + audio/ohos/PcmAudioService.h + audio/ohos/AudioBufferProvider.h + audio/ohos/IAudioPlayer.h + audio/ohos/AudioResampler.h + audio/ohos/AudioDecoder.h + audio/ohos/AudioResamplerPublic.h + audio/ohos/AudioMixer.h + audio/ohos/tinysndfile.h + audio/ohos/mp3reader.h + audio/ohos/AudioMixerOps.h + audio/ohos/cutils/bitops.h + audio/ohos/cutils/log.h + audio/ohos/audio.h + audio/ohos/AudioPlayerProvider.h + audio/ohos/utils/Utils.h + audio/ohos/utils/Errors.h + audio/ohos/utils/Compat.h + audio/ohos/AudioDecoderOgg.h + audio/ohos/Track.h + audio/ohos/OpenSLHelper.h + audio/ohos/PcmAudioPlayer.h + audio/ohos/AssetFd.h + audio/ohos/PcmBufferProvider.h + audio/ohos/CCThreadPool.h + audio/ohos/audio_utils/include/audio_utils/minifloat.h + audio/ohos/audio_utils/include/audio_utils/primitives.h + audio/ohos/audio_utils/AudioDef.h + audio/ohos/audio_utils/RefCounted.h + audio/ohos/ICallerThreadUtils.h + audio/ohos/AudioDecoderWav.h + audio/ohos/AudioDecoderProvider.h + audio/ohos/UrlAudioPlayer.h + audio/ohos/AudioDecoderSLES.h + audio/ohos/AudioDecoderMp3.h + audio/ohos/PcmData.h + audio/ohos/AudioMixerController.h + audio/ohos/AudioResamplerCubic.h + audio/ohos/AudioEngine-inl.h + audio/ohos/IVolumeProvider.h + audio/ohos/Macros.h + audio/ohos/Value.h + ) + + set(COCOS_AUDIO_PLATFORM_SRC + audio/ohos/SimpleAudioEngine.cpp + audio/ohos/AudioEngine-inl.cpp + audio/ohos/CCThreadPool.cpp + audio/ohos/AssetFd.cpp + audio/ohos/AudioDecoder.cpp + audio/ohos/AudioDecoderProvider.cpp + audio/ohos/AudioDecoderSLES.cpp + audio/ohos/AudioDecoderOgg.cpp + audio/ohos/AudioDecoderMp3.cpp + audio/ohos/AudioDecoderWav.cpp + audio/ohos/AudioPlayerProvider.cpp + audio/ohos/AudioResampler.cpp + audio/ohos/AudioResamplerCubic.cpp + audio/ohos/PcmBufferProvider.cpp + audio/ohos/PcmAudioPlayer.cpp + audio/ohos/PcmData.cpp + audio/ohos/PcmAudioService.cpp + audio/ohos/UrlAudioPlayer.cpp + audio/ohos/AudioMixerController.cpp + audio/ohos/AudioMixer.cpp + audio/ohos/mp3reader.cpp + audio/ohos/tinysndfile.cpp + audio/ohos/Track.cpp + audio/ohos/audio_utils/RefCounted.cpp + audio/ohos/audio_utils/Value.cpp + audio/ohos/audio_utils/minifloat.cpp + audio/ohos/audio_utils/primitives.cpp + audio/ohos/utils/Utils.cpp + ) elseif(LINUX) set(COCOS_AUDIO_PLATFORM_SRC @@ -51,5 +126,12 @@ elseif(MACOSX) PROPERTIES LANGUAGE C ) endif() - +if(OHOS) + set(COCOS_AUDIO_HEADER + audio/include/AudioEngine.h + audio/include/Export.h + audio/include/SimpleAudioEngine.h + ${COCOS_AUDIO_PLATFORM_HEADER} + ) +endif() list(APPEND COCOS_AUDIO_SRC ${COCOS_AUDIO_PLATFORM_SRC}) diff --git a/cocos/audio/include/AudioEngine.h b/cocos/audio/include/AudioEngine.h index 942c6325f003..6dde93cca81e 100644 --- a/cocos/audio/include/AudioEngine.h +++ b/cocos/audio/include/AudioEngine.h @@ -176,6 +176,9 @@ class EXPORT_DLL AudioEngine /** Pause all playing audio instances. */ static void pauseAll(); + static void onEnterBackground(); + static void onEnterForeground(); + /** * Resume an audio instance. * @@ -304,6 +307,11 @@ class EXPORT_DLL AudioEngine std::list audioIDs; + #if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS + std::chrono::high_resolution_clock::time_point lastPlayTime; + ProfileHelper() = default; + #else + double lastPlayTime; ProfileHelper() @@ -311,6 +319,7 @@ class EXPORT_DLL AudioEngine { } + #endif }; struct AudioInfo diff --git a/cocos/audio/ohos/AssetFd.cpp b/cocos/audio/ohos/AssetFd.cpp new file mode 100644 index 000000000000..7e3c8822c3dc --- /dev/null +++ b/cocos/audio/ohos/AssetFd.cpp @@ -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. +****************************************************************************/ + +#define LOG_TAG "AssetFd" + +#include "cutils/log.h" +#include "AssetFd.h" + +namespace cocos2d { namespace experimental { + +AssetFd::AssetFd(int assetFd) + : _assetFd(assetFd) { +} + +AssetFd::~AssetFd() { + ALOGV("~AssetFd: %d", _assetFd); + if (_assetFd > 0) { + ::close(_assetFd); + _assetFd = 0; + } +}; + +}} // namespace CocosDenshion diff --git a/cocos/audio/ohos/AssetFd.h b/cocos/audio/ohos/AssetFd.h new file mode 100644 index 000000000000..87afa5d0ccf3 --- /dev/null +++ b/cocos/audio/ohos/AssetFd.h @@ -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 + +namespace cocos2d { namespace experimental { + +class AssetFd { +public: + AssetFd(int assetFd); + ~AssetFd(); + + inline int getFd() const { return _assetFd; }; + +private: + int _assetFd; +}; + +}} // namespace CocosDenshion diff --git a/cocos/audio/ohos/AudioBufferProvider.h b/cocos/audio/ohos/AudioBufferProvider.h new file mode 100644 index 000000000000..a8ef8c1f24e5 --- /dev/null +++ b/cocos/audio/ohos/AudioBufferProvider.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include "utils/Errors.h" + +namespace cocos2d { namespace experimental { +// ---------------------------------------------------------------------------- + +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; // 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 CocosDenshion diff --git a/cocos/audio/ohos/AudioDecoder.cpp b/cocos/audio/ohos/AudioDecoder.cpp new file mode 100644 index 000000000000..a1f7017d818f --- /dev/null +++ b/cocos/audio/ohos/AudioDecoder.cpp @@ -0,0 +1,261 @@ +/**************************************************************************** +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 "AudioDecoder.h" +#include "AudioResampler.h" +#include "PcmBufferProvider.h" + +#include +#include + +namespace cocos2d { namespace experimental { + +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 = static_cast(offset); + else if (whence == SEEK_CUR) + thiz->_fileCurrPos = static_cast(thiz->_fileCurrPos + offset); + else if (whence == SEEK_END) + thiz->_fileCurrPos = static_cast(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>(); + 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); + auto outputFrames = static_cast(((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 Ovalues; + + if (Ovalues.empty()) { + Ovalues.push_back(static_cast(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 = static_cast(resampler->resample(static_cast(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 = static_cast(outputFrames); + _result.sampleRate = outFrameRate; + + auto buffer = std::make_shared>(); + 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>(); + 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 { namespace experimental +} \ No newline at end of file diff --git a/cocos/audio/ohos/AudioDecoder.h b/cocos/audio/ohos/AudioDecoder.h new file mode 100644 index 000000000000..ddb4cd55becf --- /dev/null +++ b/cocos/audio/ohos/AudioDecoder.h @@ -0,0 +1,62 @@ +/**************************************************************************** +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 "OpenSLHelper.h" +#include "PcmData.h" +#include "base/CCData.h" + +namespace cocos2d { +namespace experimental { + +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); // NOLINT + + std::string _url; + PcmData _result; + int _sampleRate; + Data _fileData; + size_t _fileCurrPos; +}; +} // namespace experimental +} // namespace cocos2d { namespace experimental diff --git a/cocos/audio/ohos/AudioDecoderMp3.cpp b/cocos/audio/ohos/AudioDecoderMp3.cpp new file mode 100644 index 000000000000..416c0c35f9cc --- /dev/null +++ b/cocos/audio/ohos/AudioDecoderMp3.cpp @@ -0,0 +1,76 @@ +/**************************************************************************** +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 "AudioDecoderMp3.h" +#include "mp3reader.h" +#include "platform/CCFileUtils.h" + +namespace cocos2d { namespace experimental { + +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 { namespace experimental +} diff --git a/cocos/audio/ohos/AudioDecoderMp3.h b/cocos/audio/ohos/AudioDecoderMp3.h new file mode 100644 index 000000000000..13d2cf54e2f7 --- /dev/null +++ b/cocos/audio/ohos/AudioDecoderMp3.h @@ -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 "AudioDecoder.h" + +namespace cocos2d { namespace experimental { + +class AudioDecoderMp3 : public AudioDecoder { +protected: + AudioDecoderMp3(); + virtual ~AudioDecoderMp3(); + + virtual bool decodeToPcm() override; + + friend class AudioDecoderProvider; +}; + +}} // namespace cocos2d { namespace experimental \ No newline at end of file diff --git a/cocos/audio/ohos/AudioDecoderOgg.cpp b/cocos/audio/ohos/AudioDecoderOgg.cpp new file mode 100644 index 000000000000..b6d44ef2e6bf --- /dev/null +++ b/cocos/audio/ohos/AudioDecoderOgg.cpp @@ -0,0 +1,101 @@ +/**************************************************************************** +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 "AudioDecoderOgg.h" +#include "platform/CCFileUtils.h" + +namespace cocos2d { namespace experimental { + +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 { namespace experimental diff --git a/cocos/audio/ohos/AudioDecoderOgg.h b/cocos/audio/ohos/AudioDecoderOgg.h new file mode 100644 index 000000000000..75b8e5afcf64 --- /dev/null +++ b/cocos/audio/ohos/AudioDecoderOgg.h @@ -0,0 +1,44 @@ +/**************************************************************************** +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 "AudioDecoder.h" + +#include "Tremolo/ivorbisfile.h" + +namespace cocos2d { namespace experimental { + +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 { namespace experimental \ No newline at end of file diff --git a/cocos/audio/ohos/AudioDecoderProvider.cpp b/cocos/audio/ohos/AudioDecoderProvider.cpp new file mode 100644 index 000000000000..cbad30ea8762 --- /dev/null +++ b/cocos/audio/ohos/AudioDecoderProvider.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** + 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 "AudioDecoderProvider.h" +#include "AudioDecoderMp3.h" +#include "AudioDecoderOgg.h" +#include "AudioDecoderSLES.h" +#include "AudioDecoderWav.h" +#include "platform/CCFileUtils.h" + +namespace cocos2d { namespace experimental { + +cocos2d::experimental::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); + ALOGE("url:%s, extension:%s, sampleRate:%d", url.c_str(), extension.c_str(), sampleRate); + 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 { namespace experimental diff --git a/cocos/audio/ohos/AudioDecoderProvider.h b/cocos/audio/ohos/AudioDecoderProvider.h new file mode 100644 index 000000000000..1a650ce2fad8 --- /dev/null +++ b/cocos/audio/ohos/AudioDecoderProvider.h @@ -0,0 +1,40 @@ +/**************************************************************************** +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 "OpenSLHelper.h" + +namespace cocos2d { namespace experimental { + +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 { namespace experimental diff --git a/cocos/audio/ohos/AudioDecoderSLES.cpp b/cocos/audio/ohos/AudioDecoderSLES.cpp new file mode 100644 index 000000000000..65af9af3b62a --- /dev/null +++ b/cocos/audio/ohos/AudioDecoderSLES.cpp @@ -0,0 +1,292 @@ +/**************************************************************************** +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 "AudioDecoderSLES.h" +#include "platform/CCFileUtils.h" + +#include +#include + +namespace cocos2d { namespace experimental { + +/* 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; //NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + +static int toBufferSizeInBytes(int bufferSizeInFrames, int sampleSize, int channelCount) { + return bufferSizeInFrames * sampleSize * channelCount; +} + +static int BUFFER_SIZE_IN_BYTES = 0; // NOLINT(readability-identifier-naming) + +static void checkMetaData(int index, const char *key) { + if (index != -1) { + ALOGV("Key %s is at index %d", key, index); + } else { + ALOGV("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) { + auto *thiz = reinterpret_cast(context); + thiz->prefetchCallback(caller, event); + } + + static void decPlayCallback(CCSLBufferQueueItf queueItf, void *context) { + auto *thiz = reinterpret_cast(context); + thiz->decodeToPcmCallback(queueItf); + } + + static void decProgressCallback(SLPlayItf caller, void *context, SLuint32 event) { + auto *thiz = reinterpret_cast(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 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 = static_cast(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() { + // 当前oh不支持,直接返回true + return true; +} + +//----------------------------------------------------------------- +void AudioDecoderSLES::signalEos() { + std::unique_lock 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 = *reinterpret_cast(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 = *reinterpret_cast(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 = *reinterpret_cast(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 = *reinterpret_cast(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 = *reinterpret_cast(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 = *reinterpret_cast(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) { + CC_UNUSED_PARAM(caller); + 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(CCSLBufferQueueItf 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 { namespace experimental diff --git a/cocos/audio/ohos/AudioDecoderSLES.h b/cocos/audio/ohos/AudioDecoderSLES.h new file mode 100644 index 000000000000..5fb5ae868c90 --- /dev/null +++ b/cocos/audio/ohos/AudioDecoderSLES.h @@ -0,0 +1,96 @@ +/**************************************************************************** +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 +#include +#include "AudioDecoder.h" +#include "utils/Compat.h" + +namespace cocos2d { namespace experimental { + +class AudioDecoderSLES : public AudioDecoder { +protected: + AudioDecoderSLES(); + ~AudioDecoderSLES() override; + + bool init(SLEngineItf engineItf, const std::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback); + bool decodeToPcm() override; + +private: + void queryAudioInfo(); + + void signalEos(); + void decodeToPcmCallback(CCSLBufferQueueItf 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_ { //NOLINT(modernize-use-using, readability-identifier-naming) + 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 { namespace experimental \ No newline at end of file diff --git a/cocos/audio/ohos/AudioDecoderWav.cpp b/cocos/audio/ohos/AudioDecoderWav.cpp new file mode 100644 index 000000000000..ce1d74c0ff86 --- /dev/null +++ b/cocos/audio/ohos/AudioDecoderWav.cpp @@ -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 "AudioDecoderWav" + +#include "AudioDecoderWav.h" +#include "tinysndfile.h" +#include "platform/CCFileUtils.h" + +namespace cocos2d { namespace experimental { +using namespace sf; //NOLINT +AudioDecoderWav::AudioDecoderWav() { + ALOGV("Create AudioDecoderWav"); +} + +AudioDecoderWav::~AudioDecoderWav() = default; + +void *AudioDecoderWav::onWavOpen(const char * /*path*/, void *user) { + return user; +} + +int AudioDecoderWav::onWavSeek(void *datasource, long offset, int whence) { //NOLINT(google-runtime-int) + return AudioDecoder::fileSeek(datasource, static_cast(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 = nullptr; + 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(int16_t) * info.frames * info.channels; + auto *buf = static_cast(malloc(bufSize)); + sf_count_t readFrames = sf_readf_short(handle, reinterpret_cast(buf), info.frames); + CC_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 = static_cast(1.0F * info.frames / _result.sampleRate); //NOLINT + + free(buf); + ret = true; + } while (false); + + if (handle != nullptr) { + sf_close(handle); + } + + return ret; +} + +} }// namespace cocos2d { namespace experimental diff --git a/cocos/audio/ohos/AudioDecoderWav.h b/cocos/audio/ohos/AudioDecoderWav.h new file mode 100644 index 000000000000..2ce80d1d4936 --- /dev/null +++ b/cocos/audio/ohos/AudioDecoderWav.h @@ -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 "AudioDecoder.h" + +namespace cocos2d { namespace experimental { + +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 { namespace experimental \ No newline at end of file diff --git a/cocos/audio/ohos/AudioEngine-inl.cpp b/cocos/audio/ohos/AudioEngine-inl.cpp new file mode 100644 index 000000000000..5bdd16faf7b1 --- /dev/null +++ b/cocos/audio/ohos/AudioEngine-inl.cpp @@ -0,0 +1,530 @@ +/**************************************************************************** + 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 +#define LOG_TAG "AudioEngineImpl" + +#include "AudioEngine-inl.h" +#include + +#include +#include +#include + +#include "audio/include/AudioEngine.h" +#include "platform/ohos/CCFileUtils-ohos.h" +#include "AudioDecoder.h" +#include "AudioDecoderProvider.h" +#include "AudioPlayerProvider.h" +#include "IAudioPlayer.h" +#include "ICallerThreadUtils.h" +#include "UrlAudioPlayer.h" +#include "cutils/log.h" +#include "base/CCDirector.h" +#include "base/CCScheduler.h" +#include "base/CCEventDispatcher.h" +#include "base/CCEventType.h" +#include "base/CCEventListenerCustom.h" + +using namespace cocos2d; +using namespace cocos2d::experimental; //NOLINT + +namespace { +AudioEngineImpl *gAudioImpl = nullptr; +int outputSampleRate = 48000; + +// TODO(hack) : There is currently a bug in the opensles module, +// so openharmony must configure a fixed size, otherwise the callback will be suspended +int bufferSizeInFrames = 2048; + + +void getAudioInfo() { + +} +} // namespace + +class CallerThreadUtils : public ICallerThreadUtils { +public: + void performFunctionInCallerThread(const std::function &func) override { + cocos2d::Director::sharedDirector()->getScheduler()->performFunctionInCocosThread(func); + + }; + + std::thread::id getCallerThreadId() override { + return _tid; + }; + + void setCallerThreadId(std::thread::id tid) { + _tid = tid; + }; + +private: + std::thread::id _tid; +}; + +static CallerThreadUtils gCallerThreadUtils; + +static int fdGetter(const std::string &url, off_t *start, off_t *length) { + int fd = -1; + + RawFileDescriptor descriptor; + FileUtilsOhos *utils = dynamic_cast(FileUtils::getInstance()); + utils->getRawFileDescriptor(url, descriptor); + fd = descriptor.fd; + + if (fd <= 0) { + ALOGE("Failed to open file descriptor for '%{public}s'", url.c_str()); + } + + return fd; +}; + +//==================================================== +AudioEngineImpl::AudioEngineImpl() +: _engineObject(nullptr), + _engineEngine(nullptr), + _outputMixObject(nullptr), + _audioPlayerProvider(nullptr), + _onPauseListener(nullptr), + _onResumeListener(nullptr), + _audioIDIndex(0), + _lazyInitLoop(true) { + gCallerThreadUtils.setCallerThreadId(std::this_thread::get_id()); + gAudioImpl = this; + getAudioInfo(); +} + +AudioEngineImpl::~AudioEngineImpl() { + if (_audioPlayerProvider != nullptr) { + delete _audioPlayerProvider; + _audioPlayerProvider = nullptr; + } + + if (_outputMixObject) { + (*_outputMixObject)->Destroy(_outputMixObject); + } + if (_engineObject) { + (*_engineObject)->Destroy(_engineObject); + } + + if (_onPauseListener != nullptr) { + Director::getInstance()->getEventDispatcher()->removeEventListener(_onPauseListener); + } + + if (_onResumeListener != nullptr) { + Director::getInstance()->getEventDispatcher()->removeEventListener(_onResumeListener); + } + + gAudioImpl = nullptr; +} + +bool AudioEngineImpl::init() { + bool ret = false; + do { + // create engine + auto result = slCreateEngine(&_engineObject, 0, nullptr, 0, nullptr, nullptr); + if (SL_RESULT_SUCCESS != result) { + ALOGE("create opensl engine fail"); + break; + } + + // realize the engine + result = (*_engineObject)->Realize(_engineObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + ALOGE("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) { + ALOGE("get the engine interface fail"); + break; + } + + _audioPlayerProvider = new AudioPlayerProvider(_engineEngine, outputSampleRate, fdGetter, &gCallerThreadUtils); + + _onPauseListener = Director::getInstance()->getEventDispatcher()->addCustomEventListener(EVENT_COME_TO_BACKGROUND, CC_CALLBACK_1(AudioEngineImpl::onEnterBackground, this)); + + _onResumeListener = Director::getInstance()->getEventDispatcher()->addCustomEventListener(EVENT_COME_TO_FOREGROUND, CC_CALLBACK_1(AudioEngineImpl::onEnterForeground, this)); + + ret = true; + } while (false); + + return ret; +} + +void AudioEngineImpl::onEnterBackground(EventCustom* event) { + // _audioPlayerProvider->pause() pauses AudioMixer and PcmAudioService, + // but UrlAudioPlayers could not be paused. + if (_audioPlayerProvider != nullptr) + { + _audioPlayerProvider->pause(); + } + + // pause UrlAudioPlayers which are playing. + for (auto&& e : _audioPlayers) { + auto player = e.second; + if (dynamic_cast(player) != nullptr + && player->getState() == IAudioPlayer::State::PLAYING) { + _urlAudioPlayersNeedResume.emplace(e.first, player); + player->pause(); + } + } +} + +void AudioEngineImpl::onEnterForeground(EventCustom* event) { + // _audioPlayerProvider->resume() resumes AudioMixer and PcmAudioService, + // but UrlAudioPlayers could not be resumed. + if (_audioPlayerProvider != nullptr) { + _audioPlayerProvider->resume(); + } + + // resume UrlAudioPlayers + for (auto&& iter : _urlAudioPlayersNeedResume) { + iter.second->resume(); + } + _urlAudioPlayersNeedResume.clear(); +} + +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) { + ALOGE("play2d, _audioPlayers.size=%{public}d", (int)_audioPlayers.size()); + auto audioId = AudioEngine::INVALID_AUDIO_ID; + + do { + if (_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: %{public}d", static_cast(state)); + return; + } + + int id = player->getId(); + + ALOGE("Removing player id=%{public}d, state:%{public}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->play(); + } else { + ALOGE("Oops, player is null ..."); + return AudioEngine::INVALID_AUDIO_ID; + } + + AudioEngine::_audioIDInfoMap[audioId].state = AudioEngine::AudioState::PLAYING; + + } while (false); + + 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::rewindMusic(int audioID) { + stop(audioID); + auto iter = _audioPlayers.find(audioID); + if (iter != _audioPlayers.end()) { + auto *player = iter->second; + player->play(); + } +} + +bool AudioEngineImpl::isMusicPlaying(int audioID) +{ + auto iter = _audioPlayers.find(audioID); + if (iter != _audioPlayers.end()) { + auto *player = iter->second; + auto state = player->getState(); + return state == IAudioPlayer::State::PLAYING; + } + return false; +} + +float AudioEngineImpl::getMusicVolume(int audioID) +{ + auto iter = _audioPlayers.find(audioID); + if (iter != _audioPlayers.end()) { + auto *player = iter->second; + return player->getVolume(); + } + return 0; +} + + +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 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 &callback) { + _callbackMap[audioID] = callback; +} + +void AudioEngineImpl::preload(const std::string &filePath, const std::function &callback) { + if (_audioPlayerProvider != nullptr) { + std::string fullPath = FileUtils::getInstance()->fullPathForFilename(filePath); + _audioPlayerProvider->preloadEffect(fullPath, [callback](bool succeed, const 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(); + } +} + +PCMHeader AudioEngineImpl::getPCMHeader(const char *url) { + PCMHeader header{}; + std::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url); + if (fileFullPath.empty()) { + ALOGD("file %{public}s does not exist or failed to load", url); + return header; + } + if (_audioPlayerProvider->getPcmHeader(url, header)) { + ALOGD("file %{public}s pcm data already cached", url); + return header; + } + + AudioDecoder *decoder = AudioDecoderProvider::createAudioDecoder(_engineEngine, fileFullPath, bufferSizeInFrames, outputSampleRate, fdGetter); + + if (decoder == nullptr) { + ALOGD("decode %{public}s failed, the file formate might not support", url); + return header; + } + if (!decoder->start()) { + ALOGD("[Audio Decoder] Decode failed %{public}s", url); + return header; + } + // Ready to decode + do { + PcmData data = decoder->getResult(); + header.bytesPerFrame = data.bitsPerSample / 8; + header.channelCount = data.numChannels; + header.dataFormat = AudioDataFormat::SIGNED_16; + header.sampleRate = data.sampleRate; + header.totalFrames = data.numFrames; + } while (false); + + AudioDecoderProvider::destroyAudioDecoder(&decoder); + return header; +} + +std::vector AudioEngineImpl::getOriginalPCMBuffer(const char *url, uint32_t channelID) { + std::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url); + std::vector pcmData; + if (fileFullPath.empty()) { + ALOGD("file %{public}s does not exist or failed to load", url); + return pcmData; + } + PcmData data; + if (_audioPlayerProvider->getPcmData(url, data)) { + ALOGD("file %{public}s pcm data already cached", url); + } else { + AudioDecoder *decoder = AudioDecoderProvider::createAudioDecoder(_engineEngine, fileFullPath, bufferSizeInFrames, outputSampleRate, fdGetter); + if (decoder == nullptr) { + ALOGD("decode %{public}s failed, the file formate might not support", url); + return pcmData; + } + if (!decoder->start()) { + ALOGD("[Audio Decoder] Decode failed %{public}s", url); + return pcmData; + } + data = decoder->getResult(); + _audioPlayerProvider->registerPcmData(url, data); + AudioDecoderProvider::destroyAudioDecoder(&decoder); + } + do { + const uint32_t channelCount = data.numChannels; + if (channelID >= channelCount) { + ALOGE("channelID invalid, total channel count is %{public}d but %{public}d is required", channelCount, channelID); + break; + } + // bytesPerSample = bitsPerSample / 8, according to 1 byte = 8 bits + const uint32_t bytesPerFrame = data.numChannels * data.bitsPerSample / 8; + const uint32_t numFrames = data.numFrames; + const uint32_t bytesPerChannelInFrame = bytesPerFrame / channelCount; + + pcmData.resize(bytesPerChannelInFrame * numFrames); + uint8_t *p = pcmData.data(); + char *tmpBuf = data.pcmBuffer->data(); // shared ptr + for (int itr = 0; itr < numFrames; itr++) { + memcpy(p, tmpBuf + itr * bytesPerFrame + channelID * bytesPerChannelInFrame, bytesPerChannelInFrame); + p += bytesPerChannelInFrame; + } + } while (false); + + return pcmData; +} diff --git a/cocos/audio/ohos/AudioEngine-inl.h b/cocos/audio/ohos/AudioEngine-inl.h new file mode 100644 index 000000000000..7ae5ac253d03 --- /dev/null +++ b/cocos/audio/ohos/AudioEngine-inl.h @@ -0,0 +1,123 @@ +/**************************************************************************** + 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 +#include + +#include +#include "audio_utils/AudioDef.h" +#include "audio_utils/RefCounted.h" +//#include "base/Utils.h" +#include +#include + +#define MAX_AUDIOINSTANCES 13 + +#define ERRORLOG(msg) log("fun:%s,line:%d,msg:%s", __func__, __LINE__, #msg) + +namespace cocos2d { + +class EventCustom; +class EventListener; + +namespace experimental { + +class IAudioPlayer; +class AudioPlayerProvider; + +class AudioEngineImpl; + +class AudioEngineImpl : public RefCounted { +public: + AudioEngineImpl(); + ~AudioEngineImpl() override; + const int INVALID_AUDIO_ID = -1; + bool init(); + int play2d(const std::string &filePath, 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 &filePath); + float getCurrentTime(int audioID); + bool setCurrentTime(int audioID, float time); + void setFinishCallback(int audioID, const std::function &callback); + + void uncache(const std::string &filePath); + void uncacheAll(); + void preload(const std::string &filePath, const std::function &callback); + + void onResume(); + void onPause(); + + void rewindMusic(int audioID); + bool isMusicPlaying(int audioID); + + float getMusicVolume(int audioID); + + + void setAudioFocusForAllPlayers(bool isFocus); + + PCMHeader getPCMHeader(const char *url); + std::vector getOriginalPCMBuffer(const char *url, uint32_t channelID); + +private: + + void onEnterBackground(EventCustom* event); + void onEnterForeground(EventCustom* event); + static AudioEngineImpl *audioEngineImpl; + + // engine interfaces + SLObjectItf _engineObject; + SLEngineItf _engineEngine; + + // output mix interfaces + SLObjectItf _outputMixObject; + + //audioID,AudioInfo + std::unordered_map _audioPlayers; + std::unordered_map> _callbackMap; + + // UrlAudioPlayers which need to resumed while entering foreground + std::unordered_map _urlAudioPlayersNeedResume; + AudioPlayerProvider *_audioPlayerProvider; + EventListener* _onPauseListener; + EventListener* _onResumeListener; + + int _audioIDIndex; + + bool _lazyInitLoop; +}; + +}} // namespace cocos2d { namespace experimental diff --git a/cocos/audio/ohos/AudioMixer.cpp b/cocos/audio/ohos/AudioMixer.cpp new file mode 100644 index 000000000000..dae37451e486 --- /dev/null +++ b/cocos/audio/ohos/AudioMixer.cpp @@ -0,0 +1,2040 @@ +// clang-format off +#define LOG_TAG "AudioMixer" +#define LOG_NDEBUG 1 + +#include +#include +#include +#include +#include + +#include "audio.h" +#include "audio_utils/include/audio_utils/primitives.h" +#include "AudioMixerOps.h" +#include "AudioMixer.h" + + +// clang-format on +// The FCC_2 macro refers to the Fixed Channel Count of 2 for the legacy integer mixer. +#ifndef FCC_2 + #define FCC_2 2 +#endif + +// Look for MONO_HACK for any Mono hack involving legacy mono channel to +// stereo channel conversion. + +/* VERY_VERY_VERBOSE_LOGGING will show exactly which process hook and track hook is + * being used. This is a considerable amount of log spam, so don't enable unless you + * are verifying the hook based code. + */ +//#define VERY_VERY_VERBOSE_LOGGING +#ifdef VERY_VERY_VERBOSE_LOGGING + #define ALOGVV ALOGV +//define ALOGVV printf // for test-mixer.cpp +#else + #define ALOGVV(a...) \ + do { \ + } while (0) +#endif + +#ifndef ARRAY_SIZE + #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +// REFINE: Move these macro/inlines to a header file. +template +static inline T max(const T &x, const T &y) { + return x > y ? x : y; +} + +// Set kUseNewMixer to true to use the new mixer engine always. Otherwise the +// original code will be used for stereo sinks, the new mixer for multichannel. +static const bool kUseNewMixer = false; //NOLINT + +// Set kUseFloat to true to allow floating input into the mixer engine. +// If kUseNewMixer is false, this is ignored or may be overridden internally +// because of downmix/upmix support. +static const bool kUseFloat = false; //NOLINT + +// Set to default copy buffer size in frames for input processing. +static const size_t kCopyBufferFrameCount = 256; //NOLINT + +namespace cocos2d { namespace experimental { + +// ---------------------------------------------------------------------------- + +template +T min(const T &a, const T &b) { + return a < b ? a : b; +} + +// ---------------------------------------------------------------------------- + +// Ensure mConfiguredNames bitmask is initialized properly on all architectures. +// The value of 1 << x is undefined in C when x >= 32. + +AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate, uint32_t maxNumTracks) +: mTrackNames(0), mConfiguredNames((maxNumTracks >= 32 ? 0 : 1 << maxNumTracks) - 1), mSampleRate(sampleRate) { + ALOGVV("AudioMixer constructed, frameCount: %d, sampleRate: %d", (int)frameCount, (int)sampleRate); + ALOG_ASSERT(maxNumTracks <= MAX_NUM_TRACKS, "maxNumTracks %u > MAX_NUM_TRACKS %u", + maxNumTracks, MAX_NUM_TRACKS); + + // AudioMixer is not yet capable of more than 32 active track inputs + ALOG_ASSERT(32 >= MAX_NUM_TRACKS, "bad MAX_NUM_TRACKS %d", MAX_NUM_TRACKS); + + pthread_once(&sOnceControl, &sInitRoutine); + + mState.enabledTracks = 0; + mState.needsChanged = 0; + mState.frameCount = frameCount; + mState.hook = process__nop; + mState.outputTemp = nullptr; + mState.resampleTemp = nullptr; + //cjh mState.mLog = &mDummyLog; + // mState.reserved + + // IDEA: Most of the following initialization is probably redundant since + // tracks[i] should only be referenced if (mTrackNames & (1 << i)) != 0 + // and mTrackNames is initially 0. However, leave it here until that's verified. + track_t *t = mState.tracks; + for (unsigned i = 0; i < MAX_NUM_TRACKS; i++) { + t->resampler = nullptr; + //cjh t->downmixerBufferProvider = nullptr; + // t->mReformatBufferProvider = nullptr; + // t->mTimestretchBufferProvider = nullptr; + t++; + } +} + +AudioMixer::~AudioMixer() { + track_t *t = mState.tracks; + for (unsigned i = 0; i < MAX_NUM_TRACKS; i++) { + delete t->resampler; + //cjh delete t->downmixerBufferProvider; + // delete t->mReformatBufferProvider; + // delete t->mTimestretchBufferProvider; + t++; + } + delete[] mState.outputTemp; + delete[] mState.resampleTemp; +} + +//cjh void AudioMixer::setLog(NBLog::Writer *log) +//{ +// mState.mLog = log; +//} + +static inline audio_format_t selectMixerInFormat(audio_format_t inputFormat __unused) { + return kUseFloat && kUseNewMixer ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT; +} + +int AudioMixer::getTrackName(audio_channel_mask_t channelMask, + audio_format_t format, int sessionId) { + if (!isValidPcmTrackFormat(format)) { + ALOGE("AudioMixer::getTrackName invalid format (%#x)", format); + return -1; + } + uint32_t names = (~mTrackNames) & mConfiguredNames; + if (names != 0) { + int n = __builtin_ctz(names); + ALOGV("add track (%d)", n); + // assume default parameters for the track, except where noted below + track_t *t = &mState.tracks[n]; + t->needs = 0; + + // Integer volume. + // Currently integer volume is kept for the legacy integer mixer. + // Will be removed when the legacy mixer path is removed. + t->volume[0] = UNITY_GAIN_INT; + t->volume[1] = UNITY_GAIN_INT; + t->prevVolume[0] = UNITY_GAIN_INT << 16; + t->prevVolume[1] = UNITY_GAIN_INT << 16; + t->volumeInc[0] = 0; + t->volumeInc[1] = 0; + t->auxLevel = 0; + t->auxInc = 0; + t->prevAuxLevel = 0; + + // Floating point volume. + t->mVolume[0] = UNITY_GAIN_FLOAT; + t->mVolume[1] = UNITY_GAIN_FLOAT; + t->mPrevVolume[0] = UNITY_GAIN_FLOAT; + t->mPrevVolume[1] = UNITY_GAIN_FLOAT; + t->mVolumeInc[0] = 0.; + t->mVolumeInc[1] = 0.; + t->mAuxLevel = 0.; + t->mAuxInc = 0.; + t->mPrevAuxLevel = 0.; + + // no initialization needed + // t->frameCount + t->channelCount = audio_channel_count_from_out_mask(channelMask); + t->enabled = false; + ALOGV_IF(audio_channel_mask_get_bits(channelMask) != AUDIO_CHANNEL_OUT_STEREO, + "Non-stereo channel mask: %d\n", channelMask); + t->channelMask = channelMask; + t->sessionId = sessionId; + // setBufferProvider(name, AudioBufferProvider *) is required before enable(name) + t->bufferProvider = nullptr; + t->buffer.raw = nullptr; + // no initialization needed + // t->buffer.frameCount + t->hook = nullptr; + t->in = nullptr; + t->resampler = nullptr; + t->sampleRate = mSampleRate; + // setParameter(name, TRACK, MAIN_BUFFER, mixBuffer) is required before enable(name) + t->mainBuffer = nullptr; + t->auxBuffer = nullptr; + t->mInputBufferProvider = nullptr; + //cjh t->mReformatBufferProvider = nullptr; + // t->downmixerBufferProvider = nullptr; + // t->mPostDownmixReformatBufferProvider = nullptr; + // t->mTimestretchBufferProvider = nullptr; + t->mMixerFormat = AUDIO_FORMAT_PCM_16_BIT; + t->mFormat = format; + t->mMixerInFormat = selectMixerInFormat(format); + t->mDownmixRequiresFormat = AUDIO_FORMAT_INVALID; // no format required + t->mMixerChannelMask = audio_channel_mask_from_representation_and_bits( + AUDIO_CHANNEL_REPRESENTATION_POSITION, AUDIO_CHANNEL_OUT_STEREO); + t->mMixerChannelCount = audio_channel_count_from_out_mask(t->mMixerChannelMask); + ALOGVV("t->mMixerChannelCount: %d", t->mMixerChannelCount); + t->mPlaybackRate = AUDIO_PLAYBACK_RATE_DEFAULT; + // Check the downmixing (or upmixing) requirements. + status_t status = t->prepareForDownmix(); + if (status != OK) { + ALOGE("AudioMixer::getTrackName invalid channelMask (%#x)", channelMask); + return -1; + } + // prepareForDownmix() may change mDownmixRequiresFormat + ALOGVV("mMixerFormat:%#x mMixerInFormat:%#x\n", t->mMixerFormat, t->mMixerInFormat); + t->prepareForReformat(); + mTrackNames |= 1 << n; + ALOGVV("getTrackName return: %d", TRACK0 + n); + return TRACK0 + n; + } + ALOGE("AudioMixer::getTrackName out of available tracks"); + return -1; +} + +void AudioMixer::invalidateState(uint32_t mask) { + if (mask != 0) { + mState.needsChanged |= mask; + mState.hook = process__validate; + } +} + +// Called when channel masks have changed for a track name +// REFINE: Fix DownmixerBufferProvider not to (possibly) change mixer input format, +// which will simplify this logic. +bool AudioMixer::setChannelMasks(int name, + audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask) { + track_t &track = mState.tracks[name]; + ALOGVV("AudioMixer::setChannelMask ..."); + if (trackChannelMask == track.channelMask && mixerChannelMask == track.mMixerChannelMask) { + ALOGVV("No need to change channel mask ..."); + return false; // no need to change + } + // always recompute for both channel masks even if only one has changed. + const uint32_t trackChannelCount = audio_channel_count_from_out_mask(trackChannelMask); + const uint32_t mixerChannelCount = audio_channel_count_from_out_mask(mixerChannelMask); + const bool mixerChannelCountChanged = track.mMixerChannelCount != mixerChannelCount; + + ALOG_ASSERT((trackChannelCount <= MAX_NUM_CHANNELS_TO_DOWNMIX) && trackChannelCount && mixerChannelCount); + track.channelMask = trackChannelMask; + track.channelCount = trackChannelCount; + track.mMixerChannelMask = mixerChannelMask; + track.mMixerChannelCount = mixerChannelCount; + + // channel masks have changed, does this track need a downmixer? + // update to try using our desired format (if we aren't already using it) + const audio_format_t prevDownmixerFormat = track.mDownmixRequiresFormat; + const status_t status = mState.tracks[name].prepareForDownmix(); + ALOGE_IF(status != OK, + "prepareForDownmix error %d, track channel mask %#x, mixer channel mask %#x", + status, track.channelMask, track.mMixerChannelMask); + + if (prevDownmixerFormat != track.mDownmixRequiresFormat) { + track.prepareForReformat(); // because of downmixer, track format may change! + } + + if (track.resampler && mixerChannelCountChanged) { + // resampler channels may have changed. + const uint32_t resetToSampleRate = track.sampleRate; + delete track.resampler; + track.resampler = nullptr; + track.sampleRate = mSampleRate; // without resampler, track rate is device sample rate. + // recreate the resampler with updated format, channels, saved sampleRate. + track.setResampler(resetToSampleRate /*trackSampleRate*/, mSampleRate /*devSampleRate*/); + } + return true; +} + +void AudioMixer::track_t::unprepareForDownmix() { + ALOGV("AudioMixer::unprepareForDownmix(%p)", this); + + mDownmixRequiresFormat = AUDIO_FORMAT_INVALID; + //cjh if (downmixerBufferProvider != nullptr) { + // // this track had previously been configured with a downmixer, delete it + // ALOGV(" deleting old downmixer"); + // delete downmixerBufferProvider; + // downmixerBufferProvider = nullptr; + // reconfigureBufferProviders(); + // } else + { + ALOGV(" nothing to do, no downmixer to delete"); + } +} + +status_t AudioMixer::track_t::prepareForDownmix() { + ALOGV("AudioMixer::prepareForDownmix(%p) with mask 0x%x", + this, channelMask); + + // discard the previous downmixer if there was one + unprepareForDownmix(); + // MONO_HACK Only remix (upmix or downmix) if the track and mixer/device channel masks + // are not the same and not handled internally, as mono -> stereo currently is. + if (channelMask == mMixerChannelMask || (channelMask == AUDIO_CHANNEL_OUT_MONO && mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO)) { + return NO_ERROR; + } + // DownmixerBufferProvider is only used for position masks. + //cjh if (audio_channel_mask_get_representation(channelMask) + // == AUDIO_CHANNEL_REPRESENTATION_POSITION + // && DownmixerBufferProvider::isMultichannelCapable()) { + // DownmixerBufferProvider* pDbp = new DownmixerBufferProvider(channelMask, + // mMixerChannelMask, + // AUDIO_FORMAT_PCM_16_BIT /* REFINE: use mMixerInFormat, now only PCM 16 */, + // sampleRate, sessionId, kCopyBufferFrameCount); + // + // if (pDbp->isValid()) { // if constructor completed properly + // mDownmixRequiresFormat = AUDIO_FORMAT_PCM_16_BIT; // PCM 16 bit required for downmix + // downmixerBufferProvider = pDbp; + // reconfigureBufferProviders(); + // return NO_ERROR; + // } + // delete pDbp; + // } + // + // // Effect downmixer does not accept the channel conversion. Let's use our remixer. + // RemixBufferProvider* pRbp = new RemixBufferProvider(channelMask, + // mMixerChannelMask, mMixerInFormat, kCopyBufferFrameCount); + // // Remix always finds a conversion whereas Downmixer effect above may fail. + // downmixerBufferProvider = pRbp; + // reconfigureBufferProviders(); + return NO_ERROR; +} + +void AudioMixer::track_t::unprepareForReformat() { + ALOGV("AudioMixer::unprepareForReformat(%p)", this); + bool requiresReconfigure = false; + //cjh if (mReformatBufferProvider != nullptr) { + // delete mReformatBufferProvider; + // mReformatBufferProvider = nullptr; + // requiresReconfigure = true; + // } + // if (mPostDownmixReformatBufferProvider != nullptr) { + // delete mPostDownmixReformatBufferProvider; + // mPostDownmixReformatBufferProvider = nullptr; + // requiresReconfigure = true; + // } + if (requiresReconfigure) { + reconfigureBufferProviders(); + } +} + +status_t AudioMixer::track_t::prepareForReformat() { + ALOGV("AudioMixer::prepareForReformat(%p) with format %#x", this, mFormat); + // discard previous reformatters + unprepareForReformat(); + // only configure reformatters as needed + const audio_format_t targetFormat = mDownmixRequiresFormat != AUDIO_FORMAT_INVALID + ? mDownmixRequiresFormat + : mMixerInFormat; + bool requiresReconfigure = false; + //cjh if (mFormat != targetFormat) { + // mReformatBufferProvider = new ReformatBufferProvider( + // audio_channel_count_from_out_mask(channelMask), + // mFormat, + // targetFormat, + // kCopyBufferFrameCount); + // requiresReconfigure = true; + // } + // if (targetFormat != mMixerInFormat) { + // mPostDownmixReformatBufferProvider = new ReformatBufferProvider( + // audio_channel_count_from_out_mask(mMixerChannelMask), + // targetFormat, + // mMixerInFormat, + // kCopyBufferFrameCount); + // requiresReconfigure = true; + // } + if (requiresReconfigure) { + reconfigureBufferProviders(); + } + ALOGVV("prepareForReformat return ..."); + return NO_ERROR; +} + +void AudioMixer::track_t::reconfigureBufferProviders() { + bufferProvider = mInputBufferProvider; + //cjh if (mReformatBufferProvider) { + // mReformatBufferProvider->setBufferProvider(bufferProvider); + // bufferProvider = mReformatBufferProvider; + // } + // if (downmixerBufferProvider) { + // downmixerBufferProvider->setBufferProvider(bufferProvider); + // bufferProvider = downmixerBufferProvider; + // } + // if (mPostDownmixReformatBufferProvider) { + // mPostDownmixReformatBufferProvider->setBufferProvider(bufferProvider); + // bufferProvider = mPostDownmixReformatBufferProvider; + // } + // if (mTimestretchBufferProvider) { + // mTimestretchBufferProvider->setBufferProvider(bufferProvider); + // bufferProvider = mTimestretchBufferProvider; + // } +} + +void AudioMixer::deleteTrackName(int name) { + ALOGV("AudioMixer::deleteTrackName(%d)", name); + name -= TRACK0; + ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name); + ALOGV("deleteTrackName(%d)", name); + track_t &track(mState.tracks[name]); + if (track.enabled) { + track.enabled = false; + invalidateState(1 << name); + } + // delete the resampler + delete track.resampler; + track.resampler = nullptr; + // delete the downmixer + mState.tracks[name].unprepareForDownmix(); + // delete the reformatter + mState.tracks[name].unprepareForReformat(); + // delete the timestretch provider + //cjh delete track.mTimestretchBufferProvider; + // track.mTimestretchBufferProvider = nullptr; + mTrackNames &= ~(1 << name); +} + +void AudioMixer::enable(int name) { + name -= TRACK0; + ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name); + track_t &track = mState.tracks[name]; + + if (!track.enabled) { + track.enabled = true; + ALOGV("enable(%d)", name); + invalidateState(1 << name); + } +} + +void AudioMixer::disable(int name) { + name -= TRACK0; + ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name); + track_t &track = mState.tracks[name]; + + if (track.enabled) { + track.enabled = false; + ALOGV("disable(%d)", name); + invalidateState(1 << name); + } +} + +/* Sets the volume ramp variables for the AudioMixer. + * + * The volume ramp variables are used to transition from the previous + * volume to the set volume. ramp controls the duration of the transition. + * Its value is typically one state framecount period, but may also be 0, + * meaning "immediate." + * + * IDEA: 1) Volume ramp is enabled only if there is a nonzero integer increment + * even if there is a nonzero floating point increment (in that case, the volume + * change is immediate). This restriction should be changed when the legacy mixer + * is removed (see #2). + * IDEA: 2) Integer volume variables are used for Legacy mixing and should be removed + * when no longer needed. + * + * @param newVolume set volume target in floating point [0.0, 1.0]. + * @param ramp number of frames to increment over. if ramp is 0, the volume + * should be set immediately. Currently ramp should not exceed 65535 (frames). + * @param pIntSetVolume pointer to the U4.12 integer target volume, set on return. + * @param pIntPrevVolume pointer to the U4.28 integer previous volume, set on return. + * @param pIntVolumeInc pointer to the U4.28 increment per output audio frame, set on return. + * @param pSetVolume pointer to the float target volume, set on return. + * @param pPrevVolume pointer to the float previous volume, set on return. + * @param pVolumeInc pointer to the float increment per output audio frame, set on return. + * @return true if the volume has changed, false if volume is same. + */ +static inline bool setVolumeRampVariables(float newVolume, int32_t ramp, + int16_t *pIntSetVolume, int32_t *pIntPrevVolume, int32_t *pIntVolumeInc, + float *pSetVolume, float *pPrevVolume, float *pVolumeInc) { + // check floating point volume to see if it is identical to the previously + // set volume. + // We do not use a tolerance here (and reject changes too small) + // as it may be confusing to use a different value than the one set. + // If the resulting volume is too small to ramp, it is a direct set of the volume. + if (newVolume == *pSetVolume) { + return false; + } + if (newVolume < 0) { + newVolume = 0; // should not have negative volumes + } else { + switch (fpclassify(newVolume)) { + case FP_SUBNORMAL: + case FP_NAN: + newVolume = 0; + break; + case FP_ZERO: + break; // zero volume is fine + case FP_INFINITE: + // Infinite volume could be handled consistently since + // floating point math saturates at infinities, + // but we limit volume to unity gain float. + // ramp = 0; break; + // + newVolume = AudioMixer::UNITY_GAIN_FLOAT; + break; + case FP_NORMAL: + default: + // Floating point does not have problems with overflow wrap + // that integer has. However, we limit the volume to + // unity gain here. + // REFINE: Revisit the volume limitation and perhaps parameterize. + if (newVolume > AudioMixer::UNITY_GAIN_FLOAT) { + newVolume = AudioMixer::UNITY_GAIN_FLOAT; + } + break; + } + } + + // set floating point volume ramp + if (ramp != 0) { + // when the ramp completes, *pPrevVolume is set to *pSetVolume, so there + // is no computational mismatch; hence equality is checked here. + ALOGD_IF(*pPrevVolume != *pSetVolume, + "previous float ramp hasn't finished," + " prev:%f set_to:%f", + *pPrevVolume, *pSetVolume); + const float inc = (newVolume - *pPrevVolume) / ramp; // could be inf, nan, subnormal + const float maxv = max(newVolume, *pPrevVolume); // could be inf, cannot be nan, subnormal + + if (isnormal(inc) // inc must be a normal number (no subnormals, infinite, nan) + && maxv + inc != maxv) { // inc must make forward progress + *pVolumeInc = inc; + // ramp is set now. + // Note: if newVolume is 0, then near the end of the ramp, + // it may be possible that the ramped volume may be subnormal or + // temporarily negative by a small amount or subnormal due to floating + // point inaccuracies. + } else { + ramp = 0; // ramp not allowed + } + } + + // compute and check integer volume, no need to check negative values + // The integer volume is limited to "unity_gain" to avoid wrapping and other + // audio artifacts, so it never reaches the range limit of U4.28. + // We safely use signed 16 and 32 bit integers here. + const float scaledVolume = newVolume * AudioMixer::UNITY_GAIN_INT; // not neg, subnormal, nan + const int32_t intVolume = (scaledVolume >= static_cast(AudioMixer::UNITY_GAIN_INT)) ? AudioMixer::UNITY_GAIN_INT : static_cast(scaledVolume); + + // set integer volume ramp + if (ramp != 0) { + // integer volume is U4.12 (to use 16 bit multiplies), but ramping uses U4.28. + // when the ramp completes, *pIntPrevVolume is set to *pIntSetVolume << 16, so there + // is no computational mismatch; hence equality is checked here. + ALOGD_IF(*pIntPrevVolume != *pIntSetVolume << 16, + "previous int ramp hasn't finished," + " prev:%d set_to:%d", + *pIntPrevVolume, *pIntSetVolume << 16); + const int32_t inc = ((intVolume << 16) - *pIntPrevVolume) / ramp; + + if (inc != 0) { // inc must make forward progress + *pIntVolumeInc = inc; + } else { + ramp = 0; // ramp not allowed + } + } + + // if no ramp, or ramp not allowed, then clear float and integer increments + if (ramp == 0) { + *pVolumeInc = 0; + *pPrevVolume = newVolume; + *pIntVolumeInc = 0; + *pIntPrevVolume = intVolume << 16; + } + *pSetVolume = newVolume; + *pIntSetVolume = intVolume; + return true; +} + +void AudioMixer::setParameter(int name, int target, int param, void *value) { + name -= TRACK0; + ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name); + track_t &track = mState.tracks[name]; + + int valueInt = static_cast(reinterpret_cast(value)); + auto *valueBuf = reinterpret_cast(value); + + switch (target) { + case TRACK: + switch (param) { + case CHANNEL_MASK: { + const auto trackChannelMask = + static_cast(valueInt); + if (setChannelMasks(name, trackChannelMask, track.mMixerChannelMask)) { + ALOGV("setParameter(TRACK, CHANNEL_MASK, %x)", trackChannelMask); + invalidateState(1 << name); + } + } break; + case MAIN_BUFFER: + if (track.mainBuffer != valueBuf) { + track.mainBuffer = valueBuf; + ALOGV("setParameter(TRACK, MAIN_BUFFER, %p)", valueBuf); + invalidateState(1 << name); + } + break; + case AUX_BUFFER: + if (track.auxBuffer != valueBuf) { + track.auxBuffer = valueBuf; + ALOGV("setParameter(TRACK, AUX_BUFFER, %p)", valueBuf); + invalidateState(1 << name); + } + break; + case FORMAT: { + auto format = static_cast(valueInt); + if (track.mFormat != format) { + ALOG_ASSERT(audio_is_linear_pcm(format), "Invalid format %#x", format); + track.mFormat = format; + ALOGV("setParameter(TRACK, FORMAT, %#x)", format); + track.prepareForReformat(); + invalidateState(1 << name); + } + } break; + // IDEA: do we want to support setting the downmix type from AudioMixerController? + // for a specific track? or per mixer? + /* case DOWNMIX_TYPE: + break */ + case MIXER_FORMAT: { + auto format = static_cast(valueInt); + if (track.mMixerFormat != format) { + track.mMixerFormat = format; + ALOGV("setParameter(TRACK, MIXER_FORMAT, %#x)", format); + } + } break; + case MIXER_CHANNEL_MASK: { + const auto mixerChannelMask = + static_cast(valueInt); + if (setChannelMasks(name, track.channelMask, mixerChannelMask)) { + ALOGV("setParameter(TRACK, MIXER_CHANNEL_MASK, %#x)", mixerChannelMask); + invalidateState(1 << name); + } + } break; + default: + LOG_ALWAYS_FATAL("setParameter track: bad param %d", param); + } + break; + + case RESAMPLE: + switch (param) { + case SAMPLE_RATE: + ALOG_ASSERT(valueInt > 0, "bad sample rate %d", valueInt); + if (track.setResampler(static_cast(valueInt), mSampleRate)) { + ALOGV("setParameter(RESAMPLE, SAMPLE_RATE, %u)", + uint32_t(valueInt)); + invalidateState(1 << name); + } + break; + case RESET: + track.resetResampler(); + invalidateState(1 << name); + break; + case REMOVE: + delete track.resampler; + track.resampler = nullptr; + track.sampleRate = mSampleRate; + invalidateState(1 << name); + break; + default: + LOG_ALWAYS_FATAL("setParameter resample: bad param %d", param); + } + break; + + case RAMP_VOLUME: + case VOLUME: + switch (param) { + case AUXLEVEL: + if (setVolumeRampVariables(*reinterpret_cast(value), + target == RAMP_VOLUME ? mState.frameCount : 0, + &track.auxLevel, &track.prevAuxLevel, &track.auxInc, + &track.mAuxLevel, &track.mPrevAuxLevel, &track.mAuxInc)) { + ALOGV("setParameter(%s, AUXLEVEL: %04x)", + target == VOLUME ? "VOLUME" : "RAMP_VOLUME", track.auxLevel); + invalidateState(1 << name); + } + break; + default: + if (static_cast(param) >= VOLUME0 && static_cast(param) < VOLUME0 + MAX_NUM_VOLUMES) { + if (setVolumeRampVariables(*reinterpret_cast(value), + target == RAMP_VOLUME ? mState.frameCount : 0, + &track.volume[param - VOLUME0], &track.prevVolume[param - VOLUME0], + &track.volumeInc[param - VOLUME0], + &track.mVolume[param - VOLUME0], &track.mPrevVolume[param - VOLUME0], + &track.mVolumeInc[param - VOLUME0])) { + ALOGV("setParameter(%s, VOLUME%d: %04x)", + target == VOLUME ? "VOLUME" : "RAMP_VOLUME", param - VOLUME0, + track.volume[param - VOLUME0]); + invalidateState(1 << name); + } + } else { + LOG_ALWAYS_FATAL("setParameter volume: bad param %d", param); + } + } + break; + case TIMESTRETCH: + switch (param) { + case PLAYBACK_RATE: { + const AudioPlaybackRate *playbackRate = + reinterpret_cast(value); + ALOGW_IF(!isAudioPlaybackRateValid(*playbackRate), + "bad parameters speed %f, pitch %f", playbackRate->mSpeed, + playbackRate->mPitch); + if (track.setPlaybackRate(*playbackRate)) { + ALOGV( + "setParameter(TIMESTRETCH, PLAYBACK_RATE, STRETCH_MODE, FALLBACK_MODE " + "%f %f %d %d", + playbackRate->mSpeed, + playbackRate->mPitch, + playbackRate->mStretchMode, + playbackRate->mFallbackMode); + // invalidateState(1 << name); + } + } break; + default: + LOG_ALWAYS_FATAL("setParameter timestretch: bad param %d", param); + } + break; + + default: + LOG_ALWAYS_FATAL("setParameter: bad target %d", target); + } +} + +bool AudioMixer::track_t::setResampler(uint32_t trackSampleRate, uint32_t devSampleRate) { + if (trackSampleRate != devSampleRate || resampler != nullptr) { + if (sampleRate != trackSampleRate) { + sampleRate = trackSampleRate; + if (resampler == nullptr) { + ALOGV("Creating resampler from track %d Hz to device %d Hz", + trackSampleRate, devSampleRate); + AudioResampler::src_quality quality; + // force lowest quality level resampler if use case isn't music or video + // IDEA: this is flawed for dynamic sample rates, as we choose the resampler + // quality level based on the initial ratio, but that could change later. + // Should have a way to distinguish tracks with static ratios vs. dynamic ratios. + //cjh if (isMusicRate(trackSampleRate)) { + quality = AudioResampler::DEFAULT_QUALITY; + //cjh } else { + // quality = AudioResampler::DYN_LOW_QUALITY; + // } + + // REFINE: Remove MONO_HACK. Resampler sees #channels after the downmixer + // but if none exists, it is the channel count (1 for mono). + const int resamplerChannelCount = false /*downmixerBufferProvider != nullptr*/ + ? static_cast(mMixerChannelCount) + : static_cast(channelCount); + ALOGVV( + "Creating resampler:" + " format(%#x) channels(%d) devSampleRate(%u) quality(%d)\n", + mMixerInFormat, resamplerChannelCount, devSampleRate, quality); + resampler = AudioResampler::create( + mMixerInFormat, + resamplerChannelCount, + devSampleRate, quality); + resampler->setLocalTimeFreq(sLocalTimeFreq); + } + return true; + } + } + return false; +} + +bool AudioMixer::track_t::setPlaybackRate(const AudioPlaybackRate &playbackRate) { + //cjh if ((mTimestretchBufferProvider == nullptr && + // fabs(playbackRate.mSpeed - mPlaybackRate.mSpeed) < AUDIO_TIMESTRETCH_SPEED_MIN_DELTA && + // fabs(playbackRate.mPitch - mPlaybackRate.mPitch) < AUDIO_TIMESTRETCH_PITCH_MIN_DELTA) || + // isAudioPlaybackRateEqual(playbackRate, mPlaybackRate)) { + // return false; + // } + mPlaybackRate = playbackRate; + // if (mTimestretchBufferProvider == nullptr) { + // // REFINE: Remove MONO_HACK. Resampler sees #channels after the downmixer + // // but if none exists, it is the channel count (1 for mono). + // const int timestretchChannelCount = downmixerBufferProvider != nullptr + // ? mMixerChannelCount : channelCount; + // mTimestretchBufferProvider = new TimestretchBufferProvider(timestretchChannelCount, + // mMixerInFormat, sampleRate, playbackRate); + // reconfigureBufferProviders(); + // } else { + // reinterpret_cast(mTimestretchBufferProvider) + // ->setPlaybackRate(playbackRate); + // } + return true; +} + +/* Checks to see if the volume ramp has completed and clears the increment + * variables appropriately. + * + * IDEA: There is code to handle int/float ramp variable switchover should it not + * complete within a mixer buffer processing call, but it is preferred to avoid switchover + * due to precision issues. The switchover code is included for legacy code purposes + * and can be removed once the integer volume is removed. + * + * It is not sufficient to clear only the volumeInc integer variable because + * if one channel requires ramping, all channels are ramped. + * + * There is a bit of duplicated code here, but it keeps backward compatibility. + */ +inline void AudioMixer::track_t::adjustVolumeRamp(bool aux, bool useFloat) { + if (useFloat) { + for (uint32_t i = 0; i < MAX_NUM_VOLUMES; i++) { + if ((mVolumeInc[i] > 0 && mPrevVolume[i] + mVolumeInc[i] >= mVolume[i]) || + (mVolumeInc[i] < 0 && mPrevVolume[i] + mVolumeInc[i] <= mVolume[i])) { + volumeInc[i] = 0; + prevVolume[i] = volume[i] << 16; + mVolumeInc[i] = 0.; + mPrevVolume[i] = mVolume[i]; + } else { + //ALOGV("ramp: %f %f %f", mVolume[i], mPrevVolume[i], mVolumeInc[i]); + prevVolume[i] = u4_28_from_float(mPrevVolume[i]); + } + } + } else { + for (uint32_t i = 0; i < MAX_NUM_VOLUMES; i++) { + if (((volumeInc[i] > 0) && (((prevVolume[i] + volumeInc[i]) >> 16) >= volume[i])) || + ((volumeInc[i] < 0) && (((prevVolume[i] + volumeInc[i]) >> 16) <= volume[i]))) { + volumeInc[i] = 0; + prevVolume[i] = volume[i] << 16; + mVolumeInc[i] = 0.; + mPrevVolume[i] = mVolume[i]; + } else { + //ALOGV("ramp: %d %d %d", volume[i] << 16, prevVolume[i], volumeInc[i]); + mPrevVolume[i] = float_from_u4_28(prevVolume[i]); + } + } + } + /* REFINE: aux is always integer regardless of output buffer type */ + if (aux) { + if (((auxInc > 0) && (((prevAuxLevel + auxInc) >> 16) >= auxLevel)) || + ((auxInc < 0) && (((prevAuxLevel + auxInc) >> 16) <= auxLevel))) { + auxInc = 0; + prevAuxLevel = auxLevel << 16; + mAuxInc = 0.; + mPrevAuxLevel = mAuxLevel; + } else { + //ALOGV("aux ramp: %d %d %d", auxLevel << 16, prevAuxLevel, auxInc); + } + } +} + +size_t AudioMixer::getUnreleasedFrames(int name) const { + name -= TRACK0; + if (static_cast(name) < MAX_NUM_TRACKS) { + return mState.tracks[name].getUnreleasedFrames(); + } + return 0; +} + +void AudioMixer::setBufferProvider(int name, AudioBufferProvider *bufferProvider) { + name -= TRACK0; + ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name); + + if (mState.tracks[name].mInputBufferProvider == bufferProvider) { + return; // don't reset any buffer providers if identical. + } + //cjh if (mState.tracks[name].mReformatBufferProvider != nullptr) { + // mState.tracks[name].mReformatBufferProvider->reset(); + // } else if (mState.tracks[name].downmixerBufferProvider != nullptr) { + // mState.tracks[name].downmixerBufferProvider->reset(); + // } else if (mState.tracks[name].mPostDownmixReformatBufferProvider != nullptr) { + // mState.tracks[name].mPostDownmixReformatBufferProvider->reset(); + // } else if (mState.tracks[name].mTimestretchBufferProvider != nullptr) { + // mState.tracks[name].mTimestretchBufferProvider->reset(); + // } + + mState.tracks[name].mInputBufferProvider = bufferProvider; + mState.tracks[name].reconfigureBufferProviders(); +} + +void AudioMixer::process(int64_t pts) { + mState.hook(&mState, pts); +} + + +void AudioMixer::setBufferSize(size_t size) { + mState.frameCount = size; +} + +void AudioMixer::process__validate(state_t *state, int64_t pts) {//NOLINT + ALOGW_IF(!state->needsChanged, + "in process__validate() but nothing's invalid"); + + uint32_t changed = state->needsChanged; + state->needsChanged = 0; // clear the validation flag + + // recompute which tracks are enabled / disabled + uint32_t enabled = 0; + uint32_t disabled = 0; + while (changed) { + const int i = 31 - __builtin_clz(changed); + const uint32_t mask = 1 << i; + changed &= ~mask; + track_t &t = state->tracks[i]; + (t.enabled ? enabled : disabled) |= mask; + } + state->enabledTracks &= ~disabled; + state->enabledTracks |= enabled; + + // compute everything we need... + int countActiveTracks = 0; + // REFINE: fix all16BitsStereNoResample logic to + // either properly handle muted tracks (it should ignore them) + // or remove altogether as an obsolete optimization. + bool all16BitsStereoNoResample = true; + bool resampling = false; + bool volumeRamp = false; + uint32_t en = state->enabledTracks; + while (en) { + const int i = 31 - __builtin_clz(en); + en &= ~(1 << i); + + countActiveTracks++; + track_t &t = state->tracks[i]; + uint32_t n = 0; + // IDEA: can overflow (mask is only 3 bits) + n |= NEEDS_CHANNEL_1 + t.channelCount - 1; + if (t.doesResample()) { + n |= NEEDS_RESAMPLE; + } + if (t.auxLevel != 0 && t.auxBuffer != nullptr) { + n |= NEEDS_AUX; + } + + if (t.volumeInc[0] | t.volumeInc[1]) { + volumeRamp = true; + } else if (!t.doesResample() && t.volumeRL == 0) { + n |= NEEDS_MUTE; + } + t.needs = n; + + if (n & NEEDS_MUTE) { + t.hook = track__nop; + } else { + if (n & NEEDS_AUX) { + all16BitsStereoNoResample = false; + } + if (n & NEEDS_RESAMPLE) { + all16BitsStereoNoResample = false; + resampling = true; + t.hook = getTrackHook(TRACKTYPE_RESAMPLE, t.mMixerChannelCount, + t.mMixerInFormat, t.mMixerFormat); + ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2, + "Track %d needs downmix + resample", i); + } else { + if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1) { + t.hook = getTrackHook( + (t.mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO // REFINE: MONO_HACK + && t.channelMask == AUDIO_CHANNEL_OUT_MONO) + ? TRACKTYPE_NORESAMPLEMONO + : TRACKTYPE_NORESAMPLE, + t.mMixerChannelCount, + t.mMixerInFormat, t.mMixerFormat); + all16BitsStereoNoResample = false; + } + if ((n & NEEDS_CHANNEL_COUNT__MASK) >= NEEDS_CHANNEL_2) { + t.hook = getTrackHook(TRACKTYPE_NORESAMPLE, t.mMixerChannelCount, + t.mMixerInFormat, t.mMixerFormat); + ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2, + "Track %d needs downmix", i); + } + } + } + } + + // select the processing hooks + state->hook = process__nop; + if (countActiveTracks > 0) { + if (resampling) { + if (!state->outputTemp) { + state->outputTemp = new int32_t[MAX_NUM_CHANNELS * state->frameCount]; + } + if (!state->resampleTemp) { + state->resampleTemp = new int32_t[MAX_NUM_CHANNELS * state->frameCount]; + } + state->hook = process__genericResampling; + } else { + if (state->outputTemp) { + delete[] state->outputTemp; + state->outputTemp = nullptr; + } + if (state->resampleTemp) { + delete[] state->resampleTemp; + state->resampleTemp = nullptr; + } + state->hook = process__genericNoResampling; + if (all16BitsStereoNoResample && !volumeRamp) { + if (countActiveTracks == 1) { + const int i = 31 - __builtin_clz(state->enabledTracks); + track_t &t = state->tracks[i]; + if ((t.needs & NEEDS_MUTE) == 0) { + // The check prevents a muted track from acquiring a process hook. + // + // This is dangerous if the track is MONO as that requires + // special case handling due to implicit channel duplication. + // Stereo or Multichannel should actually be fine here. + state->hook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK, + t.mMixerChannelCount, t.mMixerInFormat, t.mMixerFormat); + } + } + } + } + } + + ALOGV( + "mixer configuration change: %d activeTracks (%08x) " + "all16BitsStereoNoResample=%d, resampling=%d, volumeRamp=%d", + countActiveTracks, state->enabledTracks, + all16BitsStereoNoResample, resampling, volumeRamp); + + state->hook(state, pts); + + // Now that the volume ramp has been done, set optimal state and + // track hooks for subsequent mixer process + if (countActiveTracks > 0) { + bool allMuted = true; + uint32_t en = state->enabledTracks; + while (en) { + const int i = 31 - __builtin_clz(en); + en &= ~(1 << i); + track_t &t = state->tracks[i]; + if (!t.doesResample() && t.volumeRL == 0) { + t.needs |= NEEDS_MUTE; + t.hook = track__nop; + } else { + allMuted = false; + } + } + if (allMuted) { + state->hook = process__nop; + } else if (all16BitsStereoNoResample) { + if (countActiveTracks == 1) { + const int i = 31 - __builtin_clz(state->enabledTracks); + track_t &t = state->tracks[i]; + // Muted single tracks handled by allMuted above. + state->hook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK, + t.mMixerChannelCount, t.mMixerInFormat, t.mMixerFormat); + } + } + } +} + +void AudioMixer::track__genericResample(track_t *t, int32_t *out, size_t outFrameCount,//NOLINT + int32_t *temp, int32_t *aux) { + ALOGVV("track__genericResample\n"); + t->resampler->setSampleRate(t->sampleRate); + + // ramp gain - resample to temp buffer and scale/mix in 2nd step + if (aux != nullptr) { + // always resample with unity gain when sending to auxiliary buffer to be able + // to apply send level after resampling + t->resampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT); + memset(temp, 0, outFrameCount * t->mMixerChannelCount * sizeof(int32_t)); + t->resampler->resample(temp, outFrameCount, t->bufferProvider); + if (CC_UNLIKELY(t->volumeInc[0] | t->volumeInc[1] | t->auxInc)) { + volumeRampStereo(t, out, outFrameCount, temp, aux); + } else { + volumeStereo(t, out, outFrameCount, temp, aux); + } + } else { + if (CC_UNLIKELY(t->volumeInc[0] | t->volumeInc[1])) { + t->resampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT); + memset(temp, 0, outFrameCount * MAX_NUM_CHANNELS * sizeof(int32_t)); + t->resampler->resample(temp, outFrameCount, t->bufferProvider); + volumeRampStereo(t, out, outFrameCount, temp, aux); + } + + // constant gain + else { + t->resampler->setVolume(t->mVolume[0], t->mVolume[1]); + t->resampler->resample(out, outFrameCount, t->bufferProvider); + } + } +} + +void AudioMixer::track__nop(track_t *t __unused, int32_t *out __unused,//NOLINT + size_t outFrameCount __unused, int32_t *temp __unused, int32_t *aux __unused) { +} + +void AudioMixer::volumeRampStereo(track_t *t, int32_t *out, size_t frameCount, int32_t *temp, + int32_t *aux) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + + //ALOGD("[0] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + // ramp volume + if (CC_UNLIKELY(aux != nullptr)) { + int32_t va = t->prevAuxLevel; + const int32_t vaInc = t->auxInc; + int32_t l; + int32_t r; + + do { + l = (*temp++ >> 12); + r = (*temp++ >> 12); + *out++ += (vl >> 16) * l; + *out++ += (vr >> 16) * r; + *aux++ += (va >> 17) * (l + r); + vl += vlInc; + vr += vrInc; + va += vaInc; + } while (--frameCount); + t->prevAuxLevel = va; + } else { + do { + *out++ += (vl >> 16) * (*temp++ >> 12); + *out++ += (vr >> 16) * (*temp++ >> 12); + vl += vlInc; + vr += vrInc; + } while (--frameCount); + } + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->adjustVolumeRamp(aux != nullptr); +} + +void AudioMixer::volumeStereo(track_t *t, int32_t *out, size_t frameCount, int32_t *temp, + int32_t *aux) { + const int16_t vl = t->volume[0]; + const int16_t vr = t->volume[1]; + + if (CC_UNLIKELY(aux != nullptr)) { + const int16_t va = t->auxLevel; + do { + auto l = static_cast(*temp++ >> 12); + auto r = static_cast(*temp++ >> 12); + out[0] = mulAdd(l, vl, out[0]); + auto a = static_cast((static_cast(l) + r) >> 1); + out[1] = mulAdd(r, vr, out[1]); + out += 2; + aux[0] = mulAdd(a, va, aux[0]); + aux++; + } while (--frameCount); + } else { + do { + auto l = static_cast(*temp++ >> 12); + auto r = static_cast(*temp++ >> 12); + out[0] = mulAdd(l, vl, out[0]); + out[1] = mulAdd(r, vr, out[1]); + out += 2; + } while (--frameCount); + } +} + +void AudioMixer::track__16BitsStereo(track_t *t, int32_t *out, size_t frameCount,//NOLINT + int32_t *temp __unused, int32_t *aux) { + ALOGVV("track__16BitsStereo\n"); + const auto *in = static_cast(t->in); + + if (CC_UNLIKELY(aux != nullptr)) { + int32_t l; + int32_t r; + // ramp gain + if (CC_UNLIKELY(t->volumeInc[0] | t->volumeInc[1] | t->auxInc)) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + int32_t va = t->prevAuxLevel; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + const int32_t vaInc = t->auxInc; + // ALOGD("[1] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + do { + l = static_cast(*in++); + r = static_cast(*in++); + *out++ += (vl >> 16) * l; + *out++ += (vr >> 16) * r; + *aux++ += (va >> 17) * (l + r); + vl += vlInc; + vr += vrInc; + va += vaInc; + } while (--frameCount); + + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->prevAuxLevel = va; + t->adjustVolumeRamp(true); + } + + // constant gain + else { + const uint32_t vrl = t->volumeRL; + const auto va = t->auxLevel; + do { + uint32_t rl = *reinterpret_cast(in); + auto a = static_cast((static_cast(in[0]) + in[1]) >> 1); + in += 2; + out[0] = mulAddRL(1, rl, vrl, out[0]); + out[1] = mulAddRL(0, rl, vrl, out[1]); + out += 2; + aux[0] = mulAdd(a, va, aux[0]); + aux++; + } while (--frameCount); + } + } else { + // ramp gain + if (CC_UNLIKELY(t->volumeInc[0] | t->volumeInc[1])) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + + // ALOGD("[1] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + do { + *out++ += (vl >> 16) * static_cast(*in++); + *out++ += (vr >> 16) * static_cast(*in++); + vl += vlInc; + vr += vrInc; + } while (--frameCount); + + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->adjustVolumeRamp(false); + } + + // constant gain + else { + const uint32_t vrl = t->volumeRL; + do { + uint32_t rl = *reinterpret_cast(in); + in += 2; + out[0] = mulAddRL(1, rl, vrl, out[0]); + out[1] = mulAddRL(0, rl, vrl, out[1]); + out += 2; + } while (--frameCount); + } + } + t->in = in; +} + +void AudioMixer::track__16BitsMono(track_t *t, int32_t *out, size_t frameCount,//NOLINT + int32_t *temp __unused, int32_t *aux) { + ALOGVV("track__16BitsMono\n"); + const auto *in = static_cast(t->in); + + if (CC_UNLIKELY(aux != nullptr)) { + // ramp gain + if (CC_UNLIKELY(t->volumeInc[0] | t->volumeInc[1] | t->auxInc)) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + int32_t va = t->prevAuxLevel; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + const int32_t vaInc = t->auxInc; + + // ALOGD("[2] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + do { + int32_t l = *in++; + *out++ += (vl >> 16) * l; + *out++ += (vr >> 16) * l; + *aux++ += (va >> 16) * l; + vl += vlInc; + vr += vrInc; + va += vaInc; + } while (--frameCount); + + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->prevAuxLevel = va; + t->adjustVolumeRamp(true); + } + // constant gain + else { + const int16_t vl = t->volume[0]; + const int16_t vr = t->volume[1]; + const auto va = t->auxLevel; + do { + int16_t l = *in++; + out[0] = mulAdd(l, vl, out[0]); + out[1] = mulAdd(l, vr, out[1]); + out += 2; + aux[0] = mulAdd(l, va, aux[0]); + aux++; + } while (--frameCount); + } + } else { + // ramp gain + if (CC_UNLIKELY(t->volumeInc[0] | t->volumeInc[1])) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + + // ALOGD("[2] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + do { + int32_t l = *in++; + *out++ += (vl >> 16) * l; + *out++ += (vr >> 16) * l; + vl += vlInc; + vr += vrInc; + } while (--frameCount); + + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->adjustVolumeRamp(false); + } + // constant gain + else { + const int16_t vl = t->volume[0]; + const int16_t vr = t->volume[1]; + do { + int16_t l = *in++; + out[0] = mulAdd(l, vl, out[0]); + out[1] = mulAdd(l, vr, out[1]); + out += 2; + } while (--frameCount); + } + } + t->in = in; +} + +// no-op case +void AudioMixer::process__nop(state_t *state, int64_t pts) {//NOLINT + ALOGVV("process__nop\n"); + uint32_t e0 = state->enabledTracks; + while (e0) { + // process by group of tracks with same output buffer to + // avoid multiple memset() on same buffer + uint32_t e1 = e0; + uint32_t e2 = e0; + int i = 31 - __builtin_clz(e1); + { + track_t &t1 = state->tracks[i]; + e2 &= ~(1 << i); + while (e2) { + i = 31 - __builtin_clz(e2); + e2 &= ~(1 << i); + track_t &t2 = state->tracks[i]; + if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) { + e1 &= ~(1 << i); + } + } + e0 &= ~(e1); + + memset(t1.mainBuffer, 0, state->frameCount * t1.mMixerChannelCount * audio_bytes_per_sample(t1.mMixerFormat)); + } + + while (e1) { + i = 31 - __builtin_clz(e1); + e1 &= ~(1 << i); + { + track_t &t3 = state->tracks[i]; + size_t outFrames = state->frameCount; + while (outFrames) { + t3.buffer.frameCount = outFrames; + int64_t outputPTS = calculateOutputPTS( + t3, pts, state->frameCount - outFrames);//NOLINT + t3.bufferProvider->getNextBuffer(&t3.buffer, outputPTS); + if (t3.buffer.raw == nullptr) break; + outFrames -= t3.buffer.frameCount; + t3.bufferProvider->releaseBuffer(&t3.buffer); + } + } + } + } +} + +// generic code without resampling +void AudioMixer::process__genericNoResampling(state_t *state, int64_t pts) {//NOLINT + ALOGVV("process__genericNoResampling\n"); + int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32))); + + // acquire each track's buffer + uint32_t enabledTracks = state->enabledTracks; + uint32_t e0 = enabledTracks; + while (e0) { + const int i = 31 - __builtin_clz(e0); + e0 &= ~(1 << i); + track_t &t = state->tracks[i]; + t.buffer.frameCount = state->frameCount; + t.bufferProvider->getNextBuffer(&t.buffer, pts); + t.frameCount = t.buffer.frameCount; + t.in = t.buffer.raw; + } + + e0 = enabledTracks; + while (e0) { + // process by group of tracks with same output buffer to + // optimize cache use + uint32_t e1 = e0; + uint32_t e2 = e0; + int j = 31 - __builtin_clz(e1); + track_t &t1 = state->tracks[j]; + e2 &= ~(1 << j); + while (e2) { + j = 31 - __builtin_clz(e2); + e2 &= ~(1 << j); + track_t &t2 = state->tracks[j]; + if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) { + e1 &= ~(1 << j); + } + } + e0 &= ~(e1); + // this assumes output 16 bits stereo, no resampling + int32_t *out = t1.mainBuffer; + size_t numFrames = 0; + do { + memset(outTemp, 0, sizeof(outTemp)); + e2 = e1; + while (e2) { + const int i = 31 - __builtin_clz(e2); + e2 &= ~(1 << i); + track_t &t = state->tracks[i]; + size_t outFrames = BLOCKSIZE; + int32_t *aux = nullptr; + if (CC_UNLIKELY(t.needs & NEEDS_AUX)) { + aux = t.auxBuffer + numFrames; + } + while (outFrames) { + // t.in == nullptr can happen if the track was flushed just after having + // been enabled for mixing. + if (t.in == nullptr) { + enabledTracks &= ~(1 << i); + e1 &= ~(1 << i); + break; + } + size_t inFrames = (t.frameCount > outFrames) ? outFrames : t.frameCount; + if (inFrames > 0) { + t.hook(&t, outTemp + (BLOCKSIZE - outFrames) * t.mMixerChannelCount, + inFrames, state->resampleTemp, aux); + t.frameCount -= inFrames; + outFrames -= inFrames; + if (CC_UNLIKELY(aux != nullptr)) { + aux += inFrames; + } + } + if (t.frameCount == 0 && outFrames) { + t.bufferProvider->releaseBuffer(&t.buffer); + t.buffer.frameCount = (state->frameCount - numFrames) - + (BLOCKSIZE - outFrames); + int64_t outputPTS = calculateOutputPTS( + t, pts, numFrames + (BLOCKSIZE - outFrames));//NOLINT + t.bufferProvider->getNextBuffer(&t.buffer, outputPTS); + t.in = t.buffer.raw; + if (t.in == nullptr) { + enabledTracks &= ~(1 << i); + e1 &= ~(1 << i); + break; + } + t.frameCount = t.buffer.frameCount; + } + } + } + + convertMixerFormat(out, t1.mMixerFormat, outTemp, t1.mMixerInFormat, + BLOCKSIZE * t1.mMixerChannelCount); + // REFINE: fix ugly casting due to choice of out pointer type + out = reinterpret_cast(reinterpret_cast(out) + BLOCKSIZE * t1.mMixerChannelCount * audio_bytes_per_sample(t1.mMixerFormat)); + numFrames += BLOCKSIZE; + } while (numFrames < state->frameCount); + } + + // release each track's buffer + e0 = enabledTracks; + while (e0) { + const int i = 31 - __builtin_clz(e0); + e0 &= ~(1 << i); + track_t &t = state->tracks[i]; + t.bufferProvider->releaseBuffer(&t.buffer); + } +} + +// generic code with resampling +void AudioMixer::process__genericResampling(state_t *state, int64_t pts) {//NOLINT + ALOGVV("process__genericResampling\n"); + // this const just means that local variable outTemp doesn't change + int32_t *const outTemp = state->outputTemp; + size_t numFrames = state->frameCount; + + uint32_t e0 = state->enabledTracks; + while (e0) { + // process by group of tracks with same output buffer + // to optimize cache use + uint32_t e1 = e0; + uint32_t e2 = e0; + int j = 31 - __builtin_clz(e1); + track_t &t1 = state->tracks[j]; + e2 &= ~(1 << j); + while (e2) { + j = 31 - __builtin_clz(e2); + e2 &= ~(1 << j); + track_t &t2 = state->tracks[j]; + if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) { + e1 &= ~(1 << j); + } + } + e0 &= ~(e1); + int32_t *out = t1.mainBuffer; + memset(outTemp, 0, sizeof(*outTemp) * t1.mMixerChannelCount * state->frameCount); + while (e1) { + const int i = 31 - __builtin_clz(e1); + e1 &= ~(1 << i); + track_t &t = state->tracks[i]; + int32_t *aux = nullptr; + if (CC_UNLIKELY(t.needs & NEEDS_AUX)) { + aux = t.auxBuffer; + } + + // this is a little goofy, on the resampling case we don't + // acquire/release the buffers because it's done by + // the resampler. + if (t.needs & NEEDS_RESAMPLE) { + t.resampler->setPTS(pts); + t.hook(&t, outTemp, numFrames, state->resampleTemp, aux); + } else { + size_t outFrames = 0; + + while (outFrames < numFrames) { + t.buffer.frameCount = numFrames - outFrames; + int64_t outputPTS = calculateOutputPTS(t, pts, outFrames); + t.bufferProvider->getNextBuffer(&t.buffer, outputPTS); + t.in = t.buffer.raw; + // t.in == nullptr can happen if the track was flushed just after having + // been enabled for mixing. + if (t.in == nullptr) break; + + if (CC_UNLIKELY(aux != nullptr)) { + aux += outFrames; + } + t.hook(&t, outTemp + outFrames * t.mMixerChannelCount, t.buffer.frameCount, + state->resampleTemp, aux); + outFrames += t.buffer.frameCount; + t.bufferProvider->releaseBuffer(&t.buffer); + } + } + } + convertMixerFormat(out, t1.mMixerFormat, + outTemp, t1.mMixerInFormat, numFrames * t1.mMixerChannelCount); + } +} + +// one track, 16 bits stereo without resampling is the most common case +void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t *state,//NOLINT + int64_t pts) { + ALOGVV("process__OneTrack16BitsStereoNoResampling\n"); + // This method is only called when state->enabledTracks has exactly + // one bit set. The asserts below would verify this, but are commented out + // since the whole point of this method is to optimize performance. + //ALOG_ASSERT(0 != state->enabledTracks, "no tracks enabled"); + const int i = 31 - __builtin_clz(state->enabledTracks); + //ALOG_ASSERT((1 << i) == state->enabledTracks, "more than 1 track enabled"); + const track_t &t = state->tracks[i]; + + AudioBufferProvider::Buffer &b(t.buffer); + + int32_t *out = t.mainBuffer; + auto *fout = reinterpret_cast(out); + size_t numFrames = state->frameCount; + + const int16_t vl = t.volume[0]; + const int16_t vr = t.volume[1]; + const uint32_t vrl = t.volumeRL; + while (numFrames) { + b.frameCount = numFrames; + int64_t outputPTS = calculateOutputPTS(t, pts, out - t.mainBuffer); + t.bufferProvider->getNextBuffer(&b, outputPTS); + const int16_t *in = b.i16; + + // in == nullptr can happen if the track was flushed just after having + // been enabled for mixing. + if (in == nullptr || (((uintptr_t)in) & 3)) {//NOLINT + memset(out, 0, numFrames * t.mMixerChannelCount * audio_bytes_per_sample(t.mMixerFormat)); + ALOGE_IF((((uintptr_t)in) & 3), + "process__OneTrack16BitsStereoNoResampling: misaligned buffer" + " %p track %d, channels %d, needs %08x, volume %08x vfl %f vfr %f", + in, i, t.channelCount, t.needs, vrl, t.mVolume[0], t.mVolume[1]); + return; + } + size_t outFrames = b.frameCount; + + switch (t.mMixerFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + do { + uint32_t rl = *reinterpret_cast(in); + in += 2; + int32_t l = mulRL(1, rl, vrl); + int32_t r = mulRL(0, rl, vrl); + *fout++ = float_from_q4_27(l); + *fout++ = float_from_q4_27(r); + // Note: In case of later int16_t sink output, + // conversion and clamping is done by memcpy_to_i16_from_float(). + } while (--outFrames); + break; + case AUDIO_FORMAT_PCM_16_BIT: + if (CC_UNLIKELY(uint32_t(vl) > UNITY_GAIN_INT || uint32_t(vr) > UNITY_GAIN_INT)) { + // volume is boosted, so we might need to clamp even though + // we process only one track. + do { + uint32_t rl = *reinterpret_cast(in); + in += 2; + int32_t l = mulRL(1, rl, vrl) >> 12; + int32_t r = mulRL(0, rl, vrl) >> 12; + // clamping... + l = clamp16(l); + r = clamp16(r); + *out++ = (r << 16) | (l & 0xFFFF); + } while (--outFrames); + } else { + do { + uint32_t rl = *reinterpret_cast(in); + in += 2; + int32_t l = mulRL(1, rl, vrl) >> 12; + int32_t r = mulRL(0, rl, vrl) >> 12; + *out++ = (r << 16) | (l & 0xFFFF); + } while (--outFrames); + } + break; + default: + LOG_ALWAYS_FATAL("bad mixer format: %d", t.mMixerFormat); + } + numFrames -= b.frameCount; + t.bufferProvider->releaseBuffer(&b); + } +} + +int64_t AudioMixer::calculateOutputPTS(const track_t &t, int64_t basePTS, + int outputFrameIndex) { + if (AudioBufferProvider::kInvalidPTS == basePTS) { + return AudioBufferProvider::kInvalidPTS; + } + + return basePTS + ((outputFrameIndex * sLocalTimeFreq) / t.sampleRate); +} + +/*static*/ uint64_t AudioMixer::sLocalTimeFreq; +/*static*/ pthread_once_t AudioMixer::sOnceControl = PTHREAD_ONCE_INIT; + +/*static*/ void AudioMixer::sInitRoutine() { + //cjh LocalClock lc; + // sLocalTimeFreq = lc.getLocalFreq(); // for the resampler + // + // DownmixerBufferProvider::init(); // for the downmixer +} + +/* REFINE: consider whether this level of optimization is necessary. + * Perhaps just stick with a single for loop. + */ + +// Needs to derive a compile time constant (constexpr). Could be targeted to go +// to a MONOVOL mixtype based on MAX_NUM_VOLUMES, but that's an unnecessary complication. +#define MIXTYPE_MONOVOL(mixtype) (mixtype == MIXTYPE_MULTI ? MIXTYPE_MULTI_MONOVOL : mixtype == MIXTYPE_MULTI_SAVEONLY ? MIXTYPE_MULTI_SAVEONLY_MONOVOL : mixtype) + +/* MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) + * 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 +static void volumeRampMulti(uint32_t channels, TO *out, size_t frameCount, + const TI *in, TA *aux, TV *vol, const TV *volinc, TAV *vola, TAV volainc) { + switch (channels) { + case 1: + volumeRampMulti(out, frameCount, in, aux, vol, volinc, vola, volainc); + break; + case 2: + volumeRampMulti(out, frameCount, in, aux, vol, volinc, vola, volainc); + break; + case 3: + volumeRampMulti(out, + frameCount, in, aux, vol, volinc, vola, volainc); + break; + case 4: + volumeRampMulti(out, + frameCount, in, aux, vol, volinc, vola, volainc); + break; + case 5: + volumeRampMulti(out, + frameCount, in, aux, vol, volinc, vola, volainc); + break; + case 6: + volumeRampMulti(out, + frameCount, in, aux, vol, volinc, vola, volainc); + break; + case 7: + volumeRampMulti(out, + frameCount, in, aux, vol, volinc, vola, volainc); + break; + case 8: + volumeRampMulti(out, + frameCount, in, aux, vol, volinc, vola, volainc); + break; + } +} + +/* MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) + * 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 +static void volumeMulti(uint32_t channels, TO *out, size_t frameCount, + const TI *in, TA *aux, const TV *vol, TAV vola) { + switch (channels) { + case 1: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + case 2: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + case 3: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + case 4: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + case 5: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + case 6: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + case 7: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + case 8: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + } +} + +/* 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 +void AudioMixer::volumeMix(TO *out, size_t outFrames, + const TI *in, TA *aux, bool ramp, AudioMixer::track_t *t) { + if (USEFLOATVOL) { + if (ramp) { + volumeRampMulti(t->mMixerChannelCount, out, outFrames, in, aux, + t->mPrevVolume, t->mVolumeInc, &t->prevAuxLevel, t->auxInc); + if (ADJUSTVOL) { + t->adjustVolumeRamp(aux != nullptr, true); + } + } else { + volumeMulti(t->mMixerChannelCount, out, outFrames, in, aux, + t->mVolume, t->auxLevel); + } + } else { + if (ramp) { + volumeRampMulti(t->mMixerChannelCount, out, outFrames, in, aux, + t->prevVolume, t->volumeInc, &t->prevAuxLevel, t->auxInc); + if (ADJUSTVOL) { + t->adjustVolumeRamp(aux != nullptr); + } + } else { + volumeMulti(t->mMixerChannelCount, out, outFrames, in, aux, + t->volume, t->auxLevel); + } + } +} + +/* This process hook is called when there is a single track without + * aux buffer, volume ramp, or resampling. + * REFINE: Update the hook selection: this can properly handle aux and ramp. + * + * MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) + * 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 +void AudioMixer::process_NoResampleOneTrack(state_t *state, int64_t pts) {//NOLINT + ALOGVV("process_NoResampleOneTrack\n"); + // CLZ is faster than CTZ on ARM, though really not sure if true after 31 - clz. + const int i = 31 - __builtin_clz(state->enabledTracks); + ALOG_ASSERT((1 << i) == state->enabledTracks, "more than 1 track enabled"); + track_t *t = &state->tracks[i]; + const uint32_t channels = t->mMixerChannelCount; + TO *out = reinterpret_cast(t->mainBuffer); + TA *aux = reinterpret_cast(t->auxBuffer); + const bool ramp = t->needsRamp(); + + for (size_t numFrames = state->frameCount; numFrames;) { + AudioBufferProvider::Buffer &b(t->buffer); + // get input buffer + b.frameCount = numFrames; + const int64_t outputPTS = calculateOutputPTS(*t, pts, state->frameCount - numFrames);//NOLINT + t->bufferProvider->getNextBuffer(&b, outputPTS); + const TI *in = reinterpret_cast(b.raw); + + // in == nullptr can happen if the track was flushed just after having + // been enabled for mixing. + if (in == nullptr || (((uintptr_t)in) & 3)) {//NOLINT + memset(out, 0, numFrames * channels * audio_bytes_per_sample(t->mMixerFormat)); + ALOGE_IF((((uintptr_t)in) & 3), + "process_NoResampleOneTrack: bus error: " + "buffer %p track %p, channels %d, needs %#x", + in, t, t->channelCount, t->needs); + return; + } + + const size_t outFrames = b.frameCount; + volumeMix::value, false>( + out, outFrames, in, aux, ramp, t); + + out += outFrames * channels; + if (aux != nullptr) { + aux += channels; + } + numFrames -= b.frameCount; + + // release buffer + t->bufferProvider->releaseBuffer(&b); + } + if (ramp) { + t->adjustVolumeRamp(aux != nullptr, is_same::value); + } +} + +/* This track hook is called to do resampling then mixing, + * pulling from the track's upstream AudioBufferProvider. + * + * MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) + * 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 +void AudioMixer::track__Resample(track_t *t, TO *out, size_t outFrameCount, TO *temp, TA *aux) {//NOLINT + ALOGVV("track__Resample\n"); + t->resampler->setSampleRate(t->sampleRate); + const bool ramp = t->needsRamp(); + if (ramp || aux != nullptr) { + // if ramp: resample with unity gain to temp buffer and scale/mix in 2nd step. + // if aux != nullptr: resample with unity gain to temp buffer then apply send level. + + t->resampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT); + memset(temp, 0, outFrameCount * t->mMixerChannelCount * sizeof(TO)); + t->resampler->resample((int32_t *)temp, outFrameCount, t->bufferProvider);//NOLINT + + volumeMix::value, true>( + out, outFrameCount, temp, aux, ramp, t); + + } else { // constant volume gain + t->resampler->setVolume(t->mVolume[0], t->mVolume[1]); + t->resampler->resample((int32_t *)out, outFrameCount, t->bufferProvider);//NOLINT + } +} + +/* This track hook is called to mix a track, when no resampling is required. + * The input buffer should be present in t->in. + * + * MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) + * 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 +void AudioMixer::track__NoResample(track_t *t, TO *out, size_t frameCount,//NOLINT + TO *temp __unused, TA *aux) { + ALOGVV("track__NoResample\n"); + const TI *in = static_cast(t->in); + + volumeMix::value, true>( + out, frameCount, in, aux, t->needsRamp(), t); + + // MIXTYPE_MONOEXPAND reads a single input channel and expands to NCHAN output channels. + // MIXTYPE_MULTI reads NCHAN input channels and places to NCHAN output channels. + in += (MIXTYPE == MIXTYPE_MONOEXPAND) ? frameCount : frameCount * t->mMixerChannelCount; + t->in = in; +} + +/* The Mixer engine generates either int32_t (Q4_27) or float data. + * We use this function to convert the engine buffers + * to the desired mixer output format, either int16_t (Q.15) or float. + */ +void AudioMixer::convertMixerFormat(void *out, audio_format_t mixerOutFormat, + void *in, audio_format_t mixerInFormat, size_t sampleCount) { + switch (mixerInFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + switch (mixerOutFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + memcpy(out, in, sampleCount * sizeof(float)); // MEMCPY. REFINE: optimize out + break; + case AUDIO_FORMAT_PCM_16_BIT: + memcpy_to_i16_from_float(static_cast(out), static_cast(in), sampleCount); + break; + default: + LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat); + break; + } + break; + case AUDIO_FORMAT_PCM_16_BIT: + switch (mixerOutFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + memcpy_to_float_from_q4_27(static_cast(out), static_cast(in), sampleCount); + break; + case AUDIO_FORMAT_PCM_16_BIT: + // two int16_t are produced per iteration + ditherAndClamp(static_cast(out), static_cast(in), sampleCount >> 1); + break; + default: + LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat); + break; + } + break; + default: + LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat); + break; + } +} + +/* Returns the proper track hook to use for mixing the track into the output buffer. + */ +AudioMixer::hook_t AudioMixer::getTrackHook(int trackType, uint32_t channelCount, + audio_format_t mixerInFormat, audio_format_t mixerOutFormat __unused) { + if (!kUseNewMixer && channelCount == FCC_2 && mixerInFormat == AUDIO_FORMAT_PCM_16_BIT) { + switch (trackType) { + case TRACKTYPE_NOP: + return track__nop; + case TRACKTYPE_RESAMPLE: + return track__genericResample; + case TRACKTYPE_NORESAMPLEMONO: + return track__16BitsMono; + case TRACKTYPE_NORESAMPLE: + return track__16BitsStereo; + default: + LOG_ALWAYS_FATAL("bad trackType: %d", trackType); + break; + } + } + LOG_ALWAYS_FATAL_IF(channelCount > MAX_NUM_CHANNELS); + switch (trackType) { + case TRACKTYPE_NOP: + return track__nop; + case TRACKTYPE_RESAMPLE: + switch (mixerInFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + return reinterpret_cast(track__Resample); + case AUDIO_FORMAT_PCM_16_BIT: + return static_cast(track__Resample); + default: + LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat); + break; + } + break; + case TRACKTYPE_NORESAMPLEMONO: + switch (mixerInFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + return reinterpret_cast(track__NoResample); + case AUDIO_FORMAT_PCM_16_BIT: + return static_cast(track__NoResample); + default: + LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat); + break; + } + break; + case TRACKTYPE_NORESAMPLE: + switch (mixerInFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + return reinterpret_cast(track__NoResample); + case AUDIO_FORMAT_PCM_16_BIT: + return static_cast(track__NoResample); + default: + LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat); + break; + } + break; + default: + LOG_ALWAYS_FATAL("bad trackType: %d", trackType); + break; + } + return nullptr; +} + +/* Returns the proper process hook for mixing tracks. Currently works only for + * PROCESSTYPE_NORESAMPLEONETRACK, a mix involving one track, no resampling. + * + * REFINE: Due to the special mixing considerations of duplicating to + * a stereo output track, the input track cannot be MONO. This should be + * prevented by the caller. + */ +AudioMixer::process_hook_t AudioMixer::getProcessHook(int processType, uint32_t channelCount, + audio_format_t mixerInFormat, audio_format_t mixerOutFormat) { + if (processType != PROCESSTYPE_NORESAMPLEONETRACK) { // Only NORESAMPLEONETRACK + LOG_ALWAYS_FATAL("bad processType: %d", processType); + return nullptr; + } + if (!kUseNewMixer && channelCount == FCC_2 && mixerInFormat == AUDIO_FORMAT_PCM_16_BIT) { + return process__OneTrack16BitsStereoNoResampling; + } + LOG_ALWAYS_FATAL_IF(channelCount > MAX_NUM_CHANNELS); + switch (mixerInFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + switch (mixerOutFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + return process_NoResampleOneTrack; + case AUDIO_FORMAT_PCM_16_BIT: + return process_NoResampleOneTrack; + default: + LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat); + break; + } + break; + case AUDIO_FORMAT_PCM_16_BIT: + switch (mixerOutFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + return process_NoResampleOneTrack; + case AUDIO_FORMAT_PCM_16_BIT: + return process_NoResampleOneTrack; + default: + LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat); + break; + } + break; + default: + LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat); + break; + } + return nullptr; +} + +// ---------------------------------------------------------------------------- +}} // namespace cocos2d { namespace experimental diff --git a/cocos/audio/ohos/AudioMixer.h b/cocos/audio/ohos/AudioMixer.h new file mode 100644 index 000000000000..317c30b06f2b --- /dev/null +++ b/cocos/audio/ohos/AudioMixer.h @@ -0,0 +1,362 @@ +#pragma once + +#include +#include +#include + +#include "AudioBufferProvider.h" +#include "AudioResamplerPublic.h" + +#include "AudioResampler.h" +#include "audio.h" +#include "utils/Compat.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 { namespace experimental { + +// ---------------------------------------------------------------------------- + +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); + void setBufferSize(size_t size); + 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, // NOLINT(bugprone-reserved-identifier) + }; + + 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); //NOLINT(modernize-use-using) + 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 != nullptr; } + void resetResampler() const { + if (resampler != nullptr) resampler->reset(); + } + void adjustVolumeRamp(bool aux, bool useFloat = false); + size_t getUnreleasedFrames() const { return resampler != nullptr ? 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); // NOLINT(modernize-use-using) + + // 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;// NOLINT(readability-identifier-naming) + + // 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;// NOLINT(readability-identifier-naming) + + const uint32_t mSampleRate;// NOLINT(readability-identifier-naming) + + //cjh NBLog::Writer mDummyLog; +public: + //cjh void setLog(NBLog::Writer* log); +private: + state_t mState __attribute__((aligned(32)));// NOLINT(readability-identifier-naming) + + // 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);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + static void track__nop(track_t *t, int32_t *out, size_t numFrames, int32_t *temp, int32_t *aux);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + static void track__16BitsStereo(track_t *t, int32_t *out, size_t numFrames, int32_t *temp, int32_t *aux);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + static void track__16BitsMono(track_t *t, int32_t *out, size_t numFrames, int32_t *temp, int32_t *aux);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + 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);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + static void process__nop(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + static void process__genericNoResampling(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + static void process__genericResampling(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + static void process__OneTrack16BitsStereoNoResampling(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + + 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 + static void volumeMix(TO *out, size_t outFrames, + const TI *in, TA *aux, bool ramp, AudioMixer::track_t *t); + + // multi-format process hooks + template + static void process_NoResampleOneTrack(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + + // multi-format track hooks + template + static void track__Resample(track_t *t, TO *out, size_t frameCount, TO *temp __unused, TA *aux);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + template + static void track__NoResample(track_t *t, TO *out, size_t frameCount, TO *temp __unused, TA *aux); // NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + + 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 { namespace experimental diff --git a/cocos/audio/ohos/AudioMixerController.cpp b/cocos/audio/ohos/AudioMixerController.cpp new file mode 100644 index 000000000000..d8b3036dec4b --- /dev/null +++ b/cocos/audio/ohos/AudioMixerController.cpp @@ -0,0 +1,306 @@ +/**************************************************************************** +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 "AudioMixerController.h" +#include +#include "AudioMixer.h" +#include "OpenSLHelper.h" +#include "Track.h" + + +namespace cocos2d { namespace experimental { + +AudioMixerController::AudioMixerController(int sampleRate, int channelCount) +: _sampleRate(sampleRate), _channelCount(channelCount), _mixer(nullptr), _isPaused(false), _isMixingFrame(false) { + ALOGV("In the constructor of AudioMixerController!"); + // For OHAudio, bluetooth buffer size is 17832 + int32_t maxBufferSize = 17832; + _mixingBuffer.buf = memalign(32, maxBufferSize); +} + +AudioMixerController::~AudioMixerController() { + destroy(); + + if (_mixer != nullptr) { + delete _mixer; + _mixer = nullptr; + } + + free(_mixingBuffer.buf); +} + +void AudioMixerController::updateBufferSize(int bufferSize) { + _mixer->setBufferSize(bufferSize / _channelCount / 2); + _mixingBuffer.size = bufferSize; + + uint32_t channelMask = audio_channel_out_mask_from_count(_channelCount); + int32_t name = _mixer->getTrackName(channelMask, AUDIO_FORMAT_PCM_16_BIT, AUDIO_SESSION_OUTPUT_MIX); + _mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::MAIN_BUFFER, + _mixingBuffer.buf); +} + +bool AudioMixerController::init(int bufferSizeInFrames) { + _mixingBuffer.size = (size_t)bufferSizeInFrames * 2 * _channelCount; + memset(_mixingBuffer.buf, 0, _mixingBuffer.size); + _bufferSizeInFrames = bufferSizeInFrames; + _mixer = new 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 lk(_activeTracksMutex); + + auto iter = std::find(_activeTracks.begin(), _activeTracks.end(), track); + if (iter == _activeTracks.end()) { + _activeTracks.push_back(track); + ret = true; + } + + return ret; +} + +template +static void removeItemFromVector(std::vector &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 &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 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 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 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(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(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 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 { namespace experimental diff --git a/cocos/audio/ohos/AudioMixerController.h b/cocos/audio/ohos/AudioMixerController.h new file mode 100644 index 000000000000..7bbce1030860 --- /dev/null +++ b/cocos/audio/ohos/AudioMixerController.h @@ -0,0 +1,87 @@ +/**************************************************************************** +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 +#include +#include +#include +#include +#include "utils/Errors.h" + + +namespace cocos2d { namespace experimental { + +class Track; +class AudioMixer; + +class AudioMixerController { +public: + struct OutputBuffer { + void *buf; + size_t size; + }; + + AudioMixerController(int sampleRate, int channelCount); + + void updateBufferSize(int bufferSize); + + bool init(int bufferSizeInFrames); + + ~AudioMixerController(); + + 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 &tracksToRemove); + +private: + int _bufferSizeInFrames; + int _sampleRate; + int _channelCount; + + AudioMixer *_mixer; + + std::mutex _activeTracksMutex; + std::vector _activeTracks; + + OutputBuffer _mixingBuffer; + + std::atomic_bool _isPaused; + std::atomic_bool _isMixingFrame; +}; + +}} // namespace cocos2d { namespace experimental diff --git a/cocos/audio/ohos/AudioMixerOps.h b/cocos/audio/ohos/AudioMixerOps.h new file mode 100644 index 000000000000..3084471c12ca --- /dev/null +++ b/cocos/audio/ohos/AudioMixerOps.h @@ -0,0 +1,429 @@ +#pragma once + +#include "cutils/log.h" + +namespace cocos2d { namespace experimental { + +/* Behavior of is_same<>::value is true if the types are identical, + * false otherwise. Identical to the STL std::is_same. + */ +template +struct is_same { + static const bool value = false; +}; + +template +struct is_same // 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 = + * 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 +TO MixMul(TI value, TV volume); + +template <> +inline int32_t MixMul(int16_t value, int16_t volume) { + return value * volume; +} + +template <> +inline int32_t MixMul(int32_t value, int16_t volume) { + return (value >> 12) * volume; +} + +template <> +inline int32_t MixMul(int16_t value, int32_t volume) { + return value * (volume >> 16); +} + +template <> +inline int32_t MixMul(int32_t value, int32_t volume) { + return (value >> 12) * (volume >> 16); +} + +template <> +inline float MixMul(float value, int16_t volume) { + static const float norm = 1. / (1 << 12); + return value * volume * norm; +} + +template <> +inline float MixMul(float value, int32_t volume) { + static const float norm = 1. / (1 << 28); + return value * volume * norm; +} + +template <> +inline int16_t MixMul(float value, int16_t volume) { + return clamp16_from_float(MixMul(value, volume)); +} + +template <> +inline int16_t MixMul(float value, int32_t volume) { + return clamp16_from_float(MixMul(value, volume)); +} + +template <> +inline float MixMul(int16_t value, int16_t volume) { + static const float norm = 1. / (1 << (15 + 12)); + return static_cast(value) * static_cast(volume) * norm; +} + +template <> +inline float MixMul(int16_t value, int32_t volume) { + static const float norm = 1. / (1ULL << (15 + 28)); + return static_cast(value) * static_cast(volume) * norm; +} + +template <> +inline int16_t MixMul(int16_t value, int16_t volume) { + return clamp16(MixMul(value, volume) >> 12); +} + +template <> +inline int16_t MixMul(int32_t value, int16_t volume) { + return clamp16(MixMul(value, volume) >> 12); +} + +template <> +inline int16_t MixMul(int16_t value, int32_t volume) { + return clamp16(MixMul(value, volume) >> 12); +} + +template <> +inline int16_t MixMul(int32_t value, int32_t volume) { + return clamp16(MixMul(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 value, float volume) { + return value * volume; +} + +template <> +inline float MixMul(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 value, float volume) { + LOG_ALWAYS_FATAL("MixMul Runtime Should not be here"); + return value * volume; +} + +template <> +inline int32_t MixMul(int16_t value, float volume) { + LOG_ALWAYS_FATAL("MixMul 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 value, float volume) { + LOG_ALWAYS_FATAL("MixMul Runtime Should not be here"); + return clamp16_from_float(MixMul(value, volume)); +} + +template <> +inline int16_t MixMul(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 +inline void MixAccum(TO *auxaccum, TI value) { + if (!is_same::value) { + LOG_ALWAYS_FATAL("MixAccum type not properly specialized: %zu %zu\n", + sizeof(TO), sizeof(TI)); + } + *auxaccum += value; +} + +template <> +inline void MixAccum(float *auxaccum, int16_t value) { + static const float norm = 1. / (1 << 15); + *auxaccum += norm * value; +} + +template <> +inline void MixAccum(float *auxaccum, int32_t value) { + static const float norm = 1. / (1 << 27); + *auxaccum += norm * value; +} + +template <> +inline void MixAccum(int32_t *auxaccum, int16_t value) { + *auxaccum += value << 12; +} + +template <> +inline void MixAccum(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 +inline TO MixMulAux(TI value, TV volume, TA *auxaccum) { + MixAccum(auxaccum, value); + return MixMul(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 +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(*in++, vol[i], &auxaccum); + vol[i] += volinc[i]; + } + break; + case MIXTYPE_MONOEXPAND: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMulAux(*in, vol[i], &auxaccum); + vol[i] += volinc[i]; + } + in++; + break; + case MIXTYPE_MULTI_SAVEONLY: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMulAux(*in++, vol[i], &auxaccum); + vol[i] += volinc[i]; + } + break; + case MIXTYPE_MULTI_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMulAux(*in++, vol[0], &auxaccum); + } + vol[0] += volinc[0]; + break; + case MIXTYPE_MULTI_SAVEONLY_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMulAux(*in++, vol[0], &auxaccum); + } + vol[0] += volinc[0]; + break; + default: + LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE); + break; + } + auxaccum /= NCHAN; + *aux++ += MixMul(auxaccum, *vola); + vola[0] += volainc; + } while (--frameCount); + } else { + do { + switch (MIXTYPE) { + case MIXTYPE_MULTI: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMul(*in++, vol[i]); + vol[i] += volinc[i]; + } + break; + case MIXTYPE_MONOEXPAND: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMul(*in, vol[i]); + vol[i] += volinc[i]; + } + in++; + break; + case MIXTYPE_MULTI_SAVEONLY: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMul(*in++, vol[i]); + vol[i] += volinc[i]; + } + break; + case MIXTYPE_MULTI_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMul(*in++, vol[0]); + } + vol[0] += volinc[0]; + break; + case MIXTYPE_MULTI_SAVEONLY_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMul(*in++, vol[0]); + } + vol[0] += volinc[0]; + break; + default: + LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE); + break; + } + } while (--frameCount); + } +} + +template +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(*in++, vol[i], &auxaccum); + } + break; + case MIXTYPE_MONOEXPAND: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMulAux(*in, vol[i], &auxaccum); + } + in++; + break; + case MIXTYPE_MULTI_SAVEONLY: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMulAux(*in++, vol[i], &auxaccum); + } + break; + case MIXTYPE_MULTI_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMulAux(*in++, vol[0], &auxaccum); + } + break; + case MIXTYPE_MULTI_SAVEONLY_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMulAux(*in++, vol[0], &auxaccum); + } + break; + default: + LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE); + break; + } + auxaccum /= NCHAN; + *aux++ += MixMul(auxaccum, vola); + } while (--frameCount); + } else { + do { + switch (MIXTYPE) { + case MIXTYPE_MULTI: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMul(*in++, vol[i]); + } + break; + case MIXTYPE_MONOEXPAND: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMul(*in, vol[i]); + } + in++; + break; + case MIXTYPE_MULTI_SAVEONLY: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMul(*in++, vol[i]); + } + break; + case MIXTYPE_MULTI_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMul(*in++, vol[0]); + } + break; + case MIXTYPE_MULTI_SAVEONLY_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMul(*in++, vol[0]); + } + break; + default: + LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE); + break; + } + } while (--frameCount); + } +} + +}} // namespace cocos2d { namespace experimental diff --git a/cocos/audio/ohos/AudioPlayerProvider.cpp b/cocos/audio/ohos/AudioPlayerProvider.cpp new file mode 100644 index 000000000000..dfd7dc3ed996 --- /dev/null +++ b/cocos/audio/ohos/AudioPlayerProvider.cpp @@ -0,0 +1,476 @@ +/**************************************************************************** +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 +#include "PcmData.h" +#include "audio_utils/AudioDef.h" +#include "cutils/log.h" +#define LOG_TAG "AudioPlayerProvider" + +#include // for std::find_if +#include +#include +#include +#include "platform/ohos/CCFileUtils-ohos.h" +#include "AudioDecoder.h" +#include "AudioDecoderProvider.h" +#include "AudioMixerController.h" +#include "AudioPlayerProvider.h" +#include "ICallerThreadUtils.h" +#include "PcmAudioPlayer.h" +#include "PcmAudioService.h" +#include "UrlAudioPlayer.h" +#include "utils/Utils.h" +#include "CCThreadPool.h" + + + +#include // for std::find_if +#include +#include + +namespace cocos2d { namespace experimental { + +static int getSystemAPILevel() { + // TODO(qgh): On the openharmony platform, pcm streaming must be used + return std::numeric_limits::max(); +} + +struct AudioFileIndicator { + std::string extension; + int smallSizeIndicator; +}; + +static AudioFileIndicator gAudioFileIndicator[] = { + {"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, int deviceSampleRate, + + const FdGetterCallback &fdGetterCallback, //NOLINT(modernize-pass-by-value) + ICallerThreadUtils *callerThreadUtils) +: _engineItf(engineItf), _deviceSampleRate(deviceSampleRate), _fdGetterCallback(fdGetterCallback), _callerThreadUtils(callerThreadUtils), _pcmAudioService(nullptr), _mixController(nullptr), _threadPool(LegacyThreadPool::newCachedThreadPool(1, 8, 5, 2, 2)) { + ALOGI("deviceSampleRate: %d", _deviceSampleRate); + if (getSystemAPILevel() >= 17) { + _mixController = new AudioMixerController(_deviceSampleRate, 2); + _pcmAudioService = new PcmAudioService(); + _pcmAudioService->init(_mixController, CHANNEL_NUMBERS, deviceSampleRate, &_bufferSizeInFrames); + _mixController->init(_bufferSizeInFrames); + } + + 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 dynamic_cast(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 = dynamic_cast(obtainPcmAudioPlayer(audioFilePath, pcmData)); + ALOGV_IF(player == nullptr, "%{public}s, %{public}d: player is nullptr, path: %{public}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(); + auto isSucceed = std::make_shared(false); + auto isReturnFromCache = std::make_shared(false); + auto isPreloadFinished = std::make_shared(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 = std::move(data); + *isSucceed = succeed; + *isPreloadFinished = true; + ALOGV("FileInfo (%{public}p), Set isSucceed flag: %{public}d, path: %{public}s", infoPtr, succeed, url.c_str()); + }, + true); + + if (!*isReturnFromCache && !*isPreloadFinished) { + std::unique_lock lck(_preloadWaitMutex); + // Wait for 2 seconds for the decoding in sub thread finishes. + ALOGV("FileInfo (%{public}p), Waiting preload (%{public}s) to finish ...", &info, audioFilePath.c_str()); + _preloadWaitCond.wait_for(lck, std::chrono::seconds(2)); + ALOGV("FileInfo (%{public}p), Waitup preload (%{public}s) ...", &info, audioFilePath.c_str()); + } + + if (*isSucceed) { + if (pcmData->isValid()) { + player = dynamic_cast(obtainPcmAudioPlayer(info.url, *pcmData)); + ALOGV_IF(player == nullptr, "%{public}s, %{public}d: player is nullptr, path: %{public}s", __FUNCTION__, __LINE__, audioFilePath.c_str()); + } else { + ALOGE("pcm data is invalid, path: %{public}s", audioFilePath.c_str()); + } + } else { + ALOGE("FileInfo (%{public}p), preloadEffect (%{public}s) failed", &info, audioFilePath.c_str()); + } + } else { + player = dynamic_cast(createUrlAudioPlayer(info)); + ALOGV_IF(player == nullptr, "%{public}s, %{public}d: player is nullptr, path: %{public}s", __FUNCTION__, __LINE__, audioFilePath.c_str()); + } + } else { + ALOGE("File info is invalid, path: %{public}s", audioFilePath.c_str()); + } + } + + ALOGV_IF(player == nullptr, "%{public}s, %{public}d return nullptr", __FUNCTION__, __LINE__); + return player; +} + +void AudioPlayerProvider::preloadEffect(const std::string &audioFilePath, const PreloadCallback &callback) { + // Pcm data decoding by OpenSLES API only supports in API level 17 and later. + if (getSystemAPILevel() < 17) { + PcmData data; + callback(true, data); + return; + } + + _pcmCacheMutex.lock(); + auto &&iter = _pcmCache.find(audioFilePath); + if (iter != _pcmCache.end()) { + ALOGV("preload return from cache: (%{public}s)", audioFilePath.c_str()); + _pcmCacheMutex.unlock(); + callback(true, iter->second); + return; + } + _pcmCacheMutex.unlock(); + + auto info = getFileInfo(audioFilePath); + preloadEffect( + info, [this, callback, audioFilePath](bool succeed, const PcmData &data) { + _callerThreadUtils->performFunctionInCallerThread([this, succeed, data, callback]() { + callback(succeed, data); + }); + }, + false); +} + +// Used internally +void AudioPlayerProvider::preloadEffect(const AudioFileInfo &info, const PreloadCallback &callback, bool isPreloadInPlay2d) { + PcmData pcmData; + + if (!info.isValid()) { + callback(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: %{public}s", info.url.c_str()); + _pcmCacheMutex.unlock(); + callback(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 lck(_preloadCallbackMutex); + + auto &&preloadIter = _preloadCallbackMap.find(audioFilePath); + if (preloadIter != _preloadCallbackMap.end()) { + ALOGV("audio (%{public}s) is being preloaded, add to callback vector!", audioFilePath.c_str()); + PreloadCallbackParam param; + param.callback = callback; + 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: %{public}s", info.url.c_str()); + _pcmCacheMutex.unlock(); + callback(true, iter->second); + return; + } + _pcmCacheMutex.unlock(); + + PreloadCallbackParam param; + param.callback = callback; + param.isPreloadInPlay2d = isPreloadInPlay2d; + std::vector 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: (%{public}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 lck(_pcmCacheMutex); + _pcmCache.insert(std::make_pair(audioFilePath, d)); + } else { + ALOGE("decode (%{public}s) failed!", audioFilePath.c_str()); + } + + ALOGV("decode %{public}s", (ret ? "succeed" : "failed")); + + std::lock_guard lck(_preloadCallbackMutex); + auto &&preloadIter = _preloadCallbackMap.find(audioFilePath); + if (preloadIter != _preloadCallbackMap.end()) { + auto &¶ms = preloadIter->second; + ALOGV("preload (%{public}s) callback count: %{public}d", audioFilePath.c_str(), (int)params.size()); + PcmData result = decoder->getResult(); + for (auto &¶m : params) { + param.callback(ret, result); + if (param.isPreloadInPlay2d) { + _preloadWaitCond.notify_one(); + } + } + _preloadCallbackMap.erase(preloadIter); + } + + AudioDecoderProvider::destroyAudioDecoder(&decoder); + }); + } else { + ALOGV("File (%{public}s) is too large, ignore preload!", info.url.c_str()); + callback(true, pcmData); + } +} + +AudioPlayerProvider::AudioFileInfo AudioPlayerProvider::getFileInfo(const std::string &audioFilePath) +{ + AudioFileInfo info; + long fileSize = 0; //NOLINT(google-runtime-int) + off_t start = 0; + int assetFd = -1; + + if(audioFilePath[0]!='/'){ + RawFileDescriptor descriptor; + FileUtilsOhos *utils = dynamic_cast(FileUtils::getInstance()); + FileUtils::Status result = utils->getRawFileDescriptor(audioFilePath, descriptor); + if(result != FileUtils::Status::OK|| descriptor.fd <= 0){ + ALOGE("Failed to open file descriptor for '%s'", audioFilePath.c_str()); + return info; + } + assetFd = descriptor.fd; + start = descriptor.start; + fileSize = descriptor.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); + info.start = start; + info.length = fileSize; + ALOGI("AudioPlayerProvide::getFileInfo(%{public}s) file size:%{public}ld,fd is %d",audioFilePath.c_str(), fileSize,assetFd); + return info; +} + +bool AudioPlayerProvider::isSmallFile(const AudioFileInfo &info) { //NOLINT(readability-convert-member-functions-to-static) + //REFINE: If file size is smaller than 100k, we think it's a small file. This value should be set by developers. + auto &audioFileInfo = const_cast(info); + if(audioFileInfo.url[0] == '/') { + // avplayer does not support playing audio files in sandbox path currently. + return true; + } + size_t judgeCount = sizeof(gAudioFileIndicator) / sizeof(gAudioFileIndicator[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(gAudioFileIndicator), std::end(gAudioFileIndicator), + [&extension](const AudioFileIndicator &judge) -> bool { + return judge.extension == extension; + }); + + if (iter != std::end(gAudioFileIndicator)) { + // ALOGV("isSmallFile: found: %{public}s: ", iter->extension.c_str()); + return info.length < iter->smallSizeIndicator; + } + + // ALOGV("isSmallFile: not found return default value"); + return info.length < gAudioFileIndicator[0].smallSizeIndicator; +} + +float AudioPlayerProvider::getDurationFromFile(const std::string &filePath) { + std::lock_guard lck(_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 lck(_pcmCacheMutex); + auto iter = _pcmCache.find(audioFilePath); + if (iter != _pcmCache.end()) { + ALOGV("clear pcm cache: (%{public}s)", audioFilePath.c_str()); + _pcmCache.erase(iter); + } else { + ALOGW("Couldn't find the pcm cache: (%{public}s)", audioFilePath.c_str()); + } +} + +void AudioPlayerProvider::clearAllPcmCaches() { + std::lock_guard lck(_pcmCacheMutex); + _pcmCache.clear(); +} + +PcmAudioPlayer *AudioPlayerProvider::obtainPcmAudioPlayer(const std::string &url, + const PcmData &pcmData) { + PcmAudioPlayer *pcmPlayer = nullptr; + if (pcmData.isValid()) { + pcmPlayer = new 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; + } + + auto *urlPlayer = new (std::nothrow) UrlAudioPlayer(_callerThreadUtils); + bool ret = urlPlayer->prepare(info.url, info.assetFd, info.start, info.length); + if (!ret) { + if (urlPlayer != nullptr) { + delete urlPlayer; + urlPlayer = nullptr; + } + } + 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(); + } +} +void AudioPlayerProvider::registerPcmData(const std::string &audioFilePath, PcmData &data) { + std::lock_guard lck(_pcmCacheMutex); + if (_pcmCache.find(audioFilePath) != _pcmCache.end()){ + ALOGE("file %{public}s pcm data is already cached.", audioFilePath.c_str()); + return; + } + _pcmCache.emplace(audioFilePath, data); +} + +bool AudioPlayerProvider::getPcmHeader(const std::string &audioFilePath, PCMHeader &header) { + std::lock_guard lck(_pcmCacheMutex); + auto &&iter = _pcmCache.find(audioFilePath); + if (iter != _pcmCache.end()) { + ALOGV("get pcm header from cache, url: %{public}s", audioFilePath.c_str()); + // On Android, all pcm buffer is resampled to sign16. + header.bytesPerFrame = iter->second.bitsPerSample / 8; + header.channelCount = iter->second.numChannels; + header.dataFormat = AudioDataFormat::SIGNED_16; + header.sampleRate = iter->second.sampleRate; + header.totalFrames = iter->second.numFrames; + return true; + } + return false; +} +bool AudioPlayerProvider::getPcmData(const std::string &audioFilePath, PcmData &data) { + std::lock_guard lck(_pcmCacheMutex); + auto &&iter = _pcmCache.find(audioFilePath); + if (iter != _pcmCache.end()) { + ALOGV("get pcm buffer from cache, url: %{public}s", audioFilePath.c_str()); + // On Android, all pcm buffer is resampled to sign16. + data = iter->second; + return true; + } + return false; +} +}} // namespace cocos2d { namespace experimental diff --git a/cocos/audio/ohos/AudioPlayerProvider.h b/cocos/audio/ohos/AudioPlayerProvider.h new file mode 100644 index 000000000000..bbf914b9a85d --- /dev/null +++ b/cocos/audio/ohos/AudioPlayerProvider.h @@ -0,0 +1,122 @@ +/**************************************************************************** +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 +#include +#include +#include "IAudioPlayer.h" +#include "OpenSLHelper.h" +#include "PcmData.h" +#include "audio_utils/AudioDef.h" + + +namespace cocos2d { namespace experimental { +// Manage PcmAudioPlayer& UrlAudioPlayer + +class PcmAudioPlayer; +class PcmAudioService; +class UrlAudioPlayer; +class AudioMixerController; +class ICallerThreadUtils; +class AssetFd; +class LegacyThreadPool; + +class AudioPlayerProvider { +public: + AudioPlayerProvider(SLEngineItf engineItf, int deviceSampleRate, + const FdGetterCallback &fdGetterCallback, ICallerThreadUtils *callerThreadUtils); + + virtual ~AudioPlayerProvider(); + bool isFileCached(const std::string &audioFilePath); + IAudioPlayer *getAudioPlayer(const std::string &audioFilePath); + bool getPcmHeader(const std::string &audioFilePath, PCMHeader &header); + bool getPcmData(const std::string &audioFilePath, PcmData &data); + using PreloadCallback = std::function; + void preloadEffect(const std::string &audioFilePath, const PreloadCallback &callback); + void registerPcmData(const std::string &audioFilePath, PcmData &data); + float getDurationFromFile(const std::string &filePath); + void clearPcmCache(const std::string &audioFilePath); + + void clearAllPcmCaches(); + + void pause(); + + void resume(); + + + struct AudioFileInfo { + std::string url; + std::shared_ptr assetFd; + off_t start{}; + off_t length; + + AudioFileInfo() + : assetFd(nullptr) {} + + 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 &callback, bool isPreloadInPlay2d); + + AudioFileInfo getFileInfo(const std::string &audioFilePath); + + bool isSmallFile(const AudioFileInfo &info); + + SLEngineItf _engineItf; + SLObjectItf _outputMixObject; + int _deviceSampleRate; + int _bufferSizeInFrames; + FdGetterCallback _fdGetterCallback; + ICallerThreadUtils *_callerThreadUtils; + + std::unordered_map _pcmCache; + std::mutex _pcmCacheMutex; + + struct PreloadCallbackParam { + PreloadCallback callback; + bool isPreloadInPlay2d; + }; + + std::unordered_map> _preloadCallbackMap; + std::mutex _preloadCallbackMutex; + + std::mutex _preloadWaitMutex; + std::condition_variable _preloadWaitCond; + + PcmAudioService *_pcmAudioService; + AudioMixerController *_mixController; + + LegacyThreadPool *_threadPool; +}; + +}} // namespace cocos2d { namespace experimental diff --git a/cocos/audio/ohos/AudioResampler.cpp b/cocos/audio/ohos/AudioResampler.cpp new file mode 100644 index 000000000000..026e04172aa2 --- /dev/null +++ b/cocos/audio/ohos/AudioResampler.cpp @@ -0,0 +1,770 @@ +#define LOG_TAG "AudioResampler" + +#include +#include +#include +#include +#include +#include "cutils/log.h" +#include "utils/Utils.h" +#include "AudioResampler.h" +#include "audio_utils/include/audio_utils/primitives.h" +#include "AudioResamplerCubic.h" + +//cjh #ifdef __arm__ +// #define ASM_ARM_RESAMP1 // enable asm optimisation for ResamplerOrder1 +//#endif + +namespace cocos2d { namespace experimental { + +// ---------------------------------------------------------------------------- + +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 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 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 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 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 { namespace experimental diff --git a/cocos/audio/ohos/AudioResampler.h b/cocos/audio/ohos/AudioResampler.h new file mode 100644 index 000000000000..49abf8470f71 --- /dev/null +++ b/cocos/audio/ohos/AudioResampler.h @@ -0,0 +1,154 @@ +#pragma once + +#include +#include "AudioBufferProvider.h" +#include +#include "audio.h" + +namespace cocos2d { namespace experimental { + +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 { // NOLINT(readability-identifier-naming) + 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; // NOLINT(readability-identifier-naming) + + // phase mask for fraction + static const uint32_t kPhaseMask = (1LU << kNumPhaseBits) - 1; // NOLINT(readability-identifier-naming) + + // multiplier to calculate fixed point phase increment + static const double kPhaseMultiplier; // NOLINT(readability-identifier-naming) + + 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;// NOLINT(readability-identifier-naming) + const int32_t mSampleRate;// NOLINT(readability-identifier-naming) + int32_t mInSampleRate;// NOLINT(readability-identifier-naming) + AudioBufferProvider::Buffer mBuffer;// NOLINT(readability-identifier-naming) + union { + int16_t mVolume[2];// NOLINT(readability-identifier-naming) + uint32_t mVolumeRL;// NOLINT(readability-identifier-naming) + }; + int16_t mTargetVolume[2];// NOLINT(readability-identifier-naming) + size_t mInputIndex;// NOLINT(readability-identifier-naming) + int32_t mPhaseIncrement;// NOLINT(readability-identifier-naming) + uint32_t mPhaseFraction;// NOLINT(readability-identifier-naming) + uint64_t mLocalTimeFreq;// NOLINT(readability-identifier-naming) + int64_t mPTS;// NOLINT(readability-identifier-naming) + + // 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(outFrameCount)*mInSampleRate + (mSampleRate - 1))/mSampleRate; + // + // The double precision equivalent (float may not be precise enough): + // ceil(static_cast(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) const { + return (static_cast(outFrameCount) * mInSampleRate + (mSampleRate - 1)) / mSampleRate; + } + + inline float clampFloatVol(float volume) {//NOLINT(readability-identifier-naming, readability-convert-member-functions-to-static) + float ret = 0.0F; + if (volume > UNITY_GAIN_FLOAT) { + ret = UNITY_GAIN_FLOAT; + } else if (volume >= 0.) { + ret = volume; + } + return ret; // NaN or negative volume maps to 0. + } + +private: + const src_quality mQuality;// NOLINT(readability-identifier-naming) + + // Return 'true' if the quality level is supported without explicit request + static bool qualityIsSupported(src_quality quality); + + // For pthread_once() + static void init_routine(); // NOLINT(readability-identifier-naming) + + // 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 { namespace experimental diff --git a/cocos/audio/ohos/AudioResamplerCubic.cpp b/cocos/audio/ohos/AudioResamplerCubic.cpp new file mode 100644 index 000000000000..a04d8afadccd --- /dev/null +++ b/cocos/audio/ohos/AudioResamplerCubic.cpp @@ -0,0 +1,170 @@ +#define LOG_TAG "AudioResamplerCubic" + +#include +#include +#include +#include "cutils/log.h" + +#include "AudioResampler.h" +#include "AudioResamplerCubic.h" + +namespace cocos2d { namespace experimental { +// ---------------------------------------------------------------------------- + +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 { namespace experimental diff --git a/cocos/audio/ohos/AudioResamplerCubic.h b/cocos/audio/ohos/AudioResamplerCubic.h new file mode 100644 index 000000000000..8d692342d56d --- /dev/null +++ b/cocos/audio/ohos/AudioResamplerCubic.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include "AudioResampler.h" +#include "AudioBufferProvider.h" + +namespace cocos2d { namespace experimental { +// ---------------------------------------------------------------------------- + +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 { namespace experimental diff --git a/cocos/audio/ohos/AudioResamplerPublic.h b/cocos/audio/ohos/AudioResamplerPublic.h new file mode 100644 index 000000000000..465e1153caf9 --- /dev/null +++ b/cocos/audio/ohos/AudioResamplerPublic.h @@ -0,0 +1,155 @@ +#pragma once + +#include +#include + +namespace cocos2d { namespace experimental { + +// 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 ? static_cast(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 { namespace experimental + +// --------------------------------------------------------------------------- diff --git a/cocos/audio/ohos/CCThreadPool.cpp b/cocos/audio/ohos/CCThreadPool.cpp new file mode 100644 index 000000000000..7e35a0802f79 --- /dev/null +++ b/cocos/audio/ohos/CCThreadPool.cpp @@ -0,0 +1,376 @@ +/**************************************************************************** + Copyright (c) 2016-2017 Chukong Technologies Inc. + Copyright (c) 2017-2022 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. +****************************************************************************/ + +#include "CCThreadPool.h" +#include +#include + + + +#define LOGD(...) printf(__VA_ARGS__) + + +#define TIME_MINUS(now, prev) (std::chrono::duration_cast((now) - (prev)).count() / 1000.0f) + +namespace cocos2d { namespace experimental { + +#define DEFAULT_THREAD_POOL_MIN_NUM (4) +#define DEFAULT_THREAD_POOL_MAX_NUM (20) + +#define DEFAULT_SHRINK_INTERVAL (5) +#define DEFAULT_SHRINK_STEP (2) +#define DEFAULT_STRETCH_STEP (2) + +LegacyThreadPool *LegacyThreadPool::_instance = nullptr; + +LegacyThreadPool *LegacyThreadPool::getDefaultThreadPool() { + if (LegacyThreadPool::_instance == nullptr) { + LegacyThreadPool::_instance = newCachedThreadPool(DEFAULT_THREAD_POOL_MIN_NUM, + DEFAULT_THREAD_POOL_MAX_NUM, + DEFAULT_SHRINK_INTERVAL, DEFAULT_SHRINK_STEP, + DEFAULT_STRETCH_STEP); + } + + return LegacyThreadPool::_instance; +} + +void LegacyThreadPool::destroyDefaultThreadPool() { + delete LegacyThreadPool::_instance; + LegacyThreadPool::_instance = nullptr; +} + +LegacyThreadPool *LegacyThreadPool::newCachedThreadPool(int minThreadNum, int maxThreadNum, int shrinkInterval, + int shrinkStep, int stretchStep) { + auto *pool = new LegacyThreadPool(minThreadNum, maxThreadNum); + if (pool != nullptr) { + pool->setFixedSize(false); + pool->setShrinkInterval(shrinkInterval); + pool->setShrinkStep(shrinkStep); + pool->setStretchStep(stretchStep); + } + return pool; +} + +LegacyThreadPool *LegacyThreadPool::newFixedThreadPool(int threadNum) { + auto *pool = new LegacyThreadPool(threadNum, threadNum); + if (pool != nullptr) { + pool->setFixedSize(true); + } + return pool; +} + +LegacyThreadPool *LegacyThreadPool::newSingleThreadPool() { + auto *pool = new LegacyThreadPool(1, 1); + if (pool != nullptr) { + pool->setFixedSize(true); + } + return pool; +} + +LegacyThreadPool::LegacyThreadPool(int minNum, int maxNum) +: _minThreadNum(minNum), + _maxThreadNum(maxNum) { + init(); +} + +// the destructor waits for all the functions in the queue to be finished +LegacyThreadPool::~LegacyThreadPool() { + stop(); +} + +// number of idle threads +int LegacyThreadPool::getIdleThreadNum() const { + auto *thiz = const_cast(this); + std::lock_guard lk(thiz->_idleThreadNumMutex); + return _idleThreadNum; +} + +void LegacyThreadPool::init() { + _lastShrinkTime = std::chrono::high_resolution_clock::now(); + + _maxThreadNum = std::max(_minThreadNum, _maxThreadNum); + + _threads.resize(_maxThreadNum); + _abortFlags.resize(_maxThreadNum); + _idleFlags.resize(_maxThreadNum); + _initedFlags.resize(_maxThreadNum); + + for (int i = 0; i < _maxThreadNum; ++i) { + _idleFlags[i] = std::make_shared>(false); + if (i < _minThreadNum) { + _abortFlags[i] = std::make_shared>(false); + setThread(i); + _initedFlags[i] = std::make_shared>(true); + ++_initedThreadNum; + } else { + _abortFlags[i] = std::make_shared>(true); + _initedFlags[i] = std::make_shared>(false); + } + } +} + +bool LegacyThreadPool::tryShrinkPool() { + LOGD("shrink pool, _idleThreadNum = %d \n", getIdleThreadNum()); + + auto before = std::chrono::high_resolution_clock::now(); + + std::vector threadIDsToJoin; + int maxThreadNumToJoin = std::min(_initedThreadNum - _minThreadNum, _shrinkStep); + + for (int i = 0; i < _maxThreadNum; ++i) { + if ((int)threadIDsToJoin.size() >= maxThreadNumToJoin) { + break; + } + + if (*_idleFlags[i]) { + *_abortFlags[i] = true; + threadIDsToJoin.push_back(i); + } + } + + { + // stop the detached threads that were waiting + std::unique_lock lock(_mutex); + _cv.notify_all(); + } + + for (const auto &threadID : threadIDsToJoin) { // wait for the computing threads to finish + if (_threads[threadID]->joinable()) { + _threads[threadID]->join(); + } + + _threads[threadID].reset(); + *_initedFlags[threadID] = false; + --_initedThreadNum; + } + + auto after = std::chrono::high_resolution_clock::now(); + + float seconds = TIME_MINUS(after, before); + + LOGD("shrink %d threads, waste: %f seconds\n", (int)threadIDsToJoin.size(), seconds); + + return (_initedThreadNum <= _minThreadNum); +} + +void LegacyThreadPool::stretchPool(int count) { + auto before = std::chrono::high_resolution_clock::now(); + + int oldThreadCount = _initedThreadNum; + int newThreadCount = 0; + + for (int i = 0; i < _maxThreadNum; ++i) { + if (!*_initedFlags[i]) { + *_abortFlags[i] = false; + setThread(i); + *_initedFlags[i] = true; + ++_initedThreadNum; + + if (++newThreadCount >= count) { + break; + } + } + } + + if (newThreadCount > 0) { + auto after = std::chrono::high_resolution_clock::now(); + float seconds = TIME_MINUS(after, before); + + LOGD("stretch pool from %d to %d, waste %f seconds\n", oldThreadCount, _initedThreadNum, + seconds); + } +} + +void LegacyThreadPool::pushTask(const std::function &runnable, + TaskType type /* = DEFAULT*/) { + if (!_isFixedSize) { + _idleThreadNumMutex.lock(); + int idleNum = _idleThreadNum; + _idleThreadNumMutex.unlock(); + + if (idleNum > _minThreadNum) { + if (_taskQueue.empty()) { + auto now = std::chrono::high_resolution_clock::now(); + float seconds = TIME_MINUS(now, _lastShrinkTime); + if (seconds > _shrinkInterval) { + tryShrinkPool(); + _lastShrinkTime = now; + } + } + } else if (idleNum == 0) { + stretchPool(_stretchStep); + } + } + + auto callback = new std::function([runnable](int tid) { + runnable(tid); + }); + + Task task; + task.type = type; + task.callback = callback; + _taskQueue.push(task); + + { + std::unique_lock lock(_mutex); + _cv.notify_one(); + } +} + +void LegacyThreadPool::stopAllTasks() { + Task task; + while (_taskQueue.pop(task)) { + delete task.callback; // empty the queue + } +} + +void LegacyThreadPool::stopTasksByType(TaskType type) { + Task task; + + std::vector notStopTasks; + notStopTasks.reserve(_taskQueue.size()); + + while (_taskQueue.pop(task)) { + if (task.type == type) { // Delete the task from queue + delete task.callback; + } else { // If task type isn't match, push it into a vector, then insert to task queue again + notStopTasks.push_back(task); + } + } + + if (!notStopTasks.empty()) { + for (const auto &t : notStopTasks) { + _taskQueue.push(t); + } + } +} + +void LegacyThreadPool::joinThread(int tid) { + if (tid < 0 || tid >= (int)_threads.size()) { + LOGD("Invalid thread id %d\n", tid); + return; + } + + // wait for the computing threads to finish + if (*_initedFlags[tid] && _threads[tid]->joinable()) { + _threads[tid]->join(); + *_initedFlags[tid] = false; + --_initedThreadNum; + } +} + +int LegacyThreadPool::getTaskNum() const { + return (int)_taskQueue.size(); +} + +void LegacyThreadPool::setFixedSize(bool isFixedSize) { + _isFixedSize = isFixedSize; +} + +void LegacyThreadPool::setShrinkInterval(int seconds) { + if (seconds >= 0) { + _shrinkInterval = static_cast(seconds); + } +} + +void LegacyThreadPool::setShrinkStep(int step) { + if (step > 0) { + _shrinkStep = step; + } +} + +void LegacyThreadPool::setStretchStep(int step) { + if (step > 0) { + _stretchStep = step; + } +} + +void LegacyThreadPool::stop() { + if (_isDone || _isStop) { + return; + } + + _isDone = true; // give the waiting threads a command to finish + + { + std::unique_lock lock(_mutex); + _cv.notify_all(); // stop all waiting threads + } + + for (int i = 0, n = static_cast(_threads.size()); i < n; ++i) { + joinThread(i); + } + // if there were no threads in the pool but some functors in the queue, the functors are not deleted by the threads + // therefore delete them here + stopAllTasks(); + _threads.clear(); + _abortFlags.clear(); +} + +void LegacyThreadPool::setThread(int tid) { + std::shared_ptr> abortPtr( + _abortFlags[tid]); // a copy of the shared ptr to the flag + auto f = [this, tid, abortPtr /* a copy of the shared ptr to the abort */]() { + std::atomic &abort = *abortPtr; + Task task; + bool isPop = _taskQueue.pop(task); + while (true) { + while (isPop) { // if there is anything in the queue + std::unique_ptr> func( + task.callback); // at return, delete the function even if an exception occurred + (*task.callback)(tid); + if (abort) { + return; // the thread is wanted to stop, return even if the queue is not empty yet + } + + isPop = _taskQueue.pop(task); + } + // the queue is empty here, wait for the next command + std::unique_lock lock(_mutex); + _idleThreadNumMutex.lock(); + ++_idleThreadNum; + _idleThreadNumMutex.unlock(); + + *_idleFlags[tid] = true; + _cv.wait(lock, [this, &task, &isPop, &abort]() { + isPop = _taskQueue.pop(task); + return isPop || _isDone || abort; + }); + *_idleFlags[tid] = false; + _idleThreadNumMutex.lock(); + --_idleThreadNum; + _idleThreadNumMutex.unlock(); + + if (!isPop) { + return; // if the queue is empty and isDone == true or *flag then return + } + } + }; + _threads[tid].reset( + new std::thread(f)); // compiler may not support std::make_unique() +} + +}} // namespace cocos2d { namespace experimental diff --git a/cocos/audio/ohos/CCThreadPool.h b/cocos/audio/ohos/CCThreadPool.h new file mode 100644 index 000000000000..24d15b76c3f8 --- /dev/null +++ b/cocos/audio/ohos/CCThreadPool.h @@ -0,0 +1,218 @@ +/**************************************************************************** + Copyright (c) 2016-2017 Chukong Technologies Inc. + Copyright (c) 2017-2022 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. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils/Utils.h" + + +namespace cocos2d { namespace experimental { + +class LegacyThreadPool { +public: + enum class TaskType { + DEFAULT = 0, + NETWORK, + IO, + AUDIO, + USER = 1000 + }; + + /* + * Gets the default thread pool which is a cached thread pool with default parameters. + */ + static LegacyThreadPool *getDefaultThreadPool(); + + /* + * Destroys the default thread pool + */ + static void destroyDefaultThreadPool(); + + /* + * Creates a cached thread pool + * @note The return value has to be delete while it doesn't needed + */ + static LegacyThreadPool *newCachedThreadPool(int minThreadNum, int maxThreadNum, int shrinkInterval, + int shrinkStep, int stretchStep); + + /* + * Creates a thread pool with fixed thread count + * @note The return value has to be delete while it doesn't needed + */ + static LegacyThreadPool *newFixedThreadPool(int threadNum); + + /* + * Creates a thread pool with only one thread in the pool, it could be used to execute multiply tasks serially in just one thread. + * @note The return value has to be delete while it doesn't needed + */ + static LegacyThreadPool *newSingleThreadPool(); + + // the destructor waits for all the functions in the queue to be finished + ~LegacyThreadPool(); + + /* Pushs a task to thread pool + * @param runnable The callback of the task executed in sub thread + * @param type The task type, it's TASK_TYPE_DEFAULT if this argument isn't assigned + * @note This function has to be invoked in cocos thread + */ + void pushTask(const std::function &runnable, TaskType type = TaskType::DEFAULT); + + // Stops all tasks, it will remove all tasks in queue + void stopAllTasks(); + + // Stops some tasks by type + void stopTasksByType(TaskType type); + + // Gets the minimum thread numbers + inline int getMinThreadNum() const { return _minThreadNum; }; + + // Gets the maximum thread numbers + inline int getMaxThreadNum() const { return _maxThreadNum; }; + + // Gets the number of idle threads + int getIdleThreadNum() const; + + // Gets the number of initialized threads + inline int getInitedThreadNum() const { return _initedThreadNum; }; + + // Gets the task number + int getTaskNum() const; + + /* + * Trys to shrink pool + * @note This method is only available for cached thread pool + */ + bool tryShrinkPool(); + +private: + LegacyThreadPool(int minNum, int maxNum); + + LegacyThreadPool(const LegacyThreadPool &); + + LegacyThreadPool(LegacyThreadPool &&) noexcept; + + LegacyThreadPool &operator=(const LegacyThreadPool &); + + LegacyThreadPool &operator=(LegacyThreadPool &&) noexcept; + + void init(); + + void stop(); + + void setThread(int tid); + + void joinThread(int tid); + + void setFixedSize(bool isFixedSize); + + void setShrinkInterval(int seconds); + + void setShrinkStep(int step); + + void setStretchStep(int step); + + void stretchPool(int count); + + std::vector> _threads; + std::vector>> _abortFlags; + std::vector>> _idleFlags; + std::vector>> _initedFlags; + + template + class ThreadSafeQueue { + public: + bool push(T const &value) { + std::unique_lock lock(this->mutex); + this->q.push(value); + return true; + } + + // deletes the retrieved element, do not use for non integral types + bool pop(T &v) { + std::unique_lock lock(this->mutex); + if (this->q.empty()) + return false; + v = this->q.front(); + this->q.pop(); + return true; + } + + bool empty() const { + auto thiz = const_cast(this); + std::unique_lock lock(thiz->mutex); + return this->q.empty(); + } + + size_t size() const { + auto thiz = const_cast(this); + std::unique_lock lock(thiz->mutex); + return this->q.size(); + } + + private: + std::queue q; + std::mutex mutex; + }; + + struct Task { + TaskType type; + std::function *callback; + }; + + static LegacyThreadPool *_instance; + + ThreadSafeQueue _taskQueue; + std::atomic _isDone{false}; + std::atomic _isStop{false}; + + //IDEA: std::atomic isn't supported by ndk-r10e while compiling with `armeabi` arch. + // So using a mutex here instead. + int _idleThreadNum{0}; // how many threads are waiting + std::mutex _idleThreadNumMutex; + + std::mutex _mutex; + std::condition_variable _cv; + + int _minThreadNum{0}; + int _maxThreadNum{0}; + int _initedThreadNum{0}; + + std::chrono::time_point _lastShrinkTime; + float _shrinkInterval{5}; + int _shrinkStep{2}; + int _stretchStep{2}; + bool _isFixedSize{false}; +}; + +}} // namespace cocos2d { namespace experimental diff --git a/cocos/audio/ohos/IAudioPlayer.h b/cocos/audio/ohos/IAudioPlayer.h new file mode 100644 index 000000000000..ed14ca4be54e --- /dev/null +++ b/cocos/audio/ohos/IAudioPlayer.h @@ -0,0 +1,87 @@ +/**************************************************************************** +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 +#include + +namespace cocos2d { namespace experimental { + +class IAudioPlayer { +public: + enum class State { + INVALID = 0, + INITIALIZED, + PLAYING, + PAUSED, + STOPPED, + OVER + }; + + using PlayEventCallback = std::function; + + 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 { namespace experimental diff --git a/cocos/audio/ohos/ICallerThreadUtils.h b/cocos/audio/ohos/ICallerThreadUtils.h new file mode 100644 index 000000000000..c729752e8b6a --- /dev/null +++ b/cocos/audio/ohos/ICallerThreadUtils.h @@ -0,0 +1,40 @@ +/**************************************************************************** +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 +#include + +namespace cocos2d { namespace experimental { + +class ICallerThreadUtils { +public: + virtual ~ICallerThreadUtils(){}; + + virtual void performFunctionInCallerThread(const std::function &func) = 0; + virtual std::thread::id getCallerThreadId() = 0; +}; + +}} // namespace cocos2d { namespace experimental diff --git a/cocos/audio/ohos/IVolumeProvider.h b/cocos/audio/ohos/IVolumeProvider.h new file mode 100644 index 000000000000..bb6b41ef6fe6 --- /dev/null +++ b/cocos/audio/ohos/IVolumeProvider.h @@ -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 "audio_utils/include/audio_utils//minifloat.h" + +namespace cocos2d { namespace experimental { + +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 { namespace experimental diff --git a/cocos/audio/ohos/Macros.h b/cocos/audio/ohos/Macros.h new file mode 100644 index 000000000000..bc7cebbad166 --- /dev/null +++ b/cocos/audio/ohos/Macros.h @@ -0,0 +1,415 @@ +/**************************************************************************** + Copyright (c) 2008-2010 Ricardo Quesada + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2011 Zynga Inc. + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2022 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. +****************************************************************************/ + +#pragma once + +#include // To include uint8_t, uint16_t and so on. + +#include +#define CC_ASSERT(cond) assert(cond) + +#define CC_AUDIO_SAFE_DELETE(p) do { if(p) { delete (p); (p) = 0; } } while(0) + +#if (CC_PLATFORM == CC_PLATFORM_WINDOWS) + #if defined(CC_STATIC) + #define CC_DLL + #else + #if defined(_USRDLL) + #define CC_DLL __declspec(dllexport) + #else /* use a DLL library */ + #define CC_DLL __declspec(dllimport) + #endif + #endif +#else + #define CC_DLL +#endif + +/** @def CC_DEGREES_TO_RADIANS + converts degrees to radians + */ +#define CC_DEGREES_TO_RADIANS(__ANGLE__) ((__ANGLE__)*0.01745329252f) // PI / 180 + +/** @def CC_RADIANS_TO_DEGREES + converts radians to degrees + */ +#define CC_RADIANS_TO_DEGREES(__ANGLE__) ((__ANGLE__)*57.29577951f) // PI * 180 + +#ifndef FLT_EPSILON + #define FLT_EPSILON 1.192092896e-07F +#endif // FLT_EPSILON + +/** +Helper macros which converts 4-byte little/big endian +integral number to the machine native number representation + +It should work same as apples CFSwapInt32LittleToHost(..) +*/ + +/// when define returns true it means that our architecture uses big endian +#define CC_HOST_IS_BIG_ENDIAN (bool)(*(unsigned short *)"\0\xff" < 0x100) +#define CC_SWAP32(i) ((i & 0x000000ff) << 24 | (i & 0x0000ff00) << 8 | (i & 0x00ff0000) >> 8 | (i & 0xff000000) >> 24) +#define CC_SWAP16(i) ((i & 0x00ff) << 8 | (i & 0xff00) >> 8) +#define CC_SWAP_INT32_LITTLE_TO_HOST(i) ((CC_HOST_IS_BIG_ENDIAN == true) ? CC_SWAP32(i) : (i)) +#define CC_SWAP_INT16_LITTLE_TO_HOST(i) ((CC_HOST_IS_BIG_ENDIAN == true) ? CC_SWAP16(i) : (i)) +#define CC_SWAP_INT32_BIG_TO_HOST(i) ((CC_HOST_IS_BIG_ENDIAN == true) ? (i) : CC_SWAP32(i)) +#define CC_SWAP_INT16_BIG_TO_HOST(i) ((CC_HOST_IS_BIG_ENDIAN == true) ? (i) : CC_SWAP16(i)) + +// new callbacks based on C++11 +#define CC_CALLBACK_0(__selector__, __target__, ...) std::bind(&__selector__, __target__, ##__VA_ARGS__) +#define CC_CALLBACK_1(__selector__, __target__, ...) std::bind(&__selector__, __target__, std::placeholders::_1, ##__VA_ARGS__) +#define CC_CALLBACK_2(__selector__, __target__, ...) std::bind(&__selector__, __target__, std::placeholders::_1, std::placeholders::_2, ##__VA_ARGS__) +#define CC_CALLBACK_3(__selector__, __target__, ...) std::bind(&__selector__, __target__, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, ##__VA_ARGS__) + +// Generic macros + +#define CC_BREAK_IF(cond) \ + if (cond) break + +/** @def CC_DEPRECATED_ATTRIBUTE +* Only certain compilers support __attribute__((deprecated)). +*/ +#if defined(__GNUC__) && ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1))) + #define CC_DEPRECATED_ATTRIBUTE __attribute__((deprecated)) +#elif _MSC_VER >= 1400 //vs 2005 or higher + #define CC_DEPRECATED_ATTRIBUTE __declspec(deprecated) +#else + #define CC_DEPRECATED_ATTRIBUTE +#endif + +/** @def CC_DEPRECATED(...) +* Macro to mark things deprecated as of a particular version +* can be used with arbitrary parameters which are thrown away. +* e.g. CC_DEPRECATED(4.0) or CC_DEPRECATED(4.0, "not going to need this anymore") etc. +*/ +#define CC_DEPRECATED(...) CC_DEPRECATED_ATTRIBUTE + +#ifdef __GNUC__ + #define CC_UNUSED __attribute__((unused)) +#else + #define CC_UNUSED +#endif + +#define CC_UNUSED_PARAM(unusedparam) (void)unusedparam + +/** @def CC_FORMAT_PRINTF(formatPos, argPos) + * Only certain compiler support __attribute__((format)) + * + * @param formatPos 1-based position of format string argument. + * @param argPos 1-based position of first format-dependent argument. + */ +#if defined(__GNUC__) && (__GNUC__ >= 4) + #define CC_FORMAT_PRINTF(formatPos, argPos) __attribute__((__format__(printf, formatPos, argPos))) +#elif defined(__has_attribute) + #if __has_attribute(format) + #define CC_FORMAT_PRINTF(formatPos, argPos) __attribute__((__format__(printf, formatPos, argPos))) + #else + #define CC_FORMAT_PRINTF(formatPos, argPos) + #endif // __has_attribute(format) +#else + #define CC_FORMAT_PRINTF(formatPos, argPos) +#endif + +// Initial compiler-related stuff to set. +#define CC_COMPILER_MSVC 1 +#define CC_COMPILER_CLANG 2 +#define CC_COMPILER_GNUC 3 + +// CPU Architecture +#define CC_CPU_UNKNOWN 0 +#define CC_CPU_X86 1 +#define CC_CPU_PPC 2 +#define CC_CPU_ARM 3 +#define CC_CPU_MIPS 4 + +// 32-bits or 64-bits CPU +#define CC_CPU_ARCH_32 1 +#define CC_CPU_ARCH_64 2 + +// Mode +#define CC_MODE_DEBUG 1 +#define CC_MODE_RELEASE 2 + +// Compiler type and version recognition +#if defined(_MSC_VER) + #define CC_COMPILER CC_COMPILER_MSVC +#elif defined(__clang__) + #define CC_COMPILER CC_COMPILER_CLANG +#elif defined(__GNUC__) + #define CC_COMPILER CC_COMPILER_GNUC +#else + #error "Unknown compiler. Abort!" +#endif + +#if INTPTR_MAX == INT32_MAX + #define CC_CPU_ARCH CC_CPU_ARCH_32 +#else + #define CC_CPU_ARCH CC_CPU_ARCH_64 +#endif + +#if defined(__arm64__) || defined(__aarch64__) + #define CC_ARCH_ARM64 1 +#else + #define CC_ARCH_ARM64 0 +#endif + +// CC_HAS_ARM64_FP16 set to 1 if the architecture provides an IEEE compliant ARM fp16 type +#if CC_ARCH_ARM64 + #ifndef CC_HAS_ARM64_FP16 + #if defined(__ARM_FP16_FORMAT_IEEE) + #define CC_HAS_ARM64_FP16 1 + #else + #define CC_HAS_ARM64_FP16 0 + #endif + #endif +#endif + +// CC_HAS_ARM64_FP16_VECTOR_ARITHMETIC set to 1 if the architecture supports Neon vector intrinsics for fp16. +#if CC_ARCH_ARM64 + #ifndef CC_HAS_ARM64_FP16_VECTOR_ARITHMETIC + #if defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC) + #define CC_HAS_ARM64_FP16_VECTOR_ARITHMETIC 1 + #else + #define CC_HAS_ARM64_FP16_VECTOR_ARITHMETIC 0 + #endif + #endif +#endif + +// CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC set to 1 if the architecture supports Neon scalar intrinsics for fp16. +#if CC_ARCH_ARM64 + #ifndef CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC + #if defined(__ARM_FEATURE_FP16_SCALAR_ARITHMETIC) + #define CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC 1 + #else + #define CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC 0 + #endif + #endif +#endif + +// Disable MSVC warning +#if (CC_COMPILER == CC_COMPILER_MSVC) + #pragma warning(disable : 4251 4275 4819) + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + #ifndef _SCL_SECURE_NO_DEPRECATE + #define _SCL_SECURE_NO_DEPRECATE + #endif +#endif + +#define CC_CACHELINE_SIZE 64 + +#if (CC_COMPILER == CC_COMPILER_MSVC) + // MSVC ENABLE/DISABLE WARNING DEFINITION + #define CC_DISABLE_WARNINGS() \ + __pragma(warning(push, 0)) + + #define CC_ENABLE_WARNINGS() \ + __pragma(warning(pop)) +#elif (CC_COMPILER == CC_COMPILER_GNUC) + // GCC ENABLE/DISABLE WARNING DEFINITION + #define CC_DISABLE_WARNINGS() \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wall\"") \ + _Pragma("clang diagnostic ignored \"-Wextra\"") \ + _Pragma("clang diagnostic ignored \"-Wtautological-compare\"") + + #define CC_ENABLE_WARNINGS() \ + _Pragma("GCC diagnostic pop") +#elif (CC_COMPILER == CC_COMPILER_CLANG) + // CLANG ENABLE/DISABLE WARNING DEFINITION + #define CC_DISABLE_WARNINGS() \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wall\"") \ + _Pragma("clang diagnostic ignored \"-Wextra\"") \ + _Pragma("clang diagnostic ignored \"-Wtautological-compare\"") + + #define CC_ENABLE_WARNINGS() \ + _Pragma("clang diagnostic pop") +#endif + +#define CC_DISALLOW_ASSIGN(TypeName) \ + TypeName &operator=(const TypeName &) = delete; \ + TypeName &operator=(TypeName &&) = delete; + +#define CC_DISALLOW_COPY_MOVE_ASSIGN(TypeName) \ + TypeName(const TypeName &) = delete; \ + TypeName(TypeName &&) = delete; \ + CC_DISALLOW_ASSIGN(TypeName) + +#if (CC_COMPILER == CC_COMPILER_MSVC) + #define CC_ALIGN(N) __declspec(align(N)) + #define CC_CACHE_ALIGN __declspec(align(CC_CACHELINE_SIZE)) + #define CC_PACKED_ALIGN(N) __declspec(align(N)) + + #define CC_ALIGNED_DECL(type, var, alignment) __declspec(align(alignment)) type var + + #define CC_READ_COMPILER_BARRIER() _ReadBarrier() + #define CC_WRITE_COMPILER_BARRIER() _WriteBarrier() + #define CC_COMPILER_BARRIER() _ReadWriteBarrier() + + #define CC_READ_MEMORY_BARRIER() MemoryBarrier() + #define CC_WRITE_MEMORY_BARRIER() MemoryBarrier() + #define CC_MEMORY_BARRIER() MemoryBarrier() + + #define CC_CPU_READ_MEMORY_BARRIER() \ + do { \ + __asm { lfence} \ + } while (0) + #define CC_CPU_WRITE_MEMORY_BARRIER() \ + do { \ + __asm { sfence} \ + } while (0) + #define CC_CPU_MEMORY_BARRIER() \ + do { \ + __asm { mfence} \ + } while (0) + +#elif (CC_COMPILER == CC_COMPILER_GNUC) || (CC_COMPILER == CC_COMPILER_CLANG) + #define CC_ALIGN(N) __attribute__((__aligned__((N)))) + #define CC_CACHE_ALIGN __attribute__((__aligned__((CC_CACHELINE_SIZE)))) + #define CC_PACKED_ALIGN(N) __attribute__((packed, aligned(N))) + + #define CC_ALIGNED_DECL(type, var, alignment) type var __attribute__((__aligned__(alignment))) + + #define CC_READ_COMPILER_BARRIER() \ + do { \ + __asm__ __volatile__("" \ + : \ + : \ + : "memory"); \ + } while (0) + #define CC_WRITE_COMPILER_BARRIER() \ + do { \ + __asm__ __volatile__("" \ + : \ + : \ + : "memory"); \ + } while (0) + #define CC_COMPILER_BARRIER() \ + do { \ + __asm__ __volatile__("" \ + : \ + : \ + : "memory"); \ + } while (0) + + #define CC_READ_MEMORY_BARRIER() \ + do { \ + __sync_synchronize(); \ + } while (0) + #define CC_WRITE_MEMORY_BARRIER() \ + do { \ + __sync_synchronize(); \ + } while (0) + #define CC_MEMORY_BARRIER() \ + do { \ + __sync_synchronize(); \ + } while (0) + + #define CC_CPU_READ_MEMORY_BARRIER() \ + do { \ + __asm__ __volatile__("lfence" \ + : \ + : \ + : "memory"); \ + } while (0) + #define CC_CPU_WRITE_MEMORY_BARRIER() \ + do { \ + __asm__ __volatile__("sfence" \ + : \ + : \ + : "memory"); \ + } while (0) + #define CC_CPU_MEMORY_BARRIER() \ + do { \ + __asm__ __volatile__("mfence" \ + : \ + : \ + : "memory"); \ + } while (0) + +#else + #error "Unsupported compiler!" +#endif + +/* Stack-alignment + If macro __CC_SIMD_ALIGN_STACK defined, means there requests + special code to ensure stack align to a 16-bytes boundary. + + Note: + This macro can only guarantee callee stack pointer (esp) align + to a 16-bytes boundary, but not that for frame pointer (ebp). + Because most compiler might use frame pointer to access to stack + variables, so you need to wrap those alignment required functions + with extra function call. + */ +#if defined(__INTEL_COMPILER) + // For intel's compiler, simply calling alloca seems to do the right + // thing. The size of the allocated block seems to be irrelevant. + #define CC_SIMD_ALIGN_STACK() _alloca(16) + #define CC_SIMD_ALIGN_ATTRIBUTE + +#elif (CC_CPU == CC_CPU_X86) && (CC_COMPILER == CC_COMPILER_GNUC || CC_COMPILER == CC_COMPILER_CLANG) && (CC_CPU_ARCH != CC_CPU_ARCH_64) + // mark functions with GCC attribute to force stack alignment to 16 bytes + #define CC_SIMD_ALIGN_ATTRIBUTE __attribute__((force_align_arg_pointer)) +#elif (CC_COMPILER == CC_COMPILER_MSVC) + // Fortunately, MSVC will align the stack automatically + #define CC_SIMD_ALIGN_ATTRIBUTE +#else + #define CC_SIMD_ALIGN_ATTRIBUTE +#endif + +// mode +#if (defined(_DEBUG) || defined(DEBUG)) && (CC_PLATFORM == CC_PLATFORM_WINDOWS) + #define CC_MODE CC_MODE_DEBUG +#else + #define CC_MODE CC_MODE_RELEASE +#endif + +#define CC_TOSTR(s) #s + +#if defined(__GNUC__) && __GNUC__ >= 4 + #define CC_PREDICT_TRUE(x) __builtin_expect(!!(x), 1) + #define CC_PREDICT_FALSE(x) __builtin_expect(!!(x), 0) +#else + #define CC_PREDICT_TRUE(x) (x) + #define CC_PREDICT_FALSE(x) (x) +#endif + +#if defined(_MSC_VER) + #define CC_FORCE_INLINE __forceinline +#elif defined(__GNUC__) || defined(__clang__) + #define CC_FORCE_INLINE inline __attribute__((always_inline)) +#else + #if defined(__cplusplus) || defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ + #define CC_FORCE_INLINE static inline + #elif + #define CC_FORCE_INLINE inline + #endif +#endif diff --git a/cocos/audio/ohos/OpenSLHelper.h b/cocos/audio/ohos/OpenSLHelper.h new file mode 100644 index 000000000000..aefce32f5ba7 --- /dev/null +++ b/cocos/audio/ohos/OpenSLHelper.h @@ -0,0 +1,105 @@ +/**************************************************************************** +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 "cutils/log.h" + +#include + +#include + + +#include +#include + +#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 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(std::chrono::duration_cast((newTime) - (oldTime)).count()) / 1000.f) + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) diff --git a/cocos/audio/ohos/PcmAudioPlayer.cpp b/cocos/audio/ohos/PcmAudioPlayer.cpp new file mode 100644 index 000000000000..c7e0faa0ae7b --- /dev/null +++ b/cocos/audio/ohos/PcmAudioPlayer.cpp @@ -0,0 +1,194 @@ +/**************************************************************************** +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 "PcmAudioPlayer.h" +#include "AudioMixerController.h" +#include "ICallerThreadUtils.h" +#include "cutils/log.h" + + +namespace cocos2d { namespace experimental { + +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 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(); + ALOGE("PcmAudioPlayer %{public}p onStateChanged: preState = %{public}d, state = %{public}d", this, prevState, state); + 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 (%{public}p) play, url: %{public}s", this, _url.c_str()); + _controller->addTrack(_track); + _track->setState(Track::State::PLAYING); +} + +void PcmAudioPlayer::pause() { + ALOGV("PcmAudioPlayer (%{public}p) pause, url: %{public}s", this, _url.c_str()); + _track->setState(Track::State::PAUSED); +} + +void PcmAudioPlayer::resume() { + ALOGV("PcmAudioPlayer (%{public}p) resume, url: %{public}s", this, _url.c_str()); + _track->setState(Track::State::RESUMED); +} + +void PcmAudioPlayer::stop() { + ALOGV("PcmAudioPlayer (%{public}p) stop, url: %{public}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 { namespace experimental diff --git a/cocos/audio/ohos/PcmAudioPlayer.h b/cocos/audio/ohos/PcmAudioPlayer.h new file mode 100644 index 000000000000..9a1c7b628c90 --- /dev/null +++ b/cocos/audio/ohos/PcmAudioPlayer.h @@ -0,0 +1,96 @@ +/**************************************************************************** +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 +#include "IAudioPlayer.h" +#include "PcmData.h" +#include "Track.h" + +namespace cocos2d { namespace experimental { + +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 + + + 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 { namespace experimental diff --git a/cocos/audio/ohos/PcmAudioService.cpp b/cocos/audio/ohos/PcmAudioService.cpp new file mode 100644 index 000000000000..ff33a876c7b0 --- /dev/null +++ b/cocos/audio/ohos/PcmAudioService.cpp @@ -0,0 +1,159 @@ +/**************************************************************************** +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 "Macros.h" +#include "PcmAudioService.h" +#include "AudioMixerController.h" +#include "utils/Compat.h" + +namespace cocos2d { namespace experimental { + + +static std::vector __silenceData;//NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + +PcmAudioService::PcmAudioService() +: _controller(nullptr) { +} + +PcmAudioService::~PcmAudioService() { + ALOGV("PcmAudioService() (%p), before destroy play object", this); + if (_audioRenderer != nullptr) { + OH_AudioRenderer_Stop(_audioRenderer); + OH_AudioRenderer_Release(_audioRenderer); + } + + if (_builder != nullptr) { + OH_AudioStreamBuilder_Destroy(_builder); + } + ALOGV("PcmAudioService() end"); +} + +int32_t PcmAudioService::AudioRendererOnWriteData(OH_AudioRenderer* renderer, + void* userData, + void* buffer, + int32_t bufferLen) +{ + auto *thiz = reinterpret_cast(userData); + if (bufferLen != thiz->_bufferSizeInBytes) { + __silenceData.resize(bufferLen, 0x00); + thiz->_bufferSizeInBytes = bufferLen; + thiz->_controller->updateBufferSize(thiz->_bufferSizeInBytes); + } + + if (thiz->_controller->hasPlayingTacks()) { + if (thiz->_controller->isPaused()) { + memcpy(buffer, __silenceData.data(), bufferLen); + } else { + + thiz->_controller->mixOneFrame(); + auto *current = thiz->_controller->current(); + ALOG_ASSERT(current != nullptr, "current buffer is nullptr ..."); + memcpy(buffer, current->buf, current->size < bufferLen ? current->size : bufferLen); + } + } else { + memcpy(buffer, __silenceData.data(), bufferLen); + } + + return 0; +} + +int32_t PcmAudioService::AudioRendererOnInterrupt(OH_AudioRenderer* renderer, + void* userData, + OH_AudioInterrupt_ForceType type, + OH_AudioInterrupt_Hint hint) +{ + auto *thiz = reinterpret_cast(userData); + if (thiz->_audioRenderer != nullptr) { + if (hint == AUDIOSTREAM_INTERRUPT_HINT_RESUME) { + OH_AudioRenderer_Start(thiz->_audioRenderer); + } else if (hint == AUDIOSTREAM_INTERRUPT_HINT_PAUSE) { + OH_AudioRenderer_Pause(thiz->_audioRenderer); + } + } + return 0; +} + +bool PcmAudioService::init(AudioMixerController *controller, int numChannels, int sampleRate, int *bufferSizeInBytes) { + _controller = controller; + + OH_AudioStream_Result ret; + OH_AudioStream_Type type = AUDIOSTREAM_TYPE_RENDERER; + ret = OH_AudioStreamBuilder_Create(&_builder, type); + if (ret != AUDIOSTREAM_SUCCESS) { + return false; + } + + OH_AudioStreamBuilder_SetSamplingRate(_builder, sampleRate); + OH_AudioStreamBuilder_SetChannelCount(_builder, numChannels); + OH_AudioStreamBuilder_SetLatencyMode(_builder, AUDIOSTREAM_LATENCY_MODE_FAST); + OH_AudioStreamBuilder_SetRendererInfo(_builder, AUDIOSTREAM_USAGE_GAME); + + OH_AudioRenderer_Callbacks callbacks; + callbacks.OH_AudioRenderer_OnWriteData = AudioRendererOnWriteData; + callbacks.OH_AudioRenderer_OnInterruptEvent = AudioRendererOnInterrupt; + callbacks.OH_AudioRenderer_OnError = nullptr; + callbacks.OH_AudioRenderer_OnStreamEvent = nullptr; + ret = OH_AudioStreamBuilder_SetRendererCallback(_builder, callbacks, this); + if (ret != AUDIOSTREAM_SUCCESS) { + return false; + } + + ret = OH_AudioStreamBuilder_GenerateRenderer(_builder, &_audioRenderer); + if (ret != AUDIOSTREAM_SUCCESS) { + return false; + } + + int32_t buffer_size; + OH_AudioRenderer_GetFrameSizeInCallback(_audioRenderer, &buffer_size); + _bufferSizeInBytes = buffer_size * numChannels * 2; + *bufferSizeInBytes = buffer_size; + + if (__silenceData.empty()) { + __silenceData.resize(_bufferSizeInBytes, 0x00); + } + + ret = OH_AudioRenderer_Start(_audioRenderer); + if (ret != AUDIOSTREAM_SUCCESS) { + return false; + } + + return true; +} + +void PcmAudioService::pause() { + if (_audioRenderer != nullptr) { + OH_AudioRenderer_Pause(_audioRenderer); + } +} + +void PcmAudioService::resume() { + if (_audioRenderer != nullptr) { + OH_AudioRenderer_Start(_audioRenderer); + } +} + +}} // namespace cocos2d { namespace experimental diff --git a/cocos/audio/ohos/PcmAudioService.h b/cocos/audio/ohos/PcmAudioService.h new file mode 100644 index 000000000000..3f090d14dd7e --- /dev/null +++ b/cocos/audio/ohos/PcmAudioService.h @@ -0,0 +1,71 @@ +/**************************************************************************** +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 "IAudioPlayer.h" +#include "PcmData.h" + +#include +#include +#include "utils/Compat.h" +#include "cutils/log.h" +#include +#include + +namespace cocos2d { namespace experimental { + +class AudioMixerController; + +class PcmAudioService { +public: + inline int getChannelCount() const { return _numChannels; }; + + inline int getSampleRate() const { return _sampleRate; }; + + + PcmAudioService(); + + virtual ~PcmAudioService(); + + bool init(AudioMixerController* controller, int numChannels, int sampleRate, int* bufferSizeInBytes); + static int32_t AudioRendererOnWriteData(OH_AudioRenderer* renderer, void* userData, void* buffer, int32_t bufferLen); + static int32_t AudioRendererOnInterrupt(OH_AudioRenderer* renderer, void* userData, OH_AudioInterrupt_ForceType type, OH_AudioInterrupt_Hint hint); + + void pause(); + void resume(); + + int _numChannels; + int _sampleRate; + int _bufferSizeInBytes; + + AudioMixerController *_controller; + OH_AudioRenderer *_audioRenderer; + OH_AudioStreamBuilder *_builder; + + friend class AudioPlayerProvider; +}; + +}} // namespace cocos2d { namespace experimental diff --git a/cocos/audio/ohos/PcmBufferProvider.cpp b/cocos/audio/ohos/PcmBufferProvider.cpp new file mode 100644 index 000000000000..06573c9f8c09 --- /dev/null +++ b/cocos/audio/ohos/PcmBufferProvider.cpp @@ -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 "PcmBufferProvider.h" +#include "cutils/log.h" + +//#define VERY_VERY_VERBOSE_LOGGING +#ifdef VERY_VERY_VERBOSE_LOGGING + #define ALOGVV ALOGV +#else + #define ALOGVV(a...) \ + do { \ + } while (0) +#endif + +namespace cocos2d { namespace experimental { + +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 { namespace experimental diff --git a/cocos/audio/ohos/PcmBufferProvider.h b/cocos/audio/ohos/PcmBufferProvider.h new file mode 100644 index 000000000000..48f612aef366 --- /dev/null +++ b/cocos/audio/ohos/PcmBufferProvider.h @@ -0,0 +1,51 @@ +/**************************************************************************** +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 "AudioBufferProvider.h" + +#include +#include + +namespace cocos2d { namespace experimental { + +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 { namespace experimental diff --git a/cocos/audio/ohos/PcmData.cpp b/cocos/audio/ohos/PcmData.cpp new file mode 100644 index 000000000000..d2a8d79720a1 --- /dev/null +++ b/cocos/audio/ohos/PcmData.cpp @@ -0,0 +1,128 @@ +/**************************************************************************** +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 "PcmData.h" +#include "OpenSLHelper.h" + +namespace cocos2d { namespace experimental { + +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 { namespace experimental diff --git a/cocos/audio/ohos/PcmData.h b/cocos/audio/ohos/PcmData.h new file mode 100644 index 000000000000..65a5189debf9 --- /dev/null +++ b/cocos/audio/ohos/PcmData.h @@ -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 + +#include +#include +#include +#include + +namespace cocos2d { namespace experimental { + +struct PcmData { + std::shared_ptr> 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 { namespace experimental diff --git a/cocos/audio/ohos/SimpleAudioEngine.cpp b/cocos/audio/ohos/SimpleAudioEngine.cpp new file mode 100644 index 000000000000..30b0c81b12b5 --- /dev/null +++ b/cocos/audio/ohos/SimpleAudioEngine.cpp @@ -0,0 +1,220 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org + +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 +#include "../include/SimpleAudioEngine.h" +#include "platform/ohos/CCFileUtils-ohos.h" +#include "audio/include/AudioEngine.h" +#include "cutils/log.h" +#include + +using namespace cocos2d::experimental; + +namespace CocosDenshion { + +static int _lastBackGroundAudioID = -1; +static float _backGroundVolume = 1.0; +static std::list _effectAudioIDs; +static float _effectsVolume = 1.0; + +static std::string getFullPathWithoutAssetsPrefix(const char* pszFilename) +{ + std::string fullPath = cocos2d::FileUtils::sharedFileUtils()->fullPathForFilename(pszFilename); + size_t pos = fullPath.find("hap:/"); + if (pos == 0) + { + fullPath = fullPath.substr(strlen("hap:/")); + } + return fullPath; +} + +static SimpleAudioEngine *s_pEngine = 0; + +SimpleAudioEngine::SimpleAudioEngine() +{ +} + +SimpleAudioEngine::~SimpleAudioEngine() +{ +} + +SimpleAudioEngine* SimpleAudioEngine::getInstance() +{ + if (! s_pEngine) + { + s_pEngine = new SimpleAudioEngine(); + } + + return s_pEngine; +} + +void SimpleAudioEngine::end() +{ + cocos2d::experimental::AudioEngine::end(); +} + +void SimpleAudioEngine::preloadBackgroundMusic(const char* pszFilePath) +{ + ALOGD("preloadBackgroundMusic start!"); + std::string uri(getFullPathWithoutAssetsPrefix(pszFilePath)); +} + +void SimpleAudioEngine::playBackgroundMusic(const char* pszFilePath, bool bLoop) +{ + if(_lastBackGroundAudioID != -1) { + stopBackgroundMusic(); + } + ALOGD("playBackgroundMusic: %{public}s", pszFilePath); + std::string uri(getFullPathWithoutAssetsPrefix(pszFilePath)); + _lastBackGroundAudioID = AudioEngine::play2d(uri, bLoop, _backGroundVolume); + ALOGD("playBackgroundMusic %{public}s end, and id:%{public}d", uri.c_str(), _lastBackGroundAudioID); +} + +void SimpleAudioEngine::stopBackgroundMusic(bool bReleaseData) +{ + ALOGD("stopBackgroundMusic"); + cocos2d::experimental::AudioEngine::stop(_lastBackGroundAudioID); + _lastBackGroundAudioID = -1; + ALOGD("stopBackgroundMusic end, and id:%{public}d", _lastBackGroundAudioID); +} + +void SimpleAudioEngine::pauseBackgroundMusic() +{ + ALOGD("pauseBackgroundMusic"); + cocos2d::experimental::AudioEngine::pause(_lastBackGroundAudioID); +} + +void SimpleAudioEngine::resumeBackgroundMusic() +{ + ALOGD("resumeBackgroundMusic start!"); + cocos2d::experimental::AudioEngine::resume(_lastBackGroundAudioID); +} + +void SimpleAudioEngine::rewindBackgroundMusic() +{ + ALOGD("rewindBackgroundMusic"); + cocos2d::experimental::AudioEngine::setCurrentTime(_lastBackGroundAudioID, 0); +} + +bool SimpleAudioEngine::willPlayBackgroundMusic() +{ + return _lastBackGroundAudioID != -1; +} + +bool SimpleAudioEngine::isBackgroundMusicPlaying() +{ + return cocos2d::experimental::AudioEngine::getState(_lastBackGroundAudioID) == AudioEngine::AudioState::PLAYING; +} + +float SimpleAudioEngine::getBackgroundMusicVolume() +{ + return _backGroundVolume; +} + +void SimpleAudioEngine::setBackgroundMusicVolume(float volume) +{ + if(_backGroundVolume != volume) { + _backGroundVolume = volume; + AudioEngine::setVolume(_lastBackGroundAudioID, volume); + } +} + +float SimpleAudioEngine::getEffectsVolume() +{ + return _effectsVolume; +} + +void SimpleAudioEngine::setEffectsVolume(float volume) +{ + if(_effectsVolume != volume) { + _effectsVolume = volume; + for(auto it : _effectAudioIDs) { + AudioEngine::setVolume(it, volume); + } + } +} + +// TBD need check pitch pan and gain +unsigned int SimpleAudioEngine::playEffect(const char* pszFilePath, bool bLoop, float pitch, float pan, float gain) +{ + std::string uri(getFullPathWithoutAssetsPrefix(pszFilePath)); + ALOGD("playEffect start! and uri:%{public}s", uri.data()); + int effectAudioID = AudioEngine::play2d(uri, bLoop, _effectsVolume); + _effectAudioIDs.push_back(effectAudioID); + return effectAudioID; +} + + +void SimpleAudioEngine::stopEffect(unsigned int nSoundId) +{ + AudioEngine::stop(nSoundId); + _effectAudioIDs.remove(nSoundId); +} + +void SimpleAudioEngine::preloadEffect(const char* pszFilePath) +{ + std::string uri(getFullPathWithoutAssetsPrefix(pszFilePath)); + AudioEngine::preload(uri); +} + +void SimpleAudioEngine::unloadEffect(const char* pszFilePath) +{ + std::string uri(getFullPathWithoutAssetsPrefix(pszFilePath)); + AudioEngine::uncache(uri); +} + +void SimpleAudioEngine::pauseEffect(unsigned int nSoundId) +{ + AudioEngine::pause(nSoundId); +} + +void SimpleAudioEngine::pauseAllEffects() +{ + for (auto it : _effectAudioIDs) { + AudioEngine::pause(it); + } +} + +void SimpleAudioEngine::resumeEffect(unsigned int nSoundId) +{ + cocos2d::experimental::AudioEngine::resume(nSoundId); +} + +void SimpleAudioEngine::resumeAllEffects() +{ + for (auto it : _effectAudioIDs) { + AudioEngine::resume(it); + } +} + +void SimpleAudioEngine::stopAllEffects() +{ + for (auto it : _effectAudioIDs) { + AudioEngine::stop(it); + } + _effectAudioIDs.clear(); +} + +} diff --git a/cocos/audio/ohos/Track.cpp b/cocos/audio/ohos/Track.cpp new file mode 100644 index 000000000000..34906ce9ea4d --- /dev/null +++ b/cocos/audio/ohos/Track.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** +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 "cutils/log.h" +#include "Track.h" + +#include + +namespace cocos2d { namespace experimental { + +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 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 lk(_stateMutex); + if (_state != state) { + _prevState = _state; + _state = state; + onStateChanged(_state); + } +}; + +}} // namespace cocos2d { namespace experimental \ No newline at end of file diff --git a/cocos/audio/ohos/Track.h b/cocos/audio/ohos/Track.h new file mode 100644 index 000000000000..fab6df4af00c --- /dev/null +++ b/cocos/audio/ohos/Track.h @@ -0,0 +1,101 @@ +/**************************************************************************** +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 "PcmData.h" +#include "IVolumeProvider.h" +#include "PcmBufferProvider.h" + +#include +#include + +namespace cocos2d { namespace experimental { + +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 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 { namespace experimental diff --git a/cocos/audio/ohos/UrlAudioPlayer.cpp b/cocos/audio/ohos/UrlAudioPlayer.cpp new file mode 100644 index 000000000000..8263428e46da --- /dev/null +++ b/cocos/audio/ohos/UrlAudioPlayer.cpp @@ -0,0 +1,336 @@ +/**************************************************************************** +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 "UrlAudioPlayer.h" +#include "ICallerThreadUtils.h" +#include +#include "Macros.h" +#include +#include // for std::find + +namespace { + +std::mutex __playerContainerMutex;//NOLINT(bugprone-reserved-identifier,readability-identifier-naming) +std::map __playerContainer;//NOLINT(bugprone-reserved-identifier,readability-identifier-naming) + +std::once_flag __onceFlag;//NOLINT(bugprone-reserved-identifier,readability-identifier-naming) + +} // namespace + +namespace cocos2d { namespace experimental { + +UrlAudioPlayer::UrlAudioPlayer(ICallerThreadUtils *callerThreadUtils) + : _callerThreadUtils(callerThreadUtils), _id(-1), _playObj(nullptr),_volume(0.0F), _duration(0.0F), _isLoop(false), _isAudioFocus(true), _state(State::INVALID), _playEventCallback(nullptr), _isDestroyed(std::make_shared(false)) +{ + _callerThreadId = callerThreadUtils->getCallerThreadId(); +} + +UrlAudioPlayer::~UrlAudioPlayer() +{ + __playerContainerMutex.lock(); + + auto it = __playerContainer.begin(); + while(it != __playerContainer.end()) + { + if(it->second == this) + { + it = __playerContainer.erase(it); + } + else + { + it++; + } + } + + __playerContainerMutex.unlock(); +} + +void UrlAudioPlayer::playEventCallback() { + std::shared_ptr 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; + } + + 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()); + int32_t status = OH_AVPlayer_Stop(_playObj); + if(status != 0) { + ALOGE("UrlAudioPlayer::stop failed"); + return; + } + + 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(_state)); + } +} + +void UrlAudioPlayer::pause() { + if (_state == State::PLAYING) { + int32_t status = OH_AVPlayer_Pause(_playObj); + if(status != 0) { + ALOGE("UrlAudioPlayer::pause failed"); + return; + } + setState(State::PAUSED); + } else { + ALOGW("UrlAudioPlayer (%p, state:%d) isn't playing, could not invoke pause!", this, static_cast(_state)); + } +} + +void UrlAudioPlayer::resume() { + if (_state == State::PAUSED) { + int32_t status = OH_AVPlayer_Play(_playObj); + if(status != 0) { + ALOGE("UrlAudioPlayer::resume failed"); + return; + } + setState(State::PLAYING); + } else { + ALOGW("UrlAudioPlayer (%p, state:%d) isn't paused, could not invoke resume!", this, static_cast(_state)); + } +} + +void UrlAudioPlayer::play() { + if (_state == State::INITIALIZED || _state == State::PAUSED) { + int32_t status = OH_AVPlayer_Play(_playObj); + if(status != 0) { + ALOGE("UrlAudioPlayer::play failed"); + return; + } + setState(State::PLAYING); + } else { + ALOGW("UrlAudioPlayer (%p, state:%d) isn't paused or initialized, could not invoke play!", this, static_cast(_state)); + } +} + +void UrlAudioPlayer::setVolumeToAVPlayer(float volume) +{ + int32_t status = OH_AVPlayer_SetVolume(_playObj, volume, volume); + if(status != 0) { + ALOGE("UrlAudioPlayer::setVolume %d failed", volume); + } +} + +void UrlAudioPlayer::setVolume(float volume) { + _volume = volume; + if (_isAudioFocus) { + setVolumeToAVPlayer(_volume); + } +} + +float UrlAudioPlayer::getVolume() const { + return _volume; +} + +void UrlAudioPlayer::setAudioFocus(bool isFocus) { + _isAudioFocus = isFocus; + float volume = _isAudioFocus ? _volume : 0.0F; + setVolumeToAVPlayer(volume); +} + +float UrlAudioPlayer::getDuration() const { + if (_duration > 0) { + return _duration; + } + + int32_t duration = 0; + int32_t status = OH_AVPlayer_GetDuration(_playObj, &duration); + if(status != 0) { + ALOGE("UrlAudioPlayer::getDuration failed"); + return -1.0F; + } + const_cast(this)->_duration = duration / 1000.0F; + if (_duration <= 0) { + return -1.0F; + } + return _duration; +} + +float UrlAudioPlayer::getPosition() const { + int32_t currentTime = 0; + int status = OH_AVPlayer_GetCurrentTime(_playObj, ¤tTime); + if(status != 0) { + ALOGE("UrlAudioPlayer::getPosition failed"); + return -1.0F; + } + return currentTime / 1000.0F; +} + +bool UrlAudioPlayer::setPosition(float pos) +{ + int32_t millisecond = 1000.0F * pos; + int status = OH_AVPlayer_Seek(_playObj, millisecond, AV_SEEK_NEXT_SYNC); + if(status != 0) { + ALOGE("UrlAudioPlayer::setPosition %f failed", pos); + return false; + } + return true; +} + +void UrlAudioPlayer::onInfo(OH_AVPlayer *player, AVPlayerOnInfoType type, int32_t extra) +{ + if (type == AV_INFO_TYPE_STATE_CHANGE) { + if (extra == AV_COMPLETED) { + auto it = __playerContainer.find(player); + if (it != __playerContainer.end()) + { + UrlAudioPlayer *audioPlayer = it->second; + audioPlayer->playEventCallback(); + } + } + } else if (type == AV_INFO_TYPE_INTERRUPT_EVENT) { + auto it = __playerContainer.find(player); + if (it != __playerContainer.end()) { + UrlAudioPlayer *audioPlayer = it->second; + if (extra == 1) { + audioPlayer->resume(); + } else if (extra == 2) { + audioPlayer->pause(); + } else { + ALOGV("UrlAudioPlayer was interrupted, hint type is %d", extra); + } + } + } +} + +void UrlAudioPlayer::onError(OH_AVPlayer *player, int32_t errorCode, const char *errorMsg) { + ALOGE("UrlAudioPlayer play failed, errorCode is: %d, errorMsg is %s", errorCode, errorMsg); +} + +bool UrlAudioPlayer::prepare(const std::string &url, std::shared_ptr assetFd, int32_t start, int32_t length) +{ + _url = url; + _assetFd = assetFd; + _playObj = OH_AVPlayer_Create(); + __playerContainerMutex.lock(); + __playerContainer[_playObj] = this; + __playerContainerMutex.unlock(); + + AVPlayerCallback callback; + callback.onInfo = this->onInfo; + callback.onError = this->onError; + OH_AVPlayer_SetPlayerCallback(_playObj, callback); + OH_AVPlayer_SetFDSource(_playObj, _assetFd->getFd(), start, length); + OH_AVPlayer_SetAudioRendererInfo(_playObj, OH_AudioStream_Usage::AUDIOSTREAM_USAGE_GAME); + OH_AVErrCode code = OH_AVPlayer_Prepare(_playObj); + + if (code == AV_ERR_OK) { + setState(State::INITIALIZED); + setVolume(1.0f); + return true; + } + + ALOGE("Oops, prepare error: %d", (int)code); + return false; +} + +void UrlAudioPlayer::rewind() { + // Not supported currently. since cocos audio engine will new -> prepare -> play again. +} + +void UrlAudioPlayer::setLoop(bool isLoop) { + _isLoop = isLoop; + int status = OH_AVPlayer_SetLooping(_playObj, _isLoop); + if(status != 0) { + ALOGE( "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(); + + auto it = temp.begin(); + while(it != temp.end()) + { + UrlAudioPlayer* thiz = it->second; + thiz->stop(); + it++; + } +} + +void UrlAudioPlayer::destroy() +{ + if (!*_isDestroyed) + { + *_isDestroyed = true; + OH_AVErrCode code = OH_AVPlayer_Release(_playObj); + if (code != AV_ERR_OK) { + ALOGE( "UrlAudioPlayer release failed, errcode is %d", code); + } + } +} + +}} // namespace cocos2d { namespace experimental diff --git a/cocos/audio/ohos/UrlAudioPlayer.h b/cocos/audio/ohos/UrlAudioPlayer.h new file mode 100644 index 000000000000..62e4d4262494 --- /dev/null +++ b/cocos/audio/ohos/UrlAudioPlayer.h @@ -0,0 +1,125 @@ +/**************************************************************************** +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 +#include +#include +#include +#include "AssetFd.h" +#include "IAudioPlayer.h" +#include "cutils/log.h" +#include "multimedia/player_framework/avplayer.h" + +namespace cocos2d { namespace experimental { + +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; + + UrlAudioPlayer(ICallerThreadUtils *callerThreadUtils); + + virtual ~UrlAudioPlayer(); + + bool prepare(const std::string &url, std::shared_ptr assetFd, int32_t start, int32_t length); + + static void onInfo(OH_AVPlayer *player, AVPlayerOnInfoType type, int32_t extra); + + static void onError(OH_AVPlayer *player, int32_t errorCode, const char *errorMsg); + + static void stopAll(); + + void destroy(); + + inline void setState(State state) { _state = state; }; + + void playEventCallback(); + + void setVolumeToAVPlayer(float volume); + +private: + ICallerThreadUtils *_callerThreadUtils; + + int _id; + std::string _url; + + std::shared_ptr _assetFd; + + OH_AVPlayer *_playObj; + + float _volume; + float _duration; + bool _isLoop; + bool _isAudioFocus; + State _state; + + PlayEventCallback _playEventCallback; + + std::thread::id _callerThreadId; + std::shared_ptr _isDestroyed; + + friend class AudioPlayerProvider; +}; + +}} // namespace cocos2d { namespace experimental diff --git a/cocos/audio/ohos/Value.h b/cocos/audio/ohos/Value.h new file mode 100644 index 000000000000..a31860c0e16b --- /dev/null +++ b/cocos/audio/ohos/Value.h @@ -0,0 +1,240 @@ +/**************************************************************************** + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2022 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. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include "Macros.h" + + +namespace cocos2d { namespace experimental { + +class Value; + +using ValueVector = std::vector; +using ValueMap = std::unordered_map; +using ValueMapIntKey = std::unordered_map; + +extern const ValueVector VALUE_VECTOR_NULL; +extern const ValueMap VALUE_MAP_NULL; +extern const ValueMapIntKey VALUE_MAP_INT_KEY_NULL; + +/* + * This class is provide as a wrapper of basic types, such as int and bool. + */ +class Value { +public: + /** A predefined Value that has not value. */ + static const Value VALUE_NULL; + + /** Default constructor. */ + Value(); + + /** Create a Value by an unsigned char value. */ + explicit Value(unsigned char v); + + /** Create a Value by an integer value. */ + explicit Value(int v); + + /** Create a Value by an unsigned value. */ + explicit Value(unsigned int v); + + /** Create a Value by a float value. */ + explicit Value(float v); + + /** Create a Value by a double value. */ + explicit Value(double v); + + /** Create a Value by a bool value. */ + explicit Value(bool v); + + /** Create a Value by a char pointer. It will copy the chars internally. */ + explicit Value(const char *v); + + /** Create a Value by a string. */ + explicit Value(const std::string &v); + + /** Create a Value by a ValueVector object. */ + explicit Value(const ValueVector &v); + /** Create a Value by a ValueVector object. It will use std::move internally. */ + explicit Value(ValueVector &&v); + + /** Create a Value by a ValueMap object. */ + explicit Value(const ValueMap &v); + /** Create a Value by a ValueMap object. It will use std::move internally. */ + explicit Value(ValueMap &&v); + + /** Create a Value by a ValueMapIntKey object. */ + explicit Value(const ValueMapIntKey &v); + /** Create a Value by a ValueMapIntKey object. It will use std::move internally. */ + explicit Value(ValueMapIntKey &&v); + + /** Create a Value by another Value object. */ + Value(const Value &other); + /** Create a Value by a Value object. It will use std::move internally. */ + Value(Value &&other) noexcept; + + /** Destructor. */ + ~Value(); + + /** Assignment operator, assign from Value to Value. */ + Value &operator=(const Value &other); + /** Assignment operator, assign from Value to Value. It will use std::move internally. */ + Value &operator=(Value &&other) noexcept; + + /** Assignment operator, assign from unsigned char to Value. */ + Value &operator=(unsigned char v); + /** Assignment operator, assign from integer to Value. */ + Value &operator=(int v); + /** Assignment operator, assign from integer to Value. */ + Value &operator=(unsigned int v); + /** Assignment operator, assign from float to Value. */ + Value &operator=(float v); + /** Assignment operator, assign from double to Value. */ + Value &operator=(double v); + /** Assignment operator, assign from bool to Value. */ + Value &operator=(bool v); + /** Assignment operator, assign from char* to Value. */ + Value &operator=(const char *v); + /** Assignment operator, assign from string to Value. */ + Value &operator=(const std::string &v); + + /** Assignment operator, assign from ValueVector to Value. */ + Value &operator=(const ValueVector &v); + /** Assignment operator, assign from ValueVector to Value. */ + Value &operator=(ValueVector &&v); + + /** Assignment operator, assign from ValueMap to Value. */ + Value &operator=(const ValueMap &v); + /** Assignment operator, assign from ValueMap to Value. It will use std::move internally. */ + Value &operator=(ValueMap &&v); + + /** Assignment operator, assign from ValueMapIntKey to Value. */ + Value &operator=(const ValueMapIntKey &v); + /** Assignment operator, assign from ValueMapIntKey to Value. It will use std::move internally. */ + Value &operator=(ValueMapIntKey &&v); + + /** != operator overloading */ + bool operator!=(const Value &v); + /** != operator overloading */ + bool operator!=(const Value &v) const; + /** == operator overloading */ + bool operator==(const Value &v); + /** == operator overloading */ + bool operator==(const Value &v) const; + + /** Gets as a byte value. Will convert to unsigned char if possible, or will trigger assert error. */ + unsigned char asByte() const; + /** Gets as an integer value. Will convert to integer if possible, or will trigger assert error. */ + int asInt() const; + /** Gets as an unsigned value. Will convert to unsigned if possible, or will trigger assert error. */ + unsigned int asUnsignedInt() const; + /** Gets as a float value. Will convert to float if possible, or will trigger assert error. */ + float asFloat() const; + /** Gets as a double value. Will convert to double if possible, or will trigger assert error. */ + double asDouble() const; + /** Gets as a bool value. Will convert to bool if possible, or will trigger assert error. */ + bool asBool() const; + /** Gets as a string value. Will convert to string if possible, or will trigger assert error. */ + std::string asString() const; + + /** Gets as a ValueVector reference. Will convert to ValueVector if possible, or will trigger assert error. */ + ValueVector &asValueVector(); + /** Gets as a const ValueVector reference. Will convert to ValueVector if possible, or will trigger assert error. */ + const ValueVector &asValueVector() const; + + /** Gets as a ValueMap reference. Will convert to ValueMap if possible, or will trigger assert error. */ + ValueMap &asValueMap(); + /** Gets as a const ValueMap reference. Will convert to ValueMap if possible, or will trigger assert error. */ + const ValueMap &asValueMap() const; + + /** Gets as a ValueMapIntKey reference. Will convert to ValueMapIntKey if possible, or will trigger assert error. */ + ValueMapIntKey &asIntKeyMap(); + /** Gets as a const ValueMapIntKey reference. Will convert to ValueMapIntKey if possible, or will trigger assert error. */ + const ValueMapIntKey &asIntKeyMap() const; + + /** + * Checks if the Value is null. + * @return True if the Value is null, false if not. + */ + inline bool isNull() const { return _type == Type::NONE; } + + /** Value type wrapped by Value. */ + enum class Type { + /// no value is wrapped, an empty Value + NONE = 0, + /// wrap byte + BYTE, + /// wrap integer + INTEGER, + /// wrap unsigned + UNSIGNED, + /// wrap float + FLOAT, + /// wrap double + DOUBLE, + /// wrap bool + BOOLEAN, + /// wrap string + STRING, + /// wrap vector + VECTOR, + /// wrap ValueMap + MAP, + /// wrap ValueMapIntKey + INT_KEY_MAP + }; + + /** Gets the value type. */ + inline Type getType() const { return _type; } + + /** Gets the description of the class. */ + std::string getDescription() const; + +private: + void clear(); + void reset(Type type); + + union { + unsigned char byteVal; + int intVal; + unsigned int unsignedVal; + float floatVal; + double doubleVal; + bool boolVal; + + std::string *strVal; + ValueVector *vectorVal; + ValueMap *mapVal; + ValueMapIntKey *intKeyMapVal; + } _field; + + Type _type; +}; + +}} diff --git a/cocos/audio/ohos/audio.h b/cocos/audio/ohos/audio.h new file mode 100644 index 000000000000..cada3009e973 --- /dev/null +++ b/cocos/audio/ohos/audio.h @@ -0,0 +1,491 @@ +/**************************************************************************** +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 +#include "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); +} diff --git a/cocos/audio/ohos/audio_utils/AudioDef.h b/cocos/audio/ohos/audio_utils/AudioDef.h new file mode 100644 index 000000000000..51008475f08f --- /dev/null +++ b/cocos/audio/ohos/audio_utils/AudioDef.h @@ -0,0 +1,49 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2022 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. +****************************************************************************/ + +#pragma once +#include +#include +enum class AudioDataFormat { + UNKNOWN = 0, + SIGNED_8, + UNSIGNED_8, + SIGNED_16, + UNSIGNED_16, + SIGNED_32, + UNSIGNED_32, + FLOAT_32, + FLOAT_64, +}; +struct PCMHeader { + uint32_t totalFrames{0}; + uint32_t bytesPerFrame{0}; + uint32_t sampleRate{0}; + uint32_t channelCount{0}; + AudioDataFormat dataFormat{AudioDataFormat::UNKNOWN}; +}; + +#define CHANNEL_NUMBERS 2 diff --git a/cocos/audio/ohos/audio_utils/RefCounted.cpp b/cocos/audio/ohos/audio_utils/RefCounted.cpp new file mode 100644 index 000000000000..765bb22c8c4c --- /dev/null +++ b/cocos/audio/ohos/audio_utils/RefCounted.cpp @@ -0,0 +1,110 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2021 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. +****************************************************************************/ + +#include "RefCounted.h" + +#if CC_REF_LEAK_DETECTION + #include // std::find + #include "base/Log.h" + #include "base/std/container/list.h" +#endif + +namespace cocos2d { namespace experimental { + +#if CC_REF_LEAK_DETECTION +static void trackRef(RefCounted *ref); +static void untrackRef(RefCounted *ref); +#endif + +RefCounted::RefCounted() { // NOLINT(modernize-use-equals-default) +#if CC_REF_LEAK_DETECTION + trackRef(this); +#endif +} + +RefCounted::~RefCounted() { // NOLINT(modernize-use-equals-default) +#if CC_REF_LEAK_DETECTION + untrackRef(this); +#endif +} + +void RefCounted::addRef() { + ++_referenceCount; +} + +void RefCounted::release() { + CC_ASSERT(_referenceCount > 0); + --_referenceCount; + + if (_referenceCount == 0) { + delete this; + } +} + +unsigned int RefCounted::getRefCount() const { + return _referenceCount; +} + +#if CC_REF_LEAK_DETECTION + +static std::list __refAllocationList; + +void RefCounted::printLeaks() { + // Dump Ref object memory leaks + if (__refAllocationList.empty()) { + CC_LOG_INFO("[memory] All Ref objects successfully cleaned up (no leaks detected).\n"); + } else { + CC_LOG_INFO("[memory] WARNING: %d Ref objects still active in memory.\n", (int)__refAllocationList.size()); + + for (const auto &ref : __refAllocationList) { + CC_ASSERT(ref); + const char *type = typeid(*ref).name(); + CC_LOG_INFO("[memory] LEAK: Ref object '%s' still active with reference count %d.\n", (type ? type : ""), ref->getRefCount()); + } + } +} + +static void trackRef(RefCounted *ref) { + CC_ASSERT(ref); + + // Create memory allocation record. + __refAllocationList.push_back(ref); +} + +static void untrackRef(RefCounted *ref) { + auto iter = std::find(__refAllocationList.begin(), __refAllocationList.end(), ref); + if (iter == __refAllocationList.end()) { + CC_LOG_INFO("[memory] CORRUPTION: Attempting to free (%s) with invalid ref tracking record.\n", typeid(*ref).name()); + return; + } + + __refAllocationList.erase(iter); +} + +#endif // #if CC_REF_LEAK_DETECTION + +}} // namespace CocosDenshion diff --git a/cocos/audio/ohos/audio_utils/RefCounted.h b/cocos/audio/ohos/audio_utils/RefCounted.h new file mode 100644 index 000000000000..e16c0421aaef --- /dev/null +++ b/cocos/audio/ohos/audio_utils/RefCounted.h @@ -0,0 +1,106 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2022 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. +****************************************************************************/ + +#pragma once + +#include "../Macros.h" + +#define CC_REF_LEAK_DETECTION 0 + +namespace cocos2d { namespace experimental { + +class RefCounted; + +/** + * Interface that defines how to clone an Ref. + */ +class Clonable { +public: + /** Returns a copy of the Ref. */ + virtual Clonable *clone() const = 0; + + virtual ~Clonable() = default; +}; + +/** + * Ref is used for reference count management. If a class inherits from Ref, + * then it is easy to be shared in different places. + */ +class RefCounted { +public: + virtual ~RefCounted(); + + /** + * Retains the ownership. + * + * This increases the Ref's reference count. + * + * @see release, autorelease + */ + void addRef(); + + /** + * Releases the ownership immediately. + * + * This decrements the Ref's reference count. + * + * If the reference count reaches 0 after the decrement, this Ref is + * destructed. + * + * @see retain, autorelease + */ + void release(); + + /** + * Returns the Ref's current reference count. + * + * @returns The Ref's reference count. + */ + unsigned int getRefCount() const; + +protected: + /** + * Constructor + * + * The Ref's reference count is 1 after construction. + */ + RefCounted(); + + /// count of references + unsigned int _referenceCount{0}; + + // Memory leak diagnostic data (only included when CC_REF_LEAK_DETECTION is defined and its value isn't zero) +#if CC_REF_LEAK_DETECTION +public: + static void printLeaks(); +#endif +}; + +using SCHEDULE_CB = void (RefCounted::*)(float); +#define CC_SCHEDULE_CALLBACK(cb) static_cast(&cb) + +}} // namespace CocosDenshion diff --git a/cocos/audio/ohos/audio_utils/Value.cpp b/cocos/audio/ohos/audio_utils/Value.cpp new file mode 100644 index 000000000000..88057eb1eba7 --- /dev/null +++ b/cocos/audio/ohos/audio_utils/Value.cpp @@ -0,0 +1,847 @@ +/**************************************************************************** + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2022 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. +****************************************************************************/ + +#include "../Value.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "../utils/Utils.h" + +namespace cocos2d { namespace experimental { + +const ValueVector VALUE_VECTOR_NULL; +const ValueMap VALUE_MAP_NULL = {}; +const ValueMapIntKey VALUE_MAP_INT_KEY_NULL = {}; + +const Value Value::VALUE_NULL; + +Value::Value() +: _type(Type::NONE) { + memset(&_field, 0, sizeof(_field)); +} + +Value::Value(unsigned char v) +: _type(Type::BYTE) { + _field.byteVal = v; +} + +Value::Value(int v) +: _type(Type::INTEGER) { + _field.intVal = v; +} + +Value::Value(unsigned int v) +: _type(Type::UNSIGNED) { + _field.unsignedVal = v; +} + +Value::Value(float v) +: _type(Type::FLOAT) { + _field.floatVal = v; +} + +Value::Value(double v) +: _type(Type::DOUBLE) { + _field.doubleVal = v; +} + +Value::Value(bool v) +: _type(Type::BOOLEAN) { + _field.boolVal = v; +} + +Value::Value(const char *v) +: _type(Type::STRING) { + _field.strVal = new std::string(); + if (v) { + *_field.strVal = v; + } +} + +Value::Value(const std::string &v) +: _type(Type::STRING) { + _field.strVal = new std::string(); + *_field.strVal = v; +} + +Value::Value(const ValueVector &v) +: _type(Type::VECTOR) { + _field.vectorVal = new ValueVector(); + *_field.vectorVal = v; +} + +Value::Value(ValueVector &&v) +: _type(Type::VECTOR) { + _field.vectorVal = new ValueVector(); + *_field.vectorVal = std::move(v); +} + +Value::Value(const ValueMap &v) +: _type(Type::MAP) { + _field.mapVal = new ValueMap(); + *_field.mapVal = v; +} + +Value::Value(ValueMap &&v) +: _type(Type::MAP) { + _field.mapVal = new ValueMap(); + *_field.mapVal = std::move(v); +} + +Value::Value(const ValueMapIntKey &v) +: _type(Type::INT_KEY_MAP) { + _field.intKeyMapVal = new ValueMapIntKey(); + *_field.intKeyMapVal = v; +} + +Value::Value(ValueMapIntKey &&v) +: _type(Type::INT_KEY_MAP) { + _field.intKeyMapVal = new ValueMapIntKey(); + *_field.intKeyMapVal = std::move(v); +} + +Value::Value(const Value &other) //NOLINT(misc-no-recursion) +: _type(Type::NONE) { + *this = other; +} + +Value::Value(Value &&other) noexcept +: _type(Type::NONE) { + *this = std::move(other); +} + +Value::~Value() { + clear(); +} + +Value &Value::operator=(const Value &other) { //NOLINT(misc-no-recursion) + if (this != &other) { + reset(other._type); + + switch (other._type) { + case Type::BYTE: + _field.byteVal = other._field.byteVal; + break; + case Type::INTEGER: + _field.intVal = other._field.intVal; + break; + case Type::UNSIGNED: + _field.unsignedVal = other._field.unsignedVal; + break; + case Type::FLOAT: + _field.floatVal = other._field.floatVal; + break; + case Type::DOUBLE: + _field.doubleVal = other._field.doubleVal; + break; + case Type::BOOLEAN: + _field.boolVal = other._field.boolVal; + break; + case Type::STRING: + if (_field.strVal == nullptr) { + _field.strVal = new std::string(); + } + *_field.strVal = *other._field.strVal; + break; + case Type::VECTOR: + if (_field.vectorVal == nullptr) { + _field.vectorVal = new ValueVector(); + } + *_field.vectorVal = *other._field.vectorVal; + break; + case Type::MAP: + if (_field.mapVal == nullptr) { + _field.mapVal = new ValueMap(); + } + *_field.mapVal = *other._field.mapVal; + break; + case Type::INT_KEY_MAP: + if (_field.intKeyMapVal == nullptr) { + _field.intKeyMapVal = new ValueMapIntKey(); + } + *_field.intKeyMapVal = *other._field.intKeyMapVal; + break; + default: + break; + } + } + return *this; +} + +Value &Value::operator=(Value &&other) noexcept { + if (this != &other) { + clear(); + switch (other._type) { + case Type::BYTE: + _field.byteVal = other._field.byteVal; + break; + case Type::INTEGER: + _field.intVal = other._field.intVal; + break; + case Type::UNSIGNED: + _field.unsignedVal = other._field.unsignedVal; + break; + case Type::FLOAT: + _field.floatVal = other._field.floatVal; + break; + case Type::DOUBLE: + _field.doubleVal = other._field.doubleVal; + break; + case Type::BOOLEAN: + _field.boolVal = other._field.boolVal; + break; + case Type::STRING: + _field.strVal = other._field.strVal; + break; + case Type::VECTOR: + _field.vectorVal = other._field.vectorVal; + break; + case Type::MAP: + _field.mapVal = other._field.mapVal; + break; + case Type::INT_KEY_MAP: + _field.intKeyMapVal = other._field.intKeyMapVal; + break; + default: + break; + } + _type = other._type; + + memset(&other._field, 0, sizeof(other._field)); + other._type = Type::NONE; + } + + return *this; +} + +Value &Value::operator=(unsigned char v) { + reset(Type::BYTE); + _field.byteVal = v; + return *this; +} + +Value &Value::operator=(int v) { + reset(Type::INTEGER); + _field.intVal = v; + return *this; +} + +Value &Value::operator=(unsigned int v) { + reset(Type::UNSIGNED); + _field.unsignedVal = v; + return *this; +} + +Value &Value::operator=(float v) { + reset(Type::FLOAT); + _field.floatVal = v; + return *this; +} + +Value &Value::operator=(double v) { + reset(Type::DOUBLE); + _field.doubleVal = v; + return *this; +} + +Value &Value::operator=(bool v) { + reset(Type::BOOLEAN); + _field.boolVal = v; + return *this; +} + +Value &Value::operator=(const char *v) { + reset(Type::STRING); + *_field.strVal = v ? v : ""; + return *this; +} + +Value &Value::operator=(const std::string &v) { + reset(Type::STRING); + *_field.strVal = v; + return *this; +} + +Value &Value::operator=(const ValueVector &v) { + reset(Type::VECTOR); + *_field.vectorVal = v; + return *this; +} + +Value &Value::operator=(ValueVector &&v) { + reset(Type::VECTOR); + *_field.vectorVal = std::move(v); + return *this; +} + +Value &Value::operator=(const ValueMap &v) { + reset(Type::MAP); + *_field.mapVal = v; + return *this; +} + +Value &Value::operator=(ValueMap &&v) { + reset(Type::MAP); + *_field.mapVal = std::move(v); + return *this; +} + +Value &Value::operator=(const ValueMapIntKey &v) { + reset(Type::INT_KEY_MAP); + *_field.intKeyMapVal = v; + return *this; +} + +Value &Value::operator=(ValueMapIntKey &&v) { + reset(Type::INT_KEY_MAP); + *_field.intKeyMapVal = std::move(v); + return *this; +} + +bool Value::operator!=(const Value &v) { + return !(*this == v); +} +bool Value::operator!=(const Value &v) const { //NOLINT(misc-no-recursion) + return !(*this == v); +} + +bool Value::operator==(const Value &v) { + const auto &t = *this; + return t == v; +} +bool Value::operator==(const Value &v) const { //NOLINT(misc-no-recursion) + if (this == &v) { + return true; + } + + if (v._type != this->_type) { + return false; + } + + if (this->isNull()) { + return true; + } + + switch (_type) { + case Type::BYTE: return v._field.byteVal == this->_field.byteVal; + case Type::INTEGER: return v._field.intVal == this->_field.intVal; + case Type::UNSIGNED: return v._field.unsignedVal == this->_field.unsignedVal; + case Type::BOOLEAN: return v._field.boolVal == this->_field.boolVal; + case Type::STRING: return *v._field.strVal == *this->_field.strVal; + case Type::FLOAT: return std::abs(v._field.floatVal - this->_field.floatVal) <= FLT_EPSILON; + case Type::DOUBLE: return std::abs(v._field.doubleVal - this->_field.doubleVal) <= DBL_EPSILON; + case Type::VECTOR: { + const auto &v1 = *(this->_field.vectorVal); + const auto &v2 = *(v._field.vectorVal); + const auto size = v1.size(); + if (size == v2.size()) { + for (size_t i = 0; i < size; i++) { + if (v1[i] != v2[i]) { + return false; + } + } + return true; + } + return false; + } + case Type::MAP: { + const auto &map1 = *(this->_field.mapVal); + const auto &map2 = *(v._field.mapVal); + for (const auto &kvp : map1) { //NOLINT + auto it = map2.find(kvp.first); + if (it == map2.end() || it->second != kvp.second) { + return false; + } + } + return true; + } + case Type::INT_KEY_MAP: { + const auto &map1 = *(this->_field.intKeyMapVal); + const auto &map2 = *(v._field.intKeyMapVal); + for (const auto &kvp : map1) { //NOLINT + auto it = map2.find(kvp.first); + if (it == map2.end() || it->second != kvp.second) { + return false; + } + } + return true; + } + default: + break; + }; + + return false; +} + +/// Convert value to a specified type +unsigned char Value::asByte() const { + CC_ASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP); + + if (_type == Type::BYTE) { + return _field.byteVal; + } + + if (_type == Type::INTEGER) { + return static_cast(_field.intVal); + } + + if (_type == Type::UNSIGNED) { + return static_cast(_field.unsignedVal); + } + + if (_type == Type::STRING) { + return static_cast(atoi(_field.strVal->c_str())); + } + + if (_type == Type::FLOAT) { + return static_cast(_field.floatVal); + } + + if (_type == Type::DOUBLE) { + return static_cast(_field.doubleVal); + } + + if (_type == Type::BOOLEAN) { + return _field.boolVal ? 1 : 0; + } + + return 0; +} + +int Value::asInt() const { + CC_ASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP); + if (_type == Type::INTEGER) { + return _field.intVal; + } + + if (_type == Type::UNSIGNED) { + CC_ASSERT(_field.unsignedVal < INT_MAX); + return static_cast(_field.unsignedVal); + } + + if (_type == Type::BYTE) { + return _field.byteVal; + } + + if (_type == Type::STRING) { + return atoi(_field.strVal->c_str()); + } + + if (_type == Type::FLOAT) { + return static_cast(_field.floatVal); + } + + if (_type == Type::DOUBLE) { + return static_cast(_field.doubleVal); + } + + if (_type == Type::BOOLEAN) { + return _field.boolVal ? 1 : 0; + } + + return 0; +} + +unsigned int Value::asUnsignedInt() const { + CC_ASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP); + if (_type == Type::UNSIGNED) { + return _field.unsignedVal; + } + + if (_type == Type::INTEGER) { + CC_ASSERT(_field.intVal >= 0); + return static_cast(_field.intVal); + } + + if (_type == Type::BYTE) { + return static_cast(_field.byteVal); + } + + if (_type == Type::STRING) { + // NOTE: strtoul is required (need to augment on unsupported platforms) + return static_cast(strtoul(_field.strVal->c_str(), nullptr, 10)); + } + + if (_type == Type::FLOAT) { + return static_cast(_field.floatVal); + } + + if (_type == Type::DOUBLE) { + return static_cast(_field.doubleVal); + } + + if (_type == Type::BOOLEAN) { + return _field.boolVal ? 1U : 0U; + } + + return 0U; +} + +#define MAX_ITOA_BUFFER_SIZE 256 +double audioAtof(const char *str) { + if (str == nullptr) { + return 0.0; + } + + char buf[MAX_ITOA_BUFFER_SIZE]; + strncpy(buf, str, MAX_ITOA_BUFFER_SIZE); + + // strip string, only remain 7 numbers after '.' + char *dot = strchr(buf, '.'); + if (dot != nullptr && dot - buf + 8 < MAX_ITOA_BUFFER_SIZE) { + dot[8] = '\0'; + } + + return ::atof(buf); +} + +float Value::asFloat() const { + CC_ASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP); + if (_type == Type::FLOAT) { + return _field.floatVal; + } + + if (_type == Type::BYTE) { + return static_cast(_field.byteVal); + } + + if (_type == Type::STRING) { + return static_cast(audioAtof(_field.strVal->c_str())); + } + + if (_type == Type::INTEGER) { + return static_cast(_field.intVal); + } + + if (_type == Type::UNSIGNED) { + return static_cast(_field.unsignedVal); + } + + if (_type == Type::DOUBLE) { + return static_cast(_field.doubleVal); + } + + if (_type == Type::BOOLEAN) { + return _field.boolVal ? 1.0F : 0.0F; + } + + return 0.0F; +} + +double Value::asDouble() const { + CC_ASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP); + if (_type == Type::DOUBLE) { + return _field.doubleVal; + } + + if (_type == Type::BYTE) { + return static_cast(_field.byteVal); + } + + if (_type == Type::STRING) { + return static_cast(audioAtof(_field.strVal->c_str())); + } + + if (_type == Type::INTEGER) { + return static_cast(_field.intVal); + } + + if (_type == Type::UNSIGNED) { + return static_cast(_field.unsignedVal); + } + + if (_type == Type::FLOAT) { + return static_cast(_field.floatVal); + } + + if (_type == Type::BOOLEAN) { + return _field.boolVal ? 1.0 : 0.0; + } + + return 0.0; +} + +bool Value::asBool() const { + CC_ASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP); + if (_type == Type::BOOLEAN) { + return _field.boolVal; + } + + if (_type == Type::BYTE) { + return _field.byteVal != 0; + } + + if (_type == Type::STRING) { + return *_field.strVal != "0" || *_field.strVal != "false"; + } + + if (_type == Type::INTEGER) { + return _field.intVal != 0; + } + + if (_type == Type::UNSIGNED) { + return _field.unsignedVal != 0; + } + + if (_type == Type::FLOAT) { + return _field.floatVal != 0.0F; + } + + if (_type == Type::DOUBLE) { + return _field.doubleVal != 0.0; + } + + return false; +} + +std::string Value::asString() const { + CC_ASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP); + + if (_type == Type::STRING) { + return *_field.strVal; + } + + std::stringstream ret; + + switch (_type) { + case Type::BYTE: + ret << _field.byteVal; + break; + case Type::INTEGER: + ret << _field.intVal; + break; + case Type::UNSIGNED: + ret << _field.unsignedVal; + break; + case Type::FLOAT: + ret << std::fixed << std::setprecision(7) << _field.floatVal; + break; + case Type::DOUBLE: + ret << std::fixed << std::setprecision(16) << _field.doubleVal; + break; + case Type::BOOLEAN: + ret << (_field.boolVal ? "true" : "false"); + break; + default: + break; + } + return ret.str(); +} + +ValueVector &Value::asValueVector() { + CC_ASSERT(_type == Type::VECTOR); + return *_field.vectorVal; +} + +const ValueVector &Value::asValueVector() const { + CC_ASSERT(_type == Type::VECTOR); + return *_field.vectorVal; +} + +ValueMap &Value::asValueMap() { + CC_ASSERT(_type == Type::MAP); + return *_field.mapVal; +} + +const ValueMap &Value::asValueMap() const { + CC_ASSERT(_type == Type::MAP); + return *_field.mapVal; +} + +ValueMapIntKey &Value::asIntKeyMap() { + CC_ASSERT(_type == Type::INT_KEY_MAP); + return *_field.intKeyMapVal; +} + +const ValueMapIntKey &Value::asIntKeyMap() const { + CC_ASSERT(_type == Type::INT_KEY_MAP); + return *_field.intKeyMapVal; +} + +static std::string getTabs(int depth) { + std::string tabWidth; + + for (int i = 0; i < depth; ++i) { + tabWidth += "\t"; + } + + return tabWidth; +} + +static std::string visit(const Value &v, int depth); + +static std::string visitVector(const ValueVector &v, int depth) { //NOLINT[misc-no-recursion] + std::stringstream ret; + + if (depth > 0) { + ret << "\n"; + } + + ret << getTabs(depth) << "[\n"; + + int i = 0; + for (const auto &child : v) { + ret << getTabs(depth + 1) << i << ": " << visit(child, depth + 1); + ++i; + } + + ret << getTabs(depth) << "]\n"; + + return ret.str(); +} + +template +static std::string visitMap(const T &v, int depth) { //NOLINT[misc-no-recursion] + std::stringstream ret; + + if (depth > 0) { + ret << "\n"; + } + + ret << getTabs(depth) << "{\n"; + + for (auto iter = v.begin(); iter != v.end(); ++iter) { + ret << getTabs(depth + 1) << iter->first << ": "; + ret << visit(iter->second, depth + 1); + } + + ret << getTabs(depth) << "}\n"; + + return ret.str(); +} + +static std::string visit(const Value &v, int depth) { //NOLINT[misc-no-recursion] + std::stringstream ret; + + switch (v.getType()) { + case Value::Type::NONE: + case Value::Type::BYTE: + case Value::Type::INTEGER: + case Value::Type::UNSIGNED: + case Value::Type::FLOAT: + case Value::Type::DOUBLE: + case Value::Type::BOOLEAN: + case Value::Type::STRING: + ret << v.asString() << "\n"; + break; + case Value::Type::VECTOR: + ret << visitVector(v.asValueVector(), depth); + break; + case Value::Type::MAP: + ret << visitMap(v.asValueMap(), depth); + break; + case Value::Type::INT_KEY_MAP: + ret << visitMap(v.asIntKeyMap(), depth); + break; + default: + CC_ASSERT(false); + break; + } + + return ret.str(); +} + +std::string Value::getDescription() const { + std::string ret("\n"); + ret += visit(*this, 0); + return ret; +} + +void Value::clear() { + // Free memory the old value allocated + switch (_type) { + case Type::BYTE: + _field.byteVal = 0; + break; + case Type::INTEGER: + _field.intVal = 0; + break; + case Type::UNSIGNED: + _field.unsignedVal = 0U; + break; + case Type::FLOAT: + _field.floatVal = 0.0F; + break; + case Type::DOUBLE: + _field.doubleVal = 0.0; + break; + case Type::BOOLEAN: + _field.boolVal = false; + break; + case Type::STRING: + CC_AUDIO_SAFE_DELETE(_field.strVal); + break; + case Type::VECTOR: + CC_AUDIO_SAFE_DELETE(_field.vectorVal); + break; + case Type::MAP: + CC_AUDIO_SAFE_DELETE(_field.mapVal); + break; + case Type::INT_KEY_MAP: + CC_AUDIO_SAFE_DELETE(_field.intKeyMapVal); + break; + default: + break; + } + + _type = Type::NONE; +} + +void Value::reset(Type type) { + if (_type == type) { + return; + } + + clear(); + + // Allocate memory for the new value + switch (type) { + case Type::STRING: + _field.strVal = new std::string(); + break; + case Type::VECTOR: + _field.vectorVal = new ValueVector(); + break; + case Type::MAP: + _field.mapVal = new ValueMap(); + break; + case Type::INT_KEY_MAP: + _field.intKeyMapVal = new ValueMapIntKey(); + break; + default: + break; + } + + _type = type; +} + +}} // namespace CocosDenshion diff --git a/cocos/audio/ohos/audio_utils/include/audio_utils/minifloat.h b/cocos/audio/ohos/audio_utils/include/audio_utils/minifloat.h new file mode 100644 index 000000000000..63d8b3831fc5 --- /dev/null +++ b/cocos/audio/ohos/audio_utils/include/audio_utils/minifloat.h @@ -0,0 +1,88 @@ +/* + * 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 +#include + + + +/* 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 + +#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) + +/* 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 v); + +/* Convert the internal representation used for gains to float */ +float float_from_gain(gain_minifloat_t a); + + +#endif // COCOS_AUDIO_MINIFLOAT_H diff --git a/cocos/audio/ohos/audio_utils/include/audio_utils/primitives.h b/cocos/audio/ohos/audio_utils/include/audio_utils/primitives.h new file mode 100644 index 000000000000..ea868ceece83 --- /dev/null +++ b/cocos/audio/ohos/audio_utils/include/audio_utils/primitives.h @@ -0,0 +1,936 @@ +/* + * 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. + */ + +#pragma once + + +#include +#include +#include + +extern "C" { +typedef struct __attribute__((__packed__)) { + uint8_t c[3]; +} uint8x3_t; +} + + + +/* 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 INT32_MIN; /* or 0x80000000 */ + } else if (f >= limpos) { + return INT32_MAX; + } + 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 INT32_MIN; /* or 0x80000000 */ + } else if (f >= limpos) { + return INT32_MAX; + } + 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 = 16.0f; + + if (f <= 0.) { + return 0; + } else if (f >= limpos) { + // return 0xffffffff; + return UINT32_MAX; + } + /* 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; + return UINT16_MAX; + } + /* 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); + } + 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); + } + return (int16_t)(inRL >> 16) * (int16_t)(vRL >> 16); +#endif +} diff --git a/cocos/audio/ohos/audio_utils/minifloat.cpp b/cocos/audio/ohos/audio_utils/minifloat.cpp new file mode 100644 index 000000000000..7b2b40971d5d --- /dev/null +++ b/cocos/audio/ohos/audio_utils/minifloat.cpp @@ -0,0 +1,47 @@ +#include +#include "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 + + + +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); +} + + diff --git a/cocos/audio/ohos/audio_utils/primitives.cpp b/cocos/audio/ohos/audio_utils/primitives.cpp new file mode 100644 index 000000000000..9c02f3247be7 --- /dev/null +++ b/cocos/audio/ohos/audio_utils/primitives.cpp @@ -0,0 +1,477 @@ +#include "include/audio_utils/primitives.h" +#include "../utils/Utils.h" +using namespace cocos2d::experimental::utils; + +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 = static_cast(*--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++ = static_cast(*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++ = static_cast(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++ = static_cast(*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++ = static_cast((static_cast(src[0]) + static_cast(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 dstMask, + const void *src, uint32_t srcMask, size_t sampleSize, 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 (dstMask == srcMask) { + memcpy(dst, src, sampleSize * popcount(dstMask) * count); + return; + } + switch (sampleSize) { + case 1: { + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + + COPY_FRAME_BY_MASK(udst, dstMask, usrc, srcMask, count, 0); + } break; + case 2: { + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + + COPY_FRAME_BY_MASK(udst, dstMask, usrc, srcMask, count, 0); + } break; + case 3: { /* could be slow. use a struct to represent 3 bytes of data. */ + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + static const uint8x3_t ZERO{0,0,0}; /* tricky - we use this to zero out a sample */ + + COPY_FRAME_BY_MASK(udst, dstMask, usrc, srcMask, count, ZERO); + } break; + case 4: { + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + + COPY_FRAME_BY_MASK(udst, dstMask, usrc, srcMask, 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 dstChannels, + const void *src, uint32_t srcChannels, + const int8_t *idxary, size_t sampleSize, size_t count) { + switch (sampleSize) { + case 1: { + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + + COPY_FRAME_BY_IDX(udst, dstChannels, usrc, srcChannels, idxary, count, 0); // NOLINT + } break; + case 2: { + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + + COPY_FRAME_BY_IDX(udst, dstChannels, usrc, srcChannels, idxary, count, 0); // NOLINT + } break; + case 3: { /* could be slow. use a struct to represent 3 bytes of data. */ + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + static const uint8x3_t ZERO{0,0,0}; + + COPY_FRAME_BY_IDX(udst, dstChannels, usrc, srcChannels, idxary, count, ZERO); + } break; + case 4: { + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + + COPY_FRAME_BY_IDX(udst, dstChannels, usrc, srcChannels, idxary, count, 0); + } break; + default: + abort(); /* illegal value */ + break; + } +} + +size_t memcpy_by_index_array_initialization(int8_t *idxary, size_t idxcount, + uint32_t dstMask, uint32_t srcMask) { + size_t n = 0; + int srcidx = 0; + uint32_t bit; + uint32_t ormask = srcMask | dstMask; + + while (ormask && n < idxcount) { + bit = ormask & -ormask; /* get lowest bit */ + ormask ^= bit; /* remove lowest bit */ + if (srcMask & dstMask & bit) { /* matching channel */ + idxary[n++] = srcidx++; + } else if (srcMask & bit) { /* source channel only */ + ++srcidx; + } else { /* destination channel only */ + idxary[n++] = -1; + } + } + return n + popcount(ormask & dstMask); +} + +size_t memcpy_by_index_array_initialization_src_index(int8_t *idxary, size_t idxcount, + uint32_t dstMask, uint32_t srcMask) { + size_t dstCount = popcount(dstMask); + if (idxcount == 0) { + return dstCount; + } + if (dstCount > idxcount) { + dstCount = idxcount; + } + + size_t srcIdx; + size_t dstIdx; + for (srcIdx = 0, dstIdx = 0; dstIdx < dstCount; ++dstIdx) { + if (srcMask & 1) { + idxary[dstIdx] = srcIdx++; + } else { + idxary[dstIdx] = -1; + } + srcMask >>= 1; + } + return dstIdx; +} + +size_t memcpy_by_index_array_initialization_dst_index(int8_t *idxary, size_t idxcount, + uint32_t dstMask, uint32_t srcMask) { + size_t srcIdx; + size_t dstIdx; + size_t dstCount = popcount(dstMask); + size_t srcCount = popcount(srcMask); + if (idxcount == 0) { + return dstCount; + } + if (dstCount > idxcount) { + dstCount = idxcount; + } + for (srcIdx = 0, dstIdx = 0; dstIdx < dstCount; ++srcIdx) { + if (dstMask & 1) { + idxary[dstIdx++] = srcIdx < srcCount ? static_cast(srcIdx) : -1; + } + dstMask >>= 1; + } + return dstIdx; +} diff --git a/cocos/audio/ohos/cutils/bitops.h b/cocos/audio/ohos/cutils/bitops.h new file mode 100644 index 000000000000..d32ec53b794c --- /dev/null +++ b/cocos/audio/ohos/cutils/bitops.h @@ -0,0 +1,30 @@ +#ifndef COCOS_CUTILS_BITOPS_H +#define COCOS_CUTILS_BITOPS_H + +#include +#include +#include + + + +#ifdef __cplusplus +extern "C" { +#endif + +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); +} + +#ifdef __cplusplus +} +#endif + +#endif /* COCOS_CUTILS_BITOPS_H */ diff --git a/cocos/audio/ohos/cutils/log.h b/cocos/audio/ohos/cutils/log.h new file mode 100644 index 000000000000..edc29618de31 --- /dev/null +++ b/cocos/audio/ohos/cutils/log.h @@ -0,0 +1,567 @@ +// +// 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 +#include +#include +#include +#include +#include +#define APP_LOG_DOMAIN 0x0001 +#define APP_LOG_TAG "cocos" + +#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. + */ +#define CC_DEBUG 0 +#ifndef LOG_NDEBUG + #if defined(CC_DEBUG) && CC_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, ...) + +#endif + +#ifndef LOG_ALWAYS_FATAL + +#define LOG_ALWAYS_FATAL(...) + +#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, ...) \ + ((void)OH_LOG_Print(LOG_APP, LOG_INFO, APP_LOG_DOMAIN, APP_LOG_TAG, __VA_ARGS__)) +#endif + +/* + * Log macro that allows you to specify a number for the priority. + */ +#ifndef LOG_PRI + #define LOG_PRI(priority, tag, ...) ((void)0) +#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." 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 */ diff --git a/cocos/audio/ohos/mp3reader.cpp b/cocos/audio/ohos/mp3reader.cpp new file mode 100644 index 000000000000..be4c7c9fefea --- /dev/null +++ b/cocos/audio/ohos/mp3reader.cpp @@ -0,0 +1,497 @@ +#define LOG_TAG "mp3reader" + +#include +#include +#include +#include // Resolves that memset, memcpy aren't found while APP_PLATFORM >= 22 on Android +#include +#include "cutils/log.h" + +#include "mp3reader.h" +#include "pvmp3decoder_api.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 &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(malloc(kInputBufferSize)); + assert(inputBuf != NULL); + + // Allocate output buffer. + int16_t *outputBuf = static_cast(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; +} diff --git a/cocos/audio/ohos/mp3reader.h b/cocos/audio/ohos/mp3reader.h new file mode 100644 index 000000000000..9efd317e26eb --- /dev/null +++ b/cocos/audio/ohos/mp3reader.h @@ -0,0 +1,33 @@ +#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 &pcmBuffer, int *numChannels, int *sampleRate, int *numFrames); + +#endif /* MP3READER_H_ */ diff --git a/cocos/audio/ohos/tinysndfile.cpp b/cocos/audio/ohos/tinysndfile.cpp new file mode 100644 index 000000000000..6abe11edcd9d --- /dev/null +++ b/cocos/audio/ohos/tinysndfile.cpp @@ -0,0 +1,502 @@ +#define LOG_TAG "tinysndfile" + +#include "tinysndfile.h" +#include "audio_utils/include/audio_utils/primitives.h" + +#include "cutils/log.h" + +#include +#include +#include + +#ifndef HAVE_STDERR + #define HAVE_STDERR +#endif + +#define WAVE_FORMAT_PCM 1 +#define WAVE_FORMAT_IEEE_FLOAT 3 +#define WAVE_FORMAT_EXTENSIBLE 0xFFFE + +namespace sf { + +static snd_callbacks sDefaultCallback; + +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() { + static const uint16_t ONE = 1; + return *(reinterpret_cast(&ONE)) == 1; +} + +// "swab" conflicts with OS X +static void my_swab(int16_t *ptr, size_t numToSwap) { //NOLINT(readability-identifier-naming) + while (numToSwap > 0) { + *ptr = static_cast(little2u(reinterpret_cast(ptr))); + --numToSwap; + ++ptr; + } +} + +static void *open_func(const char *path, void * /*user*/) { //NOLINT(readability-identifier-naming) + return fopen(path, "rb"); +} + +static size_t read_func(void *ptr, size_t size, size_t nmemb, void *datasource) { //NOLINT(readability-identifier-naming) + return fread(ptr, size, nmemb, static_cast(datasource)); +} + +static int seek_func(void *datasource, long offset, int whence) { //NOLINT(google-runtime-int,readability-identifier-naming) + return fseek(static_cast(datasource), offset, whence); +} + +static int close_func(void *datasource) { //NOLINT(readability-identifier-naming) + return fclose(static_cast(datasource)); +} + +static long tell_func(void *datasource) { //NOLINT(google-runtime-int,readability-identifier-naming) + return ftell(static_cast(datasource)); +} + +static void sf_lazy_init() { //NOLINT(readability-identifier-naming) + if (sInited == 0) { + sDefaultCallback.open = open_func; + sDefaultCallback.read = read_func; + sDefaultCallback.seek = seek_func; + sDefaultCallback.close = close_func; + sDefaultCallback.tell = tell_func; + sInited = 1; + } +} + +SNDFILE *sf_open_read(const char *path, SF_INFO *info, snd_callbacks *cb, void *user) { //NOLINT(readability-identifier-naming) + sf_lazy_init(); + + if (path == nullptr || info == nullptr) { +#ifdef HAVE_STDERR + ALOGE("path=%p info=%p\n", path, info); +#endif + return nullptr; + } + + auto *handle = static_cast(malloc(sizeof(SNDFILE))); + handle->temp = nullptr; + + handle->info.format = SF_FORMAT_WAV; + if (cb != nullptr) { + handle->callback = *cb; + } else { + handle->callback = sDefaultCallback; + } + + void *stream = handle->callback.open(path, user); + if (stream == nullptr) { +#ifdef HAVE_STDERR + ALOGE("fopen %s failed errno %d\n", path, errno); +#endif + free(handle); + return nullptr; + } + 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; //NOLINT(google-runtime-int) + + 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)) { //NOLINT(bugprone-suspicious-string-compare) +#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)) { //NOLINT(bugprone-suspicious-string-compare) +#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, static_cast(chunkSize - minSize), SEEK_CUR); //NOLINT(google-runtime-int) + } + 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 = static_cast(samplerate); + handle->info.channels = static_cast(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, static_cast(chunkSize), SEEK_CUR); //NOLINT(google-runtime-int) + } + hadData = 1; + } else if (!memcmp(&chunk[0], "fact", 4)) { + // ignore fact + if (chunkSize > 0) { + handle->callback.seek(stream, static_cast(chunkSize), SEEK_CUR); //NOLINT(google-runtime-int) + } + } 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, static_cast(chunkSize), SEEK_CUR); //NOLINT(google-runtime-int) + } + } + 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 nullptr; +} + +void sf_close(SNDFILE *handle) { //NOLINT(readability-identifier-naming) + if (handle == nullptr) { + return; + } + free(handle->temp); + (void)handle->callback.close(handle->stream); + free(handle); +} + +off_t sf_seek(SNDFILE *handle, int offset, int whence) { //NOLINT(readability-identifier-naming) + if (whence == SEEK_SET) { + assert(offset >= 0 && offset <= handle->info.frames); + } else if (whence == SEEK_CUR) { + offset += sf_tell(handle); + assert(offset >= 0 && offset <= handle->info.frames); + } else if (whence == SEEK_END) { + offset += handle->info.frames; + assert(offset >= 0 && offset <= handle->info.frames); + } else { + assert(false); // base whence value + } + handle->remaining = handle->info.frames - offset; + return offset; +} + +off_t sf_tell(SNDFILE *handle) { //NOLINT(readability-identifier-naming) + return handle->info.frames - handle->remaining; +} + +sf_count_t sf_readf_short(SNDFILE *handle, int16_t *ptr, sf_count_t desiredFrames) { //NOLINT(readability-identifier-naming) + if (handle == nullptr || ptr == nullptr || !handle->remaining || + desiredFrames <= 0) { + return 0; + } + if (handle->remaining < static_cast(desiredFrames)) { + desiredFrames = handle->remaining; + } + // does not check for numeric overflow + size_t desiredBytes = desiredFrames * handle->bytesPerFrame; + size_t actualBytes; + void *temp = nullptr; + 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, reinterpret_cast(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, static_cast(temp), actualFrames * handle->info.channels); + free(temp); + break; + case SF_FORMAT_FLOAT: + memcpy_to_i16_from_float(ptr, static_cast(temp), actualFrames * handle->info.channels); + free(temp); + break; + case SF_FORMAT_PCM_24: + memcpy_to_i16_from_p24(ptr, static_cast(temp), actualFrames * handle->info.channels); + free(temp); + break; + default: + memset(ptr, 0, actualFrames * handle->info.channels * sizeof(int16_t)); + break; + } + return actualFrames; +} + +/* +sf_count_t sf_readf_float(SNDFILE *handle, float *ptr, sf_count_t desiredFrames) +{ + if (handle == nullptr || ptr == nullptr || !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 = nullptr; + 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 int16_t *) 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 == nullptr || ptr == nullptr || !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 = nullptr; + 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 int16_t *) 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; +} + */ +} // namespace sf diff --git a/cocos/audio/ohos/tinysndfile.h b/cocos/audio/ohos/tinysndfile.h new file mode 100644 index 000000000000..13079e5f2365 --- /dev/null +++ b/cocos/audio/ohos/tinysndfile.h @@ -0,0 +1,72 @@ +#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 +#include + +namespace sf { + +// visible to clients +using sf_count_t = int; + +using SF_INFO = struct { + sf_count_t frames; + int samplerate; + int channels; + int format; +}; + +// opaque to clients +using SNDFILE = struct 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 + +using snd_callbacks = 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); //NOLINT(google-runtime-int) + int (*close)(void *datasource); + long (*tell)(void *datasource); //NOLINT(google-runtime-int) +}; + +// Open stream +SNDFILE *sf_open_read(const char *path, SF_INFO *info, snd_callbacks *cb, void *user); //NOLINT(readability-identifier-naming) + +// Close stream +void sf_close(SNDFILE *handle); //NOLINT(readability-identifier-naming) + +// Read interleaved frames and return actual number of frames read +sf_count_t sf_readf_short(SNDFILE *handle, int16_t *ptr, sf_count_t desired); + +//NOLINT(readability-identifier-naming) +/* +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); +*/ + +off_t sf_seek(SNDFILE *handle, int offset, int whence); //NOLINT(readability-identifier-naming) +off_t sf_tell(SNDFILE *handle); //NOLINT(readability-identifier-naming) +static int sInited = 0; +static void sf_lazy_init(); //NOLINT(readability-identifier-naming) +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; +}; +} // namespace sf diff --git a/cocos/audio/ohos/utils/Compat.h b/cocos/audio/ohos/utils/Compat.h new file mode 100644 index 000000000000..c9650c133828 --- /dev/null +++ b/cocos/audio/ohos/utils/Compat.h @@ -0,0 +1,49 @@ +#ifndef COCOS_LIB_UTILS_COMPAT_H +#define COCOS_LIB_UTILS_COMPAT_H + +#include +#include + +#define ZD "%zd" +#define ZD_TYPE ssize_t + + +/* + * 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 + * . (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 + + +typedef SLOHBufferQueueItf CCSLBufferQueueItf; +#define CC_SL_IDD_BUFFER_QUEUE SL_IID_OH_BUFFERQUEUE +#define __unused + + +#endif /* COCOS_LIB_UTILS_COMPAT_H */ diff --git a/cocos/audio/ohos/utils/Errors.h b/cocos/audio/ohos/utils/Errors.h new file mode 100644 index 000000000000..dcbbb931a652 --- /dev/null +++ b/cocos/audio/ohos/utils/Errors.h @@ -0,0 +1,72 @@ +#ifndef COCOS_ERRORS_H +#define COCOS_ERRORS_H + +#include +#include + +namespace cocos2d { namespace experimental { + +// 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 CocosDenshion + +// --------------------------------------------------------------------------- + +#endif // COCOS_ERRORS_H diff --git a/cocos/audio/ohos/utils/Utils.cpp b/cocos/audio/ohos/utils/Utils.cpp new file mode 100644 index 000000000000..b0ff6e564c97 --- /dev/null +++ b/cocos/audio/ohos/utils/Utils.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** + Copyright (c) 2010 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2022 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. +****************************************************************************/ + +#include "Utils.h" + +#if CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS + #include +#endif + +#include +#include +#include + +#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED + + +namespace cocos2d { namespace experimental { +namespace utils { + +#define MAX_ITOA_BUFFER_SIZE 256 +double atof(const char *str) { + if (str == nullptr) { + return 0.0; + } + + char buf[MAX_ITOA_BUFFER_SIZE]; + strncpy(buf, str, MAX_ITOA_BUFFER_SIZE); + + // strip string, only remain 7 numbers after '.' + char *dot = strchr(buf, '.'); + if (dot != nullptr && dot - buf + 8 < MAX_ITOA_BUFFER_SIZE) { + dot[8] = '\0'; + } + + return ::atof(buf); +} + +uint32_t nextPOT(uint32_t x) { + x = x - 1; + x = x | (x >> 1); + x = x | (x >> 2); + x = x | (x >> 4); + x = x | (x >> 8); + x = x | (x >> 16); + return x + 1; +} + +// painfully slow to execute, use with caution +std::string getStacktrace(uint32_t skip, uint32_t maxDepth) { + return "not support for 2dx"; +} + +} // namespace utils + +#if USE_MEMORY_LEAK_DETECTOR + + // Make sure GMemoryHook to be initialized first. + #if (CC_COMPILER == CC_COMPILER_MSVC) + #pragma warning(push) + #pragma warning(disable : 4073) + #pragma init_seg(lib) +MemoryHook GMemoryHook; + #pragma warning(pop) + #elif (CC_COMPILER == CC_COMPILER_GNUC || CC_COMPILER == CC_COMPILER_CLANG) +MemoryHook GMemoryHook __attribute__((init_priority(101))); + #endif + +#endif +}} // namespace cc diff --git a/cocos/audio/ohos/utils/Utils.h b/cocos/audio/ohos/utils/Utils.h new file mode 100644 index 000000000000..591b8d02aea9 --- /dev/null +++ b/cocos/audio/ohos/utils/Utils.h @@ -0,0 +1,393 @@ +/**************************************************************************** + Copyright (c) 2010 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2022 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. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "../Macros.h" + +#include +#include +/** @file ccUtils.h +Misc free functions +*/ + +namespace cocos2d { namespace experimental { +namespace utils { +std::string getStacktrace(uint32_t skip = 0, uint32_t maxDepth = UINT32_MAX); + +/** + * Returns the Next Power of Two value. + * Examples: + * - If "value" is 15, it will return 16. + * - If "value" is 16, it will return 16. + * - If "value" is 17, it will return 32. + * @param value The value to get next power of two. + * @return Returns the next power of two value. + * @since v0.99.5 +*/ +uint32_t nextPOT(uint32_t x); + +/** + * Same to ::atof, but strip the string, remain 7 numbers after '.' before call atof. + * Why we need this? Because in android c++_static, atof ( and std::atof ) is unsupported for numbers have long decimal part and contain + * several numbers can approximate to 1 ( like 90.099998474121094 ), it will return inf. This function is used to fix this bug. + * @param str The string be to converted to double. + * @return Returns converted value of a string. + */ +double atof(const char *str); + +#pragma warning(disable : 4146) +template ::value && std::is_unsigned::value>> +inline T getLowestBit(T mask) { + return mask & (-mask); +} +#pragma warning(default : 4146) + +template ::value && std::is_unsigned::value>> +inline T clearLowestBit(T mask) { + return mask & (mask - 1); +} + +// v must be power of 2 +inline uint32_t getBitPosition(uint32_t v) { + if (!v) return 0; + uint32_t c = 32; + if (v & 0x0000FFFF) c -= 16; + if (v & 0x00FF00FF) c -= 8; + if (v & 0x0F0F0F0F) c -= 4; + if (v & 0x33333333) c -= 2; + if (v & 0x55555555) c -= 1; + return c; +} + +// v must be power of 2 +inline uint64_t getBitPosition(uint64_t v) { + if (!v) return 0; + uint64_t c = 64; + if (v & 0x00000000FFFFFFFFLL) c -= 32; + if (v & 0x0000FFFF0000FFFFLL) c -= 16; + if (v & 0x00FF00FF00FF00FFLL) c -= 8; + if (v & 0x0F0F0F0F0F0F0F0FLL) c -= 4; + if (v & 0x3333333333333333LL) c -= 2; + if (v & 0x5555555555555555LL) c -= 1; + return c; +} + +template ::value>> +inline size_t popcount(T mask) { + return std::bitset(mask).count(); +} + +template ::value>> +inline T alignTo(T size, T alignment) { + return ((size - 1) / alignment + 1) * alignment; +} + +template +constexpr uint32_t ALIGN_TO = ((size - 1) / alignment + 1) * alignment; + +template +inline uint32_t toUint(T value) { + static_assert(std::is_arithmetic::value, "T must be numeric"); + + CC_ASSERT(static_cast(value) <= static_cast(std::numeric_limits::max())); + + return static_cast(value); +} + +template +Map &mergeToMap(Map &outMap, const Map &inMap) { + for (const auto &e : inMap) { + outMap.emplace(e.first, e.second); + } + return outMap; +} + +namespace numext { + +template +CC_FORCE_INLINE Tgt bit_cast(const Src &src) { // NOLINT(readability-identifier-naming) + // The behaviour of memcpy is not specified for non-trivially copyable types + static_assert(std::is_trivially_copyable::value, "THIS_TYPE_IS_NOT_SUPPORTED"); + static_assert(std::is_trivially_copyable::value && std::is_default_constructible::value, + "THIS_TYPE_IS_NOT_SUPPORTED"); + static_assert(sizeof(Src) == sizeof(Tgt), "THIS_TYPE_IS_NOT_SUPPORTED"); + + Tgt tgt; + // Load src into registers first. This allows the memcpy to be elided by CUDA. + const Src staged = src; + memcpy(&tgt, &staged, sizeof(Tgt)); + return tgt; +} + +} // namespace numext + +// Following the Arm ACLE arm_neon.h should also include arm_fp16.h but not all +// compilers seem to follow this. We therefore include it explicitly. +// See also: https://bugs.llvm.org/show_bug.cgi?id=47955 +#if defined(CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC) + #include +#endif + +// Code from https://gitlab.com/libeigen/eigen/-/blob/master/Eigen/src/Core/arch/Default/Half.h#L586 +struct HalfRaw { + constexpr HalfRaw() : x(0) {} +#if defined(CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC) + explicit HalfRaw(uint16_t raw) : x(numext::bit_cast<__fp16>(raw)) { + } + __fp16 x; +#else + explicit constexpr HalfRaw(uint16_t raw) : x(raw) {} + uint16_t x; // NOLINT(modernize-use-default-member-init) +#endif +}; + +// Conversion routines, including fallbacks for the host or older CUDA. +// Note that newer Intel CPUs (Haswell or newer) have vectorized versions of +// these in hardware. If we need more performance on older/other CPUs, they are +// also possible to vectorize directly. + +CC_FORCE_INLINE HalfRaw rawUint16ToHalf(uint16_t x) { + // We cannot simply do a "return HalfRaw(x)" here, because HalfRaw is union type + // in the hip_fp16 header file, and that will trigger a compile error + // On the other hand, having anything but a return statement also triggers a compile error + // because this is constexpr function. + // Fortunately, since we need to disable EIGEN_CONSTEXPR for GPU anyway, we can get out + // of this catch22 by having separate bodies for GPU / non GPU +#if defined(CC_HAS_GPU_FP16) + HalfRaw h; + h.x = x; + return h; +#else + return HalfRaw(x); +#endif +} + +CC_FORCE_INLINE uint16_t rawHalfAsUint16(const HalfRaw &h) { + // HIP/CUDA/Default have a member 'x' of type uint16_t. + // For ARM64 native half, the member 'x' is of type __fp16, so we need to bit-cast. + // For SYCL, cl::sycl::half is _Float16, so cast directly. +#if defined(CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC) + return numext::bit_cast(h.x); +#else + return h.x; +#endif +} + +union float32_bits { + unsigned int u; + float f; +}; + +CC_FORCE_INLINE HalfRaw floatToHalf(float ff) { +#if defined(CC_HAS_FP16_C) + HalfRaw h; + #ifdef _MSC_VER + // MSVC does not have scalar instructions. + h.x = _mm_extract_epi16(_mm_cvtps_ph(_mm_set_ss(ff), 0), 0); + #else + h.x = _cvtss_sh(ff, 0); + #endif + return h; + +#elif defined(CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC) + HalfRaw h; + h.x = static_cast<__fp16>(ff); + return h; + +#else + float32_bits f; + f.f = ff; + + const float32_bits f32infty = {255 << 23}; + const float32_bits f16max = {(127 + 16) << 23}; + const float32_bits denorm_magic = {((127 - 15) + (23 - 10) + 1) << 23}; // NOLINT(readability-identifier-naming) + unsigned int sign_mask = 0x80000000U; // NOLINT + HalfRaw o; + o.x = static_cast(0x0U); + + unsigned int sign = f.u & sign_mask; + f.u ^= sign; + + // NOTE all the integer compares in this function can be safely + // compiled into signed compares since all operands are below + // 0x80000000. Important if you want fast straight SSE2 code + // (since there's no unsigned PCMPGTD). + + if (f.u >= f16max.u) { // result is Inf or NaN (all exponent bits set) + o.x = (f.u > f32infty.u) ? 0x7e00 : 0x7c00; // NaN->qNaN and Inf->Inf + } else { // (De)normalized number or zero + if (f.u < (113 << 23)) { // resulting FP16 is subnormal or zero + // use a magic value to align our 10 mantissa bits at the bottom of + // the float. as long as FP addition is round-to-nearest-even this + // just works. + f.f += denorm_magic.f; + + // and one integer subtract of the bias later, we have our final float! + o.x = static_cast(f.u - denorm_magic.u); + } else { + unsigned int mant_odd = (f.u >> 13) & 1; // NOLINT(readability-identifier-naming) // resulting mantissa is odd + + // update exponent, rounding bias part 1 + // Equivalent to `f.u += ((unsigned int)(15 - 127) << 23) + 0xfff`, but + // without arithmetic overflow. + f.u += 0xc8000fffU; + // rounding bias part 2 + f.u += mant_odd; + // take the bits! + o.x = static_cast(f.u >> 13); + } + } + + o.x |= static_cast(sign >> 16); + return o; +#endif +} + +CC_FORCE_INLINE float halfToFloat(HalfRaw h) { +#if defined(CC_HAS_FP16_C) + #ifdef _MSC_VER + // MSVC does not have scalar instructions. + return _mm_cvtss_f32(_mm_cvtph_ps(_mm_set1_epi16(h.x))); + #else + return _cvtsh_ss(h.x); + #endif +#elif defined(CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC) + return static_cast(h.x); +#else + const float32_bits magic = {113 << 23}; + const unsigned int shifted_exp = 0x7c00 << 13; // NOLINT(readability-identifier-naming) // exponent mask after shift + float32_bits o; + + o.u = (h.x & 0x7fff) << 13; // exponent/mantissa bits + unsigned int exp = shifted_exp & o.u; // just the exponent + o.u += (127 - 15) << 23; // exponent adjust + + // handle exponent special cases + if (exp == shifted_exp) { // Inf/NaN? + o.u += (128 - 16) << 23; // extra exp adjust + } else if (exp == 0) { // Zero/Denormal? + o.u += 1 << 23; // extra exp adjust + o.f -= magic.f; // renormalize + } + + o.u |= (h.x & 0x8000) << 16; // sign bit + return o.f; +#endif +} + +namespace array { + +/** + * @zh + * 移除首个指定的数组元素。判定元素相等时相当于于使用了 `Array.prototype.indexOf`。 + * @en + * Removes the first occurrence of a specific object from the array. + * Decision of the equality of elements is similar to `Array.prototype.indexOf`. + * @param array 数组。 + * @param value 待移除元素。 + */ +template +bool remove(std::vector &array, T value) { + auto iter = std::find(array.begin(), array.end(), value); + if (iter != array.end()) { + array.erase(iter); + return true; + } + return false; +} + +/** + * @zh + * 移除指定索引的数组元素。 + * @en + * Removes the array item at the specified index. + * @param array 数组。 + * @param index 待移除元素的索引。 + */ +template +bool removeAt(std::vector &array, int32_t index) { + if (index >= 0 && index < static_cast(array.size())) { + array.erase(array.begin() + index); + return true; + } + return false; +} + +/** + * @zh + * 移除指定索引的数组元素。 + * 此函数十分高效,但会改变数组的元素次序。 + * @en + * Removes the array item at the specified index. + * It's faster but the order of the array will be changed. + * @param array 数组。 + * @param index 待移除元素的索引。 + */ +template +bool fastRemoveAt(std::vector &array, int32_t index) { + const auto length = static_cast(array.size()); + if (index < 0 || index >= length) { + return false; + } + array[index] = array[length - 1]; + array.resize(length - 1); + return true; +} + +/** + * @zh + * 移除首个指定的数组元素。判定元素相等时相当于于使用了 `Array.prototype.indexOf`。 + * 此函数十分高效,但会改变数组的元素次序。 + * @en + * Removes the first occurrence of a specific object from the array. + * Decision of the equality of elements is similar to `Array.prototype.indexOf`. + * It's faster but the order of the array will be changed. + * @param array 数组。 + * @param value 待移除元素。 + */ +template +bool fastRemove(std::vector &array, T value) { + auto iter = std::find(array.begin(), array.end(), value); + if (iter != array.end()) { + *iter = array[array.size() - 1]; + array.resize(array.size() - 1); + return true; + } + return false; +} + +} // namespace array +} // namespace utils +}} // namespace cc diff --git a/cocos/base/CCConsole.cpp b/cocos/base/CCConsole.cpp index d8048fc1321f..19325afa2143 100644 --- a/cocos/base/CCConsole.cpp +++ b/cocos/base/CCConsole.cpp @@ -151,7 +151,8 @@ namespace { #if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID __android_log_print(ANDROID_LOG_DEBUG, "cocos2d-x debug info", "%s", buf); - +#elif CC_TARGET_PLATFORM == CC_PLATFORM_OHOS + OHOS_LOGI("cocos2d-x debug info %{public}s", buf); #elif CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT int pos = 0; diff --git a/cocos/base/CCController-ohos.cpp b/cocos/base/CCController-ohos.cpp new file mode 100644 index 000000000000..393c3c8ac57a --- /dev/null +++ b/cocos/base/CCController-ohos.cpp @@ -0,0 +1,164 @@ +/**************************************************************************** + Copyright (c) 2014 cocos2d-x.org + 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 "base/CCController.h" + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) +#include +#include "base/ccMacros.h" +#include "base/CCDirector.h" +#include "base/CCEventController.h" + +NS_CC_BEGIN + +class ControllerImpl +{ +public: + ControllerImpl(Controller* controller) + : _controller(controller) + { + } + + static std::vector::iterator findController(const std::string& deviceName, int deviceId) + { + auto iter = std::find_if(Controller::s_allController.begin(), Controller::s_allController.end(), [&](Controller* controller){ + return (deviceName == controller->_deviceName) && (deviceId == controller->_deviceId); + }); + + return iter; + } + + static void onConnected(const std::string& deviceName, int deviceId) + { + // Check whether the controller is already connected. + CCLOG("onConnected %s,%d", deviceName.c_str(),deviceId); + + auto iter = findController(deviceName, deviceId); + if (iter != Controller::s_allController.end()) + return; + + // It's a new controller being connected. + auto controller = new cocos2d::Controller(); + controller->_deviceId = deviceId; + controller->_deviceName = deviceName; + Controller::s_allController.push_back(controller); + + controller->onConnected(); + } + + static void onDisconnected(const std::string& deviceName, int deviceId) + { + CCLOG("onDisconnected %s,%d", deviceName.c_str(),deviceId); + + auto iter = findController(deviceName, deviceId); + if (iter == Controller::s_allController.end()) + { + CCLOGERROR("Could not find the controller!"); + return; + } + + (*iter)->onDisconnected(); + Controller::s_allController.erase(iter); + } + + static void onButtonEvent(const std::string& deviceName, int deviceId, int keyCode, bool isPressed, float value, bool isAnalog) + { + auto iter = findController(deviceName, deviceId); + if (iter == Controller::s_allController.end()) + { + CCLOG("onButtonEvent:connect new controller."); + onConnected(deviceName, deviceId); + iter = findController(deviceName, deviceId); + } + + (*iter)->onButtonEvent(keyCode, isPressed, value, isAnalog); + } + + static void onAxisEvent(const std::string& deviceName, int deviceId, int axisCode, float value, bool isAnalog) + { + auto iter = findController(deviceName, deviceId); + if (iter == Controller::s_allController.end()) + { + CCLOG("onAxisEvent:connect new controller."); + onConnected(deviceName, deviceId); + iter = findController(deviceName, deviceId); + } + + (*iter)->onAxisEvent(axisCode, value, isAnalog); + } + +private: + Controller* _controller; +}; + +void Controller::startDiscoveryController() +{ + // Empty implementation +} + +void Controller::stopDiscoveryController() +{ + // Empty implementation +} + +Controller::~Controller() +{ + delete _impl; + + delete _connectEvent; + delete _keyEvent; + delete _axisEvent; +} + +void Controller::registerListeners() +{ +} + +bool Controller::isConnected() const +{ + // If there is a controller instance, it means that the controller is connected. + // If a controller is disconnected, the instance will be destroyed. + // So always returns true for this method. + return true; +} + +Controller::Controller() + : _controllerTag(TAG_UNSET) + , _impl(new ControllerImpl(this)) + , _connectEvent(nullptr) + , _keyEvent(nullptr) + , _axisEvent(nullptr) +{ + init(); +} + +void Controller::receiveExternalKeyEvent(int externalKeyCode,bool receive) { + // TBD need fixed +// JniHelper::callStaticVoidMethod("org.cocos2dx.lib.GameControllerHelper", "receiveExternalKeyEvent", _deviceId, externalKeyCode, receive); +} + +NS_CC_END +#endif // #if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) diff --git a/cocos/base/CCController.cpp b/cocos/base/CCController.cpp index e326a7feec2b..fd5439d51153 100644 --- a/cocos/base/CCController.cpp +++ b/cocos/base/CCController.cpp @@ -25,7 +25,7 @@ #include "base/CCController.h" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) #include "base/CCEventDispatcher.h" #include "base/CCEventController.h" diff --git a/cocos/base/CCEventMouse.h b/cocos/base/CCEventMouse.h index 8bfc72e46399..6f475de3d06b 100644 --- a/cocos/base/CCEventMouse.h +++ b/cocos/base/CCEventMouse.h @@ -28,7 +28,7 @@ #include "base/CCEvent.h" #include "math/CCGeometry.h" - +#define MOUSE_BUTTON_UNSET -1 #define MOUSE_BUTTON_LEFT 0 #define MOUSE_BUTTON_RIGHT 1 #define MOUSE_BUTTON_MIDDLE 2 diff --git a/cocos/base/CMakeLists.txt b/cocos/base/CMakeLists.txt index 6f5e9da4bdb0..3e975df446ac 100644 --- a/cocos/base/CMakeLists.txt +++ b/cocos/base/CMakeLists.txt @@ -3,7 +3,10 @@ if(MACOSX OR APPLE) set(COCOS_BASE_SPECIFIC_SRC base/CCUserDefault-apple.mm ) - +elseif(OHOS) + set(COCOS_BASE_SPECIFIC_SRC + base/CCController-ohos.cpp + ) elseif(ANDROID) set(COCOS_BASE_SPECIFIC_SRC base/CCUserDefault-android.cpp diff --git a/cocos/base/allocator/CCAllocatorMutex.h b/cocos/base/allocator/CCAllocatorMutex.h index 026aca94752f..d625ee7a71d3 100644 --- a/cocos/base/allocator/CCAllocatorMutex.h +++ b/cocos/base/allocator/CCAllocatorMutex.h @@ -29,7 +29,7 @@ #include "platform/CCPlatformMacros.h" -#if CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX +#if CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX || CC_TARGET_PLATFORM == CC_PLATFORM_OHOS #include "pthread.h" #define MUTEX pthread_mutex_t #define MUTEX_INIT(m) \ diff --git a/cocos/base/ccConfig.h b/cocos/base/ccConfig.h index 4a251b76462b..17f82880060b 100644 --- a/cocos/base/ccConfig.h +++ b/cocos/base/ccConfig.h @@ -265,7 +265,7 @@ THE SOFTWARE. /** Use 3d physics integration API. */ #ifndef CC_USE_3D_PHYSICS -#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) #define CC_USE_3D_PHYSICS 1 #endif #endif diff --git a/cocos/cocos2d.h b/cocos/cocos2d.h index 3064be350ffa..f6e6b06bf8ff 100644 --- a/cocos/cocos2d.h +++ b/cocos/cocos2d.h @@ -249,6 +249,13 @@ THE SOFTWARE. #include "platform/tizen/CCStdC-tizen.h" #endif +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + #include "platform/ohos/CCApplication-ohos.h" + #include "platform/ohos/CCGLViewImpl-ohos.h" + #include "platform/ohos/CCGL-ohos.h" + #include "platform/ohos/CCStdC-ohos.h" +#endif // (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + // script_support #include "base/CCScriptSupport.h" diff --git a/cocos/network/CCDownloader-curl.cpp b/cocos/network/CCDownloader-curl.cpp index 9f9a01eebd90..2138eb7da32f 100644 --- a/cocos/network/CCDownloader-curl.cpp +++ b/cocos/network/CCDownloader-curl.cpp @@ -38,6 +38,8 @@ // member function with suffix "Proc" designed called in DownloaderCURL::_threadProc // member function without suffix designed called in main thread +#define CC_CURL_POLL_TIMEOUT_MS 50 //wait until DNS query done + namespace cocos2d { namespace network { using namespace std; @@ -383,7 +385,12 @@ namespace cocos2d { namespace network { static const long LOW_SPEED_TIME = 5; curl_easy_setopt(handle, CURLOPT_LOW_SPEED_LIMIT, LOW_SPEED_LIMIT); curl_easy_setopt(handle, CURLOPT_LOW_SPEED_TIME, LOW_SPEED_TIME); - + + if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS){ + curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0); + } + static const int MAX_REDIRS = 2; if (MAX_REDIRS) { @@ -507,7 +514,11 @@ namespace cocos2d { namespace network { // do wait action if(maxfd == -1) { - this_thread::sleep_for(chrono::milliseconds(timeoutMS)); + if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS){ + this_thread::sleep_for(chrono::milliseconds(CC_CURL_POLL_TIMEOUT_MS)); + }else{ + this_thread::sleep_for(chrono::milliseconds(timeoutMS)); + } rc = 0; } else diff --git a/cocos/platform/CCApplication.h b/cocos/platform/CCApplication.h index 21aa673d99f4..64a2022dbfb9 100644 --- a/cocos/platform/CCApplication.h +++ b/cocos/platform/CCApplication.h @@ -43,6 +43,8 @@ THE SOFTWARE. #include "platform/linux/CCApplication-linux.h" #elif CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN #include "platform/tizen/CCApplication-tizen.h" +#elif CC_TARGET_PLATFORM == CC_PLATFORM_OHOS +#include "platform/ohos/CCApplication-ohos.h" #endif /// @endcond diff --git a/cocos/platform/CCApplicationProtocol.h b/cocos/platform/CCApplicationProtocol.h index 8c3532c0d394..4f0fe0158703 100644 --- a/cocos/platform/CCApplicationProtocol.h +++ b/cocos/platform/CCApplicationProtocol.h @@ -57,7 +57,8 @@ class CC_DLL ApplicationProtocol OS_EMSCRIPTEN, /**< Emscripten */ OS_TIZEN, /**< Tizen */ OS_WINRT, /**< Windows Runtime Applications */ - OS_WP8 /**< Windows Phone 8 Applications */ + OS_WP8, /**< Windows Phone 8 Applications */ + OS_HARMONY_NEXT /**< OpenHarmony OS */ }; /** diff --git a/cocos/platform/CCGL.h b/cocos/platform/CCGL.h index a5ba7cfb2688..0bf5ccd8b4f4 100644 --- a/cocos/platform/CCGL.h +++ b/cocos/platform/CCGL.h @@ -43,6 +43,8 @@ THE SOFTWARE. #include "platform/linux/CCGL-linux.h" #elif CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN #include "platform/tizen/CCGL-tizen.h" +#elif CC_TARGET_PLATFORM == CC_PLATFORM_OHOS +#include "platform/ohos/CCGL-ohos.h" #endif /// @endcond diff --git a/cocos/platform/CCGLView.cpp b/cocos/platform/CCGLView.cpp index 1ab18e18c02f..d87dd5edede4 100644 --- a/cocos/platform/CCGLView.cpp +++ b/cocos/platform/CCGLView.cpp @@ -205,6 +205,11 @@ Rect GLView::getVisibleRect() const return ret; } +Rect GLView::getSafeAreaRect() const +{ + return getVisibleRect(); +} + Size GLView::getVisibleSize() const { if (_resolutionPolicy == ResolutionPolicy::NO_BORDER) diff --git a/cocos/platform/CCGLView.h b/cocos/platform/CCGLView.h index 81388edb0908..a4fd50307e8e 100644 --- a/cocos/platform/CCGLView.h +++ b/cocos/platform/CCGLView.h @@ -239,6 +239,11 @@ class CC_DLL GLView : public Ref */ virtual Rect getVisibleRect() const; + /** + * Gets safe area rectangle + */ + virtual Rect getSafeAreaRect() const; + /** * Set the design resolution size. * @param width Design resolution width. diff --git a/cocos/platform/CCImage.cpp b/cocos/platform/CCImage.cpp index ee977724c668..044e45ff6274 100644 --- a/cocos/platform/CCImage.cpp +++ b/cocos/platform/CCImage.cpp @@ -88,6 +88,9 @@ extern "C" #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) #include "platform/android/CCFileUtils-android.h" #endif +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) +#include "platform/ohos/CCFileUtils-ohos.h" +#endif #define CC_GL_ATC_RGB_AMD 0x8C92 #define CC_GL_ATC_RGBA_EXPLICIT_ALPHA_AMD 0x8C93 diff --git a/cocos/platform/CCPlatformConfig.h b/cocos/platform/CCPlatformConfig.h index 77cb78fc7d45..4cfd9b01b990 100644 --- a/cocos/platform/CCPlatformConfig.h +++ b/cocos/platform/CCPlatformConfig.h @@ -52,6 +52,7 @@ THE SOFTWARE. #define CC_PLATFORM_TIZEN 11 #define CC_PLATFORM_QT5 12 #define CC_PLATFORM_WINRT 13 +#define CC_PLATFORM_OHOS 14 // Determine target platform by compile environment macro. #define CC_TARGET_PLATFORM CC_PLATFORM_UNKNOWN @@ -134,6 +135,12 @@ THE SOFTWARE. #define CC_TARGET_PLATFORM CC_PLATFORM_WINRT #endif +// OpenHarmony +#if defined(OHOS) + #undef CC_TARGET_PLATFORM + #define CC_TARGET_PLATFORM CC_PLATFORM_OHOS +#endif + ////////////////////////////////////////////////////////////////////////// // post configure ////////////////////////////////////////////////////////////////////////// diff --git a/cocos/platform/CCPlatformDefine.h b/cocos/platform/CCPlatformDefine.h index 1f6fbec0f690..b3b0a09eda48 100644 --- a/cocos/platform/CCPlatformDefine.h +++ b/cocos/platform/CCPlatformDefine.h @@ -43,6 +43,8 @@ THE SOFTWARE. #include "platform/linux/CCPlatformDefine-linux.h" #elif CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN #include "platform/tizen/CCPlatformDefine-tizen.h" +#elif CC_TARGET_PLATFORM == CC_PLATFORM_OHOS +#include "platform/ohos/CCPlatformDefine-ohos.h" #endif /// @endcond diff --git a/cocos/platform/CCPlatformMacros.h b/cocos/platform/CCPlatformMacros.h index 03abd8b759ad..7c6f77a55fbd 100644 --- a/cocos/platform/CCPlatformMacros.h +++ b/cocos/platform/CCPlatformMacros.h @@ -85,13 +85,13 @@ CC_DEPRECATED_ATTRIBUTE static __TYPE__* node() \ * * @since v0.99.5 */ -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) || (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) #define CC_ENABLE_CACHE_TEXTURE_DATA 1 #else #define CC_ENABLE_CACHE_TEXTURE_DATA 0 #endif -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) || (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_EMSCRIPTEN) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) || (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_EMSCRIPTEN) || (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) /** Application will crash in glDrawElements function on some win32 computers and some android devices. * Indices should be bound again while drawing to avoid this bug. */ diff --git a/cocos/platform/CCStdC.h b/cocos/platform/CCStdC.h index 69ce64ad61ac..c64d4c369bb1 100644 --- a/cocos/platform/CCStdC.h +++ b/cocos/platform/CCStdC.h @@ -42,6 +42,8 @@ THE SOFTWARE. #include "platform/linux/CCStdC-linux.h" #elif CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN #include "platform/tizen/CCStdC-tizen.h" +#elif CC_TARGET_PLATFORM == CC_PLATFORM_OHOS +#include "platform/ohos/CCStdC-ohos.h" #endif #endif /* __PLATFORM_CCSTDC_H__*/ diff --git a/cocos/platform/CMakeLists.txt b/cocos/platform/CMakeLists.txt index 9e8be55a1194..bfd0f168cec6 100644 --- a/cocos/platform/CMakeLists.txt +++ b/cocos/platform/CMakeLists.txt @@ -94,6 +94,36 @@ set_target_properties(cocos2dxandroid_static VERSION "${COCOS2D_X_VERSION}" ) +elseif(OHOS) + set(COCOS_PLATFORM_SPECIFIC_HEADER + platform/ohos/napi/plugin_manager.h + platform/ohos/napi/helper/NapiValueConverter.h + platform/ohos/napi/helper/Js_Cocos2dxHelper.h + platform/ohos/napi/helper/NapiHelper.h + ) + set(COCOS_PLATFORM_SPECIFIC_SRC + platform/ohos/CCDevice-ohos.cpp + platform/ohos/CCGLViewImpl-ohos.cpp + platform/ohos/CCApplication-ohos.cpp + platform/ohos/CCCommon-ohos.cpp + platform/ohos/CCFileUtils-ohos.cpp + platform/ohos/CCTextBitmap.cpp + platform/ohos/napi/modules/RawFileUtils.cpp + platform/ohos/napi/modules/TouchesNapi.cpp + platform/ohos/napi/modules/InputNapi.cpp + platform/ohos/napi/modules/MouseNapi.cpp + platform/ohos/napi/modules/WebViewNapi.cpp + platform/ohos/napi/modules/SensorNapi.cpp + platform/ohos/napi/modules/VideoPlayerNapi.cpp + platform/ohos/napi/helper/NapiValueConverter.cpp + platform/ohos/napi/helper/Js_Cocos2dxHelper.cpp + platform/ohos/napi/helper/NapiHelper.cpp + platform/ohos/napi/render/egl_core.cpp + platform/ohos/napi/render/plugin_render.cpp + platform/ohos/napi/plugin_manager.cpp + platform/ohos/napi/WorkerMessageQueue.cpp + ) + endif() #leave andatory external stuff here also diff --git a/cocos/platform/ohos/CCApplication-ohos.cpp b/cocos/platform/ohos/CCApplication-ohos.cpp new file mode 100644 index 000000000000..7c0001e6b3dc --- /dev/null +++ b/cocos/platform/ohos/CCApplication-ohos.cpp @@ -0,0 +1,121 @@ +#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS +#include "napi/helper/Js_Cocos2dxHelper.h" +#include "napi/helper/NapiHelper.h" +#include "napi/render/plugin_render.h" +#include "platform/CCApplication.h" +#include "base/CCDirector.h" +#include "CCLogOhos.h" +#include + +NS_CC_BEGIN + +// sharedApplication pointer +Application * Application::sm_pSharedApplication = nullptr; + +Application::Application() { + CCAssert(! sm_pSharedApplication, ""); + sm_pSharedApplication = this; +} + +Application::~Application() { + CCAssert(this == sm_pSharedApplication, ""); + sm_pSharedApplication = nullptr; +} + +int Application::run() { + // Initialize instance and cocos2d. + if (!applicationDidFinishLaunching()) { + return 0; + } + + return -1; +} + +void Application::setAnimationInterval(float interval) { + OHOS_LOGD("setAnimationInterval param is [%{public}f] =========", interval); + PluginRender::GetInstance()->changeFPS((uint64_t)(interval * 1000)); // s to ms +} + +////////////////////////////////////////////////////////////////////////// +// static member function +////////////////////////////////////////////////////////////////////////// +Application* Application::getInstance() { + CCAssert(sm_pSharedApplication, ""); + return sm_pSharedApplication; +} + +// @deprecated Use getInstance() instead +Application* Application::sharedApplication() { + return Application::getInstance(); +} + +const char * Application::getCurrentLanguageCode() { + static char code[3]={0}; + std::string systemLanguage = JSFunction::getFunction("DeviceUtils.getSystemLanguage").invoke(); + OHOS_LOGD("==========getCurrentLanguageCode is [%{public}s] =========",systemLanguage.c_str()); + strncpy(code, systemLanguage.c_str(), 2); + code[2]='\0'; + return code; +} + +LanguageType Application::getCurrentLanguage(){ + const char* pLanguageName = getCurrentLanguageCode(); + + OHOS_LOGD("current language:%{public}s", pLanguageName); + LanguageType ret = LanguageType::ENGLISH; + + if (0 == strcmp("zh", pLanguageName)) { + ret = LanguageType::CHINESE; + } else if (0 == strcmp("en", pLanguageName)) { + ret = LanguageType::ENGLISH; + } else if (0 == strcmp("fr", pLanguageName)) { + ret = LanguageType::FRENCH; + } else if (0 == strcmp("it", pLanguageName)) { + ret = LanguageType::ITALIAN; + } else if (0 == strcmp("de", pLanguageName)) { + ret = LanguageType::GERMAN; + } else if (0 == strcmp("es", pLanguageName)) { + ret = LanguageType::SPANISH; + } else if (0 == strcmp("nl", pLanguageName)) { + ret = LanguageType::SPANISH; + } else if (0 == strcmp("ru", pLanguageName)) { + ret = LanguageType::RUSSIAN; + } else if (0 == strcmp("ko", pLanguageName)) { + ret = LanguageType::KOREAN; + } else if (0 == strcmp("ja", pLanguageName)) { + ret = LanguageType::JAPANESE; + } else if (0 == strcmp("hu", pLanguageName)) { + ret = LanguageType::HUNGARIAN; + } else if (0 == strcmp("pt", pLanguageName)) { + ret = LanguageType::PORTUGUESE; + } else if (0 == strcmp("ar", pLanguageName)) { + ret = LanguageType::ARABIC; + } + return ret; +} + +ApplicationProtocol::Platform Application::getTargetPlatform() { + return ApplicationProtocol::Platform::OS_HARMONY_NEXT; +} + + +std::string Application::getVersion() { + return JSFunction::getFunction("ApplicationManager.getVersionName").invoke(); +} + +bool Application::openURL(const std::string &url) { + try { + JSFunction::getFunction("JumpManager.openUrl").invoke(url); + } catch (std::exception& e) { + return false; + } + return true; +} + +void Application::applicationScreenSizeChanged(int newWidth, int newHeight) { + +} + +NS_CC_END + +#endif // CC_TARGET_PLATFORM == CC_PLATFORM_OHOS diff --git a/cocos/platform/ohos/CCApplication-ohos.h b/cocos/platform/ohos/CCApplication-ohos.h new file mode 100644 index 000000000000..6adacb04d5ab --- /dev/null +++ b/cocos/platform/ohos/CCApplication-ohos.h @@ -0,0 +1,88 @@ +#ifndef __CC_APPLICATION_OHOS_H__ +#define __CC_APPLICATION_OHOS_H__ + +#include "platform/CCPlatformConfig.h" +#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS + +#include "platform/CCCommon.h" +#include "platform/CCApplicationProtocol.h" + +NS_CC_BEGIN + +class CC_DLL Application : public ApplicationProtocol { +public: + /** + * @js ctor + */ + Application(); + /** + * @js NA + * @lua NA + */ + virtual ~Application(); + + /** + @brief Callback by Director to limit FPS. + @param interval The time, expressed in seconds, between current frame and next. + */ + virtual void setAnimationInterval(float interval) override; + + /** + @brief Run the message loop. + */ + int run(); + + /** + @brief Get current application instance. + @return Current application instance pointer. + */ + static Application* getInstance(); + + /** @deprecated Use getInstance() instead */ + CC_DEPRECATED_ATTRIBUTE static Application* sharedApplication(); + + /** + @brief Get current language config + @return Current language config + */ + virtual LanguageType getCurrentLanguage() override; + + /** + @brief Get current language iso 639-1 code + @return Current language iso 639-1 code + */ + virtual const char * getCurrentLanguageCode() override; + + /** + @brief Get target platform + */ + virtual Platform getTargetPlatform() override; + + /** + @brief Get application version. + */ + virtual std::string getVersion() override; + + /** + @brief Open url in default browser + @param String with url to open. + @return true if the resource located by the URL was successfully opened; otherwise false. + */ + virtual bool openURL(const std::string &url) override; + + /** + @brief This function will be called when the application screen size is changed. + @param new width + @param new height + */ + virtual void applicationScreenSizeChanged(int newWidth, int newHeight); + +protected: + static Application * sm_pSharedApplication; +}; + +NS_CC_END + +#endif // CC_TARGET_PLATFORM == CC_PLATFORM_OHOS + +#endif // __CC_APPLICATION_OHOS_H__ diff --git a/cocos/platform/ohos/CCCommon-ohos.cpp b/cocos/platform/ohos/CCCommon-ohos.cpp new file mode 100644 index 000000000000..c0c061461ce6 --- /dev/null +++ b/cocos/platform/ohos/CCCommon-ohos.cpp @@ -0,0 +1,26 @@ +#include "platform/CCPlatformConfig.h" +#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS + +#include "platform/CCCommon.h" +#include "platform/ohos/napi/helper/NapiHelper.h" +#include "CCLogOhos.h" +#include + +NS_CC_BEGIN + +#define MAX_LEN (cocos2d::kMaxLogLen + 1) + +void MessageBox(const char * pszMsg, const char * pszTitle) { + std::string msg(pszMsg); + std::string title(pszTitle); + JSFunction::getFunction("DiaLog.showDialog").invoke(msg, title); +} + +void LuaLog(const char * pszFormat) { + OHOS_LOGI("cocos2d-x debug info %{public}s", pszFormat); +} + +NS_CC_END + +#endif // CC_TARGET_PLATFORM == CC_PLATFORM_OHOS + diff --git a/cocos/platform/ohos/CCDevice-ohos.cpp b/cocos/platform/ohos/CCDevice-ohos.cpp new file mode 100644 index 000000000000..d5c1c17784b2 --- /dev/null +++ b/cocos/platform/ohos/CCDevice-ohos.cpp @@ -0,0 +1,99 @@ +#include "platform/CCPlatformConfig.h" +#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS +#include "napi/helper/Js_Cocos2dxHelper.h" +#include "platform/CCDevice.h" +#include "base/ccTypes.h" +#include "CCTextBitmap.h" +#include "napi/helper/NapiHelper.h" + +NS_CC_BEGIN + +int Device::getDPI() { + return JSFunction::getFunction("DeviceUtils.getDpi").invoke(); +} + +void Device::setAccelerometerEnabled(bool isEnabled) { + if (isEnabled) { + Js_Cocos2dxHelper::enableAccelerometer(); + } + else { + Js_Cocos2dxHelper::disableAccelerometer(); + } +} + +void Device::setAccelerometerInterval(float interval) { + Js_Cocos2dxHelper::setAccelerometerInterval(interval); +} + +class BitmapDC { +public: + + BitmapDC() + : m_nWidth(0) + , m_nHeight(0) + , m_pData(NULL) { + } + + ~BitmapDC(void) { + if (m_pData) { + delete [] m_pData; + } + } + + bool getBitmapWithDrawing( const char *text, int nWidth, int nHeight, Device::TextAlign eAlignMask, const FontDefinition& textDefinition) { + CCTextBitmap *cCtextBitmap = new CCTextBitmap(); + CCTextBitmap::createCCTextBitmap(cCtextBitmap, text, textDefinition._fontName.data(), textDefinition._fontAlpha, textDefinition._fontFillColor.r, + textDefinition._fontFillColor.g, textDefinition._fontFillColor.b ,eAlignMask, nWidth, nHeight, textDefinition._fontSize); + void* pixels = cCtextBitmap->getPixelAddr(); + cocos2d::BitmapDC& bitmapDC = sharedBitmapDC(); + bitmapDC.m_nWidth = cCtextBitmap->GetWidth(); + bitmapDC.m_nHeight = cCtextBitmap->GetHeight(); + long size = bitmapDC.m_nWidth * bitmapDC.m_nHeight * 4; + bitmapDC.m_pData = (unsigned char*)malloc(sizeof(unsigned char) * size); + memcpy(bitmapDC.m_pData, pixels, size); + + delete cCtextBitmap; + return true; + } + +public: + int m_nWidth; + int m_nHeight; + unsigned char *m_pData; + + + + static BitmapDC& sharedBitmapDC() { + // TBD not safe for multi threads + static BitmapDC s_BmpDC; + return s_BmpDC; + } +}; + +Data Device::getTextureDataForText(const char * text, const FontDefinition& textDefinition, TextAlign align, int &width, int &height, bool& hasPremultipliedAlpha) { + Data ret; + do { + BitmapDC &dc = BitmapDC::sharedBitmapDC(); + if(!dc.getBitmapWithDrawing(text, (int)textDefinition._dimensions.width, (int)textDefinition._dimensions.height, align, textDefinition )) { + break; + }; + + width = dc.m_nWidth; + height = dc.m_nHeight; + ret.fastSet(dc.m_pData,width * height * 4); + hasPremultipliedAlpha = true; + } while (0); + + return ret; +} + + +void Device::setKeepScreenOn(bool value) { + JSFunction::getFunction("DeviceUtils.setKeepScreenOn").invoke(value); +} + +void Device::vibrate(float duration) { + JSFunction::getFunction("DeviceUtils.startVibration").invoke(duration); +} +NS_CC_END +#endif // CC_TARGET_PLATFORM == CC_PLATFORM_OHOS diff --git a/cocos/platform/ohos/CCFileUtils-ohos.cpp b/cocos/platform/ohos/CCFileUtils-ohos.cpp new file mode 100644 index 000000000000..11fa402f8c5c --- /dev/null +++ b/cocos/platform/ohos/CCFileUtils-ohos.cpp @@ -0,0 +1,206 @@ +#include "platform/CCPlatformConfig.h" +#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS + +#include "platform/ohos/CCFileUtils-ohos.h" +#include "platform/CCCommon.h" +#include "base/ZipUtils.h" +#include "platform/ohos/CCLogOhos.h" + +#include +#include + +using namespace std; + +#define DECLARE_GUARD std::lock_guard mutexGuard(mutex) + +NS_CC_BEGIN +NativeResourceManager* FileUtilsOhos::nativeResourceManager_ = nullptr; +ZipFile* FileUtilsOhos::obbfile = nullptr; +string FileUtilsOhos::ohWritablePath; + +void FileUtilsOhos::setassetmanager(NativeResourceManager* a) { + if (nullptr == a) { + OHOS_LOGD("setassetmanager : received unexpected nullptr parameter"); + return; + } + + cocos2d::FileUtilsOhos::nativeResourceManager_ = a; +} + +FileUtils* FileUtils::getInstance() { + OHOS_LOGD("FileUtils::getInstance()"); + if (s_sharedFileUtils == nullptr) + { + s_sharedFileUtils = new FileUtilsOhos(); + if(!s_sharedFileUtils->init()) + { + delete s_sharedFileUtils; + s_sharedFileUtils = nullptr; + } + std::string resourcePath(""); + FileUtilsOhos::obbfile = new ZipFile(resourcePath, "/data/storage/el2/base/haps/entry/files/"); + } + return s_sharedFileUtils; +} + +FileUtilsOhos::FileUtilsOhos() { +} + +FileUtilsOhos::~FileUtilsOhos() { + CC_SAFE_DELETE(obbfile); +} + +bool FileUtilsOhos::init() { + DECLARE_GUARD; + _defaultResRootPath = ""; + OHOS_LOGD("FileUtilsOhos::init()"); + + std::string assetsPath("");//getApkPath()); + if (assetsPath.find("/obb/") != std::string::npos) + { + obbfile = new ZipFile(assetsPath); + } + + return FileUtils::init(); +} + +bool FileUtilsOhos::isFileExistInternal(const std::string& strFilePath) const { + if (strFilePath.empty()) { + return false; + } + + bool bFound = false; + if (strFilePath[0] != '/') { + RawFile *fp = RawFileUtils::GetInstance().Open(strFilePath.c_str()); + if(fp) { + OHOS_LOGI("FileUtilsOhos::isFileExistInternal() - open %{public}s success", strFilePath.c_str()); + bFound = true; + RawFileUtils::GetInstance().Close(fp); + } + } else { + FILE *fp = fopen(strFilePath.c_str(), "r"); + if (fp) { + bFound = true; + fclose(fp); + } + } + return bFound; +} + +bool FileUtilsOhos::isDirectoryExistInternal(const std::string& dirPath) const { + if (dirPath.empty()) return false; + std::string dirPathMf = dirPath[dirPath.length() - 1] == '/' ? dirPath.substr(0, dirPath.length() - 1) : dirPath; + + if (dirPathMf[0] == '/') { + struct stat st; + return stat(dirPathMf.c_str(), &st) == 0 && S_ISDIR(st.st_mode); + } + + if (dirPathMf.find(_defaultResRootPath) == 0) { + dirPathMf = dirPathMf.substr(_defaultResRootPath.length(), dirPathMf.length()); + } + + RawDir* rawDir = RawFileUtils::GetInstance().OpenDir(dirPathMf.c_str()); + if(rawDir) { + int file_count = RawFileUtils::GetInstance().GetDirSize(rawDir); + RawFileUtils::GetInstance().CloseDir(rawDir); + if (file_count) { + return true; + } + } + return false; +} + +bool FileUtilsOhos::isAbsolutePath(const std::string& strPath) const { + DECLARE_GUARD; + if (strPath[0] == '/' || (!_defaultResRootPath.empty() && strPath.find(_defaultResRootPath) == 0)) { + return true; + } + return false; +} + +long FileUtilsOhos::getFileSize(const std::string& filepath) { + DECLARE_GUARD; + + if(filepath[0] == '/') { + return FileUtils::getFileSize(filepath); + } + + RawFile *fp = RawFileUtils::GetInstance().Open(filepath.c_str());//fopen(strFilePath.c_str(), "r"); + OHOS_LOGI("FileUtilsOhos::getFileSize ===================> doGetFileData %{public}s", filepath.c_str()); + long size = RawFileUtils::GetInstance().GetSize(fp); + RawFileUtils::GetInstance().Close(fp); + if (size != -1) { + return size; + } + return size; +} + +FileUtils::Status FileUtilsOhos::getContents(const std::string& filename, ResizableBuffer* buffer) { + if (filename.empty()) { + OHOS_LOGD("FileUtilsOhos::getContents() - filename is empty"); + return FileUtils::Status::NotExists; + } + + string fullPath = fullPathForFilename(filename); + + if (fullPath[0] == '/') { + OHOS_LOGD("FileUtilsOhos::getContents() - fullPath[0] == '/'"); + return FileUtils::getContents(fullPath, buffer); + } + + RawFile *fp = RawFileUtils::GetInstance().Open(fullPath.c_str()); + if (!fp) { + OHOS_LOGD("FileUtilsOhos::fp is nullptr"); + return FileUtils::Status::NotInitialized; + } + auto size = RawFileUtils::GetInstance().GetSize(fp); + buffer->resize(size); + + int readsize = RawFileUtils::GetInstance().Read(fp, buffer->buffer(), size); + RawFileUtils::GetInstance().Close(fp); + + if (readsize < size) { + if (readsize >= 0) + buffer->resize(readsize); + OHOS_LOGD("FileUtilsOhos::getContents() - readsize < size"); + return FileUtils::Status::ReadFaild; + } + + if (!buffer->buffer()) + { + std::string msg = "Get data from file("; + msg.append(filename).append(") failed!"); + OHOS_LOGD("%{public}s", msg.c_str()); + } + + return FileUtils::Status::OK; +} + +FileUtils::Status FileUtilsOhos::getRawFileDescriptor(const std::string &filename, RawFileDescriptor &descriptor) { + if (filename.empty()) { + return FileUtils::Status::NotExists; + } + string fullPath = fullPathForFilename(filename); + + RawFile *fp = RawFileUtils::GetInstance().Open(fullPath.c_str());//fopen(strFilePath.c_str(), "r"); + if (!fp) { + OHOS_LOGD("FileUtilsOhos::fp is nullptr"); + return FileUtils::Status::NotInitialized; + } + + bool result = RawFileUtils::GetInstance().GetRawFileDescriptor(fp, descriptor); + RawFileUtils::GetInstance().Close(fp); + if (!result) { + return FileUtils::Status::OpenFailed; + } + return FileUtils::Status::OK; +} + +string FileUtilsOhos::getWritablePath() const { + return ohWritablePath; +} + +NS_CC_END + +#endif // CC_TARGET_PLATFORM == CC_PLATFORM_OHOS diff --git a/cocos/platform/ohos/CCFileUtils-ohos.h b/cocos/platform/ohos/CCFileUtils-ohos.h new file mode 100644 index 000000000000..8caf803899fc --- /dev/null +++ b/cocos/platform/ohos/CCFileUtils-ohos.h @@ -0,0 +1,69 @@ +#ifndef __CC_FILEUTILS_OHOS_H__ +#define __CC_FILEUTILS_OHOS_H__ + +#include "platform/CCPlatformConfig.h" +#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS + +#include "platform/CCFileUtils.h" +#include "platform/CCPlatformMacros.h" +#include "base/ccTypes.h" +#include +#include +#include +#include + +#include "napi/modules/RawFileUtils.h" + +NS_CC_BEGIN + +class ZipFile; + +/** + * @addtogroup platform + * @{ + */ + +//! @brief Helper class to handle file operations +class CC_DLL FileUtilsOhos : public FileUtils +{ + friend class FileUtils; +public: + FileUtilsOhos(); + /** + * @js NA + * @lua NA + */ + virtual ~FileUtilsOhos(); + + static void setassetmanager(NativeResourceManager* a); + static NativeResourceManager* getAssetManager() { return nativeResourceManager_; } + static ZipFile* getObbFile() { return obbfile; } + FileUtils::Status getRawFileDescriptor(const std::string &filename, RawFileDescriptor &descriptor); + + /* override functions */ + bool init() override; + + virtual FileUtils::Status getContents(const std::string& filename, ResizableBuffer* buffer); + + virtual std::string getWritablePath() const override; + virtual bool isAbsolutePath(const std::string& strPath) const override; + + virtual long getFileSize(const std::string& filepath); + static std::string ohWritablePath; +private: + virtual bool isFileExistInternal(const std::string& strFilePath) const override; + virtual bool isDirectoryExistInternal(const std::string& dirPath) const override; + + static NativeResourceManager* nativeResourceManager_; + static ZipFile* obbfile; +}; + +// end of platform group +/// @} + +NS_CC_END + +#endif // CC_TARGET_PLATFORM == CC_PLATFORM_OHOS + +#endif // __CC_FILEUTILS_OHOS_H__ + diff --git a/cocos/platform/ohos/CCGL-ohos.h b/cocos/platform/ohos/CCGL-ohos.h new file mode 100644 index 000000000000..b41857c3af18 --- /dev/null +++ b/cocos/platform/ohos/CCGL-ohos.h @@ -0,0 +1,43 @@ +#ifndef __CCGL_OHOS_H__ +#define __CCGL_OHOS_H__ + +#include "platform/CCPlatformConfig.h" +#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS + +#define glClearDepth glClearDepthf +#define glDeleteVertexArrays glDeleteVertexArraysOES +#define glGenVertexArrays glGenVertexArraysOES +#define glBindVertexArray glBindVertexArrayOES +#define glMapBuffer glMapBufferOES +#define glUnmapBuffer glUnmapBufferOES + +#define GL_DEPTH24_STENCIL8 GL_DEPTH24_STENCIL8_OES +#define GL_WRITE_ONLY GL_WRITE_ONLY_OES + +// we manually define it here +#include +#ifndef GL_GLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES 1 +#endif + +// normal process +#include +#include + +typedef char GLchar; +#ifndef GL_BGRA +#define GL_BGRA 0x80E1 +#endif + +extern PFNGLGENVERTEXARRAYSOESPROC glGenVertexArraysOESEXT; +extern PFNGLBINDVERTEXARRAYOESPROC glBindVertexArrayOESEXT; +extern PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArraysOESEXT; + +#define glGenVertexArraysOES glGenVertexArraysOESEXT +#define glBindVertexArrayOES glBindVertexArrayOESEXT +#define glDeleteVertexArraysOES glDeleteVertexArraysOESEXT + + +#endif // CC_TARGET_PLATFORM == CC_PLATFORM_OHOS + +#endif // __CCGL_OHOS_H__ diff --git a/cocos/platform/ohos/CCGLViewImpl-ohos.cpp b/cocos/platform/ohos/CCGLViewImpl-ohos.cpp new file mode 100644 index 000000000000..edb0d8178e19 --- /dev/null +++ b/cocos/platform/ohos/CCGLViewImpl-ohos.cpp @@ -0,0 +1,188 @@ +#include "platform/CCPlatformConfig.h" +#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS +#include +#include "CCGLViewImpl-ohos.h" +#include "base/CCDirector.h" +#include "base/ccMacros.h" +#include "base/CCIMEDispatcher.h" +#include "napi/helper/Js_Cocos2dxHelper.h" +#include "CCGL-ohos.h" +#include "CCLogOhos.h" +#include "napi/helper/NapiHelper.h" + + + +//#if CC_TEXTURE_ATLAS_USE_VAO +#include +PFNGLGENVERTEXARRAYSOESPROC glGenVertexArraysOESEXT = 0; +PFNGLBINDVERTEXARRAYOESPROC glBindVertexArrayOESEXT = 0; +PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArraysOESEXT = 0; + +//#endif + +#define DEFAULT_MARGIN_OHOS 30.0f +#define WIDE_SCREEN_ASPECT_RATIO_OHOS 2.0f + +void initExtensions() { +//#if CC_TEXTURE_ATLAS_USE_VAO + glGenVertexArraysOESEXT = (PFNGLGENVERTEXARRAYSOESPROC)eglGetProcAddress("glGenVertexArraysOES"); + glBindVertexArrayOESEXT = (PFNGLBINDVERTEXARRAYOESPROC)eglGetProcAddress("glBindVertexArrayOES"); + glDeleteVertexArraysOESEXT = (PFNGLDELETEVERTEXARRAYSOESPROC)eglGetProcAddress("glDeleteVertexArraysOES"); +//#endif +} + +NS_CC_BEGIN + +GLViewImpl* GLViewImpl::createWithRect(const std::string& viewName, Rect rect, float frameZoomFactor) { + auto ret = new GLViewImpl; + if(ret && ret->initWithRect(viewName, rect, frameZoomFactor)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +GLViewImpl* GLViewImpl::create(const std::string& viewName) { + auto ret = new GLViewImpl; + if(ret && ret->initWithFullScreen(viewName)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +GLViewImpl* GLViewImpl::createWithFullScreen(const std::string& viewName) { + auto ret = new GLViewImpl(); + if(ret && ret->initWithFullScreen(viewName)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +GLViewImpl::GLViewImpl() { + initExtensions(); +} + +GLViewImpl::~GLViewImpl() { + +} + +bool GLViewImpl::initWithRect(const std::string& viewName, Rect rect, float frameZoomFactor) { + return true; +} + +bool GLViewImpl::initWithFullScreen(const std::string& viewName) { + return true; +} + +bool GLViewImpl::isOpenGLReady() { + return (_screenSize.width != 0 && _screenSize.height != 0); +} + +void GLViewImpl::end() { + OHOS_LOGD("GLViewImpl terminateProcess"); + Js_Cocos2dxHelper::terminateProcess(); +} + +void GLViewImpl::swapBuffers() { +} + +GLViewImpl* GLViewImpl::sharedOpenGLView() { + static GLViewImpl instance; + return &instance; +} + +void GLViewImpl::setIMEKeyboardState(bool bOpen) { + if (bOpen) { + std::string pszText = cocos2d::IMEDispatcher::sharedDispatcher()->getContentText(); + JSFunction::getFunction("DiaLog.showTextInputDialog").invoke(pszText); + } else { + JSFunction::getFunction("DiaLog.hideTextInputDialog").invoke(); + } +} + +Rect GLViewImpl::getSafeAreaRect() const { + Rect safeAreaRect = GLView::getSafeAreaRect(); + float deviceAspectRatio = 0; + if(safeAreaRect.size.height > safeAreaRect.size.width) { + deviceAspectRatio = safeAreaRect.size.height / safeAreaRect.size.width; + } else { + deviceAspectRatio = safeAreaRect.size.width / safeAreaRect.size.height; + } + + float marginX = DEFAULT_MARGIN_OHOS / _scaleX; + float marginY = DEFAULT_MARGIN_OHOS / _scaleY; + + bool isScreenRound = JSFunction::getFunction("DeviceUtils.isRoundScreen").invoke(); + bool hasSoftKeys = JSFunction::getFunction("DeviceUtils.hasSoftKeys").invoke(); + bool isCutoutEnabled = JSFunction::getFunction("DeviceUtils.isCutoutEnable").invoke(); + + if(isScreenRound) { + // edge screen + if(safeAreaRect.size.width < safeAreaRect.size.height) { + safeAreaRect.origin.y += marginY * 2.f; + safeAreaRect.size.height -= (marginY * 2.f); + + safeAreaRect.origin.x += marginX; + safeAreaRect.size.width -= (marginX * 2.f); + } else { + safeAreaRect.origin.y += marginY; + safeAreaRect.size.height -= (marginY * 2.f); + + // landscape: no changes with X-coords + } + } else if (deviceAspectRatio >= WIDE_SCREEN_ASPECT_RATIO_OHOS) { + // almost all devices on the market have round corners + float bottomMarginIfPortrait = 0; + if(hasSoftKeys) { + bottomMarginIfPortrait = marginY * 2.f; + } + + if(safeAreaRect.size.width < safeAreaRect.size.height) { + // portrait: double margin space if device has soft menu + safeAreaRect.origin.y += bottomMarginIfPortrait; + safeAreaRect.size.height -= (bottomMarginIfPortrait + marginY); + } else { + // landscape: ignore double margin at the bottom in any cases + // prepare signle margin for round corners + safeAreaRect.origin.y += marginY; + safeAreaRect.size.height -= (marginY * 2.f); + } + } else { + if(hasSoftKeys && (safeAreaRect.size.width < safeAreaRect.size.height)) { + // portrait: preserve only for soft system menu + safeAreaRect.origin.y += marginY * 2.f; + safeAreaRect.size.height -= (marginY * 2.f); + } + } + + if (isCutoutEnabled) { + // screen with enabled cutout area + int orientation = JSFunction::getFunction("DeviceUtils.getOrientation").invoke(); + + if(static_cast(GLViewImpl::Orientation::PORTRAIT) == orientation) { + double height = JSFunction::getFunction("DeviceUtils.getCutoutHeight").invoke() / _scaleY; + safeAreaRect.origin.y += height; + safeAreaRect.size.height -= height; + } else if(static_cast(GLViewImpl::Orientation::PORTRAIT_INVERTED) == orientation) { + double height =JSFunction::getFunction("DeviceUtils.getCutoutHeight").invoke() / _scaleY; + safeAreaRect.size.height -= height; + } else if(static_cast(GLViewImpl::Orientation::LANDSCAPE) == orientation) { + double width = JSFunction::getFunction("DeviceUtils.getCutoutWidth").invoke() / _scaleX; + safeAreaRect.size.width -= width; + } else if(static_cast(GLViewImpl::Orientation::LANDSCAPE_INVERTED) == orientation) { + double width = JSFunction::getFunction("DeviceUtils.getCutoutWidth").invoke() / _scaleX; + safeAreaRect.origin.x += width; + safeAreaRect.size.width -= width; + } + } + + return safeAreaRect; +} +NS_CC_END + +#endif // CC_TARGET_PLATFORM == CC_PLATFORM_OHOS diff --git a/cocos/platform/ohos/CCGLViewImpl-ohos.h b/cocos/platform/ohos/CCGLViewImpl-ohos.h new file mode 100644 index 000000000000..63aa6c8f0808 --- /dev/null +++ b/cocos/platform/ohos/CCGLViewImpl-ohos.h @@ -0,0 +1,50 @@ +#ifndef __CC_EGLVIEWIMPL_OHOS_H__ +#define __CC_EGLVIEWIMPL_OHOS_H__ + +#include "platform/CCPlatformConfig.h" +#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS +#include "math/CCGeometry.h" +#include "platform/CCGLView.h" + +NS_CC_BEGIN + +class CC_DLL GLViewImpl : public GLView { +public: + enum class Orientation { + PORTRAIT = 0, + LANDSCAPE, + PORTRAIT_INVERTED, + LANDSCAPE_INVERTED, + UNKNOWN + }; + + // static function + static GLViewImpl* create(const std::string &viewname); + static GLViewImpl* createWithRect(const std::string& viewName, Rect rect, float frameZoomFactor = 1.0f); + static GLViewImpl* createWithFullScreen(const std::string& viewName); + + bool isOpenGLReady() override; + + // keep compatible + void end() override; + void swapBuffers() override; + void setIMEKeyboardState(bool bOpen) override; + virtual Rect getSafeAreaRect() const override; + + // static function + /** + @brief get the shared main open gl window + */ + static GLViewImpl* sharedOpenGLView(); + +protected: + GLViewImpl(); + virtual ~GLViewImpl(); + + bool initWithRect(const std::string& viewName, Rect rect, float frameZoomFactor); + bool initWithFullScreen(const std::string& viewName); +}; + +NS_CC_END +#endif // end of CC_TARGET_PLATFORM == CC_PLATFORM_OHOS +#endif // end of __CC_EGLVIEW_ANDROID_H__ diff --git a/cocos/platform/ohos/CCLogOhos.h b/cocos/platform/ohos/CCLogOhos.h new file mode 100644 index 000000000000..fd66ca1eabe1 --- /dev/null +++ b/cocos/platform/ohos/CCLogOhos.h @@ -0,0 +1,121 @@ +#ifndef __CC_LOG_OHOS_H__ +#define __CC_LOG_OHOS_H__ +#include +#include +#include +#ifdef HDF_LOG_TAG +#undef HDF_LOG_TAG +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +#define APP_LOG_DOMAIN 0x0001 +#define APP_LOG_TAG "cocos" +#define OHOS_LOGI(...) ((void)OH_LOG_Print(LOG_APP, LOG_INFO, APP_LOG_DOMAIN, APP_LOG_TAG, __VA_ARGS__)) +#define OHOS_LOGD(...) ((void)OH_LOG_Print(LOG_APP, LOG_DEBUG, APP_LOG_DOMAIN, APP_LOG_TAG, __VA_ARGS__)) +#define OHOS_LOGW(...) ((void)OH_LOG_Print(LOG_APP, LOG_WARN, APP_LOG_DOMAIN, APP_LOG_TAG, __VA_ARGS__)) +#define OHOS_LOGE(...) ((void)OH_LOG_Print(LOG_APP, LOG_ERROR, APP_LOG_DOMAIN, APP_LOG_TAG, __VA_ARGS__)) + +#if 0 +#undef LOG_TAG +#undef LOG_DOMAIN +#define APP_LOG_DOMAIN 0x0001 +#define APP_LOG_TAG "cocos" + +#ifndef DISPLAY_UNUSED +#define DISPLAY_UNUSED(x) (void)x +#endif + +#define __FILENAME__ (strrchr(__FILE__, '/') ? (strrchr(__FILE__, '/') + 1) : __FILE__) + +#ifndef DISPLAY_DEBUG_ENABLE +#define DISPLAY_DEBUG_ENABLE 1 + +#endif + +#ifndef OHOS_LOGD +#define OHOS_LOGD(format, ...) \ + do { \ + if (DISPLAY_DEBUG_ENABLE) { \ + OH_LOG_Print(LOG_APP, LOG_INFO, APP_LOG_DOMAIN, "[%{public}s@%{public}s:%{public}d] " format "\n", \ + __FUNCTION__, __FILENAME__, __LINE__, \ + ##__VA_ARGS__); \ + } \ + } while (0) +#endif + +#ifndef OHOS_LOGI +#define OHOS_LOGI(format, ...) \ + do { \ + OH_LOG_Print(LOG_APP, LOG_INFO, APP_LOG_DOMAIN, "[%{public}s@%{public}s:%{public}d] " format "\n", __FUNCTION__, __FILENAME__, __LINE__, \ + ##__VA_ARGS__); \ + } while (0) +#endif + +#ifndef OHOS_LOGW +#define OHOS_LOGW(format, ...) \ + do { \ + OH_LOG_Print(LOG_APP, LOG_INFO, APP_LOG_DOMAIN, "[%{public}s@%{public}s:%{public}d] " format "\n", __FUNCTION__, __FILENAME__, __LINE__, \ + ##__VA_ARGS__); \ + } while (0) +#endif + +#ifndef OHOS_LOGE +#define OHOS_LOGE(format, ...) \ + do { \ + OH_LOG_Print(LOG_APP, LOG_INFO, APP_LOG_DOMAIN, \ + "\033[0;32;31m" \ + "[%{public}s@%{public}s:%{public}d] " format "\033[m" \ + "\n", \ + __FUNCTION__, __FILENAME__, __LINE__, ##__VA_ARGS__); \ + } while (0) +#endif + +#ifndef CHECK_NULLPOINTER_RETURN_VALUE +#define CHECK_NULLPOINTER_RETURN_VALUE(pointer, ret) \ + do { \ + if ((pointer) == NULL) { \ + OHOS_LOGE("pointer is null and return ret\n"); \ + return (ret); \ + } \ + } while (0) +#endif + +#ifndef CHECK_NULLPOINTER_RETURN +#define CHECK_NULLPOINTER_RETURN(pointer) \ + do { \ + if ((pointer) == NULL) { \ + OHOS_LOGE("pointer is null and return\n"); \ + return; \ + } \ + } while (0) +#endif + +#ifndef DISPLAY_CHK_RETURN +#define DISPLAY_CHK_RETURN(val, ret, ...) \ + do { \ + if (val) { \ + __VA_ARGS__; \ + return (ret); \ + } \ + } while (0) +#endif + +#ifndef DISPLAY_CHK_RETURN_NOT_VALUE +#define DISPLAY_CHK_RETURN_NOT_VALUE(val, ...) \ + do { \ + if (val) { \ + __VA_ARGS__; \ + return; \ + } \ + } while (0) +#endif + +#endif +#ifdef __cplusplus +} +#endif + +#endif /* __CC_LOG_OHOS_H__ */ diff --git a/cocos/platform/ohos/CCPlatformDefine-ohos.h b/cocos/platform/ohos/CCPlatformDefine-ohos.h new file mode 100644 index 000000000000..28de9ec5d383 --- /dev/null +++ b/cocos/platform/ohos/CCPlatformDefine-ohos.h @@ -0,0 +1,37 @@ +#ifndef __CCPLATFORMDEFINE_OHOS_H__ +#define __CCPLATFORMDEFINE_OHOS_H__ + +#include "platform/CCPlatformConfig.h" +#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS +#include +#include "CCLogOhos.h" + +#define CC_DLL + +#define CC_NO_MESSAGE_PSEUDOASSERT(cond) \ + if (!(cond)) { \ + OHOS_LOGI("[cocos2d-x assert] %s function:%s line:%d", __FILE__, __FUNCTION__, __LINE__); \ + } + +#define CC_MESSAGE_PSEUDOASSERT(cond, msg) \ + if (!(cond)) { \ + OHOS_LOGI("[cocos2d-x assert] file:%s function:%s line:%d, %s", __FILE__, __FUNCTION__, __LINE__, msg); \ + } + + +#define CC_ASSERT(cond) CC_NO_MESSAGE_PSEUDOASSERT(cond) + +#define CC_UNUSED_PARAM(unusedparam) (void)unusedparam + +/* Define NULL pointer value */ +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif + +#endif // CC_TARGET_PLATFORM == CC_PLATFORM_OHOS + +#endif /* __CCPLATFORMDEFINE_OHOS_H__*/ diff --git a/cocos/platform/ohos/CCStdC-ohos.h b/cocos/platform/ohos/CCStdC-ohos.h new file mode 100644 index 000000000000..4c1ef75735dc --- /dev/null +++ b/cocos/platform/ohos/CCStdC-ohos.h @@ -0,0 +1,23 @@ +#ifndef __CC_STD_C_OHOS_H__ +#define __CC_STD_C_OHOS_H__ + +#include "platform/CCPlatformMacros.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MIN +#define MIN(x,y) (((x) > (y)) ? (y) : (x)) +#endif // MIN + +#ifndef MAX +#define MAX(x,y) (((x) < (y)) ? (y) : (x)) +#endif // MAX + +#endif // __CC_STD_C_OHOS_H__ diff --git a/cocos/platform/ohos/CCTextBitmap.cpp b/cocos/platform/ohos/CCTextBitmap.cpp new file mode 100644 index 000000000000..80857ffb764b --- /dev/null +++ b/cocos/platform/ohos/CCTextBitmap.cpp @@ -0,0 +1,190 @@ +#include +#include "CCTextBitmap.h" +#include "platform/CCPlatformMacros.h" +#include "platform/CCCommon.h" +#include "platform/ohos/napi/helper/NapiHelper.h" + +NS_CC_BEGIN + +void CCTextBitmap::createCCTextBitmap(CCTextBitmap *cCTextBitmap, const char *text, const char *pFontName, + const float a, const float r, const float g, const float b, + const Device::TextAlign eAlignMask, int width_, int height_, double fontSize) { + createCCTextBitmap(cCTextBitmap, text, pFontName, fontSize, a, r, g, b, eAlignMask, width_, height_, false, 1, 1, 1, + false, 1, 1, 1, 1); +} + +double CCTextBitmap::calxStartPosition(int pAlignment, int layoutWidth, int realWidth, int textWidth) { + if (pAlignment == TEXT_ALIGN_LEFT) { + return 0; + } + if (pAlignment == TEXT_ALIGN_CENTER) { + // Move to the leftmost part of the text, and then add the margin between the text and the actual rendering + // position. Note that the content drawn from the drawing moves to the left using the - position and to the + // right using the + position. + return (-(layoutWidth - realWidth) / 2) + ((textWidth - realWidth) / 2); + } + + if (pAlignment == TEXT_ALIGN_RIGHT) { + return -(layoutWidth - textWidth); + } + return 0; +} +double CCTextBitmap::calyStartPosition(int pAlignment, int realHeight, int textHeight) { + const int pVerticalAlignment = (pAlignment >> 4) & 0x0F; + int y = 0; + switch (pVerticalAlignment) { + case VERTICALALIGN_TOP: + y = 0; + break; + case VERTICALALIGN_CENTER: + y = (textHeight - realHeight) / 2; + break; + case VERTICALALIGN_BOTTOM: + y = textHeight - realHeight; + break; + default: + break; + } + + return y; +} +int CCTextBitmap::processTextAlign(int pAlignment) { + const int horizontalAlignment = pAlignment & 0x0F; + int align = TEXT_ALIGN_LEFT; + switch (horizontalAlignment) { + case HORIZONTALALIGN_CENTER: + align = TEXT_ALIGN_CENTER; + break; + case HORIZONTALALIGN_RIGHT: + align = TEXT_ALIGN_RIGHT; + break; + case HORIZONTALALIGN_LEFT: + default: + align = TEXT_ALIGN_LEFT; + break; + } + return align; +} + +void CCTextBitmap::createCCTextBitmap(CCTextBitmap *cCTextBitmap, const char *text, const char *pFontName, + const int fontSize, const float fontTintA, const float fontTintR, + const float fontTintG, const float fontTintB, const Device::TextAlign eAlignMask, + const int pWidth, const int pHeight, const bool shadow, const float shadowDX, + const float shadowDY, const float shadowBlur, const bool stroke, + const float strokeR, const float strokeG, const float strokeB, + const float strokeSize) { + // Manages typographical styles, such as text orientation. + cCTextBitmap->_typographyStyle = OH_Drawing_CreateTypographyStyle(); + // Set the text to be displayed from left to right. + OH_Drawing_SetTypographyTextDirection(cCTextBitmap->_typographyStyle, TEXT_DIRECTION_LTR); + int align = processTextAlign((int)eAlignMask); + // Set text alignment + OH_Drawing_SetTypographyTextAlign(cCTextBitmap->_typographyStyle, align); + // Used to load fonts + cCTextBitmap->_fontCollection = OH_Drawing_CreateFontCollection(); + // Creates a pointer to the OH_Drawing_TypographyCreate object + cCTextBitmap->_typographyCreate = OH_Drawing_CreateTypographyHandler(cCTextBitmap->_typographyStyle, cCTextBitmap->_fontCollection); + // Used to manage font colors, decorations, etc. + cCTextBitmap->_textStyle = OH_Drawing_CreateTextStyle(); + + // Set Text Color + OH_Drawing_SetTextStyleColor(cCTextBitmap->_textStyle, OH_Drawing_ColorSetArgb(fontTintA, fontTintR, fontTintG, fontTintB)); + + // Set text size + OH_Drawing_SetTextStyleFontSize(cCTextBitmap->_textStyle, fontSize == 0 ? DEFAULT_FONTSIZE : fontSize); + // Set word weight + OH_Drawing_SetTextStyleFontWeight(cCTextBitmap->_textStyle, FONT_WEIGHT_400); + // Set the font baseline position. TEXT_BASELINE_ALPHABotic is used to display phonetic characters and the baseline + // position is lower in the middle. TEXT_BASELINE_IDEOGRAPHIC for ideographic text with baseline at bottom + OH_Drawing_SetTextStyleBaseLine(cCTextBitmap->_textStyle, TEXT_BASELINE_ALPHABETIC); + // Set font height + OH_Drawing_SetTextStyleFontHeight(cCTextBitmap->_textStyle, 1); + const char *fontFamilies[] = {pFontName}; + // Set the font type + OH_Drawing_SetTextStyleFontFamilies(cCTextBitmap->_textStyle, 1, fontFamilies); + // Set the font style. The font style is not italicized. FONT_EVEN_ITALIC Italic + OH_Drawing_SetTextStyleFontStyle(cCTextBitmap->_textStyle, FONT_STYLE_NORMAL); + // Setting the Language Area + OH_Drawing_SetTextStyleLocale(cCTextBitmap->_textStyle, "en"); + + // Set the typesetting style + OH_Drawing_TypographyHandlerPushTextStyle(cCTextBitmap->_typographyCreate, cCTextBitmap->_textStyle); + // Set text content + OH_Drawing_TypographyHandlerAddText(cCTextBitmap->_typographyCreate, text); + // Typesetting pop-up + OH_Drawing_TypographyHandlerPopTextStyle(cCTextBitmap->_typographyCreate); + + // Used to create OH_Drawing_Typography, which is used to manage the layout and display of typesetting. + cCTextBitmap->_typography = OH_Drawing_CreateTypography(cCTextBitmap->_typographyCreate); + + // The input width of the outer layer is preferentially used. + int layoutWidth = pWidth; + if (pWidth == 0) { + // If there is no width set, assume a maximum width and calculate the actual width as the layout width + OH_Drawing_TypographyLayout(cCTextBitmap->_typography, 100000); + layoutWidth = OH_Drawing_TypographyGetMaxIntrinsicWidth(cCTextBitmap->_typography) + 1; + } + + // typographic layout, setting maximum text width + OH_Drawing_TypographyLayout(cCTextBitmap->_typography, layoutWidth); + + // Obtains the maximum inherent width. + int realWidth = OH_Drawing_TypographyGetMaxIntrinsicWidth(cCTextBitmap->_typography); + // Obtaining the height + int realHeight = OH_Drawing_TypographyGetHeight(cCTextBitmap->_typography); + int textWidth = pWidth != 0 ? pWidth : realWidth; + int textHeight = pHeight != 0 ? pHeight : realHeight; + + // Format used to describe the bit pixel, including color type and transparency type. + cCTextBitmap->_bitmap = OH_Drawing_BitmapCreate(); + // COLOR_FORMAT_RGBA_8888:Each pixel is represented by a 32-bit quantity. 8 bits indicate transparency, 8 bits + // indicate red, 8 bits indicate green, and 8 bits indicate blue. ALPHA_FORMAT_OPAQUE:Bitmap has no transparency + OH_Drawing_BitmapFormat cFormat = {COLOR_FORMAT_RGBA_8888, ALPHA_FORMAT_OPAQUE}; + + // Initializes the width and height of the bitmap object, and sets the pixel format for the bitmap. + OH_Drawing_BitmapBuild(cCTextBitmap->_bitmap, textWidth, textHeight, &cFormat); + + // Create a canvas object + cCTextBitmap->_canvas = OH_Drawing_CanvasCreate(); + // Bind a bitmap object to the canvas so that the content drawn on the canvas is output to the bitmap (i.e., CPU rendering). + OH_Drawing_CanvasBind(cCTextBitmap->_canvas, cCTextBitmap->_bitmap); + + double xStart = calxStartPosition(align, layoutWidth, realWidth, textWidth); + double yStart = calyStartPosition((int)eAlignMask, realHeight, textHeight); + double position[2] = {xStart, yStart}; + // Uses the specified color to clear the canvas. OH_Drawing_ColorSetArgb: Converts four variables (respectively + // describing transparency, red, green, and blue) to a 32-bit (ARGB) variable that describes colors. + OH_Drawing_CanvasClear(cCTextBitmap->_canvas, OH_Drawing_ColorSetArgb(0x00, 0xFF, 0x00, 0x00)); + // Display Text + OH_Drawing_TypographyPaint(cCTextBitmap->_typography, cCTextBitmap->_canvas, position[0], position[1]); + + // Obtains the pixel address of a specified bitmap. The pixel data of the bitmap can be obtained based on the pixel address. + cCTextBitmap->pixelAddr = OH_Drawing_BitmapGetPixels(cCTextBitmap->_bitmap); + cCTextBitmap->width = textWidth; + cCTextBitmap->height = textHeight; +} + +void *CCTextBitmap::getPixelAddr() { + return pixelAddr; +} + +CCTextBitmap::~CCTextBitmap() { + OH_Drawing_CanvasDestroy(_canvas); + _canvas = nullptr; + OH_Drawing_BitmapDestroy(_bitmap); + _bitmap = nullptr; + OH_Drawing_DestroyTypography(_typography); + _typography = nullptr; + OH_Drawing_DestroyTextStyle(_textStyle); + _textStyle = nullptr; + OH_Drawing_DestroyTypographyHandler(_typographyCreate); + _typographyCreate = nullptr; + OH_Drawing_DestroyFontCollection(_fontCollection); + _fontCollection = nullptr; + OH_Drawing_DestroyTypographyStyle(_typographyStyle); + _typographyStyle = nullptr; + + pixelAddr = nullptr; +} + +NS_CC_END diff --git a/cocos/platform/ohos/CCTextBitmap.h b/cocos/platform/ohos/CCTextBitmap.h new file mode 100644 index 000000000000..39cea74e5c70 --- /dev/null +++ b/cocos/platform/ohos/CCTextBitmap.h @@ -0,0 +1,72 @@ +#ifndef XComponent_CCTextBitmap_H +#define XComponent_CCTextBitmap_H + +#include "platform/CCPlatformMacros.h" +#include "platform/CCImage.h" +#include "platform/CCDevice.h" + +#include +#include +#include +#include + +#define DEFAULT_FONTSIZE 20 + +NS_CC_BEGIN +class CCTextBitmap { + public: + static const int HORIZONTALALIGN_LEFT = 1; + static const int HORIZONTALALIGN_RIGHT = 2; + static const int HORIZONTALALIGN_CENTER = 3; + + static const int VERTICALALIGN_TOP = 1; + static const int VERTICALALIGN_BOTTOM = 2; + static const int VERTICALALIGN_CENTER = 3; + static void createCCTextBitmap(CCTextBitmap* cCTextBitmap, const char *text, const char *pFontName, const float a, const float r, const float g, const float b, + const Device::TextAlign eAlignMask, int width_, int height_, double fontSize); + static void createCCTextBitmap(CCTextBitmap* cCTextBitmap, const char *text, const char *pFontName, const int pFontSize, + const float fontTintA, const float fontTintR, const float fontTintG, const float fontTintB, + const Device::TextAlign eAlignMask, const int pWidth, const int pHeight, const bool shadow, + const float shadowDX, const float shadowDY, const float shadowBlur, const bool stroke, + const float strokeR, const float strokeG, const float strokeB, const float strokeSize); + + ~CCTextBitmap(); + void* getPixelAddr(); + int GetWidth() { return width; } + int GetHeight() { return height; } + private: + + /** + * Calculate the start point of x-drawing + * @param pAlignment Edge settings for text + * @param layoutWidth Width when text is measured + * @param realWidth Actual text width + * @param textWidth Width of the last display of the text control, that is, the width of the bitmap. + * @return x The starting point of the drawing + */ + static double calxStartPosition(int pAlignment, int layoutWidth, int realWidth, int textWidth); + + /** + * Calculate the start point of y drawing + * @param pAlignment Edge settings for text + * @param realWidth Actual text width + * @param textWidth Width of the last display of the text control, that is, the width of the bitmap. + * @return x The starting point of the drawing + */ + static double calyStartPosition(int pAlignment, int realHeight, int textHeight); + + static int processTextAlign(int pAlignment); + void* pixelAddr = nullptr; + int width = 0; + int height = 0; + + OH_Drawing_Bitmap* _bitmap{nullptr}; + OH_Drawing_Canvas* _canvas{nullptr}; + OH_Drawing_TypographyStyle* _typographyStyle{nullptr}; + OH_Drawing_TypographyCreate* _typographyCreate{nullptr}; + OH_Drawing_FontCollection* _fontCollection{nullptr}; + OH_Drawing_TextStyle* _textStyle{nullptr}; + OH_Drawing_Typography *_typography{nullptr}; + }; +NS_CC_END +#endif diff --git a/cocos/platform/ohos/libSysCapabilities/Readme.txt b/cocos/platform/ohos/libSysCapabilities/Readme.txt new file mode 100644 index 000000000000..c830be7540cc --- /dev/null +++ b/cocos/platform/ohos/libSysCapabilities/Readme.txt @@ -0,0 +1,2 @@ +Currently, the DevEco does not support referencing Har projects outside the project directory. +Related capabilities are being planned. The libSysCapabilities folder of each project will be unified here. \ No newline at end of file diff --git a/cocos/platform/ohos/napi/WorkerMessageQueue.cpp b/cocos/platform/ohos/napi/WorkerMessageQueue.cpp new file mode 100644 index 000000000000..dfec23503bb4 --- /dev/null +++ b/cocos/platform/ohos/napi/WorkerMessageQueue.cpp @@ -0,0 +1,20 @@ +#include "WorkerMessageQueue.h" + +void WorkerMessageQueue::enqueue(const WorkerMessageData& data) { + std::lock_guard lck(_mutex); + _queue.push(data); +} + +bool WorkerMessageQueue::dequeue(WorkerMessageData *data) { + std::lock_guard lck(_mutex); + if (empty()) { + return false; + } + *data = _queue.front(); + _queue.pop(); + return true; +} + +bool WorkerMessageQueue::empty() const { + return _queue.empty(); +} diff --git a/cocos/platform/ohos/napi/WorkerMessageQueue.h b/cocos/platform/ohos/napi/WorkerMessageQueue.h new file mode 100644 index 000000000000..3af2ab04b7f8 --- /dev/null +++ b/cocos/platform/ohos/napi/WorkerMessageQueue.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include +#include +#include + +enum class MessageType { + WM_XCOMPONENT_SURFACE_CREATED = 0, + WM_XCOMPONENT_TOUCH_EVENT, + WM_XCOMPONENT_KEY_EVENT, + WM_XCOMPONENT_MOUSE_EVENT, + WM_XCOMPONENT_MOUSE_WHEEL_EVENT, + WM_XCOMPONENT_SURFACE_CHANGED, + WM_XCOMPONENT_SURFACE_HIDE, + WM_XCOMPONENT_SURFACE_SHOW, + WM_XCOMPONENT_SURFACE_DESTROY, + WM_APP_SHOW, + WM_APP_HIDE, + WM_APP_DESTROY, + WM_VSYNC, +}; + +struct WorkerMessageData { + MessageType type; + void* data; + void* window; + OH_NativeXComponent_TouchEvent* touchEvent; +}; + +class WorkerMessageQueue final { +public: + void enqueue(const WorkerMessageData& data); + bool dequeue(WorkerMessageData *data); + bool empty() const; + size_t size() const { + return _queue.size(); + } + +private: + std::mutex _mutex; + std::queue _queue; +}; diff --git a/cocos/platform/ohos/napi/common/native_common.h b/cocos/platform/ohos/napi/common/native_common.h new file mode 100644 index 000000000000..8875458426f2 --- /dev/null +++ b/cocos/platform/ohos/napi/common/native_common.h @@ -0,0 +1,83 @@ +#ifndef _NATIVE_COMMON_H_ +#define _NATIVE_COMMON_H_ + +#define NAPI_RETVAL_NOTHING + +#define GET_AND_THROW_LAST_ERROR(env) \ + do { \ + const napi_extended_error_info* errorInfo = nullptr; \ + napi_get_last_error_info((env), &errorInfo); \ + bool isPending = false; \ + napi_is_exception_pending((env), &isPending); \ + if (!isPending && errorInfo != nullptr) { \ + const char* errorMessage = \ + errorInfo->error_message != nullptr ? errorInfo->error_message : "empty error message"; \ + napi_throw_error((env), nullptr, errorMessage); \ + } \ + } while (0) + +#define NAPI_ASSERT_BASE(env, assertion, message, retVal) \ + do { \ + if (!(assertion)) { \ + napi_throw_error((env), nullptr, "assertion (" #assertion ") failed: " message); \ + return retVal; \ + } \ + } while (0) + +#define NAPI_ASSERT(env, assertion, message) NAPI_ASSERT_BASE(env, assertion, message, nullptr) + +#define NAPI_ASSERT_RETURN_VOID(env, assertion, message) NAPI_ASSERT_BASE(env, assertion, message, NAPI_RETVAL_NOTHING) + +#define NAPI_CALL_BASE(env, theCall, retVal) \ + do { \ + if ((theCall) != napi_ok) { \ + GET_AND_THROW_LAST_ERROR((env)); \ + return retVal; \ + } \ + } while (0) + +#define NAPI_CALL(env, theCall) NAPI_CALL_BASE(env, theCall, nullptr) + +#define NAPI_CALL_RETURN_VOID(env, theCall) NAPI_CALL_BASE(env, theCall, NAPI_RETVAL_NOTHING) + +#define DECLARE_NAPI_PROPERTY(name, val) \ + { \ + (name), nullptr, nullptr, nullptr, nullptr, val, napi_default, nullptr \ + } + +#define DECLARE_NAPI_STATIC_PROPERTY(name, val) \ + { \ + (name), nullptr, nullptr, nullptr, nullptr, val, napi_static, nullptr \ + } + +#define DECLARE_NAPI_FUNCTION(name, func) \ + { \ + (name), nullptr, (func), nullptr, nullptr, nullptr, napi_default, nullptr \ + } + +#define DECLARE_NAPI_FUNCTION_WITH_DATA(name, func, data) \ + { \ + (name), nullptr, (func), nullptr, nullptr, nullptr, napi_default, data \ + } + +#define DECLARE_NAPI_STATIC_FUNCTION(name, func) \ + { \ + (name), nullptr, (func), nullptr, nullptr, nullptr, napi_static, nullptr \ + } + +#define DECLARE_NAPI_GETTER(name, getter) \ + { \ + (name), nullptr, nullptr, (getter), nullptr, nullptr, napi_default, nullptr \ + } + +#define DECLARE_NAPI_SETTER(name, setter) \ + { \ + (name), nullptr, nullptr, nullptr, (setter), nullptr, napi_default, nullptr \ + } + +#define DECLARE_NAPI_GETTER_SETTER(name, getter, setter) \ + { \ + (name), nullptr, nullptr, (getter), (setter), nullptr, napi_default, nullptr \ + } + +#endif /* _NATIVE_COMMON_H_ */ diff --git a/cocos/platform/ohos/napi/helper/JSRegisterUtils.h b/cocos/platform/ohos/napi/helper/JSRegisterUtils.h new file mode 100644 index 000000000000..d7947f06c4b2 --- /dev/null +++ b/cocos/platform/ohos/napi/helper/JSRegisterUtils.h @@ -0,0 +1,73 @@ +#ifndef CC_OH_JsRegister_H +#define CC_OH_JsRegister_H + +#include +#include +#include "NapiHelper.h" + +#define APP_LOG_DOMAIN 0x0001 +#define APP_LOG_TAG "JSRegisterUtils" +#define LOGI(...) ((void)OH_LOG_Print(LOG_APP, LOG_INFO, APP_LOG_DOMAIN, APP_LOG_TAG, __VA_ARGS__)) + +napi_env _env = nullptr; +napi_value initRegisterFunction(napi_env env, napi_value exports) { + LOGI("initRegisterFunction start!"); + _env = env; + return 0; +} + +napi_value registerFunction(napi_env env, napi_callback_info info) { + LOGI("====begin to registerFunction!"); + napi_status status; + napi_value exports; + size_t argc = 2; + napi_value args[2]; + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + + if (argc != 2) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_string) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + auto jsArg = args[0]; + size_t len = 0; + status = napi_get_value_string_utf8(env, jsArg, nullptr, 0, &len); + std::string functionName = ""; + functionName.resize(len, '\0'); + status = napi_get_value_string_utf8(env, jsArg, (char*)functionName.data(), functionName.size() + 1, &len); + + napi_valuetype functionType; + status = napi_typeof(env, args[1], &functionType); + if (status != napi_ok) { + return nullptr; + } + if (functionType != napi_function) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + napi_ref fucRef; + NAPI_CALL(env, napi_create_reference(env, args[1], 1, &fucRef)); + + char* name = new char[functionName.length() + 1]; + strcpy(name, functionName.c_str()); + JSFunction* jsFunction = new JSFunction(name, env, fucRef); + + JSFunction::addFunction(name, jsFunction); + + LOGI("begin to return!"); + return nullptr; +} + +#endif //CC_OH_JsRegister_H \ No newline at end of file diff --git a/cocos/platform/ohos/napi/helper/Js_Cocos2dxHelper.cpp b/cocos/platform/ohos/napi/helper/Js_Cocos2dxHelper.cpp new file mode 100644 index 000000000000..a833d8b07969 --- /dev/null +++ b/cocos/platform/ohos/napi/helper/Js_Cocos2dxHelper.cpp @@ -0,0 +1,54 @@ +#include +#include +#include "NapiHelper.h" +#include "Js_Cocos2dxHelper.h" +#include "platform/ohos/CCLogOhos.h" + +napi_env Js_Cocos2dxHelper::_env = nullptr; +napi_value Js_Cocos2dxHelper::initJsCocos2dxHelper(napi_env env, napi_callback_info info) { + _env = env; + return 0; +} + +/** + * If you have more information that can be obtained asynchronously, add it here. + */ +napi_value Js_Cocos2dxHelper::initAsyncInfo(napi_env env, napi_callback_info info) { + JSFunction::getFunction("DeviceUtils.initScreenInfo").invoke(); + return nullptr; +} + +std::string Js_Cocos2dxHelper::_asyncInfoMap[AsyncInfo::LAST_INDEX]; + +void Js_Cocos2dxHelper::terminateProcess() { + JSFunction::getFunction("ApplicationManager.exit").invoke(); +} + +// The default accelerometer interval is 10000000 ns, that is, 10 ms. +float Js_Cocos2dxHelper::_accelerometerInterval = 10000000.0f; +bool Js_Cocos2dxHelper::_accelerometerFlag = false; +void Js_Cocos2dxHelper::enableAccelerometer() { + // Start accelerometer subscription when allowed use default interval + JSFunction::getFunction("Accelerometer.enable").invoke(_accelerometerInterval); + _accelerometerFlag = true; +} + +void Js_Cocos2dxHelper::disableAccelerometer() { + JSFunction::getFunction("Accelerometer.disable").invoke(); + _accelerometerFlag = false; +} + +void Js_Cocos2dxHelper::setAccelerometerInterval(float interval) { + OHOS_LOGD("accelerometer setAccelerometerInterval, change to %{public}f", interval); + // Same as the original one. No handling is required. + if(_accelerometerInterval == interval) { + return; + } + _accelerometerInterval = interval; + + // if accelerometer running, restart with new interval + if(_accelerometerFlag) { + JSFunction::getFunction("Accelerometer.enable").invoke(_accelerometerInterval); + } +} + diff --git a/cocos/platform/ohos/napi/helper/Js_Cocos2dxHelper.h b/cocos/platform/ohos/napi/helper/Js_Cocos2dxHelper.h new file mode 100644 index 000000000000..1d0001badff6 --- /dev/null +++ b/cocos/platform/ohos/napi/helper/Js_Cocos2dxHelper.h @@ -0,0 +1,41 @@ +#ifndef __Js_Cocos2dxHelper_H__ +#define __Js_Cocos2dxHelper_H__ + +#include +#include +#include +#include + +#define APP_LOG_DOMAIN 0x0001 +#define APP_LOG_TAG "Js_Cocos2dxHelper" +#define LOGI(...) ((void)OH_LOG_Print(LOG_APP, LOG_INFO, APP_LOG_DOMAIN, APP_LOG_TAG, __VA_ARGS__)) + +enum AsyncInfo { + // VERSION_NAME = 0, + LAST_INDEX // Only indicates the size of the array used for storing data. It is meaningless. If an enumeration is added later, keep LAST_INDEX at the end. +}; + +class Js_Cocos2dxHelper { +public: + static napi_value initJsCocos2dxHelper(napi_env env, napi_callback_info info); + static napi_value initAsyncInfo(napi_env env, napi_callback_info info); + static void setAsyncInfo(AsyncInfo key, const std::string& value) { + _asyncInfoMap[key] = value; + } + + static std::string getAsyncInfo(AsyncInfo key) { + return _asyncInfoMap[key]; + } + + static void terminateProcess(); + static void enableAccelerometer(); + static void disableAccelerometer(); + static void setAccelerometerInterval(float interval); + +private: + static std::string _asyncInfoMap[]; + static napi_env _env; + static float _accelerometerInterval; + static bool _accelerometerFlag; +}; +#endif /* __Js_Cocos2dxHelper_H__ */ diff --git a/cocos/platform/ohos/napi/helper/NapiHelper.cpp b/cocos/platform/ohos/napi/helper/NapiHelper.cpp new file mode 100644 index 000000000000..898362cd144c --- /dev/null +++ b/cocos/platform/ohos/napi/helper/NapiHelper.cpp @@ -0,0 +1,3 @@ +#include "NapiHelper.h" + +std::unordered_map JSFunction::FUNCTION_MAP; diff --git a/cocos/platform/ohos/napi/helper/NapiHelper.h b/cocos/platform/ohos/napi/helper/NapiHelper.h new file mode 100644 index 000000000000..ac662559af63 --- /dev/null +++ b/cocos/platform/ohos/napi/helper/NapiHelper.h @@ -0,0 +1,254 @@ +#ifndef CC_OH_NapiHelper_H +#define CC_OH_NapiHelper_H + +#include +#include +#include +#include +#include "../common/native_common.h" +#include +#include "NapiValueConverter.h" +#include +#include +#include "Js_Cocos2dxHelper.h" + +#define APP_LOG_DOMAIN 0x0001 +#define APP_LOG_TAG "NapiHelper" +#define LOGI(...) ((void)OH_LOG_Print(LOG_APP, LOG_INFO, APP_LOG_DOMAIN, APP_LOG_TAG, __VA_ARGS__)) + +const static int BUFFER_SIZE = 1024 * 10; + +// Defines locks and semaphores. +typedef struct ThreadLockInfo { + std::mutex mutex; + std::condition_variable condition; + bool ready = false; +} ThreadLockInfo; + +typedef struct WorkParam { + napi_env env; + napi_ref funcRef; + std::string inParam; + std::string replyParam = std::string(); + int replyInt = -1; + char replyString[64] = {0}; + // lock structure + std::shared_ptr < ThreadLockInfo > lockInfo; +} WorkParam; + +static auto successCallback = [](napi_env env, napi_callback_info info) -> napi_value { + size_t sLen = 0; + size_t argc = 1; + napi_value args[1] = {nullptr}; + void *param_in = nullptr; + napi_get_cb_info(env, info, &argc, args, nullptr, ¶m_in); + WorkParam *callbackParam = reinterpret_cast(param_in); + + // Use the napi_get_value method to read the return value based on the return value type (number or string). + napi_valuetype type; + napi_typeof(env, args[0], &type); + switch(type) { + case napi_number: + if (napi_get_value_int32(env, args[0], &callbackParam->replyInt) != napi_ok) { + LOGI("[%s] get number value error", __FUNCTION__); + } + break; + case napi_string: + if (napi_get_value_string_utf8(env, args[0], callbackParam->replyString, sizeof(callbackParam->replyString), &sLen) != napi_ok) { + LOGI("[%s] get string value error", __FUNCTION__); + } + callbackParam->replyParam =callbackParam->replyString; + LOGI("XXXXXX:retChar %{public}s", callbackParam->replyString); + callbackParam->replyInt = sLen; + break; + default: + LOGI("[%s] type is unknown", __FUNCTION__); + break; + } + + /* Child thread unlocking */ + std::unique_lock lock(callbackParam->lockInfo->mutex); + callbackParam->lockInfo->ready = true; + callbackParam->lockInfo->condition.notify_all(); + return nullptr; +}; + +class JSFunction { +public: + napi_ref funcRef; + napi_env env; + char* name = nullptr; + +public: + static std::unordered_map FUNCTION_MAP; + + explicit JSFunction(char* name, napi_env env, napi_ref funcRef) + : name(name), env(env), funcRef(funcRef){} + + explicit JSFunction(char* name, napi_env env) + : name(name), env(env){} + + explicit JSFunction(char* name) + : name(name){} + + static JSFunction getFunction(std::string functionName) + { + return FUNCTION_MAP.at(functionName); + } + + static void addFunction(std::string name, JSFunction* jsFunction) { + FUNCTION_MAP.emplace(name, *jsFunction); + } + + template + typename std::enable_if::value, ReturnType>::type + invoke(Args... args) { + LOGI("=========cocos-[NApiHelper]=========JSFunction::invoke ========="); + napi_value global; + napi_status status = napi_get_global(env, &global); + //if (status != napi_ok) return; + + napi_value func; + status = napi_get_reference_value(env, funcRef, &func); + + napi_value jsArgs[sizeof...(Args)] = {NapiValueConverter::ToNapiValue(env, args)...}; + napi_value return_val; + status = napi_call_function(env, global, func, sizeof...(Args), jsArgs, &return_val); + + ReturnType value; + if (!NapiValueConverter::ToCppValue(env, return_val, value)) { + // Handle error here + } + return value; + } + + template + typename std::enable_if::value, void>::type + invoke(Args... args) { + LOGI("=========cocos-[NApiHelper]=========JSFunction::invoke ========="); + napi_value global; + napi_status status = napi_get_global(env, &global); + if (status != napi_ok) return; + + napi_value func; + status = napi_get_reference_value(env, funcRef, &func); + + napi_value jsArgs[sizeof...(Args)] = {NapiValueConverter::ToNapiValue(env, args)...}; + napi_value return_val; + status = napi_call_function(env, global, func, sizeof...(Args), jsArgs, &return_val); + } + + static void callFunctionWithParams(WorkParam *param) { + napi_ref funcRef = param->funcRef; + std::string inParam = param->inParam; + napi_env env = param->env; + napi_value global; + napi_status status = napi_get_global(env, &global); + if (status != napi_ok) { + LOGI("XXXXXX:[getClassObject] napi_get_global != napi_ok"); + } + + napi_value func; + // Obtains the native interface and invokes the JS function transactFromNative. + status = napi_get_reference_value(env, funcRef, &func); + if (status != napi_ok) { + LOGI("XXXXXX: napi_get_named_property getClassObject != napi_ok %{public}d", status); + } + + napi_value promise; + // Obtains the promise returned by the transactFromNative function. + if (inParam.empty()){ + status = napi_call_function(env, global, func, 0, nullptr, &promise); + }else { + napi_value argsOne[1] = { nullptr }; + napi_create_string_utf8(env, inParam.c_str(), NAPI_AUTO_LENGTH, &argsOne[0]); + //napi_create_int32(env, 22, &argsOne[0]); + status = napi_call_function(env, global, func, 1, argsOne, &promise); + } + if (status != napi_ok) { + LOGI("XXXXXX:napi_call_function getClassObject != napi_ok %{public}d", status); + } + + napi_value thenFunc = nullptr; + // Obtains the then function of the promise. + status = napi_get_named_property(env, promise, "then", &thenFunc); + if (status != napi_ok) { + LOGI("XXXXXX:napi_get_named_property then failed, ret: %{public}d", status); + } + + napi_value successFunc = nullptr; + // Note that the fifth parameter is passed to the callback function to obtain the return value in the callback. + status = napi_create_function(env, "successFunc", NAPI_AUTO_LENGTH, successCallback, param, &successFunc); + if (status != napi_ok) { + LOGI("XXXXXX:napi_create_function successFunc failed, ret: %{public}d", status); + } + napi_value ret; + // Call the then function + status = napi_call_function(env, promise, thenFunc, 1, &successFunc, &ret); + if (status != napi_ok) { + LOGI("XXXXXX:napi_call_function thenFunc failed, ret: %{public}d", status); + } + } + // Callback Function Type + typedef std::function Callback; + + // Subthread function RunPromiseType: used to access the asynchronous function that returns promise in JS and implement synchronization. + static void RunPromiseType(napi_env env,napi_ref funcRef, Callback callback,const char *argstr) { + WorkParam *workParam = new (std::nothrow) WorkParam; + workParam->env = env; + workParam->funcRef = funcRef; + workParam->inParam = argstr; + workParam->lockInfo = std::make_shared(); + + uv_loop_s * loop = nullptr; + // Obtains the loop thread corresponding to the env in the JS. + napi_get_uv_event_loop(workParam->env, &loop); + // create uv_work + uv_work_t * work = new (std::nothrow) uv_work_t; + if (work == nullptr) { + LOGI("XXXXXX:failed to new uv_work_t"); + delete workParam; + } + // Thread input parameters are stored work->data + work->data = reinterpret_cast < void * > (workParam); + + // Execute work in the JS thread. + uv_queue_work( + loop, work, [](uv_work_t * work) {}, [](uv_work_t * work, int _status) { + WorkParam * param = reinterpret_cast < WorkParam * > (work->data); + callFunctionWithParams(param); + }); + LOGI("XXXXXX:childThread lock and wait"); + // The sub-thread is locked and waits for the callback result of the JS thread. + std::unique_lock lock(workParam->lockInfo->mutex); + // You can change the value to wait_for to prevent timeout. + workParam->lockInfo->condition.wait(lock,[&workParam] { return workParam->lockInfo->ready; }); + // workParam->lockInfo->condition.wait_for(lock,std::chrono::milliseconds(2000), [&workParam] { return workParam->lockInfo->ready; }); + LOGI("XXXXXX:wait work end, result %{public}s", workParam->replyParam.c_str()); + callback(workParam->replyParam); + delete workParam; + delete work; + } + + void invokeAsync(AsyncInfo key, const char *argstr) { + Callback callback = [key](std::string value) { + Js_Cocos2dxHelper::setAsyncInfo(key, value);//callback + }; + // Create a child thread + std::thread threadTestPromise(RunPromiseType,env,funcRef,callback,argstr); + // Thread separation + threadTestPromise.detach(); + } + + void invokeAsync(AsyncInfo key) { + Callback callback = [key](std::string value) { + Js_Cocos2dxHelper::setAsyncInfo(key, value);//callback + }; + // Create a child thread + std::thread threadTestPromise(RunPromiseType,env,funcRef,callback,""); + // Thread separation + threadTestPromise.detach(); + } + +}; +#endif //CC_OH_NapiHelper_H diff --git a/cocos/platform/ohos/napi/helper/NapiValueConverter.cpp b/cocos/platform/ohos/napi/helper/NapiValueConverter.cpp new file mode 100644 index 000000000000..9cfcec7cf54f --- /dev/null +++ b/cocos/platform/ohos/napi/helper/NapiValueConverter.cpp @@ -0,0 +1,89 @@ +#include "NapiValueConverter.h" +#include + +napi_valuetype GetNapiValueType(napi_env env, napi_value value) { + napi_valuetype type; + napi_typeof(env, value, &type); + return type; +} + +template<> +bool NapiValueConverter::ToCppValue(napi_env env, napi_value value, int& result) { + if (GetNapiValueType(env, value) != napi_number) { + napi_throw_type_error(env, nullptr, "Expected number"); + return false; + } + napi_get_value_int32(env, value, &result); + return true; +} + +template<> +bool NapiValueConverter::ToCppValue(napi_env env, napi_value value, bool& result) { + if (GetNapiValueType(env, value) != napi_boolean) { + napi_throw_type_error(env, nullptr, "Expected boolean"); + return false; + } + napi_get_value_bool(env, value, &result); + return true; +} + +template<> +bool NapiValueConverter::ToCppValue(napi_env env, napi_value value, double& result) { + if (GetNapiValueType(env, value) != napi_number) { + napi_throw_type_error(env, nullptr, "Expected number"); + return false; + } + napi_get_value_double(env, value, &result); + return true; +} + +template<> +bool NapiValueConverter::ToCppValue(napi_env env, napi_value value, std::string& result) { + if (GetNapiValueType(env, value) != napi_string) { + napi_throw_type_error(env, nullptr, "Expected string"); + return false; + } + + size_t str_size; + napi_get_value_string_utf8(env, value, nullptr, 0, &str_size); + char* buf = new char[str_size + 1]; + napi_get_value_string_utf8(env, value, buf, str_size + 1, &str_size); + result = std::string(buf); + delete[] buf; + + return true; +} + +napi_value NapiValueConverter::ToNapiValue(napi_env env, int32_t value) { + napi_value result; + napi_create_int32(env, value, &result); + return result; +} + +napi_value NapiValueConverter::ToNapiValue(napi_env env, int64_t value) { + napi_value result; + napi_create_int64(env, value, &result); + return result; +} + +napi_value NapiValueConverter::ToNapiValue(napi_env env, double value) { + napi_value result; + napi_create_double(env, value, &result); + return result; +} + +napi_value NapiValueConverter::ToNapiValue(napi_env env, bool value) { + napi_value result; + napi_get_boolean(env, value, &result); + return result; +} + +napi_value NapiValueConverter::ToNapiValue(napi_env env, const char* value) { + napi_value result; + napi_create_string_utf8(env, value, strlen(value), &result); + return result; +} + +napi_value NapiValueConverter::ToNapiValue(napi_env env, std::string value) { + return ToNapiValue(env, value.c_str()); +} \ No newline at end of file diff --git a/cocos/platform/ohos/napi/helper/NapiValueConverter.h b/cocos/platform/ohos/napi/helper/NapiValueConverter.h new file mode 100644 index 000000000000..a26e327ed637 --- /dev/null +++ b/cocos/platform/ohos/napi/helper/NapiValueConverter.h @@ -0,0 +1,16 @@ +#include +#include + +class NapiValueConverter { +public: + template + static bool ToCppValue(napi_env env, napi_value value, ReturnType& result); + + static napi_value ToNapiValue(napi_env env, int32_t value); + static napi_value ToNapiValue(napi_env env, int64_t value); + static napi_value ToNapiValue(napi_env env, double value); + static napi_value ToNapiValue(napi_env env, bool value); + static napi_value ToNapiValue(napi_env env, const char* value); + static napi_value ToNapiValue(napi_env env, std::string value); + +}; diff --git a/cocos/platform/ohos/napi/modules/InputNapi.cpp b/cocos/platform/ohos/napi/modules/InputNapi.cpp new file mode 100644 index 000000000000..73b87c2a3a3d --- /dev/null +++ b/cocos/platform/ohos/napi/modules/InputNapi.cpp @@ -0,0 +1,185 @@ +#include +#include +#include "InputNapi.h" +#include "platform/ohos/napi/plugin_manager.h" +#include "../../CCLogOhos.h" +#include "ui/UIEditBox/UIEditBoxImpl-ohos.h" +#include "base/CCIMEDispatcher.h" + +napi_value InputNapi::editBoxOnFocusCB(napi_env env, napi_callback_info info) { + + napi_status status; + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + if (argc != 1) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_number) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + int32_t index; + NAPI_CALL(env, napi_get_value_int32(env, args[0], &index)); + + cocos2d::ui::EditBoxImplOhos::onBeginCallBack(index); + return nullptr; +} + +napi_value InputNapi::editBoxOnChangeCB(napi_env env, napi_callback_info info) { + + napi_status status; + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + if (argc != 2) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_number) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + status = napi_typeof(env, args[1], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_string) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + int32_t index; + NAPI_CALL(env, napi_get_value_int32(env, args[0], &index)); + size_t pInt; + char text[256]; + NAPI_CALL(env, napi_get_value_string_utf8(env, args[1], text, 256, &pInt)); + + cocos2d::ui::EditBoxImplOhos::onChangeCallBack(index, text); + return nullptr; +} + +napi_value InputNapi::editBoxOnEnterCB(napi_env env, napi_callback_info info) { + + napi_status status; + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + if (argc != 2) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_number) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + status = napi_typeof(env, args[1], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_string) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + int32_t index; + NAPI_CALL(env, napi_get_value_int32(env, args[0], &index)); + size_t pInt; + char text[256]; + NAPI_CALL(env, napi_get_value_string_utf8(env, args[1], text, 256, &pInt)); + + cocos2d::ui::EditBoxImplOhos::onEnterCallBack(index, text); + return nullptr; +} + +napi_value InputNapi::textFieldTTFOnChangeCB(napi_env env, napi_callback_info info) { + + napi_status status; + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + if (argc != 1) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_string) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + auto dispatcher = cocos2d::IMEDispatcher::sharedDispatcher(); + const std::string& oldContent = dispatcher->getContentText(); + + size_t textLen; + char text[2560] = {0}; + NAPI_CALL(env, napi_get_value_string_utf8(env, args[0], text, 2560, &textLen)); + + // Optimization: Use string_view to avoid unnecessary string copying + std::string_view oldView(oldContent); + std::string_view newView(text, textLen); + + // Find the first different character position + size_t commonPrefixLen = 0; + const size_t minLen = std::min(oldView.length(), newView.length()); + while (commonPrefixLen < minLen && oldView[commonPrefixLen] == newView[commonPrefixLen]) { + commonPrefixLen++; + } + + // Delete the old content characters after the difference + const size_t charsToDelete = oldView.length() - commonPrefixLen; + const size_t deleteOperations = [&](){ + size_t count =0; + size_t pos = oldView.length() -1; + size_t remaining = charsToDelete; + while(remaining >0){ + //Check UTF-8 Chinese characters (3-byte characters starting with 0xE0-0xEF) + bool isChineseChar = (pos >= 2 && + (unsigned char)oldView[pos -2]>=0xE0 && + (unsigned char)oldView[pos -2] <= 0xEF); + remaining -= isChineseChar? 3:1; + pos -= isChineseChar?3:1; + count++; + } + return count; + }(); + //Delete characters in batches + for (size_t i = 0; i < deleteOperations; i++) { + dispatcher->dispatchDeleteBackward(); + } + + // Insert new characters after the difference + const size_t insertLen = newView.length() - commonPrefixLen; + if (insertLen > 0) { + const char* newText = text + commonPrefixLen; + dispatcher->dispatchInsertText(newText, insertLen); + } + return nullptr; +} diff --git a/cocos/platform/ohos/napi/modules/InputNapi.h b/cocos/platform/ohos/napi/modules/InputNapi.h new file mode 100644 index 000000000000..c36d91d4af87 --- /dev/null +++ b/cocos/platform/ohos/napi/modules/InputNapi.h @@ -0,0 +1,15 @@ +#ifndef MyApplication_InputNapi_H +#define MyApplication_InputNapi_H + +#include +#include + +class InputNapi { +public: + static napi_value editBoxOnFocusCB(napi_env env, napi_callback_info info); + static napi_value editBoxOnChangeCB(napi_env env, napi_callback_info info); + static napi_value editBoxOnEnterCB(napi_env env, napi_callback_info info); + static napi_value textFieldTTFOnChangeCB(napi_env env, napi_callback_info info); +}; + +#endif //MyApplication_InputNapi_H diff --git a/cocos/platform/ohos/napi/modules/MouseNapi.cpp b/cocos/platform/ohos/napi/modules/MouseNapi.cpp new file mode 100644 index 000000000000..b79d5eef4490 --- /dev/null +++ b/cocos/platform/ohos/napi/modules/MouseNapi.cpp @@ -0,0 +1,50 @@ +// +// Created on 2024/01/05. +// +// Node APIs are not fully supported. To solve the compilation error of the interface cannot be found, +// please include "napi/native_api.h". + +#include +#include +#include "MouseNapi.h" +#include "platform/ohos/napi/plugin_manager.h" +#include "../../CCLogOhos.h" +#include "base/CCIMEDispatcher.h" +#include "platform/ohos/napi/render/plugin_render.h" +#include + +napi_value MouseNapi::mouseWheelCB(napi_env env, napi_callback_info info) { + napi_status status; + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + if (argc != 2) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_string) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + status = napi_typeof(env, args[1], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_number) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + size_t pInt; + char eventType[256]; + NAPI_CALL(env, napi_get_value_string_utf8(env, args[0], eventType, 256, &pInt)); + double scrollY; + NAPI_CALL(env, napi_get_value_double(env, args[1], &scrollY)); + PluginRender::MouseWheelCB(eventType, scrollY); + return nullptr; +} \ No newline at end of file diff --git a/cocos/platform/ohos/napi/modules/MouseNapi.h b/cocos/platform/ohos/napi/modules/MouseNapi.h new file mode 100644 index 000000000000..39971a4da045 --- /dev/null +++ b/cocos/platform/ohos/napi/modules/MouseNapi.h @@ -0,0 +1,18 @@ +// +// Created on 2024/01/05. +// +// Node APIs are not fully supported. To solve the compilation error of the interface cannot be found, +// please include "napi/native_api.h". + +#ifndef MyApplication_MouseNapi_H +#define MyApplication_MouseNapi_H + +#include +#include + +class MouseNapi { +public: + static napi_value mouseWheelCB(napi_env env, napi_callback_info info); +}; + +#endif //MyApplication_MouseNapi_H \ No newline at end of file diff --git a/cocos/platform/ohos/napi/modules/RawFileUtils.cpp b/cocos/platform/ohos/napi/modules/RawFileUtils.cpp new file mode 100644 index 000000000000..d98954faa976 --- /dev/null +++ b/cocos/platform/ohos/napi/modules/RawFileUtils.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include + +#include "platform/ohos/napi/plugin_manager.h" +#include "../../CCLogOhos.h" + +#include +#include "RawFileUtils.h" + + +NativeResourceManager* RawFileUtils::nativeResourceManager_ = nullptr; + +bool RawFileUtils::InitResourceManager(napi_env env, napi_value param) { + nativeResourceManager_ = OH_ResourceManager_InitNativeResourceManager(env, param); + OHOS_LOGD("cocos qgh initResourceManager %{public}p", nativeResourceManager_); + return true; +} + +std::vector RawFileUtils::searchFiles(const char *folder, bool recursive) { + std::vector results; + char *realFolder = const_cast (folder); + if (strcmp(folder, "/") == 0) { + realFolder = ""; + } + auto dir = OH_ResourceManager_OpenRawDir(nativeResourceManager_, realFolder); + if (dir) { + int file_count = GetDirSize(dir); + for (int index = 0; index < file_count; index++) { + std::string fileName = OH_ResourceManager_GetRawFileName(dir, index); + auto item = std::string(realFolder) + "/" + fileName; + results.push_back(item); + if (recursive) { + auto items = searchFiles(item.c_str(), recursive); + results.insert(results.end(), items.begin(), items.end()); + } + } + OH_ResourceManager_CloseRawDir(dir); + } + return results; +} diff --git a/cocos/platform/ohos/napi/modules/RawFileUtils.h b/cocos/platform/ohos/napi/modules/RawFileUtils.h new file mode 100644 index 000000000000..86fec15b7fe1 --- /dev/null +++ b/cocos/platform/ohos/napi/modules/RawFileUtils.h @@ -0,0 +1,84 @@ +#ifndef __RawFileUtils_H__ +#define __RawFileUtils_H__ + + +#include +#include + +#include +#include +#include + +#include "../common/native_common.h" + +#include +#include +#include + +class RawFileUtils { +public: + static bool InitResourceManager(napi_env env, napi_value info); + + static napi_value nativeResourceManagerInit(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + InitResourceManager(env, args[0]); + return nullptr; + } + + static RawFileUtils& GetInstance() { + static RawFileUtils instance; + return instance; + } + + RawFile *Open(const char *fileName) { + return OH_ResourceManager_OpenRawFile(nativeResourceManager_, fileName); + } + + RawDir *OpenDir(const char *dirName) { + return OH_ResourceManager_OpenRawDir(nativeResourceManager_, dirName); + } + + void Close(RawFile *file) { + return OH_ResourceManager_CloseRawFile(file); + } + + void CloseDir(RawDir *rawDir) { + return OH_ResourceManager_CloseRawDir(rawDir); + } + + int Seek(const RawFile *file, long offset, int whence) { + return OH_ResourceManager_SeekRawFile(file, offset, whence); + } + + long GetSize(RawFile* file) { + return OH_ResourceManager_GetRawFileSize(file); + } + + long Read(RawFile *file, void* buf, size_t length) { + return OH_ResourceManager_ReadRawFile(file, buf, length); + } + + int GetDirSize(RawDir* rawDir) { + return OH_ResourceManager_GetRawFileCount(rawDir); + } + + bool GetRawFileDescriptor(RawFile *file, RawFileDescriptor &descriptor) { + return OH_ResourceManager_GetRawFileDescriptor(file, descriptor); + } + + bool ReleaseRawFileDescriptor(RawFileDescriptor &descriptor) { + return OH_ResourceManager_ReleaseRawFileDescriptor(descriptor); + } + + std::vector searchFiles(const char *folder, bool recursive = false); + + static NativeResourceManager* GetRM() { return nativeResourceManager_;} +private: + static NativeResourceManager* nativeResourceManager_; +}; + + + +#endif // __RawFileUtils_H__ diff --git a/cocos/platform/ohos/napi/modules/SensorNapi.cpp b/cocos/platform/ohos/napi/modules/SensorNapi.cpp new file mode 100644 index 000000000000..b8f3eeb56449 --- /dev/null +++ b/cocos/platform/ohos/napi/modules/SensorNapi.cpp @@ -0,0 +1,63 @@ +#include +#include +#include "cocos2d.h" +#include "base/CCEventAcceleration.h" +#include "platform/ohos/CCLogOhos.h" +#include "SensorNapi.h" +#include "../common/native_common.h" + +napi_value SensorNapi::onAccelerometerCallBack(napi_env env, napi_callback_info info) { + napi_status status; + size_t argc = 4; + napi_value args[4]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + if (argc != 4) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_number) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + status = napi_typeof(env, args[3], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_number) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + double x; + NAPI_CALL(env, napi_get_value_double(env, args[0], &x)); + double y; + NAPI_CALL(env, napi_get_value_double(env, args[1], &y)); + double z; + NAPI_CALL(env, napi_get_value_double(env, args[2], &z)); + long timestamp; + NAPI_CALL(env, napi_get_value_int64(env, args[3], ×tamp)); + + dispatchAccelerometer(x, y, z, timestamp); + return nullptr; +} + +#define TG3_GRAVITY_EARTH (9.80665f) + +void SensorNapi::dispatchAccelerometer(double x, double y, double z, long timeStamp) { + OHOS_LOGD("accelerometer callback, interval is %{public}d", timeStamp); + cocos2d::Acceleration a; + a.x = -(x / TG3_GRAVITY_EARTH); + a.y = -(y / TG3_GRAVITY_EARTH); + a.z = -(z / TG3_GRAVITY_EARTH); + a.timestamp = (double)timeStamp; + + cocos2d::EventAcceleration event(a); + cocos2d::Director::getInstance()->getEventDispatcher()->dispatchEvent(&event); +} \ No newline at end of file diff --git a/cocos/platform/ohos/napi/modules/SensorNapi.h b/cocos/platform/ohos/napi/modules/SensorNapi.h new file mode 100644 index 000000000000..2325d5767c54 --- /dev/null +++ b/cocos/platform/ohos/napi/modules/SensorNapi.h @@ -0,0 +1,14 @@ +#ifndef _SENSOR_NAPI_H +#define _SENSOR_NAPI_H + +#include + +class SensorNapi { +public: + static napi_value onAccelerometerCallBack(napi_env env, napi_callback_info info); + +private: + static void dispatchAccelerometer(double x, double y, double z, long timeStamp); +}; + +#endif //_SENSOR_NAPI_H \ No newline at end of file diff --git a/cocos/platform/ohos/napi/modules/TouchesNapi.cpp b/cocos/platform/ohos/napi/modules/TouchesNapi.cpp new file mode 100644 index 000000000000..e4e2398e656d --- /dev/null +++ b/cocos/platform/ohos/napi/modules/TouchesNapi.cpp @@ -0,0 +1,46 @@ +#include "deprecated/CCSet.h" +#include "base/CCDirector.h" +#include "base/CCTouch.h" +#include "platform/CCGLView.h" +#include "platform/ohos/CCLogOhos.h" +#include + +using namespace cocos2d; + +extern "C" { + void Cocos2dxRenderer_nativeTouchesBegin(int num, intptr_t ids[] , float xs[], float ys[]) { + cocos2d::Director::sharedDirector()->getOpenGLView()->handleTouchesBegin(num, ids, xs, ys); + } + + void Cocos2dxRenderer_nativeTouchesEnd(intptr_t id, float x, float y) { + cocos2d::Director::sharedDirector()->getOpenGLView()->handleTouchesEnd(1, &id, &x, &y); + } + + void Cocos2dxRenderer_nativeTouchesMove(int num, intptr_t ids[] , float xs[], float ys[]) { + cocos2d::Director::sharedDirector()->getOpenGLView()->handleTouchesMove(num, ids, xs, ys); + } + + void Cocos2dxRenderer_nativeTouchesCancel(int num, intptr_t ids[] , float xs[], float ys[]) { + cocos2d::Director::sharedDirector()->getOpenGLView()->handleTouchesCancel(num, ids, xs, ys); + } + + #define KEYCODE_BACK 0x04 + #define KEYCODE_MENU 0x52 + bool Cocos2dxRenderer_nativeKeyDown(int keyCode) { + Director* pDirector = Director::sharedDirector(); + switch (keyCode) { + case KEYCODE_BACK: + // TBD need fixed +// if (pDirector->getKeypadDispatcher()->dispatchKeypadMSG(kTypeBackClicked)) + return true; + break; + case KEYCODE_MENU: +// if (pDirector->getKeypadDispatcher()->dispatchKeypadMSG(kTypeMenuClicked)) + return true; + break; + default: + return false; + } + return 0; + } +} diff --git a/cocos/platform/ohos/napi/modules/TouchesNapi.h b/cocos/platform/ohos/napi/modules/TouchesNapi.h new file mode 100644 index 000000000000..4f159199a042 --- /dev/null +++ b/cocos/platform/ohos/napi/modules/TouchesNapi.h @@ -0,0 +1,21 @@ +#ifndef __TouchesNapi_H__ +#define __TouchesNapi_H__ + +#include "deprecated/CCSet.h" +#include "base/CCDirector.h" +#include "base/CCTouch.h" +#include "platform/CCGLView.h" +#include "iostream" +#include "platform/ohos/CCLogOhos.h" + +using namespace cocos2d; + +extern "C" { + void Cocos2dxRenderer_nativeTouchesBegin(int num, intptr_t ids[], float xs[], float ys[]); + void Cocos2dxRenderer_nativeTouchesEnd(intptr_t id, float x, float y); + void Cocos2dxRenderer_nativeTouchesMove(int num, intptr_t ids[], float xs[], float ys[]); + void Cocos2dxRenderer_nativeTouchesCancel(int num, intptr_t ids[], float xs[], float ys[]); + bool Cocos2dxRenderer_nativeKeyDown(int keyCode); +} + +#endif \ No newline at end of file diff --git a/cocos/platform/ohos/napi/modules/VideoPlayerNapi.cpp b/cocos/platform/ohos/napi/modules/VideoPlayerNapi.cpp new file mode 100644 index 000000000000..367410c2e161 --- /dev/null +++ b/cocos/platform/ohos/napi/modules/VideoPlayerNapi.cpp @@ -0,0 +1,45 @@ +#include "VideoPlayerNapi.h" +#include "platform/ohos/napi/plugin_manager.h" +#include "../../CCLogOhos.h" +#include "ui/UIVideoPlayer-ohos.h" +#include +#include + +napi_value VideoPlayerNapi::onVideoCallBack(napi_env env, napi_callback_info info) { + + napi_status status; + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + if (argc != 2) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_number) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + status = napi_typeof(env, args[1], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_number) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + int32_t viewTag; + NAPI_CALL(env, napi_get_value_int32(env, args[0], &viewTag)); + int32_t event; + NAPI_CALL(env, napi_get_value_int32(env, args[1], &event)); + + executeVideoCallback(viewTag, event); + return nullptr; +} \ No newline at end of file diff --git a/cocos/platform/ohos/napi/modules/VideoPlayerNapi.h b/cocos/platform/ohos/napi/modules/VideoPlayerNapi.h new file mode 100644 index 000000000000..d8ad5380c0c6 --- /dev/null +++ b/cocos/platform/ohos/napi/modules/VideoPlayerNapi.h @@ -0,0 +1,6 @@ +#include + +class VideoPlayerNapi { +public: + static napi_value onVideoCallBack(napi_env env, napi_callback_info info); +}; \ No newline at end of file diff --git a/cocos/platform/ohos/napi/modules/WebViewNapi.cpp b/cocos/platform/ohos/napi/modules/WebViewNapi.cpp new file mode 100644 index 000000000000..f70a4c4605c5 --- /dev/null +++ b/cocos/platform/ohos/napi/modules/WebViewNapi.cpp @@ -0,0 +1,167 @@ +#include +#include + +#include "WebViewNapi.h" +#include "platform/ohos/napi/plugin_manager.h" +#include "../../CCLogOhos.h" +#include "ui/UIWebViewImpl-ohos.h" + +napi_value WebViewNapi::shouldStartLoading(napi_env env, napi_callback_info info) { + + napi_status status; + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + if (argc != 2) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_number) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + status = napi_typeof(env, args[1], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_string) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + int32_t viewTag; + NAPI_CALL(env, napi_get_value_int32(env, args[0], &viewTag)); + size_t pInt; + char url[256]; + NAPI_CALL(env, napi_get_value_string_utf8(env, args[1], url, 256, &pInt)); + + cocos2d::experimental::ui::WebViewImpl::shouldStartLoading(viewTag, url); + return nullptr; +} + +napi_value WebViewNapi::finishLoading(napi_env env, napi_callback_info info) { + + napi_status status; + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + if (argc != 2) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_number) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + status = napi_typeof(env, args[1], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_string) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + int32_t viewTag; + NAPI_CALL(env, napi_get_value_int32(env, args[0], &viewTag)); + size_t pInt; + char url[256]; + NAPI_CALL(env, napi_get_value_string_utf8(env, args[1], url, 256, &pInt)); + + cocos2d::experimental::ui::WebViewImpl::finishLoading(viewTag, url); + return nullptr; +} + +napi_value WebViewNapi::failLoading(napi_env env, napi_callback_info info) { + + napi_status status; + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + if (argc != 2) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_number) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + status = napi_typeof(env, args[1], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_string) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + int32_t viewTag; + NAPI_CALL(env, napi_get_value_int32(env, args[0], &viewTag)); + size_t pInt; + char url[256]; + NAPI_CALL(env, napi_get_value_string_utf8(env, args[1], url, 256, &pInt)); + + cocos2d::experimental::ui::WebViewImpl::failLoading(viewTag, url); + return nullptr; +} + +napi_value WebViewNapi::jsCallback(napi_env env, napi_callback_info info) { + + napi_status status; + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + if (argc != 2) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_number) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + status = napi_typeof(env, args[1], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_string) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + int32_t viewTag; + NAPI_CALL(env, napi_get_value_int32(env, args[0], &viewTag)); + size_t pInt; + char url[256]; + NAPI_CALL(env, napi_get_value_string_utf8(env, args[1], url, 256, &pInt)); + + cocos2d::experimental::ui::WebViewImpl::jsCallback(viewTag, url); + return nullptr; +} \ No newline at end of file diff --git a/cocos/platform/ohos/napi/modules/WebViewNapi.h b/cocos/platform/ohos/napi/modules/WebViewNapi.h new file mode 100644 index 000000000000..64dd953f5164 --- /dev/null +++ b/cocos/platform/ohos/napi/modules/WebViewNapi.h @@ -0,0 +1,15 @@ +#ifndef _WEB_VIEW_NAPI_H +#define _WEB_VIEW_NAPI_H + +#include +#include + +class WebViewNapi { +public: + static napi_value shouldStartLoading(napi_env env, napi_callback_info info); + static napi_value finishLoading(napi_env env, napi_callback_info info); + static napi_value failLoading(napi_env env, napi_callback_info info); + static napi_value jsCallback(napi_env env, napi_callback_info info); +}; + +#endif //_WEB_VIEW_NAPI_H \ No newline at end of file diff --git a/cocos/platform/ohos/napi/plugin_manager.cpp b/cocos/platform/ohos/napi/plugin_manager.cpp new file mode 100644 index 000000000000..25bfe3c70cdb --- /dev/null +++ b/cocos/platform/ohos/napi/plugin_manager.cpp @@ -0,0 +1,285 @@ +#include +#include +#include + +#include + +#include "modules/RawFileUtils.h" +#include "modules/InputNapi.h" +#include "modules/MouseNapi.h" +#include "modules/WebViewNapi.h" +#include "modules/SensorNapi.h" +#include "modules/VideoPlayerNapi.h" +#include "plugin_manager.h" +#include "../CCLogOhos.h" +#include "cocos2d.h" +#include "platform/CCApplication.h" +#include "platform/ohos/CCFileUtils-ohos.h" +#include "platform/ohos/napi/helper/JSRegisterUtils.h" +#include "platform/ohos/napi/helper/Js_Cocos2dxHelper.h" +#include "base/CCDirector.h" +#include "base/CCEventKeyboard.h" + +const int32_t kMaxStringLen = 512; +enum ContextType { + APP_LIFECYCLE = 0, + JS_PAGE_LIFECYCLE, + RAW_FILE_UTILS, + WORKER_INIT, + NATIVE_API, + INPUT_NAPI, + MOUSE_NAPI, + WEBVIEW_NAPI, + VIDEOPLAYER_NAPI, + SENSOR_API +}; + +NapiManager NapiManager::manager_; + +napi_value NapiManager::GetContext(napi_env env, napi_callback_info info) { + napi_status status; + napi_value exports; + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + + if (argc != 1) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + if (status != napi_ok) { + return nullptr; + } + if (valuetype != napi_number) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return nullptr; + } + + int64_t value; + NAPI_CALL(env, napi_get_value_int64(env, args[0], &value)); + + NAPI_CALL(env, napi_create_object(env, &exports)); + + switch (value) { + case APP_LIFECYCLE: { + /**** application life cycle: onCreate, onShow, onHide, onDestroy ******/ + OHOS_LOGD("GetContext APP_LIFECYCLE"); + napi_property_descriptor desc[] = { + DECLARE_NAPI_FUNCTION("onCreate", NapiManager::NapiOnCreate), + DECLARE_NAPI_FUNCTION("onShow", NapiManager::NapiOnShow), + DECLARE_NAPI_FUNCTION("onHide", NapiManager::NapiOnHide), + DECLARE_NAPI_FUNCTION("onBackPress", NapiManager::NapiOnBackPress), + DECLARE_NAPI_FUNCTION("onDestroy", NapiManager::NapiOnDestroy), + }; + NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); + } + break; + case JS_PAGE_LIFECYCLE: { + /**************** JS Page Lifecycle ****************************/ + OHOS_LOGD("GetContext JS_PAGE_LIFECYCLE"); + napi_property_descriptor desc[] = { + DECLARE_NAPI_FUNCTION("onPageShow", NapiManager::NapiOnPageShow), + DECLARE_NAPI_FUNCTION("onPageHide", NapiManager::NapiOnPageHide), + }; + NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); + } + break; + case RAW_FILE_UTILS: { + /**************** Rawfile ****************************/ + OHOS_LOGD("GetContext RAW_FILE_UTILS"); + napi_property_descriptor desc[] = { + DECLARE_NAPI_FUNCTION("nativeResourceManagerInit", RawFileUtils::nativeResourceManagerInit), + DECLARE_NAPI_FUNCTION("writablePathInit", NapiManager::napiWritablePathInit), + }; + NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); + + } + break; + case WORKER_INIT: { + OHOS_LOGD("NapiManager::GetContext WORKER_INIT"); + napi_property_descriptor desc[] = { + DECLARE_NAPI_FUNCTION("workerInit", NapiManager::napiWorkerInit), + }; + NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); + } + break; + case NATIVE_API: { + OHOS_LOGD("NapiManager::GetContext NATIVE_RENDER_API"); + napi_property_descriptor desc[] = { + DECLARE_NAPI_FUNCTION("nativeEngineStart", NapiManager::napiNativeEngineStart), + DECLARE_NAPI_FUNCTION("registerFunction", registerFunction), + DECLARE_NAPI_FUNCTION("initAsyncInfo", Js_Cocos2dxHelper::initAsyncInfo), + }; + NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); + } + break; + case INPUT_NAPI: { + OHOS_LOGD("NapiManager::GetContext INPUT_NAPI"); + napi_property_descriptor desc[] = { + DECLARE_NAPI_FUNCTION("editBoxOnFocusCB", InputNapi::editBoxOnFocusCB), + DECLARE_NAPI_FUNCTION("editBoxOnChangeCB", InputNapi::editBoxOnChangeCB), + DECLARE_NAPI_FUNCTION("editBoxOnEnterCB", InputNapi::editBoxOnEnterCB), + DECLARE_NAPI_FUNCTION("textFieldTTFOnChangeCB", InputNapi::textFieldTTFOnChangeCB), + }; + NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); + } + break; + case MOUSE_NAPI: { + OHOS_LOGD("NapiManager::GetContext INPUT_NAPI"); + napi_property_descriptor desc[] = { + DECLARE_NAPI_FUNCTION("mouseWheelCB", MouseNapi::mouseWheelCB), + }; + NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); + } + break; + case WEBVIEW_NAPI: { + OHOS_LOGD("NapiManager::GetContext WEBVIEW_NAPI"); + napi_property_descriptor desc[] = { + DECLARE_NAPI_FUNCTION("shouldStartLoading", WebViewNapi::shouldStartLoading), + DECLARE_NAPI_FUNCTION("finishLoading", WebViewNapi::finishLoading), + DECLARE_NAPI_FUNCTION("failLoading", WebViewNapi::failLoading), + DECLARE_NAPI_FUNCTION("jsCallback", WebViewNapi::jsCallback), + }; + NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); + } + break; + case VIDEOPLAYER_NAPI: { + OHOS_LOGE("VideoPlayerNapi::Export"); + napi_property_descriptor desc[] = { + DECLARE_NAPI_FUNCTION("onVideoCallBack", VideoPlayerNapi::onVideoCallBack), + }; + NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); + OHOS_LOGE("VideoPlayerNapi::Export finish"); + } + break; + case SENSOR_API: { + OHOS_LOGD("NapiManager::GetContext SENSOR_API"); + napi_property_descriptor desc[] = { + DECLARE_NAPI_FUNCTION("onAccelerometerCallBack", SensorNapi::onAccelerometerCallBack), + }; + NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); + } + break; + default: + OHOS_LOGE("unknown type"); + } + return exports; +} + +bool NapiManager::Export(napi_env env, napi_value exports) { + OHOS_LOGD("NapiManager::Export"); + napi_status status; + napi_value exportInstance = nullptr; + OH_NativeXComponent *nativeXComponent = nullptr; + int32_t ret; + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { }; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + + status = napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance); + if (status != napi_ok) { + return false; + } + + status = napi_unwrap(env, exportInstance, reinterpret_cast(&nativeXComponent)); + if (status != napi_ok) { + return false; + } + + auto context = NapiManager::GetInstance(); + if (context) { + context->SetNativeXComponent(nativeXComponent); + PluginRender::GetInstance()->SetNativeXComponent(nativeXComponent); + PluginRender::GetInstance()->Export(env, exports); + return true; + } + return false; +} + +void NapiManager::SetNativeXComponent(OH_NativeXComponent* nativeXComponent) { + nativeXComponent_ = nativeXComponent; +} + +OH_NativeXComponent* NapiManager::GetNativeXComponent() { + return nativeXComponent_; +} + +void NapiManager::MainOnMessage(const uv_async_t* req) { + OHOS_LOGD("MainOnMessage Triggered"); +} + +napi_value NapiManager::NapiOnCreate(napi_env env, napi_callback_info info) { + return nullptr; +} + +napi_value NapiManager::NapiOnShow(napi_env env, napi_callback_info info) { + WorkerMessageData data{MessageType::WM_APP_SHOW, nullptr, nullptr}; + PluginRender::GetInstance()->enqueue(data); + return nullptr; +} + +napi_value NapiManager::NapiOnHide(napi_env env, napi_callback_info info) { + WorkerMessageData data{MessageType::WM_APP_HIDE, nullptr, nullptr}; + PluginRender::GetInstance()->enqueue(data); + return nullptr; +} + +napi_value NapiManager::NapiOnBackPress(napi_env env, napi_callback_info info) { + OHOS_LOGD("NapiManager::NapiOnBackPress"); + cocos2d::EventKeyboard event(cocos2d::EventKeyboard::KeyCode::KEY_BACK, false); + cocos2d::Director::getInstance()->getEventDispatcher()->dispatchEvent(&event); + return nullptr; +} + +napi_value NapiManager::NapiOnDestroy(napi_env env, napi_callback_info info) { + WorkerMessageData data{MessageType::WM_APP_DESTROY, nullptr, nullptr}; + PluginRender::GetInstance()->enqueue(data); + return nullptr; +} + +napi_value NapiManager::napiWorkerInit(napi_env env, napi_callback_info info) { + OHOS_LOGD("NapiManager::napiWorkerInit"); + uv_loop_t* loop = nullptr; + NAPI_CALL(env, napi_get_uv_event_loop(env, &loop)); + PluginRender::GetInstance()->workerInit(env, loop); + return nullptr; +} + +napi_value NapiManager::napiNativeEngineStart(napi_env env, napi_callback_info info) { + OHOS_LOGD("NapiManager::napiNativeEngineStart"); + PluginRender::GetInstance()->run(); + Js_Cocos2dxHelper::initJsCocos2dxHelper(env, nullptr); + return nullptr; +} + +napi_value NapiManager::napiWritablePathInit(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + + char buffer[kMaxStringLen]; + size_t result = 0; + NAPI_CALL(env, napi_get_value_string_utf8(env, args[0], buffer, kMaxStringLen, &result)); + cocos2d::FileUtilsOhos::ohWritablePath = std::string(buffer) + '/'; + return nullptr; +} + +napi_value NapiManager::NapiOnPageShow(napi_env env, napi_callback_info info) { + OHOS_LOGD("NapiManager::NapiOnPageShow"); + return nullptr; +} + +napi_value NapiManager::NapiOnPageHide(napi_env env, napi_callback_info info) { + OHOS_LOGD("NapiManager::NapiOnPageHide"); + return nullptr; +} + +void NapiManager::OnPageShowNative() { + OHOS_LOGD("NapiManager::OnPageShowNative"); +} + +void NapiManager::OnPageHideNative() { + OHOS_LOGD("NapiManager::OnPageHideNative"); +} diff --git a/cocos/platform/ohos/napi/plugin_manager.h b/cocos/platform/ohos/napi/plugin_manager.h new file mode 100644 index 000000000000..468f0251ff13 --- /dev/null +++ b/cocos/platform/ohos/napi/plugin_manager.h @@ -0,0 +1,63 @@ +#ifndef _PLUGIN_MANAGER_H_ +#define _PLUGIN_MANAGER_H_ + +#include +#include + +#include +#include +#include + +#include "common/native_common.h" +#include "WorkerMessageQueue.h" +#include "render/plugin_render.h" + +class NapiManager { +public: + ~NapiManager() {} + + static NapiManager* GetInstance() { + return &NapiManager::manager_; + } + + static napi_value GetContext(napi_env env, napi_callback_info info); + + /******************************APP Lifecycle******************************/ + static napi_value NapiOnCreate(napi_env env, napi_callback_info info); + static napi_value NapiOnShow(napi_env env, napi_callback_info info); + static napi_value NapiOnHide(napi_env env, napi_callback_info info); + static napi_value NapiOnBackPress(napi_env env, napi_callback_info info); + static napi_value NapiOnDestroy(napi_env env, napi_callback_info info); + /*********************************************************************/ + + /******************************JS Page : Lifecycle*****************************/ + static napi_value NapiOnPageShow(napi_env env, napi_callback_info info); + static napi_value NapiOnPageHide(napi_env env, napi_callback_info info); + void OnPageShowNative(); + void OnPageHideNative(); + /*************************************************************************/ + + // Worker Func + static napi_value napiWorkerInit(napi_env env, napi_callback_info info); + static napi_value napiNativeEngineStart(napi_env env, napi_callback_info info); + static napi_value napiWritablePathInit(napi_env env, napi_callback_info info); + + OH_NativeXComponent* GetNativeXComponent(); + void SetNativeXComponent(OH_NativeXComponent* nativeXComponent); + +public: + // Napi export + bool Export(napi_env env, napi_value exports); +private: + static void MainOnMessage(const uv_async_t* req); + static NapiManager manager_; + + OH_NativeXComponent* nativeXComponent_ = nullptr; + +public: + napi_env mainEnv_ = nullptr; + uv_loop_t* mainLoop_ = nullptr; + uv_async_t mainOnMessageSignal_ {}; +}; + +#endif // _PLUGIN_MANAGER_H_ \ No newline at end of file diff --git a/cocos/platform/ohos/napi/render/egl_core.cpp b/cocos/platform/ohos/napi/render/egl_core.cpp new file mode 100644 index 000000000000..073ead11eb16 --- /dev/null +++ b/cocos/platform/ohos/napi/render/egl_core.cpp @@ -0,0 +1,116 @@ +#include "egl_core.h" + +#include "../../CCLogOhos.h" +#include "plugin_render.h" +#include +#include + +EGLConfig getConfig(int version, EGLDisplay eglDisplay) { + int attribList[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_STENCIL_SIZE, 8, + EGL_DEPTH_SIZE, 24, + EGL_NONE + }; + EGLConfig configs = NULL; + int configsNum; + if (!eglChooseConfig(eglDisplay, attribList, &configs, 1, &configsNum)) { + OHOS_LOGE("eglChooseConfig ERROR"); + return NULL; + } + return configs; +} + +void EGLCore::GLContextInit(void* window, int w, int h) +{ + OHOS_LOGD("EGLCore::GLContextInit window = %{public}p, w = %{public}d, h = %{public}d.", window, w, h); + width_ = w; + height_ = h; + mEglWindow = (EGLNativeWindowType)(window); + + // 1. create sharedcontext + mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (mEGLDisplay == EGL_NO_DISPLAY) { + OHOS_LOGE("EGLCore::unable to get EGL display."); + return; + } + + EGLint eglMajVers, eglMinVers; + if (!eglInitialize(mEGLDisplay, &eglMajVers, &eglMinVers)) { + mEGLDisplay = EGL_NO_DISPLAY; + OHOS_LOGE("EGLCore::unable to initialize display"); + return; + } + + mEGLConfig = getConfig(3, mEGLDisplay); + if (mEGLConfig == nullptr) { + OHOS_LOGE("EGLCore::GLContextInit config ERROR"); + return; + } + + // 2. Create EGL Surface from Native Window + EGLint winAttribs[] = {EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_LINEAR_KHR, EGL_NONE}; + if (mEglWindow) { + mEGLSurface = eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mEglWindow, winAttribs); + if (mEGLSurface == nullptr) { + OHOS_LOGE("EGLCore::eglCreateContext eglSurface is null"); + return; + } + } + + // 3. Create EGLContext from + int attrib3_list[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + mEGLContext = eglCreateContext(mEGLDisplay, mEGLConfig, mSharedEGLContext, attrib3_list); + + if (!eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { + OHOS_LOGE("EGLCore::eglMakeCurrent error = %{public}d", eglGetError()); + } +} + +void EGLCore::Update() +{ + eglSwapBuffers(mEGLDisplay, mEGLSurface); +} + +bool EGLCore::checkGlError(const char* op) +{ + OHOS_LOGE("EGL ERROR CODE = %{public}x", eglGetError()); + GLint error; + for (error = glGetError(); error; error = glGetError()) { + OHOS_LOGE("ERROR: %{public}s, ERROR CODE = %{public}x", op, error); + return true; + } + return false; +} + +void EGLCore::destroySurface() { + if(!eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { + OHOS_LOGE("eglMakeCurrent error = %{public}d", eglGetError()); + } + eglDestroySurface(mEGLDisplay, mEGLSurface); + mEGLSurface = nullptr; +} + +void EGLCore::createSurface(void* window) { + mEglWindow = (EGLNativeWindowType)(window); + if(mEglWindow) { + mEGLSurface = eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mEglWindow, NULL); + if(mEGLSurface == nullptr) { + OHOS_LOGE("EGL eglCreateWindowSurface eglSurface is null"); + return; + } + } + if(!(eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext))){ + OHOS_LOGE("eglMakeCurrent error = %{public}d", eglGetError()); + } + return; +} \ No newline at end of file diff --git a/cocos/platform/ohos/napi/render/egl_core.h b/cocos/platform/ohos/napi/render/egl_core.h new file mode 100644 index 000000000000..6d9efee79e36 --- /dev/null +++ b/cocos/platform/ohos/napi/render/egl_core.h @@ -0,0 +1,32 @@ +#ifndef _GL_CORE_ +#define _GL_CORE_ + +#include +#include +#include + +#include + +class EGLCore { +public: + EGLCore() {}; + void GLContextInit(void* window, int w, int h); + void Update(); + void destroySurface(); + void createSurface(void* window); +public: + int width_; + int height_; + +private: + bool checkGlError(const char* op); + + EGLNativeWindowType mEglWindow; + EGLDisplay mEGLDisplay = EGL_NO_DISPLAY; + EGLConfig mEGLConfig = nullptr; + EGLContext mEGLContext = EGL_NO_CONTEXT; + EGLContext mSharedEGLContext = EGL_NO_CONTEXT; + EGLSurface mEGLSurface = nullptr; +}; + +#endif // _GL_CORE_ diff --git a/cocos/platform/ohos/napi/render/plugin_render.cpp b/cocos/platform/ohos/napi/render/plugin_render.cpp new file mode 100644 index 000000000000..670b61a2ea1d --- /dev/null +++ b/cocos/platform/ohos/napi/render/plugin_render.cpp @@ -0,0 +1,567 @@ +#include +#include + +#include "plugin_render.h" +#include "platform/ohos/napi/plugin_manager.h" +#include "../modules/TouchesNapi.h" +#include "../helper/NapiHelper.h" +#include "platform/ohos/CCLogOhos.h" +#include "cocos2d.h" +#include "native_window/external_window.h" +#include "native_buffer/native_buffer.h" + +using namespace cocos2d; + +#ifdef __cplusplus +extern "C" { +#endif + +PluginRender* PluginRender::instance_ = nullptr; +OH_NativeXComponent_Callback PluginRender::callback_; +OH_NativeXComponent_MouseEvent_Callback PluginRender::mouseCallback_; +std::queue PluginRender::keyEventQueue_; +std::queue PluginRender::mouseEventQueue_; +std::queue PluginRender::mouseWheelEventQueue_; +uint64_t PluginRender::animationInterval_ = 16; +uint64_t PluginRender::lastTime = 0; +const int keyCodeUnknownInOH = -1; +const int keyActionUnknownInOH = -1; +float mousePositionX = -1; +float mousePositionY = -1; +bool isMouseLeftActive = false; +double scrollDistance = 0; + +std::unordered_map ohKeyMap = { + {KEY_ESCAPE, cocos2d::EventKeyboard::KeyCode::KEY_ESCAPE}, + {KEY_GRAVE, cocos2d::EventKeyboard::KeyCode::KEY_GRAVE}, + {KEY_MINUS, cocos2d::EventKeyboard::KeyCode::KEY_MINUS}, + {KEY_EQUALS, cocos2d::EventKeyboard::KeyCode::KEY_EQUAL}, + {KEY_DEL, cocos2d::EventKeyboard::KeyCode::KEY_BACKSPACE}, + {KEY_TAB, cocos2d::EventKeyboard::KeyCode::KEY_TAB}, + {KEY_LEFT_BRACKET, cocos2d::EventKeyboard::KeyCode::KEY_LEFT_BRACKET}, + {KEY_RIGHT_BRACKET, cocos2d::EventKeyboard::KeyCode::KEY_RIGHT_BRACKET}, + {KEY_BACKSLASH, cocos2d::EventKeyboard::KeyCode::KEY_BACK_SLASH}, + {KEY_CAPS_LOCK, cocos2d::EventKeyboard::KeyCode::KEY_CAPS_LOCK}, + {KEY_SEMICOLON, cocos2d::EventKeyboard::KeyCode::KEY_SEMICOLON}, + {KEY_APOSTROPHE, cocos2d::EventKeyboard::KeyCode::KEY_QUOTE}, + {KEY_ENTER, cocos2d::EventKeyboard::KeyCode::KEY_ENTER}, + {KEY_SHIFT_LEFT, cocos2d::EventKeyboard::KeyCode::KEY_LEFT_SHIFT}, + {KEY_COMMA, cocos2d::EventKeyboard::KeyCode::KEY_COMMA}, + {KEY_PERIOD, cocos2d::EventKeyboard::KeyCode::KEY_PERIOD}, + {KEY_SLASH, cocos2d::EventKeyboard::KeyCode::KEY_SLASH}, + {KEY_SHIFT_RIGHT, cocos2d::EventKeyboard::KeyCode::KEY_RIGHT_SHIFT}, + {KEY_CTRL_LEFT, cocos2d::EventKeyboard::KeyCode::KEY_LEFT_CTRL}, + {KEY_ALT_LEFT, cocos2d::EventKeyboard::KeyCode::KEY_LEFT_ALT}, + {KEY_SPACE, cocos2d::EventKeyboard::KeyCode::KEY_SPACE}, + {KEY_ALT_RIGHT, cocos2d::EventKeyboard::KeyCode::KEY_RIGHT_ALT}, + {KEY_CTRL_RIGHT, cocos2d::EventKeyboard::KeyCode::KEY_RIGHT_CTRL}, + {KEY_DPAD_LEFT, cocos2d::EventKeyboard::KeyCode::KEY_DPAD_LEFT}, + {KEY_DPAD_RIGHT, cocos2d::EventKeyboard::KeyCode::KEY_DPAD_RIGHT}, + {KEY_DPAD_DOWN, cocos2d::EventKeyboard::KeyCode::KEY_DPAD_DOWN}, + {KEY_DPAD_UP, cocos2d::EventKeyboard::KeyCode::KEY_DPAD_UP}, + {KEY_SYSRQ, cocos2d::EventKeyboard::KeyCode::KEY_PRINT}, + {KEY_INSERT, cocos2d::EventKeyboard::KeyCode::KEY_INSERT}, + {KEY_FORWARD_DEL, cocos2d::EventKeyboard::KeyCode::KEY_DELETE} +}; + +cocos2d::EventKeyboard::KeyCode ohKeyCodeToCocosKeyCode(OH_NativeXComponent_KeyCode ohKeyCode) +{ + auto it = ohKeyMap.find(ohKeyCode); + if (it != ohKeyMap.end()) { + return static_cast(it->second); + } + if (ohKeyCode >= KEY_0 && ohKeyCode <= KEY_9) { + // 0 - 9 + return cocos2d::EventKeyboard::KeyCode(int(cocos2d::EventKeyboard::KeyCode::KEY_0) + (ohKeyCode - KEY_0)); + } + if (ohKeyCode >= KEY_F1 && ohKeyCode <= KEY_F12) { + // F1 - F12 + return cocos2d::EventKeyboard::KeyCode(int(cocos2d::EventKeyboard::KeyCode::KEY_F1) + (ohKeyCode - KEY_F1)); + } + if (ohKeyCode >= KEY_A && ohKeyCode <= KEY_Z) { + // A - Z + return cocos2d::EventKeyboard::KeyCode(int(cocos2d::EventKeyboard::KeyCode::KEY_A) + (ohKeyCode - KEY_A)); + } + return cocos2d::EventKeyboard::KeyCode(ohKeyCode); +} + +void OnSurfaceCreatedCB(OH_NativeXComponent* component, void* window) +{ + OHOS_LOGD("OnSurfaceCreatedCB"); + PluginRender::GetInstance()->sendMsgToWorker(MessageType::WM_XCOMPONENT_SURFACE_CREATED, component, window); +} + +void OnSurfaceChangedCB(OH_NativeXComponent* component, void* window) { + OHOS_LOGD("OnSurfaceChangedCB"); + PluginRender::GetInstance()->sendMsgToWorker(MessageType::WM_XCOMPONENT_SURFACE_CHANGED, component, window); +} + +void onSurfaceHideCB(OH_NativeXComponent* component, void* window) { + int32_t ret; + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {}; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + ret = OH_NativeXComponent_GetXComponentId(component, idStr, &idSize); + if(ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + return; + } + PluginRender::GetInstance()->sendMsgToWorker(MessageType::WM_XCOMPONENT_SURFACE_HIDE, component, window); +} + +void onSurfaceShowCB(OH_NativeXComponent* component, void* window) { + int32_t ret; + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {}; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + ret = OH_NativeXComponent_GetXComponentId(component, idStr, &idSize); + if(ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + return; + } + PluginRender::GetInstance()->sendMsgToWorker(MessageType::WM_XCOMPONENT_SURFACE_SHOW, component, window); +} + +void OnSurfaceDestroyedCB(OH_NativeXComponent* component, void* window) { + OHOS_LOGD("OnSurfaceDestroyedCB"); + PluginRender::GetInstance()->sendMsgToWorker(MessageType::WM_XCOMPONENT_SURFACE_DESTROY, component, window); +} + +void DispatchKeyEventCB(OH_NativeXComponent* component, void* window) { + OH_NativeXComponent_KeyEvent* keyEvent; + if (OH_NativeXComponent_GetKeyEvent(component, &keyEvent) >= 0) { + PluginRender::keyEventQueue_.push(keyEvent); + PluginRender::GetInstance()->sendMsgToWorker(MessageType::WM_XCOMPONENT_KEY_EVENT, component, window); + } else { + OHOS_LOGE("OpenHarmonyPlatform::getKeyEventError"); + } +} + +void DispatchMouseEventCB(OH_NativeXComponent* component, void* window) { + OH_NativeXComponent_MouseEvent mouseEvent; + int32_t ret = OH_NativeXComponent_GetMouseEvent(component, window, &mouseEvent); + if (ret == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + PluginRender::mouseEventQueue_.push(mouseEvent); + PluginRender::GetInstance()->sendMsgToWorker(MessageType::WM_XCOMPONENT_MOUSE_EVENT, component, window); + } else { + OHOS_LOGE("OpenHarmonyPlatform::getMouseEventError"); + } +} + +void DispatchHoverEventCB(OH_NativeXComponent* component, bool isHover) { + OHOS_LOGD("OpenHarmonyPlatform::DispatchHoverEventCB"); +} + +void DispatchTouchEventCB(OH_NativeXComponent* component, void* window) { + OH_NativeXComponent_TouchEvent* touchEvent = new(std::nothrow) OH_NativeXComponent_TouchEvent(); + if (!touchEvent) { + OHOS_LOGE("DispatchTouchEventCB::touchEvent alloc failed"); + return; + } + int32_t ret = OH_NativeXComponent_GetTouchEvent(component, window, touchEvent); + if (ret == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + PluginRender::GetInstance()->sendMsgToWorker(MessageType::WM_XCOMPONENT_TOUCH_EVENT, component, window, touchEvent); + } else { + delete touchEvent; + } +} + +PluginRender::PluginRender() : component_(nullptr) { + auto renderCallback = PluginRender::GetNXComponentCallback(); + renderCallback->OnSurfaceCreated = OnSurfaceCreatedCB; + renderCallback->OnSurfaceChanged = OnSurfaceChangedCB; + renderCallback->OnSurfaceDestroyed = OnSurfaceDestroyedCB; + renderCallback->DispatchTouchEvent = DispatchTouchEventCB; +} + +PluginRender* PluginRender::GetInstance() { + if (instance_ == nullptr) { + instance_ = new PluginRender(); + } + return instance_; +} + +OH_NativeXComponent_Callback* PluginRender::GetNXComponentCallback() { + return &PluginRender::callback_; +} + +// static +void PluginRender::onMessageCallback(const uv_async_t* /* req */) { + void* window = nullptr; + WorkerMessageData msgData; + PluginRender* render = PluginRender::GetInstance(); + + while (true) { + //loop until all msg dispatch + if (!render->dequeue(reinterpret_cast(&msgData))) { + // Queue has no data + break; + } + + if ((msgData.type >= MessageType::WM_XCOMPONENT_SURFACE_CREATED) && (msgData.type <= MessageType::WM_XCOMPONENT_SURFACE_DESTROY)) { + OH_NativeXComponent* nativexcomponet = reinterpret_cast(msgData.data); + CC_ASSERT(nativexcomponet != nullptr); + + if (msgData.type == MessageType::WM_XCOMPONENT_SURFACE_CREATED) { + render->OnSurfaceCreated(nativexcomponet, msgData.window); + } else if (msgData.type == MessageType::WM_XCOMPONENT_KEY_EVENT) { + render->DispatchKeyEvent(nativexcomponet, msgData.window); + } else if (msgData.type == MessageType::WM_XCOMPONENT_MOUSE_EVENT) { + render->DispatchMouseEvent(nativexcomponet, msgData.window); + } else if (msgData.type == MessageType::WM_XCOMPONENT_MOUSE_WHEEL_EVENT) { + render->DispatchMouseWheelEvent(); + } else if (msgData.type == MessageType::WM_XCOMPONENT_TOUCH_EVENT) { + render->DispatchTouchEvent(nativexcomponet, msgData.window, msgData.touchEvent); + } else if (msgData.type == MessageType::WM_XCOMPONENT_SURFACE_CHANGED) { + render->OnSurfaceChanged(nativexcomponet, msgData.window); + } else if (msgData.type == MessageType::WM_XCOMPONENT_SURFACE_SHOW) { + render->onSurfaceShow(msgData.window); + } else if (msgData.type == MessageType::WM_XCOMPONENT_SURFACE_HIDE) { + render->onSurfaceHide(); + } else if (msgData.type == MessageType::WM_XCOMPONENT_SURFACE_DESTROY) { + render->OnSurfaceDestroyed(nativexcomponet, msgData.window); + } else { + CC_ASSERT(false); + } + continue; + } + + if (msgData.type == MessageType::WM_APP_SHOW) { + render->OnShowNative(); + } else if (msgData.type == MessageType::WM_APP_HIDE) { + render->OnHideNative(); + } else if (msgData.type == MessageType::WM_APP_DESTROY) { + render->OnDestroyNative(); + } + if(msgData.type == MessageType::WM_VSYNC) { + // render->runTask(); + } + } +} + +static uint64_t getCurrentMillSecond() { + struct timeval stCurrentTime; + + gettimeofday(&stCurrentTime,NULL); + return stCurrentTime.tv_sec * 1000 + stCurrentTime.tv_usec / 1000; //millseconds +} + +// static +void PluginRender::timerCb(uv_timer_t* handle) { + // OHOS_LOGD("PluginRender::timerCb, animationInterval_ is %{public}lu", animationInterval_); + if (PluginRender::GetInstance()->eglCore_ != nullptr) { + cocos2d::CCDirector::sharedDirector()->mainLoop(); + PluginRender::GetInstance()->eglCore_->Update(); + } + uint64_t curTime = getCurrentMillSecond(); + if (curTime - lastTime < animationInterval_) + { + usleep((animationInterval_ - curTime + lastTime) * 1000); + } + lastTime = getCurrentMillSecond(); +} + +void PluginRender::SetNativeXComponent(OH_NativeXComponent* component) { + component_ = component; + OH_NativeXComponent_RegisterCallback(component_, &PluginRender::callback_); + // register keyEvent + OH_NativeXComponent_RegisterKeyEventCallback(component_, DispatchKeyEventCB); + // register mouseEvent + PluginRender::mouseCallback_.DispatchMouseEvent = DispatchMouseEventCB; + PluginRender::mouseCallback_.DispatchHoverEvent = DispatchHoverEventCB; + OH_NativeXComponent_RegisterMouseEventCallback(component_, &mouseCallback_); + OH_NativeXComponent_RegisterSurfaceHideCallback(component_, onSurfaceHideCB); + OH_NativeXComponent_RegisterSurfaceShowCallback(component_, onSurfaceShowCB); +} + +void PluginRender::workerInit(napi_env env, uv_loop_t* loop) { + OHOS_LOGD("PluginRender::workerInit"); + workerLoop_ = loop; + if (workerLoop_) { + uv_async_init(workerLoop_, &messageSignal_, reinterpret_cast(PluginRender::onMessageCallback)); + if (!messageQueue_.empty()) { + triggerMessageSignal(); // trigger the signal to handle the pending message + } + } +} + +void PluginRender::DispatchKeyEvent(OH_NativeXComponent* component, void* window) +{ + OH_NativeXComponent_KeyEvent* keyEvent; + while (!keyEventQueue_.empty()) { + keyEvent = keyEventQueue_.front(); + keyEventQueue_.pop(); + OH_NativeXComponent_KeyAction action; + OH_NativeXComponent_GetKeyEventAction(keyEvent, &action); + OH_NativeXComponent_KeyCode code; + OH_NativeXComponent_GetKeyEventCode(keyEvent, &code); + if (code == keyCodeUnknownInOH || action == keyActionUnknownInOH) { + // unknown code and action don't callback + return; + } + bool isPressed = action == 0; + EventKeyboard event(ohKeyCodeToCocosKeyCode(code), isPressed); + Director::getInstance()->getEventDispatcher()->dispatchEvent(&event); + } +} + +void PluginRender::DispatchMouseEvent(OH_NativeXComponent* component, void* window) +{ + OH_NativeXComponent_MouseEvent mouseEvent; + while (!mouseEventQueue_.empty()) { + mouseEvent = mouseEventQueue_.front(); + mouseEventQueue_.pop(); + EventMouse::MouseEventType mouseAction; + mousePositionX = mouseEvent.x; + mousePositionY = mouseEvent.y; + switch (mouseEvent.action) { + case 1: + mouseAction = EventMouse::MouseEventType::MOUSE_DOWN; + break; + case 2: + mouseAction = EventMouse::MouseEventType::MOUSE_UP; + break; + case 3: + mouseAction = EventMouse::MouseEventType::MOUSE_MOVE; + break; + default: + mouseAction = EventMouse::MouseEventType::MOUSE_NONE; + break; + } + int mouseButton; + switch (mouseEvent.button) { + case 1: + mouseButton = MOUSE_BUTTON_LEFT; + break; + case 2: + mouseButton = MOUSE_BUTTON_RIGHT; + break; + case 4: + mouseButton = MOUSE_BUTTON_MIDDLE; + break; + default: + mouseButton = MOUSE_BUTTON_UNSET; + break; + } + if (mouseEvent.action == 1 && mouseEvent.button == 1) { + isMouseLeftActive = true; + } + if (mouseEvent.action == 2 && mouseEvent.button == 1) { + isMouseLeftActive = false; + } + EventMouse event(mouseAction); + event.setCursorPosition(mouseEvent.x, mouseEvent.y); + event.setMouseButton(mouseButton); + Director::getInstance()->getEventDispatcher()->dispatchEvent(&event); + } + +} + +void PluginRender::sendMsgToWorker(const MessageType& type, OH_NativeXComponent* component, void* window) { + WorkerMessageData data{type, static_cast(component), window}; + enqueue(data); +} + +void PluginRender::sendMsgToWorker(const MessageType& type, OH_NativeXComponent* component, void* window, OH_NativeXComponent_TouchEvent* touchEvent) { + WorkerMessageData data{type, static_cast(component), window, touchEvent}; + enqueue(data); +} + +void PluginRender::enqueue(const WorkerMessageData& msg) { + messageQueue_.enqueue(msg); + triggerMessageSignal(); +} + +bool PluginRender::dequeue(WorkerMessageData* msg) { + return messageQueue_.dequeue(msg); +} + +void PluginRender::triggerMessageSignal() { + if(workerLoop_ != nullptr) { + // It is possible that when the message is sent, the worker thread has not yet started. + uv_async_send(&messageSignal_); + } +} + +void PluginRender::run() { + OHOS_LOGD("PluginRender::run"); + if (workerLoop_) { + uv_timer_init(workerLoop_, &timerHandle_); + timerInited_ = true; + } +} + +void PluginRender::changeFPS(uint64_t animationInterval) { + OHOS_LOGD("PluginRender::changeFPS, animationInterval from %{public}lu to %{public}lu", animationInterval_, animationInterval); + animationInterval_ = animationInterval; +} + +void Cocos2dxRenderer_nativeInit(int w, int h); +void PluginRender::OnSurfaceCreated(OH_NativeXComponent* component, void* window) { + OHOS_LOGD("PluginRender::OnSurfaceCreated"); + eglCore_ = new EGLCore(); + int32_t ret = OH_NativeXComponent_GetXComponentSize(component, window, &width_, &height_); + if (ret == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + int32_t code = SET_USAGE; + OHNativeWindow *nativeWindow = static_cast(window); + int32_t ret = OH_NativeWindow_NativeWindowHandleOpt(nativeWindow, code, NATIVEBUFFER_USAGE_MEM_DMA); + eglCore_->GLContextInit(window, width_, height_); + Cocos2dxRenderer_nativeInit(width_, height_); + } +} + +void PluginRender::OnSurfaceChanged(OH_NativeXComponent* component, void* window) { + OHOS_LOGD("PluginRender::OnSurfaceChanged"); + int32_t ret = OH_NativeXComponent_GetXComponentSize(component, window, &width_, &height_); + if (ret == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OHOS_LOGD("PluginRender::OnSurfaceChanged, width is %lu, height is %lu", width_, height_); + cocos2d::Application::getInstance()->applicationScreenSizeChanged(width_, height_); + } +} + +void PluginRender::onSurfaceHide() { + eglCore_->destroySurface(); +} + +void PluginRender::onSurfaceShow(void* window) { + eglCore_->createSurface(window); +} +void PluginRender::OnSurfaceDestroyed(OH_NativeXComponent* component, void* window) { +} +void PluginRender::DispatchMouseWheelEvent() +{ + EventMouseWheelData mouseWheelData; + while (!mouseWheelEventQueue_.empty()) { + mouseWheelData = mouseWheelEventQueue_.front(); + mouseWheelEventQueue_.pop(); + EventMouse mouseWheelEvent(cocos2d::EventMouse::MouseEventType::MOUSE_SCROLL); + mouseWheelEvent.setScrollData(0, -mouseWheelData.scrollY); + // mouseWheelEvent.setScrollData(0, -mouseWheelData.scrollY > 0 ? 1 : -1); + mouseWheelEvent.setCursorPosition(mouseWheelData.positonX, mouseWheelData.positonY); + Director::getInstance()->getEventDispatcher()->dispatchEvent(&mouseWheelEvent); + } +} + +void PluginRender::DispatchTouchEvent(OH_NativeXComponent* component, void* window, OH_NativeXComponent_TouchEvent* touchEvent) +{ + intptr_t ids[touchEvent->numPoints]; + float xs[touchEvent->numPoints]; + float ys[touchEvent->numPoints]; + for (int i = 0; i < touchEvent->numPoints; i++) { + ids[i] = touchEvent->touchPoints[i].id; + xs[i] = touchEvent->touchPoints[i].x; + ys[i] = touchEvent->touchPoints[i].y; + OHOS_LOGD("Touch Info : x = %{public}f, y = %{public}f", xs[i], ys[i]); + } + switch (touchEvent -> type) { + case OH_NATIVEXCOMPONENT_DOWN: + JSFunction::getFunction("CocosEditBox.hideAllEditBox").invoke(); // hide all editbox + Cocos2dxRenderer_nativeTouchesBegin(touchEvent->numPoints, ids, xs, ys); + OHOS_LOGD("Touch Info : OH_NATIVEXCOMPONENT_DOWN"); + break; + case OH_NATIVEXCOMPONENT_UP: + Cocos2dxRenderer_nativeTouchesEnd(touchEvent->id, touchEvent->x, touchEvent->y); + OHOS_LOGD("Touch Info : OH_NATIVEXCOMPONENT_UP"); + break; + case OH_NATIVEXCOMPONENT_MOVE: + Cocos2dxRenderer_nativeTouchesMove(touchEvent->numPoints, ids, xs, ys); + OHOS_LOGD("Touch Info : OH_NATIVEXCOMPONENT_MOVE"); + break; + case OH_NATIVEXCOMPONENT_CANCEL: + Cocos2dxRenderer_nativeTouchesCancel(touchEvent->numPoints, ids, xs, ys); + OHOS_LOGD("Touch Info : OH_NATIVEXCOMPONENT_CANCEL"); + break; + case OH_NATIVEXCOMPONENT_UNKNOWN: + OHOS_LOGD("Touch Info : OH_NATIVEXCOMPONENT_UNKNOWN"); + break; + default: + OHOS_LOGD("Touch Info : default"); + break; + } + delete touchEvent; +} + +void PluginRender::MouseWheelCB(std::string eventType, double scrollY) { + if (isMouseLeftActive) { + return; + } + if (eventType == "actionEnd") { + scrollDistance = 0; + } + if (eventType == "actionUpdate") { + double moveScrollY = scrollY - scrollDistance; + scrollDistance = scrollY; + PluginRender::EventMouseWheelData mouseWheelData{mousePositionX, mousePositionY, moveScrollY}; + PluginRender::mouseWheelEventQueue_.push(mouseWheelData); + PluginRender::GetInstance()->sendMsgToWorker(MessageType::WM_XCOMPONENT_MOUSE_WHEEL_EVENT, nullptr, nullptr); + } +} + +void PluginRender::OnCreateNative(napi_env env, uv_loop_t* loop) { + OHOS_LOGD("PluginRender::OnCreateNative"); +} + +void PluginRender::OnShowNative() { + OHOS_LOGD("PluginRender::OnShowNative"); + cocos2d::Application* app = cocos2d::Application::getInstance(); + if(app) { + app->applicationWillEnterForeground(); + } + cocos2d::EventCustom foregroundEvent(EVENT_COME_TO_FOREGROUND); + cocos2d::Director::getInstance()->getEventDispatcher()->dispatchEvent(&foregroundEvent); + if (timerInited_) { + uv_timer_start(&timerHandle_, &PluginRender::timerCb, 0, 1); + } +} + +void PluginRender::OnHideNative() { + OHOS_LOGD("PluginRender::OnHideNative"); + cocos2d::Application* app = cocos2d::Application::getInstance(); + if(app) { + app->applicationDidEnterBackground(); + } + cocos2d::EventCustom backgroundEvent(EVENT_COME_TO_BACKGROUND); + cocos2d::Director::getInstance()->getEventDispatcher()->dispatchEvent(&backgroundEvent); + if (timerInited_) { + uv_timer_stop(&timerHandle_); + } +} + +void PluginRender::OnDestroyNative() { + OHOS_LOGD("PluginRender::OnDestoryNative"); + if (timerInited_) { + uv_timer_stop(&timerHandle_); + } +} + +napi_value PluginRender::Export(napi_env env, napi_value exports) { + OHOS_LOGD("PluginRender::Export"); + // Register JS API + napi_property_descriptor desc[] = { + DECLARE_NAPI_FUNCTION("changeShape", PluginRender::NapiChangeShape), + DECLARE_NAPI_FUNCTION("drawTriangle", PluginRender::NapiDrawTriangle), + DECLARE_NAPI_FUNCTION("changeColor", PluginRender::NapiChangeColor), + }; + NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); + return exports; +} + +napi_value PluginRender::NapiChangeShape(napi_env env, napi_callback_info info) { + OHOS_LOGD("PluginRender::NapiChangeShape"); + PluginRender* instance = PluginRender::GetInstance(); + if (instance) { + CCDirector::sharedDirector()->mainLoop(); + instance->eglCore_->Update(); + } + return nullptr; +} + +napi_value PluginRender::NapiDrawTriangle(napi_env env, napi_callback_info info) { + OHOS_LOGD("NapiDrawTriangle"); + return nullptr; +} + +napi_value PluginRender::NapiChangeColor(napi_env env, napi_callback_info info) { + OHOS_LOGD("NapiChangeColor"); + return nullptr; +} + +#ifdef __cplusplus +} +#endif diff --git a/cocos/platform/ohos/napi/render/plugin_render.h b/cocos/platform/ohos/napi/render/plugin_render.h new file mode 100644 index 000000000000..fbfb5419808f --- /dev/null +++ b/cocos/platform/ohos/napi/render/plugin_render.h @@ -0,0 +1,101 @@ +#ifndef _PLUGIN_RENDER_H_ +#define _PLUGIN_RENDER_H_ + +#include +#include +#include + +#include +#include + +#include "egl_core.h" +#include "../WorkerMessageQueue.h" + +class PluginRender { +public: + PluginRender(); + static PluginRender* GetInstance(); + static OH_NativeXComponent_Callback* GetNXComponentCallback(); + + static void onMessageCallback(const uv_async_t* req); + static void timerCb(uv_timer_t* handle); + + void SetNativeXComponent(OH_NativeXComponent* component); + + void workerInit(napi_env env, uv_loop_t* loop); + + void sendMsgToWorker(const MessageType& type, OH_NativeXComponent* component, void* window); + void sendMsgToWorker(const MessageType& type, OH_NativeXComponent* component, void* window, OH_NativeXComponent_TouchEvent* touchEvent); + void enqueue(const WorkerMessageData& data); + bool dequeue(WorkerMessageData* data); + void triggerMessageSignal(); + void run(); + void changeFPS(uint64_t animationInterval); + +public: + // NAPI interface + napi_value Export(napi_env env, napi_value exports); + + // Exposed to JS developers by NAPI + static napi_value NapiChangeShape(napi_env env, napi_callback_info info); + static napi_value NapiDrawTriangle(napi_env env, napi_callback_info info); + static napi_value NapiChangeColor(napi_env env, napi_callback_info info); + static napi_value NapiChangeColorWorker(napi_env env, napi_callback_info info); + + static void MouseWheelCB(std::string eventType, double scrollY); + // Callback, called by ACE XComponent + void OnSurfaceCreated(OH_NativeXComponent* component, void* window); + + void OnSurfaceChanged(OH_NativeXComponent* component, void* window); + + void OnSurfaceDestroyed(OH_NativeXComponent* component, void* window); + + void onSurfaceHide(); + + void onSurfaceShow(void* window); + + void DispatchKeyEvent(OH_NativeXComponent* component, void* window); + + void DispatchMouseEvent(OH_NativeXComponent* component, void* window); + + void DispatchTouchEvent(OH_NativeXComponent* component, void* window, OH_NativeXComponent_TouchEvent* touchEvent); + + void DispatchMouseWheelEvent(); + + void OnCreateNative(napi_env env, uv_loop_t* loop); + void OnShowNative(); + void OnHideNative(); + void OnDestroyNative(); + +public: + struct EventMouseWheelData { + float positonX; + float positonY; + double scrollY; + }; + static PluginRender* instance_; + static OH_NativeXComponent_Callback callback_; + static OH_NativeXComponent_MouseEvent_Callback mouseCallback_; + static std::queue keyEventQueue_; + static std::queue mouseEventQueue_; + static std::queue mouseWheelEventQueue_; + + OH_NativeXComponent* component_{nullptr}; + uv_timer_t timerHandle_; + bool timerInited_{false}; + uv_loop_t* workerLoop_{nullptr}; + uv_async_t messageSignal_{}; + WorkerMessageQueue messageQueue_; + EGLCore* eglCore_{nullptr}; + + uint64_t width_; + uint64_t height_; + + double x_; + double y_; + + static uint64_t animationInterval_; + static uint64_t lastTime; +}; + +#endif // _PLUGIN_RENDER_H_ diff --git a/cocos/renderer/CCGLProgramState.cpp b/cocos/renderer/CCGLProgramState.cpp index 60703c28a0ae..0ce91ddc23c7 100644 --- a/cocos/renderer/CCGLProgramState.cpp +++ b/cocos/renderer/CCGLProgramState.cpp @@ -363,7 +363,7 @@ GLProgramState::GLProgramState() , _glprogram(nullptr) , _nodeBinding(nullptr) { -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) /** listen the event that renderer was recreated on Android/WP8 */ CCLOG("create rendererRecreatedListener for GLProgramState"); _backToForegroundlistener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, @@ -378,7 +378,7 @@ GLProgramState::GLProgramState() GLProgramState::~GLProgramState() { -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) Director::getInstance()->getEventDispatcher()->removeEventListener(_backToForegroundlistener); #endif diff --git a/cocos/renderer/CCGLProgramState.h b/cocos/renderer/CCGLProgramState.h index b27e4fe06773..1b580078ff96 100644 --- a/cocos/renderer/CCGLProgramState.h +++ b/cocos/renderer/CCGLProgramState.h @@ -465,7 +465,7 @@ class CC_DLL GLProgramState : public Ref // Map of custom auto binding resolvers. static std::vector _customAutoBindingResolvers; -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) EventListenerCustom* _backToForegroundlistener; #endif diff --git a/cocos/renderer/CCMeshCommand.cpp b/cocos/renderer/CCMeshCommand.cpp index a7454b21173e..0474b307bb0f 100644 --- a/cocos/renderer/CCMeshCommand.cpp +++ b/cocos/renderer/CCMeshCommand.cpp @@ -57,7 +57,7 @@ MeshCommand::MeshCommand() { _type = RenderCommand::Type::MESH_COMMAND; -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) // listen the event that renderer was recreated on Android/WP8 _rendererRecreatedListener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, CC_CALLBACK_1(MeshCommand::listenRendererRecreated, this)); Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(_rendererRecreatedListener, -1); @@ -152,7 +152,7 @@ void MeshCommand::setMatrixPaletteSize(int size) MeshCommand::~MeshCommand() { releaseVAO(); -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) Director::getInstance()->getEventDispatcher()->removeEventListener(_rendererRecreatedListener); #endif } @@ -328,7 +328,7 @@ void MeshCommand::releaseVAO() } } -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) void MeshCommand::listenRendererRecreated(EventCustom* event) { _vao = 0; diff --git a/cocos/renderer/CCMeshCommand.h b/cocos/renderer/CCMeshCommand.h index 998708a6297e..6c8346b0194f 100644 --- a/cocos/renderer/CCMeshCommand.h +++ b/cocos/renderer/CCMeshCommand.h @@ -66,7 +66,7 @@ class CC_DLL MeshCommand : public RenderCommand uint32_t getMaterialID() const; -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) void listenRendererRecreated(EventCustom* event); #endif @@ -112,7 +112,7 @@ class CC_DLL MeshCommand : public RenderCommand GLuint _textureID; -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) EventListenerCustom* _rendererRecreatedListener; #endif }; diff --git a/cocos/renderer/CCRenderer.cpp b/cocos/renderer/CCRenderer.cpp index 0159a40edaeb..5dc785f80692 100644 --- a/cocos/renderer/CCRenderer.cpp +++ b/cocos/renderer/CCRenderer.cpp @@ -786,6 +786,7 @@ void Renderer::drawBatchedTriangles() /************** 2: Copy vertices/indices to GL objects *************/ if (Configuration::getInstance()->supportsShareableVAO()) { +#if (CC_TARGET_PLATFORM != CC_PLATFORM_OHOS) //Bind VAO GL::bindVAO(_buffersVAO); //Set VBO data @@ -810,6 +811,7 @@ void Renderer::drawBatchedTriangles() glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[1]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(_indices[0]) * _filledIndex, _indices, GL_STATIC_DRAW); +#endif } else { diff --git a/cocos/renderer/CCTexture2D.cpp b/cocos/renderer/CCTexture2D.cpp index 2444fd75485a..10c6bd7c4ac3 100644 --- a/cocos/renderer/CCTexture2D.cpp +++ b/cocos/renderer/CCTexture2D.cpp @@ -1121,8 +1121,8 @@ bool Texture2D::initWithString(const char *text, const FontDefinition& textDefin return false; } -#if (CC_TARGET_PLATFORM != CC_PLATFORM_ANDROID) && (CC_TARGET_PLATFORM != CC_PLATFORM_IOS) - CCASSERT(textDefinition._stroke._strokeEnabled == false, "Currently stroke only supported on iOS and Android!"); +#if (CC_TARGET_PLATFORM != CC_PLATFORM_ANDROID) && (CC_TARGET_PLATFORM != CC_PLATFORM_IOS) && (CC_TARGET_PLATFORM != CC_PLATFORM_OHOS) + CCASSERT(textDefinition._stroke._strokeEnabled == false, "Currently stroke only supported on iOS, Android and OHOS!"); #endif PixelFormat pixelFormat = g_defaultAlphaPixelFormat; diff --git a/cocos/renderer/CCTextureAtlas.cpp b/cocos/renderer/CCTextureAtlas.cpp index bba45181532f..6282f8a9c831 100644 --- a/cocos/renderer/CCTextureAtlas.cpp +++ b/cocos/renderer/CCTextureAtlas.cpp @@ -608,6 +608,7 @@ void TextureAtlas::drawNumberOfQuads(ssize_t numberOfQuads, ssize_t start) if (Configuration::getInstance()->supportsShareableVAO()) { +#if (CC_TARGET_PLATFORM != CC_PLATFORM_OHOS) // // Using VBO and VAO // @@ -646,7 +647,7 @@ void TextureAtlas::drawNumberOfQuads(ssize_t numberOfQuads, ssize_t start) #if CC_REBIND_INDICES_BUFFER glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); #endif - +#endif // glBindVertexArray(0); } else diff --git a/cocos/renderer/CCVertexIndexBuffer.cpp b/cocos/renderer/CCVertexIndexBuffer.cpp index 2fa659b72e57..3123a174ad9a 100644 --- a/cocos/renderer/CCVertexIndexBuffer.cpp +++ b/cocos/renderer/CCVertexIndexBuffer.cpp @@ -62,7 +62,7 @@ VertexBuffer::VertexBuffer() , _vertexNumber(0) { -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) auto callBack = [this](EventCustom* event) { this->recreateVBO(); @@ -80,7 +80,7 @@ VertexBuffer::~VertexBuffer() glDeleteBuffers(1, &_vbo); _vbo = 0; } -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) Director::getInstance()->getEventDispatcher()->removeEventListener(_recreateVBOEventListener); #endif } @@ -190,7 +190,7 @@ IndexBuffer::IndexBuffer() , _indexNumber(0) , _recreateVBOEventListener(nullptr) { -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) auto callBack = [this](EventCustom* event) { this->recreateVBO(); @@ -207,7 +207,7 @@ IndexBuffer::~IndexBuffer() glDeleteBuffers(1, &_vbo); _vbo = 0; } -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) Director::getInstance()->getEventDispatcher()->removeEventListener(_recreateVBOEventListener); #endif } diff --git a/cocos/scripting/js-bindings/manual/ScriptingCore.cpp b/cocos/scripting/js-bindings/manual/ScriptingCore.cpp index 8e7718e4618d..e880f045d06b 100644 --- a/cocos/scripting/js-bindings/manual/ScriptingCore.cpp +++ b/cocos/scripting/js-bindings/manual/ScriptingCore.cpp @@ -347,6 +347,8 @@ bool JSBCore_os(JSContext *cx, uint32_t argc, jsval *vp) os = JS_InternString(cx, "OS X"); #elif (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) os = JS_InternString(cx, "WINRT"); +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + os = JS_InternString(cx, "HarmonyOS Next"); #else os = JS_InternString(cx, "Unknown"); #endif diff --git a/cocos/scripting/lua-bindings/CMakeLists.txt b/cocos/scripting/lua-bindings/CMakeLists.txt index 16eac6eb6788..543040090260 100644 --- a/cocos/scripting/lua-bindings/CMakeLists.txt +++ b/cocos/scripting/lua-bindings/CMakeLists.txt @@ -1,5 +1,16 @@ set(cocos_root ${Cocos2d-X_SOURCE_DIR}) +if(OHOS) +include_directories( + ${cocos_root}/external/lua/tolua + ${cocos_root}/external/lua/luajit/include + ${cocos_root}/external/lua + ${cocos_root}/external/xxtea + ${cocos_root}/external + ${cocos_root}/cocos + ${cocos_root}/cocos/editor-support +) +else() include_directories( ${cocos_root}/external/lua/tolua ${cocos_root}/external/lua/lua @@ -9,12 +20,23 @@ include_directories( ${cocos_root}/cocos ${cocos_root}/cocos/editor-support ) +endif() + +if(OHOS) +file(GLOB lua_cocos2d_source_files + "${cocos_root}/external/lua/luajit/include/*.c" + "${cocos_root}/external/lua/tolua/*.c" + "${cocos_root}/external/xxtea/xxtea.cpp" + ) +else() file(GLOB lua_cocos2d_source_files "${cocos_root}/external/lua/lua/*.c" "${cocos_root}/external/lua/tolua/*.c" "${cocos_root}/external/xxtea/xxtea.cpp" ) +endif() + list(APPEND lua_cocos2d_source_files ${cocos_root}/external/lua/luasocket/luasocket.c @@ -49,6 +71,9 @@ elseif(UNIX) if(APPLE) add_definitions(-D_DARWIN_C_SOURCE) endif() + if(OHOS) + add_definitions(-D_GNU_SOURCE) + endif() list(APPEND lua_cocos2d_source_files ${cocos_root}/external/lua/luasocket/serial.c @@ -123,12 +148,39 @@ if(MACOSX) ${lua_bindings_manual_files} manual/platform/ios/CCLuaObjcBridge.mm ) +elseif(OHOS) + set(lua_bindings_manual_headers + ${lua_bindings_manual_headers} + auto/lua_cocos2dx_experimental_webview_auto.hpp + manual/ui/lua_cocos2dx_experimental_webview_manual.hpp + auto/lua_cocos2dx_experimental_video_auto.hpp + manual/ui/lua_cocos2dx_experimental_video_manual.hpp + manual/ui/lua_cocos2dx_ui_manual.hpp + ) + set(lua_bindings_manual_files + ${lua_bindings_manual_files} + auto/lua_cocos2dx_experimental_webview_auto.hpp + manual/ui/lua_cocos2dx_experimental_webview_manual.hpp + auto/lua_cocos2dx_experimental_video_auto.hpp + manual/ui/lua_cocos2dx_experimental_video_manual.hpp + auto/lua_cocos2dx_experimental_webview_auto.cpp + manual/ui/lua_cocos2dx_experimental_webview_manual.cpp + auto/lua_cocos2dx_experimental_video_auto.cpp + manual/ui/lua_cocos2dx_experimental_video_manual.cpp + manual/lua_module_register.cpp + ) endif() set(lua_bindings_files ${lua_cocos2d_source_files} ${lua_bindings_manual_files} ${lua_bindings_auto_files}) add_library(luacocos2d ${lua_bindings_files}) target_link_libraries(luacocos2d cocos2d) + +if(OHOS) + target_link_libraries(luacocos2d ext_luajit) + # Luajit has an additional header file called Luajit. h, which can be found in.../...// Under external/lua/luajit/src, I haven't used it yet +endif() + set_target_properties(luacocos2d PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" diff --git a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_audioengine_auto.cpp b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_audioengine_auto.cpp index 498c4d21499e..d097858841c6 100644 --- a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_audioengine_auto.cpp +++ b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_audioengine_auto.cpp @@ -1,5 +1,5 @@ #include "scripting/lua-bindings/auto/lua_cocos2dx_audioengine_auto.hpp" -#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN +#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN #include "audio/include/AudioEngine.h" #include "scripting/lua-bindings/manual/tolua_fix.h" #include "scripting/lua-bindings/manual/LuaBasicConversions.h" diff --git a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_audioengine_auto.hpp b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_audioengine_auto.hpp index 58f93112b115..ab8446cee8ab 100644 --- a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_audioengine_auto.hpp +++ b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_audioengine_auto.hpp @@ -1,5 +1,5 @@ #include "base/ccConfig.h" -#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN +#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN #ifndef __cocos2dx_audioengine_h__ #define __cocos2dx_audioengine_h__ @@ -41,4 +41,4 @@ int register_all_cocos2dx_audioengine(lua_State* tolua_S); #endif // __cocos2dx_audioengine_h__ -#endif //#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN +#endif //#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN diff --git a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_controller_auto.cpp b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_controller_auto.cpp index eb121f0cf3dc..d97f598f7df3 100644 --- a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_controller_auto.cpp +++ b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_controller_auto.cpp @@ -1,5 +1,5 @@ #include "scripting/lua-bindings/auto/lua_cocos2dx_controller_auto.hpp" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) #include "base/CCGameController.h" #include "scripting/lua-bindings/manual/tolua_fix.h" #include "scripting/lua-bindings/manual/LuaBasicConversions.h" diff --git a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_controller_auto.hpp b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_controller_auto.hpp index 4fd667b49f1a..f8c12c84b2b5 100644 --- a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_controller_auto.hpp +++ b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_controller_auto.hpp @@ -1,5 +1,5 @@ #include "base/ccConfig.h" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) #ifndef __cocos2dx_controller_h__ #define __cocos2dx_controller_h__ @@ -34,4 +34,4 @@ int register_all_cocos2dx_controller(lua_State* tolua_S); #endif // __cocos2dx_controller_h__ -#endif //#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#endif //#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) diff --git a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_video_auto.cpp b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_video_auto.cpp index b9ff08d9c4ec..4025cdccf973 100644 --- a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_video_auto.cpp +++ b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_video_auto.cpp @@ -1,5 +1,5 @@ #include "scripting/lua-bindings/auto/lua_cocos2dx_experimental_video_auto.hpp" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) #include "ui/UIVideoPlayer.h" #include "scripting/lua-bindings/manual/tolua_fix.h" #include "scripting/lua-bindings/manual/LuaBasicConversions.h" diff --git a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_video_auto.hpp b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_video_auto.hpp index 0892c03b49ae..211c0faea149 100644 --- a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_video_auto.hpp +++ b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_video_auto.hpp @@ -1,5 +1,5 @@ #include "base/ccConfig.h" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) #ifndef __cocos2dx_experimental_video_h__ #define __cocos2dx_experimental_video_h__ @@ -30,4 +30,4 @@ int register_all_cocos2dx_experimental_video(lua_State* tolua_S); #endif // __cocos2dx_experimental_video_h__ -#endif //#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) +#endif //#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) diff --git a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_webview_auto.cpp b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_webview_auto.cpp index 86a9cea4ffac..96ff4d160bdd 100644 --- a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_webview_auto.cpp +++ b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_webview_auto.cpp @@ -1,5 +1,5 @@ #include "scripting/lua-bindings/auto/lua_cocos2dx_experimental_webview_auto.hpp" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) #include "ui/UIWebView.h" #include "scripting/lua-bindings/manual/tolua_fix.h" #include "scripting/lua-bindings/manual/LuaBasicConversions.h" diff --git a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_webview_auto.hpp b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_webview_auto.hpp index 4c48ba6da9db..d009ba866a72 100644 --- a/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_webview_auto.hpp +++ b/cocos/scripting/lua-bindings/auto/lua_cocos2dx_experimental_webview_auto.hpp @@ -1,5 +1,5 @@ #include "base/ccConfig.h" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) #ifndef __cocos2dx_experimental_webview_h__ #define __cocos2dx_experimental_webview_h__ @@ -31,4 +31,4 @@ int register_all_cocos2dx_experimental_webview(lua_State* tolua_S); #endif // __cocos2dx_experimental_webview_h__ -#endif //#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) +#endif //#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) diff --git a/cocos/scripting/lua-bindings/manual/CCLuaEngine.cpp b/cocos/scripting/lua-bindings/manual/CCLuaEngine.cpp index 4b05f96ca74f..8f7c63a07b7d 100644 --- a/cocos/scripting/lua-bindings/manual/CCLuaEngine.cpp +++ b/cocos/scripting/lua-bindings/manual/CCLuaEngine.cpp @@ -36,11 +36,13 @@ #include "base/CCDirector.h" #include "base/CCEventCustom.h" +#if _WIN32 #if _MSC_VER > 1800 #pragma comment(lib,"lua51-2015.lib") #else #pragma comment(lib,"lua51.lib") #endif +#endif NS_CC_BEGIN diff --git a/cocos/scripting/lua-bindings/manual/CCLuaStack.cpp b/cocos/scripting/lua-bindings/manual/CCLuaStack.cpp index be53250526d7..cffbe5e873b7 100644 --- a/cocos/scripting/lua-bindings/manual/CCLuaStack.cpp +++ b/cocos/scripting/lua-bindings/manual/CCLuaStack.cpp @@ -88,6 +88,10 @@ namespace { std::string t; get_string_for_print(L, &t); CCLOG("[LUA-print] %s", t.c_str()); +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + OHOS_LOGD("[LUA-print] %{public}s", t.c_str()); +#endif + return 0; } @@ -97,7 +101,9 @@ namespace { std::string t; get_string_for_print(L, &t); log("[LUA-print] %s", t.c_str()); - +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + OHOS_LOGD("[LUA-print] %{public}s", t.c_str()); +#endif return 0; } } diff --git a/cocos/scripting/lua-bindings/manual/audioengine/lua_cocos2dx_audioengine_manual.cpp b/cocos/scripting/lua-bindings/manual/audioengine/lua_cocos2dx_audioengine_manual.cpp index 99017031fb8a..167a5a4db23b 100644 --- a/cocos/scripting/lua-bindings/manual/audioengine/lua_cocos2dx_audioengine_manual.cpp +++ b/cocos/scripting/lua-bindings/manual/audioengine/lua_cocos2dx_audioengine_manual.cpp @@ -25,7 +25,7 @@ #include "scripting/lua-bindings/manual/audioengine/lua_cocos2dx_audioengine_manual.h" -#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN +#if CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN #include "scripting/lua-bindings/auto/lua_cocos2dx_audioengine_auto.hpp" #include "scripting/lua-bindings/manual/tolua_fix.h" diff --git a/cocos/scripting/lua-bindings/manual/controller/lua_cocos2dx_controller_manual.cpp b/cocos/scripting/lua-bindings/manual/controller/lua_cocos2dx_controller_manual.cpp index 6231d950d9af..e85164bae8df 100644 --- a/cocos/scripting/lua-bindings/manual/controller/lua_cocos2dx_controller_manual.cpp +++ b/cocos/scripting/lua-bindings/manual/controller/lua_cocos2dx_controller_manual.cpp @@ -23,7 +23,7 @@ ****************************************************************************/ #include "scripting/lua-bindings/manual/controller/lua_cocos2dx_controller_manual.hpp" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) #include "scripting/lua-bindings/manual/tolua_fix.h" @@ -370,4 +370,4 @@ int register_all_cocos2dx_controller_manual(lua_State* L) return 0; } -#endif //#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#endif //#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) diff --git a/cocos/scripting/lua-bindings/manual/controller/lua_cocos2dx_controller_manual.hpp b/cocos/scripting/lua-bindings/manual/controller/lua_cocos2dx_controller_manual.hpp index 7d44970ea0b0..3e7bb3b716c2 100644 --- a/cocos/scripting/lua-bindings/manual/controller/lua_cocos2dx_controller_manual.hpp +++ b/cocos/scripting/lua-bindings/manual/controller/lua_cocos2dx_controller_manual.hpp @@ -32,10 +32,10 @@ extern "C" { } #endif -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) TOLUA_API int register_all_cocos2dx_controller_manual(lua_State* L); -#endif //#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#endif //#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) #endif // #ifndef COCOS_SCRIPTING_LUA_BINDINGS_LUA_COCOS2DX_CONTROLLER_MANUAL_H diff --git a/cocos/scripting/lua-bindings/manual/network/Lua_web_socket.cpp b/cocos/scripting/lua-bindings/manual/network/Lua_web_socket.cpp index 666828aea8f1..28655cb8a9da 100644 --- a/cocos/scripting/lua-bindings/manual/network/Lua_web_socket.cpp +++ b/cocos/scripting/lua-bindings/manual/network/Lua_web_socket.cpp @@ -22,7 +22,7 @@ THE SOFTWARE. ****************************************************************************/ -#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_MAC) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_MAC) #include "scripting/lua-bindings/manual/network/Lua_web_socket.h" #include #include diff --git a/cocos/scripting/lua-bindings/manual/network/Lua_web_socket.h b/cocos/scripting/lua-bindings/manual/network/Lua_web_socket.h index 1a85155ae64c..be372fbb6a46 100644 --- a/cocos/scripting/lua-bindings/manual/network/Lua_web_socket.h +++ b/cocos/scripting/lua-bindings/manual/network/Lua_web_socket.h @@ -24,7 +24,7 @@ #ifndef __LUA_WEB_SOCKET_H__ #define __LUA_WEB_SOCKET_H__ -#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_MAC) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_MAC) #ifdef __cplusplus extern "C" { diff --git a/cocos/scripting/lua-bindings/manual/network/lua_cocos2dx_network_manual.cpp b/cocos/scripting/lua-bindings/manual/network/lua_cocos2dx_network_manual.cpp index 31ab7f21e63e..32b17b9bf013 100644 --- a/cocos/scripting/lua-bindings/manual/network/lua_cocos2dx_network_manual.cpp +++ b/cocos/scripting/lua-bindings/manual/network/lua_cocos2dx_network_manual.cpp @@ -23,11 +23,11 @@ ****************************************************************************/ #include "scripting/lua-bindings/manual/network/lua_cocos2dx_network_manual.h" extern "C" { -#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_MAC) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_MAC) #include "scripting/lua-bindings/manual/network/lua_extensions.h" #endif } -#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_MAC) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_MAC) #include "scripting/lua-bindings/manual/network/Lua_web_socket.h" #endif @@ -40,11 +40,11 @@ int register_network_module(lua_State* L) lua_getglobal(L, "_G"); if (lua_istable(L,-1))//stack:...,_G, { -#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_MAC) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) luaopen_lua_extensions(L); #endif -#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_MAC) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) tolua_web_socket_open(L); register_web_socket_manual(L); #endif diff --git a/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_video_manual.cpp b/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_video_manual.cpp index 7cf4cbb8c16a..d36c53b36011 100644 --- a/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_video_manual.cpp +++ b/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_video_manual.cpp @@ -1,6 +1,6 @@ #include "scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_video_manual.hpp" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) #include "ui/UIVideoPlayer.h" #include "scripting/lua-bindings/manual/tolua_fix.h" diff --git a/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_video_manual.hpp b/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_video_manual.hpp index 6201540e5927..c62e40eb7ec4 100644 --- a/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_video_manual.hpp +++ b/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_video_manual.hpp @@ -22,7 +22,7 @@ THE SOFTWARE. ****************************************************************************/ #include "base/ccConfig.h" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) #ifndef COCOS_SCRIPTING_LUA_BINDINGS_LUA_COCOS2DX_EXPERIMENTAL_VIDEO_MANUAL_H #define COCOS_SCRIPTING_LUA_BINDINGS_LUA_COCOS2DX_EXPERIMENTAL_VIDEO_MANUAL_H @@ -38,4 +38,4 @@ extern "C" { TOLUA_API int register_all_cocos2dx_experimental_video_manual(lua_State* L); #endif //#ifndef COCOS_SCRIPTING_LUA_BINDINGS_LUA_COCOS2DX_EXPERIMENTAL_VIDEO_MANUAL_H -#endif //#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#endif //#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) diff --git a/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_webview_manual.cpp b/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_webview_manual.cpp index 55d0c4a869da..427b30681a4a 100644 --- a/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_webview_manual.cpp +++ b/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_webview_manual.cpp @@ -1,6 +1,6 @@ #include "scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_video_manual.hpp" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) #include "ui/UIWebView.h" #include "scripting/lua-bindings/manual/tolua_fix.h" diff --git a/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_webview_manual.hpp b/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_webview_manual.hpp index 909e64530c06..3f1012094f0c 100644 --- a/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_webview_manual.hpp +++ b/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_webview_manual.hpp @@ -22,7 +22,7 @@ THE SOFTWARE. ****************************************************************************/ #include "base/ccConfig.h" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) #ifndef COCOS_SCRIPTING_LUA_BINDINGS_LUA_COCOS2DX_EXPERIMENTAL_WEBVIEW_MANUAL_H #define COCOS_SCRIPTING_LUA_BINDINGS_LUA_COCOS2DX_EXPERIMENTAL_WEBVIEW_MANUAL_H @@ -38,4 +38,4 @@ extern "C" { TOLUA_API int register_all_cocos2dx_experimental_webview_manual(lua_State* L); #endif //#ifndef COCOS_SCRIPTING_LUA_BINDINGS_LUA_COCOS2DX_EXPERIMENTAL_WEBVIEW_MANUAL_H -#endif //#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) +#endif //#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) diff --git a/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_ui_manual.cpp b/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_ui_manual.cpp index 0ea60b3e6210..c4ad93b8bb62 100644 --- a/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_ui_manual.cpp +++ b/cocos/scripting/lua-bindings/manual/ui/lua_cocos2dx_ui_manual.cpp @@ -23,7 +23,7 @@ ****************************************************************************/ #include "scripting/lua-bindings/manual/ui/lua_cocos2dx_ui_manual.hpp" #include "scripting/lua-bindings/auto/lua_cocos2dx_ui_auto.hpp" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS ||CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) #include "scripting/lua-bindings/auto/lua_cocos2dx_experimental_video_auto.hpp" #include "scripting/lua-bindings/manual/ui/lua_cocos2dx_experimental_video_manual.hpp" #include "scripting/lua-bindings/auto/lua_cocos2dx_experimental_webview_auto.hpp" @@ -1181,7 +1181,7 @@ int register_ui_moudle(lua_State* L) { register_all_cocos2dx_ui(L); register_all_cocos2dx_ui_manual(L); -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) && !defined(CC_TARGET_OS_TVOS) register_all_cocos2dx_experimental_video(L); register_all_cocos2dx_experimental_video_manual(L); register_all_cocos2dx_experimental_webview(L); diff --git a/cocos/scripting/lua-bindings/manual/video/lua_cocos2dx_experimental_video_manual.cpp b/cocos/scripting/lua-bindings/manual/video/lua_cocos2dx_experimental_video_manual.cpp index 40e9d8c82a37..fe4b65a70bf3 100644 --- a/cocos/scripting/lua-bindings/manual/video/lua_cocos2dx_experimental_video_manual.cpp +++ b/cocos/scripting/lua-bindings/manual/video/lua_cocos2dx_experimental_video_manual.cpp @@ -1,6 +1,6 @@ #include "scripting/lua-bindings/manual/video/lua_cocos2dx_experimental_video_manual.hpp" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) #include "ui/UIVideoPlayer.h" #include "scripting/lua-bindings/manual/tolua_fix.h" diff --git a/cocos/scripting/lua-bindings/manual/video/lua_cocos2dx_experimental_video_manual.hpp b/cocos/scripting/lua-bindings/manual/video/lua_cocos2dx_experimental_video_manual.hpp index 6201540e5927..8b8b9eca55dd 100644 --- a/cocos/scripting/lua-bindings/manual/video/lua_cocos2dx_experimental_video_manual.hpp +++ b/cocos/scripting/lua-bindings/manual/video/lua_cocos2dx_experimental_video_manual.hpp @@ -22,7 +22,7 @@ THE SOFTWARE. ****************************************************************************/ #include "base/ccConfig.h" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) #ifndef COCOS_SCRIPTING_LUA_BINDINGS_LUA_COCOS2DX_EXPERIMENTAL_VIDEO_MANUAL_H #define COCOS_SCRIPTING_LUA_BINDINGS_LUA_COCOS2DX_EXPERIMENTAL_VIDEO_MANUAL_H diff --git a/cocos/scripting/lua-bindings/script/cocos2d/Cocos2dConstants.lua b/cocos/scripting/lua-bindings/script/cocos2d/Cocos2dConstants.lua index ff51b72ebef0..e745b7fcd978 100644 --- a/cocos/scripting/lua-bindings/script/cocos2d/Cocos2dConstants.lua +++ b/cocos/scripting/lua-bindings/script/cocos2d/Cocos2dConstants.lua @@ -188,6 +188,7 @@ cc.PLATFORM_OS_EMSCRIPTEN = 8 cc.PLATFORM_OS_TIZEN = 9 cc.PLATFORM_OS_WINRT = 10 cc.PLATFORM_OS_WP8 = 11 +cc.PLATFORM_OS_OHOS = 12 cc.LANGUAGE_ENGLISH = 0 cc.LANGUAGE_CHINESE = 1 diff --git a/cocos/scripting/lua-bindings/script/framework/device.lua b/cocos/scripting/lua-bindings/script/framework/device.lua index acf7746c4398..eb02b1351129 100644 --- a/cocos/scripting/lua-bindings/script/framework/device.lua +++ b/cocos/scripting/lua-bindings/script/framework/device.lua @@ -58,6 +58,8 @@ elseif target == cc.PLATFORM_OS_WINRT then device.platform = "winrt" elseif target == cc.PLATFORM_OS_WP8 then device.platform = "wp8" +elseif target == cc.PLATFORM_OS_OHOS then + device.platform = "HarmonyOS Next" end local language_ = app:getCurrentLanguage() diff --git a/cocos/storage/local-storage/LocalStorage-ohos.cpp b/cocos/storage/local-storage/LocalStorage-ohos.cpp new file mode 100644 index 000000000000..7312f4cfa44f --- /dev/null +++ b/cocos/storage/local-storage/LocalStorage-ohos.cpp @@ -0,0 +1,140 @@ +/*************************************************************************** + Copyright (c) 2012 Zynga Inc. + Copyright (c) 2013 cocos2d-x.org + Copyright (c) 2013-2017 Chukong Technologic Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + 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. +***************************************************************************/ + + +/* + Local Storage support for the JS Bindings for iOS. + Works on cocos2d-iphone and cocos2d-x. + */ + +#include "storage/local-storage/LocalStorage.h" +#include "platform/CCPlatformMacros.h" + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + +#include +#include +#include +#include "platform/CCPlatformConfig.h" + +//using namespace cocos2d; +static int _initialized = 0; + +static std::string className = "org.cocos2dx.lib.Cocos2dxLocalStorage"; + +static void splitFilename (std::string& str) +{ + size_t found = 0; + found = str.find_last_of("/\\"); + if (found != std::string::npos) + { + str = str.substr(found + 1); + } +} + +void localStorageInit( const std::string& fullpath) +{ + if (fullpath.empty()) + return; + + if (!_initialized) + { + std::string strDBFilename = fullpath; + splitFilename(strDBFilename); + // TBD need fixed +// if (JniHelper::callStaticBooleanMethod(className, "init", strDBFilename, "data")) { +// _initialized = 1; +// } + } +} + +void localStorageFree() +{ + // TBD need fixed +// if (_initialized) { +// JniHelper::callStaticVoidMethod(className, "destroy"); +// _initialized = 0; +// } +} + +/** sets an item in the LS */ +void localStorageSetItem( const std::string& key, const std::string& value) +{ + assert( _initialized ); + // TBD need fixed +// JniHelper::callStaticVoidMethod(className, "setItem", key, value); +} + +/** gets an item from the LS */ +bool localStorageGetItem( const std::string& key, std::string *outItem ) +{ +// assert( _initialized ); +// JniMethodInfo t; +// +// if (JniHelper::getStaticMethodInfo(t, className.c_str(), "getItem", "(Ljava/lang/String;)Ljava/lang/String;")) +// { +// jstring jkey = t.env->NewStringUTF(key.c_str()); +// jstring jret = (jstring)t.env->CallStaticObjectMethod(t.classID, t.methodID, jkey); +// if (jret == nullptr) +// { +// t.env->DeleteLocalRef(jret); +// t.env->DeleteLocalRef(jkey); +// t.env->DeleteLocalRef(t.classID); +// return false; +// } +// else +// { +// outItem->assign(JniHelper::jstring2string(jret)); +// t.env->DeleteLocalRef(jret); +// t.env->DeleteLocalRef(jkey); +// t.env->DeleteLocalRef(t.classID); +// return true; +// } +// } +// else +// { +// return false; +// } + // TBD need fixed + return false; +} + +/** removes an item from the LS */ +void localStorageRemoveItem( const std::string& key ) +{ + assert( _initialized ); + // TBD need fixed +// JniHelper::callStaticVoidMethod(className, "removeItem", key); +} + +/** removes all items from the LS */ +void localStorageClear() +{ + assert( _initialized ); + // TBD need fixed +// JniHelper::callStaticVoidMethod(className, "clear"); +} + +#endif // #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) diff --git a/cocos/ui/CMakeLists.txt b/cocos/ui/CMakeLists.txt index 722c7f40bada..75af47c97f33 100644 --- a/cocos/ui/CMakeLists.txt +++ b/cocos/ui/CMakeLists.txt @@ -26,6 +26,19 @@ ELSEIF(ANDROID) ui/UIVideoPlayer-android.cpp ui/UIWebViewImpl-android.cpp ) +elseif(OHOS) + set(COCOS_UI_SPECIFIC_SRC + ui/UIWebView.h + ui/UIWebView-inl.h + ui/UIEditBox/UIEditBoxImpl-ohos.h + ui/UIWebViewImpl-ohos.h + ui/UIVideoPlayer.h + ui/UIEditBox/UIEditBoxImpl-common.cpp + ui/UIEditBox/UIEditBoxImpl-ohos.cpp + ui/UIWebViewImpl-ohos.cpp + ui/UIWebView.cpp + ui/UIVideoPlayer-ohos.cpp + ) endif() #todo: android UIWebViewImpl and UIVideoPlayer diff --git a/cocos/ui/CocosGUI.h b/cocos/ui/CocosGUI.h index 87fae1bb79a9..25ad0433c654 100644 --- a/cocos/ui/CocosGUI.h +++ b/cocos/ui/CocosGUI.h @@ -46,10 +46,10 @@ THE SOFTWARE. #include "ui/UIHBox.h" #include "ui/UIVBox.h" #include "ui/UIRelativeBox.h" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN) #include "ui/UIVideoPlayer.h" #endif -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN) #include "ui/UIWebView.h" #endif #include "ui/UIDeprecated.h" diff --git a/cocos/ui/UIEditBox/UIEditBoxImpl-ohos.cpp b/cocos/ui/UIEditBox/UIEditBoxImpl-ohos.cpp new file mode 100644 index 000000000000..a6d32d2bb6c1 --- /dev/null +++ b/cocos/ui/UIEditBox/UIEditBoxImpl-ohos.cpp @@ -0,0 +1,212 @@ +#include "ui/UIEditBox/UIEditBoxImpl-ohos.h" +#include + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + +#include "ui/UIEditBox/UIEditBox.h" +#include "2d/CCLabel.h" +#include "base/ccUTF8.h" +#include "math/Vec2.h" +#include "ui/UIHelper.h" +#include "base/CCDirector.h" +#include "platform/CCFileUtils.h" +#include "platform/ohos/napi/helper/NapiHelper.h" +#include "platform/ohos/CCLogOhos.h" + + +NS_CC_BEGIN + +namespace ui { + + static std::unordered_map s_allEditBoxes; + static int curIndex = 0; + + EditBoxImpl* __createSystemEditBox(EditBox* editBox) + { + return new EditBoxImplOhos(editBox); + } + + void EditBoxImplOhos::createNativeControl(const Rect& frame) + { + OHOS_LOGD("create textinput"); + + auto director = cocos2d::Director::getInstance(); + auto glView = director->getOpenGLView(); + auto frameSize = glView->getFrameSize(); + + auto winSize = director->getWinSize(); + auto leftBottom = _editBox->convertToWorldSpace(Point::ZERO); + + auto contentSize = frame.size; + auto rightTop = _editBox->convertToWorldSpace(Point(contentSize.width, contentSize.height)); + auto uiLeft = frameSize.width / 2 + (leftBottom.x - winSize.width / 2) * glView->getScaleX(); + auto uiTop = frameSize.height / 2 - (rightTop.y - winSize.height / 2) * glView->getScaleY(); + auto uiWidth = (rightTop.x - leftBottom.x) * glView->getScaleX(); + auto uiHeight = (rightTop.y - leftBottom.y) * glView->getScaleY(); + auto paddingW = (int)(5 * glView->getScaleX()); + auto paddingH = (int)(uiHeight * 0.33f / 2); + + s_allEditBoxes[curIndex] = this; + _editBoxIndex = curIndex; + JSFunction::getFunction("CocosEditBox.createCocosEditBox").invoke(_editBoxIndex, uiLeft, uiTop, uiWidth, uiHeight, paddingW, paddingH); + curIndex++; + } + + EditBoxImplOhos::EditBoxImplOhos(EditBox* pEditText) + : EditBoxImplCommon(pEditText) + , _editBoxIndex(-1) + { + + } + + EditBoxImplOhos::~EditBoxImplOhos() + { + s_allEditBoxes.erase(_editBoxIndex); + JSFunction::getFunction("CocosEditBox.removeCocosEditBox").invoke(_editBoxIndex); + } + + bool EditBoxImplOhos::isEditing() + { + return false; + } + + void EditBoxImplOhos::setNativeText(const char* pText) + { + JSFunction::getFunction("CocosEditBox.setCurrentText").invoke(_editBoxIndex, pText); + } + + void EditBoxImplOhos::setNativeFont(const char* pFontName, int fontSize) + { + auto director = cocos2d::Director::getInstance(); + auto glView = director->getOpenGLView(); + auto isFontFileExists = cocos2d::FileUtils::getInstance()->isFileExist(pFontName); + std::string realFontPath = pFontName; + if (isFontFileExists) { + realFontPath = cocos2d::FileUtils::getInstance()->fullPathForFilename(pFontName); + if (realFontPath.find("rawfile/") == 0) + { + realFontPath = realFontPath.substr(strlen("rawfile/")); // Chop out the 'assets/' portion of the path. + } + } + auto realFontsize = fontSize * glView->getScaleX(); + JSFunction::getFunction("CocosEditBox.setEditBoxFontSize").invoke(_editBoxIndex, realFontsize); + JSFunction::getFunction("CocosEditBox.setEditBoxFontPath").invoke(_editBoxIndex, realFontPath); + } + + void EditBoxImplOhos::setNativeFontColor(const Color4B& color) + { + JSFunction::getFunction("CocosEditBox.setEditBoxFontColor").invoke(_editBoxIndex, (int)color.r, (int)color.g, (int)color.b, (int)color.a); + } + + void EditBoxImplOhos::setNativePlaceHolder(const char* pText) + { + JSFunction::getFunction("CocosEditBox.setEditBoxPlaceHolder").invoke(_editBoxIndex, pText); + } + + void EditBoxImplOhos::setNativePlaceholderFont(const char* pFontName, int fontSize) + { + auto director = cocos2d::Director::getInstance(); + auto glView = director->getOpenGLView(); + auto isFontFileExists = cocos2d::FileUtils::getInstance()->isFileExist(pFontName); + std::string realFontPath = pFontName; + if (isFontFileExists) { + realFontPath = cocos2d::FileUtils::getInstance()->fullPathForFilename(pFontName); + if (realFontPath.find("rawfile/") == 0) + { + realFontPath = realFontPath.substr(strlen("rawfile/")); // Chop out the 'assets/' portion of the path. + } + } + auto realFontsize = fontSize * glView->getScaleX(); + JSFunction::getFunction("CocosEditBox.setEditBoxPlaceHolderFontSize").invoke(_editBoxIndex, realFontsize); + JSFunction::getFunction("CocosEditBox.setEditBoxPlaceHolderFontPath").invoke(_editBoxIndex, realFontPath); + } + + void EditBoxImplOhos::setNativePlaceholderFontColor(const Color4B& color) + { + JSFunction::getFunction("CocosEditBox.setEditBoxPlaceHolderFontColor").invoke(_editBoxIndex, (int)color.r, (int)color.g, (int)color.b, (int)color.a); + } + + void EditBoxImplOhos::setNativeMaxLength(int maxLength) + { + JSFunction::getFunction("CocosEditBox.setEditBoxMaxLength").invoke(_editBoxIndex, maxLength); + } + + void EditBoxImplOhos::setNativeInputMode(EditBox::InputMode inputMode) + { + JSFunction::getFunction("CocosEditBox.setNativeInputMode").invoke(_editBoxIndex, static_cast(inputMode)); + } + + void EditBoxImplOhos::setNativeInputFlag(EditBox::InputFlag inputFlag) + { + JSFunction::getFunction("CocosEditBox.setNativeInputFlag").invoke(_editBoxIndex, static_cast(inputFlag)); + } + + void EditBoxImplOhos::setNativeReturnType(EditBox::KeyboardReturnType returnType) + { + OHOS_LOGW("OHOS not support returnType %{public}d", returnType); + } + + void EditBoxImplOhos::setNativeVisible(bool visible) + { + JSFunction::getFunction("CocosEditBox.setEditBoxVisible").invoke(_editBoxIndex, visible); + } + + void EditBoxImplOhos::updateNativeFrame(const Rect& rect) + { + JSFunction::getFunction("CocosEditBox.setEditBoxViewRect").invoke(_editBoxIndex, (int)rect.origin.x, (int)rect.origin.y, (int)rect.size.width, (int)rect.size.height); + } + + void EditBoxImplOhos::nativeOpenKeyboard() + { + JSFunction::getFunction("CocosEditBox.setEditBoxVisible").invoke(_editBoxIndex, true); + } + + void EditBoxImplOhos::nativeCloseKeyboard() + { + JSFunction::getFunction("CocosEditBox.setEditBoxVisible").invoke(_editBoxIndex, false); + } + + void EditBoxImplOhos::hideAllEditBox() { + JSFunction::getFunction("CocosEditBox.hideAllEditBox").invoke(); + } + + void EditBoxImplOhos::onBeginCallBack(int index) + { + OHOS_LOGD("textinput editBoxEditingDidBegin"); + auto it = s_allEditBoxes.find(index); + if (it != s_allEditBoxes.end()) + { + s_allEditBoxes[index]->editBoxEditingDidBegin(); + } + } + + void EditBoxImplOhos::onChangeCallBack(int index, const std::string& text) + { + OHOS_LOGD("textinput onChangeCallBack"); + auto it = s_allEditBoxes.find(index); + if (it != s_allEditBoxes.end()) + { + s_allEditBoxes[index]->editBoxEditingChanged(text); + } + } + + void EditBoxImplOhos::onEnterCallBack(int index, const std::string& text) + { + OHOS_LOGD("textinput onEnterCallBack"); + JSFunction::getFunction("CocosEditBox.setEditBoxVisible").invoke(index, false); + auto it = s_allEditBoxes.find(index); + if (it != s_allEditBoxes.end()) + { + s_allEditBoxes[index]->editBoxEditingDidEnd(text); + } + } + + const char* EditBoxImplOhos::getNativeDefaultFontName() + { + return "sans-serif"; + } +} + +NS_CC_END + +#endif /* #if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) */ \ No newline at end of file diff --git a/cocos/ui/UIEditBox/UIEditBoxImpl-ohos.h b/cocos/ui/UIEditBox/UIEditBoxImpl-ohos.h new file mode 100644 index 000000000000..8f7f761b2491 --- /dev/null +++ b/cocos/ui/UIEditBox/UIEditBoxImpl-ohos.h @@ -0,0 +1,67 @@ +#ifndef __UIEDITBOXIMPLOHOS_H__ +#define __UIEDITBOXIMPLOHOS_H__ + +#include "platform/CCPlatformConfig.h" + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + +#include "ui/UIEditBox/UIEditBoxImpl-common.h" + +NS_CC_BEGIN + +class Label; + +namespace ui { + + class EditBox; + + class EditBoxImplOhos : public EditBoxImplCommon + { + public: + /** + * @js NA + */ + EditBoxImplOhos(EditBox* pEditText); + /** + * @js NA + * @lua NA + */ + virtual ~EditBoxImplOhos(); + + virtual bool isEditing() override; + virtual void createNativeControl(const Rect& frame) override; + virtual void setNativeFont(const char* pFontName, int fontSize) override ; + virtual void setNativeFontColor(const Color4B& color) override ; + virtual void setNativePlaceholderFont(const char* pFontName, int fontSize) override ; + virtual void setNativePlaceholderFontColor(const Color4B& color) override ; + virtual void setNativeInputMode(EditBox::InputMode inputMode) override ; + virtual void setNativeInputFlag(EditBox::InputFlag inputFlag) override ; + virtual void setNativeReturnType(EditBox::KeyboardReturnType returnType)override ; + virtual void setNativeText(const char* pText) override ; + virtual void setNativePlaceHolder(const char* pText) override ; + virtual void setNativeVisible(bool visible) override ; + virtual void updateNativeFrame(const Rect& rect) override ; + virtual const char* getNativeDefaultFontName() override ; + virtual void nativeOpenKeyboard() override ; + virtual void nativeCloseKeyboard() override ; + virtual void setNativeMaxLength(int maxLength) override; + + static void hideAllEditBox(); + static void onBeginCallBack(int index); + static void onChangeCallBack(int index, const std::string& text); + static void onEnterCallBack(int index, const std::string& text); + + private: + virtual void doAnimationWhenKeyboardMove(float duration, float distance)override {}; + int _editBoxIndex; + }; + + +} + + +NS_CC_END + +#endif /* #if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) */ + +#endif /* __UIEDITBOXIMPLOHOS_H__ */ diff --git a/cocos/ui/UIVideoPlayer-ohos.cpp b/cocos/ui/UIVideoPlayer-ohos.cpp new file mode 100644 index 000000000000..9c157abaca7d --- /dev/null +++ b/cocos/ui/UIVideoPlayer-ohos.cpp @@ -0,0 +1,305 @@ +/**************************************************************************** + 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 "ui/UIVideoPlayer.h" +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + +#include +#include +#include +#include "base/CCDirector.h" +#include "base/CCEventListenerKeyboard.h" +#include "platform/ohos/CCFileUtils-ohos.h" +#include "ui/UIVideoPlayer-ohos.h" +#include "platform/ohos/napi/helper/NapiHelper.h" + +#include "ui/UIHelper.h" + +USING_NS_CC; +#define QUIT_FULLSCREEN 1000 + +//----------------------------------------------------------------------------------------------------------- + +using namespace cocos2d::experimental::ui; +static int32_t kVideoPlayerTag = 0; +static std::unordered_map s_allVideoPlayers; +static const std::string SANDBOX_PREFIX = "file://"; + +VideoPlayer::VideoPlayer() + : _fullScreenDirty(false), + _fullScreenEnabled(false), + _keepAspectRatioEnabled(false), + _videoPlayerIndex(-1), + _eventCallback(nullptr), + _isPlaying(false), + _isLooping(false), + _isUserInputEnabled(true), + _styleType(StyleType::DEFAULT) +{ + // 增加索引 + _videoPlayerIndex = kVideoPlayerTag++; + s_allVideoPlayers[_videoPlayerIndex] = this; + +#if CC_VIDEOPLAYER_DEBUG_DRAW + _debugDrawNode = DrawNode::create(); + addChild(_debugDrawNode); +#endif + JSFunction::getFunction("VideoPlayer.createVideoPlayer").invoke(_videoPlayerIndex); +} + +VideoPlayer::~VideoPlayer() +{ + if (_videoPlayerIndex != -1 && kVideoPlayerTag != -1) { + JSFunction::getFunction("VideoPlayer.removeVideoPlayer").invoke(_videoPlayerIndex); + auto iter = s_allVideoPlayers.find(_videoPlayerIndex); + if (iter != s_allVideoPlayers.end()) { + s_allVideoPlayers.erase(iter); + } + } +} + +void VideoPlayer::setFileName(const std::string &fileName) +{ + _videoURL = FileUtils::getInstance()->fullPathForFilename(fileName); + if (_videoURL[0] == '/') { + _videoSource = VideoPlayer::Source::URL; + JSFunction::getFunction("VideoPlayer.setURL").invoke(_videoPlayerIndex, SANDBOX_PREFIX + _videoURL, (int)_videoSource); + } else { + _videoSource = VideoPlayer::Source::FILENAME; + JSFunction::getFunction("VideoPlayer.setURL").invoke(_videoPlayerIndex, _videoURL, (int)_videoSource); + } +} + +void VideoPlayer::setURL(const std::string &videoUrl) +{ + _videoURL = videoUrl; + _videoSource = VideoPlayer::Source::URL; + JSFunction::getFunction("VideoPlayer.setURL").invoke(_videoPlayerIndex, _videoURL, (int)_videoSource); +} + +void VideoPlayer::setLooping(bool looping) +{ + _isLooping = looping; + JSFunction::getFunction("VideoPlayer.setLooping").invoke(_videoPlayerIndex, _isLooping); +} + +void VideoPlayer::setUserInputEnabled(bool enableInput) +{ + _isUserInputEnabled = enableInput; + // todo:鸿蒙暂时不支持 +} + +void VideoPlayer::setStyle(StyleType style) +{ + _styleType = style; +} + +void VideoPlayer::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) +{ + cocos2d::ui::Widget::draw(renderer, transform, flags); + + if (flags & FLAGS_TRANSFORM_DIRTY) { + auto uiRect = cocos2d::ui::Helper::convertBoundingBoxToScreen(this); + JSFunction::getFunction("VideoPlayer.setVideoPlayerRect").invoke(_videoPlayerIndex, (int)uiRect.origin.x, (int)uiRect.origin.y, + (int)uiRect.size.width, (int)uiRect.size.height); + } + +#if CC_VIDEOPLAYER_DEBUG_DRAW + _debugDrawNode->clear(); + auto size = getContentSize(); + Point vertices[4] = {Point::ZERO, Point(size.width, 0), Point(size.width, size.height), Point(0, size.height)}; + _debugdrawNode->drawPoly(vertices, 4, true, Color4F(1.0, 1.0, 1.0, 1.0)); +#endif +} + +void VideoPlayer::setFullScreenEnabled(bool enabled) +{ + if (_fullScreenEnabled != enabled) { + _fullScreenEnabled = enabled; + JSFunction::getFunction("VideoPlayer.requestFullscreen").invoke(_videoPlayerIndex, enabled); + } +} + +bool VideoPlayer::isFullScreenEnabled() const +{ + return _fullScreenEnabled; +} + +void VideoPlayer::setKeepAspectRatioEnabled(bool enable) +{ + if (_keepAspectRatioEnabled != enable) { + _keepAspectRatioEnabled = enable; + JSFunction::getFunction("VideoPlayer.setKeepAspectRatioEnabled").invoke(_videoPlayerIndex, enable); + } +} + +#if CC_VIDEOPLAYER_DEBUG_DRAW +void VideoPlayer::drawDebugData() +{ + Director *director = Director::getInstance(); + CCASSERT(nullptr != director, "Director is null when setting matrix stack"); + + director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); + director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform); + + auto size = getContentSize(); + + Point vertices[4] = {Point::ZERO, Point(size.width, 0), Point(size.width, size.height), Point(0, size.height)}; + + DrawPrimitives::drawPoly(vertices, 4, true); + + director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); +} +#endif + +void VideoPlayer::play() +{ + if (!_videoURL.empty()) { + JSFunction::getFunction("VideoPlayer.play").invoke(_videoPlayerIndex); + } +} + +void VideoPlayer::pause() +{ + if (!_videoURL.empty()) { + JSFunction::getFunction("VideoPlayer.pause").invoke(_videoPlayerIndex); + } +} + +void VideoPlayer::resume() +{ + if (!_videoURL.empty()) { + JSFunction::getFunction("VideoPlayer.play").invoke(_videoPlayerIndex); + } +} + +void VideoPlayer::stop() +{ + if (!_videoURL.empty()) { + JSFunction::getFunction("VideoPlayer.stop").invoke(_videoPlayerIndex); + } +} + +void VideoPlayer::seekTo(float sec) +{ + if (!_videoURL.empty()) { + JSFunction::getFunction("VideoPlayer.seekTo").invoke(_videoPlayerIndex, (int)sec); + } +} + +bool VideoPlayer::isPlaying() const +{ + return _isPlaying; +} + +bool VideoPlayer::isLooping() const +{ + return _isLooping; +} + +bool VideoPlayer::isUserInputEnabled() const +{ + return _isUserInputEnabled; +} + +void VideoPlayer::setVisible(bool visible) +{ + cocos2d::ui::Widget::setVisible(visible); + + if (!visible || isRunning()) { + JSFunction::getFunction("VideoPlayer.setVisible").invoke(_videoPlayerIndex, visible); + } +} + +void VideoPlayer::onEnter() +{ + Widget::onEnter(); + if (isVisible() && !_videoURL.empty()) { + JSFunction::getFunction("VideoPlayer.setVisible").invoke(_videoPlayerIndex, true); + } +} + +void VideoPlayer::onExit() +{ + Widget::onExit(); + JSFunction::getFunction("VideoPlayer.setVisible").invoke(_videoPlayerIndex, false); +} + +void VideoPlayer::addEventListener(const VideoPlayer::ccVideoPlayerCallback &callback) +{ + _eventCallback = callback; +} + +void VideoPlayer::onPlayEvent(int event) +{ + if (event == QUIT_FULLSCREEN) { + _fullScreenEnabled = false; + } else { + VideoPlayer::EventType videoEvent = (VideoPlayer::EventType)event; + if (videoEvent == VideoPlayer::EventType::PLAYING) { + _isPlaying = true; + } else { + _isPlaying = false; + } + + if (_eventCallback) { + _eventCallback(this, videoEvent); + } + } +} + +cocos2d::ui::Widget *VideoPlayer::createCloneInstance() +{ + return VideoPlayer::create(); +} + +void VideoPlayer::copySpecialProperties(Widget *widget) +{ + VideoPlayer *videoPlayer = dynamic_cast(widget); + if (videoPlayer) { + _isPlaying = videoPlayer->_isPlaying; + _isLooping = videoPlayer->_isLooping; + _isUserInputEnabled = videoPlayer->_isUserInputEnabled; + _styleType = videoPlayer->_styleType; + _fullScreenEnabled = videoPlayer->_fullScreenEnabled; + _fullScreenDirty = videoPlayer->_fullScreenDirty; + _videoURL = videoPlayer->_videoURL; + _keepAspectRatioEnabled = videoPlayer->_keepAspectRatioEnabled; + _videoSource = videoPlayer->_videoSource; + _videoPlayerIndex = videoPlayer->_videoPlayerIndex; + _eventCallback = videoPlayer->_eventCallback; + _videoView = videoPlayer->_videoView; + } +} + +void executeVideoCallback(int index, int event) +{ + auto it = s_allVideoPlayers.find(index); + if (it != s_allVideoPlayers.end()) { + s_allVideoPlayers[index]->onPlayEvent(event); + } +} + +#endif diff --git a/cocos/ui/UIVideoPlayer-ohos.h b/cocos/ui/UIVideoPlayer-ohos.h new file mode 100644 index 000000000000..46ea07db33ab --- /dev/null +++ b/cocos/ui/UIVideoPlayer-ohos.h @@ -0,0 +1,7 @@ +#pragma once + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + +void executeVideoCallback(int index, int event); + +#endif \ No newline at end of file diff --git a/cocos/ui/UIVideoPlayer.h b/cocos/ui/UIVideoPlayer.h index 0aabb26bf4ce..1f38d4f673b5 100644 --- a/cocos/ui/UIVideoPlayer.h +++ b/cocos/ui/UIVideoPlayer.h @@ -25,7 +25,7 @@ #ifndef __COCOS2D_UI_VIDEOWEIGTH_H_ #define __COCOS2D_UI_VIDEOWEIGTH_H_ -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN) && !defined(CC_PLATFORM_OS_TVOS) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN) && !defined(CC_PLATFORM_OS_TVOS) #include "ui/UIWidget.h" @@ -57,7 +57,19 @@ namespace experimental{ PLAYING = 0, PAUSED, STOPPED, - COMPLETED + COMPLETED, + ERROR + }; + + /** + * Styles of how the the video player is presented + * For now only used on iOS to use either MPMovieControlStyleEmbedded (DEFAULT) or + * MPMovieControlStyleNone (NONE) + */ + enum class StyleType + { + DEFAULT = 0, + NONE }; /** @@ -94,6 +106,27 @@ namespace experimental{ * @return A remoting URL address. */ virtual const std::string& getURL() const { return _videoURL;} + + /** + * @brief Set if playback is done in loop mode + * + * @param looping the video will or not automatically restart at the end + */ + virtual void setLooping(bool looping); + + /** + * Set if the player will enable user input for basic pause and resume of video + * + * @param enableInput If true, input will be handled for basic functionality (pause/resume) + */ + virtual void setUserInputEnabled(bool enableInput); + + /** + * Set the style of the player + * + * @param style The corresponding style + */ + virtual void setStyle(StyleType style); /** * Starts playback. @@ -128,6 +161,22 @@ namespace experimental{ * @return True if currently playing, false otherwise. */ virtual bool isPlaying() const; + + /** + * Checks whether the VideoPlayer is set with looping mode. + * + * @return true if the videoplayer is set to loop, false otherwise. + */ + virtual bool isLooping() const; + + + /** + * Checks whether the VideoPlayer is set to listen user input to resume and pause the video + * + * @return true if the videoplayer user input is set, false otherwise. + */ + virtual bool isUserInputEnabled() const; + /** * Causes the video player to keep aspect ratio or no when displaying the video. @@ -195,10 +244,14 @@ namespace experimental{ }; bool _isPlaying; + bool _isLooping; + bool _isUserInputEnabled; bool _fullScreenDirty; bool _fullScreenEnabled; bool _keepAspectRatioEnabled; + StyleType _styleType; + std::string _videoURL; Source _videoSource; diff --git a/cocos/ui/UIWebView-inl.h b/cocos/ui/UIWebView-inl.h index fc7e33e860e3..30f5ebfb1b47 100644 --- a/cocos/ui/UIWebView-inl.h +++ b/cocos/ui/UIWebView-inl.h @@ -80,7 +80,12 @@ namespace experimental{ void WebView::loadURL(const std::string &url) { - _impl->loadURL(url); + this->loadURL(url, false); + } + + void WebView::loadURL(const std::string& url, bool cleanCachedData) + { + _impl->loadURL(url, cleanCachedData); } void WebView::loadFile(const std::string &fileName) @@ -142,6 +147,19 @@ namespace experimental{ _impl->setVisible(visible); } } + + void WebView::setOpacityWebView(float opacity){ + _impl->setOpacityWebView(opacity); + } + + float WebView::getOpacityWebView() const{ + return _impl->getOpacityWebView(); + } + + void WebView::setBackgroundTransparent() + { + _impl->setBackgroundTransparent(); + }; void WebView::onEnter() { diff --git a/cocos/ui/UIWebView.cpp b/cocos/ui/UIWebView.cpp index af795785e058..49405129eb47 100644 --- a/cocos/ui/UIWebView.cpp +++ b/cocos/ui/UIWebView.cpp @@ -29,3 +29,7 @@ #include "ui/UIWebView-inl.h" #endif +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) +#include "ui/UIWebViewImpl-ohos.h" +#include "ui/UIWebView-inl.h" +#endif diff --git a/cocos/ui/UIWebView.h b/cocos/ui/UIWebView.h index 42c422823f4b..be2e6cc07421 100644 --- a/cocos/ui/UIWebView.h +++ b/cocos/ui/UIWebView.h @@ -27,9 +27,7 @@ #include "platform/CCPlatformConfig.h" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN) && !defined(CC_PLATFORM_OS_TVOS) - - +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN) && !defined(CC_PLATFORM_OS_TVOS) #include "ui/UIWidget.h" #include "ui/GUIExport.h" @@ -95,6 +93,13 @@ class CC_GUI_DLL WebView : public cocos2d::ui::Widget { */ void loadURL(const std::string &url); + /** + * Loads the given URL with cleaning cached data or not. + * @param url Content URL. + * @cleanCachedData Whether to clean cached data. + */ + void loadURL(const std::string &url, bool cleanCachedData); + /** * Loads the given fileName. * @@ -209,6 +214,20 @@ class CC_GUI_DLL WebView : public cocos2d::ui::Widget { * Toggle visibility of WebView. */ virtual void setVisible(bool visible) override; + /** + * SetOpacity of webview. + */ + virtual void setOpacityWebView(float opacity); + + /** + * getOpacity of webview. + */ + virtual float getOpacityWebView() const; + + /** + * set the background transparent + */ + virtual void setBackgroundTransparent(); virtual void onEnter() override; virtual void onExit() override; diff --git a/cocos/ui/UIWebViewImpl-ohos.cpp b/cocos/ui/UIWebViewImpl-ohos.cpp new file mode 100644 index 000000000000..9000bc96088e --- /dev/null +++ b/cocos/ui/UIWebViewImpl-ohos.cpp @@ -0,0 +1,209 @@ +/**************************************************************************** + Copyright (c) 2022-2023 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. +****************************************************************************/ +#include "ui/UIWebViewImpl-ohos.h" + +#include +#include +#include + +#include "platform/CCFileUtils.h" +#include "platform/ohos/CCLogOhos.h" +#include "platform/ohos/napi/helper/NapiHelper.h" +#include "ui/UIHelper.h" +#include "ui/UIWebView.h" + +static const std::string SANDBOX_PREFIX = "file://"; +static const char S_MIME_TYPE_TEXT[] = "text/html"; +static const char S_ENCODING_UTF8[] = "UTF-8"; + +NS_CC_BEGIN +namespace experimental { + namespace ui { + static int32_t kWebViewTag = 0; + static std::unordered_map sWebViewImpls; + + WebViewImpl::WebViewImpl(WebView *webView) : _viewTag(-1), _webView(webView) { + _viewTag = kWebViewTag++; + JSFunction::getFunction("WebView.createWebView").invoke(_viewTag); + sWebViewImpls[_viewTag] = this; + } + + WebViewImpl::~WebViewImpl() { + if (_viewTag != -1) { + JSFunction::getFunction("WebView.removeWebView").invoke(_viewTag); + auto iter = sWebViewImpls.find(_viewTag); + if (iter != sWebViewImpls.end()) { + sWebViewImpls.erase(iter); + } + _viewTag = -1; + } + } + + void WebViewImpl::setJavascriptInterfaceScheme(const std::string &scheme) { + JSFunction::getFunction("WebView.setJavascriptInterfaceScheme").invoke(_viewTag, scheme); + } + + void WebViewImpl::loadData(const Data &data, const std::string &mimeType, + const std::string &encoding, const std::string &baseURL) { + std::string dataString(reinterpret_cast(data.getBytes()), + static_cast(data.getSize())); + JSFunction::getFunction("WebView.loadData").invoke(_viewTag, dataString, mimeType, encoding, baseURL); + } + + void WebViewImpl::loadHTMLString(const std::string &string, const std::string &baseURL) { + JSFunction::getFunction("WebView.loadData").invoke(_viewTag, string, S_MIME_TYPE_TEXT, S_ENCODING_UTF8, baseURL); + } + + void WebViewImpl::loadURL(const std::string &url) { + JSFunction::getFunction("WebView.loadURL").invoke(_viewTag, url); + } + + void WebViewImpl::loadURL(const std::string &url, bool cleanCachedData) { + // 官网接口未提供缓存相关参数,故与上一个loadUrl实现一致 + JSFunction::getFunction("WebView.loadURL").invoke(_viewTag, url); + } + + void WebViewImpl::loadFile(const std::string &fileName) { + std::string fullPath = FileUtils::getInstance()->fullPathForFilename(fileName); + if(fullPath[0] == '/') { + JSFunction::getFunction("WebView.loadURL").invoke(_viewTag, SANDBOX_PREFIX + fullPath); + } else { + JSFunction::getFunction("WebView.loadFile").invoke(_viewTag, fullPath); + } + } + + void WebViewImpl::stopLoading() { + JSFunction::getFunction("WebView.stopLoading").invoke(_viewTag); + } + + void WebViewImpl::reload() { + JSFunction::getFunction("WebView.reload").invoke(_viewTag); + } + + bool WebViewImpl::canGoBack() { + // return JSFunction::getFunction("WebView.canGoBack").invoke(_viewTag); + return true; + } + + bool WebViewImpl::canGoForward() { + // return JSFunction::getFunction("WebView.canGoForward").invoke(_viewTag); + return true; + } + + void WebViewImpl::goBack() { + JSFunction::getFunction("WebView.goBack").invoke(_viewTag); + } + + void WebViewImpl::goForward() { + JSFunction::getFunction("WebView.goForward").invoke(_viewTag); + } + + void WebViewImpl::evaluateJS(const std::string &js) { + JSFunction::getFunction("WebView.evaluateJS").invoke(_viewTag, js); + } + + void WebViewImpl::setScalesPageToFit(bool scalesPageToFit) { + JSFunction::getFunction("WebView.setScalesPageToFit").invoke(_viewTag, scalesPageToFit); + } + + void WebViewImpl::draw(cocos2d::Renderer *renderer, cocos2d::Mat4 const &transform, uint32_t flags) { + if (flags & cocos2d::Node::FLAGS_TRANSFORM_DIRTY) { + auto uiRect = cocos2d::ui::Helper::convertBoundingBoxToScreen(_webView); + JSFunction::getFunction("WebView.setWebViewRect") + .invoke(_viewTag, (int) uiRect.origin.x, (int) uiRect.origin.y, + (int) uiRect.size.width, (int) uiRect.size.height); + } + } + + void WebViewImpl::setVisible(bool visible) { + JSFunction::getFunction("WebView.setVisible").invoke(_viewTag, visible); + } + + void WebViewImpl::setOpacityWebView(const float opacity) { + _opacity = opacity; + JSFunction::getFunction("WebView.setOpacityWebView").invoke(_viewTag, (double)_opacity); + } + + float WebViewImpl::getOpacityWebView() const { + return _opacity; + } + + void WebViewImpl::setBackgroundTransparent() { + JSFunction::getFunction("WebView.setBackgroundTransparent").invoke(_viewTag); + } + + void WebViewImpl::setBounces(bool bounces) { + // empty function as this was mainly a fix for iOS + } + + bool WebViewImpl::shouldStartLoading(int viewTag, const std::string &url) { + bool allowLoad = true; + auto it = sWebViewImpls.find(viewTag); + if (it != sWebViewImpls.end()) { + auto webView = it->second->_webView; + if (webView->getOnShouldStartLoading()) { + std::function < bool(WebView * sender, + const std::string &url)> fun = webView->getOnShouldStartLoading(); + allowLoad = fun(webView, url); + } + } + return allowLoad; + } + + void WebViewImpl::finishLoading(int viewTag, const std::string &url) { + auto it = sWebViewImpls.find(viewTag); + if (it != sWebViewImpls.end()) { + auto webView = it->second->_webView; + if (webView->getOnDidFinishLoading()) { + WebView::ccWebViewCallback fun = webView->getOnDidFinishLoading(); + fun(webView, url); + } + } + } + + void WebViewImpl::failLoading(int viewTag, const std::string &url) { + auto it = sWebViewImpls.find(viewTag); + if (it != sWebViewImpls.end()) { + auto webView = it->second->_webView; + if (webView->getOnDidFailLoading()) { + WebView::ccWebViewCallback fun = webView->getOnDidFailLoading(); + fun(webView, url); + } + } + } + + void WebViewImpl::jsCallback(int viewTag, const std::string &message) { + auto it = sWebViewImpls.find(viewTag); + if (it != sWebViewImpls.end()) { + auto webView = it->second->_webView; + if (webView->getOnJSCallback()) { + WebView::ccWebViewCallback fun = webView->getOnJSCallback(); + fun(webView, message); + } + } + } + } // namespace ui +} // namespace experimental +NS_CC_END \ No newline at end of file diff --git a/cocos/ui/UIWebViewImpl-ohos.h b/cocos/ui/UIWebViewImpl-ohos.h new file mode 100644 index 000000000000..8d2a84af6cd5 --- /dev/null +++ b/cocos/ui/UIWebViewImpl-ohos.h @@ -0,0 +1,111 @@ +/**************************************************************************** + 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. + ****************************************************************************/ +#ifndef __COCOS2D__UI__WEBVIEWIMPL_OPENHARMONY_H_ +#define __COCOS2D__UI__WEBVIEWIMPL_OPENHARMONY_H_ +#include +#include + +namespace cocos2d { + class Data; + class Renderer; + class Mat4; + + namespace experimental { + namespace ui{ + class WebView; + } + } +} + +namespace cocos2d { + namespace experimental { + namespace ui{ + + class WebViewImpl { + public: + WebViewImpl(cocos2d::experimental::ui::WebView *webView); + + virtual ~WebViewImpl(); + + void setJavascriptInterfaceScheme(const std::string &scheme); + + void loadData(const cocos2d::Data &data, const std::string &MIMEType, const std::string &encoding, const std::string &baseURL); + + void loadHTMLString(const std::string &string, const std::string &baseURL); + + void loadURL(const std::string &url); + + void loadURL(const std::string &url, bool cleanCachedData); + + void loadFile(const std::string &fileName); + + void stopLoading(); + + void reload(); + + bool canGoBack(); + + bool canGoForward(); + + void goBack(); + + void goForward(); + + void evaluateJS(const std::string &js); + + void setScalesPageToFit(const bool scalesPageToFit); + + virtual void draw(cocos2d::Renderer *renderer, cocos2d::Mat4 const &transform, uint32_t flags); + + virtual void setVisible(bool visible); + + void setOpacityWebView(float opacity); + + float getOpacityWebView()const; + + void setBackgroundTransparent(); + + void setBounces(bool bounces); + + static bool shouldStartLoading(int viewTag, const std::string& url); + + static void finishLoading(int viewTag, const std::string& url); + + static void failLoading(int viewTag, const std::string& url); + + static void jsCallback(int viewTag, const std::string& message); + public: + int _viewTag; + WebView *_webView; + float _opacity = 1.0f; + }; + + } // namespace ui + } // namespace experimental +} //cocos2d + + +/// @endcond +#endif /* __COCOS2D__UI__WEBVIEWIMPL_OPENHARMONY_H_ */ diff --git a/extensions/Particle3D/PU/CCPUMaterialManager.cpp b/extensions/Particle3D/PU/CCPUMaterialManager.cpp index 2fb33bbea773..4317877bae80 100644 --- a/extensions/Particle3D/PU/CCPUMaterialManager.cpp +++ b/extensions/Particle3D/PU/CCPUMaterialManager.cpp @@ -40,6 +40,11 @@ #include #include #include +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) +#include "platform/ohos/CCFileUtils-ohos.h" +#include +#include +#include #endif NS_CC_BEGIN @@ -161,7 +166,7 @@ bool PUMaterialCache::loadMaterialsFromSearchPaths( const std::string &fileFolde #elif (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC) ftw(fileFolder.c_str(), iterPath, 500); -#elif (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN) +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX || CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN || CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) DIR *d; //dir handle struct dirent *file; //readdir struct stat statbuf; diff --git a/tests/cpp-tests/CMakeLists.txt b/tests/cpp-tests/CMakeLists.txt index 5994767e04ee..9e363890cac3 100644 --- a/tests/cpp-tests/CMakeLists.txt +++ b/tests/cpp-tests/CMakeLists.txt @@ -21,6 +21,11 @@ elseif(ANDROID) Classes/JNITest/JNITest.cpp proj.android/jni/main.cpp) set(RES_PREFIX "/Resources") +elseif(OHOS) + set(PLATFORM_SRC + proj.ohos/entry/src/main/cpp/main.cpp + proj.ohos/entry/src/main/cpp/napi_init.cpp) + set(RES_PREFIX "/Resources") else() message( FATAL_ERROR "Unsupported platform, CMake will exit" ) endif() @@ -210,6 +215,38 @@ if(ANDROID) IF(CMAKE_BUILD_TYPE MATCHES RELEASE) ADD_CUSTOM_COMMAND(TARGET ${APP_NAME} POST_BUILD COMMAND ${CMAKE_STRIP} lib${APP_NAME}.so) ENDIF() +elseif (OHOS) + add_library(${APP_NAME} STATIC ${TESTS_SRC} ${EXTENDED_TESTS_SRC}) + find_library( # Sets the name of the path variable. + GLES-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + GLESv3 ) + find_library( # Sets the name of the path variable. + hilog-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + hilog_ndk.z ) + + find_library( # Sets the name of the path variable. + libnapi-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + ace_napi.z ) + message(" *************** target link cpptest and gles : ${GLES-lib} ******************") + target_link_libraries(${APP_NAME} cocos2d ${GLES-lib} ${hilog-lib} ${libnapi-lib}) + target_include_directories(${APP_NAME} + PRIVATE Classes + ) + + target_include_directories(${APP_NAME} PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/platform/ohos/napi/common + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/platform/ohos/napi/modules + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/platform/ohos/napi + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/platform/ohos + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/editor-support + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/base + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos + ) else() # add the executable add_executable(${APP_NAME} ${TESTS_SRC} ${EXTENDED_TESTS_SRC}) diff --git a/tests/cpp-tests/Classes/AppDelegate.cpp b/tests/cpp-tests/Classes/AppDelegate.cpp index 2b68dc9e55c7..adb96499bbf1 100644 --- a/tests/cpp-tests/Classes/AppDelegate.cpp +++ b/tests/cpp-tests/Classes/AppDelegate.cpp @@ -29,6 +29,7 @@ #include "controller.h" #include "editor-support/cocostudio/CocoStudio.h" #include "extensions/cocos-ext.h" +#include "audio/include/AudioEngine.h" USING_NS_CC; @@ -72,14 +73,14 @@ bool AppDelegate::applicationDidFinishLaunching() director->setAnimationInterval(1.0f / 60); auto screenSize = glview->getFrameSize(); - auto designSize = Size(480, 320); + auto designSize = Size(1024/2, 2112/2); auto fileUtils = FileUtils::getInstance(); std::vector searchPaths; if (screenSize.height > 320) { - auto resourceSize = Size(960, 640); + auto resourceSize = Size(1024, 2112); searchPaths.push_back("hd"); searchPaths.push_back("ccs-res/hd"); searchPaths.push_back("ccs-res"); @@ -96,8 +97,12 @@ bool AppDelegate::applicationDidFinishLaunching() } fileUtils->setSearchPaths(searchPaths); - +#if (CC_TARGET_PLATFORM != CC_PLATFORM_OHOS) + // a bug in DirectX 11 level9-x on the device prevents ResolutionPolicy::NO_BORDER from working correctly glview->setDesignResolutionSize(designSize.width, designSize.height, ResolutionPolicy::SHOW_ALL); +#else + glview->setDesignResolutionSize(designSize.width, designSize.height, ResolutionPolicy::NO_BORDER); +#endif // Enable Remote Console auto console = director->getConsole(); @@ -121,6 +126,7 @@ void AppDelegate::applicationDidEnterBackground() } Director::getInstance()->stopAnimation(); + _testController->onEnterBackground(); } // this function will be called when the app is active again @@ -132,4 +138,21 @@ void AppDelegate::applicationWillEnterForeground() } Director::getInstance()->startAnimation(); + // resume audioEngine, otherwise the opensl audioPlayer will always be suspended. + _testController->onEnterForeground(); } + +void AppDelegate::applicationScreenSizeChanged(int newWidth, int newHeight) +{ + auto director = cocos2d::Director::getInstance(); + auto glview = director->getOpenGLView(); + if (glview != NULL) { + // Set ResolutionPolicy to a proper value. here use the original value when the game is started. + ResolutionPolicy resolutionPolicy = glview->getResolutionPolicy(); + Size designSize = glview->getDesignResolutionSize(); + glview->setFrameSize(newWidth, newHeight); + // Set the design resolution to a proper value. here use the original value when the game is started. + glview->setDesignResolutionSize(designSize.width, designSize.height, resolutionPolicy); + } +} + diff --git a/tests/cpp-tests/Classes/AppDelegate.h b/tests/cpp-tests/Classes/AppDelegate.h index e218281707a4..15a70501b61a 100644 --- a/tests/cpp-tests/Classes/AppDelegate.h +++ b/tests/cpp-tests/Classes/AppDelegate.h @@ -63,6 +63,13 @@ class AppDelegate : private cocos2d::Application private: TestController* _testController; + + /** + @brief This function will be called when the application screen size is changed. + @param new width + @param new height + */ + virtual void applicationScreenSizeChanged(int newWidth, int newHeight); }; #endif // _APP_DELEGATE_H_ diff --git a/tests/cpp-tests/Classes/CocosDenshionTest/CocosDenshionTest.cpp b/tests/cpp-tests/Classes/CocosDenshionTest/CocosDenshionTest.cpp index 7a6be1b28572..d9084f005d3b 100644 --- a/tests/cpp-tests/Classes/CocosDenshionTest/CocosDenshionTest.cpp +++ b/tests/cpp-tests/Classes/CocosDenshionTest/CocosDenshionTest.cpp @@ -4,7 +4,7 @@ #include "audio/include/SimpleAudioEngine.h" // android effect only support ogg -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) || (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) #define EFFECT_FILE "effect2.ogg" #elif( CC_TARGET_PLATFORM == CC_PLATFORM_MARMALADE) #define EFFECT_FILE "effect1.raw" diff --git a/tests/cpp-tests/Classes/CurlTest/CurlTest.cpp b/tests/cpp-tests/Classes/CurlTest/CurlTest.cpp index 19f6f0171c02..175db7e80d4b 100644 --- a/tests/cpp-tests/Classes/CurlTest/CurlTest.cpp +++ b/tests/cpp-tests/Classes/CurlTest/CurlTest.cpp @@ -72,7 +72,7 @@ void CurlTest::onTouchesEnded(const std::vector& touches, Event *event) curl = curl_easy_init(); if (curl) { - curl_easy_setopt(curl, CURLOPT_URL, "http://webtest.cocos2d-x.org/curltest"); + curl_easy_setopt(curl, CURLOPT_URL, "http://httpbin.org/get"); //code from http://curl.haxx.se/libcurl/c/getinmemory.html /* we pass our 'chunk' struct to the callback function */ curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); diff --git a/tests/cpp-tests/Classes/DownloaderTest/DownloaderTest.cpp b/tests/cpp-tests/Classes/DownloaderTest/DownloaderTest.cpp index e804077771f1..45de7460cbac 100644 --- a/tests/cpp-tests/Classes/DownloaderTest/DownloaderTest.cpp +++ b/tests/cpp-tests/Classes/DownloaderTest/DownloaderTest.cpp @@ -96,7 +96,7 @@ struct DownloaderTest : public TestCase bg->addChild(btn, 10); // add a progress bar - auto bar = ui::LoadingBar::create("cocosui/UIEditorTest/UISlider/silder_progressBar.png"); + auto bar = ui::LoadingBar::create("cocosui/sliderProgress.png"); bar->setTag(TAG_PROGRESS_BAR); bar->ignoreContentAdaptWithSize(false); bar->setAnchorPoint(Vec2(0.5, 0)); diff --git a/tests/cpp-tests/Classes/ExtensionsTest/ExtensionsTest.cpp b/tests/cpp-tests/Classes/ExtensionsTest/ExtensionsTest.cpp index e87f9a81b4b9..9c9279941138 100644 --- a/tests/cpp-tests/Classes/ExtensionsTest/ExtensionsTest.cpp +++ b/tests/cpp-tests/Classes/ExtensionsTest/ExtensionsTest.cpp @@ -9,7 +9,7 @@ #endif #include "TableViewTest/TableViewTestScene.h" -#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) || (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) || (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) || (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) #include "NetworkTest/WebSocketTest.h" #include "NetworkTest/SocketIOTest.h" #endif @@ -23,7 +23,7 @@ ExtensionsTests::ExtensionsTests() #if (CC_TARGET_PLATFORM != CC_PLATFORM_EMSCRIPTEN) && (CC_TARGET_PLATFORM != CC_PLATFORM_NACL) addTest("HttpClientTest", [](){ return new (std::nothrow) HttpClientTests; }); #endif -#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) || (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) || (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) || (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) addTest("WebSocketTest", [](){ return new (std::nothrow) WebSocketTests; }); addTest("SocketIOTest", [](){ return new (std::nothrow) SocketIOTests; }); #endif diff --git a/tests/cpp-tests/Classes/controller.cpp b/tests/cpp-tests/Classes/controller.cpp index 266c718ec5aa..fa810bfd3f8c 100644 --- a/tests/cpp-tests/Classes/controller.cpp +++ b/tests/cpp-tests/Classes/controller.cpp @@ -415,7 +415,8 @@ void TestController::logEx(const char * format, ...) #if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID __android_log_print(ANDROID_LOG_DEBUG, "cocos2d-x debug info", "%s", buff); - +#elif CC_TARGET_PLATFORM == CC_PLATFORM_OHOS + OHOS_LOGI("cocos2d-x info %{public}s", buff); #elif CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT WCHAR wszBuf[1024] = { 0 }; MultiByteToWideChar(CP_UTF8, 0, buff, -1, wszBuf, sizeof(wszBuf)); diff --git a/tests/cpp-tests/proj.ohos/.gitignore b/tests/cpp-tests/proj.ohos/.gitignore new file mode 100644 index 000000000000..43fc9cb4cc28 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/.gitignore @@ -0,0 +1,14 @@ +.hvigor +.idea +node_modules +entry/build +entry/.idea +entry/.cxx +local.properties +oh-package-lock.json5 +entry/src/main/resources/rawfile +oh_modules +/.clangd +/.clang-tidy +.clang-format +hvigor/hvigor-wrapper.js \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/AppScope/app.json5 b/tests/cpp-tests/proj.ohos/AppScope/app.json5 new file mode 100644 index 000000000000..654798e64bd3 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/AppScope/app.json5 @@ -0,0 +1,11 @@ +{ + "app": { + "bundleName": "ohos.cocos3_12.cpp.tests", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name", + "distributedNotificationEnabled": true + } +} diff --git a/tests/cpp-tests/proj.ohos/AppScope/resources/base/element/string.json b/tests/cpp-tests/proj.ohos/AppScope/resources/base/element/string.json new file mode 100644 index 000000000000..38b78d04289d --- /dev/null +++ b/tests/cpp-tests/proj.ohos/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "3.12_cpp_tests" + } + ] +} diff --git a/tests/cpp-tests/proj.ohos/AppScope/resources/base/media/app_icon.png b/tests/cpp-tests/proj.ohos/AppScope/resources/base/media/app_icon.png new file mode 100644 index 000000000000..ce307a8827bd Binary files /dev/null and b/tests/cpp-tests/proj.ohos/AppScope/resources/base/media/app_icon.png differ diff --git a/tests/cpp-tests/proj.ohos/build-profile.json5 b/tests/cpp-tests/proj.ohos/build-profile.json5 new file mode 100644 index 000000000000..b45eb20941ec --- /dev/null +++ b/tests/cpp-tests/proj.ohos/build-profile.json5 @@ -0,0 +1,45 @@ +{ + "app": { + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.0.0(12)", + "runtimeOS": "HarmonyOS" + } + ], + "signingConfigs": [ + { + "name": "default", + "type": "HarmonyOS", + "material": { + "certpath": "C:\\Users\\cocos\\.ohos\\config\\default_proj.ohos_UKj2_3L0mUzFKA3_Cppy53zUANceT_L0yPOfMZGLt_o=.cer", + "storePassword": "0000001B129903AAC55A4AD34A9CD8EE67B0A6FDADA16F17A4679D24B6A0F33271699920327B8236C29EAC", + "keyAlias": "debugKey", + "keyPassword": "0000001B1A10FC600917D2FB16557A48875C0B9CAF1E755F037EB0DE685CE9CD153FED8D870D1951B118E5", + "profile": "C:\\Users\\cocos\\.ohos\\config\\default_proj.ohos_UKj2_3L0mUzFKA3_Cppy53zUANceT_L0yPOfMZGLt_o=.p7b", + "signAlg": "SHA256withECDSA", + "storeFile": "C:\\Users\\cocos\\.ohos\\config\\default_proj.ohos_UKj2_3L0mUzFKA3_Cppy53zUANceT_L0yPOfMZGLt_o=.p12" + } + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + }, + { + "name": "libSysCapabilities", + "srcPath": "./libSysCapabilities" + } + ] +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/entry/.gitignore b/tests/cpp-tests/proj.ohos/entry/.gitignore new file mode 100644 index 000000000000..268e7ec2a7df --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/.preview +/build +/.cxx +entry/src/main/resources/rawfile +/oh_modules diff --git a/tests/cpp-tests/proj.ohos/entry/build-profile.json5 b/tests/cpp-tests/proj.ohos/entry/build-profile.json5 new file mode 100644 index 000000000000..edb7d04f042b --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/build-profile.json5 @@ -0,0 +1,42 @@ +{ + "apiType": 'stageMode', + "buildOption": { + "externalNativeOptions": { + "path": "./src/main/cpp/CMakeLists.txt", + "arguments": "", + "abiFilters": [ + //"armeabi-v7a", + "arm64-v8a" + ], + "cppFlags": "", + }, + "sourceOption": { + "workers": [ + './src/main/ets/workers/CocosWorker.ts' + ] + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest" + } + ] +} diff --git a/tests/cpp-tests/proj.ohos/entry/hvigorfile.ts b/tests/cpp-tests/proj.ohos/entry/hvigorfile.ts new file mode 100644 index 000000000000..533eece8e0be --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/hvigorfile.ts @@ -0,0 +1,6 @@ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/tests/cpp-tests/proj.ohos/entry/obfuscation-rules.txt b/tests/cpp-tests/proj.ohos/entry/obfuscation-rules.txt new file mode 100644 index 000000000000..985b2aeb7658 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/obfuscation-rules.txt @@ -0,0 +1,18 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/entry/oh-package.json5 b/tests/cpp-tests/proj.ohos/entry/oh-package.json5 new file mode 100644 index 000000000000..7732d142af3b --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/oh-package.json5 @@ -0,0 +1,13 @@ +{ + "license": "ISC", + "devDependencies": { + "@types/libnativerender.so": "file:./src/main/cpp/types/libentry" + }, + "name": "entry", + "description": "example description", + "main": "", + "version": "1.0.0", + "dependencies": { + "@ohos/libSysCapabilities": "../libSysCapabilities" + } +} diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/cpp/CMakeLists.txt b/tests/cpp-tests/proj.ohos/entry/src/main/cpp/CMakeLists.txt new file mode 100644 index 000000000000..abeb892edb93 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/cpp/CMakeLists.txt @@ -0,0 +1,87 @@ +cmake_minimum_required(VERSION 3.4.1) +project(nativerender) + +set(COCOS2DX_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../..) + +set(CLASSES_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../Classes) + +set(platform_name "ohos") +set(OHOS true) +add_definitions(-DOHOS) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fms-extensions") + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2") + +find_library( # Sets the name of the path variable. + EGL-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + EGL ) + +find_library( # Sets the name of the path variable. + GLES-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + GLESv3 ) + +find_library( # Sets the name of the path variable. + hilog-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + hilog_ndk.z ) + +find_library( # Sets the name of the path variable. + libace-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + ace_ndk.z ) + +find_library( # Sets the name of the path variable. + libnapi-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + ace_napi.z ) + +find_library( # Sets the name of the path variable. + libuv-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + uv ) +find_library( # Sets the name of the path variable. + rawfile-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + rawfile.z ) + +if(NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/../resources/rawfile) + file(GLOB ALL_RESOURCES_FILES "${CMAKE_CURRENT_LIST_DIR}/../../../../../Resources/*") + file(COPY ${ALL_RESOURCES_FILES} + DESTINATION ${CMAKE_CURRENT_LIST_DIR}/../resources/rawfile) +endif() + +add_library(${PROJECT_NAME} SHARED main.cpp + napi_init.cpp + ) + +target_include_directories(${PROJECT_NAME} PUBLIC ${CLASSES_PATH} + PUBLIC ${CLASSES_PATH}/../.. + ) +target_include_directories(${PROJECT_NAME} PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/platform/ohos/napi/common + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/platform/ohos/napi/modules + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/platform/ohos/napi + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/platform/ohos + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/editor-support + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/base + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos + ) + +#TBD need fixed +target_include_directories(${PROJECT_NAME} PUBLIC ${COCOS2DX_ROOT_PATH}/external/chipmunk/include + ) + +add_subdirectory(${COCOS2DX_ROOT_PATH} Cocos-2dX) +target_link_libraries(${PROJECT_NAME} PUBLIC cocos2d cpp-tests ${libnapi-lib} ${GLES-lib}) \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/cpp/main.cpp b/tests/cpp-tests/proj.ohos/entry/src/main/cpp/main.cpp new file mode 100644 index 000000000000..c08c2c7362f8 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/cpp/main.cpp @@ -0,0 +1,31 @@ +#include "AppDelegate.h" +#include "cocos2d.h" +#include "CCEventType.h" +#include "CCLogOhos.h" + +using namespace cocos2d; + +extern "C" { + +void Cocos2dxRenderer_nativeInit(int w, int h) { + OHOS_LOGD("Cocos2dxRenderer_nativeInit() - window width:[%{public}d], height:[%{public}d]", w, h); + if (!CCDirector::sharedDirector()->getOpenGLView()) { + OHOS_LOGD("Cocos2dxRenderer_nativeInit() - branch 1"); + GLView *view = GLViewImpl::sharedOpenGLView(); + view->setFrameSize(w, h); + CCDirector::sharedDirector()->setOpenGLView(view); + + AppDelegate *pAppDelegate = new AppDelegate(); + CCApplication::sharedApplication()->run(); + } else { + OHOS_LOGD("Cocos2dxRenderer_nativeInit() - branch 2"); + ccGLInvalidateStateCache(); + CCShaderCache::sharedShaderCache()->reloadDefaultShaders(); + ccDrawInit(); + CCTextureCache::reloadAllTextures(); + CCNotificationCenter::sharedNotificationCenter()->postNotification(EVENT_COME_TO_FOREGROUND, NULL); + CCDirector::sharedDirector()->setGLDefaultValues(); + } +} + +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/cpp/napi_init.cpp b/tests/cpp-tests/proj.ohos/entry/src/main/cpp/napi_init.cpp new file mode 100644 index 000000000000..7ad39018ae95 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/cpp/napi_init.cpp @@ -0,0 +1,40 @@ +#include "CCLogOhos.h" +#include "napi/plugin_manager.h" +#include "plugin_manager.h" + +/* + * function for module exports + */ +static napi_value Init(napi_env env, napi_value exports) +{ + OHOS_LOGI("Init"); + napi_property_descriptor desc[] ={ + DECLARE_NAPI_FUNCTION("getContext", NapiManager::GetContext), + }; + NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); + + bool ret = NapiManager::GetInstance()->Export(env, exports); + if (!ret) { + OHOS_LOGE("Init failed"); + } + return exports; +} + +/* + * Napi Module define + */ +static napi_module nativerenderModule = { + .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = Init, + .nm_modname = "nativerender", + .nm_priv = ((void*)0), + .reserved = { 0 }, +}; +/* + * Module register function + */ +extern "C" __attribute__((constructor)) void RegisterModule(void) { + napi_module_register(&nativerenderModule); +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/cpp/types/libentry/index.d.ts b/tests/cpp-tests/proj.ohos/entry/src/main/cpp/types/libentry/index.d.ts new file mode 100644 index 000000000000..6a9293652bfe --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/cpp/types/libentry/index.d.ts @@ -0,0 +1,30 @@ +import resmgr from '@ohos.resourceManager'; + +export interface CPPFunctions { + onCreate: () => void; + onShow: () => void; + onHide: () => void; + onBackPress: () => void; + onDestroy: () => void; + onPageShow: () => void; + onPageHide: () => void; + nativeResourceManagerInit: (resourceManager: resmgr.ResourceManager) => void; + writablePathInit: (writePath: string) => void; + workerInit: () => void; + nativeEngineStart: () => void; + registerFunction: () => void; + initAsyncInfo: () => void; + mouseWheelCB: (eventType: string, scrollY : number) => void; + editBoxOnFocusCB: (viewTag: number) => void; + editBoxOnChangeCB: (viewTag: number, text: string) => void; + editBoxOnEnterCB: (viewTag: number, text: string) => void; + textFieldTTFOnChangeCB: (text: string) => void; + shouldStartLoading: (viewTag: number, url: string) => void; + finishLoading: (viewTag: number, url: string) => void; + failLoading: (viewTag: number, url: string) => void; + jsCallback: () => void; + onVideoCallBack: (viewTag: number, event: number) => void; + onAccelerometerCallBack: (x: number, y: number, z: number, interval: number) => void; +} + +export const getContext: (a: number) => CPPFunctions; diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/cpp/types/libentry/oh-package.json5 b/tests/cpp-tests/proj.ohos/entry/src/main/cpp/types/libentry/oh-package.json5 new file mode 100644 index 000000000000..fa7874d5940d --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/cpp/types/libentry/oh-package.json5 @@ -0,0 +1,6 @@ +{ + "name": "libnativerender.so", + "types": "./index.d.ts", + "version": "", + "description": "Please describe the basic information." +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/ets/MainAbility/MainAbility.ts b/tests/cpp-tests/proj.ohos/entry/src/main/ets/MainAbility/MainAbility.ts new file mode 100644 index 000000000000..a89ce067f484 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/ets/MainAbility/MainAbility.ts @@ -0,0 +1,80 @@ +import window from '@ohos.window'; +import UIAbility from '@ohos.app.ability.UIAbility'; +import web_webview from '@ohos.web.webview'; +import nativeRender from "libnativerender.so"; +import { ContextType } from "@ohos/libSysCapabilities" +import { GlobalContext,GlobalContextConstants} from "@ohos/libSysCapabilities" + +const nativeAppLifecycle: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.APP_LIFECYCLE); +const rawFileUtils: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.RAW_FILE_UTILS); + +export default class MainAbility extends UIAbility { + onCreate(want, launchParam) { + nativeAppLifecycle.onCreate(); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT, this.context); + // Initializes the webView kernel of the system. This parameter is optional if it is not used. + web_webview.WebviewController.initializeWebEngine(); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_WANT, this.context); + console.info('[LIFECYCLE-App] onCreate') + } + + onDestroy() { + nativeAppLifecycle.onDestroy(); + console.info('[LIFECYCLE-App] onDestroy') + } + + onWindowStageCreate(windowStage) { + // Main window is created, set main page for this ability + windowStage.loadContent('pages/Index', (err, data) => { + if (err.code) { + return; + } + rawFileUtils.nativeResourceManagerInit(this.context.resourceManager); + rawFileUtils.writablePathInit(this.context.filesDir); + }); + + windowStage.getMainWindow().then((windowIns: window.Window) => { + // Set whether to display the status bar and navigation bar. If they are not displayed, [] is displayed. + let systemBarPromise = windowIns.setWindowSystemBarEnable([]); + // Whether the window layout is displayed in full screen mode + let fullScreenPromise = windowIns.setWindowLayoutFullScreen(true); + // Sets whether the screen is always on. + let keepScreenOnPromise = windowIns.setWindowKeepScreenOn(true); + Promise.all([systemBarPromise, fullScreenPromise, keepScreenOnPromise]).then(() => { + console.info('Succeeded in setting the window'); + }).catch((err) => { + console.error('Failed to set the window, cause ' + JSON.stringify(err)); + }); + }) + + windowStage.on("windowStageEvent", (data) => { + let stageEventType: window.WindowStageEventType = data; + switch (stageEventType) { + case window.WindowStageEventType.RESUMED: + nativeAppLifecycle.onShow(); + break; + case window.WindowStageEventType.PAUSED: + nativeAppLifecycle.onHide(); + break; + default: + break; + } + }); + } + + onWindowStageDestroy() { + // Main window is destroyed, release UI related resources + } + + onForeground() { + // Ability has brought to foreground + console.info('[LIFECYCLE-App] onShow') + nativeAppLifecycle.onShow(); + } + + onBackground() { + // Ability has back to background + console.info('[LIFECYCLE-App] onDestroy') + nativeAppLifecycle.onHide(); + } +}; diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/ets/components/CocosEditBox.ets b/tests/cpp-tests/proj.ohos/entry/src/main/ets/components/CocosEditBox.ets new file mode 100644 index 000000000000..2587169f240f --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/ets/components/CocosEditBox.ets @@ -0,0 +1,82 @@ +import { TextInputInfo } from '@ohos/libSysCapabilities/src/main/ets/components/editbox/EditBoxMsg' +import worker from '@ohos.worker'; +import { WorkerManager } from '../workers/WorkerManager'; + +@Component +export struct CocosEditBox { + @ObjectLink textInputInfo: TextInputInfo; + cocosWorker: worker.ThreadWorker = WorkerManager.getInstance().getWorker(); + + build(){ + if(this.textInputInfo.multiline){ + TextArea({text: this.textInputInfo.text, placeholder: this.textInputInfo.placeHolder}) + .id('TextArea'+this.textInputInfo.viewTag) + .position({x: px2vp(this.textInputInfo.x), y: px2vp(this.textInputInfo.y)}) + .size({width: px2vp(this.textInputInfo.w), height: px2vp(this.textInputInfo.h)}) + .padding({top: px2vp(this.textInputInfo.paddingH), right: px2vp(this.textInputInfo.paddingW), + bottom: px2vp(this.textInputInfo.paddingH), left: px2vp(this.textInputInfo.paddingW)}) + .borderRadius(0) + .maxLength(this.textInputInfo.maxLength) + .type(TextAreaType.NORMAL) + .fontFamily($rawfile(this.textInputInfo.fontPath)) + .fontSize(px2fp(this.textInputInfo.fontSize)) + .fontColor(`rgba(${this.textInputInfo.fontColor.r}, ${this.textInputInfo.fontColor.g}, ${this.textInputInfo.fontColor.b}, ${this.textInputInfo.fontColor.a})`) + .placeholderFont({size: px2fp(this.textInputInfo.placeholderFontSize), family: $rawfile(this.textInputInfo.placeHolderFontPath)}) + .placeholderColor(`rgba(${this.textInputInfo.placeholderFontColor.r}, ${this.textInputInfo.placeholderFontColor.g}, ${this.textInputInfo.placeholderFontColor.b}, ${this.textInputInfo.placeholderFontColor.a})`) + .backgroundColor(Color.Transparent) + .visibility(this.textInputInfo.visible ? Visibility.Visible : Visibility.None) + .onVisibleAreaChange([0.0, 1.0], () => { + focusControl.requestFocus('TextArea' + this.textInputInfo.viewTag); + }) + .onFocus(() => { + this.cocosWorker.postMessage({type: "editBoxOnFocus", viewTag: this.textInputInfo.viewTag}); + }) + .onChange((value: string)=>{ + this.textInputInfo.text = value; + this.cocosWorker.postMessage({type: "editBoxOnChange", viewTag: this.textInputInfo.viewTag, value: value}); + }) + .onBlur(()=> { + this.textInputInfo.visible = false + this.cocosWorker.postMessage({type: "editBoxOnEnter", viewTag: this.textInputInfo.viewTag, text: this.textInputInfo.text}); + }) + .onSubmit(()=>{ + this.cocosWorker.postMessage({type: "editBoxOnEnter", viewTag: this.textInputInfo.viewTag, text: this.textInputInfo.text}); + }) + }else{ + TextInput({text: this.textInputInfo.text, placeholder: this.textInputInfo.placeHolder}) + .id('TextInput'+this.textInputInfo.viewTag) + .position({x: px2vp(this.textInputInfo.x), y: px2vp(this.textInputInfo.y)}) + .size({width: px2vp(this.textInputInfo.w), height: px2vp(this.textInputInfo.h)}) + .padding({top: px2vp(this.textInputInfo.paddingH), right: px2vp(this.textInputInfo.paddingW), + bottom: px2vp(this.textInputInfo.paddingH), left: px2vp(this.textInputInfo.paddingW)}) + .borderRadius(0) + .maxLength(this.textInputInfo.maxLength) + .type(this.textInputInfo.inputMode) + .fontFamily($rawfile(this.textInputInfo.fontPath)) + .fontSize(px2fp(this.textInputInfo.fontSize)) + .fontColor(`rgba(${this.textInputInfo.fontColor.r}, ${this.textInputInfo.fontColor.g}, ${this.textInputInfo.fontColor.b}, ${this.textInputInfo.fontColor.a})`) + .placeholderFont({size: px2fp(this.textInputInfo.placeholderFontSize), family: $rawfile(this.textInputInfo.placeHolderFontPath)}) + .placeholderColor(`rgba(${this.textInputInfo.placeholderFontColor.r}, ${this.textInputInfo.placeholderFontColor.g}, ${this.textInputInfo.placeholderFontColor.b}, ${this.textInputInfo.placeholderFontColor.a})`) + .backgroundColor(Color.Transparent) + .visibility(this.textInputInfo.visible ? Visibility.Visible : Visibility.None) + .showPasswordIcon(false) + .onVisibleAreaChange([0.0, 1.0], () => { + focusControl.requestFocus('TextInput' + this.textInputInfo.viewTag); + }) + .onFocus(() => { + this.cocosWorker.postMessage({type: "editBoxOnFocus", viewTag: this.textInputInfo.viewTag}); + }) + .onChange((value: string)=>{ + this.textInputInfo.text = value; + this.cocosWorker.postMessage({type: "editBoxOnChange", viewTag: this.textInputInfo.viewTag, value: value}); + }) + .onBlur(()=> { + this.textInputInfo.visible = false + this.cocosWorker.postMessage({type: "editBoxOnEnter", viewTag: this.textInputInfo.viewTag, text: this.textInputInfo.text}); + }) + .onSubmit(()=>{ + this.cocosWorker.postMessage({type: "editBoxOnEnter", viewTag: this.textInputInfo.viewTag, text: this.textInputInfo.text}); + }) + } + } +} diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/ets/components/CocosVideoPlayer.ets b/tests/cpp-tests/proj.ohos/entry/src/main/ets/components/CocosVideoPlayer.ets new file mode 100644 index 000000000000..6f67afe3b443 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/ets/components/CocosVideoPlayer.ets @@ -0,0 +1,39 @@ +import { WorkerManager } from '../workers/WorkerManager' +import { VideoPlayerInfo, VideoEvent } from '@ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayerMsg'; +import worker from '@ohos.worker'; + +@Component +export struct CocosVideoPlayer { + @ObjectLink videoPlayerInfo: VideoPlayerInfo; + cocosWorker: worker.ThreadWorker = WorkerManager.getInstance().getWorker(); + + build() { + Column() { + Video({ + src: this.videoPlayerInfo.isUrl ? this.videoPlayerInfo.url : this.videoPlayerInfo.rawfile, + controller: this.videoPlayerInfo.controller + }) + .width(this.videoPlayerInfo.w) + .height(this.videoPlayerInfo.h) + .visibility(this.videoPlayerInfo.visible ? Visibility.Visible : Visibility.None) + .controls(false) + .loop(this.videoPlayerInfo.isLoop) + .objectFit(this.videoPlayerInfo.objectFit) + .onPrepared(()=>{ + if(this.videoPlayerInfo.isPlay) { + this.videoPlayerInfo.controller.start() + } + this.videoPlayerInfo.controller.requestFullscreen(this.videoPlayerInfo.isFullScreen) + }) + .onStart(()=>{ + this.cocosWorker.postMessage({type: "onVideoCallBack", viewTag: this.videoPlayerInfo.viewTag, event: VideoEvent.PLAYING}); + }) + .onPause(()=>{ + this.cocosWorker.postMessage({type: "onVideoCallBack", viewTag: this.videoPlayerInfo.viewTag, event: VideoEvent.PAUSED}); + }) + .onFinish(()=>{ + this.cocosWorker.postMessage({type: "onVideoCallBack", viewTag: this.videoPlayerInfo.viewTag, event: VideoEvent.COMPLETED}); + }) + } + } +} diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/ets/components/CocosWebview.ets b/tests/cpp-tests/proj.ohos/entry/src/main/ets/components/CocosWebview.ets new file mode 100644 index 000000000000..f26ba14cc6e7 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/ets/components/CocosWebview.ets @@ -0,0 +1,43 @@ +import { WebViewInfo } from "@ohos/libSysCapabilities/src/main/ets/components/webview/WebViewMsg" +import worker from '@ohos.worker'; +import { WorkerManager } from '../workers/WorkerManager'; + +@Component +export struct CocosWebView { + viewInfo: WebViewInfo = new WebViewInfo(0, 0, 0, 0, 0); + cocosWorker: worker.ThreadWorker = WorkerManager.getInstance().getWorker(); + + build() { + Web({ src: this.viewInfo.isUrl ? this.viewInfo.url : $rawfile(this.viewInfo.url), controller: this.viewInfo.controller }) + .position({ x: this.viewInfo.x, y: this.viewInfo.y }) + .width(this.viewInfo.w) + .height(this.viewInfo.h) + .border({ width: 1 }) + .domStorageAccess(true) + .databaseAccess(true) + .imageAccess(true) + .onlineImageAccess(true) + .zoomAccess(this.viewInfo.zoomAccess) + .javaScriptAccess(true) + .visibility(this.viewInfo.visible ? Visibility.Visible : Visibility.None) + .opacity(this.viewInfo.opacity) + .backgroundColor(this.viewInfo.backgroundColor) + .onPageBegin((event) => { + this.cocosWorker.postMessage({ type: "onPageBegin", viewTag: this.viewInfo.viewTag, url: event == null ? '' : event.url }); + }) + .onPageEnd((event) => { + this.cocosWorker.postMessage({ type: "onPageEnd", viewTag: this.viewInfo.viewTag, url: event == null ? '' : event.url }) + if(this.viewInfo.jsInterfaceScheme != "" && event != null && event.url.startsWith(this.viewInfo.jsInterfaceScheme)) { + this.cocosWorker.postMessage({ type: "onJsCallBack", viewTag: this.viewInfo.viewTag, url: event == null ? '' : event.url }) + } + // if u want use the javascript on ur page, u can use registerJavaScriptProxy() to register js function here + // and confirm that just register once by a local variable + }) + .onErrorReceive((event) => { + this.cocosWorker.postMessage({ type: "onErrorReceive", viewTag: this.viewInfo.viewTag, url: this.viewInfo.url }) + }) + .onHttpErrorReceive((event) => { + this.cocosWorker.postMessage({ type: "onErrorReceive", viewTag: this.viewInfo.viewTag, url: this.viewInfo.url }) + }) + } +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/ets/components/TextInputDialog.ets b/tests/cpp-tests/proj.ohos/entry/src/main/ets/components/TextInputDialog.ets new file mode 100644 index 000000000000..4a689341cc0a --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/ets/components/TextInputDialog.ets @@ -0,0 +1,33 @@ +import { TextInputDialogEntity } from '@ohos/libSysCapabilities' +import worker from '@ohos.worker'; +import { WorkerManager } from '../workers/WorkerManager'; + +@CustomDialog +export struct TextInputDialog { + @State showMessage: TextInputDialogEntity = new TextInputDialogEntity(''); + cocosWorker: worker.ThreadWorker = WorkerManager.getInstance().getWorker(); + controller?: CustomDialogController + + build() { + Column() { + Row() { + TextInput({ text: this.showMessage.message }) + .backgroundColor('#ffffff') + .layoutWeight(1) + .defaultFocus(true) + .caretColor(Color.Transparent) + .onChange((value) => { + this.cocosWorker.postMessage({type: "textFieldTTFOnChange", data: value}) + }) + Blank(8).width(16) + Button($r('app.string.text_field_ttf_complete')).onClick(() => { + this.controller!.close(); + }) + }.padding({ left: 8, right: 8, top: 8, bottom: 8 }) + .backgroundColor(Color.Gray) + } + .width('100%') + + .justifyContent(FlexAlign.End) + } +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/ets/pages/Index.ets b/tests/cpp-tests/proj.ohos/entry/src/main/ets/pages/Index.ets new file mode 100644 index 000000000000..dbb983025e14 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,158 @@ +import deviceInfo from '@ohos.deviceInfo'; + +import nativeRender from 'libnativerender.so' +import { ContextType } from '@ohos/libSysCapabilities' +import { TextInputInfo } from '@ohos/libSysCapabilities/src/main/ets/components/editbox/EditBoxMsg' +import { TextInputDialogEntity } from '@ohos/libSysCapabilities' +import { WebViewInfo } from '@ohos/libSysCapabilities/src/main/ets/components/webview/WebViewMsg' +import { VideoPlayerInfo } from '@ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayerMsg' +import { WorkerMsgUtils } from '@ohos/libSysCapabilities/src/main/ets/utils/WorkerMsgUtils' +import { WorkerManager } from '../workers/WorkerManager' +import { CocosEditBox } from '../components/CocosEditBox' +import { CocosWebView } from '../components/CocosWebview' +import { CocosVideoPlayer } from '../components/CocosVideoPlayer' +import { TextInputDialog } from '../components/TextInputDialog' +import { GlobalContext,GlobalContextConstants} from "@ohos/libSysCapabilities" +import promptAction from '@ohos.promptAction'; +import process from '@ohos.process'; +const nativePageLifecycle:nativeRender.CPPFunctions = nativeRender.getContext(ContextType.JSPAGE_LIFECYCLE); + +let cocosWorker = WorkerManager.getInstance().getWorker(); + +@Entry +@Component +struct Index { + xcomponentController: XComponentController = new XComponentController(); + + processMgr = new process.ProcessManager(); + + + // EditBox + @State editBoxArray: TextInputInfo[] = []; + private editBoxIndexMap: Map = new Map; + + // WebView + @State webViewArray: WebViewInfo[] = []; + private webViewIndexMap: Map = new Map; + + // videoPlayer + @State videoPlayerInfoArray: VideoPlayerInfo[] = []; + private videoPlayerIndexMap: Map = new Map; + + // textInputDialog + showMessage: TextInputDialogEntity = new TextInputDialogEntity(''); + dialogController: CustomDialogController = new CustomDialogController({ + builder: TextInputDialog({ + showMessage: this.showMessage + }), + autoCancel: true, + alignment: DialogAlignment.Bottom, + customStyle: true, + }) + // PanGesture + private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Up | PanDirection.Down }); + + onPageShow() { + console.log('[LIFECYCLE-Page] onPageShow'); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_ARRAY, this.editBoxArray); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP, this.editBoxIndexMap); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_COCOS_WORKER, cocosWorker); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY, this.webViewArray); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP, this.webViewIndexMap); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_ARRAY, this.videoPlayerInfoArray); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP, this.videoPlayerIndexMap); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_DIALOG_CONTROLLER, this.dialogController); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_SHOW_MESSAGE, this.showMessage); + nativePageLifecycle.onPageShow(); + } + + onPageHide() { + console.log('[LIFECYCLE-Page] onPageHide'); + nativePageLifecycle.onPageHide(); + } + + onBackPress() { + console.log('[LIFECYCLE-Page] onBackPress'); + try { + promptAction.showDialog({ + title: "提示", + message: "确认退出游戏吗", + buttons: [ + { + text: '取消', + color: '#000000' + }, + { + text: '确认', + color: '#000000' + } + ], + }).then(data => { + console.info('showDialog success, click button: ' + data.index); + if(data.index == 0) { + console.info('showDialog click button cancel'); + return; + } else { + console.info('showDialog click button ok'); + this.processMgr.exit(0); + } + }) + } catch (error) { + console.error(`showDialog args error code is ${error.code}, message is ${error.message}`); + }; + // If disable system exit needed, remove comment "return true" + return true; + } + + onMouseWheel(eventType: string, scrollY: number) { + cocosWorker.postMessage({ type: "onMouseWheel", eventType: eventType, scrollY: scrollY }); + } + + build() { + Stack() { + XComponent({ + id: 'xcomponentId', + type: 'surface', + libraryname: 'nativerender', + controller: this.xcomponentController + }) + .focusable(true) + .focusOnTouch(true) + .gesture( + PanGesture(this.panOption) + .onActionStart(() => { + this.onMouseWheel("actionStart", 0); + }) + .onActionUpdate((event: GestureEvent) => { + if (deviceInfo.deviceType === '2in1') { + this.onMouseWheel("actionUpdate", event.offsetY); + } + }) + .onActionEnd(() => { + this.onMouseWheel("actionEnd", 0); + }) + ) + .onLoad((context) => { + console.log('[cocos] XComponent.onLoad Callback'); + cocosWorker.postMessage({ type: "abilityContextInit", data: GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT)}); + cocosWorker.postMessage({ type: "onXCLoad", data: "XComponent" }); + cocosWorker.onmessage = WorkerMsgUtils.recvWorkerThreadMessage; + }) + .onDestroy(() => { + }) + ForEach(this.editBoxArray, (item:TextInputInfo) => { + CocosEditBox({ textInputInfo: item}); + }, (item:TextInputInfo) => item.viewTag.toString()) + + ForEach(this.webViewArray, (item:WebViewInfo) => { + CocosWebView({ viewInfo: item }) + }, (item:WebViewInfo) => item.uniqueId.toString()) + + ForEach(this.videoPlayerInfoArray, (item:VideoPlayerInfo) => { + CocosVideoPlayer({ videoPlayerInfo: item }) + }, (item:VideoPlayerInfo) => item.viewTag.toString()) + } + .width('100%') + .height('100%') + } +} diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/ets/workers/CocosWorker.ts b/tests/cpp-tests/proj.ohos/entry/src/main/ets/workers/CocosWorker.ts new file mode 100644 index 000000000000..94ddb8d38920 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/ets/workers/CocosWorker.ts @@ -0,0 +1,79 @@ +import worker, { ThreadWorkerGlobalScope } from '@ohos.worker'; +import nativeRender from "libnativerender.so"; +import { ContextType } from "@ohos/libSysCapabilities" +import { VideoPlayer } from "@ohos/libSysCapabilities" +import { CocosEditBox } from "@ohos/libSysCapabilities" +import { Dialog } from "@ohos/libSysCapabilities" +import { WebView } from "@ohos/libSysCapabilities" +import { JumpManager } from "@ohos/libSysCapabilities" +import { NapiHelper } from "@ohos/libSysCapabilities" +import { ApplicationManager } from "@ohos/libSysCapabilities" +import { GlobalContext,GlobalContextConstants} from "@ohos/libSysCapabilities" + +const appLifecycle: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.APP_LIFECYCLE); +const workerContext: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.WORKER_INIT); +const inputNapi: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.INPUT_NAPI); +const mouseNapi: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.MOUSE_NAPI); +const webViewNapi: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.WEBVIEW_NAPI); +const videoPlayNapi: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.VIDEOPLAYER_NAPI); +const napiContext: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.NATIVE_API); +workerContext.workerInit() + +napiContext.nativeEngineStart(); +NapiHelper.registerFunctions(napiContext.registerFunction) + +const workerPort: ThreadWorkerGlobalScope = worker.workerPort; + +workerPort.onmessage = function(e) : void { + let data = e.data; + switch(data.type) { + case "onXCLoad": + console.log("[cocos] onXCLoad Callback"); + Dialog.init(workerPort); + CocosEditBox.init(workerPort); + JumpManager.init(workerPort); + WebView.init(workerPort); + VideoPlayer.init(workerPort); + ApplicationManager.init(workerPort); + napiContext.initAsyncInfo(); + break; + case "abilityContextInit": + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT, data.data); + break; + case "editBoxOnFocus": + inputNapi.editBoxOnFocusCB(data.viewTag); + break; + case "editBoxOnChange": + inputNapi.editBoxOnChangeCB(data.viewTag, data.value); + break; + case "editBoxOnEnter": + inputNapi.editBoxOnEnterCB(data.viewTag, data.text); + break; + case "textFieldTTFOnChange": + inputNapi.textFieldTTFOnChangeCB(data.data); + break; + case "onMouseWheel": + mouseNapi.mouseWheelCB(data.eventType, data.scrollY); + break; + case "onPageBegin": + webViewNapi.shouldStartLoading(data.viewTag, data.url); + break; + case "onPageEnd": + webViewNapi.finishLoading(data.viewTag, data.url); + break; + case "onJsCallBack": + webViewNapi.jsCallback(); + break; + case "onErrorReceive": + webViewNapi.failLoading(data.viewTag, data.url); + break; + case "onVideoCallBack": + videoPlayNapi.onVideoCallBack(data.viewTag, data.event); + break; + case "exit": + appLifecycle.onBackPress(); + break; + default: + console.error("cocos worker: message type unknown") + } +} diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/ets/workers/WorkerManager.ts b/tests/cpp-tests/proj.ohos/entry/src/main/ets/workers/WorkerManager.ts new file mode 100644 index 000000000000..e2ec70b1873e --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/ets/workers/WorkerManager.ts @@ -0,0 +1,34 @@ +import worker from '@ohos.worker'; +import { Constants } from '@ohos/libSysCapabilities'; + +export class WorkerManager { + private cocosWorker: worker.ThreadWorker; + + private constructor() { + this.cocosWorker = new worker.ThreadWorker("entry/ets/workers/CocosWorker.ts", { + type: "classic", + name: "CocosWorker" + }); + this.cocosWorker.onerror = (e) => { + let msg = e.message; + let filename = e.filename; + let lineno = e.lineno; + let colno = e.colno; + console.error(`on Error ${msg} ${filename} ${lineno} ${colno}`); + } + } + + public static getInstance(): WorkerManager { + let workerManger: WorkerManager | undefined = AppStorage.get(Constants.APP_KEY_WORKER_MANAGER); + if (workerManger == undefined) { + workerManger = new WorkerManager; + AppStorage.setOrCreate(Constants.APP_KEY_WORKER_MANAGER, workerManger); + return workerManger; + } + return workerManger; + } + + public getWorker(): worker.ThreadWorker { + return this.cocosWorker; + } +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/module.json5 b/tests/cpp-tests/proj.ohos/entry/src/main/module.json5 new file mode 100644 index 000000000000..3dc05ad51820 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/module.json5 @@ -0,0 +1,70 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:entry_desc", + "mainElement": "MainAbility", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "MainAbility", + "srcEntry": "./ets/MainAbility/MainAbility.ts", + "description": "$string:MainAbility_desc", + "icon": "$media:icon", + "label": "$string:MainAbility_label", + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:white", + "exported": true, + // "orientation": "landscape", + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ], + // https://docs.openharmony.cn/pages/v4.0/zh-cn/application-dev/quick-start/module-configuration-file.md/ + "supportWindowMode": ["fullscreen"], + "maxWindowWidth": 1080, + "minWindowWidth": 1080, + "maxWindowHeight": 720, + "minWindowHeight": 720 + } + ], + "requestPermissions": [ + { + "name": "ohos.permission.INTERNET" + }, { + "name" : "ohos.permission.SET_NETWORK_INFO" + }, { + "name" : "ohos.permission.GET_NETWORK_INFO" + }, { + "name": "ohos.permission.GET_WIFI_INFO" + }, { + "name": "ohos.permission.ACCELEROMETER" + },{ + "name": "ohos.permission.VIBRATE" + } + ], + "metadata": [ + { + "name": "ArkTSPartialUpdate", + "value": "true" + }, + { + "name": "partialUpdateStrictCheck", + "value": "warn" + } + ] + } +} diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/resources/base/element/color.json b/tests/cpp-tests/proj.ohos/entry/src/main/resources/base/element/color.json new file mode 100644 index 000000000000..1bbc9aa9617e --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "white", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/resources/base/element/string.json b/tests/cpp-tests/proj.ohos/entry/src/main/resources/base/element/string.json new file mode 100644 index 000000000000..e426f66b20ee --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/resources/base/element/string.json @@ -0,0 +1,20 @@ +{ + "string": [ + { + "name": "entry_desc", + "value": "3.12_cpp_tests" + }, + { + "name": "MainAbility_label", + "value": "3.12_cpp_tests" + }, + { + "name": "MainAbility_desc", + "value": "description" + }, + { + "name": "text_field_ttf_complete", + "value": "完成" + } + ] +} diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/resources/base/media/icon.png b/tests/cpp-tests/proj.ohos/entry/src/main/resources/base/media/icon.png new file mode 100644 index 000000000000..ce307a8827bd Binary files /dev/null and b/tests/cpp-tests/proj.ohos/entry/src/main/resources/base/media/icon.png differ diff --git a/tests/cpp-tests/proj.ohos/entry/src/main/resources/base/profile/main_pages.json b/tests/cpp-tests/proj.ohos/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 000000000000..1898d94f58d6 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/tests/cpp-tests/proj.ohos/hvigor/hvigor-config.json5 b/tests/cpp-tests/proj.ohos/hvigor/hvigor-config.json5 new file mode 100644 index 000000000000..f70ecd4112d9 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/hvigor/hvigor-config.json5 @@ -0,0 +1,5 @@ +{ + "modelVersion": "5.0.0", + "dependencies": { + } +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/hvigorfile.ts b/tests/cpp-tests/proj.ohos/hvigorfile.ts new file mode 100644 index 000000000000..796f0d2c4f40 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/BuildProfile.ets b/tests/cpp-tests/proj.ohos/libSysCapabilities/BuildProfile.ets new file mode 100644 index 000000000000..3a501e5ddee8 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/build-profile.json5 b/tests/cpp-tests/proj.ohos/libSysCapabilities/build-profile.json5 new file mode 100644 index 000000000000..08f43dcb5e3e --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/build-profile.json5 @@ -0,0 +1,28 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + } + }, + ], + "targets": [ + { + "name": "default" + } + ] +} diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/build/default/intermediates/merge_profile/default/module.json b/tests/cpp-tests/proj.ohos/libSysCapabilities/build/default/intermediates/merge_profile/default/module.json new file mode 100644 index 000000000000..37ef8060cb0d --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/build/default/intermediates/merge_profile/default/module.json @@ -0,0 +1,26 @@ +{ + "app": { + "bundleName": "ohos.cocos3_12.cpp.tests", + "debug": true, + "versionCode": 1000000, + "versionName": "1.0.0", + "minAPIVersion": 50000012, + "targetAPIVersion": 50001013, + "apiReleaseType": "Beta3", + "compileSdkVersion": "5.0.1.106", + "compileSdkType": "HarmonyOS", + "appEnvironments": [], + "bundleType": "app" + }, + "module": { + "name": "libSysCapabilities", + "type": "har", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "packageName": "libsyscapabilities", + "installationFree": false + } +} diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/hvigorfile.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/hvigorfile.ts new file mode 100644 index 000000000000..872c6d60fa92 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/index.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/index.ts new file mode 100644 index 000000000000..6c537383d25b --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/index.ts @@ -0,0 +1,15 @@ +export { ContextType, Constants } from './src/main/ets/common/Constants' +export { GlobalContext,GlobalContextConstants } from './src/main/ets/common/GlobalContext' + +export { Dialog } from './src/main/ets/components/dialog/DialogWorker' +export { CocosEditBox } from './src/main/ets/components/editbox/CocosEditBox' +export { VideoPlayer } from './src/main/ets/components/videoplayer/VideoPlayer' +export { WebView } from './src/main/ets/components/webview/WebView' + +export { TextInputDialogEntity } from './src/main/ets/entity/TextInputDialogEntity' + +export { NapiHelper } from './src/main/ets/napi/NapiHelper' + +export { JumpManager } from './src/main/ets/system/appJump/JumpManager' +export { DeviceUtils } from './src/main/ets/system/device/DeviceUtils' +export { ApplicationManager } from './src/main/ets/system/application/ApplicationManager' diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/obfuscation-rules.txt b/tests/cpp-tests/proj.ohos/libSysCapabilities/obfuscation-rules.txt new file mode 100644 index 000000000000..985b2aeb7658 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/obfuscation-rules.txt @@ -0,0 +1,18 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/oh-package.json5 b/tests/cpp-tests/proj.ohos/libSysCapabilities/oh-package.json5 new file mode 100644 index 000000000000..d6aebc1ddf6a --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "license": "Apache-2.0", + "devDependencies": {}, + "author": "", + "name": "libsyscapabilities", + "description": "Please describe the basic information.", + "main": "index.ts", + "version": "1.0.0", + "dependencies": {} +} diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/common/Constants.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/common/Constants.ts new file mode 100644 index 000000000000..e0f60659d802 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/common/Constants.ts @@ -0,0 +1,22 @@ +export enum ContextType { + APP_LIFECYCLE = 0, + JSPAGE_LIFECYCLE, + RAW_FILE_UTILS, + WORKER_INIT, + NATIVE_API, + INPUT_NAPI, + MOUSE_NAPI, + WEBVIEW_NAPI, + VIDEOPLAYER_NAPI, + SENSOR_API +} + +export class Constants { + static readonly APP_KEY_WORKER_MANAGER = "app_key_worker_manager"; +} + +export class AppPermissionConsts { + static readonly REQUEST_CODE_REQUIRED: number = 1000; + + static readonly REQUEST_CODE_CUSTOM: number = 1001; +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/common/GlobalContext.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/common/GlobalContext.ts new file mode 100644 index 000000000000..616534b2664e --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/common/GlobalContext.ts @@ -0,0 +1,25 @@ +export class GlobalContext { + public static loadGlobalThis(name: string) { + return globalThis[name] + } + + public static storeGlobalThis(name: string, obj: Object) { + globalThis[name] = obj + } +} + +export class GlobalContextConstants { + static readonly COCOS2DX_EDIT_BOX_ARRAY = "Cocos2dx.editBoxArray"; + static readonly COCOS2DX_EDIT_BOX_INDEX_MAP = "Cocos2dx.editBoxIndexMap"; + static readonly COCOS2DX_COCOS_WORKER = "Cocos2dx.cocosWorker"; + static readonly COCOS2DX_WEB_VIEW_ARRAY = "Cocos2dx.WebViewArray"; + static readonly COCOS2DX_WEB_VIEW_INDEX_MAP = "Cocos2dx.WebViewIndexMap"; + static readonly COCOS2DX_VIDEO_PLAYER_ARRAY = "Cocos2dx.VideoPlayerArray"; + static readonly COCOS2DX_VIDEO_PLAYER_INDEX_MAP = "Cocos2dx.VideoPlayerIndexMap"; + static readonly COCOS2DX_DIALOG_CONTROLLER = "Cocos2dx.dialogController"; + static readonly COCOS2DX_SHOW_MESSAGE = "Cocos2dx.showMessage"; + + static readonly COCOS2DX_ABILITY_CONTEXT = "Cocos2dx.abilityContext"; + static readonly COCOS2DX_ABILITY_WANT = "Cocos2dx.abilityWant"; + static readonly COCOS2DX_WEB_RESULT= "Cocos2dx.webResult"; +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/dialog/DialogMsg.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/dialog/DialogMsg.ts new file mode 100644 index 000000000000..37b698c6dce5 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/dialog/DialogMsg.ts @@ -0,0 +1,47 @@ +import prompt from '@system.prompt' +import Logger from '../../utils/Logger' +import { GlobalContext,GlobalContextConstants } from "../../common/GlobalContext" +import { TextInputDialogEntity } from '../../entity/TextInputDialogEntity'; +import { DialogMsgEntity } from '../../entity/WorkerMsgEntity'; + + +let log: Logger = new Logger(0x0001, "DialogMsg"); + +export function handleDialogMsg(eventData: DialogMsgEntity) : void { + switch (eventData.function) { + case "showDialog": { + let title = eventData.title; + let message = eventData.message; + showDialog(title, message); + break; + } + case "showTextInputDialog": { + let tempShowMessage: TextInputDialogEntity = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_SHOW_MESSAGE); + tempShowMessage.message = eventData.message; + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_DIALOG_CONTROLLER).open(); + break; + } + case "hideTextInputDialog": { + let tempShowMessage: TextInputDialogEntity = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_SHOW_MESSAGE); + tempShowMessage.message = ''; + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_DIALOG_CONTROLLER).close(); + break; + } + } +} + +function showDialog(dialogTitle: string, dialogMessage: string) { + prompt.showDialog({ + title: dialogTitle, + message: dialogMessage, + buttons: [ + { + text: 'OK', + color: '#000000' + }, + ], + success: function(data) { + log.debug("handling callback, data:%{public}s", data); + } + }); +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/dialog/DialogWorker.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/dialog/DialogWorker.ts new file mode 100644 index 000000000000..112bd864dda5 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/dialog/DialogWorker.ts @@ -0,0 +1,29 @@ +import type { ThreadWorkerGlobalScope } from '@ohos.worker'; +import { DialogMsgEntity } from '../../entity/WorkerMsgEntity'; + +export class Dialog { + static MODULE_NAME : string = 'Dialog'; + static workerPort; + + static init(workerPort: ThreadWorkerGlobalScope) : void { + Dialog.workerPort = workerPort; + } + + static showDialog(message: string, title: string) : void { + let dialogMsgEntity: DialogMsgEntity = new DialogMsgEntity(Dialog.MODULE_NAME, 'showDialog'); + dialogMsgEntity.title = title; + dialogMsgEntity.message = message; + Dialog.workerPort.postMessage(dialogMsgEntity); + } + + static showTextInputDialog(message: string) : void { + let dialogMsgEntity: DialogMsgEntity = new DialogMsgEntity(Dialog.MODULE_NAME, 'showTextInputDialog'); + dialogMsgEntity.message = message; + Dialog.workerPort.postMessage(dialogMsgEntity); + } + + static hideTextInputDialog() : void { + let dialogMsgEntity: DialogMsgEntity = new DialogMsgEntity(Dialog.MODULE_NAME, 'hideTextInputDialog'); + Dialog.workerPort.postMessage(dialogMsgEntity); + } +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/editbox/CocosEditBox.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/editbox/CocosEditBox.ts new file mode 100644 index 000000000000..f07362f91007 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/editbox/CocosEditBox.ts @@ -0,0 +1,111 @@ +import { Color4B, EditBoxMsgEntity, ViewRect } from '../../entity/WorkerMsgEntity'; + +export class CocosEditBox { + static MODULE_NAME : string = 'EditBox'; + + private static workerPort; + + static init(workerPort) : void { + CocosEditBox.workerPort = workerPort; + } + + static createCocosEditBox(viewTag: number, x: number, y: number, w: number, h: number, paddingW: number, paddingH: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'createCocosEditBox', viewTag); + let viewRect: ViewRect = new ViewRect(x, y, w, h); + editBoxMsgEntity.viewRect = viewRect; + editBoxMsgEntity.paddingW = paddingW; + editBoxMsgEntity.paddingH = paddingH; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static removeCocosEditBox(viewTag: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'removeCocosEditBox', viewTag); + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxViewRect(viewTag: number, x: number, y: number, w: number, h: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxViewRect', viewTag); + let viewRect: ViewRect = new ViewRect(x, y, w, h); + editBoxMsgEntity.viewRect = viewRect; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxVisible(viewTag: number, visible: boolean) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxVisible', viewTag); + editBoxMsgEntity.visible = visible; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setCurrentText(viewTag: number, text: string) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setCurrentText', viewTag); + editBoxMsgEntity.text = text; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxFontSize(viewTag: number, size: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxFontSize', viewTag); + editBoxMsgEntity.fontSize = size; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxFontColor(viewTag: number, r: number, g: number, b: number, a: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxFontColor', viewTag); + let color: Color4B = new Color4B(r, g, b, a); + editBoxMsgEntity.color = color; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxPlaceHolderFontColor(viewTag: number, r: number, g: number, b: number, a: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxPlaceHolderFontColor', viewTag); + let color: Color4B = new Color4B(r, g, b, a); + editBoxMsgEntity.placeHolderColor = color; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxPlaceHolderFontSize(viewTag: number, size: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxPlaceHolderFontSize', viewTag); + editBoxMsgEntity.placeHolderSize = size; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxPlaceHolder(viewTag: number, text: string) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxPlaceHolder', viewTag); + editBoxMsgEntity.placeHolderText = text; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxMaxLength(viewTag: number, maxLength: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxMaxLength', viewTag); + editBoxMsgEntity.maxLength = maxLength; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setNativeInputMode(viewTag: number, inputMode: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setNativeInputMode', viewTag); + editBoxMsgEntity.inputMode = inputMode; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setNativeInputFlag(viewTag: number, inputFlag: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setNativeInputFlag', viewTag); + editBoxMsgEntity.inputFlag = inputFlag; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxFontPath(viewTag: number, fontPath: string) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxFontPath', viewTag); + editBoxMsgEntity.fontPath = fontPath; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxPlaceHolderFontPath(viewTag: number, fontPath: string) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxPlaceHolderFontPath', viewTag); + editBoxMsgEntity.placeHolderFontPath = fontPath; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static hideAllEditBox() : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'hideAllEditBox'); + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/editbox/EditBoxMsg.ets b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/editbox/EditBoxMsg.ets new file mode 100644 index 000000000000..cd1beb888389 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/editbox/EditBoxMsg.ets @@ -0,0 +1,194 @@ +import { GlobalContext, GlobalContextConstants } from '../../common/GlobalContext'; +import { EditBoxMsgEntity } from '../../entity/WorkerMsgEntity'; +import worker from '@ohos.worker'; + +@Observed +export class TextInputInfo { + public viewTag: number = 0; + public x: number = 0; + public y: number = 0; + public w: number = 0; + public h: number = 0; + public paddingW: number = 0; + public paddingH: number = 0; + public fontSize: number = 0; + public fontColor: FontColor = new FontColor(0, 0, 0, 1); + public text: string = ''; + public fontPath: string = ''; + public placeholderFontSize: number = 0; + public placeholderFontColor: FontColor = new FontColor(0, 0, 0, 0.5); + public placeHolder: string = ''; + public placeHolderFontPath: string = ''; + public maxLength = 256; + public inputMode = InputType.Normal; + public visible: boolean = false; + public multiline: boolean = false; + + constructor(x: number, y: number, w: number, h: number, paddingW: number, paddingH: number, viewTag: number) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.paddingW = paddingW; + this.paddingH = paddingH; + this.viewTag = viewTag; + } +} + +@Observed +class FontColor { + public r: number = 0; + public g: number = 0; + public b: number = 0; + public a: number = 1; + + constructor(r: number, g: number, b: number, a: number) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } +} + +export function handleEditBoxMsg(eventData: EditBoxMsgEntity) { + switch (eventData.function) { + case "createCocosEditBox": { + let newTextInputInfo = new TextInputInfo(eventData.viewRect.x, eventData.viewRect.y, eventData.viewRect.w, eventData.viewRect.h, eventData.paddingW, eventData.paddingH, eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_ARRAY).push(newTextInputInfo); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .set(eventData.viewTag, newTextInputInfo); + break; + } + case "removeCocosEditBox": { + let removeIndex = -1; + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_ARRAY) + .forEach((item: TextInputInfo, index: number) => { + if (item.viewTag == eventData.viewTag) { + removeIndex = index; + } + }); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_ARRAY).splice(removeIndex, 1); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP).delete(eventData.viewTag); + break; + } + case "setCurrentText": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.text = eventData.text; + break; + } + case "setEditBoxViewRect": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.x = eventData.viewRect.x; + textInputInfo.y = eventData.viewRect.y; + textInputInfo.w = eventData.viewRect.w; + textInputInfo.h = eventData.viewRect.h; + break; + } + case "setEditBoxVisible": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.visible = eventData.visible; + break; + } + case "setEditBoxFontSize": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.fontSize = eventData.fontSize; + break; + } + case "setEditBoxFontColor": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.fontColor = new FontColor(eventData.color.r, eventData.color.g, eventData.color.b, eventData.color.a / 255); + break; + } + case "setEditBoxPlaceHolderFontSize": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.placeholderFontSize = eventData.placeHolderSize; + break; + } + case "setEditBoxPlaceHolderFontColor": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.placeholderFontColor = new FontColor(eventData.placeHolderColor.r, eventData.placeHolderColor.g, eventData.placeHolderColor.b, eventData.placeHolderColor.a / 255); + break; + } + case "setEditBoxPlaceHolder": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.placeHolder = eventData.placeHolderText; + break; + } + case "setEditBoxMaxLength": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.maxLength = eventData.maxLength; + break; + } + case "setNativeInputMode": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.multiline = (eventData.inputMode == 0) ? true : false; + if (textInputInfo.inputMode != InputType.Password) { + switch (eventData.inputMode) { + case 0: + case 4: + case 6: + textInputInfo.inputMode = InputType.Normal; + break; + case 2: + case 5: + textInputInfo.inputMode = InputType.Number; + break; + case 3: + textInputInfo.inputMode = InputType.PhoneNumber; + break; + case 1: + textInputInfo.inputMode = InputType.Email; + break; + default: + break; + } + } + break; + } + case "setNativeInputFlag": { + // Any type can be changed to a password. The password can be changed to any type, + // but the password is mapped to the general type. Other changes are not allowed. + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + if (eventData.inputFlag == 0) { + textInputInfo.inputMode = InputType.Password; + } else if (textInputInfo.inputMode == InputType.Password) { + textInputInfo.inputMode = InputType.Normal; + } + break; + } + case "setEditBoxFontPath": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.fontPath = eventData.fontPath; + break; + } + case "setEditBoxPlaceHolderFontPath": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.placeHolderFontPath = eventData.placeHolderFontPath; + break; + } + case "hideAllEditBox": { + let editBoxArray: TextInputInfo[] = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_ARRAY); + editBoxArray.forEach(item => { + if (item.visible) { + item.visible = false; + let cocosWorker: worker.ThreadWorker = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_COCOS_WORKER); + cocosWorker.postMessage({ type: "editBoxOnEnter", viewTag: item.viewTag, text: item.text }) + } + }) + break; + } + } +} diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayer.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayer.ts new file mode 100644 index 000000000000..50b7a7e69d00 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayer.ts @@ -0,0 +1,79 @@ +import { VideoPlayMsgEntity, ViewRect } from '../../entity/WorkerMsgEntity'; + +export class VideoPlayer { + static MODULE_NAME: string = 'VideoPlay'; + + private static workerPort; + + static init(workerPort) : void { + VideoPlayer.workerPort = workerPort; + } + + static setURL(viewTag: number, url: string, isUrl: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'setURL', viewTag); + videoPlayMsgEntity.url = url; + videoPlayMsgEntity.isUrl = isUrl; + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static setLooping(viewTag: number, isLoop: boolean) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'setLooping', viewTag); + videoPlayMsgEntity.isLoop = isLoop; + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static createVideoPlayer(viewTag: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'createVideoPlayer', viewTag); + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static removeVideoPlayer(viewTag: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'removeVideoPlayer', viewTag); + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static setVideoPlayerRect(viewTag: number, x: number, y: number, w: number, h: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'setVideoPlayerRect', viewTag); + let viewRect: ViewRect = new ViewRect(x, y, w, h); + videoPlayMsgEntity.viewRect = viewRect; + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static play(viewTag: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'play', viewTag); + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + static pause(viewTag: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'pause', viewTag); + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static stop(viewTag: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'stop', viewTag); + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static setVisible(viewTag: number, visible: boolean) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'setVisible', viewTag); + videoPlayMsgEntity.visible = visible + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static requestFullscreen(viewTag: number, isFullScreen: boolean) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'requestFullscreen', viewTag); + videoPlayMsgEntity.isFullScreen = isFullScreen; + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static seekTo(viewTag: number, seekTo: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'seekTo', viewTag); + videoPlayMsgEntity.seekTo = seekTo; + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static setKeepAspectRatioEnabled(viewTag: number, enable: boolean) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'setKeepAspectRatioEnabled', viewTag); + videoPlayMsgEntity.keepAspectRatioEnabled = enable; + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } +} diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayerMsg.ets b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayerMsg.ets new file mode 100644 index 000000000000..f11bdac82c9a --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayerMsg.ets @@ -0,0 +1,145 @@ +import Logger from '../../utils/Logger' +import { BusinessError } from '@ohos.base'; +import { GlobalContext,GlobalContextConstants } from "../../common/GlobalContext" +import { VideoPlayMsgEntity } from '../../entity/WorkerMsgEntity'; + +let log: Logger = new Logger(0x0001, "VideoPlayerMsg"); + +@Observed +export class VideoPlayerInfo { + // position + public x: number = 0; + public y: number = 0; + // size + public w: number = 0; + public h: number = 0; + // url + public url: string = '' + + public rawfile?: Resource; + public isUrl: boolean = false + // tag + public viewTag: number = 0 + + public isPlay: boolean = false + public isFullScreen: boolean = false + + // Whether to display + public visible: boolean = true + + public isLoop: boolean = false + + public objectFit: ImageFit = ImageFit.Auto + + public controller: VideoController = new VideoController() + + constructor(x: number, y: number, w: number, h: number, viewTag: number) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.viewTag = viewTag; + } +} + +export enum VideoEvent { + PLAYING = 0, + PAUSED, + STOPPED, + COMPLETED, +} + +export function handleVideoPlayMsg(eventData: VideoPlayMsgEntity) { + + switch (eventData.function) { + case "createVideoPlayer": { + let newVideoPlayerInfo = new VideoPlayerInfo(0, 0, 0, 0, eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_ARRAY).push(newVideoPlayerInfo); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).set(eventData.viewTag, newVideoPlayerInfo); + break; + } + case "removeVideoPlayer": { + let removeIndex = -1; + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_ARRAY).forEach((item:VideoPlayerInfo,index:number) => { + if (item.viewTag == eventData.viewTag) { + removeIndex = index; + } + }); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag).controller.requestFullscreen(false); //4.x已修复 + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_ARRAY).splice(removeIndex, 1); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).delete(eventData.viewTag); + break; + } + case "play": { + let videoPlayInfo :VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + videoPlayInfo.controller.start(); + videoPlayInfo.isPlay = true; + break; + } + case "pause": { + let videoPlayInfo:VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + videoPlayInfo.controller.pause(); + videoPlayInfo.isPlay = false; + break; + } + case "stop": { + let videoPlayInfo:VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + videoPlayInfo.controller.stop(); + videoPlayInfo.isPlay = false; + break; + } + case "setURL": { + let videoPlayInfo:VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + if(eventData.isUrl == 0) { + videoPlayInfo.isUrl = false; + videoPlayInfo.rawfile = $rawfile(eventData.url); + } else { + videoPlayInfo.isUrl = true; + videoPlayInfo.url = eventData.url; + } + break; + } + case "setLooping": { + let videoPlayInfo: VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + videoPlayInfo.isLoop = eventData.isLoop; + break; + } + case "setVideoPlayerRect": { + let videoPlayInfo:VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + try { + videoPlayInfo.x = px2vp(eventData.viewRect.x); + videoPlayInfo.y = px2vp(eventData.viewRect.y); + videoPlayInfo.w = px2vp(eventData.viewRect.w); + videoPlayInfo.h = px2vp(eventData.viewRect.h); + } catch (error) { + let e: BusinessError = error as BusinessError; + log.error('videoPlayerInfo ErrorCode: %{public}d, Message: %{public}s', e.code, e.message); + } + break; + } + case "setVisible": { + let videoPlayInfo:VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + if (videoPlayInfo.visible == eventData.visible) { + return; + } + videoPlayInfo.visible = eventData.visible; + break; + } + case "requestFullscreen": { + let videoPlayInfo: VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + videoPlayInfo.controller.requestFullscreen(eventData.isFullScreen); + videoPlayInfo.isFullScreen = eventData.isFullScreen; + break; + } + case "seekTo": { + let videoPlayInfo: VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + videoPlayInfo.controller.setCurrentTime(eventData.seekTo, SeekMode.Accurate); + break; + } + case "setKeepAspectRatioEnabled": { + let videoPlayInfo: VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + videoPlayInfo.objectFit = eventData.keepAspectRatioEnabled? ImageFit.Cover : ImageFit.Auto; + break; + } + } +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/webview/WebView.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/webview/WebView.ts new file mode 100644 index 000000000000..872ec4ce70fe --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/webview/WebView.ts @@ -0,0 +1,114 @@ +import { ViewRect, WebViewMsgEntity } from '../../entity/WorkerMsgEntity'; + +export class WebView { + static MODULE_NAME: string = 'WebView'; + + private static workerPort; + + static init(workerPort) : void { + WebView.workerPort = workerPort; + } + + static createWebView(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'createWebView', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static removeWebView(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'removeWebView', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static setJavascriptInterfaceScheme(viewTag: number, jsInterfaceScheme: string) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'setJavascriptInterfaceScheme', viewTag); + webViewMsgEntity.jsInterfaceScheme = jsInterfaceScheme; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static loadData(viewTag: number, data: string, mimeType: string, encoding: string, baseURL: string) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'loadData', viewTag); + webViewMsgEntity.data = data; + webViewMsgEntity.mimeType = mimeType; + webViewMsgEntity.encoding = encoding; + webViewMsgEntity.baseURL = baseURL; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static loadURL(viewTag: number, url: string) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'loadURL', viewTag); + webViewMsgEntity.url = url; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static loadFile(viewTag: number, filePath: string) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'loadFile', viewTag); + webViewMsgEntity.filePath = filePath; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static stopLoading(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'stopLoading', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static reload(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'reload', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static canGoBack(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'canGoBack', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static canGoForward(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'canGoForward', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static goBack(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'goBack', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static goForward(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'goForward', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static setWebViewRect(viewTag: number, x: number, y: number, w: number, h: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'setWebViewRect', viewTag); + let viewRect: ViewRect = new ViewRect(x, y, w, h); + webViewMsgEntity.viewRect = viewRect; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static setVisible(viewTag: number, visible: boolean) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'setVisible', viewTag); + webViewMsgEntity.visible = visible; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static setOpacityWebView(viewTag: number, opacity: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'setOpacityWebView', viewTag); + webViewMsgEntity.opacity = opacity; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static setBackgroundTransparent(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'setBackgroundTransparent', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static evaluateJS(viewTag: number, js: string) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'evaluateJS', viewTag); + webViewMsgEntity.js = js; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static setScalesPageToFit(viewTag: number, scalesPageToFit: boolean) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'setScalesPageToFit', viewTag); + webViewMsgEntity.scalesPageToFit = scalesPageToFit; + WebView.workerPort.postMessage(webViewMsgEntity); + } +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/webview/WebViewMsg.ets b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/webview/WebViewMsg.ets new file mode 100644 index 000000000000..b7e6d82afc84 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/components/webview/WebViewMsg.ets @@ -0,0 +1,244 @@ +import web_webview from '@ohos.web.webview' +import Logger from '../../utils/Logger'; +import { GlobalContext, GlobalContextConstants } from '../../common/GlobalContext'; +import { WebViewMsgEntity } from '../../entity/WorkerMsgEntity'; +import { BusinessError } from '@ohos.base'; + +let log: Logger = new Logger(0x0001, "WebViewMsg"); + +export class WebViewInfo { + public uniqueId: number = 0; + // position + public x: number = 0; + public y: number = 0; + // size + public w: number = 0; + public h: number = 0; + // url + public url: string = ''; + public isUrl: boolean = true; + public viewTag: number = 0 + public zoomAccess: boolean = true + public visible: boolean = true + public opacity: number = 1 + public backgroundColor: number = 0xffffff; + public jsInterfaceScheme: string = ""; + public controller: web_webview.WebviewController = new web_webview.WebviewController() + + constructor(x: number, y: number, w: number, h: number, viewTag: number) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.viewTag = viewTag + this.uniqueId = viewTag; + } +} + +function copyWebViewInfo(viewInfo: WebViewInfo): WebViewInfo { + let newViewInfo: WebViewInfo = new WebViewInfo(viewInfo.x, viewInfo.y, viewInfo.w, viewInfo.h, viewInfo.viewTag); + newViewInfo.url = viewInfo.url; + newViewInfo.isUrl = viewInfo.isUrl; + newViewInfo.zoomAccess = viewInfo.zoomAccess; + newViewInfo.visible = viewInfo.visible; + newViewInfo.controller = viewInfo.controller; + newViewInfo.opacity = viewInfo.opacity; + newViewInfo.backgroundColor = viewInfo.backgroundColor; + newViewInfo.jsInterfaceScheme = viewInfo.jsInterfaceScheme; + newViewInfo.uniqueId = 100000 - viewInfo.uniqueId; // support 50000 webView exist at the same time + return newViewInfo; +} + +function waitUtilControllerAttached(): Promise { + return new Promise((resolve, reject) => { + resolve(1); + }) +} + +export function handleWebViewMsg(eventData: WebViewMsgEntity) { + let index: number; + switch (eventData.function) { + case "createWebView": { + let view = new WebViewInfo(0, 0, 0, 0, eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY).push(view); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP) + .set(eventData.viewTag, GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY) + .length - 1); + break; + } + case "removeWebView": { + if (GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY).length > 0) { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY).splice(index, 1); + // Do not assume the invoking time of removeWebView. After an element is deleted, the following elements need to be advanced by one bit. + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP) + .forEach((value: number, key: number, map: Map) => { + if (value > index) { + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).set(key, value - 1); + } + }) + + // Delete the subscript mapping of the removed webView. + let tempInfoMap: Map = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP); + tempInfoMap.delete(eventData.viewTag); + } + break; + } + case "setJavascriptInterfaceScheme": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + let webViewInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + webViewInfo.jsInterfaceScheme = eventData.jsInterfaceScheme; + break; + } + case "loadData": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + let webViewInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + waitUtilControllerAttached().then(() => { + webViewInfo.controller.loadData(eventData.data, eventData.mimeType, eventData.encoding, eventData.baseURL) + }); + break; + } + case "loadURL": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index].url = eventData.url; + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index].isUrl = true; + let webViewInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + waitUtilControllerAttached().then(() => { + webViewInfo.controller.loadUrl(eventData.url); + }) + break; + } + case "loadFile": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index].url = eventData.filePath; + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index].isUrl = false; + let webViewInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + waitUtilControllerAttached().then(() => { + webViewInfo.controller.loadUrl($rawfile(eventData.filePath)); + }) + break; + } + case "stopLoading": + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index].controller.stop(); + break; + case "reload": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + let webViewInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + waitUtilControllerAttached().then(() => { + webViewInfo.controller.refresh(); + }) + break; + } + case "canGoBack": + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + let result: number = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .controller + .accessBackward(); + // todo The value needs to be sent back to the worker thread. + break; + case "canGoForward": + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + result = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .controller + .accessForward(); + // todo The value needs to be sent back to the worker thread. + break; + case "goBack": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + let webViewInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + waitUtilControllerAttached().then(() => { + webViewInfo.controller.backward(); + }) + break; + } + case "goForward": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + let webViewInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + waitUtilControllerAttached().then(() => { + webViewInfo.controller.forward(); + }) + break; + } + case "setWebViewRect": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + let tmpWebInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + tmpWebInfo.x = px2vp(eventData.viewRect.x); + tmpWebInfo.y = px2vp(eventData.viewRect.y); + tmpWebInfo.w = px2vp(eventData.viewRect.w); + tmpWebInfo.h = px2vp(eventData.viewRect.h); + let newViewInfo: WebViewInfo = copyWebViewInfo(tmpWebInfo); + let tempInfoArray: WebViewInfo[] = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY); + tempInfoArray[index] = newViewInfo; + break; + } + case "setVisible": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + if (GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .visible == eventData.visible) { + return; + } + let tmpWebInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + tmpWebInfo.visible = eventData.visible; + let newViewInfo: WebViewInfo = copyWebViewInfo(tmpWebInfo); + let tempInfoArray: WebViewInfo[] = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY); + tempInfoArray[index] = newViewInfo; + break; + } + case "setOpacityWebView": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + if (GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .opacity == eventData.opacity) { + return; + } + let tmpWebInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + tmpWebInfo.opacity = eventData.opacity; + let newViewInfo: WebViewInfo = copyWebViewInfo(tmpWebInfo); + let tempInfoArray: WebViewInfo[] = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY); + tempInfoArray[index] = newViewInfo; + break; + } + case "setBackgroundTransparent": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + if (GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .backgroundColor == Color.Transparent) { + return; + } + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .backgroundColor = Color.Transparent; + let newViewInfo: WebViewInfo = copyWebViewInfo(GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]); + let tempInfoArray: WebViewInfo[] = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY); + tempInfoArray[index] = newViewInfo; + break; + } + case "evaluateJS": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .controller + .runJavaScript(eventData.js, (error: BusinessError, result: string) => { + if (error) { + log.warn("webView run JavaScript error:%{public}s", JSON.stringify(error)); + return; + } + if (result) { + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_WEB_RESULT, result) + log.debug("webView run JavaScript result is %{public}s:", result); + } + }) + break; + } + case "setScalesPageToFit": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + if (GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .zoomAccess == eventData.scalesPageToFit) { + return; + } + let tmpWebInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + tmpWebInfo.zoomAccess == eventData.scalesPageToFit + let newViewInfo: WebViewInfo = copyWebViewInfo(tmpWebInfo); + let tempInfoArray: WebViewInfo[] = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY); + tempInfoArray[index] = newViewInfo; + } + break; + } +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/entity/Result.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/entity/Result.ts new file mode 100644 index 000000000000..dd9e38897da3 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/entity/Result.ts @@ -0,0 +1,16 @@ +export class Result { + public static success(data){ + return { + "errCode": 0, + "errMsg": "", + "data": data, + }; + } + + public static error(errCode, errMsg) { + return { + "errCode": errCode, + "errMsg": errMsg, + }; + } +}; \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/entity/TextInputDialogEntity.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/entity/TextInputDialogEntity.ts new file mode 100644 index 000000000000..39d272a0b68c --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/entity/TextInputDialogEntity.ts @@ -0,0 +1,7 @@ +export class TextInputDialogEntity { + message : string; + + constructor(msg: string) { + this.message = msg; + } +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/entity/WorkerMsgEntity.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/entity/WorkerMsgEntity.ts new file mode 100644 index 000000000000..608158d25be9 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/entity/WorkerMsgEntity.ts @@ -0,0 +1,141 @@ +export class ViewRect { + x: number + y: number + w: number + h: number + + constructor(x: number, y: number, w: number, h: number) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } +} + +export class Color4B { + r: number + g: number + b: number + a: number + + constructor(r: number, g: number, b: number, a: number) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } +} + +export class BaseWorkerMsgEntity { + module: string; + + function: string; + + constructor(module: string, func: string) { + this.module = module; + this.function = func; + } +} + +export class DialogMsgEntity extends BaseWorkerMsgEntity { + title: string; + + message: string; + + constructor(module: string, func: string) { + super(module, func); + } +} + +export class EditBoxMsgEntity extends BaseWorkerMsgEntity { + viewTag: number + + viewRect: ViewRect + + paddingW: number + paddingH: number + + visible: boolean + + text: string + fontSize: number + color: Color4B + fontPath: string + + placeHolderText: string + placeHolderSize: number + placeHolderColor: Color4B + placeHolderFontPath: string + + maxLength: number + + inputMode: number + + inputFlag: number + + constructor(module: string, func: string, viewTag?: number) { + super(module, func); + this.viewTag = viewTag; + } +} + +export class VideoPlayMsgEntity extends BaseWorkerMsgEntity { + viewTag: number + + url: string + isUrl: number + + isLoop: boolean + + viewRect: ViewRect + + visible: boolean + + isFullScreen: boolean + + seekTo: number + + keepAspectRatioEnabled: boolean + + constructor(module: string, func: string, viewTag: number) { + super(module, func); + this.viewTag = viewTag; + } +} + +export class WebViewMsgEntity extends BaseWorkerMsgEntity { + viewTag: number + + data: string + mimeType: string + encoding: string + baseURL: string + + url: string + + filePath: string + + viewRect: ViewRect + + visible: boolean + + opacity: number + + js: string + + scalesPageToFit: boolean + jsInterfaceScheme: string + + constructor(module: string, func: string, viewTag: number) { + super(module, func); + this.viewTag = viewTag; + } +} + +export class JumpMsgEntity extends BaseWorkerMsgEntity { + url: string; + + constructor(module: string, func: string) { + super(module, func); + } +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/napi/NapiHelper.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/napi/NapiHelper.ts new file mode 100644 index 000000000000..d4df1a5f9806 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/napi/NapiHelper.ts @@ -0,0 +1,118 @@ +import { Dialog } from '../components/dialog/DialogWorker' +import StringUtils from '../utils/StringUtils' +import { JumpManager } from '../system/appJump/JumpManager' +import { DeviceUtils } from '../system/device/DeviceUtils' +import { ApplicationManager } from '../system/application/ApplicationManager' +import { CocosEditBox } from '../components/editbox/CocosEditBox' +import { WebView } from '../components/webview/WebView' +import { VideoPlayer } from '../components/videoplayer/VideoPlayer' +import Accelerometer from '../system/sensor/AccelerometerUtils' +import Preferences from '../preferences/Preferences' + +export class NapiHelper { + + static registerFunctions(registerFunc : Function) { + NapiHelper.registerOthers(registerFunc); + NapiHelper.registerDeviceUtils(registerFunc); + NapiHelper.registerEditBox(registerFunc); + NapiHelper.registerWebView(registerFunc); + NapiHelper.registerVideoPlay(registerFunc); + NapiHelper.registerSensor(registerFunc); + NapiHelper.registerPreferences(registerFunc); + } + + private static registerOthers(registerFunc : Function) { + registerFunc('DiaLog.showDialog', Dialog.showDialog); + registerFunc('DiaLog.showTextInputDialog', Dialog.showTextInputDialog); + registerFunc('DiaLog.hideTextInputDialog', Dialog.hideTextInputDialog); + registerFunc('StringUtils.getWidth', StringUtils.getWidth); + registerFunc('ApplicationManager.exit', ApplicationManager.exit); + registerFunc('ApplicationManager.getVersionName', ApplicationManager.getVersionName); + registerFunc('JumpManager.openUrl', JumpManager.openUrl); + } + + private static registerDeviceUtils(registerFunc : Function) { + registerFunc('DeviceUtils.getDpi', DeviceUtils.getDpi); + registerFunc('DeviceUtils.getSystemLanguage', DeviceUtils.getSystemLanguage); + registerFunc('DeviceUtils.startVibration', DeviceUtils.startVibration); + registerFunc('DeviceUtils.setKeepScreenOn', DeviceUtils.setKeepScreenOn); + registerFunc('DeviceUtils.isRoundScreen', DeviceUtils.isRoundScreen); + registerFunc('DeviceUtils.hasSoftKeys', DeviceUtils.hasSoftKeys); + registerFunc('DeviceUtils.isCutoutEnable', DeviceUtils.isCutoutEnable); + registerFunc('DeviceUtils.initScreenInfo', DeviceUtils.initScreenInfo); + registerFunc('DeviceUtils.getOrientation', DeviceUtils.getOrientation); + registerFunc('DeviceUtils.getCutoutHeight', DeviceUtils.getCutoutHeight); + registerFunc('DeviceUtils.getCutoutWidth', DeviceUtils.getCutoutWidth); + } + + private static registerEditBox(registerFunc : Function) { + registerFunc('CocosEditBox.createCocosEditBox', CocosEditBox.createCocosEditBox); + registerFunc('CocosEditBox.removeCocosEditBox', CocosEditBox.removeCocosEditBox); + registerFunc('CocosEditBox.setCurrentText', CocosEditBox.setCurrentText); + registerFunc('CocosEditBox.setEditBoxViewRect', CocosEditBox.setEditBoxViewRect); + registerFunc('CocosEditBox.setEditBoxVisible', CocosEditBox.setEditBoxVisible); + registerFunc('CocosEditBox.setEditBoxPlaceHolder', CocosEditBox.setEditBoxPlaceHolder); + registerFunc('CocosEditBox.setEditBoxFontSize', CocosEditBox.setEditBoxFontSize); + registerFunc('CocosEditBox.setEditBoxFontColor', CocosEditBox.setEditBoxFontColor); + registerFunc('CocosEditBox.setEditBoxFontPath', CocosEditBox.setEditBoxFontPath); + registerFunc('CocosEditBox.setEditBoxPlaceHolderFontSize', CocosEditBox.setEditBoxPlaceHolderFontSize); + registerFunc('CocosEditBox.setEditBoxPlaceHolderFontColor', CocosEditBox.setEditBoxPlaceHolderFontColor); + registerFunc('CocosEditBox.setEditBoxPlaceHolderFontPath', CocosEditBox.setEditBoxPlaceHolderFontPath); + registerFunc('CocosEditBox.setEditBoxMaxLength', CocosEditBox.setEditBoxMaxLength); + registerFunc('CocosEditBox.setNativeInputMode', CocosEditBox.setNativeInputMode); + registerFunc('CocosEditBox.setNativeInputFlag', CocosEditBox.setNativeInputFlag); + registerFunc('CocosEditBox.hideAllEditBox', CocosEditBox.hideAllEditBox); + } + + private static registerWebView(registerFunc : Function) { + registerFunc('WebView.createWebView', WebView.createWebView); + registerFunc('WebView.removeWebView', WebView.removeWebView); + registerFunc('WebView.setJavascriptInterfaceScheme', WebView.setJavascriptInterfaceScheme); + registerFunc('WebView.loadData', WebView.loadData); + registerFunc('WebView.loadURL', WebView.loadURL); + registerFunc('WebView.loadFile', WebView.loadFile); + registerFunc('WebView.stopLoading', WebView.stopLoading); + registerFunc('WebView.reload', WebView.reload); + registerFunc('WebView.canGoBack', WebView.canGoBack); + registerFunc('WebView.canGoForward', WebView.canGoForward); + registerFunc('WebView.goBack', WebView.goBack); + registerFunc('WebView.goForward', WebView.goForward); + registerFunc('WebView.setWebViewRect', WebView.setWebViewRect); + registerFunc('WebView.setVisible', WebView.setVisible); + registerFunc('WebView.setOpacityWebView', WebView.setOpacityWebView); + registerFunc('WebView.setBackgroundTransparent', WebView.setBackgroundTransparent); + registerFunc('WebView.evaluateJS', WebView.evaluateJS); + registerFunc('WebView.setScalesPageToFit', WebView.setScalesPageToFit); + } + + private static registerVideoPlay(registerFunc : Function) { + registerFunc('VideoPlayer.createVideoPlayer', VideoPlayer.createVideoPlayer); + registerFunc('VideoPlayer.removeVideoPlayer', VideoPlayer.removeVideoPlayer); + registerFunc('VideoPlayer.setURL', VideoPlayer.setURL); + registerFunc('VideoPlayer.setLooping', VideoPlayer.setLooping); + registerFunc('VideoPlayer.setVideoPlayerRect', VideoPlayer.setVideoPlayerRect); + registerFunc('VideoPlayer.play', VideoPlayer.play); + registerFunc('VideoPlayer.pause', VideoPlayer.pause); + registerFunc('VideoPlayer.stop', VideoPlayer.stop); + registerFunc('VideoPlayer.setVisible', VideoPlayer.setVisible); + registerFunc('VideoPlayer.requestFullscreen', VideoPlayer.requestFullscreen); + registerFunc('VideoPlayer.seekTo', VideoPlayer.seekTo); + registerFunc('VideoPlayer.setKeepAspectRatioEnabled', VideoPlayer.setKeepAspectRatioEnabled); + } + + private static registerSensor(registerFunc : Function) { + registerFunc('Accelerometer.enable', Accelerometer.enable); + registerFunc('Accelerometer.disable', Accelerometer.disable); + } + + private static registerPreferences(registerFunc : Function) { + registerFunc('Preferences.get', Preferences.get); + registerFunc('Preferences.getAll', Preferences.getAll); + registerFunc('Preferences.put', Preferences.put); + registerFunc('Preferences.has', Preferences.has); + registerFunc('Preferences.delete', Preferences.delete); + registerFunc('Preferences.flush', Preferences.flush); + registerFunc('Preferences.clear', Preferences.clear); + } + +} diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/preferences/Preferences.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/preferences/Preferences.ts new file mode 100644 index 000000000000..e95e89b29d10 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/preferences/Preferences.ts @@ -0,0 +1,218 @@ +import Logger from '../utils/Logger'; +import data_preferences from '@ohos.data.preferences'; +import { BusinessError } from '@ohos.base'; +import common from '@ohos.app.ability.common'; +import { GlobalContext, GlobalContextConstants } from '../common/GlobalContext'; + +let log: Logger = new Logger(0x0001, "Preferences"); +let preferences: data_preferences.Preferences | null = null; +const PREFS_NAME: string = "Cocos2dxPreferences"; + +export default class Preferences { + + // 通过 preferencesName 获取Preferences实例 + static getPreferences(): data_preferences.Preferences { + let context: common.UIAbilityContext = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT); + try { + preferences = data_preferences.getPreferencesSync(context, {name: PREFS_NAME}); + log.info("Succeeded in getting preferences."); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to get preferences. code =" + code + ", message =" + message); + } + return preferences; + } + + /* + 通过 preferencesName 从缓存中移出指定的Preferences实例,若Preferences实例有对应的持久化文件,则同时删除其持久化文件。使用Promise异步回调。 + 调用该接口后,不建议再使用旧的Preferences实例进行数据操作,否则会出现数据一致性问题,应将Preferences实例置为null,系统将会统一回收。 + */ + static deletePreferences(): void { + let context: common.UIAbilityContext = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT); + try { + data_preferences.deletePreferences(context, PREFS_NAME).then(() => { + log.info("Succeeded in deleting preferences."); + }); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to delete preferences. code =" + code + ", message =" + message); + } + } + + /* + 通过 preferencesName 从缓存中移出指定的Preferences实例,使用Promise异步回调。 + 应用首次调用getPreferences接口获取某个Preferences实例后,该实例会被会被缓存起来,后续再次getPreferences时不会再次从持久化文件中读取, + 直接从缓存中获取Preferences实例。调用此接口移出缓存中的实例之后,再次getPreferences将会重新读取持久化文件,生成新的Preferences实例。 + 调用该接口后,不建议再使用旧的Preferences实例进行数据操作,否则会出现数据一致性问题,应将Preferences实例置为null,系统将会统一回收。 + */ + static removePreferencesFromCache(): void { + let context: common.UIAbilityContext = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT); + try { + data_preferences.removePreferencesFromCacheSync(context, PREFS_NAME); + log.info("Succeeded in removing preferences."); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to remove preferences. code =" + code + ", message =" + message); + } + } + + // 从缓存的Preferences实例中获取键对应的值,如果值为null或者非默认值类型,返回默认数据defValue + static get(key: string, defValue: data_preferences.ValueType): data_preferences.ValueType { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + let data = preferences.getSync(key, defValue); + log.info("Succeeded in getting value of 'startup'. Data: " + data); + return data; + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to get value of 'startup'. code =" + code + ", message =" + message); + return defValue; + } + } + + // 将数据写入缓存的Preferences实例中,可通过flush将Preferences实例持久化 + static put(key: string, value: data_preferences.ValueType): void { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + preferences.putSync(key, value); + log.info("Succeeded in put the key."); + Preferences.flush(); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to put. code =" + code + ", message =" + message); + } + } + + // 从缓存的Preferences实例中获取所有键值数据。 + static getAll(): string | undefined { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + let object = preferences.getAllSync(); + let allKeys = getObjKeys(object); + log.info('getAll keys = ' + allKeys); + log.info("getAll object = " + JSON.stringify(object)); + return JSON.stringify(object); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to getAll. code =" + code + ", message =" + message); + return undefined; + } + } + + // 检查缓存的Preferences实例中是否包含名为给定Key的存储键值对 + static has(key: string): boolean { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + let val = preferences.hasSync(key); + if (val) { + log.info("The key 'startup' is contained."); + return true; + } else { + log.info("The key 'startup' dose not contain."); + return false; + } + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to has. code =" + code + ", message =" + message); + return false; + } + } + + // 从缓存的Preferences实例中删除名为给定Key的存储键值对,可通过flush将Preferences实例持久化 + static delete(key: string): void { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + preferences.deleteSync(key); + log.info("Succeeded in deleting the key."); + Preferences.flush(); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to delete. code =" + code + ", message =" + message); + } + } + + // 将缓存的Preferences实例中的数据异步存储到用户首选项的持久化文件中,使用Promise异步回调。 + static flush(): void { + if (preferences === null) { + Preferences.getPreferences(); + } + preferences.flush().then(()=>{ + log.info("Succeeded in flushing."); + }); + } + + // 清除缓存的Preferences实例中的所有数据,可通过flush将Preferences实例持久化,使用Promise异步回调。 + static clear(): void { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + preferences.clearSync(); + log.info("Succeeded in clearing."); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to clear. code =" + code + ", message =" + message); + } + } + + + // 订阅数据变更,订阅的Key的值发生变更后,在执行flush方法后,触发callback回调。 + static onChange(cb: Function): void { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + preferences.on('change', (key: string) => { + log.info("The key " + key + " changed."); + cb(key); + }); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to flush. code =" + code + ", message =" + message); + } + } + + // 取消订阅数据变更。 + static offChange(cb: Function): void { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + preferences.off('change', (key: string) => { + log.info("The key " + key + " changed."); + cb(key); + }); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to flush. code =" + code + ", message =" + message); + } + } +} + +// 由于ArkTS中无Object.keys,且无法使用for..in... +// 若报ArkTS问题,请将此方法单独抽离至一个ts文件中并暴露,在需要用到的ets文件中引入使用 +function getObjKeys(obj: Object): string[] { + let keys = Object.keys(obj); + return keys; +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/system/appJump/JumpManager.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/system/appJump/JumpManager.ts new file mode 100644 index 000000000000..dfa95dfaa5ab --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/system/appJump/JumpManager.ts @@ -0,0 +1,19 @@ +import type { ThreadWorkerGlobalScope } from '@ohos.worker'; +import { JumpMsgEntity } from '../../entity/WorkerMsgEntity'; + +export class JumpManager { + + static MODULE_NAME: string = 'JumpManager'; + + private static workerPort: ThreadWorkerGlobalScope; + + static init(workerPort: ThreadWorkerGlobalScope) : void { + JumpManager.workerPort = workerPort; + } + + static openUrl(url: string) : void { + let jumpMsgEntity: JumpMsgEntity = new JumpMsgEntity(JumpManager.MODULE_NAME, 'openUrl'); + jumpMsgEntity.url = url; + JumpManager.workerPort.postMessage(jumpMsgEntity); + } +} diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/system/appJump/JumpManagerMsg.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/system/appJump/JumpManagerMsg.ts new file mode 100644 index 000000000000..ea373af90811 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/system/appJump/JumpManagerMsg.ts @@ -0,0 +1,31 @@ +import type common from '@ohos.app.ability.common'; +import { GlobalContext, GlobalContextConstants } from '../../common/GlobalContext'; +import {Result} from "../../entity/Result" +import type { JumpMsgEntity } from '../../entity/WorkerMsgEntity'; +import Logger from '../../utils/Logger' + +let log: Logger = new Logger(0x0001, "JumpManagerMsg"); + +export function handleJumpManagerMsg(eventData: JumpMsgEntity) : void { + switch (eventData.function) { + case "openUrl": + openUrl(eventData.url); + break; + default: + log.error('%{public}s has not implement yet', eventData.function); + } +} + +function openUrl(url: string): void { + let context: common.UIAbilityContext = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT); + let wantInfo = { + 'action': 'ohos.want.action.viewData', + 'entities': ['entity.system.browsable'], + 'uri': url + } + context.startAbility(wantInfo).then(() => { + log.info('%{public}s', JSON.stringify(Result.success({}))); + }).catch((err) => { + log.error('openUrl : err : %{public}s', JSON.stringify(Result.error(-1, JSON.stringify(err))) ?? ''); + }); +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/system/application/ApplicationManager.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/system/application/ApplicationManager.ts new file mode 100644 index 000000000000..9a552f56e49f --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/system/application/ApplicationManager.ts @@ -0,0 +1,54 @@ +import bundleManager from '@ohos.bundle.bundleManager'; +import type { ThreadWorkerGlobalScope } from '@ohos.worker'; +import { BaseWorkerMsgEntity } from '../../entity/WorkerMsgEntity'; +import { common } from '@kit.AbilityKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { GlobalContext, GlobalContextConstants } from '../../common/GlobalContext'; + +export class ApplicationManager { + static MODULE_NAME: string = 'ApplicationManager'; + + private static workerPort: ThreadWorkerGlobalScope; + + static init(workerPort: ThreadWorkerGlobalScope): void { + ApplicationManager.workerPort = workerPort; + } + + static exit(): void { + let workerMsg: BaseWorkerMsgEntity = new BaseWorkerMsgEntity(ApplicationManager.MODULE_NAME, 'exit'); + ApplicationManager.workerPort.postMessage(workerMsg); + } + + static getVersionName(): string { + let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT; + return bundleManager.getBundleInfoForSelfSync(bundleFlags).versionName; + } +} + +export function handleApplicationMsg(eventData: BaseWorkerMsgEntity): void { + switch (eventData.function) { + case "exit": + terminateSelf(); + break; + default: + console.error('%{public}s has not implement yet', eventData.function); + } +} + +function terminateSelf(): void { + try { + let context: common.UIAbilityContext = + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT); + context.terminateSelf((err: BusinessError) => { + if (err.code) { + console.error(`terminateSelf failed, code is ${err.code}, message is ${err.message}`); + return; + } + console.info('terminateSelf succeed'); + }); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + console.error(`terminateSelf failed, code is ${code}, message is ${message}`); + } +} diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/system/device/DeviceUtils.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/system/device/DeviceUtils.ts new file mode 100644 index 000000000000..b778f1584810 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/system/device/DeviceUtils.ts @@ -0,0 +1,164 @@ +import display from '@ohos.display' +import i18n from '@ohos.i18n'; +import vibrator from '@ohos.vibrator'; +import Logger from '../../utils/Logger'; +import window from '@ohos.window'; +import { GlobalContext, GlobalContextConstants } from '../../common/GlobalContext'; +import { Rect } from '@ohos.application.AccessibilityExtensionAbility'; + +let log = new Logger(0x0001, "DeviceUtils"); + +export class DeviceUtils { + static MODULE_NAME: string = 'DeviceUtils'; + static _roundScreen: boolean = false; + static _hasSoftKeys: boolean = false; + static _isCutoutEnable: boolean = false; + static _cutoutLeft: number; + static _cutoutWidth: number; + static _cutoutTop: number; + static _cutoutHeight: number; + + static getDpi(): number { + return display.getDefaultDisplaySync().densityDPI; + } + + static getSystemLanguage(): string { + return i18n.System.getSystemLanguage(); + } + + static startVibration(time: number) { + try { + vibrator.startVibration({ + type: 'time', + duration: time * 1000, // Seconds to milliseconds + }, { + id: 0, + usage: 'unknown' + }, (error) => { + if (error) { + log.error('vibrate fail, error.code: %{public}d, error.message: %{public}s', error.code, error.message); + return; + } + }); + } catch (err) { + log.error('error.code: %{public}d, error.message: %{public}s', err.code, err.message); + } + } + + static setKeepScreenOn(value: boolean) { + let windowClass = null; + try { + window.getLastWindow(GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT), (err, data) => { //获取窗口实例 + if (err.code) { + log.error('Failed to obtain last window when setKeepScreenOn. Cause:%{public}s', JSON.stringify(err)); + return; + } + windowClass = data; + // Sets whether the screen is always on. + let keepScreenOnPromise = windowClass.setWindowKeepScreenOn(value); + Promise.all([keepScreenOnPromise]).then(() => { + log.info('Succeeded in setKeepScreenOn, value:%{public}s', value); + }).catch((err) => { + log.error('Failed to setKeepScreenOn, cause:%{public}s', JSON.stringify(err)); + }); + }); + } catch (exception) { + log.error('Failed to get or set the window when setKeepScreenOn, cause:%{public}s', JSON.stringify(exception)); + } + } + + static isRoundScreen() : boolean { + return DeviceUtils._roundScreen; + } + + static hasSoftKeys() : boolean { + return DeviceUtils._hasSoftKeys; + } + + static isCutoutEnable() : boolean { + return DeviceUtils._isCutoutEnable; + } + + static initScreenInfo() : void { + let windowClass = null; + try { + window.getLastWindow(GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT), (err, data) => { + if (err.code) { + log.error('Failed to obtain last window when initScreenInfo. Cause:%{public}s', JSON.stringify(err)); + return; + } + + windowClass = data; + let windowProperties: window.WindowProperties = windowClass.getWindowProperties(); + DeviceUtils._roundScreen = windowProperties.isRoundCorner; + let rect: Rect = windowProperties.windowRect; + if(rect.top + rect.height < display.getDefaultDisplaySync().height) { + DeviceUtils._hasSoftKeys = true; + } else { + DeviceUtils._hasSoftKeys = false; + } + }); + } catch (exception) { + log.error('Failed to get or set the window when initScreenInfo, cause:%{public}s', JSON.stringify(exception)); + } + + display.getDefaultDisplaySync().getCutoutInfo().then((data) => { + if(data.boundingRects.length == 0) { + DeviceUtils._isCutoutEnable = false; + return; + } + + DeviceUtils._isCutoutEnable = true; + DeviceUtils._cutoutLeft = data.boundingRects[0].left; + DeviceUtils._cutoutTop = data.boundingRects[0].top; + DeviceUtils._cutoutWidth = data.boundingRects[0].width; + DeviceUtils._cutoutHeight = data.boundingRects[0].height; + }).catch((err) => { + log.error('Failed to obtain all the display objects. Code: ' + JSON.stringify(err)); + }); + } + + static getOrientation() : number { + let orientation: display.Orientation = display.getDefaultDisplaySync().orientation; + + // If the system enumeration value changes, the processing logic in the C++ code needs to be changed. Therefore, the mapping is performed again. + if(orientation == display.Orientation.PORTRAIT) { + return 0; + } + + if(orientation == display.Orientation.LANDSCAPE) { + return 1; + } + + if(orientation == display.Orientation.PORTRAIT_INVERTED) { + return 2; + } + + if(orientation == display.Orientation.LANDSCAPE_INVERTED) { + return 3; + } + + return 4; + } + + static getCutoutHeight() : number { + if(DeviceUtils._cutoutHeight) { + let height = DeviceUtils._cutoutTop + DeviceUtils._cutoutHeight; + return height; + } + return 0; + } + + static getCutoutWidth() : number { + if(!DeviceUtils._cutoutWidth) { + return 0; + } + + let disPlayWidth = display.getDefaultDisplaySync().width; + if(DeviceUtils._cutoutLeft + DeviceUtils._cutoutWidth > disPlayWidth - DeviceUtils._cutoutLeft) { + return disPlayWidth - DeviceUtils._cutoutLeft; + } + + return DeviceUtils._cutoutLeft + DeviceUtils._cutoutWidth; + } +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/system/sensor/AccelerometerUtils.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/system/sensor/AccelerometerUtils.ts new file mode 100644 index 000000000000..56309439f011 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/system/sensor/AccelerometerUtils.ts @@ -0,0 +1,55 @@ +import { getContext } from "libnativerender.so"; +import { ContextType } from "../../common/Constants" +import sensor from '@ohos.sensor'; +import display from '@ohos.display'; +import {Result} from "../../entity/Result" +import Logger from '../../utils/Logger' + +let log: Logger = new Logger(0x0001, "AccelerometerUtils"); + +const accUtils = getContext(ContextType.SENSOR_API); + +export default class Accelerometer { + + private static instance = new Accelerometer(); + + static getInstance() : Accelerometer { + return Accelerometer.instance; + } + + static enable(intervalTime: number) : void { + try { + /* HarmonyOS allow multiple subscriptions, but the game only need one + so if the interval changed, cancel subscription and redo with the new interval */ + sensor.off(sensor.SensorId.ACCELEROMETER); + sensor.on(sensor.SensorId.ACCELEROMETER, function (data) { + let rotation = display.getDefaultDisplaySync().rotation; + if (rotation === 0) { + // Display device screen rotation 0° + accUtils.onAccelerometerCallBack(data.x, data.y, data.z, intervalTime); + } else if (rotation === 1) { + // Display device screen rotation 90° + accUtils.onAccelerometerCallBack(data.y, -data.x, data.z, intervalTime); + } else if (rotation === 2) { + // Display device screen rotation 180° + accUtils.onAccelerometerCallBack(-data.x, -data.y, data.z, intervalTime); + } else if (rotation === 3) { + // Display device screen rotation 270° + accUtils.onAccelerometerCallBack(-data.y, data.x, data.z, intervalTime); + } else { + log.error('Accelerometer init fail, err: %{public}s', JSON.stringify(Result.error(-1, 'unsupported rotation: ' + rotation))); + } + }, { interval: intervalTime }); + } catch (err) { + log.error('Accelerometer init fail, err: %{public}s', JSON.stringify(Result.error(-1, JSON.stringify(err))) ?? ''); + } + } + + static disable() : void { + try { + sensor.off(sensor.SensorId.ACCELEROMETER); + } catch (err) { + log.error('Accelerometer off fail, err: %{public}s', JSON.stringify(Result.error(-1, JSON.stringify(err))) ?? ''); + } + } +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/utils/Logger.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/utils/Logger.ts new file mode 100644 index 000000000000..6277b0e1ab52 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/utils/Logger.ts @@ -0,0 +1,27 @@ +import hilog from '@ohos.hilog' + +export default class Logger { + private domain: number + private prefix: string + + constructor(domain : number, prefix: string) { + this.domain = domain + this.prefix = prefix + } + + debug(format: string, ...args: any[]) { + hilog.debug(this.domain, this.prefix, format, args) + } + + info(format: string, ...args: any[]) { + hilog.info(this.domain, this.prefix, format, args) + } + + warn(format: string, ...args: any[]) { + hilog.warn(this.domain, this.prefix, format, args) + } + + error(format: string, ...args: any[]) { + hilog.error(this.domain, this.prefix, format, args) + } +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/utils/StringUtils.ts b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/utils/StringUtils.ts new file mode 100644 index 000000000000..4e3d20ec55e1 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/utils/StringUtils.ts @@ -0,0 +1,8 @@ +import measure from '@ohos.measure'; + +export default class StringUtils { + + public static getWidth(text: string, fontSize: number, fontWeight: number): number { + return measure.measureText({ textContent: text, fontSize: fontSize + 'px', fontWeight: fontWeight }); + } +} \ No newline at end of file diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/utils/WorkerMsgUtils.ets b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/utils/WorkerMsgUtils.ets new file mode 100644 index 000000000000..b9cac7106b18 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/utils/WorkerMsgUtils.ets @@ -0,0 +1,42 @@ +import worker, { MessageEvents } from '@ohos.worker'; +import Logger from './Logger'; +import { handleEditBoxMsg } from '../components/editbox/EditBoxMsg' +import { handleWebViewMsg } from '../components/webview/WebViewMsg' +import { handleVideoPlayMsg } from '../components/videoplayer/VideoPlayerMsg' +import { handleDialogMsg } from '../components/dialog/DialogMsg' +import { handleJumpManagerMsg } from '../system/appJump/JumpManagerMsg' +import { handleApplicationMsg } from '../system/application/ApplicationManager' +import { BaseWorkerMsgEntity, DialogMsgEntity, EditBoxMsgEntity, JumpMsgEntity, VideoPlayMsgEntity, WebViewMsgEntity } from '../entity/WorkerMsgEntity'; + +export class WorkerMsgUtils { + static workPort = worker.workerPort; + static log : Logger = new Logger(0x0001, 'WorkerMsgUtils') + + static async recvWorkerThreadMessage(event: MessageEvents) { + let eventData: BaseWorkerMsgEntity = event.data; + WorkerMsgUtils.log.debug('mainThread receiveMsg, module:%{public}s, function:%{public}s', eventData.module, eventData.function); + + switch (eventData.module) { + case 'EditBox': + handleEditBoxMsg(eventData as EditBoxMsgEntity); + break; + case "Dialog": + handleDialogMsg(eventData as DialogMsgEntity); + break; + case 'WebView': + handleWebViewMsg(eventData as WebViewMsgEntity); + break; + case 'VideoPlay': + handleVideoPlayMsg(eventData as VideoPlayMsgEntity); + break; + case 'JumpManager': + handleJumpManagerMsg(eventData as JumpMsgEntity); + break; + case 'ApplicationManager': + handleApplicationMsg(eventData as BaseWorkerMsgEntity); + break; + default: + WorkerMsgUtils.log.error('%{public}s has not implement yet', eventData.module); + } + } +} diff --git a/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/module.json5 b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/module.json5 new file mode 100644 index 000000000000..e06cbeaffaa9 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "libSysCapabilities", + "type": "har", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ] + } +} diff --git a/tests/cpp-tests/proj.ohos/oh-package.json5 b/tests/cpp-tests/proj.ohos/oh-package.json5 new file mode 100644 index 000000000000..89c79559f675 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/oh-package.json5 @@ -0,0 +1,11 @@ +{ + "modelVersion": "5.0.0", + "license": "", + "devDependencies": {}, + "author": "", + "name": "proj.ohos", + "description": "Please describe the basic information.", + "main": "", + "version": "1.0.0", + "dependencies": {} +} \ No newline at end of file diff --git a/tests/lua-tests/project/CMakeLists.txt b/tests/lua-tests/project/CMakeLists.txt index c7b617335cb4..a044ad46006f 100644 --- a/tests/lua-tests/project/CMakeLists.txt +++ b/tests/lua-tests/project/CMakeLists.txt @@ -25,8 +25,29 @@ elseif(MACOSX) cocos_mark_resources(FILES ${TEST_RESOURCES} BASEDIR ${CMAKE_SOURCE_DIR}/tests/cpp-tests/Resources RESOURCEBASE Resources/res) list(APPEND SAMPLE_SRC ${APP_RESOURCES} ${APP_SCRIPTS} ${COCOS_SCRIPTS} ${TEST_RESOURCES}) +elseif(OHOS) + list(APPEND GAME_SOURCE proj.ohos/entry/src/main/cpp/main.cpp) + file(GLOB resFiles "${CMAKE_CURRENT_LIST_DIR}/../res/*") + file(COPY ${resFiles} DESTINATION ${CMAKE_CURRENT_LIST_DIR}/proj.ohos/entry/src/main/resources/rawfile) + file(GLOB srcFiles "${CMAKE_CURRENT_LIST_DIR}/../src/*") + file(COPY ${srcFiles} DESTINATION ${CMAKE_CURRENT_LIST_DIR}/proj.ohos/entry/src/main/resources/rawfile) + file(GLOB testResource ${COCOS2DX_ROOT_PATH}/tests/cpp-tests/Resources/*) + file(COPY ${testResource} DESTINATION ${CMAKE_CURRENT_LIST_DIR}/proj.ohos/entry/src/main/resources/rawfile) + if(NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/proj.ohos/entry/src/main/resources/rawfile/cocos) + file(GLOB cocosFiles "${COCOS2DX_ROOT_PATH}/cocos/scripting/lua-bindings/script/*") + file(COPY ${cocosFiles} DESTINATION ${CMAKE_CURRENT_LIST_DIR}/proj.ohos/entry/src/main/resources/rawfile/cocos) + endif() endif() +if(OHOS) +include_directories( + Classes + ../../../cocos/scripting/lua-bindings/manual + ../../../cocos/scripting/lua-bindings/auto + ../../../external/lua/luajit/include + ../../../external/lua/tolua +) +else() include_directories( Classes ../../../cocos/scripting/lua-bindings/manual @@ -34,9 +55,15 @@ include_directories( ../../../external/lua/lua ../../../external/lua/tolua ) +endif() + -# add the executable -add_executable(${APP_NAME} ${SAMPLE_SRC}) +if (OHOS) + add_library(${APP_NAME} STATIC ${SAMPLE_SRC}) +else() + # add the executable + add_executable(${APP_NAME} ${SAMPLE_SRC}) +endif () target_link_libraries(${APP_NAME} luacocos2d cocos2d) @@ -52,12 +79,22 @@ else() set_target_properties(${APP_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${APP_BIN_DIR}") + if(OHOS) pre_build(${APP_NAME} - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/../res ${APP_BIN_DIR}${RES_PREFIX}/res - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/../src ${APP_BIN_DIR}${RES_PREFIX}/src - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/cocos/scripting/lua-bindings/script/ ${APP_BIN_DIR}/Resources/src/cocos - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/tests/cpp-tests/Resources ${APP_BIN_DIR}${RES_PREFIX}/res - ) + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/../res ${APP_BIN_DIR}${RES_PREFIX}/res + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/../src ${APP_BIN_DIR}${RES_PREFIX}/src + COMMAND ${CMAKE_COMMAND} -E copy_directory ${COCOS2DX_ROOT_PATH}/cocos/scripting/lua-bindings/script/ ${APP_BIN_DIR}/Resources/src/cocos + COMMAND ${CMAKE_COMMAND} -E copy_directory ${COCOS2DX_ROOT_PATH}/tests/cpp-tests/Resources ${APP_BIN_DIR}${RES_PREFIX}/res + ) + else() + pre_build(${APP_NAME} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/../res ${APP_BIN_DIR}${RES_PREFIX}/res + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/../src ${APP_BIN_DIR}${RES_PREFIX}/src + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/cocos/scripting/lua-bindings/script/ ${APP_BIN_DIR}/Resources/src/cocos + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/tests/cpp-tests/Resources ${APP_BIN_DIR}${RES_PREFIX}/res + ) + endif() + endif() diff --git a/tests/lua-tests/project/Classes/AppDelegate.cpp b/tests/lua-tests/project/Classes/AppDelegate.cpp index 600abed3494a..72e5464d7b80 100644 --- a/tests/lua-tests/project/Classes/AppDelegate.cpp +++ b/tests/lua-tests/project/Classes/AppDelegate.cpp @@ -43,16 +43,18 @@ bool AppDelegate::applicationDidFinishLaunching() lua_getglobal(L, "_G"); if (lua_istable(L,-1))//stack:...,_G, { -#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID ||CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID ||CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) register_assetsmanager_test_sample(L); #endif register_test_binding(L); } lua_pop(L, 1); - +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + pEngine->executeScriptFile("controller.lua"); +#else pEngine->executeScriptFile("src/controller.lua"); - +#endif return true; } @@ -71,3 +73,17 @@ void AppDelegate::applicationWillEnterForeground() SimpleAudioEngine::getInstance()->resumeBackgroundMusic(); } + +void AppDelegate::applicationScreenSizeChanged(int newWidth, int newHeight) +{ + auto director = cocos2d::Director::getInstance(); + auto glview = director->getOpenGLView(); + if (glview != NULL) { + // Set ResolutionPolicy to a proper value. here use the original value when the game is started. + ResolutionPolicy resolutionPolicy = glview->getResolutionPolicy(); + Size designSize = glview->getDesignResolutionSize(); + glview->setFrameSize(newWidth, newHeight); + // Set the design resolution to a proper value. here use the original value when the game is started. + glview->setDesignResolutionSize(designSize.width, designSize.height, resolutionPolicy); + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/Classes/AppDelegate.h b/tests/lua-tests/project/Classes/AppDelegate.h index 2c1a5b57202a..cc74520d30d1 100644 --- a/tests/lua-tests/project/Classes/AppDelegate.h +++ b/tests/lua-tests/project/Classes/AppDelegate.h @@ -33,6 +33,13 @@ class AppDelegate : private cocos2d::Application @param the pointer of the application */ virtual void applicationWillEnterForeground(); + + /** + @brief This function will be called when the application screen size is changed. + @param new width + @param new height + */ + virtual void applicationScreenSizeChanged(int newWidth, int newHeight); }; #endif // __APP_DELEGATE_H__ diff --git a/tests/lua-tests/project/proj.ohos/.gitignore b/tests/lua-tests/project/proj.ohos/.gitignore new file mode 100644 index 000000000000..ec2bca6cb530 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/.gitignore @@ -0,0 +1,14 @@ +.hvigor +.idea +node_modules +entry/build +entry/.idea +entry/.cxx +local.properties +package-lock.json +entry/src/main/resources/rawfile +oh_modules +/.clangd +/.clang-tidy +.clang-format +hvigor/hvigor-wrapper.js \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/AppScope/app.json5 b/tests/lua-tests/project/proj.ohos/AppScope/app.json5 new file mode 100644 index 000000000000..601c0a2d6e00 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/AppScope/app.json5 @@ -0,0 +1,11 @@ +{ + "app": { + "bundleName": "ohos.cocos3_12.lua.tests", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name", + "distributedNotificationEnabled": true + } +} diff --git a/tests/lua-tests/project/proj.ohos/AppScope/resources/base/element/string.json b/tests/lua-tests/project/proj.ohos/AppScope/resources/base/element/string.json new file mode 100644 index 000000000000..da5c0b16dfe2 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "3.12_lua_tests" + } + ] +} diff --git a/tests/lua-tests/project/proj.ohos/AppScope/resources/base/media/app_icon.png b/tests/lua-tests/project/proj.ohos/AppScope/resources/base/media/app_icon.png new file mode 100644 index 000000000000..ce307a8827bd Binary files /dev/null and b/tests/lua-tests/project/proj.ohos/AppScope/resources/base/media/app_icon.png differ diff --git a/tests/lua-tests/project/proj.ohos/build-profile.json5 b/tests/lua-tests/project/proj.ohos/build-profile.json5 new file mode 100644 index 000000000000..d6ab823c7220 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/build-profile.json5 @@ -0,0 +1,45 @@ +{ + "app": { + "signingConfigs": [ + { + "name": "default", + "type": "HarmonyOS", + "material": { + "certpath": "C:\\Users\\cocos\\.ohos\\config\\default_proj.ohos_3KID1kMOquDAR0Itbw1eUaPrQ1HW8F3DNaZR4Jdj-xQ=.cer", + "storePassword": "0000001B8547817EC3C752A900A9EE1CA542FAC6CAA6BAA43B782DD3C553D32FC65C4D7E5061F036E064E8", + "keyAlias": "debugKey", + "keyPassword": "0000001BBDDB5FC893F6E27DD2FDE22E76D3864FC830918EA2036916C821D12FCCF7C0D58BCE0DDCC66ED0", + "profile": "C:\\Users\\cocos\\.ohos\\config\\default_proj.ohos_3KID1kMOquDAR0Itbw1eUaPrQ1HW8F3DNaZR4Jdj-xQ=.p7b", + "signAlg": "SHA256withECDSA", + "storeFile": "C:\\Users\\cocos\\.ohos\\config\\default_proj.ohos_3KID1kMOquDAR0Itbw1eUaPrQ1HW8F3DNaZR4Jdj-xQ=.p12" + } + } + ], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.0.0(12)", + "runtimeOS": "HarmonyOS" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + }, + { + "name": "libSysCapabilities", + "srcPath": "./libSysCapabilities" + } + ] +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/entry/.gitignore b/tests/lua-tests/project/proj.ohos/entry/.gitignore new file mode 100644 index 000000000000..a6e0f4f0335b --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/.preview +/build +/.cxx +oh_modules +/src/main/resources/rawfile diff --git a/tests/lua-tests/project/proj.ohos/entry/build-profile.json5 b/tests/lua-tests/project/proj.ohos/entry/build-profile.json5 new file mode 100644 index 000000000000..63c3ceddf3bf --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/build-profile.json5 @@ -0,0 +1,40 @@ +{ + "apiType": 'stageMode', + "buildOption": { + "externalNativeOptions": { + "path": "./src/main/cpp/CMakeLists.txt", + "arguments": "", + "abiFilters": [ + //"armeabi-v7a", + "arm64-v8a" + ], + "cppFlags": "", + }, + "sourceOption": { + "workers": [ + './src/main/ets/workers/CocosWorker.ts' + ] + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [{ + "name": "default" + }, { + "name": "ohosTest" + } + ] +} diff --git a/tests/lua-tests/project/proj.ohos/entry/hvigorfile.ts b/tests/lua-tests/project/proj.ohos/entry/hvigorfile.ts new file mode 100644 index 000000000000..533eece8e0be --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/hvigorfile.ts @@ -0,0 +1,6 @@ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/tests/lua-tests/project/proj.ohos/entry/obfuscation-rules.txt b/tests/lua-tests/project/proj.ohos/entry/obfuscation-rules.txt new file mode 100644 index 000000000000..985b2aeb7658 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/obfuscation-rules.txt @@ -0,0 +1,18 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/entry/oh-package-lock.json5 b/tests/lua-tests/project/proj.ohos/entry/oh-package-lock.json5 new file mode 100644 index 000000000000..347e61a7b76a --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/oh-package-lock.json5 @@ -0,0 +1,25 @@ +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/libSysCapabilities@../libSysCapabilities": "@ohos/libSysCapabilities@../libSysCapabilities", + "@types/libnativerender.so@src/main/cpp/types/libentry": "@types/libnativerender.so@src/main/cpp/types/libentry" + }, + "packages": { + "@ohos/libSysCapabilities@../libSysCapabilities": { + "name": "libsyscapabilities", + "version": "1.0.0", + "resolved": "../libSysCapabilities", + "registryType": "local" + }, + "@types/libnativerender.so@src/main/cpp/types/libentry": { + "name": "libnativerender.so", + "version": "0.0.0", + "resolved": "src/main/cpp/types/libentry", + "registryType": "local" + } + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/entry/oh-package.json5 b/tests/lua-tests/project/proj.ohos/entry/oh-package.json5 new file mode 100644 index 000000000000..7732d142af3b --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/oh-package.json5 @@ -0,0 +1,13 @@ +{ + "license": "ISC", + "devDependencies": { + "@types/libnativerender.so": "file:./src/main/cpp/types/libentry" + }, + "name": "entry", + "description": "example description", + "main": "", + "version": "1.0.0", + "dependencies": { + "@ohos/libSysCapabilities": "../libSysCapabilities" + } +} diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/CMakeLists.txt b/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/CMakeLists.txt new file mode 100644 index 000000000000..c9e522cf3cd7 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/CMakeLists.txt @@ -0,0 +1,82 @@ +cmake_minimum_required(VERSION 3.4.1) +project(nativerender) + +set(COCOS2DX_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../..) + +set(CLASSES_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../Classes) + +set(platform_name "ohos") + +set(OHOS true) +add_definitions(-DOHOS) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fms-extensions") +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2") + +find_library( # Sets the name of the path variable. + EGL-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + EGL ) + +find_library( # Sets the name of the path variable. + GLES-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + GLESv3 ) + +find_library( # Sets the name of the path variable. + hilog-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + hilog_ndk.z ) + +find_library( # Sets the name of the path variable. + libace-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + ace_ndk.z ) + +find_library( # Sets the name of the path variable. + libnapi-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + ace_napi.z ) + +find_library( # Sets the name of the path variable. + libuv-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + uv ) +find_library( # Sets the name of the path variable. + rawfile-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + rawfile.z ) + +add_library(${PROJECT_NAME} SHARED main.cpp + napi_init.cpp) + +target_include_directories(${PROJECT_NAME} PUBLIC ${CLASSES_PATH} + PUBLIC ${CLASSES_PATH}/../.. + ) +target_include_directories(${PROJECT_NAME} PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/platform/ohos/napi/common + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/platform/ohos/napi/modules + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/platform/ohos/napi + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/platform/ohos + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/base + ) + +# 选择要build的模块为LUA-EMPTY-TEST +set(BUILD_LUA_LIBS_DEFAULT ON) +set(BUILD_LUA_TESTS_DEFAULT ON) +set(SELECT_MODULE_DOWN ON) + + +target_include_directories(${PROJECT_NAME} PUBLIC ${COCOS2DX_ROOT_PATH}/external/chipmunk/include) +add_subdirectory(${COCOS2DX_ROOT_PATH} cocos2dx) +target_link_libraries(${PROJECT_NAME} PUBLIC lua-tests) \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/main.cpp b/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/main.cpp new file mode 100644 index 000000000000..74309865e2ec --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/main.cpp @@ -0,0 +1,31 @@ +#include "AppDelegate.h" +#include "cocos2d.h" +#include "base/CCEventType.h" +#include "CCLogOhos.h" + +using namespace cocos2d; + +extern "C" { + +void Cocos2dxRenderer_nativeInit(int w, int h) { + OHOS_LOGD("Cocos2dxRenderer_nativeInit() - window width:[%{public}d], height:[%{public}d]", w, h); + if (!CCDirector::sharedDirector()->getOpenGLView()) { + OHOS_LOGD("Cocos2dxRenderer_nativeInit() - branch 1"); + GLView *view = GLViewImpl::sharedOpenGLView(); + view->setFrameSize(w, h); + CCDirector::sharedDirector()->setOpenGLView(view); + + AppDelegate *pAppDelegate = new AppDelegate(); + CCApplication::sharedApplication()->run(); + } else { + OHOS_LOGD("Cocos2dxRenderer_nativeInit() - branch 2"); + ccGLInvalidateStateCache(); + CCShaderCache::sharedShaderCache()->reloadDefaultShaders(); + ccDrawInit(); + CCTextureCache::reloadAllTextures(); + CCNotificationCenter::sharedNotificationCenter()->postNotification(EVENT_COME_TO_FOREGROUND, NULL); + CCDirector::sharedDirector()->setGLDefaultValues(); + } +} + +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/napi_init.cpp b/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/napi_init.cpp new file mode 100644 index 000000000000..5524d88b40bb --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/napi_init.cpp @@ -0,0 +1,37 @@ +#include "CCLogOhos.h" +#include "napi/plugin_manager.h" + +/* + * function for module exports + */ +static napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor desc[] ={ + DECLARE_NAPI_FUNCTION("getContext", NapiManager::GetContext), + }; + NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); + + bool ret = NapiManager::GetInstance()->Export(env, exports); + if (!ret) { + OHOS_LOGE("Init failed"); + } + return exports; +} + +/* + * Napi Module define + */ +static napi_module nativerenderModule = { + .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = Init, + .nm_modname = "nativerender", + .nm_priv = ((void*)0), + .reserved = { 0 }, +}; +/* + * Module register function + */ +extern "C" __attribute__((constructor)) void RegisterModule(void) { + napi_module_register(&nativerenderModule); +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/types/libentry/index.d.ts b/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/types/libentry/index.d.ts new file mode 100644 index 000000000000..6a9293652bfe --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/types/libentry/index.d.ts @@ -0,0 +1,30 @@ +import resmgr from '@ohos.resourceManager'; + +export interface CPPFunctions { + onCreate: () => void; + onShow: () => void; + onHide: () => void; + onBackPress: () => void; + onDestroy: () => void; + onPageShow: () => void; + onPageHide: () => void; + nativeResourceManagerInit: (resourceManager: resmgr.ResourceManager) => void; + writablePathInit: (writePath: string) => void; + workerInit: () => void; + nativeEngineStart: () => void; + registerFunction: () => void; + initAsyncInfo: () => void; + mouseWheelCB: (eventType: string, scrollY : number) => void; + editBoxOnFocusCB: (viewTag: number) => void; + editBoxOnChangeCB: (viewTag: number, text: string) => void; + editBoxOnEnterCB: (viewTag: number, text: string) => void; + textFieldTTFOnChangeCB: (text: string) => void; + shouldStartLoading: (viewTag: number, url: string) => void; + finishLoading: (viewTag: number, url: string) => void; + failLoading: (viewTag: number, url: string) => void; + jsCallback: () => void; + onVideoCallBack: (viewTag: number, event: number) => void; + onAccelerometerCallBack: (x: number, y: number, z: number, interval: number) => void; +} + +export const getContext: (a: number) => CPPFunctions; diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/types/libentry/oh-package.json5 b/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/types/libentry/oh-package.json5 new file mode 100644 index 000000000000..fa7874d5940d --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/types/libentry/oh-package.json5 @@ -0,0 +1,6 @@ +{ + "name": "libnativerender.so", + "types": "./index.d.ts", + "version": "", + "description": "Please describe the basic information." +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/ets/MainAbility/MainAbility.ts b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/MainAbility/MainAbility.ts new file mode 100644 index 000000000000..a4378409d4f4 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/MainAbility/MainAbility.ts @@ -0,0 +1,80 @@ +import window from '@ohos.window'; +import UIAbility from '@ohos.app.ability.UIAbility'; +import web_webview from '@ohos.web.webview'; +import nativeRender from "libnativerender.so"; +import { ContextType } from "@ohos/libSysCapabilities" +import { GlobalContext,GlobalContextConstants} from "@ohos/libSysCapabilities" + +const nativeAppLifecycle: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.APP_LIFECYCLE); +const rawFileUtils: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.RAW_FILE_UTILS); + +export default class MainAbility extends UIAbility { + onCreate(want, launchParam) { + nativeAppLifecycle.onCreate(); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT, this.context); + // Initializes the webView kernel of the system. This parameter is optional if it is not used. + web_webview.WebviewController.initializeWebEngine(); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_WANT, this.context); + console.info('[LIFECYCLE-App] onCreate') + } + + onDestroy() { + nativeAppLifecycle.onDestroy(); + console.info('[LIFECYCLE-App] onDestroy') + } + + onWindowStageCreate(windowStage) { + // Main window is created, set main page for this ability + windowStage.loadContent('pages/Index', (err, data) => { + if (err.code) { + return; + } + rawFileUtils.nativeResourceManagerInit(this.context.resourceManager); + rawFileUtils.writablePathInit(this.context.filesDir); + }); + + windowStage.getMainWindow().then((windowIns: window.Window) => { + // Set whether to display the status bar and navigation bar. If they are not displayed, [] is displayed. + let systemBarPromise = windowIns.setWindowSystemBarEnable([]); + // Whether the window layout is displayed in full screen mode + let fullScreenPromise = windowIns.setWindowLayoutFullScreen(true); + // Sets whether the screen is always on. + let keepScreenOnPromise = windowIns.setWindowKeepScreenOn(true); + Promise.all([systemBarPromise, fullScreenPromise, keepScreenOnPromise]).then(() => { + console.info('Succeeded in setting the window'); + }).catch((err) => { + console.error('Failed to set the window, cause ' + JSON.stringify(err)); + }); + }) + + windowStage.on("windowStageEvent", (data) => { + let stageEventType: window.WindowStageEventType = data; + switch (stageEventType) { + case window.WindowStageEventType.RESUMED: + nativeAppLifecycle.onShow(); + break; + case window.WindowStageEventType.PAUSED: + nativeAppLifecycle.onHide(); + break; + default: + break; + } + }); + } + + onWindowStageDestroy() { + // Main window is destroyed, release UI related resources + } + + onForeground() { + // Ability has brought to foreground + console.info('[LIFECYCLE-App] onShow') + nativeAppLifecycle.onShow(); + } + + onBackground() { + // Ability has back to background + console.info('[LIFECYCLE-App] onDestroy') + nativeAppLifecycle.onHide(); + } +}; diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/ets/components/CocosEditBox.ets b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/components/CocosEditBox.ets new file mode 100644 index 000000000000..2587169f240f --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/components/CocosEditBox.ets @@ -0,0 +1,82 @@ +import { TextInputInfo } from '@ohos/libSysCapabilities/src/main/ets/components/editbox/EditBoxMsg' +import worker from '@ohos.worker'; +import { WorkerManager } from '../workers/WorkerManager'; + +@Component +export struct CocosEditBox { + @ObjectLink textInputInfo: TextInputInfo; + cocosWorker: worker.ThreadWorker = WorkerManager.getInstance().getWorker(); + + build(){ + if(this.textInputInfo.multiline){ + TextArea({text: this.textInputInfo.text, placeholder: this.textInputInfo.placeHolder}) + .id('TextArea'+this.textInputInfo.viewTag) + .position({x: px2vp(this.textInputInfo.x), y: px2vp(this.textInputInfo.y)}) + .size({width: px2vp(this.textInputInfo.w), height: px2vp(this.textInputInfo.h)}) + .padding({top: px2vp(this.textInputInfo.paddingH), right: px2vp(this.textInputInfo.paddingW), + bottom: px2vp(this.textInputInfo.paddingH), left: px2vp(this.textInputInfo.paddingW)}) + .borderRadius(0) + .maxLength(this.textInputInfo.maxLength) + .type(TextAreaType.NORMAL) + .fontFamily($rawfile(this.textInputInfo.fontPath)) + .fontSize(px2fp(this.textInputInfo.fontSize)) + .fontColor(`rgba(${this.textInputInfo.fontColor.r}, ${this.textInputInfo.fontColor.g}, ${this.textInputInfo.fontColor.b}, ${this.textInputInfo.fontColor.a})`) + .placeholderFont({size: px2fp(this.textInputInfo.placeholderFontSize), family: $rawfile(this.textInputInfo.placeHolderFontPath)}) + .placeholderColor(`rgba(${this.textInputInfo.placeholderFontColor.r}, ${this.textInputInfo.placeholderFontColor.g}, ${this.textInputInfo.placeholderFontColor.b}, ${this.textInputInfo.placeholderFontColor.a})`) + .backgroundColor(Color.Transparent) + .visibility(this.textInputInfo.visible ? Visibility.Visible : Visibility.None) + .onVisibleAreaChange([0.0, 1.0], () => { + focusControl.requestFocus('TextArea' + this.textInputInfo.viewTag); + }) + .onFocus(() => { + this.cocosWorker.postMessage({type: "editBoxOnFocus", viewTag: this.textInputInfo.viewTag}); + }) + .onChange((value: string)=>{ + this.textInputInfo.text = value; + this.cocosWorker.postMessage({type: "editBoxOnChange", viewTag: this.textInputInfo.viewTag, value: value}); + }) + .onBlur(()=> { + this.textInputInfo.visible = false + this.cocosWorker.postMessage({type: "editBoxOnEnter", viewTag: this.textInputInfo.viewTag, text: this.textInputInfo.text}); + }) + .onSubmit(()=>{ + this.cocosWorker.postMessage({type: "editBoxOnEnter", viewTag: this.textInputInfo.viewTag, text: this.textInputInfo.text}); + }) + }else{ + TextInput({text: this.textInputInfo.text, placeholder: this.textInputInfo.placeHolder}) + .id('TextInput'+this.textInputInfo.viewTag) + .position({x: px2vp(this.textInputInfo.x), y: px2vp(this.textInputInfo.y)}) + .size({width: px2vp(this.textInputInfo.w), height: px2vp(this.textInputInfo.h)}) + .padding({top: px2vp(this.textInputInfo.paddingH), right: px2vp(this.textInputInfo.paddingW), + bottom: px2vp(this.textInputInfo.paddingH), left: px2vp(this.textInputInfo.paddingW)}) + .borderRadius(0) + .maxLength(this.textInputInfo.maxLength) + .type(this.textInputInfo.inputMode) + .fontFamily($rawfile(this.textInputInfo.fontPath)) + .fontSize(px2fp(this.textInputInfo.fontSize)) + .fontColor(`rgba(${this.textInputInfo.fontColor.r}, ${this.textInputInfo.fontColor.g}, ${this.textInputInfo.fontColor.b}, ${this.textInputInfo.fontColor.a})`) + .placeholderFont({size: px2fp(this.textInputInfo.placeholderFontSize), family: $rawfile(this.textInputInfo.placeHolderFontPath)}) + .placeholderColor(`rgba(${this.textInputInfo.placeholderFontColor.r}, ${this.textInputInfo.placeholderFontColor.g}, ${this.textInputInfo.placeholderFontColor.b}, ${this.textInputInfo.placeholderFontColor.a})`) + .backgroundColor(Color.Transparent) + .visibility(this.textInputInfo.visible ? Visibility.Visible : Visibility.None) + .showPasswordIcon(false) + .onVisibleAreaChange([0.0, 1.0], () => { + focusControl.requestFocus('TextInput' + this.textInputInfo.viewTag); + }) + .onFocus(() => { + this.cocosWorker.postMessage({type: "editBoxOnFocus", viewTag: this.textInputInfo.viewTag}); + }) + .onChange((value: string)=>{ + this.textInputInfo.text = value; + this.cocosWorker.postMessage({type: "editBoxOnChange", viewTag: this.textInputInfo.viewTag, value: value}); + }) + .onBlur(()=> { + this.textInputInfo.visible = false + this.cocosWorker.postMessage({type: "editBoxOnEnter", viewTag: this.textInputInfo.viewTag, text: this.textInputInfo.text}); + }) + .onSubmit(()=>{ + this.cocosWorker.postMessage({type: "editBoxOnEnter", viewTag: this.textInputInfo.viewTag, text: this.textInputInfo.text}); + }) + } + } +} diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/ets/components/CocosVideoPlayer.ets b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/components/CocosVideoPlayer.ets new file mode 100644 index 000000000000..6f67afe3b443 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/components/CocosVideoPlayer.ets @@ -0,0 +1,39 @@ +import { WorkerManager } from '../workers/WorkerManager' +import { VideoPlayerInfo, VideoEvent } from '@ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayerMsg'; +import worker from '@ohos.worker'; + +@Component +export struct CocosVideoPlayer { + @ObjectLink videoPlayerInfo: VideoPlayerInfo; + cocosWorker: worker.ThreadWorker = WorkerManager.getInstance().getWorker(); + + build() { + Column() { + Video({ + src: this.videoPlayerInfo.isUrl ? this.videoPlayerInfo.url : this.videoPlayerInfo.rawfile, + controller: this.videoPlayerInfo.controller + }) + .width(this.videoPlayerInfo.w) + .height(this.videoPlayerInfo.h) + .visibility(this.videoPlayerInfo.visible ? Visibility.Visible : Visibility.None) + .controls(false) + .loop(this.videoPlayerInfo.isLoop) + .objectFit(this.videoPlayerInfo.objectFit) + .onPrepared(()=>{ + if(this.videoPlayerInfo.isPlay) { + this.videoPlayerInfo.controller.start() + } + this.videoPlayerInfo.controller.requestFullscreen(this.videoPlayerInfo.isFullScreen) + }) + .onStart(()=>{ + this.cocosWorker.postMessage({type: "onVideoCallBack", viewTag: this.videoPlayerInfo.viewTag, event: VideoEvent.PLAYING}); + }) + .onPause(()=>{ + this.cocosWorker.postMessage({type: "onVideoCallBack", viewTag: this.videoPlayerInfo.viewTag, event: VideoEvent.PAUSED}); + }) + .onFinish(()=>{ + this.cocosWorker.postMessage({type: "onVideoCallBack", viewTag: this.videoPlayerInfo.viewTag, event: VideoEvent.COMPLETED}); + }) + } + } +} diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/ets/components/CocosWebview.ets b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/components/CocosWebview.ets new file mode 100644 index 000000000000..f26ba14cc6e7 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/components/CocosWebview.ets @@ -0,0 +1,43 @@ +import { WebViewInfo } from "@ohos/libSysCapabilities/src/main/ets/components/webview/WebViewMsg" +import worker from '@ohos.worker'; +import { WorkerManager } from '../workers/WorkerManager'; + +@Component +export struct CocosWebView { + viewInfo: WebViewInfo = new WebViewInfo(0, 0, 0, 0, 0); + cocosWorker: worker.ThreadWorker = WorkerManager.getInstance().getWorker(); + + build() { + Web({ src: this.viewInfo.isUrl ? this.viewInfo.url : $rawfile(this.viewInfo.url), controller: this.viewInfo.controller }) + .position({ x: this.viewInfo.x, y: this.viewInfo.y }) + .width(this.viewInfo.w) + .height(this.viewInfo.h) + .border({ width: 1 }) + .domStorageAccess(true) + .databaseAccess(true) + .imageAccess(true) + .onlineImageAccess(true) + .zoomAccess(this.viewInfo.zoomAccess) + .javaScriptAccess(true) + .visibility(this.viewInfo.visible ? Visibility.Visible : Visibility.None) + .opacity(this.viewInfo.opacity) + .backgroundColor(this.viewInfo.backgroundColor) + .onPageBegin((event) => { + this.cocosWorker.postMessage({ type: "onPageBegin", viewTag: this.viewInfo.viewTag, url: event == null ? '' : event.url }); + }) + .onPageEnd((event) => { + this.cocosWorker.postMessage({ type: "onPageEnd", viewTag: this.viewInfo.viewTag, url: event == null ? '' : event.url }) + if(this.viewInfo.jsInterfaceScheme != "" && event != null && event.url.startsWith(this.viewInfo.jsInterfaceScheme)) { + this.cocosWorker.postMessage({ type: "onJsCallBack", viewTag: this.viewInfo.viewTag, url: event == null ? '' : event.url }) + } + // if u want use the javascript on ur page, u can use registerJavaScriptProxy() to register js function here + // and confirm that just register once by a local variable + }) + .onErrorReceive((event) => { + this.cocosWorker.postMessage({ type: "onErrorReceive", viewTag: this.viewInfo.viewTag, url: this.viewInfo.url }) + }) + .onHttpErrorReceive((event) => { + this.cocosWorker.postMessage({ type: "onErrorReceive", viewTag: this.viewInfo.viewTag, url: this.viewInfo.url }) + }) + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/ets/components/TextInputDialog.ets b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/components/TextInputDialog.ets new file mode 100644 index 000000000000..4a689341cc0a --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/components/TextInputDialog.ets @@ -0,0 +1,33 @@ +import { TextInputDialogEntity } from '@ohos/libSysCapabilities' +import worker from '@ohos.worker'; +import { WorkerManager } from '../workers/WorkerManager'; + +@CustomDialog +export struct TextInputDialog { + @State showMessage: TextInputDialogEntity = new TextInputDialogEntity(''); + cocosWorker: worker.ThreadWorker = WorkerManager.getInstance().getWorker(); + controller?: CustomDialogController + + build() { + Column() { + Row() { + TextInput({ text: this.showMessage.message }) + .backgroundColor('#ffffff') + .layoutWeight(1) + .defaultFocus(true) + .caretColor(Color.Transparent) + .onChange((value) => { + this.cocosWorker.postMessage({type: "textFieldTTFOnChange", data: value}) + }) + Blank(8).width(16) + Button($r('app.string.text_field_ttf_complete')).onClick(() => { + this.controller!.close(); + }) + }.padding({ left: 8, right: 8, top: 8, bottom: 8 }) + .backgroundColor(Color.Gray) + } + .width('100%') + + .justifyContent(FlexAlign.End) + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/ets/pages/Index.ets b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/pages/Index.ets new file mode 100644 index 000000000000..0a46024aa591 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,157 @@ +import deviceInfo from '@ohos.deviceInfo'; + +import nativeRender from 'libnativerender.so' +import { ContextType } from '@ohos/libSysCapabilities' +import { TextInputInfo } from '@ohos/libSysCapabilities/src/main/ets/components/editbox/EditBoxMsg' +import { TextInputDialogEntity } from '@ohos/libSysCapabilities' +import { WebViewInfo } from '@ohos/libSysCapabilities/src/main/ets/components/webview/WebViewMsg' +import { VideoPlayerInfo } from '@ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayerMsg' +import { WorkerMsgUtils } from '@ohos/libSysCapabilities/src/main/ets/utils/WorkerMsgUtils' +import { WorkerManager } from '../workers/WorkerManager' +import { CocosEditBox } from '../components/CocosEditBox' +import { CocosWebView } from '../components/CocosWebview' +import { CocosVideoPlayer } from '../components/CocosVideoPlayer' +import { TextInputDialog } from '../components/TextInputDialog' +import { GlobalContext,GlobalContextConstants} from "@ohos/libSysCapabilities" +import promptAction from '@ohos.promptAction'; +import process from '@ohos.process'; +const nativePageLifecycle:nativeRender.CPPFunctions = nativeRender.getContext(ContextType.JSPAGE_LIFECYCLE); + +@Entry +@Component +struct Index { + + cocosWorker = WorkerManager.getInstance().getWorker(); + xcomponentController: XComponentController = new XComponentController(); + + processMgr = new process.ProcessManager(); + + + // EditBox + @State editBoxArray: TextInputInfo[] = []; + private editBoxIndexMap: Map = new Map; + + // WebView + @State webViewArray: WebViewInfo[] = []; + private webViewIndexMap: Map = new Map; + + // videoPlayer + @State videoPlayerInfoArray: VideoPlayerInfo[] = []; + private videoPlayerIndexMap: Map = new Map; + + // textInputDialog + showMessage: TextInputDialogEntity = new TextInputDialogEntity(''); + dialogController: CustomDialogController = new CustomDialogController({ + builder: TextInputDialog({ + showMessage: this.showMessage + }), + autoCancel: true, + alignment: DialogAlignment.Bottom, + customStyle: true, + }) + // PanGesture + private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Up | PanDirection.Down }); + + onPageShow() { + console.log('[LIFECYCLE-Page] onPageShow'); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_ARRAY, this.editBoxArray); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP, this.editBoxIndexMap); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_COCOS_WORKER, this.cocosWorker); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY, this.webViewArray); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP, this.webViewIndexMap); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_ARRAY, this.videoPlayerInfoArray); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP, this.videoPlayerIndexMap); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_DIALOG_CONTROLLER, this.dialogController); + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_SHOW_MESSAGE, this.showMessage); + nativePageLifecycle.onPageShow(); + } + + onPageHide() { + console.log('[LIFECYCLE-Page] onPageHide'); + nativePageLifecycle.onPageHide(); + } + + onBackPress() { + console.log('[LIFECYCLE-Page] onBackPress'); + try { + promptAction.showDialog({ + title: "提示", + message: "确认退出游戏吗", + buttons: [ + { + text: '取消', + color: '#000000' + }, + { + text: '确认', + color: '#000000' + } + ], + }).then(data => { + console.info('showDialog success, click button: ' + data.index); + if(data.index == 0) { + console.info('showDialog click button cancel'); + return; + } else { + console.info('showDialog click button ok'); + this.processMgr.exit(0); + } + }) + } catch (error) { + console.error(`showDialog args error code is ${error.code}, message is ${error.message}`); + }; + // If disable system exit needed, remove comment "return true" + return true; + } + onMouseWheel(eventType: string, scrollY: number) { + this.cocosWorker.postMessage({ type: "onMouseWheel", eventType: eventType, scrollY: scrollY }); + } + + build() { + Stack() { + XComponent({ + id: 'xcomponentId', + type: 'surface', + libraryname: 'nativerender', + controller: this.xcomponentController + }) + .focusable(true) + .focusOnTouch(true) + .gesture( + PanGesture(this.panOption) + .onActionStart(() => { + this.onMouseWheel("actionStart", 0); + }) + .onActionUpdate((event: GestureEvent) => { + if (deviceInfo.deviceType === '2in1') { + this.onMouseWheel("actionUpdate", event.offsetY); + } + }) + .onActionEnd(() => { + this.onMouseWheel("actionEnd", 0); + }) + ) + .onLoad((context) => { + console.log('[cocos] XComponent.onLoad Callback'); + this.cocosWorker.postMessage({ type: "abilityContextInit", data: GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT)}); + this.cocosWorker.postMessage({ type: "onXCLoad", data: "XComponent" }); + this.cocosWorker.onmessage = WorkerMsgUtils.recvWorkerThreadMessage; + }) + .onDestroy(() => { + }) + ForEach(this.editBoxArray, (item:TextInputInfo) => { + CocosEditBox({ textInputInfo: item}); + }, (item:TextInputInfo) => item.viewTag.toString()) + + ForEach(this.webViewArray, (item:WebViewInfo) => { + CocosWebView({ viewInfo: item }) + }, (item:WebViewInfo) => item.uniqueId.toString()) + + ForEach(this.videoPlayerInfoArray, (item:VideoPlayerInfo) => { + CocosVideoPlayer({ videoPlayerInfo: item }) + }, (item:VideoPlayerInfo) => item.viewTag.toString()) + } + .width('100%') + .height('100%') + } +} diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/ets/workers/CocosWorker.ts b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/workers/CocosWorker.ts new file mode 100644 index 000000000000..94ddb8d38920 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/workers/CocosWorker.ts @@ -0,0 +1,79 @@ +import worker, { ThreadWorkerGlobalScope } from '@ohos.worker'; +import nativeRender from "libnativerender.so"; +import { ContextType } from "@ohos/libSysCapabilities" +import { VideoPlayer } from "@ohos/libSysCapabilities" +import { CocosEditBox } from "@ohos/libSysCapabilities" +import { Dialog } from "@ohos/libSysCapabilities" +import { WebView } from "@ohos/libSysCapabilities" +import { JumpManager } from "@ohos/libSysCapabilities" +import { NapiHelper } from "@ohos/libSysCapabilities" +import { ApplicationManager } from "@ohos/libSysCapabilities" +import { GlobalContext,GlobalContextConstants} from "@ohos/libSysCapabilities" + +const appLifecycle: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.APP_LIFECYCLE); +const workerContext: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.WORKER_INIT); +const inputNapi: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.INPUT_NAPI); +const mouseNapi: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.MOUSE_NAPI); +const webViewNapi: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.WEBVIEW_NAPI); +const videoPlayNapi: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.VIDEOPLAYER_NAPI); +const napiContext: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.NATIVE_API); +workerContext.workerInit() + +napiContext.nativeEngineStart(); +NapiHelper.registerFunctions(napiContext.registerFunction) + +const workerPort: ThreadWorkerGlobalScope = worker.workerPort; + +workerPort.onmessage = function(e) : void { + let data = e.data; + switch(data.type) { + case "onXCLoad": + console.log("[cocos] onXCLoad Callback"); + Dialog.init(workerPort); + CocosEditBox.init(workerPort); + JumpManager.init(workerPort); + WebView.init(workerPort); + VideoPlayer.init(workerPort); + ApplicationManager.init(workerPort); + napiContext.initAsyncInfo(); + break; + case "abilityContextInit": + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT, data.data); + break; + case "editBoxOnFocus": + inputNapi.editBoxOnFocusCB(data.viewTag); + break; + case "editBoxOnChange": + inputNapi.editBoxOnChangeCB(data.viewTag, data.value); + break; + case "editBoxOnEnter": + inputNapi.editBoxOnEnterCB(data.viewTag, data.text); + break; + case "textFieldTTFOnChange": + inputNapi.textFieldTTFOnChangeCB(data.data); + break; + case "onMouseWheel": + mouseNapi.mouseWheelCB(data.eventType, data.scrollY); + break; + case "onPageBegin": + webViewNapi.shouldStartLoading(data.viewTag, data.url); + break; + case "onPageEnd": + webViewNapi.finishLoading(data.viewTag, data.url); + break; + case "onJsCallBack": + webViewNapi.jsCallback(); + break; + case "onErrorReceive": + webViewNapi.failLoading(data.viewTag, data.url); + break; + case "onVideoCallBack": + videoPlayNapi.onVideoCallBack(data.viewTag, data.event); + break; + case "exit": + appLifecycle.onBackPress(); + break; + default: + console.error("cocos worker: message type unknown") + } +} diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/ets/workers/WorkerManager.ts b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/workers/WorkerManager.ts new file mode 100644 index 000000000000..e2ec70b1873e --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/workers/WorkerManager.ts @@ -0,0 +1,34 @@ +import worker from '@ohos.worker'; +import { Constants } from '@ohos/libSysCapabilities'; + +export class WorkerManager { + private cocosWorker: worker.ThreadWorker; + + private constructor() { + this.cocosWorker = new worker.ThreadWorker("entry/ets/workers/CocosWorker.ts", { + type: "classic", + name: "CocosWorker" + }); + this.cocosWorker.onerror = (e) => { + let msg = e.message; + let filename = e.filename; + let lineno = e.lineno; + let colno = e.colno; + console.error(`on Error ${msg} ${filename} ${lineno} ${colno}`); + } + } + + public static getInstance(): WorkerManager { + let workerManger: WorkerManager | undefined = AppStorage.get(Constants.APP_KEY_WORKER_MANAGER); + if (workerManger == undefined) { + workerManger = new WorkerManager; + AppStorage.setOrCreate(Constants.APP_KEY_WORKER_MANAGER, workerManger); + return workerManger; + } + return workerManger; + } + + public getWorker(): worker.ThreadWorker { + return this.cocosWorker; + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/module.json5 b/tests/lua-tests/project/proj.ohos/entry/src/main/module.json5 new file mode 100644 index 000000000000..9a36f2652d8e --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/module.json5 @@ -0,0 +1,68 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:entry_desc", + "mainElement": "MainAbility", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "MainAbility", + "srcEntry": "./ets/MainAbility/MainAbility.ts", + "description": "$string:MainAbility_desc", + "icon": "$media:icon", + "label": "$string:MainAbility_label", + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:white", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ], + "supportWindowMode": ["fullscreen"], + "maxWindowWidth": 1080, + "minWindowWidth": 1080, + "maxWindowHeight": 720, + "minWindowHeight": 720 + } + ], + "requestPermissions": [ + { + "name": "ohos.permission.INTERNET" + }, { + "name" : "ohos.permission.SET_NETWORK_INFO" + }, { + "name" : "ohos.permission.GET_NETWORK_INFO" + }, { + "name": "ohos.permission.GET_WIFI_INFO" + }, { + "name": "ohos.permission.ACCELEROMETER" + },{ + "name": "ohos.permission.VIBRATE" + } + ], + "metadata": [ + { + "name": "ArkTSPartialUpdate", + "value": "true" + }, + { + "name": "partialUpdateStrictCheck", + "value": "warn" + } + ] + } +} diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/resources/base/element/color.json b/tests/lua-tests/project/proj.ohos/entry/src/main/resources/base/element/color.json new file mode 100644 index 000000000000..1bbc9aa9617e --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "white", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/resources/base/element/string.json b/tests/lua-tests/project/proj.ohos/entry/src/main/resources/base/element/string.json new file mode 100644 index 000000000000..f5b29d25972b --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/resources/base/element/string.json @@ -0,0 +1,20 @@ +{ + "string": [ + { + "name": "entry_desc", + "value": "3.12_lua_tests" + }, + { + "name": "MainAbility_label", + "value": "3.12_lua_tests" + }, + { + "name": "MainAbility_desc", + "value": "description" + }, + { + "name": "text_field_ttf_complete", + "value": "完成" + } + ] +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/resources/base/media/icon.png b/tests/lua-tests/project/proj.ohos/entry/src/main/resources/base/media/icon.png new file mode 100644 index 000000000000..ce307a8827bd Binary files /dev/null and b/tests/lua-tests/project/proj.ohos/entry/src/main/resources/base/media/icon.png differ diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/resources/base/profile/main_pages.json b/tests/lua-tests/project/proj.ohos/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 000000000000..1898d94f58d6 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/tests/lua-tests/project/proj.ohos/hvigor/hvigor-config.json5 b/tests/lua-tests/project/proj.ohos/hvigor/hvigor-config.json5 new file mode 100644 index 000000000000..f70ecd4112d9 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/hvigor/hvigor-config.json5 @@ -0,0 +1,5 @@ +{ + "modelVersion": "5.0.0", + "dependencies": { + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/hvigorfile.ts b/tests/lua-tests/project/proj.ohos/hvigorfile.ts new file mode 100644 index 000000000000..796f0d2c4f40 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/BuildProfile.ets b/tests/lua-tests/project/proj.ohos/libSysCapabilities/BuildProfile.ets new file mode 100644 index 000000000000..3a501e5ddee8 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/build-profile.json5 b/tests/lua-tests/project/proj.ohos/libSysCapabilities/build-profile.json5 new file mode 100644 index 000000000000..50d055b1eca9 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/build-profile.json5 @@ -0,0 +1,27 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + } + }, + ], + "targets": [{ + "name": "default" + } + ] +} diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/build/default/intermediates/merge_profile/default/module.json b/tests/lua-tests/project/proj.ohos/libSysCapabilities/build/default/intermediates/merge_profile/default/module.json new file mode 100644 index 000000000000..2c69bca395bf --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/build/default/intermediates/merge_profile/default/module.json @@ -0,0 +1,26 @@ +{ + "app": { + "bundleName": "ohos.cocos3_12.lua.tests", + "debug": true, + "versionCode": 1000000, + "versionName": "1.0.0", + "minAPIVersion": 50000012, + "targetAPIVersion": 50001013, + "apiReleaseType": "Beta3", + "compileSdkVersion": "5.0.1.106", + "compileSdkType": "HarmonyOS", + "appEnvironments": [], + "bundleType": "app" + }, + "module": { + "name": "libSysCapabilities", + "type": "har", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "packageName": "libsyscapabilities", + "installationFree": false + } +} diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/hvigorfile.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/hvigorfile.ts new file mode 100644 index 000000000000..872c6d60fa92 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/index.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/index.ts new file mode 100644 index 000000000000..6c537383d25b --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/index.ts @@ -0,0 +1,15 @@ +export { ContextType, Constants } from './src/main/ets/common/Constants' +export { GlobalContext,GlobalContextConstants } from './src/main/ets/common/GlobalContext' + +export { Dialog } from './src/main/ets/components/dialog/DialogWorker' +export { CocosEditBox } from './src/main/ets/components/editbox/CocosEditBox' +export { VideoPlayer } from './src/main/ets/components/videoplayer/VideoPlayer' +export { WebView } from './src/main/ets/components/webview/WebView' + +export { TextInputDialogEntity } from './src/main/ets/entity/TextInputDialogEntity' + +export { NapiHelper } from './src/main/ets/napi/NapiHelper' + +export { JumpManager } from './src/main/ets/system/appJump/JumpManager' +export { DeviceUtils } from './src/main/ets/system/device/DeviceUtils' +export { ApplicationManager } from './src/main/ets/system/application/ApplicationManager' diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/obfuscation-rules.txt b/tests/lua-tests/project/proj.ohos/libSysCapabilities/obfuscation-rules.txt new file mode 100644 index 000000000000..985b2aeb7658 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/obfuscation-rules.txt @@ -0,0 +1,18 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/oh-package.json5 b/tests/lua-tests/project/proj.ohos/libSysCapabilities/oh-package.json5 new file mode 100644 index 000000000000..8c935e9b2a00 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/oh-package.json5 @@ -0,0 +1,11 @@ +{ + "license": "Apache-2.0", + "devDependencies": {}, + "author": "", + "name": "libsyscapabilities", + "description": "Please describe the basic information.", + "main": "index.ts", + "version": "1.0.0", + "dynamicDependencies": {}, + "dependencies": {} +} diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/common/Constants.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/common/Constants.ts new file mode 100644 index 000000000000..e0f60659d802 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/common/Constants.ts @@ -0,0 +1,22 @@ +export enum ContextType { + APP_LIFECYCLE = 0, + JSPAGE_LIFECYCLE, + RAW_FILE_UTILS, + WORKER_INIT, + NATIVE_API, + INPUT_NAPI, + MOUSE_NAPI, + WEBVIEW_NAPI, + VIDEOPLAYER_NAPI, + SENSOR_API +} + +export class Constants { + static readonly APP_KEY_WORKER_MANAGER = "app_key_worker_manager"; +} + +export class AppPermissionConsts { + static readonly REQUEST_CODE_REQUIRED: number = 1000; + + static readonly REQUEST_CODE_CUSTOM: number = 1001; +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/common/GlobalContext.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/common/GlobalContext.ts new file mode 100644 index 000000000000..616534b2664e --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/common/GlobalContext.ts @@ -0,0 +1,25 @@ +export class GlobalContext { + public static loadGlobalThis(name: string) { + return globalThis[name] + } + + public static storeGlobalThis(name: string, obj: Object) { + globalThis[name] = obj + } +} + +export class GlobalContextConstants { + static readonly COCOS2DX_EDIT_BOX_ARRAY = "Cocos2dx.editBoxArray"; + static readonly COCOS2DX_EDIT_BOX_INDEX_MAP = "Cocos2dx.editBoxIndexMap"; + static readonly COCOS2DX_COCOS_WORKER = "Cocos2dx.cocosWorker"; + static readonly COCOS2DX_WEB_VIEW_ARRAY = "Cocos2dx.WebViewArray"; + static readonly COCOS2DX_WEB_VIEW_INDEX_MAP = "Cocos2dx.WebViewIndexMap"; + static readonly COCOS2DX_VIDEO_PLAYER_ARRAY = "Cocos2dx.VideoPlayerArray"; + static readonly COCOS2DX_VIDEO_PLAYER_INDEX_MAP = "Cocos2dx.VideoPlayerIndexMap"; + static readonly COCOS2DX_DIALOG_CONTROLLER = "Cocos2dx.dialogController"; + static readonly COCOS2DX_SHOW_MESSAGE = "Cocos2dx.showMessage"; + + static readonly COCOS2DX_ABILITY_CONTEXT = "Cocos2dx.abilityContext"; + static readonly COCOS2DX_ABILITY_WANT = "Cocos2dx.abilityWant"; + static readonly COCOS2DX_WEB_RESULT= "Cocos2dx.webResult"; +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/dialog/DialogMsg.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/dialog/DialogMsg.ts new file mode 100644 index 000000000000..37b698c6dce5 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/dialog/DialogMsg.ts @@ -0,0 +1,47 @@ +import prompt from '@system.prompt' +import Logger from '../../utils/Logger' +import { GlobalContext,GlobalContextConstants } from "../../common/GlobalContext" +import { TextInputDialogEntity } from '../../entity/TextInputDialogEntity'; +import { DialogMsgEntity } from '../../entity/WorkerMsgEntity'; + + +let log: Logger = new Logger(0x0001, "DialogMsg"); + +export function handleDialogMsg(eventData: DialogMsgEntity) : void { + switch (eventData.function) { + case "showDialog": { + let title = eventData.title; + let message = eventData.message; + showDialog(title, message); + break; + } + case "showTextInputDialog": { + let tempShowMessage: TextInputDialogEntity = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_SHOW_MESSAGE); + tempShowMessage.message = eventData.message; + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_DIALOG_CONTROLLER).open(); + break; + } + case "hideTextInputDialog": { + let tempShowMessage: TextInputDialogEntity = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_SHOW_MESSAGE); + tempShowMessage.message = ''; + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_DIALOG_CONTROLLER).close(); + break; + } + } +} + +function showDialog(dialogTitle: string, dialogMessage: string) { + prompt.showDialog({ + title: dialogTitle, + message: dialogMessage, + buttons: [ + { + text: 'OK', + color: '#000000' + }, + ], + success: function(data) { + log.debug("handling callback, data:%{public}s", data); + } + }); +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/dialog/DialogWorker.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/dialog/DialogWorker.ts new file mode 100644 index 000000000000..112bd864dda5 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/dialog/DialogWorker.ts @@ -0,0 +1,29 @@ +import type { ThreadWorkerGlobalScope } from '@ohos.worker'; +import { DialogMsgEntity } from '../../entity/WorkerMsgEntity'; + +export class Dialog { + static MODULE_NAME : string = 'Dialog'; + static workerPort; + + static init(workerPort: ThreadWorkerGlobalScope) : void { + Dialog.workerPort = workerPort; + } + + static showDialog(message: string, title: string) : void { + let dialogMsgEntity: DialogMsgEntity = new DialogMsgEntity(Dialog.MODULE_NAME, 'showDialog'); + dialogMsgEntity.title = title; + dialogMsgEntity.message = message; + Dialog.workerPort.postMessage(dialogMsgEntity); + } + + static showTextInputDialog(message: string) : void { + let dialogMsgEntity: DialogMsgEntity = new DialogMsgEntity(Dialog.MODULE_NAME, 'showTextInputDialog'); + dialogMsgEntity.message = message; + Dialog.workerPort.postMessage(dialogMsgEntity); + } + + static hideTextInputDialog() : void { + let dialogMsgEntity: DialogMsgEntity = new DialogMsgEntity(Dialog.MODULE_NAME, 'hideTextInputDialog'); + Dialog.workerPort.postMessage(dialogMsgEntity); + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/editbox/CocosEditBox.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/editbox/CocosEditBox.ts new file mode 100644 index 000000000000..f07362f91007 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/editbox/CocosEditBox.ts @@ -0,0 +1,111 @@ +import { Color4B, EditBoxMsgEntity, ViewRect } from '../../entity/WorkerMsgEntity'; + +export class CocosEditBox { + static MODULE_NAME : string = 'EditBox'; + + private static workerPort; + + static init(workerPort) : void { + CocosEditBox.workerPort = workerPort; + } + + static createCocosEditBox(viewTag: number, x: number, y: number, w: number, h: number, paddingW: number, paddingH: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'createCocosEditBox', viewTag); + let viewRect: ViewRect = new ViewRect(x, y, w, h); + editBoxMsgEntity.viewRect = viewRect; + editBoxMsgEntity.paddingW = paddingW; + editBoxMsgEntity.paddingH = paddingH; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static removeCocosEditBox(viewTag: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'removeCocosEditBox', viewTag); + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxViewRect(viewTag: number, x: number, y: number, w: number, h: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxViewRect', viewTag); + let viewRect: ViewRect = new ViewRect(x, y, w, h); + editBoxMsgEntity.viewRect = viewRect; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxVisible(viewTag: number, visible: boolean) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxVisible', viewTag); + editBoxMsgEntity.visible = visible; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setCurrentText(viewTag: number, text: string) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setCurrentText', viewTag); + editBoxMsgEntity.text = text; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxFontSize(viewTag: number, size: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxFontSize', viewTag); + editBoxMsgEntity.fontSize = size; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxFontColor(viewTag: number, r: number, g: number, b: number, a: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxFontColor', viewTag); + let color: Color4B = new Color4B(r, g, b, a); + editBoxMsgEntity.color = color; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxPlaceHolderFontColor(viewTag: number, r: number, g: number, b: number, a: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxPlaceHolderFontColor', viewTag); + let color: Color4B = new Color4B(r, g, b, a); + editBoxMsgEntity.placeHolderColor = color; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxPlaceHolderFontSize(viewTag: number, size: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxPlaceHolderFontSize', viewTag); + editBoxMsgEntity.placeHolderSize = size; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxPlaceHolder(viewTag: number, text: string) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxPlaceHolder', viewTag); + editBoxMsgEntity.placeHolderText = text; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxMaxLength(viewTag: number, maxLength: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxMaxLength', viewTag); + editBoxMsgEntity.maxLength = maxLength; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setNativeInputMode(viewTag: number, inputMode: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setNativeInputMode', viewTag); + editBoxMsgEntity.inputMode = inputMode; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setNativeInputFlag(viewTag: number, inputFlag: number) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setNativeInputFlag', viewTag); + editBoxMsgEntity.inputFlag = inputFlag; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxFontPath(viewTag: number, fontPath: string) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxFontPath', viewTag); + editBoxMsgEntity.fontPath = fontPath; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static setEditBoxPlaceHolderFontPath(viewTag: number, fontPath: string) : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'setEditBoxPlaceHolderFontPath', viewTag); + editBoxMsgEntity.placeHolderFontPath = fontPath; + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } + + static hideAllEditBox() : void { + let editBoxMsgEntity: EditBoxMsgEntity = new EditBoxMsgEntity(CocosEditBox.MODULE_NAME, 'hideAllEditBox'); + CocosEditBox.workerPort.postMessage(editBoxMsgEntity); + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/editbox/EditBoxMsg.ets b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/editbox/EditBoxMsg.ets new file mode 100644 index 000000000000..cd1beb888389 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/editbox/EditBoxMsg.ets @@ -0,0 +1,194 @@ +import { GlobalContext, GlobalContextConstants } from '../../common/GlobalContext'; +import { EditBoxMsgEntity } from '../../entity/WorkerMsgEntity'; +import worker from '@ohos.worker'; + +@Observed +export class TextInputInfo { + public viewTag: number = 0; + public x: number = 0; + public y: number = 0; + public w: number = 0; + public h: number = 0; + public paddingW: number = 0; + public paddingH: number = 0; + public fontSize: number = 0; + public fontColor: FontColor = new FontColor(0, 0, 0, 1); + public text: string = ''; + public fontPath: string = ''; + public placeholderFontSize: number = 0; + public placeholderFontColor: FontColor = new FontColor(0, 0, 0, 0.5); + public placeHolder: string = ''; + public placeHolderFontPath: string = ''; + public maxLength = 256; + public inputMode = InputType.Normal; + public visible: boolean = false; + public multiline: boolean = false; + + constructor(x: number, y: number, w: number, h: number, paddingW: number, paddingH: number, viewTag: number) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.paddingW = paddingW; + this.paddingH = paddingH; + this.viewTag = viewTag; + } +} + +@Observed +class FontColor { + public r: number = 0; + public g: number = 0; + public b: number = 0; + public a: number = 1; + + constructor(r: number, g: number, b: number, a: number) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } +} + +export function handleEditBoxMsg(eventData: EditBoxMsgEntity) { + switch (eventData.function) { + case "createCocosEditBox": { + let newTextInputInfo = new TextInputInfo(eventData.viewRect.x, eventData.viewRect.y, eventData.viewRect.w, eventData.viewRect.h, eventData.paddingW, eventData.paddingH, eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_ARRAY).push(newTextInputInfo); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .set(eventData.viewTag, newTextInputInfo); + break; + } + case "removeCocosEditBox": { + let removeIndex = -1; + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_ARRAY) + .forEach((item: TextInputInfo, index: number) => { + if (item.viewTag == eventData.viewTag) { + removeIndex = index; + } + }); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_ARRAY).splice(removeIndex, 1); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP).delete(eventData.viewTag); + break; + } + case "setCurrentText": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.text = eventData.text; + break; + } + case "setEditBoxViewRect": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.x = eventData.viewRect.x; + textInputInfo.y = eventData.viewRect.y; + textInputInfo.w = eventData.viewRect.w; + textInputInfo.h = eventData.viewRect.h; + break; + } + case "setEditBoxVisible": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.visible = eventData.visible; + break; + } + case "setEditBoxFontSize": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.fontSize = eventData.fontSize; + break; + } + case "setEditBoxFontColor": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.fontColor = new FontColor(eventData.color.r, eventData.color.g, eventData.color.b, eventData.color.a / 255); + break; + } + case "setEditBoxPlaceHolderFontSize": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.placeholderFontSize = eventData.placeHolderSize; + break; + } + case "setEditBoxPlaceHolderFontColor": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.placeholderFontColor = new FontColor(eventData.placeHolderColor.r, eventData.placeHolderColor.g, eventData.placeHolderColor.b, eventData.placeHolderColor.a / 255); + break; + } + case "setEditBoxPlaceHolder": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.placeHolder = eventData.placeHolderText; + break; + } + case "setEditBoxMaxLength": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.maxLength = eventData.maxLength; + break; + } + case "setNativeInputMode": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.multiline = (eventData.inputMode == 0) ? true : false; + if (textInputInfo.inputMode != InputType.Password) { + switch (eventData.inputMode) { + case 0: + case 4: + case 6: + textInputInfo.inputMode = InputType.Normal; + break; + case 2: + case 5: + textInputInfo.inputMode = InputType.Number; + break; + case 3: + textInputInfo.inputMode = InputType.PhoneNumber; + break; + case 1: + textInputInfo.inputMode = InputType.Email; + break; + default: + break; + } + } + break; + } + case "setNativeInputFlag": { + // Any type can be changed to a password. The password can be changed to any type, + // but the password is mapped to the general type. Other changes are not allowed. + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + if (eventData.inputFlag == 0) { + textInputInfo.inputMode = InputType.Password; + } else if (textInputInfo.inputMode == InputType.Password) { + textInputInfo.inputMode = InputType.Normal; + } + break; + } + case "setEditBoxFontPath": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.fontPath = eventData.fontPath; + break; + } + case "setEditBoxPlaceHolderFontPath": { + let textInputInfo: TextInputInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_INDEX_MAP) + .get(eventData.viewTag); + textInputInfo.placeHolderFontPath = eventData.placeHolderFontPath; + break; + } + case "hideAllEditBox": { + let editBoxArray: TextInputInfo[] = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_EDIT_BOX_ARRAY); + editBoxArray.forEach(item => { + if (item.visible) { + item.visible = false; + let cocosWorker: worker.ThreadWorker = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_COCOS_WORKER); + cocosWorker.postMessage({ type: "editBoxOnEnter", viewTag: item.viewTag, text: item.text }) + } + }) + break; + } + } +} diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayer.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayer.ts new file mode 100644 index 000000000000..50b7a7e69d00 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayer.ts @@ -0,0 +1,79 @@ +import { VideoPlayMsgEntity, ViewRect } from '../../entity/WorkerMsgEntity'; + +export class VideoPlayer { + static MODULE_NAME: string = 'VideoPlay'; + + private static workerPort; + + static init(workerPort) : void { + VideoPlayer.workerPort = workerPort; + } + + static setURL(viewTag: number, url: string, isUrl: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'setURL', viewTag); + videoPlayMsgEntity.url = url; + videoPlayMsgEntity.isUrl = isUrl; + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static setLooping(viewTag: number, isLoop: boolean) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'setLooping', viewTag); + videoPlayMsgEntity.isLoop = isLoop; + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static createVideoPlayer(viewTag: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'createVideoPlayer', viewTag); + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static removeVideoPlayer(viewTag: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'removeVideoPlayer', viewTag); + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static setVideoPlayerRect(viewTag: number, x: number, y: number, w: number, h: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'setVideoPlayerRect', viewTag); + let viewRect: ViewRect = new ViewRect(x, y, w, h); + videoPlayMsgEntity.viewRect = viewRect; + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static play(viewTag: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'play', viewTag); + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + static pause(viewTag: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'pause', viewTag); + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static stop(viewTag: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'stop', viewTag); + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static setVisible(viewTag: number, visible: boolean) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'setVisible', viewTag); + videoPlayMsgEntity.visible = visible + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static requestFullscreen(viewTag: number, isFullScreen: boolean) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'requestFullscreen', viewTag); + videoPlayMsgEntity.isFullScreen = isFullScreen; + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static seekTo(viewTag: number, seekTo: number) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'seekTo', viewTag); + videoPlayMsgEntity.seekTo = seekTo; + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } + + static setKeepAspectRatioEnabled(viewTag: number, enable: boolean) : void { + let videoPlayMsgEntity: VideoPlayMsgEntity = new VideoPlayMsgEntity(VideoPlayer.MODULE_NAME, 'setKeepAspectRatioEnabled', viewTag); + videoPlayMsgEntity.keepAspectRatioEnabled = enable; + VideoPlayer.workerPort.postMessage(videoPlayMsgEntity); + } +} diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayerMsg.ets b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayerMsg.ets new file mode 100644 index 000000000000..f11bdac82c9a --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/videoplayer/VideoPlayerMsg.ets @@ -0,0 +1,145 @@ +import Logger from '../../utils/Logger' +import { BusinessError } from '@ohos.base'; +import { GlobalContext,GlobalContextConstants } from "../../common/GlobalContext" +import { VideoPlayMsgEntity } from '../../entity/WorkerMsgEntity'; + +let log: Logger = new Logger(0x0001, "VideoPlayerMsg"); + +@Observed +export class VideoPlayerInfo { + // position + public x: number = 0; + public y: number = 0; + // size + public w: number = 0; + public h: number = 0; + // url + public url: string = '' + + public rawfile?: Resource; + public isUrl: boolean = false + // tag + public viewTag: number = 0 + + public isPlay: boolean = false + public isFullScreen: boolean = false + + // Whether to display + public visible: boolean = true + + public isLoop: boolean = false + + public objectFit: ImageFit = ImageFit.Auto + + public controller: VideoController = new VideoController() + + constructor(x: number, y: number, w: number, h: number, viewTag: number) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.viewTag = viewTag; + } +} + +export enum VideoEvent { + PLAYING = 0, + PAUSED, + STOPPED, + COMPLETED, +} + +export function handleVideoPlayMsg(eventData: VideoPlayMsgEntity) { + + switch (eventData.function) { + case "createVideoPlayer": { + let newVideoPlayerInfo = new VideoPlayerInfo(0, 0, 0, 0, eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_ARRAY).push(newVideoPlayerInfo); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).set(eventData.viewTag, newVideoPlayerInfo); + break; + } + case "removeVideoPlayer": { + let removeIndex = -1; + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_ARRAY).forEach((item:VideoPlayerInfo,index:number) => { + if (item.viewTag == eventData.viewTag) { + removeIndex = index; + } + }); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag).controller.requestFullscreen(false); //4.x已修复 + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_ARRAY).splice(removeIndex, 1); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).delete(eventData.viewTag); + break; + } + case "play": { + let videoPlayInfo :VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + videoPlayInfo.controller.start(); + videoPlayInfo.isPlay = true; + break; + } + case "pause": { + let videoPlayInfo:VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + videoPlayInfo.controller.pause(); + videoPlayInfo.isPlay = false; + break; + } + case "stop": { + let videoPlayInfo:VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + videoPlayInfo.controller.stop(); + videoPlayInfo.isPlay = false; + break; + } + case "setURL": { + let videoPlayInfo:VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + if(eventData.isUrl == 0) { + videoPlayInfo.isUrl = false; + videoPlayInfo.rawfile = $rawfile(eventData.url); + } else { + videoPlayInfo.isUrl = true; + videoPlayInfo.url = eventData.url; + } + break; + } + case "setLooping": { + let videoPlayInfo: VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + videoPlayInfo.isLoop = eventData.isLoop; + break; + } + case "setVideoPlayerRect": { + let videoPlayInfo:VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + try { + videoPlayInfo.x = px2vp(eventData.viewRect.x); + videoPlayInfo.y = px2vp(eventData.viewRect.y); + videoPlayInfo.w = px2vp(eventData.viewRect.w); + videoPlayInfo.h = px2vp(eventData.viewRect.h); + } catch (error) { + let e: BusinessError = error as BusinessError; + log.error('videoPlayerInfo ErrorCode: %{public}d, Message: %{public}s', e.code, e.message); + } + break; + } + case "setVisible": { + let videoPlayInfo:VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + if (videoPlayInfo.visible == eventData.visible) { + return; + } + videoPlayInfo.visible = eventData.visible; + break; + } + case "requestFullscreen": { + let videoPlayInfo: VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + videoPlayInfo.controller.requestFullscreen(eventData.isFullScreen); + videoPlayInfo.isFullScreen = eventData.isFullScreen; + break; + } + case "seekTo": { + let videoPlayInfo: VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + videoPlayInfo.controller.setCurrentTime(eventData.seekTo, SeekMode.Accurate); + break; + } + case "setKeepAspectRatioEnabled": { + let videoPlayInfo: VideoPlayerInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_VIDEO_PLAYER_INDEX_MAP).get(eventData.viewTag); + videoPlayInfo.objectFit = eventData.keepAspectRatioEnabled? ImageFit.Cover : ImageFit.Auto; + break; + } + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/webview/WebView.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/webview/WebView.ts new file mode 100644 index 000000000000..872ec4ce70fe --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/webview/WebView.ts @@ -0,0 +1,114 @@ +import { ViewRect, WebViewMsgEntity } from '../../entity/WorkerMsgEntity'; + +export class WebView { + static MODULE_NAME: string = 'WebView'; + + private static workerPort; + + static init(workerPort) : void { + WebView.workerPort = workerPort; + } + + static createWebView(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'createWebView', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static removeWebView(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'removeWebView', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static setJavascriptInterfaceScheme(viewTag: number, jsInterfaceScheme: string) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'setJavascriptInterfaceScheme', viewTag); + webViewMsgEntity.jsInterfaceScheme = jsInterfaceScheme; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static loadData(viewTag: number, data: string, mimeType: string, encoding: string, baseURL: string) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'loadData', viewTag); + webViewMsgEntity.data = data; + webViewMsgEntity.mimeType = mimeType; + webViewMsgEntity.encoding = encoding; + webViewMsgEntity.baseURL = baseURL; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static loadURL(viewTag: number, url: string) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'loadURL', viewTag); + webViewMsgEntity.url = url; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static loadFile(viewTag: number, filePath: string) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'loadFile', viewTag); + webViewMsgEntity.filePath = filePath; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static stopLoading(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'stopLoading', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static reload(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'reload', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static canGoBack(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'canGoBack', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static canGoForward(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'canGoForward', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static goBack(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'goBack', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static goForward(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'goForward', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static setWebViewRect(viewTag: number, x: number, y: number, w: number, h: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'setWebViewRect', viewTag); + let viewRect: ViewRect = new ViewRect(x, y, w, h); + webViewMsgEntity.viewRect = viewRect; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static setVisible(viewTag: number, visible: boolean) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'setVisible', viewTag); + webViewMsgEntity.visible = visible; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static setOpacityWebView(viewTag: number, opacity: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'setOpacityWebView', viewTag); + webViewMsgEntity.opacity = opacity; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static setBackgroundTransparent(viewTag: number) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'setBackgroundTransparent', viewTag); + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static evaluateJS(viewTag: number, js: string) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'evaluateJS', viewTag); + webViewMsgEntity.js = js; + WebView.workerPort.postMessage(webViewMsgEntity); + } + + static setScalesPageToFit(viewTag: number, scalesPageToFit: boolean) : void { + let webViewMsgEntity: WebViewMsgEntity = new WebViewMsgEntity(WebView.MODULE_NAME, 'setScalesPageToFit', viewTag); + webViewMsgEntity.scalesPageToFit = scalesPageToFit; + WebView.workerPort.postMessage(webViewMsgEntity); + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/webview/WebViewMsg.ets b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/webview/WebViewMsg.ets new file mode 100644 index 000000000000..de125a3331f5 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/components/webview/WebViewMsg.ets @@ -0,0 +1,246 @@ +import web_webview from '@ohos.web.webview' +import Logger from '../../utils/Logger'; +import { GlobalContext, GlobalContextConstants } from '../../common/GlobalContext'; +import { WebViewMsgEntity } from '../../entity/WorkerMsgEntity'; +import { BusinessError } from '@ohos.base'; + +let log: Logger = new Logger(0x0001, "WebViewMsg"); + +export class WebViewInfo { + public uniqueId: number = 0; + // position + public x: number = 0; + public y: number = 0; + // size + public w: number = 0; + public h: number = 0; + // url + public url: string = ''; + public isUrl: boolean = true; + public viewTag: number = 0 + public zoomAccess: boolean = true + public visible: boolean = true + public opacity: number = 1 + public backgroundColor: number = 0xffffff; + + public jsInterfaceScheme: string = ""; + + public controller: web_webview.WebviewController = new web_webview.WebviewController() + + constructor(x: number, y: number, w: number, h: number, viewTag: number) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.viewTag = viewTag + this.uniqueId = viewTag; + } +} + +function copyWebViewInfo(viewInfo: WebViewInfo): WebViewInfo { + let newViewInfo: WebViewInfo = new WebViewInfo(viewInfo.x, viewInfo.y, viewInfo.w, viewInfo.h, viewInfo.viewTag); + newViewInfo.url = viewInfo.url; + newViewInfo.isUrl = viewInfo.isUrl; + newViewInfo.zoomAccess = viewInfo.zoomAccess; + newViewInfo.visible = viewInfo.visible; + newViewInfo.controller = viewInfo.controller; + newViewInfo.opacity = viewInfo.opacity; + newViewInfo.backgroundColor = viewInfo.backgroundColor; + newViewInfo.jsInterfaceScheme = viewInfo.jsInterfaceScheme; + newViewInfo.uniqueId = 100000 - viewInfo.uniqueId; // support 50000 webView exist at the same time + return newViewInfo; +} + +function waitUtilControllerAttached(): Promise { + return new Promise((resolve, reject) => { + resolve(1); + }) +} + +export function handleWebViewMsg(eventData: WebViewMsgEntity) { + let index: number; + switch (eventData.function) { + case "createWebView": { + let view = new WebViewInfo(0, 0, 0, 0, eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY).push(view); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP) + .set(eventData.viewTag, GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY) + .length - 1); + break; + } + case "removeWebView": { + if (GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY).length > 0) { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY).splice(index, 1); + // Do not assume the invoking time of removeWebView. After an element is deleted, the following elements need to be advanced by one bit. + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP) + .forEach((value: number, key: number, map: Map) => { + if (value > index) { + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).set(key, value - 1); + } + }) + + // Delete the subscript mapping of the removed webView. + let tempInfoMap: Map = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP); + tempInfoMap.delete(eventData.viewTag); + } + break; + } + case "setJavascriptInterfaceScheme": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + let webViewInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + webViewInfo.jsInterfaceScheme = eventData.jsInterfaceScheme; + break; + } + case "loadData": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + let webViewInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + waitUtilControllerAttached().then(() => { + webViewInfo.controller.loadData(eventData.data, eventData.mimeType, eventData.encoding, eventData.baseURL) + }); + break; + } + case "loadURL": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index].url = eventData.url; + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index].isUrl = true; + let webViewInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + waitUtilControllerAttached().then(() => { + webViewInfo.controller.loadUrl(eventData.url); + }) + break; + } + case "loadFile": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index].url = eventData.filePath; + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index].isUrl = false; + let webViewInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + waitUtilControllerAttached().then(() => { + webViewInfo.controller.loadUrl($rawfile(eventData.filePath)); + }) + break; + } + case "stopLoading": + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index].controller.stop(); + break; + case "reload": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + let webViewInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + waitUtilControllerAttached().then(() => { + webViewInfo.controller.refresh(); + }) + break; + } + case "canGoBack": + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + let result: number = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .controller + .accessBackward(); + // todo The value needs to be sent back to the worker thread. + break; + case "canGoForward": + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + result = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .controller + .accessForward(); + // todo The value needs to be sent back to the worker thread. + break; + case "goBack": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + let webViewInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + waitUtilControllerAttached().then(() => { + webViewInfo.controller.backward(); + }) + break; + } + case "goForward": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + let webViewInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + waitUtilControllerAttached().then(() => { + webViewInfo.controller.forward(); + }) + break; + } + case "setWebViewRect": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + let tmpWebInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + tmpWebInfo.x = px2vp(eventData.viewRect.x); + tmpWebInfo.y = px2vp(eventData.viewRect.y); + tmpWebInfo.w = px2vp(eventData.viewRect.w); + tmpWebInfo.h = px2vp(eventData.viewRect.h); + let newViewInfo: WebViewInfo = copyWebViewInfo(tmpWebInfo); + let tempInfoArray: WebViewInfo[] = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY); + tempInfoArray[index] = newViewInfo; + break; + } + case "setVisible": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + if (GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .visible == eventData.visible) { + return; + } + let tmpWebInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + tmpWebInfo.visible = eventData.visible; + let newViewInfo: WebViewInfo = copyWebViewInfo(tmpWebInfo); + let tempInfoArray: WebViewInfo[] = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY); + tempInfoArray[index] = newViewInfo; + break; + } + case "setOpacityWebView": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + if (GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .opacity == eventData.opacity) { + return; + } + let tmpWebInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + tmpWebInfo.opacity = eventData.opacity; + let newViewInfo: WebViewInfo = copyWebViewInfo(tmpWebInfo); + let tempInfoArray: WebViewInfo[] = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY); + tempInfoArray[index] = newViewInfo; + break; + } + case "setBackgroundTransparent": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + if (GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .backgroundColor == Color.Transparent) { + return; + } + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .backgroundColor = Color.Transparent; + let newViewInfo: WebViewInfo = copyWebViewInfo(GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]); + let tempInfoArray: WebViewInfo[] = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY); + tempInfoArray[index] = newViewInfo; + break; + } + case "evaluateJS": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .controller + .runJavaScript(eventData.js, (error: BusinessError, result: string) => { + if (error) { + log.warn("webView run JavaScript error:%{public}s", JSON.stringify(error)); + return; + } + if (result) { + GlobalContext.storeGlobalThis(GlobalContextConstants.COCOS2DX_WEB_RESULT, result) + log.debug("webView run JavaScript result is %{public}s:", result); + } + }) + break; + } + case "setScalesPageToFit": { + index = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_INDEX_MAP).get(eventData.viewTag); + if (GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index] + .zoomAccess == eventData.scalesPageToFit) { + return; + } + let tmpWebInfo: WebViewInfo = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY)[index]; + tmpWebInfo.zoomAccess == eventData.scalesPageToFit + let newViewInfo: WebViewInfo = copyWebViewInfo(tmpWebInfo); + let tempInfoArray: WebViewInfo[] = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_WEB_VIEW_ARRAY); + tempInfoArray[index] = newViewInfo; + } + break; + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/entity/Result.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/entity/Result.ts new file mode 100644 index 000000000000..dd9e38897da3 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/entity/Result.ts @@ -0,0 +1,16 @@ +export class Result { + public static success(data){ + return { + "errCode": 0, + "errMsg": "", + "data": data, + }; + } + + public static error(errCode, errMsg) { + return { + "errCode": errCode, + "errMsg": errMsg, + }; + } +}; \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/entity/TextInputDialogEntity.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/entity/TextInputDialogEntity.ts new file mode 100644 index 000000000000..39d272a0b68c --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/entity/TextInputDialogEntity.ts @@ -0,0 +1,7 @@ +export class TextInputDialogEntity { + message : string; + + constructor(msg: string) { + this.message = msg; + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/entity/WorkerMsgEntity.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/entity/WorkerMsgEntity.ts new file mode 100644 index 000000000000..608158d25be9 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/entity/WorkerMsgEntity.ts @@ -0,0 +1,141 @@ +export class ViewRect { + x: number + y: number + w: number + h: number + + constructor(x: number, y: number, w: number, h: number) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } +} + +export class Color4B { + r: number + g: number + b: number + a: number + + constructor(r: number, g: number, b: number, a: number) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } +} + +export class BaseWorkerMsgEntity { + module: string; + + function: string; + + constructor(module: string, func: string) { + this.module = module; + this.function = func; + } +} + +export class DialogMsgEntity extends BaseWorkerMsgEntity { + title: string; + + message: string; + + constructor(module: string, func: string) { + super(module, func); + } +} + +export class EditBoxMsgEntity extends BaseWorkerMsgEntity { + viewTag: number + + viewRect: ViewRect + + paddingW: number + paddingH: number + + visible: boolean + + text: string + fontSize: number + color: Color4B + fontPath: string + + placeHolderText: string + placeHolderSize: number + placeHolderColor: Color4B + placeHolderFontPath: string + + maxLength: number + + inputMode: number + + inputFlag: number + + constructor(module: string, func: string, viewTag?: number) { + super(module, func); + this.viewTag = viewTag; + } +} + +export class VideoPlayMsgEntity extends BaseWorkerMsgEntity { + viewTag: number + + url: string + isUrl: number + + isLoop: boolean + + viewRect: ViewRect + + visible: boolean + + isFullScreen: boolean + + seekTo: number + + keepAspectRatioEnabled: boolean + + constructor(module: string, func: string, viewTag: number) { + super(module, func); + this.viewTag = viewTag; + } +} + +export class WebViewMsgEntity extends BaseWorkerMsgEntity { + viewTag: number + + data: string + mimeType: string + encoding: string + baseURL: string + + url: string + + filePath: string + + viewRect: ViewRect + + visible: boolean + + opacity: number + + js: string + + scalesPageToFit: boolean + jsInterfaceScheme: string + + constructor(module: string, func: string, viewTag: number) { + super(module, func); + this.viewTag = viewTag; + } +} + +export class JumpMsgEntity extends BaseWorkerMsgEntity { + url: string; + + constructor(module: string, func: string) { + super(module, func); + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/napi/NapiHelper.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/napi/NapiHelper.ts new file mode 100644 index 000000000000..e70836332ec2 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/napi/NapiHelper.ts @@ -0,0 +1,119 @@ +import { Dialog } from '../components/dialog/DialogWorker' +import StringUtils from '../utils/StringUtils' +import { JumpManager } from '../system/appJump/JumpManager' +import { DeviceUtils } from '../system/device/DeviceUtils' +import { ApplicationManager } from '../system/application/ApplicationManager' +import { CocosEditBox } from '../components/editbox/CocosEditBox' +import { WebView } from '../components/webview/WebView' +import { VideoPlayer } from '../components/videoplayer/VideoPlayer' +import Accelerometer from '../system/sensor/AccelerometerUtils' +import Preferences from '../preferences/Preferences' + +export class NapiHelper { + + static registerFunctions(registerFunc : Function) { + NapiHelper.registerOthers(registerFunc); + NapiHelper.registerDeviceUtils(registerFunc); + NapiHelper.registerEditBox(registerFunc); + NapiHelper.registerWebView(registerFunc); + NapiHelper.registerVideoPlay(registerFunc); + NapiHelper.registerSensor(registerFunc); + NapiHelper.registerPreferences(registerFunc); + } + + private static registerOthers(registerFunc : Function) { + registerFunc('DiaLog.showDialog', Dialog.showDialog); + registerFunc('DiaLog.showTextInputDialog', Dialog.showTextInputDialog); + registerFunc('DiaLog.hideTextInputDialog', Dialog.hideTextInputDialog); + registerFunc('StringUtils.getWidth', StringUtils.getWidth); + registerFunc('ApplicationManager.exit', ApplicationManager.exit); + registerFunc('ApplicationManager.getVersionName', ApplicationManager.getVersionName); + registerFunc('JumpManager.openUrl', JumpManager.openUrl); + } + + + private static registerDeviceUtils(registerFunc : Function) { + registerFunc('DeviceUtils.getDpi', DeviceUtils.getDpi); + registerFunc('DeviceUtils.getSystemLanguage', DeviceUtils.getSystemLanguage); + registerFunc('DeviceUtils.startVibration', DeviceUtils.startVibration); + registerFunc('DeviceUtils.setKeepScreenOn', DeviceUtils.setKeepScreenOn); + registerFunc('DeviceUtils.isRoundScreen', DeviceUtils.isRoundScreen); + registerFunc('DeviceUtils.hasSoftKeys', DeviceUtils.hasSoftKeys); + registerFunc('DeviceUtils.isCutoutEnable', DeviceUtils.isCutoutEnable); + registerFunc('DeviceUtils.initScreenInfo', DeviceUtils.initScreenInfo); + registerFunc('DeviceUtils.getOrientation', DeviceUtils.getOrientation); + registerFunc('DeviceUtils.getCutoutHeight', DeviceUtils.getCutoutHeight); + registerFunc('DeviceUtils.getCutoutWidth', DeviceUtils.getCutoutWidth); + } + + private static registerEditBox(registerFunc : Function) { + registerFunc('CocosEditBox.createCocosEditBox', CocosEditBox.createCocosEditBox); + registerFunc('CocosEditBox.removeCocosEditBox', CocosEditBox.removeCocosEditBox); + registerFunc('CocosEditBox.setCurrentText', CocosEditBox.setCurrentText); + registerFunc('CocosEditBox.setEditBoxViewRect', CocosEditBox.setEditBoxViewRect); + registerFunc('CocosEditBox.setEditBoxVisible', CocosEditBox.setEditBoxVisible); + registerFunc('CocosEditBox.setEditBoxPlaceHolder', CocosEditBox.setEditBoxPlaceHolder); + registerFunc('CocosEditBox.setEditBoxFontSize', CocosEditBox.setEditBoxFontSize); + registerFunc('CocosEditBox.setEditBoxFontColor', CocosEditBox.setEditBoxFontColor); + registerFunc('CocosEditBox.setEditBoxFontPath', CocosEditBox.setEditBoxFontPath); + registerFunc('CocosEditBox.setEditBoxPlaceHolderFontSize', CocosEditBox.setEditBoxPlaceHolderFontSize); + registerFunc('CocosEditBox.setEditBoxPlaceHolderFontColor', CocosEditBox.setEditBoxPlaceHolderFontColor); + registerFunc('CocosEditBox.setEditBoxPlaceHolderFontPath', CocosEditBox.setEditBoxPlaceHolderFontPath); + registerFunc('CocosEditBox.setEditBoxMaxLength', CocosEditBox.setEditBoxMaxLength); + registerFunc('CocosEditBox.setNativeInputMode', CocosEditBox.setNativeInputMode); + registerFunc('CocosEditBox.setNativeInputFlag', CocosEditBox.setNativeInputFlag); + registerFunc('CocosEditBox.hideAllEditBox', CocosEditBox.hideAllEditBox); + } + + private static registerWebView(registerFunc : Function) { + registerFunc('WebView.createWebView', WebView.createWebView); + registerFunc('WebView.removeWebView', WebView.removeWebView); + registerFunc('WebView.setJavascriptInterfaceScheme', WebView.setJavascriptInterfaceScheme); + registerFunc('WebView.loadData', WebView.loadData); + registerFunc('WebView.loadURL', WebView.loadURL); + registerFunc('WebView.loadFile', WebView.loadFile); + registerFunc('WebView.stopLoading', WebView.stopLoading); + registerFunc('WebView.reload', WebView.reload); + registerFunc('WebView.canGoBack', WebView.canGoBack); + registerFunc('WebView.canGoForward', WebView.canGoForward); + registerFunc('WebView.goBack', WebView.goBack); + registerFunc('WebView.goForward', WebView.goForward); + registerFunc('WebView.setWebViewRect', WebView.setWebViewRect); + registerFunc('WebView.setVisible', WebView.setVisible); + registerFunc('WebView.setOpacityWebView', WebView.setOpacityWebView); + registerFunc('WebView.setBackgroundTransparent', WebView.setBackgroundTransparent); + registerFunc('WebView.evaluateJS', WebView.evaluateJS); + registerFunc('WebView.setScalesPageToFit', WebView.setScalesPageToFit); + } + + private static registerVideoPlay(registerFunc : Function) { + registerFunc('VideoPlayer.createVideoPlayer', VideoPlayer.createVideoPlayer); + registerFunc('VideoPlayer.removeVideoPlayer', VideoPlayer.removeVideoPlayer); + registerFunc('VideoPlayer.setURL', VideoPlayer.setURL); + registerFunc('VideoPlayer.setLooping', VideoPlayer.setLooping); + registerFunc('VideoPlayer.setVideoPlayerRect', VideoPlayer.setVideoPlayerRect); + registerFunc('VideoPlayer.play', VideoPlayer.play); + registerFunc('VideoPlayer.pause', VideoPlayer.pause); + registerFunc('VideoPlayer.stop', VideoPlayer.stop); + registerFunc('VideoPlayer.setVisible', VideoPlayer.setVisible); + registerFunc('VideoPlayer.requestFullscreen', VideoPlayer.requestFullscreen); + registerFunc('VideoPlayer.seekTo', VideoPlayer.seekTo); + registerFunc('VideoPlayer.setKeepAspectRatioEnabled', VideoPlayer.setKeepAspectRatioEnabled); + } + + private static registerSensor(registerFunc : Function) { + registerFunc('Accelerometer.enable', Accelerometer.enable); + registerFunc('Accelerometer.disable', Accelerometer.disable); + } + + private static registerPreferences(registerFunc : Function) { + registerFunc('Preferences.get', Preferences.get); + registerFunc('Preferences.getAll', Preferences.getAll); + registerFunc('Preferences.put', Preferences.put); + registerFunc('Preferences.has', Preferences.has); + registerFunc('Preferences.delete', Preferences.delete); + registerFunc('Preferences.flush', Preferences.flush); + registerFunc('Preferences.clear', Preferences.clear); + } + +} diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/preferences/Preferences.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/preferences/Preferences.ts new file mode 100644 index 000000000000..e95e89b29d10 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/preferences/Preferences.ts @@ -0,0 +1,218 @@ +import Logger from '../utils/Logger'; +import data_preferences from '@ohos.data.preferences'; +import { BusinessError } from '@ohos.base'; +import common from '@ohos.app.ability.common'; +import { GlobalContext, GlobalContextConstants } from '../common/GlobalContext'; + +let log: Logger = new Logger(0x0001, "Preferences"); +let preferences: data_preferences.Preferences | null = null; +const PREFS_NAME: string = "Cocos2dxPreferences"; + +export default class Preferences { + + // 通过 preferencesName 获取Preferences实例 + static getPreferences(): data_preferences.Preferences { + let context: common.UIAbilityContext = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT); + try { + preferences = data_preferences.getPreferencesSync(context, {name: PREFS_NAME}); + log.info("Succeeded in getting preferences."); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to get preferences. code =" + code + ", message =" + message); + } + return preferences; + } + + /* + 通过 preferencesName 从缓存中移出指定的Preferences实例,若Preferences实例有对应的持久化文件,则同时删除其持久化文件。使用Promise异步回调。 + 调用该接口后,不建议再使用旧的Preferences实例进行数据操作,否则会出现数据一致性问题,应将Preferences实例置为null,系统将会统一回收。 + */ + static deletePreferences(): void { + let context: common.UIAbilityContext = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT); + try { + data_preferences.deletePreferences(context, PREFS_NAME).then(() => { + log.info("Succeeded in deleting preferences."); + }); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to delete preferences. code =" + code + ", message =" + message); + } + } + + /* + 通过 preferencesName 从缓存中移出指定的Preferences实例,使用Promise异步回调。 + 应用首次调用getPreferences接口获取某个Preferences实例后,该实例会被会被缓存起来,后续再次getPreferences时不会再次从持久化文件中读取, + 直接从缓存中获取Preferences实例。调用此接口移出缓存中的实例之后,再次getPreferences将会重新读取持久化文件,生成新的Preferences实例。 + 调用该接口后,不建议再使用旧的Preferences实例进行数据操作,否则会出现数据一致性问题,应将Preferences实例置为null,系统将会统一回收。 + */ + static removePreferencesFromCache(): void { + let context: common.UIAbilityContext = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT); + try { + data_preferences.removePreferencesFromCacheSync(context, PREFS_NAME); + log.info("Succeeded in removing preferences."); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to remove preferences. code =" + code + ", message =" + message); + } + } + + // 从缓存的Preferences实例中获取键对应的值,如果值为null或者非默认值类型,返回默认数据defValue + static get(key: string, defValue: data_preferences.ValueType): data_preferences.ValueType { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + let data = preferences.getSync(key, defValue); + log.info("Succeeded in getting value of 'startup'. Data: " + data); + return data; + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to get value of 'startup'. code =" + code + ", message =" + message); + return defValue; + } + } + + // 将数据写入缓存的Preferences实例中,可通过flush将Preferences实例持久化 + static put(key: string, value: data_preferences.ValueType): void { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + preferences.putSync(key, value); + log.info("Succeeded in put the key."); + Preferences.flush(); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to put. code =" + code + ", message =" + message); + } + } + + // 从缓存的Preferences实例中获取所有键值数据。 + static getAll(): string | undefined { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + let object = preferences.getAllSync(); + let allKeys = getObjKeys(object); + log.info('getAll keys = ' + allKeys); + log.info("getAll object = " + JSON.stringify(object)); + return JSON.stringify(object); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to getAll. code =" + code + ", message =" + message); + return undefined; + } + } + + // 检查缓存的Preferences实例中是否包含名为给定Key的存储键值对 + static has(key: string): boolean { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + let val = preferences.hasSync(key); + if (val) { + log.info("The key 'startup' is contained."); + return true; + } else { + log.info("The key 'startup' dose not contain."); + return false; + } + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to has. code =" + code + ", message =" + message); + return false; + } + } + + // 从缓存的Preferences实例中删除名为给定Key的存储键值对,可通过flush将Preferences实例持久化 + static delete(key: string): void { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + preferences.deleteSync(key); + log.info("Succeeded in deleting the key."); + Preferences.flush(); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to delete. code =" + code + ", message =" + message); + } + } + + // 将缓存的Preferences实例中的数据异步存储到用户首选项的持久化文件中,使用Promise异步回调。 + static flush(): void { + if (preferences === null) { + Preferences.getPreferences(); + } + preferences.flush().then(()=>{ + log.info("Succeeded in flushing."); + }); + } + + // 清除缓存的Preferences实例中的所有数据,可通过flush将Preferences实例持久化,使用Promise异步回调。 + static clear(): void { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + preferences.clearSync(); + log.info("Succeeded in clearing."); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to clear. code =" + code + ", message =" + message); + } + } + + + // 订阅数据变更,订阅的Key的值发生变更后,在执行flush方法后,触发callback回调。 + static onChange(cb: Function): void { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + preferences.on('change', (key: string) => { + log.info("The key " + key + " changed."); + cb(key); + }); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to flush. code =" + code + ", message =" + message); + } + } + + // 取消订阅数据变更。 + static offChange(cb: Function): void { + if (preferences === null) { + Preferences.getPreferences(); + } + try { + preferences.off('change', (key: string) => { + log.info("The key " + key + " changed."); + cb(key); + }); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + log.error("Failed to flush. code =" + code + ", message =" + message); + } + } +} + +// 由于ArkTS中无Object.keys,且无法使用for..in... +// 若报ArkTS问题,请将此方法单独抽离至一个ts文件中并暴露,在需要用到的ets文件中引入使用 +function getObjKeys(obj: Object): string[] { + let keys = Object.keys(obj); + return keys; +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/system/appJump/JumpManager.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/system/appJump/JumpManager.ts new file mode 100644 index 000000000000..dfa95dfaa5ab --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/system/appJump/JumpManager.ts @@ -0,0 +1,19 @@ +import type { ThreadWorkerGlobalScope } from '@ohos.worker'; +import { JumpMsgEntity } from '../../entity/WorkerMsgEntity'; + +export class JumpManager { + + static MODULE_NAME: string = 'JumpManager'; + + private static workerPort: ThreadWorkerGlobalScope; + + static init(workerPort: ThreadWorkerGlobalScope) : void { + JumpManager.workerPort = workerPort; + } + + static openUrl(url: string) : void { + let jumpMsgEntity: JumpMsgEntity = new JumpMsgEntity(JumpManager.MODULE_NAME, 'openUrl'); + jumpMsgEntity.url = url; + JumpManager.workerPort.postMessage(jumpMsgEntity); + } +} diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/system/appJump/JumpManagerMsg.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/system/appJump/JumpManagerMsg.ts new file mode 100644 index 000000000000..ea373af90811 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/system/appJump/JumpManagerMsg.ts @@ -0,0 +1,31 @@ +import type common from '@ohos.app.ability.common'; +import { GlobalContext, GlobalContextConstants } from '../../common/GlobalContext'; +import {Result} from "../../entity/Result" +import type { JumpMsgEntity } from '../../entity/WorkerMsgEntity'; +import Logger from '../../utils/Logger' + +let log: Logger = new Logger(0x0001, "JumpManagerMsg"); + +export function handleJumpManagerMsg(eventData: JumpMsgEntity) : void { + switch (eventData.function) { + case "openUrl": + openUrl(eventData.url); + break; + default: + log.error('%{public}s has not implement yet', eventData.function); + } +} + +function openUrl(url: string): void { + let context: common.UIAbilityContext = GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT); + let wantInfo = { + 'action': 'ohos.want.action.viewData', + 'entities': ['entity.system.browsable'], + 'uri': url + } + context.startAbility(wantInfo).then(() => { + log.info('%{public}s', JSON.stringify(Result.success({}))); + }).catch((err) => { + log.error('openUrl : err : %{public}s', JSON.stringify(Result.error(-1, JSON.stringify(err))) ?? ''); + }); +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/system/application/ApplicationManager.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/system/application/ApplicationManager.ts new file mode 100644 index 000000000000..ffe0826a5f7a --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/system/application/ApplicationManager.ts @@ -0,0 +1,55 @@ +import bundleManager from '@ohos.bundle.bundleManager'; +import type { ThreadWorkerGlobalScope } from '@ohos.worker'; +import { BaseWorkerMsgEntity } from '../../entity/WorkerMsgEntity'; +import { common } from '@kit.AbilityKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { GlobalContext, GlobalContextConstants } from '../../common/GlobalContext'; + +export class ApplicationManager { + static MODULE_NAME: string = 'ApplicationManager'; + + private static workerPort: ThreadWorkerGlobalScope; + + static init(workerPort: ThreadWorkerGlobalScope): void { + ApplicationManager.workerPort = workerPort; + } + + static exit(): void { + let workerMsg: BaseWorkerMsgEntity = new BaseWorkerMsgEntity(ApplicationManager.MODULE_NAME, 'exit'); + ApplicationManager.workerPort.postMessage(workerMsg); + } + + static getVersionName(): string { + let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT; + return bundleManager.getBundleInfoForSelfSync(bundleFlags).versionName; + } +} + +export function handleApplicationMsg(eventData: BaseWorkerMsgEntity): void { + switch (eventData.function) { + case "exit": + terminateSelf(); + break; + default: + console.error('%{public}s has not implement yet', eventData.function); + } +} + +function terminateSelf(): void { + try { + let context: common.UIAbilityContext = + GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT); + context.terminateSelf((err: BusinessError) => { + if (err.code) { + console.error(`terminateSelf failed, code is ${err.code}, message is ${err.message}`); + return; + } + console.info('terminateSelf succeed'); + }); + } catch (err) { + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + console.error(`terminateSelf failed, code is ${code}, message is ${message}`); + } +} + diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/system/device/DeviceUtils.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/system/device/DeviceUtils.ts new file mode 100644 index 000000000000..b778f1584810 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/system/device/DeviceUtils.ts @@ -0,0 +1,164 @@ +import display from '@ohos.display' +import i18n from '@ohos.i18n'; +import vibrator from '@ohos.vibrator'; +import Logger from '../../utils/Logger'; +import window from '@ohos.window'; +import { GlobalContext, GlobalContextConstants } from '../../common/GlobalContext'; +import { Rect } from '@ohos.application.AccessibilityExtensionAbility'; + +let log = new Logger(0x0001, "DeviceUtils"); + +export class DeviceUtils { + static MODULE_NAME: string = 'DeviceUtils'; + static _roundScreen: boolean = false; + static _hasSoftKeys: boolean = false; + static _isCutoutEnable: boolean = false; + static _cutoutLeft: number; + static _cutoutWidth: number; + static _cutoutTop: number; + static _cutoutHeight: number; + + static getDpi(): number { + return display.getDefaultDisplaySync().densityDPI; + } + + static getSystemLanguage(): string { + return i18n.System.getSystemLanguage(); + } + + static startVibration(time: number) { + try { + vibrator.startVibration({ + type: 'time', + duration: time * 1000, // Seconds to milliseconds + }, { + id: 0, + usage: 'unknown' + }, (error) => { + if (error) { + log.error('vibrate fail, error.code: %{public}d, error.message: %{public}s', error.code, error.message); + return; + } + }); + } catch (err) { + log.error('error.code: %{public}d, error.message: %{public}s', err.code, err.message); + } + } + + static setKeepScreenOn(value: boolean) { + let windowClass = null; + try { + window.getLastWindow(GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT), (err, data) => { //获取窗口实例 + if (err.code) { + log.error('Failed to obtain last window when setKeepScreenOn. Cause:%{public}s', JSON.stringify(err)); + return; + } + windowClass = data; + // Sets whether the screen is always on. + let keepScreenOnPromise = windowClass.setWindowKeepScreenOn(value); + Promise.all([keepScreenOnPromise]).then(() => { + log.info('Succeeded in setKeepScreenOn, value:%{public}s', value); + }).catch((err) => { + log.error('Failed to setKeepScreenOn, cause:%{public}s', JSON.stringify(err)); + }); + }); + } catch (exception) { + log.error('Failed to get or set the window when setKeepScreenOn, cause:%{public}s', JSON.stringify(exception)); + } + } + + static isRoundScreen() : boolean { + return DeviceUtils._roundScreen; + } + + static hasSoftKeys() : boolean { + return DeviceUtils._hasSoftKeys; + } + + static isCutoutEnable() : boolean { + return DeviceUtils._isCutoutEnable; + } + + static initScreenInfo() : void { + let windowClass = null; + try { + window.getLastWindow(GlobalContext.loadGlobalThis(GlobalContextConstants.COCOS2DX_ABILITY_CONTEXT), (err, data) => { + if (err.code) { + log.error('Failed to obtain last window when initScreenInfo. Cause:%{public}s', JSON.stringify(err)); + return; + } + + windowClass = data; + let windowProperties: window.WindowProperties = windowClass.getWindowProperties(); + DeviceUtils._roundScreen = windowProperties.isRoundCorner; + let rect: Rect = windowProperties.windowRect; + if(rect.top + rect.height < display.getDefaultDisplaySync().height) { + DeviceUtils._hasSoftKeys = true; + } else { + DeviceUtils._hasSoftKeys = false; + } + }); + } catch (exception) { + log.error('Failed to get or set the window when initScreenInfo, cause:%{public}s', JSON.stringify(exception)); + } + + display.getDefaultDisplaySync().getCutoutInfo().then((data) => { + if(data.boundingRects.length == 0) { + DeviceUtils._isCutoutEnable = false; + return; + } + + DeviceUtils._isCutoutEnable = true; + DeviceUtils._cutoutLeft = data.boundingRects[0].left; + DeviceUtils._cutoutTop = data.boundingRects[0].top; + DeviceUtils._cutoutWidth = data.boundingRects[0].width; + DeviceUtils._cutoutHeight = data.boundingRects[0].height; + }).catch((err) => { + log.error('Failed to obtain all the display objects. Code: ' + JSON.stringify(err)); + }); + } + + static getOrientation() : number { + let orientation: display.Orientation = display.getDefaultDisplaySync().orientation; + + // If the system enumeration value changes, the processing logic in the C++ code needs to be changed. Therefore, the mapping is performed again. + if(orientation == display.Orientation.PORTRAIT) { + return 0; + } + + if(orientation == display.Orientation.LANDSCAPE) { + return 1; + } + + if(orientation == display.Orientation.PORTRAIT_INVERTED) { + return 2; + } + + if(orientation == display.Orientation.LANDSCAPE_INVERTED) { + return 3; + } + + return 4; + } + + static getCutoutHeight() : number { + if(DeviceUtils._cutoutHeight) { + let height = DeviceUtils._cutoutTop + DeviceUtils._cutoutHeight; + return height; + } + return 0; + } + + static getCutoutWidth() : number { + if(!DeviceUtils._cutoutWidth) { + return 0; + } + + let disPlayWidth = display.getDefaultDisplaySync().width; + if(DeviceUtils._cutoutLeft + DeviceUtils._cutoutWidth > disPlayWidth - DeviceUtils._cutoutLeft) { + return disPlayWidth - DeviceUtils._cutoutLeft; + } + + return DeviceUtils._cutoutLeft + DeviceUtils._cutoutWidth; + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/system/sensor/AccelerometerUtils.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/system/sensor/AccelerometerUtils.ts new file mode 100644 index 000000000000..56309439f011 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/system/sensor/AccelerometerUtils.ts @@ -0,0 +1,55 @@ +import { getContext } from "libnativerender.so"; +import { ContextType } from "../../common/Constants" +import sensor from '@ohos.sensor'; +import display from '@ohos.display'; +import {Result} from "../../entity/Result" +import Logger from '../../utils/Logger' + +let log: Logger = new Logger(0x0001, "AccelerometerUtils"); + +const accUtils = getContext(ContextType.SENSOR_API); + +export default class Accelerometer { + + private static instance = new Accelerometer(); + + static getInstance() : Accelerometer { + return Accelerometer.instance; + } + + static enable(intervalTime: number) : void { + try { + /* HarmonyOS allow multiple subscriptions, but the game only need one + so if the interval changed, cancel subscription and redo with the new interval */ + sensor.off(sensor.SensorId.ACCELEROMETER); + sensor.on(sensor.SensorId.ACCELEROMETER, function (data) { + let rotation = display.getDefaultDisplaySync().rotation; + if (rotation === 0) { + // Display device screen rotation 0° + accUtils.onAccelerometerCallBack(data.x, data.y, data.z, intervalTime); + } else if (rotation === 1) { + // Display device screen rotation 90° + accUtils.onAccelerometerCallBack(data.y, -data.x, data.z, intervalTime); + } else if (rotation === 2) { + // Display device screen rotation 180° + accUtils.onAccelerometerCallBack(-data.x, -data.y, data.z, intervalTime); + } else if (rotation === 3) { + // Display device screen rotation 270° + accUtils.onAccelerometerCallBack(-data.y, data.x, data.z, intervalTime); + } else { + log.error('Accelerometer init fail, err: %{public}s', JSON.stringify(Result.error(-1, 'unsupported rotation: ' + rotation))); + } + }, { interval: intervalTime }); + } catch (err) { + log.error('Accelerometer init fail, err: %{public}s', JSON.stringify(Result.error(-1, JSON.stringify(err))) ?? ''); + } + } + + static disable() : void { + try { + sensor.off(sensor.SensorId.ACCELEROMETER); + } catch (err) { + log.error('Accelerometer off fail, err: %{public}s', JSON.stringify(Result.error(-1, JSON.stringify(err))) ?? ''); + } + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/utils/Logger.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/utils/Logger.ts new file mode 100644 index 000000000000..6277b0e1ab52 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/utils/Logger.ts @@ -0,0 +1,27 @@ +import hilog from '@ohos.hilog' + +export default class Logger { + private domain: number + private prefix: string + + constructor(domain : number, prefix: string) { + this.domain = domain + this.prefix = prefix + } + + debug(format: string, ...args: any[]) { + hilog.debug(this.domain, this.prefix, format, args) + } + + info(format: string, ...args: any[]) { + hilog.info(this.domain, this.prefix, format, args) + } + + warn(format: string, ...args: any[]) { + hilog.warn(this.domain, this.prefix, format, args) + } + + error(format: string, ...args: any[]) { + hilog.error(this.domain, this.prefix, format, args) + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/utils/StringUtils.ts b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/utils/StringUtils.ts new file mode 100644 index 000000000000..4e3d20ec55e1 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/utils/StringUtils.ts @@ -0,0 +1,8 @@ +import measure from '@ohos.measure'; + +export default class StringUtils { + + public static getWidth(text: string, fontSize: number, fontWeight: number): number { + return measure.measureText({ textContent: text, fontSize: fontSize + 'px', fontWeight: fontWeight }); + } +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/utils/WorkerMsgUtils.ets b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/utils/WorkerMsgUtils.ets new file mode 100644 index 000000000000..b9cac7106b18 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/utils/WorkerMsgUtils.ets @@ -0,0 +1,42 @@ +import worker, { MessageEvents } from '@ohos.worker'; +import Logger from './Logger'; +import { handleEditBoxMsg } from '../components/editbox/EditBoxMsg' +import { handleWebViewMsg } from '../components/webview/WebViewMsg' +import { handleVideoPlayMsg } from '../components/videoplayer/VideoPlayerMsg' +import { handleDialogMsg } from '../components/dialog/DialogMsg' +import { handleJumpManagerMsg } from '../system/appJump/JumpManagerMsg' +import { handleApplicationMsg } from '../system/application/ApplicationManager' +import { BaseWorkerMsgEntity, DialogMsgEntity, EditBoxMsgEntity, JumpMsgEntity, VideoPlayMsgEntity, WebViewMsgEntity } from '../entity/WorkerMsgEntity'; + +export class WorkerMsgUtils { + static workPort = worker.workerPort; + static log : Logger = new Logger(0x0001, 'WorkerMsgUtils') + + static async recvWorkerThreadMessage(event: MessageEvents) { + let eventData: BaseWorkerMsgEntity = event.data; + WorkerMsgUtils.log.debug('mainThread receiveMsg, module:%{public}s, function:%{public}s', eventData.module, eventData.function); + + switch (eventData.module) { + case 'EditBox': + handleEditBoxMsg(eventData as EditBoxMsgEntity); + break; + case "Dialog": + handleDialogMsg(eventData as DialogMsgEntity); + break; + case 'WebView': + handleWebViewMsg(eventData as WebViewMsgEntity); + break; + case 'VideoPlay': + handleVideoPlayMsg(eventData as VideoPlayMsgEntity); + break; + case 'JumpManager': + handleJumpManagerMsg(eventData as JumpMsgEntity); + break; + case 'ApplicationManager': + handleApplicationMsg(eventData as BaseWorkerMsgEntity); + break; + default: + WorkerMsgUtils.log.error('%{public}s has not implement yet', eventData.module); + } + } +} diff --git a/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/module.json5 b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/module.json5 new file mode 100644 index 000000000000..e06cbeaffaa9 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "libSysCapabilities", + "type": "har", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ] + } +} diff --git a/tests/lua-tests/project/proj.ohos/oh-package.json5 b/tests/lua-tests/project/proj.ohos/oh-package.json5 new file mode 100644 index 000000000000..68eed70e4e07 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/oh-package.json5 @@ -0,0 +1,12 @@ +{ + "modelVersion": "5.0.0", + "license": "", + "devDependencies": {}, + "author": "", + "name": "proj.ohos", + "description": "Please describe the basic information.", + "main": "", + "version": "1.0.0", + "dynamicDependencies": {}, + "dependencies": {} +} \ No newline at end of file diff --git a/tests/lua-tests/src/AssetsManagerTest/AssetsManagerTest.lua b/tests/lua-tests/src/AssetsManagerTest/AssetsManagerTest.lua index 15f3db45e5ce..10ca86650fca 100644 --- a/tests/lua-tests/src/AssetsManagerTest/AssetsManagerTest.lua +++ b/tests/lua-tests/src/AssetsManagerTest/AssetsManagerTest.lua @@ -17,7 +17,7 @@ local function updateLayer() local support = false if (cc.PLATFORM_OS_IPHONE == targetPlatform) or (cc.PLATFORM_OS_IPAD == targetPlatform) or (cc.PLATFORM_OS_WINDOWS == targetPlatform) or (cc.PLATFORM_OS_ANDROID == targetPlatform) - or (cc.PLATFORM_OS_MAC == targetPlatform) then + or (cc.PLATFORM_OS_MAC == targetPlatform) or (cc.PLATFORM_OS_OHOS == targetPlatform) then support = true end diff --git a/tests/lua-tests/src/Camera3DTest/Camera3DTest.lua b/tests/lua-tests/src/Camera3DTest/Camera3DTest.lua index 0583c23b7e6e..52eef3ad1d43 100644 --- a/tests/lua-tests/src/Camera3DTest/Camera3DTest.lua +++ b/tests/lua-tests/src/Camera3DTest/Camera3DTest.lua @@ -773,7 +773,7 @@ function FogTestDemo:createLayer3D() self._layer3D:setCameraMask(2) local targetPlatform = cc.Application:getInstance():getTargetPlatform() - if targetPlatform == cc.PLATFORM_OS_ANDROID or targetPlatform == cc.PLATFORM_OS_WINRT or targetPlatform == cc.PLATFORM_OS_WP8 then + if targetPlatform == cc.PLATFORM_OS_ANDROID or targetPlatform == cc.PLATFORM_OS_WINRT or targetPlatform == cc.PLATFORM_OS_WP8 or targetPlatform == cc.PLATFORM_OS_OHOS then self._backToForegroundListener = cc.EventListenerCustom:create("event_renderer_recreated", function (eventCustom) -- body cc.Director:getInstance():setClearColor(cc.c4f(0.5,0.5,0.5,1)) @@ -805,7 +805,7 @@ function FogTestDemo:onExit() self._camera = nil end local targetPlatform = cc.Application:getInstance():getTargetPlatform() - if targetPlatform == cc.PLATFORM_OS_ANDROID or targetPlatform == cc.PLATFORM_OS_WINRT or targetPlatform == cc.PLATFORM_OS_WP8 then + if targetPlatform == cc.PLATFORM_OS_ANDROID or targetPlatform == cc.PLATFORM_OS_WINRT or targetPlatform == cc.PLATFORM_OS_WP8 or targetPlatform == cc.PLATFORM_OS_OHOS then cc.Director:getInstance():getEventDispatcher():removeEventListener(self._backToForegroundListener) end end diff --git a/tests/lua-tests/src/CocosDenshionTest/CocosDenshionTest.lua b/tests/lua-tests/src/CocosDenshionTest/CocosDenshionTest.lua index 0d058f8be70c..70c3b04fde61 100644 --- a/tests/lua-tests/src/CocosDenshionTest/CocosDenshionTest.lua +++ b/tests/lua-tests/src/CocosDenshionTest/CocosDenshionTest.lua @@ -4,6 +4,8 @@ local MUSIC_FILE = nil local targetPlatform = cc.Application:getInstance():getTargetPlatform() if (cc.PLATFORM_OS_IPHONE == targetPlatform) or (cc.PLATFORM_OS_IPAD == targetPlatform) then MUSIC_FILE = "background.caf" +elseif (cc.PLATFORM_OS_OHOS == targetPlatform) then + MUSIC_FILE = "background.wav" else MUSIC_FILE = "background.mp3" end diff --git a/tests/lua-tests/src/ExtensionTest/ExtensionTest.lua b/tests/lua-tests/src/ExtensionTest/ExtensionTest.lua index e20f04c7c2de..2df1b9dae393 100644 --- a/tests/lua-tests/src/ExtensionTest/ExtensionTest.lua +++ b/tests/lua-tests/src/ExtensionTest/ExtensionTest.lua @@ -1215,13 +1215,13 @@ local function ExtensionsMainLayer() cc.MenuItemFont:setFontSize(24) local targetPlatform = cc.Application:getInstance():getTargetPlatform() local bSupportWebSocket = false - if (cc.PLATFORM_OS_IPHONE == targetPlatform) or (cc.PLATFORM_OS_IPAD == targetPlatform) or (cc.PLATFORM_OS_ANDROID == targetPlatform) or (cc.PLATFORM_OS_WINDOWS == targetPlatform) or (cc.PLATFORM_OS_MAC == targetPlatform) then + if (cc.PLATFORM_OS_IPHONE == targetPlatform) or (cc.PLATFORM_OS_IPAD == targetPlatform) or (cc.PLATFORM_OS_ANDROID == targetPlatform) or (cc.PLATFORM_OS_WINDOWS == targetPlatform) or (cc.PLATFORM_OS_MAC == targetPlatform) or (cc.PLATFORM_OS_OHOS == targetPlatform) then bSupportWebSocket = true end local bSupportEdit = false if (cc.PLATFORM_OS_IPHONE == targetPlatform) or (cc.PLATFORM_OS_IPAD == targetPlatform) or (cc.PLATFORM_OS_ANDROID == targetPlatform) or (cc.PLATFORM_OS_WINDOWS == targetPlatform) or - (cc.PLATFORM_OS_MAC == targetPlatform) or (cc.PLATFORM_OS_TIZEN == targetPlatform) then + (cc.PLATFORM_OS_MAC == targetPlatform) or (cc.PLATFORM_OS_TIZEN == targetPlatform) or (cc.PLATFORM_OS_OHOS == targetPlatform) then bSupportEdit = true end for i = 1, ExtensionTestEnum.TEST_MAX_COUNT do diff --git a/tests/lua-tests/src/NewAudioEngineTest/NewAudioEngineTest.lua b/tests/lua-tests/src/NewAudioEngineTest/NewAudioEngineTest.lua index b2d1fd0f71ab..ce02f807ff5c 100644 --- a/tests/lua-tests/src/NewAudioEngineTest/NewAudioEngineTest.lua +++ b/tests/lua-tests/src/NewAudioEngineTest/NewAudioEngineTest.lua @@ -455,6 +455,9 @@ function InvalidAudioFileTest.create() if (cc.PLATFORM_OS_ANDROID == targetPlatform) then ccexp.AudioEngine:play2d("background.caf") end + if (cc.PLATFORM_OS_OHOS == targetPlatform) then + ccexp.AudioEngine:play2d("background.wav") + end end local playItem1 = cc.MenuItemFont:create("play unsupported media type") diff --git a/tests/lua-tests/src/Particle3DTest/Particle3DTest.lua b/tests/lua-tests/src/Particle3DTest/Particle3DTest.lua index 6b7382fcf489..43e8c9b60ce6 100644 --- a/tests/lua-tests/src/Particle3DTest/Particle3DTest.lua +++ b/tests/lua-tests/src/Particle3DTest/Particle3DTest.lua @@ -8,7 +8,7 @@ local function baseInit(self) self._angle = 0 local targetPlatform = cc.Application:getInstance():getTargetPlatform() - if targetPlatform == cc.PLATFORM_OS_MAC or targetPlatform == cc.PLATFORM_OS_IPHONE or targetPlatform == cc.PLATFORM_OS_IPAD or targetPlatform == cc.PLATFORM_OS_TIZEN then + if targetPlatform == cc.PLATFORM_OS_MAC or targetPlatform == cc.PLATFORM_OS_IPHONE or targetPlatform == cc.PLATFORM_OS_IPAD or targetPlatform == cc.PLATFORM_OS_TIZEN or targetPlatform == cc.PLATFORM_OS_OHOS then cc.FileUtils:getInstance():addSearchPath("Particle3D/materials") cc.FileUtils:getInstance():addSearchPath("Particle3D/scripts") else diff --git a/tests/lua-tests/src/Scene3DTest/Scene3DTest.lua b/tests/lua-tests/src/Scene3DTest/Scene3DTest.lua index ac6b05fc6ce9..a6a58168c9d8 100644 --- a/tests/lua-tests/src/Scene3DTest/Scene3DTest.lua +++ b/tests/lua-tests/src/Scene3DTest/Scene3DTest.lua @@ -354,7 +354,7 @@ function Scene3DTest:create3DWorld() self:addChild(self._skyBox) local targetPlatform = cc.Application:getInstance():getTargetPlatform() - if targetPlatform == cc.PLATFORM_OS_ANDROID or targetPlatform == cc.PLATFORM_OS_WINRT or targetPlatform == cc.PLATFORM_OS_WP8 then + if targetPlatform == cc.PLATFORM_OS_ANDROID or targetPlatform == cc.PLATFORM_OS_WINRT or targetPlatform == cc.PLATFORM_OS_WP8 or targetPlatform == cc.PLATFORM_OS_OHOS then self._backToForegroundListener = cc.EventListenerCustom:create("event_renderer_recreated", function (eventCustom) local state = self._skyBox:getGLProgramState() @@ -658,7 +658,7 @@ end function Scene3DTest:onExit() local targetPlatform = cc.Application:getInstance():getTargetPlatform() - if targetPlatform == cc.PLATFORM_OS_ANDROID or targetPlatform == cc.PLATFORM_OS_WINRT or targetPlatform == cc.PLATFORM_OS_WP8 then + if targetPlatform == cc.PLATFORM_OS_ANDROID or targetPlatform == cc.PLATFORM_OS_WINRT or targetPlatform == cc.PLATFORM_OS_WP8 or targetPlatform == cc.PLATFORM_OS_OHOS then cc.Director:getInstance():getEventDispatcher():removeEventListener(self._backToForegroundListener) end end diff --git a/tests/lua-tests/src/controller.lua b/tests/lua-tests/src/controller.lua index 5b51b7739f66..850de2807b41 100644 --- a/tests/lua-tests/src/controller.lua +++ b/tests/lua-tests/src/controller.lua @@ -1,18 +1,28 @@ - +local currPlatform = cc.Application:getInstance():getTargetPlatform() +cc.PLATFORM_OS_OHOS = 12 +if (cc.PLATFORM_OS_OHOS == currPlatform) then + local jit = require("jit") + jit.off() + print("jit.off()") +end -- avoid memory leak collectgarbage("setpause", 100) collectgarbage("setstepmul", 5000) ---------------- -- run +if (cc.PLATFORM_OS_OHOS ~= currPlatform) then cc.FileUtils:getInstance():addSearchPath("src") +end CC_USE_DEPRECATED_API = true require "cocos.init" local director = cc.Director:getInstance() local glView = director:getOpenGLView() +local widthx = 1024 +local heighty = 2112 if nil == glView then - glView = cc.GLViewImpl:createWithRect("Lua Tests", cc.rect(0,0,900,640)) + glView = cc.GLViewImpl:createWithRect("Lua Tests", cc.rect(0,0,widthx,heighty)) director:setOpenGLView(glView) end @@ -24,10 +34,10 @@ director:setAnimationInterval(1.0 / 60) local screenSize = glView:getFrameSize() -local designSize = {width = 480, height = 320} +local designSize = {width = widthx / 2, height = heighty / 2} if screenSize.height > 320 then - local resourceSize = {width = 960, height = 640} + local resourceSize = {width = widthx, height = heighty} cc.Director:getInstance():setContentScaleFactor(resourceSize.height/designSize.height) end diff --git a/tests/lua-tests/src/mainMenu.lua b/tests/lua-tests/src/mainMenu.lua index 9968cb7dc528..b3efb65595d0 100644 --- a/tests/lua-tests/src/mainMenu.lua +++ b/tests/lua-tests/src/mainMenu.lua @@ -11,7 +11,10 @@ require "AssetsManagerTest/AssetsManagerTest" require "AssetsManagerExTest/AssetsManagerExTest" require "BillBoardTest/BillBoardTest" require "BugsTest/BugsTest" +local currPlatform = cc.Application:getInstance():getTargetPlatform() +if (cc.PLATFORM_OS_OHOS ~= currPlatform) then require "ByteCodeEncryptTest/ByteCodeEncryptTest" +end require "Camera3DTest/Camera3DTest" require "ClickAndMoveTest/ClickAndMoveTest" require "CocosDenshionTest/CocosDenshionTest" @@ -71,8 +74,7 @@ local CurPos = {x = 0, y = 0} local BeginPos = {x = 0, y = 0} local audioEndineSupported = false -local currPlatform = cc.Application:getInstance():getTargetPlatform() -if (cc.PLATFORM_OS_WINDOWS == currPlatform or cc.PLATFORM_OS_MAC == currPlatform or cc.PLATFORM_OS_IPHONE == currPlatform or cc.PLATFORM_OS_IPAD == currPlatform or cc.PLATFORM_OS_ANDROID == currPlatform) then +if (cc.PLATFORM_OS_WINDOWS == currPlatform or cc.PLATFORM_OS_MAC == currPlatform or cc.PLATFORM_OS_IPHONE == currPlatform or cc.PLATFORM_OS_IPAD == currPlatform or cc.PLATFORM_OS_ANDROID == currPlatform or cc.PLATFORM_OS_OHOS == currPlatform) then audioEndineSupported = true end @@ -212,13 +214,13 @@ function CreateTestMenu() end if obj.name == "VideoPlayerTest" then - if cc.PLATFORM_OS_IPHONE ~= targetPlatform and cc.PLATFORM_OS_ANDROID ~= targetPlatform then + if cc.PLATFORM_OS_IPHONE ~= targetPlatform and cc.PLATFORM_OS_ANDROID ~= targetPlatform and cc.PLATFORM_OS_OHOS ~= targetPlatform then testMenuItem:setEnabled(false) end end if obj.name == "WebViewTest" then - if cc.PLATFORM_OS_IPHONE ~= targetPlatform and cc.PLATFORM_OS_ANDROID ~= targetPlatform then + if cc.PLATFORM_OS_IPHONE ~= targetPlatform and cc.PLATFORM_OS_ANDROID ~= targetPlatform and cc.PLATFORM_OS_OHOS ~= targetPlatform then testMenuItem:setEnabled(false) end end