mirror of https://github.com/axmolengine/axmol.git
Tidy audio folder
This commit is contained in:
parent
86f4211f78
commit
a651879ee6
|
@ -135,7 +135,7 @@ target_include_directories(${ADXE_CORE_LIB}
|
|||
INTERFACE ${ADXE_ROOT_PATH}/thirdparty
|
||||
INTERFACE ${ADXE_ROOT_PATH}/extensions
|
||||
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}
|
||||
)
|
||||
|
||||
|
|
|
@ -1,50 +1,50 @@
|
|||
|
||||
# common headers and sources
|
||||
set(COCOS_AUDIO_HEADER
|
||||
audio/include/alconfig.h
|
||||
audio/include/AudioEngine.h
|
||||
audio/include/AudioMacros.h
|
||||
audio/include/AudioDecoderManager.h
|
||||
audio/include/AudioDecoder.h
|
||||
audio/include/AudioDecoderOgg.h
|
||||
audio/include/AudioPlayer.h
|
||||
audio/include/AudioCache.h
|
||||
audio/include/AudioEngineImpl.h
|
||||
audio/alconfig.h
|
||||
audio/AudioEngine.h
|
||||
audio/AudioMacros.h
|
||||
audio/AudioDecoderManager.h
|
||||
audio/AudioDecoder.h
|
||||
audio/AudioDecoderOgg.h
|
||||
audio/AudioPlayer.h
|
||||
audio/AudioCache.h
|
||||
audio/AudioEngineImpl.h
|
||||
)
|
||||
|
||||
set(COCOS_AUDIO_SRC
|
||||
audio/src/AudioEngine.cpp
|
||||
audio/src/AudioDecoderManager.cpp
|
||||
audio/src/AudioDecoder.cpp
|
||||
audio/src/AudioDecoderOgg.cpp
|
||||
audio/src/AudioPlayer.cpp
|
||||
audio/src/AudioCache.cpp
|
||||
audio/AudioEngine.cpp
|
||||
audio/AudioDecoderManager.cpp
|
||||
audio/AudioDecoder.cpp
|
||||
audio/AudioDecoderOgg.cpp
|
||||
audio/AudioPlayer.cpp
|
||||
audio/AudioCache.cpp
|
||||
)
|
||||
|
||||
# stupid, clang always compie .mm as objc/cpp mix obj
|
||||
if(ANDROID OR LINUX)
|
||||
set(COCOS_AUDIO_SRC ${COCOS_AUDIO_SRC}
|
||||
audio/src/linux-link.cpp
|
||||
audio/linux-link.cpp
|
||||
)
|
||||
else()
|
||||
set(COCOS_AUDIO_SRC ${COCOS_AUDIO_SRC}
|
||||
audio/src/AudioEngineImpl.mm
|
||||
audio/AudioEngineImpl.mm
|
||||
)
|
||||
endif()
|
||||
|
||||
# Apple, we use system audio decoder
|
||||
if(APPLE)
|
||||
set(COCOS_AUDIO_HEADER ${COCOS_AUDIO_HEADER}
|
||||
audio/include/AudioDecoderEXT.h)
|
||||
audio/AudioDecoderEXT.h)
|
||||
set(COCOS_AUDIO_SRC ${COCOS_AUDIO_SRC}
|
||||
audio/src/AudioDecoderEXT.mm)
|
||||
audio/AudioDecoderEXT.mm)
|
||||
else()
|
||||
set(COCOS_AUDIO_HEADER ${COCOS_AUDIO_HEADER}
|
||||
audio/include/AudioDecoderMp3.h
|
||||
audio/include/AudioDecoderWav.h
|
||||
audio/AudioDecoderMp3.h
|
||||
audio/AudioDecoderWav.h
|
||||
)
|
||||
set(COCOS_AUDIO_SRC ${COCOS_AUDIO_SRC}
|
||||
audio/src/AudioDecoderMp3.cpp
|
||||
audio/src/AudioDecoderWav.cpp
|
||||
audio/AudioDecoderMp3.cpp
|
||||
audio/AudioDecoderWav.cpp
|
||||
)
|
||||
endif()
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
/// @}
|
|
@ -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_
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -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();
|
||||
});
|
||||
}
|
|
@ -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
|
|
@ -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 {
|
|
@ -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
|
|
@ -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
|
|
@ -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, ¤tSection);
|
||||
return bytesToFrames(bytesRead);
|
||||
}
|
||||
|
||||
bool AudioDecoderOgg::seek(uint32_t frameOffset)
|
||||
{
|
||||
return 0 == ov_pcm_seek(&_vf, frameOffset);
|
||||
}
|
||||
} // namespace cocos2d
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
/* linux-link.cpp: the workaround for solving clang link issue, said: unrefenreced "gnu_objc_personality_v0" */
|
||||
#include "AudioEngineImpl.mm"
|
|
@ -61,7 +61,7 @@ THE SOFTWARE.
|
|||
#include "base/ObjectFactory.h"
|
||||
#include "platform/CCApplication.h"
|
||||
#include "renderer/backend/ProgramCache.h"
|
||||
#include "audio/include/AudioEngine.h"
|
||||
#include "audio/AudioEngine.h"
|
||||
|
||||
#if CC_ENABLE_SCRIPT_BINDING
|
||||
# include "base/CCScriptSupport.h"
|
||||
|
|
|
@ -23,7 +23,6 @@ THE SOFTWARE.
|
|||
****************************************************************************/
|
||||
|
||||
#include "CCComAudio.h"
|
||||
// #include "audio/include/SimpleAudioEngine.h"
|
||||
#include "platform/CCFileUtils.h"
|
||||
|
||||
namespace cocostudio
|
||||
|
|
|
@ -25,7 +25,6 @@ THE SOFTWARE.
|
|||
|
||||
#include "CocoStudio.h"
|
||||
#include "ui/CocosGUI.h"
|
||||
// #include "audio/include/SimpleAudioEngine.h"
|
||||
#include "base/ObjectFactory.h"
|
||||
#include "base/ccUtils.h"
|
||||
#include "platform/CCFileUtils.h"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#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
|
||||
#include "audio/include/AudioEngine.h"
|
||||
#include "audio/AudioEngine.h"
|
||||
#include "scripting/lua-bindings/manual/tolua_fix.h"
|
||||
#include "scripting/lua-bindings/manual/LuaBasicConversions.h"
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
#include "scripting/lua-bindings/manual/tolua_fix.h"
|
||||
#include "scripting/lua-bindings/manual/LuaBasicConversions.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)
|
||||
{
|
||||
|
|
|
@ -143,7 +143,7 @@ endif()
|
|||
|
||||
target_include_directories(${APP_NAME}
|
||||
PRIVATE Classes
|
||||
PRIVATE ${ADXE_ROOT_PATH}/core/audio/include/
|
||||
PRIVATE ${ADXE_ROOT_PATH}/core/audio
|
||||
)
|
||||
|
||||
# mark app resources
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
#define USE_AUDIO_ENGINE 1
|
||||
|
||||
#if USE_AUDIO_ENGINE
|
||||
# include "audio/include/AudioEngine.h"
|
||||
# include "audio/AudioEngine.h"
|
||||
#endif
|
||||
|
||||
USING_NS_CC;
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
#define USE_AUDIO_ENGINE 1
|
||||
|
||||
#if USE_AUDIO_ENGINE
|
||||
# include "audio/include/AudioEngine.h"
|
||||
# include "audio/AudioEngine.h"
|
||||
#endif
|
||||
|
||||
USING_NS_CC;
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
# include "cocos2d.h"
|
||||
# include "../BaseTest.h"
|
||||
|
||||
# include "audio/include/AudioEngine.h"
|
||||
# include "audio/AudioEngine.h"
|
||||
|
||||
DEFINE_TEST_SUITE(AudioEngineTests);
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
#define _TEST_BASIC_H_
|
||||
|
||||
#include "cocos2d.h"
|
||||
#include "audio/include/SimpleAudioEngine.h"
|
||||
#include "VisibleRect.h"
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,20 +1,7 @@
|
|||
#include "AppDelegate.h"
|
||||
#include "MenuScene.h"
|
||||
|
||||
// #define USE_AUDIO_ENGINE 1
|
||||
// #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
|
||||
#include "audio/AudioEngine.h"
|
||||
|
||||
USING_NS_CC;
|
||||
|
||||
|
@ -29,11 +16,7 @@ AppDelegate::AppDelegate()
|
|||
|
||||
AppDelegate::~AppDelegate()
|
||||
{
|
||||
#if USE_AUDIO_ENGINE
|
||||
AudioEngine::end();
|
||||
#elif USE_SIMPLE_AUDIO_ENGINE
|
||||
SimpleAudioEngine::end();
|
||||
#endif
|
||||
}
|
||||
|
||||
// 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.
|
||||
void AppDelegate::applicationDidEnterBackground() {
|
||||
Director::getInstance()->stopAnimation();
|
||||
|
||||
#if USE_AUDIO_ENGINE
|
||||
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
|
||||
void AppDelegate::applicationWillEnterForeground() {
|
||||
Director::getInstance()->startAnimation();
|
||||
|
||||
#if USE_AUDIO_ENGINE
|
||||
AudioEngine::resumeAll();
|
||||
#elif USE_SIMPLE_AUDIO_ENGINE
|
||||
SimpleAudioEngine::getInstance()->resumeBackgroundMusic();
|
||||
SimpleAudioEngine::getInstance()->resumeAllEffects();
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
# 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
|
||||
# expression, it will be enclosed in "^$", like this: "^Menu*$".
|
||||
|
|
Loading…
Reference in New Issue