mirror of https://github.com/axmolengine/axmol.git
Fixes crash that playing long audio and then uncacheAll (#17079)
* Fixes crash of playing long audio then uncacheAll. * update * Updates comments in MciPlayer.h * tab -> 4spaces * include path fix.
This commit is contained in:
parent
c15da89c5e
commit
be43c4210f
|
@ -398,6 +398,10 @@ xcopy /Y /Q "$(ProjectDir)..\..\external\chipmunk\prebuilt\win32\release-lib\*.*
|
||||||
<ClCompile Include="..\3d\CCTerrain.cpp" />
|
<ClCompile Include="..\3d\CCTerrain.cpp" />
|
||||||
<ClCompile Include="..\audio\AudioEngine.cpp" />
|
<ClCompile Include="..\audio\AudioEngine.cpp" />
|
||||||
<ClCompile Include="..\audio\win32\AudioCache.cpp" />
|
<ClCompile Include="..\audio\win32\AudioCache.cpp" />
|
||||||
|
<ClCompile Include="..\audio\win32\AudioDecoder.cpp" />
|
||||||
|
<ClCompile Include="..\audio\win32\AudioDecoderManager.cpp" />
|
||||||
|
<ClCompile Include="..\audio\win32\AudioDecoderMp3.cpp" />
|
||||||
|
<ClCompile Include="..\audio\win32\AudioDecoderOgg.cpp" />
|
||||||
<ClCompile Include="..\audio\win32\AudioEngine-win32.cpp" />
|
<ClCompile Include="..\audio\win32\AudioEngine-win32.cpp" />
|
||||||
<ClCompile Include="..\audio\win32\AudioPlayer.cpp" />
|
<ClCompile Include="..\audio\win32\AudioPlayer.cpp" />
|
||||||
<ClCompile Include="..\audio\win32\MciPlayer.cpp" />
|
<ClCompile Include="..\audio\win32\MciPlayer.cpp" />
|
||||||
|
@ -979,7 +983,12 @@ xcopy /Y /Q "$(ProjectDir)..\..\external\chipmunk\prebuilt\win32\release-lib\*.*
|
||||||
<ClInclude Include="..\audio\include\Export.h" />
|
<ClInclude Include="..\audio\include\Export.h" />
|
||||||
<ClInclude Include="..\audio\include\SimpleAudioEngine.h" />
|
<ClInclude Include="..\audio\include\SimpleAudioEngine.h" />
|
||||||
<ClInclude Include="..\audio\win32\AudioCache.h" />
|
<ClInclude Include="..\audio\win32\AudioCache.h" />
|
||||||
|
<ClInclude Include="..\audio\win32\AudioDecoder.h" />
|
||||||
|
<ClInclude Include="..\audio\win32\AudioDecoderManager.h" />
|
||||||
|
<ClInclude Include="..\audio\win32\AudioDecoderMp3.h" />
|
||||||
|
<ClInclude Include="..\audio\win32\AudioDecoderOgg.h" />
|
||||||
<ClInclude Include="..\audio\win32\AudioEngine-win32.h" />
|
<ClInclude Include="..\audio\win32\AudioEngine-win32.h" />
|
||||||
|
<ClInclude Include="..\audio\win32\AudioMacros.h" />
|
||||||
<ClInclude Include="..\audio\win32\AudioPlayer.h" />
|
<ClInclude Include="..\audio\win32\AudioPlayer.h" />
|
||||||
<ClInclude Include="..\audio\win32\MciPlayer.h" />
|
<ClInclude Include="..\audio\win32\MciPlayer.h" />
|
||||||
<ClInclude Include="..\base\allocator\CCAllocatorBase.h" />
|
<ClInclude Include="..\base\allocator\CCAllocatorBase.h" />
|
||||||
|
|
|
@ -1971,6 +1971,18 @@
|
||||||
<ClCompile Include="..\vr\CCVRGenericRenderer.cpp">
|
<ClCompile Include="..\vr\CCVRGenericRenderer.cpp">
|
||||||
<Filter>vr</Filter>
|
<Filter>vr</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\audio\win32\AudioDecoder.cpp">
|
||||||
|
<Filter>audioengine</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\audio\win32\AudioDecoderMp3.cpp">
|
||||||
|
<Filter>audioengine</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\audio\win32\AudioDecoderOgg.cpp">
|
||||||
|
<Filter>audioengine</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\audio\win32\AudioDecoderManager.cpp">
|
||||||
|
<Filter>audioengine</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\physics\CCPhysicsBody.h">
|
<ClInclude Include="..\physics\CCPhysicsBody.h">
|
||||||
|
@ -3851,6 +3863,21 @@
|
||||||
<ClInclude Include="..\vr\CCVRGenericRenderer.h">
|
<ClInclude Include="..\vr\CCVRGenericRenderer.h">
|
||||||
<Filter>vr</Filter>
|
<Filter>vr</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\audio\win32\AudioDecoder.h">
|
||||||
|
<Filter>audioengine</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\audio\win32\AudioMacros.h">
|
||||||
|
<Filter>audioengine</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\audio\win32\AudioDecoderMp3.h">
|
||||||
|
<Filter>audioengine</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\audio\win32\AudioDecoderOgg.h">
|
||||||
|
<Filter>audioengine</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\audio\win32\AudioDecoderManager.h">
|
||||||
|
<Filter>audioengine</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\math\Mat4.inl">
|
<None Include="..\math\Mat4.inl">
|
||||||
|
|
|
@ -70,17 +70,12 @@ AudioEngine::AudioEngineThreadPool* AudioEngine::s_threadPool = nullptr;
|
||||||
class AudioEngine::AudioEngineThreadPool
|
class AudioEngine::AudioEngineThreadPool
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AudioEngineThreadPool(bool detach, int threads = 4)
|
AudioEngineThreadPool(int threads = 4)
|
||||||
: _detach(detach)
|
: _stop(false)
|
||||||
, _stop(false)
|
|
||||||
{
|
{
|
||||||
for (int index = 0; index < threads; ++index)
|
for (int index = 0; index < threads; ++index)
|
||||||
{
|
{
|
||||||
_workers.emplace_back(std::thread(std::bind(&AudioEngineThreadPool::threadFunc, this)));
|
_workers.emplace_back(std::thread(std::bind(&AudioEngineThreadPool::threadFunc, this)));
|
||||||
if (_detach)
|
|
||||||
{
|
|
||||||
_workers[index].detach();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,13 +93,10 @@ public:
|
||||||
_taskCondition.notify_all();
|
_taskCondition.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_detach)
|
|
||||||
{
|
|
||||||
for (auto&& worker : _workers) {
|
for (auto&& worker : _workers) {
|
||||||
worker.join();
|
worker.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void threadFunc()
|
void threadFunc()
|
||||||
|
@ -138,7 +130,6 @@ private:
|
||||||
|
|
||||||
std::mutex _queueMutex;
|
std::mutex _queueMutex;
|
||||||
std::condition_variable _taskCondition;
|
std::condition_variable _taskCondition;
|
||||||
bool _detach;
|
|
||||||
bool _stop;
|
bool _stop;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -169,17 +160,10 @@ bool AudioEngine::lazyInit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
|
|
||||||
if (_audioEngineImpl && s_threadPool == nullptr)
|
if (_audioEngineImpl && s_threadPool == nullptr)
|
||||||
{
|
{
|
||||||
s_threadPool = new (std::nothrow) AudioEngineThreadPool(true);
|
s_threadPool = new (std::nothrow) AudioEngineThreadPool();
|
||||||
}
|
}
|
||||||
#elif CC_TARGET_PLATFORM != CC_PLATFORM_ANDROID
|
|
||||||
if (_audioEngineImpl && s_threadPool == nullptr)
|
|
||||||
{
|
|
||||||
s_threadPool = new (std::nothrow) AudioEngineThreadPool(false);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
Copyright (c) 2014 Chukong Technologies Inc.
|
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||||
|
|
||||||
http://www.cocos2d-x.org
|
http://www.cocos2d-x.org
|
||||||
|
|
||||||
|
@ -30,350 +30,350 @@
|
||||||
|
|
||||||
#include "audio/win32/AudioCache.h"
|
#include "audio/win32/AudioCache.h"
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <algorithm>
|
|
||||||
#include "vorbis/codec.h"
|
|
||||||
#include "vorbis/vorbisfile.h"
|
|
||||||
#include "platform/CCFileUtils.h"
|
|
||||||
#include "mpg123.h"
|
|
||||||
#include "base/CCDirector.h"
|
#include "base/CCDirector.h"
|
||||||
#include "base/CCScheduler.h"
|
#include "base/CCScheduler.h"
|
||||||
|
|
||||||
#include <windows.h>
|
#include "audio/win32/AudioDecoderManager.h"
|
||||||
|
#include "audio/win32/AudioDecoder.h"
|
||||||
|
|
||||||
#define PCMDATA_CACHEMAXSIZE 2621440
|
#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
|
||||||
|
|
||||||
|
using namespace cocos2d;
|
||||||
using namespace cocos2d::experimental;
|
using namespace cocos2d::experimental;
|
||||||
|
|
||||||
//FIXME: Move _winLog, winLog to a separated file
|
|
||||||
static void _winLog(const char *format, va_list args)
|
|
||||||
{
|
|
||||||
static const int MAX_LOG_LENGTH = 16 * 1024;
|
|
||||||
int bufferSize = MAX_LOG_LENGTH;
|
|
||||||
char* buf = nullptr;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
buf = new (std::nothrow) char[bufferSize];
|
|
||||||
if (buf == nullptr)
|
|
||||||
return; // not enough memory
|
|
||||||
|
|
||||||
int ret = vsnprintf(buf, bufferSize - 3, format, args);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
bufferSize *= 2;
|
|
||||||
|
|
||||||
delete[] buf;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
|
|
||||||
} while (true);
|
|
||||||
|
|
||||||
strcat(buf, "\n");
|
|
||||||
|
|
||||||
int pos = 0;
|
|
||||||
int len = strlen(buf);
|
|
||||||
char tempBuf[MAX_LOG_LENGTH + 1] = { 0 };
|
|
||||||
WCHAR wszBuf[MAX_LOG_LENGTH + 1] = { 0 };
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
std::copy(buf + pos, buf + pos + MAX_LOG_LENGTH, tempBuf);
|
|
||||||
|
|
||||||
tempBuf[MAX_LOG_LENGTH] = 0;
|
|
||||||
|
|
||||||
MultiByteToWideChar(CP_UTF8, 0, tempBuf, -1, wszBuf, sizeof(wszBuf));
|
|
||||||
OutputDebugStringW(wszBuf);
|
|
||||||
|
|
||||||
pos += MAX_LOG_LENGTH;
|
|
||||||
|
|
||||||
} while (pos < len);
|
|
||||||
|
|
||||||
delete[] buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
void audioLog(const char * format, ...)
|
|
||||||
{
|
|
||||||
va_list args;
|
|
||||||
va_start(args, format);
|
|
||||||
_winLog(format, args);
|
|
||||||
va_end(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioCache::AudioCache()
|
AudioCache::AudioCache()
|
||||||
: _pcmData(nullptr)
|
: _totalFrames(0)
|
||||||
, _pcmDataSize(0)
|
, _framesRead(0)
|
||||||
, _bytesOfRead(0)
|
, _format(-1)
|
||||||
, _alBufferReady(false)
|
, _duration(0.0f)
|
||||||
, _loadFail(false)
|
, _alBufferId(INVALID_AL_BUFFER_ID)
|
||||||
, _fileFormat(FileFormat::UNKNOWN)
|
, _pcmData(nullptr)
|
||||||
, _queBufferFrames(0)
|
, _queBufferFrames(0)
|
||||||
, _queBufferBytes(0)
|
, _state(State::INITIAL)
|
||||||
, _mp3Encoding(0)
|
, _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)
|
||||||
|
{
|
||||||
AudioCache::AudioCache(const AudioCache& cache)
|
_queBuffers[i] = nullptr;
|
||||||
{
|
_queBufferSize[i] = 0;
|
||||||
_pcmData = cache._pcmData;
|
}
|
||||||
_pcmDataSize = cache._pcmDataSize;
|
|
||||||
_bytesOfRead = cache._bytesOfRead;
|
|
||||||
_alBufferReady = cache._alBufferReady;
|
|
||||||
_fileFormat = cache._fileFormat;
|
|
||||||
_queBufferFrames = cache._queBufferFrames;
|
|
||||||
_queBufferBytes = cache._queBufferBytes;
|
|
||||||
_mp3Encoding = cache._mp3Encoding;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioCache::~AudioCache()
|
AudioCache::~AudioCache()
|
||||||
{
|
{
|
||||||
if(_pcmData){
|
ALOGVV("~AudioCache() %p, id=%u, begin", this, _id);
|
||||||
if (_alBufferReady){
|
*_isDestroyed = true;
|
||||||
alDeleteBuffers(1, &_alBufferId);
|
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
|
//wait for the 'readDataTask' task to exit
|
||||||
_readDataTaskMutex.lock();
|
_readDataTaskMutex.lock();
|
||||||
_readDataTaskMutex.unlock();
|
_readDataTaskMutex.unlock();
|
||||||
|
|
||||||
free(_pcmData);
|
if (_pcmData)
|
||||||
}
|
|
||||||
|
|
||||||
if (_queBufferFrames > 0) {
|
|
||||||
for (int index = 0; index < QUEUEBUFFER_NUM; ++index) {
|
|
||||||
free(_queBuffers[index]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioCache::readDataTask()
|
|
||||||
{
|
|
||||||
_readDataTaskMutex.lock();
|
|
||||||
|
|
||||||
OggVorbis_File* vf = nullptr;
|
|
||||||
mpg123_handle* mpg123handle = nullptr;
|
|
||||||
long totalFrames = 0;
|
|
||||||
|
|
||||||
switch (_fileFormat)
|
|
||||||
{
|
{
|
||||||
case FileFormat::OGG:
|
if (_state == State::READY)
|
||||||
{
|
{
|
||||||
vf = new OggVorbis_File;
|
if (_alBufferId != INVALID_AL_BUFFER_ID && alIsBuffer(_alBufferId))
|
||||||
int openCode;
|
|
||||||
if (openCode = ov_fopen(FileUtils::getInstance()->getSuitableFOpen(_fileFullPath).c_str(), vf)){
|
|
||||||
ALOGE("Input does not appear to be an Ogg bitstream: %s. Code: 0x%x\n", _fileFullPath.c_str(), openCode);
|
|
||||||
goto ExitThread;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto vi = ov_info(vf,-1);
|
|
||||||
totalFrames = (long)ov_pcm_total(vf,-1);
|
|
||||||
_bytesPerFrame = vi->channels * 2;
|
|
||||||
_alBufferFormat = (vi->channels > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
|
|
||||||
_sampleRate = vi->rate;
|
|
||||||
_pcmDataSize = totalFrames * _bytesPerFrame;
|
|
||||||
_duration = 1.0f * totalFrames / _sampleRate;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case FileFormat::MP3:
|
|
||||||
{
|
{
|
||||||
long rate = 0;
|
ALOGV("~AudioCache(id=%u), delete buffer: %u", _id, _alBufferId);
|
||||||
int error = MPG123_OK;
|
alDeleteBuffers(1, &_alBufferId);
|
||||||
mpg123handle = mpg123_new(nullptr, &error);
|
_alBufferId = INVALID_AL_BUFFER_ID;
|
||||||
if (!mpg123handle){
|
|
||||||
ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
|
|
||||||
goto ExitThread;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mpg123_open(mpg123handle,_fileFullPath.c_str()) != MPG123_OK ||
|
|
||||||
mpg123_getformat(mpg123handle, &rate, &_channels, &_mp3Encoding) != MPG123_OK) {
|
|
||||||
ALOGE("Trouble with mpg123: %s\n", mpg123_strerror(mpg123handle) );
|
|
||||||
goto ExitThread;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_mp3Encoding == MPG123_ENC_SIGNED_16){
|
|
||||||
_bytesPerFrame = 2 * _channels;
|
|
||||||
}
|
|
||||||
else if (_mp3Encoding == MPG123_ENC_FLOAT_32){
|
|
||||||
_bytesPerFrame = 4 * _channels;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
log("Bad encoding: 0x%x!\n", _mp3Encoding);
|
|
||||||
goto ExitThread;
|
|
||||||
}
|
|
||||||
|
|
||||||
_alBufferFormat = (_channels > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
|
|
||||||
_sampleRate = rate;
|
|
||||||
|
|
||||||
/* Ensure that this output format will not change (it could, when we allow it). */
|
|
||||||
mpg123_format_none(mpg123handle);
|
|
||||||
mpg123_format(mpg123handle, rate, _channels, _mp3Encoding);
|
|
||||||
/* Ensure that we can get accurate length by call mpg123_length */
|
|
||||||
mpg123_scan(mpg123handle);
|
|
||||||
|
|
||||||
auto framesLength = mpg123_length(mpg123handle);
|
|
||||||
totalFrames = framesLength;
|
|
||||||
_pcmDataSize = totalFrames * _bytesPerFrame;
|
|
||||||
_duration = 1.0f * totalFrames / _sampleRate;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case FileFormat::UNKNOWN:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_pcmDataSize <= PCMDATA_CACHEMAXSIZE)
|
|
||||||
{
|
|
||||||
_pcmData = malloc(_pcmDataSize);
|
|
||||||
auto alError = alGetError();
|
|
||||||
alGenBuffers(1, &_alBufferId);
|
|
||||||
alError = alGetError();
|
|
||||||
if (alError != AL_NO_ERROR) {
|
|
||||||
log("%s: attaching audio to buffer fail: %x\n", __FUNCTION__, alError);
|
|
||||||
goto ExitThread;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (_fileFormat)
|
|
||||||
{
|
|
||||||
case FileFormat::OGG:
|
|
||||||
{
|
|
||||||
int current_section;
|
|
||||||
unsigned int currPos = 0;
|
|
||||||
long readRet = 0;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
readRet = ov_read(vf,(char*)_pcmData + _bytesOfRead,4096,0,2,1,¤t_section);
|
|
||||||
if (readRet > 0){
|
|
||||||
_bytesOfRead += readRet;
|
|
||||||
}
|
|
||||||
} while (_bytesOfRead < _pcmDataSize);
|
|
||||||
|
|
||||||
_alBufferReady = true;
|
|
||||||
_bytesOfRead = _pcmDataSize;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FileFormat::MP3:
|
|
||||||
{
|
|
||||||
size_t done = 0;
|
|
||||||
auto err = mpg123_read(mpg123handle,(unsigned char*)_pcmData, _pcmDataSize,&done);
|
|
||||||
if (err == MPG123_ERR){
|
|
||||||
log("Trouble with mpg123: %s\n", mpg123_strerror(mpg123handle) );
|
|
||||||
goto ExitThread;
|
|
||||||
}
|
|
||||||
if (err == MPG123_DONE || err == MPG123_OK){
|
|
||||||
_alBufferReady = true;
|
|
||||||
_pcmDataSize = done;
|
|
||||||
_bytesOfRead = done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case FileFormat::UNKNOWN:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
alBufferData(_alBufferId,_alBufferFormat,_pcmData,_pcmDataSize,_sampleRate);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
_queBufferFrames = _sampleRate * QUEUEBUFFER_TIME_STEP;
|
|
||||||
_queBufferBytes = _queBufferFrames * _bytesPerFrame;
|
|
||||||
|
|
||||||
for (int index = 0; index < QUEUEBUFFER_NUM; ++index) {
|
|
||||||
_queBuffers[index] = (char*)malloc(_queBufferBytes);
|
|
||||||
|
|
||||||
switch (_fileFormat){
|
|
||||||
case FileFormat::MP3:
|
|
||||||
{
|
|
||||||
size_t done = 0;
|
|
||||||
mpg123_read(mpg123handle,(unsigned char*)_queBuffers[index], _queBufferBytes,&done);
|
|
||||||
_queBufferSize[index] = done;
|
|
||||||
_bytesOfRead += done;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case FileFormat::OGG:
|
|
||||||
{
|
|
||||||
int current_section;
|
|
||||||
auto readRet = ov_read(vf,_queBuffers[index],_queBufferBytes,0,2,1,¤t_section);
|
|
||||||
_queBufferSize[index] = readRet;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ExitThread:
|
|
||||||
switch (_fileFormat)
|
|
||||||
{
|
|
||||||
case FileFormat::OGG:
|
|
||||||
ov_clear(vf);
|
|
||||||
delete vf;
|
|
||||||
break;
|
|
||||||
case FileFormat::MP3:
|
|
||||||
mpg123_close(mpg123handle);
|
|
||||||
mpg123_delete(mpg123handle);
|
|
||||||
break;
|
|
||||||
case FileFormat::UNKNOWN:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
_readDataTaskMutex.unlock();
|
|
||||||
if (_queBufferFrames > 0)
|
|
||||||
{
|
|
||||||
_alBufferReady = true;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_loadFail = true;
|
ALOGW("AudioCache (%p), id=%u, buffer isn't ready, state=%d", this, _id, _state);
|
||||||
}
|
}
|
||||||
|
|
||||||
invokingLoadCallbacks();
|
free(_pcmData);
|
||||||
invokingPlayCallbacks();
|
}
|
||||||
|
|
||||||
|
if (_queBufferFrames > 0)
|
||||||
|
{
|
||||||
|
for (int index = 0; index < QUEUEBUFFER_NUM; ++index)
|
||||||
|
{
|
||||||
|
free(_queBuffers[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ALOGVV("~AudioCache() %p, id=%u, end", this, _id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioCache::invokingPlayCallbacks()
|
void AudioCache::readDataTask(unsigned int selfId)
|
||||||
{
|
{
|
||||||
_callbackMutex.lock();
|
//Note: It's in sub thread
|
||||||
auto count = _callbacks.size();
|
ALOGVV("readDataTask begin, cache id=%u", selfId);
|
||||||
for (size_t index = 0; index < count; ++index) {
|
|
||||||
_callbacks[index]();
|
_readDataTaskMutex.lock();
|
||||||
|
_state = State::LOADING;
|
||||||
|
|
||||||
|
AudioDecoder* decoder = AudioDecoderManager::createDecoder(_fileFullPath.c_str());
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (decoder == nullptr || !decoder->open(_fileFullPath.c_str()))
|
||||||
|
break;
|
||||||
|
|
||||||
|
const uint32_t originalTotalFrames = decoder->getTotalFrames();
|
||||||
|
const uint32_t bytesPerFrame = decoder->getBytesPerFrame();
|
||||||
|
const uint32_t sampleRate = decoder->getSampleRate();
|
||||||
|
const uint32_t channelCount = decoder->getChannelCount();
|
||||||
|
|
||||||
|
uint32_t totalFrames = originalTotalFrames;
|
||||||
|
uint32_t dataSize = totalFrames * bytesPerFrame;
|
||||||
|
uint32_t remainingFrames = totalFrames;
|
||||||
|
uint32_t adjustFrames = 0;
|
||||||
|
|
||||||
|
_format = channelCount > 1 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
|
||||||
|
_sampleRate = (ALsizei)sampleRate;
|
||||||
|
_duration = 1.0f * totalFrames / sampleRate;
|
||||||
|
_totalFrames = totalFrames;
|
||||||
|
|
||||||
|
if (dataSize <= PCMDATA_CACHEMAXSIZE)
|
||||||
|
{
|
||||||
|
uint32_t framesRead = 0;
|
||||||
|
const uint32_t framesToReadOnce = std::min(totalFrames, static_cast<uint32_t>(sampleRate * QUEUEBUFFER_TIME_STEP * QUEUEBUFFER_NUM));
|
||||||
|
|
||||||
|
std::vector<char> adjustFrameBuf;
|
||||||
|
|
||||||
|
if (decoder->seek(totalFrames))
|
||||||
|
{
|
||||||
|
char* tmpBuf = (char*)malloc(framesToReadOnce * bytesPerFrame);
|
||||||
|
adjustFrameBuf.reserve(framesToReadOnce * bytesPerFrame);
|
||||||
|
|
||||||
|
// Adjust total frames by setting position to the end of frames and try to read more data.
|
||||||
|
// This is a workaround for https://github.com/cocos2d/cocos2d-x/issues/16938
|
||||||
|
do
|
||||||
|
{
|
||||||
|
framesRead = decoder->read(framesToReadOnce, tmpBuf);
|
||||||
|
if (framesRead > 0)
|
||||||
|
{
|
||||||
|
adjustFrames += framesRead;
|
||||||
|
adjustFrameBuf.insert(adjustFrameBuf.end(), tmpBuf, tmpBuf + framesRead * bytesPerFrame);
|
||||||
}
|
}
|
||||||
_callbacks.clear();
|
|
||||||
_callbackMutex.unlock();
|
} while (framesRead > 0);
|
||||||
|
|
||||||
|
if (adjustFrames > 0)
|
||||||
|
{
|
||||||
|
ALOGV("Orignal total frames: %u, adjust frames: %u, current total frames: %u", totalFrames, adjustFrames, totalFrames + adjustFrames);
|
||||||
|
totalFrames += adjustFrames;
|
||||||
|
_totalFrames = remainingFrames = totalFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset dataSize
|
||||||
|
dataSize = totalFrames * bytesPerFrame;
|
||||||
|
|
||||||
|
free(tmpBuf);
|
||||||
|
}
|
||||||
|
// Reset to frame 0
|
||||||
|
BREAK_IF_ERR_LOG(!decoder->seek(0), "AudioDecoder::seek(0) failed!");
|
||||||
|
|
||||||
|
_pcmData = (char*)malloc(dataSize);
|
||||||
|
memset(_pcmData, 0x00, dataSize);
|
||||||
|
|
||||||
|
if (adjustFrames > 0)
|
||||||
|
{
|
||||||
|
memcpy(_pcmData + (dataSize - adjustFrameBuf.size()), adjustFrameBuf.data(), adjustFrameBuf.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
alGenBuffers(1, &_alBufferId);
|
||||||
|
auto alError = alGetError();
|
||||||
|
if (alError != AL_NO_ERROR) {
|
||||||
|
ALOGE("%s: attaching audio to buffer fail: %x", __FUNCTION__, alError);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*_isDestroyed)
|
||||||
|
break;
|
||||||
|
|
||||||
|
framesRead = decoder->readFixedFrames(std::min(framesToReadOnce, remainingFrames), _pcmData + _framesRead * bytesPerFrame);
|
||||||
|
_framesRead += framesRead;
|
||||||
|
remainingFrames -= framesRead;
|
||||||
|
|
||||||
|
if (*_isDestroyed)
|
||||||
|
break;
|
||||||
|
|
||||||
|
uint32_t frames = 0;
|
||||||
|
while (!*_isDestroyed && _framesRead < originalTotalFrames)
|
||||||
|
{
|
||||||
|
frames = std::min(framesToReadOnce, remainingFrames);
|
||||||
|
if (_framesRead + frames > originalTotalFrames)
|
||||||
|
{
|
||||||
|
frames = originalTotalFrames - _framesRead;
|
||||||
|
}
|
||||||
|
framesRead = decoder->read(frames, _pcmData + _framesRead * bytesPerFrame);
|
||||||
|
if (framesRead == 0)
|
||||||
|
break;
|
||||||
|
_framesRead += framesRead;
|
||||||
|
remainingFrames -= framesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*_isDestroyed)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (_framesRead < originalTotalFrames)
|
||||||
|
{
|
||||||
|
memset(_pcmData + _framesRead * bytesPerFrame, 0x00, (totalFrames - _framesRead) * bytesPerFrame);
|
||||||
|
}
|
||||||
|
ALOGV("pcm buffer was loaded successfully, total frames: %u, total read frames: %u, adjust frames: %u, remainingFrames: %u", totalFrames, _framesRead, adjustFrames, remainingFrames);
|
||||||
|
|
||||||
|
_framesRead += adjustFrames;
|
||||||
|
|
||||||
|
alBufferData(_alBufferId, _format, _pcmData, (ALsizei)dataSize, (ALsizei)sampleRate);
|
||||||
|
|
||||||
|
_state = State::READY;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_queBufferFrames = sampleRate * QUEUEBUFFER_TIME_STEP;
|
||||||
|
BREAK_IF_ERR_LOG(_queBufferFrames == 0, "_queBufferFrames == 0");
|
||||||
|
|
||||||
|
const uint32_t queBufferBytes = _queBufferFrames * bytesPerFrame;
|
||||||
|
|
||||||
|
for (int index = 0; index < QUEUEBUFFER_NUM; ++index)
|
||||||
|
{
|
||||||
|
_queBuffers[index] = (char*)malloc(queBufferBytes);
|
||||||
|
_queBufferSize[index] = queBufferBytes;
|
||||||
|
|
||||||
|
decoder->readFixedFrames(_queBufferFrames, _queBuffers[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
_state = State::READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (false);
|
||||||
|
|
||||||
|
if (decoder != nullptr)
|
||||||
|
{
|
||||||
|
decoder->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioDecoderManager::destroyDecoder(decoder);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//FIXME: Why to invoke play callback first? Should it be after 'load' callback?
|
||||||
|
invokingPlayCallbacks();
|
||||||
|
invokingLoadCallbacks();
|
||||||
|
|
||||||
|
_isLoadingFinished = true;
|
||||||
|
_readDataTaskMutex.unlock();
|
||||||
|
ALOGVV("readDataTask end, cache id=%u", selfId);
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
{
|
||||||
|
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();
|
callback();
|
||||||
} else {
|
break;
|
||||||
_callbacks.push_back(callback);
|
|
||||||
|
default:
|
||||||
|
ALOGE("Invalid state: %d", _state);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
_callbackMutex.unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioCache::invokingLoadCallbacks()
|
void AudioCache::invokingPlayCallbacks()
|
||||||
{
|
{
|
||||||
auto scheduler = Director::getInstance()->getScheduler();
|
std::lock_guard<std::mutex> lk(_playCallbackMutex);
|
||||||
scheduler->performFunctionInCocosThread([&](){
|
|
||||||
auto count = _loadCallbacks.size();
|
for (auto&& cb : _playCallbacks)
|
||||||
for (size_t index = 0; index < count; ++index) {
|
{
|
||||||
_loadCallbacks[index](_alBufferReady);
|
cb();
|
||||||
}
|
}
|
||||||
_loadCallbacks.clear();
|
|
||||||
});
|
_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);
|
{
|
||||||
}
|
case State::INITIAL:
|
||||||
else if (_loadFail){
|
case State::LOADING:
|
||||||
callback(false);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_loadCallbacks.push_back(callback);
|
_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([&, 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
Copyright (c) 2014 Chukong Technologies Inc.
|
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||||
|
|
||||||
http://www.cocos2d-x.org
|
http://www.cocos2d-x.org
|
||||||
|
|
||||||
|
@ -21,57 +21,20 @@
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#include "platform/CCPlatformConfig.h"
|
#include "platform/CCPlatformConfig.h"
|
||||||
|
|
||||||
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
|
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
|
||||||
|
|
||||||
#ifndef __AUDIO_CACHE_H_
|
|
||||||
#define __AUDIO_CACHE_H_
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#ifdef OPENAL_PLAIN_INCLUDES
|
#include <memory>
|
||||||
#include <al.h>
|
|
||||||
#else
|
|
||||||
#include <AL/al.h>
|
#include <AL/al.h>
|
||||||
#endif
|
|
||||||
#include "platform/CCPlatformMacros.h"
|
#include "platform/CCPlatformMacros.h"
|
||||||
|
#include "audio/apple/AudioMacros.h"
|
||||||
#define QUEUEBUFFER_NUM 5
|
|
||||||
#define QUEUEBUFFER_TIME_STEP 0.1f
|
|
||||||
|
|
||||||
// log, CCLOG aren't threadsafe, since we uses sub threads for parsing pcm data, threadsafe log output
|
|
||||||
// is needed. Define the following macros (ALOGV, ALOGD, ALOGI, ALOGW, ALOGE) for threadsafe log output.
|
|
||||||
|
|
||||||
//FIXME:Move the definition of the following macros to a separated file.
|
|
||||||
|
|
||||||
void audioLog(const char * format, ...);
|
|
||||||
|
|
||||||
#define QUOTEME_(x) #x
|
|
||||||
#define QUOTEME(x) QUOTEME_(x)
|
|
||||||
|
|
||||||
#if defined(COCOS2D_DEBUG) && COCOS2D_DEBUG > 0
|
|
||||||
#define ALOGV(fmt, ...) audioLog("V/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define ALOGV(fmt, ...) do {} while(false)
|
|
||||||
#endif
|
|
||||||
#define ALOGD(fmt, ...) audioLog("D/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
|
|
||||||
#define ALOGI(fmt, ...) audioLog("I/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
|
|
||||||
#define ALOGW(fmt, ...) audioLog("W/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
|
|
||||||
#define ALOGE(fmt, ...) audioLog("E/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__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{
|
||||||
|
@ -79,17 +42,19 @@ namespace experimental{
|
||||||
class AudioEngineImpl;
|
class AudioEngineImpl;
|
||||||
class AudioPlayer;
|
class AudioPlayer;
|
||||||
|
|
||||||
class CC_DLL AudioCache{
|
class CC_DLL AudioCache
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
enum class FileFormat
|
|
||||||
|
enum class State
|
||||||
{
|
{
|
||||||
UNKNOWN,
|
INITIAL,
|
||||||
OGG,
|
LOADING,
|
||||||
MP3
|
READY,
|
||||||
|
FAILED
|
||||||
};
|
};
|
||||||
|
|
||||||
AudioCache();
|
AudioCache();
|
||||||
AudioCache(const AudioCache&);
|
|
||||||
~AudioCache();
|
~AudioCache();
|
||||||
|
|
||||||
void addPlayCallback(const std::function<void()>& callback);
|
void addPlayCallback(const std::function<void()>& callback);
|
||||||
|
@ -97,53 +62,54 @@ 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();
|
||||||
|
|
||||||
void invokingLoadCallbacks();
|
void invokingLoadCallbacks();
|
||||||
|
|
||||||
std::string _fileFullPath;
|
|
||||||
FileFormat _fileFormat;
|
|
||||||
//pcm data related stuff
|
//pcm data related stuff
|
||||||
size_t _pcmDataSize;
|
ALenum _format;
|
||||||
ALenum _alBufferFormat;
|
ALsizei _sampleRate;
|
||||||
|
|
||||||
int _channels;
|
|
||||||
ALuint _sampleRate;
|
|
||||||
size_t _bytesPerFrame;
|
|
||||||
float _duration;
|
float _duration;
|
||||||
|
uint32_t _totalFrames;
|
||||||
|
uint32_t _framesRead;
|
||||||
|
|
||||||
/*Cache related stuff;
|
/*Cache related stuff;
|
||||||
* Cache pcm data when sizeInBytes less than PCMDATA_CACHEMAXSIZE
|
* Cache pcm data when sizeInBytes less than PCMDATA_CACHEMAXSIZE
|
||||||
*/
|
*/
|
||||||
ALuint _alBufferId;
|
ALuint _alBufferId;
|
||||||
void* _pcmData;
|
char* _pcmData;
|
||||||
size_t _bytesOfRead;
|
|
||||||
|
|
||||||
/*Queue buffer related stuff
|
/*Queue buffer related stuff
|
||||||
* Streaming in OpenAL when sizeInBytes greater then PCMDATA_CACHEMAXSIZE
|
* Streaming in OpenAL when sizeInBytes greater then PCMDATA_CACHEMAXSIZE
|
||||||
*/
|
*/
|
||||||
char* _queBuffers[QUEUEBUFFER_NUM];
|
char* _queBuffers[QUEUEBUFFER_NUM];
|
||||||
ALsizei _queBufferSize[QUEUEBUFFER_NUM];
|
ALsizei _queBufferSize[QUEUEBUFFER_NUM];
|
||||||
int _queBufferFrames;
|
uint32_t _queBufferFrames;
|
||||||
int _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;
|
||||||
|
|
||||||
int _mp3Encoding;
|
State _state;
|
||||||
|
|
||||||
|
std::shared_ptr<bool> _isDestroyed;
|
||||||
|
std::string _fileFullPath;
|
||||||
|
unsigned int _id;
|
||||||
|
bool _isLoadingFinished;
|
||||||
|
bool _isSkipReadDataTask;
|
||||||
|
|
||||||
friend class AudioEngineImpl;
|
friend class AudioEngineImpl;
|
||||||
friend class AudioPlayer;
|
friend class AudioPlayer;
|
||||||
} ;
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
NS_CC_END
|
NS_CC_END
|
||||||
|
|
||||||
#endif // __AUDIO_CACHE_H_
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
/****************************************************************************
|
||||||
|
Copyright (c) 2016 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.
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "audio/win32/AudioDecoder.h"
|
||||||
|
#include "audio/win32/AudioMacros.h"
|
||||||
|
#include "platform/CCFileUtils.h"
|
||||||
|
|
||||||
|
#define LOG_TAG "AudioDecoder"
|
||||||
|
|
||||||
|
namespace cocos2d { namespace experimental {
|
||||||
|
|
||||||
|
AudioDecoder::AudioDecoder()
|
||||||
|
: _isOpened(false)
|
||||||
|
, _totalFrames(0)
|
||||||
|
, _bytesPerFrame(0)
|
||||||
|
, _sampleRate(0)
|
||||||
|
, _channelCount(0)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioDecoder::~AudioDecoder()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool AudioDecoder::isOpened() const
|
||||||
|
{
|
||||||
|
return _isOpened;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AudioDecoder::readFixedFrames(uint32_t framesToRead, char* pcmBuf)
|
||||||
|
{
|
||||||
|
uint32_t framesRead = 0;
|
||||||
|
uint32_t framesReadOnce = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
framesReadOnce = read(framesToRead - framesRead, pcmBuf + framesRead * _bytesPerFrame);
|
||||||
|
framesRead += framesReadOnce;
|
||||||
|
} while (framesReadOnce != 0 && framesRead < framesToRead);
|
||||||
|
|
||||||
|
if (framesRead < framesToRead)
|
||||||
|
{
|
||||||
|
memset(pcmBuf + framesRead * _bytesPerFrame, 0x00, (framesToRead - framesRead) * _bytesPerFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
return framesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AudioDecoder::getTotalFrames() const
|
||||||
|
{
|
||||||
|
return _totalFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AudioDecoder::getBytesPerFrame() const
|
||||||
|
{
|
||||||
|
return _bytesPerFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AudioDecoder::getSampleRate() const
|
||||||
|
{
|
||||||
|
return _sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AudioDecoder::getChannelCount() const
|
||||||
|
{
|
||||||
|
return _channelCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace cocos2d { namespace experimental {
|
|
@ -0,0 +1,119 @@
|
||||||
|
/****************************************************************************
|
||||||
|
Copyright (c) 2016 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.
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "vorbis/vorbisfile.h"
|
||||||
|
|
||||||
|
namespace cocos2d { namespace experimental {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The class for decoding compressed audio file to PCM buffer.
|
||||||
|
*/
|
||||||
|
class AudioDecoder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const uint32_t INVALID_FRAME_INDEX = UINT32_MAX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Opens an audio file specified by a file path.
|
||||||
|
* @return true if succeed, otherwise false.
|
||||||
|
*/
|
||||||
|
virtual bool open(const char* path) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether decoder has opened file successfully.
|
||||||
|
* @return true if succeed, otherwise false.
|
||||||
|
*/
|
||||||
|
virtual bool isOpened() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Closes opened audio file.
|
||||||
|
* @note The method will also be automatically invoked in the destructor.
|
||||||
|
*/
|
||||||
|
virtual void close() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reads audio frames of PCM format.
|
||||||
|
* @param framesToRead The number of frames excepted to be read.
|
||||||
|
* @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame.
|
||||||
|
* @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end of file.
|
||||||
|
*/
|
||||||
|
virtual uint32_t read(uint32_t framesToRead, char* pcmBuf) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reads fixed audio frames of PCM format.
|
||||||
|
* @param framesToRead The number of frames excepted to be read.
|
||||||
|
* @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame.
|
||||||
|
* @return The number of frames actually read, it's probably less than |framesToRead|. Returns 0 means reach the end of file.
|
||||||
|
* @note The different between |read| and |readFixedFrames| is |readFixedFrames| will do multiple reading operations if |framesToRead| frames
|
||||||
|
* isn't filled entirely, while |read| just does reading operation once whatever |framesToRead| is or isn't filled entirely.
|
||||||
|
* If current position reaches the end of frames, the return value may smaller than |framesToRead| and the remaining
|
||||||
|
* buffer in |pcmBuf| will be set with silence data (0x00).
|
||||||
|
*/
|
||||||
|
virtual uint32_t readFixedFrames(uint32_t framesToRead, char* pcmBuf);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets frame offest to be read.
|
||||||
|
* @param frameOffset The frame offest to be set.
|
||||||
|
* @return true if succeed, otherwise false
|
||||||
|
*/
|
||||||
|
virtual bool seek(uint32_t frameOffset) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tells the current frame offset.
|
||||||
|
* @return The current frame offset.
|
||||||
|
*/
|
||||||
|
virtual uint32_t tell() const = 0;
|
||||||
|
|
||||||
|
/** Gets total frames of current audio.*/
|
||||||
|
virtual uint32_t getTotalFrames() const;
|
||||||
|
|
||||||
|
/** Gets bytes per frame of current audio.*/
|
||||||
|
virtual uint32_t getBytesPerFrame() const;
|
||||||
|
|
||||||
|
/** Gets sample rate of current audio.*/
|
||||||
|
virtual uint32_t getSampleRate() const;
|
||||||
|
|
||||||
|
/** Gets the channel count of current audio.
|
||||||
|
* @note Currently we only support 1 or 2 channels.
|
||||||
|
*/
|
||||||
|
virtual uint32_t getChannelCount() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AudioDecoder();
|
||||||
|
virtual ~AudioDecoder();
|
||||||
|
|
||||||
|
bool _isOpened;
|
||||||
|
uint32_t _totalFrames;
|
||||||
|
uint32_t _bytesPerFrame;
|
||||||
|
uint32_t _sampleRate;
|
||||||
|
uint32_t _channelCount;
|
||||||
|
|
||||||
|
friend class AudioDecoderManager;
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespace cocos2d { namespace experimental {
|
|
@ -0,0 +1,70 @@
|
||||||
|
/****************************************************************************
|
||||||
|
Copyright (c) 2016 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.
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#define LOG_TAG "AudioDecoderManager"
|
||||||
|
|
||||||
|
#include "audio/win32/AudioDecoderManager.h"
|
||||||
|
#include "audio/win32/AudioDecoderOgg.h"
|
||||||
|
#include "audio/win32/AudioDecoderMp3.h"
|
||||||
|
#include "audio/win32/AudioMacros.h"
|
||||||
|
#include "platform/CCFileUtils.h"
|
||||||
|
#include "base/CCConsole.h"
|
||||||
|
#include "mpg123.h"
|
||||||
|
|
||||||
|
namespace cocos2d { namespace experimental {
|
||||||
|
|
||||||
|
static bool __mp3Inited = false;
|
||||||
|
|
||||||
|
bool AudioDecoderManager::init()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDecoderManager::destroy()
|
||||||
|
{
|
||||||
|
AudioDecoderMp3::destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioDecoder* AudioDecoderManager::createDecoder(const char* path)
|
||||||
|
{
|
||||||
|
std::string suffix = FileUtils::getInstance()->getFileExtension(path);
|
||||||
|
if (suffix == ".ogg")
|
||||||
|
{
|
||||||
|
return new (std::nothrow) AudioDecoderOgg();
|
||||||
|
}
|
||||||
|
else if (suffix == ".mp3")
|
||||||
|
{
|
||||||
|
return new (std::nothrow) AudioDecoderMp3();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDecoderManager::destroyDecoder(AudioDecoder* decoder)
|
||||||
|
{
|
||||||
|
delete decoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace cocos2d { namespace experimental {
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/****************************************************************************
|
||||||
|
Copyright (c) 2016 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.
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace cocos2d { namespace experimental {
|
||||||
|
|
||||||
|
class AudioDecoder;
|
||||||
|
|
||||||
|
class AudioDecoderManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static bool init();
|
||||||
|
static void destroy();
|
||||||
|
static AudioDecoder* createDecoder(const char* path);
|
||||||
|
static void destroyDecoder(AudioDecoder* decoder);
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespace cocos2d { namespace experimental {
|
||||||
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
/****************************************************************************
|
||||||
|
Copyright (c) 2016 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.
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "audio/win32/AudioDecoderMp3.h"
|
||||||
|
#include "audio/win32/AudioMacros.h"
|
||||||
|
#include "platform/CCFileUtils.h"
|
||||||
|
|
||||||
|
#include "base/CCConsole.h"
|
||||||
|
#include "mpg123.h"
|
||||||
|
|
||||||
|
#define LOG_TAG "AudioDecoderMp3"
|
||||||
|
|
||||||
|
namespace cocos2d { namespace experimental {
|
||||||
|
|
||||||
|
static bool __mp3Inited = false;
|
||||||
|
|
||||||
|
bool AudioDecoderMp3::lazyInit()
|
||||||
|
{
|
||||||
|
bool ret = true;
|
||||||
|
if (!__mp3Inited)
|
||||||
|
{
|
||||||
|
int error = mpg123_init();
|
||||||
|
if (error == MPG123_OK)
|
||||||
|
{
|
||||||
|
__mp3Inited = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDecoderMp3::destroy()
|
||||||
|
{
|
||||||
|
if (__mp3Inited)
|
||||||
|
{
|
||||||
|
mpg123_exit();
|
||||||
|
__mp3Inited = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioDecoderMp3::AudioDecoderMp3()
|
||||||
|
: _mpg123handle(nullptr)
|
||||||
|
{
|
||||||
|
lazyInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioDecoderMp3::~AudioDecoderMp3()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioDecoderMp3::open(const char* path)
|
||||||
|
{
|
||||||
|
std::string fullPath = FileUtils::getInstance()->fullPathForFilename(path);
|
||||||
|
|
||||||
|
long rate = 0;
|
||||||
|
int error = MPG123_OK;
|
||||||
|
int mp3Encoding = 0;
|
||||||
|
int channel = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
_mpg123handle = mpg123_new(nullptr, &error);
|
||||||
|
if (nullptr == _mpg123handle)
|
||||||
|
{
|
||||||
|
ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mpg123_open(_mpg123handle, fullPath.c_str()) != MPG123_OK
|
||||||
|
|| mpg123_getformat(_mpg123handle, &rate, &channel, &mp3Encoding) != MPG123_OK)
|
||||||
|
{
|
||||||
|
ALOGE("Trouble with mpg123: %s\n", mpg123_strerror(_mpg123handle) );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_channelCount = channel;
|
||||||
|
_sampleRate = rate;
|
||||||
|
|
||||||
|
if (mp3Encoding == MPG123_ENC_SIGNED_16)
|
||||||
|
{
|
||||||
|
_bytesPerFrame = 2 * _channelCount;
|
||||||
|
}
|
||||||
|
else if (mp3Encoding == MPG123_ENC_FLOAT_32)
|
||||||
|
{
|
||||||
|
_bytesPerFrame = 4 * _channelCount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ALOGE("Bad encoding: 0x%x!\n", mp3Encoding);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure that this output format will not change (it could, when we allow it). */
|
||||||
|
mpg123_format_none(_mpg123handle);
|
||||||
|
mpg123_format(_mpg123handle, rate, channel, mp3Encoding);
|
||||||
|
/* Ensure that we can get accurate length by call mpg123_length */
|
||||||
|
mpg123_scan(_mpg123handle);
|
||||||
|
|
||||||
|
_totalFrames = mpg123_length(_mpg123handle);
|
||||||
|
|
||||||
|
_isOpened = true;
|
||||||
|
return true;
|
||||||
|
} while (false);
|
||||||
|
|
||||||
|
if (_mpg123handle != nullptr)
|
||||||
|
{
|
||||||
|
mpg123_close(_mpg123handle);
|
||||||
|
mpg123_delete(_mpg123handle);
|
||||||
|
_mpg123handle = nullptr;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDecoderMp3::close()
|
||||||
|
{
|
||||||
|
if (isOpened())
|
||||||
|
{
|
||||||
|
if (_mpg123handle != nullptr)
|
||||||
|
{
|
||||||
|
mpg123_close(_mpg123handle);
|
||||||
|
mpg123_delete(_mpg123handle);
|
||||||
|
_mpg123handle = nullptr;
|
||||||
|
}
|
||||||
|
_isOpened = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AudioDecoderMp3::read(uint32_t framesToRead, char* pcmBuf)
|
||||||
|
{
|
||||||
|
int bytesToRead = framesToRead * _bytesPerFrame;
|
||||||
|
size_t bytesRead = 0;
|
||||||
|
int err = mpg123_read(_mpg123handle, (unsigned char*)pcmBuf, bytesToRead, &bytesRead);
|
||||||
|
if (err == MPG123_ERR)
|
||||||
|
{
|
||||||
|
ALOGE("Trouble with mpg123: %s\n", mpg123_strerror(_mpg123handle) );
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<uint32_t>(bytesRead / _bytesPerFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioDecoderMp3::seek(uint32_t frameOffset)
|
||||||
|
{
|
||||||
|
off_t offset = mpg123_seek(_mpg123handle, frameOffset, SEEK_SET);
|
||||||
|
//ALOGD("mpg123_seek return: %d", (int)offset);
|
||||||
|
if (offset >= 0 && offset == frameOffset)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AudioDecoderMp3::tell() const
|
||||||
|
{
|
||||||
|
return static_cast<uint32_t>(mpg123_tell(_mpg123handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace cocos2d { namespace experimental {
|
|
@ -0,0 +1,85 @@
|
||||||
|
/****************************************************************************
|
||||||
|
Copyright (c) 2016 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.
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "audio/win32/AudioDecoder.h"
|
||||||
|
|
||||||
|
struct mpg123_handle_struct;
|
||||||
|
|
||||||
|
namespace cocos2d { namespace experimental {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The class for decoding compressed audio file to PCM buffer.
|
||||||
|
*/
|
||||||
|
class AudioDecoderMp3 : public AudioDecoder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Opens an audio file specified by a file path.
|
||||||
|
* @return true if succeed, otherwise false.
|
||||||
|
*/
|
||||||
|
virtual bool open(const char* path) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Closes opened audio file.
|
||||||
|
* @note The method will also be automatically invoked in the destructor.
|
||||||
|
*/
|
||||||
|
virtual void close() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reads audio frames of PCM format.
|
||||||
|
* @param framesToRead The number of frames excepted to be read.
|
||||||
|
* @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame.
|
||||||
|
* @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end of file.
|
||||||
|
*/
|
||||||
|
virtual uint32_t read(uint32_t framesToRead, char* pcmBuf) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets frame offest to be read.
|
||||||
|
* @param frameOffset The frame offest to be set.
|
||||||
|
* @return true if succeed, otherwise false
|
||||||
|
*/
|
||||||
|
virtual bool seek(uint32_t frameOffset) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tells the current frame offset.
|
||||||
|
* @return The current frame offset.
|
||||||
|
*/
|
||||||
|
virtual uint32_t tell() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
AudioDecoderMp3();
|
||||||
|
~AudioDecoderMp3();
|
||||||
|
|
||||||
|
static bool lazyInit();
|
||||||
|
static void destroy();
|
||||||
|
|
||||||
|
struct mpg123_handle_struct* _mpg123handle;
|
||||||
|
|
||||||
|
friend class AudioDecoderManager;
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespace cocos2d { namespace experimental {
|
|
@ -0,0 +1,86 @@
|
||||||
|
/****************************************************************************
|
||||||
|
Copyright (c) 2016 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.
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "audio/win32/AudioDecoderOgg.h"
|
||||||
|
#include "audio/win32/AudioMacros.h"
|
||||||
|
#include "platform/CCFileUtils.h"
|
||||||
|
|
||||||
|
#define LOG_TAG "AudioDecoderOgg"
|
||||||
|
|
||||||
|
namespace cocos2d { namespace experimental {
|
||||||
|
|
||||||
|
AudioDecoderOgg::AudioDecoderOgg()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioDecoderOgg::~AudioDecoderOgg()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioDecoderOgg::open(const char* path)
|
||||||
|
{
|
||||||
|
std::string fullPath = FileUtils::getInstance()->fullPathForFilename(path);
|
||||||
|
if (0 == ov_fopen(fullPath.c_str(), &_vf))
|
||||||
|
{
|
||||||
|
// header
|
||||||
|
vorbis_info* vi = ov_info(&_vf, -1);
|
||||||
|
_sampleRate = static_cast<uint32_t>(vi->rate);
|
||||||
|
_channelCount = vi->channels;
|
||||||
|
_bytesPerFrame = vi->channels * sizeof(short);
|
||||||
|
_totalFrames = static_cast<uint32_t>(ov_pcm_total(&_vf, -1));
|
||||||
|
_isOpened = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDecoderOgg::close()
|
||||||
|
{
|
||||||
|
if (isOpened())
|
||||||
|
{
|
||||||
|
ov_clear(&_vf);
|
||||||
|
_isOpened = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AudioDecoderOgg::read(uint32_t framesToRead, char* pcmBuf)
|
||||||
|
{
|
||||||
|
int currentSection = 0;
|
||||||
|
int bytesToRead = framesToRead * _bytesPerFrame;
|
||||||
|
long bytesRead = ov_read(&_vf, pcmBuf, bytesToRead, 0, 2, 1, ¤tSection);
|
||||||
|
return static_cast<uint32_t>(bytesRead / _bytesPerFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioDecoderOgg::seek(uint32_t frameOffset)
|
||||||
|
{
|
||||||
|
return 0 == ov_pcm_seek(&_vf, frameOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AudioDecoderOgg::tell() const
|
||||||
|
{
|
||||||
|
return static_cast<uint32_t>(ov_pcm_tell(const_cast<OggVorbis_File*>(&_vf)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace cocos2d { namespace experimental {
|
|
@ -0,0 +1,81 @@
|
||||||
|
/****************************************************************************
|
||||||
|
Copyright (c) 2016 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.
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "audio/win32/AudioDecoder.h"
|
||||||
|
|
||||||
|
#include "vorbis/vorbisfile.h"
|
||||||
|
|
||||||
|
namespace cocos2d { namespace experimental {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The class for decoding compressed audio file to PCM buffer.
|
||||||
|
*/
|
||||||
|
class AudioDecoderOgg : public AudioDecoder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Opens an audio file specified by a file path.
|
||||||
|
* @return true if succeed, otherwise false.
|
||||||
|
*/
|
||||||
|
virtual bool open(const char* path) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Closes opened audio file.
|
||||||
|
* @note The method will also be automatically invoked in the destructor.
|
||||||
|
*/
|
||||||
|
virtual void close() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reads audio frames of PCM format.
|
||||||
|
* @param framesToRead The number of frames excepted to be read.
|
||||||
|
* @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame.
|
||||||
|
* @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end of file.
|
||||||
|
*/
|
||||||
|
virtual uint32_t read(uint32_t framesToRead, char* pcmBuf) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets frame offest to be read.
|
||||||
|
* @param frameOffset The frame offest to be set.
|
||||||
|
* @return true if succeed, otherwise false
|
||||||
|
*/
|
||||||
|
virtual bool seek(uint32_t frameOffset) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tells the current frame offset.
|
||||||
|
* @return The current frame offset.
|
||||||
|
*/
|
||||||
|
virtual uint32_t tell() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AudioDecoderOgg();
|
||||||
|
~AudioDecoderOgg();
|
||||||
|
|
||||||
|
OggVorbis_File _vf;
|
||||||
|
|
||||||
|
friend class AudioDecoderManager;
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespace cocos2d { namespace experimental {
|
|
@ -1,5 +1,5 @@
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
Copyright (c) 2014 Chukong Technologies Inc.
|
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||||
|
|
||||||
http://www.cocos2d-x.org
|
http://www.cocos2d-x.org
|
||||||
|
|
||||||
|
@ -40,14 +40,74 @@
|
||||||
#include "base/CCDirector.h"
|
#include "base/CCDirector.h"
|
||||||
#include "base/CCScheduler.h"
|
#include "base/CCScheduler.h"
|
||||||
#include "platform/CCFileUtils.h"
|
#include "platform/CCFileUtils.h"
|
||||||
#include "mpg123.h"
|
#include "audio/win32/AudioDecoderManager.h"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
// log, CCLOG aren't threadsafe, since we uses sub threads for parsing pcm data, threadsafe log output
|
||||||
|
// is needed. Define the following macros (ALOGV, ALOGD, ALOGI, ALOGW, ALOGE) for threadsafe log output.
|
||||||
|
|
||||||
|
//FIXME: Move _winLog, winLog to a separated file
|
||||||
|
static void _winLog(const char *format, va_list args)
|
||||||
|
{
|
||||||
|
static const int MAX_LOG_LENGTH = 16 * 1024;
|
||||||
|
int bufferSize = MAX_LOG_LENGTH;
|
||||||
|
char* buf = nullptr;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
buf = new (std::nothrow) char[bufferSize];
|
||||||
|
if (buf == nullptr)
|
||||||
|
return; // not enough memory
|
||||||
|
|
||||||
|
int ret = vsnprintf(buf, bufferSize - 3, format, args);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
bufferSize *= 2;
|
||||||
|
|
||||||
|
delete[] buf;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
strcat(buf, "\n");
|
||||||
|
|
||||||
|
int pos = 0;
|
||||||
|
int len = strlen(buf);
|
||||||
|
char tempBuf[MAX_LOG_LENGTH + 1] = { 0 };
|
||||||
|
WCHAR wszBuf[MAX_LOG_LENGTH + 1] = { 0 };
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
std::copy(buf + pos, buf + pos + MAX_LOG_LENGTH, tempBuf);
|
||||||
|
|
||||||
|
tempBuf[MAX_LOG_LENGTH] = 0;
|
||||||
|
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, tempBuf, -1, wszBuf, sizeof(wszBuf));
|
||||||
|
OutputDebugStringW(wszBuf);
|
||||||
|
|
||||||
|
pos += MAX_LOG_LENGTH;
|
||||||
|
|
||||||
|
} while (pos < len);
|
||||||
|
|
||||||
|
delete[] buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void audioLog(const char * format, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
_winLog(format, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
using namespace cocos2d;
|
using namespace cocos2d;
|
||||||
using namespace cocos2d::experimental;
|
using namespace cocos2d::experimental;
|
||||||
|
|
||||||
static ALCdevice *s_ALDevice = nullptr;
|
static ALCdevice *s_ALDevice = nullptr;
|
||||||
static ALCcontext *s_ALContext = nullptr;
|
static ALCcontext *s_ALContext = nullptr;
|
||||||
static bool MPG123_LAZYINIT = true;
|
|
||||||
|
|
||||||
AudioEngineImpl::AudioEngineImpl()
|
AudioEngineImpl::AudioEngineImpl()
|
||||||
: _lazyInitLoop(true)
|
: _lazyInitLoop(true)
|
||||||
|
@ -67,30 +127,31 @@ AudioEngineImpl::~AudioEngineImpl()
|
||||||
alcDestroyContext(s_ALContext);
|
alcDestroyContext(s_ALContext);
|
||||||
s_ALContext = nullptr;
|
s_ALContext = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_ALDevice) {
|
if (s_ALDevice) {
|
||||||
alcCloseDevice(s_ALDevice);
|
alcCloseDevice(s_ALDevice);
|
||||||
s_ALDevice = nullptr;
|
s_ALDevice = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
mpg123_exit();
|
AudioDecoderManager::destroy();
|
||||||
MPG123_LAZYINIT = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioEngineImpl::init()
|
bool AudioEngineImpl::init()
|
||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
do{
|
do{
|
||||||
s_ALDevice = alcOpenDevice(NULL);
|
s_ALDevice = alcOpenDevice(nullptr);
|
||||||
|
|
||||||
if (s_ALDevice) {
|
if (s_ALDevice) {
|
||||||
auto alError = alGetError();
|
auto alError = alGetError();
|
||||||
s_ALContext = alcCreateContext(s_ALDevice, NULL);
|
s_ALContext = alcCreateContext(s_ALDevice, nullptr);
|
||||||
alcMakeContextCurrent(s_ALContext);
|
alcMakeContextCurrent(s_ALContext);
|
||||||
|
|
||||||
alGenSources(MAX_AUDIOINSTANCES, _alSources);
|
alGenSources(MAX_AUDIOINSTANCES, _alSources);
|
||||||
alError = alGetError();
|
alError = alGetError();
|
||||||
if(alError != AL_NO_ERROR){
|
if(alError != AL_NO_ERROR)
|
||||||
ALOGE("%s:generating sources fail! error = %x\n", __FUNCTION__, alError);
|
{
|
||||||
|
ALOGE("%s:generating sources failed! error = %x\n", __FUNCTION__, alError);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +159,9 @@ bool AudioEngineImpl::init()
|
||||||
_alSourceUsed[_alSources[i]] = false;
|
_alSourceUsed[_alSources[i]] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = true;
|
_scheduler = Director::getInstance()->getScheduler();
|
||||||
|
ret = AudioDecoderManager::init();
|
||||||
|
ALOGI("OpenAL was initialized successfully!");
|
||||||
}
|
}
|
||||||
}while (false);
|
}while (false);
|
||||||
|
|
||||||
|
@ -109,106 +172,80 @@ AudioCache* AudioEngineImpl::preload(const std::string& filePath, std::function<
|
||||||
{
|
{
|
||||||
AudioCache* audioCache = nullptr;
|
AudioCache* audioCache = nullptr;
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
auto it = _audioCaches.find(filePath);
|
auto it = _audioCaches.find(filePath);
|
||||||
if (it != _audioCaches.end())
|
if (it == _audioCaches.end()) {
|
||||||
{
|
|
||||||
audioCache = &it->second;
|
|
||||||
if (callback && audioCache->_alBufferReady)
|
|
||||||
{
|
|
||||||
callback(true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioCache::FileFormat fileFormat = AudioCache::FileFormat::UNKNOWN;
|
|
||||||
|
|
||||||
std::string fileExtension = FileUtils::getInstance()->getFileExtension(filePath);
|
|
||||||
if (fileExtension == ".ogg")
|
|
||||||
{
|
|
||||||
fileFormat = AudioCache::FileFormat::OGG;
|
|
||||||
}
|
|
||||||
else if (fileExtension == ".mp3")
|
|
||||||
{
|
|
||||||
fileFormat = AudioCache::FileFormat::MP3;
|
|
||||||
|
|
||||||
if (MPG123_LAZYINIT)
|
|
||||||
{
|
|
||||||
auto error = mpg123_init();
|
|
||||||
if (error == MPG123_OK)
|
|
||||||
{
|
|
||||||
MPG123_LAZYINIT = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ALOGE("Unsupported media type file: %s\n", filePath.c_str());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
audioCache = &_audioCaches[filePath];
|
audioCache = &_audioCaches[filePath];
|
||||||
audioCache->_fileFormat = fileFormat;
|
|
||||||
|
|
||||||
audioCache->_fileFullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
|
audioCache->_fileFullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
|
||||||
AudioEngine::addTask(std::bind(&AudioCache::readDataTask, audioCache));
|
unsigned int cacheId = audioCache->_id;
|
||||||
} while (false);
|
auto isCacheDestroyed = audioCache->_isDestroyed;
|
||||||
|
AudioEngine::addTask([audioCache, cacheId, isCacheDestroyed](){
|
||||||
if (callback)
|
if (*isCacheDestroyed)
|
||||||
{
|
{
|
||||||
if (audioCache)
|
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)
|
||||||
{
|
{
|
||||||
audioCache->addLoadCallback(callback);
|
audioCache->addLoadCallback(callback);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
callback(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return audioCache;
|
return audioCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
int AudioEngineImpl::play2d(const std::string &filePath ,bool loop ,float volume)
|
int AudioEngineImpl::play2d(const std::string &filePath ,bool loop ,float volume)
|
||||||
{
|
{
|
||||||
bool availableSourceExist = false;
|
if (s_ALDevice == nullptr) {
|
||||||
ALuint alSource;
|
return AudioEngine::INVALID_AUDIO_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sourceFlag = false;
|
||||||
|
ALuint alSource = 0;
|
||||||
for (int i = 0; i < MAX_AUDIOINSTANCES; ++i) {
|
for (int i = 0; i < MAX_AUDIOINSTANCES; ++i) {
|
||||||
alSource = _alSources[i];
|
alSource = _alSources[i];
|
||||||
|
|
||||||
if ( !_alSourceUsed[alSource]) {
|
if ( !_alSourceUsed[alSource]) {
|
||||||
availableSourceExist = true;
|
sourceFlag = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!availableSourceExist){
|
if(!sourceFlag){
|
||||||
return AudioEngine::INVALID_AUDIO_ID;
|
return AudioEngine::INVALID_AUDIO_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioCache* audioCache = preload(filePath, nullptr);
|
auto player = new (std::nothrow) AudioPlayer;
|
||||||
if (audioCache == nullptr)
|
if (player == nullptr) {
|
||||||
{
|
|
||||||
return AudioEngine::INVALID_AUDIO_ID;
|
return AudioEngine::INVALID_AUDIO_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto player = &_audioPlayers[_currentAudioID];
|
|
||||||
player->_alSource = alSource;
|
player->_alSource = alSource;
|
||||||
player->_loop = loop;
|
player->_loop = loop;
|
||||||
player->_volume = volume;
|
player->_volume = volume;
|
||||||
audioCache->addPlayCallback(std::bind(&AudioEngineImpl::_play2d, this, audioCache, _currentAudioID));
|
|
||||||
|
auto audioCache = preload(filePath, nullptr);
|
||||||
|
if (audioCache == nullptr) {
|
||||||
|
delete player;
|
||||||
|
return AudioEngine::INVALID_AUDIO_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
player->setCache(audioCache);
|
||||||
|
_threadMutex.lock();
|
||||||
|
_audioPlayers[_currentAudioID] = player;
|
||||||
|
_threadMutex.unlock();
|
||||||
|
|
||||||
_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);
|
||||||
auto scheduler = cocos2d::Director::getInstance()->getScheduler();
|
|
||||||
scheduler->schedule(schedule_selector(AudioEngineImpl::update), this, 0.05f, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return _currentAudioID++;
|
return _currentAudioID++;
|
||||||
|
@ -216,71 +253,76 @@ 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();
|
||||||
auto playerIt = _audioPlayers.find(audioID);
|
auto playerIt = _audioPlayers.find(audioID);
|
||||||
if (playerIt != _audioPlayers.end()) {
|
if (playerIt != _audioPlayers.end() && playerIt->second->play2d()) {
|
||||||
if (playerIt->second.play2d(cache)) {
|
_scheduler->performFunctionInCocosThread([audioID](){
|
||||||
|
|
||||||
|
if (AudioEngine::_audioIDInfoMap.find(audioID) != AudioEngine::_audioIDInfoMap.end()) {
|
||||||
AudioEngine::_audioIDInfoMap[audioID].state = AudioEngine::AudioState::PLAYING;
|
AudioEngine::_audioIDInfoMap[audioID].state = AudioEngine::AudioState::PLAYING;
|
||||||
}
|
}
|
||||||
else{
|
});
|
||||||
_threadMutex.lock();
|
}
|
||||||
_toRemoveAudioIDs.push_back(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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
_threadMutex.lock();
|
|
||||||
_toRemoveCaches.push_back(cache);
|
|
||||||
_toRemoveAudioIDs.push_back(audioID);
|
|
||||||
_threadMutex.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioEngineImpl::setVolume(int audioID,float volume)
|
void AudioEngineImpl::setVolume(int audioID,float volume)
|
||||||
{
|
{
|
||||||
auto& player = _audioPlayers[audioID];
|
auto player = _audioPlayers[audioID];
|
||||||
player._volume = volume;
|
player->_volume = volume;
|
||||||
|
|
||||||
if (player._ready) {
|
if (player->_ready) {
|
||||||
alSourcef(_audioPlayers[audioID]._alSource, AL_GAIN, volume);
|
alSourcef(_audioPlayers[audioID]->_alSource, AL_GAIN, volume);
|
||||||
|
|
||||||
auto error = alGetError();
|
auto error = alGetError();
|
||||||
if (error != AL_NO_ERROR) {
|
if (error != AL_NO_ERROR) {
|
||||||
ALOGE("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error);
|
ALOGE("%s: audio id = %d, error = %x", __FUNCTION__,audioID,error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioEngineImpl::setLoop(int audioID, bool loop)
|
void AudioEngineImpl::setLoop(int audioID, bool loop)
|
||||||
{
|
{
|
||||||
auto& player = _audioPlayers[audioID];
|
auto player = _audioPlayers[audioID];
|
||||||
|
|
||||||
if (player._ready) {
|
if (player->_ready) {
|
||||||
if (player._streamingSource) {
|
if (player->_streamingSource) {
|
||||||
player.setLoop(loop);
|
player->setLoop(loop);
|
||||||
} else {
|
} else {
|
||||||
if (loop) {
|
if (loop) {
|
||||||
alSourcei(player._alSource, AL_LOOPING, AL_TRUE);
|
alSourcei(player->_alSource, AL_LOOPING, AL_TRUE);
|
||||||
} else {
|
} else {
|
||||||
alSourcei(player._alSource, AL_LOOPING, AL_FALSE);
|
alSourcei(player->_alSource, AL_LOOPING, AL_FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto error = alGetError();
|
auto error = alGetError();
|
||||||
if (error != AL_NO_ERROR) {
|
if (error != AL_NO_ERROR) {
|
||||||
ALOGE("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error);
|
ALOGE("%s: audio id = %d, error = %x", __FUNCTION__,audioID,error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
player._loop = loop;
|
player->_loop = loop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioEngineImpl::pause(int audioID)
|
bool AudioEngineImpl::pause(int audioID)
|
||||||
{
|
{
|
||||||
bool ret = true;
|
bool ret = true;
|
||||||
alSourcePause(_audioPlayers[audioID]._alSource);
|
alSourcePause(_audioPlayers[audioID]->_alSource);
|
||||||
|
|
||||||
auto error = alGetError();
|
auto error = alGetError();
|
||||||
if (error != AL_NO_ERROR) {
|
if (error != AL_NO_ERROR) {
|
||||||
|
@ -294,7 +336,7 @@ bool AudioEngineImpl::pause(int audioID)
|
||||||
bool AudioEngineImpl::resume(int audioID)
|
bool AudioEngineImpl::resume(int audioID)
|
||||||
{
|
{
|
||||||
bool ret = true;
|
bool ret = true;
|
||||||
alSourcePlay(_audioPlayers[audioID]._alSource);
|
alSourcePlay(_audioPlayers[audioID]->_alSource);
|
||||||
|
|
||||||
auto error = alGetError();
|
auto error = alGetError();
|
||||||
if (error != AL_NO_ERROR) {
|
if (error != AL_NO_ERROR) {
|
||||||
|
@ -305,66 +347,34 @@ bool AudioEngineImpl::resume(int audioID)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioEngineImpl::stop(int audioID)
|
void AudioEngineImpl::stop(int audioID)
|
||||||
{
|
{
|
||||||
bool ret = true;
|
auto player = _audioPlayers[audioID];
|
||||||
auto& player = _audioPlayers[audioID];
|
player->destroy();
|
||||||
if (player._ready) {
|
//Note: Don't set the flag to false here, it should be set in 'update' function.
|
||||||
alSourceStop(player._alSource);
|
// Otherwise, the state got from alSourceState may be wrong
|
||||||
|
// _alSourceUsed[player->_alSource] = false;
|
||||||
auto error = alGetError();
|
|
||||||
if (error != AL_NO_ERROR) {
|
|
||||||
ret = false;
|
|
||||||
ALOGE("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
alSourcei(player._alSource, AL_BUFFER, NULL);
|
|
||||||
|
|
||||||
_alSourceUsed[player._alSource] = false;
|
|
||||||
if (player._streamingSource)
|
|
||||||
{
|
|
||||||
player._ready = false;
|
|
||||||
player.notifyExitThread();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_audioPlayers.erase(audioID);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioEngineImpl::stopAll()
|
void AudioEngineImpl::stopAll()
|
||||||
{
|
{
|
||||||
for(int index = 0; index < MAX_AUDIOINSTANCES; ++index)
|
for(auto&& player : _audioPlayers)
|
||||||
{
|
{
|
||||||
alSourceStop(_alSources[index]);
|
player.second->destroy();
|
||||||
alSourcei(_alSources[index], AL_BUFFER, NULL);
|
|
||||||
_alSourceUsed[_alSources[index]] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto it = _audioPlayers.begin(); it != _audioPlayers.end();)
|
|
||||||
{
|
|
||||||
auto& player = it->second;
|
|
||||||
if (player._streamingSource)
|
|
||||||
{
|
|
||||||
player._ready = false;
|
|
||||||
player.notifyExitThread();
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
it = _audioPlayers.erase(it);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
//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)
|
float AudioEngineImpl::getDuration(int audioID)
|
||||||
{
|
{
|
||||||
auto& player = _audioPlayers[audioID];
|
auto player = _audioPlayers[audioID];
|
||||||
if(player._ready){
|
if(player->_ready){
|
||||||
return player._audioCache->_duration;
|
return player->_audioCache->_duration;
|
||||||
} else {
|
} else {
|
||||||
return AudioEngine::TIME_UNKNOWN;
|
return AudioEngine::TIME_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
@ -373,12 +383,12 @@ float AudioEngineImpl::getDuration(int audioID)
|
||||||
float AudioEngineImpl::getCurrentTime(int audioID)
|
float AudioEngineImpl::getCurrentTime(int audioID)
|
||||||
{
|
{
|
||||||
float ret = 0.0f;
|
float ret = 0.0f;
|
||||||
auto& player = _audioPlayers[audioID];
|
auto player = _audioPlayers[audioID];
|
||||||
if(player._ready){
|
if(player->_ready){
|
||||||
if (player._streamingSource) {
|
if (player->_streamingSource) {
|
||||||
ret = player.getTime();
|
ret = player->getTime();
|
||||||
} else {
|
} else {
|
||||||
alGetSourcef(player._alSource, AL_SEC_OFFSET, &ret);
|
alGetSourcef(player->_alSource, AL_SEC_OFFSET, &ret);
|
||||||
|
|
||||||
auto error = alGetError();
|
auto error = alGetError();
|
||||||
if (error != AL_NO_ERROR) {
|
if (error != AL_NO_ERROR) {
|
||||||
|
@ -393,23 +403,29 @@ float AudioEngineImpl::getCurrentTime(int audioID)
|
||||||
bool AudioEngineImpl::setCurrentTime(int audioID, float time)
|
bool AudioEngineImpl::setCurrentTime(int audioID, float time)
|
||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
auto& player = _audioPlayers[audioID];
|
auto player = _audioPlayers[audioID];
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (!player._ready) {
|
if (!player->_ready) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player._streamingSource) {
|
if (player->_streamingSource) {
|
||||||
ret = player.setTime(time);
|
ret = player->setTime(time);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
alSourcef(player._alSource, AL_SEC_OFFSET, time);
|
if (player->_audioCache->_framesRead != player->_audioCache->_totalFrames &&
|
||||||
|
(time * player->_audioCache->_sampleRate) > player->_audioCache->_framesRead) {
|
||||||
|
ALOGE("%s: audio id = %d", __FUNCTION__,audioID);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
alSourcef(player->_alSource, AL_SEC_OFFSET, time);
|
||||||
|
|
||||||
auto error = alGetError();
|
auto error = alGetError();
|
||||||
if (error != AL_NO_ERROR) {
|
if (error != AL_NO_ERROR) {
|
||||||
ALOGE("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error);
|
ALOGE("%s: audio id = %d, error = %x", __FUNCTION__,audioID,error);
|
||||||
}
|
}
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
|
@ -420,70 +436,45 @@ bool AudioEngineImpl::setCurrentTime(int audioID, float time)
|
||||||
|
|
||||||
void AudioEngineImpl::setFinishCallback(int audioID, const std::function<void (int, const std::string &)> &callback)
|
void AudioEngineImpl::setFinishCallback(int audioID, const std::function<void (int, const std::string &)> &callback)
|
||||||
{
|
{
|
||||||
_audioPlayers[audioID]._finishCallbak = callback;
|
_audioPlayers[audioID]->_finishCallbak = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioEngineImpl::update(float dt)
|
void AudioEngineImpl::update(float dt)
|
||||||
{
|
{
|
||||||
ALint sourceState;
|
ALint sourceState;
|
||||||
int audioID;
|
int audioID;
|
||||||
|
AudioPlayer* player;
|
||||||
|
|
||||||
if (_threadMutex.try_lock()) {
|
// ALOGV("AudioPlayer count: %d", (int)_audioPlayers.size());
|
||||||
size_t removeAudioCount = _toRemoveAudioIDs.size();
|
|
||||||
for (size_t index = 0; index < removeAudioCount; ++index) {
|
|
||||||
audioID = _toRemoveAudioIDs[index];
|
|
||||||
auto playerIt = _audioPlayers.find(audioID);
|
|
||||||
if (playerIt != _audioPlayers.end()) {
|
|
||||||
_alSourceUsed[playerIt->second._alSource] = false;
|
|
||||||
if(playerIt->second._finishCallbak) {
|
|
||||||
auto& audioInfo = AudioEngine::_audioIDInfoMap[audioID];
|
|
||||||
playerIt->second._finishCallbak(audioID, *audioInfo.filePath);
|
|
||||||
}
|
|
||||||
_audioPlayers.erase(audioID);
|
|
||||||
AudioEngine::remove(audioID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
size_t removeCacheCount = _toRemoveCaches.size();
|
|
||||||
for (size_t index = 0; index < removeCacheCount; ++index) {
|
|
||||||
auto itEnd = _audioCaches.end();
|
|
||||||
for (auto it = _audioCaches.begin(); it != itEnd; ++it) {
|
|
||||||
if (&it->second == _toRemoveCaches[index]) {
|
|
||||||
_audioCaches.erase(it);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_threadMutex.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto it = _audioPlayers.begin(); it != _audioPlayers.end(); ) {
|
for (auto it = _audioPlayers.begin(); it != _audioPlayers.end(); ) {
|
||||||
audioID = it->first;
|
audioID = it->first;
|
||||||
auto& player = it->second;
|
player = it->second;
|
||||||
alGetSourcei(player._alSource, AL_SOURCE_STATE, &sourceState);
|
alGetSourcei(player->_alSource, AL_SOURCE_STATE, &sourceState);
|
||||||
|
|
||||||
if (player._readForRemove && !player._ready)
|
if (player->_removeByAudioEngine)
|
||||||
{
|
{
|
||||||
|
_alSourceUsed[player->_alSource] = false;
|
||||||
|
|
||||||
|
AudioEngine::remove(audioID);
|
||||||
|
_threadMutex.lock();
|
||||||
it = _audioPlayers.erase(it);
|
it = _audioPlayers.erase(it);
|
||||||
|
_threadMutex.unlock();
|
||||||
|
delete player;
|
||||||
}
|
}
|
||||||
else if (player._ready && sourceState == AL_STOPPED) {
|
else if (player->_ready && sourceState == AL_STOPPED) {
|
||||||
_alSourceUsed[player._alSource] = false;
|
|
||||||
if (player._finishCallbak) {
|
_alSourceUsed[player->_alSource] = false;
|
||||||
|
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);
|
||||||
|
delete player;
|
||||||
if (player._streamingSource)
|
_threadMutex.lock();
|
||||||
{
|
|
||||||
player._ready = false;
|
|
||||||
player.notifyExitThread();
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
it = _audioPlayers.erase(it);
|
it = _audioPlayers.erase(it);
|
||||||
}
|
_threadMutex.unlock();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
++it;
|
++it;
|
||||||
|
@ -492,9 +483,7 @@ void AudioEngineImpl::update(float dt)
|
||||||
|
|
||||||
if(_audioPlayers.empty()){
|
if(_audioPlayers.empty()){
|
||||||
_lazyInitLoop = true;
|
_lazyInitLoop = true;
|
||||||
|
_scheduler->unschedule(CC_SCHEDULE_SELECTOR(AudioEngineImpl::update), this);
|
||||||
auto scheduler = cocos2d::Director::getInstance()->getScheduler();
|
|
||||||
scheduler->unschedule(schedule_selector(AudioEngineImpl::update), this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
Copyright (c) 2014 Chukong Technologies Inc.
|
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||||
|
|
||||||
http://www.cocos2d-x.org
|
http://www.cocos2d-x.org
|
||||||
|
|
||||||
|
@ -35,7 +35,10 @@
|
||||||
#include "audio/win32/AudioPlayer.h"
|
#include "audio/win32/AudioPlayer.h"
|
||||||
|
|
||||||
NS_CC_BEGIN
|
NS_CC_BEGIN
|
||||||
namespace experimental{
|
|
||||||
|
class Scheduler;
|
||||||
|
|
||||||
|
namespace experimental {
|
||||||
#define MAX_AUDIOINSTANCES 32
|
#define MAX_AUDIOINSTANCES 32
|
||||||
|
|
||||||
class CC_DLL AudioEngineImpl : public cocos2d::Ref
|
class CC_DLL AudioEngineImpl : public cocos2d::Ref
|
||||||
|
@ -50,7 +53,7 @@ public:
|
||||||
void setLoop(int audioID, bool loop);
|
void setLoop(int audioID, bool loop);
|
||||||
bool pause(int audioID);
|
bool pause(int audioID);
|
||||||
bool resume(int audioID);
|
bool resume(int audioID);
|
||||||
bool stop(int audioID);
|
void stop(int audioID);
|
||||||
void stopAll();
|
void stopAll();
|
||||||
float getDuration(int audioID);
|
float getDuration(int audioID);
|
||||||
float getCurrentTime(int audioID);
|
float getCurrentTime(int audioID);
|
||||||
|
@ -60,7 +63,6 @@ public:
|
||||||
void uncache(const std::string& filePath);
|
void uncache(const std::string& filePath);
|
||||||
void uncacheAll();
|
void uncacheAll();
|
||||||
AudioCache* preload(const std::string& filePath, std::function<void(bool)> callback);
|
AudioCache* preload(const std::string& filePath, std::function<void(bool)> callback);
|
||||||
|
|
||||||
void update(float dt);
|
void update(float dt);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -75,17 +77,13 @@ private:
|
||||||
std::unordered_map<std::string, AudioCache> _audioCaches;
|
std::unordered_map<std::string, AudioCache> _audioCaches;
|
||||||
|
|
||||||
//audioID,AudioInfo
|
//audioID,AudioInfo
|
||||||
std::unordered_map<int, AudioPlayer> _audioPlayers;
|
std::unordered_map<int, AudioPlayer*> _audioPlayers;
|
||||||
|
|
||||||
std::mutex _threadMutex;
|
std::mutex _threadMutex;
|
||||||
|
|
||||||
std::vector<AudioCache*> _toRemoveCaches;
|
|
||||||
std::vector<int> _toRemoveAudioIDs;
|
|
||||||
|
|
||||||
bool _lazyInitLoop;
|
bool _lazyInitLoop;
|
||||||
|
|
||||||
int _currentAudioID;
|
int _currentAudioID;
|
||||||
|
Scheduler* _scheduler;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
NS_CC_END
|
NS_CC_END
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
/****************************************************************************
|
||||||
|
Copyright (c) 2016 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.
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define QUEUEBUFFER_NUM (3)
|
||||||
|
#define QUEUEBUFFER_TIME_STEP (0.1f)
|
||||||
|
|
||||||
|
// log, CCLOG aren't threadsafe, since we uses sub threads for parsing pcm data, threadsafe log output
|
||||||
|
// is needed. Define the following macros (ALOGV, ALOGD, ALOGI, ALOGW, ALOGE) for threadsafe log output.
|
||||||
|
|
||||||
|
//FIXME:Move the definition of the following macros to a separated file.
|
||||||
|
|
||||||
|
void audioLog(const char * format, ...);
|
||||||
|
|
||||||
|
#define QUOTEME_(x) #x
|
||||||
|
#define QUOTEME(x) QUOTEME_(x)
|
||||||
|
|
||||||
|
#if defined(COCOS2D_DEBUG) && COCOS2D_DEBUG > 0
|
||||||
|
#define ALOGV(fmt, ...) audioLog("V/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define ALOGV(fmt, ...) do {} while(false)
|
||||||
|
#endif
|
||||||
|
#define ALOGD(fmt, ...) audioLog("D/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
|
||||||
|
#define ALOGI(fmt, ...) audioLog("I/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
|
||||||
|
#define ALOGW(fmt, ...) audioLog("W/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
|
||||||
|
#define ALOGE(fmt, ...) audioLog("E/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__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
|
||||||
|
|
||||||
|
#define BREAK_IF(condition) \
|
||||||
|
if (!!(condition)) { \
|
||||||
|
break; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BREAK_IF_ERR_LOG(condition, fmt, ...) \
|
||||||
|
if (!!(condition)) { \
|
||||||
|
ALOGE("(" QUOTEME(condition) ") failed, message: " fmt, ##__VA_ARGS__); \
|
||||||
|
break; \
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
Copyright (c) 2014 Chukong Technologies Inc.
|
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||||
|
|
||||||
http://www.cocos2d-x.org
|
http://www.cocos2d-x.org
|
||||||
|
|
||||||
|
@ -29,231 +29,246 @@
|
||||||
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
|
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
|
||||||
#include "audio/win32/AudioPlayer.h"
|
#include "audio/win32/AudioPlayer.h"
|
||||||
#include "audio/win32/AudioCache.h"
|
#include "audio/win32/AudioCache.h"
|
||||||
#include "base/CCConsole.h"
|
|
||||||
#include "platform/CCFileUtils.h"
|
#include "platform/CCFileUtils.h"
|
||||||
#include "mpg123.h"
|
#include "audio/win32/AudioDecoderManager.h"
|
||||||
#include "vorbis/codec.h"
|
#include "audio/win32/Audiodecoder.h"
|
||||||
#include "vorbis/vorbisfile.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;
|
using namespace cocos2d::experimental;
|
||||||
|
|
||||||
AudioPlayer::AudioPlayer()
|
namespace {
|
||||||
: _exitThread(false)
|
unsigned int __idIndex = 0;
|
||||||
, _timeDirty(false)
|
|
||||||
, _streamingSource(false)
|
|
||||||
, _currTime(0.0f)
|
|
||||||
, _finishCallbak(nullptr)
|
|
||||||
, _ready(false)
|
|
||||||
, _audioCache(nullptr)
|
|
||||||
, _readForRemove(false)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioPlayer::AudioPlayer(const AudioPlayer& player)
|
AudioPlayer::AudioPlayer()
|
||||||
|
: _audioCache(nullptr)
|
||||||
|
, _finishCallbak(nullptr)
|
||||||
|
, _isDestroyed(false)
|
||||||
|
, _removeByAudioEngine(false)
|
||||||
|
, _ready(false)
|
||||||
|
, _currTime(0.0f)
|
||||||
|
, _streamingSource(false)
|
||||||
|
, _rotateBufferThread(nullptr)
|
||||||
|
, _timeDirty(false)
|
||||||
|
, _isRotateThreadExited(false)
|
||||||
|
, _id(++__idIndex)
|
||||||
{
|
{
|
||||||
_exitThread = player._exitThread;
|
memset(_bufferIds, 0, sizeof(_bufferIds));
|
||||||
_timeDirty = player._timeDirty;
|
|
||||||
_streamingSource = player._streamingSource;
|
|
||||||
_currTime = player._currTime;
|
|
||||||
_finishCallbak = player._finishCallbak;
|
|
||||||
_ready = player._ready;
|
|
||||||
_audioCache = player._audioCache;
|
|
||||||
_readForRemove = player._readForRemove;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioPlayer::~AudioPlayer()
|
AudioPlayer::~AudioPlayer()
|
||||||
{
|
{
|
||||||
if (_audioCache && _audioCache->_queBufferFrames > 0) {
|
ALOGVV("~AudioPlayer() (%p), id=%u", this, _id);
|
||||||
alDeleteBuffers(QUEUEBUFFER_NUM, _bufferIds);
|
destroy();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioPlayer::notifyExitThread()
|
|
||||||
{
|
|
||||||
if (_audioCache && _audioCache->_queBufferFrames > 0) {
|
|
||||||
std::unique_lock<std::mutex> lk(_sleepMutex);
|
|
||||||
_exitThread = true;
|
|
||||||
_sleepCondition.notify_all();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AudioPlayer::play2d(AudioCache* cache)
|
|
||||||
{
|
|
||||||
if (!cache->_alBufferReady) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_audioCache = cache;
|
|
||||||
|
|
||||||
alSourcei(_alSource, AL_BUFFER, NULL);
|
|
||||||
alSourcef(_alSource, AL_PITCH, 1.0f);
|
|
||||||
alSourcef(_alSource, AL_GAIN, _volume);
|
|
||||||
|
|
||||||
if (_audioCache->_queBufferFrames == 0) {
|
|
||||||
if (_loop) {
|
|
||||||
alSourcei(_alSource, AL_LOOPING, AL_TRUE);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
alSourcei(_alSource, AL_LOOPING, AL_FALSE);
|
|
||||||
}
|
|
||||||
alSourcei(_alSource, AL_BUFFER, _audioCache->_alBufferId);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
_streamingSource = true;
|
|
||||||
alSourcei(_alSource, AL_LOOPING, AL_FALSE);
|
|
||||||
|
|
||||||
auto alError = alGetError();
|
|
||||||
alGenBuffers(QUEUEBUFFER_NUM, _bufferIds);
|
|
||||||
alError = alGetError();
|
|
||||||
if (alError == AL_NO_ERROR) {
|
|
||||||
for (int index = 0; index < QUEUEBUFFER_NUM; ++index) {
|
|
||||||
alBufferData(_bufferIds[index], _audioCache->_alBufferFormat, _audioCache->_queBuffers[index], _audioCache->_queBufferSize[index], _audioCache->_sampleRate);
|
|
||||||
}
|
|
||||||
alSourceQueueBuffers(_alSource, QUEUEBUFFER_NUM, _bufferIds);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ALOGE("%s:alGenBuffers error code:%x", __FUNCTION__,alError);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_streamingSource)
|
if (_streamingSource)
|
||||||
{
|
{
|
||||||
_rotateBufferThread = std::thread(&AudioPlayer::rotateBufferThread, this, _audioCache->_queBufferFrames * QUEUEBUFFER_NUM + 1);
|
alDeleteBuffers(3, _bufferIds);
|
||||||
_rotateBufferThread.detach();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioPlayer::destroy()
|
||||||
|
{
|
||||||
|
if (_isDestroyed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ALOGVV("AudioPlayer::destroy begin, id=%u", _id);
|
||||||
|
|
||||||
|
_isDestroyed = true;
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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, player id=%u", _alSource, _id);
|
||||||
|
|
||||||
|
/*********************************************************************/
|
||||||
|
/* Note that it may be in sub thread or in main thread. **/
|
||||||
|
/*********************************************************************/
|
||||||
|
bool ret = false;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (_audioCache->_state != AudioCache::State::READY)
|
||||||
|
{
|
||||||
|
ALOGE("alBuffer isn't ready for play!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
else
|
||||||
{
|
{
|
||||||
alSourcePlay(_alSource);
|
alGenBuffers(3, _bufferIds);
|
||||||
|
|
||||||
auto alError = alGetError();
|
auto alError = alGetError();
|
||||||
if (alError != AL_NO_ERROR) {
|
if (alError == AL_NO_ERROR)
|
||||||
ALOGE("%s:alSourcePlay error code:%x\n", __FUNCTION__, alError);
|
{
|
||||||
return false;
|
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", __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", __FUNCTION__,alError);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ALint state;
|
||||||
|
alGetSourcei(_alSource, AL_SOURCE_STATE, &state);
|
||||||
|
if (state != AL_PLAYING)
|
||||||
|
{
|
||||||
|
ALOGE("state isn't playing, %d, %s, cache id=%u, player id=%u", state, _audioCache->_fileFullPath.c_str(), _audioCache->_id, _id);
|
||||||
|
}
|
||||||
|
assert(state == AL_PLAYING);
|
||||||
_ready = true;
|
_ready = true;
|
||||||
|
ret = true;
|
||||||
|
} while (false);
|
||||||
|
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
_removeByAudioEngine = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
_play2dMutex.unlock();
|
||||||
}
|
return ret;
|
||||||
|
|
||||||
int AudioPlayer::readPcmData(char* buffer, int bufferSize, const std::function<int/*readBytes*/(char* /*buf*/, int /*bytesToRead*/)>& fileReader)
|
|
||||||
{
|
|
||||||
assert(buffer != nullptr && bufferSize > 0);
|
|
||||||
|
|
||||||
int readBytes = 0;
|
|
||||||
int readBytesOnce = 0;
|
|
||||||
int remainBytes = bufferSize;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
readBytesOnce = fileReader(buffer + readBytes, remainBytes);
|
|
||||||
if (readBytesOnce > 0)
|
|
||||||
{
|
|
||||||
readBytes += readBytesOnce;
|
|
||||||
remainBytes -= readBytesOnce;
|
|
||||||
}
|
|
||||||
} while (readBytesOnce > 0 && readBytes < bufferSize);
|
|
||||||
|
|
||||||
return readBytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioPlayer::rotateBufferThread(int offsetFrame)
|
void AudioPlayer::rotateBufferThread(int offsetFrame)
|
||||||
{
|
{
|
||||||
|
char* tmpBuffer = nullptr;
|
||||||
|
AudioDecoder* decoder = AudioDecoderManager::createDecoder(_audioCache->_fileFullPath.c_str());
|
||||||
|
do
|
||||||
|
{
|
||||||
|
BREAK_IF(decoder == nullptr || !decoder->open(_audioCache->_fileFullPath.c_str()));
|
||||||
|
|
||||||
|
uint32_t framesRead = 0;
|
||||||
|
const uint32_t framesToRead = _audioCache->_queBufferFrames;
|
||||||
|
const uint32_t bufferSize = framesToRead * decoder->getBytesPerFrame();
|
||||||
|
tmpBuffer = (char*)malloc(bufferSize);
|
||||||
|
memset(tmpBuffer, 0, bufferSize);
|
||||||
|
|
||||||
|
if (offsetFrame != 0) {
|
||||||
|
decoder->seek(offsetFrame);
|
||||||
|
}
|
||||||
|
|
||||||
ALint sourceState;
|
ALint sourceState;
|
||||||
ALint bufferProcessed = 0;
|
ALint bufferProcessed = 0;
|
||||||
mpg123_handle* mpg123handle = nullptr;
|
bool needToExitThread = false;
|
||||||
OggVorbis_File* vorbisFile = nullptr;
|
|
||||||
|
|
||||||
std::function<int(char*, int)> fileReader = nullptr;
|
while (!_isDestroyed) {
|
||||||
std::function<void(int)> fileSeeker = nullptr;
|
|
||||||
|
|
||||||
auto audioFileFormat = _audioCache->_fileFormat;
|
|
||||||
char* tmpBuffer = (char*)malloc(_audioCache->_queBufferBytes);
|
|
||||||
|
|
||||||
switch (audioFileFormat)
|
|
||||||
{
|
|
||||||
case AudioCache::FileFormat::MP3:
|
|
||||||
{
|
|
||||||
int error = MPG123_OK;
|
|
||||||
mpg123handle = mpg123_new(nullptr, &error);
|
|
||||||
if (!mpg123handle){
|
|
||||||
ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
|
|
||||||
goto ExitBufferThread;
|
|
||||||
}
|
|
||||||
long rate = 0;
|
|
||||||
int channels = 0;
|
|
||||||
int mp3Encoding = 0;
|
|
||||||
if (mpg123_open(mpg123handle,_audioCache->_fileFullPath.c_str()) != MPG123_OK
|
|
||||||
|| mpg123_getformat(mpg123handle, &rate, &channels, &mp3Encoding) != MPG123_OK){
|
|
||||||
ALOGE("Trouble with mpg123: %s\n", mpg123_strerror(mpg123handle) );
|
|
||||||
goto ExitBufferThread;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure that this output format will not change (it could, when we allow it). */
|
|
||||||
mpg123_format_none(mpg123handle);
|
|
||||||
mpg123_format(mpg123handle, _audioCache->_sampleRate, _audioCache->_channels, _audioCache->_mp3Encoding);
|
|
||||||
|
|
||||||
if (offsetFrame != 0) {
|
|
||||||
mpg123_seek(mpg123handle,offsetFrame,SEEK_SET);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case AudioCache::FileFormat::OGG:
|
|
||||||
{
|
|
||||||
vorbisFile = new OggVorbis_File;
|
|
||||||
int openCode;
|
|
||||||
if (openCode = ov_fopen(FileUtils::getInstance()->getSuitableFOpen(_audioCache->_fileFullPath).c_str(), vorbisFile)){
|
|
||||||
ALOGE("Input does not appear to be an Ogg bitstream: %s. Code: 0x%x\n", _audioCache->_fileFullPath.c_str(), openCode);
|
|
||||||
goto ExitBufferThread;
|
|
||||||
}
|
|
||||||
if (offsetFrame != 0) {
|
|
||||||
ov_pcm_seek(vorbisFile,offsetFrame);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
alSourcePlay(_alSource);
|
|
||||||
|
|
||||||
if (audioFileFormat == AudioCache::FileFormat::MP3)
|
|
||||||
{
|
|
||||||
fileReader = [&](char* buffer, int bufferSize) -> int {
|
|
||||||
size_t ret = 0;
|
|
||||||
mpg123_read(mpg123handle, (unsigned char*)buffer, bufferSize, &ret);
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
fileSeeker = [&](int pos) {
|
|
||||||
mpg123_seek(mpg123handle, pos, SEEK_SET);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else if (audioFileFormat == AudioCache::FileFormat::OGG)
|
|
||||||
{
|
|
||||||
fileReader = [&](char* buffer, int bufferSize) -> int {
|
|
||||||
int current_section = 0;
|
|
||||||
return ov_read(vorbisFile, buffer, bufferSize, 0, 2, 1, ¤t_section);
|
|
||||||
};
|
|
||||||
|
|
||||||
fileSeeker = [&](int pos) {
|
|
||||||
ov_pcm_seek(vorbisFile, pos);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!_exitThread) {
|
|
||||||
alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState);
|
alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState);
|
||||||
if (sourceState == AL_PLAYING) {
|
if (sourceState == AL_PLAYING) {
|
||||||
_ready = true;
|
|
||||||
|
|
||||||
alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed);
|
alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed);
|
||||||
while (bufferProcessed > 0) {
|
while (bufferProcessed > 0) {
|
||||||
bufferProcessed--;
|
bufferProcessed--;
|
||||||
if (_timeDirty) {
|
if (_timeDirty) {
|
||||||
_timeDirty = false;
|
_timeDirty = false;
|
||||||
offsetFrame = _currTime * _audioCache->_sampleRate;
|
offsetFrame = _currTime * decoder->getSampleRate();
|
||||||
fileSeeker(offsetFrame);
|
decoder->seek(offsetFrame);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
_currTime += QUEUEBUFFER_TIME_STEP;
|
_currTime += QUEUEBUFFER_TIME_STEP;
|
||||||
|
@ -266,65 +281,49 @@ void AudioPlayer::rotateBufferThread(int offsetFrame)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int readRet = readPcmData(tmpBuffer, _audioCache->_queBufferBytes, fileReader);
|
framesRead = decoder->readFixedFrames(framesToRead, tmpBuffer);
|
||||||
if (readRet <= 0) {
|
|
||||||
|
if (framesRead == 0) {
|
||||||
if (_loop) {
|
if (_loop) {
|
||||||
fileSeeker(0);
|
decoder->seek(0);
|
||||||
readRet = readPcmData(tmpBuffer, _audioCache->_queBufferBytes, fileReader);
|
framesRead = decoder->readFixedFrames(framesToRead, tmpBuffer);
|
||||||
}
|
} else {
|
||||||
else {
|
needToExitThread = true;
|
||||||
_exitThread = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_audioCache->_bytesOfRead += readRet;
|
|
||||||
}
|
|
||||||
|
|
||||||
//ALOGV("readRet: %d, queBufferBytes: %d", (int)readRet, _audioCache->_queBufferBytes);
|
|
||||||
ALuint bid;
|
ALuint bid;
|
||||||
alSourceUnqueueBuffers(_alSource, 1, &bid);
|
alSourceUnqueueBuffers(_alSource, 1, &bid);
|
||||||
CHECK_AL_ERROR_DEBUG();
|
alBufferData(bid, _audioCache->_format, tmpBuffer, framesRead * decoder->getBytesPerFrame(), decoder->getSampleRate());
|
||||||
alBufferData(bid, _audioCache->_alBufferFormat, tmpBuffer, readRet, _audioCache->_sampleRate);
|
|
||||||
CHECK_AL_ERROR_DEBUG();
|
|
||||||
alSourceQueueBuffers(_alSource, 1, &bid);
|
alSourceQueueBuffers(_alSource, 1, &bid);
|
||||||
CHECK_AL_ERROR_DEBUG();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lk(_sleepMutex);
|
std::unique_lock<std::mutex> lk(_sleepMutex);
|
||||||
if (_exitThread)
|
if (_isDestroyed || needToExitThread) {
|
||||||
{
|
|
||||||
ALOGV("thread exit...");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_sleepCondition.wait_for(lk,std::chrono::milliseconds(35));
|
_sleepCondition.wait_for(lk,std::chrono::milliseconds(75));
|
||||||
}
|
}
|
||||||
ExitBufferThread:
|
|
||||||
switch (audioFileFormat)
|
} while(false);
|
||||||
|
|
||||||
|
ALOGV("Exit rotate buffer thread ...");
|
||||||
|
if (decoder != nullptr)
|
||||||
{
|
{
|
||||||
case AudioCache::FileFormat::OGG:
|
decoder->close();
|
||||||
ov_clear(vorbisFile);
|
|
||||||
delete vorbisFile;
|
|
||||||
break;
|
|
||||||
case AudioCache::FileFormat::MP3:
|
|
||||||
mpg123_close(mpg123handle);
|
|
||||||
mpg123_delete(mpg123handle);
|
|
||||||
break;
|
|
||||||
case AudioCache::FileFormat::UNKNOWN:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
AudioDecoderManager::destroyDecoder(decoder);
|
||||||
free(tmpBuffer);
|
free(tmpBuffer);
|
||||||
_readForRemove = true;
|
_isRotateThreadExited = true;
|
||||||
ALOGV("%s exited.\n", __FUNCTION__);
|
ALOGV("%s exited.\n", __FUNCTION__);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioPlayer::setLoop(bool loop)
|
bool AudioPlayer::setLoop(bool loop)
|
||||||
{
|
{
|
||||||
if (!_exitThread ) {
|
if (!_isDestroyed ) {
|
||||||
_loop = loop;
|
_loop = loop;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -334,9 +333,11 @@ bool AudioPlayer::setLoop(bool loop)
|
||||||
|
|
||||||
bool AudioPlayer::setTime(float time)
|
bool AudioPlayer::setTime(float time)
|
||||||
{
|
{
|
||||||
if (!_exitThread && time >= 0.0f && time < _audioCache->_duration) {
|
if (!_isDestroyed && time >= 0.0f && time < _audioCache->_duration) {
|
||||||
|
|
||||||
_currTime = time;
|
_currTime = time;
|
||||||
_timeDirty = true;
|
_timeDirty = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -21,15 +21,16 @@
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#include "platform/CCPlatformConfig.h"
|
#include "platform/CCPlatformConfig.h"
|
||||||
|
|
||||||
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
|
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
|
||||||
|
|
||||||
#ifndef __AUDIO_PLAYER_H_
|
|
||||||
#define __AUDIO_PLAYER_H_
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#ifdef OPENAL_PLAIN_INCLUDES
|
#ifdef OPENAL_PLAIN_INCLUDES
|
||||||
#include <al.h>
|
#include <al.h>
|
||||||
|
@ -37,7 +38,6 @@
|
||||||
#include <AL/al.h>
|
#include <AL/al.h>
|
||||||
#endif
|
#endif
|
||||||
#include "platform/CCPlatformMacros.h"
|
#include "platform/CCPlatformMacros.h"
|
||||||
#include "audio/win32/AudioCache.h"
|
|
||||||
|
|
||||||
NS_CC_BEGIN
|
NS_CC_BEGIN
|
||||||
namespace experimental{
|
namespace experimental{
|
||||||
|
@ -49,19 +49,19 @@ class CC_DLL AudioPlayer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AudioPlayer();
|
AudioPlayer();
|
||||||
AudioPlayer(const AudioPlayer&);
|
|
||||||
~AudioPlayer();
|
~AudioPlayer();
|
||||||
|
|
||||||
|
void destroy();
|
||||||
|
|
||||||
//queue buffer related stuff
|
//queue buffer related stuff
|
||||||
bool setTime(float time);
|
bool setTime(float time);
|
||||||
float getTime() { return _currTime;}
|
float getTime() { return _currTime;}
|
||||||
bool setLoop(bool loop);
|
bool setLoop(bool loop);
|
||||||
void notifyExitThread();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void setCache(AudioCache* cache);
|
||||||
void rotateBufferThread(int offsetFrame);
|
void rotateBufferThread(int offsetFrame);
|
||||||
bool play2d(AudioCache* cache);
|
bool play2d();
|
||||||
int readPcmData(char* buffer, int bufferSize, const std::function<int/*readBytes*/(char* /*buf*/, int /*bytesToRead*/)>& fileReader);
|
|
||||||
|
|
||||||
AudioCache* _audioCache;
|
AudioCache* _audioCache;
|
||||||
|
|
||||||
|
@ -69,25 +69,30 @@ protected:
|
||||||
bool _loop;
|
bool _loop;
|
||||||
std::function<void (int, const std::string &)> _finishCallbak;
|
std::function<void (int, const std::string &)> _finishCallbak;
|
||||||
|
|
||||||
|
bool _isDestroyed;
|
||||||
|
bool _removeByAudioEngine;
|
||||||
bool _ready;
|
bool _ready;
|
||||||
ALuint _alSource;
|
ALuint _alSource;
|
||||||
|
|
||||||
//play by circular buffer
|
//play by circular buffer
|
||||||
float _currTime;
|
float _currTime;
|
||||||
bool _timeDirty;
|
|
||||||
bool _streamingSource;
|
bool _streamingSource;
|
||||||
ALuint _bufferIds[QUEUEBUFFER_NUM];
|
ALuint _bufferIds[3];
|
||||||
std::thread _rotateBufferThread;
|
std::thread* _rotateBufferThread;
|
||||||
std::mutex _sleepMutex;
|
|
||||||
std::condition_variable _sleepCondition;
|
std::condition_variable _sleepCondition;
|
||||||
bool _exitThread;
|
std::mutex _sleepMutex;
|
||||||
bool _readForRemove;
|
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 //CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
|
#endif //CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,16 @@ public:
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief Play sound file
|
@brief Open audio file
|
||||||
@param pFileName Sound's file name,include the file path.
|
@param pFileName The file name which includes the file path.
|
||||||
@param nTimes Play mode£¬default value is 1,paly once
|
@param uId The audio ID
|
||||||
*/
|
*/
|
||||||
void Open(const char* pFileName, UINT uId);
|
void Open(const char* pFileName, UINT uId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Play audio file
|
||||||
|
@param nTimes The repeat times, its default value is 1 which means only play once
|
||||||
|
*/
|
||||||
void Play(UINT uTimes = 1);
|
void Play(UINT uTimes = 1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue