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
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioCache"
#include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
@ -35,10 +38,68 @@
#include "base/CCDirector.h"
#include "base/CCScheduler.h"
#include <windows.h>
#define PCMDATA_CACHEMAXSIZE 2621440
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)
@ -100,7 +161,7 @@ void AudioCache::readDataTask()
vf = new OggVorbis_File;
int openCode;
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;
}
@ -119,13 +180,13 @@ void AudioCache::readDataTask()
int error = MPG123_OK;
mpg123handle = mpg123_new(nullptr, &error);
if (!mpg123handle){
log("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
goto ExitThread;
}
if (mpg123_open(mpg123handle,_fileFullPath.c_str()) != MPG123_OK ||
mpg123_getformat(mpg123handle, &rate, &_channels, &_mp3Encoding) != MPG123_OK) {
log("Trouble with mpg123: %s\n", mpg123_strerror(mpg123handle) );
ALOGE("Trouble with mpg123: %s\n", mpg123_strerror(mpg123handle) );
goto ExitThread;
}

View File

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

View File

@ -21,6 +21,8 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioEngine-Win32"
#include "platform/CCPlatformConfig.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
@ -88,7 +90,7 @@ bool AudioEngineImpl::init()
alGenSources(MAX_AUDIOINSTANCES, _alSources);
alError = alGetError();
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;
}
@ -140,14 +142,14 @@ AudioCache* AudioEngineImpl::preload(const std::string& filePath, std::function<
}
else
{
log("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
break;
}
}
}
else
{
log("Unsupported media type file: %s\n", filePath.c_str());
ALOGE("Unsupported media type file: %s\n", filePath.c_str());
break;
}
@ -245,7 +247,7 @@ void AudioEngineImpl::setVolume(int audioID,float volume)
auto error = alGetError();
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();
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();
if (error != AL_NO_ERROR) {
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;
@ -297,7 +299,7 @@ bool AudioEngineImpl::resume(int audioID)
auto error = alGetError();
if (error != AL_NO_ERROR) {
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;
@ -313,7 +315,7 @@ bool AudioEngineImpl::stop(int audioID)
auto error = alGetError();
if (error != AL_NO_ERROR) {
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();
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();
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;
}

View File

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

View File

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

View File

@ -88,13 +88,13 @@ void MciPlayer::Open(const char* pFileName, UINT uId)
MCI_OPEN_PARMS mciOpen = {0};
MCIERROR mciError;
mciOpen.lpstrDeviceType = (LPCTSTR)MCI_ALL_DEVICE_ID;
WCHAR* fileNameWideChar = new WCHAR[nLen + 1];
BREAK_IF(! fileNameWideChar);
MultiByteToWideChar(CP_ACP, 0, pFileName, nLen + 1, fileNameWideChar, nLen + 1);
WCHAR* fileNameWideChar = new WCHAR[nLen + 1];
BREAK_IF(! fileNameWideChar);
MultiByteToWideChar(CP_ACP, 0, pFileName, nLen + 1, fileNameWideChar, nLen + 1);
mciOpen.lpstrElementName = fileNameWideChar;
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);
_dev = mciOpen.wDeviceID;