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.
|
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
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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";
|
||||||
|
}
|
||||||
|
|
|
@ -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_) */
|
||||||
|
|
Loading…
Reference in New Issue