Skip to content
50 changes: 46 additions & 4 deletions native/cocos/audio/AudioEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ unsigned int AudioEngine::sMaxInstances = MAX_AUDIOINSTANCES;
AudioEngine::ProfileHelper *AudioEngine::sDefaultProfileHelper = nullptr;
ccstd::unordered_map<int, AudioEngine::AudioInfo> AudioEngine::sAudioIDInfoMap;
AudioEngineImpl *AudioEngine::sAudioEngineImpl = nullptr;
// Decoder-only fallback instance, defined here to keep AudioDecoderManager
// initialization separate from OpenAL initialization.
AudioEngineImpl *AudioEngine::sDecoderImpl = nullptr;

float AudioEngine::sVolumeFactor = 1.0F;
events::EnterBackground::Listener AudioEngine::sOnPauseListenerID;
Expand Down Expand Up @@ -156,6 +159,9 @@ void AudioEngine::end() {
delete sAudioEngineImpl;
sAudioEngineImpl = nullptr;

delete sDecoderImpl;
sDecoderImpl = nullptr;

delete sDefaultProfileHelper;
sDefaultProfileHelper = nullptr;

Expand Down Expand Up @@ -587,12 +593,48 @@ bool AudioEngine::isEnabled() {
return sIsEnabled;
}

AudioEngineImpl *AudioEngine::getDecoderImpl() {
if (AudioEngine::sAudioEngineImpl != nullptr) {
return AudioEngine::sAudioEngineImpl;
}
// Try full init first; if it succeeds we get OpenAL + decoder.
Comment thread
troublemaker52025 marked this conversation as resolved.
AudioEngine::lazyInit();
if (AudioEngine::sAudioEngineImpl != nullptr) {
return AudioEngine::sAudioEngineImpl;
}
// On oalsoft platforms, PCM decoding via AudioDecoderManager works
// independently of OpenAL — create a decoder-only fallback instance.
// On Android/OpenHarmony and Apple, AudioEngineImpl::getPCMHeader/
// getOriginalPCMBuffer dereferences _audioPlayerProvider/_engineEngine
// which are only set up by init(). Returning nullptr here lets callers
// fail gracefully instead of crashing.
#if CC_PLATFORM == CC_PLATFORM_WINDOWS || CC_PLATFORM == CC_PLATFORM_OHOS || \
CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX
if (AudioEngine::sDecoderImpl == nullptr) {
AudioEngine::sDecoderImpl = ccnew AudioEngineImpl();
AudioEngineImpl::initDecoder();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to make initDecoder() a non-static member function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think keep static member function will be batter. Called independently without relying on a full OpenAL initialization

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it

}
return AudioEngine::sDecoderImpl;
#else
return nullptr;
#endif
}

PCMHeader AudioEngine::getPCMHeader(const char *url) {
lazyInit();
return sAudioEngineImpl->getPCMHeader(url);
AudioEngineImpl *impl = AudioEngine::getDecoderImpl();
if (impl == nullptr) {
CC_LOG_WARNING("AudioEngine::getPCMHeader: audio engine unavailable, url: %s", url);
return {};
}
return impl->getPCMHeader(url);
}

ccstd::vector<uint8_t> AudioEngine::getOriginalPCMBuffer(const char *url, uint32_t channelID) {
lazyInit();
return sAudioEngineImpl->getOriginalPCMBuffer(url, channelID);
AudioEngineImpl *impl = AudioEngine::getDecoderImpl();
if (impl == nullptr) {
CC_LOG_WARNING("AudioEngine::getOriginalPCMBuffer: audio engine unavailable, url: %s", url);
return {};
}
return impl->getOriginalPCMBuffer(url, channelID);
}
} // namespace cc
6 changes: 6 additions & 0 deletions native/cocos/audio/android/AudioEngine-inl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,12 @@ void AudioEngineImpl::onResume() {
}
}

bool AudioEngineImpl::initDecoder() {
// On Android the decoder infrastructure is set up by init().
// This method exists so AudioEngine.cpp compiles uniformly across all platforms.
return true;
}

PCMHeader AudioEngineImpl::getPCMHeader(const char *url) {
PCMHeader header{};
ccstd::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url);
Expand Down
1 change: 1 addition & 0 deletions native/cocos/audio/android/AudioEngine-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class AudioEngineImpl : public RefCounted {
~AudioEngineImpl() override;

bool init();
static bool initDecoder();
int play2d(const ccstd::string &filePath, bool loop, float volume);
void setVolume(int audioID, float volume);
void setLoop(int audioID, bool loop);
Expand Down
1 change: 1 addition & 0 deletions native/cocos/audio/apple/AudioEngine-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class AudioEngineImpl : public cc::RefCounted {
~AudioEngineImpl();

bool init();
static bool initDecoder();
int play2d(const ccstd::string &fileFullPath, bool loop, float volume);
void setVolume(int audioID, float volume);
void setLoop(int audioID, bool loop);
Expand Down
6 changes: 6 additions & 0 deletions native/cocos/audio/apple/AudioEngine-inl.mm
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,12 @@ AL_API ALvoid AL_APIENTRY alGenBuffers(ALsizei n, ALuint *bids)
return _audioPlayers.find(audioID) != _audioPlayers.end();
}

bool AudioEngineImpl::initDecoder() {
// On Apple the decoder infrastructure is set up by init().
// This method exists so AudioEngine.cpp compiles uniformly across all platforms.
return true;
}

PCMHeader AudioEngineImpl::getPCMHeader(const char *url){
PCMHeader header {};
auto itr = _audioCaches.find(url);
Expand Down
17 changes: 17 additions & 0 deletions native/cocos/audio/include/AudioEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,19 @@ class EXPORT_DLL AudioEngine {
static void pauseAll(ccstd::vector<int> *pausedAudioIDs);
static void resumeAll(ccstd::vector<int> *pausedAudioIDs);

/**
* @brief Get Audio Decoder
*
* Strategy:
* 1. If the full impl (with OpenAL) is already alive, reuse it (cache + decoder both available).
* 2. If OpenAL init has not been attempted yet, try lazyInit() so that a successful OpenAL
* environment still gets the full impl (keeps _audioCaches coherent with play2d).
* 3. Only fall back to the decoder-only sDecoderImpl when lazyInit() has failed
* (alcOpenDevice returned null, headless / OHOS / CI environments).
* @return The impl instance to use for PCM decoding.
*/
static AudioEngineImpl *getDecoderImpl();

struct ProfileHelper {
AudioProfile profile;

Expand Down Expand Up @@ -382,6 +395,10 @@ class EXPORT_DLL AudioEngine {
static ProfileHelper *sDefaultProfileHelper;

static AudioEngineImpl *sAudioEngineImpl;
// Decoder-only fallback instance used when OpenAL is unavailable.
// getPCMHeader / getOriginalPCMBuffer do not depend on OpenAL and use this
// instance directly instead of going through lazyInit().
static AudioEngineImpl *sDecoderImpl;

class AudioEngineThreadPool;
static AudioEngineThreadPool *sThreadPool;
Expand Down
56 changes: 38 additions & 18 deletions native/cocos/audio/oalsoft/AudioEngine-soft.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
// 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
// IDEA: Move _winLog, winLog to a separate file
static void _winLog(const char *format, va_list args) {
static const int MAX_LOG_LENGTH = 16 * 1024;
int bufferSize = MAX_LOG_LENGTH;
Expand Down Expand Up @@ -153,31 +153,51 @@ AudioEngineImpl::~AudioEngineImpl() {
AudioDecoderManager::destroy();
}

bool AudioEngineImpl::initDecoder() {
return AudioDecoderManager::init();
}

bool AudioEngineImpl::init() {
bool ret = false;
do {
sALDevice = alcOpenDevice(nullptr);
if (sALDevice == nullptr) {
// alcGetError(nullptr) is valid when no device is open
ALCenum alcErr = alcGetError(nullptr);
CC_LOG_ERROR("%s: alcOpenDevice failed, ALC error = 0x%x", __FUNCTION__, alcErr);
break;
}

if (sALDevice) {
alGetError();
sALContext = alcCreateContext(sALDevice, nullptr);
alcMakeContextCurrent(sALContext);
alGetError();
sALContext = alcCreateContext(sALDevice, nullptr);
if (sALContext == nullptr) {
ALCenum alcErr = alcGetError(sALDevice);
CC_LOG_ERROR("%s: alcCreateContext failed, ALC error = 0x%x", __FUNCTION__, alcErr);
break;
}

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

for (unsigned int src : _alSources) {
_alSourceUsed[src] = false;
}
for (auto &src : _alSources) {
Comment thread
troublemaker52025 marked this conversation as resolved.
Outdated
src = 0;
}
alGenSources(MAX_AUDIOINSTANCES, _alSources);
auto alError = alGetError();
if (alError != AL_NO_ERROR) {
CC_LOG_ERROR("%s:generating sources failed! error = %x\n", __FUNCTION__, alError);
alcMakeContextCurrent(nullptr);
alcDestroyContext(sALContext);
sALContext = nullptr;
break;
}
Comment thread
troublemaker52025 marked this conversation as resolved.

_scheduler = CC_CURRENT_ENGINE()->getScheduler();
ret = AudioDecoderManager::init();
CC_LOG_DEBUG("OpenAL was initialized successfully!");
for (unsigned int src : _alSources) {
_alSourceUsed[src] = false;
}

_scheduler = CC_CURRENT_ENGINE()->getScheduler();
ret = initDecoder();
CC_LOG_DEBUG("OpenAL was initialized successfully!");
} while (false);

return ret;
Expand Down Expand Up @@ -264,7 +284,7 @@ int AudioEngineImpl::play2d(const ccstd::string &filePath, bool loop, float volu
}

void AudioEngineImpl::play2dImpl(AudioCache *cache, int audioID) {
//Note: It may bn in sub thread or main thread :(
// 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);
Expand Down
3 changes: 3 additions & 0 deletions native/cocos/audio/oalsoft/AudioEngine-soft.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ class CC_DLL AudioEngineImpl : public RefCounted {
~AudioEngineImpl() override;

bool init();
// Initialize only the PCM decoder subsystem (AudioDecoderManager).
// Does not require an OpenAL device. Safe to call when init() failed.
static bool initDecoder();
int play2d(const ccstd::string &filePath, bool loop, float volume);
void setVolume(int audioID, float volume);
void setLoop(int audioID, bool loop);
Expand Down
Loading