no message
This commit is contained in:
598
cocos/audio/AudioEngine.cpp
Normal file
598
cocos/audio/AudioEngine.cpp
Normal file
@@ -0,0 +1,598 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "audio/include/AudioEngine.h"
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include "base/Log.h"
|
||||
#include "base/Utils.h"
|
||||
#include "base/memory/Memory.h"
|
||||
#include "base/std/container/queue.h"
|
||||
#include "platform/FileUtils.h"
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
// OpenHarmony and Android use the same audio playback module
|
||||
#include "audio/android/AudioEngine-inl.h"
|
||||
#elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS
|
||||
#include "audio/apple/AudioEngine-inl.h"
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS || CC_PLATFORM == CC_PLATFORM_OHOS
|
||||
#include "audio/oalsoft/AudioEngine-soft.h"
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINRT
|
||||
#include "audio/winrt/AudioEngine-winrt.h"
|
||||
#elif CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX
|
||||
#include "audio/oalsoft/AudioEngine-soft.h"
|
||||
#elif CC_PLATFORM == CC_PLATFORM_TIZEN
|
||||
#include "audio/tizen/AudioEngine-tizen.h"
|
||||
#endif
|
||||
|
||||
#define TIME_DELAY_PRECISION 0.0001
|
||||
|
||||
#ifdef ERROR
|
||||
#undef ERROR
|
||||
#endif // ERROR
|
||||
|
||||
namespace cc {
|
||||
|
||||
const int AudioEngine::INVALID_AUDIO_ID = -1;
|
||||
const float AudioEngine::TIME_UNKNOWN = -1.0F;
|
||||
|
||||
//audio file path,audio IDs
|
||||
ccstd::unordered_map<ccstd::string, ccstd::list<int>> AudioEngine::sAudioPathIDMap;
|
||||
//profileName,ProfileHelper
|
||||
ccstd::unordered_map<ccstd::string, AudioEngine::ProfileHelper> AudioEngine::sAudioPathProfileHelperMap;
|
||||
unsigned int AudioEngine::sMaxInstances = MAX_AUDIOINSTANCES;
|
||||
AudioEngine::ProfileHelper *AudioEngine::sDefaultProfileHelper = nullptr;
|
||||
ccstd::unordered_map<int, AudioEngine::AudioInfo> AudioEngine::sAudioIDInfoMap;
|
||||
AudioEngineImpl *AudioEngine::sAudioEngineImpl = nullptr;
|
||||
|
||||
float AudioEngine::sVolumeFactor = 1.0F;
|
||||
events::EnterBackground::Listener AudioEngine::sOnPauseListenerID;
|
||||
events::EnterForeground::Listener AudioEngine::sOnResumeListenerID;
|
||||
|
||||
ccstd::vector<int> AudioEngine::sBreakAudioID;
|
||||
|
||||
AudioEngine::AudioEngineThreadPool *AudioEngine::sThreadPool = nullptr;
|
||||
bool AudioEngine::sIsEnabled = true;
|
||||
|
||||
AudioEngine::AudioInfo::AudioInfo()
|
||||
: filePath(nullptr),
|
||||
profileHelper(nullptr),
|
||||
volume(1.0F),
|
||||
loop(false),
|
||||
duration(TIME_UNKNOWN),
|
||||
state(AudioState::INITIALIZING) {
|
||||
}
|
||||
|
||||
class AudioEngine::AudioEngineThreadPool {
|
||||
public:
|
||||
explicit AudioEngineThreadPool(int threads = 4) {
|
||||
for (int index = 0; index < threads; ++index) {
|
||||
_workers.emplace_back(std::thread([this]() {
|
||||
threadFunc();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void addTask(const std::function<void()> &task) {
|
||||
std::unique_lock<std::mutex> lk(_queueMutex);
|
||||
_taskQueue.emplace(task);
|
||||
_taskCondition.notify_one();
|
||||
}
|
||||
|
||||
~AudioEngineThreadPool() {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(_queueMutex);
|
||||
_stop = true;
|
||||
_taskCondition.notify_all();
|
||||
}
|
||||
|
||||
for (auto &&worker : _workers) {
|
||||
worker.join();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void threadFunc() {
|
||||
while (true) {
|
||||
std::function<void()> task = nullptr;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(_queueMutex);
|
||||
if (_stop) {
|
||||
break;
|
||||
}
|
||||
if (!_taskQueue.empty()) {
|
||||
task = std::move(_taskQueue.front());
|
||||
_taskQueue.pop();
|
||||
} else {
|
||||
_taskCondition.wait(lk);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
task();
|
||||
}
|
||||
}
|
||||
|
||||
ccstd::vector<std::thread> _workers;
|
||||
ccstd::queue<std::function<void()>> _taskQueue;
|
||||
|
||||
std::mutex _queueMutex;
|
||||
std::condition_variable _taskCondition;
|
||||
bool _stop{};
|
||||
};
|
||||
|
||||
void AudioEngine::end() {
|
||||
stopAll();
|
||||
|
||||
if (sThreadPool) {
|
||||
delete sThreadPool;
|
||||
sThreadPool = nullptr;
|
||||
}
|
||||
|
||||
delete sAudioEngineImpl;
|
||||
sAudioEngineImpl = nullptr;
|
||||
|
||||
delete sDefaultProfileHelper;
|
||||
sDefaultProfileHelper = nullptr;
|
||||
|
||||
sOnPauseListenerID.reset();
|
||||
sOnResumeListenerID.reset();
|
||||
}
|
||||
|
||||
bool AudioEngine::lazyInit() {
|
||||
if (sAudioEngineImpl == nullptr) {
|
||||
sAudioEngineImpl = ccnew AudioEngineImpl();
|
||||
if (!sAudioEngineImpl || !sAudioEngineImpl->init()) {
|
||||
delete sAudioEngineImpl;
|
||||
sAudioEngineImpl = nullptr;
|
||||
return false;
|
||||
}
|
||||
sOnPauseListenerID.bind(&onEnterBackground);
|
||||
sOnResumeListenerID.bind(&onEnterForeground);
|
||||
}
|
||||
|
||||
#if (CC_PLATFORM != CC_PLATFORM_ANDROID)
|
||||
if (sAudioEngineImpl && sThreadPool == nullptr) {
|
||||
sThreadPool = ccnew AudioEngineThreadPool();
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int AudioEngine::play2d(const ccstd::string &filePath, bool loop, float volume, const AudioProfile *profile) {
|
||||
int ret = AudioEngine::INVALID_AUDIO_ID;
|
||||
|
||||
do {
|
||||
if (!isEnabled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!lazyInit()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!FileUtils::getInstance()->isFileExist(filePath)) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto *profileHelper = sDefaultProfileHelper;
|
||||
if (profile && profile != &profileHelper->profile) {
|
||||
CC_ASSERT(!profile->name.empty());
|
||||
profileHelper = &sAudioPathProfileHelperMap[profile->name];
|
||||
profileHelper->profile = *profile;
|
||||
}
|
||||
|
||||
if (sAudioIDInfoMap.size() >= sMaxInstances) {
|
||||
CC_LOG_INFO("Fail to play %s cause by limited max instance of AudioEngine", filePath.c_str());
|
||||
break;
|
||||
}
|
||||
if (profileHelper) {
|
||||
if (profileHelper->profile.maxInstances != 0 && profileHelper->audioIDs.size() >= profileHelper->profile.maxInstances) {
|
||||
CC_LOG_INFO("Fail to play %s cause by limited max instance of AudioProfile", filePath.c_str());
|
||||
break;
|
||||
}
|
||||
if (profileHelper->profile.minDelay > TIME_DELAY_PRECISION) {
|
||||
auto currTime = std::chrono::high_resolution_clock::now();
|
||||
auto delay = static_cast<float>(std::chrono::duration_cast<std::chrono::microseconds>(currTime - profileHelper->lastPlayTime).count()) / 1000000.0;
|
||||
if (profileHelper->lastPlayTime.time_since_epoch().count() != 0 && delay <= profileHelper->profile.minDelay) {
|
||||
CC_LOG_INFO("Fail to play %s cause by limited minimum delay", filePath.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (volume < 0.0F) {
|
||||
volume = 0.0F;
|
||||
} else if (volume > 1.0F) {
|
||||
volume = 1.0F;
|
||||
}
|
||||
|
||||
ret = sAudioEngineImpl->play2d(filePath, loop, volume);
|
||||
if (ret != INVALID_AUDIO_ID) {
|
||||
sAudioPathIDMap[filePath].push_back(ret);
|
||||
auto it = sAudioPathIDMap.find(filePath);
|
||||
|
||||
auto &audioRef = sAudioIDInfoMap[ret];
|
||||
audioRef.volume = volume;
|
||||
audioRef.loop = loop;
|
||||
audioRef.filePath = &it->first;
|
||||
audioRef.state = AudioState::PLAYING;
|
||||
|
||||
if (profileHelper) {
|
||||
profileHelper->lastPlayTime = std::chrono::high_resolution_clock::now();
|
||||
profileHelper->audioIDs.push_back(ret);
|
||||
}
|
||||
audioRef.profileHelper = profileHelper;
|
||||
}
|
||||
} while (false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AudioEngine::setLoop(int audioID, bool loop) {
|
||||
auto it = sAudioIDInfoMap.find(audioID);
|
||||
if (it != sAudioIDInfoMap.end() && it->second.loop != loop) {
|
||||
sAudioEngineImpl->setLoop(audioID, loop);
|
||||
it->second.loop = loop;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngine::setVolume(int audioID, float volume) {
|
||||
auto it = sAudioIDInfoMap.find(audioID);
|
||||
if (it != sAudioIDInfoMap.end()) {
|
||||
if (volume < 0.0F) {
|
||||
volume = 0.0F;
|
||||
} else if (volume > 1.0F) {
|
||||
volume = 1.0F;
|
||||
}
|
||||
|
||||
if (it->second.volume != volume) {
|
||||
sAudioEngineImpl->setVolume(audioID, volume * sVolumeFactor);
|
||||
it->second.volume = volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngine::setVolumeFactor(float factor) {
|
||||
if (factor > 1.0F) {
|
||||
factor = 1.0F;
|
||||
}
|
||||
if (factor < 0) {
|
||||
factor = 0.0F;
|
||||
}
|
||||
sVolumeFactor = factor;
|
||||
for (auto &item : sAudioIDInfoMap) {
|
||||
sAudioEngineImpl->setVolume(item.first, item.second.volume * sVolumeFactor);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngine::pause(int audioID) {
|
||||
auto it = sAudioIDInfoMap.find(audioID);
|
||||
if (it != sAudioIDInfoMap.end() && it->second.state == AudioState::PLAYING) {
|
||||
sAudioEngineImpl->pause(audioID);
|
||||
it->second.state = AudioState::PAUSED;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngine::pauseAll() {
|
||||
auto itEnd = sAudioIDInfoMap.end();
|
||||
for (auto it = sAudioIDInfoMap.begin(); it != itEnd; ++it) {
|
||||
if (it->second.state == AudioState::PLAYING) {
|
||||
sAudioEngineImpl->pause(it->first);
|
||||
it->second.state = AudioState::PAUSED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngine::resume(int audioID) {
|
||||
auto it = sAudioIDInfoMap.find(audioID);
|
||||
if (it != sAudioIDInfoMap.end() && it->second.state == AudioState::PAUSED) {
|
||||
sAudioEngineImpl->resume(audioID);
|
||||
it->second.state = AudioState::PLAYING;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngine::resumeAll() {
|
||||
auto itEnd = sAudioIDInfoMap.end();
|
||||
for (auto it = sAudioIDInfoMap.begin(); it != itEnd; ++it) {
|
||||
if (it->second.state == AudioState::PAUSED) {
|
||||
sAudioEngineImpl->resume(it->first);
|
||||
it->second.state = AudioState::PLAYING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngine::onEnterBackground() {
|
||||
auto itEnd = sAudioIDInfoMap.end();
|
||||
for (auto it = sAudioIDInfoMap.begin(); it != itEnd; ++it) {
|
||||
if (it->second.state == AudioState::PLAYING) {
|
||||
sAudioEngineImpl->pause(it->first);
|
||||
it->second.state = AudioState::PAUSED;
|
||||
sBreakAudioID.push_back(it->first);
|
||||
}
|
||||
}
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
if (sAudioEngineImpl) {
|
||||
sAudioEngineImpl->onPause();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioEngine::onEnterForeground() {
|
||||
auto itEnd = sBreakAudioID.end();
|
||||
for (auto it = sBreakAudioID.begin(); it != itEnd; ++it) {
|
||||
auto iter = sAudioIDInfoMap.find(*it);
|
||||
if (iter != sAudioIDInfoMap.end() && iter->second.state == AudioState::PAUSED) {
|
||||
sAudioEngineImpl->resume(*it);
|
||||
iter->second.state = AudioState::PLAYING;
|
||||
}
|
||||
}
|
||||
sBreakAudioID.clear();
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
if (sAudioEngineImpl) {
|
||||
sAudioEngineImpl->onResume();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioEngine::stop(int audioID) {
|
||||
auto it = sAudioIDInfoMap.find(audioID);
|
||||
if (it != sAudioIDInfoMap.end()) {
|
||||
sAudioEngineImpl->stop(audioID);
|
||||
|
||||
remove(audioID);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngine::remove(int audioID) {
|
||||
auto it = sAudioIDInfoMap.find(audioID);
|
||||
if (it != sAudioIDInfoMap.end()) {
|
||||
if (it->second.profileHelper) {
|
||||
it->second.profileHelper->audioIDs.remove(audioID);
|
||||
}
|
||||
sAudioPathIDMap[*it->second.filePath].remove(audioID);
|
||||
sAudioIDInfoMap.erase(audioID);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngine::stopAll() {
|
||||
if (!sAudioEngineImpl) {
|
||||
return;
|
||||
}
|
||||
sAudioEngineImpl->stopAll();
|
||||
auto itEnd = sAudioIDInfoMap.end();
|
||||
for (auto it = sAudioIDInfoMap.begin(); it != itEnd; ++it) {
|
||||
if (it->second.profileHelper) {
|
||||
it->second.profileHelper->audioIDs.remove(it->first);
|
||||
}
|
||||
}
|
||||
sAudioPathIDMap.clear();
|
||||
sAudioIDInfoMap.clear();
|
||||
}
|
||||
|
||||
void AudioEngine::uncache(const ccstd::string &filePath) {
|
||||
auto audioIDsIter = sAudioPathIDMap.find(filePath);
|
||||
if (audioIDsIter != sAudioPathIDMap.end()) {
|
||||
//@Note: For safely iterating elements from the audioID list, we need to copy the list
|
||||
// since 'AudioEngine::remove' may be invoked in 'sAudioEngineImpl->stop' synchronously.
|
||||
// If this happens, it will break the iteration, and crash will appear on some devices.
|
||||
ccstd::list<int> copiedIDs(audioIDsIter->second);
|
||||
|
||||
for (int audioID : copiedIDs) {
|
||||
sAudioEngineImpl->stop(audioID);
|
||||
|
||||
auto itInfo = sAudioIDInfoMap.find(audioID);
|
||||
if (itInfo != sAudioIDInfoMap.end()) {
|
||||
if (itInfo->second.profileHelper) {
|
||||
itInfo->second.profileHelper->audioIDs.remove(audioID);
|
||||
}
|
||||
sAudioIDInfoMap.erase(audioID);
|
||||
}
|
||||
}
|
||||
sAudioPathIDMap.erase(filePath);
|
||||
}
|
||||
|
||||
if (sAudioEngineImpl) {
|
||||
sAudioEngineImpl->uncache(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngine::uncacheAll() {
|
||||
if (!sAudioEngineImpl) {
|
||||
return;
|
||||
}
|
||||
stopAll();
|
||||
sAudioEngineImpl->uncacheAll();
|
||||
}
|
||||
|
||||
float AudioEngine::getDuration(int audioID) {
|
||||
auto it = sAudioIDInfoMap.find(audioID);
|
||||
if (it != sAudioIDInfoMap.end() && it->second.state != AudioState::INITIALIZING) {
|
||||
if (it->second.duration == TIME_UNKNOWN) {
|
||||
it->second.duration = sAudioEngineImpl->getDuration(audioID);
|
||||
}
|
||||
return it->second.duration;
|
||||
}
|
||||
|
||||
return TIME_UNKNOWN;
|
||||
}
|
||||
|
||||
float AudioEngine::getDurationFromFile(const ccstd::string &filePath) {
|
||||
lazyInit();
|
||||
|
||||
if (sAudioEngineImpl) {
|
||||
return sAudioEngineImpl->getDurationFromFile(filePath);
|
||||
}
|
||||
|
||||
return TIME_UNKNOWN;
|
||||
}
|
||||
|
||||
bool AudioEngine::setCurrentTime(int audioID, float time) {
|
||||
auto it = sAudioIDInfoMap.find(audioID);
|
||||
if (it != sAudioIDInfoMap.end() && it->second.state != AudioState::INITIALIZING) {
|
||||
return sAudioEngineImpl->setCurrentTime(audioID, time);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
float AudioEngine::getCurrentTime(int audioID) {
|
||||
auto it = sAudioIDInfoMap.find(audioID);
|
||||
if (it != sAudioIDInfoMap.end() && it->second.state != AudioState::INITIALIZING) {
|
||||
return sAudioEngineImpl->getCurrentTime(audioID);
|
||||
}
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
void AudioEngine::setFinishCallback(int audioID, const std::function<void(int, const ccstd::string &)> &callback) {
|
||||
auto it = sAudioIDInfoMap.find(audioID);
|
||||
if (it != sAudioIDInfoMap.end()) {
|
||||
sAudioEngineImpl->setFinishCallback(audioID, callback);
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioEngine::setMaxAudioInstance(int maxInstances) {
|
||||
if (maxInstances > 0 && maxInstances <= MAX_AUDIOINSTANCES) {
|
||||
sMaxInstances = maxInstances;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioEngine::isLoop(int audioID) {
|
||||
auto tmpIterator = sAudioIDInfoMap.find(audioID);
|
||||
if (tmpIterator != sAudioIDInfoMap.end()) {
|
||||
return tmpIterator->second.loop;
|
||||
}
|
||||
|
||||
CC_LOG_INFO("AudioEngine::isLoop-->The audio instance %d is non-existent", audioID);
|
||||
return false;
|
||||
}
|
||||
|
||||
float AudioEngine::getVolume(int audioID) {
|
||||
auto tmpIterator = sAudioIDInfoMap.find(audioID);
|
||||
if (tmpIterator != sAudioIDInfoMap.end()) {
|
||||
return tmpIterator->second.volume;
|
||||
}
|
||||
|
||||
CC_LOG_INFO("AudioEngine::getVolume-->The audio instance %d is non-existent", audioID);
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
AudioEngine::AudioState AudioEngine::getState(int audioID) {
|
||||
auto tmpIterator = sAudioIDInfoMap.find(audioID);
|
||||
if (tmpIterator != sAudioIDInfoMap.end()) {
|
||||
return tmpIterator->second.state;
|
||||
}
|
||||
|
||||
return AudioState::ERROR;
|
||||
}
|
||||
|
||||
AudioProfile *AudioEngine::getProfile(int audioID) {
|
||||
auto it = sAudioIDInfoMap.find(audioID);
|
||||
if (it != sAudioIDInfoMap.end()) {
|
||||
return &it->second.profileHelper->profile;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AudioProfile *AudioEngine::getDefaultProfile() {
|
||||
if (sDefaultProfileHelper == nullptr) {
|
||||
sDefaultProfileHelper = ccnew ProfileHelper();
|
||||
}
|
||||
|
||||
return &sDefaultProfileHelper->profile;
|
||||
}
|
||||
|
||||
AudioProfile *AudioEngine::getProfile(const ccstd::string &name) {
|
||||
auto it = sAudioPathProfileHelperMap.find(name);
|
||||
if (it != sAudioPathProfileHelperMap.end()) {
|
||||
return &it->second.profile;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void AudioEngine::preload(const ccstd::string &filePath, const std::function<void(bool isSuccess)> &callback) {
|
||||
if (!isEnabled()) {
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
lazyInit();
|
||||
|
||||
if (sAudioEngineImpl) {
|
||||
if (!FileUtils::getInstance()->isFileExist(filePath)) {
|
||||
if (callback) {
|
||||
callback(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sAudioEngineImpl->preload(filePath, callback);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngine::addTask(const std::function<void()> &task) {
|
||||
lazyInit();
|
||||
|
||||
if (sAudioEngineImpl && sThreadPool) {
|
||||
sThreadPool->addTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
int AudioEngine::getPlayingAudioCount() {
|
||||
return static_cast<int>(sAudioIDInfoMap.size());
|
||||
}
|
||||
|
||||
void AudioEngine::setEnabled(bool isEnabled) {
|
||||
if (sIsEnabled != isEnabled) {
|
||||
sIsEnabled = isEnabled;
|
||||
|
||||
if (!sIsEnabled) {
|
||||
stopAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioEngine::isEnabled() {
|
||||
return sIsEnabled;
|
||||
}
|
||||
|
||||
PCMHeader AudioEngine::getPCMHeader(const char *url) {
|
||||
lazyInit();
|
||||
return sAudioEngineImpl->getPCMHeader(url);
|
||||
}
|
||||
ccstd::vector<uint8_t> AudioEngine::getOriginalPCMBuffer(const char *url, uint32_t channelID) {
|
||||
lazyInit();
|
||||
return sAudioEngineImpl->getOriginalPCMBuffer(url, channelID);
|
||||
}
|
||||
} // namespace cc
|
||||
45
cocos/audio/android/AssetFd.cpp
Normal file
45
cocos/audio/android/AssetFd.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#define LOG_TAG "AssetFd"
|
||||
|
||||
#include "audio/android/AssetFd.h"
|
||||
#include "audio/android/cutils/log.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
AssetFd::AssetFd(int assetFd)
|
||||
: _assetFd(assetFd) {
|
||||
}
|
||||
|
||||
AssetFd::~AssetFd() {
|
||||
ALOGV("~AssetFd: %d", _assetFd);
|
||||
if (_assetFd > 0) {
|
||||
::close(_assetFd);
|
||||
_assetFd = 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
42
cocos/audio/android/AssetFd.h
Normal file
42
cocos/audio/android/AssetFd.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
namespace cc {
|
||||
|
||||
class AssetFd {
|
||||
public:
|
||||
AssetFd(int assetFd);
|
||||
~AssetFd();
|
||||
|
||||
inline int getFd() const { return _assetFd; };
|
||||
|
||||
private:
|
||||
int _assetFd;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
77
cocos/audio/android/AudioBufferProvider.h
Normal file
77
cocos/audio/android/AudioBufferProvider.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "audio/android/utils/Errors.h"
|
||||
|
||||
namespace cc {
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class AudioBufferProvider {
|
||||
public:
|
||||
// IDEA: merge with AudioTrackShared::Buffer, AudioTrack::Buffer, and AudioRecord::Buffer
|
||||
// and rename getNextBuffer() to obtainBuffer()
|
||||
struct Buffer {
|
||||
Buffer() : raw(NULL), frameCount(0) {}
|
||||
union {
|
||||
void *raw;
|
||||
short *i16;
|
||||
int8_t *i8;
|
||||
};
|
||||
size_t frameCount;
|
||||
};
|
||||
|
||||
virtual ~AudioBufferProvider() {}
|
||||
|
||||
// value representing an invalid presentation timestamp
|
||||
static const int64_t kInvalidPTS = 0x7FFFFFFFFFFFFFFFLL; // <stdint.h> is too painful
|
||||
|
||||
// pts is the local time when the next sample yielded by getNextBuffer
|
||||
// will be rendered.
|
||||
// Pass kInvalidPTS if the PTS is unknown or not applicable.
|
||||
// On entry:
|
||||
// buffer != NULL
|
||||
// buffer->raw unused
|
||||
// buffer->frameCount maximum number of desired frames
|
||||
// On successful return:
|
||||
// status NO_ERROR
|
||||
// buffer->raw non-NULL pointer to buffer->frameCount contiguous available frames
|
||||
// buffer->frameCount number of contiguous available frames at buffer->raw,
|
||||
// 0 < buffer->frameCount <= entry value
|
||||
// On error return:
|
||||
// status != NO_ERROR
|
||||
// buffer->raw NULL
|
||||
// buffer->frameCount 0
|
||||
virtual status_t getNextBuffer(Buffer *buffer, int64_t pts = kInvalidPTS) = 0;
|
||||
|
||||
// Release (a portion of) the buffer previously obtained by getNextBuffer().
|
||||
// It is permissible to call releaseBuffer() multiple times per getNextBuffer().
|
||||
// On entry:
|
||||
// buffer->frameCount number of frames to release, must be <= number of frames
|
||||
// obtained but not yet released
|
||||
// buffer->raw unused
|
||||
// On return:
|
||||
// buffer->frameCount 0; implementation MUST set to zero
|
||||
// buffer->raw undefined; implementation is PERMITTED to set to any value,
|
||||
// so if caller needs to continue using this buffer it must
|
||||
// keep track of the pointer itself
|
||||
virtual void releaseBuffer(Buffer *buffer) = 0;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
} // namespace cc
|
||||
260
cocos/audio/android/AudioDecoder.cpp
Normal file
260
cocos/audio/android/AudioDecoder.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#define LOG_TAG "AudioDecoder"
|
||||
|
||||
#include "audio/android/AudioDecoder.h"
|
||||
#include "audio/android/AudioResampler.h"
|
||||
#include "audio/android/PcmBufferProvider.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace cc {
|
||||
|
||||
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<size_t>(offset);
|
||||
else if (whence == SEEK_CUR)
|
||||
thiz->_fileCurrPos = static_cast<size_t>(thiz->_fileCurrPos + offset);
|
||||
else if (whence == SEEK_END)
|
||||
thiz->_fileCurrPos = static_cast<size_t>(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<ccstd::vector<char>>();
|
||||
pcmBuffer->reserve(4096);
|
||||
_result.pcmBuffer = pcmBuffer;
|
||||
}
|
||||
|
||||
AudioDecoder::~AudioDecoder() {
|
||||
ALOGV("~AudioDecoder() %p", this);
|
||||
}
|
||||
|
||||
bool AudioDecoder::init(const ccstd::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<size_t>(((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);
|
||||
|
||||
ccstd::vector<int> Ovalues;
|
||||
|
||||
if (Ovalues.empty()) {
|
||||
Ovalues.push_back(static_cast<int>(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<int>(resampler->resample(static_cast<int32_t *>(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<int>(outputFrames);
|
||||
_result.sampleRate = outFrameRate;
|
||||
|
||||
auto buffer = std::make_shared<ccstd::vector<char>>();
|
||||
buffer->reserve(_result.numFrames * _result.bitsPerSample / 8);
|
||||
buffer->insert(buffer->end(), (char *)convert,
|
||||
(char *)convert + outputFrames * channels * sizeof(int16_t));
|
||||
_result.pcmBuffer = buffer;
|
||||
|
||||
ALOGV("pcm buffer size: %d", (int)_result.pcmBuffer->size());
|
||||
|
||||
free(convert);
|
||||
free(outputVAddr);
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
bool AudioDecoder::interleave() {
|
||||
if (_result.numChannels == 2) {
|
||||
ALOGI("Audio channel count is 2, no need to interleave");
|
||||
return true;
|
||||
} else if (_result.numChannels == 1) {
|
||||
// If it's a mono audio, try to compose a fake stereo buffer
|
||||
size_t newBufferSize = _result.pcmBuffer->size() * 2;
|
||||
auto newBuffer = std::make_shared<ccstd::vector<char>>();
|
||||
newBuffer->reserve(newBufferSize);
|
||||
size_t totalFrameSizeInBytes = (size_t)(_result.numFrames * _result.bitsPerSample / 8);
|
||||
|
||||
for (size_t i = 0; i < totalFrameSizeInBytes; i += 2) {
|
||||
// get one short value
|
||||
char byte1 = _result.pcmBuffer->at(i);
|
||||
char byte2 = _result.pcmBuffer->at(i + 1);
|
||||
|
||||
// push two short value
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
newBuffer->push_back(byte1);
|
||||
newBuffer->push_back(byte2);
|
||||
}
|
||||
}
|
||||
_result.numChannels = 2;
|
||||
_result.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
_result.pcmBuffer = newBuffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
ALOGE("Audio channel count (%d) is wrong, interleave only supports converting mono to stereo!", _result.numChannels);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
61
cocos/audio/android/AudioDecoder.h
Normal file
61
cocos/audio/android/AudioDecoder.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include "audio/android/OpenSLHelper.h"
|
||||
#include "audio/android/PcmData.h"
|
||||
#include "base/Data.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class AudioDecoder {
|
||||
public:
|
||||
AudioDecoder();
|
||||
virtual ~AudioDecoder();
|
||||
|
||||
virtual bool init(const ccstd::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
|
||||
|
||||
ccstd::string _url;
|
||||
PcmData _result;
|
||||
int _sampleRate;
|
||||
Data _fileData;
|
||||
size_t _fileCurrPos;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
75
cocos/audio/android/AudioDecoderMp3.cpp
Normal file
75
cocos/audio/android/AudioDecoderMp3.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#define LOG_TAG "AudioDecoderMp3"
|
||||
|
||||
#include "audio/android/AudioDecoderMp3.h"
|
||||
#include "audio/android/mp3reader.h"
|
||||
#include "platform/FileUtils.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
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;
|
||||
|
||||
ccstd::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 cc
|
||||
41
cocos/audio/android/AudioDecoderMp3.h
Normal file
41
cocos/audio/android/AudioDecoderMp3.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include "audio/android/AudioDecoder.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class AudioDecoderMp3 : public AudioDecoder {
|
||||
protected:
|
||||
AudioDecoderMp3();
|
||||
virtual ~AudioDecoderMp3();
|
||||
|
||||
virtual bool decodeToPcm() override;
|
||||
|
||||
friend class AudioDecoderProvider;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
101
cocos/audio/android/AudioDecoderOgg.cpp
Normal file
101
cocos/audio/android/AudioDecoderOgg.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#define LOG_TAG "AudioDecoderOgg"
|
||||
|
||||
#include "audio/android/AudioDecoderOgg.h"
|
||||
#include "platform/FileUtils.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
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 cc
|
||||
44
cocos/audio/android/AudioDecoderOgg.h
Normal file
44
cocos/audio/android/AudioDecoderOgg.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include "audio/android/AudioDecoder.h"
|
||||
|
||||
#include "tremolo/Tremolo/ivorbisfile.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
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 cc
|
||||
78
cocos/audio/android/AudioDecoderProvider.cpp
Normal file
78
cocos/audio/android/AudioDecoderProvider.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2017-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 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 "AudioDecoderProvider"
|
||||
|
||||
#include "audio/android/AudioDecoderProvider.h"
|
||||
#include "audio/android/AudioDecoderMp3.h"
|
||||
#include "audio/android/AudioDecoderOgg.h"
|
||||
#include "audio/android/AudioDecoderSLES.h"
|
||||
#include "audio/android/AudioDecoderWav.h"
|
||||
#include "base/memory/Memory.h"
|
||||
#include "platform/FileUtils.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
AudioDecoder *AudioDecoderProvider::createAudioDecoder(SLEngineItf engineItf, const ccstd::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback) {
|
||||
AudioDecoder *decoder = nullptr;
|
||||
ccstd::string extension = FileUtils::getInstance()->getFileExtension(url);
|
||||
ALOGV("url:%s, extension:%s", url.c_str(), extension.c_str());
|
||||
if (extension == ".ogg") {
|
||||
decoder = ccnew AudioDecoderOgg();
|
||||
if (!decoder->init(url, sampleRate)) {
|
||||
delete decoder;
|
||||
decoder = nullptr;
|
||||
}
|
||||
} else if (extension == ".mp3") {
|
||||
decoder = ccnew AudioDecoderMp3();
|
||||
if (!decoder->init(url, sampleRate)) {
|
||||
delete decoder;
|
||||
decoder = nullptr;
|
||||
}
|
||||
} else if (extension == ".wav") {
|
||||
decoder = ccnew AudioDecoderWav();
|
||||
if (!decoder->init(url, sampleRate)) {
|
||||
delete decoder;
|
||||
decoder = nullptr;
|
||||
}
|
||||
} else {
|
||||
auto slesDecoder = ccnew 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 cc
|
||||
40
cocos/audio/android/AudioDecoderProvider.h
Normal file
40
cocos/audio/android/AudioDecoderProvider.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio/android/OpenSLHelper.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class AudioDecoder;
|
||||
|
||||
class AudioDecoderProvider {
|
||||
public:
|
||||
static AudioDecoder *createAudioDecoder(SLEngineItf engineItf, const ccstd::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback);
|
||||
static void destroyAudioDecoder(AudioDecoder **decoder);
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
588
cocos/audio/android/AudioDecoderSLES.cpp
Normal file
588
cocos/audio/android/AudioDecoderSLES.cpp
Normal file
@@ -0,0 +1,588 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 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 "base/Macros.h"
|
||||
#include "audio/android/AudioDecoderSLES.h"
|
||||
#include "platform/FileUtils.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace cc {
|
||||
|
||||
/* 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 {
|
||||
ALOGE("Unable to find key %s", key);
|
||||
}
|
||||
}
|
||||
|
||||
class SLAudioDecoderCallbackProxy {
|
||||
public:
|
||||
//-----------------------------------------------------------------
|
||||
/* Callback for "prefetch" events, here used to detect audio resource opening errors */
|
||||
static void prefetchEventCallback(SLPrefetchStatusItf caller, void *context, SLuint32 event) {
|
||||
auto *thiz = reinterpret_cast<AudioDecoderSLES *>(context);
|
||||
thiz->prefetchCallback(caller, event);
|
||||
}
|
||||
|
||||
static void decPlayCallback(CCSLBufferQueueItf queueItf, void *context) {
|
||||
auto *thiz = reinterpret_cast<AudioDecoderSLES *>(context);
|
||||
thiz->decodeToPcmCallback(queueItf);
|
||||
}
|
||||
|
||||
static void decProgressCallback(SLPlayItf caller, void *context, SLuint32 event) {
|
||||
auto *thiz = reinterpret_cast<AudioDecoderSLES *>(context);
|
||||
thiz->decodeProgressCallback(caller, event);
|
||||
}
|
||||
};
|
||||
|
||||
AudioDecoderSLES::AudioDecoderSLES()
|
||||
: _engineItf(nullptr), _playObj(nullptr), _formatQueried(false), _prefetchError(false), _counter(0), _numChannelsKeyIndex(-1), _sampleRateKeyIndex(-1), _bitsPerSampleKeyIndex(-1), _containerSizeKeyIndex(-1), _channelMaskKeyIndex(-1), _endiannessKeyIndex(-1), _eos(false), _bufferSizeInFrames(-1), _assetFd(0), _fdGetterCallback(nullptr), _isDecodingCallbackInvoked(false) {
|
||||
ALOGV("Create AudioDecoderSLES");
|
||||
}
|
||||
|
||||
AudioDecoderSLES::~AudioDecoderSLES() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(__SLPlayerMutex);
|
||||
SL_DESTROY_OBJ(_playObj);
|
||||
}
|
||||
ALOGV("After destroying SL play object");
|
||||
if (_assetFd > 0) {
|
||||
ALOGV("Closing assetFd: %d", _assetFd);
|
||||
::close(_assetFd);
|
||||
_assetFd = 0;
|
||||
}
|
||||
free(_pcmData);
|
||||
}
|
||||
|
||||
bool AudioDecoderSLES::init(SLEngineItf engineItf, const ccstd::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<char *>(malloc(NB_BUFFERS_IN_QUEUE * BUFFER_SIZE_IN_BYTES));
|
||||
memset(_pcmData, 0x00, NB_BUFFERS_IN_QUEUE * BUFFER_SIZE_IN_BYTES);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioDecoderSLES::decodeToPcm() {
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
SLresult result;
|
||||
|
||||
/* Objects this application uses: one audio player */
|
||||
SLObjectItf player;
|
||||
|
||||
/* Interfaces for the audio player */
|
||||
CCSLBufferQueueItf decBuffQueueItf;
|
||||
SLPrefetchStatusItf prefetchItf;
|
||||
SLPlayItf playItf;
|
||||
SLMetadataExtractionItf mdExtrItf;
|
||||
|
||||
/* Source of audio data for the decoding */
|
||||
SLDataSource decSource;
|
||||
|
||||
// decUri & locFd should be defined here
|
||||
SLDataLocator_URI decUri;
|
||||
SLDataLocator_AndroidFD locFd;
|
||||
|
||||
/* Data sink for decoded audio */
|
||||
SLDataSink decDest;
|
||||
SLDataLocator_AndroidSimpleBufferQueue decBuffQueue;
|
||||
SLDataFormat_PCM pcm;
|
||||
|
||||
SLboolean required[NUM_EXPLICIT_INTERFACES_FOR_PLAYER];
|
||||
SLInterfaceID iidArray[NUM_EXPLICIT_INTERFACES_FOR_PLAYER];
|
||||
|
||||
/* Initialize arrays required[] and iidArray[] */
|
||||
for (int i = 0; i < NUM_EXPLICIT_INTERFACES_FOR_PLAYER; i++) {
|
||||
required[i] = SL_BOOLEAN_FALSE;
|
||||
iidArray[i] = SL_IID_NULL;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------ */
|
||||
/* Configuration of the player */
|
||||
|
||||
/* Request the AndroidSimpleBufferQueue interface */
|
||||
required[0] = SL_BOOLEAN_TRUE;
|
||||
iidArray[0] = SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
|
||||
/* Request the PrefetchStatus interface */
|
||||
required[1] = SL_BOOLEAN_TRUE;
|
||||
iidArray[1] = SL_IID_PREFETCHSTATUS;
|
||||
/* Request the PrefetchStatus interface */
|
||||
required[2] = SL_BOOLEAN_TRUE;
|
||||
iidArray[2] = SL_IID_METADATAEXTRACTION;
|
||||
|
||||
SLDataFormat_MIME formatMime = {SL_DATAFORMAT_MIME, nullptr, SL_CONTAINERTYPE_UNSPECIFIED};
|
||||
decSource.pFormat = &formatMime;
|
||||
|
||||
if (_url[0] != '/') {
|
||||
off_t start = 0;
|
||||
off_t length = 0;
|
||||
ccstd::string relativePath;
|
||||
size_t position = _url.find("@assets/");
|
||||
|
||||
if (0 == position) {
|
||||
// "@assets/" is at the beginning of the path and we don't want it
|
||||
relativePath = _url.substr(strlen("@assets/"));
|
||||
} else {
|
||||
relativePath = _url;
|
||||
}
|
||||
|
||||
_assetFd = _fdGetterCallback(relativePath, &start, &length);
|
||||
|
||||
if (_assetFd <= 0) {
|
||||
ALOGE("Failed to open file descriptor for '%s'", _url.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// configure audio source
|
||||
locFd = {SL_DATALOCATOR_ANDROIDFD, _assetFd, start, length};
|
||||
|
||||
decSource.pLocator = &locFd;
|
||||
} else {
|
||||
decUri = {SL_DATALOCATOR_URI, (SLchar *)_url.c_str()}; // NOLINT(google-readability-casting)
|
||||
decSource.pLocator = &decUri;
|
||||
}
|
||||
|
||||
/* Setup the data sink */
|
||||
decBuffQueue.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
|
||||
decBuffQueue.numBuffers = NB_BUFFERS_IN_QUEUE;
|
||||
/* set up the format of the data in the buffer queue */
|
||||
pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
// IDEA: valid value required but currently ignored
|
||||
pcm.numChannels = 2;
|
||||
pcm.samplesPerSec = SL_SAMPLINGRATE_44_1;
|
||||
pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
|
||||
pcm.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
|
||||
pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
|
||||
|
||||
decDest.pLocator = reinterpret_cast<void *>(&decBuffQueue);
|
||||
decDest.pFormat = reinterpret_cast<void *>(&pcm);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(__SLPlayerMutex);
|
||||
/* Create the audio player */
|
||||
result = (*_engineItf)->CreateAudioPlayer(_engineItf, &player, &decSource, &decDest, NUM_EXPLICIT_INTERFACES_FOR_PLAYER, iidArray, required);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "CreateAudioPlayer failed");
|
||||
|
||||
_playObj = player;
|
||||
/* Realize the player in synchronous mode. */
|
||||
result = (*player)->Realize(player, SL_BOOLEAN_FALSE);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "Realize failed");
|
||||
}
|
||||
|
||||
/* Get the play interface which is implicit */
|
||||
result = (*player)->GetInterface(player, SL_IID_PLAY, reinterpret_cast<void *>(&playItf));
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_PLAY failed");
|
||||
|
||||
/* Set up the player callback to get events during the decoding */
|
||||
// IDEA: currently ignored
|
||||
result = (*playItf)->SetMarkerPosition(playItf, 2000);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "SetMarkerPosition failed");
|
||||
|
||||
result = (*playItf)->SetPositionUpdatePeriod(playItf, 500);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "SetPositionUpdatePeriod failed");
|
||||
result = (*playItf)->SetCallbackEventsMask(playItf,
|
||||
SL_PLAYEVENT_HEADATMARKER |
|
||||
SL_PLAYEVENT_HEADATNEWPOS | SL_PLAYEVENT_HEADATEND);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "SetCallbackEventsMask failed");
|
||||
result = (*playItf)->RegisterCallback(playItf, SLAudioDecoderCallbackProxy::decProgressCallback,
|
||||
this);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "RegisterCallback failed");
|
||||
ALOGV("Play callback registered");
|
||||
|
||||
/* Get the buffer queue interface which was explicitly requested */
|
||||
result = (*player)->GetInterface(player, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
||||
reinterpret_cast<void *>(&decBuffQueueItf));
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE failed");
|
||||
|
||||
/* Get the prefetch status interface which was explicitly requested */
|
||||
result = (*player)->GetInterface(player, SL_IID_PREFETCHSTATUS, reinterpret_cast<void *>(&prefetchItf));
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_PREFETCHSTATUS failed");
|
||||
|
||||
/* Get the metadata extraction interface which was explicitly requested */
|
||||
result = (*player)->GetInterface(player, SL_IID_METADATAEXTRACTION, reinterpret_cast<void *>(&mdExtrItf));
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_METADATAEXTRACTION failed");
|
||||
|
||||
/* ------------------------------------------------------ */
|
||||
/* Initialize the callback and its context for the decoding buffer queue */
|
||||
_decContext.playItf = playItf;
|
||||
_decContext.metaItf = mdExtrItf;
|
||||
_decContext.pDataBase = reinterpret_cast<int8_t *>(_pcmData);
|
||||
_decContext.pData = _decContext.pDataBase;
|
||||
_decContext.size = NB_BUFFERS_IN_QUEUE * BUFFER_SIZE_IN_BYTES;
|
||||
|
||||
result = (*decBuffQueueItf)->RegisterCallback(decBuffQueueItf, SLAudioDecoderCallbackProxy::decPlayCallback, this);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "decBuffQueueItf RegisterCallback failed");
|
||||
|
||||
/* Enqueue buffers to map the region of memory allocated to store the decoded data */
|
||||
// ALOGV("Enqueueing buffer ");
|
||||
for (int i = 0; i < NB_BUFFERS_IN_QUEUE; i++) {
|
||||
result = (*decBuffQueueItf)->Enqueue(decBuffQueueItf, _decContext.pData, BUFFER_SIZE_IN_BYTES);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "Enqueue failed");
|
||||
_decContext.pData += BUFFER_SIZE_IN_BYTES;
|
||||
}
|
||||
|
||||
_decContext.pData = _decContext.pDataBase;
|
||||
|
||||
/* ------------------------------------------------------ */
|
||||
/* Initialize the callback for prefetch errors, if we can't open the resource to decode */
|
||||
result = (*prefetchItf)->RegisterCallback(prefetchItf, SLAudioDecoderCallbackProxy::prefetchEventCallback, this);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "prefetchItf RegisterCallback failed");
|
||||
|
||||
result = (*prefetchItf)->SetCallbackEventsMask(prefetchItf, PREFETCHEVENT_ERROR_CANDIDATE);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "prefetchItf SetCallbackEventsMask failed");
|
||||
|
||||
/* ------------------------------------------------------ */
|
||||
/* Prefetch the data so we can get information about the format before starting to decode */
|
||||
/* 1/ cause the player to prefetch the data */
|
||||
result = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PAUSED);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "SetPlayState SL_PLAYSTATE_PAUSED failed");
|
||||
|
||||
/* 2/ block until data has been prefetched */
|
||||
SLuint32 prefetchStatus = SL_PREFETCHSTATUS_UNDERFLOW;
|
||||
SLuint32 timeOutIndex = 1000; //cjh time out prefetching after 2s
|
||||
while ((prefetchStatus != SL_PREFETCHSTATUS_SUFFICIENTDATA) && (timeOutIndex > 0) &&
|
||||
!_prefetchError) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||
(*prefetchItf)->GetPrefetchStatus(prefetchItf, &prefetchStatus);
|
||||
timeOutIndex--;
|
||||
}
|
||||
if (timeOutIndex == 0 || _prefetchError) {
|
||||
ALOGE("Failure to prefetch data in time, exiting");
|
||||
SL_RETURN_VAL_IF_FAILED(SL_RESULT_CONTENT_NOT_FOUND, false,
|
||||
"Failure to prefetch data in time");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------ */
|
||||
/* Display duration */
|
||||
SLmillisecond durationInMsec = SL_TIME_UNKNOWN;
|
||||
result = (*playItf)->GetDuration(playItf, &durationInMsec);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "GetDuration failed");
|
||||
|
||||
if (durationInMsec == SL_TIME_UNKNOWN) {
|
||||
ALOGV("Content duration is unknown");
|
||||
} else {
|
||||
ALOGV("Content duration is %dms", (int)durationInMsec);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------ */
|
||||
/* Display the metadata obtained from the decoder */
|
||||
// This is for test / demonstration purposes only where we discover the key and value sizes
|
||||
// of a PCM decoder. An application that would want to directly get access to those values
|
||||
// can make assumptions about the size of the keys and their matching values (all SLuint32)
|
||||
SLuint32 itemCount;
|
||||
result = (*mdExtrItf)->GetItemCount(mdExtrItf, &itemCount);
|
||||
SLuint32 i;
|
||||
SLuint32 keySize;
|
||||
SLuint32 valueSize;
|
||||
SLMetadataInfo *keyInfo;
|
||||
SLMetadataInfo *value;
|
||||
for (i = 0; i < itemCount; i++) {
|
||||
keyInfo = nullptr;
|
||||
keySize = 0;
|
||||
value = nullptr;
|
||||
valueSize = 0;
|
||||
result = (*mdExtrItf)->GetKeySize(mdExtrItf, i, &keySize);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "GetKeySize(%d) failed", (int)i);
|
||||
|
||||
result = (*mdExtrItf)->GetValueSize(mdExtrItf, i, &valueSize);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "GetValueSize(%d) failed", (int)i);
|
||||
|
||||
keyInfo = reinterpret_cast<SLMetadataInfo *>(malloc(keySize));
|
||||
if (nullptr != keyInfo) {
|
||||
result = (*mdExtrItf)->GetKey(mdExtrItf, i, keySize, keyInfo);
|
||||
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "GetKey(%d) failed", (int)i);
|
||||
|
||||
ALOGV("key[%d] size=%d, name=%s, value size=%d",
|
||||
(int)i, (int)keyInfo->size, keyInfo->data, (int)valueSize);
|
||||
/* find out the key index of the metadata we're interested in */
|
||||
if (!strcmp(reinterpret_cast<char *>(keyInfo->data), ANDROID_KEY_PCMFORMAT_NUMCHANNELS)) {
|
||||
_numChannelsKeyIndex = i;
|
||||
} else if (!strcmp(reinterpret_cast<char *>(keyInfo->data), ANDROID_KEY_PCMFORMAT_SAMPLERATE)) {
|
||||
_sampleRateKeyIndex = i;
|
||||
} else if (!strcmp(reinterpret_cast<char *>(keyInfo->data), ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE)) {
|
||||
_bitsPerSampleKeyIndex = i;
|
||||
} else if (!strcmp(reinterpret_cast<char *>(keyInfo->data), ANDROID_KEY_PCMFORMAT_CONTAINERSIZE)) {
|
||||
_containerSizeKeyIndex = i;
|
||||
} else if (!strcmp(reinterpret_cast<char *>(keyInfo->data), ANDROID_KEY_PCMFORMAT_CHANNELMASK)) {
|
||||
_channelMaskKeyIndex = i;
|
||||
} else if (!strcmp(reinterpret_cast<char *>(keyInfo->data), ANDROID_KEY_PCMFORMAT_ENDIANNESS)) {
|
||||
_endiannessKeyIndex = i;
|
||||
}
|
||||
free(keyInfo);
|
||||
}
|
||||
}
|
||||
|
||||
checkMetaData(_numChannelsKeyIndex, ANDROID_KEY_PCMFORMAT_NUMCHANNELS);
|
||||
checkMetaData(_sampleRateKeyIndex, ANDROID_KEY_PCMFORMAT_SAMPLERATE);
|
||||
checkMetaData(_bitsPerSampleKeyIndex, ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE);
|
||||
checkMetaData(_containerSizeKeyIndex, ANDROID_KEY_PCMFORMAT_CONTAINERSIZE);
|
||||
checkMetaData(_channelMaskKeyIndex, ANDROID_KEY_PCMFORMAT_CHANNELMASK);
|
||||
checkMetaData(_endiannessKeyIndex, ANDROID_KEY_PCMFORMAT_ENDIANNESS);
|
||||
|
||||
/* ------------------------------------------------------ */
|
||||
/* Start decoding */
|
||||
result = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PLAYING);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "SetPlayState SL_PLAYSTATE_PLAYING failed");
|
||||
|
||||
ALOGV("Starting to decode");
|
||||
|
||||
/* Decode until the end of the stream is reached */
|
||||
{
|
||||
std::unique_lock<std::mutex> autoLock(_eosLock);
|
||||
while (!_eos) {
|
||||
_eosCondition.wait(autoLock);
|
||||
}
|
||||
}
|
||||
ALOGV("EOS signaled");
|
||||
|
||||
/* ------------------------------------------------------ */
|
||||
/* End of decoding */
|
||||
|
||||
/* Stop decoding */
|
||||
result = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_STOPPED);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "SetPlayState SL_PLAYSTATE_STOPPED failed");
|
||||
|
||||
ALOGV("Stopped decoding");
|
||||
|
||||
/* Destroy the UrlAudioPlayer object */
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(__SLPlayerMutex);
|
||||
SL_DESTROY_OBJ(_playObj);
|
||||
}
|
||||
|
||||
ALOGV("After destroy player ...");
|
||||
|
||||
_result.numFrames =
|
||||
static_cast<int>(_result.pcmBuffer->size() / _result.numChannels / (_result.bitsPerSample / 8));
|
||||
|
||||
ccstd::string info = _result.toString();
|
||||
ALOGI("Original audio info: %s, total size: %d", info.c_str(), (int)_result.pcmBuffer->size());
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
void AudioDecoderSLES::signalEos() {
|
||||
std::unique_lock<std::mutex> autoLock(_eosLock);
|
||||
_eos = true;
|
||||
_eosCondition.notify_one();
|
||||
}
|
||||
|
||||
void AudioDecoderSLES::queryAudioInfo() {
|
||||
if (_formatQueried) {
|
||||
return;
|
||||
}
|
||||
|
||||
SLresult result;
|
||||
/* Get duration in callback where we use the callback context for the SLPlayItf*/
|
||||
SLmillisecond durationInMsec = SL_TIME_UNKNOWN;
|
||||
result = (*_decContext.playItf)->GetDuration(_decContext.playItf, &durationInMsec);
|
||||
SL_RETURN_IF_FAILED(result, "decodeProgressCallback,GetDuration failed");
|
||||
|
||||
if (durationInMsec == SL_TIME_UNKNOWN) {
|
||||
ALOGV("Content duration is unknown (in dec callback)");
|
||||
} else {
|
||||
ALOGV("Content duration is %dms (in dec callback)", (int)durationInMsec);
|
||||
_result.duration = durationInMsec / 1000.0F;
|
||||
}
|
||||
|
||||
/* used to query metadata values */
|
||||
SLMetadataInfo pcmMetaData;
|
||||
|
||||
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _sampleRateKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData);
|
||||
|
||||
SL_RETURN_IF_FAILED(result, "%s GetValue _sampleRateKeyIndex failed", __FUNCTION__);
|
||||
// Note: here we could verify the following:
|
||||
// pcmMetaData->encoding == SL_CHARACTERENCODING_BINARY
|
||||
// pcmMetaData->size == sizeof(SLuint32)
|
||||
// but the call was successful for the PCM format keys, so those conditions are implied
|
||||
|
||||
_result.sampleRate = *reinterpret_cast<SLuint32 *>(pcmMetaData.data);
|
||||
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _numChannelsKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData);
|
||||
SL_RETURN_IF_FAILED(result, "%s GetValue _numChannelsKeyIndex failed", __FUNCTION__);
|
||||
|
||||
_result.numChannels = *reinterpret_cast<SLuint32 *>(pcmMetaData.data);
|
||||
|
||||
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _bitsPerSampleKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData);
|
||||
SL_RETURN_IF_FAILED(result, "%s GetValue _bitsPerSampleKeyIndex failed", __FUNCTION__)
|
||||
_result.bitsPerSample = *reinterpret_cast<SLuint32 *>(pcmMetaData.data);
|
||||
|
||||
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _containerSizeKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData);
|
||||
SL_RETURN_IF_FAILED(result, "%s GetValue _containerSizeKeyIndex failed", __FUNCTION__)
|
||||
_result.containerSize = *reinterpret_cast<SLuint32 *>(pcmMetaData.data);
|
||||
|
||||
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _channelMaskKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData);
|
||||
SL_RETURN_IF_FAILED(result, "%s GetValue _channelMaskKeyIndex failed", __FUNCTION__)
|
||||
_result.channelMask = *reinterpret_cast<SLuint32 *>(pcmMetaData.data);
|
||||
|
||||
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _endiannessKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData);
|
||||
SL_RETURN_IF_FAILED(result, "%s GetValue _endiannessKeyIndex failed", __FUNCTION__)
|
||||
_result.endianness = *reinterpret_cast<SLuint32 *>(pcmMetaData.data);
|
||||
|
||||
_formatQueried = true;
|
||||
}
|
||||
|
||||
void AudioDecoderSLES::prefetchCallback(SLPrefetchStatusItf caller, SLuint32 event) {
|
||||
SLpermille level = 0;
|
||||
SLresult result;
|
||||
result = (*caller)->GetFillLevel(caller, &level);
|
||||
SL_RETURN_IF_FAILED(result, "GetFillLevel failed");
|
||||
|
||||
SLuint32 status;
|
||||
//ALOGV("PrefetchEventCallback: received event %u", event);
|
||||
result = (*caller)->GetPrefetchStatus(caller, &status);
|
||||
|
||||
SL_RETURN_IF_FAILED(result, "GetPrefetchStatus failed");
|
||||
|
||||
if ((PREFETCHEVENT_ERROR_CANDIDATE == (event & PREFETCHEVENT_ERROR_CANDIDATE)) && (level == 0) && (status == SL_PREFETCHSTATUS_UNDERFLOW)) {
|
||||
ALOGV("PrefetchEventCallback: Error while prefetching data, exiting");
|
||||
_prefetchError = true;
|
||||
signalEos();
|
||||
}
|
||||
}
|
||||
|
||||
/* Callback for "playback" events, i.e. event happening during decoding */
|
||||
void AudioDecoderSLES::decodeProgressCallback(SLPlayItf caller, SLuint32 event) {
|
||||
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 cc
|
||||
96
cocos/audio/android/AudioDecoderSLES.h
Normal file
96
cocos/audio/android/AudioDecoderSLES.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 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 <condition_variable>
|
||||
#include <mutex>
|
||||
#include "audio/android/AudioDecoder.h"
|
||||
#include "audio/android/utils/Compat.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class AudioDecoderSLES : public AudioDecoder {
|
||||
protected:
|
||||
AudioDecoderSLES();
|
||||
~AudioDecoderSLES() override;
|
||||
|
||||
bool init(SLEngineItf engineItf, const ccstd::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 cc
|
||||
106
cocos/audio/android/AudioDecoderWav.cpp
Normal file
106
cocos/audio/android/AudioDecoderWav.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#define LOG_TAG "AudioDecoderWav"
|
||||
|
||||
#include "audio/android/AudioDecoderWav.h"
|
||||
#include "audio/common/utils/include/tinysndfile.h"
|
||||
#include "platform/FileUtils.h"
|
||||
|
||||
namespace cc {
|
||||
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<int64_t>(offset), whence);
|
||||
}
|
||||
|
||||
int AudioDecoderWav::onWavClose(void * /*datasource*/) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AudioDecoderWav::decodeToPcm() {
|
||||
_fileData = FileUtils::getInstance()->getDataFromFile(_url);
|
||||
if (_fileData.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SF_INFO info;
|
||||
|
||||
snd_callbacks cb;
|
||||
cb.open = onWavOpen;
|
||||
cb.read = AudioDecoder::fileRead;
|
||||
cb.seek = onWavSeek;
|
||||
cb.close = onWavClose;
|
||||
cb.tell = AudioDecoder::fileTell;
|
||||
|
||||
SNDFILE *handle = 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<unsigned char *>(malloc(bufSize));
|
||||
sf_count_t readFrames = sf_readf_short(handle, reinterpret_cast<int16_t *>(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<float>(1.0F * info.frames / _result.sampleRate); //NOLINT
|
||||
|
||||
free(buf);
|
||||
ret = true;
|
||||
} while (false);
|
||||
|
||||
if (handle != nullptr) {
|
||||
sf_close(handle);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
46
cocos/audio/android/AudioDecoderWav.h
Normal file
46
cocos/audio/android/AudioDecoderWav.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio/android/AudioDecoder.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
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 cc
|
||||
507
cocos/audio/android/AudioEngine-inl.cpp
Normal file
507
cocos/audio/android/AudioEngine-inl.cpp
Normal file
@@ -0,0 +1,507 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 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 "AudioEngineImpl"
|
||||
|
||||
#include "audio/android/AudioEngine-inl.h"
|
||||
|
||||
#include <unistd.h>
|
||||
// for native asset manager
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include <android/asset_manager.h>
|
||||
#include <android/asset_manager_jni.h>
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "application/ApplicationManager.h"
|
||||
#include "audio/include/AudioEngine.h"
|
||||
#include "base/Log.h"
|
||||
#include "base/Scheduler.h"
|
||||
#include "base/UTF8.h"
|
||||
#include "base/memory/Memory.h"
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include "platform/android/FileUtils-android.h"
|
||||
#include "platform/java/jni/JniHelper.h"
|
||||
#include "platform/java/jni/JniImp.h"
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
#include "cocos/platform/openharmony/FileUtils-OpenHarmony.h"
|
||||
#endif
|
||||
|
||||
#include "audio/android/AudioDecoder.h"
|
||||
#include "audio/android/AudioDecoderProvider.h"
|
||||
#include "audio/android/AudioPlayerProvider.h"
|
||||
#include "audio/android/IAudioPlayer.h"
|
||||
#include "audio/android/ICallerThreadUtils.h"
|
||||
#include "audio/android/UrlAudioPlayer.h"
|
||||
#include "audio/android/cutils/log.h"
|
||||
#include "engine/EngineEvents.h"
|
||||
|
||||
using namespace cc; //NOLINT
|
||||
|
||||
// Audio focus values synchronized with which in cocos/platform/android/java/src/com/cocos/lib/CocosNativeActivity.java
|
||||
namespace {
|
||||
AudioEngineImpl *gAudioImpl = nullptr;
|
||||
int outputSampleRate = 44100;
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
int bufferSizeInFrames = 192;
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
// 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;
|
||||
#endif
|
||||
|
||||
void getAudioInfo() {
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
JNIEnv * env = JniHelper::getEnv();
|
||||
jclass audioSystem = env->FindClass("android/media/AudioSystem");
|
||||
jmethodID method = env->GetStaticMethodID(audioSystem, "getPrimaryOutputSamplingRate", "()I");
|
||||
outputSampleRate = env->CallStaticIntMethod(audioSystem, method);
|
||||
method = env->GetStaticMethodID(audioSystem, "getPrimaryOutputFrameCount", "()I");
|
||||
bufferSizeInFrames = env->CallStaticIntMethod(audioSystem, method);
|
||||
#else
|
||||
// In openharmony, setting to 48K does not cause audio delays
|
||||
outputSampleRate = 48000;
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class CallerThreadUtils : public ICallerThreadUtils {
|
||||
public:
|
||||
void performFunctionInCallerThread(const std::function<void()> &func) override {
|
||||
CC_CURRENT_ENGINE()->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 ccstd::string &url, off_t *start, off_t *length) {
|
||||
int fd = -1;
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
if (cc::FileUtilsAndroid::getObbFile() != nullptr) {
|
||||
int64_t startV;
|
||||
int64_t lenV;
|
||||
fd = cc::getObbAssetFileDescriptorJNI(url, &startV, &lenV);
|
||||
*start = static_cast<off_t>(startV);
|
||||
*length = static_cast<off_t>(lenV);
|
||||
}
|
||||
if (fd <= 0) {
|
||||
auto *asset = AAssetManager_open(cc::FileUtilsAndroid::getAssetManager(), url.c_str(), AASSET_MODE_UNKNOWN);
|
||||
// open asset as file descriptor
|
||||
fd = AAsset_openFileDescriptor(asset, start, length);
|
||||
AAsset_close(asset);
|
||||
}
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
FileUtilsOpenHarmony* fileUtils = dynamic_cast<FileUtilsOpenHarmony*>(FileUtils::getInstance());
|
||||
if(fileUtils) {
|
||||
RawFileDescriptor descriptor;
|
||||
fileUtils->getRawFileDescriptor(url, descriptor);
|
||||
fd = descriptor.fd;
|
||||
}
|
||||
#endif
|
||||
if (fd <= 0) {
|
||||
ALOGE("Failed to open file descriptor for '%s'", url.c_str());
|
||||
}
|
||||
|
||||
return fd;
|
||||
};
|
||||
|
||||
//====================================================
|
||||
AudioEngineImpl::AudioEngineImpl()
|
||||
: _engineObject(nullptr),
|
||||
_engineEngine(nullptr),
|
||||
_outputMixObject(nullptr),
|
||||
_audioPlayerProvider(nullptr),
|
||||
_audioIDIndex(0),
|
||||
_lazyInitLoop(true) {
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
CC_LOG_ERROR("create opensl engine fail");
|
||||
break;
|
||||
}
|
||||
|
||||
// realize the engine
|
||||
result = (*_engineObject)->Realize(_engineObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
CC_LOG_ERROR("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) {
|
||||
CC_LOG_ERROR("get the engine interface fail");
|
||||
break;
|
||||
}
|
||||
|
||||
// create output mix
|
||||
const SLInterfaceID outputMixIIDs[] = {};
|
||||
const SLboolean outputMixReqs[] = {};
|
||||
result = (*_engineEngine)->CreateOutputMix(_engineEngine, &_outputMixObject, 0, outputMixIIDs, outputMixReqs);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
CC_LOG_ERROR("create output mix fail");
|
||||
break;
|
||||
}
|
||||
|
||||
// realize the output mix
|
||||
result = (*_outputMixObject)->Realize(_outputMixObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
CC_LOG_ERROR("realize the output mix fail");
|
||||
break;
|
||||
}
|
||||
|
||||
_audioPlayerProvider = ccnew AudioPlayerProvider(_engineEngine, _outputMixObject, outputSampleRate, bufferSizeInFrames, fdGetter, &gCallerThreadUtils);
|
||||
|
||||
ret = true;
|
||||
} while (false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AudioEngineImpl::setAudioFocusForAllPlayers(bool isFocus) {
|
||||
for (const auto &e : _audioPlayers) {
|
||||
e.second->setAudioFocus(isFocus);
|
||||
}
|
||||
}
|
||||
|
||||
int AudioEngineImpl::play2d(const ccstd::string &filePath, bool loop, float volume) {
|
||||
ALOGV("play2d, _audioPlayers.size=%d", (int)_audioPlayers.size());
|
||||
auto audioId = AudioEngine::INVALID_AUDIO_ID;
|
||||
|
||||
do {
|
||||
if (_engineEngine == nullptr || _audioPlayerProvider == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto fullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
|
||||
|
||||
audioId = _audioIDIndex++;
|
||||
|
||||
auto *player = _audioPlayerProvider->getAudioPlayer(fullPath);
|
||||
if (player != nullptr) {
|
||||
player->setId(audioId);
|
||||
_audioPlayers.insert(std::make_pair(audioId, player));
|
||||
|
||||
player->setPlayEventCallback([this, player, filePath](IAudioPlayer::State state) {
|
||||
if (state != IAudioPlayer::State::OVER && state != IAudioPlayer::State::STOPPED) {
|
||||
ALOGV("Ignore state: %d", static_cast<int>(state));
|
||||
return;
|
||||
}
|
||||
|
||||
int id = player->getId();
|
||||
|
||||
ALOGV("Removing player id=%d, state:%d", id, (int)state);
|
||||
|
||||
AudioEngine::remove(id);
|
||||
if (_audioPlayers.find(id) != _audioPlayers.end()) {
|
||||
_audioPlayers.erase(id);
|
||||
}
|
||||
if (_urlAudioPlayersNeedResume.find(id) != _urlAudioPlayersNeedResume.end()) {
|
||||
_urlAudioPlayersNeedResume.erase(id);
|
||||
}
|
||||
|
||||
auto iter = _callbackMap.find(id);
|
||||
if (iter != _callbackMap.end()) {
|
||||
if (state == IAudioPlayer::State::OVER) {
|
||||
iter->second(id, filePath);
|
||||
}
|
||||
_callbackMap.erase(iter);
|
||||
}
|
||||
});
|
||||
|
||||
player->setLoop(loop);
|
||||
player->setVolume(volume);
|
||||
player->play();
|
||||
} else {
|
||||
ALOGE("Oops, player is null ...");
|
||||
return AudioEngine::INVALID_AUDIO_ID;
|
||||
}
|
||||
|
||||
AudioEngine::sAudioIDInfoMap[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::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
|
||||
ccstd::vector<IAudioPlayer *> players;
|
||||
players.reserve(_audioPlayers.size());
|
||||
|
||||
for (const auto &e : _audioPlayers) {
|
||||
players.push_back(e.second);
|
||||
}
|
||||
|
||||
for (auto *p : players) {
|
||||
p->stop();
|
||||
}
|
||||
}
|
||||
|
||||
float AudioEngineImpl::getDuration(int audioID) {
|
||||
auto iter = _audioPlayers.find(audioID);
|
||||
if (iter != _audioPlayers.end()) {
|
||||
auto *player = iter->second;
|
||||
return player->getDuration();
|
||||
}
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
float AudioEngineImpl::getDurationFromFile(const ccstd::string &filePath) {
|
||||
if (_audioPlayerProvider != nullptr) {
|
||||
auto fullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
|
||||
return _audioPlayerProvider->getDurationFromFile(fullPath);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
float AudioEngineImpl::getCurrentTime(int audioID) {
|
||||
auto iter = _audioPlayers.find(audioID);
|
||||
if (iter != _audioPlayers.end()) {
|
||||
auto *player = iter->second;
|
||||
return player->getPosition();
|
||||
}
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
bool AudioEngineImpl::setCurrentTime(int audioID, float time) {
|
||||
auto iter = _audioPlayers.find(audioID);
|
||||
if (iter != _audioPlayers.end()) {
|
||||
auto *player = iter->second;
|
||||
return player->setPosition(time);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioEngineImpl::setFinishCallback(int audioID, const std::function<void(int, const ccstd::string &)> &callback) {
|
||||
_callbackMap[audioID] = callback;
|
||||
}
|
||||
|
||||
void AudioEngineImpl::preload(const ccstd::string &filePath, const std::function<void(bool)> &callback) {
|
||||
if (_audioPlayerProvider != nullptr) {
|
||||
ccstd::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 ccstd::string &filePath) {
|
||||
if (_audioPlayerProvider != nullptr) {
|
||||
ccstd::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{};
|
||||
ccstd::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url);
|
||||
if (fileFullPath.empty()) {
|
||||
CC_LOG_DEBUG("file %s does not exist or failed to load", url);
|
||||
return header;
|
||||
}
|
||||
if (_audioPlayerProvider->getPcmHeader(url, header)) {
|
||||
CC_LOG_DEBUG("file %s pcm data already cached", url);
|
||||
return header;
|
||||
}
|
||||
|
||||
AudioDecoder *decoder = AudioDecoderProvider::createAudioDecoder(_engineEngine, fileFullPath, bufferSizeInFrames, outputSampleRate, fdGetter);
|
||||
|
||||
if (decoder == nullptr) {
|
||||
CC_LOG_DEBUG("decode %s failed, the file formate might not support", url);
|
||||
return header;
|
||||
}
|
||||
if (!decoder->start()) {
|
||||
CC_LOG_DEBUG("[Audio Decoder] Decode failed %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;
|
||||
}
|
||||
|
||||
ccstd::vector<uint8_t> AudioEngineImpl::getOriginalPCMBuffer(const char *url, uint32_t channelID) {
|
||||
ccstd::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url);
|
||||
ccstd::vector<uint8_t> pcmData;
|
||||
if (fileFullPath.empty()) {
|
||||
CC_LOG_DEBUG("file %s does not exist or failed to load", url);
|
||||
return pcmData;
|
||||
}
|
||||
PcmData data;
|
||||
if (_audioPlayerProvider->getPcmData(url, data)) {
|
||||
CC_LOG_DEBUG("file %s pcm data already cached", url);
|
||||
} else {
|
||||
AudioDecoder *decoder = AudioDecoderProvider::createAudioDecoder(_engineEngine, fileFullPath, bufferSizeInFrames, outputSampleRate, fdGetter);
|
||||
if (decoder == nullptr) {
|
||||
CC_LOG_DEBUG("decode %s failed, the file formate might not support", url);
|
||||
return pcmData;
|
||||
}
|
||||
if (!decoder->start()) {
|
||||
CC_LOG_DEBUG("[Audio Decoder] Decode failed %s", url);
|
||||
return pcmData;
|
||||
}
|
||||
data = decoder->getResult();
|
||||
_audioPlayerProvider->registerPcmData(url, data);
|
||||
AudioDecoderProvider::destroyAudioDecoder(&decoder);
|
||||
}
|
||||
do {
|
||||
const uint32_t channelCount = data.numChannels;
|
||||
if (channelID >= channelCount) {
|
||||
CC_LOG_ERROR("channelID invalid, total channel count is %d but %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;
|
||||
}
|
||||
105
cocos/audio/android/AudioEngine-inl.h
Normal file
105
cocos/audio/android/AudioEngine-inl.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 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 <SLES/OpenSLES.h>
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
#include <SLES/OpenSLES_Platform.h>
|
||||
#endif
|
||||
#include <functional>
|
||||
#include "audio/include/AudioDef.h"
|
||||
#include "base/RefCounted.h"
|
||||
#include "base/Utils.h"
|
||||
#include "base/std/container/string.h"
|
||||
#include "base/std/container/unordered_map.h"
|
||||
#define MAX_AUDIOINSTANCES 13
|
||||
|
||||
#define ERRORLOG(msg) log("fun:%s,line:%d,msg:%s", __func__, __LINE__, #msg)
|
||||
|
||||
namespace cc {
|
||||
|
||||
struct CustomEvent;
|
||||
|
||||
class IAudioPlayer;
|
||||
class AudioPlayerProvider;
|
||||
|
||||
class AudioEngineImpl;
|
||||
|
||||
class AudioEngineImpl : public RefCounted {
|
||||
public:
|
||||
AudioEngineImpl();
|
||||
~AudioEngineImpl() override;
|
||||
|
||||
bool init();
|
||||
int play2d(const ccstd::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 ccstd::string &filePath);
|
||||
float getCurrentTime(int audioID);
|
||||
bool setCurrentTime(int audioID, float time);
|
||||
void setFinishCallback(int audioID, const std::function<void(int, const ccstd::string &)> &callback);
|
||||
|
||||
void uncache(const ccstd::string &filePath);
|
||||
void uncacheAll();
|
||||
void preload(const ccstd::string &filePath, const std::function<void(bool)> &callback);
|
||||
|
||||
void onResume();
|
||||
void onPause();
|
||||
|
||||
void setAudioFocusForAllPlayers(bool isFocus);
|
||||
|
||||
PCMHeader getPCMHeader(const char *url);
|
||||
std::vector<uint8_t> getOriginalPCMBuffer(const char *url, uint32_t channelID);
|
||||
|
||||
private:
|
||||
// engine interfaces
|
||||
SLObjectItf _engineObject;
|
||||
SLEngineItf _engineEngine;
|
||||
|
||||
// output mix interfaces
|
||||
SLObjectItf _outputMixObject;
|
||||
|
||||
//audioID,AudioInfo
|
||||
ccstd::unordered_map<int, IAudioPlayer *> _audioPlayers;
|
||||
ccstd::unordered_map<int, std::function<void(int, const ccstd::string &)>> _callbackMap;
|
||||
|
||||
// UrlAudioPlayers which need to resumed while entering foreground
|
||||
ccstd::unordered_map<int, IAudioPlayer *> _urlAudioPlayersNeedResume;
|
||||
|
||||
AudioPlayerProvider *_audioPlayerProvider;
|
||||
|
||||
int _audioIDIndex;
|
||||
|
||||
bool _lazyInitLoop;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
2053
cocos/audio/android/AudioMixer.cpp
Normal file
2053
cocos/audio/android/AudioMixer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
379
cocos/audio/android/AudioMixer.h
Normal file
379
cocos/audio/android/AudioMixer.h
Normal file
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
**
|
||||
** Copyright 2007, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <sys/types.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "audio/android/AudioBufferProvider.h"
|
||||
#include "audio/android/AudioResamplerPublic.h"
|
||||
|
||||
#include "audio/android/AudioResampler.h"
|
||||
#include "audio/android/audio.h"
|
||||
#include "audio/android/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 cc {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class AudioMixer {
|
||||
public:
|
||||
AudioMixer(size_t frameCount, uint32_t sampleRate,
|
||||
uint32_t maxNumTracks = MAX_NUM_TRACKS);
|
||||
|
||||
/*virtual*/ ~AudioMixer(); // non-virtual saves a v-table, restore if sub-classed
|
||||
|
||||
// This mixer has a hard-coded upper limit of 32 active track inputs.
|
||||
// Adding support for > 32 tracks would require more than simply changing this value.
|
||||
static const uint32_t MAX_NUM_TRACKS = 32;
|
||||
// maximum number of channels supported by the mixer
|
||||
|
||||
// This mixer has a hard-coded upper limit of 8 channels for output.
|
||||
static const uint32_t MAX_NUM_CHANNELS = 8;
|
||||
static const uint32_t MAX_NUM_VOLUMES = 2; // stereo volume only
|
||||
// maximum number of channels supported for the content
|
||||
static const uint32_t MAX_NUM_CHANNELS_TO_DOWNMIX = AUDIO_CHANNEL_COUNT_MAX;
|
||||
|
||||
static const uint16_t UNITY_GAIN_INT = 0x1000;
|
||||
static const CONSTEXPR float UNITY_GAIN_FLOAT = 1.0F;
|
||||
|
||||
enum { // names
|
||||
|
||||
// track names (MAX_NUM_TRACKS units)
|
||||
TRACK0 = 0x1000,
|
||||
|
||||
// 0x2000 is unused
|
||||
|
||||
// setParameter targets
|
||||
TRACK = 0x3000,
|
||||
RESAMPLE = 0x3001,
|
||||
RAMP_VOLUME = 0x3002, // ramp to new volume
|
||||
VOLUME = 0x3003, // don't ramp
|
||||
TIMESTRETCH = 0x3004,
|
||||
|
||||
// set Parameter names
|
||||
// for target TRACK
|
||||
CHANNEL_MASK = 0x4000,
|
||||
FORMAT = 0x4001,
|
||||
MAIN_BUFFER = 0x4002,
|
||||
AUX_BUFFER = 0x4003,
|
||||
DOWNMIX_TYPE = 0X4004,
|
||||
MIXER_FORMAT = 0x4005, // AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
|
||||
MIXER_CHANNEL_MASK = 0x4006, // Channel mask for mixer output
|
||||
// for target RESAMPLE
|
||||
SAMPLE_RATE = 0x4100, // Configure sample rate conversion on this track name;
|
||||
// parameter 'value' is the new sample rate in Hz.
|
||||
// Only creates a sample rate converter the first time that
|
||||
// the track sample rate is different from the mix sample rate.
|
||||
// If the new sample rate is the same as the mix sample rate,
|
||||
// and a sample rate converter already exists,
|
||||
// then the sample rate converter remains present but is a no-op.
|
||||
RESET = 0x4101, // Reset sample rate converter without changing sample rate.
|
||||
// This clears out the resampler's input buffer.
|
||||
REMOVE = 0x4102, // Remove the sample rate converter on this track name;
|
||||
// the track is restored to the mix sample rate.
|
||||
// for target RAMP_VOLUME and VOLUME (8 channels max)
|
||||
// IDEA: use float for these 3 to improve the dynamic range
|
||||
VOLUME0 = 0x4200,
|
||||
VOLUME1 = 0x4201,
|
||||
AUXLEVEL = 0x4210,
|
||||
// for target TIMESTRETCH
|
||||
PLAYBACK_RATE = 0x4300, // Configure timestretch on this track name;
|
||||
// parameter 'value' is a pointer to the new playback rate.
|
||||
};
|
||||
|
||||
// For all APIs with "name": TRACK0 <= name < TRACK0 + MAX_NUM_TRACKS
|
||||
|
||||
// Allocate a track name. Returns new track name if successful, -1 on failure.
|
||||
// The failure could be because of an invalid channelMask or format, or that
|
||||
// the track capacity of the mixer is exceeded.
|
||||
int getTrackName(audio_channel_mask_t channelMask,
|
||||
audio_format_t format, int sessionId);
|
||||
|
||||
// Free an allocated track by name
|
||||
void deleteTrackName(int name);
|
||||
|
||||
// Enable or disable an allocated track by name
|
||||
void enable(int name);
|
||||
void disable(int name);
|
||||
|
||||
void setParameter(int name, int target, int param, void *value);
|
||||
|
||||
void setBufferProvider(int name, AudioBufferProvider *bufferProvider);
|
||||
void process(int64_t pts);
|
||||
|
||||
uint32_t trackNames() const { return mTrackNames; }
|
||||
|
||||
size_t getUnreleasedFrames(int name) const;
|
||||
|
||||
static inline bool isValidPcmTrackFormat(audio_format_t format) {
|
||||
switch (format) {
|
||||
case AUDIO_FORMAT_PCM_8_BIT:
|
||||
case AUDIO_FORMAT_PCM_16_BIT:
|
||||
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
|
||||
case AUDIO_FORMAT_PCM_32_BIT:
|
||||
case AUDIO_FORMAT_PCM_FLOAT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
enum {
|
||||
// IDEA: this representation permits up to 8 channels
|
||||
NEEDS_CHANNEL_COUNT__MASK = 0x00000007, // 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 <int MIXTYPE, bool USEFLOATVOL, bool ADJUSTVOL,
|
||||
typename TO, typename TI, typename TA>
|
||||
static void volumeMix(TO *out, size_t outFrames,
|
||||
const TI *in, TA *aux, bool ramp, AudioMixer::track_t *t);
|
||||
|
||||
// multi-format process hooks
|
||||
template <int MIXTYPE, typename TO, typename TI, typename TA>
|
||||
static void process_NoResampleOneTrack(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
|
||||
|
||||
// multi-format track hooks
|
||||
template <int MIXTYPE, typename TO, typename TI, typename TA>
|
||||
static void track__Resample(track_t *t, TO *out, size_t frameCount, TO *temp __unused, TA *aux);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
|
||||
template <int MIXTYPE, typename TO, typename TI, typename TA>
|
||||
static void track__NoResample(track_t *t, TO *out, size_t frameCount, TO *temp __unused, TA *aux); // 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 cc
|
||||
296
cocos/audio/android/AudioMixerController.cpp
Normal file
296
cocos/audio/android/AudioMixerController.cpp
Normal file
@@ -0,0 +1,296 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#define LOG_TAG "AudioMixerController"
|
||||
|
||||
#include "audio/android/AudioMixerController.h"
|
||||
#include <algorithm>
|
||||
#include "audio/android/AudioMixer.h"
|
||||
#include "audio/android/OpenSLHelper.h"
|
||||
#include "audio/android/Track.h"
|
||||
#include "base/memory/Memory.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
AudioMixerController::AudioMixerController(int bufferSizeInFrames, int sampleRate, int channelCount)
|
||||
: _bufferSizeInFrames(bufferSizeInFrames), _sampleRate(sampleRate), _channelCount(channelCount), _mixer(nullptr), _isPaused(false), _isMixingFrame(false) {
|
||||
ALOGV("In the constructor of AudioMixerController!");
|
||||
|
||||
_mixingBuffer.size = (size_t)bufferSizeInFrames * 2 * channelCount;
|
||||
// Don't use posix_memalign since it was added from API 16, it will crash on Android 2.3
|
||||
// Therefore, for a workaround, we uses memalign here.
|
||||
_mixingBuffer.buf = memalign(32, _mixingBuffer.size);
|
||||
memset(_mixingBuffer.buf, 0, _mixingBuffer.size);
|
||||
}
|
||||
|
||||
AudioMixerController::~AudioMixerController() {
|
||||
destroy();
|
||||
|
||||
if (_mixer != nullptr) {
|
||||
delete _mixer;
|
||||
_mixer = nullptr;
|
||||
}
|
||||
|
||||
free(_mixingBuffer.buf);
|
||||
}
|
||||
|
||||
bool AudioMixerController::init() {
|
||||
_mixer = ccnew AudioMixer(_bufferSizeInFrames, _sampleRate);
|
||||
return _mixer != nullptr;
|
||||
}
|
||||
|
||||
bool AudioMixerController::addTrack(Track *track) {
|
||||
ALOG_ASSERT(track != nullptr, "Shouldn't pass nullptr to addTrack");
|
||||
bool ret = false;
|
||||
|
||||
std::lock_guard<std::mutex> lk(_activeTracksMutex);
|
||||
|
||||
auto iter = std::find(_activeTracks.begin(), _activeTracks.end(), track);
|
||||
if (iter == _activeTracks.end()) {
|
||||
_activeTracks.push_back(track);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void removeItemFromVector(ccstd::vector<T> &v, T item) {
|
||||
auto iter = std::find(v.begin(), v.end(), item);
|
||||
if (iter != v.end()) {
|
||||
v.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioMixerController::initTrack(Track *track, ccstd::vector<Track *> &tracksToRemove) {
|
||||
if (track->isInitialized())
|
||||
return;
|
||||
|
||||
uint32_t channelMask = audio_channel_out_mask_from_count(2);
|
||||
int32_t name = _mixer->getTrackName(channelMask, AUDIO_FORMAT_PCM_16_BIT,
|
||||
AUDIO_SESSION_OUTPUT_MIX);
|
||||
if (name < 0) {
|
||||
// If we could not get the track name, it means that there're MAX_NUM_TRACKS tracks
|
||||
// So ignore the new track.
|
||||
tracksToRemove.push_back(track);
|
||||
} else {
|
||||
_mixer->setBufferProvider(name, track);
|
||||
_mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
|
||||
_mixingBuffer.buf);
|
||||
_mixer->setParameter(
|
||||
name,
|
||||
AudioMixer::TRACK,
|
||||
AudioMixer::MIXER_FORMAT,
|
||||
(void *)(uintptr_t)AUDIO_FORMAT_PCM_16_BIT);
|
||||
_mixer->setParameter(
|
||||
name,
|
||||
AudioMixer::TRACK,
|
||||
AudioMixer::FORMAT,
|
||||
(void *)(uintptr_t)AUDIO_FORMAT_PCM_16_BIT);
|
||||
_mixer->setParameter(
|
||||
name,
|
||||
AudioMixer::TRACK,
|
||||
AudioMixer::MIXER_CHANNEL_MASK,
|
||||
(void *)(uintptr_t)channelMask);
|
||||
_mixer->setParameter(
|
||||
name,
|
||||
AudioMixer::TRACK,
|
||||
AudioMixer::CHANNEL_MASK,
|
||||
(void *)(uintptr_t)channelMask);
|
||||
|
||||
track->setName(name);
|
||||
_mixer->enable(name);
|
||||
|
||||
std::lock_guard<std::mutex> lk(track->_volumeDirtyMutex);
|
||||
gain_minifloat_packed_t volume = track->getVolumeLR();
|
||||
float lVolume = float_from_gain(gain_minifloat_unpack_left(volume));
|
||||
float rVolume = float_from_gain(gain_minifloat_unpack_right(volume));
|
||||
|
||||
_mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &lVolume);
|
||||
_mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &rVolume);
|
||||
|
||||
track->setVolumeDirty(false);
|
||||
track->setInitialized(true);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioMixerController::mixOneFrame() {
|
||||
_isMixingFrame = true;
|
||||
_activeTracksMutex.lock();
|
||||
|
||||
auto mixStart = clockNow();
|
||||
|
||||
ccstd::vector<Track *> tracksToRemove;
|
||||
tracksToRemove.reserve(_activeTracks.size());
|
||||
|
||||
// FOR TESTING BEGIN
|
||||
// Track* track = _activeTracks[0];
|
||||
//
|
||||
// AudioBufferProvider::Buffer buffer;
|
||||
// buffer.frameCount = _bufferSizeInFrames;
|
||||
// status_t r = track->getNextBuffer(&buffer);
|
||||
//// ALOG_ASSERT(buffer.frameCount == _mixing->size / 2, "buffer.frameCount:%d, _mixing->size/2:%d", buffer.frameCount, _mixing->size/2);
|
||||
// if (r == NO_ERROR)
|
||||
// {
|
||||
// ALOGV("getNextBuffer succeed ...");
|
||||
// memcpy(_mixing->buf, buffer.raw, _mixing->size);
|
||||
// }
|
||||
// if (buffer.raw == nullptr)
|
||||
// {
|
||||
// ALOGV("Play over ...");
|
||||
// tracksToRemove.push_back(track);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// track->releaseBuffer(&buffer);
|
||||
// }
|
||||
//
|
||||
// _mixing->state = BufferState::FULL;
|
||||
// _activeTracksMutex.unlock();
|
||||
// FOR TESTING END
|
||||
|
||||
Track::State state;
|
||||
// set up the tracks.
|
||||
for (auto &&track : _activeTracks) {
|
||||
state = track->getState();
|
||||
|
||||
if (state == Track::State::PLAYING) {
|
||||
initTrack(track, tracksToRemove);
|
||||
|
||||
int name = track->getName();
|
||||
ALOG_ASSERT(name >= 0);
|
||||
|
||||
std::lock_guard<std::mutex> lk(track->_volumeDirtyMutex);
|
||||
|
||||
if (track->isVolumeDirty()) {
|
||||
gain_minifloat_packed_t volume = track->getVolumeLR();
|
||||
float lVolume = float_from_gain(gain_minifloat_unpack_left(volume));
|
||||
float rVolume = float_from_gain(gain_minifloat_unpack_right(volume));
|
||||
|
||||
ALOGV("Track (name: %d)'s volume is dirty, update volume to L: %f, R: %f", name, lVolume, rVolume);
|
||||
|
||||
_mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &lVolume);
|
||||
_mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &rVolume);
|
||||
|
||||
track->setVolumeDirty(false);
|
||||
}
|
||||
} else if (state == Track::State::RESUMED) {
|
||||
initTrack(track, tracksToRemove);
|
||||
|
||||
if (track->getPrevState() == Track::State::PAUSED) {
|
||||
_mixer->enable(track->getName());
|
||||
track->setState(Track::State::PLAYING);
|
||||
} else {
|
||||
ALOGW("Previous state (%d) isn't PAUSED, couldn't resume!", static_cast<int>(track->getPrevState()));
|
||||
}
|
||||
} else if (state == Track::State::PAUSED) {
|
||||
initTrack(track, tracksToRemove);
|
||||
|
||||
if (track->getPrevState() == Track::State::PLAYING || track->getPrevState() == Track::State::RESUMED) {
|
||||
_mixer->disable(track->getName());
|
||||
} else {
|
||||
ALOGW("Previous state (%d) isn't PLAYING, couldn't pause!", static_cast<int>(track->getPrevState()));
|
||||
}
|
||||
} else if (state == Track::State::STOPPED) {
|
||||
if (track->isInitialized()) {
|
||||
_mixer->deleteTrackName(track->getName());
|
||||
} else {
|
||||
ALOGV("Track (%p) hasn't been initialized yet!", track);
|
||||
}
|
||||
tracksToRemove.push_back(track);
|
||||
}
|
||||
|
||||
if (track->isPlayOver()) {
|
||||
if (track->isLoop()) {
|
||||
track->reset();
|
||||
} else {
|
||||
ALOGV("Play over ...");
|
||||
_mixer->deleteTrackName(track->getName());
|
||||
tracksToRemove.push_back(track);
|
||||
track->setState(Track::State::OVER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool hasAvailableTracks = _activeTracks.size() - tracksToRemove.size() > 0;
|
||||
|
||||
if (hasAvailableTracks) {
|
||||
ALOGV_IF(_activeTracks.size() > 8, "More than 8 active tracks: %d", (int)_activeTracks.size());
|
||||
_mixer->process(AudioBufferProvider::kInvalidPTS);
|
||||
} else {
|
||||
ALOGV("Doesn't have enough tracks: %d, %d", (int)_activeTracks.size(), (int)tracksToRemove.size());
|
||||
}
|
||||
|
||||
// Remove stopped or playover tracks for active tracks container
|
||||
for (auto &&track : tracksToRemove) {
|
||||
removeItemFromVector(_activeTracks, track);
|
||||
|
||||
if (track != nullptr && track->onStateChanged != nullptr) {
|
||||
track->onStateChanged(Track::State::DESTROYED);
|
||||
} else {
|
||||
ALOGE("track (%p) was released ...", track);
|
||||
}
|
||||
}
|
||||
|
||||
_activeTracksMutex.unlock();
|
||||
|
||||
auto mixEnd = clockNow();
|
||||
float mixInterval = intervalInMS(mixStart, mixEnd);
|
||||
ALOGV_IF(mixInterval > 1.0f, "Mix a frame waste: %fms", mixInterval);
|
||||
|
||||
_isMixingFrame = false;
|
||||
}
|
||||
|
||||
void AudioMixerController::destroy() {
|
||||
while (_isMixingFrame) {
|
||||
usleep(10);
|
||||
}
|
||||
usleep(2000); // Wait for more 2ms
|
||||
}
|
||||
|
||||
void AudioMixerController::pause() {
|
||||
_isPaused = true;
|
||||
}
|
||||
|
||||
void AudioMixerController::resume() {
|
||||
_isPaused = false;
|
||||
}
|
||||
|
||||
bool AudioMixerController::hasPlayingTacks() {
|
||||
std::lock_guard<std::mutex> lk(_activeTracksMutex);
|
||||
if (_activeTracks.empty())
|
||||
return false;
|
||||
|
||||
for (auto &&track : _activeTracks) {
|
||||
Track::State state = track->getState();
|
||||
if (state == Track::State::IDLE || state == Track::State::PLAYING || state == Track::State::RESUMED) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
84
cocos/audio/android/AudioMixerController.h
Normal file
84
cocos/audio/android/AudioMixerController.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 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 <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include "audio/android/utils/Errors.h"
|
||||
#include "base/std/container/vector.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class Track;
|
||||
class AudioMixer;
|
||||
|
||||
class AudioMixerController {
|
||||
public:
|
||||
struct OutputBuffer {
|
||||
void *buf;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
AudioMixerController(int bufferSizeInFrames, int sampleRate, int channelCount);
|
||||
|
||||
~AudioMixerController();
|
||||
|
||||
bool init();
|
||||
|
||||
bool addTrack(Track *track);
|
||||
bool hasPlayingTacks();
|
||||
|
||||
void pause();
|
||||
void resume();
|
||||
inline bool isPaused() const { return _isPaused; };
|
||||
|
||||
void mixOneFrame();
|
||||
|
||||
inline OutputBuffer *current() { return &_mixingBuffer; }
|
||||
|
||||
private:
|
||||
void destroy();
|
||||
void initTrack(Track *track, ccstd::vector<Track *> &tracksToRemove);
|
||||
|
||||
private:
|
||||
int _bufferSizeInFrames;
|
||||
int _sampleRate;
|
||||
int _channelCount;
|
||||
|
||||
AudioMixer *_mixer;
|
||||
|
||||
std::mutex _activeTracksMutex;
|
||||
ccstd::vector<Track *> _activeTracks;
|
||||
|
||||
OutputBuffer _mixingBuffer;
|
||||
|
||||
std::atomic_bool _isPaused;
|
||||
std::atomic_bool _isMixingFrame;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
445
cocos/audio/android/AudioMixerOps.h
Normal file
445
cocos/audio/android/AudioMixerOps.h
Normal file
@@ -0,0 +1,445 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio/android/cutils/log.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
/* Behavior of is_same<>::value is true if the types are identical,
|
||||
* false otherwise. Identical to the STL std::is_same.
|
||||
*/
|
||||
template <typename T, typename U>
|
||||
struct is_same {
|
||||
static const bool value = false;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_same<T, T> // partial specialization
|
||||
{
|
||||
static const bool value = true;
|
||||
};
|
||||
|
||||
/* MixMul is a multiplication operator to scale an audio input signal
|
||||
* by a volume gain, with the formula:
|
||||
*
|
||||
* O(utput) = I(nput) * V(olume)
|
||||
*
|
||||
* The output, input, and volume may have different types.
|
||||
* There are 27 variants, of which 14 are actually defined in an
|
||||
* explicitly templated class.
|
||||
*
|
||||
* The following type variables and the underlying meaning:
|
||||
*
|
||||
* Output type TO: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1]
|
||||
* Input signal type TI: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1]
|
||||
* Volume type TV: int32_t (U4.28) or int16_t (U4.12) or float [-1,1]
|
||||
*
|
||||
* For high precision audio, only the <TO, TI, TV> = <float, float, float>
|
||||
* needs to be accelerated. This is perhaps the easiest form to do quickly as well.
|
||||
*
|
||||
* A generic version is NOT defined to catch any mistake of using it.
|
||||
*/
|
||||
|
||||
template <typename TO, typename TI, typename TV>
|
||||
TO MixMul(TI value, TV volume);
|
||||
|
||||
template <>
|
||||
inline int32_t MixMul<int32_t, int16_t, int16_t>(int16_t value, int16_t volume) {
|
||||
return value * volume;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int32_t MixMul<int32_t, int32_t, int16_t>(int32_t value, int16_t volume) {
|
||||
return (value >> 12) * volume;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int32_t MixMul<int32_t, int16_t, int32_t>(int16_t value, int32_t volume) {
|
||||
return value * (volume >> 16);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int32_t MixMul<int32_t, int32_t, int32_t>(int32_t value, int32_t volume) {
|
||||
return (value >> 12) * (volume >> 16);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline float MixMul<float, float, int16_t>(float value, int16_t volume) {
|
||||
static const float norm = 1. / (1 << 12);
|
||||
return value * volume * norm;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline float MixMul<float, float, int32_t>(float value, int32_t volume) {
|
||||
static const float norm = 1. / (1 << 28);
|
||||
return value * volume * norm;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int16_t MixMul<int16_t, float, int16_t>(float value, int16_t volume) {
|
||||
return clamp16_from_float(MixMul<float, float, int16_t>(value, volume));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int16_t MixMul<int16_t, float, int32_t>(float value, int32_t volume) {
|
||||
return clamp16_from_float(MixMul<float, float, int32_t>(value, volume));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline float MixMul<float, int16_t, int16_t>(int16_t value, int16_t volume) {
|
||||
static const float norm = 1. / (1 << (15 + 12));
|
||||
return static_cast<float>(value) * static_cast<float>(volume) * norm;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline float MixMul<float, int16_t, int32_t>(int16_t value, int32_t volume) {
|
||||
static const float norm = 1. / (1ULL << (15 + 28));
|
||||
return static_cast<float>(value) * static_cast<float>(volume) * norm;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int16_t MixMul<int16_t, int16_t, int16_t>(int16_t value, int16_t volume) {
|
||||
return clamp16(MixMul<int32_t, int16_t, int16_t>(value, volume) >> 12);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int16_t MixMul<int16_t, int32_t, int16_t>(int32_t value, int16_t volume) {
|
||||
return clamp16(MixMul<int32_t, int32_t, int16_t>(value, volume) >> 12);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int16_t MixMul<int16_t, int16_t, int32_t>(int16_t value, int32_t volume) {
|
||||
return clamp16(MixMul<int32_t, int16_t, int32_t>(value, volume) >> 12);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int16_t MixMul<int16_t, int32_t, int32_t>(int32_t value, int32_t volume) {
|
||||
return clamp16(MixMul<int32_t, int32_t, int32_t>(value, volume) >> 12);
|
||||
}
|
||||
|
||||
/* Required for floating point volume. Some are needed for compilation but
|
||||
* are not needed in execution and should be removed from the final build by
|
||||
* an optimizing compiler.
|
||||
*/
|
||||
template <>
|
||||
inline float MixMul<float, float, float>(float value, float volume) {
|
||||
return value * volume;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline float MixMul<float, int16_t, float>(int16_t value, float volume) {
|
||||
static const float float_from_q_15 = 1. / (1 << 15);
|
||||
return value * volume * float_from_q_15;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int32_t MixMul<int32_t, int32_t, float>(int32_t value, float volume) {
|
||||
LOG_ALWAYS_FATAL("MixMul<int32_t, int32_t, float> Runtime Should not be here");
|
||||
return value * volume;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int32_t MixMul<int32_t, int16_t, float>(int16_t value, float volume) {
|
||||
LOG_ALWAYS_FATAL("MixMul<int32_t, int16_t, float> Runtime Should not be here");
|
||||
static const float u4_12_from_float = (1 << 12);
|
||||
return value * volume * u4_12_from_float;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int16_t MixMul<int16_t, int16_t, float>(int16_t value, float volume) {
|
||||
LOG_ALWAYS_FATAL("MixMul<int16_t, int16_t, float> Runtime Should not be here");
|
||||
return clamp16_from_float(MixMul<float, int16_t, float>(value, volume));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int16_t MixMul<int16_t, float, float>(float value, float volume) {
|
||||
return clamp16_from_float(value * volume);
|
||||
}
|
||||
|
||||
/*
|
||||
* MixAccum is used to add into an accumulator register of a possibly different
|
||||
* type. The TO and TI types are the same as MixMul.
|
||||
*/
|
||||
|
||||
template <typename TO, typename TI>
|
||||
inline void MixAccum(TO *auxaccum, TI value) {
|
||||
if (!is_same<TO, TI>::value) {
|
||||
LOG_ALWAYS_FATAL("MixAccum type not properly specialized: %zu %zu\n",
|
||||
sizeof(TO), sizeof(TI));
|
||||
}
|
||||
*auxaccum += value;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void MixAccum<float, int16_t>(float *auxaccum, int16_t value) {
|
||||
static const float norm = 1. / (1 << 15);
|
||||
*auxaccum += norm * value;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void MixAccum<float, int32_t>(float *auxaccum, int32_t value) {
|
||||
static const float norm = 1. / (1 << 27);
|
||||
*auxaccum += norm * value;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void MixAccum<int32_t, int16_t>(int32_t *auxaccum, int16_t value) {
|
||||
*auxaccum += value << 12;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void MixAccum<int32_t, float>(int32_t *auxaccum, float value) {
|
||||
*auxaccum += clampq4_27_from_float(value);
|
||||
}
|
||||
|
||||
/* MixMulAux is just like MixMul except it combines with
|
||||
* an accumulator operation MixAccum.
|
||||
*/
|
||||
|
||||
template <typename TO, typename TI, typename TV, typename TA>
|
||||
inline TO MixMulAux(TI value, TV volume, TA *auxaccum) {
|
||||
MixAccum<TA, TI>(auxaccum, value);
|
||||
return MixMul<TO, TI, TV>(value, volume);
|
||||
}
|
||||
|
||||
/* MIXTYPE is used to determine how the samples in the input frame
|
||||
* are mixed with volume gain into the output frame.
|
||||
* See the volumeRampMulti functions below for more details.
|
||||
*/
|
||||
enum {
|
||||
MIXTYPE_MULTI,
|
||||
MIXTYPE_MONOEXPAND,
|
||||
MIXTYPE_MULTI_SAVEONLY,
|
||||
MIXTYPE_MULTI_MONOVOL,
|
||||
MIXTYPE_MULTI_SAVEONLY_MONOVOL,
|
||||
};
|
||||
|
||||
/*
|
||||
* The volumeRampMulti and volumeRamp functions take a MIXTYPE
|
||||
* which indicates the per-frame mixing and accumulation strategy.
|
||||
*
|
||||
* MIXTYPE_MULTI:
|
||||
* NCHAN represents number of input and output channels.
|
||||
* TO: int32_t (Q4.27) or float
|
||||
* TI: int32_t (Q4.27) or int16_t (Q0.15) or float
|
||||
* TV: int32_t (U4.28) or int16_t (U4.12) or float
|
||||
* vol: represents a volume array.
|
||||
*
|
||||
* This accumulates into the out pointer.
|
||||
*
|
||||
* MIXTYPE_MONOEXPAND:
|
||||
* Single input channel. NCHAN represents number of output channels.
|
||||
* TO: int32_t (Q4.27) or float
|
||||
* TI: int32_t (Q4.27) or int16_t (Q0.15) or float
|
||||
* TV: int32_t (U4.28) or int16_t (U4.12) or float
|
||||
* Input channel count is 1.
|
||||
* vol: represents volume array.
|
||||
*
|
||||
* This accumulates into the out pointer.
|
||||
*
|
||||
* MIXTYPE_MULTI_SAVEONLY:
|
||||
* NCHAN represents number of input and output channels.
|
||||
* TO: int16_t (Q.15) or float
|
||||
* TI: int32_t (Q4.27) or int16_t (Q0.15) or float
|
||||
* TV: int32_t (U4.28) or int16_t (U4.12) or float
|
||||
* vol: represents a volume array.
|
||||
*
|
||||
* MIXTYPE_MULTI_SAVEONLY does not accumulate into the out pointer.
|
||||
*
|
||||
* MIXTYPE_MULTI_MONOVOL:
|
||||
* Same as MIXTYPE_MULTI, but uses only volume[0].
|
||||
*
|
||||
* MIXTYPE_MULTI_SAVEONLY_MONOVOL:
|
||||
* Same as MIXTYPE_MULTI_SAVEONLY, but uses only volume[0].
|
||||
*
|
||||
*/
|
||||
|
||||
template <int MIXTYPE, int NCHAN,
|
||||
typename TO, typename TI, typename TV, typename TA, typename TAV>
|
||||
inline void volumeRampMulti(TO *out, size_t frameCount,
|
||||
const TI *in, TA *aux, TV *vol, const TV *volinc, TAV *vola, TAV volainc) {
|
||||
#ifdef ALOGVV
|
||||
ALOGVV("volumeRampMulti, MIXTYPE:%d\n", MIXTYPE);
|
||||
#endif
|
||||
if (aux != NULL) {
|
||||
do {
|
||||
TA auxaccum = 0;
|
||||
switch (MIXTYPE) {
|
||||
case MIXTYPE_MULTI:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
|
||||
vol[i] += volinc[i];
|
||||
}
|
||||
break;
|
||||
case MIXTYPE_MONOEXPAND:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
|
||||
vol[i] += volinc[i];
|
||||
}
|
||||
in++;
|
||||
break;
|
||||
case MIXTYPE_MULTI_SAVEONLY:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
|
||||
vol[i] += volinc[i];
|
||||
}
|
||||
break;
|
||||
case MIXTYPE_MULTI_MONOVOL:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
|
||||
}
|
||||
vol[0] += volinc[0];
|
||||
break;
|
||||
case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
|
||||
}
|
||||
vol[0] += volinc[0];
|
||||
break;
|
||||
default:
|
||||
LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
|
||||
break;
|
||||
}
|
||||
auxaccum /= NCHAN;
|
||||
*aux++ += MixMul<TA, TA, TAV>(auxaccum, *vola);
|
||||
vola[0] += volainc;
|
||||
} while (--frameCount);
|
||||
} else {
|
||||
do {
|
||||
switch (MIXTYPE) {
|
||||
case MIXTYPE_MULTI:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
|
||||
vol[i] += volinc[i];
|
||||
}
|
||||
break;
|
||||
case MIXTYPE_MONOEXPAND:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ += MixMul<TO, TI, TV>(*in, vol[i]);
|
||||
vol[i] += volinc[i];
|
||||
}
|
||||
in++;
|
||||
break;
|
||||
case MIXTYPE_MULTI_SAVEONLY:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
|
||||
vol[i] += volinc[i];
|
||||
}
|
||||
break;
|
||||
case MIXTYPE_MULTI_MONOVOL:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ += MixMul<TO, TI, TV>(*in++, vol[0]);
|
||||
}
|
||||
vol[0] += volinc[0];
|
||||
break;
|
||||
case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ = MixMul<TO, TI, TV>(*in++, vol[0]);
|
||||
}
|
||||
vol[0] += volinc[0];
|
||||
break;
|
||||
default:
|
||||
LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
|
||||
break;
|
||||
}
|
||||
} while (--frameCount);
|
||||
}
|
||||
}
|
||||
|
||||
template <int MIXTYPE, int NCHAN,
|
||||
typename TO, typename TI, typename TV, typename TA, typename TAV>
|
||||
inline void volumeMulti(TO *out, size_t frameCount,
|
||||
const TI *in, TA *aux, const TV *vol, TAV vola) {
|
||||
#ifdef ALOGVV
|
||||
ALOGVV("volumeMulti MIXTYPE:%d\n", MIXTYPE);
|
||||
#endif
|
||||
if (aux != NULL) {
|
||||
do {
|
||||
TA auxaccum = 0;
|
||||
switch (MIXTYPE) {
|
||||
case MIXTYPE_MULTI:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
|
||||
}
|
||||
break;
|
||||
case MIXTYPE_MONOEXPAND:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
|
||||
}
|
||||
in++;
|
||||
break;
|
||||
case MIXTYPE_MULTI_SAVEONLY:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
|
||||
}
|
||||
break;
|
||||
case MIXTYPE_MULTI_MONOVOL:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
|
||||
}
|
||||
break;
|
||||
case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
|
||||
break;
|
||||
}
|
||||
auxaccum /= NCHAN;
|
||||
*aux++ += MixMul<TA, TA, TAV>(auxaccum, vola);
|
||||
} while (--frameCount);
|
||||
} else {
|
||||
do {
|
||||
switch (MIXTYPE) {
|
||||
case MIXTYPE_MULTI:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
|
||||
}
|
||||
break;
|
||||
case MIXTYPE_MONOEXPAND:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ += MixMul<TO, TI, TV>(*in, vol[i]);
|
||||
}
|
||||
in++;
|
||||
break;
|
||||
case MIXTYPE_MULTI_SAVEONLY:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
|
||||
}
|
||||
break;
|
||||
case MIXTYPE_MULTI_MONOVOL:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ += MixMul<TO, TI, TV>(*in++, vol[0]);
|
||||
}
|
||||
break;
|
||||
case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
|
||||
for (int i = 0; i < NCHAN; ++i) {
|
||||
*out++ = MixMul<TO, TI, TV>(*in++, vol[0]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
|
||||
break;
|
||||
}
|
||||
} while (--frameCount);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
520
cocos/audio/android/AudioPlayerProvider.cpp
Normal file
520
cocos/audio/android/AudioPlayerProvider.cpp
Normal file
@@ -0,0 +1,520 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 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 <memory>
|
||||
#include "audio/android/PcmData.h"
|
||||
#include "audio/include/AudioDef.h"
|
||||
#include "base/Log.h"
|
||||
#define LOG_TAG "AudioPlayerProvider"
|
||||
|
||||
#include <algorithm> // for std::find_if
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
#include "audio/android/AudioDecoder.h"
|
||||
#include "audio/android/AudioDecoderProvider.h"
|
||||
#include "audio/android/AudioMixerController.h"
|
||||
#include "audio/android/AudioPlayerProvider.h"
|
||||
#include "audio/android/ICallerThreadUtils.h"
|
||||
#include "audio/android/PcmAudioPlayer.h"
|
||||
#include "audio/android/PcmAudioService.h"
|
||||
#include "audio/android/UrlAudioPlayer.h"
|
||||
#include "audio/android/utils/Utils.h"
|
||||
#include "base/ThreadPool.h"
|
||||
#include "base/memory/Memory.h"
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include <sys/system_properties.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
#include "cocos/platform/FileUtils.h"
|
||||
#include "cocos/platform/openharmony/FileUtils-OpenHarmony.h"
|
||||
#endif
|
||||
#include <algorithm> // for std::find_if
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
|
||||
namespace cc {
|
||||
|
||||
static int getSystemAPILevel() {
|
||||
static int sSystemApiLevel = -1;
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
if (sSystemApiLevel > 0) {
|
||||
return sSystemApiLevel;
|
||||
}
|
||||
|
||||
int apiLevel = getSDKVersion();
|
||||
if (apiLevel > 0) {
|
||||
ALOGD("Android API level: %d", apiLevel);
|
||||
} else {
|
||||
ALOGE("Fail to get Android API level!");
|
||||
}
|
||||
sSystemApiLevel = apiLevel;
|
||||
return apiLevel;
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
// TODO(qgh): On the openharmony platform, pcm streaming must be used
|
||||
return std::numeric_limits<int>::max();
|
||||
#endif
|
||||
}
|
||||
|
||||
struct AudioFileIndicator {
|
||||
ccstd::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, SLObjectItf outputMixObject,
|
||||
int deviceSampleRate, int bufferSizeInFrames,
|
||||
const FdGetterCallback &fdGetterCallback, //NOLINT(modernize-pass-by-value)
|
||||
ICallerThreadUtils *callerThreadUtils)
|
||||
: _engineItf(engineItf), _outputMixObject(outputMixObject), _deviceSampleRate(deviceSampleRate), _bufferSizeInFrames(bufferSizeInFrames), _fdGetterCallback(fdGetterCallback), _callerThreadUtils(callerThreadUtils), _pcmAudioService(nullptr), _mixController(nullptr), _threadPool(LegacyThreadPool::newCachedThreadPool(1, 8, 5, 2, 2)) {
|
||||
ALOGI("deviceSampleRate: %d, bufferSizeInFrames: %d", _deviceSampleRate, _bufferSizeInFrames);
|
||||
if (getSystemAPILevel() >= 17) {
|
||||
_mixController = ccnew AudioMixerController(_bufferSizeInFrames, _deviceSampleRate, 2);
|
||||
_mixController->init();
|
||||
_pcmAudioService = ccnew PcmAudioService(engineItf, outputMixObject);
|
||||
_pcmAudioService->init(_mixController, 2, deviceSampleRate, bufferSizeInFrames * 2);
|
||||
}
|
||||
|
||||
ALOG_ASSERT(callerThreadUtils != nullptr, "Caller thread utils parameter should not be nullptr!");
|
||||
}
|
||||
|
||||
AudioPlayerProvider::~AudioPlayerProvider() {
|
||||
ALOGV("~AudioPlayerProvider()");
|
||||
UrlAudioPlayer::stopAll();
|
||||
|
||||
SL_SAFE_DELETE(_pcmAudioService);
|
||||
SL_SAFE_DELETE(_mixController);
|
||||
SL_SAFE_DELETE(_threadPool);
|
||||
}
|
||||
|
||||
IAudioPlayer *AudioPlayerProvider::getAudioPlayer(const ccstd::string &audioFilePath) {
|
||||
// Pcm data decoding by OpenSLES API only supports in API level 17 and later.
|
||||
if (getSystemAPILevel() < 17) {
|
||||
AudioFileInfo info = getFileInfo(audioFilePath);
|
||||
if (info.isValid()) {
|
||||
return createUrlAudioPlayer(info);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IAudioPlayer *player = nullptr;
|
||||
|
||||
_pcmCacheMutex.lock();
|
||||
auto iter = _pcmCache.find(audioFilePath);
|
||||
if (iter != _pcmCache.end()) { // Found pcm cache means it was used to be a PcmAudioService
|
||||
PcmData pcmData = iter->second;
|
||||
_pcmCacheMutex.unlock();
|
||||
player = obtainPcmAudioPlayer(audioFilePath, pcmData);
|
||||
ALOGV_IF(player == nullptr, "%s, %d: player is nullptr, path: %s", __FUNCTION__, __LINE__, audioFilePath.c_str());
|
||||
} else {
|
||||
_pcmCacheMutex.unlock();
|
||||
// Check audio file size to determine to use a PcmAudioService or UrlAudioPlayer,
|
||||
// generally PcmAudioService is used for playing short audio like game effects while
|
||||
// playing background music uses UrlAudioPlayer
|
||||
AudioFileInfo info = getFileInfo(audioFilePath);
|
||||
if (info.isValid()) {
|
||||
if (isSmallFile(info)) {
|
||||
// Put an empty lambda to preloadEffect since we only want the future object to get PcmData
|
||||
auto pcmData = std::make_shared<PcmData>();
|
||||
auto isSucceed = std::make_shared<bool>(false);
|
||||
auto isReturnFromCache = std::make_shared<bool>(false);
|
||||
auto isPreloadFinished = std::make_shared<bool>(false);
|
||||
|
||||
std::thread::id threadId = std::this_thread::get_id();
|
||||
|
||||
void *infoPtr = &info;
|
||||
ccstd::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 (%p), Set isSucceed flag: %d, path: %s", infoPtr, succeed, url.c_str());
|
||||
},
|
||||
true);
|
||||
|
||||
if (!*isReturnFromCache && !*isPreloadFinished) {
|
||||
std::unique_lock<std::mutex> lck(_preloadWaitMutex);
|
||||
// Wait for 2 seconds for the decoding in sub thread finishes.
|
||||
ALOGV("FileInfo (%p), Waiting preload (%s) to finish ...", &info, audioFilePath.c_str());
|
||||
_preloadWaitCond.wait_for(lck, std::chrono::seconds(2));
|
||||
ALOGV("FileInfo (%p), Waitup preload (%s) ...", &info, audioFilePath.c_str());
|
||||
}
|
||||
|
||||
if (*isSucceed) {
|
||||
if (pcmData->isValid()) {
|
||||
player = obtainPcmAudioPlayer(info.url, *pcmData);
|
||||
ALOGV_IF(player == nullptr, "%s, %d: player is nullptr, path: %s", __FUNCTION__, __LINE__, audioFilePath.c_str());
|
||||
} else {
|
||||
ALOGE("pcm data is invalid, path: %s", audioFilePath.c_str());
|
||||
}
|
||||
} else {
|
||||
ALOGE("FileInfo (%p), preloadEffect (%s) failed", &info, audioFilePath.c_str());
|
||||
}
|
||||
} else {
|
||||
player = createUrlAudioPlayer(info);
|
||||
ALOGV_IF(player == nullptr, "%s, %d: player is nullptr, path: %s", __FUNCTION__, __LINE__, audioFilePath.c_str());
|
||||
}
|
||||
} else {
|
||||
ALOGE("File info is invalid, path: %s", audioFilePath.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ALOGV_IF(player == nullptr, "%s, %d return nullptr", __FUNCTION__, __LINE__);
|
||||
return player;
|
||||
}
|
||||
|
||||
void AudioPlayerProvider::preloadEffect(const ccstd::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: (%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)) {
|
||||
ccstd::string audioFilePath = info.url;
|
||||
|
||||
// 1. First time check, if it wasn't in the cache, goto 2 step
|
||||
_pcmCacheMutex.lock();
|
||||
auto &&iter = _pcmCache.find(audioFilePath);
|
||||
if (iter != _pcmCache.end()) {
|
||||
ALOGV("1. Return pcm data from cache, url: %s", info.url.c_str());
|
||||
_pcmCacheMutex.unlock();
|
||||
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<std::mutex> lck(_preloadCallbackMutex);
|
||||
|
||||
auto &&preloadIter = _preloadCallbackMap.find(audioFilePath);
|
||||
if (preloadIter != _preloadCallbackMap.end()) {
|
||||
ALOGV("audio (%s) is being preloaded, add to callback vector!", audioFilePath.c_str());
|
||||
PreloadCallbackParam param;
|
||||
param.callback = 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: %s", info.url.c_str());
|
||||
_pcmCacheMutex.unlock();
|
||||
callback(true, iter->second);
|
||||
return;
|
||||
}
|
||||
_pcmCacheMutex.unlock();
|
||||
|
||||
PreloadCallbackParam param;
|
||||
param.callback = callback;
|
||||
param.isPreloadInPlay2d = isPreloadInPlay2d;
|
||||
ccstd::vector<PreloadCallbackParam> callbacks;
|
||||
callbacks.push_back(std::move(param));
|
||||
_preloadCallbackMap.insert(std::make_pair(audioFilePath, std::move(callbacks)));
|
||||
}
|
||||
|
||||
_threadPool->pushTask([this, audioFilePath](int /*tid*/) {
|
||||
ALOGV("AudioPlayerProvider::preloadEffect: (%s)", audioFilePath.c_str());
|
||||
PcmData d;
|
||||
AudioDecoder *decoder = AudioDecoderProvider::createAudioDecoder(_engineItf, audioFilePath, _bufferSizeInFrames, _deviceSampleRate, _fdGetterCallback);
|
||||
bool ret = decoder != nullptr && decoder->start();
|
||||
if (ret) {
|
||||
d = decoder->getResult();
|
||||
std::lock_guard<std::mutex> lck(_pcmCacheMutex);
|
||||
_pcmCache.insert(std::make_pair(audioFilePath, d));
|
||||
} else {
|
||||
ALOGE("decode (%s) failed!", audioFilePath.c_str());
|
||||
}
|
||||
|
||||
ALOGV("decode %s", (ret ? "succeed" : "failed"));
|
||||
|
||||
std::lock_guard<std::mutex> lck(_preloadCallbackMutex);
|
||||
auto &&preloadIter = _preloadCallbackMap.find(audioFilePath);
|
||||
if (preloadIter != _preloadCallbackMap.end()) {
|
||||
auto &¶ms = preloadIter->second;
|
||||
ALOGV("preload (%s) callback count: %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 (%s) is too large, ignore preload!", info.url.c_str());
|
||||
callback(true, pcmData);
|
||||
}
|
||||
}
|
||||
|
||||
AudioPlayerProvider::AudioFileInfo AudioPlayerProvider::getFileInfo(
|
||||
const ccstd::string &audioFilePath) {
|
||||
AudioFileInfo info;
|
||||
long fileSize = 0; //NOLINT(google-runtime-int)
|
||||
off_t start = 0;
|
||||
off_t length = 0;
|
||||
int assetFd = -1;
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
if (audioFilePath[0] != '/') {
|
||||
ccstd::string relativePath;
|
||||
size_t position = audioFilePath.find("@assets/");
|
||||
|
||||
if (0 == position) {
|
||||
// "@assets/" is at the beginning of the path and we don't want it
|
||||
relativePath = audioFilePath.substr(strlen("@assets/"));
|
||||
} else {
|
||||
relativePath = audioFilePath;
|
||||
}
|
||||
|
||||
assetFd = _fdGetterCallback(relativePath, &start, &length);
|
||||
|
||||
if (assetFd <= 0) {
|
||||
ALOGE("Failed to open file descriptor for '%s'", audioFilePath.c_str());
|
||||
return info;
|
||||
}
|
||||
|
||||
fileSize = length;
|
||||
} else {
|
||||
FILE *fp = fopen(audioFilePath.c_str(), "rb");
|
||||
if (fp != nullptr) {
|
||||
fseek(fp, 0, SEEK_END);
|
||||
fileSize = ftell(fp);
|
||||
fclose(fp);
|
||||
} else {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
info.url = audioFilePath;
|
||||
info.assetFd = std::make_shared<AssetFd>(assetFd);
|
||||
info.start = start;
|
||||
info.length = fileSize;
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
FileUtilsOpenHarmony* fileUtils = dynamic_cast<FileUtilsOpenHarmony*>(FileUtils::getInstance());
|
||||
if(!fileUtils) {
|
||||
return info;
|
||||
}
|
||||
|
||||
RawFileDescriptor descriptor;
|
||||
fileUtils->getRawFileDescriptor(audioFilePath, descriptor);
|
||||
info.url = audioFilePath;
|
||||
info.assetFd = std::make_shared<AssetFd>(descriptor.fd);
|
||||
info.start = descriptor.start;
|
||||
info.length = descriptor.length;
|
||||
#endif
|
||||
|
||||
ALOGV("(%s) file size: %ld", audioFilePath.c_str(), fileSize);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
bool AudioPlayerProvider::isSmallFile(const AudioFileInfo &info) { //NOLINT(readability-convert-member-functions-to-static)
|
||||
#if CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
// TODO(qgh): OpenHarmony system does not support this function yet
|
||||
return true;
|
||||
#endif
|
||||
//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<AudioFileInfo &>(info);
|
||||
size_t judgeCount = sizeof(gAudioFileIndicator) / sizeof(gAudioFileIndicator[0]);
|
||||
size_t pos = audioFileInfo.url.rfind('.');
|
||||
ccstd::string extension;
|
||||
if (pos != ccstd::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: %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 ccstd::string &filePath) {
|
||||
std::lock_guard<std::mutex> lck(_pcmCacheMutex);
|
||||
auto iter = _pcmCache.find(filePath);
|
||||
if (iter != _pcmCache.end()) {
|
||||
return iter->second.duration;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AudioPlayerProvider::clearPcmCache(const ccstd::string &audioFilePath) {
|
||||
std::lock_guard<std::mutex> lck(_pcmCacheMutex);
|
||||
auto iter = _pcmCache.find(audioFilePath);
|
||||
if (iter != _pcmCache.end()) {
|
||||
ALOGV("clear pcm cache: (%s)", audioFilePath.c_str());
|
||||
_pcmCache.erase(iter);
|
||||
} else {
|
||||
ALOGW("Couldn't find the pcm cache: (%s)", audioFilePath.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void AudioPlayerProvider::clearAllPcmCaches() {
|
||||
std::lock_guard<std::mutex> lck(_pcmCacheMutex);
|
||||
_pcmCache.clear();
|
||||
}
|
||||
|
||||
PcmAudioPlayer *AudioPlayerProvider::obtainPcmAudioPlayer(const ccstd::string &url,
|
||||
const PcmData &pcmData) {
|
||||
PcmAudioPlayer *pcmPlayer = nullptr;
|
||||
if (pcmData.isValid()) {
|
||||
pcmPlayer = ccnew 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;
|
||||
}
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
SLuint32 locatorType = info.assetFd->getFd() > 0 ? SL_DATALOCATOR_ANDROIDFD : SL_DATALOCATOR_URI;
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
SLuint32 locatorType = SL_DATALOCATOR_URI;
|
||||
#endif
|
||||
|
||||
auto *urlPlayer = new (std::nothrow) UrlAudioPlayer(_engineItf, _outputMixObject, _callerThreadUtils);
|
||||
bool ret = urlPlayer->prepare(info.url, locatorType, info.assetFd, info.start, info.length);
|
||||
if (!ret) {
|
||||
SL_SAFE_DELETE(urlPlayer);
|
||||
}
|
||||
return urlPlayer;
|
||||
}
|
||||
|
||||
void AudioPlayerProvider::pause() {
|
||||
if (_mixController != nullptr) {
|
||||
_mixController->pause();
|
||||
}
|
||||
|
||||
if (_pcmAudioService != nullptr) {
|
||||
_pcmAudioService->pause();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioPlayerProvider::resume() {
|
||||
if (_mixController != nullptr) {
|
||||
_mixController->resume();
|
||||
}
|
||||
|
||||
if (_pcmAudioService != nullptr) {
|
||||
_pcmAudioService->resume();
|
||||
}
|
||||
}
|
||||
void AudioPlayerProvider::registerPcmData(const ccstd::string &audioFilePath, PcmData &data) {
|
||||
std::lock_guard<std::mutex> lck(_pcmCacheMutex);
|
||||
if (_pcmCache.find(audioFilePath) != _pcmCache.end()) {
|
||||
CC_LOG_DEBUG("file %s pcm data is already cached.", audioFilePath.c_str());
|
||||
return;
|
||||
}
|
||||
_pcmCache.emplace(audioFilePath, data);
|
||||
}
|
||||
|
||||
bool AudioPlayerProvider::getPcmHeader(const ccstd::string &audioFilePath, PCMHeader &header) {
|
||||
std::lock_guard<std::mutex> lck(_pcmCacheMutex);
|
||||
auto &&iter = _pcmCache.find(audioFilePath);
|
||||
if (iter != _pcmCache.end()) {
|
||||
ALOGV("get pcm header from cache, url: %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 ccstd::string &audioFilePath, PcmData &data) {
|
||||
std::lock_guard<std::mutex> lck(_pcmCacheMutex);
|
||||
auto &&iter = _pcmCache.find(audioFilePath);
|
||||
if (iter != _pcmCache.end()) {
|
||||
ALOGV("get pcm buffer from cache, url: %s", audioFilePath.c_str());
|
||||
// On Android, all pcm buffer is resampled to sign16.
|
||||
data = iter->second;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace cc
|
||||
122
cocos/audio/android/AudioPlayerProvider.h
Normal file
122
cocos/audio/android/AudioPlayerProvider.h
Normal file
@@ -0,0 +1,122 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 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 <condition_variable>
|
||||
#include <memory>
|
||||
#include "audio/android/IAudioPlayer.h"
|
||||
#include "audio/android/OpenSLHelper.h"
|
||||
#include "audio/android/PcmData.h"
|
||||
#include "audio/include/AudioDef.h"
|
||||
#include "base/std/container/unordered_map.h"
|
||||
|
||||
namespace cc {
|
||||
// Manage PcmAudioPlayer& UrlAudioPlayer
|
||||
|
||||
class PcmAudioPlayer;
|
||||
class PcmAudioService;
|
||||
class UrlAudioPlayer;
|
||||
class AudioMixerController;
|
||||
class ICallerThreadUtils;
|
||||
class AssetFd;
|
||||
class LegacyThreadPool;
|
||||
|
||||
class AudioPlayerProvider {
|
||||
public:
|
||||
AudioPlayerProvider(SLEngineItf engineItf, SLObjectItf outputMixObject, int deviceSampleRate,
|
||||
int bufferSizeInFrames, const FdGetterCallback &fdGetterCallback,
|
||||
ICallerThreadUtils *callerThreadUtils);
|
||||
|
||||
virtual ~AudioPlayerProvider();
|
||||
bool isFileCached(const ccstd::string &audioFilePath);
|
||||
IAudioPlayer *getAudioPlayer(const ccstd::string &audioFilePath);
|
||||
bool getPcmHeader(const ccstd::string &audioFilePath, PCMHeader &header);
|
||||
bool getPcmData(const ccstd::string &audioFilePath, PcmData &data);
|
||||
using PreloadCallback = std::function<void(bool, PcmData)>;
|
||||
void preloadEffect(const ccstd::string &audioFilePath, const PreloadCallback &callback);
|
||||
void registerPcmData(const ccstd::string &audioFilePath, PcmData &data);
|
||||
float getDurationFromFile(const ccstd::string &filePath);
|
||||
void clearPcmCache(const ccstd::string &audioFilePath);
|
||||
|
||||
void clearAllPcmCaches();
|
||||
|
||||
void pause();
|
||||
|
||||
void resume();
|
||||
|
||||
private:
|
||||
struct AudioFileInfo {
|
||||
ccstd::string url;
|
||||
std::shared_ptr<AssetFd> assetFd;
|
||||
off_t start{};
|
||||
off_t length;
|
||||
|
||||
AudioFileInfo()
|
||||
: assetFd(nullptr) {}
|
||||
|
||||
inline bool isValid() const {
|
||||
return !url.empty() && length > 0;
|
||||
}
|
||||
};
|
||||
|
||||
PcmAudioPlayer *obtainPcmAudioPlayer(const ccstd::string &url, const PcmData &pcmData);
|
||||
|
||||
UrlAudioPlayer *createUrlAudioPlayer(const AudioFileInfo &info);
|
||||
|
||||
void preloadEffect(const AudioFileInfo &info, const PreloadCallback &callback, bool isPreloadInPlay2d);
|
||||
|
||||
AudioFileInfo getFileInfo(const ccstd::string &audioFilePath);
|
||||
|
||||
bool isSmallFile(const AudioFileInfo &info);
|
||||
|
||||
SLEngineItf _engineItf;
|
||||
SLObjectItf _outputMixObject;
|
||||
int _deviceSampleRate;
|
||||
int _bufferSizeInFrames;
|
||||
FdGetterCallback _fdGetterCallback;
|
||||
ICallerThreadUtils *_callerThreadUtils;
|
||||
|
||||
ccstd::unordered_map<ccstd::string, PcmData> _pcmCache;
|
||||
std::mutex _pcmCacheMutex;
|
||||
|
||||
struct PreloadCallbackParam {
|
||||
PreloadCallback callback;
|
||||
bool isPreloadInPlay2d;
|
||||
};
|
||||
|
||||
ccstd::unordered_map<ccstd::string, ccstd::vector<PreloadCallbackParam>> _preloadCallbackMap;
|
||||
std::mutex _preloadCallbackMutex;
|
||||
|
||||
std::mutex _preloadWaitMutex;
|
||||
std::condition_variable _preloadWaitCond;
|
||||
|
||||
PcmAudioService *_pcmAudioService;
|
||||
AudioMixerController *_mixController;
|
||||
|
||||
LegacyThreadPool *_threadPool;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
792
cocos/audio/android/AudioResampler.cpp
Normal file
792
cocos/audio/android/AudioResampler.cpp
Normal file
@@ -0,0 +1,792 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "AudioResampler"
|
||||
//#define LOG_NDEBUG 0
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <new>
|
||||
#include "audio/android/cutils/log.h"
|
||||
#include "audio/android/utils/Utils.h"
|
||||
//#include <cutils/properties.h>
|
||||
#include "audio/android/AudioResampler.h"
|
||||
#include "audio/common/utils/include/primitives.h"
|
||||
//#include "audio/android/AudioResamplerSinc.h"
|
||||
#include "audio/android/AudioResamplerCubic.h"
|
||||
#include "base/memory/Memory.h"
|
||||
|
||||
//#include "AudioResamplerDyn.h"
|
||||
|
||||
//cjh #ifdef __arm__
|
||||
// #define ASM_ARM_RESAMP1 // enable asm optimisation for ResamplerOrder1
|
||||
//#endif
|
||||
|
||||
namespace cc {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
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 = ccnew 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 = ccnew 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 = ccnew 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 = ccnew 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 cc
|
||||
182
cocos/audio/android/AudioResampler.h
Normal file
182
cocos/audio/android/AudioResampler.h
Normal file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include <android/log.h>
|
||||
#include <sys/system_properties.h>
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#include "audio/android/AudioBufferProvider.h"
|
||||
|
||||
//#include <cutils/compiler.h>
|
||||
//#include <utils/Compat.h>
|
||||
|
||||
//#include <media/AudioBufferProvider.h>
|
||||
//#include <system/audio.h>
|
||||
#include <cassert>
|
||||
#include "audio/android/audio.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
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<uint64_t>(outFrameCount)*mInSampleRate + (mSampleRate - 1))/mSampleRate;
|
||||
//
|
||||
// The double precision equivalent (float may not be precise enough):
|
||||
// ceil(static_cast<double>(outFrameCount) * mInSampleRate / mSampleRate);
|
||||
//
|
||||
// this relies on the fact that the mPhaseIncrement is rounded down from
|
||||
// #phases * mInSampleRate/mSampleRate and the fact that Sum(Floor(x)) <= Floor(Sum(x)).
|
||||
// http://www.proofwiki.org/wiki/Sum_of_Floors_Not_Greater_Than_Floor_of_Sums
|
||||
//
|
||||
// (so long as double precision is computed accurately enough to be considered
|
||||
// greater than or equal to the Floor(x) value in int32_t arithmetic; thus this
|
||||
// will not necessarily hold for floats).
|
||||
//
|
||||
// REFINE:
|
||||
// Greater accuracy and a tight bound is obtained by:
|
||||
// 1) subtract and adjust for the current state of the AudioBufferProvider buffer.
|
||||
// 2) using the exact integer formula where (ignoring 64b casting)
|
||||
// inFrameCount = (mPhaseIncrement * (outFrameCount - 1) + mPhaseFraction) / phaseWrapLimit;
|
||||
// phaseWrapLimit is the wraparound (1 << kNumPhaseBits), if not specified explicitly.
|
||||
//
|
||||
inline size_t getInFrameCountRequired(size_t outFrameCount) const {
|
||||
return (static_cast<size_t>(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 cc
|
||||
186
cocos/audio/android/AudioResamplerCubic.cpp
Normal file
186
cocos/audio/android/AudioResamplerCubic.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "AudioResamplerCubic"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include "audio/android/cutils/log.h"
|
||||
|
||||
#include "audio/android/AudioResampler.h"
|
||||
#include "audio/android/AudioResamplerCubic.h"
|
||||
|
||||
namespace cc {
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
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 cc
|
||||
65
cocos/audio/android/AudioResamplerCubic.h
Normal file
65
cocos/audio/android/AudioResamplerCubic.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "audio/android/AudioResampler.h"
|
||||
#include "audio/android/AudioBufferProvider.h"
|
||||
|
||||
namespace cc {
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
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 cc
|
||||
171
cocos/audio/android/AudioResamplerPublic.h
Normal file
171
cocos/audio/android/AudioResamplerPublic.h
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace cc {
|
||||
|
||||
// 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<size_t>(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 cc
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
87
cocos/audio/android/IAudioPlayer.h
Normal file
87
cocos/audio/android/IAudioPlayer.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include "base/std/container/string.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class IAudioPlayer {
|
||||
public:
|
||||
enum class State {
|
||||
INVALID = 0,
|
||||
INITIALIZED,
|
||||
PLAYING,
|
||||
PAUSED,
|
||||
STOPPED,
|
||||
OVER
|
||||
};
|
||||
|
||||
using PlayEventCallback = std::function<void(State)>;
|
||||
|
||||
virtual ~IAudioPlayer(){};
|
||||
|
||||
virtual int getId() const = 0;
|
||||
|
||||
virtual void setId(int id) = 0;
|
||||
|
||||
virtual ccstd::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 cc
|
||||
40
cocos/audio/android/ICallerThreadUtils.h
Normal file
40
cocos/audio/android/ICallerThreadUtils.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
namespace cc {
|
||||
|
||||
class ICallerThreadUtils {
|
||||
public:
|
||||
virtual ~ICallerThreadUtils(){};
|
||||
|
||||
virtual void performFunctionInCallerThread(const std::function<void()> &func) = 0;
|
||||
virtual std::thread::id getCallerThreadId() = 0;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
42
cocos/audio/android/IVolumeProvider.h
Normal file
42
cocos/audio/android/IVolumeProvider.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 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/common/utils/include/minifloat.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
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 cc
|
||||
107
cocos/audio/android/OpenSLHelper.h
Normal file
107
cocos/audio/android/OpenSLHelper.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio/android/cutils/log.h"
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
#include <SLES/OpenSLES_Platform.h>
|
||||
#endif
|
||||
|
||||
#include <functional>
|
||||
#include "base/std/container/string.h"
|
||||
|
||||
#define SL_SAFE_DELETE(obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
delete (obj); \
|
||||
(obj) = nullptr; \
|
||||
}
|
||||
|
||||
#define SL_DESTROY_OBJ(OBJ) \
|
||||
if ((OBJ) != nullptr) { \
|
||||
(*(OBJ))->Destroy(OBJ); \
|
||||
(OBJ) = nullptr; \
|
||||
}
|
||||
|
||||
#define SL_RETURN_VAL_IF_FAILED(r, rval, ...) \
|
||||
if (r != SL_RESULT_SUCCESS) { \
|
||||
ALOGE(__VA_ARGS__); \
|
||||
return rval; \
|
||||
}
|
||||
|
||||
#define SL_RETURN_IF_FAILED(r, ...) \
|
||||
if (r != SL_RESULT_SUCCESS) { \
|
||||
ALOGE(__VA_ARGS__); \
|
||||
return; \
|
||||
}
|
||||
|
||||
#define SL_PRINT_ERROR_IF_FAILED(r, ...) \
|
||||
if (r != SL_RESULT_SUCCESS) { \
|
||||
ALOGE(__VA_ARGS__); \
|
||||
}
|
||||
|
||||
typedef std::function<int(const ccstd::string &, off_t *start, off_t *length)> FdGetterCallback;
|
||||
|
||||
// Copied from OpenSLES_AndroidMetadata.h in android-21
|
||||
// It's because android-10 doesn't contain this header file
|
||||
/**
|
||||
* Additional metadata keys to be used in SLMetadataExtractionItf:
|
||||
* the ANDROID_KEY_PCMFORMAT_* keys follow the fields of the SLDataFormat_PCM struct, and as such
|
||||
* all values corresponding to these keys are of SLuint32 type, and are defined as the fields
|
||||
* of the same name in SLDataFormat_PCM. The exception is that sample rate is expressed here
|
||||
* in Hz units, rather than in milliHz units.
|
||||
*/
|
||||
#ifndef ANDROID_KEY_PCMFORMAT_NUMCHANNELS
|
||||
#define ANDROID_KEY_PCMFORMAT_NUMCHANNELS "AndroidPcmFormatNumChannels"
|
||||
#endif
|
||||
|
||||
#ifndef ANDROID_KEY_PCMFORMAT_SAMPLERATE
|
||||
#define ANDROID_KEY_PCMFORMAT_SAMPLERATE "AndroidPcmFormatSampleRate"
|
||||
#endif
|
||||
|
||||
#ifndef ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE
|
||||
#define ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE "AndroidPcmFormatBitsPerSample"
|
||||
#endif
|
||||
|
||||
#ifndef ANDROID_KEY_PCMFORMAT_CONTAINERSIZE
|
||||
#define ANDROID_KEY_PCMFORMAT_CONTAINERSIZE "AndroidPcmFormatContainerSize"
|
||||
#endif
|
||||
|
||||
#ifndef ANDROID_KEY_PCMFORMAT_CHANNELMASK
|
||||
#define ANDROID_KEY_PCMFORMAT_CHANNELMASK "AndroidPcmFormatChannelMask"
|
||||
#endif
|
||||
|
||||
#ifndef ANDROID_KEY_PCMFORMAT_ENDIANNESS
|
||||
#define ANDROID_KEY_PCMFORMAT_ENDIANNESS "AndroidPcmFormatEndianness"
|
||||
#endif
|
||||
|
||||
#define clockNow() std::chrono::high_resolution_clock::now()
|
||||
#define intervalInMS(oldTime, newTime) (static_cast<long>(std::chrono::duration_cast<std::chrono::microseconds>((newTime) - (oldTime)).count()) / 1000.f)
|
||||
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
|
||||
193
cocos/audio/android/PcmAudioPlayer.cpp
Normal file
193
cocos/audio/android/PcmAudioPlayer.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#define LOG_TAG "PcmAudioPlayer"
|
||||
|
||||
#include "audio/android/PcmAudioPlayer.h"
|
||||
#include "audio/android/AudioMixerController.h"
|
||||
#include "audio/android/ICallerThreadUtils.h"
|
||||
#include "audio/android/cutils/log.h"
|
||||
#include "base/memory/Memory.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
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 ccstd::string &url, const PcmData &decResult) {
|
||||
_url = url;
|
||||
_decResult = decResult;
|
||||
|
||||
_track = ccnew Track(_decResult);
|
||||
|
||||
std::thread::id callerThreadId = _callerThreadUtils->getCallerThreadId();
|
||||
|
||||
// @note The logic may cause this issue https://github.com/cocos2d/cocos2d-x/issues/17707
|
||||
// Assume that AudioEngine::stop(id) is invoked and the audio is played over meanwhile.
|
||||
// Since State::OVER and State::DESTROYED are triggered in the audio mixing thread, it will
|
||||
// call 'performFunctionInCallerThread' to post events to cocos's message queue.
|
||||
// Therefore, the sequence in cocos's thread will be |STOP|OVER|DESTROYED|.
|
||||
// Although, we remove the audio id in |STOPPED| callback, because it's asynchronous operation,
|
||||
// |OVER| and |DESTROYED| callbacks will still be invoked in cocos's thread.
|
||||
// HOW TO FIX: If the previous state is |STOPPED| and the current state
|
||||
// is |OVER|, just skip to invoke |OVER| callback.
|
||||
|
||||
_track->onStateChanged = [this, callerThreadId](Track::State state) {
|
||||
// It maybe in sub thread
|
||||
Track::State prevState = _track->getPrevState();
|
||||
auto func = [this, state, prevState]() {
|
||||
// It's in caller's thread
|
||||
if (state == Track::State::OVER && prevState != Track::State::STOPPED) {
|
||||
if (_playEventCallback != nullptr) {
|
||||
_playEventCallback(State::OVER);
|
||||
}
|
||||
} else if (state == Track::State::STOPPED) {
|
||||
if (_playEventCallback != nullptr) {
|
||||
_playEventCallback(State::STOPPED);
|
||||
}
|
||||
} else if (state == Track::State::DESTROYED) {
|
||||
delete this;
|
||||
}
|
||||
};
|
||||
|
||||
if (callerThreadId == std::this_thread::get_id()) { // onStateChanged(Track::State::STOPPED) is in caller's (Cocos's) thread.
|
||||
func();
|
||||
} else { // onStateChanged(Track::State::OVER) or onStateChanged(Track::State::DESTROYED) are in audio mixing thread.
|
||||
_callerThreadUtils->performFunctionInCallerThread(func);
|
||||
}
|
||||
};
|
||||
|
||||
setVolume(1.0f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PcmAudioPlayer::rewind() {
|
||||
ALOGW("PcmAudioPlayer::rewind isn't supported!");
|
||||
}
|
||||
|
||||
void PcmAudioPlayer::setVolume(float volume) {
|
||||
_track->setVolume(volume);
|
||||
}
|
||||
|
||||
float PcmAudioPlayer::getVolume() const {
|
||||
return _track->getVolume();
|
||||
}
|
||||
|
||||
void PcmAudioPlayer::setAudioFocus(bool isFocus) {
|
||||
_track->setAudioFocus(isFocus);
|
||||
}
|
||||
|
||||
void PcmAudioPlayer::setLoop(bool isLoop) {
|
||||
_track->setLoop(isLoop);
|
||||
}
|
||||
|
||||
bool PcmAudioPlayer::isLoop() const {
|
||||
return _track->isLoop();
|
||||
}
|
||||
|
||||
float PcmAudioPlayer::getDuration() const {
|
||||
return _decResult.duration;
|
||||
}
|
||||
|
||||
float PcmAudioPlayer::getPosition() const {
|
||||
return _track->getPosition();
|
||||
}
|
||||
|
||||
bool PcmAudioPlayer::setPosition(float pos) {
|
||||
return _track->setPosition(pos);
|
||||
}
|
||||
|
||||
void PcmAudioPlayer::setPlayEventCallback(const PlayEventCallback &playEventCallback) {
|
||||
_playEventCallback = playEventCallback;
|
||||
}
|
||||
|
||||
void PcmAudioPlayer::play() {
|
||||
// put track to AudioMixerController
|
||||
ALOGV("PcmAudioPlayer (%p) play, url: %s", this, _url.c_str());
|
||||
_controller->addTrack(_track);
|
||||
_track->setState(Track::State::PLAYING);
|
||||
}
|
||||
|
||||
void PcmAudioPlayer::pause() {
|
||||
ALOGV("PcmAudioPlayer (%p) pause, url: %s", this, _url.c_str());
|
||||
_track->setState(Track::State::PAUSED);
|
||||
}
|
||||
|
||||
void PcmAudioPlayer::resume() {
|
||||
ALOGV("PcmAudioPlayer (%p) resume, url: %s", this, _url.c_str());
|
||||
_track->setState(Track::State::RESUMED);
|
||||
}
|
||||
|
||||
void PcmAudioPlayer::stop() {
|
||||
ALOGV("PcmAudioPlayer (%p) stop, url: %s", this, _url.c_str());
|
||||
_track->setState(Track::State::STOPPED);
|
||||
}
|
||||
|
||||
IAudioPlayer::State PcmAudioPlayer::getState() const {
|
||||
IAudioPlayer::State state = State::INVALID;
|
||||
|
||||
if (_track != nullptr) {
|
||||
switch (_track->getState()) {
|
||||
case Track::State::IDLE:
|
||||
state = State::INITIALIZED;
|
||||
break;
|
||||
|
||||
case Track::State::PLAYING:
|
||||
state = State::PLAYING;
|
||||
break;
|
||||
|
||||
case Track::State::RESUMED:
|
||||
state = State::PLAYING;
|
||||
break;
|
||||
|
||||
case Track::State::PAUSED:
|
||||
state = State::PAUSED;
|
||||
break;
|
||||
|
||||
case Track::State::STOPPED:
|
||||
state = State::STOPPED;
|
||||
break;
|
||||
|
||||
case Track::State::OVER:
|
||||
state = State::OVER;
|
||||
break;
|
||||
|
||||
default:
|
||||
state = State::INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
96
cocos/audio/android/PcmAudioPlayer.h
Normal file
96
cocos/audio/android/PcmAudioPlayer.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include "audio/android/IAudioPlayer.h"
|
||||
#include "audio/android/PcmData.h"
|
||||
#include "audio/android/Track.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class ICallerThreadUtils;
|
||||
class AudioMixerController;
|
||||
|
||||
class PcmAudioPlayer : public IAudioPlayer {
|
||||
public:
|
||||
bool prepare(const ccstd::string &url, const PcmData &decResult);
|
||||
|
||||
// Override Functions Begin
|
||||
virtual int getId() const override { return _id; };
|
||||
|
||||
virtual void setId(int id) override { _id = id; };
|
||||
|
||||
virtual ccstd::string getUrl() const override { return _url; };
|
||||
|
||||
virtual State getState() const override;
|
||||
|
||||
virtual void play() override;
|
||||
|
||||
virtual void pause() override;
|
||||
|
||||
virtual void resume() override;
|
||||
|
||||
virtual void stop() override;
|
||||
|
||||
virtual void rewind() override;
|
||||
|
||||
virtual void setVolume(float volume) override;
|
||||
|
||||
virtual float getVolume() const override;
|
||||
|
||||
virtual void setAudioFocus(bool isFocus) override;
|
||||
|
||||
virtual void setLoop(bool isLoop) override;
|
||||
|
||||
virtual bool isLoop() const override;
|
||||
|
||||
virtual float getDuration() const override;
|
||||
|
||||
virtual float getPosition() const override;
|
||||
|
||||
virtual bool setPosition(float pos) override;
|
||||
|
||||
virtual void setPlayEventCallback(const PlayEventCallback &playEventCallback) override;
|
||||
|
||||
// Override Functions End
|
||||
|
||||
private:
|
||||
PcmAudioPlayer(AudioMixerController *controller, ICallerThreadUtils *callerThreadUtils);
|
||||
virtual ~PcmAudioPlayer();
|
||||
|
||||
private:
|
||||
int _id;
|
||||
ccstd::string _url;
|
||||
PcmData _decResult;
|
||||
Track *_track;
|
||||
PlayEventCallback _playEventCallback;
|
||||
AudioMixerController *_controller;
|
||||
ICallerThreadUtils *_callerThreadUtils;
|
||||
|
||||
friend class AudioPlayerProvider;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
190
cocos/audio/android/PcmAudioService.cpp
Normal file
190
cocos/audio/android/PcmAudioService.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 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 "base/Macros.h"
|
||||
#include "audio/android/PcmAudioService.h"
|
||||
#include "audio/android/AudioMixerController.h"
|
||||
#include "audio/android/utils/Compat.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
static ccstd::vector<char> __silenceData;//NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
|
||||
|
||||
#define AUDIO_PLAYER_BUFFER_COUNT (2)
|
||||
|
||||
class SLPcmAudioPlayerCallbackProxy {
|
||||
public:
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
static void samplePlayerCallback(CCSLBufferQueueItf bq, void *context) {
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
static void samplePlayerCallback(CCSLBufferQueueItf bq, void *context, SLuint32 size) {
|
||||
#endif
|
||||
auto *thiz = reinterpret_cast<PcmAudioService *>(context);
|
||||
thiz->bqFetchBufferCallback(bq);
|
||||
}
|
||||
};
|
||||
|
||||
PcmAudioService::PcmAudioService(SLEngineItf engineItf, SLObjectItf outputMixObject)
|
||||
: _engineItf(engineItf), _outputMixObj(outputMixObject), _playObj(nullptr), _playItf(nullptr), _volumeItf(nullptr), _bufferQueueItf(nullptr), _numChannels(-1), _sampleRate(-1), _bufferSizeInBytes(0), _controller(nullptr) {
|
||||
}
|
||||
|
||||
PcmAudioService::~PcmAudioService() {
|
||||
ALOGV("PcmAudioServicee() (%p), before destroy play object", this);
|
||||
SL_DESTROY_OBJ(_playObj);
|
||||
ALOGV("PcmAudioServicee() end");
|
||||
}
|
||||
|
||||
bool PcmAudioService::enqueue() {
|
||||
#if CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
// We need to call this interface in openharmony, otherwise there will be noise
|
||||
SLuint8 *buffer = nullptr;
|
||||
SLuint32 size = 0;
|
||||
(*_bufferQueueItf)->GetBuffer(_bufferQueueItf, &buffer, &size);
|
||||
#endif
|
||||
if (_controller->hasPlayingTacks()) {
|
||||
if (_controller->isPaused()) {
|
||||
SLresult r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, __silenceData.data(), __silenceData.size());
|
||||
SL_RETURN_VAL_IF_FAILED(r, false, "enqueue silent data failed!");
|
||||
} else {
|
||||
_controller->mixOneFrame();
|
||||
|
||||
auto *current = _controller->current();
|
||||
ALOG_ASSERT(current != nullptr, "current buffer is nullptr ...");
|
||||
SLresult r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, current->buf, current->size);
|
||||
SL_RETURN_VAL_IF_FAILED(r, false, "enqueue failed!");
|
||||
}
|
||||
} else {
|
||||
SLresult r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, __silenceData.data(), __silenceData.size());
|
||||
SL_RETURN_VAL_IF_FAILED(r, false, "enqueue silent data failed!");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PcmAudioService::bqFetchBufferCallback(CCSLBufferQueueItf bq) {
|
||||
CC_UNUSED_PARAM(bq);
|
||||
// IDEA: PcmAudioService instance may be destroyed, we need to find a way to wait...
|
||||
// It's in sub thread
|
||||
enqueue();
|
||||
}
|
||||
|
||||
bool PcmAudioService::init(AudioMixerController *controller, int numChannels, int sampleRate, int bufferSizeInBytes) {
|
||||
_controller = controller;
|
||||
_numChannels = numChannels;
|
||||
_sampleRate = sampleRate;
|
||||
_bufferSizeInBytes = bufferSizeInBytes;
|
||||
|
||||
SLuint32 channelMask = SL_SPEAKER_FRONT_CENTER;
|
||||
|
||||
if (numChannels > 1) {
|
||||
channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
}
|
||||
|
||||
SLDataFormat_PCM formatPcm = {
|
||||
SL_DATAFORMAT_PCM,
|
||||
static_cast<SLuint32>(numChannels),
|
||||
static_cast<SLuint32>(sampleRate * 1000),
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
channelMask,
|
||||
SL_BYTEORDER_LITTLEENDIAN};
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
SLDataLocator_AndroidSimpleBufferQueue locBufQueue = {
|
||||
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
|
||||
AUDIO_PLAYER_BUFFER_COUNT};
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
SLDataLocator_BufferQueue locBufQueue = {SL_DATALOCATOR_BUFFERQUEUE, AUDIO_PLAYER_BUFFER_COUNT};
|
||||
#endif
|
||||
SLDataSource source = {&locBufQueue, &formatPcm};
|
||||
|
||||
SLDataLocator_OutputMix locOutmix = {
|
||||
SL_DATALOCATOR_OUTPUTMIX,
|
||||
_outputMixObj};
|
||||
SLDataSink sink = {&locOutmix, nullptr};
|
||||
|
||||
const SLInterfaceID ids[] = {
|
||||
SL_IID_PLAY,
|
||||
SL_IID_VOLUME,
|
||||
CC_SL_IDD_BUFFER_QUEUE,
|
||||
};
|
||||
|
||||
const SLboolean req[] = {
|
||||
SL_BOOLEAN_TRUE,
|
||||
SL_BOOLEAN_TRUE,
|
||||
SL_BOOLEAN_TRUE,
|
||||
};
|
||||
|
||||
SLresult r;
|
||||
|
||||
r = (*_engineItf)->CreateAudioPlayer(_engineItf, &_playObj, &source, &sink, sizeof(ids) / sizeof(ids[0]), ids, req);//NOLINT(bugprone-sizeof-expression)
|
||||
SL_RETURN_VAL_IF_FAILED(r, false, "CreateAudioPlayer failed");
|
||||
|
||||
r = (*_playObj)->Realize(_playObj, SL_BOOLEAN_FALSE);
|
||||
SL_RETURN_VAL_IF_FAILED(r, false, "Realize failed");
|
||||
|
||||
r = (*_playObj)->GetInterface(_playObj, SL_IID_PLAY, &_playItf);
|
||||
SL_RETURN_VAL_IF_FAILED(r, false, "GetInterface SL_IID_PLAY failed");
|
||||
|
||||
r = (*_playObj)->GetInterface(_playObj, SL_IID_VOLUME, &_volumeItf);
|
||||
SL_RETURN_VAL_IF_FAILED(r, false, "GetInterface SL_IID_VOLUME failed");
|
||||
|
||||
r = (*_playObj)->GetInterface(_playObj, CC_SL_IDD_BUFFER_QUEUE, &_bufferQueueItf);
|
||||
SL_RETURN_VAL_IF_FAILED(r, false, "GetInterface CC_SL_IDD_BUFFER_QUEUE failed");
|
||||
|
||||
r = (*_bufferQueueItf)->RegisterCallback(_bufferQueueItf, SLPcmAudioPlayerCallbackProxy::samplePlayerCallback, this);
|
||||
SL_RETURN_VAL_IF_FAILED(r, false, "_bufferQueueItf RegisterCallback failed");
|
||||
|
||||
if (__silenceData.empty()) {
|
||||
__silenceData.resize(_numChannels * _bufferSizeInBytes, 0x00);
|
||||
}
|
||||
#if CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
// We need to call this interface in openharmony, otherwise there will be noise
|
||||
SLuint8 *buffer = nullptr;
|
||||
SLuint32 size = 0;
|
||||
(*_bufferQueueItf)->GetBuffer(_bufferQueueItf, &buffer, &size);
|
||||
#endif
|
||||
r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, __silenceData.data(), __silenceData.size());
|
||||
SL_RETURN_VAL_IF_FAILED(r, false, "_bufferQueueItf Enqueue failed");
|
||||
|
||||
r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING);
|
||||
SL_RETURN_VAL_IF_FAILED(r, false, "SetPlayState failed");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PcmAudioService::pause() {
|
||||
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PAUSED);
|
||||
SL_RETURN_IF_FAILED(r, "PcmAudioService::pause failed");
|
||||
}
|
||||
|
||||
void PcmAudioService::resume() {
|
||||
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING);
|
||||
SL_RETURN_IF_FAILED(r, "PcmAudioService::resume failed");
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
78
cocos/audio/android/PcmAudioService.h
Normal file
78
cocos/audio/android/PcmAudioService.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio/android/IAudioPlayer.h"
|
||||
#include "audio/android/OpenSLHelper.h"
|
||||
#include "audio/android/PcmData.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include "audio/android/utils/Compat.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class AudioMixerController;
|
||||
|
||||
class PcmAudioService {
|
||||
public:
|
||||
inline int getChannelCount() const { return _numChannels; };
|
||||
|
||||
inline int getSampleRate() const { return _sampleRate; };
|
||||
|
||||
private:
|
||||
PcmAudioService(SLEngineItf engineItf, SLObjectItf outputMixObject);
|
||||
|
||||
virtual ~PcmAudioService();
|
||||
|
||||
bool init(AudioMixerController *controller, int numChannels, int sampleRate, int bufferSizeInBytes);
|
||||
|
||||
bool enqueue();
|
||||
|
||||
void bqFetchBufferCallback(CCSLBufferQueueItf bq);
|
||||
|
||||
void pause();
|
||||
void resume();
|
||||
|
||||
SLEngineItf _engineItf;
|
||||
SLObjectItf _outputMixObj;
|
||||
|
||||
SLObjectItf _playObj;
|
||||
SLPlayItf _playItf;
|
||||
SLVolumeItf _volumeItf;
|
||||
CCSLBufferQueueItf _bufferQueueItf;
|
||||
|
||||
int _numChannels;
|
||||
int _sampleRate;
|
||||
int _bufferSizeInBytes;
|
||||
|
||||
AudioMixerController *_controller;
|
||||
|
||||
friend class SLPcmAudioPlayerCallbackProxy;
|
||||
friend class AudioPlayerProvider;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
102
cocos/audio/android/PcmBufferProvider.cpp
Normal file
102
cocos/audio/android/PcmBufferProvider.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#define LOG_TAG "PcmBufferProvider"
|
||||
|
||||
#include "audio/android/PcmBufferProvider.h"
|
||||
#include "audio/android/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 cc {
|
||||
|
||||
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 cc
|
||||
51
cocos/audio/android/PcmBufferProvider.h
Normal file
51
cocos/audio/android/PcmBufferProvider.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio/android/AudioBufferProvider.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace cc {
|
||||
|
||||
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 cc
|
||||
128
cocos/audio/android/PcmData.cpp
Normal file
128
cocos/audio/android/PcmData.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#define LOG_TAG "PcmData"
|
||||
|
||||
#include "audio/android/PcmData.h"
|
||||
#include "audio/android/OpenSLHelper.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ccstd::string PcmData::toString() const {
|
||||
ccstd::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 cc
|
||||
65
cocos/audio/android/PcmData.h
Normal file
65
cocos/audio/android/PcmData.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <memory>
|
||||
#include "base/std/container/string.h"
|
||||
#include "base/std/container/vector.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
struct PcmData {
|
||||
std::shared_ptr<ccstd::vector<char>> pcmBuffer;
|
||||
int numChannels;
|
||||
int sampleRate;
|
||||
int bitsPerSample;
|
||||
int containerSize;
|
||||
int channelMask;
|
||||
int endianness;
|
||||
int numFrames;
|
||||
float duration; // in seconds
|
||||
|
||||
PcmData();
|
||||
|
||||
~PcmData();
|
||||
|
||||
PcmData(const PcmData &o);
|
||||
|
||||
PcmData(PcmData &&o);
|
||||
|
||||
PcmData &operator=(const PcmData &o);
|
||||
|
||||
PcmData &operator=(PcmData &&o);
|
||||
|
||||
void reset();
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
ccstd::string toString() const;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
86
cocos/audio/android/Track.cpp
Normal file
86
cocos/audio/android/Track.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#define LOG_TAG "Track"
|
||||
|
||||
#include "audio/android/Track.h"
|
||||
#include "audio/android/cutils/log.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
namespace cc {
|
||||
|
||||
Track::Track(const PcmData &pcmData)
|
||||
: onStateChanged(nullptr), _pcmData(pcmData), _prevState(State::IDLE), _state(State::IDLE), _name(-1), _volume(1.0f), _isVolumeDirty(true), _isLoop(false), _isInitialized(false), _isAudioFocus(true) {
|
||||
init(_pcmData.pcmBuffer->data(), _pcmData.numFrames, _pcmData.bitsPerSample / 8 * _pcmData.numChannels);
|
||||
}
|
||||
|
||||
Track::~Track() {
|
||||
ALOGV("~Track(): %p", this);
|
||||
}
|
||||
|
||||
gain_minifloat_packed_t Track::getVolumeLR() {
|
||||
float volume = _isAudioFocus ? _volume : 0.0f;
|
||||
gain_minifloat_t v = gain_from_float(volume);
|
||||
return gain_minifloat_pack(v, v);
|
||||
}
|
||||
|
||||
bool Track::setPosition(float pos) {
|
||||
_nextFrame = (size_t)(pos * _numFrames / _pcmData.duration);
|
||||
_unrel = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
float Track::getPosition() const {
|
||||
return _nextFrame * _pcmData.duration / _numFrames;
|
||||
}
|
||||
|
||||
void Track::setVolume(float volume) {
|
||||
std::lock_guard<std::mutex> lk(_volumeDirtyMutex);
|
||||
if (fabs(_volume - volume) > 0.00001) {
|
||||
_volume = volume;
|
||||
setVolumeDirty(true);
|
||||
}
|
||||
}
|
||||
|
||||
float Track::getVolume() const {
|
||||
return _volume;
|
||||
}
|
||||
|
||||
void Track::setAudioFocus(bool isFocus) {
|
||||
_isAudioFocus = isFocus;
|
||||
setVolumeDirty(true);
|
||||
}
|
||||
|
||||
void Track::setState(State state) {
|
||||
std::lock_guard<std::mutex> lk(_stateMutex);
|
||||
if (_state != state) {
|
||||
_prevState = _state;
|
||||
_state = state;
|
||||
onStateChanged(_state);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
101
cocos/audio/android/Track.h
Normal file
101
cocos/audio/android/Track.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio/android/IVolumeProvider.h"
|
||||
#include "audio/android/PcmBufferProvider.h"
|
||||
#include "audio/android/PcmData.h"
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
namespace cc {
|
||||
|
||||
class Track : public PcmBufferProvider, public IVolumeProvider {
|
||||
public:
|
||||
enum class State {
|
||||
IDLE,
|
||||
PLAYING,
|
||||
RESUMED,
|
||||
PAUSED,
|
||||
STOPPED,
|
||||
OVER,
|
||||
DESTROYED
|
||||
};
|
||||
|
||||
Track(const PcmData &pcmData);
|
||||
virtual ~Track();
|
||||
|
||||
inline State getState() const { return _state; };
|
||||
void setState(State state);
|
||||
|
||||
inline State getPrevState() const { return _prevState; };
|
||||
|
||||
inline bool isPlayOver() const { return _state == State::PLAYING && _nextFrame >= _numFrames; };
|
||||
inline void setName(int name) { _name = name; };
|
||||
inline int getName() const { return _name; };
|
||||
|
||||
void setVolume(float volume);
|
||||
float getVolume() const;
|
||||
|
||||
void setAudioFocus(bool isFocus);
|
||||
|
||||
bool setPosition(float pos);
|
||||
float getPosition() const;
|
||||
|
||||
virtual gain_minifloat_packed_t getVolumeLR() override;
|
||||
|
||||
inline void setLoop(bool isLoop) { _isLoop = isLoop; };
|
||||
inline bool isLoop() const { return _isLoop; };
|
||||
|
||||
std::function<void(State)> onStateChanged;
|
||||
|
||||
private:
|
||||
inline bool isVolumeDirty() const { return _isVolumeDirty; };
|
||||
|
||||
inline void setVolumeDirty(bool isDirty) { _isVolumeDirty = isDirty; };
|
||||
|
||||
inline bool isInitialized() const { return _isInitialized; };
|
||||
|
||||
inline void setInitialized(bool isInitialized) { _isInitialized = isInitialized; };
|
||||
|
||||
private:
|
||||
PcmData _pcmData;
|
||||
State _prevState;
|
||||
State _state;
|
||||
std::mutex _stateMutex;
|
||||
int _name;
|
||||
float _volume;
|
||||
bool _isVolumeDirty;
|
||||
std::mutex _volumeDirtyMutex;
|
||||
bool _isLoop;
|
||||
bool _isInitialized;
|
||||
bool _isAudioFocus;
|
||||
|
||||
friend class AudioMixerController;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
360
cocos/audio/android/UrlAudioPlayer.cpp
Normal file
360
cocos/audio/android/UrlAudioPlayer.cpp
Normal file
@@ -0,0 +1,360 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#define LOG_TAG "UrlAudioPlayer"
|
||||
|
||||
#include "audio/android/UrlAudioPlayer.h"
|
||||
#include "audio/android/ICallerThreadUtils.h"
|
||||
#include "base/std/container/vector.h"
|
||||
#include "base/Macros.h"
|
||||
#include <cmath>
|
||||
#include <algorithm> // for std::find
|
||||
|
||||
namespace {
|
||||
|
||||
std::mutex __playerContainerMutex;//NOLINT(bugprone-reserved-identifier,readability-identifier-naming)
|
||||
ccstd::vector<cc::UrlAudioPlayer *> __playerContainer;//NOLINT(bugprone-reserved-identifier,readability-identifier-naming)
|
||||
std::once_flag __onceFlag;//NOLINT(bugprone-reserved-identifier,readability-identifier-naming)
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace cc {
|
||||
|
||||
class SLUrlAudioPlayerCallbackProxy {
|
||||
public:
|
||||
static void playEventCallback(SLPlayItf caller, void *context, SLuint32 playEvent) {
|
||||
auto *thiz = reinterpret_cast<UrlAudioPlayer *>(context);
|
||||
// We must use a mutex for the whole block of the following function invocation.
|
||||
std::lock_guard<std::mutex> lk(__playerContainerMutex);
|
||||
auto iter = std::find(__playerContainer.begin(), __playerContainer.end(), thiz);
|
||||
if (iter != __playerContainer.end()) {
|
||||
thiz->playEventCallback(caller, playEvent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
UrlAudioPlayer::UrlAudioPlayer(SLEngineItf engineItf, SLObjectItf outputMixObject, ICallerThreadUtils *callerThreadUtils)
|
||||
: _engineItf(engineItf), _outputMixObj(outputMixObject), _callerThreadUtils(callerThreadUtils), _id(-1), _assetFd(nullptr), _playObj(nullptr), _playItf(nullptr), _seekItf(nullptr), _volumeItf(nullptr), _volume(0.0F), _duration(0.0F), _isLoop(false), _isAudioFocus(true), _state(State::INVALID), _playEventCallback(nullptr), _isDestroyed(std::make_shared<bool>(false)) {
|
||||
std::call_once(__onceFlag, []() {
|
||||
__playerContainer.reserve(10);
|
||||
});
|
||||
|
||||
__playerContainerMutex.lock();
|
||||
__playerContainer.push_back(this);
|
||||
ALOGV("Current UrlAudioPlayer instance count: %d", (int)__playerContainer.size());
|
||||
__playerContainerMutex.unlock();
|
||||
|
||||
_callerThreadId = callerThreadUtils->getCallerThreadId();
|
||||
}
|
||||
|
||||
UrlAudioPlayer::~UrlAudioPlayer() {
|
||||
ALOGV("~UrlAudioPlayer(): %p", this);
|
||||
|
||||
__playerContainerMutex.lock();
|
||||
|
||||
auto iter = std::find(__playerContainer.begin(), __playerContainer.end(), this);
|
||||
if (iter != __playerContainer.end()) {
|
||||
__playerContainer.erase(iter);
|
||||
}
|
||||
|
||||
__playerContainerMutex.unlock();
|
||||
}
|
||||
|
||||
void UrlAudioPlayer::playEventCallback(SLPlayItf caller, SLuint32 playEvent) {
|
||||
CC_UNUSED_PARAM(caller);
|
||||
// Note that it's on sub thread, please don't invoke OpenSLES API on sub thread
|
||||
if (playEvent == SL_PLAYEVENT_HEADATEND) {
|
||||
std::shared_ptr<bool> isDestroyed = _isDestroyed;
|
||||
|
||||
auto func = [this, isDestroyed]() {
|
||||
// If it was destroyed, just return.
|
||||
if (*isDestroyed) {
|
||||
ALOGV("The UrlAudioPlayer (%p) was destroyed!", this);
|
||||
return;
|
||||
}
|
||||
|
||||
//Note that It's in the caller's thread (Cocos Thread)
|
||||
// If state is already stopped, ignore the play over event.
|
||||
|
||||
if (_state == State::STOPPED) {
|
||||
return;
|
||||
}
|
||||
|
||||
//fix issue#8965:AudioEngine can't looping audio on Android 2.3.x
|
||||
if (isLoop()) {
|
||||
play();
|
||||
} else {
|
||||
setState(State::OVER);
|
||||
if (_playEventCallback != nullptr) {
|
||||
_playEventCallback(State::OVER);
|
||||
}
|
||||
|
||||
ALOGV("UrlAudioPlayer (%p) played over, destroy self ...", this);
|
||||
destroy();
|
||||
delete this;
|
||||
}
|
||||
};
|
||||
|
||||
if (_callerThreadId == std::this_thread::get_id()) {
|
||||
func();
|
||||
} else {
|
||||
_callerThreadUtils->performFunctionInCallerThread(func);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UrlAudioPlayer::setPlayEventCallback(const PlayEventCallback &playEventCallback) {
|
||||
_playEventCallback = playEventCallback;
|
||||
}
|
||||
|
||||
void UrlAudioPlayer::stop() {
|
||||
ALOGV("UrlAudioPlayer::stop (%p, %d)", this, getId());
|
||||
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_STOPPED);
|
||||
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::stop failed");
|
||||
|
||||
if (_state == State::PLAYING || _state == State::PAUSED) {
|
||||
setLoop(false);
|
||||
setState(State::STOPPED);
|
||||
|
||||
if (_playEventCallback != nullptr) {
|
||||
_playEventCallback(State::STOPPED);
|
||||
}
|
||||
|
||||
destroy();
|
||||
delete this;
|
||||
} else {
|
||||
ALOGW("UrlAudioPlayer (%p, state:%d) isn't playing or paused, could not invoke stop!", this, static_cast<int>(_state));
|
||||
}
|
||||
}
|
||||
|
||||
void UrlAudioPlayer::pause() {
|
||||
if (_state == State::PLAYING) {
|
||||
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PAUSED);
|
||||
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::pause failed");
|
||||
setState(State::PAUSED);
|
||||
} else {
|
||||
ALOGW("UrlAudioPlayer (%p, state:%d) isn't playing, could not invoke pause!", this, static_cast<int>(_state));
|
||||
}
|
||||
}
|
||||
|
||||
void UrlAudioPlayer::resume() {
|
||||
if (_state == State::PAUSED) {
|
||||
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING);
|
||||
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::resume failed");
|
||||
setState(State::PLAYING);
|
||||
} else {
|
||||
ALOGW("UrlAudioPlayer (%p, state:%d) isn't paused, could not invoke resume!", this, static_cast<int>(_state));
|
||||
}
|
||||
}
|
||||
|
||||
void UrlAudioPlayer::play() {
|
||||
if (_state == State::INITIALIZED || _state == State::PAUSED) {
|
||||
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING);
|
||||
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::play failed");
|
||||
setState(State::PLAYING);
|
||||
} else {
|
||||
ALOGW("UrlAudioPlayer (%p, state:%d) isn't paused or initialized, could not invoke play!", this, static_cast<int>(_state));
|
||||
}
|
||||
}
|
||||
|
||||
void UrlAudioPlayer::setVolumeToSLPlayer(float volume) {
|
||||
int dbVolume = static_cast<int>(2000 * log10(volume));
|
||||
if (dbVolume < SL_MILLIBEL_MIN) {
|
||||
dbVolume = SL_MILLIBEL_MIN;
|
||||
}
|
||||
SLresult r = (*_volumeItf)->SetVolumeLevel(_volumeItf, dbVolume);
|
||||
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::setVolumeToSLPlayer %d failed", dbVolume);
|
||||
}
|
||||
|
||||
void UrlAudioPlayer::setVolume(float volume) {
|
||||
_volume = volume;
|
||||
if (_isAudioFocus) {
|
||||
setVolumeToSLPlayer(_volume);
|
||||
}
|
||||
}
|
||||
|
||||
float UrlAudioPlayer::getVolume() const {
|
||||
return _volume;
|
||||
}
|
||||
|
||||
void UrlAudioPlayer::setAudioFocus(bool isFocus) {
|
||||
_isAudioFocus = isFocus;
|
||||
float volume = _isAudioFocus ? _volume : 0.0F;
|
||||
setVolumeToSLPlayer(volume);
|
||||
}
|
||||
|
||||
float UrlAudioPlayer::getDuration() const {
|
||||
if (_duration > 0) {
|
||||
return _duration;
|
||||
}
|
||||
|
||||
SLmillisecond duration;
|
||||
SLresult r = (*_playItf)->GetDuration(_playItf, &duration);
|
||||
SL_RETURN_VAL_IF_FAILED(r, 0.0F, "UrlAudioPlayer::getDuration failed");
|
||||
|
||||
if (duration == SL_TIME_UNKNOWN) {
|
||||
return -1.0F;
|
||||
} else {// NOLINT(readability-else-after-return)
|
||||
const_cast<UrlAudioPlayer *>(this)->_duration = duration / 1000.0F;
|
||||
|
||||
if (_duration <= 0) {
|
||||
return -1.0F;
|
||||
}
|
||||
}
|
||||
return _duration;
|
||||
}
|
||||
|
||||
float UrlAudioPlayer::getPosition() const {
|
||||
SLmillisecond millisecond;
|
||||
SLresult r = (*_playItf)->GetPosition(_playItf, &millisecond);
|
||||
SL_RETURN_VAL_IF_FAILED(r, 0.0F, "UrlAudioPlayer::getPosition failed");
|
||||
return millisecond / 1000.0F;
|
||||
}
|
||||
|
||||
bool UrlAudioPlayer::setPosition(float pos) {
|
||||
SLmillisecond millisecond = 1000.0F * pos;
|
||||
SLresult r = (*_seekItf)->SetPosition(_seekItf, millisecond, SL_SEEKMODE_ACCURATE);
|
||||
SL_RETURN_VAL_IF_FAILED(r, false, "UrlAudioPlayer::setPosition %f failed", pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UrlAudioPlayer::prepare(const ccstd::string &url, SLuint32 locatorType, std::shared_ptr<AssetFd> assetFd, int start,
|
||||
int length) {
|
||||
_url = url;
|
||||
_assetFd = std::move(assetFd);
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
const char *locatorTypeStr = "UNKNOWN";
|
||||
if (locatorType == SL_DATALOCATOR_ANDROIDFD) {
|
||||
locatorTypeStr = "SL_DATALOCATOR_ANDROIDFD";
|
||||
} else if (locatorType == SL_DATALOCATOR_URI) {
|
||||
locatorTypeStr = "SL_DATALOCATOR_URI";
|
||||
} else {
|
||||
ALOGE("Oops, invalid locatorType: %d", (int)locatorType);
|
||||
return false;
|
||||
}
|
||||
|
||||
ALOGV("UrlAudioPlayer::prepare: %s, %s, %d, %d, %d", _url.c_str(), locatorTypeStr, _assetFd->getFd(), start,
|
||||
length);
|
||||
SLDataSource audioSrc;
|
||||
|
||||
SLDataFormat_MIME formatMime = {SL_DATAFORMAT_MIME, nullptr, SL_CONTAINERTYPE_UNSPECIFIED};
|
||||
audioSrc.pFormat = &formatMime;
|
||||
|
||||
//Note: locFd & locUri should be outside of the following if/else block
|
||||
// Although locFd & locUri are only used inside if/else block, its lifecycle
|
||||
// will be destroyed right after '}' block. And since we pass a pointer to
|
||||
// 'audioSrc.pLocator=&locFd/&locUri', pLocator will point to an invalid address
|
||||
// while invoking Engine::createAudioPlayer interface. So be care of change the position
|
||||
// of these two variables.
|
||||
SLDataLocator_AndroidFD locFd;
|
||||
SLDataLocator_URI locUri;
|
||||
|
||||
if (locatorType == SL_DATALOCATOR_ANDROIDFD) {
|
||||
locFd = {locatorType, _assetFd->getFd(), start, length};
|
||||
audioSrc.pLocator = &locFd;
|
||||
} else if (locatorType == SL_DATALOCATOR_URI) {
|
||||
locUri = {locatorType, (SLchar *)_url.c_str()}; // NOLINT(google-readability-casting)
|
||||
audioSrc.pLocator = &locUri;
|
||||
ALOGV("locUri: locatorType: %d", (int)locUri.locatorType);
|
||||
}
|
||||
|
||||
// configure audio sink
|
||||
SLDataLocator_OutputMix locOutmix = {SL_DATALOCATOR_OUTPUTMIX, _outputMixObj};
|
||||
SLDataSink audioSnk = {&locOutmix, nullptr};
|
||||
|
||||
// create audio player
|
||||
const SLInterfaceID ids[3] = {SL_IID_SEEK, SL_IID_PREFETCHSTATUS, SL_IID_VOLUME};
|
||||
const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
|
||||
|
||||
SLresult result = (*_engineItf)->CreateAudioPlayer(_engineItf, &_playObj, &audioSrc, &audioSnk, 3, ids, req);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "CreateAudioPlayer failed");
|
||||
|
||||
// realize the player
|
||||
result = (*_playObj)->Realize(_playObj, SL_BOOLEAN_FALSE);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "Realize failed");
|
||||
|
||||
// get the play interface
|
||||
result = (*_playObj)->GetInterface(_playObj, SL_IID_PLAY, &_playItf);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_PLAY failed");
|
||||
|
||||
// get the seek interface
|
||||
result = (*_playObj)->GetInterface(_playObj, SL_IID_SEEK, &_seekItf);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_SEEK failed");
|
||||
|
||||
// get the volume interface
|
||||
result = (*_playObj)->GetInterface(_playObj, SL_IID_VOLUME, &_volumeItf);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_VOLUME failed");
|
||||
|
||||
result = (*_playItf)->RegisterCallback(_playItf,
|
||||
SLUrlAudioPlayerCallbackProxy::playEventCallback, this);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "RegisterCallback failed");
|
||||
|
||||
result = (*_playItf)->SetCallbackEventsMask(_playItf, SL_PLAYEVENT_HEADATEND);
|
||||
SL_RETURN_VAL_IF_FAILED(result, false, "SetCallbackEventsMask SL_PLAYEVENT_HEADATEND failed");
|
||||
|
||||
setState(State::INITIALIZED);
|
||||
|
||||
setVolume(1.0F);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void UrlAudioPlayer::rewind() {
|
||||
// Not supported currently. since cocos audio engine will new -> prepare -> play again.
|
||||
}
|
||||
|
||||
void UrlAudioPlayer::setLoop(bool isLoop) {
|
||||
_isLoop = isLoop;
|
||||
|
||||
SLboolean loopEnable = _isLoop ? SL_BOOLEAN_TRUE : SL_BOOLEAN_FALSE;
|
||||
SLresult r = (*_seekItf)->SetLoop(_seekItf, loopEnable, 0, SL_TIME_UNKNOWN);
|
||||
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::setLoop %d failed", _isLoop ? 1 : 0);
|
||||
}
|
||||
|
||||
bool UrlAudioPlayer::isLoop() const {
|
||||
return _isLoop;
|
||||
}
|
||||
|
||||
void UrlAudioPlayer::stopAll() {
|
||||
// To avoid break the for loop, we need to copy a new map
|
||||
__playerContainerMutex.lock();
|
||||
auto temp = __playerContainer;
|
||||
__playerContainerMutex.unlock();
|
||||
|
||||
for (auto &&player : temp) {
|
||||
player->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void UrlAudioPlayer::destroy() {
|
||||
if (!*_isDestroyed) {
|
||||
*_isDestroyed = true;
|
||||
ALOGV("UrlAudioPlayer::destroy() %p", this);
|
||||
SL_DESTROY_OBJ(_playObj);
|
||||
ALOGV("UrlAudioPlayer::destroy end");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
127
cocos/audio/android/UrlAudioPlayer.h
Normal file
127
cocos/audio/android/UrlAudioPlayer.h
Normal file
@@ -0,0 +1,127 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 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 <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include "audio/android/AssetFd.h"
|
||||
#include "audio/android/IAudioPlayer.h"
|
||||
#include "audio/android/OpenSLHelper.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
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 ccstd::string getUrl() const override { return _url; };
|
||||
|
||||
virtual State getState() const override { return _state; };
|
||||
|
||||
virtual void play() override;
|
||||
|
||||
virtual void pause() override;
|
||||
|
||||
virtual void resume() override;
|
||||
|
||||
virtual void stop() override;
|
||||
|
||||
virtual void rewind() override;
|
||||
|
||||
virtual void setVolume(float volume) override;
|
||||
|
||||
virtual float getVolume() const override;
|
||||
|
||||
virtual void setAudioFocus(bool isFocus) override;
|
||||
|
||||
virtual void setLoop(bool isLoop) override;
|
||||
|
||||
virtual bool isLoop() const override;
|
||||
|
||||
virtual float getDuration() const override;
|
||||
|
||||
virtual float getPosition() const override;
|
||||
|
||||
virtual bool setPosition(float pos) override;
|
||||
|
||||
virtual void setPlayEventCallback(const PlayEventCallback &playEventCallback) override;
|
||||
|
||||
// Override Functions EndOv
|
||||
|
||||
private:
|
||||
UrlAudioPlayer(SLEngineItf engineItf, SLObjectItf outputMixObject, ICallerThreadUtils *callerThreadUtils);
|
||||
virtual ~UrlAudioPlayer();
|
||||
|
||||
bool prepare(const ccstd::string &url, SLuint32 locatorType, std::shared_ptr<AssetFd> assetFd, int start, int length);
|
||||
|
||||
static void stopAll();
|
||||
|
||||
void destroy();
|
||||
|
||||
inline void setState(State state) { _state = state; };
|
||||
|
||||
void playEventCallback(SLPlayItf caller, SLuint32 playEvent);
|
||||
|
||||
void setVolumeToSLPlayer(float volume);
|
||||
|
||||
private:
|
||||
SLEngineItf _engineItf;
|
||||
SLObjectItf _outputMixObj;
|
||||
ICallerThreadUtils *_callerThreadUtils;
|
||||
|
||||
int _id;
|
||||
ccstd::string _url;
|
||||
|
||||
std::shared_ptr<AssetFd> _assetFd;
|
||||
|
||||
SLObjectItf _playObj;
|
||||
SLPlayItf _playItf;
|
||||
SLSeekItf _seekItf;
|
||||
SLVolumeItf _volumeItf;
|
||||
|
||||
float _volume;
|
||||
float _duration;
|
||||
bool _isLoop;
|
||||
bool _isAudioFocus;
|
||||
State _state;
|
||||
|
||||
PlayEventCallback _playEventCallback;
|
||||
|
||||
std::thread::id _callerThreadId;
|
||||
std::shared_ptr<bool> _isDestroyed;
|
||||
|
||||
friend class SLUrlAudioPlayerCallbackProxy;
|
||||
friend class AudioPlayerProvider;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
491
cocos/audio/android/audio.h
Normal file
491
cocos/audio/android/audio.h
Normal file
@@ -0,0 +1,491 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#include <stdint.h>
|
||||
#include "audio/android/cutils/bitops.h"
|
||||
|
||||
#define PROPERTY_VALUE_MAX 256
|
||||
#define CONSTEXPR constexpr
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define CC_LIKELY(exp) (__builtin_expect(!!(exp), true))
|
||||
#define CC_UNLIKELY(exp) (__builtin_expect(!!(exp), false))
|
||||
#else
|
||||
#define CC_LIKELY(exp) (__builtin_expect(!!(exp), 1))
|
||||
#define CC_UNLIKELY(exp) (__builtin_expect(!!(exp), 0))
|
||||
#endif
|
||||
|
||||
/* special audio session values
|
||||
* (XXX: should this be living in the audio effects land?)
|
||||
*/
|
||||
typedef enum {
|
||||
/* session for effects attached to a particular output stream
|
||||
* (value must be less than 0)
|
||||
*/
|
||||
AUDIO_SESSION_OUTPUT_STAGE = -1,
|
||||
|
||||
/* session for effects applied to output mix. These effects can
|
||||
* be moved by audio policy manager to another output stream
|
||||
* (value must be 0)
|
||||
*/
|
||||
AUDIO_SESSION_OUTPUT_MIX = 0,
|
||||
|
||||
/* application does not specify an explicit session ID to be used,
|
||||
* and requests a new session ID to be allocated
|
||||
* REFINE: use unique values for AUDIO_SESSION_OUTPUT_MIX and AUDIO_SESSION_ALLOCATE,
|
||||
* after all uses have been updated from 0 to the appropriate symbol, and have been tested.
|
||||
*/
|
||||
AUDIO_SESSION_ALLOCATE = 0,
|
||||
} audio_session_t;
|
||||
|
||||
/* Audio sub formats (see enum audio_format). */
|
||||
|
||||
/* PCM sub formats */
|
||||
typedef enum {
|
||||
/* All of these are in native byte order */
|
||||
AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1, /* DO NOT CHANGE - PCM signed 16 bits */
|
||||
AUDIO_FORMAT_PCM_SUB_8_BIT = 0x2, /* DO NOT CHANGE - PCM unsigned 8 bits */
|
||||
AUDIO_FORMAT_PCM_SUB_32_BIT = 0x3, /* PCM signed .31 fixed point */
|
||||
AUDIO_FORMAT_PCM_SUB_8_24_BIT = 0x4, /* PCM signed 8.23 fixed point */
|
||||
AUDIO_FORMAT_PCM_SUB_FLOAT = 0x5, /* PCM single-precision floating point */
|
||||
AUDIO_FORMAT_PCM_SUB_24_BIT_PACKED = 0x6, /* PCM signed .23 fixed point packed in 3 bytes */
|
||||
} audio_format_pcm_sub_fmt_t;
|
||||
|
||||
/* The audio_format_*_sub_fmt_t declarations are not currently used */
|
||||
|
||||
/* MP3 sub format field definition : can use 11 LSBs in the same way as MP3
|
||||
* frame header to specify bit rate, stereo mode, version...
|
||||
*/
|
||||
typedef enum {
|
||||
AUDIO_FORMAT_MP3_SUB_NONE = 0x0,
|
||||
} audio_format_mp3_sub_fmt_t;
|
||||
|
||||
/* AMR NB/WB sub format field definition: specify frame block interleaving,
|
||||
* bandwidth efficient or octet aligned, encoding mode for recording...
|
||||
*/
|
||||
typedef enum {
|
||||
AUDIO_FORMAT_AMR_SUB_NONE = 0x0,
|
||||
} audio_format_amr_sub_fmt_t;
|
||||
|
||||
/* AAC sub format field definition: specify profile or bitrate for recording... */
|
||||
typedef enum {
|
||||
AUDIO_FORMAT_AAC_SUB_MAIN = 0x1,
|
||||
AUDIO_FORMAT_AAC_SUB_LC = 0x2,
|
||||
AUDIO_FORMAT_AAC_SUB_SSR = 0x4,
|
||||
AUDIO_FORMAT_AAC_SUB_LTP = 0x8,
|
||||
AUDIO_FORMAT_AAC_SUB_HE_V1 = 0x10,
|
||||
AUDIO_FORMAT_AAC_SUB_SCALABLE = 0x20,
|
||||
AUDIO_FORMAT_AAC_SUB_ERLC = 0x40,
|
||||
AUDIO_FORMAT_AAC_SUB_LD = 0x80,
|
||||
AUDIO_FORMAT_AAC_SUB_HE_V2 = 0x100,
|
||||
AUDIO_FORMAT_AAC_SUB_ELD = 0x200,
|
||||
} audio_format_aac_sub_fmt_t;
|
||||
|
||||
/* VORBIS sub format field definition: specify quality for recording... */
|
||||
typedef enum {
|
||||
AUDIO_FORMAT_VORBIS_SUB_NONE = 0x0,
|
||||
} audio_format_vorbis_sub_fmt_t;
|
||||
|
||||
/* Audio format consists of a main format field (upper 8 bits) and a sub format
|
||||
* field (lower 24 bits).
|
||||
*
|
||||
* The main format indicates the main codec type. The sub format field
|
||||
* indicates options and parameters for each format. The sub format is mainly
|
||||
* used for record to indicate for instance the requested bitrate or profile.
|
||||
* It can also be used for certain formats to give informations not present in
|
||||
* the encoded audio stream (e.g. octet alignment for AMR).
|
||||
*/
|
||||
typedef enum {
|
||||
AUDIO_FORMAT_INVALID = 0xFFFFFFFFUL,
|
||||
AUDIO_FORMAT_DEFAULT = 0,
|
||||
AUDIO_FORMAT_PCM = 0x00000000UL, /* DO NOT CHANGE */
|
||||
AUDIO_FORMAT_MP3 = 0x01000000UL,
|
||||
AUDIO_FORMAT_AMR_NB = 0x02000000UL,
|
||||
AUDIO_FORMAT_AMR_WB = 0x03000000UL,
|
||||
AUDIO_FORMAT_AAC = 0x04000000UL,
|
||||
AUDIO_FORMAT_HE_AAC_V1 = 0x05000000UL, /* Deprecated, Use AUDIO_FORMAT_AAC_HE_V1*/
|
||||
AUDIO_FORMAT_HE_AAC_V2 = 0x06000000UL, /* Deprecated, Use AUDIO_FORMAT_AAC_HE_V2*/
|
||||
AUDIO_FORMAT_VORBIS = 0x07000000UL,
|
||||
AUDIO_FORMAT_OPUS = 0x08000000UL,
|
||||
AUDIO_FORMAT_AC3 = 0x09000000UL,
|
||||
AUDIO_FORMAT_E_AC3 = 0x0A000000UL,
|
||||
AUDIO_FORMAT_DTS = 0x0B000000UL,
|
||||
AUDIO_FORMAT_DTS_HD = 0x0C000000UL,
|
||||
AUDIO_FORMAT_MAIN_MASK = 0xFF000000UL,
|
||||
AUDIO_FORMAT_SUB_MASK = 0x00FFFFFFUL,
|
||||
|
||||
/* Aliases */
|
||||
/* note != AudioFormat.ENCODING_PCM_16BIT */
|
||||
AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM |
|
||||
AUDIO_FORMAT_PCM_SUB_16_BIT),
|
||||
/* note != AudioFormat.ENCODING_PCM_8BIT */
|
||||
AUDIO_FORMAT_PCM_8_BIT = (AUDIO_FORMAT_PCM |
|
||||
AUDIO_FORMAT_PCM_SUB_8_BIT),
|
||||
AUDIO_FORMAT_PCM_32_BIT = (AUDIO_FORMAT_PCM |
|
||||
AUDIO_FORMAT_PCM_SUB_32_BIT),
|
||||
AUDIO_FORMAT_PCM_8_24_BIT = (AUDIO_FORMAT_PCM |
|
||||
AUDIO_FORMAT_PCM_SUB_8_24_BIT),
|
||||
AUDIO_FORMAT_PCM_FLOAT = (AUDIO_FORMAT_PCM |
|
||||
AUDIO_FORMAT_PCM_SUB_FLOAT),
|
||||
AUDIO_FORMAT_PCM_24_BIT_PACKED = (AUDIO_FORMAT_PCM |
|
||||
AUDIO_FORMAT_PCM_SUB_24_BIT_PACKED),
|
||||
AUDIO_FORMAT_AAC_MAIN = (AUDIO_FORMAT_AAC |
|
||||
AUDIO_FORMAT_AAC_SUB_MAIN),
|
||||
AUDIO_FORMAT_AAC_LC = (AUDIO_FORMAT_AAC |
|
||||
AUDIO_FORMAT_AAC_SUB_LC),
|
||||
AUDIO_FORMAT_AAC_SSR = (AUDIO_FORMAT_AAC |
|
||||
AUDIO_FORMAT_AAC_SUB_SSR),
|
||||
AUDIO_FORMAT_AAC_LTP = (AUDIO_FORMAT_AAC |
|
||||
AUDIO_FORMAT_AAC_SUB_LTP),
|
||||
AUDIO_FORMAT_AAC_HE_V1 = (AUDIO_FORMAT_AAC |
|
||||
AUDIO_FORMAT_AAC_SUB_HE_V1),
|
||||
AUDIO_FORMAT_AAC_SCALABLE = (AUDIO_FORMAT_AAC |
|
||||
AUDIO_FORMAT_AAC_SUB_SCALABLE),
|
||||
AUDIO_FORMAT_AAC_ERLC = (AUDIO_FORMAT_AAC |
|
||||
AUDIO_FORMAT_AAC_SUB_ERLC),
|
||||
AUDIO_FORMAT_AAC_LD = (AUDIO_FORMAT_AAC |
|
||||
AUDIO_FORMAT_AAC_SUB_LD),
|
||||
AUDIO_FORMAT_AAC_HE_V2 = (AUDIO_FORMAT_AAC |
|
||||
AUDIO_FORMAT_AAC_SUB_HE_V2),
|
||||
AUDIO_FORMAT_AAC_ELD = (AUDIO_FORMAT_AAC |
|
||||
AUDIO_FORMAT_AAC_SUB_ELD),
|
||||
} audio_format_t;
|
||||
|
||||
/* For the channel mask for position assignment representation */
|
||||
enum {
|
||||
/* These can be a complete audio_channel_mask_t. */
|
||||
AUDIO_CHANNEL_NONE = 0x0,
|
||||
AUDIO_CHANNEL_INVALID = 0xC0000000,
|
||||
/* These can be the bits portion of an audio_channel_mask_t
|
||||
* with representation AUDIO_CHANNEL_REPRESENTATION_POSITION.
|
||||
* Using these bits as a complete audio_channel_mask_t is deprecated.
|
||||
*/
|
||||
/* output channels */
|
||||
AUDIO_CHANNEL_OUT_FRONT_LEFT = 0x1,
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT = 0x2,
|
||||
AUDIO_CHANNEL_OUT_FRONT_CENTER = 0x4,
|
||||
AUDIO_CHANNEL_OUT_LOW_FREQUENCY = 0x8,
|
||||
AUDIO_CHANNEL_OUT_BACK_LEFT = 0x10,
|
||||
AUDIO_CHANNEL_OUT_BACK_RIGHT = 0x20,
|
||||
AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x40,
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x80,
|
||||
AUDIO_CHANNEL_OUT_BACK_CENTER = 0x100,
|
||||
AUDIO_CHANNEL_OUT_SIDE_LEFT = 0x200,
|
||||
AUDIO_CHANNEL_OUT_SIDE_RIGHT = 0x400,
|
||||
AUDIO_CHANNEL_OUT_TOP_CENTER = 0x800,
|
||||
AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT = 0x1000,
|
||||
AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER = 0x2000,
|
||||
AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT = 0x4000,
|
||||
AUDIO_CHANNEL_OUT_TOP_BACK_LEFT = 0x8000,
|
||||
AUDIO_CHANNEL_OUT_TOP_BACK_CENTER = 0x10000,
|
||||
AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT = 0x20000,
|
||||
/* REFINE: should these be considered complete channel masks, or only bits? */
|
||||
AUDIO_CHANNEL_OUT_MONO = AUDIO_CHANNEL_OUT_FRONT_LEFT,
|
||||
AUDIO_CHANNEL_OUT_STEREO = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT),
|
||||
AUDIO_CHANNEL_OUT_QUAD = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
|
||||
AUDIO_CHANNEL_OUT_BACK_LEFT |
|
||||
AUDIO_CHANNEL_OUT_BACK_RIGHT),
|
||||
AUDIO_CHANNEL_OUT_QUAD_BACK = AUDIO_CHANNEL_OUT_QUAD,
|
||||
/* like AUDIO_CHANNEL_OUT_QUAD_BACK with *_SIDE_* instead of *_BACK_* */
|
||||
AUDIO_CHANNEL_OUT_QUAD_SIDE = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
|
||||
AUDIO_CHANNEL_OUT_SIDE_LEFT |
|
||||
AUDIO_CHANNEL_OUT_SIDE_RIGHT),
|
||||
AUDIO_CHANNEL_OUT_5POINT1 = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
|
||||
AUDIO_CHANNEL_OUT_FRONT_CENTER |
|
||||
AUDIO_CHANNEL_OUT_LOW_FREQUENCY |
|
||||
AUDIO_CHANNEL_OUT_BACK_LEFT |
|
||||
AUDIO_CHANNEL_OUT_BACK_RIGHT),
|
||||
AUDIO_CHANNEL_OUT_5POINT1_BACK = AUDIO_CHANNEL_OUT_5POINT1,
|
||||
/* like AUDIO_CHANNEL_OUT_5POINT1_BACK with *_SIDE_* instead of *_BACK_* */
|
||||
AUDIO_CHANNEL_OUT_5POINT1_SIDE = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
|
||||
AUDIO_CHANNEL_OUT_FRONT_CENTER |
|
||||
AUDIO_CHANNEL_OUT_LOW_FREQUENCY |
|
||||
AUDIO_CHANNEL_OUT_SIDE_LEFT |
|
||||
AUDIO_CHANNEL_OUT_SIDE_RIGHT),
|
||||
// matches the correct AudioFormat.CHANNEL_OUT_7POINT1_SURROUND definition for 7.1
|
||||
AUDIO_CHANNEL_OUT_7POINT1 = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
|
||||
AUDIO_CHANNEL_OUT_FRONT_CENTER |
|
||||
AUDIO_CHANNEL_OUT_LOW_FREQUENCY |
|
||||
AUDIO_CHANNEL_OUT_BACK_LEFT |
|
||||
AUDIO_CHANNEL_OUT_BACK_RIGHT |
|
||||
AUDIO_CHANNEL_OUT_SIDE_LEFT |
|
||||
AUDIO_CHANNEL_OUT_SIDE_RIGHT),
|
||||
AUDIO_CHANNEL_OUT_ALL = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
|
||||
AUDIO_CHANNEL_OUT_FRONT_CENTER |
|
||||
AUDIO_CHANNEL_OUT_LOW_FREQUENCY |
|
||||
AUDIO_CHANNEL_OUT_BACK_LEFT |
|
||||
AUDIO_CHANNEL_OUT_BACK_RIGHT |
|
||||
AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER |
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER |
|
||||
AUDIO_CHANNEL_OUT_BACK_CENTER |
|
||||
AUDIO_CHANNEL_OUT_SIDE_LEFT |
|
||||
AUDIO_CHANNEL_OUT_SIDE_RIGHT |
|
||||
AUDIO_CHANNEL_OUT_TOP_CENTER |
|
||||
AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT |
|
||||
AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER |
|
||||
AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT |
|
||||
AUDIO_CHANNEL_OUT_TOP_BACK_LEFT |
|
||||
AUDIO_CHANNEL_OUT_TOP_BACK_CENTER |
|
||||
AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT),
|
||||
/* These are bits only, not complete values */
|
||||
/* input channels */
|
||||
AUDIO_CHANNEL_IN_LEFT = 0x4,
|
||||
AUDIO_CHANNEL_IN_RIGHT = 0x8,
|
||||
AUDIO_CHANNEL_IN_FRONT = 0x10,
|
||||
AUDIO_CHANNEL_IN_BACK = 0x20,
|
||||
AUDIO_CHANNEL_IN_LEFT_PROCESSED = 0x40,
|
||||
AUDIO_CHANNEL_IN_RIGHT_PROCESSED = 0x80,
|
||||
AUDIO_CHANNEL_IN_FRONT_PROCESSED = 0x100,
|
||||
AUDIO_CHANNEL_IN_BACK_PROCESSED = 0x200,
|
||||
AUDIO_CHANNEL_IN_PRESSURE = 0x400,
|
||||
AUDIO_CHANNEL_IN_X_AXIS = 0x800,
|
||||
AUDIO_CHANNEL_IN_Y_AXIS = 0x1000,
|
||||
AUDIO_CHANNEL_IN_Z_AXIS = 0x2000,
|
||||
AUDIO_CHANNEL_IN_VOICE_UPLINK = 0x4000,
|
||||
AUDIO_CHANNEL_IN_VOICE_DNLINK = 0x8000,
|
||||
/* REFINE: should these be considered complete channel masks, or only bits, or deprecated? */
|
||||
AUDIO_CHANNEL_IN_MONO = AUDIO_CHANNEL_IN_FRONT,
|
||||
AUDIO_CHANNEL_IN_STEREO = (AUDIO_CHANNEL_IN_LEFT | AUDIO_CHANNEL_IN_RIGHT),
|
||||
AUDIO_CHANNEL_IN_FRONT_BACK = (AUDIO_CHANNEL_IN_FRONT | AUDIO_CHANNEL_IN_BACK),
|
||||
AUDIO_CHANNEL_IN_ALL = (AUDIO_CHANNEL_IN_LEFT |
|
||||
AUDIO_CHANNEL_IN_RIGHT |
|
||||
AUDIO_CHANNEL_IN_FRONT |
|
||||
AUDIO_CHANNEL_IN_BACK |
|
||||
AUDIO_CHANNEL_IN_LEFT_PROCESSED |
|
||||
AUDIO_CHANNEL_IN_RIGHT_PROCESSED |
|
||||
AUDIO_CHANNEL_IN_FRONT_PROCESSED |
|
||||
AUDIO_CHANNEL_IN_BACK_PROCESSED |
|
||||
AUDIO_CHANNEL_IN_PRESSURE |
|
||||
AUDIO_CHANNEL_IN_X_AXIS |
|
||||
AUDIO_CHANNEL_IN_Y_AXIS |
|
||||
AUDIO_CHANNEL_IN_Z_AXIS |
|
||||
AUDIO_CHANNEL_IN_VOICE_UPLINK |
|
||||
AUDIO_CHANNEL_IN_VOICE_DNLINK),
|
||||
};
|
||||
/* A channel mask per se only defines the presence or absence of a channel, not the order.
|
||||
* But see AUDIO_INTERLEAVE_* below for the platform convention of order.
|
||||
*
|
||||
* audio_channel_mask_t is an opaque type and its internal layout should not
|
||||
* be assumed as it may change in the future.
|
||||
* Instead, always use the functions declared in this header to examine.
|
||||
*
|
||||
* These are the current representations:
|
||||
*
|
||||
* AUDIO_CHANNEL_REPRESENTATION_POSITION
|
||||
* is a channel mask representation for position assignment.
|
||||
* Each low-order bit corresponds to the spatial position of a transducer (output),
|
||||
* or interpretation of channel (input).
|
||||
* The user of a channel mask needs to know the context of whether it is for output or input.
|
||||
* The constants AUDIO_CHANNEL_OUT_* or AUDIO_CHANNEL_IN_* apply to the bits portion.
|
||||
* It is not permitted for no bits to be set.
|
||||
*
|
||||
* AUDIO_CHANNEL_REPRESENTATION_INDEX
|
||||
* is a channel mask representation for index assignment.
|
||||
* Each low-order bit corresponds to a selected channel.
|
||||
* There is no platform interpretation of the various bits.
|
||||
* There is no concept of output or input.
|
||||
* It is not permitted for no bits to be set.
|
||||
*
|
||||
* All other representations are reserved for future use.
|
||||
*
|
||||
* Warning: current representation distinguishes between input and output, but this will not the be
|
||||
* case in future revisions of the platform. Wherever there is an ambiguity between input and output
|
||||
* that is currently resolved by checking the channel mask, the implementer should look for ways to
|
||||
* fix it with additional information outside of the mask.
|
||||
*/
|
||||
typedef uint32_t audio_channel_mask_t;
|
||||
|
||||
/* Maximum number of channels for all representations */
|
||||
#define AUDIO_CHANNEL_COUNT_MAX 30
|
||||
|
||||
/* log(2) of maximum number of representations, not part of public API */
|
||||
#define AUDIO_CHANNEL_REPRESENTATION_LOG2 2
|
||||
|
||||
/* Representations */
|
||||
typedef enum {
|
||||
AUDIO_CHANNEL_REPRESENTATION_POSITION = 0, // must be zero for compatibility
|
||||
// 1 is reserved for future use
|
||||
AUDIO_CHANNEL_REPRESENTATION_INDEX = 2,
|
||||
// 3 is reserved for future use
|
||||
} audio_channel_representation_t;
|
||||
|
||||
/* The return value is undefined if the channel mask is invalid. */
|
||||
static inline uint32_t audio_channel_mask_get_bits(audio_channel_mask_t channel) {
|
||||
return channel & ((1 << AUDIO_CHANNEL_COUNT_MAX) - 1);
|
||||
}
|
||||
|
||||
/* The return value is undefined if the channel mask is invalid. */
|
||||
static inline audio_channel_representation_t audio_channel_mask_get_representation(
|
||||
audio_channel_mask_t channel) {
|
||||
// The right shift should be sufficient, but also "and" for safety in case mask is not 32 bits
|
||||
return (audio_channel_representation_t)((channel >> AUDIO_CHANNEL_COUNT_MAX) & ((1 << AUDIO_CHANNEL_REPRESENTATION_LOG2) - 1));
|
||||
}
|
||||
|
||||
/* Returns the number of channels from an output channel mask,
|
||||
* used in the context of audio output or playback.
|
||||
* If a channel bit is set which could _not_ correspond to an output channel,
|
||||
* it is excluded from the count.
|
||||
* Returns zero if the representation is invalid.
|
||||
*/
|
||||
static inline uint32_t audio_channel_count_from_out_mask(audio_channel_mask_t channel) {
|
||||
uint32_t bits = audio_channel_mask_get_bits(channel);
|
||||
switch (audio_channel_mask_get_representation(channel)) {
|
||||
case AUDIO_CHANNEL_REPRESENTATION_POSITION:
|
||||
// REFINE: We can now merge with from_in_mask and remove anding
|
||||
bits &= AUDIO_CHANNEL_OUT_ALL;
|
||||
// fall through
|
||||
case AUDIO_CHANNEL_REPRESENTATION_INDEX:
|
||||
return popcount(bits);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool audio_is_valid_format(audio_format_t format) {
|
||||
switch (format & AUDIO_FORMAT_MAIN_MASK) {
|
||||
case AUDIO_FORMAT_PCM:
|
||||
switch (format) {
|
||||
case AUDIO_FORMAT_PCM_16_BIT:
|
||||
case AUDIO_FORMAT_PCM_8_BIT:
|
||||
case AUDIO_FORMAT_PCM_32_BIT:
|
||||
case AUDIO_FORMAT_PCM_8_24_BIT:
|
||||
case AUDIO_FORMAT_PCM_FLOAT:
|
||||
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
/* not reached */
|
||||
case AUDIO_FORMAT_MP3:
|
||||
case AUDIO_FORMAT_AMR_NB:
|
||||
case AUDIO_FORMAT_AMR_WB:
|
||||
case AUDIO_FORMAT_AAC:
|
||||
case AUDIO_FORMAT_HE_AAC_V1:
|
||||
case AUDIO_FORMAT_HE_AAC_V2:
|
||||
case AUDIO_FORMAT_VORBIS:
|
||||
case AUDIO_FORMAT_OPUS:
|
||||
case AUDIO_FORMAT_AC3:
|
||||
case AUDIO_FORMAT_E_AC3:
|
||||
case AUDIO_FORMAT_DTS:
|
||||
case AUDIO_FORMAT_DTS_HD:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool audio_is_linear_pcm(audio_format_t format) {
|
||||
return ((format & AUDIO_FORMAT_MAIN_MASK) == AUDIO_FORMAT_PCM);
|
||||
}
|
||||
|
||||
static inline size_t audio_bytes_per_sample(audio_format_t format) {
|
||||
size_t size = 0;
|
||||
|
||||
switch (format) {
|
||||
case AUDIO_FORMAT_PCM_32_BIT:
|
||||
case AUDIO_FORMAT_PCM_8_24_BIT:
|
||||
size = sizeof(int32_t);
|
||||
break;
|
||||
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
|
||||
size = sizeof(uint8_t) * 3;
|
||||
break;
|
||||
case AUDIO_FORMAT_PCM_16_BIT:
|
||||
size = sizeof(int16_t);
|
||||
break;
|
||||
case AUDIO_FORMAT_PCM_8_BIT:
|
||||
size = sizeof(uint8_t);
|
||||
break;
|
||||
case AUDIO_FORMAT_PCM_FLOAT:
|
||||
size = sizeof(float);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/* Not part of public API */
|
||||
static inline audio_channel_mask_t audio_channel_mask_from_representation_and_bits(
|
||||
audio_channel_representation_t representation, uint32_t bits) {
|
||||
return (audio_channel_mask_t)((representation << AUDIO_CHANNEL_COUNT_MAX) | bits);
|
||||
}
|
||||
|
||||
/* Derive an output channel mask for position assignment from a channel count.
|
||||
* This is to be used when the content channel mask is unknown. The 1, 2, 4, 5, 6, 7 and 8 channel
|
||||
* cases are mapped to the standard game/home-theater layouts, but note that 4 is mapped to quad,
|
||||
* and not stereo + FC + mono surround. A channel count of 3 is arbitrarily mapped to stereo + FC
|
||||
* for continuity with stereo.
|
||||
* Returns the matching channel mask,
|
||||
* or AUDIO_CHANNEL_NONE if the channel count is zero,
|
||||
* or AUDIO_CHANNEL_INVALID if the channel count exceeds that of the
|
||||
* configurations for which a default output channel mask is defined.
|
||||
*/
|
||||
static inline audio_channel_mask_t audio_channel_out_mask_from_count(uint32_t channel_count) {
|
||||
uint32_t bits;
|
||||
switch (channel_count) {
|
||||
case 0:
|
||||
return AUDIO_CHANNEL_NONE;
|
||||
case 1:
|
||||
bits = AUDIO_CHANNEL_OUT_MONO;
|
||||
break;
|
||||
case 2:
|
||||
bits = AUDIO_CHANNEL_OUT_STEREO;
|
||||
break;
|
||||
case 3:
|
||||
bits = AUDIO_CHANNEL_OUT_STEREO | AUDIO_CHANNEL_OUT_FRONT_CENTER;
|
||||
break;
|
||||
case 4: // 4.0
|
||||
bits = AUDIO_CHANNEL_OUT_QUAD;
|
||||
break;
|
||||
case 5: // 5.0
|
||||
bits = AUDIO_CHANNEL_OUT_QUAD | AUDIO_CHANNEL_OUT_FRONT_CENTER;
|
||||
break;
|
||||
case 6: // 5.1
|
||||
bits = AUDIO_CHANNEL_OUT_5POINT1;
|
||||
break;
|
||||
case 7: // 6.1
|
||||
bits = AUDIO_CHANNEL_OUT_5POINT1 | AUDIO_CHANNEL_OUT_BACK_CENTER;
|
||||
break;
|
||||
case 8:
|
||||
bits = AUDIO_CHANNEL_OUT_7POINT1;
|
||||
break;
|
||||
// IDEA: FCC_8
|
||||
default:
|
||||
return AUDIO_CHANNEL_INVALID;
|
||||
}
|
||||
return audio_channel_mask_from_representation_and_bits(
|
||||
AUDIO_CHANNEL_REPRESENTATION_POSITION, bits);
|
||||
}
|
||||
48
cocos/audio/android/cutils/bitops.h
Normal file
48
cocos/audio/android/cutils/bitops.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef COCOS_CUTILS_BITOPS_H
|
||||
#define COCOS_CUTILS_BITOPS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include <sys/cdefs.h>
|
||||
#endif
|
||||
|
||||
#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 */
|
||||
597
cocos/audio/android/cutils/log.h
Normal file
597
cocos/audio/android/cutils/log.h
Normal file
@@ -0,0 +1,597 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//
|
||||
// C/C++ logging functions. See the logging documentation for API details.
|
||||
//
|
||||
// We'd like these to be available from C code (in case we import some from
|
||||
// somewhere), so this has a C interface.
|
||||
//
|
||||
// The output will be correct when the log file is shared between multiple
|
||||
// threads and/or multiple processes so long as the operating system
|
||||
// supports O_APPEND. These calls have mutex-protected data structures
|
||||
// and so are NOT reentrant. Do not use LOG in a signal handler.
|
||||
//
|
||||
#ifndef COCOS_CUTILS_LOG_H
|
||||
#define COCOS_CUTILS_LOG_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include <android/log.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
// TODO(qgh):May be implemented in later versions
|
||||
// #include <Hilog/log.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
/*
|
||||
* Normally we strip ALOGV (VERBOSE messages) from release builds.
|
||||
* You can modify this (for example with "#define LOG_NDEBUG 0"
|
||||
* at the top of your source file) to change that behavior.
|
||||
*/
|
||||
#ifndef LOG_NDEBUG
|
||||
#if defined(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
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#define LOG_ALWAYS_FATAL_IF(cond, ...) \
|
||||
((__predict_false(cond)) \
|
||||
? ((void)android_printAssert(#cond, LOG_TAG, ##__VA_ARGS__)) \
|
||||
: (void)0)
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
#define LOG_ALWAYS_FATAL_IF(cond, ...)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef LOG_ALWAYS_FATAL
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#define LOG_ALWAYS_FATAL(...) \
|
||||
(((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__)))
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
#define LOG_ALWAYS_FATAL(...)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
|
||||
* are stripped out of release builds.
|
||||
*/
|
||||
#if LOG_NDEBUG
|
||||
|
||||
#ifndef LOG_FATAL_IF
|
||||
#define LOG_FATAL_IF(cond, ...) ((void)0)
|
||||
#endif
|
||||
#ifndef LOG_FATAL
|
||||
#define LOG_FATAL(...) ((void)0)
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#ifndef LOG_FATAL_IF
|
||||
#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__)
|
||||
#endif
|
||||
#ifndef LOG_FATAL
|
||||
#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Assertion that generates a log message when the assertion fails.
|
||||
* Stripped out of release builds. Uses the current LOG_TAG.
|
||||
*/
|
||||
#ifndef ALOG_ASSERT
|
||||
#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__)
|
||||
//#define ALOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond)
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* Basic log message macro.
|
||||
*
|
||||
* Example:
|
||||
* ALOG(LOG_WARN, NULL, "Failed with error %d", errno);
|
||||
*
|
||||
* The second argument may be NULL or "" to indicate the "global" tag.
|
||||
*/
|
||||
#ifndef ALOG
|
||||
#define ALOG(priority, tag, ...) \
|
||||
LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Log macro that allows you to specify a number for the priority.
|
||||
*/
|
||||
#ifndef LOG_PRI
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#define LOG_PRI(priority, tag, ...) \
|
||||
android_printLog(priority, tag, __VA_ARGS__)
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
#define LOG_PRI(priority, tag, ...) ((void)0)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Log macro that allows you to pass in a varargs ("args" is a va_list).
|
||||
*/
|
||||
#ifndef LOG_PRI_VA
|
||||
#define LOG_PRI_VA(priority, tag, fmt, args) \
|
||||
android_vprintLog(priority, NULL, tag, fmt, args)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Conditional given a desired logging priority and tag.
|
||||
*/
|
||||
#ifndef IF_ALOG
|
||||
#define IF_ALOG(priority, tag) \
|
||||
if (android_testLog(ANDROID_##priority, tag))
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
*
|
||||
* The stuff in the rest of this file should not be used directly.
|
||||
*/
|
||||
|
||||
#define android_printLog(prio, tag, ...) \
|
||||
__android_log_print(prio, tag, __VA_ARGS__)
|
||||
|
||||
#define android_vprintLog(prio, cond, tag, ...) \
|
||||
__android_log_vprint(prio, tag, __VA_ARGS__)
|
||||
|
||||
/* XXX Macros to work around syntax errors in places where format string
|
||||
* arg is not passed to ALOG_ASSERT, LOG_ALWAYS_FATAL or LOG_ALWAYS_FATAL_IF
|
||||
* (happens only in debug builds).
|
||||
*/
|
||||
|
||||
/* Returns 2nd arg. Used to substitute default value if caller's vararg list
|
||||
* is empty.
|
||||
*/
|
||||
#define __android_second(dummy, second, ...) second
|
||||
|
||||
/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise
|
||||
* returns nothing.
|
||||
*/
|
||||
#define __android_rest(first, ...) , ##__VA_ARGS__
|
||||
|
||||
#define android_printAssert(cond, tag, ...) \
|
||||
__android_log_assert(cond, tag, \
|
||||
__android_second(0, ##__VA_ARGS__, NULL) __android_rest(__VA_ARGS__))
|
||||
|
||||
#define android_writeLog(prio, tag, text) \
|
||||
__android_log_write(prio, tag, text)
|
||||
|
||||
#define android_bWriteLog(tag, payload, len) \
|
||||
__android_log_bwrite(tag, payload, len)
|
||||
#define android_btWriteLog(tag, type, payload, len) \
|
||||
__android_log_btwrite(tag, type, payload, len)
|
||||
|
||||
#define android_errorWriteLog(tag, subTag) \
|
||||
__android_log_error_write(tag, subTag, -1, NULL, 0)
|
||||
|
||||
#define android_errorWriteWithInfoLog(tag, subTag, uid, data, dataLen) \
|
||||
__android_log_error_write(tag, subTag, uid, data, dataLen)
|
||||
|
||||
/*
|
||||
* IF_ALOG uses android_testLog, but IF_ALOG can be overridden.
|
||||
* android_testLog will remain constant in its purpose as a wrapper
|
||||
* for Android logging filter policy, and can be subject to
|
||||
* change. It can be reused by the developers that override
|
||||
* IF_ALOG as a convenient means to reimplement their policy
|
||||
* over Android.
|
||||
*/
|
||||
#if LOG_NDEBUG /* Production */
|
||||
#define android_testLog(prio, tag) \
|
||||
(__android_log_is_loggable(prio, tag, ANDROID_LOG_DEBUG) != 0)
|
||||
#else
|
||||
#define android_testLog(prio, tag) \
|
||||
(__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE) != 0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Use the per-tag properties "log.tag.<tagname>" to generate a runtime
|
||||
* result of non-zero to expose a log. prio is ANDROID_LOG_VERBOSE to
|
||||
* ANDROID_LOG_FATAL. default_prio if no property. Undefined behavior if
|
||||
* any other value.
|
||||
*/
|
||||
int __android_log_is_loggable(int prio, const char *tag, int default_prio);
|
||||
|
||||
int __android_log_security(); /* Device Owner is present */
|
||||
|
||||
int __android_log_error_write(int tag, const char *subTag, int32_t uid, const char *data,
|
||||
uint32_t dataLen);
|
||||
|
||||
/*
|
||||
* Send a simple string to the log.
|
||||
*/
|
||||
int __android_log_buf_write(int bufID, int prio, const char *tag, const char *text);
|
||||
int __android_log_buf_print(int bufID, int prio, const char *tag, const char *fmt, ...)
|
||||
#if defined(__GNUC__)
|
||||
__attribute__((__format__(printf, 4, 5)))
|
||||
#endif
|
||||
;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* COCOS_CUTILS_LOG_H */
|
||||
525
cocos/audio/android/mp3reader.cpp
Normal file
525
cocos/audio/android/mp3reader.cpp
Normal file
@@ -0,0 +1,525 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "mp3reader"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h> // Resolves that memset, memcpy aren't found while APP_PLATFORM >= 22 on Android
|
||||
#include <vector>
|
||||
#include "audio/android/cutils/log.h"
|
||||
|
||||
#include "audio/android/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<char> &pcmBuffer, int *numChannels, int *sampleRate, int *numFrames) {
|
||||
// Initialize the config.
|
||||
tPVMP3DecoderExternal config;
|
||||
config.equalizerType = flat;
|
||||
config.crcEnabled = false;
|
||||
|
||||
// Allocate the decoder memory.
|
||||
uint32_t memRequirements = pvmp3_decoderMemRequirements();
|
||||
void *decoderBuf = malloc(memRequirements);
|
||||
assert(decoderBuf != NULL);
|
||||
|
||||
// Initialize the decoder.
|
||||
pvmp3_InitDecoder(&config, decoderBuf);
|
||||
|
||||
// Open the input file.
|
||||
Mp3Reader mp3Reader;
|
||||
bool success = mp3Reader.init(cb, source);
|
||||
if (!success) {
|
||||
ALOGE("mp3Reader.init: Encountered error reading\n");
|
||||
free(decoderBuf);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Open the output file.
|
||||
// SF_INFO sfInfo;
|
||||
// memset(&sfInfo, 0, sizeof(SF_INFO));
|
||||
// sfInfo.channels = mp3Reader.getNumChannels();
|
||||
// sfInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
|
||||
// sfInfo.samplerate = mp3Reader.getSampleRate();
|
||||
// SNDFILE *handle = sf_open(argv[2], SFM_WRITE, &sfInfo);
|
||||
// if (handle == NULL) {
|
||||
// ALOGE("Encountered error writing %s\n", argv[2]);
|
||||
// mp3Reader.close();
|
||||
// free(decoderBuf);
|
||||
// return EXIT_FAILURE;
|
||||
// }
|
||||
|
||||
// Allocate input buffer.
|
||||
uint8_t *inputBuf = static_cast<uint8_t *>(malloc(kInputBufferSize));
|
||||
assert(inputBuf != NULL);
|
||||
|
||||
// Allocate output buffer.
|
||||
int16_t *outputBuf = static_cast<int16_t *>(malloc(kOutputBufferSize));
|
||||
assert(outputBuf != NULL);
|
||||
|
||||
// Decode loop.
|
||||
int retVal = EXIT_SUCCESS;
|
||||
while (1) {
|
||||
// Read input from the file.
|
||||
uint32_t bytesRead;
|
||||
bool success = mp3Reader.getFrame(inputBuf, &bytesRead);
|
||||
if (!success) break;
|
||||
|
||||
*numChannels = mp3Reader.getNumChannels();
|
||||
*sampleRate = mp3Reader.getSampleRate();
|
||||
|
||||
// Set the input config.
|
||||
config.inputBufferCurrentLength = bytesRead;
|
||||
config.inputBufferMaxLength = 0;
|
||||
config.inputBufferUsedLength = 0;
|
||||
config.pInputBuffer = inputBuf;
|
||||
config.pOutputBuffer = outputBuf;
|
||||
config.outputFrameSize = kOutputBufferSize / sizeof(int16_t);
|
||||
|
||||
ERROR_CODE decoderErr;
|
||||
decoderErr = pvmp3_framedecoder(&config, decoderBuf);
|
||||
if (decoderErr != NO_DECODING_ERROR) {
|
||||
ALOGE("Decoder encountered error=%d", decoderErr);
|
||||
retVal = EXIT_FAILURE;
|
||||
break;
|
||||
}
|
||||
|
||||
pcmBuffer.insert(pcmBuffer.end(), (char *)outputBuf, ((char *)outputBuf) + config.outputFrameSize * 2);
|
||||
*numFrames += config.outputFrameSize / mp3Reader.getNumChannels();
|
||||
}
|
||||
|
||||
// Close input reader and output writer.
|
||||
mp3Reader.close();
|
||||
// sf_close(handle);
|
||||
|
||||
// Free allocated memory.
|
||||
free(inputBuf);
|
||||
free(outputBuf);
|
||||
free(decoderBuf);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
61
cocos/audio/android/mp3reader.h
Normal file
61
cocos/audio/android/mp3reader.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef MP3READER_H_
|
||||
#define MP3READER_H_
|
||||
|
||||
typedef struct {
|
||||
size_t (*read)(void *ptr, size_t size, size_t nmemb, void *datasource);
|
||||
int (*seek)(void *datasource, int64_t offset, int whence);
|
||||
int (*close)(void *datasource);
|
||||
long (*tell)(void *datasource);
|
||||
} mp3_callbacks;
|
||||
|
||||
class Mp3Reader {
|
||||
public:
|
||||
Mp3Reader();
|
||||
bool init(mp3_callbacks *callback, void *source);
|
||||
bool getFrame(void *buffer, uint32_t *size);
|
||||
uint32_t getSampleRate() { return mSampleRate; }
|
||||
uint32_t getNumChannels() { return mNumChannels; }
|
||||
void close();
|
||||
~Mp3Reader();
|
||||
|
||||
private:
|
||||
void *mSource;
|
||||
mp3_callbacks *mCallback;
|
||||
uint32_t mFixedHeader;
|
||||
off64_t mCurrentPos;
|
||||
uint32_t mSampleRate;
|
||||
uint32_t mNumChannels;
|
||||
uint32_t mBitrate;
|
||||
};
|
||||
|
||||
int decodeMP3(mp3_callbacks *cb, void *source, std::vector<char> &pcmBuffer, int *numChannels, int *sampleRate, int *numFrames);
|
||||
|
||||
#endif /* MP3READER_H_ */
|
||||
107
cocos/audio/android/utils/Compat.h
Normal file
107
cocos/audio/android/utils/Compat.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef COCOS_LIB_UTILS_COMPAT_H
|
||||
#define COCOS_LIB_UTILS_COMPAT_H
|
||||
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include <unistd.h>
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
#include <SLES/OpenSLES_OpenHarmony.h>
|
||||
#include <SLES/OpenSLES_Platform.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
/* Mac OS has always had a 64-bit off_t, so it doesn't have off64_t. */
|
||||
|
||||
typedef off_t off64_t;
|
||||
|
||||
static inline off64_t lseek64(int fd, off64_t offset, int whence) {
|
||||
return lseek(fd, offset, whence);
|
||||
}
|
||||
|
||||
static inline ssize_t pread64(int fd, void *buf, size_t nbytes, off64_t offset) {
|
||||
return pread(fd, buf, nbytes, offset);
|
||||
}
|
||||
|
||||
static inline ssize_t pwrite64(int fd, const void *buf, size_t nbytes, off64_t offset) {
|
||||
return pwrite(fd, buf, nbytes, offset);
|
||||
}
|
||||
|
||||
#endif /* __APPLE__ */
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define O_CLOEXEC O_NOINHERIT
|
||||
#define O_NOFOLLOW 0
|
||||
#define DEFFILEMODE 0666
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define ZD "%ld"
|
||||
#define ZD_TYPE long
|
||||
#else
|
||||
#define ZD "%zd"
|
||||
#define ZD_TYPE ssize_t
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Needed for cases where something should be constexpr if possible, but not
|
||||
* being constexpr is fine if in pre-C++11 code (such as a const static float
|
||||
* member variable).
|
||||
*/
|
||||
#if __cplusplus >= 201103L
|
||||
#define CONSTEXPR constexpr
|
||||
#else
|
||||
#define CONSTEXPR
|
||||
#endif
|
||||
|
||||
/*
|
||||
* TEMP_FAILURE_RETRY is defined by some, but not all, versions of
|
||||
* <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
|
||||
* not already defined, then define it here.
|
||||
*/
|
||||
#ifndef TEMP_FAILURE_RETRY
|
||||
/* Used to retry syscalls that can return EINTR. */
|
||||
#define TEMP_FAILURE_RETRY(exp) ({ \
|
||||
typeof (exp) _rc; \
|
||||
do { \
|
||||
_rc = (exp); \
|
||||
} while (_rc == -1 && errno == EINTR); \
|
||||
_rc; })
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define OS_PATH_SEPARATOR '\\'
|
||||
#else
|
||||
#define OS_PATH_SEPARATOR '/'
|
||||
#endif
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
typedef SLAndroidSimpleBufferQueueItf CCSLBufferQueueItf;
|
||||
#define CC_SL_IDD_BUFFER_QUEUE SL_IID_ANDROIDSIMPLEBUFFERQUEUE
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
typedef SLOHBufferQueueItf CCSLBufferQueueItf;
|
||||
#define CC_SL_IDD_BUFFER_QUEUE SL_IID_OH_BUFFERQUEUE
|
||||
#define __unused
|
||||
#endif
|
||||
|
||||
#endif /* COCOS_LIB_UTILS_COMPAT_H */
|
||||
88
cocos/audio/android/utils/Errors.h
Normal file
88
cocos/audio/android/utils/Errors.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef COCOS_ERRORS_H
|
||||
#define COCOS_ERRORS_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <errno.h>
|
||||
|
||||
namespace cc {
|
||||
|
||||
// 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 cc
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#endif // COCOS_ERRORS_H
|
||||
34
cocos/audio/android/utils/Utils.cpp
Normal file
34
cocos/audio/android/utils/Utils.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#include "audio/android/utils/Utils.h"
|
||||
#include "platform/BasePlatform.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
int getSDKVersion() {
|
||||
return BasePlatform::getPlatform()->getSdkVersion();
|
||||
}
|
||||
|
||||
} // end of namespace cc
|
||||
31
cocos/audio/android/utils/Utils.h
Normal file
31
cocos/audio/android/utils/Utils.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 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 "base/std/container/string.h"
|
||||
|
||||
namespace cc {
|
||||
extern int getSDKVersion();
|
||||
}
|
||||
110
cocos/audio/apple/AudioCache.h
Normal file
110
cocos/audio/apple/AudioCache.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 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
|
||||
|
||||
#import <OpenAL/al.h>
|
||||
|
||||
#include <mutex>
|
||||
#include "base/std/container/string.h"
|
||||
|
||||
#include "audio/apple/AudioMacros.h"
|
||||
#include "base/Macros.h"
|
||||
#include "base/std/container/vector.h"
|
||||
|
||||
#define INVALID_AL_BUFFER_ID 0xFFFFFFFF
|
||||
|
||||
namespace cc {
|
||||
class AudioEngineImpl;
|
||||
class AudioPlayer;
|
||||
|
||||
class AudioCache {
|
||||
public:
|
||||
enum class State {
|
||||
INITIAL,
|
||||
LOADING,
|
||||
READY,
|
||||
FAILED
|
||||
};
|
||||
|
||||
AudioCache();
|
||||
~AudioCache();
|
||||
|
||||
void addPlayCallback(const std::function<void()> &callback);
|
||||
|
||||
void addLoadCallback(const std::function<void(bool)> &callback);
|
||||
inline bool isStreaming() const { return _isStreaming; }
|
||||
|
||||
protected:
|
||||
void setSkipReadDataTask(bool isSkip) { _isSkipReadDataTask = isSkip; };
|
||||
void readDataTask(unsigned int selfId);
|
||||
|
||||
void invokingPlayCallbacks();
|
||||
|
||||
void invokingLoadCallbacks();
|
||||
|
||||
//pcm data related stuff
|
||||
ALenum _format{-1};
|
||||
ALsizei _sampleRate{0};
|
||||
float _duration{0};
|
||||
uint32_t _totalFrames{0};
|
||||
uint32_t _framesRead{0};
|
||||
uint32_t _bytesPerFrame{0};
|
||||
bool _isStreaming{false};
|
||||
uint32_t _channelCount{1};
|
||||
/*Cache related stuff;
|
||||
* Cache pcm data when sizeInBytes less than PCMDATA_CACHEMAXSIZE
|
||||
*/
|
||||
ALuint _alBufferId{INVALID_AL_BUFFER_ID};
|
||||
char *_pcmData{nullptr};
|
||||
|
||||
/*Queue buffer related stuff
|
||||
* Streaming in openal when sizeInBytes greater then PCMDATA_CACHEMAXSIZE
|
||||
*/
|
||||
char *_queBuffers[QUEUEBUFFER_NUM];
|
||||
ALsizei _queBufferSize[QUEUEBUFFER_NUM];
|
||||
uint32_t _queBufferFrames{0};
|
||||
|
||||
std::mutex _playCallbackMutex;
|
||||
ccstd::vector<std::function<void()>> _playCallbacks;
|
||||
|
||||
// loadCallbacks doesn't need mutex since it's invoked only in Cocos thread.
|
||||
ccstd::vector<std::function<void(bool)>> _loadCallbacks;
|
||||
|
||||
std::mutex _readDataTaskMutex;
|
||||
|
||||
State _state{State::INITIAL};
|
||||
|
||||
std::shared_ptr<bool> _isDestroyed;
|
||||
ccstd::string _fileFullPath;
|
||||
unsigned int _id;
|
||||
bool _isLoadingFinished{false};
|
||||
bool _isSkipReadDataTask{false};
|
||||
|
||||
friend class AudioEngineImpl;
|
||||
friend class AudioPlayer;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
354
cocos/audio/apple/AudioCache.mm
Normal file
354
cocos/audio/apple/AudioCache.mm
Normal file
@@ -0,0 +1,354 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-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.
|
||||
****************************************************************************/
|
||||
|
||||
#define LOG_TAG "AudioCache"
|
||||
|
||||
#include "audio/apple/AudioCache.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <OpenAL/alc.h>
|
||||
#include <thread>
|
||||
#include "application/ApplicationManager.h"
|
||||
#include "base/Scheduler.h"
|
||||
#include "base/memory/Memory.h"
|
||||
|
||||
#include "audio/apple/AudioDecoder.h"
|
||||
|
||||
#ifdef VERY_VERY_VERBOSE_LOGGING
|
||||
#define ALOGVV ALOGV
|
||||
#else
|
||||
#define ALOGVV(...) \
|
||||
do { \
|
||||
} while (false)
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
unsigned int __idIndex = 0;
|
||||
}
|
||||
|
||||
#define PCMDATA_CACHEMAXSIZE 1048576
|
||||
|
||||
@interface NSTimerWrapper : NSObject {
|
||||
std::function<void()> _timeoutCallback;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSTimerWrapper
|
||||
|
||||
- (id)initWithTimeInterval:(double)seconds callback:(const std::function<void()> &)cb {
|
||||
if (self = [super init]) {
|
||||
_timeoutCallback = cb;
|
||||
NSTimer *timer = [NSTimer timerWithTimeInterval:seconds target:self selector:@selector(onTimeoutCallback:) userInfo:nil repeats:NO];
|
||||
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onTimeoutCallback:(NSTimer *)timer {
|
||||
if (_timeoutCallback != nullptr) {
|
||||
_timeoutCallback();
|
||||
_timeoutCallback = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
using namespace cc;
|
||||
|
||||
AudioCache::AudioCache()
|
||||
: _isDestroyed(std::make_shared<bool>(false)), _id(++__idIndex){
|
||||
ALOGVV("AudioCache() %p, id=%u", this, _id);
|
||||
for (int i = 0; i < QUEUEBUFFER_NUM; ++i) {
|
||||
_queBuffers[i] = nullptr;
|
||||
_queBufferSize[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
AudioCache::~AudioCache() {
|
||||
ALOGVV("~AudioCache() %p, id=%u, begin", this, _id);
|
||||
*_isDestroyed = true;
|
||||
while (!_isLoadingFinished) {
|
||||
if (_isSkipReadDataTask) {
|
||||
ALOGV("id=%u, Skip read data task, don't continue to wait!", _id);
|
||||
break;
|
||||
}
|
||||
ALOGVV("id=%u, waiting readData thread to finish ...", _id);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
//wait for the 'readDataTask' task to exit
|
||||
_readDataTaskMutex.lock();
|
||||
|
||||
if (_state == State::READY) {
|
||||
if (_alBufferId != INVALID_AL_BUFFER_ID && alIsBuffer(_alBufferId)) {
|
||||
ALOGV("~AudioCache(id=%u), delete buffer: %u", _id, _alBufferId);
|
||||
alDeleteBuffers(1, &_alBufferId);
|
||||
_alBufferId = INVALID_AL_BUFFER_ID;
|
||||
}
|
||||
} else {
|
||||
ALOGW("AudioCache (%p), id=%u, buffer isn't ready, state=%d", this, _id, _state);
|
||||
}
|
||||
|
||||
if (_queBufferFrames > 0) {
|
||||
for (int index = 0; index < QUEUEBUFFER_NUM; ++index) {
|
||||
free(_queBuffers[index]);
|
||||
}
|
||||
}
|
||||
ALOGVV("~AudioCache() %p, id=%u, end", this, _id);
|
||||
_readDataTaskMutex.unlock();
|
||||
}
|
||||
|
||||
void AudioCache::readDataTask(unsigned int selfId) {
|
||||
//Note: It's in sub thread
|
||||
ALOGVV("readDataTask, cache id=%u", selfId);
|
||||
|
||||
_readDataTaskMutex.lock();
|
||||
_state = State::LOADING;
|
||||
|
||||
AudioDecoder decoder;
|
||||
do {
|
||||
if (!decoder.open(_fileFullPath.c_str()))
|
||||
break;
|
||||
|
||||
const uint32_t originalTotalFrames = decoder.getTotalFrames();
|
||||
const uint32_t bytesPerFrame = decoder.getBytesPerFrame();
|
||||
const uint32_t sampleRate = decoder.getSampleRate();
|
||||
_channelCount = decoder.getChannelCount();
|
||||
|
||||
uint32_t totalFrames = originalTotalFrames;
|
||||
uint32_t dataSize = totalFrames * bytesPerFrame;
|
||||
uint32_t remainingFrames = totalFrames;
|
||||
uint32_t adjustFrames = 0;
|
||||
|
||||
_format = _channelCount > 1 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
|
||||
_sampleRate = (ALsizei)sampleRate;
|
||||
_duration = 1.0f * totalFrames / sampleRate;
|
||||
_totalFrames = totalFrames;
|
||||
|
||||
if (dataSize <= PCMDATA_CACHEMAXSIZE) {
|
||||
uint32_t framesRead = 0;
|
||||
const uint32_t framesToReadOnce = std::min(totalFrames, static_cast<uint32_t>(sampleRate * QUEUEBUFFER_TIME_STEP * QUEUEBUFFER_NUM));
|
||||
|
||||
BREAK_IF_ERR_LOG(!decoder.seek(totalFrames), "AudioDecoder::seek(%u) error", totalFrames);
|
||||
|
||||
char *tmpBuf = (char *)malloc(framesToReadOnce * bytesPerFrame);
|
||||
ccstd::vector<char> adjustFrameBuf;
|
||||
adjustFrameBuf.reserve(framesToReadOnce * bytesPerFrame);
|
||||
|
||||
// Adjust total frames by setting position to the end of frames and try to read more data.
|
||||
// This is a workaround for https://github.com/cocos2d/cocos2d-x/issues/16938
|
||||
do {
|
||||
framesRead = decoder.read(framesToReadOnce, tmpBuf);
|
||||
if (framesRead > 0) {
|
||||
adjustFrames += framesRead;
|
||||
adjustFrameBuf.insert(adjustFrameBuf.end(), tmpBuf, tmpBuf + framesRead * bytesPerFrame);
|
||||
}
|
||||
|
||||
} while (framesRead > 0);
|
||||
|
||||
if (adjustFrames > 0) {
|
||||
ALOGV("Orignal total frames: %u, adjust frames: %u, current total frames: %u", totalFrames, adjustFrames, totalFrames + adjustFrames);
|
||||
totalFrames += adjustFrames;
|
||||
_totalFrames = remainingFrames = totalFrames;
|
||||
}
|
||||
|
||||
// Reset dataSize
|
||||
dataSize = totalFrames * bytesPerFrame;
|
||||
|
||||
free(tmpBuf);
|
||||
|
||||
// Reset to frame 0
|
||||
BREAK_IF_ERR_LOG(!decoder.seek(0), "AudioDecoder::seek(0) failed!");
|
||||
|
||||
_pcmData = (char *)malloc(dataSize);
|
||||
memset(_pcmData, 0x00, dataSize);
|
||||
ALOGV(" id=%u _pcmData alloc: %p", selfId, _pcmData);
|
||||
|
||||
if (adjustFrames > 0) {
|
||||
memcpy(_pcmData + (dataSize - adjustFrameBuf.size()), adjustFrameBuf.data(), adjustFrameBuf.size());
|
||||
}
|
||||
|
||||
if (*_isDestroyed)
|
||||
break;
|
||||
|
||||
framesRead = decoder.readFixedFrames(std::min(framesToReadOnce, remainingFrames), _pcmData + _framesRead * bytesPerFrame);
|
||||
_framesRead += framesRead;
|
||||
remainingFrames -= framesRead;
|
||||
|
||||
if (*_isDestroyed)
|
||||
break;
|
||||
|
||||
uint32_t frames = 0;
|
||||
while (!*_isDestroyed && _framesRead < originalTotalFrames) {
|
||||
frames = std::min(framesToReadOnce, remainingFrames);
|
||||
if (_framesRead + frames > originalTotalFrames) {
|
||||
frames = originalTotalFrames - _framesRead;
|
||||
}
|
||||
framesRead = decoder.read(frames, _pcmData + _framesRead * bytesPerFrame);
|
||||
if (framesRead == 0)
|
||||
break;
|
||||
_framesRead += framesRead;
|
||||
remainingFrames -= framesRead;
|
||||
}
|
||||
|
||||
if (_framesRead < originalTotalFrames) {
|
||||
memset(_pcmData + _framesRead * bytesPerFrame, 0x00, (totalFrames - _framesRead) * bytesPerFrame);
|
||||
}
|
||||
|
||||
ALOGV("pcm buffer was loaded successfully, total frames: %u, total read frames: %u, adjust frames: %u, remainingFrames: %u", totalFrames, _framesRead, adjustFrames, remainingFrames);
|
||||
_framesRead += adjustFrames;
|
||||
|
||||
alGenBuffers(1, &_alBufferId);
|
||||
auto alError = alGetError();
|
||||
if (alError != AL_NO_ERROR) {
|
||||
ALOGE("%s: attaching audio to buffer fail: %x", __PRETTY_FUNCTION__, alError);
|
||||
break;
|
||||
}
|
||||
ALOGV(" id=%u generated alGenBuffers: %u for _pcmData: %p", selfId, _alBufferId, _pcmData);
|
||||
ALOGV(" id=%u _pcmData alBufferData: %p", selfId, _pcmData);
|
||||
alBufferData(_alBufferId, _format, _pcmData, (ALsizei)dataSize, (ALsizei)sampleRate);
|
||||
_state = State::READY;
|
||||
invokingPlayCallbacks();
|
||||
|
||||
} else {
|
||||
_isStreaming = true;
|
||||
_queBufferFrames = sampleRate * QUEUEBUFFER_TIME_STEP;
|
||||
BREAK_IF_ERR_LOG(_queBufferFrames == 0, "_queBufferFrames == 0");
|
||||
|
||||
const uint32_t queBufferBytes = _queBufferFrames * bytesPerFrame;
|
||||
|
||||
for (int index = 0; index < QUEUEBUFFER_NUM; ++index) {
|
||||
_queBuffers[index] = (char *)malloc(queBufferBytes);
|
||||
_queBufferSize[index] = queBufferBytes;
|
||||
|
||||
decoder.readFixedFrames(_queBufferFrames, _queBuffers[index]);
|
||||
}
|
||||
|
||||
_state = State::READY;
|
||||
}
|
||||
|
||||
} while (false);
|
||||
|
||||
if (_pcmData != nullptr) {
|
||||
CC_SAFE_FREE(_pcmData);
|
||||
}
|
||||
|
||||
decoder.close();
|
||||
|
||||
//IDEA: Why to invoke play callback first? Should it be after 'load' callback?
|
||||
invokingPlayCallbacks();
|
||||
invokingLoadCallbacks();
|
||||
|
||||
_isLoadingFinished = true;
|
||||
if (_state != State::READY) {
|
||||
_state = State::FAILED;
|
||||
if (_alBufferId != INVALID_AL_BUFFER_ID && alIsBuffer(_alBufferId)) {
|
||||
ALOGV(" id=%u readDataTask failed, delete buffer: %u", selfId, _alBufferId);
|
||||
alDeleteBuffers(1, &_alBufferId);
|
||||
_alBufferId = INVALID_AL_BUFFER_ID;
|
||||
}
|
||||
}
|
||||
|
||||
_readDataTaskMutex.unlock();
|
||||
}
|
||||
|
||||
void AudioCache::addPlayCallback(const std::function<void()> &callback) {
|
||||
std::lock_guard<std::mutex> lk(_playCallbackMutex);
|
||||
switch (_state) {
|
||||
case State::INITIAL:
|
||||
case State::LOADING:
|
||||
_playCallbacks.push_back(callback);
|
||||
break;
|
||||
|
||||
case State::READY:
|
||||
// If state is failure, we still need to invoke the callback
|
||||
// since the callback will set the 'AudioPlayer::_removeByAudioEngine' flag to true.
|
||||
case State::FAILED:
|
||||
callback();
|
||||
break;
|
||||
|
||||
default:
|
||||
ALOGE("Invalid state: %d", _state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioCache::invokingPlayCallbacks() {
|
||||
std::lock_guard<std::mutex> lk(_playCallbackMutex);
|
||||
|
||||
for (auto &&cb : _playCallbacks) {
|
||||
cb();
|
||||
}
|
||||
|
||||
_playCallbacks.clear();
|
||||
}
|
||||
|
||||
void AudioCache::addLoadCallback(const std::function<void(bool)> &callback) {
|
||||
switch (_state) {
|
||||
case State::INITIAL:
|
||||
case State::LOADING:
|
||||
_loadCallbacks.push_back(callback);
|
||||
break;
|
||||
|
||||
case State::READY:
|
||||
callback(true);
|
||||
break;
|
||||
case State::FAILED:
|
||||
callback(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
ALOGE("Invalid state: %d", _state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioCache::invokingLoadCallbacks() {
|
||||
if (*_isDestroyed) {
|
||||
ALOGV("AudioCache (%p) was destroyed, don't invoke preload callback ...", this);
|
||||
return;
|
||||
}
|
||||
|
||||
auto isDestroyed = _isDestroyed;
|
||||
auto scheduler = CC_CURRENT_ENGINE()->getScheduler();
|
||||
scheduler->performFunctionInCocosThread([&, isDestroyed]() {
|
||||
if (*isDestroyed) {
|
||||
ALOGV("invokingLoadCallbacks perform in cocos thread, AudioCache (%p) was destroyed!", this);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &&cb : _loadCallbacks) {
|
||||
cb(_state == State::READY);
|
||||
}
|
||||
|
||||
_loadCallbacks.clear();
|
||||
});
|
||||
}
|
||||
118
cocos/audio/apple/AudioDecoder.h
Normal file
118
cocos/audio/apple/AudioDecoder.h
Normal file
@@ -0,0 +1,118 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 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
|
||||
|
||||
#import <AudioToolbox/ExtendedAudioFile.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace cc {
|
||||
|
||||
/**
|
||||
* @brief The class for decoding compressed audio file to PCM buffer.
|
||||
*/
|
||||
class AudioDecoder {
|
||||
public:
|
||||
static const uint32_t INVALID_FRAME_INDEX = UINT32_MAX;
|
||||
|
||||
AudioDecoder();
|
||||
~AudioDecoder();
|
||||
|
||||
/**
|
||||
* @brief Opens an audio file specified by a file path.
|
||||
* @return true if succeed, otherwise false.
|
||||
*/
|
||||
bool open(const char *path);
|
||||
|
||||
/**
|
||||
* @brief Checks whether decoder has opened file successfully.
|
||||
* @return true if succeed, otherwise false.
|
||||
*/
|
||||
bool isOpened() const;
|
||||
|
||||
/**
|
||||
* @brief Closes opened audio file.
|
||||
* @note The method will also be automatically invoked in the destructor.
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* @brief Reads audio frames of PCM format.
|
||||
* @param framesToRead The number of frames excepted to be read.
|
||||
* @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame.
|
||||
* @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end of file.
|
||||
*/
|
||||
uint32_t read(uint32_t framesToRead, char *pcmBuf);
|
||||
|
||||
/**
|
||||
* @brief Reads fixed audio frames of PCM format.
|
||||
* @param framesToRead The number of frames excepted to be read.
|
||||
* @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame.
|
||||
* @return The number of frames actually read, it's probably less than |framesToRead|. Returns 0 means reach the end of file.
|
||||
* @note The different between |read| and |readFixedFrames| is |readFixedFrames| will do multiple reading operations if |framesToRead| frames
|
||||
* isn't filled entirely, while |read| just does reading operation once whatever |framesToRead| is or isn't filled entirely.
|
||||
* If current position reaches the end of frames, the return value may smaller than |framesToRead| and the remaining
|
||||
* buffer in |pcmBuf| will be set with silence data (0x00).
|
||||
*/
|
||||
uint32_t readFixedFrames(uint32_t framesToRead, char *pcmBuf);
|
||||
|
||||
/**
|
||||
* @brief Sets frame offest to be read.
|
||||
* @param frameOffset The frame offest to be set.
|
||||
* @return true if succeed, otherwise false
|
||||
*/
|
||||
bool seek(uint32_t frameOffset);
|
||||
|
||||
/**
|
||||
* @brief Tells the current frame offset.
|
||||
* @return The current frame offset.
|
||||
*/
|
||||
uint32_t tell() const;
|
||||
|
||||
/** Gets total frames of current audio.*/
|
||||
uint32_t getTotalFrames() const;
|
||||
|
||||
/** Gets bytes per frame of current audio.*/
|
||||
uint32_t getBytesPerFrame() const;
|
||||
|
||||
/** Gets sample rate of current audio.*/
|
||||
uint32_t getSampleRate() const;
|
||||
|
||||
/** Gets the channel count of current audio.
|
||||
* @note Currently we only support 1 or 2 channels.
|
||||
*/
|
||||
uint32_t getChannelCount() const;
|
||||
|
||||
private:
|
||||
bool _isOpened;
|
||||
ExtAudioFileRef _extRef;
|
||||
uint32_t _totalFrames;
|
||||
uint32_t _bytesPerFrame;
|
||||
uint32_t _sampleRate;
|
||||
uint32_t _channelCount;
|
||||
AudioStreamBasicDescription _outputFormat;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
203
cocos/audio/apple/AudioDecoder.mm
Normal file
203
cocos/audio/apple/AudioDecoder.mm
Normal file
@@ -0,0 +1,203 @@
|
||||
/****************************************************************************
|
||||
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.
|
||||
****************************************************************************/
|
||||
|
||||
#include "audio/apple/AudioDecoder.h"
|
||||
#include "audio/apple/AudioMacros.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#define LOG_TAG "AudioDecoder"
|
||||
|
||||
namespace cc {
|
||||
|
||||
AudioDecoder::AudioDecoder()
|
||||
: _isOpened(false), _extRef(nullptr), _totalFrames(0), _bytesPerFrame(0), _sampleRate(0), _channelCount(0) {
|
||||
memset(&_outputFormat, 0, sizeof(_outputFormat));
|
||||
}
|
||||
|
||||
AudioDecoder::~AudioDecoder() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool AudioDecoder::open(const char *path) {
|
||||
bool ret = false;
|
||||
CFURLRef fileURL = nil;
|
||||
do {
|
||||
BREAK_IF_ERR_LOG(path == nullptr || strlen(path) == 0, "Invalid path!");
|
||||
|
||||
NSString *fileFullPath = [[NSString alloc] initWithCString:path encoding:NSUTF8StringEncoding];
|
||||
fileURL = (CFURLRef)[[NSURL alloc] initFileURLWithPath:fileFullPath];
|
||||
[fileFullPath release];
|
||||
BREAK_IF_ERR_LOG(fileURL == nil, "Converting path to CFURLRef failed!");
|
||||
|
||||
OSStatus status = ExtAudioFileOpenURL(fileURL, &_extRef);
|
||||
BREAK_IF_ERR_LOG(status != noErr, "ExtAudioFileOpenURL FAILED, Error = %ld", (long)ret);
|
||||
|
||||
AudioStreamBasicDescription fileFormat;
|
||||
UInt32 propertySize = sizeof(fileFormat);
|
||||
|
||||
// Get the audio data format
|
||||
ret = ExtAudioFileGetProperty(_extRef, kExtAudioFileProperty_FileDataFormat, &propertySize, &fileFormat);
|
||||
BREAK_IF_ERR_LOG(status != noErr, "ExtAudioFileGetProperty(kExtAudioFileProperty_FileDataFormat) FAILED, Error = %ld", (long)ret);
|
||||
BREAK_IF_ERR_LOG(fileFormat.mChannelsPerFrame > 2, "Unsupported Format, channel count is greater than stereo!");
|
||||
|
||||
// Set the client format to 16 bit signed integer (native-endian) data
|
||||
// Maintain the channel count and sample rate of the original source format
|
||||
_outputFormat.mSampleRate = fileFormat.mSampleRate;
|
||||
_outputFormat.mChannelsPerFrame = fileFormat.mChannelsPerFrame;
|
||||
_outputFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
_outputFormat.mFramesPerPacket = 1;
|
||||
_outputFormat.mBitsPerChannel = 16;
|
||||
_outputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
|
||||
_sampleRate = _outputFormat.mSampleRate;
|
||||
_channelCount = _outputFormat.mChannelsPerFrame;
|
||||
_bytesPerFrame = 2 * _outputFormat.mChannelsPerFrame;
|
||||
|
||||
_outputFormat.mBytesPerPacket = _bytesPerFrame;
|
||||
_outputFormat.mBytesPerFrame = _bytesPerFrame;
|
||||
|
||||
ret = ExtAudioFileSetProperty(_extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(_outputFormat), &_outputFormat);
|
||||
BREAK_IF_ERR_LOG(status != noErr, "ExtAudioFileSetProperty FAILED, Error = %ld", (long)ret);
|
||||
|
||||
// Get the total frame count
|
||||
SInt64 totalFrames = 0;
|
||||
propertySize = sizeof(totalFrames);
|
||||
ret = ExtAudioFileGetProperty(_extRef, kExtAudioFileProperty_FileLengthFrames, &propertySize, &totalFrames);
|
||||
BREAK_IF_ERR_LOG(status != noErr, "ExtAudioFileGetProperty(kExtAudioFileProperty_FileLengthFrames) FAILED, Error = %ld", (long)ret);
|
||||
BREAK_IF_ERR_LOG(totalFrames <= 0, "Total frames is 0, it's an invalid audio file: %s", path);
|
||||
_totalFrames = static_cast<uint32_t>(totalFrames);
|
||||
_isOpened = true;
|
||||
|
||||
ret = true;
|
||||
} while (false);
|
||||
|
||||
if (fileURL != nil)
|
||||
CFRelease(fileURL);
|
||||
|
||||
if (!ret) {
|
||||
close();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AudioDecoder::close() {
|
||||
if (_extRef != nullptr) {
|
||||
ExtAudioFileDispose(_extRef);
|
||||
_extRef = nullptr;
|
||||
|
||||
_totalFrames = 0;
|
||||
_bytesPerFrame = 0;
|
||||
_sampleRate = 0;
|
||||
_channelCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t AudioDecoder::read(uint32_t framesToRead, char *pcmBuf) {
|
||||
uint32_t ret = 0;
|
||||
do {
|
||||
BREAK_IF_ERR_LOG(!isOpened(), "decoder isn't openned");
|
||||
BREAK_IF_ERR_LOG(framesToRead == INVALID_FRAME_INDEX, "frameToRead is INVALID_FRAME_INDEX");
|
||||
BREAK_IF_ERR_LOG(framesToRead == 0, "frameToRead is 0");
|
||||
BREAK_IF_ERR_LOG(pcmBuf == nullptr, "pcmBuf is nullptr");
|
||||
|
||||
AudioBufferList bufferList;
|
||||
bufferList.mNumberBuffers = 1;
|
||||
bufferList.mBuffers[0].mDataByteSize = framesToRead * _bytesPerFrame;
|
||||
bufferList.mBuffers[0].mNumberChannels = _outputFormat.mChannelsPerFrame;
|
||||
bufferList.mBuffers[0].mData = pcmBuf;
|
||||
|
||||
UInt32 frames = framesToRead;
|
||||
OSStatus status = ExtAudioFileRead(_extRef, &frames, &bufferList);
|
||||
BREAK_IF(status != noErr);
|
||||
ret = frames;
|
||||
} while (false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t AudioDecoder::readFixedFrames(uint32_t framesToRead, char *pcmBuf) {
|
||||
uint32_t framesRead = 0;
|
||||
uint32_t framesReadOnce = 0;
|
||||
do {
|
||||
framesReadOnce = read(framesToRead - framesRead, pcmBuf + framesRead * _bytesPerFrame);
|
||||
framesRead += framesReadOnce;
|
||||
} while (framesReadOnce != 0 && framesRead < framesToRead);
|
||||
|
||||
if (framesRead < framesToRead) {
|
||||
memset(pcmBuf + framesRead * _bytesPerFrame, 0x00, (framesToRead - framesRead) * _bytesPerFrame);
|
||||
}
|
||||
|
||||
return framesRead;
|
||||
}
|
||||
|
||||
bool AudioDecoder::seek(uint32_t frameOffset) {
|
||||
bool ret = false;
|
||||
do {
|
||||
BREAK_IF_ERR_LOG(!isOpened(), "decoder isn't openned");
|
||||
BREAK_IF_ERR_LOG(frameOffset == INVALID_FRAME_INDEX, "frameIndex is INVALID_FRAME_INDEX");
|
||||
|
||||
OSStatus status = ExtAudioFileSeek(_extRef, frameOffset);
|
||||
BREAK_IF(status != noErr);
|
||||
ret = true;
|
||||
} while (false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t AudioDecoder::tell() const {
|
||||
uint32_t ret = INVALID_FRAME_INDEX;
|
||||
do {
|
||||
BREAK_IF_ERR_LOG(!isOpened(), "decoder isn't openned");
|
||||
SInt64 frameIndex = INVALID_FRAME_INDEX;
|
||||
OSStatus status = ExtAudioFileTell(_extRef, &frameIndex);
|
||||
BREAK_IF(status != noErr);
|
||||
ret = static_cast<uint32_t>(frameIndex);
|
||||
} while (false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t AudioDecoder::getTotalFrames() const {
|
||||
return _totalFrames;
|
||||
}
|
||||
|
||||
uint32_t AudioDecoder::getBytesPerFrame() const {
|
||||
return _bytesPerFrame;
|
||||
}
|
||||
|
||||
uint32_t AudioDecoder::getSampleRate() const {
|
||||
return _sampleRate;
|
||||
}
|
||||
|
||||
uint32_t AudioDecoder::getChannelCount() const {
|
||||
return _channelCount;
|
||||
}
|
||||
|
||||
bool AudioDecoder::isOpened() const {
|
||||
return _isOpened;
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
91
cocos/audio/apple/AudioEngine-inl.h
Normal file
91
cocos/audio/apple/AudioEngine-inl.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 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/apple/AudioCache.h"
|
||||
#include "audio/apple/AudioPlayer.h"
|
||||
#include "audio/include/AudioDef.h"
|
||||
#include "base/RefCounted.h"
|
||||
#include "base/std/container/list.h"
|
||||
#include "base/std/container/unordered_map.h"
|
||||
|
||||
namespace cc {
|
||||
class Scheduler;
|
||||
|
||||
#define MAX_AUDIOINSTANCES 24
|
||||
|
||||
class AudioEngineImpl : public cc::RefCounted {
|
||||
public:
|
||||
AudioEngineImpl();
|
||||
~AudioEngineImpl();
|
||||
|
||||
bool init();
|
||||
int play2d(const ccstd::string &fileFullPath, bool loop, float volume);
|
||||
void setVolume(int audioID, float volume);
|
||||
void setLoop(int audioID, bool loop);
|
||||
bool pause(int audioID);
|
||||
bool resume(int audioID);
|
||||
void stop(int audioID);
|
||||
void stopAll();
|
||||
float getDuration(int audioID);
|
||||
float getDurationFromFile(const ccstd::string &fileFullPath);
|
||||
float getCurrentTime(int audioID);
|
||||
bool setCurrentTime(int audioID, float time);
|
||||
void setFinishCallback(int audioID, const std::function<void(int, const ccstd::string &)> &callback);
|
||||
|
||||
void uncache(const ccstd::string &filePath);
|
||||
void uncacheAll();
|
||||
AudioCache *preload(const ccstd::string &filePath, std::function<void(bool)> callback);
|
||||
void update(float dt);
|
||||
|
||||
PCMHeader getPCMHeader(const char *url);
|
||||
std::vector<uint8_t> getOriginalPCMBuffer(const char *url, uint32_t channelID);
|
||||
|
||||
private:
|
||||
bool checkAudioIdValid(int audioID);
|
||||
void play2dImpl(AudioCache *cache, int audioID);
|
||||
ALuint findValidSource();
|
||||
|
||||
static ALvoid myAlSourceNotificationCallback(ALuint sid, ALuint notificationID, ALvoid *userData);
|
||||
|
||||
ALuint _alSources[MAX_AUDIOINSTANCES];
|
||||
|
||||
//source,used
|
||||
ccstd::list<ALuint> _unusedSourcesPool;
|
||||
|
||||
//filePath,bufferInfo
|
||||
ccstd::unordered_map<ccstd::string, AudioCache> _audioCaches;
|
||||
|
||||
//audioID,AudioInfo
|
||||
ccstd::unordered_map<int, AudioPlayer *> _audioPlayers;
|
||||
std::mutex _threadMutex;
|
||||
|
||||
bool _lazyInitLoop;
|
||||
|
||||
int _currentAudioID;
|
||||
std::weak_ptr<Scheduler> _scheduler;
|
||||
};
|
||||
} // namespace cc
|
||||
789
cocos/audio/apple/AudioEngine-inl.mm
Normal file
789
cocos/audio/apple/AudioEngine-inl.mm
Normal file
@@ -0,0 +1,789 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-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.
|
||||
****************************************************************************/
|
||||
|
||||
#define LOG_TAG "AudioEngine-inl.mm"
|
||||
#include "audio/apple/AudioEngine-inl.h"
|
||||
|
||||
#import <OpenAL/alc.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#if CC_PLATFORM == CC_PLATFORM_IOS
|
||||
#import <UIKit/UIApplication.h>
|
||||
#endif
|
||||
|
||||
#include "audio/include/AudioEngine.h"
|
||||
#include "application/ApplicationManager.h"
|
||||
#include "base/Scheduler.h"
|
||||
#include "base/Utils.h"
|
||||
#include "base/memory/Memory.h"
|
||||
#include "platform/FileUtils.h"
|
||||
#include "AudioDecoder.h"
|
||||
|
||||
using namespace cc;
|
||||
|
||||
static ALCdevice *s_ALDevice = nullptr;
|
||||
static ALCcontext *s_ALContext = nullptr;
|
||||
static AudioEngineImpl *s_instance = nullptr;
|
||||
|
||||
typedef ALvoid (*alSourceNotificationProc)(ALuint sid, ALuint notificationID, ALvoid *userData);
|
||||
typedef ALenum (*alSourceAddNotificationProcPtr)(ALuint sid, ALuint notificationID, alSourceNotificationProc notifyProc, ALvoid *userData);
|
||||
static ALenum alSourceAddNotificationExt(ALuint sid, ALuint notificationID, alSourceNotificationProc notifyProc, ALvoid *userData) {
|
||||
static alSourceAddNotificationProcPtr proc = nullptr;
|
||||
|
||||
if (proc == nullptr) {
|
||||
proc = (alSourceAddNotificationProcPtr)alcGetProcAddress(nullptr, "alSourceAddNotification");
|
||||
}
|
||||
|
||||
if (proc) {
|
||||
return proc(sid, notificationID, notifyProc, userData);
|
||||
}
|
||||
return AL_INVALID_VALUE;
|
||||
}
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_IOS
|
||||
@interface AudioEngineSessionHandler : NSObject {
|
||||
}
|
||||
|
||||
- (id)init;
|
||||
- (void)handleInterruption:(NSNotification *)notification;
|
||||
|
||||
@end
|
||||
|
||||
@implementation AudioEngineSessionHandler
|
||||
|
||||
void AudioEngineInterruptionListenerCallback(void *user_data, UInt32 interruption_state) {
|
||||
if (kAudioSessionBeginInterruption == interruption_state) {
|
||||
alcMakeContextCurrent(nullptr);
|
||||
} else if (kAudioSessionEndInterruption == interruption_state) {
|
||||
OSStatus result = AudioSessionSetActive(true);
|
||||
if (result) NSLog(@"Error setting audio session active! %d\n", static_cast<int>(result));
|
||||
|
||||
alcMakeContextCurrent(s_ALContext);
|
||||
}
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
if (self = [super init]) {
|
||||
if ([[[UIDevice currentDevice] systemVersion] intValue] > 5) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:UIApplicationWillResignActiveNotification object:nil];
|
||||
}
|
||||
else {
|
||||
AudioSessionInitialize(NULL, NULL, AudioEngineInterruptionListenerCallback, self);
|
||||
}
|
||||
|
||||
BOOL success = [[AVAudioSession sharedInstance]
|
||||
setCategory:AVAudioSessionCategoryPlayback
|
||||
error:nil];
|
||||
if (!success)
|
||||
ALOGE("Fail to set audio session.");
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)handleInterruption:(NSNotification *)notification {
|
||||
static bool isAudioSessionInterrupted = false;
|
||||
static bool resumeOnBecomingActive = false;
|
||||
static bool pauseOnResignActive = false;
|
||||
|
||||
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
|
||||
NSInteger reason = [[[notification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey] integerValue];
|
||||
if (reason == AVAudioSessionInterruptionTypeBegan) {
|
||||
isAudioSessionInterrupted = true;
|
||||
alcMakeContextCurrent(nullptr);
|
||||
|
||||
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
|
||||
ALOGD("AVAudioSessionInterruptionTypeBegan, application == UIApplicationStateActive, pauseOnResignActive = true");
|
||||
pauseOnResignActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (reason == AVAudioSessionInterruptionTypeEnded) {
|
||||
isAudioSessionInterrupted = false;
|
||||
|
||||
NSError *error = nil;
|
||||
[[AVAudioSession sharedInstance] setActive:YES error:&error];
|
||||
alcMakeContextCurrent(s_ALContext);
|
||||
|
||||
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) {
|
||||
ALOGD("AVAudioSessionInterruptionTypeEnded, application != UIApplicationStateActive, resumeOnBecomingActive = true");
|
||||
resumeOnBecomingActive = true;
|
||||
}
|
||||
}
|
||||
} else if ([notification.name isEqualToString:UIApplicationWillResignActiveNotification]) {
|
||||
ALOGD("UIApplicationWillResignActiveNotification");
|
||||
if (pauseOnResignActive) {
|
||||
pauseOnResignActive = false;
|
||||
ALOGD("UIApplicationWillResignActiveNotification, alcMakeContextCurrent(nullptr)");
|
||||
alcMakeContextCurrent(nullptr);
|
||||
}
|
||||
} else if ([notification.name isEqualToString:UIApplicationDidBecomeActiveNotification]) {
|
||||
ALOGD("UIApplicationDidBecomeActiveNotification");
|
||||
if (resumeOnBecomingActive) {
|
||||
resumeOnBecomingActive = false;
|
||||
ALOGD("UIApplicationDidBecomeActiveNotification, alcMakeContextCurrent(s_ALContext)");
|
||||
NSError *error = nil;
|
||||
BOOL success = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
|
||||
if (!success) {
|
||||
ALOGE("Fail to set audio session.");
|
||||
return;
|
||||
}
|
||||
[[AVAudioSession sharedInstance] setActive:YES error:&error];
|
||||
alcMakeContextCurrent(s_ALContext);
|
||||
} else if (isAudioSessionInterrupted) {
|
||||
isAudioSessionInterrupted = false;
|
||||
ALOGD("UIApplicationDidBecomeActiveNotification, alcMakeContextCurrent(s_ALContext)");
|
||||
NSError *error = nil;
|
||||
BOOL success = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&error];
|
||||
if (!success) {
|
||||
ALOGE("Fail to set audio session.");
|
||||
return;
|
||||
}
|
||||
[[AVAudioSession sharedInstance] setActive:YES error:&error];
|
||||
alcMakeContextCurrent(s_ALContext);
|
||||
|
||||
// ALOGD("Audio session is still interrupted, pause director!");
|
||||
//IDEA: Director::getInstance()->pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionInterruptionNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
@end
|
||||
|
||||
static id s_AudioEngineSessionHandler = nullptr;
|
||||
#endif
|
||||
|
||||
ALvoid AudioEngineImpl::myAlSourceNotificationCallback(ALuint sid, ALuint notificationID, ALvoid *userData) {
|
||||
// Currently, we only care about AL_BUFFERS_PROCESSED event
|
||||
if (notificationID != AL_BUFFERS_PROCESSED)
|
||||
return;
|
||||
|
||||
AudioPlayer *player = nullptr;
|
||||
s_instance->_threadMutex.lock();
|
||||
for (const auto &e : s_instance->_audioPlayers) {
|
||||
player = e.second;
|
||||
if (player->_alSource == sid && player->_streamingSource) {
|
||||
player->wakeupRotateThread();
|
||||
}
|
||||
}
|
||||
s_instance->_threadMutex.unlock();
|
||||
}
|
||||
|
||||
AudioEngineImpl::AudioEngineImpl()
|
||||
: _lazyInitLoop(true), _currentAudioID(0) {
|
||||
s_instance = this;
|
||||
}
|
||||
|
||||
AudioEngineImpl::~AudioEngineImpl() {
|
||||
if (auto sche = _scheduler.lock()) {
|
||||
sche->unschedule("AudioEngine", this);
|
||||
}
|
||||
|
||||
if (s_ALContext) {
|
||||
alDeleteSources(MAX_AUDIOINSTANCES, _alSources);
|
||||
|
||||
_audioCaches.clear();
|
||||
|
||||
alcMakeContextCurrent(nullptr);
|
||||
alcDestroyContext(s_ALContext);
|
||||
}
|
||||
if (s_ALDevice) {
|
||||
alcCloseDevice(s_ALDevice);
|
||||
}
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_IOS
|
||||
[s_AudioEngineSessionHandler release];
|
||||
#endif
|
||||
s_instance = nullptr;
|
||||
}
|
||||
|
||||
bool AudioEngineImpl::init() {
|
||||
bool ret = false;
|
||||
do {
|
||||
#if CC_PLATFORM == CC_PLATFORM_IOS
|
||||
s_AudioEngineSessionHandler = [[AudioEngineSessionHandler alloc] init];
|
||||
#endif
|
||||
|
||||
s_ALDevice = alcOpenDevice(nullptr);
|
||||
|
||||
if (s_ALDevice) {
|
||||
s_ALContext = alcCreateContext(s_ALDevice, nullptr);
|
||||
alcMakeContextCurrent(s_ALContext);
|
||||
|
||||
alGenSources(MAX_AUDIOINSTANCES, _alSources);
|
||||
auto alError = alGetError();
|
||||
if (alError != AL_NO_ERROR) {
|
||||
ALOGE("%s:generating sources failed! error = %x", __PRETTY_FUNCTION__, alError);
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_AUDIOINSTANCES; ++i) {
|
||||
_unusedSourcesPool.push_back(_alSources[i]);
|
||||
alSourceAddNotificationExt(_alSources[i], AL_BUFFERS_PROCESSED, myAlSourceNotificationCallback, nullptr);
|
||||
}
|
||||
|
||||
// fixed #16170: Random crash in alGenBuffers(AudioCache::readDataTask) at startup
|
||||
// Please note that, as we know the OpenAL operation is atomic (threadsafe),
|
||||
// 'alGenBuffers' may be invoked by different threads. But in current implementation of 'alGenBuffers',
|
||||
// When the first time it's invoked, application may crash!!!
|
||||
// Why? OpenAL is opensource by Apple and could be found at
|
||||
// http://opensource.apple.com/source/OpenAL/OpenAL-48.7/Source/OpenAL/oalImp.cpp .
|
||||
/*
|
||||
|
||||
void InitializeBufferMap()
|
||||
{
|
||||
if (gOALBufferMap == NULL) // Position 1
|
||||
{
|
||||
gOALBufferMap = ccnew OALBufferMap (); // Position 2
|
||||
|
||||
// Position Gap
|
||||
|
||||
gBufferMapLock = ccnew CAGuard("OAL:BufferMapLock"); // Position 3
|
||||
gDeadOALBufferMap = ccnew OALBufferMap ();
|
||||
|
||||
OALBuffer *newBuffer = ccnew OALBuffer (AL_NONE);
|
||||
gOALBufferMap->Add(AL_NONE, &newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGenBuffers(ALsizei n, ALuint *bids)
|
||||
{
|
||||
...
|
||||
|
||||
try {
|
||||
if (n < 0)
|
||||
throw ((OSStatus) AL_INVALID_VALUE);
|
||||
|
||||
InitializeBufferMap();
|
||||
if (gOALBufferMap == NULL)
|
||||
throw ((OSStatus) AL_INVALID_OPERATION);
|
||||
|
||||
CAGuard::Locker locked(*gBufferMapLock); // Position 4
|
||||
...
|
||||
...
|
||||
}
|
||||
|
||||
*/
|
||||
// 'gBufferMapLock' will be initialized in the 'InitializeBufferMap' function,
|
||||
// that's the problem. It means that 'InitializeBufferMap' may be invoked in different threads.
|
||||
// It will be very dangerous in multi-threads environment.
|
||||
// Imagine there're two threads (Thread A, Thread B), they call 'alGenBuffers' simultaneously.
|
||||
// While A goto 'Position Gap', 'gOALBufferMap' was assigned, then B goto 'Position 1' and find
|
||||
// that 'gOALBufferMap' isn't NULL, B just jump over 'InitialBufferMap' and goto 'Position 4'.
|
||||
// Meanwhile, A is still at 'Position Gap', B will crash at '*gBufferMapLock' since 'gBufferMapLock'
|
||||
// is still a null pointer. Oops, how could Apple implemented this method in this fucking way?
|
||||
|
||||
// Workaround is do an unused invocation in the mainthread right after OpenAL is initialized successfully
|
||||
// as bellow.
|
||||
// ================ Workaround begin ================ //
|
||||
|
||||
ALuint unusedAlBufferId = 0;
|
||||
alGenBuffers(1, &unusedAlBufferId);
|
||||
alDeleteBuffers(1, &unusedAlBufferId);
|
||||
|
||||
// ================ Workaround end ================ //
|
||||
|
||||
_scheduler = CC_CURRENT_ENGINE()->getScheduler();
|
||||
ret = true;
|
||||
ALOGI("OpenAL was initialized successfully!");
|
||||
}
|
||||
} while (false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
AudioCache *AudioEngineImpl::preload(const ccstd::string &filePath, std::function<void(bool)> callback) {
|
||||
AudioCache *audioCache = nullptr;
|
||||
|
||||
auto it = _audioCaches.find(filePath);
|
||||
if (it == _audioCaches.end()) {
|
||||
audioCache = &_audioCaches[filePath];
|
||||
audioCache->_fileFullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
|
||||
unsigned int cacheId = audioCache->_id;
|
||||
auto isCacheDestroyed = audioCache->_isDestroyed;
|
||||
AudioEngine::addTask([audioCache, cacheId, isCacheDestroyed]() {
|
||||
if (*isCacheDestroyed) {
|
||||
ALOGV("AudioCache (id=%u) was destroyed, no need to launch readDataTask.", cacheId);
|
||||
audioCache->setSkipReadDataTask(true);
|
||||
return;
|
||||
}
|
||||
audioCache->readDataTask(cacheId);
|
||||
});
|
||||
} else {
|
||||
audioCache = &it->second;
|
||||
}
|
||||
|
||||
if (audioCache && callback) {
|
||||
audioCache->addLoadCallback(callback);
|
||||
}
|
||||
return audioCache;
|
||||
}
|
||||
|
||||
int AudioEngineImpl::play2d(const ccstd::string &filePath, bool loop, float volume) {
|
||||
if (s_ALDevice == nullptr) {
|
||||
return AudioEngine::INVALID_AUDIO_ID;
|
||||
}
|
||||
|
||||
ALuint alSource = findValidSource();
|
||||
if (alSource == AL_INVALID) {
|
||||
return AudioEngine::INVALID_AUDIO_ID;
|
||||
}
|
||||
|
||||
auto *player = ccnew AudioPlayer;
|
||||
if (player == nullptr) {
|
||||
return AudioEngine::INVALID_AUDIO_ID;
|
||||
}
|
||||
|
||||
player->_alSource = alSource;
|
||||
player->_loop = loop;
|
||||
player->_volume = volume;
|
||||
|
||||
auto audioCache = preload(filePath, nullptr);
|
||||
if (audioCache == nullptr) {
|
||||
delete player;
|
||||
return AudioEngine::INVALID_AUDIO_ID;
|
||||
}
|
||||
|
||||
player->setCache(audioCache);
|
||||
_threadMutex.lock();
|
||||
_audioPlayers[_currentAudioID] = player;
|
||||
_threadMutex.unlock();
|
||||
|
||||
audioCache->addPlayCallback(std::bind(&AudioEngineImpl::play2dImpl, this, audioCache, _currentAudioID));
|
||||
|
||||
if (_lazyInitLoop) {
|
||||
_lazyInitLoop = false;
|
||||
if (auto sche = _scheduler.lock()) {
|
||||
sche->schedule(CC_CALLBACK_1(AudioEngineImpl::update, this), this, 0.05f, false, "AudioEngine");
|
||||
}
|
||||
}
|
||||
|
||||
return _currentAudioID++;
|
||||
}
|
||||
|
||||
void AudioEngineImpl::play2dImpl(AudioCache *cache, int audioID) {
|
||||
//Note: It may be in sub thread or main thread :(
|
||||
if (!*cache->_isDestroyed && cache->_state == AudioCache::State::READY) {
|
||||
_threadMutex.lock();
|
||||
auto playerIt = _audioPlayers.find(audioID);
|
||||
if (playerIt != _audioPlayers.end()) {
|
||||
// Trust it, or assert it out.
|
||||
bool res = playerIt->second->play2d();
|
||||
CC_ASSERT(res);
|
||||
}
|
||||
_threadMutex.unlock();
|
||||
} else {
|
||||
ALOGD("AudioEngineImpl::play2dImpl, cache was destroyed or not ready!");
|
||||
auto iter = _audioPlayers.find(audioID);
|
||||
if (iter != _audioPlayers.end()) {
|
||||
iter->second->_removeByAudioEngine = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ALuint AudioEngineImpl::findValidSource() {
|
||||
ALuint sourceId = AL_INVALID;
|
||||
if (!_unusedSourcesPool.empty()) {
|
||||
sourceId = _unusedSourcesPool.front();
|
||||
_unusedSourcesPool.pop_front();
|
||||
}
|
||||
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
void AudioEngineImpl::setVolume(int audioID, float volume) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return;
|
||||
}
|
||||
auto player = _audioPlayers[audioID];
|
||||
player->_volume = volume;
|
||||
|
||||
if (player->_ready) {
|
||||
alSourcef(_audioPlayers[audioID]->_alSource, AL_GAIN, volume);
|
||||
|
||||
auto error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__, audioID, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngineImpl::setLoop(int audioID, bool loop) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return;
|
||||
}
|
||||
auto player = _audioPlayers[audioID];
|
||||
|
||||
if (player->_ready) {
|
||||
if (player->_streamingSource) {
|
||||
player->setLoop(loop);
|
||||
} else {
|
||||
if (loop) {
|
||||
alSourcei(player->_alSource, AL_LOOPING, AL_TRUE);
|
||||
} else {
|
||||
alSourcei(player->_alSource, AL_LOOPING, AL_FALSE);
|
||||
}
|
||||
|
||||
auto error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__, audioID, error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
player->_loop = loop;
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioEngineImpl::pause(int audioID) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return false;
|
||||
}
|
||||
bool ret = true;
|
||||
alSourcePause(_audioPlayers[audioID]->_alSource);
|
||||
|
||||
auto error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
ret = false;
|
||||
ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__, audioID, error);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AudioEngineImpl::resume(int audioID) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return false;
|
||||
}
|
||||
bool ret = true;
|
||||
alSourcePlay(_audioPlayers[audioID]->_alSource);
|
||||
|
||||
auto error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
ret = false;
|
||||
ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__, audioID, error);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AudioEngineImpl::stop(int audioID) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return;
|
||||
}
|
||||
auto player = _audioPlayers[audioID];
|
||||
player->destroy();
|
||||
|
||||
// Call 'update' method to cleanup immediately since the schedule may be cancelled without any notification.
|
||||
update(0.0f);
|
||||
}
|
||||
|
||||
void AudioEngineImpl::stopAll() {
|
||||
for (auto &&player : _audioPlayers) {
|
||||
player.second->destroy();
|
||||
}
|
||||
|
||||
// Call 'update' method to cleanup immediately since the schedule may be cancelled without any notification.
|
||||
update(0.0f);
|
||||
}
|
||||
|
||||
float AudioEngineImpl::getDuration(int audioID) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return 0.0f;
|
||||
}
|
||||
auto player = _audioPlayers[audioID];
|
||||
if (player->_ready) {
|
||||
return player->_audioCache->_duration;
|
||||
} else {
|
||||
return AudioEngine::TIME_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
float AudioEngineImpl::getDurationFromFile(const ccstd::string &filePath) {
|
||||
auto it = _audioCaches.find(filePath);
|
||||
if (it == _audioCaches.end()) {
|
||||
this->preload(filePath, nullptr);
|
||||
return AudioEngine::TIME_UNKNOWN;
|
||||
}
|
||||
|
||||
return it->second._duration;
|
||||
}
|
||||
|
||||
float AudioEngineImpl::getCurrentTime(int audioID) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return 0.0f;
|
||||
}
|
||||
float ret = 0.0f;
|
||||
auto player = _audioPlayers[audioID];
|
||||
if (player->_ready) {
|
||||
if (player->_streamingSource) {
|
||||
ret = player->getTime();
|
||||
} else {
|
||||
alGetSourcef(player->_alSource, AL_SEC_OFFSET, &ret);
|
||||
|
||||
auto error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
ALOGE("%s, audio id:%d,error code:%x", __PRETTY_FUNCTION__, audioID, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AudioEngineImpl::setCurrentTime(int audioID, float time) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return false;
|
||||
}
|
||||
bool ret = false;
|
||||
auto player = _audioPlayers[audioID];
|
||||
|
||||
do {
|
||||
if (!player->_ready) {
|
||||
std::lock_guard<std::mutex> lck(player->_play2dMutex);// To prevent the race condition
|
||||
player->_timeDirty = true;
|
||||
player->_currTime = time;
|
||||
break;
|
||||
}
|
||||
|
||||
if (player->_streamingSource) {
|
||||
ret = player->setTime(time);
|
||||
break;
|
||||
} else {
|
||||
if (player->_audioCache->_framesRead != player->_audioCache->_totalFrames &&
|
||||
(time * player->_audioCache->_sampleRate) > player->_audioCache->_framesRead) {
|
||||
ALOGE("%s: audio id = %d", __PRETTY_FUNCTION__, audioID);
|
||||
break;
|
||||
}
|
||||
|
||||
alSourcef(player->_alSource, AL_SEC_OFFSET, time);
|
||||
|
||||
auto error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__, audioID, error);
|
||||
}
|
||||
ret = true;
|
||||
}
|
||||
} while (0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AudioEngineImpl::setFinishCallback(int audioID, const std::function<void(int, const ccstd::string &)> &callback) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return;
|
||||
}
|
||||
_audioPlayers[audioID]->_finishCallbak = callback;
|
||||
}
|
||||
|
||||
void AudioEngineImpl::update(float dt) {
|
||||
ALint sourceState;
|
||||
int audioID;
|
||||
AudioPlayer *player;
|
||||
ALuint alSource;
|
||||
|
||||
// ALOGV("AudioPlayer count: %d", (int)_audioPlayers.size());
|
||||
|
||||
for (auto it = _audioPlayers.begin(); it != _audioPlayers.end();) {
|
||||
audioID = it->first;
|
||||
player = it->second;
|
||||
alSource = player->_alSource;
|
||||
alGetSourcei(alSource, AL_SOURCE_STATE, &sourceState);
|
||||
|
||||
if (player->_removeByAudioEngine) {
|
||||
AudioEngine::remove(audioID);
|
||||
_threadMutex.lock();
|
||||
it = _audioPlayers.erase(it);
|
||||
_threadMutex.unlock();
|
||||
delete player;
|
||||
_unusedSourcesPool.push_back(alSource);
|
||||
} else if (player->_ready && sourceState == AL_STOPPED) {
|
||||
ccstd::string filePath;
|
||||
if (player->_finishCallbak) {
|
||||
auto &audioInfo = AudioEngine::sAudioIDInfoMap[audioID];
|
||||
filePath = *audioInfo.filePath;
|
||||
}
|
||||
|
||||
AudioEngine::remove(audioID);
|
||||
_threadMutex.lock();
|
||||
it = _audioPlayers.erase(it);
|
||||
_threadMutex.unlock();
|
||||
|
||||
if (auto sche = _scheduler.lock()) {
|
||||
if (player->_finishCallbak) {
|
||||
auto cb = player->_finishCallbak;
|
||||
sche->performFunctionInCocosThread([audioID, cb, filePath]() {
|
||||
cb(audioID, filePath); //IDEA: callback will delay 50ms
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
delete player;
|
||||
_unusedSourcesPool.push_back(alSource);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (_audioPlayers.empty()) {
|
||||
_lazyInitLoop = true;
|
||||
if (auto sche = _scheduler.lock()) {
|
||||
sche->unschedule("AudioEngine", this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngineImpl::uncache(const ccstd::string &filePath) {
|
||||
_audioCaches.erase(filePath);
|
||||
}
|
||||
|
||||
void AudioEngineImpl::uncacheAll() {
|
||||
_audioCaches.clear();
|
||||
}
|
||||
|
||||
bool AudioEngineImpl::checkAudioIdValid(int audioID) {
|
||||
return _audioPlayers.find(audioID) != _audioPlayers.end();
|
||||
}
|
||||
|
||||
PCMHeader AudioEngineImpl::getPCMHeader(const char *url){
|
||||
PCMHeader header {};
|
||||
auto itr = _audioCaches.find(url);
|
||||
if (itr != _audioCaches.end() && itr->second._state == AudioCache::State::READY) {
|
||||
CC_LOG_DEBUG("file %s found in cache, load header directly", url);
|
||||
auto cache = &itr->second;
|
||||
header.bytesPerFrame = cache->_bytesPerFrame;
|
||||
header.channelCount = cache->_channelCount;
|
||||
header.dataFormat = AudioDataFormat::SIGNED_16;
|
||||
header.sampleRate = cache->_sampleRate;
|
||||
header.totalFrames = cache->_totalFrames;
|
||||
return header;
|
||||
}
|
||||
ccstd::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url);
|
||||
if (fileFullPath == "") {
|
||||
CC_LOG_DEBUG("file %s does not exist or failed to load", url);
|
||||
return header;
|
||||
}
|
||||
AudioDecoder decoder;
|
||||
do {
|
||||
if (!decoder.open(fileFullPath.c_str())) {
|
||||
CC_LOG_ERROR("[Audio Decoder] File open failed %s", url);
|
||||
break;
|
||||
}
|
||||
header.bytesPerFrame = decoder.getBytesPerFrame();
|
||||
header.channelCount = decoder.getChannelCount();
|
||||
header.dataFormat = AudioDataFormat::SIGNED_16;
|
||||
header.sampleRate = decoder.getSampleRate();
|
||||
header.totalFrames = decoder.getTotalFrames();
|
||||
} while (false);
|
||||
|
||||
decoder.close();
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
ccstd::vector<uint8_t> AudioEngineImpl::getOriginalPCMBuffer(const char *url, uint32_t channelID) {
|
||||
ccstd::vector<uint8_t> pcmData;
|
||||
auto itr = _audioCaches.find(url);
|
||||
if (itr != _audioCaches.end() && itr->second._state == AudioCache::State::READY) {
|
||||
auto cache = &itr->second;
|
||||
auto bytesPerChannelInFrame = cache->_bytesPerFrame / cache->_channelCount;
|
||||
pcmData.resize(bytesPerChannelInFrame * cache->_totalFrames);
|
||||
auto *p = pcmData.data();
|
||||
if (!cache->isStreaming()) { // Cache contains a fully prepared buffer.
|
||||
for (int itr = 0; itr < cache->_totalFrames; itr++) {
|
||||
memcpy(p, cache->_pcmData + itr * cache->_bytesPerFrame + channelID * bytesPerChannelInFrame, bytesPerChannelInFrame);
|
||||
p += bytesPerChannelInFrame;
|
||||
}
|
||||
return pcmData;
|
||||
}
|
||||
}
|
||||
ccstd::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url);
|
||||
if (fileFullPath.empty()) {
|
||||
CC_LOG_DEBUG("file %s does not exist or failed to load", url);
|
||||
return pcmData;
|
||||
}
|
||||
AudioDecoder decoder;
|
||||
|
||||
do {
|
||||
if (!decoder.open(fileFullPath.c_str())) {
|
||||
CC_LOG_ERROR("[Audio Decoder] File open failed %s", url);
|
||||
break;
|
||||
}
|
||||
const uint32_t bytesPerFrame = decoder.getBytesPerFrame();
|
||||
const uint32_t channelCount = decoder.getChannelCount();
|
||||
if (channelID >= channelCount) {
|
||||
CC_LOG_ERROR("channelID invalid, total channel count is %d but %d is required", channelCount, channelID);
|
||||
break;
|
||||
}
|
||||
uint32_t totalFrames = decoder.getTotalFrames();
|
||||
uint32_t remainingFrames = totalFrames;
|
||||
uint32_t framesRead = 0;
|
||||
uint32_t framesToReadOnce = std::min(totalFrames, static_cast<uint32_t>(decoder.getSampleRate() * QUEUEBUFFER_TIME_STEP * QUEUEBUFFER_NUM));
|
||||
const uint32_t bytesPerChannelInFrame = bytesPerFrame / channelCount;
|
||||
|
||||
pcmData.resize(bytesPerChannelInFrame * totalFrames);
|
||||
uint8_t *p = pcmData.data();
|
||||
|
||||
auto tmpBuf = static_cast<char *>(malloc(framesToReadOnce * bytesPerFrame));
|
||||
|
||||
while (remainingFrames > 0) {
|
||||
framesToReadOnce = std::min(framesToReadOnce, remainingFrames);
|
||||
framesRead = decoder.read(framesToReadOnce, tmpBuf);
|
||||
for (int itr = 0; itr < framesToReadOnce; itr++) {
|
||||
memcpy(p, tmpBuf + itr * bytesPerFrame + channelID * bytesPerChannelInFrame, bytesPerChannelInFrame);
|
||||
p += bytesPerChannelInFrame;
|
||||
}
|
||||
remainingFrames -= framesToReadOnce;
|
||||
|
||||
};
|
||||
free(tmpBuf);
|
||||
// Adjust total frames by setting position to the end of frames and try to read more data.
|
||||
// This is a workaround for https://github.com/cocos2d/cocos2d-x/issues/16938
|
||||
if (decoder.seek(totalFrames)) {
|
||||
tmpBuf = static_cast<char *>(malloc(bytesPerFrame * framesToReadOnce));
|
||||
do {
|
||||
framesRead = decoder.read(framesToReadOnce, tmpBuf); //read one by one to easy divide
|
||||
if (framesRead > 0) { // Adjust frames exist
|
||||
// transfer char data to float data
|
||||
for (int itr = 0; itr < framesRead; itr++) {
|
||||
memcpy(p, tmpBuf + itr * bytesPerFrame + channelID * bytesPerChannelInFrame, bytesPerChannelInFrame);
|
||||
p += bytesPerChannelInFrame;
|
||||
}
|
||||
}
|
||||
} while (framesRead > 0);
|
||||
free(tmpBuf);
|
||||
}
|
||||
BREAK_IF_ERR_LOG(!decoder.seek(0), "AudioDecoder::seek(0) failed!");
|
||||
} while (false);
|
||||
decoder.close();
|
||||
return pcmData;
|
||||
}
|
||||
67
cocos/audio/apple/AudioMacros.h
Normal file
67
cocos/audio/apple/AudioMacros.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define QUEUEBUFFER_NUM (4)
|
||||
#define QUEUEBUFFER_TIME_STEP (0.05f)
|
||||
|
||||
#define QUOTEME_(x) #x
|
||||
#define QUOTEME(x) QUOTEME_(x)
|
||||
|
||||
#if defined(CC_DEBUG) && CC_DEBUG > 0
|
||||
#define ALOGV(fmt, ...) printf("V/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__)
|
||||
#else
|
||||
#define ALOGV(fmt, ...) \
|
||||
do { \
|
||||
} while (false)
|
||||
#endif
|
||||
#define ALOGD(fmt, ...) printf("D/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__)
|
||||
#define ALOGI(fmt, ...) printf("I/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__)
|
||||
#define ALOGW(fmt, ...) printf("W/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__)
|
||||
#define ALOGE(fmt, ...) printf("E/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__)
|
||||
|
||||
#if defined(CC_DEBUG) && CC_DEBUG > 0
|
||||
#define CHECK_AL_ERROR_DEBUG() \
|
||||
do { \
|
||||
ALenum __error = alGetError(); \
|
||||
if (__error) { \
|
||||
ALOGE("OpenAL error 0x%04X in %s %s %d\n", __error, __FILE__, __FUNCTION__, __LINE__); \
|
||||
} \
|
||||
} while (false)
|
||||
#else
|
||||
#define CHECK_AL_ERROR_DEBUG()
|
||||
#endif
|
||||
|
||||
#define BREAK_IF(condition) \
|
||||
if (!!(condition)) { \
|
||||
break; \
|
||||
}
|
||||
|
||||
#define BREAK_IF_ERR_LOG(condition, fmt, ...) \
|
||||
if (!!(condition)) { \
|
||||
ALOGE("(" QUOTEME(condition) ") failed, message: " fmt, ##__VA_ARGS__); \
|
||||
break; \
|
||||
}
|
||||
88
cocos/audio/apple/AudioPlayer.h
Normal file
88
cocos/audio/apple/AudioPlayer.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 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/apple/AudioMacros.h"
|
||||
#include "base/Macros.h"
|
||||
|
||||
#include <OpenAL/al.h>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include "base/std/container/string.h"
|
||||
|
||||
namespace cc {
|
||||
class AudioCache;
|
||||
class AudioEngineImpl;
|
||||
|
||||
class AudioPlayer {
|
||||
public:
|
||||
AudioPlayer();
|
||||
~AudioPlayer();
|
||||
|
||||
void destroy();
|
||||
|
||||
//queue buffer related stuff
|
||||
bool setTime(float time);
|
||||
float getTime() { return _currTime; }
|
||||
bool setLoop(bool loop);
|
||||
|
||||
protected:
|
||||
void setCache(AudioCache *cache);
|
||||
void rotateBufferThread(int offsetFrame);
|
||||
bool play2d();
|
||||
void wakeupRotateThread();
|
||||
|
||||
AudioCache *_audioCache;
|
||||
|
||||
float _volume;
|
||||
bool _loop;
|
||||
std::function<void(int, const ccstd::string &)> _finishCallbak;
|
||||
|
||||
bool _isDestroyed;
|
||||
bool _removeByAudioEngine;
|
||||
bool _ready;
|
||||
ALuint _alSource;
|
||||
|
||||
//play by circular buffer
|
||||
float _currTime;
|
||||
bool _streamingSource;
|
||||
ALuint _bufferIds[QUEUEBUFFER_NUM];
|
||||
std::thread *_rotateBufferThread;
|
||||
std::condition_variable _sleepCondition;
|
||||
std::mutex _sleepMutex;
|
||||
bool _timeDirty;
|
||||
bool _isRotateThreadExited;
|
||||
std::atomic_bool _needWakeupRotateThread;
|
||||
|
||||
std::mutex _play2dMutex;
|
||||
|
||||
unsigned int _id;
|
||||
|
||||
friend class AudioEngineImpl;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
350
cocos/audio/apple/AudioPlayer.mm
Normal file
350
cocos/audio/apple/AudioPlayer.mm
Normal file
@@ -0,0 +1,350 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-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.
|
||||
****************************************************************************/
|
||||
|
||||
#define LOG_TAG "AudioPlayer"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include "audio/apple/AudioCache.h"
|
||||
#include "audio/apple/AudioDecoder.h"
|
||||
#include "audio/apple/AudioPlayer.h"
|
||||
#include "base/memory/Memory.h"
|
||||
#include "platform/FileUtils.h"
|
||||
|
||||
#ifdef VERY_VERY_VERBOSE_LOGGING
|
||||
#define ALOGVV ALOGV
|
||||
#else
|
||||
#define ALOGVV(...) \
|
||||
do { \
|
||||
} while (false)
|
||||
#endif
|
||||
|
||||
using namespace cc;
|
||||
|
||||
namespace {
|
||||
unsigned int __idIndex = 0;
|
||||
}
|
||||
|
||||
AudioPlayer::AudioPlayer()
|
||||
: _audioCache(nullptr), _finishCallbak(nullptr), _isDestroyed(false), _removeByAudioEngine(false), _ready(false), _currTime(0.0f), _streamingSource(false), _rotateBufferThread(nullptr), _timeDirty(false), _isRotateThreadExited(false), _needWakeupRotateThread(false), _id(++__idIndex) {
|
||||
memset(_bufferIds, 0, sizeof(_bufferIds));
|
||||
}
|
||||
|
||||
AudioPlayer::~AudioPlayer() {
|
||||
ALOGVV("~AudioPlayer() (%p), id=%u", this, _id);
|
||||
destroy();
|
||||
|
||||
if (_streamingSource) {
|
||||
alDeleteBuffers(QUEUEBUFFER_NUM, _bufferIds);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioPlayer::destroy() {
|
||||
if (_isDestroyed)
|
||||
return;
|
||||
|
||||
ALOGVV("AudioPlayer::destroy begin, id=%u", _id);
|
||||
|
||||
_isDestroyed = true;
|
||||
|
||||
do {
|
||||
if (_audioCache != nullptr) {
|
||||
if (_audioCache->_state == AudioCache::State::INITIAL) {
|
||||
ALOGV("AudioPlayer::destroy, id=%u, cache isn't ready!", _id);
|
||||
break;
|
||||
}
|
||||
|
||||
while (!_audioCache->_isLoadingFinished) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for play2d to be finished.
|
||||
_play2dMutex.lock();
|
||||
_play2dMutex.unlock();
|
||||
|
||||
if (_streamingSource) {
|
||||
if (_rotateBufferThread != nullptr) {
|
||||
while (!_isRotateThreadExited) {
|
||||
_sleepCondition.notify_one();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
|
||||
if (_rotateBufferThread->joinable()) {
|
||||
_rotateBufferThread->join();
|
||||
}
|
||||
|
||||
delete _rotateBufferThread;
|
||||
_rotateBufferThread = nullptr;
|
||||
ALOGVV("rotateBufferThread exited!");
|
||||
|
||||
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
|
||||
// some specific OpenAL implement defects existed on iOS platform
|
||||
// refer to: https://github.com/cocos2d/cocos2d-x/issues/18597
|
||||
ALint sourceState;
|
||||
ALint bufferProcessed = 0;
|
||||
alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState);
|
||||
if (sourceState == AL_PLAYING) {
|
||||
alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed);
|
||||
while (bufferProcessed < QUEUEBUFFER_NUM) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||
alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed);
|
||||
}
|
||||
alSourceUnqueueBuffers(_alSource, QUEUEBUFFER_NUM, _bufferIds);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
}
|
||||
ALOGVV("UnqueueBuffers Before alSourceStop");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} while (false);
|
||||
|
||||
ALOGVV("Before alSourceStop");
|
||||
alSourceStop(_alSource);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
ALOGVV("Before alSourcei");
|
||||
alSourcei(_alSource, AL_BUFFER, 0);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
|
||||
_removeByAudioEngine = true;
|
||||
|
||||
_ready = false;
|
||||
ALOGVV("AudioPlayer::destroy end, id=%u", _id);
|
||||
}
|
||||
|
||||
void AudioPlayer::setCache(AudioCache *cache) {
|
||||
_audioCache = cache;
|
||||
}
|
||||
|
||||
bool AudioPlayer::play2d() {
|
||||
_play2dMutex.lock();
|
||||
ALOGVV("AudioPlayer::play2d, _alSource: %u", _alSource);
|
||||
|
||||
/*********************************************************************/
|
||||
/* Note that it may be in sub thread or in main thread. **/
|
||||
/*********************************************************************/
|
||||
bool ret = false;
|
||||
do {
|
||||
if (_audioCache->_state != AudioCache::State::READY) {
|
||||
ALOGE("alBuffer isn't ready for play!");
|
||||
break;
|
||||
}
|
||||
|
||||
alSourcei(_alSource, AL_BUFFER, 0);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
alSourcef(_alSource, AL_PITCH, 1.0f);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
alSourcef(_alSource, AL_GAIN, _volume);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
alSourcei(_alSource, AL_LOOPING, AL_FALSE);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
|
||||
if (_audioCache->_queBufferFrames == 0) {
|
||||
if (_loop) {
|
||||
alSourcei(_alSource, AL_LOOPING, AL_TRUE);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
}
|
||||
} else {
|
||||
if (_currTime > _audioCache->_duration) {
|
||||
_currTime = 0.F; // Target current start time is invalid, reset to 0.
|
||||
}
|
||||
alGenBuffers(QUEUEBUFFER_NUM, _bufferIds);
|
||||
|
||||
auto alError = alGetError();
|
||||
if (alError == AL_NO_ERROR) {
|
||||
for (int index = 0; index < QUEUEBUFFER_NUM; ++index) {
|
||||
alBufferData(_bufferIds[index], _audioCache->_format, _audioCache->_queBuffers[index], _audioCache->_queBufferSize[index], _audioCache->_sampleRate);
|
||||
}
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
} else {
|
||||
ALOGE("%s:alGenBuffers error code:%x", __PRETTY_FUNCTION__, alError);
|
||||
break;
|
||||
}
|
||||
_streamingSource = true;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(_sleepMutex);
|
||||
if (_isDestroyed)
|
||||
break;
|
||||
|
||||
if (_streamingSource) {
|
||||
// To continuously stream audio from a source without interruption, buffer queuing is required.
|
||||
alSourceQueueBuffers(_alSource, QUEUEBUFFER_NUM, _bufferIds);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
_rotateBufferThread = ccnew std::thread(&AudioPlayer::rotateBufferThread, this, _audioCache->_queBufferFrames * QUEUEBUFFER_NUM + 1);
|
||||
} else {
|
||||
alSourcei(_alSource, AL_BUFFER, _audioCache->_alBufferId);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
}
|
||||
|
||||
alSourcePlay(_alSource);
|
||||
}
|
||||
|
||||
auto alError = alGetError();
|
||||
if (alError != AL_NO_ERROR) {
|
||||
ALOGE("%s:alSourcePlay error code:%x", __PRETTY_FUNCTION__, alError);
|
||||
break;
|
||||
}
|
||||
/** Due to the bug of OpenAL, when the second time OpenAL trying to mix audio into bus, the mRampState become kRampingComplete, and for those oalSource whose mRampState == kRampingComplete, nothing happens.
|
||||
* OALSource::Play{
|
||||
* switch(mState){
|
||||
* case kTransitionToStop:
|
||||
* case kTransitionToStop:
|
||||
* if(mRampState != kRampingComplete){..}
|
||||
* break;
|
||||
* }
|
||||
* }
|
||||
* So the assert here will trigger this bug as aolSource is reused.
|
||||
* Replace OpenAL with AVAudioEngine on V3.6 mightbe helpful
|
||||
*/
|
||||
// CC_ASSERT_EQ(state, AL_PLAYING);
|
||||
_ready = true;
|
||||
ret = true;
|
||||
} while (false);
|
||||
|
||||
if (!ret) {
|
||||
_removeByAudioEngine = true;
|
||||
}
|
||||
|
||||
_play2dMutex.unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
// rotateBufferThread is used to rotate alBufferData for _alSource when playing big audio file
|
||||
void AudioPlayer::rotateBufferThread(int offsetFrame) {
|
||||
char *tmpBuffer = nullptr;
|
||||
AudioDecoder decoder;
|
||||
long long rotateSleepTime = static_cast<long long>(QUEUEBUFFER_TIME_STEP * 1000) / 2;
|
||||
do {
|
||||
BREAK_IF(!decoder.open(_audioCache->_fileFullPath.c_str()));
|
||||
|
||||
uint32_t framesRead = 0;
|
||||
const uint32_t framesToRead = _audioCache->_queBufferFrames;
|
||||
const uint32_t bufferSize = framesToRead * decoder.getBytesPerFrame();
|
||||
tmpBuffer = (char *)malloc(bufferSize);
|
||||
memset(tmpBuffer, 0, bufferSize);
|
||||
|
||||
if (offsetFrame != 0) {
|
||||
decoder.seek(offsetFrame);
|
||||
}
|
||||
|
||||
ALint sourceState;
|
||||
ALint bufferProcessed = 0;
|
||||
bool needToExitThread = false;
|
||||
|
||||
while (!_isDestroyed) {
|
||||
alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState);
|
||||
/* On IOS, audio state will lie, when the system is not fully foreground,
|
||||
* openAl will process the buffer in queue, but our condition cannot make sure that the audio
|
||||
* is playing as it's too short. Interesting IOS system.
|
||||
* Solution is to load buffer even if it's paused, just make sure that there's no bufferProcessed in
|
||||
*/
|
||||
if (sourceState == AL_PLAYING || sourceState == AL_PAUSED) {
|
||||
alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed);
|
||||
while (bufferProcessed > 0) {
|
||||
bufferProcessed--;
|
||||
if (_timeDirty) {
|
||||
_timeDirty = false;
|
||||
offsetFrame = _currTime * decoder.getSampleRate();
|
||||
decoder.seek(offsetFrame);
|
||||
} else {
|
||||
_currTime += QUEUEBUFFER_TIME_STEP;
|
||||
if (_currTime > _audioCache->_duration) {
|
||||
if (_loop) {
|
||||
_currTime = 0.0f;
|
||||
} else {
|
||||
_currTime = _audioCache->_duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
framesRead = decoder.readFixedFrames(framesToRead, tmpBuffer);
|
||||
|
||||
if (framesRead == 0) {
|
||||
if (_loop) {
|
||||
decoder.seek(0);
|
||||
framesRead = decoder.readFixedFrames(framesToRead, tmpBuffer);
|
||||
} else {
|
||||
needToExitThread = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
While the source is playing, alSourceUnqueueBuffers can be called to remove buffers which have
|
||||
already played. Those buffers can then be filled with new data or discarded. New or refilled
|
||||
buffers can then be attached to the playing source using alSourceQueueBuffers. As long as there is
|
||||
always a new buffer to play in the queue, the source will continue to play.
|
||||
*/
|
||||
ALuint bid;
|
||||
alSourceUnqueueBuffers(_alSource, 1, &bid);
|
||||
alBufferData(bid, _audioCache->_format, tmpBuffer, framesRead * decoder.getBytesPerFrame(), decoder.getSampleRate());
|
||||
alSourceQueueBuffers(_alSource, 1, &bid);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lk(_sleepMutex);
|
||||
if (_isDestroyed || needToExitThread) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!_needWakeupRotateThread) {
|
||||
_sleepCondition.wait_for(lk, std::chrono::milliseconds(rotateSleepTime));
|
||||
}
|
||||
|
||||
_needWakeupRotateThread = false;
|
||||
}
|
||||
|
||||
} while (false);
|
||||
|
||||
ALOGVV("Exit rotate buffer thread ...");
|
||||
decoder.close();
|
||||
free(tmpBuffer);
|
||||
_isRotateThreadExited = true;
|
||||
}
|
||||
|
||||
void AudioPlayer::wakeupRotateThread() {
|
||||
_needWakeupRotateThread = true;
|
||||
_sleepCondition.notify_all();
|
||||
}
|
||||
|
||||
bool AudioPlayer::setLoop(bool loop) {
|
||||
if (!_isDestroyed) {
|
||||
_loop = loop;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioPlayer::setTime(float time) {
|
||||
if (!_isDestroyed && time >= 0.0f && time < _audioCache->_duration) {
|
||||
_currTime = time;
|
||||
_timeDirty = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
87
cocos/audio/common/decoder/AudioDecoder.cpp
Normal file
87
cocos/audio/common/decoder/AudioDecoder.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "audio/common/decoder/AudioDecoder.h"
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include "audio/include/AudioMacros.h"
|
||||
#include "platform/FileUtils.h"
|
||||
|
||||
#ifdef LOG_TAG
|
||||
#undef LOG_TAG
|
||||
#endif
|
||||
#define LOG_TAG "AudioDecoder"
|
||||
|
||||
namespace cc {
|
||||
|
||||
AudioDecoder::AudioDecoder()
|
||||
: _isOpened(false) {}
|
||||
|
||||
AudioDecoder::~AudioDecoder() = default;
|
||||
|
||||
bool AudioDecoder::isOpened() const {
|
||||
return _isOpened;
|
||||
}
|
||||
|
||||
uint32_t AudioDecoder::readFixedFrames(uint32_t framesToRead, char *pcmBuf) {
|
||||
uint32_t framesRead = 0;
|
||||
uint32_t framesReadOnce = 0;
|
||||
do {
|
||||
framesReadOnce = read(framesToRead - framesRead, pcmBuf + framesRead * _pcmHeader.bytesPerFrame);
|
||||
framesRead += framesReadOnce;
|
||||
} while (framesReadOnce != 0 && framesRead < framesToRead);
|
||||
|
||||
if (framesRead < framesToRead) {
|
||||
memset(pcmBuf + framesRead * _pcmHeader.bytesPerFrame, 0x00, (framesToRead - framesRead) * _pcmHeader.bytesPerFrame);
|
||||
}
|
||||
|
||||
return framesRead;
|
||||
}
|
||||
|
||||
uint32_t AudioDecoder::getTotalFrames() const {
|
||||
return _pcmHeader.totalFrames;
|
||||
}
|
||||
|
||||
uint32_t AudioDecoder::getBytesPerFrame() const {
|
||||
return _pcmHeader.bytesPerFrame;
|
||||
}
|
||||
|
||||
uint32_t AudioDecoder::getSampleRate() const {
|
||||
return _pcmHeader.sampleRate;
|
||||
}
|
||||
|
||||
uint32_t AudioDecoder::getChannelCount() const {
|
||||
return _pcmHeader.channelCount;
|
||||
}
|
||||
|
||||
AudioDataFormat AudioDecoder::getDataFormat() const {
|
||||
return _pcmHeader.dataFormat;
|
||||
}
|
||||
|
||||
PCMHeader AudioDecoder::getPCMHeader() const {
|
||||
return _pcmHeader;
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
130
cocos/audio/common/decoder/AudioDecoder.h
Normal file
130
cocos/audio/common/decoder/AudioDecoder.h
Normal file
@@ -0,0 +1,130 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 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 <cstdint>
|
||||
#include <functional>
|
||||
|
||||
#include "audio/include/AudioDef.h"
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
#include "vorbis/vorbisfile.h"
|
||||
#elif CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX
|
||||
#include "vorbis/vorbisfile.h"
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OHOS
|
||||
#include "ivorbisfile.h"
|
||||
#endif
|
||||
|
||||
namespace cc {
|
||||
|
||||
/**
|
||||
* @brief The class for decoding compressed audio file to PCM buffer.
|
||||
*/
|
||||
class AudioDecoder {
|
||||
public:
|
||||
static const uint32_t INVALID_FRAME_INDEX = UINT32_MAX;
|
||||
|
||||
/**
|
||||
* @brief Opens an audio file specified by a file path.
|
||||
* @return true if succeed, otherwise false.
|
||||
*/
|
||||
virtual bool open(const char *path) = 0;
|
||||
|
||||
/**
|
||||
* @brief Checks whether decoder has opened file successfully.
|
||||
* @return true if succeed, otherwise false.
|
||||
*/
|
||||
virtual bool isOpened() const;
|
||||
|
||||
/**
|
||||
* @brief Closes opened audio file.
|
||||
* @note The method will also be automatically invoked in the destructor.
|
||||
*/
|
||||
virtual void close() = 0;
|
||||
|
||||
/**
|
||||
* @brief Reads audio frames of PCM format.
|
||||
* @param framesToRead The number of frames excepted to be read.
|
||||
* @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame.
|
||||
* @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end of file.
|
||||
*/
|
||||
virtual uint32_t read(uint32_t framesToRead, char *pcmBuf) = 0;
|
||||
|
||||
/**
|
||||
* @brief Reads fixed audio frames of PCM format.
|
||||
* @param framesToRead The number of frames excepted to be read.
|
||||
* @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame.
|
||||
* @return The number of frames actually read, it's probably less than |framesToRead|. Returns 0 means reach the end of file.
|
||||
* @note The different between |read| and |readFixedFrames| is |readFixedFrames| will do multiple reading operations if |framesToRead| frames
|
||||
* isn't filled entirely, while |read| just does reading operation once whatever |framesToRead| is or isn't filled entirely.
|
||||
* If current position reaches the end of frames, the return value may smaller than |framesToRead| and the remaining
|
||||
* buffer in |pcmBuf| will be set with silence data (0x00).
|
||||
*/
|
||||
virtual uint32_t readFixedFrames(uint32_t framesToRead, char *pcmBuf);
|
||||
|
||||
/**
|
||||
* @brief Sets frame offest to be read.
|
||||
* @param frameOffset The frame offest to be set.
|
||||
* @return true if succeed, otherwise false
|
||||
*/
|
||||
virtual bool seek(uint32_t frameOffset) = 0;
|
||||
|
||||
/**
|
||||
* @brief Tells the current frame offset.
|
||||
* @return The current frame offset.
|
||||
*/
|
||||
virtual uint32_t tell() const = 0;
|
||||
|
||||
/** Gets total frames of current audio.*/
|
||||
virtual uint32_t getTotalFrames() const;
|
||||
|
||||
/** Gets bytes per frame of current audio.*/
|
||||
virtual uint32_t getBytesPerFrame() const;
|
||||
|
||||
/** Gets sample rate of current audio.*/
|
||||
virtual uint32_t getSampleRate() const;
|
||||
|
||||
/** Gets the channel count of current audio.
|
||||
* @note Currently we only support 1 or 2 channels.
|
||||
*/
|
||||
virtual uint32_t getChannelCount() const;
|
||||
|
||||
virtual AudioDataFormat getDataFormat() const;
|
||||
|
||||
virtual PCMHeader getPCMHeader() const;
|
||||
|
||||
protected:
|
||||
AudioDecoder();
|
||||
virtual ~AudioDecoder();
|
||||
|
||||
bool _isOpened;
|
||||
PCMHeader _pcmHeader;
|
||||
void *_fsHooks = nullptr;
|
||||
|
||||
friend class AudioDecoderManager;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
68
cocos/audio/common/decoder/AudioDecoderManager.cpp
Normal file
68
cocos/audio/common/decoder/AudioDecoderManager.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 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 "AudioDecoderManager"
|
||||
|
||||
#include "audio/common/decoder/AudioDecoderManager.h"
|
||||
#include "audio/common/decoder/AudioDecoderMp3.h"
|
||||
#include "audio/common/decoder/AudioDecoderOgg.h"
|
||||
#include "audio/common/decoder/AudioDecoderWav.h"
|
||||
#include "audio/include/AudioMacros.h"
|
||||
#include "base/memory/Memory.h"
|
||||
#include "platform/FileUtils.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
bool AudioDecoderManager::init() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioDecoderManager::destroy() {
|
||||
AudioDecoderMp3::destroy();
|
||||
}
|
||||
|
||||
AudioDecoder *AudioDecoderManager::createDecoder(const char *path) {
|
||||
ccstd::string suffix = FileUtils::getInstance()->getFileExtension(path);
|
||||
if (suffix == ".ogg") {
|
||||
return ccnew AudioDecoderOgg();
|
||||
}
|
||||
|
||||
if (suffix == ".mp3") {
|
||||
return ccnew AudioDecoderMp3();
|
||||
}
|
||||
#if CC_PLATFORM == CC_PLATFORM_OHOS || CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
if (suffix == ".wav") {
|
||||
return ccnew AudioDecoderWav();
|
||||
}
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void AudioDecoderManager::destroyDecoder(AudioDecoder *decoder) {
|
||||
delete decoder;
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
40
cocos/audio/common/decoder/AudioDecoderManager.h
Normal file
40
cocos/audio/common/decoder/AudioDecoderManager.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 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
|
||||
|
||||
namespace cc {
|
||||
|
||||
class AudioDecoder;
|
||||
|
||||
class AudioDecoderManager {
|
||||
public:
|
||||
static bool init();
|
||||
static void destroy();
|
||||
static AudioDecoder *createDecoder(const char *path);
|
||||
static void destroyDecoder(AudioDecoder *decoder);
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
179
cocos/audio/common/decoder/AudioDecoderMp3.cpp
Normal file
179
cocos/audio/common/decoder/AudioDecoderMp3.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "audio/common/decoder/AudioDecoderMp3.h"
|
||||
#include <malloc.h>
|
||||
#include <cstdint>
|
||||
#include "audio/include/AudioMacros.h"
|
||||
#include "platform/FileUtils.h"
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_WINDOWS || CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX
|
||||
#include "mpg123/mpg123.h"
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OHOS
|
||||
#include <unistd.h>
|
||||
#include "cocos/platform/ohos/FileUtils-ohos.h"
|
||||
#include "mpg123.h"
|
||||
#endif
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#ifdef LOG_TAG
|
||||
#undef LOG_TAG
|
||||
#endif
|
||||
#define LOG_TAG "AudioDecoderMp3"
|
||||
|
||||
namespace cc {
|
||||
|
||||
static bool sMp3Inited = false;
|
||||
|
||||
bool AudioDecoderMp3::lazyInit() {
|
||||
bool ret = true;
|
||||
if (!sMp3Inited) {
|
||||
int error = mpg123_init();
|
||||
if (error == MPG123_OK) {
|
||||
sMp3Inited = true;
|
||||
} else {
|
||||
ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AudioDecoderMp3::destroy() {
|
||||
if (sMp3Inited) {
|
||||
mpg123_exit();
|
||||
sMp3Inited = false;
|
||||
}
|
||||
}
|
||||
|
||||
AudioDecoderMp3::AudioDecoderMp3() {
|
||||
lazyInit();
|
||||
}
|
||||
|
||||
AudioDecoderMp3::~AudioDecoderMp3() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool AudioDecoderMp3::open(const char *path) {
|
||||
ccstd::string fullPath = FileUtils::getInstance()->fullPathForFilename(path);
|
||||
|
||||
long rate = 0; //NOLINT(google-runtime-int)
|
||||
int error = MPG123_OK;
|
||||
int mp3Encoding = 0;
|
||||
int channel = 0;
|
||||
do {
|
||||
_mpg123handle = mpg123_new(nullptr, &error);
|
||||
if (nullptr == _mpg123handle) {
|
||||
ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
|
||||
break;
|
||||
}
|
||||
#if CC_PLATFORM_OHOS == CC_PLATFORM
|
||||
auto *fu = static_cast<FileUtilsOHOS *>(FileUtils::getInstance());
|
||||
_fdAndDeleter = fu->getFd(fullPath);
|
||||
if (mpg123_open_fd(_mpg123handle, _fdAndDeleter.first) != MPG123_OK || mpg123_getformat(_mpg123handle, &rate, &channel, &mp3Encoding) != MPG123_OK) {
|
||||
#else
|
||||
if (mpg123_open(_mpg123handle, FileUtils::getInstance()->getSuitableFOpen(fullPath).c_str()) != MPG123_OK || mpg123_getformat(_mpg123handle, &rate, &channel, &mp3Encoding) != MPG123_OK) {
|
||||
#endif
|
||||
ALOGE("Trouble with mpg123: %s\n", mpg123_strerror(_mpg123handle));
|
||||
break;
|
||||
}
|
||||
|
||||
_pcmHeader.channelCount = channel;
|
||||
_pcmHeader.sampleRate = rate;
|
||||
|
||||
if (mp3Encoding == MPG123_ENC_SIGNED_16) {
|
||||
_pcmHeader.bytesPerFrame = 2 * _pcmHeader.channelCount;
|
||||
_pcmHeader.dataFormat = AudioDataFormat::SIGNED_16;
|
||||
} else if (mp3Encoding == MPG123_ENC_FLOAT_32) {
|
||||
_pcmHeader.bytesPerFrame = 4 * _pcmHeader.channelCount;
|
||||
_pcmHeader.dataFormat = AudioDataFormat::FLOAT_32;
|
||||
} else {
|
||||
ALOGE("Bad encoding: 0x%x!\n", mp3Encoding);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Ensure that this output format will not change (it could, when we allow it). */
|
||||
mpg123_format_none(_mpg123handle);
|
||||
mpg123_format(_mpg123handle, rate, channel, mp3Encoding);
|
||||
/* Ensure that we can get accurate length by call mpg123_length */
|
||||
mpg123_scan(_mpg123handle);
|
||||
|
||||
_pcmHeader.totalFrames = mpg123_length(_mpg123handle);
|
||||
|
||||
_isOpened = true;
|
||||
return true;
|
||||
} while (false);
|
||||
|
||||
if (_mpg123handle != nullptr) {
|
||||
mpg123_close(_mpg123handle);
|
||||
mpg123_delete(_mpg123handle);
|
||||
_mpg123handle = nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioDecoderMp3::close() {
|
||||
if (isOpened()) {
|
||||
if (_mpg123handle != nullptr) {
|
||||
mpg123_close(_mpg123handle);
|
||||
|
||||
mpg123_delete(_mpg123handle);
|
||||
_mpg123handle = nullptr;
|
||||
}
|
||||
_isOpened = false;
|
||||
}
|
||||
#if CC_PLATFORM_OHOS == CC_PLATFORM
|
||||
if (_fdAndDeleter.second) {
|
||||
_fdAndDeleter.second();
|
||||
_fdAndDeleter.second = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t AudioDecoderMp3::read(uint32_t framesToRead, char *pcmBuf) {
|
||||
size_t bytesToRead = framesToRead * _pcmHeader.bytesPerFrame;
|
||||
size_t bytesRead = 0;
|
||||
int err = mpg123_read(_mpg123handle, reinterpret_cast<unsigned char *>(pcmBuf), bytesToRead, &bytesRead);
|
||||
if (err == MPG123_ERR) {
|
||||
ALOGE("Trouble with mpg123: %s\n", mpg123_strerror(_mpg123handle));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return static_cast<uint32_t>(bytesRead / _pcmHeader.bytesPerFrame);
|
||||
}
|
||||
|
||||
bool AudioDecoderMp3::seek(uint32_t frameOffset) {
|
||||
off_t offset = mpg123_seek(_mpg123handle, frameOffset, SEEK_SET);
|
||||
//ALOGD("mpg123_seek return: %d", (int)offset);
|
||||
return offset >= 0 && offset == frameOffset;
|
||||
}
|
||||
|
||||
uint32_t AudioDecoderMp3::tell() const {
|
||||
return static_cast<uint32_t>(mpg123_tell(_mpg123handle));
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
90
cocos/audio/common/decoder/AudioDecoderMp3.h
Normal file
90
cocos/audio/common/decoder/AudioDecoderMp3.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 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/common/decoder/AudioDecoder.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
struct mpg123_handle_struct;
|
||||
|
||||
namespace cc {
|
||||
|
||||
/**
|
||||
* @brief The class for decoding compressed audio file to PCM buffer.
|
||||
*/
|
||||
class AudioDecoderMp3 : public AudioDecoder {
|
||||
public:
|
||||
/**
|
||||
* @brief Opens an audio file specified by a file path.
|
||||
* @return true if succeed, otherwise false.
|
||||
*/
|
||||
bool open(const char *path) override;
|
||||
|
||||
/**
|
||||
* @brief Closes opened audio file.
|
||||
* @note The method will also be automatically invoked in the destructor.
|
||||
*/
|
||||
void close() override;
|
||||
|
||||
/**
|
||||
* @brief Reads audio frames of PCM format.
|
||||
* @param framesToRead The number of frames excepted to be read.
|
||||
* @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame.
|
||||
* @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end of file.
|
||||
*/
|
||||
uint32_t read(uint32_t framesToRead, char *pcmBuf) override;
|
||||
|
||||
/**
|
||||
* @brief Sets frame offest to be read.
|
||||
* @param frameOffset The frame offest to be set.
|
||||
* @return true if succeed, otherwise false
|
||||
*/
|
||||
bool seek(uint32_t frameOffset) override;
|
||||
|
||||
/**
|
||||
* @brief Tells the current frame offset.
|
||||
* @return The current frame offset.
|
||||
*/
|
||||
uint32_t tell() const override;
|
||||
|
||||
protected:
|
||||
AudioDecoderMp3();
|
||||
~AudioDecoderMp3() override;
|
||||
|
||||
static bool lazyInit();
|
||||
static void destroy();
|
||||
|
||||
struct mpg123_handle_struct *_mpg123handle = nullptr;
|
||||
|
||||
#if CC_PLATFORM_OHOS == CC_PLATFORM
|
||||
std::pair<int, std::function<void()>> _fdAndDeleter;
|
||||
#endif
|
||||
|
||||
friend class AudioDecoderManager;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
112
cocos/audio/common/decoder/AudioDecoderOgg.cpp
Normal file
112
cocos/audio/common/decoder/AudioDecoderOgg.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "audio/common/decoder/AudioDecoderOgg.h"
|
||||
#include <cstdint>
|
||||
|
||||
#include "audio/include/AudioMacros.h"
|
||||
#include "platform/FileUtils.h"
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_OHOS
|
||||
#include "audio/ohos/FsCallback.h"
|
||||
namespace {
|
||||
int ohosSeek_wrap(void *source, ogg_int64_t offset, int whence) { //NOLINT
|
||||
return cc::ohosSeek(source, static_cast<long>(offset), whence); //NOLINT
|
||||
}
|
||||
|
||||
ov_callbacks ogg_callbacks = { //NOLINT
|
||||
static_cast<size_t (*)(void *, size_t, size_t, void *)>(cc::ohosRead),
|
||||
static_cast<int (*)(void *, ogg_int64_t, int)>(ohosSeek_wrap),
|
||||
static_cast<int (*)(void *)>(cc::ohosClose),
|
||||
static_cast<long (*)(void *)>(cc::ohosTell)}; //NOLINT
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
#ifdef LOG_TAG
|
||||
#undef LOG_TAG
|
||||
#endif
|
||||
#define LOG_TAG "AudioDecoderOgg"
|
||||
|
||||
namespace cc {
|
||||
|
||||
AudioDecoderOgg::AudioDecoderOgg() = default;
|
||||
|
||||
AudioDecoderOgg::~AudioDecoderOgg() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool AudioDecoderOgg::open(const char *path) {
|
||||
ccstd::string fullPath = FileUtils::getInstance()->fullPathForFilename(path);
|
||||
#if CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
if (0 == ov_fopen(FileUtils::getInstance()->getSuitableFOpen(fullPath).c_str(), &_vf)) {
|
||||
#elif CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX
|
||||
if (0 == ov_fopen(FileUtils::getInstance()->getSuitableFOpen(fullPath).c_str(), &_vf)) {
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OHOS
|
||||
auto *fp = cc::ohosOpen(FileUtils::getInstance()->getSuitableFOpen(fullPath).c_str(), this);
|
||||
if (0 == ov_open_callbacks(fp, &_vf, nullptr, 0, ogg_callbacks)) {
|
||||
#endif
|
||||
// header
|
||||
vorbis_info *vi = ov_info(&_vf, -1);
|
||||
_pcmHeader.sampleRate = static_cast<uint32_t>(vi->rate);
|
||||
_pcmHeader.channelCount = vi->channels;
|
||||
_pcmHeader.bytesPerFrame = vi->channels * sizeof(int16_t);
|
||||
_pcmHeader.dataFormat = AudioDataFormat::SIGNED_16;
|
||||
_pcmHeader.totalFrames = static_cast<uint32_t>(ov_pcm_total(&_vf, -1));
|
||||
_isOpened = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioDecoderOgg::close() {
|
||||
if (isOpened()) {
|
||||
ov_clear(&_vf);
|
||||
_isOpened = false;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t AudioDecoderOgg::read(uint32_t framesToRead, char *pcmBuf) {
|
||||
int currentSection = 0;
|
||||
auto bytesToRead = framesToRead * _pcmHeader.bytesPerFrame;
|
||||
#if CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
auto bytesRead = ov_read(&_vf, pcmBuf, bytesToRead, 0, 2, 1, ¤tSection);
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OHOS
|
||||
int bitstream = 0;
|
||||
auto bytesRead = ov_read(&_vf, pcmBuf, bytesToRead, &bitstream);
|
||||
#elif CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX
|
||||
auto bytesRead = ov_read(&_vf, pcmBuf, bytesToRead, 0, 2, 1, ¤tSection);
|
||||
#endif
|
||||
return static_cast<uint32_t>(bytesRead / _pcmHeader.bytesPerFrame);
|
||||
}
|
||||
|
||||
bool AudioDecoderOgg::seek(uint32_t frameOffset) {
|
||||
return 0 == ov_pcm_seek(&_vf, frameOffset);
|
||||
}
|
||||
|
||||
uint32_t AudioDecoderOgg::tell() const {
|
||||
return static_cast<uint32_t>(ov_pcm_tell(const_cast<OggVorbis_File *>(&_vf)));
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
85
cocos/audio/common/decoder/AudioDecoderOgg.h
Normal file
85
cocos/audio/common/decoder/AudioDecoderOgg.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 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/common/decoder/AudioDecoder.h"
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
#include "vorbis/vorbisfile.h"
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OHOS
|
||||
#include "ivorbisfile.h"
|
||||
#endif
|
||||
|
||||
namespace cc {
|
||||
|
||||
/**
|
||||
* @brief The class for decoding compressed audio file to PCM buffer.
|
||||
*/
|
||||
class AudioDecoderOgg : public AudioDecoder {
|
||||
public:
|
||||
/**
|
||||
* @brief Opens an audio file specified by a file path.
|
||||
* @return true if succeed, otherwise false.
|
||||
*/
|
||||
bool open(const char *path) override;
|
||||
|
||||
/**
|
||||
* @brief Closes opened audio file.
|
||||
* @note The method will also be automatically invoked in the destructor.
|
||||
*/
|
||||
void close() override;
|
||||
|
||||
/**
|
||||
* @brief Reads audio frames of PCM format.
|
||||
* @param framesToRead The number of frames excepted to be read.
|
||||
* @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame.
|
||||
* @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end of file.
|
||||
*/
|
||||
uint32_t read(uint32_t framesToRead, char *pcmBuf) override;
|
||||
|
||||
/**
|
||||
* @brief Sets frame offest to be read.
|
||||
* @param frameOffset The frame offest to be set.
|
||||
* @return true if succeed, otherwise false
|
||||
*/
|
||||
bool seek(uint32_t frameOffset) override;
|
||||
|
||||
/**
|
||||
* @brief Tells the current frame offset.
|
||||
* @return The current frame offset.
|
||||
*/
|
||||
uint32_t tell() const override;
|
||||
|
||||
protected:
|
||||
AudioDecoderOgg();
|
||||
~AudioDecoderOgg() override;
|
||||
|
||||
OggVorbis_File _vf;
|
||||
|
||||
friend class AudioDecoderManager;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
94
cocos/audio/common/decoder/AudioDecoderWav.cpp
Normal file
94
cocos/audio/common/decoder/AudioDecoderWav.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#define LOG_TAG "AudioDecoderWav"
|
||||
|
||||
#include "audio/common/decoder/AudioDecoderWav.h"
|
||||
#include "base/Log.h"
|
||||
#include "platform/FileUtils.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
AudioDecoderWav::AudioDecoderWav() {
|
||||
CC_LOG_DEBUG("Create AudioDecoderWav");
|
||||
}
|
||||
|
||||
AudioDecoderWav::~AudioDecoderWav() {
|
||||
close();
|
||||
};
|
||||
|
||||
bool AudioDecoderWav::open(const char *path) {
|
||||
bool ret{false};
|
||||
auto fullPath = FileUtils::getInstance()->fullPathForFilename(path);
|
||||
if (fullPath.empty()) {
|
||||
CC_LOG_DEBUG("File does not exist %s", fullPath.c_str());
|
||||
return false;
|
||||
}
|
||||
do {
|
||||
sf::SF_INFO info;
|
||||
_sf_handle = sf::sf_open_read(fullPath.c_str(), &info, nullptr, nullptr);
|
||||
if (_sf_handle == nullptr) {
|
||||
CC_LOG_ERROR("file %s open failed, it might be invalid", fullPath.c_str());
|
||||
break;
|
||||
}
|
||||
if (info.frames == 0) {
|
||||
CC_LOG_ERROR("file %s has no frame, is it an invalid wav file?", fullPath.c_str());
|
||||
break;
|
||||
}
|
||||
CC_LOG_DEBUG("wav info: frames: %d, samplerate: %d, channels: %d, format: %d", info.frames, info.samplerate, info.channels, info.format);
|
||||
_pcmHeader.channelCount = info.channels;
|
||||
_pcmHeader.bytesPerFrame = 2 * info.channels; // FIXED_16
|
||||
_pcmHeader.dataFormat = AudioDataFormat::SIGNED_16; //FIXED,
|
||||
_pcmHeader.sampleRate = info.samplerate;
|
||||
_pcmHeader.totalFrames = info.frames;
|
||||
_isOpened = true;
|
||||
ret = true;
|
||||
|
||||
} while (false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t AudioDecoderWav::read(uint32_t framesToRead, char *pcmBuf) {
|
||||
//size_t bytesToRead = framesToRead * _pcmHeader.bytesPerFrame;
|
||||
size_t framesRead = sf::sf_readf_short(_sf_handle, reinterpret_cast<int16_t *>(pcmBuf), framesToRead);
|
||||
return framesRead;
|
||||
}
|
||||
|
||||
bool AudioDecoderWav::seek(uint32_t frameOffset) {
|
||||
auto offset = sf::sf_seek(_sf_handle, frameOffset, SEEK_SET);
|
||||
return offset >= 0 && offset == frameOffset;
|
||||
}
|
||||
uint32_t AudioDecoderWav::tell() const {
|
||||
return static_cast<uint32_t>(sf::sf_tell(_sf_handle));
|
||||
}
|
||||
void AudioDecoderWav::close() {
|
||||
if (_isOpened) {
|
||||
if (_sf_handle) {
|
||||
sf::sf_close(_sf_handle);
|
||||
}
|
||||
_isOpened = false;
|
||||
}
|
||||
}
|
||||
} // namespace cc
|
||||
76
cocos/audio/common/decoder/AudioDecoderWav.h
Normal file
76
cocos/audio/common/decoder/AudioDecoderWav.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 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/common/decoder/AudioDecoder.h"
|
||||
#include "audio/common/utils/include/tinysndfile.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class AudioDecoderWav : public AudioDecoder {
|
||||
public:
|
||||
/**
|
||||
* @brief Opens an audio file specified by a file path.
|
||||
* @return true if succeed, otherwise false.
|
||||
*/
|
||||
bool open(const char *path) override;
|
||||
|
||||
/**
|
||||
* @brief Closes opened audio file.
|
||||
* @note The method will also be automatically invoked in the destructor.
|
||||
*/
|
||||
void close() override;
|
||||
|
||||
/**
|
||||
* @brief Reads audio frames of PCM format.
|
||||
* @param framesToRead The number of frames excepted to be read.
|
||||
* @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame.
|
||||
* @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end of file.
|
||||
*/
|
||||
uint32_t read(uint32_t framesToRead, char *pcmBuf) override;
|
||||
|
||||
/**
|
||||
* @brief Sets frame offest to be read.
|
||||
* @param frameOffset The frame offest to be set.
|
||||
* @return true if succeed, otherwise false
|
||||
*/
|
||||
bool seek(uint32_t frameOffset) override;
|
||||
|
||||
/**
|
||||
* @brief Tells the current frame offset.
|
||||
* @return The current frame offset.
|
||||
*/
|
||||
uint32_t tell() const override;
|
||||
|
||||
protected:
|
||||
AudioDecoderWav();
|
||||
~AudioDecoderWav() override;
|
||||
sf::SNDFILE *_sf_handle{nullptr};
|
||||
|
||||
friend class AudioDecoderManager;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
181
cocos/audio/common/utils/format.cpp
Normal file
181
cocos/audio/common/utils/format.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* #define LOG_NDEBUG 0 */
|
||||
#define LOG_TAG "audio_utils_format"
|
||||
|
||||
#include "audio/common/utils/include/format.h"
|
||||
#include "audio/android/audio.h"
|
||||
#include "audio/android/cutils/log.h"
|
||||
#include "audio/common/utils/include/primitives.h"
|
||||
|
||||
void memcpy_by_audio_format(void *dst, audio_format_t dst_format,
|
||||
const void *src, audio_format_t src_format, size_t count) {
|
||||
/* default cases for error falls through to fatal log below. */
|
||||
if (dst_format == src_format) {
|
||||
switch (dst_format) {
|
||||
case AUDIO_FORMAT_PCM_16_BIT:
|
||||
case AUDIO_FORMAT_PCM_FLOAT:
|
||||
case AUDIO_FORMAT_PCM_8_BIT:
|
||||
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
|
||||
case AUDIO_FORMAT_PCM_32_BIT:
|
||||
case AUDIO_FORMAT_PCM_8_24_BIT:
|
||||
memcpy(dst, src, count * audio_bytes_per_sample(dst_format));
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (dst_format) {
|
||||
case AUDIO_FORMAT_PCM_16_BIT:
|
||||
switch (src_format) {
|
||||
case AUDIO_FORMAT_PCM_FLOAT:
|
||||
memcpy_to_i16_from_float((int16_t *)dst, (float *)src, count);
|
||||
return;
|
||||
case AUDIO_FORMAT_PCM_8_BIT:
|
||||
memcpy_to_i16_from_u8((int16_t *)dst, (uint8_t *)src, count);
|
||||
return;
|
||||
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
|
||||
memcpy_to_i16_from_p24((int16_t *)dst, (uint8_t *)src, count);
|
||||
return;
|
||||
case AUDIO_FORMAT_PCM_32_BIT:
|
||||
memcpy_to_i16_from_i32((int16_t *)dst, (int32_t *)src, count);
|
||||
return;
|
||||
case AUDIO_FORMAT_PCM_8_24_BIT:
|
||||
memcpy_to_i16_from_q8_23((int16_t *)dst, (int32_t *)src, count);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case AUDIO_FORMAT_PCM_FLOAT:
|
||||
switch (src_format) {
|
||||
case AUDIO_FORMAT_PCM_16_BIT:
|
||||
memcpy_to_float_from_i16((float *)dst, (int16_t *)src, count);
|
||||
return;
|
||||
case AUDIO_FORMAT_PCM_8_BIT:
|
||||
memcpy_to_float_from_u8((float *)dst, (uint8_t *)src, count);
|
||||
return;
|
||||
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
|
||||
memcpy_to_float_from_p24((float *)dst, (uint8_t *)src, count);
|
||||
return;
|
||||
case AUDIO_FORMAT_PCM_32_BIT:
|
||||
memcpy_to_float_from_i32((float *)dst, (int32_t *)src, count);
|
||||
return;
|
||||
case AUDIO_FORMAT_PCM_8_24_BIT:
|
||||
memcpy_to_float_from_q8_23((float *)dst, (int32_t *)src, count);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case AUDIO_FORMAT_PCM_8_BIT:
|
||||
switch (src_format) {
|
||||
case AUDIO_FORMAT_PCM_16_BIT:
|
||||
memcpy_to_u8_from_i16((uint8_t *)dst, (int16_t *)src, count);
|
||||
return;
|
||||
case AUDIO_FORMAT_PCM_FLOAT:
|
||||
memcpy_to_u8_from_float((uint8_t *)dst, (float *)src, count);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
|
||||
switch (src_format) {
|
||||
case AUDIO_FORMAT_PCM_16_BIT:
|
||||
memcpy_to_p24_from_i16((uint8_t *)dst, (int16_t *)src, count);
|
||||
return;
|
||||
case AUDIO_FORMAT_PCM_FLOAT:
|
||||
memcpy_to_p24_from_float((uint8_t *)dst, (float *)src, count);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case AUDIO_FORMAT_PCM_32_BIT:
|
||||
switch (src_format) {
|
||||
case AUDIO_FORMAT_PCM_16_BIT:
|
||||
memcpy_to_i32_from_i16((int32_t *)dst, (int16_t *)src, count);
|
||||
return;
|
||||
case AUDIO_FORMAT_PCM_FLOAT:
|
||||
memcpy_to_i32_from_float((int32_t *)dst, (float *)src, count);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case AUDIO_FORMAT_PCM_8_24_BIT:
|
||||
switch (src_format) {
|
||||
case AUDIO_FORMAT_PCM_16_BIT:
|
||||
memcpy_to_q8_23_from_i16((int32_t *)dst, (int16_t *)src, count);
|
||||
return;
|
||||
case AUDIO_FORMAT_PCM_FLOAT:
|
||||
memcpy_to_q8_23_from_float_with_clamp((int32_t *)dst, (float *)src, count);
|
||||
return;
|
||||
case AUDIO_FORMAT_PCM_24_BIT_PACKED: {
|
||||
memcpy_to_q8_23_from_p24((int32_t *)dst, (uint8_t *)src, count);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
LOG_ALWAYS_FATAL("invalid src format %#x for dst format %#x",
|
||||
src_format, dst_format);
|
||||
}
|
||||
|
||||
size_t memcpy_by_index_array_initialization_from_channel_mask(int8_t *idxary, size_t arysize,
|
||||
audio_channel_mask_t dst_channel_mask, audio_channel_mask_t src_channel_mask) {
|
||||
const audio_channel_representation_t src_representation =
|
||||
audio_channel_mask_get_representation(src_channel_mask);
|
||||
const audio_channel_representation_t dst_representation =
|
||||
audio_channel_mask_get_representation(dst_channel_mask);
|
||||
const uint32_t src_bits = audio_channel_mask_get_bits(src_channel_mask);
|
||||
const uint32_t dst_bits = audio_channel_mask_get_bits(dst_channel_mask);
|
||||
|
||||
switch (src_representation) {
|
||||
case AUDIO_CHANNEL_REPRESENTATION_POSITION:
|
||||
switch (dst_representation) {
|
||||
case AUDIO_CHANNEL_REPRESENTATION_POSITION:
|
||||
return memcpy_by_index_array_initialization(idxary, arysize,
|
||||
dst_bits, src_bits);
|
||||
case AUDIO_CHANNEL_REPRESENTATION_INDEX:
|
||||
return memcpy_by_index_array_initialization_dst_index(idxary, arysize,
|
||||
dst_bits, src_bits);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case AUDIO_CHANNEL_REPRESENTATION_INDEX:
|
||||
switch (dst_representation) {
|
||||
case AUDIO_CHANNEL_REPRESENTATION_POSITION:
|
||||
return memcpy_by_index_array_initialization_src_index(idxary, arysize,
|
||||
dst_bits, src_bits);
|
||||
case AUDIO_CHANNEL_REPRESENTATION_INDEX:
|
||||
return memcpy_by_index_array_initialization(idxary, arysize,
|
||||
dst_bits, src_bits);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
77
cocos/audio/common/utils/include/format.h
Normal file
77
cocos/audio/common/utils/include/format.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef COCOS_AUDIO_FORMAT_H
|
||||
#define COCOS_AUDIO_FORMAT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include <sys/cdefs.h>
|
||||
#endif
|
||||
|
||||
#include "audio/android/audio.h"
|
||||
|
||||
/* Copy buffers with conversion between buffer sample formats.
|
||||
*
|
||||
* dst Destination buffer
|
||||
* dst_format Destination buffer format
|
||||
* src Source buffer
|
||||
* src_format Source buffer format
|
||||
* count Number of samples to copy
|
||||
*
|
||||
* Allowed format conversions are given by either case 1 or 2 below:
|
||||
*
|
||||
* 1) One of src_format or dst_format is AUDIO_FORMAT_PCM_16_BIT or
|
||||
* AUDIO_FORMAT_PCM_FLOAT, and the other format type is one of:
|
||||
*
|
||||
* AUDIO_FORMAT_PCM_16_BIT
|
||||
* AUDIO_FORMAT_PCM_FLOAT
|
||||
* AUDIO_FORMAT_PCM_8_BIT
|
||||
* AUDIO_FORMAT_PCM_24_BIT_PACKED
|
||||
* AUDIO_FORMAT_PCM_32_BIT
|
||||
* AUDIO_FORMAT_PCM_8_24_BIT
|
||||
*
|
||||
* 2) Both dst_format and src_format are identical and of the list given
|
||||
* in (1). This is a straight copy.
|
||||
*
|
||||
* The destination and source buffers must be completely separate if the destination
|
||||
* format size is larger than the source format size. These routines call functions
|
||||
* in primitives.h, so descriptions of detailed behavior can be reviewed there.
|
||||
*
|
||||
* Logs a fatal error if dst or src format is not allowed by the conversion rules above.
|
||||
*/
|
||||
void memcpy_by_audio_format(void *dst, audio_format_t dst_format,
|
||||
const void *src, audio_format_t src_format, size_t count);
|
||||
|
||||
/* This function creates an index array for converting audio data with different
|
||||
* channel position and index masks, used by memcpy_by_index_array().
|
||||
* Returns the number of array elements required.
|
||||
* This may be greater than idxcount, so the return value should be checked
|
||||
* if idxary size is less than 32. Returns zero if the input masks are unrecognized.
|
||||
*
|
||||
* Note that idxary is a caller allocated array
|
||||
* of at least as many channels as present in the dst_mask.
|
||||
*
|
||||
* Parameters:
|
||||
* idxary Updated array of indices of channels in the src frame for the dst frame
|
||||
* idxcount Number of caller allocated elements in idxary
|
||||
* dst_mask Bit mask corresponding to destination channels present
|
||||
* src_mask Bit mask corresponding to source channels present
|
||||
*/
|
||||
size_t memcpy_by_index_array_initialization_from_channel_mask(int8_t *idxary, size_t arysize,
|
||||
audio_channel_mask_t dst_channel_mask, audio_channel_mask_t src_channel_mask);
|
||||
|
||||
#endif // COCOS_AUDIO_FORMAT_H
|
||||
78
cocos/audio/common/utils/include/minifloat.h
Normal file
78
cocos/audio/common/utils/include/minifloat.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 <cstdint>
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include <sys/cdefs.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
/* A single gain expressed as minifloat */
|
||||
typedef uint16_t gain_minifloat_t;
|
||||
|
||||
/* A pair of gain_minifloat_t packed into a single word */
|
||||
typedef uint32_t gain_minifloat_packed_t;
|
||||
|
||||
/* The nominal range of a gain, expressed as a float */
|
||||
#define GAIN_FLOAT_ZERO 0.0f
|
||||
#define GAIN_FLOAT_UNITY 1.0f
|
||||
|
||||
/* Unity gain expressed as a minifloat */
|
||||
#define GAIN_MINIFLOAT_UNITY 0xE000
|
||||
|
||||
/* Pack a pair of gain_mini_float_t into a combined gain_minifloat_packed_t */
|
||||
static inline gain_minifloat_packed_t gain_minifloat_pack(gain_minifloat_t left,
|
||||
gain_minifloat_t right) {
|
||||
return (right << 16) | left;
|
||||
}
|
||||
|
||||
/* Unpack a gain_minifloat_packed_t into the two gain_minifloat_t components */
|
||||
static inline gain_minifloat_t gain_minifloat_unpack_left(gain_minifloat_packed_t packed) {
|
||||
return packed & 0xFFFF;
|
||||
}
|
||||
|
||||
static inline gain_minifloat_t gain_minifloat_unpack_right(gain_minifloat_packed_t packed) {
|
||||
return packed >> 16;
|
||||
}
|
||||
|
||||
/* A pair of unity gains expressed as a gain_minifloat_packed_t */
|
||||
#define GAIN_MINIFLOAT_PACKED_UNITY gain_minifloat_pack(GAIN_MINIFLOAT_UNITY, GAIN_MINIFLOAT_UNITY)
|
||||
|
||||
/* Convert a float to the internal representation used for gains.
|
||||
* The nominal range [0.0, 1.0], but the hard range is [0.0, 2.0).
|
||||
* Negative and underflow values are converted to 0.0,
|
||||
* and values larger than the hard maximum are truncated to the hard maximum.
|
||||
*
|
||||
* Minifloats are ordered, and standard comparisons may be used between them
|
||||
* in the gain_minifloat_t representation.
|
||||
*
|
||||
* Details on internal representation of gains, based on mini-floats:
|
||||
* The nominal maximum is 1.0 and the hard maximum is 1 ULP less than 2.0, or +6 dB.
|
||||
* The minimum non-zero value is approximately 1.9e-6 or -114 dB.
|
||||
* Negative numbers, infinity, and NaN are not supported.
|
||||
* There are 13 significand bits specified, 1 implied hidden bit, 3 exponent bits,
|
||||
* and no sign bit. Denormals are supported.
|
||||
*/
|
||||
gain_minifloat_t gain_from_float(float v);
|
||||
|
||||
/* Convert the internal representation used for gains to float */
|
||||
float float_from_gain(gain_minifloat_t a);
|
||||
|
||||
#endif // COCOS_AUDIO_MINIFLOAT_H
|
||||
936
cocos/audio/common/utils/include/primitives.h
Normal file
936
cocos/audio/common/utils/include/primitives.h
Normal file
@@ -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
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include <sys/cdefs.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include <sys/cdefs.h>
|
||||
#endif
|
||||
|
||||
/* 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
|
||||
}
|
||||
92
cocos/audio/common/utils/include/tinysndfile.h
Normal file
92
cocos/audio/common/utils/include/tinysndfile.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// This is a C library for reading and writing PCM .wav files. It is
|
||||
// influenced by other libraries such as libsndfile and audiofile, except is
|
||||
// much smaller and has an Apache 2.0 license.
|
||||
// The API should be familiar to clients of similar libraries, but there is
|
||||
// no guarantee that it will stay exactly source-code compatible with other libraries.
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include <sys/cdefs.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
#include <sys/types.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
|
||||
#include <cstdint>
|
||||
#endif
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
namespace sf {
|
||||
|
||||
// visible to clients
|
||||
using sf_count_t = int;
|
||||
|
||||
struct SF_INFO {
|
||||
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
|
||||
|
||||
struct snd_callbacks {
|
||||
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
|
||||
59
cocos/audio/common/utils/minifloat.cpp
Normal file
59
cocos/audio/common/utils/minifloat.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "audio/common/utils/include/minifloat.h"
|
||||
#include <cmath>
|
||||
|
||||
#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);
|
||||
}
|
||||
500
cocos/audio/common/utils/primitives.cpp
Normal file
500
cocos/audio/common/utils/primitives.cpp
Normal file
@@ -0,0 +1,500 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "audio/common/utils/include/primitives.h"
|
||||
#include "audio/common/utils/private/private.h"
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include "audio/android/cutils/bitops.h" /* for popcount() */
|
||||
#else
|
||||
#include "base/Utils.h"
|
||||
using namespace cc::utils;
|
||||
#endif
|
||||
|
||||
// namespace {
|
||||
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<int16_t>(*--src - 0x80) << 8;
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_u8_from_i16(uint8_t *dst, const int16_t *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = (*src++ >> 8) + 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_u8_from_float(uint8_t *dst, const float *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = clamp8_from_float(*src++);
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_i16_from_i32(int16_t *dst, const int32_t *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = *src++ >> 16;
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_i16_from_float(int16_t *dst, const float *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = clamp16_from_float(*src++);
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_float_from_q4_27(float *dst, const int32_t *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = float_from_q4_27(*src++);
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_float_from_i16(float *dst, const int16_t *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = float_from_i16(*src++);
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_float_from_u8(float *dst, const uint8_t *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = float_from_u8(*src++);
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_float_from_p24(float *dst, const uint8_t *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = float_from_p24(src);
|
||||
src += 3;
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_i16_from_p24(int16_t *dst, const uint8_t *src, size_t count) {
|
||||
while (count--) {
|
||||
#ifdef HAVE_BIG_ENDIAN
|
||||
*dst++ = src[1] | (src[0] << 8);
|
||||
#else
|
||||
*dst++ = src[1] | (src[2] << 8);
|
||||
#endif
|
||||
src += 3;
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_i32_from_p24(int32_t *dst, const uint8_t *src, size_t count) {
|
||||
while (count--) {
|
||||
#ifdef HAVE_BIG_ENDIAN
|
||||
*dst++ = (src[2] << 8) | (src[1] << 16) | (src[0] << 24);
|
||||
#else
|
||||
*dst++ = (src[0] << 8) | (src[1] << 16) | (src[2] << 24);
|
||||
#endif
|
||||
src += 3;
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_p24_from_i16(uint8_t *dst, const int16_t *src, size_t count) {
|
||||
while (count--) {
|
||||
#ifdef HAVE_BIG_ENDIAN
|
||||
*dst++ = *src >> 8;
|
||||
*dst++ = *src++;
|
||||
*dst++ = 0;
|
||||
#else
|
||||
*dst++ = 0;
|
||||
*dst++ = *src;
|
||||
*dst++ = *src++ >> 8;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_p24_from_float(uint8_t *dst, const float *src, size_t count) {
|
||||
while (count--) {
|
||||
int32_t ival = clamp24_from_float(*src++);
|
||||
|
||||
#ifdef HAVE_BIG_ENDIAN
|
||||
*dst++ = ival >> 16;
|
||||
*dst++ = ival >> 8;
|
||||
*dst++ = ival;
|
||||
#else
|
||||
*dst++ = ival;
|
||||
*dst++ = ival >> 8;
|
||||
*dst++ = ival >> 16;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_p24_from_q8_23(uint8_t *dst, const int32_t *src, size_t count) {
|
||||
while (count--) {
|
||||
int32_t ival = clamp24_from_q8_23(*src++);
|
||||
|
||||
#ifdef HAVE_BIG_ENDIAN
|
||||
*dst++ = ival >> 16;
|
||||
*dst++ = ival >> 8;
|
||||
*dst++ = ival;
|
||||
#else
|
||||
*dst++ = ival;
|
||||
*dst++ = ival >> 8;
|
||||
*dst++ = ival >> 16;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_p24_from_i32(uint8_t *dst, const int32_t *src, size_t count) {
|
||||
while (count--) {
|
||||
int32_t ival = *src++ >> 8;
|
||||
|
||||
#ifdef HAVE_BIG_ENDIAN
|
||||
*dst++ = ival >> 16;
|
||||
*dst++ = ival >> 8;
|
||||
*dst++ = ival;
|
||||
#else
|
||||
*dst++ = ival;
|
||||
*dst++ = ival >> 8;
|
||||
*dst++ = ival >> 16;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_q8_23_from_i16(int32_t *dst, const int16_t *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = static_cast<int32_t>(*src++) << 8;
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_q8_23_from_float_with_clamp(int32_t *dst, const float *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = clamp24_from_float(*src++);
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_q8_23_from_p24(int32_t *dst, const uint8_t *src, size_t count) {
|
||||
while (count--) {
|
||||
#ifdef HAVE_BIG_ENDIAN
|
||||
*dst++ = (int8_t)src[0] << 16 | src[1] << 8 | src[2];
|
||||
#else
|
||||
*dst++ = static_cast<int8_t>(src[2]) << 16 | src[1] << 8 | src[0];
|
||||
#endif
|
||||
src += 3;
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_q4_27_from_float(int32_t *dst, const float *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = clampq4_27_from_float(*src++);
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_i16_from_q8_23(int16_t *dst, const int32_t *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = clamp16(*src++ >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_float_from_q8_23(float *dst, const int32_t *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = float_from_q8_23(*src++);
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_i32_from_i16(int32_t *dst, const int16_t *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = static_cast<int32_t>(*src++) << 16;
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_i32_from_float(int32_t *dst, const float *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = clamp32_from_float(*src++);
|
||||
}
|
||||
}
|
||||
|
||||
void memcpy_to_float_from_i32(float *dst, const int32_t *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = float_from_i32(*src++);
|
||||
}
|
||||
}
|
||||
|
||||
void downmix_to_mono_i16_from_stereo_i16(int16_t *dst, const int16_t *src, size_t count) {
|
||||
while (count--) {
|
||||
*dst++ = static_cast<int16_t>((static_cast<int32_t>(src[0]) + static_cast<int32_t>(src[1])) >> 1);
|
||||
src += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void upmix_to_stereo_i16_from_mono_i16(int16_t *dst, const int16_t *src, size_t count) {
|
||||
while (count--) {
|
||||
int32_t temp = *src++;
|
||||
dst[0] = temp;
|
||||
dst[1] = temp;
|
||||
dst += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void downmix_to_mono_float_from_stereo_float(float *dst, const float *src, size_t frames) {
|
||||
while (frames--) {
|
||||
*dst++ = (src[0] + src[1]) * 0.5;
|
||||
src += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void upmix_to_stereo_float_from_mono_float(float *dst, const float *src, size_t frames) {
|
||||
while (frames--) {
|
||||
float temp = *src++;
|
||||
dst[0] = temp;
|
||||
dst[1] = temp;
|
||||
dst += 2;
|
||||
}
|
||||
}
|
||||
|
||||
size_t nonZeroMono32(const int32_t *samples, size_t count) {
|
||||
size_t nonZero = 0;
|
||||
while (count-- > 0) {
|
||||
if (*samples++ != 0) {
|
||||
nonZero++;
|
||||
}
|
||||
}
|
||||
return nonZero;
|
||||
}
|
||||
|
||||
size_t nonZeroMono16(const int16_t *samples, size_t count) {
|
||||
size_t nonZero = 0;
|
||||
while (count-- > 0) {
|
||||
if (*samples++ != 0) {
|
||||
nonZero++;
|
||||
}
|
||||
}
|
||||
return nonZero;
|
||||
}
|
||||
|
||||
size_t nonZeroStereo32(const int32_t *frames, size_t count) {
|
||||
size_t nonZero = 0;
|
||||
while (count-- > 0) {
|
||||
if (frames[0] != 0 || frames[1] != 0) {
|
||||
nonZero++;
|
||||
}
|
||||
frames += 2;
|
||||
}
|
||||
return nonZero;
|
||||
}
|
||||
|
||||
size_t nonZeroStereo16(const int16_t *frames, size_t count) {
|
||||
size_t nonZero = 0;
|
||||
while (count-- > 0) {
|
||||
if (frames[0] != 0 || frames[1] != 0) {
|
||||
nonZero++;
|
||||
}
|
||||
frames += 2;
|
||||
}
|
||||
return nonZero;
|
||||
}
|
||||
|
||||
/*
|
||||
* C macro to do channel mask copying independent of dst/src sample type.
|
||||
* Don't pass in any expressions for the macro arguments here.
|
||||
*/
|
||||
#define COPY_FRAME_BY_MASK(dst, dmask, src, smask, count, zero) \
|
||||
{ \
|
||||
int32_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<uint8_t *>(dst);
|
||||
const auto *usrc = static_cast<const uint8_t *>(src);
|
||||
|
||||
COPY_FRAME_BY_MASK(udst, dstMask, usrc, srcMask, count, 0);
|
||||
} break;
|
||||
case 2: {
|
||||
auto *udst = static_cast<uint16_t *>(dst);
|
||||
const auto *usrc = static_cast<const uint16_t *>(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<uint8x3_t *>(dst);
|
||||
const auto *usrc = static_cast<const uint8x3_t *>(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<uint32_t *>(dst);
|
||||
const auto *usrc = static_cast<const uint32_t *>(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<uint8_t *>(dst);
|
||||
const auto *usrc = static_cast<const uint8_t *>(src);
|
||||
|
||||
COPY_FRAME_BY_IDX(udst, dstChannels, usrc, srcChannels, idxary, count, 0); // NOLINT
|
||||
} break;
|
||||
case 2: {
|
||||
auto *udst = static_cast<uint16_t *>(dst);
|
||||
const auto *usrc = static_cast<const uint16_t *>(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<uint8x3_t *>(dst);
|
||||
const auto *usrc = static_cast<const uint8x3_t *>(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<uint32_t *>(dst);
|
||||
const auto *usrc = static_cast<const uint32_t *>(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;
|
||||
int32_t bit;
|
||||
int32_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<signed>(srcIdx) : -1;
|
||||
}
|
||||
dstMask >>= 1;
|
||||
}
|
||||
return dstIdx;
|
||||
}
|
||||
//} // namespace
|
||||
38
cocos/audio/common/utils/private/private.h
Normal file
38
cocos/audio/common/utils/private/private.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_AUDIO_PRIVATE_H
|
||||
#define ANDROID_AUDIO_PRIVATE_H
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID
|
||||
#include <sys/cdefs.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
#include <sys/types.h>
|
||||
#define __attribute__(x)
|
||||
#endif
|
||||
#include <stdint.h>
|
||||
/* Defines not necessary for external use but kept here to be common
|
||||
* to the audio_utils library.
|
||||
*/
|
||||
|
||||
/* struct representation of 3 bytes for packed PCM 24 bit data.
|
||||
* The naming follows the ARM NEON convention.
|
||||
*/
|
||||
extern "C" {
|
||||
typedef struct __attribute__((__packed__)) {
|
||||
uint8_t c[3];
|
||||
} uint8x3_t;
|
||||
}
|
||||
#endif /*ANDROID_AUDIO_PRIVATE_H*/
|
||||
522
cocos/audio/common/utils/tinysndfile.cpp
Normal file
522
cocos/audio/common/utils/tinysndfile.cpp
Normal file
@@ -0,0 +1,522 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "tinysndfile"
|
||||
|
||||
#include "audio/common/utils/include/tinysndfile.h"
|
||||
#include "audio/common/utils/include/primitives.h"
|
||||
|
||||
#include "base/Log.h"
|
||||
|
||||
// #ifdef HAVE_STDERR
|
||||
// #include <stdio.h>
|
||||
// #endif
|
||||
|
||||
#include <cassert>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
|
||||
#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<const char *>(&ONE)) == 1;
|
||||
}
|
||||
|
||||
// "swab" conflicts with OS X <string.h>
|
||||
static void my_swab(int16_t *ptr, size_t numToSwap) { //NOLINT(readability-identifier-naming)
|
||||
while (numToSwap > 0) {
|
||||
*ptr = static_cast<int16_t>(little2u(reinterpret_cast<unsigned char *>(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<FILE *>(datasource));
|
||||
}
|
||||
|
||||
static int seek_func(void *datasource, long offset, int whence) { //NOLINT(google-runtime-int,readability-identifier-naming)
|
||||
return fseek(static_cast<FILE *>(datasource), offset, whence);
|
||||
}
|
||||
|
||||
static int close_func(void *datasource) { //NOLINT(readability-identifier-naming)
|
||||
return fclose(static_cast<FILE *>(datasource));
|
||||
}
|
||||
|
||||
static long tell_func(void *datasource) { //NOLINT(google-runtime-int,readability-identifier-naming)
|
||||
return ftell(static_cast<FILE *>(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
|
||||
CC_LOG_ERROR("path=%p info=%p\n", path, info);
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto *handle = static_cast<SNDFILE *>(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
|
||||
CC_LOG_ERROR("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
|
||||
CC_LOG_ERROR("actual %zu < 44\n", actual);
|
||||
#endif
|
||||
goto close;
|
||||
}
|
||||
if (memcmp(wav, "RIFF", 4)) { //NOLINT(bugprone-suspicious-string-compare)
|
||||
#ifdef HAVE_STDERR
|
||||
CC_LOG_ERROR("wav != RIFF\n");
|
||||
#endif
|
||||
goto close;
|
||||
}
|
||||
riffSize = little4u(&wav[4]);
|
||||
if (riffSize < 4) {
|
||||
#ifdef HAVE_STDERR
|
||||
CC_LOG_ERROR("riffSize %u < 4\n", riffSize);
|
||||
#endif
|
||||
goto close;
|
||||
}
|
||||
if (memcmp(&wav[8], "WAVE", 4)) { //NOLINT(bugprone-suspicious-string-compare)
|
||||
#ifdef HAVE_STDERR
|
||||
CC_LOG_ERROR("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
|
||||
CC_LOG_ERROR("actual %zu != %zu\n", actual, sizeof(chunk));
|
||||
#endif
|
||||
goto close;
|
||||
}
|
||||
remaining -= 8;
|
||||
unsigned chunkSize = little4u(&chunk[4]);
|
||||
if (chunkSize > remaining) {
|
||||
#ifdef HAVE_STDERR
|
||||
CC_LOG_ERROR("chunkSize %u > remaining %zu\n", chunkSize, remaining);
|
||||
#endif
|
||||
goto close;
|
||||
}
|
||||
if (!memcmp(&chunk[0], "fmt ", 4)) {
|
||||
if (hadFmt) {
|
||||
#ifdef HAVE_STDERR
|
||||
CC_LOG_ERROR("multiple fmt\n");
|
||||
#endif
|
||||
goto close;
|
||||
}
|
||||
if (chunkSize < 2) {
|
||||
#ifdef HAVE_STDERR
|
||||
CC_LOG_ERROR("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
|
||||
CC_LOG_ERROR("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
|
||||
CC_LOG_ERROR("unsupported format %u\n", format);
|
||||
#endif
|
||||
goto close;
|
||||
}
|
||||
if (chunkSize < minSize) {
|
||||
#ifdef HAVE_STDERR
|
||||
CC_LOG_ERROR("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
|
||||
CC_LOG_ERROR("actual %zu != %zu\n", actual, minSize - 16);
|
||||
#endif
|
||||
goto close;
|
||||
}
|
||||
if (chunkSize > minSize) {
|
||||
handle->callback.seek(stream, static_cast<long>(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
|
||||
CC_LOG_ERROR("unsupported channels %u\n", channels);
|
||||
#endif
|
||||
goto close;
|
||||
}
|
||||
unsigned samplerate = little4u(&fmt[4]);
|
||||
if (samplerate == 0) {
|
||||
#ifdef HAVE_STDERR
|
||||
CC_LOG_ERROR("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
|
||||
CC_LOG_ERROR("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<int>(samplerate);
|
||||
handle->info.channels = static_cast<int>(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
|
||||
CC_LOG_ERROR("data not preceded by fmt\n");
|
||||
#endif
|
||||
goto close;
|
||||
}
|
||||
if (hadData) {
|
||||
#ifdef HAVE_STDERR
|
||||
CC_LOG_ERROR("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<long>(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<long>(chunkSize), SEEK_CUR); //NOLINT(google-runtime-int)
|
||||
}
|
||||
} else {
|
||||
// ignore unknown chunk
|
||||
#ifdef HAVE_STDERR
|
||||
CC_LOG_ERROR("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<long>(chunkSize), SEEK_CUR); //NOLINT(google-runtime-int)
|
||||
}
|
||||
}
|
||||
remaining -= chunkSize;
|
||||
}
|
||||
if (remaining > 0) {
|
||||
#ifdef HAVE_STDERR
|
||||
CC_LOG_ERROR("partial chunk at end of RIFF, remaining %zu\n", remaining);
|
||||
#endif
|
||||
goto close;
|
||||
}
|
||||
if (!hadData) {
|
||||
#ifdef HAVE_STDERR
|
||||
CC_LOG_ERROR("missing data\n");
|
||||
#endif
|
||||
goto close;
|
||||
}
|
||||
(void)handle->callback.seek(stream, dataTell, SEEK_SET);
|
||||
*info = handle->info;
|
||||
return handle;
|
||||
|
||||
close:
|
||||
handle->callback.close(stream);
|
||||
free(handle);
|
||||
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<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_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<unsigned char *>(ptr), actualFrames * handle->info.channels);
|
||||
break;
|
||||
case SF_FORMAT_PCM_16:
|
||||
if (!isLittleEndian()) {
|
||||
my_swab(ptr, actualFrames * handle->info.channels);
|
||||
}
|
||||
break;
|
||||
case SF_FORMAT_PCM_32:
|
||||
memcpy_to_i16_from_i32(ptr, static_cast<const int *>(temp), actualFrames * handle->info.channels);
|
||||
free(temp);
|
||||
break;
|
||||
case SF_FORMAT_FLOAT:
|
||||
memcpy_to_i16_from_float(ptr, static_cast<const float *>(temp), actualFrames * handle->info.channels);
|
||||
free(temp);
|
||||
break;
|
||||
case SF_FORMAT_PCM_24:
|
||||
memcpy_to_i16_from_p24(ptr, static_cast<const uint8_t *>(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
|
||||
46
cocos/audio/include/AudioDef.h
Normal file
46
cocos/audio/include/AudioDef.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 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 <cstdint>
|
||||
#include <functional>
|
||||
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};
|
||||
};
|
||||
406
cocos/audio/include/AudioEngine.h
Normal file
406
cocos/audio/include/AudioEngine.h
Normal file
@@ -0,0 +1,406 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 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 <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include "audio/include/AudioDef.h"
|
||||
#include "audio/include/Export.h"
|
||||
#include "base/Macros.h"
|
||||
#include "base/std/container/list.h"
|
||||
#include "base/std/container/string.h"
|
||||
#include "base/std/container/unordered_map.h"
|
||||
#include "base/std/container/vector.h"
|
||||
#include "engine/EngineEvents.h"
|
||||
|
||||
#ifdef ERROR
|
||||
#undef ERROR
|
||||
#endif // ERROR
|
||||
|
||||
/**
|
||||
* @addtogroup audio
|
||||
* @{
|
||||
*/
|
||||
|
||||
namespace cc {
|
||||
/**
|
||||
* @class AudioProfile
|
||||
*
|
||||
* @brief
|
||||
* @js NA
|
||||
*/
|
||||
class EXPORT_DLL AudioProfile {
|
||||
public:
|
||||
//Profile name can't be empty.
|
||||
ccstd::string name;
|
||||
//The maximum number of simultaneous audio instance.
|
||||
unsigned int maxInstances{};
|
||||
|
||||
/* Minimum delay in between sounds */
|
||||
double minDelay{};
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*
|
||||
* @lua new
|
||||
*/
|
||||
AudioProfile() = default;
|
||||
};
|
||||
|
||||
class AudioEngineImpl;
|
||||
|
||||
/**
|
||||
* @class AudioEngine
|
||||
*
|
||||
* @brief Offers a interface to play audio.
|
||||
*
|
||||
* @note Make sure to call AudioEngine::end() when the audio engine is not needed anymore to release resources.
|
||||
* @js NA
|
||||
*/
|
||||
|
||||
class EXPORT_DLL AudioEngine {
|
||||
public:
|
||||
/** AudioState enum,all possible states of an audio instance.*/
|
||||
enum class AudioState {
|
||||
ERROR = -1,
|
||||
INITIALIZING,
|
||||
PLAYING,
|
||||
PAUSED
|
||||
};
|
||||
|
||||
static const int INVALID_AUDIO_ID;
|
||||
|
||||
static const float TIME_UNKNOWN;
|
||||
|
||||
static bool lazyInit();
|
||||
|
||||
/**
|
||||
* Release objects relating to AudioEngine.
|
||||
*
|
||||
* @warning It must be called before the application exit.
|
||||
* @lua endToLua
|
||||
*/
|
||||
static void end();
|
||||
|
||||
/**
|
||||
* Gets the default profile of audio instances.
|
||||
*
|
||||
* @return The default profile of audio instances.
|
||||
*/
|
||||
static AudioProfile *getDefaultProfile();
|
||||
|
||||
/**
|
||||
* Play 2d sound.
|
||||
*
|
||||
* @param filePath The path of an audio file.
|
||||
* @param loop Whether audio instance loop or not.
|
||||
* @param volume Volume value (range from 0.0 to 1.0).
|
||||
* @param profile A profile for audio instance. When profile is not specified, default profile will be used.
|
||||
* @return An audio ID. It allows you to dynamically change the behavior of an audio instance on the fly.
|
||||
*
|
||||
* @see `AudioProfile`
|
||||
*/
|
||||
static int play2d(const ccstd::string &filePath, bool loop = false, float volume = 1.0F, const AudioProfile *profile = nullptr);
|
||||
|
||||
/**
|
||||
* Sets whether an audio instance loop or not.
|
||||
*
|
||||
* @param audioID An audioID returned by the play2d function.
|
||||
* @param loop Whether audio instance loop or not.
|
||||
*/
|
||||
static void setLoop(int audioID, bool loop);
|
||||
|
||||
/**
|
||||
* Checks whether an audio instance is loop.
|
||||
*
|
||||
* @param audioID An audioID returned by the play2d function.
|
||||
* @return Whether or not an audio instance is loop.
|
||||
*/
|
||||
static bool isLoop(int audioID);
|
||||
|
||||
/**
|
||||
* Sets volume for an audio instance.
|
||||
*
|
||||
* @param audioID An audioID returned by the play2d function.
|
||||
* @param volume Volume value (range from 0.0 to 1.0).
|
||||
*/
|
||||
static void setVolume(int audioID, float volume);
|
||||
|
||||
/**
|
||||
* sets volume factor for all audio instance
|
||||
* @param factor, Volume factor(range from 0.0 to 1.0).
|
||||
*/
|
||||
static void setVolumeFactor(float factor);
|
||||
|
||||
/**
|
||||
* Gets the volume value of an audio instance.
|
||||
*
|
||||
* @param audioID An audioID returned by the play2d function.
|
||||
* @return Volume value (range from 0.0 to 1.0).
|
||||
*/
|
||||
static float getVolume(int audioID);
|
||||
|
||||
/**
|
||||
* Pause an audio instance.
|
||||
*
|
||||
* @param audioID An audioID returned by the play2d function.
|
||||
*/
|
||||
static void pause(int audioID);
|
||||
|
||||
/** Pause all playing audio instances. */
|
||||
static void pauseAll();
|
||||
|
||||
/**
|
||||
* Resume an audio instance.
|
||||
*
|
||||
* @param audioID An audioID returned by the play2d function.
|
||||
*/
|
||||
static void resume(int audioID);
|
||||
|
||||
/** Resume all suspended audio instances. */
|
||||
static void resumeAll();
|
||||
|
||||
/**
|
||||
* Stop an audio instance.
|
||||
*
|
||||
* @param audioID An audioID returned by the play2d function.
|
||||
*/
|
||||
static void stop(int audioID);
|
||||
|
||||
/** Stop all audio instances. */
|
||||
static void stopAll();
|
||||
|
||||
/**
|
||||
* Sets the current playback position of an audio instance.
|
||||
*
|
||||
* @param audioID An audioID returned by the play2d function.
|
||||
* @param time The offset in seconds from the start to seek to.
|
||||
* @return
|
||||
*/
|
||||
static bool setCurrentTime(int audioID, float time);
|
||||
|
||||
/**
|
||||
* Gets the current playback position of an audio instance.
|
||||
*
|
||||
* @param audioID An audioID returned by the play2d function.
|
||||
* @return The current playback position of an audio instance.
|
||||
*/
|
||||
static float getCurrentTime(int audioID);
|
||||
|
||||
/**
|
||||
* Gets the duration of an audio instance.
|
||||
*
|
||||
* @param audioID An audioID returned by the play2d function.
|
||||
* @return The duration of an audio instance.
|
||||
*/
|
||||
static float getDuration(int audioID);
|
||||
|
||||
/**
|
||||
* Gets the duration of an audio file.
|
||||
*
|
||||
* @param filePath The path of an audio file.
|
||||
* @return The duration of an audio file.
|
||||
*/
|
||||
static float getDurationFromFile(const ccstd::string &filePath);
|
||||
|
||||
/**
|
||||
* Returns the state of an audio instance.
|
||||
*
|
||||
* @param audioID An audioID returned by the play2d function.
|
||||
* @return The status of an audio instance.
|
||||
*/
|
||||
static AudioState getState(int audioID);
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when an audio instance has completed playing.
|
||||
*
|
||||
* @param audioID An audioID returned by the play2d function.
|
||||
* @param callback
|
||||
*/
|
||||
static void setFinishCallback(int audioID, const std::function<void(int, const ccstd::string &)> &callback);
|
||||
|
||||
/**
|
||||
* Gets the maximum number of simultaneous audio instance of AudioEngine.
|
||||
*/
|
||||
static int getMaxAudioInstance() { return static_cast<int>(sMaxInstances); }
|
||||
|
||||
/**
|
||||
* Sets the maximum number of simultaneous audio instance for AudioEngine.
|
||||
*
|
||||
* @param maxInstances The maximum number of simultaneous audio instance.
|
||||
*/
|
||||
static bool setMaxAudioInstance(int maxInstances);
|
||||
|
||||
/**
|
||||
* Uncache the audio data from internal buffer.
|
||||
* AudioEngine cache audio data on ios,mac, and oalsoft platform.
|
||||
*
|
||||
* @warning This can lead to stop related audio first.
|
||||
* @param filePath Audio file path.
|
||||
*/
|
||||
static void uncache(const ccstd::string &filePath);
|
||||
|
||||
/**
|
||||
* Uncache all audio data from internal buffer.
|
||||
*
|
||||
* @warning All audio will be stopped first.
|
||||
*/
|
||||
static void uncacheAll();
|
||||
|
||||
/**
|
||||
* Gets the audio profile by id of audio instance.
|
||||
*
|
||||
* @param audioID An audioID returned by the play2d function.
|
||||
* @return The audio profile.
|
||||
*/
|
||||
static AudioProfile *getProfile(int audioID);
|
||||
|
||||
/**
|
||||
* Gets an audio profile by name.
|
||||
*
|
||||
* @param profileName A name of audio profile.
|
||||
* @return The audio profile.
|
||||
*/
|
||||
static AudioProfile *getProfile(const ccstd::string &profileName);
|
||||
|
||||
/**
|
||||
* Preload audio file.
|
||||
* @param filePath The file path of an audio.
|
||||
*/
|
||||
static void preload(const ccstd::string &filePath) { preload(filePath, nullptr); }
|
||||
|
||||
/**
|
||||
* Preload audio file.
|
||||
* @param filePath The file path of an audio.
|
||||
* @param callback A callback which will be called after loading is finished.
|
||||
*/
|
||||
static void preload(const ccstd::string &filePath, const std::function<void(bool isSuccess)> &callback);
|
||||
|
||||
/**
|
||||
* Gets playing audio count.
|
||||
*/
|
||||
static int getPlayingAudioCount();
|
||||
|
||||
/**
|
||||
* Whether to enable playing audios
|
||||
* @note If it's disabled, current playing audios will be stopped and the later 'preload', 'play2d' methods will take no effects.
|
||||
*/
|
||||
static void setEnabled(bool isEnabled);
|
||||
/**
|
||||
* Check whether AudioEngine is enabled.
|
||||
*/
|
||||
static bool isEnabled();
|
||||
|
||||
/**
|
||||
* @brief Get the PCMHeader of audio
|
||||
*
|
||||
* @param url The file url of an audio. same as filePath
|
||||
* @return PCMHeader of audio
|
||||
*/
|
||||
static PCMHeader getPCMHeader(const char *url);
|
||||
|
||||
/**
|
||||
* @brief Get the Buffer object
|
||||
*
|
||||
* @param channelID as there might be several channels at same time, select one to get buffer.
|
||||
* Start from 0
|
||||
* @return PCM datas behave as a ccstd::vector<char>. You can check byte length in PCMHeader.
|
||||
*/
|
||||
static ccstd::vector<uint8_t> getOriginalPCMBuffer(const char *url, uint32_t channelID);
|
||||
|
||||
protected:
|
||||
static void addTask(const std::function<void()> &task);
|
||||
static void remove(int audioID);
|
||||
|
||||
static void pauseAll(ccstd::vector<int> *pausedAudioIDs);
|
||||
static void resumeAll(ccstd::vector<int> *pausedAudioIDs);
|
||||
|
||||
struct ProfileHelper {
|
||||
AudioProfile profile;
|
||||
|
||||
ccstd::list<int> audioIDs;
|
||||
|
||||
std::chrono::high_resolution_clock::time_point lastPlayTime;
|
||||
|
||||
ProfileHelper() = default;
|
||||
};
|
||||
|
||||
struct AudioInfo {
|
||||
const ccstd::string *filePath;
|
||||
ProfileHelper *profileHelper;
|
||||
|
||||
float volume;
|
||||
bool loop;
|
||||
float duration;
|
||||
AudioState state;
|
||||
|
||||
AudioInfo();
|
||||
~AudioInfo() = default;
|
||||
|
||||
private:
|
||||
AudioInfo(const AudioInfo &info);
|
||||
AudioInfo(AudioInfo &&info) noexcept;
|
||||
AudioInfo &operator=(const AudioInfo &info);
|
||||
AudioInfo &operator=(AudioInfo &&info) noexcept;
|
||||
};
|
||||
|
||||
//audioID,audioAttribute
|
||||
static ccstd::unordered_map<int, AudioInfo> sAudioIDInfoMap;
|
||||
|
||||
//audio file path,audio IDs
|
||||
static ccstd::unordered_map<ccstd::string, ccstd::list<int>> sAudioPathIDMap;
|
||||
|
||||
//profileName,ProfileHelper
|
||||
static ccstd::unordered_map<ccstd::string, ProfileHelper> sAudioPathProfileHelperMap;
|
||||
|
||||
static unsigned int sMaxInstances;
|
||||
|
||||
static ProfileHelper *sDefaultProfileHelper;
|
||||
|
||||
static AudioEngineImpl *sAudioEngineImpl;
|
||||
|
||||
class AudioEngineThreadPool;
|
||||
static AudioEngineThreadPool *sThreadPool;
|
||||
|
||||
static bool sIsEnabled;
|
||||
|
||||
private:
|
||||
static float sVolumeFactor;
|
||||
static events::EnterBackground::Listener sOnPauseListenerID;
|
||||
static events::EnterForeground::Listener sOnResumeListenerID;
|
||||
static ccstd::vector<int> sBreakAudioID;
|
||||
|
||||
static void onEnterBackground();
|
||||
static void onEnterForeground();
|
||||
|
||||
friend class AudioEngineImpl;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
|
||||
// end group
|
||||
/// @}
|
||||
76
cocos/audio/include/AudioMacros.h
Normal file
76
cocos/audio/include/AudioMacros.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "base/Log.h"
|
||||
|
||||
#define QUEUEBUFFER_NUM (3)
|
||||
#define QUEUEBUFFER_TIME_STEP (0.1f)
|
||||
|
||||
// log, CC_LOG_DEBUG aren't threadsafe, since we uses sub threads for parsing pcm data, threadsafe log output
|
||||
// is needed. Define the following macros (ALOGV, ALOGD, ALOGI, ALOGW, ALOGE) for threadsafe log output.
|
||||
|
||||
//IDEA:Move the definition of the following macros to a separated file.
|
||||
|
||||
#define audioLog(...) CC_LOG_DEBUG(__VA_ARGS__)
|
||||
|
||||
#define QUOTEME_(x) #x
|
||||
#define QUOTEME(x) QUOTEME_(x)
|
||||
|
||||
#if defined(CC_DEBUG) && CC_DEBUG > 0
|
||||
#define ALOGV(fmt, ...) audioLog("V/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
|
||||
#else
|
||||
#define ALOGV(fmt, ...) \
|
||||
do { \
|
||||
} while (false)
|
||||
#endif
|
||||
#define ALOGD(fmt, ...) audioLog("D/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
|
||||
#define ALOGI(fmt, ...) audioLog("I/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
|
||||
#define ALOGW(fmt, ...) audioLog("W/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
|
||||
#define ALOGE(fmt, ...) audioLog("E/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
|
||||
|
||||
#if defined(CC_DEBUG) && CC_DEBUG > 0
|
||||
#define CHECK_AL_ERROR_DEBUG() \
|
||||
do { \
|
||||
ALenum __error = alGetError(); \
|
||||
if (__error) { \
|
||||
ALOGE("OpenAL error 0x%04X in %s %s %d\n", __error, __FILE__, __FUNCTION__, __LINE__); \
|
||||
} \
|
||||
} while (false)
|
||||
#else
|
||||
#define CHECK_AL_ERROR_DEBUG()
|
||||
#endif
|
||||
|
||||
#define BREAK_IF(condition) \
|
||||
if (!!(condition)) { \
|
||||
break; \
|
||||
}
|
||||
|
||||
#define BREAK_IF_ERR_LOG(condition, fmt, ...) \
|
||||
if (!!(condition)) { \
|
||||
CC_LOG_DEBUG("(" QUOTEME(condition) ") failed, message: " fmt, ##__VA_ARGS__); \
|
||||
break; \
|
||||
}
|
||||
50
cocos/audio/include/Export.h
Normal file
50
cocos/audio/include/Export.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2010-2012 cocos2d-x.org
|
||||
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 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
|
||||
|
||||
#if defined(SHP)
|
||||
#include <FBaseConfig.h>
|
||||
#define EXPORT_DLL _EXPORT_
|
||||
#elif defined(_WIN32)
|
||||
#if defined(CC_STATIC)
|
||||
#define EXPORT_DLL
|
||||
#else
|
||||
#if defined(_EXPORT_DLL_)
|
||||
#define EXPORT_DLL __declspec(dllexport)
|
||||
#else /* use a DLL library */
|
||||
#define EXPORT_DLL __declspec(dllimport)
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#if defined(_SHARED_)
|
||||
#define EXPORT_DLL __attribute__((visibility("default")))
|
||||
#elif defined(IGNORE_EXPORT)
|
||||
#define EXPORT_DLL
|
||||
#else
|
||||
#define EXPORT_DLL
|
||||
#endif
|
||||
#endif
|
||||
339
cocos/audio/oalsoft/AudioCache.cpp
Normal file
339
cocos/audio/oalsoft/AudioCache.cpp
Normal file
@@ -0,0 +1,339 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#define LOG_TAG "AudioCache"
|
||||
|
||||
#include "audio/oalsoft/AudioCache.h"
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include "application/ApplicationManager.h"
|
||||
#include "audio/common/decoder/AudioDecoder.h"
|
||||
#include "audio/common/decoder/AudioDecoderManager.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define VERY_VERY_VERBOSE_LOGGING
|
||||
#ifdef VERY_VERY_VERBOSE_LOGGING
|
||||
#define ALOGVV ALOGV
|
||||
#else
|
||||
#define ALOGVV(...) \
|
||||
do { \
|
||||
} while (false)
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
unsigned int gIdIndex = 0;
|
||||
}
|
||||
|
||||
#define PCMDATA_CACHEMAXSIZE 1048576
|
||||
|
||||
using namespace cc; //NOLINT
|
||||
|
||||
AudioCache::AudioCache()
|
||||
: _isDestroyed(std::make_shared<bool>(false)), _id(++gIdIndex) {
|
||||
ALOGVV("AudioCache() %p, id=%u", this, _id);
|
||||
for (int i = 0; i < QUEUEBUFFER_NUM; ++i) {
|
||||
_queBuffers[i] = nullptr;
|
||||
_queBufferSize[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
AudioCache::~AudioCache() {
|
||||
ALOGVV("~AudioCache() %p, id=%u, begin", this, _id);
|
||||
*_isDestroyed = true;
|
||||
while (!_isLoadingFinished) {
|
||||
if (_isSkipReadDataTask) {
|
||||
ALOGV("id=%u, Skip read data task, don't continue to wait!", _id);
|
||||
break;
|
||||
}
|
||||
ALOGVV("id=%u, waiting readData thread to finish ...", _id);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
//wait for the 'readDataTask' task to exit
|
||||
_readDataTaskMutex.lock();
|
||||
_readDataTaskMutex.unlock();
|
||||
|
||||
if (_pcmData) {
|
||||
if (_state == State::READY) {
|
||||
if (_alBufferId != INVALID_AL_BUFFER_ID && alIsBuffer(_alBufferId)) {
|
||||
ALOGV("~AudioCache(id=%u), delete buffer: %u", _id, _alBufferId);
|
||||
alDeleteBuffers(1, &_alBufferId);
|
||||
_alBufferId = INVALID_AL_BUFFER_ID;
|
||||
}
|
||||
} else {
|
||||
ALOGW("AudioCache (%p), id=%u, buffer isn't ready, state=%d", this, _id, _state);
|
||||
}
|
||||
|
||||
free(_pcmData);
|
||||
}
|
||||
|
||||
if (_queBufferFrames > 0) {
|
||||
for (auto &buffer : _queBuffers) {
|
||||
free(buffer);
|
||||
}
|
||||
}
|
||||
ALOGVV("~AudioCache() %p, id=%u, end", this, _id);
|
||||
}
|
||||
|
||||
void AudioCache::readDataTask(unsigned int selfId) {
|
||||
//Note: It's in sub thread
|
||||
ALOGVV("readDataTask begin, cache id=%u", selfId);
|
||||
|
||||
_readDataTaskMutex.lock();
|
||||
_state = State::LOADING;
|
||||
|
||||
AudioDecoder *decoder = AudioDecoderManager::createDecoder(_fileFullPath.c_str());
|
||||
do {
|
||||
if (decoder == nullptr || !decoder->open(_fileFullPath.c_str())) {
|
||||
break;
|
||||
}
|
||||
|
||||
const uint32_t originalTotalFrames = decoder->getTotalFrames();
|
||||
_bytesPerFrame = decoder->getBytesPerFrame();
|
||||
const uint32_t sampleRate = decoder->getSampleRate();
|
||||
_channelCount = decoder->getChannelCount();
|
||||
|
||||
uint32_t totalFrames = originalTotalFrames;
|
||||
uint32_t dataSize = totalFrames * _bytesPerFrame;
|
||||
uint32_t remainingFrames = totalFrames;
|
||||
uint32_t adjustFrames = 0;
|
||||
|
||||
_format = _channelCount > 1 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
|
||||
_sampleRate = static_cast<ALsizei>(sampleRate);
|
||||
_duration = 1.0F * totalFrames / sampleRate;
|
||||
_totalFrames = totalFrames;
|
||||
|
||||
if (dataSize <= PCMDATA_CACHEMAXSIZE) {
|
||||
uint32_t framesRead = 0;
|
||||
const uint32_t framesToReadOnce = std::min(totalFrames, static_cast<uint32_t>(sampleRate * QUEUEBUFFER_TIME_STEP * QUEUEBUFFER_NUM));
|
||||
|
||||
ccstd::vector<char> adjustFrameBuf;
|
||||
|
||||
if (decoder->seek(totalFrames)) {
|
||||
auto *tmpBuf = static_cast<char *>(malloc(framesToReadOnce * _bytesPerFrame));
|
||||
adjustFrameBuf.reserve(framesToReadOnce * _bytesPerFrame);
|
||||
|
||||
// Adjust total frames by setting position to the end of frames and try to read more data.
|
||||
// This is a workaround for https://github.com/cocos2d/cocos2d-x/issues/16938
|
||||
do {
|
||||
framesRead = decoder->read(framesToReadOnce, tmpBuf);
|
||||
if (framesRead > 0) {
|
||||
adjustFrames += framesRead;
|
||||
adjustFrameBuf.insert(adjustFrameBuf.end(), tmpBuf, tmpBuf + framesRead * _bytesPerFrame);
|
||||
}
|
||||
|
||||
} while (framesRead > 0);
|
||||
|
||||
if (adjustFrames > 0) {
|
||||
ALOGV("Orignal total frames: %u, adjust frames: %u, current total frames: %u", totalFrames, adjustFrames, totalFrames + adjustFrames);
|
||||
totalFrames += adjustFrames;
|
||||
_totalFrames = remainingFrames = totalFrames;
|
||||
}
|
||||
|
||||
// Reset dataSize
|
||||
dataSize = totalFrames * _bytesPerFrame;
|
||||
|
||||
free(tmpBuf);
|
||||
}
|
||||
// Reset to frame 0
|
||||
BREAK_IF_ERR_LOG(!decoder->seek(0), "AudioDecoder::seek(0) failed!");
|
||||
|
||||
_pcmData = static_cast<char *>(malloc(dataSize));
|
||||
|
||||
CC_ASSERT(_pcmData);
|
||||
memset(_pcmData, 0x00, dataSize);
|
||||
|
||||
if (adjustFrames > 0) {
|
||||
memcpy(_pcmData + (dataSize - adjustFrameBuf.size()), adjustFrameBuf.data(), adjustFrameBuf.size());
|
||||
}
|
||||
|
||||
alGenBuffers(1, &_alBufferId);
|
||||
auto alError = alGetError();
|
||||
if (alError != AL_NO_ERROR) {
|
||||
ALOGE("%s: attaching audio to buffer fail: %x", __FUNCTION__, alError);
|
||||
break;
|
||||
}
|
||||
|
||||
if (*_isDestroyed) {
|
||||
break;
|
||||
}
|
||||
|
||||
framesRead = decoder->readFixedFrames(std::min(framesToReadOnce, remainingFrames), _pcmData + _framesRead * _bytesPerFrame);
|
||||
_framesRead += framesRead;
|
||||
remainingFrames -= framesRead;
|
||||
|
||||
if (*_isDestroyed) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t frames = 0;
|
||||
while (!*_isDestroyed && _framesRead < originalTotalFrames) {
|
||||
frames = std::min(framesToReadOnce, remainingFrames);
|
||||
if (_framesRead + frames > originalTotalFrames) {
|
||||
frames = originalTotalFrames - _framesRead;
|
||||
}
|
||||
framesRead = decoder->read(frames, _pcmData + _framesRead * _bytesPerFrame);
|
||||
if (framesRead == 0) {
|
||||
break;
|
||||
}
|
||||
_framesRead += framesRead;
|
||||
remainingFrames -= framesRead;
|
||||
}
|
||||
|
||||
if (*_isDestroyed) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (_framesRead < originalTotalFrames) {
|
||||
memset(_pcmData + _framesRead * _bytesPerFrame, 0x00, (totalFrames - _framesRead) * _bytesPerFrame);
|
||||
}
|
||||
ALOGV("pcm buffer was loaded successfully, total frames: %u, total read frames: %u, adjust frames: %u, remainingFrames: %u", totalFrames, _framesRead, adjustFrames, remainingFrames);
|
||||
|
||||
_framesRead += adjustFrames;
|
||||
|
||||
alBufferData(_alBufferId, _format, _pcmData, static_cast<ALsizei>(dataSize), static_cast<ALsizei>(sampleRate));
|
||||
|
||||
_state = State::READY;
|
||||
} else {
|
||||
_isStreaming = true;
|
||||
_queBufferFrames = static_cast<uint32_t>(sampleRate * QUEUEBUFFER_TIME_STEP);
|
||||
BREAK_IF_ERR_LOG(_queBufferFrames == 0, "_queBufferFrames == 0");
|
||||
|
||||
const uint32_t queBufferBytes = _queBufferFrames * _bytesPerFrame;
|
||||
|
||||
for (int index = 0; index < QUEUEBUFFER_NUM; ++index) {
|
||||
_queBuffers[index] = static_cast<char *>(malloc(queBufferBytes));
|
||||
_queBufferSize[index] = queBufferBytes;
|
||||
|
||||
decoder->readFixedFrames(_queBufferFrames, _queBuffers[index]);
|
||||
}
|
||||
|
||||
_state = State::READY;
|
||||
}
|
||||
|
||||
} while (false);
|
||||
|
||||
if (decoder != nullptr) {
|
||||
decoder->close();
|
||||
}
|
||||
|
||||
AudioDecoderManager::destroyDecoder(decoder);
|
||||
|
||||
if (_state != State::READY) {
|
||||
_state = State::FAILED;
|
||||
if (_alBufferId != INVALID_AL_BUFFER_ID && alIsBuffer(_alBufferId)) {
|
||||
ALOGV("readDataTask failed, delete buffer: %u", _alBufferId);
|
||||
alDeleteBuffers(1, &_alBufferId);
|
||||
_alBufferId = INVALID_AL_BUFFER_ID;
|
||||
}
|
||||
}
|
||||
|
||||
//IDEA: Why to invoke play callback first? Should it be after 'load' callback?
|
||||
invokingPlayCallbacks();
|
||||
invokingLoadCallbacks();
|
||||
|
||||
_isLoadingFinished = true;
|
||||
_readDataTaskMutex.unlock();
|
||||
ALOGVV("readDataTask end, cache id=%u", selfId);
|
||||
}
|
||||
|
||||
void AudioCache::addPlayCallback(const std::function<void()> &callback) {
|
||||
std::lock_guard<std::mutex> lk(_playCallbackMutex);
|
||||
switch (_state) {
|
||||
case State::INITIAL:
|
||||
case State::LOADING:
|
||||
_playCallbacks.push_back(callback);
|
||||
break;
|
||||
|
||||
case State::READY:
|
||||
// If state is failure, we still need to invoke the callback
|
||||
// since the callback will set the 'AudioPlayer::_removeByAudioEngine' flag to true.
|
||||
case State::FAILED:
|
||||
callback();
|
||||
break;
|
||||
|
||||
default:
|
||||
ALOGE("Invalid state: %d", _state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioCache::invokingPlayCallbacks() {
|
||||
std::lock_guard<std::mutex> lk(_playCallbackMutex);
|
||||
|
||||
for (auto &&cb : _playCallbacks) {
|
||||
cb();
|
||||
}
|
||||
|
||||
_playCallbacks.clear();
|
||||
}
|
||||
|
||||
void AudioCache::addLoadCallback(const std::function<void(bool)> &callback) {
|
||||
switch (_state) {
|
||||
case State::INITIAL:
|
||||
case State::LOADING:
|
||||
_loadCallbacks.push_back(callback);
|
||||
break;
|
||||
|
||||
case State::READY:
|
||||
callback(true);
|
||||
break;
|
||||
case State::FAILED:
|
||||
callback(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
ALOGE("Invalid state: %d", _state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioCache::invokingLoadCallbacks() {
|
||||
if (*_isDestroyed) {
|
||||
ALOGV("AudioCache (%p) was destroyed, don't invoke preload callback ...", this);
|
||||
return;
|
||||
}
|
||||
|
||||
auto isDestroyed = _isDestroyed;
|
||||
|
||||
BaseEngine::SchedulerPtr scheduler =
|
||||
CC_CURRENT_APPLICATION() ? CC_CURRENT_APPLICATION()->getEngine()->getScheduler() : nullptr;
|
||||
if (!scheduler) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler->performFunctionInCocosThread([&, isDestroyed]() {
|
||||
if (*isDestroyed) {
|
||||
ALOGV("invokingLoadCallbacks perform in cocos thread, AudioCache (%p) was destroyed!", this);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &&cb : _loadCallbacks) {
|
||||
cb(_state == State::READY);
|
||||
}
|
||||
|
||||
_loadCallbacks.clear();
|
||||
});
|
||||
}
|
||||
121
cocos/audio/oalsoft/AudioCache.h
Normal file
121
cocos/audio/oalsoft/AudioCache.h
Normal file
@@ -0,0 +1,121 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 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 <sys/types.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include "base/std/container/string.h"
|
||||
#if defined(OPENAL_PLAIN_INCLUDES)
|
||||
#include <al.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
#include <OpenalSoft/al.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OHOS
|
||||
#include <AL/al.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX
|
||||
#include <AL/al.h>
|
||||
#endif
|
||||
#include "audio/include/AudioMacros.h"
|
||||
#include "base/Macros.h"
|
||||
#include "base/std/container/vector.h"
|
||||
#define INVALID_AL_BUFFER_ID 0xFFFFFFFF
|
||||
namespace cc {
|
||||
class AudioEngineImpl;
|
||||
class AudioPlayer;
|
||||
|
||||
class CC_DLL AudioCache {
|
||||
public:
|
||||
enum class State {
|
||||
INITIAL,
|
||||
LOADING,
|
||||
READY,
|
||||
FAILED
|
||||
};
|
||||
|
||||
AudioCache();
|
||||
~AudioCache();
|
||||
|
||||
void addPlayCallback(const std::function<void()> &callback);
|
||||
|
||||
void addLoadCallback(const std::function<void(bool)> &callback);
|
||||
|
||||
uint32_t getChannelCount() const { return _channelCount; }
|
||||
bool isStreaming() const { return _isStreaming; }
|
||||
|
||||
protected:
|
||||
void setSkipReadDataTask(bool isSkip) { _isSkipReadDataTask = isSkip; };
|
||||
void readDataTask(unsigned int selfId);
|
||||
|
||||
void invokingPlayCallbacks();
|
||||
|
||||
void invokingLoadCallbacks();
|
||||
|
||||
//pcm data related stuff
|
||||
ALenum _format{-1};
|
||||
ALsizei _sampleRate{-1};
|
||||
float _duration{0.0F};
|
||||
uint32_t _totalFrames{0};
|
||||
uint32_t _framesRead{0};
|
||||
uint32_t _bytesPerFrame{0};
|
||||
|
||||
bool _isStreaming{false};
|
||||
uint32_t _channelCount{1};
|
||||
|
||||
/*Cache related stuff;
|
||||
* Cache pcm data when sizeInBytes less than PCMDATA_CACHEMAXSIZE
|
||||
*/
|
||||
ALuint _alBufferId{INVALID_AL_BUFFER_ID};
|
||||
char *_pcmData{nullptr};
|
||||
|
||||
/*Queue buffer related stuff
|
||||
* Streaming in OpenAL when sizeInBytes greater then PCMDATA_CACHEMAXSIZE
|
||||
*/
|
||||
char *_queBuffers[QUEUEBUFFER_NUM];
|
||||
ALsizei _queBufferSize[QUEUEBUFFER_NUM];
|
||||
uint32_t _queBufferFrames{0};
|
||||
|
||||
std::mutex _playCallbackMutex;
|
||||
ccstd::vector<std::function<void()>> _playCallbacks;
|
||||
|
||||
// loadCallbacks doesn't need mutex since it's invoked only in Cocos thread.
|
||||
ccstd::vector<std::function<void(bool)>> _loadCallbacks;
|
||||
|
||||
std::mutex _readDataTaskMutex;
|
||||
|
||||
State _state{State::INITIAL};
|
||||
|
||||
std::shared_ptr<bool> _isDestroyed;
|
||||
ccstd::string _fileFullPath;
|
||||
unsigned int _id;
|
||||
bool _isLoadingFinished{false};
|
||||
bool _isSkipReadDataTask{false};
|
||||
|
||||
friend class AudioEngineImpl;
|
||||
friend class AudioPlayer;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
645
cocos/audio/oalsoft/AudioEngine-soft.cpp
Normal file
645
cocos/audio/oalsoft/AudioEngine-soft.cpp
Normal file
@@ -0,0 +1,645 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 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 <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include "audio/common/decoder/AudioDecoder.h"
|
||||
#include "base/Log.h"
|
||||
#include "base/Utils.h"
|
||||
#include "base/std/container/vector.h"
|
||||
#define LOG_TAG "AudioEngine-OALSOFT"
|
||||
|
||||
#include "audio/oalsoft/AudioEngine-soft.h"
|
||||
|
||||
#ifdef OPENAL_PLAIN_INCLUDES
|
||||
#include "alc.h"
|
||||
#include "alext.h"
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
#include "OpenalSoft/alc.h"
|
||||
#include "OpenalSoft/alext.h"
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OHOS
|
||||
#include "AL/alc.h"
|
||||
#include "AL/alext.h"
|
||||
#elif CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX
|
||||
#include "AL/alc.h"
|
||||
#include "AL/alext.h"
|
||||
#endif
|
||||
#include "application/ApplicationManager.h"
|
||||
#include "audio/common/decoder/AudioDecoderManager.h"
|
||||
#include "audio/include/AudioEngine.h"
|
||||
#include "base/Scheduler.h"
|
||||
#include "base/memory/Memory.h"
|
||||
#include "platform/FileUtils.h"
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
#include <windows.h>
|
||||
|
||||
// log, CC_LOG_DEBUG aren't threadsafe, since we uses sub threads for parsing pcm data, threadsafe log output
|
||||
// is needed. Define the following macros (ALOGV, ALOGD, ALOGI, ALOGW, ALOGE) for threadsafe log output.
|
||||
|
||||
//IDEA: Move _winLog, winLog to a separated file
|
||||
static void _winLog(const char *format, va_list args) {
|
||||
static const int MAX_LOG_LENGTH = 16 * 1024;
|
||||
int bufferSize = MAX_LOG_LENGTH;
|
||||
char *buf = nullptr;
|
||||
|
||||
do {
|
||||
buf = ccnew char[bufferSize];
|
||||
if (buf == nullptr)
|
||||
return; // not enough memory
|
||||
|
||||
int ret = vsnprintf(buf, bufferSize - 3, format, args);
|
||||
if (ret < 0) {
|
||||
bufferSize *= 2;
|
||||
|
||||
delete[] buf;
|
||||
} else
|
||||
break;
|
||||
|
||||
} while (true);
|
||||
|
||||
strcat(buf, "\n");
|
||||
|
||||
int pos = 0;
|
||||
auto len = static_cast<int>(strlen(buf));
|
||||
char tempBuf[MAX_LOG_LENGTH + 1] = {0};
|
||||
WCHAR wszBuf[MAX_LOG_LENGTH + 1] = {0};
|
||||
|
||||
do {
|
||||
std::copy(buf + pos, buf + pos + MAX_LOG_LENGTH, tempBuf);
|
||||
|
||||
tempBuf[MAX_LOG_LENGTH] = 0;
|
||||
|
||||
MultiByteToWideChar(CP_UTF8, 0, tempBuf, -1, wszBuf, sizeof(wszBuf));
|
||||
OutputDebugStringW(wszBuf);
|
||||
|
||||
pos += MAX_LOG_LENGTH;
|
||||
|
||||
} while (pos < len);
|
||||
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
#ifndef audioLog
|
||||
void audioLog(const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
_winLog(format, args);
|
||||
va_end(args);
|
||||
}
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#define audioLog(...) CC_LOG_DEBUG(__VA_ARGS__)
|
||||
|
||||
#endif
|
||||
|
||||
using namespace cc; //NOLINT
|
||||
|
||||
static ALCdevice *sALDevice = nullptr;
|
||||
static ALCcontext *sALContext = nullptr;
|
||||
|
||||
AudioEngineImpl::AudioEngineImpl()
|
||||
: _lazyInitLoop(true),
|
||||
_currentAudioID(0) {
|
||||
}
|
||||
|
||||
AudioEngineImpl::~AudioEngineImpl() {
|
||||
if (auto sche = _scheduler.lock()) {
|
||||
sche->unschedule("AudioEngine", this);
|
||||
}
|
||||
|
||||
if (sALContext) {
|
||||
alDeleteSources(MAX_AUDIOINSTANCES, _alSources);
|
||||
|
||||
_audioCaches.clear();
|
||||
|
||||
alcMakeContextCurrent(nullptr);
|
||||
alcDestroyContext(sALContext);
|
||||
sALContext = nullptr;
|
||||
}
|
||||
|
||||
if (sALDevice) {
|
||||
alcCloseDevice(sALDevice);
|
||||
sALDevice = nullptr;
|
||||
}
|
||||
|
||||
AudioDecoderManager::destroy();
|
||||
}
|
||||
|
||||
bool AudioEngineImpl::init() {
|
||||
bool ret = false;
|
||||
do {
|
||||
sALDevice = alcOpenDevice(nullptr);
|
||||
|
||||
if (sALDevice) {
|
||||
alGetError();
|
||||
sALContext = alcCreateContext(sALDevice, nullptr);
|
||||
alcMakeContextCurrent(sALContext);
|
||||
|
||||
alGenSources(MAX_AUDIOINSTANCES, _alSources);
|
||||
auto alError = alGetError();
|
||||
if (alError != AL_NO_ERROR) {
|
||||
CC_LOG_ERROR("%s:generating sources failed! error = %x\n", __FUNCTION__, alError);
|
||||
break;
|
||||
}
|
||||
|
||||
for (unsigned int src : _alSources) {
|
||||
_alSourceUsed[src] = false;
|
||||
}
|
||||
|
||||
_scheduler = CC_CURRENT_ENGINE()->getScheduler();
|
||||
ret = AudioDecoderManager::init();
|
||||
CC_LOG_DEBUG("OpenAL was initialized successfully!");
|
||||
}
|
||||
} while (false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
AudioCache *AudioEngineImpl::preload(const ccstd::string &filePath, const std::function<void(bool)> &callback) {
|
||||
AudioCache *audioCache = nullptr;
|
||||
|
||||
auto it = _audioCaches.find(filePath);
|
||||
if (it == _audioCaches.end()) {
|
||||
audioCache = &_audioCaches[filePath];
|
||||
audioCache->_fileFullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
|
||||
unsigned int cacheId = audioCache->_id;
|
||||
auto isCacheDestroyed = audioCache->_isDestroyed;
|
||||
AudioEngine::addTask([audioCache, cacheId, isCacheDestroyed]() {
|
||||
if (*isCacheDestroyed) {
|
||||
ALOGV("AudioCache (id=%u) was destroyed, no need to launch readDataTask.", cacheId);
|
||||
audioCache->setSkipReadDataTask(true);
|
||||
return;
|
||||
}
|
||||
audioCache->readDataTask(cacheId);
|
||||
});
|
||||
} else {
|
||||
audioCache = &it->second;
|
||||
}
|
||||
|
||||
if (audioCache && callback) {
|
||||
audioCache->addLoadCallback(callback);
|
||||
}
|
||||
return audioCache;
|
||||
}
|
||||
|
||||
int AudioEngineImpl::play2d(const ccstd::string &filePath, bool loop, float volume) {
|
||||
if (sALDevice == nullptr) {
|
||||
return AudioEngine::INVALID_AUDIO_ID;
|
||||
}
|
||||
|
||||
bool sourceFlag = false;
|
||||
ALuint alSource = 0;
|
||||
for (unsigned int src : _alSources) {
|
||||
alSource = src;
|
||||
|
||||
if (!_alSourceUsed[alSource]) {
|
||||
sourceFlag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!sourceFlag) {
|
||||
return AudioEngine::INVALID_AUDIO_ID;
|
||||
}
|
||||
|
||||
auto player = ccnew AudioPlayer;
|
||||
if (player == nullptr) {
|
||||
return AudioEngine::INVALID_AUDIO_ID;
|
||||
}
|
||||
|
||||
player->_alSource = alSource;
|
||||
player->_loop = loop;
|
||||
player->_volume = volume;
|
||||
|
||||
auto audioCache = preload(filePath, nullptr);
|
||||
if (audioCache == nullptr) {
|
||||
delete player;
|
||||
return AudioEngine::INVALID_AUDIO_ID;
|
||||
}
|
||||
|
||||
player->setCache(audioCache);
|
||||
_threadMutex.lock();
|
||||
_audioPlayers[_currentAudioID] = player;
|
||||
_threadMutex.unlock();
|
||||
|
||||
_alSourceUsed[alSource] = true;
|
||||
|
||||
audioCache->addPlayCallback(std::bind(&AudioEngineImpl::play2dImpl, this, audioCache, _currentAudioID));
|
||||
|
||||
if (_lazyInitLoop) {
|
||||
_lazyInitLoop = false;
|
||||
if (auto sche = _scheduler.lock()) {
|
||||
sche->schedule(CC_CALLBACK_1(AudioEngineImpl::update, this), this, 0.05F, false, "AudioEngine");
|
||||
}
|
||||
}
|
||||
|
||||
return _currentAudioID++;
|
||||
}
|
||||
|
||||
void AudioEngineImpl::play2dImpl(AudioCache *cache, int audioID) {
|
||||
//Note: It may bn in sub thread or main thread :(
|
||||
if (!*cache->_isDestroyed && cache->_state == AudioCache::State::READY) {
|
||||
_threadMutex.lock();
|
||||
auto playerIt = _audioPlayers.find(audioID);
|
||||
if (playerIt != _audioPlayers.end() && playerIt->second->play2d()) {
|
||||
if (auto sche = _scheduler.lock()) {
|
||||
sche->performFunctionInCocosThread([audioID]() {
|
||||
if (AudioEngine::sAudioIDInfoMap.find(audioID) != AudioEngine::sAudioIDInfoMap.end()) {
|
||||
AudioEngine::sAudioIDInfoMap[audioID].state = AudioEngine::AudioState::PLAYING;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
_threadMutex.unlock();
|
||||
} else {
|
||||
CC_LOG_DEBUG("AudioEngineImpl::play2dImpl, cache was destroyed or not ready!");
|
||||
auto iter = _audioPlayers.find(audioID);
|
||||
if (iter != _audioPlayers.end()) {
|
||||
iter->second->_removeByAudioEngine = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngineImpl::setVolume(int audioID, float volume) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return;
|
||||
}
|
||||
auto player = _audioPlayers[audioID];
|
||||
player->_volume = volume;
|
||||
|
||||
if (player->_ready) {
|
||||
alSourcef(_audioPlayers[audioID]->_alSource, AL_GAIN, volume);
|
||||
|
||||
auto error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
ALOGE("%s: audio id = %d, error = %x", __FUNCTION__, audioID, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngineImpl::setLoop(int audioID, bool loop) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return;
|
||||
}
|
||||
auto player = _audioPlayers[audioID];
|
||||
|
||||
if (player->_ready) {
|
||||
if (player->_streamingSource) {
|
||||
player->setLoop(loop);
|
||||
} else {
|
||||
if (loop) {
|
||||
alSourcei(player->_alSource, AL_LOOPING, AL_TRUE);
|
||||
} else {
|
||||
alSourcei(player->_alSource, AL_LOOPING, AL_FALSE);
|
||||
}
|
||||
|
||||
auto error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
ALOGE("%s: audio id = %d, error = %x", __FUNCTION__, audioID, error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
player->_loop = loop;
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioEngineImpl::pause(int audioID) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return false;
|
||||
}
|
||||
bool ret = true;
|
||||
alSourcePause(_audioPlayers[audioID]->_alSource);
|
||||
|
||||
auto error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
ret = false;
|
||||
ALOGE("%s: audio id = %d, error = %x\n", __FUNCTION__, audioID, error);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AudioEngineImpl::resume(int audioID) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return false;
|
||||
}
|
||||
bool ret = true;
|
||||
alSourcePlay(_audioPlayers[audioID]->_alSource);
|
||||
|
||||
auto error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
ret = false;
|
||||
ALOGE("%s: audio id = %d, error = %x\n", __FUNCTION__, audioID, error);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AudioEngineImpl::stop(int audioID) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return;
|
||||
}
|
||||
auto player = _audioPlayers[audioID];
|
||||
player->destroy();
|
||||
//Note: Don't set the flag to false here, it should be set in 'update' function.
|
||||
// Otherwise, the state got from alSourceState may be wrong
|
||||
// _alSourceUsed[player->_alSource] = false;
|
||||
|
||||
// Call 'update' method to cleanup immediately since the schedule may be cancelled without any notification.
|
||||
update(0.0F);
|
||||
}
|
||||
|
||||
void AudioEngineImpl::stopAll() {
|
||||
for (auto &&player : _audioPlayers) {
|
||||
player.second->destroy();
|
||||
}
|
||||
//Note: Don't set the flag to false here, it should be set in 'update' function.
|
||||
// Otherwise, the state got from alSourceState may be wrong
|
||||
// for(int index = 0; index < MAX_AUDIOINSTANCES; ++index)
|
||||
// {
|
||||
// _alSourceUsed[_alSources[index]] = false;
|
||||
// }
|
||||
|
||||
// Call 'update' method to cleanup immediately since the schedule may be cancelled without any notification.
|
||||
update(0.0F);
|
||||
}
|
||||
|
||||
float AudioEngineImpl::getDuration(int audioID) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return 0.0F;
|
||||
}
|
||||
auto player = _audioPlayers[audioID];
|
||||
if (player->_ready) {
|
||||
return player->_audioCache->_duration;
|
||||
}
|
||||
return AudioEngine::TIME_UNKNOWN;
|
||||
}
|
||||
|
||||
float AudioEngineImpl::getDurationFromFile(const ccstd::string &filePath) {
|
||||
auto it = _audioCaches.find(filePath);
|
||||
if (it == _audioCaches.end()) {
|
||||
this->preload(filePath, nullptr);
|
||||
return AudioEngine::TIME_UNKNOWN;
|
||||
}
|
||||
|
||||
return it->second._duration;
|
||||
}
|
||||
|
||||
float AudioEngineImpl::getCurrentTime(int audioID) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return 0.0F;
|
||||
}
|
||||
float ret = 0.0F;
|
||||
auto player = _audioPlayers[audioID];
|
||||
if (player->_ready) {
|
||||
if (player->_streamingSource) {
|
||||
ret = player->getTime();
|
||||
} else {
|
||||
alGetSourcef(player->_alSource, AL_SEC_OFFSET, &ret);
|
||||
|
||||
auto error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
ALOGE("%s, audio id:%d,error code:%x", __FUNCTION__, audioID, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AudioEngineImpl::setCurrentTime(int audioID, float time) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return false;
|
||||
}
|
||||
bool ret = false;
|
||||
auto player = _audioPlayers[audioID];
|
||||
|
||||
do {
|
||||
if (!player->_ready) {
|
||||
std::lock_guard<std::mutex> lck(player->_play2dMutex); // To prevent the race condition
|
||||
player->_timeDirty = true;
|
||||
player->_currTime = time;
|
||||
break;
|
||||
}
|
||||
|
||||
if (player->_streamingSource) {
|
||||
ret = player->setTime(time);
|
||||
break;
|
||||
}
|
||||
|
||||
if (player->_audioCache->_framesRead != player->_audioCache->_totalFrames &&
|
||||
(time * player->_audioCache->_sampleRate) > player->_audioCache->_framesRead) {
|
||||
ALOGE("%s: audio id = %d", __FUNCTION__, audioID);
|
||||
break;
|
||||
}
|
||||
|
||||
alSourcef(player->_alSource, AL_SEC_OFFSET, time);
|
||||
|
||||
auto error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
ALOGE("%s: audio id = %d, error = %x", __FUNCTION__, audioID, error);
|
||||
}
|
||||
ret = true;
|
||||
|
||||
} while (false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AudioEngineImpl::setFinishCallback(int audioID, const std::function<void(int, const ccstd::string &)> &callback) {
|
||||
if (!checkAudioIdValid(audioID)) {
|
||||
return;
|
||||
}
|
||||
_audioPlayers[audioID]->_finishCallbak = callback;
|
||||
}
|
||||
|
||||
void AudioEngineImpl::update(float /*dt*/) {
|
||||
ALint sourceState;
|
||||
int audioID;
|
||||
AudioPlayer *player;
|
||||
ALuint alSource;
|
||||
|
||||
// ALOGV("AudioPlayer count: %d", (int)_audioPlayers.size());
|
||||
|
||||
for (auto it = _audioPlayers.begin(); it != _audioPlayers.end();) {
|
||||
audioID = it->first;
|
||||
player = it->second;
|
||||
alSource = player->_alSource;
|
||||
alGetSourcei(alSource, AL_SOURCE_STATE, &sourceState);
|
||||
|
||||
if (player->_removeByAudioEngine) {
|
||||
AudioEngine::remove(audioID);
|
||||
_threadMutex.lock();
|
||||
it = _audioPlayers.erase(it);
|
||||
_threadMutex.unlock();
|
||||
delete player;
|
||||
_alSourceUsed[alSource] = false;
|
||||
} else if (player->_ready && sourceState == AL_STOPPED) {
|
||||
ccstd::string filePath;
|
||||
if (player->_finishCallbak) {
|
||||
auto &audioInfo = AudioEngine::sAudioIDInfoMap[audioID];
|
||||
filePath = *audioInfo.filePath;
|
||||
}
|
||||
|
||||
AudioEngine::remove(audioID);
|
||||
|
||||
_threadMutex.lock();
|
||||
it = _audioPlayers.erase(it);
|
||||
_threadMutex.unlock();
|
||||
|
||||
if (player->_finishCallbak) {
|
||||
player->_finishCallbak(audioID, filePath); //IDEA: callback will delay 50ms
|
||||
}
|
||||
delete player;
|
||||
_alSourceUsed[alSource] = false;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (_audioPlayers.empty()) {
|
||||
_lazyInitLoop = true;
|
||||
if (auto sche = _scheduler.lock()) {
|
||||
sche->unschedule("AudioEngine", this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngineImpl::uncache(const ccstd::string &filePath) {
|
||||
_audioCaches.erase(filePath);
|
||||
}
|
||||
|
||||
void AudioEngineImpl::uncacheAll() {
|
||||
_audioCaches.clear();
|
||||
}
|
||||
|
||||
bool AudioEngineImpl::checkAudioIdValid(int audioID) {
|
||||
return _audioPlayers.find(audioID) != _audioPlayers.end();
|
||||
}
|
||||
|
||||
PCMHeader AudioEngineImpl::getPCMHeader(const char *url) {
|
||||
PCMHeader header{};
|
||||
auto itr = _audioCaches.find(url);
|
||||
if (itr != _audioCaches.end() && itr->second._state == AudioCache::State::READY) {
|
||||
CC_LOG_DEBUG("file %s found in cache, load header directly", url);
|
||||
auto cache = &itr->second;
|
||||
header.bytesPerFrame = cache->_bytesPerFrame;
|
||||
header.channelCount = cache->_channelCount;
|
||||
header.dataFormat = AudioDataFormat::SIGNED_16;
|
||||
header.sampleRate = cache->_sampleRate;
|
||||
header.totalFrames = cache->_totalFrames;
|
||||
return header;
|
||||
}
|
||||
ccstd::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url);
|
||||
if (fileFullPath.empty()) {
|
||||
CC_LOG_DEBUG("file %s does not exist or failed to load", url);
|
||||
return header;
|
||||
}
|
||||
|
||||
AudioDecoder *decoder = AudioDecoderManager::createDecoder(fileFullPath.c_str());
|
||||
if (decoder == nullptr) {
|
||||
CC_LOG_DEBUG("decode %s failed, the file formate might not support", url);
|
||||
return header;
|
||||
}
|
||||
// Ready to decode
|
||||
do {
|
||||
if (!decoder->open(fileFullPath.c_str())) {
|
||||
CC_LOG_ERROR("[Audio Decoder] File open failed %s", url);
|
||||
break;
|
||||
}
|
||||
header = decoder->getPCMHeader();
|
||||
} while (false);
|
||||
|
||||
AudioDecoderManager::destroyDecoder(decoder);
|
||||
return header;
|
||||
}
|
||||
|
||||
ccstd::vector<uint8_t> AudioEngineImpl::getOriginalPCMBuffer(const char *url, uint32_t channelID) {
|
||||
ccstd::vector<uint8_t> pcmData;
|
||||
auto itr = _audioCaches.find(url);
|
||||
if (itr != _audioCaches.end() && itr->second._state == AudioCache::State::READY) {
|
||||
auto cache = &itr->second;
|
||||
auto bytesPerChannelInFrame = cache->_bytesPerFrame / cache->_channelCount;
|
||||
pcmData.resize(bytesPerChannelInFrame * cache->_totalFrames);
|
||||
auto p = pcmData.data();
|
||||
if (!cache->isStreaming()) { // Cache contains a fully prepared buffer.
|
||||
for (int itr = 0; itr < cache->_totalFrames; itr++) {
|
||||
memcpy(p, cache->_pcmData + itr * cache->_bytesPerFrame + channelID * bytesPerChannelInFrame, bytesPerChannelInFrame);
|
||||
p += bytesPerChannelInFrame;
|
||||
}
|
||||
return pcmData;
|
||||
}
|
||||
}
|
||||
ccstd::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url);
|
||||
|
||||
if (fileFullPath.empty()) {
|
||||
CC_LOG_DEBUG("file %s does not exist or failed to load", url);
|
||||
return pcmData;
|
||||
}
|
||||
|
||||
AudioDecoder *decoder = AudioDecoderManager::createDecoder(fileFullPath.c_str());
|
||||
if (decoder == nullptr) {
|
||||
CC_LOG_DEBUG("decode %s failed, the file formate might not support", url);
|
||||
return pcmData;
|
||||
}
|
||||
do {
|
||||
if (!decoder->open(fileFullPath.c_str())) {
|
||||
CC_LOG_ERROR("[Audio Decoder] File open failed %s", url);
|
||||
break;
|
||||
}
|
||||
auto audioInfo = decoder->getPCMHeader();
|
||||
const uint32_t bytesPerChannelInFrame = audioInfo.bytesPerFrame / audioInfo.channelCount;
|
||||
if (channelID >= audioInfo.channelCount) {
|
||||
CC_LOG_ERROR("channelID invalid, total channel count is %d but %d is required", audioInfo.channelCount, channelID);
|
||||
break;
|
||||
}
|
||||
uint32_t totalFrames = decoder->getTotalFrames();
|
||||
uint32_t remainingFrames = totalFrames;
|
||||
uint32_t framesRead = 0;
|
||||
uint32_t framesToReadOnce = std::min(remainingFrames, static_cast<uint32_t>(decoder->getSampleRate() * QUEUEBUFFER_TIME_STEP * QUEUEBUFFER_NUM));
|
||||
AudioDataFormat type = audioInfo.dataFormat;
|
||||
char *tmpBuf = static_cast<char *>(malloc(framesToReadOnce * audioInfo.bytesPerFrame));
|
||||
pcmData.resize(bytesPerChannelInFrame * audioInfo.totalFrames);
|
||||
uint8_t *p = pcmData.data();
|
||||
while (remainingFrames > 0) {
|
||||
framesToReadOnce = std::min(framesToReadOnce, remainingFrames);
|
||||
framesRead = decoder->read(framesToReadOnce, tmpBuf);
|
||||
for (int itr = 0; itr < framesToReadOnce; itr++) {
|
||||
memcpy(p, tmpBuf + itr * audioInfo.bytesPerFrame + channelID * bytesPerChannelInFrame, bytesPerChannelInFrame);
|
||||
|
||||
p += bytesPerChannelInFrame;
|
||||
}
|
||||
remainingFrames -= framesToReadOnce;
|
||||
};
|
||||
free(tmpBuf);
|
||||
} while (false);
|
||||
AudioDecoderManager::destroyDecoder(decoder);
|
||||
return pcmData;
|
||||
}
|
||||
89
cocos/audio/oalsoft/AudioEngine-soft.h
Normal file
89
cocos/audio/oalsoft/AudioEngine-soft.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "audio/include/AudioDef.h"
|
||||
#include "audio/oalsoft/AudioCache.h"
|
||||
#include "audio/oalsoft/AudioPlayer.h"
|
||||
#include "base/std/container/unordered_map.h"
|
||||
#include "cocos/base/RefCounted.h"
|
||||
#include "cocos/base/std/any.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class Scheduler;
|
||||
|
||||
#define MAX_AUDIOINSTANCES 32
|
||||
|
||||
class CC_DLL AudioEngineImpl : public RefCounted {
|
||||
public:
|
||||
AudioEngineImpl();
|
||||
~AudioEngineImpl() override;
|
||||
|
||||
bool init();
|
||||
int play2d(const ccstd::string &filePath, bool loop, float volume);
|
||||
void setVolume(int audioID, float volume);
|
||||
void setLoop(int audioID, bool loop);
|
||||
bool pause(int audioID);
|
||||
bool resume(int audioID);
|
||||
void stop(int audioID);
|
||||
void stopAll();
|
||||
float getDuration(int audioID);
|
||||
float getDurationFromFile(const ccstd::string &filePath);
|
||||
float getCurrentTime(int audioID);
|
||||
bool setCurrentTime(int audioID, float time);
|
||||
void setFinishCallback(int audioID, const std::function<void(int, const ccstd::string &)> &callback);
|
||||
|
||||
void uncache(const ccstd::string &filePath);
|
||||
void uncacheAll();
|
||||
AudioCache *preload(const ccstd::string &filePath, const std::function<void(bool)> &callback);
|
||||
void update(float dt);
|
||||
PCMHeader getPCMHeader(const char *url);
|
||||
ccstd::vector<uint8_t> getOriginalPCMBuffer(const char *url, uint32_t channelID);
|
||||
|
||||
private:
|
||||
bool checkAudioIdValid(int audioID);
|
||||
void play2dImpl(AudioCache *cache, int audioID);
|
||||
|
||||
ALuint _alSources[MAX_AUDIOINSTANCES];
|
||||
|
||||
//source,used
|
||||
ccstd::unordered_map<ALuint, bool> _alSourceUsed;
|
||||
|
||||
//filePath,bufferInfo
|
||||
ccstd::unordered_map<ccstd::string, AudioCache> _audioCaches;
|
||||
|
||||
//audioID,AudioInfo
|
||||
ccstd::unordered_map<int, AudioPlayer *> _audioPlayers;
|
||||
std::mutex _threadMutex;
|
||||
|
||||
bool _lazyInitLoop;
|
||||
|
||||
int _currentAudioID;
|
||||
std::weak_ptr<Scheduler> _scheduler;
|
||||
};
|
||||
} // namespace cc
|
||||
318
cocos/audio/oalsoft/AudioPlayer.cpp
Normal file
318
cocos/audio/oalsoft/AudioPlayer.cpp
Normal file
@@ -0,0 +1,318 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#define LOG_TAG "AudioPlayer"
|
||||
|
||||
#include "audio/oalsoft/AudioPlayer.h"
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include "audio/common/decoder/AudioDecoder.h"
|
||||
#include "audio/common/decoder/AudioDecoderManager.h"
|
||||
#include "audio/oalsoft/AudioCache.h"
|
||||
#include "base/Log.h"
|
||||
#include "base/memory/Memory.h"
|
||||
|
||||
using namespace cc; // NOLINT
|
||||
|
||||
namespace {
|
||||
unsigned int gIdIndex = 0;
|
||||
}
|
||||
|
||||
AudioPlayer::AudioPlayer()
|
||||
: _audioCache(nullptr),
|
||||
_finishCallbak(nullptr),
|
||||
_isDestroyed(false),
|
||||
_removeByAudioEngine(false),
|
||||
_ready(false),
|
||||
_currTime(0.0F),
|
||||
_streamingSource(false),
|
||||
_rotateBufferThread(nullptr),
|
||||
_timeDirty(false),
|
||||
_isRotateThreadExited(false),
|
||||
_id(++gIdIndex) {
|
||||
memset(_bufferIds, 0, sizeof(_bufferIds));
|
||||
}
|
||||
|
||||
AudioPlayer::~AudioPlayer() {
|
||||
// CC_LOG_DEBUG("~AudioPlayer() (%p), id=%u", this, _id);
|
||||
destroy();
|
||||
|
||||
if (_streamingSource) {
|
||||
alDeleteBuffers(3, _bufferIds);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioPlayer::destroy() {
|
||||
if (_isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// CC_LOG_DEBUG("AudioPlayer::destroy begin, id=%u", _id);
|
||||
|
||||
_isDestroyed = true;
|
||||
|
||||
do {
|
||||
if (_audioCache != nullptr) {
|
||||
if (_audioCache->_state == AudioCache::State::INITIAL) {
|
||||
CC_LOG_INFO("AudioPlayer::destroy, id=%u, cache isn't ready!", _id);
|
||||
break;
|
||||
}
|
||||
|
||||
while (!_audioCache->_isLoadingFinished) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for play2d to be finished.
|
||||
_play2dMutex.lock();
|
||||
_play2dMutex.unlock();
|
||||
|
||||
if (_streamingSource) {
|
||||
if (_rotateBufferThread != nullptr) {
|
||||
while (!_isRotateThreadExited) {
|
||||
_sleepCondition.notify_one();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
|
||||
if (_rotateBufferThread->joinable()) {
|
||||
_rotateBufferThread->join();
|
||||
}
|
||||
|
||||
delete _rotateBufferThread;
|
||||
_rotateBufferThread = nullptr;
|
||||
CC_LOG_DEBUG("rotateBufferThread exited!");
|
||||
}
|
||||
}
|
||||
} while (false);
|
||||
|
||||
// CC_LOG_DEBUG("Before alSourceStop");
|
||||
alSourceStop(_alSource);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
// CC_LOG_DEBUG("Before alSourcei");
|
||||
alSourcei(_alSource, AL_BUFFER, 0);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
|
||||
_removeByAudioEngine = true;
|
||||
|
||||
_ready = false;
|
||||
// CC_LOG_DEBUG("AudioPlayer::destroy end, id=%u", _id);
|
||||
}
|
||||
|
||||
void AudioPlayer::setCache(AudioCache *cache) {
|
||||
_audioCache = cache;
|
||||
}
|
||||
|
||||
bool AudioPlayer::play2d() {
|
||||
_play2dMutex.lock();
|
||||
//CC_LOG_INFO("AudioPlayer::play2d, _alSource: %u, player id=%u", _alSource, _id);
|
||||
|
||||
/*********************************************************************/
|
||||
/* Note that it may be in sub thread or in main thread. **/
|
||||
/*********************************************************************/
|
||||
bool ret = false;
|
||||
do {
|
||||
if (_audioCache->_state != AudioCache::State::READY) {
|
||||
CC_LOG_ERROR("alBuffer isn't ready for play!");
|
||||
break;
|
||||
}
|
||||
|
||||
alSourcei(_alSource, AL_BUFFER, 0);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
alSourcef(_alSource, AL_PITCH, 1.0F);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
alSourcef(_alSource, AL_GAIN, _volume);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
alSourcei(_alSource, AL_LOOPING, AL_FALSE);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
|
||||
if (_audioCache->_queBufferFrames == 0) {
|
||||
if (_loop) {
|
||||
alSourcei(_alSource, AL_LOOPING, AL_TRUE);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
}
|
||||
} else {
|
||||
if (_currTime > _audioCache->_duration) {
|
||||
_currTime = 0.F; // Target current start time is invalid, reset to 0.
|
||||
}
|
||||
alGenBuffers(3, _bufferIds);
|
||||
|
||||
auto alError = alGetError();
|
||||
if (alError == AL_NO_ERROR) {
|
||||
for (int index = 0; index < QUEUEBUFFER_NUM; ++index) {
|
||||
alBufferData(_bufferIds[index], _audioCache->_format, _audioCache->_queBuffers[index],
|
||||
_audioCache->_queBufferSize[index], _audioCache->_sampleRate);
|
||||
}
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
} else {
|
||||
ALOGE("%s:alGenBuffers error code:%x", __FUNCTION__, alError);
|
||||
break;
|
||||
}
|
||||
_streamingSource = true;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(_sleepMutex);
|
||||
if (_isDestroyed) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (_streamingSource) {
|
||||
alSourceQueueBuffers(_alSource, QUEUEBUFFER_NUM, _bufferIds);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
_rotateBufferThread = ccnew std::thread(&AudioPlayer::rotateBufferThread, this,
|
||||
_audioCache->_queBufferFrames * QUEUEBUFFER_NUM + 1);
|
||||
} else {
|
||||
alSourcei(_alSource, AL_BUFFER, _audioCache->_alBufferId);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
}
|
||||
|
||||
alSourcePlay(_alSource);
|
||||
}
|
||||
|
||||
auto alError = alGetError();
|
||||
if (alError != AL_NO_ERROR) {
|
||||
ALOGE("%s:alSourcePlay error code:%x", __FUNCTION__, alError);
|
||||
break;
|
||||
}
|
||||
|
||||
ALint state;
|
||||
alGetSourcei(_alSource, AL_SOURCE_STATE, &state);
|
||||
if (state != AL_PLAYING) {
|
||||
ALOGE("state isn't playing, %d, %s, cache id=%u, player id=%u", state, _audioCache->_fileFullPath.c_str(),
|
||||
_audioCache->_id, _id);
|
||||
// abort playing if the state is incorrect
|
||||
break;
|
||||
}
|
||||
_ready = true;
|
||||
ret = true;
|
||||
} while (false);
|
||||
|
||||
if (!ret) {
|
||||
_removeByAudioEngine = true;
|
||||
}
|
||||
|
||||
_play2dMutex.unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AudioPlayer::rotateBufferThread(int offsetFrame) {
|
||||
char *tmpBuffer = nullptr;
|
||||
AudioDecoder *decoder = AudioDecoderManager::createDecoder(_audioCache->_fileFullPath.c_str());
|
||||
do {
|
||||
BREAK_IF(decoder == nullptr || !decoder->open(_audioCache->_fileFullPath.c_str()));
|
||||
|
||||
uint32_t framesRead = 0;
|
||||
const uint32_t framesToRead = _audioCache->_queBufferFrames;
|
||||
const uint32_t bufferSize = framesToRead * decoder->getBytesPerFrame();
|
||||
tmpBuffer = static_cast<char *>(malloc(bufferSize));
|
||||
memset(tmpBuffer, 0, bufferSize);
|
||||
|
||||
if (offsetFrame != 0) {
|
||||
decoder->seek(offsetFrame);
|
||||
}
|
||||
|
||||
ALint sourceState;
|
||||
ALint bufferProcessed = 0;
|
||||
bool needToExitThread = false;
|
||||
|
||||
while (!_isDestroyed) {
|
||||
alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState);
|
||||
if (sourceState == AL_PLAYING) {
|
||||
alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed);
|
||||
while (bufferProcessed > 0) {
|
||||
bufferProcessed--;
|
||||
if (_timeDirty) {
|
||||
_timeDirty = false;
|
||||
offsetFrame = static_cast<int>(_currTime * decoder->getSampleRate());
|
||||
decoder->seek(offsetFrame);
|
||||
} else {
|
||||
_currTime += QUEUEBUFFER_TIME_STEP;
|
||||
if (_currTime > _audioCache->_duration) {
|
||||
if (_loop) {
|
||||
_currTime = 0.0F;
|
||||
} else {
|
||||
_currTime = _audioCache->_duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
framesRead = decoder->readFixedFrames(framesToRead, tmpBuffer);
|
||||
|
||||
if (framesRead == 0) {
|
||||
if (_loop) {
|
||||
decoder->seek(0);
|
||||
framesRead = decoder->readFixedFrames(framesToRead, tmpBuffer);
|
||||
} else {
|
||||
needToExitThread = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ALuint bid;
|
||||
alSourceUnqueueBuffers(_alSource, 1, &bid);
|
||||
alBufferData(bid, _audioCache->_format, tmpBuffer, framesRead * decoder->getBytesPerFrame(),
|
||||
decoder->getSampleRate());
|
||||
alSourceQueueBuffers(_alSource, 1, &bid);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lk(_sleepMutex);
|
||||
if (_isDestroyed || needToExitThread) {
|
||||
break;
|
||||
}
|
||||
|
||||
_sleepCondition.wait_for(lk, std::chrono::milliseconds(75));
|
||||
}
|
||||
|
||||
} while (false);
|
||||
|
||||
CC_LOG_INFO("Exit rotate buffer thread ...");
|
||||
if (decoder != nullptr) {
|
||||
decoder->close();
|
||||
}
|
||||
AudioDecoderManager::destroyDecoder(decoder);
|
||||
free(tmpBuffer);
|
||||
_isRotateThreadExited = true;
|
||||
CC_LOG_INFO("%s exited.\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
bool AudioPlayer::setLoop(bool loop) {
|
||||
if (!_isDestroyed) {
|
||||
_loop = loop;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioPlayer::setTime(float time) {
|
||||
if (!_isDestroyed && time >= 0.0F && time < _audioCache->_duration) {
|
||||
_currTime = time;
|
||||
_timeDirty = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
94
cocos/audio/oalsoft/AudioPlayer.h
Normal file
94
cocos/audio/oalsoft/AudioPlayer.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 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 <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include "base/std/container/string.h"
|
||||
#ifdef OPENAL_PLAIN_INCLUDES
|
||||
#include <al.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
|
||||
#include <OpenalSoft/al.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_OHOS
|
||||
#include <AL/al.h>
|
||||
#elif CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX
|
||||
#include <AL/al.h>
|
||||
#endif
|
||||
#include "base/Macros.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class AudioCache;
|
||||
class AudioEngineImpl;
|
||||
|
||||
class CC_DLL AudioPlayer {
|
||||
public:
|
||||
AudioPlayer();
|
||||
~AudioPlayer();
|
||||
|
||||
void destroy();
|
||||
|
||||
//queue buffer related stuff
|
||||
bool setTime(float time);
|
||||
float getTime() { return _currTime; }
|
||||
bool setLoop(bool loop);
|
||||
|
||||
protected:
|
||||
void setCache(AudioCache *cache);
|
||||
void rotateBufferThread(int offsetFrame);
|
||||
bool play2d();
|
||||
|
||||
AudioCache *_audioCache;
|
||||
|
||||
float _volume;
|
||||
bool _loop;
|
||||
std::function<void(int, const ccstd::string &)> _finishCallbak;
|
||||
|
||||
bool _isDestroyed;
|
||||
bool _removeByAudioEngine;
|
||||
bool _ready;
|
||||
ALuint _alSource;
|
||||
|
||||
//play by circular buffer
|
||||
float _currTime;
|
||||
bool _streamingSource;
|
||||
ALuint _bufferIds[3];
|
||||
std::thread *_rotateBufferThread;
|
||||
std::condition_variable _sleepCondition;
|
||||
std::mutex _sleepMutex;
|
||||
bool _timeDirty;
|
||||
bool _isRotateThreadExited;
|
||||
|
||||
std::mutex _play2dMutex;
|
||||
|
||||
unsigned int _id;
|
||||
|
||||
friend class AudioEngineImpl;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
87
cocos/audio/ohos/AudioDecoderWav.cpp
Normal file
87
cocos/audio/ohos/AudioDecoderWav.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#define LOG_TAG "AudioDecoderWav"
|
||||
|
||||
#include "audio/ohos/AudioDecoderWav.h"
|
||||
#include "audio/ohos/FsCallback.h"
|
||||
#include "base/Log.h"
|
||||
|
||||
namespace {
|
||||
snd_callbacks wavCallbacks = {
|
||||
.open = cc::ohosOpen,
|
||||
.read = cc::ohosRead,
|
||||
.seek = cc::ohosSeek,
|
||||
.close = cc::ohosClose,
|
||||
.tell = cc::ohosTell,
|
||||
};
|
||||
}
|
||||
|
||||
namespace cc {
|
||||
|
||||
AudioDecoderWav::AudioDecoderWav() {
|
||||
CC_LOG_DEBUG("Create AudioDecoderWav");
|
||||
}
|
||||
|
||||
AudioDecoderWav::~AudioDecoderWav() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool AudioDecoderWav::open(const char *path) {
|
||||
_sndHandle = sf_open_read(path, &_sndInfo, &wavCallbacks, this);
|
||||
_isOpened = (_sndHandle != nullptr) && _sndInfo.frames > 0;
|
||||
if (!_isOpened) return false;
|
||||
|
||||
_pcmHeader.channelCount = _sndInfo.channels;
|
||||
_pcmHeader.sampleRate = _sndInfo.samplerate;
|
||||
_pcmHeader.bytesPerFrame = 2 * _pcmHeader.channelCount; // short
|
||||
_pcmHeader.totalFrames = _sndInfo.frames;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioDecoderWav::close() {
|
||||
if (_sndHandle) {
|
||||
sf_close(_sndHandle);
|
||||
_sndHandle = nullptr;
|
||||
}
|
||||
_isOpened = false;
|
||||
}
|
||||
|
||||
uint32_t AudioDecoderWav::read(uint32_t framesToRead, char *pcmBuf) {
|
||||
auto *output = reinterpret_cast<int16_t *>(pcmBuf);
|
||||
auto actualFrames = sf_readf_short(_sndHandle, output, framesToRead);
|
||||
return actualFrames;
|
||||
}
|
||||
|
||||
bool AudioDecoderWav::seek(uint32_t frameOffset) {
|
||||
off_t offset = sf_seek(_sndHandle, frameOffset, SEEK_SET);
|
||||
return offset >= 0 && offset == frameOffset;
|
||||
}
|
||||
|
||||
uint32_t AudioDecoderWav::tell() const {
|
||||
return sf_tell(_sndHandle);
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
49
cocos/audio/ohos/AudioDecoderWav.h
Normal file
49
cocos/audio/ohos/AudioDecoderWav.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-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 documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio/android/tinysndfile.h"
|
||||
#include "audio/common/decoder/AudioDecoder.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class AudioDecoderWav : public AudioDecoder {
|
||||
public:
|
||||
AudioDecoderWav();
|
||||
~AudioDecoderWav() override;
|
||||
|
||||
bool open(const char *path) override;
|
||||
void close() override;
|
||||
uint32_t read(uint32_t framesToRead, char *pcmBuf) override;
|
||||
bool seek(uint32_t frameOffset) override;
|
||||
uint32_t tell() const override;
|
||||
|
||||
private:
|
||||
SF_INFO _sndInfo;
|
||||
SNDFILE *_sndHandle{};
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
98
cocos/audio/ohos/FsCallback.cpp
Normal file
98
cocos/audio/ohos/FsCallback.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
/****************************************************************************
|
||||
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 documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "audio/ohos/FsCallback.h"
|
||||
|
||||
#include <rawfile/raw_dir.h>
|
||||
#include <rawfile/raw_file.h>
|
||||
#include "base/memory/Memory.h"
|
||||
#include "platform/ohos/FileUtils-ohos.h"
|
||||
|
||||
namespace {
|
||||
inline cc::FileUtilsOHOS *getFU() {
|
||||
return static_cast<cc::FileUtilsOHOS *>(cc::FileUtils::getInstance());
|
||||
}
|
||||
|
||||
struct FatFd {
|
||||
union {
|
||||
FILE *fp;
|
||||
RawFile *rf;
|
||||
} file;
|
||||
void *user;
|
||||
bool isRawFile;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace cc {
|
||||
void *ohosOpen(const char *path, void *user) {
|
||||
bool isRawfile = false;
|
||||
const auto newPath = getFU()->expandPath(path, &isRawfile);
|
||||
auto *ret = ccnew FatFd();
|
||||
if (isRawfile) {
|
||||
ret->file.rf = OpenRawFile(cc::FileUtilsOHOS::getResourceManager(), newPath.c_str());
|
||||
} else {
|
||||
ret->file.fp = fopen(newPath.c_str(), "rb");
|
||||
}
|
||||
ret->user = user;
|
||||
ret->isRawFile = isRawfile;
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t ohosRead(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
||||
auto *fatFd = static_cast<FatFd *>(datasource);
|
||||
if (fatFd->isRawFile) {
|
||||
return ReadRawFile(fatFd->file.rf, ptr, size * nmemb) / size;
|
||||
}
|
||||
return fread(ptr, size, nmemb, fatFd->file.fp);
|
||||
}
|
||||
|
||||
int ohosSeek(void *datasource, long offset, int whence) { //NOLINT(google-runtime-int)
|
||||
auto *fatFd = static_cast<FatFd *>(datasource);
|
||||
if (fatFd->isRawFile) {
|
||||
return SeekRawFile(fatFd->file.rf, offset, whence);
|
||||
}
|
||||
return fseek(fatFd->file.fp, offset, whence);
|
||||
}
|
||||
|
||||
int ohosClose(void *datasource) {
|
||||
auto *fatFd = static_cast<FatFd *>(datasource);
|
||||
int code = 0;
|
||||
if (fatFd->isRawFile) {
|
||||
CloseRawFile(fatFd->file.rf);
|
||||
code = 0;
|
||||
} else {
|
||||
code = fclose(fatFd->file.fp);
|
||||
}
|
||||
delete fatFd;
|
||||
return code;
|
||||
}
|
||||
|
||||
long ohosTell(void *datasource) { //NOLINT(google-runtime-int)
|
||||
auto *fatFd = static_cast<FatFd *>(datasource);
|
||||
if (fatFd->isRawFile) {
|
||||
return GetRawFileOffset(fatFd->file.rf);
|
||||
}
|
||||
return ftell(fatFd->file.fp);
|
||||
}
|
||||
} // namespace cc
|
||||
40
cocos/audio/ohos/FsCallback.h
Normal file
40
cocos/audio/ohos/FsCallback.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/****************************************************************************
|
||||
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 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 <cstdint>
|
||||
|
||||
namespace cc {
|
||||
void *ohosOpen(const char *path, void *user);
|
||||
|
||||
size_t ohosRead(void *ptr, size_t size, size_t nmemb, void *datasource);
|
||||
|
||||
int ohosSeek(void *datasource, long offset, int whence); //NOLINT(google-runtime-int)
|
||||
|
||||
int ohosClose(void *datasource);
|
||||
|
||||
long ohosTell(void *datasource); //NOLINT(google-runtime-int)
|
||||
|
||||
} // namespace cc
|
||||
Reference in New Issue
Block a user