fixed #10482: New AudioEngine class can't play large ogg file on Win32. (#16303)

* fixed #10482: New AudioEngine class can't play large ogg file on Win32.

* [win32] Small logic fix in AudioPlayer::readPcmData.
This commit is contained in:
James Chen 2016-08-04 09:53:30 +08:00 committed by minggo
parent 6eaf15a2c2
commit cc9871b71d
6 changed files with 186 additions and 62 deletions

View File

@ -21,6 +21,9 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
****************************************************************************/ ****************************************************************************/
#define LOG_TAG "AudioCache"
#include "platform/CCPlatformConfig.h" #include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 #if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
@ -35,10 +38,68 @@
#include "base/CCDirector.h" #include "base/CCDirector.h"
#include "base/CCScheduler.h" #include "base/CCScheduler.h"
#include <windows.h>
#define PCMDATA_CACHEMAXSIZE 2621440 #define PCMDATA_CACHEMAXSIZE 2621440
using namespace cocos2d::experimental; using namespace cocos2d::experimental;
//FIXME: Move _winLog, winLog to a separated file
static void _winLog(const char *format, va_list args)
{
static const int MAX_LOG_LENGTH = 16 * 1024;
int bufferSize = MAX_LOG_LENGTH;
char* buf = nullptr;
do
{
buf = new (std::nothrow) char[bufferSize];
if (buf == nullptr)
return; // not enough memory
int ret = vsnprintf(buf, bufferSize - 3, format, args);
if (ret < 0)
{
bufferSize *= 2;
delete[] buf;
}
else
break;
} while (true);
strcat(buf, "\n");
int pos = 0;
int len = strlen(buf);
char tempBuf[MAX_LOG_LENGTH + 1] = { 0 };
WCHAR wszBuf[MAX_LOG_LENGTH + 1] = { 0 };
do
{
std::copy(buf + pos, buf + pos + MAX_LOG_LENGTH, tempBuf);
tempBuf[MAX_LOG_LENGTH] = 0;
MultiByteToWideChar(CP_UTF8, 0, tempBuf, -1, wszBuf, sizeof(wszBuf));
OutputDebugStringW(wszBuf);
pos += MAX_LOG_LENGTH;
} while (pos < len);
delete[] buf;
}
void audioLog(const char * format, ...)
{
va_list args;
va_start(args, format);
_winLog(format, args);
va_end(args);
}
AudioCache::AudioCache() AudioCache::AudioCache()
: _pcmData(nullptr) : _pcmData(nullptr)
, _pcmDataSize(0) , _pcmDataSize(0)
@ -100,7 +161,7 @@ void AudioCache::readDataTask()
vf = new OggVorbis_File; vf = new OggVorbis_File;
int openCode; int openCode;
if (openCode = ov_fopen(FileUtils::getInstance()->getSuitableFOpen(_fileFullPath).c_str(), vf)){ if (openCode = ov_fopen(FileUtils::getInstance()->getSuitableFOpen(_fileFullPath).c_str(), vf)){
log("Input does not appear to be an Ogg bitstream: %s. Code: 0x%x\n", _fileFullPath.c_str(), openCode); ALOGE("Input does not appear to be an Ogg bitstream: %s. Code: 0x%x\n", _fileFullPath.c_str(), openCode);
goto ExitThread; goto ExitThread;
} }
@ -119,13 +180,13 @@ void AudioCache::readDataTask()
int error = MPG123_OK; int error = MPG123_OK;
mpg123handle = mpg123_new(nullptr, &error); mpg123handle = mpg123_new(nullptr, &error);
if (!mpg123handle){ if (!mpg123handle){
log("Basic setup goes wrong: %s", mpg123_plain_strerror(error)); ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
goto ExitThread; goto ExitThread;
} }
if (mpg123_open(mpg123handle,_fileFullPath.c_str()) != MPG123_OK || if (mpg123_open(mpg123handle,_fileFullPath.c_str()) != MPG123_OK ||
mpg123_getformat(mpg123handle, &rate, &_channels, &_mp3Encoding) != MPG123_OK) { mpg123_getformat(mpg123handle, &rate, &_channels, &_mp3Encoding) != MPG123_OK) {
log("Trouble with mpg123: %s\n", mpg123_strerror(mpg123handle) ); ALOGE("Trouble with mpg123: %s\n", mpg123_strerror(mpg123handle) );
goto ExitThread; goto ExitThread;
} }

View File

@ -41,6 +41,38 @@
#define QUEUEBUFFER_NUM 5 #define QUEUEBUFFER_NUM 5
#define QUEUEBUFFER_TIME_STEP 0.1f #define QUEUEBUFFER_TIME_STEP 0.1f
// log, CCLOG aren't threadsafe, since we uses sub threads for parsing pcm data, threadsafe log output
// is needed. Define the following macros (ALOGV, ALOGD, ALOGI, ALOGW, ALOGE) for threadsafe log output.
//FIXME:Move the definition of the following macros to a separated file.
void audioLog(const char * format, ...);
#define QUOTEME_(x) #x
#define QUOTEME(x) QUOTEME_(x)
#if defined(COCOS2D_DEBUG) && COCOS2D_DEBUG > 0
#define ALOGV(fmt, ...) audioLog("V/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
#else
#define ALOGV(fmt, ...) do {} while(false)
#endif
#define ALOGD(fmt, ...) audioLog("D/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
#define ALOGI(fmt, ...) audioLog("I/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
#define ALOGW(fmt, ...) audioLog("W/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
#define ALOGE(fmt, ...) audioLog("E/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
#if defined(COCOS2D_DEBUG) && COCOS2D_DEBUG > 0
#define CHECK_AL_ERROR_DEBUG() \
do { \
GLenum __error = alGetError(); \
if (__error) { \
ALOGE("OpenAL error 0x%04X in %s %s %d\n", __error, __FILE__, __FUNCTION__, __LINE__); \
} \
} while (false)
#else
#define CHECK_AL_ERROR_DEBUG()
#endif
NS_CC_BEGIN NS_CC_BEGIN
namespace experimental{ namespace experimental{

View File

@ -21,6 +21,8 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
****************************************************************************/ ****************************************************************************/
#define LOG_TAG "AudioEngine-Win32"
#include "platform/CCPlatformConfig.h" #include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 #if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
@ -88,7 +90,7 @@ bool AudioEngineImpl::init()
alGenSources(MAX_AUDIOINSTANCES, _alSources); alGenSources(MAX_AUDIOINSTANCES, _alSources);
alError = alGetError(); alError = alGetError();
if(alError != AL_NO_ERROR){ if(alError != AL_NO_ERROR){
log("%s:generating sources fail! error = %x\n", __FUNCTION__, alError); ALOGE("%s:generating sources fail! error = %x\n", __FUNCTION__, alError);
break; break;
} }
@ -140,14 +142,14 @@ AudioCache* AudioEngineImpl::preload(const std::string& filePath, std::function<
} }
else else
{ {
log("Basic setup goes wrong: %s", mpg123_plain_strerror(error)); ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
break; break;
} }
} }
} }
else else
{ {
log("Unsupported media type file: %s\n", filePath.c_str()); ALOGE("Unsupported media type file: %s\n", filePath.c_str());
break; break;
} }
@ -245,7 +247,7 @@ void AudioEngineImpl::setVolume(int audioID,float volume)
auto error = alGetError(); auto error = alGetError();
if (error != AL_NO_ERROR) { if (error != AL_NO_ERROR) {
log("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error); ALOGE("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error);
} }
} }
} }
@ -266,7 +268,7 @@ void AudioEngineImpl::setLoop(int audioID, bool loop)
auto error = alGetError(); auto error = alGetError();
if (error != AL_NO_ERROR) { if (error != AL_NO_ERROR) {
log("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error); ALOGE("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error);
} }
} }
} }
@ -283,7 +285,7 @@ bool AudioEngineImpl::pause(int audioID)
auto error = alGetError(); auto error = alGetError();
if (error != AL_NO_ERROR) { if (error != AL_NO_ERROR) {
ret = false; ret = false;
log("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error); ALOGE("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error);
} }
return ret; return ret;
@ -297,7 +299,7 @@ bool AudioEngineImpl::resume(int audioID)
auto error = alGetError(); auto error = alGetError();
if (error != AL_NO_ERROR) { if (error != AL_NO_ERROR) {
ret = false; ret = false;
log("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error); ALOGE("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error);
} }
return ret; return ret;
@ -313,7 +315,7 @@ bool AudioEngineImpl::stop(int audioID)
auto error = alGetError(); auto error = alGetError();
if (error != AL_NO_ERROR) { if (error != AL_NO_ERROR) {
ret = false; ret = false;
log("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error); ALOGE("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error);
} }
} }
@ -380,7 +382,7 @@ float AudioEngineImpl::getCurrentTime(int audioID)
auto error = alGetError(); auto error = alGetError();
if (error != AL_NO_ERROR) { if (error != AL_NO_ERROR) {
log("%s, audio id:%d,error code:%x", __FUNCTION__,audioID,error); ALOGE("%s, audio id:%d,error code:%x", __FUNCTION__,audioID,error);
} }
} }
} }
@ -407,7 +409,7 @@ bool AudioEngineImpl::setCurrentTime(int audioID, float time)
auto error = alGetError(); auto error = alGetError();
if (error != AL_NO_ERROR) { if (error != AL_NO_ERROR) {
log("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error); ALOGE("%s: audio id = %d, error = %x\n", __FUNCTION__,audioID,error);
} }
ret = true; ret = true;
} }

View File

@ -21,6 +21,9 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
****************************************************************************/ ****************************************************************************/
#define LOG_TAG "AudioPlayer"
#include "platform/CCPlatformConfig.h" #include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 #if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
@ -109,7 +112,7 @@ bool AudioPlayer::play2d(AudioCache* cache)
alSourceQueueBuffers(_alSource, QUEUEBUFFER_NUM, _bufferIds); alSourceQueueBuffers(_alSource, QUEUEBUFFER_NUM, _bufferIds);
} }
else { else {
log("%s:alGenBuffers error code:%x", __FUNCTION__,alError); ALOGE("%s:alGenBuffers error code:%x", __FUNCTION__,alError);
return false; return false;
} }
} }
@ -125,7 +128,7 @@ bool AudioPlayer::play2d(AudioCache* cache)
auto alError = alGetError(); auto alError = alGetError();
if (alError != AL_NO_ERROR) { if (alError != AL_NO_ERROR) {
log("%s:alSourcePlay error code:%x\n", __FUNCTION__, alError); ALOGE("%s:alSourcePlay error code:%x\n", __FUNCTION__, alError);
return false; return false;
} }
@ -135,6 +138,26 @@ bool AudioPlayer::play2d(AudioCache* cache)
return 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;
}
void AudioPlayer::rotateBufferThread(int offsetFrame) void AudioPlayer::rotateBufferThread(int offsetFrame)
{ {
ALint sourceState; ALint sourceState;
@ -142,6 +165,9 @@ void AudioPlayer::rotateBufferThread(int offsetFrame)
mpg123_handle* mpg123handle = nullptr; mpg123_handle* mpg123handle = nullptr;
OggVorbis_File* vorbisFile = nullptr; OggVorbis_File* vorbisFile = nullptr;
std::function<int(char*, int)> fileReader = nullptr;
std::function<void(int)> fileSeeker = nullptr;
auto audioFileFormat = _audioCache->_fileFormat; auto audioFileFormat = _audioCache->_fileFormat;
char* tmpBuffer = (char*)malloc(_audioCache->_queBufferBytes); char* tmpBuffer = (char*)malloc(_audioCache->_queBufferBytes);
@ -152,7 +178,7 @@ void AudioPlayer::rotateBufferThread(int offsetFrame)
int error = MPG123_OK; int error = MPG123_OK;
mpg123handle = mpg123_new(nullptr, &error); mpg123handle = mpg123_new(nullptr, &error);
if (!mpg123handle){ if (!mpg123handle){
log("Basic setup goes wrong: %s", mpg123_plain_strerror(error)); ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
goto ExitBufferThread; goto ExitBufferThread;
} }
long rate = 0; long rate = 0;
@ -160,7 +186,7 @@ void AudioPlayer::rotateBufferThread(int offsetFrame)
int mp3Encoding = 0; int mp3Encoding = 0;
if (mpg123_open(mpg123handle,_audioCache->_fileFullPath.c_str()) != MPG123_OK if (mpg123_open(mpg123handle,_audioCache->_fileFullPath.c_str()) != MPG123_OK
|| mpg123_getformat(mpg123handle, &rate, &channels, &mp3Encoding) != MPG123_OK){ || mpg123_getformat(mpg123handle, &rate, &channels, &mp3Encoding) != MPG123_OK){
log("Trouble with mpg123: %s\n", mpg123_strerror(mpg123handle) ); ALOGE("Trouble with mpg123: %s\n", mpg123_strerror(mpg123handle) );
goto ExitBufferThread; goto ExitBufferThread;
} }
@ -178,7 +204,7 @@ void AudioPlayer::rotateBufferThread(int offsetFrame)
vorbisFile = new OggVorbis_File; vorbisFile = new OggVorbis_File;
int openCode; int openCode;
if (openCode = ov_fopen(FileUtils::getInstance()->getSuitableFOpen(_audioCache->_fileFullPath).c_str(), vorbisFile)){ if (openCode = ov_fopen(FileUtils::getInstance()->getSuitableFOpen(_audioCache->_fileFullPath).c_str(), vorbisFile)){
log("Input does not appear to be an Ogg bitstream: %s. Code: 0x%x\n", _audioCache->_fileFullPath.c_str(), openCode); ALOGE("Input does not appear to be an Ogg bitstream: %s. Code: 0x%x\n", _audioCache->_fileFullPath.c_str(), openCode);
goto ExitBufferThread; goto ExitBufferThread;
} }
if (offsetFrame != 0) { if (offsetFrame != 0) {
@ -192,6 +218,30 @@ void AudioPlayer::rotateBufferThread(int offsetFrame)
alSourcePlay(_alSource); 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, &current_section);
};
fileSeeker = [&](int pos) {
ov_pcm_seek(vorbisFile, pos);
};
}
while (!_exitThread) { while (!_exitThread) {
alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState); alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState);
if (sourceState == AL_PLAYING) { if (sourceState == AL_PLAYING) {
@ -203,18 +253,7 @@ void AudioPlayer::rotateBufferThread(int offsetFrame)
if (_timeDirty) { if (_timeDirty) {
_timeDirty = false; _timeDirty = false;
offsetFrame = _currTime * _audioCache->_sampleRate; offsetFrame = _currTime * _audioCache->_sampleRate;
fileSeeker(offsetFrame);
switch (audioFileFormat)
{
case AudioCache::FileFormat::MP3:
mpg123_seek(mpg123handle,offsetFrame,SEEK_SET);
break;
case AudioCache::FileFormat::OGG:
ov_pcm_seek(vorbisFile,offsetFrame);
break;
default:
break;
}
} }
else { else {
_currTime += QUEUEBUFFER_TIME_STEP; _currTime += QUEUEBUFFER_TIME_STEP;
@ -227,49 +266,37 @@ void AudioPlayer::rotateBufferThread(int offsetFrame)
} }
} }
size_t readRet = 0; int readRet = readPcmData(tmpBuffer, _audioCache->_queBufferBytes, fileReader);
if(audioFileFormat == AudioCache::FileFormat::MP3) if (readRet <= 0) {
{ if (_loop) {
mpg123_read(mpg123handle,(unsigned char*)tmpBuffer, _audioCache->_queBufferBytes,&readRet); fileSeeker(0);
if (readRet <= 0) { readRet = readPcmData(tmpBuffer, _audioCache->_queBufferBytes, fileReader);
if (_loop) {
mpg123_seek(mpg123handle,0,SEEK_SET);
mpg123_read(mpg123handle,(unsigned char*)tmpBuffer, _audioCache->_queBufferBytes,&readRet);
} else {
_exitThread = true;
break;
}
} }
else else {
{ _exitThread = true;
_audioCache->_bytesOfRead += readRet; break;
} }
} }
else if(audioFileFormat == AudioCache::FileFormat::OGG) else
{ {
int current_section; _audioCache->_bytesOfRead += readRet;
readRet = ov_read(vorbisFile,tmpBuffer,_audioCache->_queBufferBytes,0,2,1,&current_section);
if (readRet <= 0) {
if (_loop) {
ov_pcm_seek(vorbisFile,0);
readRet = ov_read(vorbisFile,tmpBuffer,_audioCache->_queBufferBytes,0,2,1,&current_section);
} else {
_exitThread = true;
break;
}
}
} }
//ALOGV("readRet: %d, queBufferBytes: %d", (int)readRet, _audioCache->_queBufferBytes);
ALuint bid; ALuint bid;
alSourceUnqueueBuffers(_alSource, 1, &bid); alSourceUnqueueBuffers(_alSource, 1, &bid);
CHECK_AL_ERROR_DEBUG();
alBufferData(bid, _audioCache->_alBufferFormat, tmpBuffer, readRet, _audioCache->_sampleRate); alBufferData(bid, _audioCache->_alBufferFormat, tmpBuffer, readRet, _audioCache->_sampleRate);
CHECK_AL_ERROR_DEBUG();
alSourceQueueBuffers(_alSource, 1, &bid); alSourceQueueBuffers(_alSource, 1, &bid);
CHECK_AL_ERROR_DEBUG();
} }
} }
std::unique_lock<std::mutex> lk(_sleepMutex); std::unique_lock<std::mutex> lk(_sleepMutex);
if (_exitThread) if (_exitThread)
{ {
ALOGV("thread exit...");
break; break;
} }
@ -292,6 +319,7 @@ ExitBufferThread:
} }
free(tmpBuffer); free(tmpBuffer);
_readForRemove = true; _readForRemove = true;
ALOGV("%s exited.\n", __FUNCTION__);
} }
bool AudioPlayer::setLoop(bool loop) bool AudioPlayer::setLoop(bool loop)

View File

@ -61,7 +61,8 @@ public:
protected: protected:
void rotateBufferThread(int offsetFrame); void rotateBufferThread(int offsetFrame);
bool play2d(AudioCache* cache); bool play2d(AudioCache* cache);
int readPcmData(char* buffer, int bufferSize, const std::function<int/*readBytes*/(char* /*buf*/, int /*bytesToRead*/)>& fileReader);
AudioCache* _audioCache; AudioCache* _audioCache;
float _volume; float _volume;

View File

@ -88,13 +88,13 @@ void MciPlayer::Open(const char* pFileName, UINT uId)
MCI_OPEN_PARMS mciOpen = {0}; MCI_OPEN_PARMS mciOpen = {0};
MCIERROR mciError; MCIERROR mciError;
mciOpen.lpstrDeviceType = (LPCTSTR)MCI_ALL_DEVICE_ID; mciOpen.lpstrDeviceType = (LPCTSTR)MCI_ALL_DEVICE_ID;
WCHAR* fileNameWideChar = new WCHAR[nLen + 1]; WCHAR* fileNameWideChar = new WCHAR[nLen + 1];
BREAK_IF(! fileNameWideChar); BREAK_IF(! fileNameWideChar);
MultiByteToWideChar(CP_ACP, 0, pFileName, nLen + 1, fileNameWideChar, nLen + 1); MultiByteToWideChar(CP_ACP, 0, pFileName, nLen + 1, fileNameWideChar, nLen + 1);
mciOpen.lpstrElementName = fileNameWideChar; mciOpen.lpstrElementName = fileNameWideChar;
mciError = mciSendCommand(0,MCI_OPEN, MCI_OPEN_ELEMENT, reinterpret_cast<DWORD_PTR>(&mciOpen)); mciError = mciSendCommand(0,MCI_OPEN, MCI_OPEN_ELEMENT, reinterpret_cast<DWORD_PTR>(&mciOpen));
CC_SAFE_DELETE_ARRAY(mciOpen.lpstrElementName); CC_SAFE_DELETE_ARRAY(mciOpen.lpstrElementName);
BREAK_IF(mciError); BREAK_IF(mciError);
_dev = mciOpen.wDeviceID; _dev = mciOpen.wDeviceID;