no message

This commit is contained in:
gem
2025-02-18 15:21:31 +08:00
commit 2d133e56d7
1980 changed files with 465595 additions and 0 deletions

598
cocos/audio/AudioEngine.cpp Normal file
View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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, &currentSection);
curPos += readBytes;
} while (readBytes > 0);
if (curPos > 0) {
_result.pcmBuffer->insert(_result.pcmBuffer->end(), pcmBuffer, pcmBuffer + bufferSize);
_result.numChannels = vi->channels;
_result.sampleRate = vi->rate;
_result.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
_result.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
_result.channelMask = vi->channels == 1 ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT);
_result.endianness = SL_BYTEORDER_LITTLEENDIAN;
_result.numFrames = pcmSamples;
_result.duration = 1.0f * pcmSamples / vi->rate;
} else {
ALOGE("ov_read returns 0 byte!");
}
ov_clear(&vf);
free(pcmBuffer);
return (curPos > 0);
}
} // namespace cc

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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;
}

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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 &&params = preloadIter->second;
ALOGV("preload (%s) callback count: %d", audioFilePath.c_str(), (int)params.size());
PcmData result = decoder->getResult();
for (auto &&param : params) {
param.callback(ret, result);
if (param.isPreloadInPlay2d) {
_preloadWaitCond.notify_one();
}
}
_preloadCallbackMap.erase(preloadIter);
}
AudioDecoderProvider::destroyAudioDecoder(&decoder);
});
} else {
ALOGV("File (%s) is too large, ignore preload!", info.url.c_str());
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

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <sys/types.h>
#include "audio/android/AudioResampler.h"
#include "audio/android/AudioBufferProvider.h"
namespace 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

View 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
// ---------------------------------------------------------------------------

View 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

View 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

View 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

View 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]))

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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
View 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

View 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

View 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
View 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);
}

View 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 */

View 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 */

View 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;
}

View File

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

View File

@@ -0,0 +1,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 */

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef COCOS_ERRORS_H
#define COCOS_ERRORS_H
#include <sys/types.h>
#include <errno.h>
namespace 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

View 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

View 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();
}

View 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

View 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();
});
}

View 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

View 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

View 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

View 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;
}

View 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; \
}

View 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

View 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;
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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, &currentSection);
#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, &currentSection);
#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

View 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

View 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

View 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

View File

@@ -0,0 +1,181 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* #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;
}
}

View 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

View 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

View 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
}

View 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

View 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);
}

View 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

View 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*/

View 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

View 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};
};

View 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
/// @}

View 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; \
}

View 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

View 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();
});
}

View 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

View 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;
}

View 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

View 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;
}

View 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

View 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

View 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

View 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

View 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