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="..\audio\AudioEngine.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\AudioPlayer.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\SimpleAudioEngine.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\AudioMacros.h" />
|
||||
<ClInclude Include="..\audio\win32\AudioPlayer.h" />
|
||||
<ClInclude Include="..\audio\win32\MciPlayer.h" />
|
||||
<ClInclude Include="..\base\allocator\CCAllocatorBase.h" />
|
||||
|
|
|
@ -1971,6 +1971,18 @@
|
|||
<ClCompile Include="..\vr\CCVRGenericRenderer.cpp">
|
||||
<Filter>vr</Filter>
|
||||
</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>
|
||||
<ClInclude Include="..\physics\CCPhysicsBody.h">
|
||||
|
@ -3851,6 +3863,21 @@
|
|||
<ClInclude Include="..\vr\CCVRGenericRenderer.h">
|
||||
<Filter>vr</Filter>
|
||||
</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>
|
||||
<None Include="..\math\Mat4.inl">
|
||||
|
|
|
@ -70,17 +70,12 @@ AudioEngine::AudioEngineThreadPool* AudioEngine::s_threadPool = nullptr;
|
|||
class AudioEngine::AudioEngineThreadPool
|
||||
{
|
||||
public:
|
||||
AudioEngineThreadPool(bool detach, int threads = 4)
|
||||
: _detach(detach)
|
||||
, _stop(false)
|
||||
AudioEngineThreadPool(int threads = 4)
|
||||
: _stop(false)
|
||||
{
|
||||
for (int index = 0; index < threads; ++index)
|
||||
{
|
||||
_workers.emplace_back(std::thread(std::bind(&AudioEngineThreadPool::threadFunc, this)));
|
||||
if (_detach)
|
||||
{
|
||||
_workers[index].detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,13 +93,10 @@ public:
|
|||
_taskCondition.notify_all();
|
||||
}
|
||||
|
||||
if (!_detach)
|
||||
{
|
||||
for (auto&& worker : _workers) {
|
||||
worker.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void threadFunc()
|
||||
|
@ -138,7 +130,6 @@ private:
|
|||
|
||||
std::mutex _queueMutex;
|
||||
std::condition_variable _taskCondition;
|
||||
bool _detach;
|
||||
bool _stop;
|
||||
};
|
||||
|
||||
|
@ -169,17 +160,10 @@ bool AudioEngine::lazyInit()
|
|||
}
|
||||
}
|
||||
|
||||
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/****************************************************************************
|
||||
Copyright (c) 2014 Chukong Technologies Inc.
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
|
@ -30,350 +30,350 @@
|
|||
|
||||
#include "audio/win32/AudioCache.h"
|
||||
#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/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;
|
||||
|
||||
//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()
|
||||
: _pcmData(nullptr)
|
||||
, _pcmDataSize(0)
|
||||
, _bytesOfRead(0)
|
||||
, _alBufferReady(false)
|
||||
, _loadFail(false)
|
||||
, _fileFormat(FileFormat::UNKNOWN)
|
||||
: _totalFrames(0)
|
||||
, _framesRead(0)
|
||||
, _format(-1)
|
||||
, _duration(0.0f)
|
||||
, _alBufferId(INVALID_AL_BUFFER_ID)
|
||||
, _pcmData(nullptr)
|
||||
, _queBufferFrames(0)
|
||||
, _queBufferBytes(0)
|
||||
, _mp3Encoding(0)
|
||||
, _state(State::INITIAL)
|
||||
, _isDestroyed(std::make_shared<bool>(false))
|
||||
, _id(++__idIndex)
|
||||
, _isLoadingFinished(false)
|
||||
, _isSkipReadDataTask(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
AudioCache::AudioCache(const AudioCache& cache)
|
||||
{
|
||||
_pcmData = cache._pcmData;
|
||||
_pcmDataSize = cache._pcmDataSize;
|
||||
_bytesOfRead = cache._bytesOfRead;
|
||||
_alBufferReady = cache._alBufferReady;
|
||||
_fileFormat = cache._fileFormat;
|
||||
_queBufferFrames = cache._queBufferFrames;
|
||||
_queBufferBytes = cache._queBufferBytes;
|
||||
_mp3Encoding = cache._mp3Encoding;
|
||||
ALOGVV("AudioCache() %p, id=%u", this, _id);
|
||||
for (int i = 0; i < QUEUEBUFFER_NUM; ++i)
|
||||
{
|
||||
_queBuffers[i] = nullptr;
|
||||
_queBufferSize[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
AudioCache::~AudioCache()
|
||||
{
|
||||
if(_pcmData){
|
||||
if (_alBufferReady){
|
||||
alDeleteBuffers(1, &_alBufferId);
|
||||
ALOGVV("~AudioCache() %p, id=%u, begin", this, _id);
|
||||
*_isDestroyed = true;
|
||||
while (!_isLoadingFinished)
|
||||
{
|
||||
if (_isSkipReadDataTask)
|
||||
{
|
||||
ALOGV("id=%u, Skip read data task, don't continue to wait!", _id);
|
||||
break;
|
||||
}
|
||||
ALOGVV("id=%u, waiting readData thread to finish ...", _id);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
//wait for the 'readDataTask' task to exit
|
||||
_readDataTaskMutex.lock();
|
||||
_readDataTaskMutex.unlock();
|
||||
|
||||
free(_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)
|
||||
if (_pcmData)
|
||||
{
|
||||
case FileFormat::OGG:
|
||||
if (_state == State::READY)
|
||||
{
|
||||
vf = new OggVorbis_File;
|
||||
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:
|
||||
if (_alBufferId != INVALID_AL_BUFFER_ID && alIsBuffer(_alBufferId))
|
||||
{
|
||||
long rate = 0;
|
||||
int error = MPG123_OK;
|
||||
mpg123handle = mpg123_new(nullptr, &error);
|
||||
if (!mpg123handle){
|
||||
ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
|
||||
goto ExitThread;
|
||||
ALOGV("~AudioCache(id=%u), delete buffer: %u", _id, _alBufferId);
|
||||
alDeleteBuffers(1, &_alBufferId);
|
||||
_alBufferId = INVALID_AL_BUFFER_ID;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
_loadFail = true;
|
||||
ALOGW("AudioCache (%p), id=%u, buffer isn't ready, state=%d", this, _id, _state);
|
||||
}
|
||||
|
||||
invokingLoadCallbacks();
|
||||
invokingPlayCallbacks();
|
||||
free(_pcmData);
|
||||
}
|
||||
|
||||
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();
|
||||
auto count = _callbacks.size();
|
||||
for (size_t index = 0; index < count; ++index) {
|
||||
_callbacks[index]();
|
||||
//Note: It's in sub thread
|
||||
ALOGVV("readDataTask begin, cache id=%u", selfId);
|
||||
|
||||
_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)
|
||||
{
|
||||
_callbackMutex.lock();
|
||||
if (_alBufferReady) {
|
||||
std::lock_guard<std::mutex> lk(_playCallbackMutex);
|
||||
switch (_state)
|
||||
{
|
||||
case State::INITIAL:
|
||||
case State::LOADING:
|
||||
_playCallbacks.push_back(callback);
|
||||
break;
|
||||
|
||||
case State::READY:
|
||||
// If state is failure, we still need to invoke the callback
|
||||
// since the callback will set the 'AudioPlayer::_removeByAudioEngine' flag to true.
|
||||
case State::FAILED:
|
||||
callback();
|
||||
} else {
|
||||
_callbacks.push_back(callback);
|
||||
break;
|
||||
|
||||
default:
|
||||
ALOGE("Invalid state: %d", _state);
|
||||
break;
|
||||
}
|
||||
_callbackMutex.unlock();
|
||||
}
|
||||
|
||||
void AudioCache::invokingLoadCallbacks()
|
||||
void AudioCache::invokingPlayCallbacks()
|
||||
{
|
||||
auto scheduler = Director::getInstance()->getScheduler();
|
||||
scheduler->performFunctionInCocosThread([&](){
|
||||
auto count = _loadCallbacks.size();
|
||||
for (size_t index = 0; index < count; ++index) {
|
||||
_loadCallbacks[index](_alBufferReady);
|
||||
std::lock_guard<std::mutex> lk(_playCallbackMutex);
|
||||
|
||||
for (auto&& cb : _playCallbacks)
|
||||
{
|
||||
cb();
|
||||
}
|
||||
_loadCallbacks.clear();
|
||||
});
|
||||
|
||||
_playCallbacks.clear();
|
||||
}
|
||||
|
||||
void AudioCache::addLoadCallback(const std::function<void(bool)>& callback)
|
||||
{
|
||||
if (_alBufferReady) {
|
||||
callback(true);
|
||||
}
|
||||
else if (_loadFail){
|
||||
callback(false);
|
||||
}
|
||||
else {
|
||||
switch (_state)
|
||||
{
|
||||
case State::INITIAL:
|
||||
case State::LOADING:
|
||||
_loadCallbacks.push_back(callback);
|
||||
break;
|
||||
|
||||
case State::READY:
|
||||
callback(true);
|
||||
break;
|
||||
case State::FAILED:
|
||||
callback(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
ALOGE("Invalid state: %d", _state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioCache::invokingLoadCallbacks()
|
||||
{
|
||||
if (*_isDestroyed)
|
||||
{
|
||||
ALOGV("AudioCache (%p) was destroyed, don't invoke preload callback ...", this);
|
||||
return;
|
||||
}
|
||||
|
||||
auto isDestroyed = _isDestroyed;
|
||||
auto scheduler = Director::getInstance()->getScheduler();
|
||||
scheduler->performFunctionInCocosThread([&, 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/****************************************************************************
|
||||
Copyright (c) 2014 Chukong Technologies Inc.
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
|
@ -21,57 +21,20 @@
|
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "platform/CCPlatformConfig.h"
|
||||
|
||||
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
|
||||
|
||||
#ifndef __AUDIO_CACHE_H_
|
||||
#define __AUDIO_CACHE_H_
|
||||
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#ifdef OPENAL_PLAIN_INCLUDES
|
||||
#include <al.h>
|
||||
#else
|
||||
#include <memory>
|
||||
#include <AL/al.h>
|
||||
#endif
|
||||
#include "platform/CCPlatformMacros.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
|
||||
#include "audio/apple/AudioMacros.h"
|
||||
|
||||
NS_CC_BEGIN
|
||||
namespace experimental{
|
||||
|
@ -79,17 +42,19 @@ namespace experimental{
|
|||
class AudioEngineImpl;
|
||||
class AudioPlayer;
|
||||
|
||||
class CC_DLL AudioCache{
|
||||
class CC_DLL AudioCache
|
||||
{
|
||||
public:
|
||||
enum class FileFormat
|
||||
|
||||
enum class State
|
||||
{
|
||||
UNKNOWN,
|
||||
OGG,
|
||||
MP3
|
||||
INITIAL,
|
||||
LOADING,
|
||||
READY,
|
||||
FAILED
|
||||
};
|
||||
|
||||
AudioCache();
|
||||
AudioCache(const AudioCache&);
|
||||
~AudioCache();
|
||||
|
||||
void addPlayCallback(const std::function<void()>& callback);
|
||||
|
@ -97,53 +62,54 @@ public:
|
|||
void addLoadCallback(const std::function<void(bool)>& callback);
|
||||
|
||||
protected:
|
||||
void readDataTask();
|
||||
void setSkipReadDataTask(bool isSkip) { _isSkipReadDataTask = isSkip; };
|
||||
void readDataTask(unsigned int selfId);
|
||||
|
||||
void invokingPlayCallbacks();
|
||||
|
||||
void invokingLoadCallbacks();
|
||||
|
||||
std::string _fileFullPath;
|
||||
FileFormat _fileFormat;
|
||||
//pcm data related stuff
|
||||
size_t _pcmDataSize;
|
||||
ALenum _alBufferFormat;
|
||||
|
||||
int _channels;
|
||||
ALuint _sampleRate;
|
||||
size_t _bytesPerFrame;
|
||||
ALenum _format;
|
||||
ALsizei _sampleRate;
|
||||
float _duration;
|
||||
uint32_t _totalFrames;
|
||||
uint32_t _framesRead;
|
||||
|
||||
/*Cache related stuff;
|
||||
* Cache pcm data when sizeInBytes less than PCMDATA_CACHEMAXSIZE
|
||||
*/
|
||||
ALuint _alBufferId;
|
||||
void* _pcmData;
|
||||
size_t _bytesOfRead;
|
||||
char* _pcmData;
|
||||
|
||||
/*Queue buffer related stuff
|
||||
* Streaming in OpenAL when sizeInBytes greater then PCMDATA_CACHEMAXSIZE
|
||||
*/
|
||||
char* _queBuffers[QUEUEBUFFER_NUM];
|
||||
ALsizei _queBufferSize[QUEUEBUFFER_NUM];
|
||||
int _queBufferFrames;
|
||||
int _queBufferBytes;
|
||||
uint32_t _queBufferFrames;
|
||||
|
||||
bool _alBufferReady;
|
||||
bool _loadFail;
|
||||
std::mutex _callbackMutex;
|
||||
std::vector< std::function<void()> > _callbacks;
|
||||
std::mutex _playCallbackMutex;
|
||||
std::vector< std::function<void()> > _playCallbacks;
|
||||
|
||||
// loadCallbacks doesn't need mutex since it's invoked only in Cocos thread.
|
||||
std::vector< std::function<void(bool)> > _loadCallbacks;
|
||||
|
||||
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 AudioPlayer;
|
||||
} ;
|
||||
};
|
||||
|
||||
}
|
||||
NS_CC_END
|
||||
|
||||
#endif // __AUDIO_CACHE_H_
|
||||
#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
|
||||
|
||||
|
@ -40,14 +40,74 @@
|
|||
#include "base/CCDirector.h"
|
||||
#include "base/CCScheduler.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::experimental;
|
||||
|
||||
static ALCdevice *s_ALDevice = nullptr;
|
||||
static ALCcontext *s_ALContext = nullptr;
|
||||
static bool MPG123_LAZYINIT = true;
|
||||
|
||||
AudioEngineImpl::AudioEngineImpl()
|
||||
: _lazyInitLoop(true)
|
||||
|
@ -67,30 +127,31 @@ AudioEngineImpl::~AudioEngineImpl()
|
|||
alcDestroyContext(s_ALContext);
|
||||
s_ALContext = nullptr;
|
||||
}
|
||||
|
||||
if (s_ALDevice) {
|
||||
alcCloseDevice(s_ALDevice);
|
||||
s_ALDevice = nullptr;
|
||||
}
|
||||
|
||||
mpg123_exit();
|
||||
MPG123_LAZYINIT = true;
|
||||
AudioDecoderManager::destroy();
|
||||
}
|
||||
|
||||
bool AudioEngineImpl::init()
|
||||
{
|
||||
bool ret = false;
|
||||
do{
|
||||
s_ALDevice = alcOpenDevice(NULL);
|
||||
s_ALDevice = alcOpenDevice(nullptr);
|
||||
|
||||
if (s_ALDevice) {
|
||||
auto alError = alGetError();
|
||||
s_ALContext = alcCreateContext(s_ALDevice, NULL);
|
||||
s_ALContext = alcCreateContext(s_ALDevice, nullptr);
|
||||
alcMakeContextCurrent(s_ALContext);
|
||||
|
||||
alGenSources(MAX_AUDIOINSTANCES, _alSources);
|
||||
alError = alGetError();
|
||||
if(alError != AL_NO_ERROR){
|
||||
ALOGE("%s:generating sources fail! error = %x\n", __FUNCTION__, alError);
|
||||
if(alError != AL_NO_ERROR)
|
||||
{
|
||||
ALOGE("%s:generating sources failed! error = %x\n", __FUNCTION__, alError);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -98,7 +159,9 @@ bool AudioEngineImpl::init()
|
|||
_alSourceUsed[_alSources[i]] = false;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
_scheduler = Director::getInstance()->getScheduler();
|
||||
ret = AudioDecoderManager::init();
|
||||
ALOGI("OpenAL was initialized successfully!");
|
||||
}
|
||||
}while (false);
|
||||
|
||||
|
@ -109,106 +172,80 @@ AudioCache* AudioEngineImpl::preload(const std::string& filePath, std::function<
|
|||
{
|
||||
AudioCache* audioCache = nullptr;
|
||||
|
||||
do
|
||||
{
|
||||
auto it = _audioCaches.find(filePath);
|
||||
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;
|
||||
}
|
||||
|
||||
if (it == _audioCaches.end()) {
|
||||
audioCache = &_audioCaches[filePath];
|
||||
audioCache->_fileFormat = fileFormat;
|
||||
|
||||
audioCache->_fileFullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
|
||||
AudioEngine::addTask(std::bind(&AudioCache::readDataTask, audioCache));
|
||||
} while (false);
|
||||
|
||||
if (callback)
|
||||
unsigned int cacheId = audioCache->_id;
|
||||
auto isCacheDestroyed = audioCache->_isDestroyed;
|
||||
AudioEngine::addTask([audioCache, cacheId, isCacheDestroyed](){
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(false);
|
||||
}
|
||||
}
|
||||
|
||||
return audioCache;
|
||||
}
|
||||
|
||||
int AudioEngineImpl::play2d(const std::string &filePath ,bool loop ,float volume)
|
||||
{
|
||||
bool availableSourceExist = false;
|
||||
ALuint alSource;
|
||||
if (s_ALDevice == nullptr) {
|
||||
return AudioEngine::INVALID_AUDIO_ID;
|
||||
}
|
||||
|
||||
bool sourceFlag = false;
|
||||
ALuint alSource = 0;
|
||||
for (int i = 0; i < MAX_AUDIOINSTANCES; ++i) {
|
||||
alSource = _alSources[i];
|
||||
|
||||
if ( !_alSourceUsed[alSource]) {
|
||||
availableSourceExist = true;
|
||||
sourceFlag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!availableSourceExist){
|
||||
if(!sourceFlag){
|
||||
return AudioEngine::INVALID_AUDIO_ID;
|
||||
}
|
||||
|
||||
AudioCache* audioCache = preload(filePath, nullptr);
|
||||
if (audioCache == nullptr)
|
||||
{
|
||||
auto player = new (std::nothrow) AudioPlayer;
|
||||
if (player == nullptr) {
|
||||
return AudioEngine::INVALID_AUDIO_ID;
|
||||
}
|
||||
|
||||
auto player = &_audioPlayers[_currentAudioID];
|
||||
player->_alSource = alSource;
|
||||
player->_loop = loop;
|
||||
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;
|
||||
|
||||
audioCache->addPlayCallback(std::bind(&AudioEngineImpl::_play2d,this,audioCache,_currentAudioID));
|
||||
|
||||
if (_lazyInitLoop) {
|
||||
_lazyInitLoop = false;
|
||||
|
||||
auto scheduler = cocos2d::Director::getInstance()->getScheduler();
|
||||
scheduler->schedule(schedule_selector(AudioEngineImpl::update), this, 0.05f, false);
|
||||
_scheduler->schedule(CC_SCHEDULE_SELECTOR(AudioEngineImpl::update), this, 0.05f, false);
|
||||
}
|
||||
|
||||
return _currentAudioID++;
|
||||
|
@ -216,71 +253,76 @@ int AudioEngineImpl::play2d(const std::string &filePath ,bool loop ,float volume
|
|||
|
||||
void AudioEngineImpl::_play2d(AudioCache *cache, int audioID)
|
||||
{
|
||||
if(cache->_alBufferReady){
|
||||
//Note: It may bn in sub thread or main thread :(
|
||||
if (!*cache->_isDestroyed && cache->_state == AudioCache::State::READY)
|
||||
{
|
||||
_threadMutex.lock();
|
||||
auto playerIt = _audioPlayers.find(audioID);
|
||||
if (playerIt != _audioPlayers.end()) {
|
||||
if (playerIt->second.play2d(cache)) {
|
||||
if (playerIt != _audioPlayers.end() && playerIt->second->play2d()) {
|
||||
_scheduler->performFunctionInCocosThread([audioID](){
|
||||
|
||||
if (AudioEngine::_audioIDInfoMap.find(audioID) != AudioEngine::_audioIDInfoMap.end()) {
|
||||
AudioEngine::_audioIDInfoMap[audioID].state = AudioEngine::AudioState::PLAYING;
|
||||
}
|
||||
else{
|
||||
_threadMutex.lock();
|
||||
_toRemoveAudioIDs.push_back(audioID);
|
||||
});
|
||||
}
|
||||
_threadMutex.unlock();
|
||||
}
|
||||
else
|
||||
{
|
||||
ALOGD("AudioEngineImpl::_play2d, cache was destroyed or not ready!");
|
||||
auto iter = _audioPlayers.find(audioID);
|
||||
if (iter != _audioPlayers.end())
|
||||
{
|
||||
iter->second->_removeByAudioEngine = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
_threadMutex.lock();
|
||||
_toRemoveCaches.push_back(cache);
|
||||
_toRemoveAudioIDs.push_back(audioID);
|
||||
_threadMutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngineImpl::setVolume(int audioID,float volume)
|
||||
{
|
||||
auto& player = _audioPlayers[audioID];
|
||||
player._volume = volume;
|
||||
auto player = _audioPlayers[audioID];
|
||||
player->_volume = volume;
|
||||
|
||||
if (player._ready) {
|
||||
alSourcef(_audioPlayers[audioID]._alSource, AL_GAIN, volume);
|
||||
if (player->_ready) {
|
||||
alSourcef(_audioPlayers[audioID]->_alSource, AL_GAIN, volume);
|
||||
|
||||
auto error = alGetError();
|
||||
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)
|
||||
{
|
||||
auto& player = _audioPlayers[audioID];
|
||||
auto player = _audioPlayers[audioID];
|
||||
|
||||
if (player._ready) {
|
||||
if (player._streamingSource) {
|
||||
player.setLoop(loop);
|
||||
if (player->_ready) {
|
||||
if (player->_streamingSource) {
|
||||
player->setLoop(loop);
|
||||
} else {
|
||||
if (loop) {
|
||||
alSourcei(player._alSource, AL_LOOPING, AL_TRUE);
|
||||
alSourcei(player->_alSource, AL_LOOPING, AL_TRUE);
|
||||
} else {
|
||||
alSourcei(player._alSource, AL_LOOPING, AL_FALSE);
|
||||
alSourcei(player->_alSource, AL_LOOPING, AL_FALSE);
|
||||
}
|
||||
|
||||
auto error = alGetError();
|
||||
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 {
|
||||
player._loop = loop;
|
||||
player->_loop = loop;
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioEngineImpl::pause(int audioID)
|
||||
{
|
||||
bool ret = true;
|
||||
alSourcePause(_audioPlayers[audioID]._alSource);
|
||||
alSourcePause(_audioPlayers[audioID]->_alSource);
|
||||
|
||||
auto error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
|
@ -294,7 +336,7 @@ bool AudioEngineImpl::pause(int audioID)
|
|||
bool AudioEngineImpl::resume(int audioID)
|
||||
{
|
||||
bool ret = true;
|
||||
alSourcePlay(_audioPlayers[audioID]._alSource);
|
||||
alSourcePlay(_audioPlayers[audioID]->_alSource);
|
||||
|
||||
auto error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
|
@ -305,66 +347,34 @@ bool AudioEngineImpl::resume(int audioID)
|
|||
return ret;
|
||||
}
|
||||
|
||||
bool AudioEngineImpl::stop(int audioID)
|
||||
void AudioEngineImpl::stop(int audioID)
|
||||
{
|
||||
bool ret = true;
|
||||
auto& player = _audioPlayers[audioID];
|
||||
if (player._ready) {
|
||||
alSourceStop(player._alSource);
|
||||
|
||||
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;
|
||||
auto player = _audioPlayers[audioID];
|
||||
player->destroy();
|
||||
//Note: Don't set the flag to false here, it should be set in 'update' function.
|
||||
// Otherwise, the state got from alSourceState may be wrong
|
||||
// _alSourceUsed[player->_alSource] = false;
|
||||
}
|
||||
|
||||
void AudioEngineImpl::stopAll()
|
||||
{
|
||||
for(int index = 0; index < MAX_AUDIOINSTANCES; ++index)
|
||||
for(auto&& player : _audioPlayers)
|
||||
{
|
||||
alSourceStop(_alSources[index]);
|
||||
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);
|
||||
}
|
||||
player.second->destroy();
|
||||
}
|
||||
//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)
|
||||
{
|
||||
auto& player = _audioPlayers[audioID];
|
||||
if(player._ready){
|
||||
return player._audioCache->_duration;
|
||||
auto player = _audioPlayers[audioID];
|
||||
if(player->_ready){
|
||||
return player->_audioCache->_duration;
|
||||
} else {
|
||||
return AudioEngine::TIME_UNKNOWN;
|
||||
}
|
||||
|
@ -373,12 +383,12 @@ float AudioEngineImpl::getDuration(int audioID)
|
|||
float AudioEngineImpl::getCurrentTime(int audioID)
|
||||
{
|
||||
float ret = 0.0f;
|
||||
auto& player = _audioPlayers[audioID];
|
||||
if(player._ready){
|
||||
if (player._streamingSource) {
|
||||
ret = player.getTime();
|
||||
auto player = _audioPlayers[audioID];
|
||||
if(player->_ready){
|
||||
if (player->_streamingSource) {
|
||||
ret = player->getTime();
|
||||
} else {
|
||||
alGetSourcef(player._alSource, AL_SEC_OFFSET, &ret);
|
||||
alGetSourcef(player->_alSource, AL_SEC_OFFSET, &ret);
|
||||
|
||||
auto error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
|
@ -393,23 +403,29 @@ float AudioEngineImpl::getCurrentTime(int audioID)
|
|||
bool AudioEngineImpl::setCurrentTime(int audioID, float time)
|
||||
{
|
||||
bool ret = false;
|
||||
auto& player = _audioPlayers[audioID];
|
||||
auto player = _audioPlayers[audioID];
|
||||
|
||||
do {
|
||||
if (!player._ready) {
|
||||
if (!player->_ready) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (player._streamingSource) {
|
||||
ret = player.setTime(time);
|
||||
if (player->_streamingSource) {
|
||||
ret = player->setTime(time);
|
||||
break;
|
||||
}
|
||||
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();
|
||||
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;
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
_audioPlayers[audioID]._finishCallbak = callback;
|
||||
_audioPlayers[audioID]->_finishCallbak = callback;
|
||||
}
|
||||
|
||||
void AudioEngineImpl::update(float dt)
|
||||
{
|
||||
ALint sourceState;
|
||||
int audioID;
|
||||
AudioPlayer* player;
|
||||
|
||||
if (_threadMutex.try_lock()) {
|
||||
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();
|
||||
}
|
||||
// ALOGV("AudioPlayer count: %d", (int)_audioPlayers.size());
|
||||
|
||||
for (auto it = _audioPlayers.begin(); it != _audioPlayers.end(); ) {
|
||||
audioID = it->first;
|
||||
auto& player = it->second;
|
||||
alGetSourcei(player._alSource, AL_SOURCE_STATE, &sourceState);
|
||||
player = it->second;
|
||||
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);
|
||||
_threadMutex.unlock();
|
||||
delete player;
|
||||
}
|
||||
else if (player._ready && sourceState == AL_STOPPED) {
|
||||
_alSourceUsed[player._alSource] = false;
|
||||
if (player._finishCallbak) {
|
||||
else if (player->_ready && sourceState == AL_STOPPED) {
|
||||
|
||||
_alSourceUsed[player->_alSource] = false;
|
||||
if (player->_finishCallbak) {
|
||||
auto& audioInfo = AudioEngine::_audioIDInfoMap[audioID];
|
||||
player._finishCallbak(audioID, *audioInfo.filePath);
|
||||
player->_finishCallbak(audioID, *audioInfo.filePath); //FIXME: callback will delay 50ms
|
||||
}
|
||||
|
||||
AudioEngine::remove(audioID);
|
||||
|
||||
if (player._streamingSource)
|
||||
{
|
||||
player._ready = false;
|
||||
player.notifyExitThread();
|
||||
++it;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete player;
|
||||
_threadMutex.lock();
|
||||
it = _audioPlayers.erase(it);
|
||||
}
|
||||
_threadMutex.unlock();
|
||||
}
|
||||
else{
|
||||
++it;
|
||||
|
@ -492,9 +483,7 @@ void AudioEngineImpl::update(float dt)
|
|||
|
||||
if(_audioPlayers.empty()){
|
||||
_lazyInitLoop = true;
|
||||
|
||||
auto scheduler = cocos2d::Director::getInstance()->getScheduler();
|
||||
scheduler->unschedule(schedule_selector(AudioEngineImpl::update), this);
|
||||
_scheduler->unschedule(CC_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
|
||||
|
||||
|
@ -35,7 +35,10 @@
|
|||
#include "audio/win32/AudioPlayer.h"
|
||||
|
||||
NS_CC_BEGIN
|
||||
namespace experimental{
|
||||
|
||||
class Scheduler;
|
||||
|
||||
namespace experimental {
|
||||
#define MAX_AUDIOINSTANCES 32
|
||||
|
||||
class CC_DLL AudioEngineImpl : public cocos2d::Ref
|
||||
|
@ -50,7 +53,7 @@ public:
|
|||
void setLoop(int audioID, bool loop);
|
||||
bool pause(int audioID);
|
||||
bool resume(int audioID);
|
||||
bool stop(int audioID);
|
||||
void stop(int audioID);
|
||||
void stopAll();
|
||||
float getDuration(int audioID);
|
||||
float getCurrentTime(int audioID);
|
||||
|
@ -60,7 +63,6 @@ public:
|
|||
void uncache(const std::string& filePath);
|
||||
void uncacheAll();
|
||||
AudioCache* preload(const std::string& filePath, std::function<void(bool)> callback);
|
||||
|
||||
void update(float dt);
|
||||
|
||||
private:
|
||||
|
@ -75,17 +77,13 @@ private:
|
|||
std::unordered_map<std::string, AudioCache> _audioCaches;
|
||||
|
||||
//audioID,AudioInfo
|
||||
std::unordered_map<int, AudioPlayer> _audioPlayers;
|
||||
|
||||
std::unordered_map<int, AudioPlayer*> _audioPlayers;
|
||||
std::mutex _threadMutex;
|
||||
|
||||
std::vector<AudioCache*> _toRemoveCaches;
|
||||
std::vector<int> _toRemoveAudioIDs;
|
||||
|
||||
bool _lazyInitLoop;
|
||||
|
||||
int _currentAudioID;
|
||||
|
||||
Scheduler* _scheduler;
|
||||
};
|
||||
}
|
||||
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
|
||||
|
||||
|
@ -29,231 +29,246 @@
|
|||
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
|
||||
#include "audio/win32/AudioPlayer.h"
|
||||
#include "audio/win32/AudioCache.h"
|
||||
#include "base/CCConsole.h"
|
||||
#include "platform/CCFileUtils.h"
|
||||
#include "mpg123.h"
|
||||
#include "vorbis/codec.h"
|
||||
#include "vorbis/vorbisfile.h"
|
||||
#include "audio/win32/AudioDecoderManager.h"
|
||||
#include "audio/win32/Audiodecoder.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;
|
||||
|
||||
AudioPlayer::AudioPlayer()
|
||||
: _exitThread(false)
|
||||
, _timeDirty(false)
|
||||
, _streamingSource(false)
|
||||
, _currTime(0.0f)
|
||||
, _finishCallbak(nullptr)
|
||||
, _ready(false)
|
||||
, _audioCache(nullptr)
|
||||
, _readForRemove(false)
|
||||
{
|
||||
|
||||
namespace {
|
||||
unsigned int __idIndex = 0;
|
||||
}
|
||||
|
||||
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;
|
||||
_timeDirty = player._timeDirty;
|
||||
_streamingSource = player._streamingSource;
|
||||
_currTime = player._currTime;
|
||||
_finishCallbak = player._finishCallbak;
|
||||
_ready = player._ready;
|
||||
_audioCache = player._audioCache;
|
||||
_readForRemove = player._readForRemove;
|
||||
memset(_bufferIds, 0, sizeof(_bufferIds));
|
||||
}
|
||||
|
||||
AudioPlayer::~AudioPlayer()
|
||||
{
|
||||
if (_audioCache && _audioCache->_queBufferFrames > 0) {
|
||||
alDeleteBuffers(QUEUEBUFFER_NUM, _bufferIds);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
ALOGVV("~AudioPlayer() (%p), id=%u", this, _id);
|
||||
destroy();
|
||||
|
||||
if (_streamingSource)
|
||||
{
|
||||
_rotateBufferThread = std::thread(&AudioPlayer::rotateBufferThread, this, _audioCache->_queBufferFrames * QUEUEBUFFER_NUM + 1);
|
||||
_rotateBufferThread.detach();
|
||||
alDeleteBuffers(3, _bufferIds);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
alSourcePlay(_alSource);
|
||||
alGenBuffers(3, _bufferIds);
|
||||
|
||||
auto alError = alGetError();
|
||||
if (alError != AL_NO_ERROR) {
|
||||
ALOGE("%s:alSourcePlay error code:%x\n", __FUNCTION__, alError);
|
||||
return false;
|
||||
if (alError == AL_NO_ERROR)
|
||||
{
|
||||
for (int index = 0; index < QUEUEBUFFER_NUM; ++index)
|
||||
{
|
||||
alBufferData(_bufferIds[index], _audioCache->_format, _audioCache->_queBuffers[index], _audioCache->_queBufferSize[index], _audioCache->_sampleRate);
|
||||
}
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
}
|
||||
else
|
||||
{
|
||||
ALOGE("%s:alGenBuffers error code:%x", __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;
|
||||
ret = true;
|
||||
} while (false);
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
_removeByAudioEngine = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
_play2dMutex.unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
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 bufferProcessed = 0;
|
||||
mpg123_handle* mpg123handle = nullptr;
|
||||
OggVorbis_File* vorbisFile = nullptr;
|
||||
bool needToExitThread = false;
|
||||
|
||||
std::function<int(char*, int)> fileReader = nullptr;
|
||||
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) {
|
||||
while (!_isDestroyed) {
|
||||
alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState);
|
||||
if (sourceState == AL_PLAYING) {
|
||||
_ready = true;
|
||||
|
||||
alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed);
|
||||
while (bufferProcessed > 0) {
|
||||
bufferProcessed--;
|
||||
if (_timeDirty) {
|
||||
_timeDirty = false;
|
||||
offsetFrame = _currTime * _audioCache->_sampleRate;
|
||||
fileSeeker(offsetFrame);
|
||||
offsetFrame = _currTime * decoder->getSampleRate();
|
||||
decoder->seek(offsetFrame);
|
||||
}
|
||||
else {
|
||||
_currTime += QUEUEBUFFER_TIME_STEP;
|
||||
|
@ -266,65 +281,49 @@ void AudioPlayer::rotateBufferThread(int offsetFrame)
|
|||
}
|
||||
}
|
||||
|
||||
int readRet = readPcmData(tmpBuffer, _audioCache->_queBufferBytes, fileReader);
|
||||
if (readRet <= 0) {
|
||||
framesRead = decoder->readFixedFrames(framesToRead, tmpBuffer);
|
||||
|
||||
if (framesRead == 0) {
|
||||
if (_loop) {
|
||||
fileSeeker(0);
|
||||
readRet = readPcmData(tmpBuffer, _audioCache->_queBufferBytes, fileReader);
|
||||
}
|
||||
else {
|
||||
_exitThread = true;
|
||||
decoder->seek(0);
|
||||
framesRead = decoder->readFixedFrames(framesToRead, tmpBuffer);
|
||||
} else {
|
||||
needToExitThread = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_audioCache->_bytesOfRead += readRet;
|
||||
}
|
||||
|
||||
//ALOGV("readRet: %d, queBufferBytes: %d", (int)readRet, _audioCache->_queBufferBytes);
|
||||
ALuint bid;
|
||||
alSourceUnqueueBuffers(_alSource, 1, &bid);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
alBufferData(bid, _audioCache->_alBufferFormat, tmpBuffer, readRet, _audioCache->_sampleRate);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
alBufferData(bid, _audioCache->_format, tmpBuffer, framesRead * decoder->getBytesPerFrame(), decoder->getSampleRate());
|
||||
alSourceQueueBuffers(_alSource, 1, &bid);
|
||||
CHECK_AL_ERROR_DEBUG();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lk(_sleepMutex);
|
||||
if (_exitThread)
|
||||
{
|
||||
ALOGV("thread exit...");
|
||||
if (_isDestroyed || needToExitThread) {
|
||||
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:
|
||||
ov_clear(vorbisFile);
|
||||
delete vorbisFile;
|
||||
break;
|
||||
case AudioCache::FileFormat::MP3:
|
||||
mpg123_close(mpg123handle);
|
||||
mpg123_delete(mpg123handle);
|
||||
break;
|
||||
case AudioCache::FileFormat::UNKNOWN:
|
||||
default:
|
||||
break;
|
||||
decoder->close();
|
||||
}
|
||||
AudioDecoderManager::destroyDecoder(decoder);
|
||||
free(tmpBuffer);
|
||||
_readForRemove = true;
|
||||
_isRotateThreadExited = true;
|
||||
ALOGV("%s exited.\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
bool AudioPlayer::setLoop(bool loop)
|
||||
{
|
||||
if (!_exitThread ) {
|
||||
if (!_isDestroyed ) {
|
||||
_loop = loop;
|
||||
return true;
|
||||
}
|
||||
|
@ -334,9 +333,11 @@ bool AudioPlayer::setLoop(bool loop)
|
|||
|
||||
bool AudioPlayer::setTime(float time)
|
||||
{
|
||||
if (!_exitThread && time >= 0.0f && time < _audioCache->_duration) {
|
||||
if (!_isDestroyed && time >= 0.0f && time < _audioCache->_duration) {
|
||||
|
||||
_currTime = time;
|
||||
_timeDirty = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -21,15 +21,16 @@
|
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "platform/CCPlatformConfig.h"
|
||||
|
||||
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
|
||||
|
||||
#ifndef __AUDIO_PLAYER_H_
|
||||
#define __AUDIO_PLAYER_H_
|
||||
|
||||
#include <string>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#ifdef OPENAL_PLAIN_INCLUDES
|
||||
#include <al.h>
|
||||
|
@ -37,7 +38,6 @@
|
|||
#include <AL/al.h>
|
||||
#endif
|
||||
#include "platform/CCPlatformMacros.h"
|
||||
#include "audio/win32/AudioCache.h"
|
||||
|
||||
NS_CC_BEGIN
|
||||
namespace experimental{
|
||||
|
@ -49,19 +49,19 @@ class CC_DLL AudioPlayer
|
|||
{
|
||||
public:
|
||||
AudioPlayer();
|
||||
AudioPlayer(const AudioPlayer&);
|
||||
~AudioPlayer();
|
||||
|
||||
void destroy();
|
||||
|
||||
//queue buffer related stuff
|
||||
bool setTime(float time);
|
||||
float getTime() { return _currTime;}
|
||||
bool setLoop(bool loop);
|
||||
void notifyExitThread();
|
||||
|
||||
protected:
|
||||
void setCache(AudioCache* cache);
|
||||
void rotateBufferThread(int offsetFrame);
|
||||
bool play2d(AudioCache* cache);
|
||||
int readPcmData(char* buffer, int bufferSize, const std::function<int/*readBytes*/(char* /*buf*/, int /*bytesToRead*/)>& fileReader);
|
||||
bool play2d();
|
||||
|
||||
AudioCache* _audioCache;
|
||||
|
||||
|
@ -69,25 +69,30 @@ protected:
|
|||
bool _loop;
|
||||
std::function<void (int, const std::string &)> _finishCallbak;
|
||||
|
||||
bool _isDestroyed;
|
||||
bool _removeByAudioEngine;
|
||||
bool _ready;
|
||||
ALuint _alSource;
|
||||
|
||||
//play by circular buffer
|
||||
float _currTime;
|
||||
bool _timeDirty;
|
||||
bool _streamingSource;
|
||||
ALuint _bufferIds[QUEUEBUFFER_NUM];
|
||||
std::thread _rotateBufferThread;
|
||||
std::mutex _sleepMutex;
|
||||
ALuint _bufferIds[3];
|
||||
std::thread* _rotateBufferThread;
|
||||
std::condition_variable _sleepCondition;
|
||||
bool _exitThread;
|
||||
bool _readForRemove;
|
||||
std::mutex _sleepMutex;
|
||||
bool _timeDirty;
|
||||
bool _isRotateThreadExited;
|
||||
|
||||
std::mutex _play2dMutex;
|
||||
|
||||
unsigned int _id;
|
||||
|
||||
friend class AudioEngineImpl;
|
||||
};
|
||||
|
||||
}
|
||||
NS_CC_END
|
||||
#endif // __AUDIO_PLAYER_H_
|
||||
|
||||
#endif //CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
|
||||
|
||||
|
|
|
@ -18,12 +18,16 @@ public:
|
|||
void Close();
|
||||
|
||||
/**
|
||||
@brief Play sound file
|
||||
@param pFileName Sound's file name,include the file path.
|
||||
@param nTimes Play mode£¬default value is 1,paly once
|
||||
@brief Open audio file
|
||||
@param pFileName The file name which includes the file path.
|
||||
@param uId The audio ID
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue