diff --git a/.appveyor.yml b/.appveyor.yml index 820224f3e153..9ac14e255a26 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -8,9 +8,9 @@ environment: PYTHON_VERSION: "2.7.13" PYTHON_ARCH: "32" matrix: - - build_type: windows32_cmake_test - - build_type: android_cpp_tests - - build_type: android_lua_tests +# - build_type: windows32_cmake_test + # - build_type: android_cpp_tests + # - build_type: android_lua_tests # - build_type: android_cocos_new_test # - build_type: android_cpp_empty_test # - build_type: android_gen_libs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8cfa6fe3cdf6..2eaa89f24ca2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,27 +12,27 @@ on: workflow_dispatch: jobs: - ubuntu-18_04: - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - run: python download-deps.py --r no - - run: echo -e "y" | bash install-deps-linux.sh - - run: cmake -B b -S . - - run: cmake --build b + # ubuntu-18_04: + # runs-on: ubuntu-18.04 + # steps: + # - uses: actions/checkout@v2 + # with: + # submodules: recursive + # - run: python download-deps.py --r no + # - run: echo -e "y" | bash install-deps-linux.sh + # - run: cmake -B b -S . + # - run: cmake --build b - ubuntu-20_04: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - run: python download-deps.py --r no - - run: echo -e "y" | bash install-deps-linux.sh - - run: cmake -B b -S . - - run: cmake --build b + # ubuntu-20_04: + # runs-on: ubuntu-20.04 + # steps: + # - uses: actions/checkout@v2 + # with: + # submodules: recursive + # - run: python download-deps.py --r no + # - run: echo -e "y" | bash install-deps-linux.sh + # - run: cmake -B b -S . + # - run: cmake --build b windows-2019: runs-on: windows-2019 @@ -54,64 +54,64 @@ jobs: - run: cmake -B b -S . -G "Visual Studio 17 2022" -A Win32 - run: cmake --build b - macos-10_15: - runs-on: macos-10.15 - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - run: python download-deps.py --r no - - run: cmake -B b -S . -GXcode - - run: cmake --build b + # macos-10_15: + # runs-on: macos-10.15 + # steps: + # - uses: actions/checkout@v2 + # with: + # submodules: recursive + # - run: python download-deps.py --r no + # - run: cmake -B b -S . -GXcode + # - run: cmake --build b - macos-11: - runs-on: macos-11 - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - run: python download-deps.py --r no - - run: cmake -B b -S . -GXcode - - run: cmake --build b + # macos-11: + # runs-on: macos-11 + # steps: + # - uses: actions/checkout@v2 + # with: + # submodules: recursive + # - run: python download-deps.py --r no + # - run: cmake -B b -S . -GXcode + # - run: cmake --build b - macos-10_15_ios: - runs-on: macos-10.15 - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - run: python download-deps.py --r no - - run: cmake -B b -S . -GXcode -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator - - run: cmake --build b --config Release --target cpp-tests -- -quiet + # macos-10_15_ios: + # runs-on: macos-10.15 + # steps: + # - uses: actions/checkout@v2 + # with: + # submodules: recursive + # - run: python download-deps.py --r no + # - run: cmake -B b -S . -GXcode -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator + # - run: cmake --build b --config Release --target cpp-tests -- -quiet - macos-11_ios: - runs-on: macos-11 - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - run: python download-deps.py --r no - - run: cmake -B b -S . -GXcode -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator - - run: cmake --build b --config Release --target cpp-tests -- -quiet -destination "platform=iOS Simulator,name=iPhone Retina (4-inch)" + # macos-11_ios: + # runs-on: macos-11 + # steps: + # - uses: actions/checkout@v2 + # with: + # submodules: recursive + # - run: python download-deps.py --r no + # - run: cmake -B b -S . -GXcode -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator + # - run: cmake --build b --config Release --target cpp-tests -- -quiet -destination "platform=iOS Simulator,name=iPhone Retina (4-inch)" - windows-2019-android: - runs-on: windows-2019 - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - run: python download-deps.py --r no - - run: ./gradlew assembleRelease -PPROP_BUILD_TYPE=cmake --info - shell: bash - working-directory: tests/cpp-tests/proj.android + # windows-2019-android: + # runs-on: windows-2019 + # steps: + # - uses: actions/checkout@v2 + # with: + # submodules: recursive + # - run: python download-deps.py --r no + # - run: ./gradlew assembleRelease -PPROP_BUILD_TYPE=cmake --info + # shell: bash + # working-directory: tests/cpp-tests/proj.android - ubuntu-20_04-android: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - run: python download-deps.py --r no - - run: ./gradlew assembleRelease -PPROP_BUILD_TYPE=cmake --info - shell: bash - working-directory: tests/cpp-tests/proj.android + # ubuntu-20_04-android: + # runs-on: ubuntu-20.04 + # steps: + # - uses: actions/checkout@v2 + # with: + # submodules: recursive + # - run: python download-deps.py --r no + # - run: ./gradlew assembleRelease -PPROP_BUILD_TYPE=cmake --info + # shell: bash + # working-directory: tests/cpp-tests/proj.android diff --git a/.gitmodules b/.gitmodules index b3bdfed93f49..6f37b31b3cfd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,9 @@ [submodule "tools/cocos2d-console"] path = tools/cocos2d-console - url = git://github.com/cocos2d/cocos2d-console.git + url = https://github.com/cocos2d/cocos2d-console.git [submodule "tools/bindings-generator"] path = tools/bindings-generator - url = git://github.com/cocos2d/bindings-generator.git + url = https://github.com/cocos2d/bindings-generator.git [submodule "tests/cpp-tests/Resources/ccs-res"] path = tests/cpp-tests/Resources/ccs-res - url = git://github.com/dumganhar/ccs-res.git + url = https://github.com/dumganhar/ccs-res.git diff --git a/.travis.yml b/.travis.yml index 908a8ed9440c..5c6351adee31 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ matrix: language: android sudo: required dist: xenial - # android_lua cmake + android_lua cmake - os: linux env: BUILD_TARGET=android_lua_cmake language: android diff --git a/cmake/Modules/CocosConfigDefine.cmake b/cmake/Modules/CocosConfigDefine.cmake index 12f1300aea6f..c1fa3a74933c 100644 --- a/cmake/Modules/CocosConfigDefine.cmake +++ b/cmake/Modules/CocosConfigDefine.cmake @@ -10,11 +10,14 @@ endif() #IOS = iOS #MACOSX = MacOS X #LINUX = Linux + #OHOS = OHOS if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") set(WINDOWS TRUE) set(PLATFORM_FOLDER win32) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Android") set(PLATFORM_FOLDER android) + elseif(CMAKE_SYSTEM_NAME MATCHES "OHOS") + set(PLATFORM_FOLDER ohos) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") if(ANDROID) set(PLATFORM_FOLDER android) @@ -60,7 +63,11 @@ define_property(TARGET # check c++ standard set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) +if(OHOS) +set(CMAKE_CXX_STANDARD 14) +else() set(CMAKE_CXX_STANDARD 11) +endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) @@ -92,6 +99,9 @@ endif() elseif(ANDROID) target_compile_definitions(${target} PUBLIC ANDROID) target_compile_definitions(${target} PUBLIC USE_FILE32API) + elseif(OHOS) + target_compile_definitions(${target} PUBLIC OHOS) + target_compile_definitions(${target} PUBLIC USE_FILE32API) elseif(WINDOWS) target_compile_definitions(${target} PUBLIC WIN32 diff --git a/cmake/Modules/CocosConfigDepend.cmake b/cmake/Modules/CocosConfigDepend.cmake index 258f3bf5f8fb..a8ec78fce080 100644 --- a/cmake/Modules/CocosConfigDepend.cmake +++ b/cmake/Modules/CocosConfigDepend.cmake @@ -21,6 +21,8 @@ macro(cocos2dx_depend) set(THREADS_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) elseif(ANDROID) list(APPEND PLATFORM_SPECIFIC_LIBS GLESv2 EGL log android OpenSLES) + elseif(OHOS) + list(APPEND PLATFORM_SPECIFIC_LIBS native_drawing EGL GLESv3 hilog_ndk.z ace_ndk.z ace_napi.z uv rawfile.z OpenSLES) elseif(APPLE) include_directories(/System/Library/Frameworks) diff --git a/cocos/CMakeLists.txt b/cocos/CMakeLists.txt index 4accdfeffdfd..8fa2dc2df2ed 100644 --- a/cocos/CMakeLists.txt +++ b/cocos/CMakeLists.txt @@ -118,6 +118,10 @@ use_cocos2dx_compile_define(cocos2d) use_cocos2dx_compile_options(cocos2d) # use all platform related system libs +if (OHOS) + + target_link_libraries(cocos2d ${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() use_cocos2dx_libs_depend(cocos2d) target_include_directories(cocos2d diff --git a/cocos/audio/AudioEngine.cpp b/cocos/audio/AudioEngine.cpp index 423b6c84f9b1..051d4d41de50 100644 --- a/cocos/audio/AudioEngine.cpp +++ b/cocos/audio/AudioEngine.cpp @@ -39,6 +39,8 @@ #include "audio/win32/AudioEngine-win32.h" #elif CC_TARGET_PLATFORM == CC_PLATFORM_LINUX #include "audio/linux/AudioEngine-linux.h" +#elif CC_TARGET_PLATFORM == CC_PLATFORM_OHOS +#include "audio/ohos/AudioEngine-inl.h" #endif #define TIME_DELAY_PRECISION 0.0001 diff --git a/cocos/audio/CMakeLists.txt b/cocos/audio/CMakeLists.txt index b9be171836a0..b86143f3963c 100644 --- a/cocos/audio/CMakeLists.txt +++ b/cocos/audio/CMakeLists.txt @@ -105,6 +105,78 @@ elseif(ANDROID) audio/android/tinysndfile.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 + ) + + set(COCOS_AUDIO_PLATFORM_SRC + 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/minifloat.cpp + audio/ohos/audio_utils/primitives.cpp + audio/ohos/utils/Utils.cpp + ) elseif(LINUX) set(COCOS_AUDIO_PLATFORM_HEADER audio/linux/AudioEngine-linux.h diff --git a/cocos/audio/ohos/AssetFd.cpp b/cocos/audio/ohos/AssetFd.cpp new file mode 100644 index 000000000000..1cf8ec83ef16 --- /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 { + +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..87b88f4f7065 --- /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 { + +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..df67c8ab32d8 --- /dev/null +++ b/cocos/audio/ohos/AudioBufferProvider.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include "utils/Errors.h" + +namespace cocos2d { +// ---------------------------------------------------------------------------- + +class AudioBufferProvider { +public: + // IDEA: merge with AudioTrackShared::Buffer, AudioTrack::Buffer, and AudioRecord::Buffer + // and rename getNextBuffer() to obtainBuffer() + struct Buffer { + Buffer() : raw(NULL), frameCount(0) {} + union { + void *raw; + short *i16; + int8_t *i8; + }; + size_t frameCount; + }; + + virtual ~AudioBufferProvider() {} + + // value representing an invalid presentation timestamp + static const int64_t kInvalidPTS = 0x7FFFFFFFFFFFFFFFLL; // 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..dc2ee37a51ee --- /dev/null +++ b/cocos/audio/ohos/AudioDecoder.cpp @@ -0,0 +1,260 @@ +/**************************************************************************** +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 { + +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 \ 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..ee702787a769 --- /dev/null +++ b/cocos/audio/ohos/AudioDecoder.h @@ -0,0 +1,60 @@ +/**************************************************************************** +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 { + +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 cocos2d diff --git a/cocos/audio/ohos/AudioDecoderMp3.cpp b/cocos/audio/ohos/AudioDecoderMp3.cpp new file mode 100644 index 000000000000..8c3cb4ae321c --- /dev/null +++ b/cocos/audio/ohos/AudioDecoderMp3.cpp @@ -0,0 +1,75 @@ +/**************************************************************************** +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 { + +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 \ No newline at end of file diff --git a/cocos/audio/ohos/AudioDecoderMp3.h b/cocos/audio/ohos/AudioDecoderMp3.h new file mode 100644 index 000000000000..c9edf8ec7ea0 --- /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 { + +class AudioDecoderMp3 : public AudioDecoder { +protected: + AudioDecoderMp3(); + virtual ~AudioDecoderMp3(); + + virtual bool decodeToPcm() override; + + friend class AudioDecoderProvider; +}; + +} // namespace cocos2d \ 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..ebca48509cbb --- /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 { + +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 \ No newline at end of file diff --git a/cocos/audio/ohos/AudioDecoderOgg.h b/cocos/audio/ohos/AudioDecoderOgg.h new file mode 100644 index 000000000000..cff6338367ea --- /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 { + +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 \ 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..eef19acb315b --- /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 { + +cocos2d::AudioDecoder *AudioDecoderProvider::createAudioDecoder(SLEngineItf engineItf, const std::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback) { + AudioDecoder *decoder = nullptr; + std::string extension = FileUtils::getInstance()->getFileExtension(url); + 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 \ No newline at end of file diff --git a/cocos/audio/ohos/AudioDecoderProvider.h b/cocos/audio/ohos/AudioDecoderProvider.h new file mode 100644 index 000000000000..083d0e86ed38 --- /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 { + +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 diff --git a/cocos/audio/ohos/AudioDecoderSLES.cpp b/cocos/audio/ohos/AudioDecoderSLES.cpp new file mode 100644 index 000000000000..49b5dab38cbd --- /dev/null +++ b/cocos/audio/ohos/AudioDecoderSLES.cpp @@ -0,0 +1,293 @@ +/**************************************************************************** +Copyright (c) 2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "AudioDecoderSLES" + +#include "Macros.h" +#include "AudioDecoderSLES.h" +#include "platform/CCFileUtils.h" + +#include +#include + +namespace cocos2d { + +/* Explicitly requesting SL_IID_ANDROIDSIMPLEBUFFERQUEUE and SL_IID_PREFETCHSTATUS +* on the UrlAudioPlayer object for decoding, SL_IID_METADATAEXTRACTION for retrieving the +* format of the decoded audio */ +#define NUM_EXPLICIT_INTERFACES_FOR_PLAYER 3 + +/* Size of the decode buffer queue */ +#define NB_BUFFERS_IN_QUEUE 4 + +/* size of the struct to retrieve the PCM format metadata values: the values we're interested in + * are SLuint32, but it is saved in the data field of a SLMetadataInfo, hence the larger size. + * Nate that this size is queried and displayed at l.452 for demonstration/test purposes. + * */ +#define PCM_METADATA_VALUE_SIZE 32 + +/* used to detect errors likely to have occurred when the OpenSL ES framework fails to open + * a resource, for instance because a file URI is invalid, or an HTTP server doesn't respond. + */ +#define PREFETCHEVENT_ERROR_CANDIDATE (SL_PREFETCHEVENT_STATUSCHANGE | SL_PREFETCHEVENT_FILLLEVELCHANGE) + +//----------------------------------------------------------------- + +static std::mutex __SLPlayerMutex; //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 \ No newline at end of file diff --git a/cocos/audio/ohos/AudioDecoderSLES.h b/cocos/audio/ohos/AudioDecoderSLES.h new file mode 100644 index 000000000000..cfb998e5e846 --- /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 { + +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 \ 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..e866d78bf940 --- /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 { +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 diff --git a/cocos/audio/ohos/AudioDecoderWav.h b/cocos/audio/ohos/AudioDecoderWav.h new file mode 100644 index 000000000000..598dd445d8d1 --- /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 { + +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 \ 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..c75799b3df40 --- /dev/null +++ b/cocos/audio/ohos/AudioEngine-inl.cpp @@ -0,0 +1,529 @@ +/**************************************************************************** + 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; + +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::getInstance()->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..0ce8d56696aa --- /dev/null +++ b/cocos/audio/ohos/AudioEngine-inl.h @@ -0,0 +1,121 @@ +/**************************************************************************** + 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; + +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 diff --git a/cocos/audio/ohos/AudioMixer.cpp b/cocos/audio/ohos/AudioMixer.cpp new file mode 100644 index 000000000000..8607ecccb7f0 --- /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 { + +// ---------------------------------------------------------------------------- + +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 diff --git a/cocos/audio/ohos/AudioMixer.h b/cocos/audio/ohos/AudioMixer.h new file mode 100644 index 000000000000..2911df5f2ab6 --- /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 { + +// ---------------------------------------------------------------------------- + +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 diff --git a/cocos/audio/ohos/AudioMixerController.cpp b/cocos/audio/ohos/AudioMixerController.cpp new file mode 100644 index 000000000000..49cc34701c1c --- /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 { + +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 diff --git a/cocos/audio/ohos/AudioMixerController.h b/cocos/audio/ohos/AudioMixerController.h new file mode 100644 index 000000000000..e7d15e4450d5 --- /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 { + +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 diff --git a/cocos/audio/ohos/AudioMixerOps.h b/cocos/audio/ohos/AudioMixerOps.h new file mode 100644 index 000000000000..943cb55bef51 --- /dev/null +++ b/cocos/audio/ohos/AudioMixerOps.h @@ -0,0 +1,429 @@ +#pragma once + +#include "cutils/log.h" + +namespace cocos2d { + +/* Behavior of is_same<>::value is true if the types are identical, + * false otherwise. Identical to the STL std::is_same. + */ +template +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 diff --git a/cocos/audio/ohos/AudioPlayerProvider.cpp b/cocos/audio/ohos/AudioPlayerProvider.cpp new file mode 100644 index 000000000000..24c73ed89c1d --- /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 { + +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 diff --git a/cocos/audio/ohos/AudioPlayerProvider.h b/cocos/audio/ohos/AudioPlayerProvider.h new file mode 100644 index 000000000000..b1dd441411f8 --- /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 { +// 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 diff --git a/cocos/audio/ohos/AudioResampler.cpp b/cocos/audio/ohos/AudioResampler.cpp new file mode 100644 index 000000000000..3ad9de21ec2d --- /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 { + +// ---------------------------------------------------------------------------- + +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 diff --git a/cocos/audio/ohos/AudioResampler.h b/cocos/audio/ohos/AudioResampler.h new file mode 100644 index 000000000000..e2960a09d8ec --- /dev/null +++ b/cocos/audio/ohos/AudioResampler.h @@ -0,0 +1,154 @@ +#pragma once + +#include +#include "AudioBufferProvider.h" +#include +#include "audio.h" + +namespace cocos2d { + +class AudioResampler { +public: + // Determines quality of SRC. + // LOW_QUALITY: linear interpolator (1st order) + // MED_QUALITY: cubic interpolator (3rd order) + // HIGH_QUALITY: fixed multi-tap FIR (e.g. 48KHz->44.1KHz) + // NOTE: high quality SRC will only be supported for + // certain fixed rate conversions. Sample rate cannot be + // changed dynamically. + enum src_quality { // 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 diff --git a/cocos/audio/ohos/AudioResamplerCubic.cpp b/cocos/audio/ohos/AudioResamplerCubic.cpp new file mode 100644 index 000000000000..4022a2140f16 --- /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 { +// ---------------------------------------------------------------------------- + +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 diff --git a/cocos/audio/ohos/AudioResamplerCubic.h b/cocos/audio/ohos/AudioResamplerCubic.h new file mode 100644 index 000000000000..019e625a8f10 --- /dev/null +++ b/cocos/audio/ohos/AudioResamplerCubic.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include "AudioResampler.h" +#include "AudioBufferProvider.h" + +namespace cocos2d { +// ---------------------------------------------------------------------------- + +class AudioResamplerCubic : public AudioResampler { +public: + AudioResamplerCubic(int inChannelCount, int32_t sampleRate) : AudioResampler(inChannelCount, sampleRate, MED_QUALITY) { + } + virtual size_t resample(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider); + +private: + // number of bits used in interpolation multiply - 14 bits avoids overflow + static const int kNumInterpBits = 14; + + // bits to shift the phase fraction down to avoid overflow + static const int kPreInterpShift = kNumPhaseBits - kNumInterpBits; + typedef struct { + int32_t a, b, c, y0, y1, y2, y3; + } state; + void init(); + size_t resampleMono16(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider); + size_t resampleStereo16(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider); + static inline int32_t interp(state *p, int32_t x) { + return (((((p->a * x >> 14) + p->b) * x >> 14) + p->c) * x >> 14) + p->y1; + } + static inline void advance(state *p, int16_t in) { + p->y0 = p->y1; + p->y1 = p->y2; + p->y2 = p->y3; + p->y3 = in; + p->a = (3 * (p->y1 - p->y2) - p->y0 + p->y3) >> 1; + p->b = (p->y2 << 1) + p->y0 - (((5 * p->y1 + p->y3)) >> 1); + p->c = (p->y2 - p->y0) >> 1; + } + state left, right; +}; + +// ---------------------------------------------------------------------------- +} // namespace cocos2d diff --git a/cocos/audio/ohos/AudioResamplerPublic.h b/cocos/audio/ohos/AudioResamplerPublic.h new file mode 100644 index 000000000000..529f4d581cf7 --- /dev/null +++ b/cocos/audio/ohos/AudioResamplerPublic.h @@ -0,0 +1,155 @@ +#pragma once + +#include +#include + +namespace cocos2d { + +// AUDIO_RESAMPLER_DOWN_RATIO_MAX is the maximum ratio between the original +// audio sample rate and the target rate when downsampling, +// as permitted in the audio framework, e.g. AudioTrack and AudioFlinger. +// In practice, it is not recommended to downsample more than 6:1 +// for best audio quality, even though the audio framework permits a larger +// downsampling ratio. +// REFINE: replace with an API +#define AUDIO_RESAMPLER_DOWN_RATIO_MAX 256 + +// AUDIO_RESAMPLER_UP_RATIO_MAX is the maximum suggested ratio between the original +// audio sample rate and the target rate when upsampling. It is loosely enforced by +// the system. One issue with large upsampling ratios is the approximation by +// an int32_t of the phase increments, making the resulting sample rate inexact. +#define AUDIO_RESAMPLER_UP_RATIO_MAX 65536 + +// AUDIO_TIMESTRETCH_SPEED_MIN and AUDIO_TIMESTRETCH_SPEED_MAX define the min and max time stretch +// speeds supported by the system. These are enforced by the system and values outside this range +// will result in a runtime error. +// Depending on the AudioPlaybackRate::mStretchMode, the effective limits might be narrower than +// the ones specified here +// AUDIO_TIMESTRETCH_SPEED_MIN_DELTA is the minimum absolute speed difference that might trigger a +// parameter update +#define AUDIO_TIMESTRETCH_SPEED_MIN 0.01f +#define AUDIO_TIMESTRETCH_SPEED_MAX 20.0f +#define AUDIO_TIMESTRETCH_SPEED_NORMAL 1.0f +#define AUDIO_TIMESTRETCH_SPEED_MIN_DELTA 0.0001f + +// AUDIO_TIMESTRETCH_PITCH_MIN and AUDIO_TIMESTRETCH_PITCH_MAX define the min and max time stretch +// pitch shifting supported by the system. These are not enforced by the system and values +// outside this range might result in a pitch different than the one requested. +// Depending on the AudioPlaybackRate::mStretchMode, the effective limits might be narrower than +// the ones specified here. +// AUDIO_TIMESTRETCH_PITCH_MIN_DELTA is the minimum absolute pitch difference that might trigger a +// parameter update +#define AUDIO_TIMESTRETCH_PITCH_MIN 0.25f +#define AUDIO_TIMESTRETCH_PITCH_MAX 4.0f +#define AUDIO_TIMESTRETCH_PITCH_NORMAL 1.0f +#define AUDIO_TIMESTRETCH_PITCH_MIN_DELTA 0.0001f + +//Determines the current algorithm used for stretching +enum AudioTimestretchStretchMode : int32_t { + AUDIO_TIMESTRETCH_STRETCH_DEFAULT = 0, + AUDIO_TIMESTRETCH_STRETCH_SPEECH = 1, + //REFINE: add more stretch modes/algorithms +}; + +//Limits for AUDIO_TIMESTRETCH_STRETCH_SPEECH mode +#define TIMESTRETCH_SONIC_SPEED_MIN 0.1f +#define TIMESTRETCH_SONIC_SPEED_MAX 6.0f + +//Determines behavior of Timestretch if current algorithm can't perform +//with current parameters. +// FALLBACK_CUT_REPEAT: (internal only) for speed <1.0 will truncate frames +// for speed > 1.0 will repeat frames +// FALLBACK_MUTE: will set all processed frames to zero +// FALLBACK_FAIL: will stop program execution and log a fatal error +enum AudioTimestretchFallbackMode : int32_t { + AUDIO_TIMESTRETCH_FALLBACK_CUT_REPEAT = -1, + AUDIO_TIMESTRETCH_FALLBACK_DEFAULT = 0, + AUDIO_TIMESTRETCH_FALLBACK_MUTE = 1, + AUDIO_TIMESTRETCH_FALLBACK_FAIL = 2, +}; + +struct AudioPlaybackRate { + float mSpeed; + float mPitch; + enum AudioTimestretchStretchMode mStretchMode; + enum AudioTimestretchFallbackMode mFallbackMode; +}; + +static const AudioPlaybackRate AUDIO_PLAYBACK_RATE_DEFAULT = { + AUDIO_TIMESTRETCH_SPEED_NORMAL, + AUDIO_TIMESTRETCH_PITCH_NORMAL, + AUDIO_TIMESTRETCH_STRETCH_DEFAULT, + AUDIO_TIMESTRETCH_FALLBACK_DEFAULT}; + +static inline bool isAudioPlaybackRateEqual(const AudioPlaybackRate &pr1, + const AudioPlaybackRate &pr2) { + return fabs(pr1.mSpeed - pr2.mSpeed) < AUDIO_TIMESTRETCH_SPEED_MIN_DELTA && + fabs(pr1.mPitch - pr2.mPitch) < AUDIO_TIMESTRETCH_PITCH_MIN_DELTA && + pr1.mStretchMode == pr2.mStretchMode && + pr1.mFallbackMode == pr2.mFallbackMode; +} + +static inline bool isAudioPlaybackRateValid(const AudioPlaybackRate &playbackRate) { + if (playbackRate.mFallbackMode == AUDIO_TIMESTRETCH_FALLBACK_FAIL && + (playbackRate.mStretchMode == AUDIO_TIMESTRETCH_STRETCH_SPEECH || + playbackRate.mStretchMode == AUDIO_TIMESTRETCH_STRETCH_DEFAULT)) { + //test sonic specific constraints + return playbackRate.mSpeed >= TIMESTRETCH_SONIC_SPEED_MIN && + playbackRate.mSpeed <= TIMESTRETCH_SONIC_SPEED_MAX && + playbackRate.mPitch >= AUDIO_TIMESTRETCH_PITCH_MIN && + playbackRate.mPitch <= AUDIO_TIMESTRETCH_PITCH_MAX; + } else { + return playbackRate.mSpeed >= AUDIO_TIMESTRETCH_SPEED_MIN && + playbackRate.mSpeed <= AUDIO_TIMESTRETCH_SPEED_MAX && + playbackRate.mPitch >= AUDIO_TIMESTRETCH_PITCH_MIN && + playbackRate.mPitch <= AUDIO_TIMESTRETCH_PITCH_MAX; + } +} + +// REFINE: Consider putting these inlines into a class scope + +// Returns the source frames needed to resample to destination frames. This is not a precise +// value and depends on the resampler (and possibly how it handles rounding internally). +// Nevertheless, this should be an upper bound on the requirements of the resampler. +// If srcSampleRate and dstSampleRate are equal, then it returns destination frames, which +// may not be true if the resampler is asynchronous. +static inline size_t sourceFramesNeeded( + uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate) { + // +1 for rounding - always do this even if matched ratio (resampler may use phases not ratio) + // +1 for additional sample needed for interpolation + return srcSampleRate == dstSampleRate ? dstFramesRequired : size_t((uint64_t)dstFramesRequired * srcSampleRate / dstSampleRate + 1 + 1); +} + +// An upper bound for the number of destination frames possible from srcFrames +// after sample rate conversion. This may be used for buffer sizing. +static inline size_t destinationFramesPossible(size_t srcFrames, uint32_t srcSampleRate, + uint32_t dstSampleRate) { + if (srcSampleRate == dstSampleRate) { + return srcFrames; + } + uint64_t dstFrames = (uint64_t)srcFrames * dstSampleRate / srcSampleRate; + return dstFrames > 2 ? 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 + +// --------------------------------------------------------------------------- diff --git a/cocos/audio/ohos/CCThreadPool.cpp b/cocos/audio/ohos/CCThreadPool.cpp new file mode 100644 index 000000000000..f658faf77890 --- /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 { + +#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 diff --git a/cocos/audio/ohos/CCThreadPool.h b/cocos/audio/ohos/CCThreadPool.h new file mode 100644 index 000000000000..ce10e660ee65 --- /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 { + +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 diff --git a/cocos/audio/ohos/CMakeLists.txt b/cocos/audio/ohos/CMakeLists.txt new file mode 100644 index 000000000000..61eea7c93b53 --- /dev/null +++ b/cocos/audio/ohos/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.4.1) + +project(cocosdenshion) + +file(GLOB_RECURSE COCOSDENSHION_SRC "./*.cpp") + +include_directories(./audio_utils/include/audio_utils ../include) + +add_library(${PROJECT_NAME} STATIC ${COCOSDENSHION_SRC}) + +#set(CMAKE_C_FLAGS -Wno-psabi -Wno-absolute-value -Wno-extra) +#set(CMAKE_CXX_FLAGS -Wno-overloaded-virtual -Wno-unused-function -Wno-unused-private-field -Wno-reorder-ctor -Wno-unsequenced -Wno-extra) +target_compile_options(${PROJECT_NAME} PUBLIC -Wno-psabi -Wno-absolute-value -Wno-extra -Wno-overloaded-virtual -Wno-unused-function -Wno-unused-private-field -Wno-reorder-ctor -Wno-unsequenced -Wno-extra -Wno-unused-command-line-argument -Wno-ignored-qualifiers) +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../include + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../cocos2dx + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../cocos2dx/include + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../cocos2dx/kazmath/include + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../cocos2dx/platform/ohos + ) +target_link_libraries(${PROJECT_NAME} PUBLIC cocos2dx_static OPENSLES pvmp3dec vorbisidec) diff --git a/cocos/audio/ohos/IAudioPlayer.h b/cocos/audio/ohos/IAudioPlayer.h new file mode 100644 index 000000000000..5f361c69a533 --- /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 { + +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 diff --git a/cocos/audio/ohos/ICallerThreadUtils.h b/cocos/audio/ohos/ICallerThreadUtils.h new file mode 100644 index 000000000000..97cca0210891 --- /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 { + +class ICallerThreadUtils { +public: + virtual ~ICallerThreadUtils(){}; + + virtual void performFunctionInCallerThread(const std::function &func) = 0; + virtual std::thread::id getCallerThreadId() = 0; +}; + +} // namespace cocos2d diff --git a/cocos/audio/ohos/IVolumeProvider.h b/cocos/audio/ohos/IVolumeProvider.h new file mode 100644 index 000000000000..081cd0fb420a --- /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 { + +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 diff --git a/cocos/audio/ohos/Macros.h b/cocos/audio/ohos/Macros.h new file mode 100644 index 000000000000..0fb8ac299b82 --- /dev/null +++ b/cocos/audio/ohos/Macros.h @@ -0,0 +1,403 @@ +/**************************************************************************** + 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) + +#define CC_DLL + +/** @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..596f7f92f3c2 --- /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 { + +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 diff --git a/cocos/audio/ohos/PcmAudioPlayer.h b/cocos/audio/ohos/PcmAudioPlayer.h new file mode 100644 index 000000000000..e739bf50c6e5 --- /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 { + +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 diff --git a/cocos/audio/ohos/PcmAudioService.cpp b/cocos/audio/ohos/PcmAudioService.cpp new file mode 100644 index 000000000000..7402eabaf86f --- /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 { + + +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); + } +} + +} diff --git a/cocos/audio/ohos/PcmAudioService.h b/cocos/audio/ohos/PcmAudioService.h new file mode 100644 index 000000000000..9262c88e3e67 --- /dev/null +++ b/cocos/audio/ohos/PcmAudioService.h @@ -0,0 +1,70 @@ +/**************************************************************************** +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 { + +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 diff --git a/cocos/audio/ohos/PcmBufferProvider.cpp b/cocos/audio/ohos/PcmBufferProvider.cpp new file mode 100644 index 000000000000..72aca6ebc1e7 --- /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 { + +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 diff --git a/cocos/audio/ohos/PcmBufferProvider.h b/cocos/audio/ohos/PcmBufferProvider.h new file mode 100644 index 000000000000..92f3f4611dbb --- /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 { + +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 diff --git a/cocos/audio/ohos/PcmData.cpp b/cocos/audio/ohos/PcmData.cpp new file mode 100644 index 000000000000..b6361ed0afbf --- /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 { + +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 \ No newline at end of file diff --git a/cocos/audio/ohos/PcmData.h b/cocos/audio/ohos/PcmData.h new file mode 100644 index 000000000000..bf41b1d70b61 --- /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 { + +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 diff --git a/cocos/audio/ohos/Track.cpp b/cocos/audio/ohos/Track.cpp new file mode 100644 index 000000000000..204c367fce50 --- /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 { + +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 \ 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..3988506cf019 --- /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 { + +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 diff --git a/cocos/audio/ohos/UrlAudioPlayer.cpp b/cocos/audio/ohos/UrlAudioPlayer.cpp new file mode 100644 index 000000000000..591a1e896263 --- /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 { + +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..a879be8fccdb --- /dev/null +++ b/cocos/audio/ohos/UrlAudioPlayer.h @@ -0,0 +1,124 @@ +/**************************************************************************** +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 { + +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 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..d30a93eff9bb --- /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 { + +#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..9a5d659a49b1 --- /dev/null +++ b/cocos/audio/ohos/audio_utils/RefCounted.h @@ -0,0 +1,95 @@ +/**************************************************************************** + 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 { + +class RefCounted; + +/** + * 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/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..dbc1377eed05 --- /dev/null +++ b/cocos/audio/ohos/audio_utils/minifloat.cpp @@ -0,0 +1,45 @@ +#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..3deeb78d9ba3 --- /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::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..382fec23085d --- /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 { + +// 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..3649bdf2a696 --- /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 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..a68621755ff8 --- /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 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/CCConfiguration.cpp b/cocos/base/CCConfiguration.cpp index 00467c1e4a0b..0308106c7058 100644 --- a/cocos/base/CCConfiguration.cpp +++ b/cocos/base/CCConfiguration.cpp @@ -255,6 +255,8 @@ bool Configuration::supportsMapBuffer() const // XXX: Warning. On iOS this is always `true`. Avoiding the comparison. #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) return _supportsOESMapBuffer; +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + return false; #else return true; #endif diff --git a/cocos/base/CCConsole.cpp b/cocos/base/CCConsole.cpp index b666520ef527..400725a68be7 100644 --- a/cocos/base/CCConsole.cpp +++ b/cocos/base/CCConsole.cpp @@ -164,6 +164,9 @@ void log(const char * format, ...) #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 int pos = 0; diff --git a/cocos/base/CCController-ohos.cpp b/cocos/base/CCController-ohos.cpp new file mode 100644 index 000000000000..240943e14182 --- /dev/null +++ b/cocos/base/CCController-ohos.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** + 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 faddf635e1d0..a1fefa06b55a 100644 --- a/cocos/base/CCController.cpp +++ b/cocos/base/CCController.cpp @@ -26,7 +26,7 @@ #include "base/CCController.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_LINUX || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 ) +#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_LINUX || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 ) #include "base/CCEventDispatcher.h" #include "base/CCEventController.h" diff --git a/cocos/base/CCController.h b/cocos/base/CCController.h index 412fda21214b..e8ff99f73851 100644 --- a/cocos/base/CCController.h +++ b/cocos/base/CCController.h @@ -25,7 +25,7 @@ #ifndef __cocos2d_libs__CCController__ #define __cocos2d_libs__CCController__ -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 ) +#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_LINUX || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 ) #include "platform/CCPlatformMacros.h" #include diff --git a/cocos/base/CMakeLists.txt b/cocos/base/CMakeLists.txt index 5c7f70e164f3..f8ddd0c6a16f 100644 --- a/cocos/base/CMakeLists.txt +++ b/cocos/base/CMakeLists.txt @@ -10,6 +10,10 @@ elseif(ANDROID) base/CCUserDefault-android.cpp base/CCController-android.cpp ) +elseif(OHOS) + set(COCOS_BASE_SPECIFIC_SRC + base/CCController-ohos.cpp + ) elseif(LINUX OR WINDOWS) set(COCOS_BASE_SPECIFIC_SRC base/CCController-linux-win32.cpp diff --git a/cocos/base/ccConfig.h b/cocos/base/ccConfig.h index 8c1df270c323..3c577495da0e 100644 --- a/cocos/base/ccConfig.h +++ b/cocos/base/ccConfig.h @@ -269,7 +269,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) +#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_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX) #define CC_USE_3D_PHYSICS 1 #endif #endif diff --git a/cocos/cocos2d.h b/cocos/cocos2d.h index a81ea7e3c538..56ca482568ba 100644 --- a/cocos/cocos2d.h +++ b/cocos/cocos2d.h @@ -216,6 +216,13 @@ THE SOFTWARE. #include "platform/linux/CCStdC-linux.h" #endif // CC_TARGET_PLATFORM == CC_PLATFORM_LINUX +#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/platform/CCApplication.h b/cocos/platform/CCApplication.h index 9c1b01d07f01..118eaff49536 100644 --- a/cocos/platform/CCApplication.h +++ b/cocos/platform/CCApplication.h @@ -40,6 +40,8 @@ THE SOFTWARE. #include "platform/win32/CCApplication-win32.h" #elif CC_TARGET_PLATFORM == CC_PLATFORM_LINUX #include "platform/linux/CCApplication-linux.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 7020611d01b0..537c6038240c 100644 --- a/cocos/platform/CCApplicationProtocol.h +++ b/cocos/platform/CCApplicationProtocol.h @@ -52,13 +52,14 @@ class CC_DLL ApplicationProtocol OS_MAC, /**< Mac OS X*/ OS_ANDROID, /**< Android */ OS_IPHONE, /**< iPhone */ - OS_IPAD /**< iPad */ + OS_IPAD, /**< iPad */ // OS_BLACKBERRY, /**< BlackBerry */ // OS_NACL, /**< Native Client in Chrome */ // OS_EMSCRIPTEN, /**< Emscripten */ // OS_TIZEN, /**< Tizen */ // OS_WINRT, /**< Windows Runtime Applications */ // OS_WP8 /**< Windows Phone 8 Applications */ + OS_OPENHARMONY /**< OPENHARMONY */ }; /** diff --git a/cocos/platform/CCGL.h b/cocos/platform/CCGL.h index ecfb28ede5e5..25a4c79cfdf9 100644 --- a/cocos/platform/CCGL.h +++ b/cocos/platform/CCGL.h @@ -36,6 +36,8 @@ THE SOFTWARE. #include "platform/win32/CCGL-win32.h" #elif CC_TARGET_PLATFORM == CC_PLATFORM_LINUX #include "platform/linux/CCGL-linux.h" +#elif CC_TARGET_PLATFORM == CC_PLATFORM_OHOS +#include "platform/ohos/CCGL-ohos.h" #endif /// @endcond diff --git a/cocos/platform/CCImage.cpp b/cocos/platform/CCImage.cpp index 5fdc3a3dc373..cf9f57e7d758 100644 --- a/cocos/platform/CCImage.cpp +++ b/cocos/platform/CCImage.cpp @@ -109,6 +109,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 7985f2ed1dc6..7ca221b22888 100644 --- a/cocos/platform/CCPlatformConfig.h +++ b/cocos/platform/CCPlatformConfig.h @@ -53,6 +53,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 @@ -87,6 +88,11 @@ THE SOFTWARE. #define CC_TARGET_PLATFORM CC_PLATFORM_LINUX #endif +// OpenHarmony +#if defined(OHOS) + #undef CC_TARGET_PLATFORM + #define CC_TARGET_PLATFORM CC_PLATFORM_OHOS +#endif ////////////////////////////////////////////////////////////////////////// // post configure @@ -103,7 +109,7 @@ THE SOFTWARE. #endif #endif // CC_PLATFORM_WIN32 -#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)) #define CC_PLATFORM_MOBILE #else #define CC_PLATFORM_PC @@ -111,7 +117,7 @@ THE SOFTWARE. #if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) #define CC_USE_METAL -#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) || (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) #define CC_USE_GLES #else #define CC_USE_GL diff --git a/cocos/platform/CCPlatformDefine.h b/cocos/platform/CCPlatformDefine.h index 44a1dea3e0f6..5178c8a741a5 100644 --- a/cocos/platform/CCPlatformDefine.h +++ b/cocos/platform/CCPlatformDefine.h @@ -39,6 +39,8 @@ THE SOFTWARE. #include "platform/win32/CCPlatformDefine-win32.h" #elif CC_TARGET_PLATFORM == CC_PLATFORM_LINUX #include "platform/linux/CCPlatformDefine-linux.h" +#elif CC_TARGET_PLATFORM == CC_PLATFORM_OHOS +#include "platform/ohos/CCPlatformDefine-ohos.h" #endif diff --git a/cocos/platform/CCPlatformMacros.h b/cocos/platform/CCPlatformMacros.h index 433929dfeac7..e6e0dbf0917a 100644 --- a/cocos/platform/CCPlatformMacros.h +++ b/cocos/platform/CCPlatformMacros.h @@ -86,13 +86,13 @@ CC_DEPRECATED_ATTRIBUTE static __TYPE__* node() \ * * @since v0.99.5 */ -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) || (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) #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) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) || (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) || (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) /** 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 21604f116344..f11d21cdc914 100644 --- a/cocos/platform/CCStdC.h +++ b/cocos/platform/CCStdC.h @@ -39,6 +39,8 @@ THE SOFTWARE. #include "platform/win32/CCStdC-win32.h" #elif CC_TARGET_PLATFORM == CC_PLATFORM_LINUX #include "platform/linux/CCStdC-linux.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 4700e2519aa1..acf9d7239b3c 100644 --- a/cocos/platform/CMakeLists.txt +++ b/cocos/platform/CMakeLists.txt @@ -31,6 +31,35 @@ if(ANDROID) platform/android/CCFileUtils-android.cpp platform/android/CCEnhanceAPI-android.cpp ) +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 + ) elseif(WINDOWS) set(COCOS_PLATFORM_SPECIFIC_HEADER platform/win32/compat/stdint.h diff --git a/cocos/platform/ohos/CCApplication-ohos.cpp b/cocos/platform/ohos/CCApplication-ohos.cpp new file mode 100644 index 000000000000..73bacfa89447 --- /dev/null +++ b/cocos/platform/ohos/CCApplication-ohos.cpp @@ -0,0 +1,91 @@ +#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 "base/ccUtils.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* code = getCurrentLanguageCode(); + return utils::getLanguageTypeByISO2(code); +} + +ApplicationProtocol::Platform Application::getTargetPlatform() { + return ApplicationProtocol::Platform::OS_OPENHARMONY; +} + + +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) { + // You can implemented it in AppDelgate +} + +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..d72ee4ac8c78 --- /dev/null +++ b/cocos/platform/ohos/CCFileUtils-ohos.cpp @@ -0,0 +1,197 @@ +#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; +string FileUtilsOhos::ohWritablePath; + +void FileUtilsOhos::setassetmanager(NativeResourceManager* a) { + if (nullptr == a) { + return; + } + + cocos2d::FileUtilsOhos::nativeResourceManager_ = a; +} + +FileUtils* FileUtils::getInstance() { + if (s_sharedFileUtils == nullptr) { + s_sharedFileUtils = new FileUtilsOhos(); + if(!s_sharedFileUtils->init()) { + delete s_sharedFileUtils; + s_sharedFileUtils = nullptr; + } + } + return s_sharedFileUtils; +} + +FileUtilsOhos::FileUtilsOhos() { +} + +FileUtilsOhos::~FileUtilsOhos() { +} + +bool FileUtilsOhos::init() { + DECLARE_GUARD; + _defaultResRootPath = ""; + OHOS_LOGI("FileUtilsOhos::init()"); + 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) const { + 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; +} + +std::vector FileUtilsOhos::listFiles(const std::string& dirPath) const { + if(!dirPath.empty() && dirPath[0] == '/') { + return FileUtils::listFiles(dirPath); + } + return RawFileUtils::GetInstance().searchFiles(dirPath.c_str(), false); +} + +FileUtils::Status FileUtilsOhos::getContents(const std::string& filename, ResizableBuffer* buffer) const { + 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::ReadFailed; + } + + 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..2fd89a96824d --- /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) const override; + + virtual std::string getWritablePath() const override; + virtual bool isAbsolutePath(const std::string& strPath) const override; + + virtual long getFileSize(const std::string& filepath) const override; + virtual std::vector listFiles(const std::string& dirPath) const override; + 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..07f2dccf2ebc --- /dev/null +++ b/cocos/platform/ohos/CCGL-ohos.h @@ -0,0 +1,35 @@ +#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 + + +#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..9a757a4829c3 --- /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_EGLVIEWIMPL_OHOS_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..00e5862eaf19 --- /dev/null +++ b/cocos/platform/ohos/CCTextBitmap.cpp @@ -0,0 +1,189 @@ +#include +#include "CCTextBitmap.h" +#include "platform/CCPlatformMacros.h" +#include "platform/CCCommon.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 \ No newline at end of file diff --git a/cocos/platform/ohos/CCTextBitmap.h b/cocos/platform/ohos/CCTextBitmap.h new file mode 100644 index 000000000000..434cb9002bea --- /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..19a04fbcc04a --- /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 + 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 + 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 JS 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; + // Transfer the fifth parameter 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 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); + // Obtains 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 in 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..ac55ac4a10df --- /dev/null +++ b/cocos/platform/ohos/napi/modules/InputNapi.cpp @@ -0,0 +1,186 @@ +#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)); + + // Using 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 old content characters after differences + 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; + }(); + // Batch delete characters + for (size_t i = 0; i < deleteOperations; i++) { + dispatcher->dispatchDeleteBackward(); + } + const size_t insertLen = newView.length() - commonPrefixLen; + // Insert new characters after differences + if ( insertLen > 0) { + const char* newText = text + commonPrefixLen; + CCLOG("textFieldTTFOnChangeCB: Inserting %zu characters: %s", insertLen, newText); + dispatcher->dispatchInsertText(newText, insertLen); + } + + return nullptr; +} \ No newline at end of file diff --git a/cocos/platform/ohos/napi/modules/InputNapi.h b/cocos/platform/ohos/napi/modules/InputNapi.h new file mode 100644 index 000000000000..c9445776d8ae --- /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 \ No newline at end of file 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..5d09611a677c --- /dev/null +++ b/cocos/platform/ohos/napi/modules/RawFileUtils.h @@ -0,0 +1,85 @@ +#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..0dd032fcb2c6 --- /dev/null +++ b/cocos/platform/ohos/napi/modules/TouchesNapi.cpp @@ -0,0 +1,46 @@ +#include "scripting/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::getInstance()->getOpenGLView()->handleTouchesBegin(num, ids, xs, ys); + } + + void Cocos2dxRenderer_nativeTouchesEnd(intptr_t id, float x, float y) { + cocos2d::Director::getInstance()->getOpenGLView()->handleTouchesEnd(1, &id, &x, &y); + } + + void Cocos2dxRenderer_nativeTouchesMove(int num, intptr_t ids[] , float xs[], float ys[]) { + cocos2d::Director::getInstance()->getOpenGLView()->handleTouchesMove(num, ids, xs, ys); + } + + void Cocos2dxRenderer_nativeTouchesCancel(int num, intptr_t ids[] , float xs[], float ys[]) { + cocos2d::Director::getInstance()->getOpenGLView()->handleTouchesCancel(num, ids, xs, ys); + } + + #define KEYCODE_BACK 0x04 + #define KEYCODE_MENU 0x52 + bool Cocos2dxRenderer_nativeKeyDown(int keyCode) { + Director* pDirector = Director::getInstance(); + 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..cf82e30c88f3 --- /dev/null +++ b/cocos/platform/ohos/napi/modules/TouchesNapi.h @@ -0,0 +1,20 @@ +#ifndef __TouchesNapi_H__ +#define __TouchesNapi_H__ + +#include "scripting/deprecated/CCSet.h" +#include "base/CCDirector.h" +#include "base/CCTouch.h" +#include "platform/CCGLView.h" +#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..5b900361a51c --- /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/UIWebView/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::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::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::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::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..6c91f6f83a2a --- /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"); +} \ No newline at end of file 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..669babce15fd --- /dev/null +++ b/cocos/platform/ohos/napi/render/egl_core.cpp @@ -0,0 +1,112 @@ +#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 + if (mEglWindow) { + mEGLSurface = eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mEglWindow, nullptr); + 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..3106d178a1e0 --- /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::Director::getInstance()->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; + } + EventMouse::MouseButton mouseButton; + switch (mouseEvent.button) { + case 1: + mouseButton = EventMouse::MouseButton::BUTTON_LEFT; + break; + case 2: + mouseButton = EventMouse::MouseButton::BUTTON_RIGHT; + break; + case 4: + mouseButton = EventMouse::MouseButton::BUTTON_MIDDLE; + break; + default: + mouseButton = EventMouse::MouseButton::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) { + cocos2d::Director::getInstance()->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 \ No newline at end of file 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/CCTexture2D.cpp b/cocos/renderer/CCTexture2D.cpp index 7d6699a05670..e8c00dbfb5e3 100644 --- a/cocos/renderer/CCTexture2D.cpp +++ b/cocos/renderer/CCTexture2D.cpp @@ -523,8 +523,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/CMakeLists.txt b/cocos/renderer/CMakeLists.txt index d84c90d768c4..2e2530451d1c 100644 --- a/cocos/renderer/CMakeLists.txt +++ b/cocos/renderer/CMakeLists.txt @@ -74,7 +74,7 @@ set(COCOS_RENDERER_SRC renderer/backend/RenderPassDescriptor.cpp ) -if(ANDROID OR WINDOWS OR LINUX) +if(ANDROID OR WINDOWS OR LINUX OR OHOS) list(APPEND COCOS_RENDERER_HEADER renderer/backend/opengl/BufferGL.h diff --git a/cocos/renderer/backend/opengl/ProgramGL.cpp b/cocos/renderer/backend/opengl/ProgramGL.cpp index 2b13e6693cfa..a8cd82f6cce9 100644 --- a/cocos/renderer/backend/opengl/ProgramGL.cpp +++ b/cocos/renderer/backend/opengl/ProgramGL.cpp @@ -40,7 +40,7 @@ namespace { ProgramGL::ProgramGL(const std::string& vertexShader, const std::string& fragmentShader) : Program(vertexShader, fragmentShader) { -#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID +#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_OHOS //some device required manually specify the precision qualifiers for vertex shader. _vertexShaderModule = static_cast(ShaderCache::newVertexShaderModule(std::move(vsPreDefine + _vertexShader))); _fragmentShaderModule = static_cast(ShaderCache::newFragmentShaderModule(std::move(fsPreDefine + _fragmentShader))); diff --git a/cocos/scripting/lua-bindings/CMakeLists.txt b/cocos/scripting/lua-bindings/CMakeLists.txt index ff22348108b1..5796385abed9 100644 --- a/cocos/scripting/lua-bindings/CMakeLists.txt +++ b/cocos/scripting/lua-bindings/CMakeLists.txt @@ -141,6 +141,21 @@ elseif(ANDROID) manual/ui/lua_cocos2dx_video_manual.cpp auto/lua_cocos2dx_video_auto.cpp ) +elseif(OHOS) + set(lua_bindings_manual_headers + ${lua_bindings_manual_headers} + auto/lua_cocos2dx_webview_auto.hpp + manual/ui/lua_cocos2dx_webview_manual.hpp + auto/lua_cocos2dx_video_auto.hpp + manual/ui/lua_cocos2dx_video_manual.hpp + ) + set(lua_bindings_manual_files + ${lua_bindings_manual_files} + auto/lua_cocos2dx_webview_auto.cpp + manual/ui/lua_cocos2dx_webview_manual.cpp + auto/lua_cocos2dx_video_auto.cpp + manual/ui/lua_cocos2dx_video_manual.cpp + ) endif() include(../deprecated/CMakeLists.txt) 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 5e68b6204fd0..2ff67c5059d8 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_LINUX +#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_LINUX #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 74f434bb906e..954ed241a282 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_LINUX +#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_LINUX #ifndef __cocos2dx_audioengine_h__ #define __cocos2dx_audioengine_h__ @@ -44,4 +44,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_LINUX +#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_LINUX 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 795ca1c2079c..59a06bc08119 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_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) #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 4d8bdd50ecb5..aec3e122fcca 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_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) #ifndef __cocos2dx_controller_h__ #define __cocos2dx_controller_h__ @@ -35,4 +35,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_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) diff --git a/cocos/scripting/lua-bindings/manual/CCLuaEngine.cpp b/cocos/scripting/lua-bindings/manual/CCLuaEngine.cpp index 8637fa8d8b4d..f660207679d7 100644 --- a/cocos/scripting/lua-bindings/manual/CCLuaEngine.cpp +++ b/cocos/scripting/lua-bindings/manual/CCLuaEngine.cpp @@ -36,7 +36,14 @@ #include "base/CCDirector.h" #include "base/CCEventCustom.h" -#pragma comment(lib,"lua51.lib") +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + #if _MSC_VER + #pragma comment(lib,"lua51.lib") + #endif +#else + #pragma comment(lib,"lua51.lib") +#endif + NS_CC_BEGIN 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 94747b0934db..4dd0bd337393 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 @@ -24,7 +24,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" 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 011c1533f889..ec9fb01f6f07 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 @@ -33,7 +33,7 @@ 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); 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 c7f1f2cb6b49..29f29b9eafad 100644 --- a/cocos/scripting/lua-bindings/manual/network/Lua_web_socket.cpp +++ b/cocos/scripting/lua-bindings/manual/network/Lua_web_socket.cpp @@ -23,7 +23,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 91c1e8456667..f22c493b3a3e 100644 --- a/cocos/scripting/lua-bindings/manual/network/Lua_web_socket.h +++ b/cocos/scripting/lua-bindings/manual/network/Lua_web_socket.h @@ -25,7 +25,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 16854f53202d..ca7628ce8557 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 @@ -24,11 +24,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 @@ -42,11 +42,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_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) 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_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) tolua_web_socket_open(L); register_web_socket_manual(L); #endif 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 0a46ceba76ba..acadd4a3c8a0 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 @@ -24,7 +24,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_video_auto.hpp" #include "scripting/lua-bindings/manual/ui/lua_cocos2dx_video_manual.hpp" #include "scripting/lua-bindings/auto/lua_cocos2dx_webview_auto.hpp" @@ -1182,7 +1182,7 @@ int register_ui_module(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_video(L); register_all_cocos2dx_video_manual(L); register_all_cocos2dx_webview(L); diff --git a/cocos/scripting/lua-bindings/proj.ohos/CMakeLists.txt b/cocos/scripting/lua-bindings/proj.ohos/CMakeLists.txt new file mode 100644 index 000000000000..9af6505d42cb --- /dev/null +++ b/cocos/scripting/lua-bindings/proj.ohos/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.6) +set(target_name lua_ohos_spec) + +project(${target_name}) + +set(${target_name}_src + ../manual/CCLuaBridge.cpp +) + +add_library(${target_name} STATIC + ${${target_name}_src} +) + +get_target_property(lua_header ext_luajit INTERFACE_INCLUDE_DIRECTORIES) +get_target_property(tolua_header ext_tolua INTERFACE_INCLUDE_DIRECTORIES) + +target_include_directories(${target_name} + PRIVATE ../../.. + PRIVATE ../manual + PRIVATE ../manual/platform + PRIVATE ${lua_header} + PRIVATE ${tolua_header} +) \ No newline at end of file diff --git a/cocos/scripting/lua-bindings/script/cocos2d/Cocos2dConstants.lua b/cocos/scripting/lua-bindings/script/cocos2d/Cocos2dConstants.lua index 66a2b4c1c688..daa6ab406e80 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 bb9a4653bf23..021827d254f4 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/ui/CMakeLists.txt b/cocos/ui/CMakeLists.txt index 71de0da37165..01faf6ac558e 100644 --- a/cocos/ui/CMakeLists.txt +++ b/cocos/ui/CMakeLists.txt @@ -70,6 +70,20 @@ elseif(ANDROID) # it's special for android, not a common file ui/UIWebView/UIWebView.cpp ) +elseif(OHOS) + set(COCOS_UI_SPECIFIC_HEADER + ui/UIWebView/UIWebView.h + ui/UIEditBox/UIEditBoxImpl-ohos.h + ui/UIWebView/UIWebViewImpl-ohos.h + ui/UIVideoPlayer.h + ui/UIVideoPlayer-ohos.h + ) + set(COCOS_UI_SPECIFIC_SRC + ui/UIEditBox/UIEditBoxImpl-ohos.cpp + ui/UIWebView/UIWebViewImpl-ohos.cpp + ui/UIWebView/UIWebView.cpp + ui/UIVideoPlayer-ohos.cpp + ) endif() set(COCOS_UI_HEADER diff --git a/cocos/ui/CocosGUI.h b/cocos/ui/CocosGUI.h index bcb8e5d94fe4..c868757f268a 100644 --- a/cocos/ui/CocosGUI.h +++ b/cocos/ui/CocosGUI.h @@ -47,7 +47,7 @@ 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) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS) #include "ui/UIVideoPlayer.h" #include "ui/UIWebView/UIWebView.h" #endif 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..44fb92f180a9 --- /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 setNativeTextHorizontalAlignment(cocos2d::TextHAlignment alignment) 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__ */ \ No newline at end of file diff --git a/cocos/ui/UIVideoPlayer-ohos.cpp b/cocos/ui/UIVideoPlayer-ohos.cpp new file mode 100644 index 000000000000..83537836145d --- /dev/null +++ b/cocos/ui/UIVideoPlayer-ohos.cpp @@ -0,0 +1,277 @@ +/**************************************************************************** + 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::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) { + // Add an index. + _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..4b27f076918c --- /dev/null +++ b/cocos/ui/UIVideoPlayer-ohos.h @@ -0,0 +1,5 @@ +#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 53c61532641e..7cf2e1ea7590 100644 --- a/cocos/ui/UIVideoPlayer.h +++ b/cocos/ui/UIVideoPlayer.h @@ -24,7 +24,7 @@ ****************************************************************************/ #pragma once -#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" diff --git a/cocos/ui/UIWebView/UIWebView.cpp b/cocos/ui/UIWebView/UIWebView.cpp index d8a7e30bcfda..e4c3ed1432f3 100644 --- a/cocos/ui/UIWebView/UIWebView.cpp +++ b/cocos/ui/UIWebView/UIWebView.cpp @@ -22,5 +22,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "ui/UIWebView/UIWebViewImpl-android.h" -#include "ui/UIWebView/UIWebView-inl.h" + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + #include "ui/UIWebView/UIWebViewImpl-ohos.h" + #include "ui/UIWebView/UIWebView-inl.h" +#else + #include "ui/UIWebView/UIWebViewImpl-android.h" + #include "ui/UIWebView/UIWebView-inl.h" +#endif \ No newline at end of file diff --git a/cocos/ui/UIWebView/UIWebViewImpl-ohos.cpp b/cocos/ui/UIWebView/UIWebViewImpl-ohos.cpp new file mode 100644 index 000000000000..251cafe29736 --- /dev/null +++ b/cocos/ui/UIWebView/UIWebViewImpl-ohos.cpp @@ -0,0 +1,212 @@ +/**************************************************************************** + 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/UIWebView/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/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 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; + // TODO There is a delay when the web component is bound to the controller. The sleep function is used to avoid errors. The onControllerAttached function that may be opened by API10 is used to control the binding. + OHOS_LOGD("webview will sleep for 2s"); + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + } + + 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) { + // The official website interface does not provide cache-related parameters. Therefore, the implementation of loadUrl is the same as that of the previous 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 + +NS_CC_END \ No newline at end of file diff --git a/cocos/ui/UIWebView/UIWebViewImpl-ohos.h b/cocos/ui/UIWebView/UIWebViewImpl-ohos.h new file mode 100644 index 000000000000..417b7ca0557c --- /dev/null +++ b/cocos/ui/UIWebView/UIWebViewImpl-ohos.h @@ -0,0 +1,107 @@ +/**************************************************************************** + 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 ui{ + class WebView; + } +} + +namespace cocos2d { + namespace ui{ + + class WebViewImpl { + public: + WebViewImpl(cocos2d::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 +} //cocos2d + + +/// @endcond +#endif /* __COCOS2D__UI__WEBVIEWIMPL_OPENHARMONY_H_ */ diff --git a/extensions/Particle3D/PU/CCPUMaterialManager.cpp b/extensions/Particle3D/PU/CCPUMaterialManager.cpp index fdbd2d58697c..fc33f79dfdcb 100644 --- a/extensions/Particle3D/PU/CCPUMaterialManager.cpp +++ b/extensions/Particle3D/PU/CCPUMaterialManager.cpp @@ -42,6 +42,11 @@ #include #include #include +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) +#include "platform/ohos/CCFileUtils-ohos.h" +#include +#include +#include #endif NS_CC_BEGIN @@ -160,7 +165,17 @@ bool PUMaterialCache::loadMaterialsFromSearchPaths( const std::string &fileFolde } } AAssetDir_close(dir); - +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + FileUtilsOhos *fileUtilsOhos = new FileUtilsOhos(); + std::vector files = fileUtilsOhos -> listFiles(fileFolder); + for (auto fileName : files) { + if (FileUtils::getInstance()->getFileExtension(fileName) == ".material") + { + std::string fullpath = std::string(fileName); + loadMaterials(fullpath); + state = true; + } + } #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) diff --git a/tests/cpp-tests/CMakeLists.txt b/tests/cpp-tests/CMakeLists.txt index 20621de2a700..5166c34eace1 100644 --- a/tests/cpp-tests/CMakeLists.txt +++ b/tests/cpp-tests/CMakeLists.txt @@ -39,6 +39,11 @@ if(ANDROID) Classes/JNITest/JNITest.cpp proj.android/app/jni/main.cpp ) +elseif(OHOS) + set(APP_NAME cpp_tests) + list(APPEND GAME_SOURCE + proj.ohos/entry/src/main/cpp/main.cpp + ) elseif(LINUX) list(APPEND GAME_SOURCE proj.linux/main.cpp @@ -335,7 +340,7 @@ list(APPEND GAME_SOURCE Classes/ZipTest/ZipTests.cpp ) -if(ANDROID OR IOS) +if(ANDROID OR IOS OR OHOS) list(APPEND GAME_HEADER Classes/UITest/CocoStudioGUITest/UIVideoPlayerTest/UIVideoPlayerTest.h Classes/UITest/CocoStudioGUITest/UIWebViewTest/UIWebViewTest.h @@ -386,7 +391,9 @@ set(all_code_files ${GAME_SOURCE} ) -if(NOT ANDROID) +if (OHOS) + add_library(${APP_NAME} STATIC ${all_code_files}) +elseif(NOT ANDROID) add_executable(${APP_NAME} ${all_code_files}) else() add_library(${APP_NAME} SHARED ${all_code_files}) diff --git a/tests/cpp-tests/Classes/AppDelegate.cpp b/tests/cpp-tests/Classes/AppDelegate.cpp index ad6b543c506b..3c9d688f6bf6 100644 --- a/tests/cpp-tests/Classes/AppDelegate.cpp +++ b/tests/cpp-tests/Classes/AppDelegate.cpp @@ -29,7 +29,10 @@ #include "controller.h" // #include "editor-support/cocostudio/CocoStudio.h" #include "extensions/cocos-ext.h" - +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) +#include "audio/include/AudioEngine.h" +#include "editor-support/cocostudio/CocoStudio.h" +#endif USING_NS_CC; AppDelegate::AppDelegate() @@ -40,8 +43,9 @@ AppDelegate::AppDelegate() AppDelegate::~AppDelegate() { //SimpleAudioEngine::end(); - //TODO: minggo - // cocostudio::ArmatureDataManager::destroyInstance(); + #if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + cocostudio::ArmatureDataManager::destroyInstance(); + #endif } // if you want a different context, modify the value of glContextAttrs @@ -73,14 +77,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"); @@ -114,10 +118,15 @@ void AppDelegate::applicationDidEnterBackground() { if (_testController) { -// _testController->onEnterBackground(); + #if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + _testController->onEnterBackground(); + #endif } Director::getInstance()->stopAnimation(); + #if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + _testController->onEnterBackground(); + #endif } // this function will be called when the app is active again @@ -125,8 +134,28 @@ void AppDelegate::applicationWillEnterForeground() { if (_testController) { -// _testController->onEnterForeground(); + #if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + _testController->onEnterForeground(); + #endif } Director::getInstance()->startAnimation(); + // resume audioEngine, otherwise the opensl audioPlayer will always be suspended. + #if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + _testController->onEnterForeground(); + #endif +} + +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 6a7ff311ecca..d72458990351 100644 --- a/tests/cpp-tests/Classes/AppDelegate.h +++ b/tests/cpp-tests/Classes/AppDelegate.h @@ -62,6 +62,13 @@ class AppDelegate : private cocos2d::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); + private: TestController* _testController; }; diff --git a/tests/cpp-tests/Classes/CurlTest/CurlTest.cpp b/tests/cpp-tests/Classes/CurlTest/CurlTest.cpp index 840c85344ed8..56469a852186 100644 --- a/tests/cpp-tests/Classes/CurlTest/CurlTest.cpp +++ b/tests/cpp-tests/Classes/CurlTest/CurlTest.cpp @@ -96,7 +96,11 @@ 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"); + #if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + curl_easy_setopt(curl, CURLOPT_URL, "http://httpbin.org/get"); + #else + curl_easy_setopt(curl, CURLOPT_URL, "http://webtest.cocos2d-x.org/curltest"); + #endif //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/ExtensionsTest/NetworkTest/WebSocketTest.cpp b/tests/cpp-tests/Classes/ExtensionsTest/NetworkTest/WebSocketTest.cpp index 39eade755380..5f0fe008db05 100644 --- a/tests/cpp-tests/Classes/ExtensionsTest/NetworkTest/WebSocketTest.cpp +++ b/tests/cpp-tests/Classes/ExtensionsTest/NetworkTest/WebSocketTest.cpp @@ -134,7 +134,24 @@ void WebSocketTest::startTestCallback(Ref* sender) _wsiSendBinary = new network::WebSocket(); _wsiError = new network::WebSocket(); - std::vector protocols; + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + if (!_wsiSendText->init(*this, "ws://echo.websocket.events")) + { + CC_SAFE_DELETE(_wsiSendText); + } + + if (!_wsiSendBinary->init(*this, "ws://echo.websocket.events")) + { + CC_SAFE_DELETE(_wsiSendBinary); + } + + if (!_wsiError->init(*this, "ws://invalid.url.com")) + { + CC_SAFE_DELETE(_wsiError); + } +#else + std::vector protocols; protocols.push_back("myprotocol_1"); protocols.push_back("myprotocol_2"); if (!_wsiSendText->init(*this, "wss://echo.websocket.org", &protocols, "cacert.pem")) @@ -164,12 +181,28 @@ void WebSocketTest::startTestCallback(Ref* sender) { retain(); // Retain self to avoid WebSocketTest instance be deleted immediately, it will be released in WebSocketTest::onClose. } +#endif } // Delegate methods void WebSocketTest::onOpen(network::WebSocket* ws) { - char status[256] = {0}; +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + log("Websocket (%p) opened", ws); + if (ws == _wsiSendText) + { + _sendTextStatus->setString("Send Text WS was opened."); + } + else if (ws == _wsiSendBinary) + { + _sendBinaryStatus->setString("Send Binary WS was opened."); + } + else if (ws == _wsiError) + { + CCASSERT(0, "error test will never go here."); + } +#else + char status[256] = {0}; sprintf(status, "Opened, url: %s, protocol: %s", ws->getUrl().c_str(), ws->getProtocol().c_str()); log("Websocket (%p) was opened, url: %s, protocol: %s", ws, ws->getUrl().c_str(), ws->getProtocol().c_str()); @@ -185,6 +218,7 @@ void WebSocketTest::onOpen(network::WebSocket* ws) { CCASSERT(0, "error test will never go here."); } +#endif } void WebSocketTest::onMessage(network::WebSocket* ws, const network::WebSocket::Data& data) diff --git a/tests/cpp-tests/Classes/FontTest/FontTest.cpp b/tests/cpp-tests/Classes/FontTest/FontTest.cpp index a188d178f663..e43bb231449b 100644 --- a/tests/cpp-tests/Classes/FontTest/FontTest.cpp +++ b/tests/cpp-tests/Classes/FontTest/FontTest.cpp @@ -94,14 +94,23 @@ void FontTest::showFont(const std::string& fontFile) removeChildByTag(kTagColor1, true); removeChildByTag(kTagColor2, true); removeChildByTag(kTagColor3, true); - - auto top = Label::createWithSystemFont(fontFile, fontFile, 24); +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) + auto top = Label::createWithTTF(fontFile, fontFile, 24); + auto left = Label::createWithTTF("alignment left", fontFile, fontSize, + blockSize, TextHAlignment::LEFT, verticalAlignment[vAlignIdx]); + auto center = Label::createWithTTF("alignment center", fontFile, fontSize, + blockSize, TextHAlignment::CENTER, verticalAlignment[vAlignIdx]); + auto right = Label::createWithTTF("alignment right", fontFile, fontSize, + blockSize, TextHAlignment::RIGHT, verticalAlignment[vAlignIdx]); +#else +auto top = Label::createWithSystemFont(fontFile, fontFile, 24); auto left = Label::createWithSystemFont("alignment left", fontFile, fontSize, blockSize, TextHAlignment::LEFT, verticalAlignment[vAlignIdx]); auto center = Label::createWithSystemFont("alignment center", fontFile, fontSize, blockSize, TextHAlignment::CENTER, verticalAlignment[vAlignIdx]); auto right = Label::createWithSystemFont("alignment right", fontFile, fontSize, blockSize, TextHAlignment::RIGHT, verticalAlignment[vAlignIdx]); +#endif auto leftColor = LayerColor::create(Color4B(100, 100, 100, 255), blockSize.width, blockSize.height); auto centerColor = LayerColor::create(Color4B(200, 100, 100, 255), blockSize.width, blockSize.height); diff --git a/tests/cpp-tests/Classes/UITest/CocoStudioGUITest/CocosGUIScene.cpp b/tests/cpp-tests/Classes/UITest/CocoStudioGUITest/CocosGUIScene.cpp index 909820433577..1a7fcf3b86e1 100644 --- a/tests/cpp-tests/Classes/UITest/CocoStudioGUITest/CocosGUIScene.cpp +++ b/tests/cpp-tests/Classes/UITest/CocoStudioGUITest/CocosGUIScene.cpp @@ -43,22 +43,22 @@ #include "UIFocusTest/UIFocusTest.h" #include "UITabControlTest/UITabControlTest.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) #include "UIVideoPlayerTest/UIVideoPlayerTest.h" #include "UIWebViewTest/UIWebViewTest.h" #endif #include "UIScale9SpriteTest.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) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) || (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) #include "UIEditBoxTest.h" #endif GUIDynamicCreateTests::GUIDynamicCreateTests() { -#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) addTest("VideoPlayer Test", [](){ return new (std::nothrow) VideoPlayerTests; }); addTest("WebView Test", [](){ return new (std::nothrow) WebViewTests; }); #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) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) || (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) addTest("EditBox Test", [](){ return new (std::nothrow) UIEditBoxTests; }); #endif addTest("Focus Test", [](){ return new (std::nothrow) UIFocusTests; }); diff --git a/tests/cpp-tests/Classes/controller.cpp b/tests/cpp-tests/Classes/controller.cpp index f6bfad54ce92..06196a1c119d 100644 --- a/tests/cpp-tests/Classes/controller.cpp +++ b/tests/cpp-tests/Classes/controller.cpp @@ -119,7 +119,7 @@ class RootTests : public TestList addTest("Unzip Test", []() {return new ZipTests();}); addTest("URL Open Test", []() { return new OpenURLTests(); }); addTest("UserDefault", []() { return new UserDefaultTests(); }); -#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_OHOS) addTest("Vibrate", []() { return new VibrateTests(); }); #endif addTest("Zwoptex", []() { return new ZwoptexTests(); }); @@ -439,7 +439,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 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..02638fbb4338 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/.gitignore @@ -0,0 +1,12 @@ +.hvigor +.idea +local.properties +**/build +entry/src/main/resources/rawfile +oh_modules +/.clangd +/.clang-tidy +.clang-format +oh-package-lock.json5 +hvigor/hvigor-wrapper.js +**/BuildProfile.ets \ 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..ad3b33b64ab6 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/AppScope/app.json5 @@ -0,0 +1,11 @@ +{ + "app": { + "bundleName": "ohos.cocos4.0.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..a5bac90d177c --- /dev/null +++ b/tests/cpp-tests/proj.ohos/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "4.0_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..152b3d7908b4 --- /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_cVH5E_jhivWpH1G1xxY8OLXUTNTmD8SODpqOIrtme5E=.cer", + "storePassword": "0000001B4F9256B9589AA3C7B855A06AC92D658834853F8C2B4EC966AC87ACBAB09C95185F7283127CD564", + "keyAlias": "debugKey", + "keyPassword": "0000001BCED9F95CE302CD9391B6663DB41F568E9E8719DB4CF3AF8FE12B62CFAE42286FDEC991A568F9BC", + "profile": "C:\\Users\\cocos\\.ohos\\config\\default_proj.ohos_cVH5E_jhivWpH1G1xxY8OLXUTNTmD8SODpqOIrtme5E=.p7b", + "signAlg": "SHA256withECDSA", + "storeFile": "C:\\Users\\cocos\\.ohos\\config\\default_proj.ohos_cVH5E_jhivWpH1G1xxY8OLXUTNTmD8SODpqOIrtme5E=.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..282fb1b44130 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/.gitignore @@ -0,0 +1,5 @@ +.preview +build +.cxx +oh_modules +src/main/resources/rawfile \ No newline at end of file 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..6c1710f7f3e8 --- /dev/null +++ b/tests/cpp-tests/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" + } + ] +} \ No newline at end of file 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..80e4b79924fc --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/oh-package.json5 @@ -0,0 +1,13 @@ +{ + "license": "", + "devDependencies": {}, + "author": "", + "name": "entry", + "description": "Please describe the basic information.", + "main": "", + "version": "1.0.0", + "dependencies": { + "@ohos/libSysCapabilities": "../libSysCapabilities", + "libnativerender.so": "file:./src/main/cpp/types/libentry" + } +} 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..526c6ab6532f --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/cpp/CMakeLists.txt @@ -0,0 +1,26 @@ +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(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -frtti -fexceptions -fsigned-char") + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2") + +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 napi_init.cpp) + +target_include_directories(${PROJECT_NAME} PUBLIC ${CLASSES_PATH} + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/platform/ohos) + +add_subdirectory(${COCOS2DX_ROOT_PATH}/tests/cpp-tests cpp_tests) +find_library(hilog-lib hilog_ndk.z) +find_library(libuv-lib uv) +find_library(libvsync-lib native_vsync) +target_link_libraries(${PROJECT_NAME} PUBLIC cpp_tests ${hilog-lib} ${libuv-lib} ${libvsync-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..9c78ead64d45 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/cpp/main.cpp @@ -0,0 +1,23 @@ +#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 (!Director::getInstance()->getOpenGLView()) { + OHOS_LOGD("Cocos2dxRenderer_nativeInit() - branch 1"); + GLView *view = GLViewImpl::sharedOpenGLView(); + view->setFrameSize(w, h); + Director::getInstance()->setOpenGLView(view); + + AppDelegate *pAppDelegate = new AppDelegate(); + Application::getInstance()->run(); + } +} + +} \ 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..5524d88b40bb --- /dev/null +++ b/tests/cpp-tests/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/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..039f1682c3da --- /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; \ No newline at end of file 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..41422338271b --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/ets/components/CocosVideoPlayer.ets @@ -0,0 +1,54 @@ +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..67ae36730878 --- /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); + +@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 can be customized + color: '#000000' + }, + { + text: '确认', + // Color can be customized + 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/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..138b986dc3da --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/ets/workers/CocosWorker.ts @@ -0,0 +1,81 @@ +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..466b7f601b49 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/entry/src/main/module.json5 @@ -0,0 +1,83 @@ +{ + "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": "auto_rotation_portrait", + "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"], +// 游戏主窗口固定窗口化 +// "supportWindowMode": ["floating"], +// 如果有全屏需求,可以同时加上fullscreen和floating,可以做到窗口化按这只宽高显示,全屏按全屏方案显示 +// "supportWindowMode": ["fullscreen", "floating"], + //最大宽高比 +// "maxWindowRatio": 3.5, + //最小宽高比 +// "minWindowRatio": 0.5, + //最大宽度为1080 + "maxWindowWidth": 1080, + //最小宽度为1080 + "minWindowWidth": 1080, + //最大高度为720 + "maxWindowHeight": 720, + //最小高度为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..650bf79b61ea --- /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": "4.0_cpp_tests" + }, + { + "name": "MainAbility_label", + "value": "4.0_cpp_tests" + }, + { + "name": "MainAbility_desc", + "value": "description" + }, + { + "name": "text_field_ttf_complete", + "value": "完成" + } + ] +} \ No newline at end of file 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/build-profile.json5 b/tests/cpp-tests/proj.ohos/libSysCapabilities/build-profile.json5 new file mode 100644 index 000000000000..50d055b1eca9 --- /dev/null +++ b/tests/cpp-tests/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/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..8c935e9b2a00 --- /dev/null +++ b/tests/cpp-tests/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/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..5ba41181de62 --- /dev/null +++ b/tests/cpp-tests/proj.ohos/libSysCapabilities/src/main/ets/napi/NapiHelper.ts @@ -0,0 +1,104 @@ +import { Dialog } from '../components/dialog/DialogWorker' +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' + +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); + } + + private static registerOthers(registerFunc : Function) { + registerFunc('DiaLog.showDialog', Dialog.showDialog); + registerFunc('DiaLog.showTextInputDialog', Dialog.showTextInputDialog); + registerFunc('DiaLog.hideTextInputDialog', Dialog.hideTextInputDialog); + 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); + } +} \ 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/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..68eed70e4e07 --- /dev/null +++ b/tests/cpp-tests/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/project/CMakeLists.txt b/tests/lua-tests/project/CMakeLists.txt index 4c052f3f33f0..2003cbfa9d54 100644 --- a/tests/lua-tests/project/CMakeLists.txt +++ b/tests/lua-tests/project/CMakeLists.txt @@ -51,6 +51,9 @@ if(ANDROID) # change APP_NAME to the share library name for Android, it's value depend on AndroidManifest.xml set(APP_NAME lua_tests) list(APPEND GAME_SOURCE proj.android/app/jni/main.cpp) +elseif(OHOS) + set(APP_NAME lua_test) + list(APPEND GAME_SOURCE proj.ohos/entry/src/main/cpp/main.cpp) elseif(LINUX) list(APPEND GAME_SOURCE proj.linux/main.cpp) elseif(WINDOWS) @@ -97,7 +100,10 @@ endif() set(APP_SRC ${GAME_HEADER} ${GAME_SOURCE}) -if(NOT ANDROID) +if(OHOS) + add_library(${APP_NAME} STATIC ${APP_SRC}) + add_subdirectory(${COCOS2DX_ROOT_PATH}/cocos/platform ${ENGINE_BINARY_PATH}/cocos/platform) +elseif(NOT ANDROID) add_executable(${APP_NAME} ${APP_SRC}) else() add_library(${APP_NAME} SHARED ${APP_SRC}) diff --git a/tests/lua-tests/project/Classes/AppDelegate.cpp b/tests/lua-tests/project/Classes/AppDelegate.cpp index d64f309fc072..ed73e0140f6c 100644 --- a/tests/lua-tests/project/Classes/AppDelegate.cpp +++ b/tests/lua-tests/project/Classes/AppDelegate.cpp @@ -63,7 +63,7 @@ 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_OHOS || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID ||CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC) register_assetsmanager_test_sample(L); #endif register_test_binding(L); @@ -91,3 +91,17 @@ void AppDelegate::applicationWillEnterForeground() { Director::getInstance()->startAnimation(); } + +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/lua-tests/project/Classes/AppDelegate.h b/tests/lua-tests/project/Classes/AppDelegate.h index fd8b0d1e48ba..d453c99aebfc 100644 --- a/tests/lua-tests/project/Classes/AppDelegate.h +++ b/tests/lua-tests/project/Classes/AppDelegate.h @@ -57,6 +57,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..02638fbb4338 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/.gitignore @@ -0,0 +1,12 @@ +.hvigor +.idea +local.properties +**/build +entry/src/main/resources/rawfile +oh_modules +/.clangd +/.clang-tidy +.clang-format +oh-package-lock.json5 +hvigor/hvigor-wrapper.js +**/BuildProfile.ets \ 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..6fe64ab4eefc --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/AppScope/app.json5 @@ -0,0 +1,11 @@ +{ + "app": { + "bundleName": "ohos.cocos4.0.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..7a36a2897ac9 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "proj.ohos" + } + ] +} 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..60a4af93eafb --- /dev/null +++ b/tests/lua-tests/project/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_32FW5HsNkJyzFFFT9hlMA2WNHmcv-u5J1UgjBwHhFPk=.cer", + "storePassword": "0000001B3A17FA76C0745EEEDCEA7A0AF53793BBE365074D6E392AAC8E1C974A73D2B1AEC1858881B528AA", + "keyAlias": "debugKey", + "keyPassword": "0000001B8BBD1EA51FC488420CD631D851E880FFFE55088F4B8178FD4C0F8AC38D7CA8F1A91B0A4AD9A3B1", + "profile": "C:\\Users\\cocos\\.ohos\\config\\default_proj.ohos_32FW5HsNkJyzFFFT9hlMA2WNHmcv-u5J1UgjBwHhFPk=.p7b", + "signAlg": "SHA256withECDSA", + "storeFile": "C:\\Users\\cocos\\.ohos\\config\\default_proj.ohos_32FW5HsNkJyzFFFT9hlMA2WNHmcv-u5J1UgjBwHhFPk=.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/lua-tests/project/proj.ohos/entry/.gitignore b/tests/lua-tests/project/proj.ohos/entry/.gitignore new file mode 100644 index 000000000000..282fb1b44130 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/.gitignore @@ -0,0 +1,5 @@ +.preview +build +.cxx +oh_modules +src/main/resources/rawfile \ No newline at end of file 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..6c1710f7f3e8 --- /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" + } + ] +} \ No newline at end of file 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.json5 b/tests/lua-tests/project/proj.ohos/entry/oh-package.json5 new file mode 100644 index 000000000000..dbfd3d89d2d6 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/oh-package.json5 @@ -0,0 +1,13 @@ +{ + "license": "", + "devDependencies": {}, + "author": "", + "name": "entry", + "description": "Please describe the basic information.", + "main": "", + "version": "1.0.0", + "dependencies": { + "libnativerender.so": "file:./src/main/cpp/types/libentry", + "@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..afbd1ddec527 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/CMakeLists.txt @@ -0,0 +1,31 @@ +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(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -frtti -fexceptions -fsigned-char") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2") + +set(res_raw_folders "${CMAKE_CURRENT_LIST_DIR}/../resources/rawfile") +file(GLOB ALL_RESOURCES_FILES "${CLASSES_PATH}/../../res/*" "${COCOS2DX_ROOT_PATH}/tests/cpp-tests/Resources/*") +file(COPY ${ALL_RESOURCES_FILES} DESTINATION ${res_raw_folders}/res) + +file(GLOB ALL_SCRIPT_FILES "${COCOS2DX_ROOT_PATH}/cocos/scripting/lua-bindings/script/*") +file(COPY ${ALL_SCRIPT_FILES} DESTINATION ${res_raw_folders}/cocos) + +file(GLOB ALL_SOURCE_FILES "${CLASSES_PATH}/../../src") +file(COPY ${ALL_SOURCE_FILES} DESTINATION ${res_raw_folders}) + +add_library(${PROJECT_NAME} SHARED napi_init.cpp) + +include_directories( + ${COCOS2DX_ROOT_PATH}/external/lua/luajit/include) +target_include_directories(${PROJECT_NAME} PUBLIC ${CLASSES_PATH} + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos + PUBLIC ${COCOS2DX_ROOT_PATH}/cocos/platform/ohos) + +add_subdirectory(${COCOS2DX_ROOT_PATH}/tests/lua-tests/project lua_test) +find_library(hilog-lib hilog_ndk.z) +find_library(libuv-lib uv) +target_link_libraries(${PROJECT_NAME} PUBLIC lua_test ext_luajit ${hilog-lib} ${libuv-lib}) \ 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..9c78ead64d45 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/cpp/main.cpp @@ -0,0 +1,23 @@ +#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 (!Director::getInstance()->getOpenGLView()) { + OHOS_LOGD("Cocos2dxRenderer_nativeInit() - branch 1"); + GLView *view = GLViewImpl::sharedOpenGLView(); + view->setFrameSize(w, h); + Director::getInstance()->setOpenGLView(view); + + AppDelegate *pAppDelegate = new AppDelegate(); + Application::getInstance()->run(); + } +} + +} \ 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..039f1682c3da --- /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; \ No newline at end of file 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..a89ce067f484 --- /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..41422338271b --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/components/CocosVideoPlayer.ets @@ -0,0 +1,54 @@ +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..d3de977c879f --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/components/TextInputDialog.ets @@ -0,0 +1,34 @@ +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) + .defaultFocus(true) + .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..67ae36730878 --- /dev/null +++ b/tests/lua-tests/project/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); + +@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 can be customized + color: '#000000' + }, + { + text: '确认', + // Color can be customized + 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..138b986dc3da --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/ets/workers/CocosWorker.ts @@ -0,0 +1,81 @@ +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..9560c07785b9 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/module.json5 @@ -0,0 +1,82 @@ +{ + "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" + ] + } + ], + // https://docs.openharmony.cn/pages/v4.0/zh-cn/application-dev/quick-start/module-configuration-file.md/ + //ģʽΪֻ֧ȫ + "supportWindowMode": ["fullscreen"], +// Ϸڹ̶ڻ +// "supportWindowMode": ["floating"], +// ȫ󣬿ͬʱfullscreenfloatingڻֻʾȫȫʾ +// "supportWindowMode": ["fullscreen", "floating"], + //߱ +// "maxWindowRatio": 3.5, + //С߱ +// "minWindowRatio": 0.5, + //Ϊ1080 + "maxWindowWidth": 1080, + //СΪ1080 + "minWindowWidth": 1080, + //߶Ϊ720 + "maxWindowHeight": 720, + //С߶Ϊ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" + } + ] + } +} \ No newline at end of file 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..09c22d01307c --- /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": "4.0_lua_tests" + }, + { + "name": "MainAbility_label", + "value": "4.0_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/entry/src/main/resources/en_US/element/string.json b/tests/lua-tests/project/proj.ohos/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 000000000000..f94595515a99 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/tests/lua-tests/project/proj.ohos/entry/src/main/resources/zh_CN/element/string.json b/tests/lua-tests/project/proj.ohos/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 000000000000..597ecf95e61d --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file 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/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/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..7fc4de231c42 --- /dev/null +++ b/tests/lua-tests/project/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; + } +} 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..997768cf2682 --- /dev/null +++ b/tests/lua-tests/project/proj.ohos/libSysCapabilities/src/main/ets/napi/NapiHelper.ts @@ -0,0 +1,104 @@ +import { Dialog } from '../components/dialog/DialogWorker' +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' + +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); + } + + private static registerOthers(registerFunc : Function) { + registerFunc('DiaLog.showDialog', Dialog.showDialog); + registerFunc('DiaLog.showTextInputDialog', Dialog.showTextInputDialog); + registerFunc('DiaLog.hideTextInputDialog', Dialog.hideTextInputDialog); + 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); + } +} 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..5fa53a657e33 --- /dev/null +++ b/tests/lua-tests/project/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/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/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 3a493f32a091..676791b8a358 100644 --- a/tests/lua-tests/src/Camera3DTest/Camera3DTest.lua +++ b/tests/lua-tests/src/Camera3DTest/Camera3DTest.lua @@ -783,7 +783,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/ExtensionTest/ExtensionTest.lua b/tests/lua-tests/src/ExtensionTest/ExtensionTest.lua index c62ed6e81c44..f1ba31f5b6f0 100644 --- a/tests/lua-tests/src/ExtensionTest/ExtensionTest.lua +++ b/tests/lua-tests/src/ExtensionTest/ExtensionTest.lua @@ -171,13 +171,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 1744fea74522..16fdc58332c2 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 cc.AudioEngine:play2d("background.caf") end + if (cc.PLATFORM_OS_OHOS == targetPlatform) then + cc.AudioEngine:play2d("background.wav") + end end local playItem1 = cc.MenuItemFont:create("play unsupported media type") diff --git a/tests/lua-tests/src/Scene3DTest/Scene3DTest.lua b/tests/lua-tests/src/Scene3DTest/Scene3DTest.lua index 232a6cc4d8ff..9447e10d6b6e 100644 --- a/tests/lua-tests/src/Scene3DTest/Scene3DTest.lua +++ b/tests/lua-tests/src/Scene3DTest/Scene3DTest.lua @@ -620,7 +620,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/Sprite3DTest/Sprite3DTest.lua b/tests/lua-tests/src/Sprite3DTest/Sprite3DTest.lua index 47f44d926a93..b66f1b0e77c4 100644 --- a/tests/lua-tests/src/Sprite3DTest/Sprite3DTest.lua +++ b/tests/lua-tests/src/Sprite3DTest/Sprite3DTest.lua @@ -1110,7 +1110,7 @@ end function Sprite3DCubeMapTest: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 95b08e603a9a..f065fb45f805 100644 --- a/tests/lua-tests/src/controller.lua +++ b/tests/lua-tests/src/controller.lua @@ -1,4 +1,6 @@ - +-- jit off +local jit = require("jit") +jit.off() -- avoid memory leak collectgarbage("setpause", 100) collectgarbage("setstepmul", 5000) @@ -10,8 +12,10 @@ 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,960,640)) + glView = cc.GLViewImpl:createWithRect("Lua Tests", cc.rect(0,0,widthx,heighty)) director:setOpenGLView(glView) end @@ -23,10 +27,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 971c82e98854..8da1ca8c2346 100644 --- a/tests/lua-tests/src/mainMenu.lua +++ b/tests/lua-tests/src/mainMenu.lua @@ -71,7 +71,7 @@ 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 @@ -211,7 +211,7 @@ function CreateTestMenu() if obj.name == "WebViewTest" or obj.name == "VibrateTest" or 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