[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.
This commit is contained in:
James Chen 2016-07-21 14:57:59 +08:00 committed by minggo
parent b8f8ff0d25
commit bb004c5e22
7 changed files with 640 additions and 282 deletions

View File

@ -22,12 +22,11 @@
THE SOFTWARE. THE SOFTWARE.
****************************************************************************/ ****************************************************************************/
#pragma once
#include "platform/CCPlatformConfig.h" #include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC #if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC
#ifndef __AUDIO_CACHE_H_
#define __AUDIO_CACHE_H_
#import <OpenAL/al.h> #import <OpenAL/al.h>
#import <AudioToolbox/AudioToolbox.h> #import <AudioToolbox/AudioToolbox.h>
@ -40,14 +39,49 @@
#define QUEUEBUFFER_NUM 3 #define QUEUEBUFFER_NUM 3
#define QUEUEBUFFER_TIME_STEP 0.1 #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 NS_CC_BEGIN
namespace experimental{ namespace experimental{
class AudioEngineImpl; class AudioEngineImpl;
class AudioPlayer; class AudioPlayer;
class AudioCache{ class AudioCache
{
public: public:
enum class State
{
INITIAL,
LOADING,
READY,
FAILED
};
AudioCache(); AudioCache();
~AudioCache(); ~AudioCache();
@ -56,7 +90,8 @@ public:
void addLoadCallback(const std::function<void(bool)>& callback); void addLoadCallback(const std::function<void(bool)>& callback);
protected: protected:
void readDataTask(); void setSkipReadDataTask(bool isSkip) { _isSkipReadDataTask = isSkip; };
void readDataTask(unsigned int selfId);
void invokingPlayCallbacks(); void invokingPlayCallbacks();
@ -68,7 +103,7 @@ protected:
ALsizei _sampleRate; ALsizei _sampleRate;
float _duration; float _duration;
int _bytesPerFrame; int _bytesPerFrame;
AudioStreamBasicDescription outputFormat; AudioStreamBasicDescription _outputFormat;
/*Cache related stuff; /*Cache related stuff;
* Cache pcm data when sizeInBytes less than PCMDATA_CACHEMAXSIZE * Cache pcm data when sizeInBytes less than PCMDATA_CACHEMAXSIZE
@ -85,16 +120,21 @@ protected:
UInt32 _queBufferFrames; UInt32 _queBufferFrames;
UInt32 _queBufferBytes; UInt32 _queBufferBytes;
bool _alBufferReady; std::mutex _playCallbackMutex;
bool _loadFail; std::vector< std::function<void()> > _playCallbacks;
std::mutex _callbackMutex;
std::vector< std::function<void()> > _callbacks; // loadCallbacks doesn't need mutex since it's invoked only in Cocos thread.
std::vector< std::function<void(bool)> > _loadCallbacks; std::vector< std::function<void(bool)> > _loadCallbacks;
std::mutex _readDataTaskMutex; std::mutex _readDataTaskMutex;
bool _exitReadDataTask; State _state;
std::shared_ptr<bool> _isDestroyed;
std::string _fileFullPath; std::string _fileFullPath;
unsigned int _id;
bool _isLoadingFinished;
bool _isSkipReadDataTask;
friend class AudioEngineImpl; friend class AudioEngineImpl;
friend class AudioPlayer; friend class AudioPlayer;
@ -103,6 +143,5 @@ protected:
} }
NS_CC_END NS_CC_END
#endif // __AUDIO_CACHE_H_
#endif #endif

View File

@ -22,6 +22,8 @@
THE SOFTWARE. THE SOFTWARE.
****************************************************************************/ ****************************************************************************/
#define LOG_TAG "AudioCache"
#include "platform/CCPlatformConfig.h" #include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC #if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC
@ -34,6 +36,18 @@
#include "base/CCDirector.h" #include "base/CCDirector.h"
#include "base/CCScheduler.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 #define PCMDATA_CACHEMAXSIZE 1048576
typedef ALvoid AL_APIENTRY (*alBufferDataStaticProcPtr) (const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq); 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() AudioCache::AudioCache()
: _dataSize(0) : _dataSize(0)
, _format(-1)
, _duration(0.0f)
, _bytesPerFrame(0)
, _alBufferId(INVALID_AL_BUFFER_ID)
, _pcmData(nullptr) , _pcmData(nullptr)
, _bytesOfRead(0) , _bytesOfRead(0)
, _queBufferFrames(0) , _queBufferFrames(0)
, _queBufferBytes(0) , _queBufferBytes(0)
, _alBufferReady(false) , _state(State::INITIAL)
, _loadFail(false) , _isDestroyed(std::make_shared<bool>(false))
, _exitReadDataTask(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() AudioCache::~AudioCache()
{ {
_exitReadDataTask = true; ALOGVV("~AudioCache() %p, id=%u, begin", this, _id);
if(_pcmData){ *_isDestroyed = true;
if (_alBufferReady){ while (!_isLoadingFinished)
alDeleteBuffers(1, &_alBufferId); {
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); free(_pcmData);
} }
if (_queBufferFrames > 0) { if (_queBufferFrames > 0)
for (int index = 0; index < QUEUEBUFFER_NUM; ++index) { {
for (int index = 0; index < QUEUEBUFFER_NUM; ++index)
{
free(_queBuffers[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(); _readDataTaskMutex.lock();
_state = State::LOADING;
AudioStreamBasicDescription theFileFormat; CFURLRef fileURL = nil;
UInt32 thePropertySize = sizeof(theFileFormat);
SInt64 theFileLengthInFrames;
SInt64 readInFrames;
SInt64 dataSize;
SInt64 frames;
AudioBufferList theDataBuffer;
ExtAudioFileRef extRef = nullptr; ExtAudioFileRef extRef = nullptr;
NSString *fileFullPath = [[NSString alloc] initWithCString:_fileFullPath.c_str() encoding:NSUTF8StringEncoding]; do
auto fileURL = (CFURLRef)[[NSURL alloc] initFileURLWithPath:fileFullPath]; {
[fileFullPath release]; 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); auto error = ExtAudioFileOpenURL(fileURL, &extRef);
if(error) { if (error) {
printf("%s: ExtAudioFileOpenURL FAILED, Error = %ld\n", __PRETTY_FUNCTION__, (long)error); ALOGE("%s: ExtAudioFileOpenURL FAILED, Error = %ld", __PRETTY_FUNCTION__, (long)error);
goto ExitThread; break;
}
// 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;
} }
dataSize = _dataSize - _bytesOfRead; if (*_isDestroyed) break;
if (!_exitReadDataTask && dataSize > 0) {
// 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].mDataByteSize = (UInt32)dataSize;
theDataBuffer.mBuffers[0].mData = _pcmData + _bytesOfRead; theDataBuffer.mBuffers[0].mNumberChannels = _outputFormat.mChannelsPerFrame;
theDataBuffer.mBuffers[0].mData = _pcmData;
frames = readInFrames; frames = readInFrames;
ExtAudioFileRead(extRef, (UInt32*)&frames, &theDataBuffer); 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; } while (false);
}
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;
}
}
ExitThread: if (fileURL != nil)
CFRelease(fileURL); CFRelease(fileURL);
if (extRef)
if (extRef != nullptr)
ExtAudioFileDispose(extRef); ExtAudioFileDispose(extRef);
_readDataTaskMutex.unlock(); //FIXME: Why to invoke play callback first? Should it be after 'load' callback?
if (_queBufferFrames > 0)
_alBufferReady = true;
else
_loadFail = true;
invokingPlayCallbacks(); invokingPlayCallbacks();
invokingLoadCallbacks(); 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<void()>& callback) void AudioCache::addPlayCallback(const std::function<void()>& callback)
{ {
_callbackMutex.lock(); std::lock_guard<std::mutex> lk(_playCallbackMutex);
if (_alBufferReady) { switch (_state)
callback(); {
} else if(!_loadFail){ case State::INITIAL:
_callbacks.push_back(callback); 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() void AudioCache::invokingPlayCallbacks()
{ {
_callbackMutex.lock(); std::lock_guard<std::mutex> lk(_playCallbackMutex);
auto count = _callbacks.size();
for (size_t index = 0; index < count; ++index) { for (auto&& cb : _playCallbacks)
_callbacks[index](); {
cb();
} }
_callbacks.clear();
_callbackMutex.unlock(); _playCallbacks.clear();
} }
void AudioCache::addLoadCallback(const std::function<void(bool)>& callback) void AudioCache::addLoadCallback(const std::function<void(bool)>& callback)
{ {
if (_alBufferReady) { switch (_state)
callback(true); {
} else if(_loadFail){ case State::INITIAL:
callback(false); case State::LOADING:
} _loadCallbacks.push_back(callback);
else { break;
_loadCallbacks.push_back(callback);
case State::READY:
callback(true);
break;
case State::FAILED:
callback(false);
break;
default:
ALOGE("Invalid state: %d", _state);
break;
} }
} }
void AudioCache::invokingLoadCallbacks() 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(); auto scheduler = Director::getInstance()->getScheduler();
scheduler->performFunctionInCocosThread([&](){ scheduler->performFunctionInCocosThread([&, isDestroyed](){
auto count = _loadCallbacks.size(); if (*isDestroyed)
for (size_t index = 0; index < count; ++index) { {
_loadCallbacks[index](_alBufferReady); ALOGV("invokingLoadCallbacks perform in cocos thread, AudioCache (%p) was destroyed!", this);
return;
} }
for (auto&& cb : _loadCallbacks)
{
cb(_state == State::READY);
}
_loadCallbacks.clear(); _loadCallbacks.clear();
}); });
} }

View File

@ -22,6 +22,8 @@
THE SOFTWARE. THE SOFTWARE.
****************************************************************************/ ****************************************************************************/
#define LOG_TAG "AudioEngine-inl.mm"
#include "platform/CCPlatformConfig.h" #include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC #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 setCategory: AVAudioSessionCategoryAmbient
error: &error]; error: &error];
if (!success) { if (!success) {
printf("Fail to set audio session.\n"); ALOGE("Fail to set audio session.");
return; return;
} }
[[AVAudioSession sharedInstance] setActive:YES error:&error]; [[AVAudioSession sharedInstance] setActive:YES error:&error];
@ -183,7 +185,7 @@ bool AudioEngineImpl::init()
auto alError = alGetError(); auto alError = alGetError();
if(alError != AL_NO_ERROR) 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; break;
} }
@ -192,6 +194,7 @@ bool AudioEngineImpl::init()
} }
_scheduler = Director::getInstance()->getScheduler(); _scheduler = Director::getInstance()->getScheduler();
ret = true; ret = true;
ALOGI("OpenAL was initialized successfully!");
} }
}while (false); }while (false);
@ -206,14 +209,23 @@ AudioCache* AudioEngineImpl::preload(const std::string& filePath, std::function<
if (it == _audioCaches.end()) { if (it == _audioCaches.end()) {
audioCache = &_audioCaches[filePath]; audioCache = &_audioCaches[filePath];
audioCache->_fileFullPath = FileUtils::getInstance()->fullPathForFilename(filePath); audioCache->_fileFullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
unsigned int cacheId = audioCache->_id;
AudioEngine::addTask(std::bind(&AudioCache::readDataTask, audioCache)); 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 { else {
audioCache = &it->second; audioCache = &it->second;
} }
if(audioCache && callback) if (audioCache && callback)
{ {
audioCache->addLoadCallback(callback); audioCache->addLoadCallback(callback);
} }
@ -244,6 +256,7 @@ int AudioEngineImpl::play2d(const std::string &filePath ,bool loop ,float volume
if (player == nullptr) { if (player == nullptr) {
return AudioEngine::INVALID_AUDIO_ID; return AudioEngine::INVALID_AUDIO_ID;
} }
player->_alSource = alSource; player->_alSource = alSource;
player->_loop = loop; player->_loop = loop;
player->_volume = volume; player->_volume = volume;
@ -254,14 +267,15 @@ int AudioEngineImpl::play2d(const std::string &filePath ,bool loop ,float volume
return AudioEngine::INVALID_AUDIO_ID; return AudioEngine::INVALID_AUDIO_ID;
} }
player->setCache(audioCache);
_threadMutex.lock(); _threadMutex.lock();
_audioPlayers[_currentAudioID] = player; _audioPlayers[_currentAudioID] = player;
_threadMutex.unlock(); _threadMutex.unlock();
audioCache->addPlayCallback(std::bind(&AudioEngineImpl::_play2d,this,audioCache,_currentAudioID));
_alSourceUsed[alSource] = true; _alSourceUsed[alSource] = true;
audioCache->addPlayCallback(std::bind(&AudioEngineImpl::_play2d,this,audioCache,_currentAudioID));
if (_lazyInitLoop) { if (_lazyInitLoop) {
_lazyInitLoop = false; _lazyInitLoop = false;
_scheduler->schedule(CC_SCHEDULE_SELECTOR(AudioEngineImpl::update), this, 0.05f, 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) 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(); _threadMutex.lock();
auto playerIt = _audioPlayers.find(audioID); auto playerIt = _audioPlayers.find(audioID);
if (playerIt != _audioPlayers.end() && playerIt->second->play2d(cache)) { if (playerIt != _audioPlayers.end() && playerIt->second->play2d()) {
_scheduler->performFunctionInCocosThread([audioID](){ _scheduler->performFunctionInCocosThread([audioID](){
if (AudioEngine::_audioIDInfoMap.find(audioID) != AudioEngine::_audioIDInfoMap.end()) { if (AudioEngine::_audioIDInfoMap.find(audioID) != AudioEngine::_audioIDInfoMap.end()) {
AudioEngine::_audioIDInfoMap[audioID].state = AudioEngine::AudioState::PLAYING; AudioEngine::_audioIDInfoMap[audioID].state = AudioEngine::AudioState::PLAYING;
} }
@ -284,6 +301,15 @@ void AudioEngineImpl::_play2d(AudioCache *cache, int audioID)
} }
_threadMutex.unlock(); _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) void AudioEngineImpl::setVolume(int audioID,float volume)
@ -296,7 +322,7 @@ void AudioEngineImpl::setVolume(int audioID,float volume)
auto error = alGetError(); auto error = alGetError();
if (error != AL_NO_ERROR) { 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(); auto error = alGetError();
if (error != AL_NO_ERROR) { 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(); auto error = alGetError();
if (error != AL_NO_ERROR) { if (error != AL_NO_ERROR) {
ret = false; 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; return ret;
@ -348,7 +374,7 @@ bool AudioEngineImpl::resume(int audioID)
auto error = alGetError(); auto error = alGetError();
if (error != AL_NO_ERROR) { if (error != AL_NO_ERROR) {
ret = false; 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; return ret;
@ -358,7 +384,9 @@ void AudioEngineImpl::stop(int audioID)
{ {
auto player = _audioPlayers[audioID]; auto player = _audioPlayers[audioID];
player->destroy(); 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() void AudioEngineImpl::stopAll()
@ -367,10 +395,12 @@ void AudioEngineImpl::stopAll()
{ {
player.second->destroy(); player.second->destroy();
} }
for(int index = 0; index < MAX_AUDIOINSTANCES; ++index) //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[_alSources[index]] = false; // for(int index = 0; index < MAX_AUDIOINSTANCES; ++index)
} // {
// _alSourceUsed[_alSources[index]] = false;
// }
} }
float AudioEngineImpl::getDuration(int audioID) float AudioEngineImpl::getDuration(int audioID)
@ -395,7 +425,7 @@ float AudioEngineImpl::getCurrentTime(int audioID)
auto error = alGetError(); auto error = alGetError();
if (error != AL_NO_ERROR) { 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 { else {
if (player->_audioCache->_bytesOfRead != player->_audioCache->_dataSize && if (player->_audioCache->_bytesOfRead != player->_audioCache->_dataSize &&
(time * player->_audioCache->_sampleRate * player->_audioCache->_bytesPerFrame) > player->_audioCache->_bytesOfRead) { (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; break;
} }
@ -428,7 +458,7 @@ bool AudioEngineImpl::setCurrentTime(int audioID, float time)
auto error = alGetError(); auto error = alGetError();
if (error != AL_NO_ERROR) { 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; ret = true;
} }
@ -448,13 +478,17 @@ void AudioEngineImpl::update(float dt)
int audioID; int audioID;
AudioPlayer* player; AudioPlayer* player;
// ALOGV("AudioPlayer count: %d", (int)_audioPlayers.size());
for (auto it = _audioPlayers.begin(); it != _audioPlayers.end(); ) { for (auto it = _audioPlayers.begin(); it != _audioPlayers.end(); ) {
audioID = it->first; audioID = it->first;
player = it->second; player = it->second;
alGetSourcei(player->_alSource, AL_SOURCE_STATE, &sourceState); alGetSourcei(player->_alSource, AL_SOURCE_STATE, &sourceState);
if(player->_removeByAudioEngine) if (player->_removeByAudioEngine)
{ {
_alSourceUsed[player->_alSource] = false;
AudioEngine::remove(audioID); AudioEngine::remove(audioID);
_threadMutex.lock(); _threadMutex.lock();
it = _audioPlayers.erase(it); it = _audioPlayers.erase(it);
@ -462,10 +496,11 @@ void AudioEngineImpl::update(float dt)
delete player; delete player;
} }
else if (player->_ready && sourceState == AL_STOPPED) { else if (player->_ready && sourceState == AL_STOPPED) {
_alSourceUsed[player->_alSource] = false; _alSourceUsed[player->_alSource] = false;
if (player->_finishCallbak) { if (player->_finishCallbak) {
auto& audioInfo = AudioEngine::_audioIDInfoMap[audioID]; auto& audioInfo = AudioEngine::_audioIDInfoMap[audioID];
player->_finishCallbak(audioID, *audioInfo.filePath); player->_finishCallbak(audioID, *audioInfo.filePath); //FIXME: callback will delay 50ms
} }
AudioEngine::remove(audioID); AudioEngine::remove(audioID);

View File

@ -22,12 +22,11 @@
THE SOFTWARE. THE SOFTWARE.
****************************************************************************/ ****************************************************************************/
#pragma once
#include "platform/CCPlatformConfig.h" #include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC #if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC
#ifndef __AUDIO_PLAYER_H_
#define __AUDIO_PLAYER_H_
#include <condition_variable> #include <condition_variable>
#include <mutex> #include <mutex>
#include <string> #include <string>
@ -55,8 +54,9 @@ public:
bool setLoop(bool loop); bool setLoop(bool loop);
protected: protected:
void setCache(AudioCache* cache);
void rotateBufferThread(int offsetFrame); void rotateBufferThread(int offsetFrame);
bool play2d(AudioCache* cache); bool play2d();
AudioCache* _audioCache; AudioCache* _audioCache;
@ -64,7 +64,7 @@ protected:
bool _loop; bool _loop;
std::function<void (int, const std::string &)> _finishCallbak; std::function<void (int, const std::string &)> _finishCallbak;
bool _beDestroy; bool _isDestroyed;
bool _removeByAudioEngine; bool _removeByAudioEngine;
bool _ready; bool _ready;
ALuint _alSource; ALuint _alSource;
@ -73,16 +73,21 @@ protected:
float _currTime; float _currTime;
bool _streamingSource; bool _streamingSource;
ALuint _bufferIds[3]; ALuint _bufferIds[3];
std::thread _rotateBufferThread; std::thread* _rotateBufferThread;
std::condition_variable _sleepCondition; std::condition_variable _sleepCondition;
std::mutex _sleepMutex; std::mutex _sleepMutex;
bool _timeDirty; bool _timeDirty;
bool _isRotateThreadExited;
std::mutex _play2dMutex;
unsigned int _id;
friend class AudioEngineImpl; friend class AudioEngineImpl;
}; };
} }
NS_CC_END NS_CC_END
#endif // __AUDIO_PLAYER_H_
#endif #endif

View File

@ -22,6 +22,8 @@
THE SOFTWARE. THE SOFTWARE.
****************************************************************************/ ****************************************************************************/
#define LOG_TAG "AudioPlayer"
#include "platform/CCPlatformConfig.h" #include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC #if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC
@ -32,111 +34,204 @@
#include "platform/CCFileUtils.h" #include "platform/CCFileUtils.h"
#import <AudioToolbox/ExtendedAudioFile.h> #import <AudioToolbox/ExtendedAudioFile.h>
#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;
using namespace cocos2d::experimental; using namespace cocos2d::experimental;
namespace {
unsigned int __idIndex = 0;
}
AudioPlayer::AudioPlayer() AudioPlayer::AudioPlayer()
: _audioCache(nullptr) : _audioCache(nullptr)
, _finishCallbak(nullptr) , _finishCallbak(nullptr)
, _beDestroy(false) , _isDestroyed(false)
, _removeByAudioEngine(false) , _removeByAudioEngine(false)
, _ready(false) , _ready(false)
, _currTime(0.0f) , _currTime(0.0f)
, _streamingSource(false) , _streamingSource(false)
, _rotateBufferThread(nullptr)
, _timeDirty(false) , _timeDirty(false)
{ , _isRotateThreadExited(false)
, _id(++__idIndex)
{
memset(_bufferIds, 0, sizeof(_bufferIds));
} }
AudioPlayer::~AudioPlayer() AudioPlayer::~AudioPlayer()
{ {
if (_streamingSource) { ALOGVV("~AudioPlayer() (%p), id=%u", this, _id);
_beDestroy = true; destroy();
_sleepCondition.notify_one();
if (_rotateBufferThread.joinable()) { if (_streamingSource)
_rotateBufferThread.join(); {
}
alDeleteBuffers(3, _bufferIds); alDeleteBuffers(3, _bufferIds);
} }
} }
void AudioPlayer::destroy() void AudioPlayer::destroy()
{ {
alSourceStop(_alSource); if (_isDestroyed)
alSourcei(_alSource, AL_BUFFER, NULL); return;
std::unique_lock<std::mutex> lk(_sleepMutex); ALOGVV("AudioPlayer::destroy begin, id=%u", _id);
_beDestroy = true;
if (_streamingSource) {
_sleepCondition.notify_one();
}
else if (_ready) {
_removeByAudioEngine = true;
}
_ready = false;
}
bool AudioPlayer::play2d(AudioCache* cache) _isDestroyed = true;
{
if (!cache->_alBufferReady) {
_removeByAudioEngine = true;
return false;
}
_audioCache = cache;
alSourcei(_alSource, AL_BUFFER, 0); do
alSourcef(_alSource, AL_PITCH, 1.0f); {
alSourcef(_alSource, AL_GAIN, _volume); if (_audioCache != nullptr)
alSourcei(_alSource, AL_LOOPING, AL_FALSE); {
if (_audioCache->_state == AudioCache::State::INITIAL)
if (_audioCache->_queBufferFrames == 0) { {
if (_loop) { ALOGV("AudioPlayer::destroy, id=%u, cache isn't ready!", _id);
alSourcei(_alSource, AL_LOOPING, AL_TRUE); break;
} }
}
else { while (!_audioCache->_isLoadingFinished)
alGetError(); {
alGenBuffers(3, _bufferIds); std::this_thread::sleep_for(std::chrono::milliseconds(5));
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);
} }
} }
else {
printf("%s:alGenBuffers error code:%x", __PRETTY_FUNCTION__,alError); // Wait for play2d to be finished.
_removeByAudioEngine = true; _play2dMutex.lock();
return false; _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<std::mutex> lk(_sleepMutex); if (_audioCache->_state != AudioCache::State::READY)
if (_beDestroy) { {
_removeByAudioEngine = true; ALOGE("alBuffer isn't ready for play!");
return false; break;
}
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);
} }
alGetError(); alSourcei(_alSource, AL_BUFFER, 0);CHECK_AL_ERROR_DEBUG();
alSourcePlay(_alSource); 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<std::mutex> 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 (!ret)
if (alError != AL_NO_ERROR) { {
printf("%s:alSourcePlay error code:%x\n", __PRETTY_FUNCTION__,alError);
_removeByAudioEngine = true; _removeByAudioEngine = true;
return false;
} }
_ready = true;
return true; _play2dMutex.unlock();
return ret;
} }
void AudioPlayer::rotateBufferThread(int offsetFrame) void AudioPlayer::rotateBufferThread(int offsetFrame)
@ -153,22 +248,22 @@ void AudioPlayer::rotateBufferThread(int offsetFrame)
auto error = ExtAudioFileOpenURL(fileURL, &extRef); auto error = ExtAudioFileOpenURL(fileURL, &extRef);
if(error) { 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; goto ExitBufferThread;
} }
error = ExtAudioFileSetProperty(extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(_audioCache->outputFormat), &_audioCache->outputFormat); error = ExtAudioFileSetProperty(extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(_audioCache->_outputFormat), &_audioCache->_outputFormat);
AudioBufferList theDataBuffer; AudioBufferList theDataBuffer;
theDataBuffer.mNumberBuffers = 1; theDataBuffer.mNumberBuffers = 1;
theDataBuffer.mBuffers[0].mData = tmpBuffer; theDataBuffer.mBuffers[0].mData = tmpBuffer;
theDataBuffer.mBuffers[0].mDataByteSize = _audioCache->_queBufferBytes; theDataBuffer.mBuffers[0].mDataByteSize = _audioCache->_queBufferBytes;
theDataBuffer.mBuffers[0].mNumberChannels = _audioCache->outputFormat.mChannelsPerFrame; theDataBuffer.mBuffers[0].mNumberChannels = _audioCache->_outputFormat.mChannelsPerFrame;
if (offsetFrame != 0) { if (offsetFrame != 0) {
ExtAudioFileSeek(extRef, offsetFrame); ExtAudioFileSeek(extRef, offsetFrame);
} }
while (!_beDestroy) { while (!_isDestroyed) {
alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState); alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState);
if (sourceState == AL_PLAYING) { if (sourceState == AL_PLAYING) {
alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed); alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed);
@ -176,7 +271,7 @@ void AudioPlayer::rotateBufferThread(int offsetFrame)
bufferProcessed--; bufferProcessed--;
if (_timeDirty) { if (_timeDirty) {
_timeDirty = false; _timeDirty = false;
offsetFrame = _currTime * _audioCache->outputFormat.mSampleRate; offsetFrame = _currTime * _audioCache->_outputFormat.mSampleRate;
ExtAudioFileSeek(extRef, offsetFrame); ExtAudioFileSeek(extRef, offsetFrame);
} }
else { else {
@ -199,20 +294,20 @@ void AudioPlayer::rotateBufferThread(int offsetFrame)
theDataBuffer.mBuffers[0].mDataByteSize = _audioCache->_queBufferBytes; theDataBuffer.mBuffers[0].mDataByteSize = _audioCache->_queBufferBytes;
ExtAudioFileRead(extRef, (UInt32*)&frames, &theDataBuffer); ExtAudioFileRead(extRef, (UInt32*)&frames, &theDataBuffer);
} else { } else {
_beDestroy = true; _isDestroyed = true;
break; break;
} }
} }
ALuint bid; ALuint bid;
alSourceUnqueueBuffers(_alSource, 1, &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); alSourceQueueBuffers(_alSource, 1, &bid);
} }
} }
std::unique_lock<std::mutex> lk(_sleepMutex); std::unique_lock<std::mutex> lk(_sleepMutex);
if (_beDestroy) { if (_isDestroyed) {
break; break;
} }
@ -220,17 +315,19 @@ void AudioPlayer::rotateBufferThread(int offsetFrame)
} }
ExitBufferThread: ExitBufferThread:
ALOGV("Exit rotate buffer thread ...");
CFRelease(fileURL); CFRelease(fileURL);
// Dispose the ExtAudioFileRef, it is no longer needed // Dispose the ExtAudioFileRef, it is no longer needed
if (extRef){ if (extRef){
ExtAudioFileDispose(extRef); ExtAudioFileDispose(extRef);
} }
free(tmpBuffer); free(tmpBuffer);
_isRotateThreadExited = true;
} }
bool AudioPlayer::setLoop(bool loop) bool AudioPlayer::setLoop(bool loop)
{ {
if (!_beDestroy ) { if (!_isDestroyed ) {
_loop = loop; _loop = loop;
return true; return true;
} }
@ -240,7 +337,7 @@ bool AudioPlayer::setLoop(bool loop)
bool AudioPlayer::setTime(float time) bool AudioPlayer::setTime(float time)
{ {
if (!_beDestroy && time >= 0.0f && time < _audioCache->_duration) { if (!_isDestroyed && time >= 0.0f && time < _audioCache->_duration) {
_currTime = time; _currTime = time;
_timeDirty = true; _timeDirty = true;

View File

@ -40,6 +40,7 @@ AudioEngineTests::AudioEngineTests()
ADD_TEST_CASE(InvalidAudioFileTest); ADD_TEST_CASE(InvalidAudioFileTest);
ADD_TEST_CASE(LargeAudioFileTest); ADD_TEST_CASE(LargeAudioFileTest);
ADD_TEST_CASE(AudioPerformanceTest); ADD_TEST_CASE(AudioPerformanceTest);
ADD_TEST_CASE(AudioSwitchStateTest);
} }
namespace { namespace {
@ -748,3 +749,33 @@ std::string AudioPerformanceTest::subtitle() const
return "Please see console for the result"; 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";
}

View File

@ -177,4 +177,15 @@ public:
virtual std::string subtitle() const override; 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_) */ #endif /* defined(__NEWAUDIOENGINE_TEST_H_) */