From bb004c5e22d1a23f8d9cfa293d0e9637e1b49f01 Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 21 Jul 2016 14:57:59 +0800 Subject: [PATCH] [audio] Fixes some issues while stop/uncache right after play/preload on OSX/iOS platform. (#16147) * [audio] Fixes some issues while stop/uncache right after play/preload on OSX/iOS platform. * Better state control in AudioCache. * Adds State enum class which contains INITIAL, LOADING, READY, FAILED states. * AudioPlayer::destroy should check AudioCache::_isAudioLoaded flag, otherwise, app will freeze in cpp-tests/NewAudioEngineTest/6:Test invalid audio file. * Should check whether state is INITIAL in the destructor of AudioCache. * Adds test case for switching play state frequently. * Skips invoking reading data callback if cache's state is INITIAL. * Variables initialization for AudioCache class and fixes protential missing delete AL buffers. --- cocos/audio/apple/AudioCache.h | 63 ++- cocos/audio/apple/AudioCache.mm | 460 ++++++++++++------ cocos/audio/apple/AudioEngine-inl.mm | 81 ++- cocos/audio/apple/AudioPlayer.h | 19 +- cocos/audio/apple/AudioPlayer.mm | 257 +++++++--- .../NewAudioEngineTest/NewAudioEngineTest.cpp | 31 ++ .../NewAudioEngineTest/NewAudioEngineTest.h | 11 + 7 files changed, 640 insertions(+), 282 deletions(-) diff --git a/cocos/audio/apple/AudioCache.h b/cocos/audio/apple/AudioCache.h index be552f8c54..26a6d59086 100644 --- a/cocos/audio/apple/AudioCache.h +++ b/cocos/audio/apple/AudioCache.h @@ -22,12 +22,11 @@ THE SOFTWARE. ****************************************************************************/ +#pragma once + #include "platform/CCPlatformConfig.h" #if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC -#ifndef __AUDIO_CACHE_H_ -#define __AUDIO_CACHE_H_ - #import #import @@ -40,14 +39,49 @@ #define QUEUEBUFFER_NUM 3 #define QUEUEBUFFER_TIME_STEP 0.1 +#define QUOTEME_(x) #x +#define QUOTEME(x) QUOTEME_(x) + +#if defined(COCOS2D_DEBUG) && COCOS2D_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(COCOS2D_DEBUG) && COCOS2D_DEBUG > 0 +#define CHECK_AL_ERROR_DEBUG() \ +do { \ + GLenum __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 + NS_CC_BEGIN namespace experimental{ class AudioEngineImpl; class AudioPlayer; -class AudioCache{ +class AudioCache +{ public: + + enum class State + { + INITIAL, + LOADING, + READY, + FAILED + }; + AudioCache(); ~AudioCache(); @@ -56,7 +90,8 @@ public: void addLoadCallback(const std::function& callback); protected: - void readDataTask(); + void setSkipReadDataTask(bool isSkip) { _isSkipReadDataTask = isSkip; }; + void readDataTask(unsigned int selfId); void invokingPlayCallbacks(); @@ -68,7 +103,7 @@ protected: ALsizei _sampleRate; float _duration; int _bytesPerFrame; - AudioStreamBasicDescription outputFormat; + AudioStreamBasicDescription _outputFormat; /*Cache related stuff; * Cache pcm data when sizeInBytes less than PCMDATA_CACHEMAXSIZE @@ -85,16 +120,21 @@ protected: UInt32 _queBufferFrames; UInt32 _queBufferBytes; - bool _alBufferReady; - bool _loadFail; - std::mutex _callbackMutex; + std::mutex _playCallbackMutex; + std::vector< std::function > _playCallbacks; - std::vector< std::function > _callbacks; + // loadCallbacks doesn't need mutex since it's invoked only in Cocos thread. std::vector< std::function > _loadCallbacks; + std::mutex _readDataTaskMutex; - bool _exitReadDataTask; + State _state; + + std::shared_ptr _isDestroyed; std::string _fileFullPath; + unsigned int _id; + bool _isLoadingFinished; + bool _isSkipReadDataTask; friend class AudioEngineImpl; friend class AudioPlayer; @@ -103,6 +143,5 @@ protected: } NS_CC_END -#endif // __AUDIO_CACHE_H_ #endif diff --git a/cocos/audio/apple/AudioCache.mm b/cocos/audio/apple/AudioCache.mm index 66f32c8a0e..8a605dd2dc 100644 --- a/cocos/audio/apple/AudioCache.mm +++ b/cocos/audio/apple/AudioCache.mm @@ -22,6 +22,8 @@ THE SOFTWARE. ****************************************************************************/ +#define LOG_TAG "AudioCache" + #include "platform/CCPlatformConfig.h" #if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC @@ -34,6 +36,18 @@ #include "base/CCDirector.h" #include "base/CCScheduler.h" +#define VERY_VERY_VERBOSE_LOGGING +#ifdef VERY_VERY_VERBOSE_LOGGING +#define ALOGVV ALOGV +#else +#define ALOGVV(...) do{} while(false) +#endif + +namespace { +unsigned int __idIndex = 0; +} + +#define INVALID_AL_BUFFER_ID 0xFFFFFFFF #define PCMDATA_CACHEMAXSIZE 1048576 typedef ALvoid AL_APIENTRY (*alBufferDataStaticProcPtr) (const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq); @@ -56,225 +70,351 @@ using namespace cocos2d::experimental; AudioCache::AudioCache() : _dataSize(0) +, _format(-1) +, _duration(0.0f) +, _bytesPerFrame(0) +, _alBufferId(INVALID_AL_BUFFER_ID) , _pcmData(nullptr) , _bytesOfRead(0) , _queBufferFrames(0) , _queBufferBytes(0) -, _alBufferReady(false) -, _loadFail(false) -, _exitReadDataTask(false) +, _state(State::INITIAL) +, _isDestroyed(std::make_shared(false)) +, _id(++__idIndex) +, _isLoadingFinished(false) +, _isSkipReadDataTask(false) { + ALOGVV("AudioCache() %p, id=%u", this, _id); + for (int i = 0; i < QUEUEBUFFER_NUM; ++i) + { + _queBuffers[i] = nullptr; + _queBufferSize[i] = 0; + } + memset(&_outputFormat, 0, sizeof(_outputFormat)); } AudioCache::~AudioCache() { - _exitReadDataTask = true; - if(_pcmData){ - if (_alBufferReady){ - alDeleteBuffers(1, &_alBufferId); + 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); } - //wait for the 'readDataTask' task to exit - _readDataTaskMutex.lock(); - _readDataTaskMutex.unlock(); free(_pcmData); } - if (_queBufferFrames > 0) { - for (int index = 0; index < QUEUEBUFFER_NUM; ++index) { + if (_queBufferFrames > 0) + { + for (int index = 0; index < QUEUEBUFFER_NUM; ++index) + { free(_queBuffers[index]); } } + ALOGVV("~AudioCache() %p, id=%u, end", this, _id); } -void AudioCache::readDataTask() +void AudioCache::readDataTask(unsigned int selfId) { + //Note: It's in sub thread + ALOGVV("readDataTask, cache id=%u", selfId); + _readDataTaskMutex.lock(); + _state = State::LOADING; - AudioStreamBasicDescription theFileFormat; - UInt32 thePropertySize = sizeof(theFileFormat); - - SInt64 theFileLengthInFrames; - SInt64 readInFrames; - SInt64 dataSize; - SInt64 frames; - AudioBufferList theDataBuffer; + CFURLRef fileURL = nil; ExtAudioFileRef extRef = nullptr; - NSString *fileFullPath = [[NSString alloc] initWithCString:_fileFullPath.c_str() encoding:NSUTF8StringEncoding]; - auto fileURL = (CFURLRef)[[NSURL alloc] initFileURLWithPath:fileFullPath]; - [fileFullPath release]; + do + { + if (*_isDestroyed) break; + + AudioStreamBasicDescription theFileFormat; + UInt32 thePropertySize = sizeof(theFileFormat); + + SInt64 theFileLengthInFrames; + SInt64 readInFrames; + SInt64 dataSize; + SInt64 frames; + AudioBufferList theDataBuffer; + + NSString *fileFullPath = [[NSString alloc] initWithCString:_fileFullPath.c_str() encoding:NSUTF8StringEncoding]; + fileURL = (CFURLRef)[[NSURL alloc] initFileURLWithPath:fileFullPath]; + [fileFullPath release]; + + if (*_isDestroyed) break; - auto error = ExtAudioFileOpenURL(fileURL, &extRef); - if(error) { - printf("%s: ExtAudioFileOpenURL FAILED, Error = %ld\n", __PRETTY_FUNCTION__, (long)error); - goto ExitThread; - } - - // Get the audio data format - error = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileDataFormat, &thePropertySize, &theFileFormat); - if(error) { - printf("%s: ExtAudioFileGetProperty(kExtAudioFileProperty_FileDataFormat) FAILED, Error = %ld\n", __PRETTY_FUNCTION__, (long)error); - goto ExitThread; - } - if (theFileFormat.mChannelsPerFrame > 2) { - printf("%s: Unsupported Format, channel count is greater than stereo\n",__PRETTY_FUNCTION__); - goto ExitThread; - } - - // 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 = theFileFormat.mSampleRate; - outputFormat.mChannelsPerFrame = theFileFormat.mChannelsPerFrame; - - _bytesPerFrame = 2 * outputFormat.mChannelsPerFrame; - outputFormat.mFormatID = kAudioFormatLinearPCM; - outputFormat.mBytesPerPacket = _bytesPerFrame; - outputFormat.mFramesPerPacket = 1; - outputFormat.mBytesPerFrame = _bytesPerFrame; - outputFormat.mBitsPerChannel = 16; - outputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger; - - error = ExtAudioFileSetProperty(extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(outputFormat), &outputFormat); - if(error) { - printf("%s: ExtAudioFileSetProperty FAILED, Error = %ld\n", __PRETTY_FUNCTION__, (long)error); - goto ExitThread; - } - - // Get the total frame count - thePropertySize = sizeof(theFileLengthInFrames); - error = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileLengthFrames, &thePropertySize, &theFileLengthInFrames); - if(error) { - printf("%s: ExtAudioFileGetProperty(kExtAudioFileProperty_FileLengthFrames) FAILED, Error = %ld\n", __PRETTY_FUNCTION__, (long)error); - goto ExitThread; - } - - _dataSize = (ALsizei)(theFileLengthInFrames * outputFormat.mBytesPerFrame); - _format = (outputFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; - _sampleRate = (ALsizei)outputFormat.mSampleRate; - _duration = 1.0f * theFileLengthInFrames / outputFormat.mSampleRate; - - if (_dataSize <= PCMDATA_CACHEMAXSIZE) { - _pcmData = (char*)malloc(_dataSize); - alGenBuffers(1, &_alBufferId); - auto alError = alGetError(); - if (alError != AL_NO_ERROR) { - printf("%s: attaching audio to buffer fail: %x\n", __PRETTY_FUNCTION__, alError); - goto ExitThread; - } - alBufferDataStaticProc(_alBufferId, _format, _pcmData, _dataSize, _sampleRate); - - readInFrames = theFileFormat.mSampleRate * QUEUEBUFFER_TIME_STEP * QUEUEBUFFER_NUM; - dataSize = outputFormat.mBytesPerFrame * readInFrames; - if (dataSize > _dataSize) { - dataSize = _dataSize; - readInFrames = theFileLengthInFrames; - } - theDataBuffer.mNumberBuffers = 1; - theDataBuffer.mBuffers[0].mDataByteSize = (UInt32)dataSize; - theDataBuffer.mBuffers[0].mNumberChannels = outputFormat.mChannelsPerFrame; - - theDataBuffer.mBuffers[0].mData = _pcmData; - frames = readInFrames; - ExtAudioFileRead(extRef, (UInt32*)&frames, &theDataBuffer); - _alBufferReady = true; - _bytesOfRead += dataSize; - invokingPlayCallbacks(); - - while (!_exitReadDataTask && _bytesOfRead + dataSize < _dataSize) { - theDataBuffer.mBuffers[0].mData = _pcmData + _bytesOfRead; - frames = readInFrames; - ExtAudioFileRead(extRef, (UInt32*)&frames, &theDataBuffer); - _bytesOfRead += dataSize; + auto error = ExtAudioFileOpenURL(fileURL, &extRef); + if (error) { + ALOGE("%s: ExtAudioFileOpenURL FAILED, Error = %ld", __PRETTY_FUNCTION__, (long)error); + break; } - dataSize = _dataSize - _bytesOfRead; - if (!_exitReadDataTask && dataSize > 0) { + if (*_isDestroyed) break; + + // Get the audio data format + error = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileDataFormat, &thePropertySize, &theFileFormat); + if(error) + { + ALOGE("%s: ExtAudioFileGetProperty(kExtAudioFileProperty_FileDataFormat) FAILED, Error = %ld", __PRETTY_FUNCTION__, (long)error); + break; + } + + if (theFileFormat.mChannelsPerFrame > 2) + { + ALOGE("%s: Unsupported Format, channel count is greater than stereo",__PRETTY_FUNCTION__); + break; + } + + if (*_isDestroyed) break; + + // 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 = theFileFormat.mSampleRate; + _outputFormat.mChannelsPerFrame = theFileFormat.mChannelsPerFrame; + + _bytesPerFrame = 2 * _outputFormat.mChannelsPerFrame; + _outputFormat.mFormatID = kAudioFormatLinearPCM; + _outputFormat.mBytesPerPacket = _bytesPerFrame; + _outputFormat.mFramesPerPacket = 1; + _outputFormat.mBytesPerFrame = _bytesPerFrame; + _outputFormat.mBitsPerChannel = 16; + _outputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger; + + error = ExtAudioFileSetProperty(extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(_outputFormat), &_outputFormat); + if(error) + { + ALOGE("%s: ExtAudioFileSetProperty FAILED, Error = %ld", __PRETTY_FUNCTION__, (long)error); + break; + } + + if (*_isDestroyed) break; + + // Get the total frame count + thePropertySize = sizeof(theFileLengthInFrames); + error = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileLengthFrames, &thePropertySize, &theFileLengthInFrames); + if(error) + { + ALOGE("%s: ExtAudioFileGetProperty(kExtAudioFileProperty_FileLengthFrames) FAILED, Error = %ld", __PRETTY_FUNCTION__, (long)error); + break; + } + + if (*_isDestroyed) break; + + _dataSize = (ALsizei)(theFileLengthInFrames * _outputFormat.mBytesPerFrame); + _format = (_outputFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; + _sampleRate = (ALsizei)_outputFormat.mSampleRate; + _duration = 1.0f * theFileLengthInFrames / _outputFormat.mSampleRate; + + if (_dataSize <= PCMDATA_CACHEMAXSIZE) + { + _pcmData = (char*)malloc(_dataSize); + alGenBuffers(1, &_alBufferId); + auto alError = alGetError(); + if (alError != AL_NO_ERROR) { + ALOGE("%s: attaching audio to buffer fail: %x", __PRETTY_FUNCTION__, alError); + break; + } + + if (*_isDestroyed) break; + + alBufferDataStaticProc(_alBufferId, _format, _pcmData, _dataSize, _sampleRate); + + readInFrames = theFileFormat.mSampleRate * QUEUEBUFFER_TIME_STEP * QUEUEBUFFER_NUM; + dataSize = _outputFormat.mBytesPerFrame * readInFrames; + if (dataSize > _dataSize) { + dataSize = _dataSize; + readInFrames = theFileLengthInFrames; + } + theDataBuffer.mNumberBuffers = 1; theDataBuffer.mBuffers[0].mDataByteSize = (UInt32)dataSize; - theDataBuffer.mBuffers[0].mData = _pcmData + _bytesOfRead; + theDataBuffer.mBuffers[0].mNumberChannels = _outputFormat.mChannelsPerFrame; + + theDataBuffer.mBuffers[0].mData = _pcmData; frames = readInFrames; ExtAudioFileRead(extRef, (UInt32*)&frames, &theDataBuffer); + + if (*_isDestroyed) break; + + _state = State::READY; + + _bytesOfRead += dataSize; + invokingPlayCallbacks(); + + while (!*_isDestroyed && _bytesOfRead + dataSize < _dataSize) { + theDataBuffer.mBuffers[0].mData = _pcmData + _bytesOfRead; + frames = readInFrames; + ExtAudioFileRead(extRef, (UInt32*)&frames, &theDataBuffer); + _bytesOfRead += dataSize; //FIXME: the buffer size of read frames may be fewer than dataSize. + } + + dataSize = _dataSize - _bytesOfRead; + if (!*_isDestroyed && dataSize > 0) { + theDataBuffer.mBuffers[0].mDataByteSize = (UInt32)dataSize; + theDataBuffer.mBuffers[0].mData = _pcmData + _bytesOfRead; + frames = readInFrames; + ExtAudioFileRead(extRef, (UInt32*)&frames, &theDataBuffer); + } + + _bytesOfRead = _dataSize; + } + else + { + _queBufferFrames = theFileFormat.mSampleRate * QUEUEBUFFER_TIME_STEP; + if (_queBufferFrames == 0) + break; + + _queBufferBytes = _queBufferFrames * _outputFormat.mBytesPerFrame; + + theDataBuffer.mNumberBuffers = 1; + theDataBuffer.mBuffers[0].mNumberChannels = _outputFormat.mChannelsPerFrame; + for (int index = 0; index < QUEUEBUFFER_NUM; ++index) { + _queBuffers[index] = (char*)malloc(_queBufferBytes); + + theDataBuffer.mBuffers[0].mDataByteSize = _queBufferBytes; + theDataBuffer.mBuffers[0].mData = _queBuffers[index]; + frames = _queBufferFrames; + ExtAudioFileRead(extRef, (UInt32*)&frames, &theDataBuffer); + + _queBufferSize[index] = theDataBuffer.mBuffers[0].mDataByteSize; + } + + _state = State::READY; } - _bytesOfRead = _dataSize; - } - else{ - _queBufferFrames = theFileFormat.mSampleRate * QUEUEBUFFER_TIME_STEP; - _queBufferBytes = _queBufferFrames * outputFormat.mBytesPerFrame; - - theDataBuffer.mNumberBuffers = 1; - theDataBuffer.mBuffers[0].mNumberChannels = outputFormat.mChannelsPerFrame; - for (int index = 0; index < QUEUEBUFFER_NUM; ++index) { - _queBuffers[index] = (char*)malloc(_queBufferBytes); - - theDataBuffer.mBuffers[0].mDataByteSize = _queBufferBytes; - theDataBuffer.mBuffers[0].mData = _queBuffers[index]; - frames = _queBufferFrames; - ExtAudioFileRead(extRef, (UInt32*)&frames, &theDataBuffer); - - _queBufferSize[index] = theDataBuffer.mBuffers[0].mDataByteSize; - } - } + } while (false); -ExitThread: - CFRelease(fileURL); - if (extRef) + if (fileURL != nil) + CFRelease(fileURL); + + if (extRef != nullptr) ExtAudioFileDispose(extRef); - _readDataTaskMutex.unlock(); - if (_queBufferFrames > 0) - _alBufferReady = true; - else - _loadFail = true; - + //FIXME: 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("readDataTask failed, delete buffer: %u", _alBufferId); + alDeleteBuffers(1, &_alBufferId); + _alBufferId = INVALID_AL_BUFFER_ID; + } + } + + _readDataTaskMutex.unlock(); } void AudioCache::addPlayCallback(const std::function& callback) { - _callbackMutex.lock(); - if (_alBufferReady) { - callback(); - } else if(!_loadFail){ - _callbacks.push_back(callback); + std::lock_guard 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; } - _callbackMutex.unlock(); } void AudioCache::invokingPlayCallbacks() { - _callbackMutex.lock(); - auto count = _callbacks.size(); - for (size_t index = 0; index < count; ++index) { - _callbacks[index](); + std::lock_guard lk(_playCallbackMutex); + + for (auto&& cb : _playCallbacks) + { + cb(); } - _callbacks.clear(); - _callbackMutex.unlock(); + + _playCallbacks.clear(); } void AudioCache::addLoadCallback(const std::function& callback) { - if (_alBufferReady) { - callback(true); - } else if(_loadFail){ - callback(false); - } - else { - _loadCallbacks.push_back(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 = Director::getInstance()->getScheduler(); - scheduler->performFunctionInCocosThread([&](){ - auto count = _loadCallbacks.size(); - for (size_t index = 0; index < count; ++index) { - _loadCallbacks[index](_alBufferReady); + 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(); }); } diff --git a/cocos/audio/apple/AudioEngine-inl.mm b/cocos/audio/apple/AudioEngine-inl.mm index fc3ef94b2c..ff7db6b2ea 100644 --- a/cocos/audio/apple/AudioEngine-inl.mm +++ b/cocos/audio/apple/AudioEngine-inl.mm @@ -22,6 +22,8 @@ THE SOFTWARE. ****************************************************************************/ +#define LOG_TAG "AudioEngine-inl.mm" + #include "platform/CCPlatformConfig.h" #if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC @@ -119,7 +121,7 @@ void AudioEngineInterruptionListenerCallback(void* user_data, UInt32 interruptio setCategory: AVAudioSessionCategoryAmbient error: &error]; if (!success) { - printf("Fail to set audio session.\n"); + ALOGE("Fail to set audio session."); return; } [[AVAudioSession sharedInstance] setActive:YES error:&error]; @@ -183,7 +185,7 @@ bool AudioEngineImpl::init() auto alError = alGetError(); if(alError != AL_NO_ERROR) { - printf("%s:generating sources fail! error = %x\n", __PRETTY_FUNCTION__, alError); + ALOGE("%s:generating sources failed! error = %x", __PRETTY_FUNCTION__, alError); break; } @@ -192,6 +194,7 @@ bool AudioEngineImpl::init() } _scheduler = Director::getInstance()->getScheduler(); ret = true; + ALOGI("OpenAL was initialized successfully!"); } }while (false); @@ -206,14 +209,23 @@ AudioCache* AudioEngineImpl::preload(const std::string& filePath, std::function< if (it == _audioCaches.end()) { audioCache = &_audioCaches[filePath]; audioCache->_fileFullPath = FileUtils::getInstance()->fullPathForFilename(filePath); - - AudioEngine::addTask(std::bind(&AudioCache::readDataTask, audioCache)); + 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) + if (audioCache && callback) { audioCache->addLoadCallback(callback); } @@ -244,6 +256,7 @@ int AudioEngineImpl::play2d(const std::string &filePath ,bool loop ,float volume if (player == nullptr) { return AudioEngine::INVALID_AUDIO_ID; } + player->_alSource = alSource; player->_loop = loop; player->_volume = volume; @@ -254,14 +267,15 @@ int AudioEngineImpl::play2d(const std::string &filePath ,bool loop ,float volume return AudioEngine::INVALID_AUDIO_ID; } + player->setCache(audioCache); _threadMutex.lock(); _audioPlayers[_currentAudioID] = player; _threadMutex.unlock(); - audioCache->addPlayCallback(std::bind(&AudioEngineImpl::_play2d,this,audioCache,_currentAudioID)); - _alSourceUsed[alSource] = true; + audioCache->addPlayCallback(std::bind(&AudioEngineImpl::_play2d,this,audioCache,_currentAudioID)); + if (_lazyInitLoop) { _lazyInitLoop = false; _scheduler->schedule(CC_SCHEDULE_SELECTOR(AudioEngineImpl::update), this, 0.05f, false); @@ -272,11 +286,14 @@ int AudioEngineImpl::play2d(const std::string &filePath ,bool loop ,float volume void AudioEngineImpl::_play2d(AudioCache *cache, int audioID) { - if(cache->_alBufferReady){ + //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(cache)) { + if (playerIt != _audioPlayers.end() && playerIt->second->play2d()) { _scheduler->performFunctionInCocosThread([audioID](){ + if (AudioEngine::_audioIDInfoMap.find(audioID) != AudioEngine::_audioIDInfoMap.end()) { AudioEngine::_audioIDInfoMap[audioID].state = AudioEngine::AudioState::PLAYING; } @@ -284,6 +301,15 @@ void AudioEngineImpl::_play2d(AudioCache *cache, int audioID) } _threadMutex.unlock(); } + else + { + ALOGD("AudioEngineImpl::_play2d, 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) @@ -296,7 +322,7 @@ void AudioEngineImpl::setVolume(int audioID,float volume) auto error = alGetError(); if (error != AL_NO_ERROR) { - printf("%s: audio id = %d, error = %x\n", __PRETTY_FUNCTION__,audioID,error); + ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__,audioID,error); } } } @@ -317,7 +343,7 @@ void AudioEngineImpl::setLoop(int audioID, bool loop) auto error = alGetError(); if (error != AL_NO_ERROR) { - printf("%s: audio id = %d, error = %x\n", __PRETTY_FUNCTION__,audioID,error); + ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__,audioID,error); } } } @@ -334,7 +360,7 @@ bool AudioEngineImpl::pause(int audioID) auto error = alGetError(); if (error != AL_NO_ERROR) { ret = false; - printf("%s: audio id = %d, error = %x\n", __PRETTY_FUNCTION__,audioID,error); + ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__,audioID,error); } return ret; @@ -348,7 +374,7 @@ bool AudioEngineImpl::resume(int audioID) auto error = alGetError(); if (error != AL_NO_ERROR) { ret = false; - printf("%s: audio id = %d, error = %x\n", __PRETTY_FUNCTION__,audioID,error); + ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__,audioID,error); } return ret; @@ -358,7 +384,9 @@ void AudioEngineImpl::stop(int audioID) { auto player = _audioPlayers[audioID]; player->destroy(); - _alSourceUsed[player->_alSource] = false; + //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; } void AudioEngineImpl::stopAll() @@ -367,10 +395,12 @@ void AudioEngineImpl::stopAll() { player.second->destroy(); } - for(int index = 0; index < MAX_AUDIOINSTANCES; ++index) - { - _alSourceUsed[_alSources[index]] = false; - } + //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; +// } } float AudioEngineImpl::getDuration(int audioID) @@ -395,7 +425,7 @@ float AudioEngineImpl::getCurrentTime(int audioID) auto error = alGetError(); if (error != AL_NO_ERROR) { - printf("%s, audio id:%d,error code:%x", __PRETTY_FUNCTION__,audioID,error); + ALOGE("%s, audio id:%d,error code:%x", __PRETTY_FUNCTION__,audioID,error); } } } @@ -420,7 +450,7 @@ bool AudioEngineImpl::setCurrentTime(int audioID, float time) else { if (player->_audioCache->_bytesOfRead != player->_audioCache->_dataSize && (time * player->_audioCache->_sampleRate * player->_audioCache->_bytesPerFrame) > player->_audioCache->_bytesOfRead) { - printf("%s: audio id = %d\n", __PRETTY_FUNCTION__,audioID); + ALOGE("%s: audio id = %d", __PRETTY_FUNCTION__,audioID); break; } @@ -428,7 +458,7 @@ bool AudioEngineImpl::setCurrentTime(int audioID, float time) auto error = alGetError(); if (error != AL_NO_ERROR) { - printf("%s: audio id = %d, error = %x\n", __PRETTY_FUNCTION__,audioID,error); + ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__,audioID,error); } ret = true; } @@ -448,13 +478,17 @@ void AudioEngineImpl::update(float dt) int audioID; AudioPlayer* player; +// ALOGV("AudioPlayer count: %d", (int)_audioPlayers.size()); + for (auto it = _audioPlayers.begin(); it != _audioPlayers.end(); ) { audioID = it->first; player = it->second; alGetSourcei(player->_alSource, AL_SOURCE_STATE, &sourceState); - if(player->_removeByAudioEngine) + if (player->_removeByAudioEngine) { + _alSourceUsed[player->_alSource] = false; + AudioEngine::remove(audioID); _threadMutex.lock(); it = _audioPlayers.erase(it); @@ -462,10 +496,11 @@ void AudioEngineImpl::update(float dt) delete player; } else if (player->_ready && sourceState == AL_STOPPED) { + _alSourceUsed[player->_alSource] = false; if (player->_finishCallbak) { auto& audioInfo = AudioEngine::_audioIDInfoMap[audioID]; - player->_finishCallbak(audioID, *audioInfo.filePath); + player->_finishCallbak(audioID, *audioInfo.filePath); //FIXME: callback will delay 50ms } AudioEngine::remove(audioID); diff --git a/cocos/audio/apple/AudioPlayer.h b/cocos/audio/apple/AudioPlayer.h index a9a4d77640..2049079bcc 100644 --- a/cocos/audio/apple/AudioPlayer.h +++ b/cocos/audio/apple/AudioPlayer.h @@ -22,12 +22,11 @@ THE SOFTWARE. ****************************************************************************/ +#pragma once + #include "platform/CCPlatformConfig.h" #if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC -#ifndef __AUDIO_PLAYER_H_ -#define __AUDIO_PLAYER_H_ - #include #include #include @@ -55,8 +54,9 @@ public: bool setLoop(bool loop); protected: + void setCache(AudioCache* cache); void rotateBufferThread(int offsetFrame); - bool play2d(AudioCache* cache); + bool play2d(); AudioCache* _audioCache; @@ -64,7 +64,7 @@ protected: bool _loop; std::function _finishCallbak; - bool _beDestroy; + bool _isDestroyed; bool _removeByAudioEngine; bool _ready; ALuint _alSource; @@ -73,16 +73,21 @@ protected: float _currTime; bool _streamingSource; ALuint _bufferIds[3]; - std::thread _rotateBufferThread; + std::thread* _rotateBufferThread; std::condition_variable _sleepCondition; std::mutex _sleepMutex; bool _timeDirty; + bool _isRotateThreadExited; + + std::mutex _play2dMutex; + + unsigned int _id; friend class AudioEngineImpl; }; } NS_CC_END -#endif // __AUDIO_PLAYER_H_ + #endif diff --git a/cocos/audio/apple/AudioPlayer.mm b/cocos/audio/apple/AudioPlayer.mm index 7b82d69289..495ed879c7 100644 --- a/cocos/audio/apple/AudioPlayer.mm +++ b/cocos/audio/apple/AudioPlayer.mm @@ -22,6 +22,8 @@ THE SOFTWARE. ****************************************************************************/ +#define LOG_TAG "AudioPlayer" + #include "platform/CCPlatformConfig.h" #if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC @@ -32,111 +34,204 @@ #include "platform/CCFileUtils.h" #import +#define VERY_VERY_VERBOSE_LOGGING +#ifdef VERY_VERY_VERBOSE_LOGGING +#define ALOGVV ALOGV +#else +#define ALOGVV(...) do{} while(false) +#endif + using namespace cocos2d; using namespace cocos2d::experimental; +namespace { +unsigned int __idIndex = 0; +} + AudioPlayer::AudioPlayer() : _audioCache(nullptr) , _finishCallbak(nullptr) -, _beDestroy(false) +, _isDestroyed(false) , _removeByAudioEngine(false) , _ready(false) , _currTime(0.0f) , _streamingSource(false) +, _rotateBufferThread(nullptr) , _timeDirty(false) -{ +, _isRotateThreadExited(false) +, _id(++__idIndex) +{ + memset(_bufferIds, 0, sizeof(_bufferIds)); } AudioPlayer::~AudioPlayer() { - if (_streamingSource) { - _beDestroy = true; - _sleepCondition.notify_one(); - if (_rotateBufferThread.joinable()) { - _rotateBufferThread.join(); - } + ALOGVV("~AudioPlayer() (%p), id=%u", this, _id); + destroy(); + + if (_streamingSource) + { alDeleteBuffers(3, _bufferIds); } } void AudioPlayer::destroy() { - alSourceStop(_alSource); - alSourcei(_alSource, AL_BUFFER, NULL); + if (_isDestroyed) + return; - std::unique_lock lk(_sleepMutex); - _beDestroy = true; - if (_streamingSource) { - _sleepCondition.notify_one(); - } - else if (_ready) { - _removeByAudioEngine = true; - } - _ready = false; -} + ALOGVV("AudioPlayer::destroy begin, id=%u", _id); -bool AudioPlayer::play2d(AudioCache* cache) -{ - if (!cache->_alBufferReady) { - _removeByAudioEngine = true; - return false; - } - _audioCache = cache; + _isDestroyed = true; - alSourcei(_alSource, AL_BUFFER, 0); - alSourcef(_alSource, AL_PITCH, 1.0f); - alSourcef(_alSource, AL_GAIN, _volume); - alSourcei(_alSource, AL_LOOPING, AL_FALSE); - - if (_audioCache->_queBufferFrames == 0) { - if (_loop) { - alSourcei(_alSource, AL_LOOPING, AL_TRUE); - } - } - else { - alGetError(); - 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); + 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)); } } - else { - printf("%s:alGenBuffers error code:%x", __PRETTY_FUNCTION__,alError); - _removeByAudioEngine = true; - return false; + + // 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!"); + } } - _streamingSource = true; - } + } while(false); + ALOGVV("Before alSourceStop"); + alSourceStop(_alSource); CHECK_AL_ERROR_DEBUG(); + ALOGVV("Before alSourcei"); + alSourcei(_alSource, AL_BUFFER, NULL); 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(); + ALOGV("AudioPlayer::play2d, _alSource: %u", _alSource); + + /*********************************************************************/ + /* Note that it may be in sub thread or in main thread. **/ + /*********************************************************************/ + bool ret = false; + do { - std::unique_lock lk(_sleepMutex); - if (_beDestroy) { - _removeByAudioEngine = true; - return false; - } - if (_streamingSource) { - alSourceQueueBuffers(_alSource, QUEUEBUFFER_NUM, _bufferIds); - _rotateBufferThread = std::thread(&AudioPlayer::rotateBufferThread,this, _audioCache->_queBufferFrames * QUEUEBUFFER_NUM + 1); - } - else { - alSourcei(_alSource, AL_BUFFER, _audioCache->_alBufferId); + if (_audioCache->_state != AudioCache::State::READY) + { + ALOGE("alBuffer isn't ready for play!"); + break; } - alGetError(); - alSourcePlay(_alSource); - } + 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 + { + 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", __PRETTY_FUNCTION__,alError); + break; + } + _streamingSource = true; + } + + { + std::unique_lock lk(_sleepMutex); + if (_isDestroyed) + break; + + if (_streamingSource) + { + alSourceQueueBuffers(_alSource, QUEUEBUFFER_NUM, _bufferIds); + CHECK_AL_ERROR_DEBUG(); + _rotateBufferThread = new 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; + } + + ALint state; + alGetSourcei(_alSource, AL_SOURCE_STATE, &state); + assert(state == AL_PLAYING); + _ready = true; + ret = true; + } while (false); - auto alError = alGetError(); - if (alError != AL_NO_ERROR) { - printf("%s:alSourcePlay error code:%x\n", __PRETTY_FUNCTION__,alError); + if (!ret) + { _removeByAudioEngine = true; - return false; } - _ready = true; - return true; + _play2dMutex.unlock(); + return ret; } void AudioPlayer::rotateBufferThread(int offsetFrame) @@ -153,22 +248,22 @@ void AudioPlayer::rotateBufferThread(int offsetFrame) auto error = ExtAudioFileOpenURL(fileURL, &extRef); if(error) { - printf("%s: ExtAudioFileOpenURL FAILED, Error = %ld\n", __PRETTY_FUNCTION__,(long) error); + ALOGE("%s: ExtAudioFileOpenURL FAILED, Error = %ld", __PRETTY_FUNCTION__,(long) error); goto ExitBufferThread; } - error = ExtAudioFileSetProperty(extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(_audioCache->outputFormat), &_audioCache->outputFormat); + error = ExtAudioFileSetProperty(extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(_audioCache->_outputFormat), &_audioCache->_outputFormat); AudioBufferList theDataBuffer; theDataBuffer.mNumberBuffers = 1; theDataBuffer.mBuffers[0].mData = tmpBuffer; theDataBuffer.mBuffers[0].mDataByteSize = _audioCache->_queBufferBytes; - theDataBuffer.mBuffers[0].mNumberChannels = _audioCache->outputFormat.mChannelsPerFrame; + theDataBuffer.mBuffers[0].mNumberChannels = _audioCache->_outputFormat.mChannelsPerFrame; if (offsetFrame != 0) { ExtAudioFileSeek(extRef, offsetFrame); } - while (!_beDestroy) { + while (!_isDestroyed) { alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState); if (sourceState == AL_PLAYING) { alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed); @@ -176,7 +271,7 @@ void AudioPlayer::rotateBufferThread(int offsetFrame) bufferProcessed--; if (_timeDirty) { _timeDirty = false; - offsetFrame = _currTime * _audioCache->outputFormat.mSampleRate; + offsetFrame = _currTime * _audioCache->_outputFormat.mSampleRate; ExtAudioFileSeek(extRef, offsetFrame); } else { @@ -199,20 +294,20 @@ void AudioPlayer::rotateBufferThread(int offsetFrame) theDataBuffer.mBuffers[0].mDataByteSize = _audioCache->_queBufferBytes; ExtAudioFileRead(extRef, (UInt32*)&frames, &theDataBuffer); } else { - _beDestroy = true; + _isDestroyed = true; break; } } ALuint bid; alSourceUnqueueBuffers(_alSource, 1, &bid); - alBufferData(bid, _audioCache->_format, tmpBuffer, frames * _audioCache->outputFormat.mBytesPerFrame, _audioCache->_sampleRate); + alBufferData(bid, _audioCache->_format, tmpBuffer, frames * _audioCache->_outputFormat.mBytesPerFrame, _audioCache->_sampleRate); alSourceQueueBuffers(_alSource, 1, &bid); } } std::unique_lock lk(_sleepMutex); - if (_beDestroy) { + if (_isDestroyed) { break; } @@ -220,17 +315,19 @@ void AudioPlayer::rotateBufferThread(int offsetFrame) } ExitBufferThread: + ALOGV("Exit rotate buffer thread ..."); CFRelease(fileURL); // Dispose the ExtAudioFileRef, it is no longer needed if (extRef){ ExtAudioFileDispose(extRef); } free(tmpBuffer); + _isRotateThreadExited = true; } bool AudioPlayer::setLoop(bool loop) { - if (!_beDestroy ) { + if (!_isDestroyed ) { _loop = loop; return true; } @@ -240,7 +337,7 @@ bool AudioPlayer::setLoop(bool loop) bool AudioPlayer::setTime(float time) { - if (!_beDestroy && time >= 0.0f && time < _audioCache->_duration) { + if (!_isDestroyed && time >= 0.0f && time < _audioCache->_duration) { _currTime = time; _timeDirty = true; diff --git a/tests/cpp-tests/Classes/NewAudioEngineTest/NewAudioEngineTest.cpp b/tests/cpp-tests/Classes/NewAudioEngineTest/NewAudioEngineTest.cpp index ecbff6d745..e0ae80f8c0 100644 --- a/tests/cpp-tests/Classes/NewAudioEngineTest/NewAudioEngineTest.cpp +++ b/tests/cpp-tests/Classes/NewAudioEngineTest/NewAudioEngineTest.cpp @@ -40,6 +40,7 @@ AudioEngineTests::AudioEngineTests() ADD_TEST_CASE(InvalidAudioFileTest); ADD_TEST_CASE(LargeAudioFileTest); ADD_TEST_CASE(AudioPerformanceTest); + ADD_TEST_CASE(AudioSwitchStateTest); } namespace { @@ -748,3 +749,33 @@ std::string AudioPerformanceTest::subtitle() const return "Please see console for the result"; } +///////////////////////////////////////////////////////////////////////// + +bool AudioSwitchStateTest::init() +{ + if (AudioEngineTestDemo::init()) + { + schedule([](float dt){ + + AudioEngine::uncacheAll(); + AudioEngine::preload("audio/SoundEffectsFX009/FX081.mp3"); + AudioEngine::play2d("audio/SoundEffectsFX009/FX082.mp3"); + AudioEngine::play2d("audio/LuckyDay.mp3"); + + }, 0.1f, "AudioSwitchStateTest"); + + return true; + } + + return false; +} + +std::string AudioSwitchStateTest::title() const +{ + return "play, preload, stop switch test"; +} + +std::string AudioSwitchStateTest::subtitle() const +{ + return "Should not crash"; +} diff --git a/tests/cpp-tests/Classes/NewAudioEngineTest/NewAudioEngineTest.h b/tests/cpp-tests/Classes/NewAudioEngineTest/NewAudioEngineTest.h index 63f2c60140..209936a5d6 100644 --- a/tests/cpp-tests/Classes/NewAudioEngineTest/NewAudioEngineTest.h +++ b/tests/cpp-tests/Classes/NewAudioEngineTest/NewAudioEngineTest.h @@ -177,4 +177,15 @@ public: virtual std::string subtitle() const override; }; +class AudioSwitchStateTest : public AudioEngineTestDemo +{ +public: + CREATE_FUNC(AudioSwitchStateTest); + + virtual bool init() override; + + virtual std::string title() const override; + virtual std::string subtitle() const override; +}; + #endif /* defined(__NEWAUDIOENGINE_TEST_H_) */