mirror of https://github.com/axmolengine/axmol.git
[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:
parent
b8f8ff0d25
commit
bb004c5e22
|
@ -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 <OpenAL/al.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
|
||||
|
@ -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<void(bool)>& 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<void()> > _playCallbacks;
|
||||
|
||||
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::mutex _readDataTaskMutex;
|
||||
|
||||
bool _exitReadDataTask;
|
||||
State _state;
|
||||
|
||||
std::shared_ptr<bool> _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
|
||||
|
||||
|
|
|
@ -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<bool>(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<void()>& callback)
|
||||
{
|
||||
_callbackMutex.lock();
|
||||
if (_alBufferReady) {
|
||||
callback();
|
||||
} else if(!_loadFail){
|
||||
_callbacks.push_back(callback);
|
||||
std::lock_guard<std::mutex> lk(_playCallbackMutex);
|
||||
switch (_state)
|
||||
{
|
||||
case State::INITIAL:
|
||||
case State::LOADING:
|
||||
_playCallbacks.push_back(callback);
|
||||
break;
|
||||
|
||||
case State::READY:
|
||||
// If state is failure, we still need to invoke the callback
|
||||
// since the callback will set the 'AudioPlayer::_removeByAudioEngine' flag to true.
|
||||
case State::FAILED:
|
||||
callback();
|
||||
break;
|
||||
|
||||
default:
|
||||
ALOGE("Invalid state: %d", _state);
|
||||
break;
|
||||
}
|
||||
_callbackMutex.unlock();
|
||||
}
|
||||
|
||||
void AudioCache::invokingPlayCallbacks()
|
||||
{
|
||||
_callbackMutex.lock();
|
||||
auto count = _callbacks.size();
|
||||
for (size_t index = 0; index < count; ++index) {
|
||||
_callbacks[index]();
|
||||
std::lock_guard<std::mutex> lk(_playCallbackMutex);
|
||||
|
||||
for (auto&& cb : _playCallbacks)
|
||||
{
|
||||
cb();
|
||||
}
|
||||
_callbacks.clear();
|
||||
_callbackMutex.unlock();
|
||||
|
||||
_playCallbacks.clear();
|
||||
}
|
||||
|
||||
void AudioCache::addLoadCallback(const std::function<void(bool)>& 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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <condition_variable>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
@ -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<void (int, const std::string &)> _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
|
||||
|
||||
|
|
|
@ -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 <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::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<std::mutex> 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<std::mutex> 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<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 (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<std::mutex> 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;
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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_) */
|
||||
|
|
Loading…
Reference in New Issue