2014-09-03 18:19:21 +08:00
|
|
|
/****************************************************************************
|
|
|
|
Copyright (c) 2014 Chukong Technologies Inc.
|
|
|
|
|
|
|
|
http://www.cocos2d-x.org
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
|
|
in the Software without restriction, including without limitation the rights
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
|
|
all copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
THE SOFTWARE.
|
|
|
|
****************************************************************************/
|
2014-09-30 01:07:11 +08:00
|
|
|
|
|
|
|
#include "platform/CCPlatformConfig.h"
|
2014-09-22 15:38:12 +08:00
|
|
|
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC
|
2014-09-30 01:07:11 +08:00
|
|
|
|
2016-03-21 11:02:00 +08:00
|
|
|
#include "audio/apple/AudioCache.h"
|
2014-09-30 01:07:11 +08:00
|
|
|
|
|
|
|
#import <Foundation/Foundation.h>
|
2014-09-03 18:19:21 +08:00
|
|
|
#import <OpenAL/alc.h>
|
|
|
|
#import <AudioToolbox/ExtendedAudioFile.h>
|
2014-09-30 01:07:11 +08:00
|
|
|
#include <thread>
|
2015-07-28 15:27:07 +08:00
|
|
|
#include "base/CCDirector.h"
|
|
|
|
#include "base/CCScheduler.h"
|
2014-09-03 18:19:21 +08:00
|
|
|
|
2014-09-10 17:12:46 +08:00
|
|
|
#define PCMDATA_CACHEMAXSIZE 1048576
|
2014-09-03 18:19:21 +08:00
|
|
|
|
|
|
|
typedef ALvoid AL_APIENTRY (*alBufferDataStaticProcPtr) (const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq);
|
2014-09-10 17:12:46 +08:00
|
|
|
static ALvoid alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq)
|
2014-09-03 18:19:21 +08:00
|
|
|
{
|
|
|
|
static alBufferDataStaticProcPtr proc = NULL;
|
|
|
|
|
2014-09-10 17:12:46 +08:00
|
|
|
if (proc == NULL){
|
2014-09-03 18:19:21 +08:00
|
|
|
proc = (alBufferDataStaticProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alBufferDataStatic");
|
|
|
|
}
|
|
|
|
|
2014-09-10 17:12:46 +08:00
|
|
|
if (proc){
|
2014-09-03 18:19:21 +08:00
|
|
|
proc(bid, format, data, size, freq);
|
2014-09-10 17:12:46 +08:00
|
|
|
}
|
2014-09-03 18:19:21 +08:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
using namespace cocos2d;
|
2014-09-16 14:57:58 +08:00
|
|
|
using namespace cocos2d::experimental;
|
2014-09-03 18:19:21 +08:00
|
|
|
|
|
|
|
AudioCache::AudioCache()
|
2014-11-26 09:53:52 +08:00
|
|
|
: _dataSize(0)
|
|
|
|
, _pcmData(nullptr)
|
2014-09-03 18:19:21 +08:00
|
|
|
, _bytesOfRead(0)
|
|
|
|
, _queBufferFrames(0)
|
|
|
|
, _queBufferBytes(0)
|
|
|
|
, _alBufferReady(false)
|
2015-07-28 15:27:07 +08:00
|
|
|
, _loadFail(false)
|
2014-11-26 09:53:52 +08:00
|
|
|
, _exitReadDataTask(false)
|
2014-09-03 18:19:21 +08:00
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
AudioCache::~AudioCache()
|
|
|
|
{
|
2014-09-11 10:53:01 +08:00
|
|
|
_exitReadDataTask = true;
|
2014-09-03 18:19:21 +08:00
|
|
|
if(_pcmData){
|
2014-09-11 10:53:01 +08:00
|
|
|
if (_alBufferReady){
|
2014-09-03 18:19:21 +08:00
|
|
|
alDeleteBuffers(1, &_alBufferId);
|
2014-09-11 10:53:01 +08:00
|
|
|
}
|
|
|
|
//wait for the 'readDataTask' task to exit
|
|
|
|
_readDataTaskMutex.lock();
|
|
|
|
_readDataTaskMutex.unlock();
|
2014-09-10 17:12:46 +08:00
|
|
|
|
2014-09-03 18:19:21 +08:00
|
|
|
free(_pcmData);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_queBufferFrames > 0) {
|
|
|
|
for (int index = 0; index < QUEUEBUFFER_NUM; ++index) {
|
|
|
|
free(_queBuffers[index]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-11 10:53:01 +08:00
|
|
|
void AudioCache::readDataTask()
|
2014-09-03 18:19:21 +08:00
|
|
|
{
|
2014-09-11 10:53:01 +08:00
|
|
|
_readDataTaskMutex.lock();
|
2014-09-03 18:19:21 +08:00
|
|
|
|
2014-09-10 17:12:46 +08:00
|
|
|
AudioStreamBasicDescription theFileFormat;
|
|
|
|
UInt32 thePropertySize = sizeof(theFileFormat);
|
2014-09-03 18:19:21 +08:00
|
|
|
|
|
|
|
SInt64 theFileLengthInFrames;
|
|
|
|
SInt64 readInFrames;
|
|
|
|
SInt64 dataSize;
|
|
|
|
SInt64 frames;
|
|
|
|
AudioBufferList theDataBuffer;
|
|
|
|
ExtAudioFileRef extRef = nullptr;
|
|
|
|
|
2015-03-31 07:24:48 +08:00
|
|
|
NSString *fileFullPath = [[NSString alloc] initWithCString:_fileFullPath.c_str() encoding:[NSString defaultCStringEncoding]];
|
|
|
|
auto fileURL = (CFURLRef)[[NSURL alloc] initFileURLWithPath:fileFullPath];
|
|
|
|
[fileFullPath release];
|
|
|
|
|
2014-09-03 18:19:21 +08:00
|
|
|
auto error = ExtAudioFileOpenURL(fileURL, &extRef);
|
|
|
|
if(error) {
|
2014-09-30 11:58:38 +08:00
|
|
|
printf("%s: ExtAudioFileOpenURL FAILED, Error = %ld\n", __PRETTY_FUNCTION__, (long)error);
|
2014-09-03 18:19:21 +08:00
|
|
|
goto ExitThread;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the audio data format
|
|
|
|
error = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileDataFormat, &thePropertySize, &theFileFormat);
|
|
|
|
if(error) {
|
2014-09-30 11:58:38 +08:00
|
|
|
printf("%s: ExtAudioFileGetProperty(kExtAudioFileProperty_FileDataFormat) FAILED, Error = %ld\n", __PRETTY_FUNCTION__, (long)error);
|
2014-09-03 18:19:21 +08:00
|
|
|
goto ExitThread;
|
|
|
|
}
|
|
|
|
if (theFileFormat.mChannelsPerFrame > 2) {
|
2014-09-11 10:53:01 +08:00
|
|
|
printf("%s: Unsupported Format, channel count is greater than stereo\n",__PRETTY_FUNCTION__);
|
2014-09-03 18:19:21 +08:00
|
|
|
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) {
|
2014-09-30 11:58:38 +08:00
|
|
|
printf("%s: ExtAudioFileSetProperty FAILED, Error = %ld\n", __PRETTY_FUNCTION__, (long)error);
|
2014-09-03 18:19:21 +08:00
|
|
|
goto ExitThread;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the total frame count
|
|
|
|
thePropertySize = sizeof(theFileLengthInFrames);
|
|
|
|
error = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileLengthFrames, &thePropertySize, &theFileLengthInFrames);
|
2014-09-10 17:12:46 +08:00
|
|
|
if(error) {
|
2014-09-30 11:58:38 +08:00
|
|
|
printf("%s: ExtAudioFileGetProperty(kExtAudioFileProperty_FileLengthFrames) FAILED, Error = %ld\n", __PRETTY_FUNCTION__, (long)error);
|
2014-09-10 17:12:46 +08:00
|
|
|
goto ExitThread;
|
|
|
|
}
|
2014-09-03 18:19:21 +08:00
|
|
|
|
|
|
|
_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) {
|
2014-09-11 10:53:01 +08:00
|
|
|
printf("%s: attaching audio to buffer fail: %x\n", __PRETTY_FUNCTION__, alError);
|
2014-09-10 17:12:46 +08:00
|
|
|
goto ExitThread;
|
2014-09-03 18:19:21 +08:00
|
|
|
}
|
|
|
|
alBufferDataStaticProc(_alBufferId, _format, _pcmData, _dataSize, _sampleRate);
|
|
|
|
|
2014-09-11 10:53:01 +08:00
|
|
|
readInFrames = theFileFormat.mSampleRate * QUEUEBUFFER_TIME_STEP * QUEUEBUFFER_NUM;
|
2014-09-03 18:19:21 +08:00
|
|
|
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;
|
2015-07-28 15:27:07 +08:00
|
|
|
invokingPlayCallbacks();
|
2014-09-03 18:19:21 +08:00
|
|
|
|
2014-09-11 10:53:01 +08:00
|
|
|
while (!_exitReadDataTask && _bytesOfRead + dataSize < _dataSize) {
|
2014-09-03 18:19:21 +08:00
|
|
|
theDataBuffer.mBuffers[0].mData = _pcmData + _bytesOfRead;
|
|
|
|
frames = readInFrames;
|
|
|
|
ExtAudioFileRead(extRef, (UInt32*)&frames, &theDataBuffer);
|
|
|
|
_bytesOfRead += dataSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
dataSize = _dataSize - _bytesOfRead;
|
2014-09-11 10:53:01 +08:00
|
|
|
if (!_exitReadDataTask && dataSize > 0) {
|
2014-09-03 18:19:21 +08:00
|
|
|
theDataBuffer.mBuffers[0].mDataByteSize = (UInt32)dataSize;
|
|
|
|
theDataBuffer.mBuffers[0].mData = _pcmData + _bytesOfRead;
|
|
|
|
frames = readInFrames;
|
|
|
|
ExtAudioFileRead(extRef, (UInt32*)&frames, &theDataBuffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
_bytesOfRead = _dataSize;
|
|
|
|
}
|
|
|
|
else{
|
2014-09-11 10:53:01 +08:00
|
|
|
_queBufferFrames = theFileFormat.mSampleRate * QUEUEBUFFER_TIME_STEP;
|
2014-09-03 18:19:21 +08:00
|
|
|
_queBufferBytes = _queBufferFrames * outputFormat.mBytesPerFrame;
|
|
|
|
|
2014-09-22 16:59:24 +08:00
|
|
|
theDataBuffer.mNumberBuffers = 1;
|
|
|
|
theDataBuffer.mBuffers[0].mNumberChannels = outputFormat.mChannelsPerFrame;
|
2014-09-03 18:19:21 +08:00
|
|
|
for (int index = 0; index < QUEUEBUFFER_NUM; ++index) {
|
|
|
|
_queBuffers[index] = (char*)malloc(_queBufferBytes);
|
|
|
|
|
2014-09-22 16:59:24 +08:00
|
|
|
theDataBuffer.mBuffers[0].mDataByteSize = _queBufferBytes;
|
|
|
|
theDataBuffer.mBuffers[0].mData = _queBuffers[index];
|
|
|
|
frames = _queBufferFrames;
|
|
|
|
ExtAudioFileRead(extRef, (UInt32*)&frames, &theDataBuffer);
|
|
|
|
|
|
|
|
_queBufferSize[index] = theDataBuffer.mBuffers[0].mDataByteSize;
|
2014-09-03 18:19:21 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ExitThread:
|
|
|
|
CFRelease(fileURL);
|
2014-09-10 17:12:46 +08:00
|
|
|
if (extRef)
|
|
|
|
ExtAudioFileDispose(extRef);
|
2014-09-03 18:19:21 +08:00
|
|
|
|
2014-09-11 10:53:01 +08:00
|
|
|
_readDataTaskMutex.unlock();
|
2014-09-10 17:12:46 +08:00
|
|
|
if (_queBufferFrames > 0)
|
2014-09-03 18:19:21 +08:00
|
|
|
_alBufferReady = true;
|
2015-07-28 15:27:07 +08:00
|
|
|
else
|
|
|
|
_loadFail = true;
|
2014-09-10 17:12:46 +08:00
|
|
|
|
2015-07-28 15:27:07 +08:00
|
|
|
invokingPlayCallbacks();
|
|
|
|
|
|
|
|
invokingLoadCallbacks();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AudioCache::addPlayCallback(const std::function<void()>& callback)
|
|
|
|
{
|
|
|
|
_callbackMutex.lock();
|
|
|
|
if (_alBufferReady) {
|
|
|
|
callback();
|
|
|
|
} else if(!_loadFail){
|
|
|
|
_callbacks.push_back(callback);
|
|
|
|
}
|
|
|
|
_callbackMutex.unlock();
|
2014-09-03 18:19:21 +08:00
|
|
|
}
|
|
|
|
|
2015-07-28 15:27:07 +08:00
|
|
|
void AudioCache::invokingPlayCallbacks()
|
2014-09-03 18:19:21 +08:00
|
|
|
{
|
|
|
|
_callbackMutex.lock();
|
|
|
|
auto count = _callbacks.size();
|
|
|
|
for (size_t index = 0; index < count; ++index) {
|
|
|
|
_callbacks[index]();
|
|
|
|
}
|
|
|
|
_callbacks.clear();
|
|
|
|
_callbackMutex.unlock();
|
|
|
|
}
|
|
|
|
|
2015-07-28 15:27:07 +08:00
|
|
|
void AudioCache::addLoadCallback(const std::function<void(bool)>& callback)
|
2014-09-03 18:19:21 +08:00
|
|
|
{
|
|
|
|
if (_alBufferReady) {
|
2015-07-28 15:27:07 +08:00
|
|
|
callback(true);
|
|
|
|
} else if(_loadFail){
|
|
|
|
callback(false);
|
2014-09-03 18:19:21 +08:00
|
|
|
}
|
2015-07-28 15:27:07 +08:00
|
|
|
else {
|
|
|
|
_loadCallbacks.push_back(callback);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AudioCache::invokingLoadCallbacks()
|
|
|
|
{
|
|
|
|
auto scheduler = Director::getInstance()->getScheduler();
|
|
|
|
scheduler->performFunctionInCocosThread([&](){
|
|
|
|
auto count = _loadCallbacks.size();
|
|
|
|
for (size_t index = 0; index < count; ++index) {
|
|
|
|
_loadCallbacks[index](_alBufferReady);
|
|
|
|
}
|
|
|
|
_loadCallbacks.clear();
|
|
|
|
});
|
2014-09-03 18:19:21 +08:00
|
|
|
}
|
2014-09-22 15:38:12 +08:00
|
|
|
|
|
|
|
#endif
|