Tidy audio folder

This commit is contained in:
halx99 2022-01-03 11:34:58 +08:00
parent 86f4211f78
commit a651879ee6
37 changed files with 33 additions and 5032 deletions

View File

@ -135,7 +135,7 @@ target_include_directories(${ADXE_CORE_LIB}
INTERFACE ${ADXE_ROOT_PATH}/thirdparty INTERFACE ${ADXE_ROOT_PATH}/thirdparty
INTERFACE ${ADXE_ROOT_PATH}/extensions INTERFACE ${ADXE_ROOT_PATH}/extensions
INTERFACE ${ADXE_ROOT_PATH}/core/base INTERFACE ${ADXE_ROOT_PATH}/core/base
INTERFACE ${ADXE_ROOT_PATH}/core/audio/include INTERFACE ${ADXE_ROOT_PATH}/core/audio
INTERFACE ${ADXE_ROOT_PATH}/core/platform/${PLATFORM_FOLDER} INTERFACE ${ADXE_ROOT_PATH}/core/platform/${PLATFORM_FOLDER}
) )

View File

@ -1,50 +1,50 @@
# common headers and sources # common headers and sources
set(COCOS_AUDIO_HEADER set(COCOS_AUDIO_HEADER
audio/include/alconfig.h audio/alconfig.h
audio/include/AudioEngine.h audio/AudioEngine.h
audio/include/AudioMacros.h audio/AudioMacros.h
audio/include/AudioDecoderManager.h audio/AudioDecoderManager.h
audio/include/AudioDecoder.h audio/AudioDecoder.h
audio/include/AudioDecoderOgg.h audio/AudioDecoderOgg.h
audio/include/AudioPlayer.h audio/AudioPlayer.h
audio/include/AudioCache.h audio/AudioCache.h
audio/include/AudioEngineImpl.h audio/AudioEngineImpl.h
) )
set(COCOS_AUDIO_SRC set(COCOS_AUDIO_SRC
audio/src/AudioEngine.cpp audio/AudioEngine.cpp
audio/src/AudioDecoderManager.cpp audio/AudioDecoderManager.cpp
audio/src/AudioDecoder.cpp audio/AudioDecoder.cpp
audio/src/AudioDecoderOgg.cpp audio/AudioDecoderOgg.cpp
audio/src/AudioPlayer.cpp audio/AudioPlayer.cpp
audio/src/AudioCache.cpp audio/AudioCache.cpp
) )
# stupid, clang always compie .mm as objc/cpp mix obj # stupid, clang always compie .mm as objc/cpp mix obj
if(ANDROID OR LINUX) if(ANDROID OR LINUX)
set(COCOS_AUDIO_SRC ${COCOS_AUDIO_SRC} set(COCOS_AUDIO_SRC ${COCOS_AUDIO_SRC}
audio/src/linux-link.cpp audio/linux-link.cpp
) )
else() else()
set(COCOS_AUDIO_SRC ${COCOS_AUDIO_SRC} set(COCOS_AUDIO_SRC ${COCOS_AUDIO_SRC}
audio/src/AudioEngineImpl.mm audio/AudioEngineImpl.mm
) )
endif() endif()
# Apple, we use system audio decoder # Apple, we use system audio decoder
if(APPLE) if(APPLE)
set(COCOS_AUDIO_HEADER ${COCOS_AUDIO_HEADER} set(COCOS_AUDIO_HEADER ${COCOS_AUDIO_HEADER}
audio/include/AudioDecoderEXT.h) audio/AudioDecoderEXT.h)
set(COCOS_AUDIO_SRC ${COCOS_AUDIO_SRC} set(COCOS_AUDIO_SRC ${COCOS_AUDIO_SRC}
audio/src/AudioDecoderEXT.mm) audio/AudioDecoderEXT.mm)
else() else()
set(COCOS_AUDIO_HEADER ${COCOS_AUDIO_HEADER} set(COCOS_AUDIO_HEADER ${COCOS_AUDIO_HEADER}
audio/include/AudioDecoderMp3.h audio/AudioDecoderMp3.h
audio/include/AudioDecoderWav.h audio/AudioDecoderWav.h
) )
set(COCOS_AUDIO_SRC ${COCOS_AUDIO_SRC} set(COCOS_AUDIO_SRC ${COCOS_AUDIO_SRC}
audio/src/AudioDecoderMp3.cpp audio/AudioDecoderMp3.cpp
audio/src/AudioDecoderWav.cpp audio/AudioDecoderWav.cpp
) )
endif() endif()

View File

@ -1,111 +0,0 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2018-2020 simdsoft.com, @HALX99
Copyright (c) 2021 Bytedance Inc.
https://adxe.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 "platform/CCPlatformConfig.h"
#include <string>
#include <mutex>
#include <vector>
#include <memory>
#include "platform/CCPlatformMacros.h"
#include "audio/include/AudioMacros.h"
#include "audio/include/alconfig.h"
NS_CC_BEGIN
class AudioEngineImpl;
class AudioPlayer;
class CC_DLL AudioCache
{
public:
enum class State
{
INITIAL,
LOADING,
READY,
FAILED
};
AudioCache();
~AudioCache();
void addPlayCallback(const std::function<void()>& callback);
void addLoadCallback(const std::function<void(bool)>& callback);
protected:
void setSkipReadDataTask(bool isSkip) { _isSkipReadDataTask = isSkip; };
void readDataTask(unsigned int selfId);
void invokingPlayCallbacks();
void invokingLoadCallbacks();
// pcm data related stuff
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;
/*Queue buffer related stuff
* Streaming in OpenAL when sizeInBytes greater then PCMDATA_CACHEMAXSIZE
*/
char* _queBuffers[QUEUEBUFFER_NUM];
ALsizei _queBufferSize[QUEUEBUFFER_NUM];
uint32_t _queBufferFrames;
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;
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

View File

@ -1,151 +0,0 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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 <string>
#include "platform/CCFileStream.h"
namespace cocos2d
{
enum class AUDIO_SOURCE_FORMAT : uint16_t
{
PCM_UNK, // Unknown
PCM_U8,
PCM_16,
PCM_24,
PCM_32,
PCM_64,
PCM_FLT32,
PCM_FLT64,
MULAW,
ALAW,
ADPCM, // Microsoft ADPCM
IMA_ADPCM, // IMA ADPCM
};
/**
* @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(std::string_view 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| / samplesPerBlock *
* _bytesPerBlock.
* @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| / samplesPerBlock *
* _bytesPerBlock.
* @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;
/** Gets total frames of current audio.*/
virtual uint32_t getTotalFrames() const;
/**
* @brief The helper function for convert frames to bytes
*/
virtual uint32_t framesToBytes(uint32_t frames) const;
/**
* @brief The helper function for convert bytes to frames
*/
virtual uint32_t bytesToFrames(uint32_t bytes) 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;
/*
* @brief Gets samples per block, usually is 1 for .mp3 or .ogg, .wav may > 1
*/
uint32_t getSamplesPerBlock() const;
virtual AUDIO_SOURCE_FORMAT getSourceFormat() const;
protected:
AudioDecoder();
virtual ~AudioDecoder();
bool _isOpened;
uint32_t _totalFrames;
uint32_t _bytesPerBlock; // Same as bytesPerFrame when _samplesPerBlock is 1
uint32_t _samplesPerBlock;
uint32_t _sampleRate;
uint32_t _channelCount;
AUDIO_SOURCE_FORMAT _sourceFormat;
friend class AudioDecoderManager;
};
} // namespace cocos2d

View File

@ -1,109 +0,0 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2018 HALX99.
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>
#import <AudioToolbox/ExtendedAudioFile.h>
#include "audio/include/AudioDecoder.h"
#include "platform/CCFileStream.h"
namespace cocos2d
{
/**
* @brief The class for decoding compressed audio file to PCM buffer.
*/
class AudioDecoderEXT : public AudioDecoder
{
public:
static const uint32_t INVALID_FRAME_INDEX = UINT32_MAX;
AudioDecoderEXT();
~AudioDecoderEXT();
/**
* @brief Opens an audio file specified by a file path.
* @return true if succeed, otherwise false.
*/
bool open(std::string_view path) override;
/**
* @brief Closes opened audio file.
* @note The method will also be automatically invoked in the destructor.
*/
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| / samplesPerBlock *
* _bytesPerBlock.
* @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end
* of file.
*/
uint32_t read(uint32_t framesToRead, char* pcmBuf) override;
/**
* @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| / samplesPerBlock *
* _bytesPerBlock.
* @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).
*/
// uint32_t readFixedFrames(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
*/
bool seek(uint32_t frameOffset) override;
private:
void closeInternal();
static OSStatus readCallback(void* inClientData,
SInt64 inPosition,
UInt32 requestCount,
void* buffer,
UInt32* actualCount);
static SInt64 getSizeCallback(void* inClientData);
ExtAudioFileRef _extRef;
std::unique_ptr<cocos2d::FileStream> _fileStream;
SInt64 _streamSize;
AudioFileID _audioFileId;
AudioStreamBasicDescription _outputFormat;
};
} // namespace cocos2d

View File

@ -1,44 +0,0 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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 <string>
namespace cocos2d
{
class AudioDecoder;
class AudioDecoderManager
{
public:
static bool init();
static void destroy();
static AudioDecoder* createDecoder(std::string_view path);
static void destroyDecoder(AudioDecoder* decoder);
};
} // namespace cocos2d

View File

@ -1,93 +0,0 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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/include/AudioDecoder.h"
#include <memory>
#if !defined(CC_USE_MPG123)
# define CC_USE_MPG123 0
#endif
#if !CC_USE_MPG123
typedef struct mp3dec_impl* mp3dec_handle_t;
#else
typedef struct mpg123_handle_struct* mp3dec_handle_t;
#endif
namespace cocos2d
{
/**
* @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.
*/
bool open(std::string_view path) override;
/**
* @brief Closes opened audio file.
* @note The method will also be automatically invoked in the destructor.
*/
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| / samplesPerBlock *
* _bytesPerBlock.
* @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end
* of file.
*/
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
*/
bool seek(uint32_t frameOffset) override;
protected:
AudioDecoderMp3();
~AudioDecoderMp3();
static bool lazyInit();
static void destroy();
std::unique_ptr<FileStream> _fileStream{};
mp3dec_handle_t _handle;
friend class AudioDecoderManager;
};
} // namespace cocos2d

View File

@ -1,81 +0,0 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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/include/AudioDecoder.h"
#include "vorbis/vorbisfile.h"
#include <memory>
namespace cocos2d
{
/**
* @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.
*/
bool open(std::string_view path) override;
/**
* @brief Closes opened audio file.
* @note The method will also be automatically invoked in the destructor.
*/
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| / samplesPerBlock *
* _bytesPerBlock.
* @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end
* of file.
*/
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
*/
bool seek(uint32_t frameOffset) override;
protected:
AudioDecoderOgg();
~AudioDecoderOgg();
OggVorbis_File _vf;
friend class AudioDecoderManager;
};
} // namespace cocos2d

View File

@ -1,174 +0,0 @@
/****************************************************************************
Copyright (c) 2018-2019 HALX99.
Copyright (c) 2020 c4games.com.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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/include/AudioDecoder.h"
#include <memory>
#if !defined(MAKE_FOURCC)
# define MAKE_FOURCC(a, b, c, d) ((uint32_t)((a) | ((b) << 8) | ((c) << 16) | (((uint32_t)(d)) << 24)))
#endif
#if !defined(_WIN32)
typedef struct _GUID
{
uint32_t Data1;
unsigned short Data2;
unsigned short Data3;
unsigned char Data4[8];
} GUID;
__inline int IsEqualGUID(const GUID& rguid1, const GUID& rguid2)
{
return !::memcmp(&rguid1, &rguid2, sizeof(GUID));
}
#endif
namespace cocos2d
{
// http://soundfile.sapp.org/doc/WaveFormat/
enum class WAV_FORMAT : uint16_t
{
UNKNOWN = 0x0, // Unknown Wave Format
PCM = 0x1, // PCM Format
ADPCM = 0x2, // Microsoft ADPCM Format
IEEE = 0x3, // IEEE float/double
ALAW = 0x6, // 8-bit ITU-T G.711 A-law
MULAW = 0x7, // 8-bit ITU-T G.711 MUL-law
IMA_ADPCM = 0x11, // IMA ADPCM Format
EXT = 0xFFFE // Set via subformat
};
#pragma pack(push, 1)
struct WAV_CHUNK_HEADER
{
uint32_t ChunkID;
/*
** The ChunkSize gives the size of the valid data in the chunk. It does not include the padding, the size of
*chunkID, or the size of chunkSize.
** see: https://docs.microsoft.com/en-us/windows/win32/xaudio2/resource-interchange-file-format--riff-
*/
uint32_t ChunkSize;
};
struct WAV_RIFF_CHUNK
{
WAV_CHUNK_HEADER Header;
uint32_t Format;
};
struct WAV_FMT_CHUNK
{
WAV_CHUNK_HEADER Header;
WAV_FORMAT AudioFormat;
uint16_t NumChannels;
uint32_t SampleRate;
uint32_t ByteRate;
uint16_t BlockAlign;
uint16_t BitsPerSample;
struct
{
/* The follow fields is optional */
uint16_t cbSize;
union
{
uint16_t ValidBitsPerSample;
uint16_t SamplesPerBlock;
uint16_t Reserved;
} Samples;
uint32_t ChannelMask;
GUID SubFormat;
} ExtParams;
};
struct WAV_FILE_HEADER
{
WAV_RIFF_CHUNK Riff;
WAV_FMT_CHUNK Fmt;
WAV_CHUNK_HEADER PcmData;
};
#pragma pack(pop)
struct WAV_FILE
{
WAV_FILE_HEADER FileHeader;
AUDIO_SOURCE_FORMAT SourceFormat;
uint32_t PcmDataOffset;
std::unique_ptr<FileStream> Stream{};
};
/**
* @brief The class for decoding compressed audio file to PCM buffer.
*/
class AudioDecoderWav : public AudioDecoder
{
public:
/**
* @brief Opens an audio file specified by a file path.
* @return true if succeed, otherwise false.
*/
bool open(std::string_view path) override;
/**
* @brief The helper function for convert frames to bytes
*/
uint32_t framesToBytes(uint32_t frames) const override;
/**
* @brief The helper function for convert bytes to frames
*/
uint32_t bytesToFrames(uint32_t bytes) const override;
/**
* @brief Closes opened audio file.
* @note The method will also be automatically invoked in the destructor.
*/
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| / samplesPerBlock *
* _bytesPerBlock.
* @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end
* of file.
*/
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
*/
bool seek(uint32_t frameOffset) override;
protected:
friend class AudioDecoderManager;
AudioDecoderWav();
~AudioDecoderWav();
mutable WAV_FILE _wavf;
};
} // namespace cocos2d

View File

@ -1,371 +0,0 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2018 HALX99.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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 "platform/CCPlatformConfig.h"
#include "platform/CCPlatformMacros.h"
#include "audio/include/AudioMacros.h"
#include <functional>
#include <list>
#include <string>
#include <unordered_map>
#ifdef ERROR
# undef ERROR
#endif // ERROR
/**
* @addtogroup audio
* @{
*/
NS_CC_BEGIN
/**
* @class AudioProfile
*
* @brief
* @js NA
*/
class CC_DLL AudioProfile
{
public:
// Profile name can't be empty.
std::string name;
// The maximum number of simultaneous audio instance.
unsigned int maxInstances;
/* Minimum delay in between sounds */
double minDelay;
/**
* Default constructor
*
* @lua new
*/
AudioProfile() : maxInstances(0), minDelay(0.0) {}
};
class AudioEngineImpl;
/**
* @class AudioEngine
*
* @brief Offers a interface to play audio.
*
* @note Make sure to call AudioEngine::end() when the audio engine is not needed anymore to release resources.
* @js NA
*/
class CC_DLL AudioEngine
{
public:
/** AudioState enum,all possible states of an audio instance.*/
enum class AudioState
{
ERROR = -1,
INITIALIZING,
PLAYING,
PAUSED
};
static const int INVALID_AUDIO_ID;
static const float TIME_UNKNOWN;
static bool lazyInit();
/**
* Release objects relating to AudioEngine.
*
* @warning It must be called before the application exit.
* @lua endToLua
*/
static void end();
/**
* Gets the default profile of audio instances.
*
* @return The default profile of audio instances.
*/
static AudioProfile* getDefaultProfile();
/**
* Play 2d sound.
*
* @param filePath The path of an audio file.
* @param loop Whether audio instance loop or not.
* @param volume Volume value (range from 0.0 to 1.0).
* @param profile A profile for audio instance. When profile is not specified, default profile will be used.
* @return An audio ID. It allows you to dynamically change the behavior of an audio instance on the fly.
*
* @see `AudioProfile`
*/
static AUDIO_ID play2d(std::string_view filePath,
bool loop = false,
float volume = 1.0f,
const AudioProfile* profile = nullptr);
/**
* Sets whether an audio instance loop or not.
*
* @param audioID An audioID returned by the play2d function.
* @param loop Whether audio instance loop or not.
*/
static void setLoop(AUDIO_ID audioID, bool loop);
/**
* Checks whether an audio instance is loop.
*
* @param audioID An audioID returned by the play2d function.
* @return Whether or not an audio instance is loop.
*/
static bool isLoop(AUDIO_ID audioID);
/**
* Sets volume for an audio instance.
*
* @param audioID An audioID returned by the play2d function.
* @param volume Volume value (range from 0.0 to 1.0).
*/
static void setVolume(AUDIO_ID audioID, float volume);
/**
* Gets the volume value of an audio instance.
*
* @param audioID An audioID returned by the play2d function.
* @return Volume value (range from 0.0 to 1.0).
*/
static float getVolume(AUDIO_ID audioID);
/**
* Pause an audio instance.
*
* @param audioID An audioID returned by the play2d function.
*/
static void pause(AUDIO_ID audioID);
/** Pause all playing audio instances. */
static void pauseAll();
/**
* Resume an audio instance.
*
* @param audioID An audioID returned by the play2d function.
*/
static void resume(AUDIO_ID audioID);
/** Resume all suspended audio instances. */
static void resumeAll();
/**
* Stop an audio instance.
*
* @param audioID An audioID returned by the play2d function.
*/
static void stop(AUDIO_ID audioID);
/** Stop all audio instances. */
static void stopAll();
/**
* Sets the current playback position of an audio instance.
*
* @param audioID An audioID returned by the play2d function.
* @param sec The offset in seconds from the start to seek to.
* @return
*/
static bool setCurrentTime(AUDIO_ID audioID, float sec);
/**
* Gets the current playback position of an audio instance.
*
* @param audioID An audioID returned by the play2d function.
* @return The current playback position of an audio instance.
*/
static float getCurrentTime(AUDIO_ID audioID);
/**
* Gets the duration of an audio instance.
*
* @param audioID An audioID returned by the play2d function.
* @return The duration of an audio instance.
*/
static float getDuration(AUDIO_ID audioID);
/**
* Returns the state of an audio instance.
*
* @param audioID An audioID returned by the play2d function.
* @return The status of an audio instance.
*/
static AudioState getState(AUDIO_ID audioID);
/**
* Register a callback to be invoked when an audio instance has completed playing.
*
* @param audioID An audioID returned by the play2d function.
* @param callback
*/
static void setFinishCallback(AUDIO_ID audioID, const std::function<void(AUDIO_ID, std::string_view)>& callback);
/**
* Gets the maximum number of simultaneous audio instance of AudioEngine.
*/
static int getMaxAudioInstance() { return _maxInstances; }
/**
* Sets the maximum number of simultaneous audio instance for AudioEngine.
*
* @param maxInstances The maximum number of simultaneous audio instance.
*/
static bool setMaxAudioInstance(int maxInstances);
/**
* Uncache the audio data from internal buffer.
* AudioEngine cache audio data on ios,mac, and win32 platform.
*
* @warning This can lead to stop related audio first.
* @param filePath Audio file path.
*/
static void uncache(std::string_view filePath);
/**
* Uncache all audio data from internal buffer.
*
* @warning All audio will be stopped first.
*/
static void uncacheAll();
/**
* Gets the audio profile by id of audio instance.
*
* @param audioID An audioID returned by the play2d function.
* @return The audio profile.
*/
static AudioProfile* getProfile(AUDIO_ID audioID);
/**
* Gets an audio profile by name.
*
* @param profileName A name of audio profile.
* @return The audio profile.
*/
static AudioProfile* getProfile(std::string_view profileName);
/**
* Preload audio file.
* @param filePath The file path of an audio.
*/
static void preload(std::string_view filePath) { preload(filePath, nullptr); }
/**
* Preload audio file.
* @param filePath The file path of an audio.
* @param callback A callback which will be called after loading is finished.
*/
static void preload(std::string_view filePath, std::function<void(bool isSuccess)> callback);
/**
* Gets playing audio count.
*/
static int getPlayingAudioCount();
/**
* Whether to enable playing audios
* @note If it's disabled, current playing audios will be stopped and the later 'preload', 'play2d' methods will
* take no effects.
*/
static void setEnabled(bool isEnabled);
/**
* Check whether AudioEngine is enabled.
*/
static bool isEnabled();
protected:
static void addTask(const std::function<void()>& task);
static void remove(AUDIO_ID audioID);
struct ProfileHelper
{
AudioProfile profile;
std::list<int> audioIDs;
double lastPlayTime;
ProfileHelper() : lastPlayTime(0.0) {}
};
struct AudioInfo
{
std::string filePath;
ProfileHelper* profileHelper;
float volume;
bool loop;
float duration;
AudioState state;
AudioInfo();
~AudioInfo();
private:
AudioInfo(const AudioInfo& info);
AudioInfo(AudioInfo&& info);
AudioInfo& operator=(const AudioInfo& info);
AudioInfo& operator=(AudioInfo&& info);
};
// audioID,audioAttribute
static std::unordered_map<AUDIO_ID, AudioInfo> _audioIDInfoMap;
// audio file path,audio IDs
static hlookup::string_map<std::list<AUDIO_ID>> _audioPathIDMap;
// profileName,ProfileHelper
static hlookup::string_map<ProfileHelper> _audioPathProfileHelperMap;
static unsigned int _maxInstances;
static ProfileHelper* _defaultProfileHelper;
static AudioEngineImpl* _audioEngineImpl;
class AudioEngineThreadPool;
static AudioEngineThreadPool* s_threadPool;
static bool _isEnabled;
friend class AudioEngineImpl;
};
NS_CC_END
// end group
/// @}

View File

@ -1,100 +0,0 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2018-2020 HALX99.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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
#ifndef __AUDIO_ENGINE_IMPL_H_
# define __AUDIO_ENGINE_IMPL_H_
# include "platform/CCPlatformConfig.h"
# include <unordered_map>
# include <queue>
# include "base/CCRef.h"
# include "audio/include/AudioMacros.h"
# include "audio/include/AudioCache.h"
# include "audio/include/AudioPlayer.h"
NS_CC_BEGIN
class Scheduler;
class CC_DLL AudioEngineImpl : public cocos2d::Ref
{
public:
AudioEngineImpl();
~AudioEngineImpl();
bool init();
AUDIO_ID play2d(std::string_view fileFullPath, bool loop, float volume);
void setVolume(AUDIO_ID audioID, float volume);
void setLoop(AUDIO_ID audioID, bool loop);
bool pause(AUDIO_ID audioID);
bool resume(AUDIO_ID audioID);
void stop(AUDIO_ID audioID);
void stopAll();
float getDuration(AUDIO_ID audioID);
float getCurrentTime(AUDIO_ID audioID);
bool setCurrentTime(AUDIO_ID audioID, float time);
void setFinishCallback(AUDIO_ID audioID, const std::function<void(AUDIO_ID, std::string_view)>& callback);
void uncache(std::string_view filePath);
void uncacheAll();
AudioCache* preload(std::string_view filePath, std::function<void(bool)> callback);
void update(float dt);
private:
// query players state per frame and dispatch finish callback if possible
void _updatePlayers(bool forStop);
void _play2d(AudioCache* cache, AUDIO_ID audioID);
void _unscheduleUpdate();
ALuint findValidSource();
# if defined(__APPLE__)
static ALvoid myAlSourceNotificationCallback(ALuint sid, ALuint notificationID, ALvoid* userData);
# endif
ALuint _alSources[MAX_AUDIOINSTANCES];
// available sources
std::queue<ALuint> _unusedSourcesPool;
// filePath,bufferInfo
hlookup::string_map<std::unique_ptr<AudioCache>> _audioCaches;
// audioID,AudioInfo
std::unordered_map<AUDIO_ID, AudioPlayer*> _audioPlayers;
std::recursive_mutex _threadMutex;
// finish callbacks
std::vector<std::function<void()>> _finishCallbacks;
bool _scheduled;
AUDIO_ID _currentAudioID;
Scheduler* _scheduler;
};
NS_CC_END
#endif // __AUDIO_ENGINE_INL_H_

View File

@ -1,93 +0,0 @@
/****************************************************************************
Copyright (c) 2016-2017 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2018 HALX99.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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 "platform/CCPlatformConfig.h"
#include <functional>
#define QUEUEBUFFER_NUM (3)
#define QUEUEBUFFER_TIME_STEP (0.05f)
#define QUOTEME_(x) #x
#define QUOTEME(x) QUOTEME_(x)
// 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.
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
# include "base/ccUTF8.h" // for StringUtils::format
# define AUDIO_LOG(fmt, ...) OutputDebugStringA(StringUtils::format((fmt "\r\n"), ##__VA_ARGS__).c_str())
#elif CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
# include <android/log.h>
# define AUDIO_LOG(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, "AudioEngine", fmt, ##__VA_ARGS__)
#else // other platforms
# define AUDIO_LOG(fmt, ...) printf(fmt "\n", ##__VA_ARGS__)
#endif
#if defined(COCOS2D_DEBUG) && COCOS2D_DEBUG > 0
# define ALOGV(fmt, ...) AUDIO_LOG("V/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
#else
# define ALOGV(fmt, ...) \
do \
{ \
} while (false)
#endif
#define ALOGD(fmt, ...) AUDIO_LOG("D/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
#define ALOGI(fmt, ...) AUDIO_LOG("I/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
#define ALOGW(fmt, ...) AUDIO_LOG("W/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
#define ALOGE(fmt, ...) AUDIO_LOG("E/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
#if defined(COCOS2D_DEBUG) && COCOS2D_DEBUG > 0
# define CHECK_AL_ERROR_DEBUG() \
do \
{ \
ALenum __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; \
}
#define AUDIO_ID int
#define AUDIO_ID_PRID "%d"

View File

@ -1,101 +0,0 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2018 HALX99.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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 "platform/CCPlatformConfig.h"
#include <string>
#include <condition_variable>
#include <mutex>
#include <thread>
#include "audio/include/AudioMacros.h"
#include "platform/CCPlatformMacros.h"
#include "audio/include/alconfig.h"
NS_CC_BEGIN
class AudioCache;
class AudioEngineImpl;
class CC_DLL AudioPlayer
{
friend class AudioEngineImpl;
public:
AudioPlayer();
~AudioPlayer();
void destroy();
// queue buffer related stuff
bool setTime(float time);
float getTime() { return _currTime; }
bool setLoop(bool loop);
bool isFinished() const;
protected:
void setCache(AudioCache* cache);
void rotateBufferThread(int offsetFrame);
bool play2d();
#if defined(__APPLE__)
void wakeupRotateThread();
#endif
AudioCache* _audioCache;
float _volume;
bool _loop;
std::function<void(AUDIO_ID, std::string_view)> _finishCallbak;
bool _isDestroyed;
bool _removeByAudioEngine;
bool _ready;
ALuint _alSource;
// play by circular buffer
float _currTime;
bool _streamingSource;
ALuint _bufferIds[QUEUEBUFFER_NUM];
std::thread* _rotateBufferThread;
std::condition_variable _sleepCondition;
std::mutex _sleepMutex;
bool _timeDirty;
bool _isRotateThreadExited;
#if defined(__APPLE__)
std::atomic_bool _needWakeupRotateThread;
#endif
std::mutex _play2dMutex;
unsigned int _id;
friend class AudioEngineImpl;
};
NS_CC_END

View File

@ -1,56 +0,0 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2020 c4games.com.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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
#if !defined(CC_USE_MOJOAL)
# define CC_USE_MOJOAL 0
#endif
#if !defined(__APPLE__)
# if !defined(CC_USE_ALSOFT) && !CC_USE_MOJOAL
# define CC_USE_ALSOFT 1
# endif
#endif
// if CC_USE_MOJOAL, force disable openal-soft
#if CC_USE_MOJOAL
# define CC_USE_ALSOFT 0
#endif
#if !CC_USE_ALSOFT && !CC_USE_MOJOAL
# import <OpenAL/al.h>
# import <OpenAL/alc.h>
# define MAX_AUDIOINSTANCES 24
#else
# define AL_ALEXT_PROTOTYPES 1
# include "AL/al.h"
# include "AL/alc.h"
# if !CC_USE_MOJOAL
# include "AL/alext.h"
# endif
# define MAX_AUDIOINSTANCES 32
#endif

View File

@ -1,426 +0,0 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2018-2020 HALX99.
Copyright (c) 2020 c4games.com.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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 "AudioCache"
#include "platform/CCPlatformConfig.h"
#include "audio/include/AudioCache.h"
#include <thread>
#include "base/CCDirector.h"
#include "base/CCScheduler.h"
#include "audio/include/AudioDecoderManager.h"
#include "audio/include/AudioDecoder.h"
#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;
AudioCache::AudioCache()
: _totalFrames(0)
, _framesRead(0)
, _format(-1)
, _duration(0.0f)
, _alBufferId(INVALID_AL_BUFFER_ID)
, _queBufferFrames(0)
, _state(State::INITIAL)
, _isDestroyed(std::make_shared<bool>(false))
, _id(++__idIndex)
, _isLoadingFinished(false)
, _isSkipReadDataTask(false)
{
ALOGVV("AudioCache() %p, id=%u", this, _id);
for (int i = 0; i < QUEUEBUFFER_NUM; ++i)
{
_queBuffers[i] = nullptr;
_queBufferSize[i] = 0;
}
}
AudioCache::~AudioCache()
{
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();
if (_state == State::READY)
{
if (_alBufferId != INVALID_AL_BUFFER_ID && alIsBuffer(_alBufferId))
{
ALOGV("~AudioCache(id=%u), delete buffer: %u", _id, _alBufferId);
alDeleteBuffers(1, &_alBufferId);
_alBufferId = INVALID_AL_BUFFER_ID;
}
}
else
{
ALOGW("AudioCache (%p), id=%u, buffer isn't ready, state=%d", this, _id, (int)_state);
}
if (_queBufferFrames > 0)
{
for (int index = 0; index < QUEUEBUFFER_NUM; ++index)
{
free(_queBuffers[index]);
}
}
ALOGVV("~AudioCache() %p, id=%u, end", this, _id);
_readDataTaskMutex.unlock();
}
void AudioCache::readDataTask(unsigned int selfId)
{
// Note: It's in sub thread
ALOGVV("readDataTask begin, cache id=%u", selfId);
_readDataTaskMutex.lock();
_state = State::LOADING;
AudioDecoder* decoder = AudioDecoderManager::createDecoder(_fileFullPath);
do
{
if (decoder == nullptr || !decoder->open(_fileFullPath))
break;
const uint32_t originalTotalFrames = decoder->getTotalFrames();
const uint32_t sampleRate = decoder->getSampleRate();
const uint32_t channelCount = decoder->getChannelCount();
const auto sourceFormat = decoder->getSourceFormat();
uint32_t totalFrames = originalTotalFrames;
uint32_t dataSize = decoder->framesToBytes(totalFrames);
uint32_t remainingFrames = totalFrames;
switch (sourceFormat)
{
case AUDIO_SOURCE_FORMAT::PCM_16:
_format = channelCount > 1 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; // bits depth: 16bits
break;
case AUDIO_SOURCE_FORMAT::PCM_U8:
_format = channelCount > 1 ? AL_FORMAT_STEREO8 : AL_FORMAT_MONO8; // bits depth: 8bits
break;
#if CC_USE_ALSOFT
case AUDIO_SOURCE_FORMAT::PCM_FLT32:
_format = channelCount > 1 ? AL_FORMAT_STEREO_FLOAT32 : AL_FORMAT_MONO_FLOAT32;
break;
case AUDIO_SOURCE_FORMAT::PCM_FLT64:
_format = channelCount > 1 ? AL_FORMAT_STEREO_DOUBLE_EXT : AL_FORMAT_MONO_DOUBLE_EXT;
break;
case AUDIO_SOURCE_FORMAT::MULAW:
_format = channelCount > 1 ? AL_FORMAT_STEREO_MULAW : AL_FORMAT_MONO_MULAW;
break;
case AUDIO_SOURCE_FORMAT::ALAW:
_format = channelCount > 1 ? AL_FORMAT_STEREO_ALAW_EXT : AL_FORMAT_MONO_ALAW_EXT;
break;
case AUDIO_SOURCE_FORMAT::ADPCM:
_format = channelCount > 1 ? AL_FORMAT_STEREO_MSADPCM_SOFT : AL_FORMAT_MONO_MSADPCM_SOFT;
break;
case AUDIO_SOURCE_FORMAT::IMA_ADPCM:
_format = channelCount > 1 ? AL_FORMAT_STEREO_IMA4 : AL_FORMAT_MONO_IMA4;
break;
#endif
default:
assert(false);
}
_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));
alGenBuffers(1, &_alBufferId);
auto alError = alGetError();
if (alError != AL_NO_ERROR)
{
ALOGE("%s: attaching audio to buffer fail: %x", __FUNCTION__, alError);
break;
}
std::vector<char> pcmBuffer(dataSize, 0);
auto pcmData = pcmBuffer.data();
if (*_isDestroyed)
break;
framesRead = decoder->readFixedFrames((std::min)(framesToReadOnce, remainingFrames),
pcmData + decoder->framesToBytes(_framesRead));
_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 + decoder->framesToBytes(_framesRead));
if (framesRead == 0)
break;
_framesRead += framesRead;
remainingFrames -= framesRead;
}
if (*_isDestroyed)
break;
if (_framesRead < originalTotalFrames)
{
memset(pcmData + decoder->framesToBytes(_framesRead), 0x00,
decoder->framesToBytes(totalFrames - _framesRead));
}
#if CC_USE_ALSOFT
ALOGV("pcm buffer was loaded successfully, total frames: %u, total read frames: %u, remainingFrames: %u",
totalFrames, _framesRead, remainingFrames);
if (sourceFormat == AUDIO_SOURCE_FORMAT::ADPCM || sourceFormat == AUDIO_SOURCE_FORMAT::IMA_ADPCM)
alBufferi(_alBufferId, AL_UNPACK_BLOCK_ALIGNMENT_SOFT, decoder->getSamplesPerBlock());
alBufferData(_alBufferId, _format, pcmData, (ALsizei)dataSize, (ALsizei)sampleRate);
#else
# if !CC_USE_ALSOFT
/// Apple OpenAL framework, try adjust frames
/// May don't need, xcode11 sdk works well
uint32_t adjustFrames = 0;
BREAK_IF_ERR_LOG(!decoder->seek(totalFrames), "AudioDecoder::seek(%u) error", totalFrames);
char* tmpBuf = (char*)malloc(decoder->framesToBytes(framesToReadOnce));
std::vector<char> adjustFrameBuf;
adjustFrameBuf.reserve(decoder->framesToBytes(framesToReadOnce));
// 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 + decoder->framesToBytes(framesRead));
}
} 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;
}
free(tmpBuf);
// Reset to frame 0
BREAK_IF_ERR_LOG(!decoder->seek(0), "AudioDecoder::seek(0) failed!");
if (adjustFrames > 0)
{
pcmBuffer.insert(pcmBuffer.end(), adjustFrameBuf.data(), adjustFrameBuf.data() + adjustFrameBuf.size());
pcmData = pcmBuffer.data();
dataSize = static_cast<uint32_t>(pcmBuffer.size());
}
# endif /* Adjust frames, may not needed */
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);
#endif
alError = alGetError();
if (alError != AL_NO_ERROR)
{
ALOGE("%s:alBufferData error code:%x", __FUNCTION__, alError);
break;
}
_state = State::READY;
}
else
{
_queBufferFrames = sampleRate * QUEUEBUFFER_TIME_STEP;
BREAK_IF_ERR_LOG(_queBufferFrames == 0, "_queBufferFrames == 0");
const uint32_t queBufferBytes = decoder->framesToBytes(_queBufferFrames);
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);
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;
}
}
// Set before invokingPlayCallbacks, otherwise, may cause dead-lock
_isLoadingFinished = true;
// FIXME: Why to invoke play callback first? Should it be after 'load' callback?
invokingPlayCallbacks();
invokingLoadCallbacks();
_readDataTaskMutex.unlock();
ALOGVV("readDataTask end, cache id=%u", selfId);
}
void AudioCache::addPlayCallback(const std::function<void()>& callback)
{
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();
break;
default:
ALOGE("Invalid state: %d", (int)_state);
break;
}
}
void AudioCache::invokingPlayCallbacks()
{
std::lock_guard<std::mutex> lk(_playCallbackMutex);
for (auto&& cb : _playCallbacks)
{
cb();
}
_playCallbacks.clear();
}
void AudioCache::addLoadCallback(const std::function<void(bool)>& callback)
{
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", (int)_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();
});
}

View File

@ -1,105 +0,0 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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/include/AudioDecoder.h"
#include "audio/include/AudioMacros.h"
#include "platform/CCFileUtils.h"
#define LOG_TAG "AudioDecoder"
namespace cocos2d
{
AudioDecoder::AudioDecoder()
: _isOpened(false)
, _totalFrames(0)
, _bytesPerBlock(0)
, _samplesPerBlock(1)
, _sampleRate(0)
, _channelCount(0)
, _sourceFormat(AUDIO_SOURCE_FORMAT::PCM_16)
{}
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 + framesToBytes((framesRead)));
framesRead += framesReadOnce;
} while (framesReadOnce != 0 && framesRead < framesToRead);
if (framesRead < framesToRead)
{
memset(pcmBuf + framesToBytes(framesRead), 0x0, framesToBytes(framesToRead - framesRead));
}
return framesRead;
}
uint32_t AudioDecoder::getTotalFrames() const
{
return _totalFrames;
}
uint32_t AudioDecoder::framesToBytes(uint32_t frames) const
{
return _bytesPerBlock * frames;
}
uint32_t AudioDecoder::bytesToFrames(uint32_t bytes) const
{
return bytes / _bytesPerBlock;
}
uint32_t AudioDecoder::getSampleRate() const
{
return _sampleRate;
}
uint32_t AudioDecoder::getChannelCount() const
{
return _channelCount;
}
uint32_t AudioDecoder::getSamplesPerBlock() const
{
return _samplesPerBlock;
}
AUDIO_SOURCE_FORMAT AudioDecoder::getSourceFormat() const
{
return _sourceFormat;
}
} // namespace cocos2d

View File

@ -1,225 +0,0 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2018 HALX99.
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/include/AudioDecoderEXT.h"
#include "audio/include/AudioMacros.h"
#include "platform/CCFileUtils.h"
#import <Foundation/Foundation.h>
#define LOG_TAG "AudioDecoder"
namespace cocos2d
{
AudioDecoderEXT::AudioDecoderEXT() : _extRef(nullptr), _fileStream(nullptr), _streamSize(0), _audioFileId(nullptr)
{
memset(&_outputFormat, 0, sizeof(_outputFormat));
}
AudioDecoderEXT::~AudioDecoderEXT()
{
closeInternal();
}
bool AudioDecoderEXT::open(std::string_view fullPath)
{
bool ret = false;
CFURLRef fileURL = nil;
do
{
BREAK_IF_ERR_LOG(fullPath.empty(), "Invalid path!");
_fileStream = cocos2d::FileUtils::getInstance()->openFileStream(fullPath, FileStream::Mode::READ);
BREAK_IF_ERR_LOG(_fileStream == nullptr, "FileUtils::openFileStream FAILED for file: %s", fullPath.data());
if (_fileStream)
{
_streamSize = _fileStream->size(); // cache the stream size
}
OSStatus status = AudioFileOpenWithCallbacks(this, &AudioDecoderEXT::readCallback, nullptr,
&AudioDecoderEXT::getSizeCallback, nullptr, 0, &_audioFileId);
BREAK_IF_ERR_LOG(status != noErr, "AudioFileOpenWithCallbacks FAILED, Error = %d", (int)status);
status = ExtAudioFileWrapAudioFileID(_audioFileId, false, &_extRef);
BREAK_IF_ERR_LOG(status != noErr, "ExtAudioFileWrapAudioFileID FAILED, Error = %d", (int)status);
BREAK_IF_ERR_LOG(status != noErr, "ExtAudioFileOpenURL FAILED, Error = %d", (int)status);
AudioStreamBasicDescription fileFormat;
UInt32 propertySize = sizeof(fileFormat);
// Get the audio data format
status = ExtAudioFileGetProperty(_extRef, kExtAudioFileProperty_FileDataFormat, &propertySize, &fileFormat);
BREAK_IF_ERR_LOG(status != noErr,
"ExtAudioFileGetProperty(kExtAudioFileProperty_FileDataFormat) FAILED, Error = %d",
(int)status);
BREAK_IF_ERR_LOG(fileFormat.mChannelsPerFrame > 2, "Unsupported Format, channel count is greater than stereo!");
// Set the client format to 16 bit signed integer (native-endian) data
// Maintain the channel count and sample rate of the original source format
_outputFormat.mSampleRate = fileFormat.mSampleRate;
_outputFormat.mChannelsPerFrame = fileFormat.mChannelsPerFrame;
_outputFormat.mFormatID = kAudioFormatLinearPCM;
_outputFormat.mFramesPerPacket = 1;
_outputFormat.mBitsPerChannel = 16;
_outputFormat.mFormatFlags =
kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
_sampleRate = _outputFormat.mSampleRate;
_channelCount = _outputFormat.mChannelsPerFrame;
_bytesPerBlock = 2 * _outputFormat.mChannelsPerFrame;
_outputFormat.mBytesPerPacket = _bytesPerBlock;
_outputFormat.mBytesPerFrame = _bytesPerBlock;
status = ExtAudioFileSetProperty(_extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(_outputFormat),
&_outputFormat);
BREAK_IF_ERR_LOG(status != noErr, "ExtAudioFileSetProperty FAILED, Error = %d", (int)status);
// Get the total frame count
SInt64 totalFrames = 0;
propertySize = sizeof(totalFrames);
status = ExtAudioFileGetProperty(_extRef, kExtAudioFileProperty_FileLengthFrames, &propertySize, &totalFrames);
BREAK_IF_ERR_LOG(status != noErr,
"ExtAudioFileGetProperty(kExtAudioFileProperty_FileLengthFrames) FAILED, Error = %d",
(int)status);
BREAK_IF_ERR_LOG(totalFrames <= 0, "Total frames is 0, it's an invalid audio file: %s", fullPath.data());
_totalFrames = static_cast<uint32_t>(totalFrames);
_isOpened = true;
ret = true;
} while (false);
if (fileURL != nil)
CFRelease(fileURL);
if (!ret)
{
close();
}
return ret;
}
void AudioDecoderEXT::close()
{
closeInternal();
}
uint32_t AudioDecoderEXT::read(uint32_t framesToRead, char* pcmBuf)
{
uint32_t ret = 0;
do
{
BREAK_IF_ERR_LOG(!isOpened(), "decoder isn't openned");
BREAK_IF_ERR_LOG(framesToRead == INVALID_FRAME_INDEX, "frameToRead is INVALID_FRAME_INDEX");
BREAK_IF_ERR_LOG(framesToRead == 0, "frameToRead is 0");
BREAK_IF_ERR_LOG(pcmBuf == nullptr, "pcmBuf is nullptr");
AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mDataByteSize = framesToRead * _bytesPerBlock;
bufferList.mBuffers[0].mNumberChannels = _outputFormat.mChannelsPerFrame;
bufferList.mBuffers[0].mData = pcmBuf;
UInt32 frames = framesToRead;
OSStatus status = ExtAudioFileRead(_extRef, &frames, &bufferList);
BREAK_IF(status != noErr);
ret = frames;
} while (false);
return ret;
}
bool AudioDecoderEXT::seek(uint32_t frameOffset)
{
bool ret = false;
do
{
BREAK_IF_ERR_LOG(!isOpened(), "decoder isn't openned");
BREAK_IF_ERR_LOG(frameOffset == INVALID_FRAME_INDEX, "frameIndex is INVALID_FRAME_INDEX");
OSStatus status = ExtAudioFileSeek(_extRef, frameOffset);
BREAK_IF(status != noErr);
ret = true;
} while (false);
return ret;
}
void AudioDecoderEXT::closeInternal()
{
if (_extRef != nullptr)
{
ExtAudioFileDispose(_extRef);
AudioFileClose(_audioFileId);
_extRef = nullptr;
_audioFileId = nullptr;
_fileStream = nullptr;
}
}
OSStatus AudioDecoderEXT::readCallback(void* inClientData,
SInt64 inPosition,
UInt32 requestCount,
void* buffer,
UInt32* actualCount)
{
if (!inClientData)
{
return kAudioFileNotOpenError;
}
auto* audioDecoder = (AudioDecoderEXT*)inClientData;
auto* fileStream = audioDecoder->_fileStream.get();
auto currPos = (SInt64)fileStream->tell();
auto posDiff = inPosition - currPos;
if (posDiff != 0)
{
if (fileStream->seek(posDiff, SEEK_CUR) < 0)
{
return kAudioFilePositionError;
}
}
const auto count = fileStream->read(buffer, requestCount);
if (count < 0)
{
return kAudioFileEndOfFileError;
}
*actualCount = count;
return noErr;
}
SInt64 AudioDecoderEXT::getSizeCallback(void* inClientData)
{
auto* audioDecoder = (AudioDecoderEXT*)inClientData;
return audioDecoder->_streamSize;
}
} // namespace cocos2d {

View File

@ -1,96 +0,0 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2018-2020 HALX99.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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/include/AudioDecoderManager.h"
#include "audio/include/AudioDecoderOgg.h"
#include "audio/include/AudioMacros.h"
#include "platform/CCFileUtils.h"
#include "base/CCConsole.h"
#if !defined(__APPLE__)
# include "audio/include/AudioDecoderMp3.h"
# include "audio/include/AudioDecoderWav.h"
#else
# include "audio/include/AudioDecoderEXT.h"
#endif
#include "yasio/cxx17/string_view.hpp"
namespace cocos2d
{
bool AudioDecoderManager::init()
{
#if !defined(__APPLE__)
AudioDecoderMp3::lazyInit();
#endif
return true;
}
void AudioDecoderManager::destroy()
{
#if !defined(__APPLE__)
AudioDecoderMp3::destroy();
#endif
}
AudioDecoder* AudioDecoderManager::createDecoder(std::string_view path)
{
cxx17::string_view svPath(path);
if (cxx20::ic::ends_with(svPath, ".ogg"))
{
return new AudioDecoderOgg();
}
#if !defined(__APPLE__)
else if (cxx20::ic::ends_with(svPath, ".mp3"))
{
return new AudioDecoderMp3();
}
else if (cxx20::ic::ends_with(svPath, ".wav"))
{
return new AudioDecoderWav();
}
#else
else
{
return new AudioDecoderEXT();
}
#endif
return nullptr;
}
void AudioDecoderManager::destroyDecoder(AudioDecoder* decoder)
{
if (decoder)
decoder->close();
delete decoder;
}
} // namespace cocos2d

View File

@ -1,301 +0,0 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2020 c4games.com
Copyright (c) 2021 Bytedance Inc.
https://adxe.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 "AudioDecoderMp3"
#include "audio/include/AudioDecoderMp3.h"
#include "audio/include/AudioMacros.h"
#include "platform/CCFileUtils.h"
#include "base/CCConsole.h"
#if !CC_USE_MPG123
# define MINIMP3_IMPLEMENTATION
# include "minimp3/minimp3_ex.h"
#else
// because the cocos2d-x engine has ssize_t typedef, so disable mpg123 ssize_t
# define MPG123_DEF_SSIZE_T
# include "mpg123.h"
#endif
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
# include <unistd.h>
# include <errno.h>
#endif
#if !CC_USE_MPG123
struct mp3dec_impl
{
mp3dec_ex_t _dec;
mp3dec_io_t _decIO;
};
#endif
namespace cocos2d
{
#if !CC_USE_MPG123
static size_t minimp3_read_r(void* buf, size_t size, void* user_data)
{
return ((FileStream*)user_data)->read(buf, size);
}
static int minimp3_seek_r(uint64_t position, void* user_data)
{
return ((FileStream*)user_data)->seek(position, SEEK_SET) < 0 ? -1 : 0;
}
#else
static bool __mp3Inited = false;
static ssize_t mpg123_read_r(void* handle, void* buffer, size_t count)
{
return ((FileStream*)handle)->read(buffer, count);
}
static off_t mpg123_lseek_r(void* handle, off_t offset, int whence)
{
return ((FileStream*)handle)->seek(offset, whence);
}
void mpg123_close_r(void* handle)
{
((FileStream*)handle)->close();
}
#endif
bool AudioDecoderMp3::lazyInit()
{
bool ret = true;
#if CC_USE_MPG123
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;
}
}
#endif
return ret;
}
void AudioDecoderMp3::destroy()
{
#if CC_USE_MPG123
if (__mp3Inited)
{
mpg123_exit();
__mp3Inited = false;
}
#endif
}
AudioDecoderMp3::AudioDecoderMp3() : _handle(nullptr)
{
lazyInit();
}
AudioDecoderMp3::~AudioDecoderMp3()
{
close();
}
bool AudioDecoderMp3::open(std::string_view fullPath)
{
#if !CC_USE_MPG123
do
{
_fileStream = FileUtils::getInstance()->openFileStream(fullPath, FileStream::Mode::READ);
if (!_fileStream)
{
ALOGE("Trouble with minimp3(1): %s\n", strerror(errno));
break;
}
auto handle = new mp3dec_impl();
handle->_decIO.read = minimp3_read_r;
handle->_decIO.seek = minimp3_seek_r;
handle->_decIO.read_data = handle->_decIO.seek_data = _fileStream.get();
if (mp3dec_ex_open_cb(&handle->_dec, &handle->_decIO, MP3D_SEEK_TO_SAMPLE) != 0)
{
delete handle;
break;
}
auto& info = handle->_dec.info;
_channelCount = info.channels; // the _channelCount is samplesPerFrame
_sampleRate = info.hz;
# ifndef MINIMP3_FLOAT_OUTPUT
_sourceFormat = AUDIO_SOURCE_FORMAT::PCM_16;
_bytesPerBlock = sizeof(uint16_t) * _channelCount;
# else
_sourceFormat = AUDIO_SOURCE_FORMAT::PCM_FLT32;
_bytesPerBlock = sizeof(float) * _channelCount;
# endif
// samples
_totalFrames = handle->_dec.samples / _channelCount;
// store the handle
_handle = handle;
_isOpened = true;
return true;
} while (false);
return false;
#else
int32_t rate = 0;
int error = MPG123_OK;
int mp3Encoding = 0;
int channel = 0;
do
{
_handle = mpg123_new(nullptr, &error);
if (nullptr == _handle)
{
ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error));
break;
}
if (!_fileStream.open(fullPath))
{
ALOGE("Trouble with mpg123(1): %s\n", strerror(errno));
break;
}
mpg123_replace_reader_handle(_handle, mpg123_read_r, mpg123_lseek_r, mpg123_close_r);
if (mpg123_open_handle(_handle, _fileStream) != MPG123_OK ||
mpg123_getformat(_handle, &rate, &channel, &mp3Encoding) != MPG123_OK)
{
ALOGE("Trouble with mpg123(2): %s\n", mpg123_strerror(_handle));
break;
}
_channelCount = channel;
_sampleRate = rate;
if (mp3Encoding == MPG123_ENC_SIGNED_16)
{
_bytesPerBlock = 2 * _channelCount;
_sourceFormat = AUDIO_SOURCE_FORMAT::PCM_16;
}
else if (mp3Encoding == MPG123_ENC_FLOAT_32)
{
_bytesPerBlock = 4 * _channelCount;
_sourceFormat = AUDIO_SOURCE_FORMAT::PCM_FLT32;
}
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(_handle);
mpg123_format(_handle, rate, channel, mp3Encoding);
/* Ensure that we can get accurate length by call mpg123_length */
mpg123_scan(_handle);
_totalFrames = mpg123_length(_handle);
_isOpened = true;
return true;
} while (false);
if (_handle != nullptr)
{
mpg123_close(_handle);
mpg123_delete(_handle);
_handle = nullptr;
}
return false;
#endif
}
void AudioDecoderMp3::close()
{
if (isOpened())
{
#if !CC_USE_MPG123
if (_handle)
{
mp3dec_ex_close(&_handle->_dec);
delete _handle;
_handle = nullptr;
_fileStream.reset();
}
#else
if (_handle != nullptr)
{
mpg123_close(_handle);
mpg123_delete(_handle);
_handle = nullptr;
_fileStream.reset();
}
#endif
_isOpened = false;
}
}
uint32_t AudioDecoderMp3::read(uint32_t framesToRead, char* pcmBuf)
{
#if !CC_USE_MPG123
auto sampelsToRead = framesToRead * _channelCount;
auto samplesRead = mp3dec_ex_read(&_handle->_dec, (mp3d_sample_t*)pcmBuf, sampelsToRead);
return static_cast<uint32_t>(samplesRead / _channelCount);
#else
int bytesToRead = framesToBytes(framesToRead);
size_t bytesRead = 0;
int err = mpg123_read(_handle, (unsigned char*)pcmBuf, bytesToRead, &bytesRead);
if (err == MPG123_ERR)
{
ALOGE("Trouble with mpg123: %s\n", mpg123_strerror(_handle));
return 0;
}
return bytesToFrames(bytesRead);
#endif
}
bool AudioDecoderMp3::seek(uint32_t frameOffset)
{
#if !CC_USE_MPG123
return 0 == mp3dec_ex_seek(&_handle->_dec, frameOffset);
#else
off_t offset = mpg123_seek(_handle, frameOffset, SEEK_SET);
// ALOGD("mpg123_seek return: %d", (int)offset);
return (offset >= 0 && offset == frameOffset);
#endif
}
} // namespace cocos2d

View File

@ -1,119 +0,0 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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 "AudioDecoderOgg"
#include "audio/include/AudioDecoderOgg.h"
#include "audio/include/AudioMacros.h"
#include "platform/CCFileUtils.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
# include <unistd.h>
# include <errno.h>
#endif
namespace cocos2d
{
static size_t ov_fread_r(void* buffer, size_t element_size, size_t element_count, void* handle)
{
auto* fs = static_cast<FileStream*>(handle);
return fs->read(buffer, static_cast<uint32_t>(element_size * element_count));
}
static int ov_fseek_r(void* handle, ogg_int64_t offset, int whence)
{
auto* fs = static_cast<FileStream*>(handle);
return fs->seek(offset, whence) < 0 ? -1 : 0;
}
static long ov_ftell_r(void* handle)
{
auto* fs = static_cast<FileStream*>(handle);
return fs->tell();
}
static int ov_fclose_r(void* handle)
{
auto* fs = static_cast<FileStream*>(handle);
delete fs;
return 0;
}
AudioDecoderOgg::AudioDecoderOgg() {}
AudioDecoderOgg::~AudioDecoderOgg()
{
close();
}
bool AudioDecoderOgg::open(std::string_view fullPath)
{
auto fs = FileUtils::getInstance()->openFileStream(fullPath, FileStream::Mode::READ).release();
if (!fs)
{
ALOGE("Trouble with ogg(1): %s\n", strerror(errno));
return false;
}
static ov_callbacks OV_CALLBACKS_POSIX = {ov_fread_r, ov_fseek_r, ov_fclose_r, ov_ftell_r};
if (0 == ov_open_callbacks(fs, &_vf, nullptr, 0, OV_CALLBACKS_POSIX))
{
// header
vorbis_info* vi = ov_info(&_vf, -1);
_sampleRate = static_cast<uint32_t>(vi->rate);
_channelCount = vi->channels;
_bytesPerBlock = 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 = framesToBytes(framesToRead);
int32_t bytesRead = ov_read(&_vf, pcmBuf, bytesToRead, 0, 2, 1, &currentSection);
return bytesToFrames(bytesRead);
}
bool AudioDecoderOgg::seek(uint32_t frameOffset)
{
return 0 == ov_pcm_seek(&_vf, frameOffset);
}
} // namespace cocos2d

View File

@ -1,261 +0,0 @@
/****************************************************************************
Copyright (c) 2018-2020 HALX99.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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 "AudioDecoderWav"
#include <stddef.h>
#include <assert.h>
#include "audio/include/AudioDecoderWav.h"
#include "audio/include/AudioMacros.h"
#include "platform/CCFileUtils.h"
namespace cocos2d
{
enum : uint32_t
{
WAV_SIGN_ID = MAKE_FOURCC('W', 'A', 'V', 'E'),
WAV_FMT_ID = MAKE_FOURCC('f', 'm', 't', ' '),
WAV_DATA_ID = MAKE_FOURCC('d', 'a', 't', 'a'),
WAV_HEADER_SIZE = sizeof(struct WAV_CHUNK_HEADER),
WAV_RIFF_SIZE = sizeof(WAV_RIFF_CHUNK),
};
// 00000001-0000-0010-8000-00aa00389b71
static const GUID WAV_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
// 00000003-0000-0010-8000-00aa00389b71
static const GUID WAV_SUBTYPE_IEEE_FLOAT = {0x00000003,
0x0000,
0x0010,
{0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
static bool wav_scan_chunk(WAV_FILE* wavf, uint32_t chunkID, void* header, void* body, uint32_t bodySize)
{
auto& fs = wavf->Stream;
auto h = (WAV_CHUNK_HEADER*)header;
for (; fs->read(h, WAV_HEADER_SIZE) == WAV_HEADER_SIZE;)
{
wavf->PcmDataOffset += WAV_HEADER_SIZE;
if (h->ChunkID == chunkID)
{ // chunk found
if (body)
{ // require read body?
auto readsize = (std::min)(bodySize, h->ChunkSize);
fs->read(body, readsize);
if (h->ChunkSize > bodySize)
fs->seek(h->ChunkSize - bodySize, SEEK_CUR);
wavf->PcmDataOffset += h->ChunkSize;
}
return true;
}
else
{
// Skip other non specified chunk
fs->seek(h->ChunkSize, SEEK_CUR);
wavf->PcmDataOffset += h->ChunkSize;
}
}
return false;
}
static bool wav_open(std::string_view fullPath, WAV_FILE* wavf)
{
wavf->Stream = FileUtils::getInstance()->openFileStream(fullPath, FileStream::Mode::READ);
if (!wavf->Stream)
return false;
auto& fileStream = wavf->Stream;
wavf->PcmDataOffset = 0;
// Parsing RIFF chunk
fileStream->read(&wavf->FileHeader, WAV_RIFF_SIZE);
wavf->PcmDataOffset += WAV_RIFF_SIZE;
if (wavf->FileHeader.Riff.Format != WAV_SIGN_ID)
return false; // not .wav file
// check somthings
auto h = &wavf->FileHeader;
// Parsing FMT chunk
if (!wav_scan_chunk(wavf, WAV_FMT_ID, &wavf->FileHeader.Fmt, &wavf->FileHeader.Fmt.AudioFormat,
sizeof(wavf->FileHeader.Fmt) - sizeof(WAV_RIFF_CHUNK)))
return false;
auto& fmtInfo = h->Fmt;
int bitDepth = (fmtInfo.BitsPerSample);
// Read PCM data or extensible data if exists.
switch (fmtInfo.AudioFormat)
{ // Check supported format
case WAV_FORMAT::PCM:
case WAV_FORMAT::IEEE:
switch (bitDepth)
{
case 8:
wavf->SourceFormat = AUDIO_SOURCE_FORMAT::PCM_U8;
break;
case 16:
wavf->SourceFormat = AUDIO_SOURCE_FORMAT::PCM_16;
break;
case 24:
wavf->SourceFormat = AUDIO_SOURCE_FORMAT::PCM_24;
break;
case 32:
wavf->SourceFormat = (fmtInfo.AudioFormat == WAV_FORMAT::IEEE) ? AUDIO_SOURCE_FORMAT::PCM_FLT32
: AUDIO_SOURCE_FORMAT::PCM_32;
break;
case 64:
wavf->SourceFormat = (fmtInfo.AudioFormat == WAV_FORMAT::IEEE) ? AUDIO_SOURCE_FORMAT::PCM_FLT64
: AUDIO_SOURCE_FORMAT::PCM_64;
break;
}
break;
case WAV_FORMAT::MULAW:
wavf->SourceFormat = AUDIO_SOURCE_FORMAT::MULAW;
break;
case WAV_FORMAT::ALAW:
wavf->SourceFormat = AUDIO_SOURCE_FORMAT::MULAW;
break;
case WAV_FORMAT::ADPCM:
wavf->SourceFormat = AUDIO_SOURCE_FORMAT::ADPCM;
break;
case WAV_FORMAT::IMA_ADPCM:
wavf->SourceFormat = AUDIO_SOURCE_FORMAT::IMA_ADPCM;
break;
case WAV_FORMAT::EXT:
// Check sub-format.
if (!IsEqualGUID(fmtInfo.ExtParams.SubFormat, WAV_SUBTYPE_PCM) &&
!IsEqualGUID(fmtInfo.ExtParams.SubFormat, WAV_SUBTYPE_IEEE_FLOAT))
{
fileStream.reset();
return false;
}
break;
default:
ALOGW("The wav format %d doesn't supported currently!", (int)fmtInfo.AudioFormat);
fileStream.reset();
assert(false);
return false;
}
return wav_scan_chunk(wavf, WAV_DATA_ID, &h->PcmData, nullptr, 0);
}
static int wav_read(WAV_FILE* wavf, char* pcmBuf, uint32_t bytesToRead)
{
return wavf->Stream->read(pcmBuf, bytesToRead);
}
static int wav_seek(WAV_FILE* wavf, int offset)
{
wavf->Stream->seek(wavf->PcmDataOffset + offset, SEEK_SET);
const auto newOffset = wavf->Stream->tell();
return newOffset >= wavf->PcmDataOffset ? newOffset - wavf->PcmDataOffset : -1;
}
static int wav_close(WAV_FILE* wavf)
{
const auto result = wavf->Stream->close();
wavf->Stream.reset();
return result;
}
AudioDecoderWav::AudioDecoderWav()
{
memset(&_wavf, 0, offsetof(WAV_FILE, Stream));
}
AudioDecoderWav::~AudioDecoderWav()
{
close();
}
bool AudioDecoderWav::open(std::string_view fullPath)
{
if (wav_open(fullPath, &_wavf))
{
auto& fmtInfo = _wavf.FileHeader.Fmt;
_sampleRate = fmtInfo.SampleRate;
_channelCount = fmtInfo.NumChannels;
_bytesPerBlock = fmtInfo.BlockAlign; // == fmtInfo.BitsPerSample * _channelCount / 8;
_sourceFormat = _wavf.SourceFormat;
// See: https://github.com/openalext/openalext/wiki/AL_SOFT_block_alignment
switch (_sourceFormat)
{
case AUDIO_SOURCE_FORMAT::ADPCM:
_samplesPerBlock = (_bytesPerBlock / _channelCount - 7) * 2 + 2;
break;
case AUDIO_SOURCE_FORMAT::IMA_ADPCM:
_samplesPerBlock = (_bytesPerBlock / _channelCount - 4) / 4 * 8 + 1;
break;
default:;
}
_totalFrames = bytesToFrames(_wavf.FileHeader.PcmData.ChunkSize);
_isOpened = true;
return true;
}
return false;
}
uint32_t AudioDecoderWav::framesToBytes(uint32_t frames) const
{
if (_samplesPerBlock == 1)
return _bytesPerBlock * frames;
return frames / _samplesPerBlock * _bytesPerBlock;
}
uint32_t AudioDecoderWav::bytesToFrames(uint32_t bytes) const
{
if (_samplesPerBlock == 1)
return bytes / _bytesPerBlock;
return bytes / _bytesPerBlock * _samplesPerBlock;
}
void AudioDecoderWav::close()
{
if (isOpened())
{
wav_close(&_wavf);
_isOpened = false;
}
}
uint32_t AudioDecoderWav::read(uint32_t framesToRead, char* pcmBuf)
{
auto bytesToRead = framesToBytes(framesToRead);
int32_t bytesRead = wav_read(&_wavf, pcmBuf, bytesToRead);
return bytesToFrames(bytesRead);
}
bool AudioDecoderWav::seek(uint32_t frameOffset)
{
auto offset = framesToBytes(frameOffset);
return wav_seek(&_wavf, offset) == offset;
}
} // namespace cocos2d

View File

@ -1,611 +0,0 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2018 HALX99.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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 "platform/CCPlatformConfig.h"
#include "audio/include/AudioEngine.h"
#include <condition_variable>
#include <queue>
#include "platform/CCFileUtils.h"
#include "base/ccUtils.h"
#include "audio/include/AudioEngineImpl.h"
#define TIME_DELAY_PRECISION 0.0001
#ifdef ERROR
# undef ERROR
#endif // ERROR
using namespace cocos2d;
const int AudioEngine::INVALID_AUDIO_ID = -1;
const float AudioEngine::TIME_UNKNOWN = -1.0f;
// audio file path,audio IDs
hlookup::string_map<std::list<AUDIO_ID>> AudioEngine::_audioPathIDMap;
// profileName,ProfileHelper
hlookup::string_map<AudioEngine::ProfileHelper> AudioEngine::_audioPathProfileHelperMap;
unsigned int AudioEngine::_maxInstances = MAX_AUDIOINSTANCES;
AudioEngine::ProfileHelper* AudioEngine::_defaultProfileHelper = nullptr;
std::unordered_map<AUDIO_ID, AudioEngine::AudioInfo> AudioEngine::_audioIDInfoMap;
AudioEngineImpl* AudioEngine::_audioEngineImpl = nullptr;
AudioEngine::AudioEngineThreadPool* AudioEngine::s_threadPool = nullptr;
bool AudioEngine::_isEnabled = true;
AudioEngine::AudioInfo::AudioInfo()
: profileHelper(nullptr), volume(1.0f), loop(false), duration(TIME_UNKNOWN), state(AudioState::INITIALIZING)
{}
AudioEngine::AudioInfo::~AudioInfo() {}
class AudioEngine::AudioEngineThreadPool
{
public:
AudioEngineThreadPool(int threads = 4) : _stop(false)
{
for (int index = 0; index < threads; ++index)
{
_workers.emplace_back(std::thread(std::bind(&AudioEngineThreadPool::threadFunc, this)));
}
}
void addTask(const std::function<void()>& task)
{
std::unique_lock<std::mutex> lk(_queueMutex);
_taskQueue.emplace(task);
_taskCondition.notify_one();
}
~AudioEngineThreadPool()
{
{
std::unique_lock<std::mutex> lk(_queueMutex);
_stop = true;
_taskCondition.notify_all();
}
for (auto&& worker : _workers)
{
worker.join();
}
}
private:
void threadFunc()
{
while (true)
{
std::function<void()> task = nullptr;
{
std::unique_lock<std::mutex> lk(_queueMutex);
if (_stop)
{
break;
}
if (!_taskQueue.empty())
{
task = std::move(_taskQueue.front());
_taskQueue.pop();
}
else
{
_taskCondition.wait(lk);
continue;
}
}
task();
}
}
std::vector<std::thread> _workers;
std::queue<std::function<void()>> _taskQueue;
std::mutex _queueMutex;
std::condition_variable _taskCondition;
bool _stop;
};
void AudioEngine::end()
{
// make sure everythings cleanup before delete audio engine
// fix #127
uncacheAll();
if (s_threadPool)
{
delete s_threadPool;
s_threadPool = nullptr;
}
delete _audioEngineImpl;
_audioEngineImpl = nullptr;
delete _defaultProfileHelper;
_defaultProfileHelper = nullptr;
}
bool AudioEngine::lazyInit()
{
if (_audioEngineImpl == nullptr)
{
_audioEngineImpl = new AudioEngineImpl();
if (!_audioEngineImpl->init())
{
delete _audioEngineImpl;
_audioEngineImpl = nullptr;
return false;
}
}
if (s_threadPool == nullptr)
{
s_threadPool = new AudioEngineThreadPool();
}
return true;
}
AUDIO_ID AudioEngine::play2d(std::string_view filePath, bool loop, float volume, const AudioProfile* profile)
{
AUDIO_ID ret = AudioEngine::INVALID_AUDIO_ID;
do
{
if (!isEnabled())
{
break;
}
if (!lazyInit())
{
break;
}
if (!FileUtils::getInstance()->isFileExist(filePath))
{
break;
}
auto profileHelper = _defaultProfileHelper;
if (profile && profile != &profileHelper->profile)
{
CC_ASSERT(!profile->name.empty());
profileHelper = &_audioPathProfileHelperMap[profile->name];
profileHelper->profile = *profile;
}
if (_audioIDInfoMap.size() >= _maxInstances)
{
log("Fail to play %s cause by limited max instance of AudioEngine", filePath.data());
break;
}
if (profileHelper)
{
if (profileHelper->profile.maxInstances != 0 &&
profileHelper->audioIDs.size() >= profileHelper->profile.maxInstances)
{
log("Fail to play %s cause by limited max instance of AudioProfile", filePath.data());
break;
}
if (profileHelper->profile.minDelay > TIME_DELAY_PRECISION)
{
auto currTime = utils::gettime();
if (profileHelper->lastPlayTime > TIME_DELAY_PRECISION &&
currTime - profileHelper->lastPlayTime <= profileHelper->profile.minDelay)
{
log("Fail to play %s cause by limited minimum delay", filePath.data());
break;
}
}
}
if (volume < 0.0f)
{
volume = 0.0f;
}
else if (volume > 1.0f)
{
volume = 1.0f;
}
ret = _audioEngineImpl->play2d(filePath, loop, volume);
if (ret != INVALID_AUDIO_ID)
{
_audioPathIDMap[filePath.data()].push_back(ret);
auto it = _audioPathIDMap.find(filePath);
auto& audioRef = _audioIDInfoMap[ret];
audioRef.volume = volume;
audioRef.loop = loop;
audioRef.filePath = it->first;
if (profileHelper)
{
profileHelper->lastPlayTime = utils::gettime();
profileHelper->audioIDs.push_back(ret);
}
audioRef.profileHelper = profileHelper;
}
} while (0);
return ret;
}
void AudioEngine::setLoop(AUDIO_ID audioID, bool loop)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end() && it->second.loop != loop)
{
_audioEngineImpl->setLoop(audioID, loop);
it->second.loop = loop;
}
}
void AudioEngine::setVolume(AUDIO_ID audioID, float volume)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end())
{
if (volume < 0.0f)
{
volume = 0.0f;
}
else if (volume > 1.0f)
{
volume = 1.0f;
}
if (it->second.volume != volume)
{
_audioEngineImpl->setVolume(audioID, volume);
it->second.volume = volume;
}
}
}
void AudioEngine::pause(AUDIO_ID audioID)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end() && it->second.state == AudioState::PLAYING)
{
_audioEngineImpl->pause(audioID);
it->second.state = AudioState::PAUSED;
}
}
void AudioEngine::pauseAll()
{
auto itEnd = _audioIDInfoMap.end();
for (auto it = _audioIDInfoMap.begin(); it != itEnd; ++it)
{
if (it->second.state == AudioState::PLAYING)
{
_audioEngineImpl->pause(it->first);
it->second.state = AudioState::PAUSED;
}
}
}
void AudioEngine::resume(AUDIO_ID audioID)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end() && it->second.state == AudioState::PAUSED)
{
_audioEngineImpl->resume(audioID);
it->second.state = AudioState::PLAYING;
}
}
void AudioEngine::resumeAll()
{
auto itEnd = _audioIDInfoMap.end();
for (auto it = _audioIDInfoMap.begin(); it != itEnd; ++it)
{
if (it->second.state == AudioState::PAUSED)
{
_audioEngineImpl->resume(it->first);
it->second.state = AudioState::PLAYING;
}
}
}
void AudioEngine::stop(AUDIO_ID audioID)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end())
{
_audioEngineImpl->stop(audioID);
remove(audioID);
}
}
void AudioEngine::remove(AUDIO_ID audioID)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end())
{
if (it->second.profileHelper)
{
it->second.profileHelper->audioIDs.remove(audioID);
}
_audioPathIDMap[it->second.filePath].remove(audioID);
_audioIDInfoMap.erase(audioID);
}
}
void AudioEngine::stopAll()
{
if (!_audioEngineImpl)
{
return;
}
_audioEngineImpl->stopAll();
auto itEnd = _audioIDInfoMap.end();
for (auto it = _audioIDInfoMap.begin(); it != itEnd; ++it)
{
if (it->second.profileHelper)
{
it->second.profileHelper->audioIDs.remove(it->first);
}
}
_audioPathIDMap.clear();
_audioIDInfoMap.clear();
}
void AudioEngine::uncache(std::string_view filePath)
{
if (!_audioEngineImpl)
{
return;
}
auto audioIDsIter = _audioPathIDMap.find(filePath);
if (audioIDsIter != _audioPathIDMap.end())
{
//@Note: For safely iterating elements from the audioID list, we need to copy the list
// since 'AudioEngine::remove' may be invoked in '_audioEngineImpl->stop' synchronously.
// If this happens, it will break the iteration, and crash will appear on some devices.
std::list<AUDIO_ID> copiedIDs(audioIDsIter->second);
for (AUDIO_ID audioID : copiedIDs)
{
_audioEngineImpl->stop(audioID);
auto itInfo = _audioIDInfoMap.find(audioID);
if (itInfo != _audioIDInfoMap.end())
{
if (itInfo->second.profileHelper)
{
itInfo->second.profileHelper->audioIDs.remove(audioID);
}
_audioIDInfoMap.erase(audioID);
}
}
_audioPathIDMap.erase(filePath);
}
if (_audioEngineImpl)
{
_audioEngineImpl->uncache(filePath);
}
}
void AudioEngine::uncacheAll()
{
if (!_audioEngineImpl)
{
return;
}
stopAll();
_audioEngineImpl->uncacheAll();
}
float AudioEngine::getDuration(AUDIO_ID audioID)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end() && it->second.state != AudioState::INITIALIZING)
{
if (it->second.duration == TIME_UNKNOWN)
{
it->second.duration = _audioEngineImpl->getDuration(audioID);
}
return it->second.duration;
}
return TIME_UNKNOWN;
}
bool AudioEngine::setCurrentTime(AUDIO_ID audioID, float time)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end() && it->second.state != AudioState::INITIALIZING)
{
return _audioEngineImpl->setCurrentTime(audioID, time);
}
return false;
}
float AudioEngine::getCurrentTime(AUDIO_ID audioID)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end() && it->second.state != AudioState::INITIALIZING)
{
return _audioEngineImpl->getCurrentTime(audioID);
}
return 0.0f;
}
void AudioEngine::setFinishCallback(AUDIO_ID audioID, const std::function<void(AUDIO_ID, std::string_view)>& callback)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end())
{
_audioEngineImpl->setFinishCallback(audioID, callback);
}
}
bool AudioEngine::setMaxAudioInstance(int maxInstances)
{
if (maxInstances > 0 && maxInstances <= MAX_AUDIOINSTANCES)
{
_maxInstances = maxInstances;
return true;
}
return false;
}
bool AudioEngine::isLoop(AUDIO_ID audioID)
{
auto tmpIterator = _audioIDInfoMap.find(audioID);
if (tmpIterator != _audioIDInfoMap.end())
{
return tmpIterator->second.loop;
}
log("AudioEngine::isLoop-->The audio instance %d is non-existent", audioID);
return false;
}
float AudioEngine::getVolume(AUDIO_ID audioID)
{
auto tmpIterator = _audioIDInfoMap.find(audioID);
if (tmpIterator != _audioIDInfoMap.end())
{
return tmpIterator->second.volume;
}
log("AudioEngine::getVolume-->The audio instance %d is non-existent", audioID);
return 0.0f;
}
AudioEngine::AudioState AudioEngine::getState(AUDIO_ID audioID)
{
auto tmpIterator = _audioIDInfoMap.find(audioID);
if (tmpIterator != _audioIDInfoMap.end())
{
return tmpIterator->second.state;
}
return AudioState::ERROR;
}
AudioProfile* AudioEngine::getProfile(AUDIO_ID audioID)
{
auto it = _audioIDInfoMap.find(audioID);
if (it != _audioIDInfoMap.end())
{
return &it->second.profileHelper->profile;
}
return nullptr;
}
AudioProfile* AudioEngine::getDefaultProfile()
{
if (_defaultProfileHelper == nullptr)
{
_defaultProfileHelper = new ProfileHelper();
}
return &_defaultProfileHelper->profile;
}
AudioProfile* AudioEngine::getProfile(std::string_view name)
{
auto it = _audioPathProfileHelperMap.find(name);
if (it != _audioPathProfileHelperMap.end())
{
return &it->second.profile;
}
else
{
return nullptr;
}
}
void AudioEngine::preload(std::string_view filePath, std::function<void(bool isSuccess)> callback)
{
if (!isEnabled())
{
callback(false);
return;
}
lazyInit();
if (_audioEngineImpl)
{
if (!FileUtils::getInstance()->isFileExist(filePath))
{
if (callback)
{
callback(false);
}
return;
}
_audioEngineImpl->preload(filePath, callback);
}
}
void AudioEngine::addTask(const std::function<void()>& task)
{
lazyInit();
if (_audioEngineImpl && s_threadPool)
{
s_threadPool->addTask(task);
}
}
int AudioEngine::getPlayingAudioCount()
{
return static_cast<int>(_audioIDInfoMap.size());
}
void AudioEngine::setEnabled(bool isEnabled)
{
if (_isEnabled != isEnabled)
{
_isEnabled = isEnabled;
if (!_isEnabled)
{
stopAll();
}
}
}
bool AudioEngine::isEnabled()
{
return _isEnabled;
}

View File

@ -1,872 +0,0 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2018-2020 HALX99.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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 "AudioEngineImpl"
#include "platform/CCPlatformConfig.h"
#include "audio/include/AudioEngineImpl.h"
#include "audio/include/AudioDecoderManager.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC
# import <AVFoundation/AVFoundation.h>
#endif
#include "audio/include/AudioEngine.h"
#include "platform/CCFileUtils.h"
#include "base/CCDirector.h"
#include "base/CCScheduler.h"
#include "base/ccUtils.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
# import <UIKit/UIKit.h>
#endif
using namespace cocos2d;
static ALCdevice* s_ALDevice = nullptr;
static ALCcontext* s_ALContext = nullptr;
static AudioEngineImpl* s_instance = nullptr;
static void ccALPauseDevice()
{
ALOGD("%s", "===> ccALPauseDevice");
#if CC_USE_ALSOFT
alcDevicePauseSOFT(s_ALDevice);
#else
if (alcGetCurrentContext())
alcMakeContextCurrent(nullptr);
#endif
}
static void ccALResumeDevice()
{
ALOGD("%s", "===> ccALResumeDevice");
#if CC_USE_ALSOFT
alcDeviceResumeSOFT(s_ALDevice);
#else
if (alcGetCurrentContext())
alcMakeContextCurrent(nullptr);
alcMakeContextCurrent(s_ALContext);
#endif
}
#if defined(__APPLE__)
typedef ALvoid (*alSourceNotificationProc)(ALuint sid, ALuint notificationID, ALvoid* userData);
typedef ALenum (*alSourceAddNotificationProcPtr)(ALuint sid,
ALuint notificationID,
alSourceNotificationProc notifyProc,
ALvoid* userData);
static ALenum alSourceAddNotificationExt(ALuint sid,
ALuint notificationID,
alSourceNotificationProc notifyProc,
ALvoid* userData)
{
static alSourceAddNotificationProcPtr proc = nullptr;
if (proc == nullptr)
{
proc = (alSourceAddNotificationProcPtr)alcGetProcAddress(nullptr, "alSourceAddNotification");
}
if (proc)
{
return proc(sid, notificationID, notifyProc, userData);
}
return AL_INVALID_VALUE;
}
# if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
@interface AudioEngineSessionHandler : NSObject {
}
- (id)init;
- (void)handleInterruption:(NSNotification*)notification;
@end
@implementation AudioEngineSessionHandler
- (id)init
{
if (self = [super init])
{
int deviceVer = [[[UIDevice currentDevice] systemVersion] intValue];
ALOGD("===> The device version: %d", deviceVer);
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleInterruption:)
name:AVAudioSessionInterruptionNotification
object:[AVAudioSession sharedInstance]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleInterruption:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleInterruption:)
name:UIApplicationWillResignActiveNotification
object:nil];
BOOL success = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
if (!success)
ALOGE("Fail to set audio session.");
}
return self;
}
- (void)handleInterruption:(NSNotification*)notification
{
static bool isAudioSessionInterrupted = false;
static bool resumeOnBecomingActive = false;
static bool pauseOnResignActive = false;
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification])
{
NSInteger reason = [[[notification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey] integerValue];
if (reason == AVAudioSessionInterruptionTypeBegan)
{
isAudioSessionInterrupted = true;
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive)
{
ALOGD("AVAudioSessionInterruptionTypeBegan, application != UIApplicationStateActive, "
"alcMakeContextCurrent(nullptr)");
}
else
{
ALOGD("AVAudioSessionInterruptionTypeBegan, application == UIApplicationStateActive, "
"pauseOnResignActive = true");
}
// We always pause device when interruption began
ccALPauseDevice();
}
else if (reason == AVAudioSessionInterruptionTypeEnded)
{
isAudioSessionInterrupted = false;
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive)
{
ALOGD("AVAudioSessionInterruptionTypeEnded, application == UIApplicationStateActive, "
"alcMakeContextCurrent(s_ALContext)");
NSError* error = nil;
[[AVAudioSession sharedInstance] setActive:YES error:&error];
ccALResumeDevice();
if (Director::getInstance()->isPaused())
{
ALOGD("AVAudioSessionInterruptionTypeEnded, director was paused, try to resume it.");
Director::getInstance()->resume();
}
}
else
{
ALOGD("AVAudioSessionInterruptionTypeEnded, application != UIApplicationStateActive, "
"resumeOnBecomingActive = true");
resumeOnBecomingActive = true;
}
}
}
else if ([notification.name isEqualToString:UIApplicationWillResignActiveNotification])
{
ALOGD("UIApplicationWillResignActiveNotification");
if (pauseOnResignActive)
{
pauseOnResignActive = false;
ALOGD("UIApplicationWillResignActiveNotification, alcMakeContextCurrent(nullptr)");
ccALPauseDevice();
}
}
else if ([notification.name isEqualToString:UIApplicationDidBecomeActiveNotification])
{
ALOGD("UIApplicationDidBecomeActiveNotification");
if (resumeOnBecomingActive)
{
resumeOnBecomingActive = false;
ALOGD("UIApplicationDidBecomeActiveNotification, alcMakeContextCurrent(s_ALContext)");
NSError* error = nil;
BOOL success = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&error];
if (!success)
{
ALOGE("Fail to set audio session.");
return;
}
[[AVAudioSession sharedInstance] setActive:YES error:&error];
ccALResumeDevice();
}
else if (isAudioSessionInterrupted)
{
ALOGD("Audio session is still interrupted!");
}
}
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionInterruptionNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationWillResignActiveNotification
object:nil];
[super dealloc];
}
@end
static id s_AudioEngineSessionHandler = nullptr;
# endif
ALvoid AudioEngineImpl::myAlSourceNotificationCallback(ALuint sid, ALuint notificationID, ALvoid* userData)
{
// Currently, we only care about AL_BUFFERS_PROCESSED event
if (notificationID != AL_BUFFERS_PROCESSED)
return;
AudioPlayer* player = nullptr;
s_instance->_threadMutex.lock();
for (const auto& e : s_instance->_audioPlayers)
{
player = e.second;
if (player->_alSource == sid && player->_streamingSource)
{
player->wakeupRotateThread();
}
}
s_instance->_threadMutex.unlock();
}
#endif
AudioEngineImpl::AudioEngineImpl() : _scheduled(false), _currentAudioID(0), _scheduler(nullptr)
{
s_instance = this;
}
AudioEngineImpl::~AudioEngineImpl()
{
if (_scheduled && _scheduler != nullptr)
{
_scheduler->unschedule(CC_SCHEDULE_SELECTOR(AudioEngineImpl::update), this);
}
if (s_ALContext)
{
alDeleteSources(MAX_AUDIOINSTANCES, _alSources);
_audioCaches.clear();
alcMakeContextCurrent(nullptr);
alcDestroyContext(s_ALContext);
s_ALContext = nullptr;
}
if (s_ALDevice)
{
alcCloseDevice(s_ALDevice);
s_ALDevice = nullptr;
}
AudioDecoderManager::destroy();
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
[s_AudioEngineSessionHandler release];
#endif
s_instance = nullptr;
}
bool AudioEngineImpl::init()
{
bool ret = false;
do
{
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
s_AudioEngineSessionHandler = [[AudioEngineSessionHandler alloc] init];
#endif
s_ALDevice = alcOpenDevice(nullptr);
if (s_ALDevice)
{
alGetError();
s_ALContext = alcCreateContext(s_ALDevice, nullptr);
alcMakeContextCurrent(s_ALContext);
alGenSources(MAX_AUDIOINSTANCES, _alSources);
auto alError = alGetError();
if (alError != AL_NO_ERROR)
{
ALOGE("%s:generating sources failed! error = %x\n", __FUNCTION__, alError);
break;
}
for (int i = 0; i < MAX_AUDIOINSTANCES; ++i)
{
_unusedSourcesPool.push(_alSources[i]);
#if defined(__APPLE__)
alSourceAddNotificationExt(_alSources[i], AL_BUFFERS_PROCESSED, myAlSourceNotificationCallback,
nullptr);
#endif
}
// fixed #16170: Random crash in alGenBuffers(AudioCache::readDataTask) at startup
// Please note that, as we know the OpenAL operation is atomic (threadsafe),
// 'alGenBuffers' may be invoked by different threads. But in current implementation of 'alGenBuffers',
// When the first time it's invoked, application may crash!!!
// Why? OpenAL is opensource by Apple and could be found at
// http://opensource.apple.com/source/OpenAL/OpenAL-48.7/Source/OpenAL/oalImp.cpp .
/*
void InitializeBufferMap()
{
if (gOALBufferMap == NULL) // Position 1
{
gOALBufferMap = new OALBufferMap (); // Position 2
// Position Gap
gBufferMapLock = new CAGuard("OAL:BufferMapLock"); // Position 3
gDeadOALBufferMap = new OALBufferMap ();
OALBuffer *newBuffer = new OALBuffer (AL_NONE);
gOALBufferMap->Add(AL_NONE, &newBuffer);
}
}
AL_API ALvoid AL_APIENTRY alGenBuffers(ALsizei n, ALuint *bids)
{
...
try {
if (n < 0)
throw ((OSStatus) AL_INVALID_VALUE);
InitializeBufferMap();
if (gOALBufferMap == NULL)
throw ((OSStatus) AL_INVALID_OPERATION);
CAGuard::Locker locked(*gBufferMapLock); // Position 4
...
...
}
*/
// 'gBufferMapLock' will be initialized in the 'InitializeBufferMap' function,
// that's the problem. It means that 'InitializeBufferMap' may be invoked in different threads.
// It will be very dangerous in multi-threads environment.
// Imagine there're two threads (Thread A, Thread B), they call 'alGenBuffers' simultaneously.
// While A goto 'Position Gap', 'gOALBufferMap' was assigned, then B goto 'Position 1' and find
// that 'gOALBufferMap' isn't NULL, B just jump over 'InitialBufferMap' and goto 'Position 4'.
// Meanwhile, A is still at 'Position Gap', B will crash at '*gBufferMapLock' since 'gBufferMapLock'
// is still a null pointer. Oops, how could Apple implemented this method in this fucking way?
// Workaround is do an unused invocation in the mainthread right after OpenAL is initialized successfully
// as bellow.
// ================ Workaround begin ================ //
#if !CC_USE_ALSOFT
ALuint unusedAlBufferId = 0;
alGenBuffers(1, &unusedAlBufferId);
alDeleteBuffers(1, &unusedAlBufferId);
#endif
// ================ Workaround end ================ //
_scheduler = Director::getInstance()->getScheduler();
ret = AudioDecoderManager::init();
const char* vender = alGetString(AL_VENDOR);
const char* version = alGetString(AL_VERSION);
ALOGI("OpenAL was initialized successfully, vender:%s, version:%s", vender, version);
}
} while (false);
return ret;
}
AudioCache* AudioEngineImpl::preload(std::string_view filePath, std::function<void(bool)> callback)
{
AudioCache* audioCache = nullptr;
auto it = _audioCaches.find(filePath);
if (it == _audioCaches.end())
{
audioCache = new AudioCache(); // hlookup_second(it);
_audioCaches.emplace(filePath, std::unique_ptr<AudioCache>(audioCache));
audioCache->_fileFullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
unsigned int cacheId = audioCache->_id;
auto isCacheDestroyed = audioCache->_isDestroyed;
AudioEngine::addTask([audioCache, cacheId, isCacheDestroyed]() {
if (*isCacheDestroyed)
{
ALOGV("AudioCache (id=%u) was destroyed, no need to launch readDataTask.", cacheId);
audioCache->setSkipReadDataTask(true);
return;
}
audioCache->readDataTask(cacheId);
});
}
else
{
audioCache = it->second.get();
}
if (audioCache && callback)
{
audioCache->addLoadCallback(callback);
}
return audioCache;
}
AUDIO_ID AudioEngineImpl::play2d(std::string_view filePath, bool loop, float volume)
{
if (s_ALDevice == nullptr)
{
return AudioEngine::INVALID_AUDIO_ID;
}
ALuint alSource = findValidSource();
if (alSource == AL_INVALID)
{
return AudioEngine::INVALID_AUDIO_ID;
}
auto player = new AudioPlayer;
if (player == nullptr)
{
return AudioEngine::INVALID_AUDIO_ID;
}
player->_alSource = alSource;
player->_loop = loop;
player->_volume = volume;
auto audioCache = preload(filePath, nullptr);
if (audioCache == nullptr)
{
delete player;
return AudioEngine::INVALID_AUDIO_ID;
}
player->setCache(audioCache);
_threadMutex.lock();
_audioPlayers.emplace(++_currentAudioID, player);
_threadMutex.unlock();
audioCache->addPlayCallback(std::bind(&AudioEngineImpl::_play2d, this, audioCache, _currentAudioID));
if (!_scheduled)
{
_scheduled = true;
_scheduler->schedule(CC_SCHEDULE_SELECTOR(AudioEngineImpl::update), this, 0.05f, false);
}
return _currentAudioID;
}
void AudioEngineImpl::_play2d(AudioCache* cache, AUDIO_ID audioID)
{
std::unique_lock<std::recursive_mutex> lck(_threadMutex);
auto iter = _audioPlayers.find(audioID);
if (iter == _audioPlayers.end())
return;
auto player = iter->second;
// Note: It maybe in sub thread or main thread :(
if (!*cache->_isDestroyed && cache->_state == AudioCache::State::READY)
{
if (player->play2d())
{
_scheduler->performFunctionInCocosThread([audioID]() {
if (AudioEngine::_audioIDInfoMap.find(audioID) != AudioEngine::_audioIDInfoMap.end())
{
AudioEngine::_audioIDInfoMap[audioID].state = AudioEngine::AudioState::PLAYING;
}
});
}
}
else
{
ALOGD("AudioEngineImpl::_play2d, cache was destroyed or not ready!");
player->_removeByAudioEngine = true;
}
}
ALuint AudioEngineImpl::findValidSource()
{
ALuint sourceId = AL_INVALID;
if (!_unusedSourcesPool.empty())
{
sourceId = _unusedSourcesPool.front();
_unusedSourcesPool.pop();
}
return sourceId;
}
void AudioEngineImpl::setVolume(AUDIO_ID audioID, float volume)
{
std::unique_lock<std::recursive_mutex> lck(_threadMutex);
auto iter = _audioPlayers.find(audioID);
if (iter == _audioPlayers.end())
return;
auto player = iter->second;
lck.unlock();
player->_volume = volume;
if (player->_ready)
{
alSourcef(player->_alSource, AL_GAIN, volume);
auto error = alGetError();
if (error != AL_NO_ERROR)
{
ALOGE("%s: audio id = " AUDIO_ID_PRID ", error = %x", __FUNCTION__, audioID, error);
}
}
}
void AudioEngineImpl::setLoop(AUDIO_ID audioID, bool loop)
{
std::unique_lock<std::recursive_mutex> lck(_threadMutex);
auto iter = _audioPlayers.find(audioID);
if (iter == _audioPlayers.end())
return;
auto player = iter->second;
lck.unlock();
if (player->_ready)
{
if (player->_streamingSource)
{
player->setLoop(loop);
}
else
{
if (loop)
{
alSourcei(player->_alSource, AL_LOOPING, AL_TRUE);
}
else
{
alSourcei(player->_alSource, AL_LOOPING, AL_FALSE);
}
auto error = alGetError();
if (error != AL_NO_ERROR)
{
ALOGE("%s: audio id = " AUDIO_ID_PRID ", error = %x", __FUNCTION__, audioID, error);
}
}
}
else
{
player->_loop = loop;
}
}
bool AudioEngineImpl::pause(AUDIO_ID audioID)
{
std::unique_lock<std::recursive_mutex> lck(_threadMutex);
auto iter = _audioPlayers.find(audioID);
if (iter == _audioPlayers.end())
return false;
auto player = iter->second;
lck.unlock();
bool ret = true;
alSourcePause(player->_alSource);
auto error = alGetError();
if (error != AL_NO_ERROR)
{
ret = false;
ALOGE("%s: audio id = " AUDIO_ID_PRID ", error = %x\n", __FUNCTION__, audioID, error);
}
return ret;
}
bool AudioEngineImpl::resume(AUDIO_ID audioID)
{
bool ret = true;
std::unique_lock<std::recursive_mutex> lck(_threadMutex);
auto iter = _audioPlayers.find(audioID);
if (iter == _audioPlayers.end())
return false;
auto player = iter->second;
lck.unlock();
alSourcePlay(player->_alSource);
auto error = alGetError();
if (error != AL_NO_ERROR)
{
ret = false;
ALOGE("%s: audio id = " AUDIO_ID_PRID ", error = %x\n", __FUNCTION__, audioID, error);
}
return ret;
}
void AudioEngineImpl::stop(AUDIO_ID audioID)
{
std::unique_lock<std::recursive_mutex> lck(_threadMutex);
auto iter = _audioPlayers.find(audioID);
if (iter == _audioPlayers.end())
return;
auto player = iter->second;
player->destroy();
// Call '_updatePlayersState' method to cleanup immediately since the schedule may be cancelled without any
// notification.
_updatePlayers(true);
}
void AudioEngineImpl::stopAll()
{
std::lock_guard<std::recursive_mutex> lck(_threadMutex);
for (auto&& player : _audioPlayers)
{
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;
// }
// Call '_updatePlayers' method to cleanup immediately since the schedule may be cancelled without any notification.
_updatePlayers(true);
}
float AudioEngineImpl::getDuration(AUDIO_ID audioID)
{
std::lock_guard<std::recursive_mutex> lck(_threadMutex);
auto it = _audioPlayers.find(audioID);
if (it != _audioPlayers.end())
{
auto player = it->second;
if (player->_ready)
{
return player->_audioCache->_duration;
}
}
return AudioEngine::TIME_UNKNOWN;
}
float AudioEngineImpl::getCurrentTime(AUDIO_ID audioID)
{
std::unique_lock<std::recursive_mutex> lck(_threadMutex);
auto it = _audioPlayers.find(audioID);
if (it == _audioPlayers.end())
return 0.0f;
float ret = 0.0f;
auto player = it->second;
if (player->_ready)
{
if (player->_streamingSource)
{
ret = player->getTime();
}
else
{
alGetSourcef(player->_alSource, AL_SEC_OFFSET, &ret);
auto error = alGetError();
if (error != AL_NO_ERROR)
{
ALOGE("%s, audio id:" AUDIO_ID_PRID ",error code:%x", __FUNCTION__, audioID, error);
}
}
}
return ret;
}
bool AudioEngineImpl::setCurrentTime(AUDIO_ID audioID, float time)
{
bool ret = false;
std::unique_lock<std::recursive_mutex> lck(_threadMutex);
auto iter = _audioPlayers.find(audioID);
if (iter == _audioPlayers.end())
return false;
auto player = iter->second;
do
{
if (!player->_ready)
{
break;
}
if (player->_streamingSource)
{
ret = player->setTime(time);
break;
}
else
{
if (player->_audioCache->_framesRead != player->_audioCache->_totalFrames &&
(time * player->_audioCache->_sampleRate) > player->_audioCache->_framesRead)
{
ALOGE("%s: audio id = " AUDIO_ID_PRID, __FUNCTION__, audioID);
break;
}
alSourcef(player->_alSource, AL_SEC_OFFSET, time);
auto error = alGetError();
if (error != AL_NO_ERROR)
{
ALOGE("%s: audio id = " AUDIO_ID_PRID ", error = %x", __FUNCTION__, audioID, error);
}
ret = true;
}
} while (0);
return ret;
}
void AudioEngineImpl::setFinishCallback(AUDIO_ID audioID,
const std::function<void(AUDIO_ID, std::string_view)>& callback)
{
std::unique_lock<std::recursive_mutex> lck(_threadMutex);
auto iter = _audioPlayers.find(audioID);
if (iter == _audioPlayers.end())
return;
auto player = iter->second;
lck.unlock();
player->_finishCallbak = callback;
}
void AudioEngineImpl::update(float /*dt*/)
{
std::unique_lock<std::recursive_mutex> lck(_threadMutex);
_updatePlayers(false);
}
void AudioEngineImpl::_updatePlayers(bool forStop)
{
AUDIO_ID audioID;
AudioPlayer* player;
ALuint alSource;
// ALOGV("AudioPlayer count: %d", (int)_audioPlayers.size());
for (auto it = _audioPlayers.begin(); it != _audioPlayers.end();)
{
audioID = it->first;
player = it->second;
alSource = player->_alSource;
if (player->_removeByAudioEngine)
{
AudioEngine::remove(audioID);
it = _audioPlayers.erase(it);
delete player;
_unusedSourcesPool.push(alSource);
}
else if (player->_ready && player->isFinished())
{
std::string filePath;
if (player->_finishCallbak)
{
auto& audioInfo = AudioEngine::_audioIDInfoMap[audioID];
filePath = audioInfo.filePath;
}
AudioEngine::remove(audioID);
it = _audioPlayers.erase(it);
if (player->_finishCallbak)
{
/// ###IMPORTANT: don't call immidiately, because at callback, user-end may play a new audio
/// cause _audioPlayers' iterator goan to invalid.
_finishCallbacks.push_back([finishCallback = std::move(player->_finishCallbak), audioID,
filePath = std::move(filePath)]() { finishCallback(audioID, filePath); });
}
// clear cache when audio player finsihed properly
player->setCache(nullptr);
delete player;
_unusedSourcesPool.push(alSource);
}
else
{
++it;
}
}
// don't invoke finish callback when stop/stopAll to avoid stack overflow
if (UTILS_LIKELY(!forStop))
{
if (!_finishCallbacks.empty())
{
for (auto& finishCallback : _finishCallbacks)
finishCallback();
_finishCallbacks.clear();
}
if (_audioPlayers.empty())
_unscheduleUpdate();
}
else if (!_audioPlayers.empty() && !_finishCallbacks.empty())
_unscheduleUpdate();
}
void AudioEngineImpl::_unscheduleUpdate()
{
if (_scheduled)
{
_scheduled = false;
_scheduler->unschedule(CC_SCHEDULE_SELECTOR(AudioEngineImpl::update), this);
}
}
void AudioEngineImpl::uncache(std::string_view filePath)
{
_audioCaches.erase(filePath);
}
void AudioEngineImpl::uncacheAll()
{
// prevent player hold invalid AudioCache* pointer, since all audio caches purged
for (auto& player : _audioPlayers)
player.second->setCache(nullptr);
_audioCaches.clear();
}

View File

@ -1,465 +0,0 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2018-2020 HALX99.
Copyright (c) 2021 Bytedance Inc.
https://adxe.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 "AudioPlayer"
#include "platform/CCPlatformConfig.h"
#include "audio/include/AudioPlayer.h"
#include "audio/include/AudioCache.h"
#include "platform/CCFileUtils.h"
#include "audio/include/AudioDecoder.h"
#include "audio/include/AudioDecoderManager.h"
#ifdef VERY_VERY_VERBOSE_LOGGING
# define ALOGVV ALOGV
#else
# define ALOGVV(...) \
do \
{ \
} while (false)
#endif
using namespace cocos2d;
namespace
{
unsigned int __idIndex = 0;
}
AudioPlayer::AudioPlayer()
: _audioCache(nullptr)
, _finishCallbak(nullptr)
, _isDestroyed(false)
, _removeByAudioEngine(false)
, _ready(false)
, _currTime(0.0f)
, _streamingSource(false)
, _rotateBufferThread(nullptr)
, _timeDirty(false)
, _isRotateThreadExited(false)
#if defined(__APPLE__)
, _needWakeupRotateThread(false)
#endif
, _id(++__idIndex)
{
memset(_bufferIds, 0, sizeof(_bufferIds));
}
AudioPlayer::~AudioPlayer()
{
ALOGVV("~AudioPlayer() (%p), id=%u", this, _id);
destroy();
if (_streamingSource)
{
alDeleteBuffers(QUEUEBUFFER_NUM, _bufferIds);
}
}
void AudioPlayer::destroy()
{
std::unique_lock<std::mutex> lck(_play2dMutex);
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));
}
}
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!");
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
// some specific OpenAL implement defects existed on iOS platform
// refer to: https://github.com/cocos2d/cocos2d-x/issues/18597
ALint sourceState;
ALint bufferProcessed = 0;
alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState);
if (sourceState == AL_PLAYING)
{
alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed);
while (bufferProcessed < QUEUEBUFFER_NUM)
{
std::this_thread::sleep_for(std::chrono::milliseconds(2));
alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed);
}
alSourceUnqueueBuffers(_alSource, QUEUEBUFFER_NUM, _bufferIds);
CHECK_AL_ERROR_DEBUG();
}
ALOGVV("UnqueueBuffers Before alSourceStop");
#endif
}
}
} while (false);
ALOGVV("Before alSourceStop");
alSourceStop(_alSource);
CHECK_AL_ERROR_DEBUG();
ALOGVV("Before alSourcei");
alSourcei(_alSource, AL_BUFFER, 0);
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()
{
std::unique_lock<std::mutex> lck(_play2dMutex);
ALOGV("AudioPlayer::play2d, _alSource: %u, player id=%u", _alSource, _id);
if (_isDestroyed)
return false;
/*********************************************************************/
/* 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
{
alGenBuffers(QUEUEBUFFER_NUM, _bufferIds);
auto alError = alGetError();
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)
{
// To continuously stream audio from a source without interruption, buffer queuing is required.
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);
// OpenAL framework: sometime when switch audio too fast, the result state will error, but there is no any
// alError, so just skip for workaround.
assert(state == AL_PLAYING);
_ready = true;
ret = true;
} while (false);
if (!ret)
{
_removeByAudioEngine = true;
}
return ret;
}
// rotateBufferThread is used to rotate alBufferData for _alSource when playing big audio file
void AudioPlayer::rotateBufferThread(int offsetFrame)
{
#if defined(__APPLE__)
pthread_setname_np("ALStreaming");
#endif
char* tmpBuffer = nullptr;
auto& fullPath = _audioCache->_fileFullPath;
AudioDecoder* decoder = AudioDecoderManager::createDecoder(fullPath);
long long rotateSleepTime = static_cast<long long>(QUEUEBUFFER_TIME_STEP * 1000) / 2;
do
{
BREAK_IF(decoder == nullptr || !decoder->open(fullPath));
uint32_t framesRead = 0;
const uint32_t framesToRead = _audioCache->_queBufferFrames;
const uint32_t bufferSize = decoder->framesToBytes(framesToRead);
#if CC_USE_ALSOFT
const auto sourceFormat = decoder->getSourceFormat();
#endif
tmpBuffer = (char*)malloc(bufferSize);
memset(tmpBuffer, 0, bufferSize);
if (offsetFrame != 0)
{
decoder->seek(offsetFrame);
}
ALint sourceState;
ALint bufferProcessed = 0;
bool needToExitThread = false;
while (!_isDestroyed)
{
alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState);
if (sourceState == AL_PLAYING)
{
alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed);
while (bufferProcessed > 0)
{
bufferProcessed--;
if (_timeDirty)
{
_timeDirty = false;
offsetFrame = _currTime * decoder->getSampleRate();
decoder->seek(offsetFrame);
}
else
{
_currTime += QUEUEBUFFER_TIME_STEP;
if (_currTime > _audioCache->_duration)
{
if (_loop)
{
_currTime = 0.0f;
}
else
{
_currTime = _audioCache->_duration;
}
}
}
framesRead = decoder->readFixedFrames(framesToRead, tmpBuffer);
if (framesRead == 0)
{
if (_loop)
{
decoder->seek(0);
framesRead = decoder->readFixedFrames(framesToRead, tmpBuffer);
}
else
{
needToExitThread = true;
break;
}
}
/*
While the source is playing, alSourceUnqueueBuffers can be called to remove buffers which have
already played. Those buffers can then be filled with new data or discarded. New or refilled
buffers can then be attached to the playing source using alSourceQueueBuffers. As long as there is
always a new buffer to play in the queue, the source will continue to play.
*/
ALuint bid;
alSourceUnqueueBuffers(_alSource, 1, &bid);
#if CC_USE_ALSOFT
if (sourceFormat == AUDIO_SOURCE_FORMAT::ADPCM || sourceFormat == AUDIO_SOURCE_FORMAT::IMA_ADPCM)
alBufferi(bid, AL_UNPACK_BLOCK_ALIGNMENT_SOFT, decoder->getSamplesPerBlock());
#endif
alBufferData(bid, _audioCache->_format, tmpBuffer, decoder->framesToBytes(framesRead),
decoder->getSampleRate());
alSourceQueueBuffers(_alSource, 1, &bid);
}
}
/* Make sure the source hasn't underrun */
else if (sourceState != AL_PAUSED)
{
ALint queued;
/* If no buffers are queued, playback is finished */
alGetSourcei(_alSource, AL_BUFFERS_QUEUED, &queued);
if (queued == 0)
{
needToExitThread = true;
}
else
{
alSourcePlay(_alSource);
if (alGetError() != AL_NO_ERROR)
{
ALOGE("Error restarting playback!");
needToExitThread = true;
}
}
}
std::unique_lock<std::mutex> lk(_sleepMutex);
if (_isDestroyed || needToExitThread)
{
break;
}
#if defined(__APPLE__)
if (!_needWakeupRotateThread)
{
_sleepCondition.wait_for(lk, std::chrono::milliseconds(rotateSleepTime));
}
_needWakeupRotateThread = false;
#else
_sleepCondition.wait_for(lk, std::chrono::milliseconds(rotateSleepTime));
#endif
}
} while (false);
ALOGVV("Exit rotate buffer thread ...");
if (decoder)
decoder->close();
AudioDecoderManager::destroyDecoder(decoder);
free(tmpBuffer);
_isRotateThreadExited = true;
}
#if defined(__APPLE__)
void AudioPlayer::wakeupRotateThread()
{
_needWakeupRotateThread = true;
_sleepCondition.notify_all();
}
#endif
bool AudioPlayer::isFinished() const
{
if (_streamingSource)
return _isRotateThreadExited;
else
{
ALint sourceState;
alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState);
return sourceState == AL_STOPPED;
}
}
bool AudioPlayer::setLoop(bool loop)
{
if (!_isDestroyed)
{
_loop = loop;
return true;
}
return false;
}
bool AudioPlayer::setTime(float time)
{
if (!_isDestroyed && time >= 0.0f && time < _audioCache->_duration)
{
_currTime = time;
_timeDirty = true;
return true;
}
return false;
}

View File

@ -1,2 +0,0 @@
/* linux-link.cpp: the workaround for solving clang link issue, said: unrefenreced "gnu_objc_personality_v0" */
#include "AudioEngineImpl.mm"

View File

@ -61,7 +61,7 @@ THE SOFTWARE.
#include "base/ObjectFactory.h" #include "base/ObjectFactory.h"
#include "platform/CCApplication.h" #include "platform/CCApplication.h"
#include "renderer/backend/ProgramCache.h" #include "renderer/backend/ProgramCache.h"
#include "audio/include/AudioEngine.h" #include "audio/AudioEngine.h"
#if CC_ENABLE_SCRIPT_BINDING #if CC_ENABLE_SCRIPT_BINDING
# include "base/CCScriptSupport.h" # include "base/CCScriptSupport.h"

View File

@ -23,7 +23,6 @@ THE SOFTWARE.
****************************************************************************/ ****************************************************************************/
#include "CCComAudio.h" #include "CCComAudio.h"
// #include "audio/include/SimpleAudioEngine.h"
#include "platform/CCFileUtils.h" #include "platform/CCFileUtils.h"
namespace cocostudio namespace cocostudio

View File

@ -25,7 +25,6 @@ THE SOFTWARE.
#include "CocoStudio.h" #include "CocoStudio.h"
#include "ui/CocosGUI.h" #include "ui/CocosGUI.h"
// #include "audio/include/SimpleAudioEngine.h"
#include "base/ObjectFactory.h" #include "base/ObjectFactory.h"
#include "base/ccUtils.h" #include "base/ccUtils.h"
#include "platform/CCFileUtils.h" #include "platform/CCFileUtils.h"

View File

@ -1,6 +1,6 @@
#include "scripting/lua-bindings/auto/lua_cocos2dx_audioengine_auto.hpp" #include "scripting/lua-bindings/auto/lua_cocos2dx_audioengine_auto.hpp"
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX #if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX
#include "audio/include/AudioEngine.h" #include "audio/AudioEngine.h"
#include "scripting/lua-bindings/manual/tolua_fix.h" #include "scripting/lua-bindings/manual/tolua_fix.h"
#include "scripting/lua-bindings/manual/LuaBasicConversions.h" #include "scripting/lua-bindings/manual/LuaBasicConversions.h"

View File

@ -27,7 +27,7 @@
#include "scripting/lua-bindings/manual/tolua_fix.h" #include "scripting/lua-bindings/manual/tolua_fix.h"
#include "scripting/lua-bindings/manual/LuaBasicConversions.h" #include "scripting/lua-bindings/manual/LuaBasicConversions.h"
#include "scripting/lua-bindings/manual/CCLuaEngine.h" #include "scripting/lua-bindings/manual/CCLuaEngine.h"
#include "audio/include/AudioEngine.h" #include "audio/AudioEngine.h"
static int lua_get_AudioProfile_name(lua_State* L) static int lua_get_AudioProfile_name(lua_State* L)
{ {

View File

@ -143,7 +143,7 @@ endif()
target_include_directories(${APP_NAME} target_include_directories(${APP_NAME}
PRIVATE Classes PRIVATE Classes
PRIVATE ${ADXE_ROOT_PATH}/core/audio/include/ PRIVATE ${ADXE_ROOT_PATH}/core/audio
) )
# mark app resources # mark app resources

View File

@ -29,7 +29,7 @@
#define USE_AUDIO_ENGINE 1 #define USE_AUDIO_ENGINE 1
#if USE_AUDIO_ENGINE #if USE_AUDIO_ENGINE
# include "audio/include/AudioEngine.h" # include "audio/AudioEngine.h"
#endif #endif
USING_NS_CC; USING_NS_CC;

View File

@ -31,7 +31,7 @@
#define USE_AUDIO_ENGINE 1 #define USE_AUDIO_ENGINE 1
#if USE_AUDIO_ENGINE #if USE_AUDIO_ENGINE
# include "audio/include/AudioEngine.h" # include "audio/AudioEngine.h"
#endif #endif
USING_NS_CC; USING_NS_CC;

View File

@ -32,7 +32,7 @@
# include "cocos2d.h" # include "cocos2d.h"
# include "../BaseTest.h" # include "../BaseTest.h"
# include "audio/include/AudioEngine.h" # include "audio/AudioEngine.h"
DEFINE_TEST_SUITE(AudioEngineTests); DEFINE_TEST_SUITE(AudioEngineTests);

View File

@ -26,7 +26,6 @@
#define _TEST_BASIC_H_ #define _TEST_BASIC_H_
#include "cocos2d.h" #include "cocos2d.h"
#include "audio/include/SimpleAudioEngine.h"
#include "VisibleRect.h" #include "VisibleRect.h"
/** /**

View File

@ -1,20 +1,7 @@
#include "AppDelegate.h" #include "AppDelegate.h"
#include "MenuScene.h" #include "MenuScene.h"
// #define USE_AUDIO_ENGINE 1 #include "audio/AudioEngine.h"
// #define USE_SIMPLE_AUDIO_ENGINE 1
#if USE_AUDIO_ENGINE && USE_SIMPLE_AUDIO_ENGINE
#error "Don't use AudioEngine and SimpleAudioEngine at the same time. Please just select one in your game!"
#endif
#if USE_AUDIO_ENGINE
#include "audio/include/AudioEngine.h"
using namespace cocos2d::experimental;
#elif USE_SIMPLE_AUDIO_ENGINE
#include "audio/include/SimpleAudioEngine.h"
using namespace CocosDenshion;
#endif
USING_NS_CC; USING_NS_CC;
@ -29,11 +16,7 @@ AppDelegate::AppDelegate()
AppDelegate::~AppDelegate() AppDelegate::~AppDelegate()
{ {
#if USE_AUDIO_ENGINE
AudioEngine::end(); AudioEngine::end();
#elif USE_SIMPLE_AUDIO_ENGINE
SimpleAudioEngine::end();
#endif
} }
// if you want a different context, modify the value of glContextAttrs // if you want a different context, modify the value of glContextAttrs
@ -112,23 +95,11 @@ bool AppDelegate::applicationDidFinishLaunching() {
// This function will be called when the app is inactive. Note, when receiving a phone call it is invoked. // This function will be called when the app is inactive. Note, when receiving a phone call it is invoked.
void AppDelegate::applicationDidEnterBackground() { void AppDelegate::applicationDidEnterBackground() {
Director::getInstance()->stopAnimation(); Director::getInstance()->stopAnimation();
#if USE_AUDIO_ENGINE
AudioEngine::pauseAll(); AudioEngine::pauseAll();
#elif USE_SIMPLE_AUDIO_ENGINE
SimpleAudioEngine::getInstance()->pauseBackgroundMusic();
SimpleAudioEngine::getInstance()->pauseAllEffects();
#endif
} }
// this function will be called when the app is active again // this function will be called when the app is active again
void AppDelegate::applicationWillEnterForeground() { void AppDelegate::applicationWillEnterForeground() {
Director::getInstance()->startAnimation(); Director::getInstance()->startAnimation();
#if USE_AUDIO_ENGINE
AudioEngine::resumeAll(); AudioEngine::resumeAll();
#elif USE_SIMPLE_AUDIO_ENGINE
SimpleAudioEngine::getInstance()->resumeBackgroundMusic();
SimpleAudioEngine::getInstance()->resumeAllEffects();
#endif
} }

View File

@ -26,7 +26,7 @@ cxxgenerator_headers =
extra_arguments = %(android_headers)s %(clang_headers)s %(cxxgenerator_headers)s %(cocos_headers)s %(android_flags)s %(clang_flags)s %(cocos_flags)s %(extra_flags)s extra_arguments = %(android_headers)s %(clang_headers)s %(cxxgenerator_headers)s %(cocos_headers)s %(android_flags)s %(clang_flags)s %(cocos_flags)s %(extra_flags)s
# what headers to parse # what headers to parse
headers = %(adxedir)s/core/audio/include/AudioEngine.h headers = %(adxedir)s/core/audio/AudioEngine.h
# what classes to produce code for. You can use regular expressions here. When testing the regular # what classes to produce code for. You can use regular expressions here. When testing the regular
# expression, it will be enclosed in "^$", like this: "^Menu*$". # expression, it will be enclosed in "^$", like this: "^Menu*$".