no message

This commit is contained in:
gem
2025-02-18 15:21:31 +08:00
commit 2d133e56d7
1980 changed files with 465595 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AssetFd"
#include "audio/android/AssetFd.h"
#include "audio/android/cutils/log.h"
namespace cc {
AssetFd::AssetFd(int assetFd)
: _assetFd(assetFd) {
}
AssetFd::~AssetFd() {
ALOGV("~AssetFd: %d", _assetFd);
if (_assetFd > 0) {
::close(_assetFd);
_assetFd = 0;
}
};
} // namespace cc

View File

@@ -0,0 +1,42 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
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 <unistd.h>
namespace cc {
class AssetFd {
public:
AssetFd(int assetFd);
~AssetFd();
inline int getFd() const { return _assetFd; };
private:
int _assetFd;
};
} // namespace cc

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "audio/android/utils/Errors.h"
namespace cc {
// ----------------------------------------------------------------------------
class AudioBufferProvider {
public:
// IDEA: merge with AudioTrackShared::Buffer, AudioTrack::Buffer, and AudioRecord::Buffer
// and rename getNextBuffer() to obtainBuffer()
struct Buffer {
Buffer() : raw(NULL), frameCount(0) {}
union {
void *raw;
short *i16;
int8_t *i8;
};
size_t frameCount;
};
virtual ~AudioBufferProvider() {}
// value representing an invalid presentation timestamp
static const int64_t kInvalidPTS = 0x7FFFFFFFFFFFFFFFLL; // <stdint.h> is too painful
// pts is the local time when the next sample yielded by getNextBuffer
// will be rendered.
// Pass kInvalidPTS if the PTS is unknown or not applicable.
// On entry:
// buffer != NULL
// buffer->raw unused
// buffer->frameCount maximum number of desired frames
// On successful return:
// status NO_ERROR
// buffer->raw non-NULL pointer to buffer->frameCount contiguous available frames
// buffer->frameCount number of contiguous available frames at buffer->raw,
// 0 < buffer->frameCount <= entry value
// On error return:
// status != NO_ERROR
// buffer->raw NULL
// buffer->frameCount 0
virtual status_t getNextBuffer(Buffer *buffer, int64_t pts = kInvalidPTS) = 0;
// Release (a portion of) the buffer previously obtained by getNextBuffer().
// It is permissible to call releaseBuffer() multiple times per getNextBuffer().
// On entry:
// buffer->frameCount number of frames to release, must be <= number of frames
// obtained but not yet released
// buffer->raw unused
// On return:
// buffer->frameCount 0; implementation MUST set to zero
// buffer->raw undefined; implementation is PERMITTED to set to any value,
// so if caller needs to continue using this buffer it must
// keep track of the pointer itself
virtual void releaseBuffer(Buffer *buffer) = 0;
};
// ----------------------------------------------------------------------------
} // namespace cc

View File

@@ -0,0 +1,260 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioDecoder"
#include "audio/android/AudioDecoder.h"
#include "audio/android/AudioResampler.h"
#include "audio/android/PcmBufferProvider.h"
#include <chrono>
#include <thread>
namespace cc {
size_t AudioDecoder::fileRead(void *ptr, size_t size, size_t nmemb, void *datasource) {
AudioDecoder *thiz = (AudioDecoder *)datasource;
ssize_t toReadBytes = std::min((ssize_t)(thiz->_fileData.getSize() - thiz->_fileCurrPos), (ssize_t)(nmemb * size));
if (toReadBytes > 0) {
memcpy(ptr, (unsigned char *)thiz->_fileData.getBytes() + thiz->_fileCurrPos, toReadBytes);
thiz->_fileCurrPos += toReadBytes;
}
// ALOGD("File size: %d, After fileRead _fileCurrPos %d", (int)thiz->_fileData.getSize(), thiz->_fileCurrPos);
return toReadBytes;
}
int AudioDecoder::fileSeek(void *datasource, int64_t offset, int whence) {
AudioDecoder *thiz = (AudioDecoder *)datasource;
if (whence == SEEK_SET)
thiz->_fileCurrPos = static_cast<size_t>(offset);
else if (whence == SEEK_CUR)
thiz->_fileCurrPos = static_cast<size_t>(thiz->_fileCurrPos + offset);
else if (whence == SEEK_END)
thiz->_fileCurrPos = static_cast<size_t>(thiz->_fileData.getSize());
return 0;
}
int AudioDecoder::fileClose(void *datasource) {
return 0;
}
long AudioDecoder::fileTell(void *datasource) {
AudioDecoder *thiz = (AudioDecoder *)datasource;
return (long)thiz->_fileCurrPos;
}
AudioDecoder::AudioDecoder()
: _fileCurrPos(0), _sampleRate(-1) {
auto pcmBuffer = std::make_shared<ccstd::vector<char>>();
pcmBuffer->reserve(4096);
_result.pcmBuffer = pcmBuffer;
}
AudioDecoder::~AudioDecoder() {
ALOGV("~AudioDecoder() %p", this);
}
bool AudioDecoder::init(const ccstd::string &url, int sampleRate) {
_url = url;
_sampleRate = sampleRate;
return true;
}
bool AudioDecoder::start() {
auto oldTime = clockNow();
auto nowTime = oldTime;
bool ret;
do {
ret = decodeToPcm();
if (!ret) {
ALOGE("decodeToPcm (%s) failed!", _url.c_str());
break;
}
nowTime = clockNow();
ALOGD("Decoding (%s) to pcm data wasted %fms", _url.c_str(), intervalInMS(oldTime, nowTime));
oldTime = nowTime;
ret = resample();
if (!ret) {
ALOGE("resample (%s) failed!", _url.c_str());
break;
}
nowTime = clockNow();
ALOGD("Resampling (%s) wasted %fms", _url.c_str(), intervalInMS(oldTime, nowTime));
oldTime = nowTime;
ret = interleave();
if (!ret) {
ALOGE("interleave (%s) failed!", _url.c_str());
break;
}
nowTime = clockNow();
ALOGD("Interleave (%s) wasted %fms", _url.c_str(), intervalInMS(oldTime, nowTime));
} while (false);
ALOGV_IF(!ret, "%s returns false, decode (%s)", __FUNCTION__, _url.c_str());
return ret;
}
bool AudioDecoder::resample() {
if (_result.sampleRate == _sampleRate) {
ALOGI("No need to resample since the sample rate (%d) of the decoded pcm data is the same as the device output sample rate",
_sampleRate);
return true;
}
ALOGV("Resample: %d --> %d", _result.sampleRate, _sampleRate);
auto r = _result;
PcmBufferProvider provider;
provider.init(r.pcmBuffer->data(), r.numFrames, r.pcmBuffer->size() / r.numFrames);
const int outFrameRate = _sampleRate;
int outputChannels = 2;
size_t outputFrameSize = outputChannels * sizeof(int32_t);
auto outputFrames = static_cast<size_t>(((int64_t)r.numFrames * outFrameRate) / r.sampleRate);
size_t outputSize = outputFrames * outputFrameSize;
void *outputVAddr = malloc(outputSize);
auto resampler = AudioResampler::create(AUDIO_FORMAT_PCM_16_BIT, r.numChannels, outFrameRate,
AudioResampler::MED_QUALITY);
resampler->setSampleRate(r.sampleRate);
resampler->setVolume(AudioResampler::UNITY_GAIN_FLOAT, AudioResampler::UNITY_GAIN_FLOAT);
memset(outputVAddr, 0, outputSize);
ALOGV("resample() %zu output frames", outputFrames);
ccstd::vector<int> Ovalues;
if (Ovalues.empty()) {
Ovalues.push_back(static_cast<int>(outputFrames));
}
for (size_t i = 0, j = 0; i < outputFrames;) {
size_t thisFrames = Ovalues[j++];
if (j >= Ovalues.size()) {
j = 0;
}
if (thisFrames == 0 || thisFrames > outputFrames - i) {
thisFrames = outputFrames - i;
}
int outFrames = static_cast<int>(resampler->resample(static_cast<int32_t *>(outputVAddr) + outputChannels * i, thisFrames,
&provider));
ALOGV("outFrames: %d", outFrames);
i += thisFrames;
}
ALOGV("resample() complete");
resampler->reset();
ALOGV("reset() complete");
delete resampler;
resampler = nullptr;
// mono takes left channel only (out of stereo output pair)
// stereo and multichannel preserve all channels.
int channels = r.numChannels;
int32_t *out = (int32_t *)outputVAddr;
int16_t *convert = (int16_t *)malloc(outputFrames * channels * sizeof(int16_t));
const int volumeShift = 12; // shift requirement for Q4.27 to Q.15
// round to half towards zero and saturate at int16 (non-dithered)
const int roundVal = (1 << (volumeShift - 1)) - 1; // volumePrecision > 0
for (size_t i = 0; i < outputFrames; i++) {
for (int j = 0; j < channels; j++) {
int32_t s = out[i * outputChannels + j] + roundVal; // add offset here
if (s < 0) {
s = (s + 1) >> volumeShift; // round to 0
if (s < -32768) {
s = -32768;
}
} else {
s = s >> volumeShift;
if (s > 32767) {
s = 32767;
}
}
convert[i * channels + j] = int16_t(s);
}
}
// Reset result
_result.numFrames = static_cast<int>(outputFrames);
_result.sampleRate = outFrameRate;
auto buffer = std::make_shared<ccstd::vector<char>>();
buffer->reserve(_result.numFrames * _result.bitsPerSample / 8);
buffer->insert(buffer->end(), (char *)convert,
(char *)convert + outputFrames * channels * sizeof(int16_t));
_result.pcmBuffer = buffer;
ALOGV("pcm buffer size: %d", (int)_result.pcmBuffer->size());
free(convert);
free(outputVAddr);
return true;
}
//-----------------------------------------------------------------
bool AudioDecoder::interleave() {
if (_result.numChannels == 2) {
ALOGI("Audio channel count is 2, no need to interleave");
return true;
} else if (_result.numChannels == 1) {
// If it's a mono audio, try to compose a fake stereo buffer
size_t newBufferSize = _result.pcmBuffer->size() * 2;
auto newBuffer = std::make_shared<ccstd::vector<char>>();
newBuffer->reserve(newBufferSize);
size_t totalFrameSizeInBytes = (size_t)(_result.numFrames * _result.bitsPerSample / 8);
for (size_t i = 0; i < totalFrameSizeInBytes; i += 2) {
// get one short value
char byte1 = _result.pcmBuffer->at(i);
char byte2 = _result.pcmBuffer->at(i + 1);
// push two short value
for (int j = 0; j < 2; ++j) {
newBuffer->push_back(byte1);
newBuffer->push_back(byte2);
}
}
_result.numChannels = 2;
_result.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
_result.pcmBuffer = newBuffer;
return true;
}
ALOGE("Audio channel count (%d) is wrong, interleave only supports converting mono to stereo!", _result.numChannels);
return false;
}
} // namespace cc

View File

@@ -0,0 +1,61 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/OpenSLHelper.h"
#include "audio/android/PcmData.h"
#include "base/Data.h"
namespace cc {
class AudioDecoder {
public:
AudioDecoder();
virtual ~AudioDecoder();
virtual bool init(const ccstd::string &url, int sampleRate);
bool start();
inline PcmData getResult() { return _result; };
protected:
virtual bool decodeToPcm() = 0;
bool resample();
bool interleave();
static size_t fileRead(void *ptr, size_t size, size_t nmemb, void *datasource);
static int fileSeek(void *datasource, int64_t offset, int whence);
static int fileClose(void *datasource);
static long fileTell(void *datasource); // NOLINT
ccstd::string _url;
PcmData _result;
int _sampleRate;
Data _fileData;
size_t _fileCurrPos;
};
} // namespace cc

View File

@@ -0,0 +1,75 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioDecoderMp3"
#include "audio/android/AudioDecoderMp3.h"
#include "audio/android/mp3reader.h"
#include "platform/FileUtils.h"
namespace cc {
AudioDecoderMp3::AudioDecoderMp3() {
ALOGV("Create AudioDecoderMp3");
}
AudioDecoderMp3::~AudioDecoderMp3() {
}
bool AudioDecoderMp3::decodeToPcm() {
_fileData = FileUtils::getInstance()->getDataFromFile(_url);
if (_fileData.isNull()) {
return false;
}
mp3_callbacks callbacks;
callbacks.read = AudioDecoder::fileRead;
callbacks.seek = AudioDecoder::fileSeek;
callbacks.close = AudioDecoder::fileClose;
callbacks.tell = AudioDecoder::fileTell;
int numChannels = 0;
int sampleRate = 0;
int numFrames = 0;
if (EXIT_SUCCESS == decodeMP3(&callbacks, this, *_result.pcmBuffer, &numChannels, &sampleRate, &numFrames) && numChannels > 0 && sampleRate > 0 && numFrames > 0) {
_result.numChannels = numChannels;
_result.sampleRate = sampleRate;
_result.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
_result.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
_result.channelMask = numChannels == 1 ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT);
_result.endianness = SL_BYTEORDER_LITTLEENDIAN;
_result.numFrames = numFrames;
_result.duration = 1.0f * numFrames / sampleRate;
ccstd::string info = _result.toString();
ALOGI("Original audio info: %s, total size: %d", info.c_str(), (int)_result.pcmBuffer->size());
return true;
}
ALOGE("Decode MP3 (%s) failed, channels: %d, rate: %d, frames: %d", _url.c_str(), numChannels, sampleRate, numFrames);
return false;
}
} // namespace cc

View File

@@ -0,0 +1,41 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/AudioDecoder.h"
namespace cc {
class AudioDecoderMp3 : public AudioDecoder {
protected:
AudioDecoderMp3();
virtual ~AudioDecoderMp3();
virtual bool decodeToPcm() override;
friend class AudioDecoderProvider;
};
} // namespace cc

View File

@@ -0,0 +1,101 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioDecoderOgg"
#include "audio/android/AudioDecoderOgg.h"
#include "platform/FileUtils.h"
namespace cc {
AudioDecoderOgg::AudioDecoderOgg() {
ALOGV("Create AudioDecoderOgg");
}
AudioDecoderOgg::~AudioDecoderOgg() {
}
int AudioDecoderOgg::fseek64Wrap(void *datasource, ogg_int64_t off, int whence) {
return AudioDecoder::fileSeek(datasource, (long)off, whence);
}
bool AudioDecoderOgg::decodeToPcm() {
_fileData = FileUtils::getInstance()->getDataFromFile(_url);
if (_fileData.isNull()) {
return false;
}
ov_callbacks callbacks;
callbacks.read_func = AudioDecoder::fileRead;
callbacks.seek_func = AudioDecoderOgg::fseek64Wrap;
callbacks.close_func = AudioDecoder::fileClose;
callbacks.tell_func = AudioDecoder::fileTell;
_fileCurrPos = 0;
OggVorbis_File vf;
int ret = ov_open_callbacks(this, &vf, NULL, 0, callbacks);
if (ret != 0) {
ALOGE("Open file error, file: %s, ov_open_callbacks return %d", _url.c_str(), ret);
return false;
}
// header
auto vi = ov_info(&vf, -1);
uint32_t pcmSamples = (uint32_t)ov_pcm_total(&vf, -1);
uint32_t bufferSize = pcmSamples * vi->channels * sizeof(short);
char *pcmBuffer = (char *)malloc(bufferSize);
memset(pcmBuffer, 0, bufferSize);
int currentSection = 0;
long curPos = 0;
long readBytes = 0;
do {
readBytes = ov_read(&vf, pcmBuffer + curPos, 4096, &currentSection);
curPos += readBytes;
} while (readBytes > 0);
if (curPos > 0) {
_result.pcmBuffer->insert(_result.pcmBuffer->end(), pcmBuffer, pcmBuffer + bufferSize);
_result.numChannels = vi->channels;
_result.sampleRate = vi->rate;
_result.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
_result.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
_result.channelMask = vi->channels == 1 ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT);
_result.endianness = SL_BYTEORDER_LITTLEENDIAN;
_result.numFrames = pcmSamples;
_result.duration = 1.0f * pcmSamples / vi->rate;
} else {
ALOGE("ov_read returns 0 byte!");
}
ov_clear(&vf);
free(pcmBuffer);
return (curPos > 0);
}
} // namespace cc

View File

@@ -0,0 +1,44 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/AudioDecoder.h"
#include "tremolo/Tremolo/ivorbisfile.h"
namespace cc {
class AudioDecoderOgg : public AudioDecoder {
protected:
AudioDecoderOgg();
virtual ~AudioDecoderOgg();
static int fseek64Wrap(void *datasource, ogg_int64_t off, int whence);
virtual bool decodeToPcm() override;
friend class AudioDecoderProvider;
};
} // namespace cc

View File

@@ -0,0 +1,78 @@
/****************************************************************************
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "AudioDecoderProvider"
#include "audio/android/AudioDecoderProvider.h"
#include "audio/android/AudioDecoderMp3.h"
#include "audio/android/AudioDecoderOgg.h"
#include "audio/android/AudioDecoderSLES.h"
#include "audio/android/AudioDecoderWav.h"
#include "base/memory/Memory.h"
#include "platform/FileUtils.h"
namespace cc {
AudioDecoder *AudioDecoderProvider::createAudioDecoder(SLEngineItf engineItf, const ccstd::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback) {
AudioDecoder *decoder = nullptr;
ccstd::string extension = FileUtils::getInstance()->getFileExtension(url);
ALOGV("url:%s, extension:%s", url.c_str(), extension.c_str());
if (extension == ".ogg") {
decoder = ccnew AudioDecoderOgg();
if (!decoder->init(url, sampleRate)) {
delete decoder;
decoder = nullptr;
}
} else if (extension == ".mp3") {
decoder = ccnew AudioDecoderMp3();
if (!decoder->init(url, sampleRate)) {
delete decoder;
decoder = nullptr;
}
} else if (extension == ".wav") {
decoder = ccnew AudioDecoderWav();
if (!decoder->init(url, sampleRate)) {
delete decoder;
decoder = nullptr;
}
} else {
auto slesDecoder = ccnew AudioDecoderSLES();
if (slesDecoder->init(engineItf, url, bufferSizeInFrames, sampleRate, fdGetterCallback)) {
decoder = slesDecoder;
} else {
delete slesDecoder;
}
}
return decoder;
}
void AudioDecoderProvider::destroyAudioDecoder(AudioDecoder **decoder) {
if (decoder != nullptr && *decoder != nullptr) {
delete (*decoder);
(*decoder) = nullptr;
}
}
} // namespace cc

View File

@@ -0,0 +1,40 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/OpenSLHelper.h"
namespace cc {
class AudioDecoder;
class AudioDecoderProvider {
public:
static AudioDecoder *createAudioDecoder(SLEngineItf engineItf, const ccstd::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback);
static void destroyAudioDecoder(AudioDecoder **decoder);
};
} // namespace cc

View File

@@ -0,0 +1,588 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioDecoderSLES"
#include "base/Macros.h"
#include "audio/android/AudioDecoderSLES.h"
#include "platform/FileUtils.h"
#include <mutex>
#include <thread>
namespace cc {
/* Explicitly requesting SL_IID_ANDROIDSIMPLEBUFFERQUEUE and SL_IID_PREFETCHSTATUS
* on the UrlAudioPlayer object for decoding, SL_IID_METADATAEXTRACTION for retrieving the
* format of the decoded audio */
#define NUM_EXPLICIT_INTERFACES_FOR_PLAYER 3
/* Size of the decode buffer queue */
#define NB_BUFFERS_IN_QUEUE 4
/* size of the struct to retrieve the PCM format metadata values: the values we're interested in
* are SLuint32, but it is saved in the data field of a SLMetadataInfo, hence the larger size.
* Nate that this size is queried and displayed at l.452 for demonstration/test purposes.
* */
#define PCM_METADATA_VALUE_SIZE 32
/* used to detect errors likely to have occurred when the OpenSL ES framework fails to open
* a resource, for instance because a file URI is invalid, or an HTTP server doesn't respond.
*/
#define PREFETCHEVENT_ERROR_CANDIDATE (SL_PREFETCHEVENT_STATUSCHANGE | SL_PREFETCHEVENT_FILLLEVELCHANGE)
//-----------------------------------------------------------------
static std::mutex __SLPlayerMutex; //NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
static int toBufferSizeInBytes(int bufferSizeInFrames, int sampleSize, int channelCount) {
return bufferSizeInFrames * sampleSize * channelCount;
}
static int BUFFER_SIZE_IN_BYTES = 0; // NOLINT(readability-identifier-naming)
static void checkMetaData(int index, const char *key) {
if (index != -1) {
ALOGV("Key %s is at index %d", key, index);
} else {
ALOGE("Unable to find key %s", key);
}
}
class SLAudioDecoderCallbackProxy {
public:
//-----------------------------------------------------------------
/* Callback for "prefetch" events, here used to detect audio resource opening errors */
static void prefetchEventCallback(SLPrefetchStatusItf caller, void *context, SLuint32 event) {
auto *thiz = reinterpret_cast<AudioDecoderSLES *>(context);
thiz->prefetchCallback(caller, event);
}
static void decPlayCallback(CCSLBufferQueueItf queueItf, void *context) {
auto *thiz = reinterpret_cast<AudioDecoderSLES *>(context);
thiz->decodeToPcmCallback(queueItf);
}
static void decProgressCallback(SLPlayItf caller, void *context, SLuint32 event) {
auto *thiz = reinterpret_cast<AudioDecoderSLES *>(context);
thiz->decodeProgressCallback(caller, event);
}
};
AudioDecoderSLES::AudioDecoderSLES()
: _engineItf(nullptr), _playObj(nullptr), _formatQueried(false), _prefetchError(false), _counter(0), _numChannelsKeyIndex(-1), _sampleRateKeyIndex(-1), _bitsPerSampleKeyIndex(-1), _containerSizeKeyIndex(-1), _channelMaskKeyIndex(-1), _endiannessKeyIndex(-1), _eos(false), _bufferSizeInFrames(-1), _assetFd(0), _fdGetterCallback(nullptr), _isDecodingCallbackInvoked(false) {
ALOGV("Create AudioDecoderSLES");
}
AudioDecoderSLES::~AudioDecoderSLES() {
{
std::lock_guard<std::mutex> lk(__SLPlayerMutex);
SL_DESTROY_OBJ(_playObj);
}
ALOGV("After destroying SL play object");
if (_assetFd > 0) {
ALOGV("Closing assetFd: %d", _assetFd);
::close(_assetFd);
_assetFd = 0;
}
free(_pcmData);
}
bool AudioDecoderSLES::init(SLEngineItf engineItf, const ccstd::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback) {
if (AudioDecoder::init(url, sampleRate)) {
_engineItf = engineItf;
_bufferSizeInFrames = bufferSizeInFrames;
_fdGetterCallback = fdGetterCallback;
BUFFER_SIZE_IN_BYTES = toBufferSizeInBytes(bufferSizeInFrames, 2, 2);
_pcmData = static_cast<char *>(malloc(NB_BUFFERS_IN_QUEUE * BUFFER_SIZE_IN_BYTES));
memset(_pcmData, 0x00, NB_BUFFERS_IN_QUEUE * BUFFER_SIZE_IN_BYTES);
return true;
}
return false;
}
bool AudioDecoderSLES::decodeToPcm() {
#if CC_PLATFORM == CC_PLATFORM_ANDROID
SLresult result;
/* Objects this application uses: one audio player */
SLObjectItf player;
/* Interfaces for the audio player */
CCSLBufferQueueItf decBuffQueueItf;
SLPrefetchStatusItf prefetchItf;
SLPlayItf playItf;
SLMetadataExtractionItf mdExtrItf;
/* Source of audio data for the decoding */
SLDataSource decSource;
// decUri & locFd should be defined here
SLDataLocator_URI decUri;
SLDataLocator_AndroidFD locFd;
/* Data sink for decoded audio */
SLDataSink decDest;
SLDataLocator_AndroidSimpleBufferQueue decBuffQueue;
SLDataFormat_PCM pcm;
SLboolean required[NUM_EXPLICIT_INTERFACES_FOR_PLAYER];
SLInterfaceID iidArray[NUM_EXPLICIT_INTERFACES_FOR_PLAYER];
/* Initialize arrays required[] and iidArray[] */
for (int i = 0; i < NUM_EXPLICIT_INTERFACES_FOR_PLAYER; i++) {
required[i] = SL_BOOLEAN_FALSE;
iidArray[i] = SL_IID_NULL;
}
/* ------------------------------------------------------ */
/* Configuration of the player */
/* Request the AndroidSimpleBufferQueue interface */
required[0] = SL_BOOLEAN_TRUE;
iidArray[0] = SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
/* Request the PrefetchStatus interface */
required[1] = SL_BOOLEAN_TRUE;
iidArray[1] = SL_IID_PREFETCHSTATUS;
/* Request the PrefetchStatus interface */
required[2] = SL_BOOLEAN_TRUE;
iidArray[2] = SL_IID_METADATAEXTRACTION;
SLDataFormat_MIME formatMime = {SL_DATAFORMAT_MIME, nullptr, SL_CONTAINERTYPE_UNSPECIFIED};
decSource.pFormat = &formatMime;
if (_url[0] != '/') {
off_t start = 0;
off_t length = 0;
ccstd::string relativePath;
size_t position = _url.find("@assets/");
if (0 == position) {
// "@assets/" is at the beginning of the path and we don't want it
relativePath = _url.substr(strlen("@assets/"));
} else {
relativePath = _url;
}
_assetFd = _fdGetterCallback(relativePath, &start, &length);
if (_assetFd <= 0) {
ALOGE("Failed to open file descriptor for '%s'", _url.c_str());
return false;
}
// configure audio source
locFd = {SL_DATALOCATOR_ANDROIDFD, _assetFd, start, length};
decSource.pLocator = &locFd;
} else {
decUri = {SL_DATALOCATOR_URI, (SLchar *)_url.c_str()}; // NOLINT(google-readability-casting)
decSource.pLocator = &decUri;
}
/* Setup the data sink */
decBuffQueue.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
decBuffQueue.numBuffers = NB_BUFFERS_IN_QUEUE;
/* set up the format of the data in the buffer queue */
pcm.formatType = SL_DATAFORMAT_PCM;
// IDEA: valid value required but currently ignored
pcm.numChannels = 2;
pcm.samplesPerSec = SL_SAMPLINGRATE_44_1;
pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
pcm.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
decDest.pLocator = reinterpret_cast<void *>(&decBuffQueue);
decDest.pFormat = reinterpret_cast<void *>(&pcm);
{
std::lock_guard<std::mutex> lk(__SLPlayerMutex);
/* Create the audio player */
result = (*_engineItf)->CreateAudioPlayer(_engineItf, &player, &decSource, &decDest, NUM_EXPLICIT_INTERFACES_FOR_PLAYER, iidArray, required);
SL_RETURN_VAL_IF_FAILED(result, false, "CreateAudioPlayer failed");
_playObj = player;
/* Realize the player in synchronous mode. */
result = (*player)->Realize(player, SL_BOOLEAN_FALSE);
SL_RETURN_VAL_IF_FAILED(result, false, "Realize failed");
}
/* Get the play interface which is implicit */
result = (*player)->GetInterface(player, SL_IID_PLAY, reinterpret_cast<void *>(&playItf));
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_PLAY failed");
/* Set up the player callback to get events during the decoding */
// IDEA: currently ignored
result = (*playItf)->SetMarkerPosition(playItf, 2000);
SL_RETURN_VAL_IF_FAILED(result, false, "SetMarkerPosition failed");
result = (*playItf)->SetPositionUpdatePeriod(playItf, 500);
SL_RETURN_VAL_IF_FAILED(result, false, "SetPositionUpdatePeriod failed");
result = (*playItf)->SetCallbackEventsMask(playItf,
SL_PLAYEVENT_HEADATMARKER |
SL_PLAYEVENT_HEADATNEWPOS | SL_PLAYEVENT_HEADATEND);
SL_RETURN_VAL_IF_FAILED(result, false, "SetCallbackEventsMask failed");
result = (*playItf)->RegisterCallback(playItf, SLAudioDecoderCallbackProxy::decProgressCallback,
this);
SL_RETURN_VAL_IF_FAILED(result, false, "RegisterCallback failed");
ALOGV("Play callback registered");
/* Get the buffer queue interface which was explicitly requested */
result = (*player)->GetInterface(player, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
reinterpret_cast<void *>(&decBuffQueueItf));
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE failed");
/* Get the prefetch status interface which was explicitly requested */
result = (*player)->GetInterface(player, SL_IID_PREFETCHSTATUS, reinterpret_cast<void *>(&prefetchItf));
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_PREFETCHSTATUS failed");
/* Get the metadata extraction interface which was explicitly requested */
result = (*player)->GetInterface(player, SL_IID_METADATAEXTRACTION, reinterpret_cast<void *>(&mdExtrItf));
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_METADATAEXTRACTION failed");
/* ------------------------------------------------------ */
/* Initialize the callback and its context for the decoding buffer queue */
_decContext.playItf = playItf;
_decContext.metaItf = mdExtrItf;
_decContext.pDataBase = reinterpret_cast<int8_t *>(_pcmData);
_decContext.pData = _decContext.pDataBase;
_decContext.size = NB_BUFFERS_IN_QUEUE * BUFFER_SIZE_IN_BYTES;
result = (*decBuffQueueItf)->RegisterCallback(decBuffQueueItf, SLAudioDecoderCallbackProxy::decPlayCallback, this);
SL_RETURN_VAL_IF_FAILED(result, false, "decBuffQueueItf RegisterCallback failed");
/* Enqueue buffers to map the region of memory allocated to store the decoded data */
// ALOGV("Enqueueing buffer ");
for (int i = 0; i < NB_BUFFERS_IN_QUEUE; i++) {
result = (*decBuffQueueItf)->Enqueue(decBuffQueueItf, _decContext.pData, BUFFER_SIZE_IN_BYTES);
SL_RETURN_VAL_IF_FAILED(result, false, "Enqueue failed");
_decContext.pData += BUFFER_SIZE_IN_BYTES;
}
_decContext.pData = _decContext.pDataBase;
/* ------------------------------------------------------ */
/* Initialize the callback for prefetch errors, if we can't open the resource to decode */
result = (*prefetchItf)->RegisterCallback(prefetchItf, SLAudioDecoderCallbackProxy::prefetchEventCallback, this);
SL_RETURN_VAL_IF_FAILED(result, false, "prefetchItf RegisterCallback failed");
result = (*prefetchItf)->SetCallbackEventsMask(prefetchItf, PREFETCHEVENT_ERROR_CANDIDATE);
SL_RETURN_VAL_IF_FAILED(result, false, "prefetchItf SetCallbackEventsMask failed");
/* ------------------------------------------------------ */
/* Prefetch the data so we can get information about the format before starting to decode */
/* 1/ cause the player to prefetch the data */
result = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PAUSED);
SL_RETURN_VAL_IF_FAILED(result, false, "SetPlayState SL_PLAYSTATE_PAUSED failed");
/* 2/ block until data has been prefetched */
SLuint32 prefetchStatus = SL_PREFETCHSTATUS_UNDERFLOW;
SLuint32 timeOutIndex = 1000; //cjh time out prefetching after 2s
while ((prefetchStatus != SL_PREFETCHSTATUS_SUFFICIENTDATA) && (timeOutIndex > 0) &&
!_prefetchError) {
std::this_thread::sleep_for(std::chrono::milliseconds(2));
(*prefetchItf)->GetPrefetchStatus(prefetchItf, &prefetchStatus);
timeOutIndex--;
}
if (timeOutIndex == 0 || _prefetchError) {
ALOGE("Failure to prefetch data in time, exiting");
SL_RETURN_VAL_IF_FAILED(SL_RESULT_CONTENT_NOT_FOUND, false,
"Failure to prefetch data in time");
}
/* ------------------------------------------------------ */
/* Display duration */
SLmillisecond durationInMsec = SL_TIME_UNKNOWN;
result = (*playItf)->GetDuration(playItf, &durationInMsec);
SL_RETURN_VAL_IF_FAILED(result, false, "GetDuration failed");
if (durationInMsec == SL_TIME_UNKNOWN) {
ALOGV("Content duration is unknown");
} else {
ALOGV("Content duration is %dms", (int)durationInMsec);
}
/* ------------------------------------------------------ */
/* Display the metadata obtained from the decoder */
// This is for test / demonstration purposes only where we discover the key and value sizes
// of a PCM decoder. An application that would want to directly get access to those values
// can make assumptions about the size of the keys and their matching values (all SLuint32)
SLuint32 itemCount;
result = (*mdExtrItf)->GetItemCount(mdExtrItf, &itemCount);
SLuint32 i;
SLuint32 keySize;
SLuint32 valueSize;
SLMetadataInfo *keyInfo;
SLMetadataInfo *value;
for (i = 0; i < itemCount; i++) {
keyInfo = nullptr;
keySize = 0;
value = nullptr;
valueSize = 0;
result = (*mdExtrItf)->GetKeySize(mdExtrItf, i, &keySize);
SL_RETURN_VAL_IF_FAILED(result, false, "GetKeySize(%d) failed", (int)i);
result = (*mdExtrItf)->GetValueSize(mdExtrItf, i, &valueSize);
SL_RETURN_VAL_IF_FAILED(result, false, "GetValueSize(%d) failed", (int)i);
keyInfo = reinterpret_cast<SLMetadataInfo *>(malloc(keySize));
if (nullptr != keyInfo) {
result = (*mdExtrItf)->GetKey(mdExtrItf, i, keySize, keyInfo);
SL_RETURN_VAL_IF_FAILED(result, false, "GetKey(%d) failed", (int)i);
ALOGV("key[%d] size=%d, name=%s, value size=%d",
(int)i, (int)keyInfo->size, keyInfo->data, (int)valueSize);
/* find out the key index of the metadata we're interested in */
if (!strcmp(reinterpret_cast<char *>(keyInfo->data), ANDROID_KEY_PCMFORMAT_NUMCHANNELS)) {
_numChannelsKeyIndex = i;
} else if (!strcmp(reinterpret_cast<char *>(keyInfo->data), ANDROID_KEY_PCMFORMAT_SAMPLERATE)) {
_sampleRateKeyIndex = i;
} else if (!strcmp(reinterpret_cast<char *>(keyInfo->data), ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE)) {
_bitsPerSampleKeyIndex = i;
} else if (!strcmp(reinterpret_cast<char *>(keyInfo->data), ANDROID_KEY_PCMFORMAT_CONTAINERSIZE)) {
_containerSizeKeyIndex = i;
} else if (!strcmp(reinterpret_cast<char *>(keyInfo->data), ANDROID_KEY_PCMFORMAT_CHANNELMASK)) {
_channelMaskKeyIndex = i;
} else if (!strcmp(reinterpret_cast<char *>(keyInfo->data), ANDROID_KEY_PCMFORMAT_ENDIANNESS)) {
_endiannessKeyIndex = i;
}
free(keyInfo);
}
}
checkMetaData(_numChannelsKeyIndex, ANDROID_KEY_PCMFORMAT_NUMCHANNELS);
checkMetaData(_sampleRateKeyIndex, ANDROID_KEY_PCMFORMAT_SAMPLERATE);
checkMetaData(_bitsPerSampleKeyIndex, ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE);
checkMetaData(_containerSizeKeyIndex, ANDROID_KEY_PCMFORMAT_CONTAINERSIZE);
checkMetaData(_channelMaskKeyIndex, ANDROID_KEY_PCMFORMAT_CHANNELMASK);
checkMetaData(_endiannessKeyIndex, ANDROID_KEY_PCMFORMAT_ENDIANNESS);
/* ------------------------------------------------------ */
/* Start decoding */
result = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PLAYING);
SL_RETURN_VAL_IF_FAILED(result, false, "SetPlayState SL_PLAYSTATE_PLAYING failed");
ALOGV("Starting to decode");
/* Decode until the end of the stream is reached */
{
std::unique_lock<std::mutex> autoLock(_eosLock);
while (!_eos) {
_eosCondition.wait(autoLock);
}
}
ALOGV("EOS signaled");
/* ------------------------------------------------------ */
/* End of decoding */
/* Stop decoding */
result = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_STOPPED);
SL_RETURN_VAL_IF_FAILED(result, false, "SetPlayState SL_PLAYSTATE_STOPPED failed");
ALOGV("Stopped decoding");
/* Destroy the UrlAudioPlayer object */
{
std::lock_guard<std::mutex> lk(__SLPlayerMutex);
SL_DESTROY_OBJ(_playObj);
}
ALOGV("After destroy player ...");
_result.numFrames =
static_cast<int>(_result.pcmBuffer->size() / _result.numChannels / (_result.bitsPerSample / 8));
ccstd::string info = _result.toString();
ALOGI("Original audio info: %s, total size: %d", info.c_str(), (int)_result.pcmBuffer->size());
#endif
return true;
}
//-----------------------------------------------------------------
void AudioDecoderSLES::signalEos() {
std::unique_lock<std::mutex> autoLock(_eosLock);
_eos = true;
_eosCondition.notify_one();
}
void AudioDecoderSLES::queryAudioInfo() {
if (_formatQueried) {
return;
}
SLresult result;
/* Get duration in callback where we use the callback context for the SLPlayItf*/
SLmillisecond durationInMsec = SL_TIME_UNKNOWN;
result = (*_decContext.playItf)->GetDuration(_decContext.playItf, &durationInMsec);
SL_RETURN_IF_FAILED(result, "decodeProgressCallback,GetDuration failed");
if (durationInMsec == SL_TIME_UNKNOWN) {
ALOGV("Content duration is unknown (in dec callback)");
} else {
ALOGV("Content duration is %dms (in dec callback)", (int)durationInMsec);
_result.duration = durationInMsec / 1000.0F;
}
/* used to query metadata values */
SLMetadataInfo pcmMetaData;
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _sampleRateKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData);
SL_RETURN_IF_FAILED(result, "%s GetValue _sampleRateKeyIndex failed", __FUNCTION__);
// Note: here we could verify the following:
// pcmMetaData->encoding == SL_CHARACTERENCODING_BINARY
// pcmMetaData->size == sizeof(SLuint32)
// but the call was successful for the PCM format keys, so those conditions are implied
_result.sampleRate = *reinterpret_cast<SLuint32 *>(pcmMetaData.data);
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _numChannelsKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData);
SL_RETURN_IF_FAILED(result, "%s GetValue _numChannelsKeyIndex failed", __FUNCTION__);
_result.numChannels = *reinterpret_cast<SLuint32 *>(pcmMetaData.data);
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _bitsPerSampleKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData);
SL_RETURN_IF_FAILED(result, "%s GetValue _bitsPerSampleKeyIndex failed", __FUNCTION__)
_result.bitsPerSample = *reinterpret_cast<SLuint32 *>(pcmMetaData.data);
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _containerSizeKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData);
SL_RETURN_IF_FAILED(result, "%s GetValue _containerSizeKeyIndex failed", __FUNCTION__)
_result.containerSize = *reinterpret_cast<SLuint32 *>(pcmMetaData.data);
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _channelMaskKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData);
SL_RETURN_IF_FAILED(result, "%s GetValue _channelMaskKeyIndex failed", __FUNCTION__)
_result.channelMask = *reinterpret_cast<SLuint32 *>(pcmMetaData.data);
result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _endiannessKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData);
SL_RETURN_IF_FAILED(result, "%s GetValue _endiannessKeyIndex failed", __FUNCTION__)
_result.endianness = *reinterpret_cast<SLuint32 *>(pcmMetaData.data);
_formatQueried = true;
}
void AudioDecoderSLES::prefetchCallback(SLPrefetchStatusItf caller, SLuint32 event) {
SLpermille level = 0;
SLresult result;
result = (*caller)->GetFillLevel(caller, &level);
SL_RETURN_IF_FAILED(result, "GetFillLevel failed");
SLuint32 status;
//ALOGV("PrefetchEventCallback: received event %u", event);
result = (*caller)->GetPrefetchStatus(caller, &status);
SL_RETURN_IF_FAILED(result, "GetPrefetchStatus failed");
if ((PREFETCHEVENT_ERROR_CANDIDATE == (event & PREFETCHEVENT_ERROR_CANDIDATE)) && (level == 0) && (status == SL_PREFETCHSTATUS_UNDERFLOW)) {
ALOGV("PrefetchEventCallback: Error while prefetching data, exiting");
_prefetchError = true;
signalEos();
}
}
/* Callback for "playback" events, i.e. event happening during decoding */
void AudioDecoderSLES::decodeProgressCallback(SLPlayItf caller, SLuint32 event) {
CC_UNUSED_PARAM(caller);
if (SL_PLAYEVENT_HEADATEND & event) {
ALOGV("SL_PLAYEVENT_HEADATEND");
if (!_isDecodingCallbackInvoked) {
queryAudioInfo();
for (int i = 0; i < NB_BUFFERS_IN_QUEUE; ++i) {
_result.pcmBuffer->insert(_result.pcmBuffer->end(), _decContext.pData,
_decContext.pData + BUFFER_SIZE_IN_BYTES);
/* Increase data pointer by buffer size */
_decContext.pData += BUFFER_SIZE_IN_BYTES;
}
}
signalEos();
}
}
//-----------------------------------------------------------------
/* Callback for decoding buffer queue events */
void AudioDecoderSLES::decodeToPcmCallback(CCSLBufferQueueItf queueItf) {
_isDecodingCallbackInvoked = true;
ALOGV("%s ...", __FUNCTION__);
_counter++;
SLresult result;
// IDEA: ??
if (_counter % 1000 == 0) {
SLmillisecond msec;
result = (*_decContext.playItf)->GetPosition(_decContext.playItf, &msec);
SL_RETURN_IF_FAILED(result, "%s, GetPosition failed", __FUNCTION__);
ALOGV("%s called (iteration %d): current position=%d ms", __FUNCTION__, _counter, (int)msec);
}
_result.pcmBuffer->insert(_result.pcmBuffer->end(), _decContext.pData,
_decContext.pData + BUFFER_SIZE_IN_BYTES);
result = (*queueItf)->Enqueue(queueItf, _decContext.pData, BUFFER_SIZE_IN_BYTES);
SL_RETURN_IF_FAILED(result, "%s, Enqueue failed", __FUNCTION__);
/* Increase data pointer by buffer size */
_decContext.pData += BUFFER_SIZE_IN_BYTES;
if (_decContext.pData >= _decContext.pDataBase + (NB_BUFFERS_IN_QUEUE * BUFFER_SIZE_IN_BYTES)) {
_decContext.pData = _decContext.pDataBase;
}
// Note: adding a sleep here or any sync point is a way to slow down the decoding, or
// synchronize it with some other event, as the OpenSL ES framework will block until the
// buffer queue callback return to proceed with the decoding.
#if 0
/* Example: buffer queue state display */
SLAndroidSimpleBufferQueueState decQueueState;
result =(*queueItf)->GetState(queueItf, &decQueueState);
SL_RETURN_IF_FAILED(result, "decQueueState.GetState failed");
ALOGV("DecBufferQueueCallback now has _decContext.pData=%p, _decContext.pDataBase=%p, queue: "
"count=%u playIndex=%u, count: %d",
_decContext.pData, _decContext.pDataBase, decQueueState.count, decQueueState.index, _counter);
#endif
#if 0
/* Example: display position in callback where we use the callback context for the SLPlayItf*/
SLmillisecond posMsec = SL_TIME_UNKNOWN;
result = (*_decContext.playItf)->GetPosition(_decContext.playItf, &posMsec);
SL_RETURN_IF_FAILED(result, "decodeToPcmCallback,GetPosition2 failed");
if (posMsec == SL_TIME_UNKNOWN) {
ALOGV("Content position is unknown (in dec callback)");
} else {
ALOGV("Content position is %ums (in dec callback)",
posMsec);
}
#endif
queryAudioInfo();
}
} // namespace cc

View File

@@ -0,0 +1,96 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
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 <condition_variable>
#include <mutex>
#include "audio/android/AudioDecoder.h"
#include "audio/android/utils/Compat.h"
namespace cc {
class AudioDecoderSLES : public AudioDecoder {
protected:
AudioDecoderSLES();
~AudioDecoderSLES() override;
bool init(SLEngineItf engineItf, const ccstd::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback);
bool decodeToPcm() override;
private:
void queryAudioInfo();
void signalEos();
void decodeToPcmCallback(CCSLBufferQueueItf queueItf);
void prefetchCallback(SLPrefetchStatusItf caller, SLuint32 event);
void decodeProgressCallback(SLPlayItf caller, SLuint32 event);
SLEngineItf _engineItf;
SLObjectItf _playObj;
/* Local storage for decoded audio data */
char *_pcmData;
/* we only want to query / display the PCM format once */
bool _formatQueried;
/* Used to signal prefetching failures */
bool _prefetchError;
/* to display the number of decode iterations */
int _counter;
/* metadata key index for the PCM format information we want to retrieve */
int _numChannelsKeyIndex;
int _sampleRateKeyIndex;
int _bitsPerSampleKeyIndex;
int _containerSizeKeyIndex;
int _channelMaskKeyIndex;
int _endiannessKeyIndex;
/* to signal to the test app the end of the stream to decode has been reached */
bool _eos;
std::mutex _eosLock;
std::condition_variable _eosCondition;
/* Structure for passing information to callback function */
typedef struct CallbackCntxt_ { //NOLINT(modernize-use-using, readability-identifier-naming)
SLPlayItf playItf;
SLMetadataExtractionItf metaItf;
SLuint32 size;
SLint8 *pDataBase; // Base address of local audio data storage
SLint8 *pData; // Current address of local audio data storage
} CallbackCntxt;
CallbackCntxt _decContext;
int _bufferSizeInFrames;
int _assetFd;
FdGetterCallback _fdGetterCallback;
bool _isDecodingCallbackInvoked;
friend class SLAudioDecoderCallbackProxy;
friend class AudioDecoderProvider;
};
} // namespace cc

View File

@@ -0,0 +1,106 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioDecoderWav"
#include "audio/android/AudioDecoderWav.h"
#include "audio/common/utils/include/tinysndfile.h"
#include "platform/FileUtils.h"
namespace cc {
using namespace sf; //NOLINT
AudioDecoderWav::AudioDecoderWav() {
ALOGV("Create AudioDecoderWav");
}
AudioDecoderWav::~AudioDecoderWav() = default;
void *AudioDecoderWav::onWavOpen(const char * /*path*/, void *user) {
return user;
}
int AudioDecoderWav::onWavSeek(void *datasource, long offset, int whence) { //NOLINT(google-runtime-int)
return AudioDecoder::fileSeek(datasource, static_cast<int64_t>(offset), whence);
}
int AudioDecoderWav::onWavClose(void * /*datasource*/) {
return 0;
}
bool AudioDecoderWav::decodeToPcm() {
_fileData = FileUtils::getInstance()->getDataFromFile(_url);
if (_fileData.isNull()) {
return false;
}
SF_INFO info;
snd_callbacks cb;
cb.open = onWavOpen;
cb.read = AudioDecoder::fileRead;
cb.seek = onWavSeek;
cb.close = onWavClose;
cb.tell = AudioDecoder::fileTell;
SNDFILE *handle = nullptr;
bool ret = false;
do {
handle = sf_open_read(_url.c_str(), &info, &cb, this);
if (handle == nullptr) {
break;
}
if (info.frames == 0) {
break;
}
ALOGD("wav info: frames: %d, samplerate: %d, channels: %d, format: %d", info.frames, info.samplerate, info.channels, info.format);
size_t bufSize = sizeof(int16_t) * info.frames * info.channels;
auto *buf = static_cast<unsigned char *>(malloc(bufSize));
sf_count_t readFrames = sf_readf_short(handle, reinterpret_cast<int16_t *>(buf), info.frames);
CC_ASSERT(readFrames == info.frames);
_result.pcmBuffer->insert(_result.pcmBuffer->end(), buf, buf + bufSize);
_result.numChannels = info.channels;
_result.sampleRate = info.samplerate;
_result.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
_result.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
_result.channelMask = _result.numChannels == 1 ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT);
_result.endianness = SL_BYTEORDER_LITTLEENDIAN;
_result.numFrames = info.frames;
_result.duration = static_cast<float>(1.0F * info.frames / _result.sampleRate); //NOLINT
free(buf);
ret = true;
} while (false);
if (handle != nullptr) {
sf_close(handle);
}
return ret;
}
} // namespace cc

View File

@@ -0,0 +1,46 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/AudioDecoder.h"
namespace cc {
class AudioDecoderWav : public AudioDecoder {
protected:
AudioDecoderWav();
virtual ~AudioDecoderWav();
virtual bool decodeToPcm() override;
static void *onWavOpen(const char *path, void *user);
static int onWavSeek(void *datasource, long offset, int whence);
static int onWavClose(void *datasource);
friend class AudioDecoderProvider;
};
} // namespace cc

View File

@@ -0,0 +1,507 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioEngineImpl"
#include "audio/android/AudioEngine-inl.h"
#include <unistd.h>
// for native asset manager
#if CC_PLATFORM == CC_PLATFORM_ANDROID
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
#include <android/log.h>
#endif
#include <sys/types.h>
#include <mutex>
#include <thread>
#include "application/ApplicationManager.h"
#include "audio/include/AudioEngine.h"
#include "base/Log.h"
#include "base/Scheduler.h"
#include "base/UTF8.h"
#include "base/memory/Memory.h"
#if CC_PLATFORM == CC_PLATFORM_ANDROID
#include "platform/android/FileUtils-android.h"
#include "platform/java/jni/JniHelper.h"
#include "platform/java/jni/JniImp.h"
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
#include "cocos/platform/openharmony/FileUtils-OpenHarmony.h"
#endif
#include "audio/android/AudioDecoder.h"
#include "audio/android/AudioDecoderProvider.h"
#include "audio/android/AudioPlayerProvider.h"
#include "audio/android/IAudioPlayer.h"
#include "audio/android/ICallerThreadUtils.h"
#include "audio/android/UrlAudioPlayer.h"
#include "audio/android/cutils/log.h"
#include "engine/EngineEvents.h"
using namespace cc; //NOLINT
// Audio focus values synchronized with which in cocos/platform/android/java/src/com/cocos/lib/CocosNativeActivity.java
namespace {
AudioEngineImpl *gAudioImpl = nullptr;
int outputSampleRate = 44100;
#if CC_PLATFORM == CC_PLATFORM_ANDROID
int bufferSizeInFrames = 192;
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
// TODO(hack) : There is currently a bug in the opensles module,
// so openharmony must configure a fixed size, otherwise the callback will be suspended
int bufferSizeInFrames = 2048;
#endif
void getAudioInfo() {
#if CC_PLATFORM == CC_PLATFORM_ANDROID
JNIEnv * env = JniHelper::getEnv();
jclass audioSystem = env->FindClass("android/media/AudioSystem");
jmethodID method = env->GetStaticMethodID(audioSystem, "getPrimaryOutputSamplingRate", "()I");
outputSampleRate = env->CallStaticIntMethod(audioSystem, method);
method = env->GetStaticMethodID(audioSystem, "getPrimaryOutputFrameCount", "()I");
bufferSizeInFrames = env->CallStaticIntMethod(audioSystem, method);
#else
// In openharmony, setting to 48K does not cause audio delays
outputSampleRate = 48000;
#endif
}
} // namespace
class CallerThreadUtils : public ICallerThreadUtils {
public:
void performFunctionInCallerThread(const std::function<void()> &func) override {
CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread(func);
};
std::thread::id getCallerThreadId() override {
return _tid;
};
void setCallerThreadId(std::thread::id tid) {
_tid = tid;
};
private:
std::thread::id _tid;
};
static CallerThreadUtils gCallerThreadUtils;
static int fdGetter(const ccstd::string &url, off_t *start, off_t *length) {
int fd = -1;
#if CC_PLATFORM == CC_PLATFORM_ANDROID
if (cc::FileUtilsAndroid::getObbFile() != nullptr) {
int64_t startV;
int64_t lenV;
fd = cc::getObbAssetFileDescriptorJNI(url, &startV, &lenV);
*start = static_cast<off_t>(startV);
*length = static_cast<off_t>(lenV);
}
if (fd <= 0) {
auto *asset = AAssetManager_open(cc::FileUtilsAndroid::getAssetManager(), url.c_str(), AASSET_MODE_UNKNOWN);
// open asset as file descriptor
fd = AAsset_openFileDescriptor(asset, start, length);
AAsset_close(asset);
}
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
FileUtilsOpenHarmony* fileUtils = dynamic_cast<FileUtilsOpenHarmony*>(FileUtils::getInstance());
if(fileUtils) {
RawFileDescriptor descriptor;
fileUtils->getRawFileDescriptor(url, descriptor);
fd = descriptor.fd;
}
#endif
if (fd <= 0) {
ALOGE("Failed to open file descriptor for '%s'", url.c_str());
}
return fd;
};
//====================================================
AudioEngineImpl::AudioEngineImpl()
: _engineObject(nullptr),
_engineEngine(nullptr),
_outputMixObject(nullptr),
_audioPlayerProvider(nullptr),
_audioIDIndex(0),
_lazyInitLoop(true) {
gCallerThreadUtils.setCallerThreadId(std::this_thread::get_id());
gAudioImpl = this;
getAudioInfo();
}
AudioEngineImpl::~AudioEngineImpl() {
if (_audioPlayerProvider != nullptr) {
delete _audioPlayerProvider;
_audioPlayerProvider = nullptr;
}
if (_outputMixObject) {
(*_outputMixObject)->Destroy(_outputMixObject);
}
if (_engineObject) {
(*_engineObject)->Destroy(_engineObject);
}
gAudioImpl = nullptr;
}
bool AudioEngineImpl::init() {
bool ret = false;
do {
// create engine
auto result = slCreateEngine(&_engineObject, 0, nullptr, 0, nullptr, nullptr);
if (SL_RESULT_SUCCESS != result) {
CC_LOG_ERROR("create opensl engine fail");
break;
}
// realize the engine
result = (*_engineObject)->Realize(_engineObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
CC_LOG_ERROR("realize the engine fail");
break;
}
// get the engine interface, which is needed in order to create other objects
result = (*_engineObject)->GetInterface(_engineObject, SL_IID_ENGINE, &_engineEngine);
if (SL_RESULT_SUCCESS != result) {
CC_LOG_ERROR("get the engine interface fail");
break;
}
// create output mix
const SLInterfaceID outputMixIIDs[] = {};
const SLboolean outputMixReqs[] = {};
result = (*_engineEngine)->CreateOutputMix(_engineEngine, &_outputMixObject, 0, outputMixIIDs, outputMixReqs);
if (SL_RESULT_SUCCESS != result) {
CC_LOG_ERROR("create output mix fail");
break;
}
// realize the output mix
result = (*_outputMixObject)->Realize(_outputMixObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
CC_LOG_ERROR("realize the output mix fail");
break;
}
_audioPlayerProvider = ccnew AudioPlayerProvider(_engineEngine, _outputMixObject, outputSampleRate, bufferSizeInFrames, fdGetter, &gCallerThreadUtils);
ret = true;
} while (false);
return ret;
}
void AudioEngineImpl::setAudioFocusForAllPlayers(bool isFocus) {
for (const auto &e : _audioPlayers) {
e.second->setAudioFocus(isFocus);
}
}
int AudioEngineImpl::play2d(const ccstd::string &filePath, bool loop, float volume) {
ALOGV("play2d, _audioPlayers.size=%d", (int)_audioPlayers.size());
auto audioId = AudioEngine::INVALID_AUDIO_ID;
do {
if (_engineEngine == nullptr || _audioPlayerProvider == nullptr) {
break;
}
auto fullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
audioId = _audioIDIndex++;
auto *player = _audioPlayerProvider->getAudioPlayer(fullPath);
if (player != nullptr) {
player->setId(audioId);
_audioPlayers.insert(std::make_pair(audioId, player));
player->setPlayEventCallback([this, player, filePath](IAudioPlayer::State state) {
if (state != IAudioPlayer::State::OVER && state != IAudioPlayer::State::STOPPED) {
ALOGV("Ignore state: %d", static_cast<int>(state));
return;
}
int id = player->getId();
ALOGV("Removing player id=%d, state:%d", id, (int)state);
AudioEngine::remove(id);
if (_audioPlayers.find(id) != _audioPlayers.end()) {
_audioPlayers.erase(id);
}
if (_urlAudioPlayersNeedResume.find(id) != _urlAudioPlayersNeedResume.end()) {
_urlAudioPlayersNeedResume.erase(id);
}
auto iter = _callbackMap.find(id);
if (iter != _callbackMap.end()) {
if (state == IAudioPlayer::State::OVER) {
iter->second(id, filePath);
}
_callbackMap.erase(iter);
}
});
player->setLoop(loop);
player->setVolume(volume);
player->play();
} else {
ALOGE("Oops, player is null ...");
return AudioEngine::INVALID_AUDIO_ID;
}
AudioEngine::sAudioIDInfoMap[audioId].state = AudioEngine::AudioState::PLAYING;
} while (false);
return audioId;
}
void AudioEngineImpl::setVolume(int audioID, float volume) {
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end()) {
auto *player = iter->second;
player->setVolume(volume);
}
}
void AudioEngineImpl::setLoop(int audioID, bool loop) {
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end()) {
auto *player = iter->second;
player->setLoop(loop);
}
}
void AudioEngineImpl::pause(int audioID) {
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end()) {
auto *player = iter->second;
player->pause();
}
}
void AudioEngineImpl::resume(int audioID) {
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end()) {
auto *player = iter->second;
player->resume();
}
}
void AudioEngineImpl::stop(int audioID) {
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end()) {
auto *player = iter->second;
player->stop();
}
}
void AudioEngineImpl::stopAll() {
if (_audioPlayers.empty()) {
return;
}
// Create a temporary vector for storing all players since
// p->stop() will trigger _audioPlayers.erase,
// and it will cause a crash as it's already in for loop
ccstd::vector<IAudioPlayer *> players;
players.reserve(_audioPlayers.size());
for (const auto &e : _audioPlayers) {
players.push_back(e.second);
}
for (auto *p : players) {
p->stop();
}
}
float AudioEngineImpl::getDuration(int audioID) {
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end()) {
auto *player = iter->second;
return player->getDuration();
}
return 0.0F;
}
float AudioEngineImpl::getDurationFromFile(const ccstd::string &filePath) {
if (_audioPlayerProvider != nullptr) {
auto fullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
return _audioPlayerProvider->getDurationFromFile(fullPath);
}
return 0;
}
float AudioEngineImpl::getCurrentTime(int audioID) {
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end()) {
auto *player = iter->second;
return player->getPosition();
}
return 0.0F;
}
bool AudioEngineImpl::setCurrentTime(int audioID, float time) {
auto iter = _audioPlayers.find(audioID);
if (iter != _audioPlayers.end()) {
auto *player = iter->second;
return player->setPosition(time);
}
return false;
}
void AudioEngineImpl::setFinishCallback(int audioID, const std::function<void(int, const ccstd::string &)> &callback) {
_callbackMap[audioID] = callback;
}
void AudioEngineImpl::preload(const ccstd::string &filePath, const std::function<void(bool)> &callback) {
if (_audioPlayerProvider != nullptr) {
ccstd::string fullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
_audioPlayerProvider->preloadEffect(fullPath, [callback](bool succeed, const PcmData & /*data*/) {
if (callback != nullptr) {
callback(succeed);
}
});
} else {
if (callback != nullptr) {
callback(false);
}
}
}
void AudioEngineImpl::uncache(const ccstd::string &filePath) {
if (_audioPlayerProvider != nullptr) {
ccstd::string fullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
_audioPlayerProvider->clearPcmCache(fullPath);
}
}
void AudioEngineImpl::uncacheAll() {
if (_audioPlayerProvider != nullptr) {
_audioPlayerProvider->clearAllPcmCaches();
}
}
void AudioEngineImpl::onPause() {
if (_audioPlayerProvider != nullptr) {
_audioPlayerProvider->pause();
}
}
void AudioEngineImpl::onResume() {
if (_audioPlayerProvider != nullptr) {
_audioPlayerProvider->resume();
}
}
PCMHeader AudioEngineImpl::getPCMHeader(const char *url) {
PCMHeader header{};
ccstd::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url);
if (fileFullPath.empty()) {
CC_LOG_DEBUG("file %s does not exist or failed to load", url);
return header;
}
if (_audioPlayerProvider->getPcmHeader(url, header)) {
CC_LOG_DEBUG("file %s pcm data already cached", url);
return header;
}
AudioDecoder *decoder = AudioDecoderProvider::createAudioDecoder(_engineEngine, fileFullPath, bufferSizeInFrames, outputSampleRate, fdGetter);
if (decoder == nullptr) {
CC_LOG_DEBUG("decode %s failed, the file formate might not support", url);
return header;
}
if (!decoder->start()) {
CC_LOG_DEBUG("[Audio Decoder] Decode failed %s", url);
return header;
}
// Ready to decode
do {
PcmData data = decoder->getResult();
header.bytesPerFrame = data.bitsPerSample / 8;
header.channelCount = data.numChannels;
header.dataFormat = AudioDataFormat::SIGNED_16;
header.sampleRate = data.sampleRate;
header.totalFrames = data.numFrames;
} while (false);
AudioDecoderProvider::destroyAudioDecoder(&decoder);
return header;
}
ccstd::vector<uint8_t> AudioEngineImpl::getOriginalPCMBuffer(const char *url, uint32_t channelID) {
ccstd::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url);
ccstd::vector<uint8_t> pcmData;
if (fileFullPath.empty()) {
CC_LOG_DEBUG("file %s does not exist or failed to load", url);
return pcmData;
}
PcmData data;
if (_audioPlayerProvider->getPcmData(url, data)) {
CC_LOG_DEBUG("file %s pcm data already cached", url);
} else {
AudioDecoder *decoder = AudioDecoderProvider::createAudioDecoder(_engineEngine, fileFullPath, bufferSizeInFrames, outputSampleRate, fdGetter);
if (decoder == nullptr) {
CC_LOG_DEBUG("decode %s failed, the file formate might not support", url);
return pcmData;
}
if (!decoder->start()) {
CC_LOG_DEBUG("[Audio Decoder] Decode failed %s", url);
return pcmData;
}
data = decoder->getResult();
_audioPlayerProvider->registerPcmData(url, data);
AudioDecoderProvider::destroyAudioDecoder(&decoder);
}
do {
const uint32_t channelCount = data.numChannels;
if (channelID >= channelCount) {
CC_LOG_ERROR("channelID invalid, total channel count is %d but %d is required", channelCount, channelID);
break;
}
// bytesPerSample = bitsPerSample / 8, according to 1 byte = 8 bits
const uint32_t bytesPerFrame = data.numChannels * data.bitsPerSample / 8;
const uint32_t numFrames = data.numFrames;
const uint32_t bytesPerChannelInFrame = bytesPerFrame / channelCount;
pcmData.resize(bytesPerChannelInFrame * numFrames);
uint8_t *p = pcmData.data();
char *tmpBuf = data.pcmBuffer->data(); // shared ptr
for (int itr = 0; itr < numFrames; itr++) {
memcpy(p, tmpBuf + itr * bytesPerFrame + channelID * bytesPerChannelInFrame, bytesPerChannelInFrame);
p += bytesPerChannelInFrame;
}
} while (false);
return pcmData;
}

View File

@@ -0,0 +1,105 @@
/****************************************************************************
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
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 <SLES/OpenSLES.h>
#if CC_PLATFORM == CC_PLATFORM_ANDROID
#include <SLES/OpenSLES_Android.h>
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
#include <SLES/OpenSLES_Platform.h>
#endif
#include <functional>
#include "audio/include/AudioDef.h"
#include "base/RefCounted.h"
#include "base/Utils.h"
#include "base/std/container/string.h"
#include "base/std/container/unordered_map.h"
#define MAX_AUDIOINSTANCES 13
#define ERRORLOG(msg) log("fun:%s,line:%d,msg:%s", __func__, __LINE__, #msg)
namespace cc {
struct CustomEvent;
class IAudioPlayer;
class AudioPlayerProvider;
class AudioEngineImpl;
class AudioEngineImpl : public RefCounted {
public:
AudioEngineImpl();
~AudioEngineImpl() override;
bool init();
int play2d(const ccstd::string &filePath, bool loop, float volume);
void setVolume(int audioID, float volume);
void setLoop(int audioID, bool loop);
void pause(int audioID);
void resume(int audioID);
void stop(int audioID);
void stopAll();
float getDuration(int audioID);
float getDurationFromFile(const ccstd::string &filePath);
float getCurrentTime(int audioID);
bool setCurrentTime(int audioID, float time);
void setFinishCallback(int audioID, const std::function<void(int, const ccstd::string &)> &callback);
void uncache(const ccstd::string &filePath);
void uncacheAll();
void preload(const ccstd::string &filePath, const std::function<void(bool)> &callback);
void onResume();
void onPause();
void setAudioFocusForAllPlayers(bool isFocus);
PCMHeader getPCMHeader(const char *url);
std::vector<uint8_t> getOriginalPCMBuffer(const char *url, uint32_t channelID);
private:
// engine interfaces
SLObjectItf _engineObject;
SLEngineItf _engineEngine;
// output mix interfaces
SLObjectItf _outputMixObject;
//audioID,AudioInfo
ccstd::unordered_map<int, IAudioPlayer *> _audioPlayers;
ccstd::unordered_map<int, std::function<void(int, const ccstd::string &)>> _callbackMap;
// UrlAudioPlayers which need to resumed while entering foreground
ccstd::unordered_map<int, IAudioPlayer *> _urlAudioPlayersNeedResume;
AudioPlayerProvider *_audioPlayerProvider;
int _audioIDIndex;
bool _lazyInitLoop;
};
} // namespace cc

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,379 @@
/*
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
#pragma once
#include <cstdint>
#include <sys/types.h>
#include <pthread.h>
#include "audio/android/AudioBufferProvider.h"
#include "audio/android/AudioResamplerPublic.h"
#include "audio/android/AudioResampler.h"
#include "audio/android/audio.h"
#include "audio/android/utils/Compat.h"
// IDEA: This is actually unity gain, which might not be max in future, expressed in U.12
#define MAX_GAIN_INT AudioMixer::UNITY_GAIN_INT
namespace cc {
// ----------------------------------------------------------------------------
class AudioMixer {
public:
AudioMixer(size_t frameCount, uint32_t sampleRate,
uint32_t maxNumTracks = MAX_NUM_TRACKS);
/*virtual*/ ~AudioMixer(); // non-virtual saves a v-table, restore if sub-classed
// This mixer has a hard-coded upper limit of 32 active track inputs.
// Adding support for > 32 tracks would require more than simply changing this value.
static const uint32_t MAX_NUM_TRACKS = 32;
// maximum number of channels supported by the mixer
// This mixer has a hard-coded upper limit of 8 channels for output.
static const uint32_t MAX_NUM_CHANNELS = 8;
static const uint32_t MAX_NUM_VOLUMES = 2; // stereo volume only
// maximum number of channels supported for the content
static const uint32_t MAX_NUM_CHANNELS_TO_DOWNMIX = AUDIO_CHANNEL_COUNT_MAX;
static const uint16_t UNITY_GAIN_INT = 0x1000;
static const CONSTEXPR float UNITY_GAIN_FLOAT = 1.0F;
enum { // names
// track names (MAX_NUM_TRACKS units)
TRACK0 = 0x1000,
// 0x2000 is unused
// setParameter targets
TRACK = 0x3000,
RESAMPLE = 0x3001,
RAMP_VOLUME = 0x3002, // ramp to new volume
VOLUME = 0x3003, // don't ramp
TIMESTRETCH = 0x3004,
// set Parameter names
// for target TRACK
CHANNEL_MASK = 0x4000,
FORMAT = 0x4001,
MAIN_BUFFER = 0x4002,
AUX_BUFFER = 0x4003,
DOWNMIX_TYPE = 0X4004,
MIXER_FORMAT = 0x4005, // AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
MIXER_CHANNEL_MASK = 0x4006, // Channel mask for mixer output
// for target RESAMPLE
SAMPLE_RATE = 0x4100, // Configure sample rate conversion on this track name;
// parameter 'value' is the new sample rate in Hz.
// Only creates a sample rate converter the first time that
// the track sample rate is different from the mix sample rate.
// If the new sample rate is the same as the mix sample rate,
// and a sample rate converter already exists,
// then the sample rate converter remains present but is a no-op.
RESET = 0x4101, // Reset sample rate converter without changing sample rate.
// This clears out the resampler's input buffer.
REMOVE = 0x4102, // Remove the sample rate converter on this track name;
// the track is restored to the mix sample rate.
// for target RAMP_VOLUME and VOLUME (8 channels max)
// IDEA: use float for these 3 to improve the dynamic range
VOLUME0 = 0x4200,
VOLUME1 = 0x4201,
AUXLEVEL = 0x4210,
// for target TIMESTRETCH
PLAYBACK_RATE = 0x4300, // Configure timestretch on this track name;
// parameter 'value' is a pointer to the new playback rate.
};
// For all APIs with "name": TRACK0 <= name < TRACK0 + MAX_NUM_TRACKS
// Allocate a track name. Returns new track name if successful, -1 on failure.
// The failure could be because of an invalid channelMask or format, or that
// the track capacity of the mixer is exceeded.
int getTrackName(audio_channel_mask_t channelMask,
audio_format_t format, int sessionId);
// Free an allocated track by name
void deleteTrackName(int name);
// Enable or disable an allocated track by name
void enable(int name);
void disable(int name);
void setParameter(int name, int target, int param, void *value);
void setBufferProvider(int name, AudioBufferProvider *bufferProvider);
void process(int64_t pts);
uint32_t trackNames() const { return mTrackNames; }
size_t getUnreleasedFrames(int name) const;
static inline bool isValidPcmTrackFormat(audio_format_t format) {
switch (format) {
case AUDIO_FORMAT_PCM_8_BIT:
case AUDIO_FORMAT_PCM_16_BIT:
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
case AUDIO_FORMAT_PCM_32_BIT:
case AUDIO_FORMAT_PCM_FLOAT:
return true;
default:
return false;
}
}
private:
enum {
// IDEA: this representation permits up to 8 channels
NEEDS_CHANNEL_COUNT__MASK = 0x00000007, // NOLINT(bugprone-reserved-identifier)
};
enum {
NEEDS_CHANNEL_1 = 0x00000000, // mono
NEEDS_CHANNEL_2 = 0x00000001, // stereo
// sample format is not explicitly specified, and is assumed to be AUDIO_FORMAT_PCM_16_BIT
NEEDS_MUTE = 0x00000100,
NEEDS_RESAMPLE = 0x00001000,
NEEDS_AUX = 0x00010000,
};
struct state_t;
struct track_t;
typedef void (*hook_t)(track_t *t, int32_t *output, size_t numOutFrames, int32_t *temp, int32_t *aux); //NOLINT(modernize-use-using)
static const int BLOCKSIZE = 16; // 4 cache lines
struct track_t {
uint32_t needs;
// REFINE: Eventually remove legacy integer volume settings
union {
int16_t volume[MAX_NUM_VOLUMES]; // U4.12 fixed point (top bit should be zero)
int32_t volumeRL;
};
int32_t prevVolume[MAX_NUM_VOLUMES];
// 16-byte boundary
int32_t volumeInc[MAX_NUM_VOLUMES];
int32_t auxInc;
int32_t prevAuxLevel;
// 16-byte boundary
int16_t auxLevel; // 0 <= auxLevel <= MAX_GAIN_INT, but signed for mul performance
uint16_t frameCount;
uint8_t channelCount; // 1 or 2, redundant with (needs & NEEDS_CHANNEL_COUNT__MASK)
uint8_t unused_padding; // formerly format, was always 16
uint16_t enabled; // actually bool
audio_channel_mask_t channelMask;
// actual buffer provider used by the track hooks, see DownmixerBufferProvider below
// for how the Track buffer provider is wrapped by another one when dowmixing is required
AudioBufferProvider *bufferProvider;
// 16-byte boundary
mutable AudioBufferProvider::Buffer buffer; // 8 bytes
hook_t hook;
const void *in; // current location in buffer
// 16-byte boundary
AudioResampler *resampler;
uint32_t sampleRate;
int32_t *mainBuffer;
int32_t *auxBuffer;
// 16-byte boundary
/* Buffer providers are constructed to translate the track input data as needed.
*
* REFINE: perhaps make a single PlaybackConverterProvider class to move
* all pre-mixer track buffer conversions outside the AudioMixer class.
*
* 1) mInputBufferProvider: The AudioTrack buffer provider.
* 2) mReformatBufferProvider: If not NULL, performs the audio reformat to
* match either mMixerInFormat or mDownmixRequiresFormat, if the downmixer
* requires reformat. For example, it may convert floating point input to
* PCM_16_bit if that's required by the downmixer.
* 3) downmixerBufferProvider: If not NULL, performs the channel remixing to match
* the number of channels required by the mixer sink.
* 4) mPostDownmixReformatBufferProvider: If not NULL, performs reformatting from
* the downmixer requirements to the mixer engine input requirements.
* 5) mTimestretchBufferProvider: Adds timestretching for playback rate
*/
AudioBufferProvider *mInputBufferProvider; // externally provided buffer provider.
//cjh PassthruBufferProvider* mReformatBufferProvider; // provider wrapper for reformatting.
// PassthruBufferProvider* downmixerBufferProvider; // wrapper for channel conversion.
// PassthruBufferProvider* mPostDownmixReformatBufferProvider;
// PassthruBufferProvider* mTimestretchBufferProvider;
int32_t sessionId;
audio_format_t mMixerFormat; // output mix format: AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
audio_format_t mFormat; // input track format
audio_format_t mMixerInFormat; // mix internal format AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
// each track must be converted to this format.
audio_format_t mDownmixRequiresFormat; // required downmixer format
// AUDIO_FORMAT_PCM_16_BIT if 16 bit necessary
// AUDIO_FORMAT_INVALID if no required format
float mVolume[MAX_NUM_VOLUMES]; // floating point set volume
float mPrevVolume[MAX_NUM_VOLUMES]; // floating point previous volume
float mVolumeInc[MAX_NUM_VOLUMES]; // floating point volume increment
float mAuxLevel; // floating point set aux level
float mPrevAuxLevel; // floating point prev aux level
float mAuxInc; // floating point aux increment
audio_channel_mask_t mMixerChannelMask;
uint32_t mMixerChannelCount;
AudioPlaybackRate mPlaybackRate;
bool needsRamp() { return (volumeInc[0] | volumeInc[1] | auxInc) != 0; }
bool setResampler(uint32_t trackSampleRate, uint32_t devSampleRate);
bool doesResample() const { return resampler != nullptr; }
void resetResampler() const {
if (resampler != nullptr) resampler->reset();
}
void adjustVolumeRamp(bool aux, bool useFloat = false);
size_t getUnreleasedFrames() const { return resampler != nullptr ? resampler->getUnreleasedFrames() : 0; };
status_t prepareForDownmix();
void unprepareForDownmix();
status_t prepareForReformat();
void unprepareForReformat();
bool setPlaybackRate(const AudioPlaybackRate &playbackRate);
void reconfigureBufferProviders();
};
typedef void (*process_hook_t)(state_t *state, int64_t pts); // NOLINT(modernize-use-using)
// pad to 32-bytes to fill cache line
struct state_t {
uint32_t enabledTracks;
uint32_t needsChanged;
size_t frameCount;
process_hook_t hook; // one of process__*, never NULL
int32_t *outputTemp;
int32_t *resampleTemp;
//cjh NBLog::Writer* mLog;
int32_t reserved[1];
// IDEA: allocate dynamically to save some memory when maxNumTracks < MAX_NUM_TRACKS
track_t tracks[MAX_NUM_TRACKS] __attribute__((aligned(32)));
};
// bitmask of allocated track names, where bit 0 corresponds to TRACK0 etc.
uint32_t mTrackNames;// NOLINT(readability-identifier-naming)
// bitmask of configured track names; ~0 if maxNumTracks == MAX_NUM_TRACKS,
// but will have fewer bits set if maxNumTracks < MAX_NUM_TRACKS
const uint32_t mConfiguredNames;// NOLINT(readability-identifier-naming)
const uint32_t mSampleRate;// NOLINT(readability-identifier-naming)
//cjh NBLog::Writer mDummyLog;
public:
//cjh void setLog(NBLog::Writer* log);
private:
state_t mState __attribute__((aligned(32)));// NOLINT(readability-identifier-naming)
// Call after changing either the enabled status of a track, or parameters of an enabled track.
// OK to call more often than that, but unnecessary.
void invalidateState(uint32_t mask);
bool setChannelMasks(int name,
audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask);
static void track__genericResample(track_t *t, int32_t *out, size_t numFrames, int32_t *temp, int32_t *aux);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
static void track__nop(track_t *t, int32_t *out, size_t numFrames, int32_t *temp, int32_t *aux);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
static void track__16BitsStereo(track_t *t, int32_t *out, size_t numFrames, int32_t *temp, int32_t *aux);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
static void track__16BitsMono(track_t *t, int32_t *out, size_t numFrames, int32_t *temp, int32_t *aux);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
static void volumeRampStereo(track_t *t, int32_t *out, size_t frameCount, int32_t *temp, int32_t *aux);
static void volumeStereo(track_t *t, int32_t *out, size_t frameCount, int32_t *temp,
int32_t *aux);
static void process__validate(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
static void process__nop(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
static void process__genericNoResampling(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
static void process__genericResampling(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
static void process__OneTrack16BitsStereoNoResampling(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
static int64_t calculateOutputPTS(const track_t &t, int64_t basePTS,
int outputFrameIndex);
static uint64_t sLocalTimeFreq;
static pthread_once_t sOnceControl;
static void sInitRoutine();
/* multi-format volume mixing function (calls template functions
* in AudioMixerOps.h). The template parameters are as follows:
*
* MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration)
* USEFLOATVOL (set to true if float volume is used)
* ADJUSTVOL (set to true if volume ramp parameters needs adjustment afterwards)
* TO: int32_t (Q4.27) or float
* TI: int32_t (Q4.27) or int16_t (Q0.15) or float
* TA: int32_t (Q4.27)
*/
template <int MIXTYPE, bool USEFLOATVOL, bool ADJUSTVOL,
typename TO, typename TI, typename TA>
static void volumeMix(TO *out, size_t outFrames,
const TI *in, TA *aux, bool ramp, AudioMixer::track_t *t);
// multi-format process hooks
template <int MIXTYPE, typename TO, typename TI, typename TA>
static void process_NoResampleOneTrack(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
// multi-format track hooks
template <int MIXTYPE, typename TO, typename TI, typename TA>
static void track__Resample(track_t *t, TO *out, size_t frameCount, TO *temp __unused, TA *aux);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
template <int MIXTYPE, typename TO, typename TI, typename TA>
static void track__NoResample(track_t *t, TO *out, size_t frameCount, TO *temp __unused, TA *aux); // NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
static void convertMixerFormat(void *out, audio_format_t mixerOutFormat,
void *in, audio_format_t mixerInFormat, size_t sampleCount);
// hook types
enum {
PROCESSTYPE_NORESAMPLEONETRACK,
};
enum {
TRACKTYPE_NOP,
TRACKTYPE_RESAMPLE,
TRACKTYPE_NORESAMPLE,
TRACKTYPE_NORESAMPLEMONO,
};
// functions for determining the proper process and track hooks.
static process_hook_t getProcessHook(int processType, uint32_t channelCount,
audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
static hook_t getTrackHook(int trackType, uint32_t channelCount,
audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
};
// ----------------------------------------------------------------------------
} // namespace cc

View File

@@ -0,0 +1,296 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "AudioMixerController"
#include "audio/android/AudioMixerController.h"
#include <algorithm>
#include "audio/android/AudioMixer.h"
#include "audio/android/OpenSLHelper.h"
#include "audio/android/Track.h"
#include "base/memory/Memory.h"
namespace cc {
AudioMixerController::AudioMixerController(int bufferSizeInFrames, int sampleRate, int channelCount)
: _bufferSizeInFrames(bufferSizeInFrames), _sampleRate(sampleRate), _channelCount(channelCount), _mixer(nullptr), _isPaused(false), _isMixingFrame(false) {
ALOGV("In the constructor of AudioMixerController!");
_mixingBuffer.size = (size_t)bufferSizeInFrames * 2 * channelCount;
// Don't use posix_memalign since it was added from API 16, it will crash on Android 2.3
// Therefore, for a workaround, we uses memalign here.
_mixingBuffer.buf = memalign(32, _mixingBuffer.size);
memset(_mixingBuffer.buf, 0, _mixingBuffer.size);
}
AudioMixerController::~AudioMixerController() {
destroy();
if (_mixer != nullptr) {
delete _mixer;
_mixer = nullptr;
}
free(_mixingBuffer.buf);
}
bool AudioMixerController::init() {
_mixer = ccnew AudioMixer(_bufferSizeInFrames, _sampleRate);
return _mixer != nullptr;
}
bool AudioMixerController::addTrack(Track *track) {
ALOG_ASSERT(track != nullptr, "Shouldn't pass nullptr to addTrack");
bool ret = false;
std::lock_guard<std::mutex> lk(_activeTracksMutex);
auto iter = std::find(_activeTracks.begin(), _activeTracks.end(), track);
if (iter == _activeTracks.end()) {
_activeTracks.push_back(track);
ret = true;
}
return ret;
}
template <typename T>
static void removeItemFromVector(ccstd::vector<T> &v, T item) {
auto iter = std::find(v.begin(), v.end(), item);
if (iter != v.end()) {
v.erase(iter);
}
}
void AudioMixerController::initTrack(Track *track, ccstd::vector<Track *> &tracksToRemove) {
if (track->isInitialized())
return;
uint32_t channelMask = audio_channel_out_mask_from_count(2);
int32_t name = _mixer->getTrackName(channelMask, AUDIO_FORMAT_PCM_16_BIT,
AUDIO_SESSION_OUTPUT_MIX);
if (name < 0) {
// If we could not get the track name, it means that there're MAX_NUM_TRACKS tracks
// So ignore the new track.
tracksToRemove.push_back(track);
} else {
_mixer->setBufferProvider(name, track);
_mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
_mixingBuffer.buf);
_mixer->setParameter(
name,
AudioMixer::TRACK,
AudioMixer::MIXER_FORMAT,
(void *)(uintptr_t)AUDIO_FORMAT_PCM_16_BIT);
_mixer->setParameter(
name,
AudioMixer::TRACK,
AudioMixer::FORMAT,
(void *)(uintptr_t)AUDIO_FORMAT_PCM_16_BIT);
_mixer->setParameter(
name,
AudioMixer::TRACK,
AudioMixer::MIXER_CHANNEL_MASK,
(void *)(uintptr_t)channelMask);
_mixer->setParameter(
name,
AudioMixer::TRACK,
AudioMixer::CHANNEL_MASK,
(void *)(uintptr_t)channelMask);
track->setName(name);
_mixer->enable(name);
std::lock_guard<std::mutex> lk(track->_volumeDirtyMutex);
gain_minifloat_packed_t volume = track->getVolumeLR();
float lVolume = float_from_gain(gain_minifloat_unpack_left(volume));
float rVolume = float_from_gain(gain_minifloat_unpack_right(volume));
_mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &lVolume);
_mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &rVolume);
track->setVolumeDirty(false);
track->setInitialized(true);
}
}
void AudioMixerController::mixOneFrame() {
_isMixingFrame = true;
_activeTracksMutex.lock();
auto mixStart = clockNow();
ccstd::vector<Track *> tracksToRemove;
tracksToRemove.reserve(_activeTracks.size());
// FOR TESTING BEGIN
// Track* track = _activeTracks[0];
//
// AudioBufferProvider::Buffer buffer;
// buffer.frameCount = _bufferSizeInFrames;
// status_t r = track->getNextBuffer(&buffer);
//// ALOG_ASSERT(buffer.frameCount == _mixing->size / 2, "buffer.frameCount:%d, _mixing->size/2:%d", buffer.frameCount, _mixing->size/2);
// if (r == NO_ERROR)
// {
// ALOGV("getNextBuffer succeed ...");
// memcpy(_mixing->buf, buffer.raw, _mixing->size);
// }
// if (buffer.raw == nullptr)
// {
// ALOGV("Play over ...");
// tracksToRemove.push_back(track);
// }
// else
// {
// track->releaseBuffer(&buffer);
// }
//
// _mixing->state = BufferState::FULL;
// _activeTracksMutex.unlock();
// FOR TESTING END
Track::State state;
// set up the tracks.
for (auto &&track : _activeTracks) {
state = track->getState();
if (state == Track::State::PLAYING) {
initTrack(track, tracksToRemove);
int name = track->getName();
ALOG_ASSERT(name >= 0);
std::lock_guard<std::mutex> lk(track->_volumeDirtyMutex);
if (track->isVolumeDirty()) {
gain_minifloat_packed_t volume = track->getVolumeLR();
float lVolume = float_from_gain(gain_minifloat_unpack_left(volume));
float rVolume = float_from_gain(gain_minifloat_unpack_right(volume));
ALOGV("Track (name: %d)'s volume is dirty, update volume to L: %f, R: %f", name, lVolume, rVolume);
_mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &lVolume);
_mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &rVolume);
track->setVolumeDirty(false);
}
} else if (state == Track::State::RESUMED) {
initTrack(track, tracksToRemove);
if (track->getPrevState() == Track::State::PAUSED) {
_mixer->enable(track->getName());
track->setState(Track::State::PLAYING);
} else {
ALOGW("Previous state (%d) isn't PAUSED, couldn't resume!", static_cast<int>(track->getPrevState()));
}
} else if (state == Track::State::PAUSED) {
initTrack(track, tracksToRemove);
if (track->getPrevState() == Track::State::PLAYING || track->getPrevState() == Track::State::RESUMED) {
_mixer->disable(track->getName());
} else {
ALOGW("Previous state (%d) isn't PLAYING, couldn't pause!", static_cast<int>(track->getPrevState()));
}
} else if (state == Track::State::STOPPED) {
if (track->isInitialized()) {
_mixer->deleteTrackName(track->getName());
} else {
ALOGV("Track (%p) hasn't been initialized yet!", track);
}
tracksToRemove.push_back(track);
}
if (track->isPlayOver()) {
if (track->isLoop()) {
track->reset();
} else {
ALOGV("Play over ...");
_mixer->deleteTrackName(track->getName());
tracksToRemove.push_back(track);
track->setState(Track::State::OVER);
}
}
}
bool hasAvailableTracks = _activeTracks.size() - tracksToRemove.size() > 0;
if (hasAvailableTracks) {
ALOGV_IF(_activeTracks.size() > 8, "More than 8 active tracks: %d", (int)_activeTracks.size());
_mixer->process(AudioBufferProvider::kInvalidPTS);
} else {
ALOGV("Doesn't have enough tracks: %d, %d", (int)_activeTracks.size(), (int)tracksToRemove.size());
}
// Remove stopped or playover tracks for active tracks container
for (auto &&track : tracksToRemove) {
removeItemFromVector(_activeTracks, track);
if (track != nullptr && track->onStateChanged != nullptr) {
track->onStateChanged(Track::State::DESTROYED);
} else {
ALOGE("track (%p) was released ...", track);
}
}
_activeTracksMutex.unlock();
auto mixEnd = clockNow();
float mixInterval = intervalInMS(mixStart, mixEnd);
ALOGV_IF(mixInterval > 1.0f, "Mix a frame waste: %fms", mixInterval);
_isMixingFrame = false;
}
void AudioMixerController::destroy() {
while (_isMixingFrame) {
usleep(10);
}
usleep(2000); // Wait for more 2ms
}
void AudioMixerController::pause() {
_isPaused = true;
}
void AudioMixerController::resume() {
_isPaused = false;
}
bool AudioMixerController::hasPlayingTacks() {
std::lock_guard<std::mutex> lk(_activeTracksMutex);
if (_activeTracks.empty())
return false;
for (auto &&track : _activeTracks) {
Track::State state = track->getState();
if (state == Track::State::IDLE || state == Track::State::PLAYING || state == Track::State::RESUMED) {
return true;
}
}
return false;
}
} // namespace cc

View File

@@ -0,0 +1,84 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
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 <atomic>
#include <condition_variable>
#include <mutex>
#include <thread>
#include "audio/android/utils/Errors.h"
#include "base/std/container/vector.h"
namespace cc {
class Track;
class AudioMixer;
class AudioMixerController {
public:
struct OutputBuffer {
void *buf;
size_t size;
};
AudioMixerController(int bufferSizeInFrames, int sampleRate, int channelCount);
~AudioMixerController();
bool init();
bool addTrack(Track *track);
bool hasPlayingTacks();
void pause();
void resume();
inline bool isPaused() const { return _isPaused; };
void mixOneFrame();
inline OutputBuffer *current() { return &_mixingBuffer; }
private:
void destroy();
void initTrack(Track *track, ccstd::vector<Track *> &tracksToRemove);
private:
int _bufferSizeInFrames;
int _sampleRate;
int _channelCount;
AudioMixer *_mixer;
std::mutex _activeTracksMutex;
ccstd::vector<Track *> _activeTracks;
OutputBuffer _mixingBuffer;
std::atomic_bool _isPaused;
std::atomic_bool _isMixingFrame;
};
} // namespace cc

View File

@@ -0,0 +1,445 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "audio/android/cutils/log.h"
namespace cc {
/* Behavior of is_same<>::value is true if the types are identical,
* false otherwise. Identical to the STL std::is_same.
*/
template <typename T, typename U>
struct is_same {
static const bool value = false;
};
template <typename T>
struct is_same<T, T> // partial specialization
{
static const bool value = true;
};
/* MixMul is a multiplication operator to scale an audio input signal
* by a volume gain, with the formula:
*
* O(utput) = I(nput) * V(olume)
*
* The output, input, and volume may have different types.
* There are 27 variants, of which 14 are actually defined in an
* explicitly templated class.
*
* The following type variables and the underlying meaning:
*
* Output type TO: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1]
* Input signal type TI: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1]
* Volume type TV: int32_t (U4.28) or int16_t (U4.12) or float [-1,1]
*
* For high precision audio, only the <TO, TI, TV> = <float, float, float>
* needs to be accelerated. This is perhaps the easiest form to do quickly as well.
*
* A generic version is NOT defined to catch any mistake of using it.
*/
template <typename TO, typename TI, typename TV>
TO MixMul(TI value, TV volume);
template <>
inline int32_t MixMul<int32_t, int16_t, int16_t>(int16_t value, int16_t volume) {
return value * volume;
}
template <>
inline int32_t MixMul<int32_t, int32_t, int16_t>(int32_t value, int16_t volume) {
return (value >> 12) * volume;
}
template <>
inline int32_t MixMul<int32_t, int16_t, int32_t>(int16_t value, int32_t volume) {
return value * (volume >> 16);
}
template <>
inline int32_t MixMul<int32_t, int32_t, int32_t>(int32_t value, int32_t volume) {
return (value >> 12) * (volume >> 16);
}
template <>
inline float MixMul<float, float, int16_t>(float value, int16_t volume) {
static const float norm = 1. / (1 << 12);
return value * volume * norm;
}
template <>
inline float MixMul<float, float, int32_t>(float value, int32_t volume) {
static const float norm = 1. / (1 << 28);
return value * volume * norm;
}
template <>
inline int16_t MixMul<int16_t, float, int16_t>(float value, int16_t volume) {
return clamp16_from_float(MixMul<float, float, int16_t>(value, volume));
}
template <>
inline int16_t MixMul<int16_t, float, int32_t>(float value, int32_t volume) {
return clamp16_from_float(MixMul<float, float, int32_t>(value, volume));
}
template <>
inline float MixMul<float, int16_t, int16_t>(int16_t value, int16_t volume) {
static const float norm = 1. / (1 << (15 + 12));
return static_cast<float>(value) * static_cast<float>(volume) * norm;
}
template <>
inline float MixMul<float, int16_t, int32_t>(int16_t value, int32_t volume) {
static const float norm = 1. / (1ULL << (15 + 28));
return static_cast<float>(value) * static_cast<float>(volume) * norm;
}
template <>
inline int16_t MixMul<int16_t, int16_t, int16_t>(int16_t value, int16_t volume) {
return clamp16(MixMul<int32_t, int16_t, int16_t>(value, volume) >> 12);
}
template <>
inline int16_t MixMul<int16_t, int32_t, int16_t>(int32_t value, int16_t volume) {
return clamp16(MixMul<int32_t, int32_t, int16_t>(value, volume) >> 12);
}
template <>
inline int16_t MixMul<int16_t, int16_t, int32_t>(int16_t value, int32_t volume) {
return clamp16(MixMul<int32_t, int16_t, int32_t>(value, volume) >> 12);
}
template <>
inline int16_t MixMul<int16_t, int32_t, int32_t>(int32_t value, int32_t volume) {
return clamp16(MixMul<int32_t, int32_t, int32_t>(value, volume) >> 12);
}
/* Required for floating point volume. Some are needed for compilation but
* are not needed in execution and should be removed from the final build by
* an optimizing compiler.
*/
template <>
inline float MixMul<float, float, float>(float value, float volume) {
return value * volume;
}
template <>
inline float MixMul<float, int16_t, float>(int16_t value, float volume) {
static const float float_from_q_15 = 1. / (1 << 15);
return value * volume * float_from_q_15;
}
template <>
inline int32_t MixMul<int32_t, int32_t, float>(int32_t value, float volume) {
LOG_ALWAYS_FATAL("MixMul<int32_t, int32_t, float> Runtime Should not be here");
return value * volume;
}
template <>
inline int32_t MixMul<int32_t, int16_t, float>(int16_t value, float volume) {
LOG_ALWAYS_FATAL("MixMul<int32_t, int16_t, float> Runtime Should not be here");
static const float u4_12_from_float = (1 << 12);
return value * volume * u4_12_from_float;
}
template <>
inline int16_t MixMul<int16_t, int16_t, float>(int16_t value, float volume) {
LOG_ALWAYS_FATAL("MixMul<int16_t, int16_t, float> Runtime Should not be here");
return clamp16_from_float(MixMul<float, int16_t, float>(value, volume));
}
template <>
inline int16_t MixMul<int16_t, float, float>(float value, float volume) {
return clamp16_from_float(value * volume);
}
/*
* MixAccum is used to add into an accumulator register of a possibly different
* type. The TO and TI types are the same as MixMul.
*/
template <typename TO, typename TI>
inline void MixAccum(TO *auxaccum, TI value) {
if (!is_same<TO, TI>::value) {
LOG_ALWAYS_FATAL("MixAccum type not properly specialized: %zu %zu\n",
sizeof(TO), sizeof(TI));
}
*auxaccum += value;
}
template <>
inline void MixAccum<float, int16_t>(float *auxaccum, int16_t value) {
static const float norm = 1. / (1 << 15);
*auxaccum += norm * value;
}
template <>
inline void MixAccum<float, int32_t>(float *auxaccum, int32_t value) {
static const float norm = 1. / (1 << 27);
*auxaccum += norm * value;
}
template <>
inline void MixAccum<int32_t, int16_t>(int32_t *auxaccum, int16_t value) {
*auxaccum += value << 12;
}
template <>
inline void MixAccum<int32_t, float>(int32_t *auxaccum, float value) {
*auxaccum += clampq4_27_from_float(value);
}
/* MixMulAux is just like MixMul except it combines with
* an accumulator operation MixAccum.
*/
template <typename TO, typename TI, typename TV, typename TA>
inline TO MixMulAux(TI value, TV volume, TA *auxaccum) {
MixAccum<TA, TI>(auxaccum, value);
return MixMul<TO, TI, TV>(value, volume);
}
/* MIXTYPE is used to determine how the samples in the input frame
* are mixed with volume gain into the output frame.
* See the volumeRampMulti functions below for more details.
*/
enum {
MIXTYPE_MULTI,
MIXTYPE_MONOEXPAND,
MIXTYPE_MULTI_SAVEONLY,
MIXTYPE_MULTI_MONOVOL,
MIXTYPE_MULTI_SAVEONLY_MONOVOL,
};
/*
* The volumeRampMulti and volumeRamp functions take a MIXTYPE
* which indicates the per-frame mixing and accumulation strategy.
*
* MIXTYPE_MULTI:
* NCHAN represents number of input and output channels.
* TO: int32_t (Q4.27) or float
* TI: int32_t (Q4.27) or int16_t (Q0.15) or float
* TV: int32_t (U4.28) or int16_t (U4.12) or float
* vol: represents a volume array.
*
* This accumulates into the out pointer.
*
* MIXTYPE_MONOEXPAND:
* Single input channel. NCHAN represents number of output channels.
* TO: int32_t (Q4.27) or float
* TI: int32_t (Q4.27) or int16_t (Q0.15) or float
* TV: int32_t (U4.28) or int16_t (U4.12) or float
* Input channel count is 1.
* vol: represents volume array.
*
* This accumulates into the out pointer.
*
* MIXTYPE_MULTI_SAVEONLY:
* NCHAN represents number of input and output channels.
* TO: int16_t (Q.15) or float
* TI: int32_t (Q4.27) or int16_t (Q0.15) or float
* TV: int32_t (U4.28) or int16_t (U4.12) or float
* vol: represents a volume array.
*
* MIXTYPE_MULTI_SAVEONLY does not accumulate into the out pointer.
*
* MIXTYPE_MULTI_MONOVOL:
* Same as MIXTYPE_MULTI, but uses only volume[0].
*
* MIXTYPE_MULTI_SAVEONLY_MONOVOL:
* Same as MIXTYPE_MULTI_SAVEONLY, but uses only volume[0].
*
*/
template <int MIXTYPE, int NCHAN,
typename TO, typename TI, typename TV, typename TA, typename TAV>
inline void volumeRampMulti(TO *out, size_t frameCount,
const TI *in, TA *aux, TV *vol, const TV *volinc, TAV *vola, TAV volainc) {
#ifdef ALOGVV
ALOGVV("volumeRampMulti, MIXTYPE:%d\n", MIXTYPE);
#endif
if (aux != NULL) {
do {
TA auxaccum = 0;
switch (MIXTYPE) {
case MIXTYPE_MULTI:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
vol[i] += volinc[i];
}
break;
case MIXTYPE_MONOEXPAND:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
vol[i] += volinc[i];
}
in++;
break;
case MIXTYPE_MULTI_SAVEONLY:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
vol[i] += volinc[i];
}
break;
case MIXTYPE_MULTI_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
}
vol[0] += volinc[0];
break;
case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
}
vol[0] += volinc[0];
break;
default:
LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
break;
}
auxaccum /= NCHAN;
*aux++ += MixMul<TA, TA, TAV>(auxaccum, *vola);
vola[0] += volainc;
} while (--frameCount);
} else {
do {
switch (MIXTYPE) {
case MIXTYPE_MULTI:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
vol[i] += volinc[i];
}
break;
case MIXTYPE_MONOEXPAND:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMul<TO, TI, TV>(*in, vol[i]);
vol[i] += volinc[i];
}
in++;
break;
case MIXTYPE_MULTI_SAVEONLY:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
vol[i] += volinc[i];
}
break;
case MIXTYPE_MULTI_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMul<TO, TI, TV>(*in++, vol[0]);
}
vol[0] += volinc[0];
break;
case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMul<TO, TI, TV>(*in++, vol[0]);
}
vol[0] += volinc[0];
break;
default:
LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
break;
}
} while (--frameCount);
}
}
template <int MIXTYPE, int NCHAN,
typename TO, typename TI, typename TV, typename TA, typename TAV>
inline void volumeMulti(TO *out, size_t frameCount,
const TI *in, TA *aux, const TV *vol, TAV vola) {
#ifdef ALOGVV
ALOGVV("volumeMulti MIXTYPE:%d\n", MIXTYPE);
#endif
if (aux != NULL) {
do {
TA auxaccum = 0;
switch (MIXTYPE) {
case MIXTYPE_MULTI:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
}
break;
case MIXTYPE_MONOEXPAND:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
}
in++;
break;
case MIXTYPE_MULTI_SAVEONLY:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
}
break;
case MIXTYPE_MULTI_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
}
break;
case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
}
break;
default:
LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
break;
}
auxaccum /= NCHAN;
*aux++ += MixMul<TA, TA, TAV>(auxaccum, vola);
} while (--frameCount);
} else {
do {
switch (MIXTYPE) {
case MIXTYPE_MULTI:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
}
break;
case MIXTYPE_MONOEXPAND:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMul<TO, TI, TV>(*in, vol[i]);
}
in++;
break;
case MIXTYPE_MULTI_SAVEONLY:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
}
break;
case MIXTYPE_MULTI_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMul<TO, TI, TV>(*in++, vol[0]);
}
break;
case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMul<TO, TI, TV>(*in++, vol[0]);
}
break;
default:
LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
break;
}
} while (--frameCount);
}
}
} // namespace cc

View File

@@ -0,0 +1,520 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
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 <memory>
#include "audio/android/PcmData.h"
#include "audio/include/AudioDef.h"
#include "base/Log.h"
#define LOG_TAG "AudioPlayerProvider"
#include <algorithm> // for std::find_if
#include <cstdlib>
#include <utility>
#include "audio/android/AudioDecoder.h"
#include "audio/android/AudioDecoderProvider.h"
#include "audio/android/AudioMixerController.h"
#include "audio/android/AudioPlayerProvider.h"
#include "audio/android/ICallerThreadUtils.h"
#include "audio/android/PcmAudioPlayer.h"
#include "audio/android/PcmAudioService.h"
#include "audio/android/UrlAudioPlayer.h"
#include "audio/android/utils/Utils.h"
#include "base/ThreadPool.h"
#include "base/memory/Memory.h"
#if CC_PLATFORM == CC_PLATFORM_ANDROID
#include <sys/system_properties.h>
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
#include "cocos/platform/FileUtils.h"
#include "cocos/platform/openharmony/FileUtils-OpenHarmony.h"
#endif
#include <algorithm> // for std::find_if
#include <cstdlib>
#include <utility>
namespace cc {
static int getSystemAPILevel() {
static int sSystemApiLevel = -1;
#if CC_PLATFORM == CC_PLATFORM_ANDROID
if (sSystemApiLevel > 0) {
return sSystemApiLevel;
}
int apiLevel = getSDKVersion();
if (apiLevel > 0) {
ALOGD("Android API level: %d", apiLevel);
} else {
ALOGE("Fail to get Android API level!");
}
sSystemApiLevel = apiLevel;
return apiLevel;
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
// TODO(qgh): On the openharmony platform, pcm streaming must be used
return std::numeric_limits<int>::max();
#endif
}
struct AudioFileIndicator {
ccstd::string extension;
int smallSizeIndicator;
};
static AudioFileIndicator gAudioFileIndicator[] = {
{"default", 128000}, // If we could not handle the audio format, return default value, the position should be first.
{".wav", 1024000},
{".ogg", 128000},
{".mp3", 160000}};
AudioPlayerProvider::AudioPlayerProvider(SLEngineItf engineItf, SLObjectItf outputMixObject,
int deviceSampleRate, int bufferSizeInFrames,
const FdGetterCallback &fdGetterCallback, //NOLINT(modernize-pass-by-value)
ICallerThreadUtils *callerThreadUtils)
: _engineItf(engineItf), _outputMixObject(outputMixObject), _deviceSampleRate(deviceSampleRate), _bufferSizeInFrames(bufferSizeInFrames), _fdGetterCallback(fdGetterCallback), _callerThreadUtils(callerThreadUtils), _pcmAudioService(nullptr), _mixController(nullptr), _threadPool(LegacyThreadPool::newCachedThreadPool(1, 8, 5, 2, 2)) {
ALOGI("deviceSampleRate: %d, bufferSizeInFrames: %d", _deviceSampleRate, _bufferSizeInFrames);
if (getSystemAPILevel() >= 17) {
_mixController = ccnew AudioMixerController(_bufferSizeInFrames, _deviceSampleRate, 2);
_mixController->init();
_pcmAudioService = ccnew PcmAudioService(engineItf, outputMixObject);
_pcmAudioService->init(_mixController, 2, deviceSampleRate, bufferSizeInFrames * 2);
}
ALOG_ASSERT(callerThreadUtils != nullptr, "Caller thread utils parameter should not be nullptr!");
}
AudioPlayerProvider::~AudioPlayerProvider() {
ALOGV("~AudioPlayerProvider()");
UrlAudioPlayer::stopAll();
SL_SAFE_DELETE(_pcmAudioService);
SL_SAFE_DELETE(_mixController);
SL_SAFE_DELETE(_threadPool);
}
IAudioPlayer *AudioPlayerProvider::getAudioPlayer(const ccstd::string &audioFilePath) {
// Pcm data decoding by OpenSLES API only supports in API level 17 and later.
if (getSystemAPILevel() < 17) {
AudioFileInfo info = getFileInfo(audioFilePath);
if (info.isValid()) {
return createUrlAudioPlayer(info);
}
return nullptr;
}
IAudioPlayer *player = nullptr;
_pcmCacheMutex.lock();
auto iter = _pcmCache.find(audioFilePath);
if (iter != _pcmCache.end()) { // Found pcm cache means it was used to be a PcmAudioService
PcmData pcmData = iter->second;
_pcmCacheMutex.unlock();
player = obtainPcmAudioPlayer(audioFilePath, pcmData);
ALOGV_IF(player == nullptr, "%s, %d: player is nullptr, path: %s", __FUNCTION__, __LINE__, audioFilePath.c_str());
} else {
_pcmCacheMutex.unlock();
// Check audio file size to determine to use a PcmAudioService or UrlAudioPlayer,
// generally PcmAudioService is used for playing short audio like game effects while
// playing background music uses UrlAudioPlayer
AudioFileInfo info = getFileInfo(audioFilePath);
if (info.isValid()) {
if (isSmallFile(info)) {
// Put an empty lambda to preloadEffect since we only want the future object to get PcmData
auto pcmData = std::make_shared<PcmData>();
auto isSucceed = std::make_shared<bool>(false);
auto isReturnFromCache = std::make_shared<bool>(false);
auto isPreloadFinished = std::make_shared<bool>(false);
std::thread::id threadId = std::this_thread::get_id();
void *infoPtr = &info;
ccstd::string url = info.url;
preloadEffect(
info, [infoPtr, url, threadId, pcmData, isSucceed, isReturnFromCache, isPreloadFinished](bool succeed, PcmData data) {
// If the callback is in the same thread as caller's, it means that we found it
// in the cache
*isReturnFromCache = std::this_thread::get_id() == threadId;
*pcmData = std::move(data);
*isSucceed = succeed;
*isPreloadFinished = true;
ALOGV("FileInfo (%p), Set isSucceed flag: %d, path: %s", infoPtr, succeed, url.c_str());
},
true);
if (!*isReturnFromCache && !*isPreloadFinished) {
std::unique_lock<std::mutex> lck(_preloadWaitMutex);
// Wait for 2 seconds for the decoding in sub thread finishes.
ALOGV("FileInfo (%p), Waiting preload (%s) to finish ...", &info, audioFilePath.c_str());
_preloadWaitCond.wait_for(lck, std::chrono::seconds(2));
ALOGV("FileInfo (%p), Waitup preload (%s) ...", &info, audioFilePath.c_str());
}
if (*isSucceed) {
if (pcmData->isValid()) {
player = obtainPcmAudioPlayer(info.url, *pcmData);
ALOGV_IF(player == nullptr, "%s, %d: player is nullptr, path: %s", __FUNCTION__, __LINE__, audioFilePath.c_str());
} else {
ALOGE("pcm data is invalid, path: %s", audioFilePath.c_str());
}
} else {
ALOGE("FileInfo (%p), preloadEffect (%s) failed", &info, audioFilePath.c_str());
}
} else {
player = createUrlAudioPlayer(info);
ALOGV_IF(player == nullptr, "%s, %d: player is nullptr, path: %s", __FUNCTION__, __LINE__, audioFilePath.c_str());
}
} else {
ALOGE("File info is invalid, path: %s", audioFilePath.c_str());
}
}
ALOGV_IF(player == nullptr, "%s, %d return nullptr", __FUNCTION__, __LINE__);
return player;
}
void AudioPlayerProvider::preloadEffect(const ccstd::string &audioFilePath, const PreloadCallback &callback) {
// Pcm data decoding by OpenSLES API only supports in API level 17 and later.
if (getSystemAPILevel() < 17) {
PcmData data;
callback(true, data);
return;
}
_pcmCacheMutex.lock();
auto &&iter = _pcmCache.find(audioFilePath);
if (iter != _pcmCache.end()) {
ALOGV("preload return from cache: (%s)", audioFilePath.c_str());
_pcmCacheMutex.unlock();
callback(true, iter->second);
return;
}
_pcmCacheMutex.unlock();
auto info = getFileInfo(audioFilePath);
preloadEffect(
info, [this, callback, audioFilePath](bool succeed, const PcmData &data) {
_callerThreadUtils->performFunctionInCallerThread([this, succeed, data, callback]() {
callback(succeed, data);
});
},
false);
}
// Used internally
void AudioPlayerProvider::preloadEffect(const AudioFileInfo &info, const PreloadCallback &callback, bool isPreloadInPlay2d) {
PcmData pcmData;
if (!info.isValid()) {
callback(false, pcmData);
return;
}
if (isSmallFile(info)) {
ccstd::string audioFilePath = info.url;
// 1. First time check, if it wasn't in the cache, goto 2 step
_pcmCacheMutex.lock();
auto &&iter = _pcmCache.find(audioFilePath);
if (iter != _pcmCache.end()) {
ALOGV("1. Return pcm data from cache, url: %s", info.url.c_str());
_pcmCacheMutex.unlock();
callback(true, iter->second);
return;
}
_pcmCacheMutex.unlock();
{
// 2. Check whether the audio file is being preloaded, if it has been removed from map just now,
// goto step 3
std::lock_guard<std::mutex> lck(_preloadCallbackMutex);
auto &&preloadIter = _preloadCallbackMap.find(audioFilePath);
if (preloadIter != _preloadCallbackMap.end()) {
ALOGV("audio (%s) is being preloaded, add to callback vector!", audioFilePath.c_str());
PreloadCallbackParam param;
param.callback = callback;
param.isPreloadInPlay2d = isPreloadInPlay2d;
preloadIter->second.push_back(std::move(param));
return;
}
// 3. Check it in cache again. If it has been removed from map just now, the file is in
// the cache absolutely.
_pcmCacheMutex.lock();
auto &&iter = _pcmCache.find(audioFilePath);
if (iter != _pcmCache.end()) {
ALOGV("2. Return pcm data from cache, url: %s", info.url.c_str());
_pcmCacheMutex.unlock();
callback(true, iter->second);
return;
}
_pcmCacheMutex.unlock();
PreloadCallbackParam param;
param.callback = callback;
param.isPreloadInPlay2d = isPreloadInPlay2d;
ccstd::vector<PreloadCallbackParam> callbacks;
callbacks.push_back(std::move(param));
_preloadCallbackMap.insert(std::make_pair(audioFilePath, std::move(callbacks)));
}
_threadPool->pushTask([this, audioFilePath](int /*tid*/) {
ALOGV("AudioPlayerProvider::preloadEffect: (%s)", audioFilePath.c_str());
PcmData d;
AudioDecoder *decoder = AudioDecoderProvider::createAudioDecoder(_engineItf, audioFilePath, _bufferSizeInFrames, _deviceSampleRate, _fdGetterCallback);
bool ret = decoder != nullptr && decoder->start();
if (ret) {
d = decoder->getResult();
std::lock_guard<std::mutex> lck(_pcmCacheMutex);
_pcmCache.insert(std::make_pair(audioFilePath, d));
} else {
ALOGE("decode (%s) failed!", audioFilePath.c_str());
}
ALOGV("decode %s", (ret ? "succeed" : "failed"));
std::lock_guard<std::mutex> lck(_preloadCallbackMutex);
auto &&preloadIter = _preloadCallbackMap.find(audioFilePath);
if (preloadIter != _preloadCallbackMap.end()) {
auto &&params = preloadIter->second;
ALOGV("preload (%s) callback count: %d", audioFilePath.c_str(), (int)params.size());
PcmData result = decoder->getResult();
for (auto &&param : params) {
param.callback(ret, result);
if (param.isPreloadInPlay2d) {
_preloadWaitCond.notify_one();
}
}
_preloadCallbackMap.erase(preloadIter);
}
AudioDecoderProvider::destroyAudioDecoder(&decoder);
});
} else {
ALOGV("File (%s) is too large, ignore preload!", info.url.c_str());
callback(true, pcmData);
}
}
AudioPlayerProvider::AudioFileInfo AudioPlayerProvider::getFileInfo(
const ccstd::string &audioFilePath) {
AudioFileInfo info;
long fileSize = 0; //NOLINT(google-runtime-int)
off_t start = 0;
off_t length = 0;
int assetFd = -1;
#if CC_PLATFORM == CC_PLATFORM_ANDROID
if (audioFilePath[0] != '/') {
ccstd::string relativePath;
size_t position = audioFilePath.find("@assets/");
if (0 == position) {
// "@assets/" is at the beginning of the path and we don't want it
relativePath = audioFilePath.substr(strlen("@assets/"));
} else {
relativePath = audioFilePath;
}
assetFd = _fdGetterCallback(relativePath, &start, &length);
if (assetFd <= 0) {
ALOGE("Failed to open file descriptor for '%s'", audioFilePath.c_str());
return info;
}
fileSize = length;
} else {
FILE *fp = fopen(audioFilePath.c_str(), "rb");
if (fp != nullptr) {
fseek(fp, 0, SEEK_END);
fileSize = ftell(fp);
fclose(fp);
} else {
return info;
}
}
info.url = audioFilePath;
info.assetFd = std::make_shared<AssetFd>(assetFd);
info.start = start;
info.length = fileSize;
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
FileUtilsOpenHarmony* fileUtils = dynamic_cast<FileUtilsOpenHarmony*>(FileUtils::getInstance());
if(!fileUtils) {
return info;
}
RawFileDescriptor descriptor;
fileUtils->getRawFileDescriptor(audioFilePath, descriptor);
info.url = audioFilePath;
info.assetFd = std::make_shared<AssetFd>(descriptor.fd);
info.start = descriptor.start;
info.length = descriptor.length;
#endif
ALOGV("(%s) file size: %ld", audioFilePath.c_str(), fileSize);
return info;
}
bool AudioPlayerProvider::isSmallFile(const AudioFileInfo &info) { //NOLINT(readability-convert-member-functions-to-static)
#if CC_PLATFORM == CC_PLATFORM_OPENHARMONY
// TODO(qgh): OpenHarmony system does not support this function yet
return true;
#endif
//REFINE: If file size is smaller than 100k, we think it's a small file. This value should be set by developers.
auto &audioFileInfo = const_cast<AudioFileInfo &>(info);
size_t judgeCount = sizeof(gAudioFileIndicator) / sizeof(gAudioFileIndicator[0]);
size_t pos = audioFileInfo.url.rfind('.');
ccstd::string extension;
if (pos != ccstd::string::npos) {
extension = audioFileInfo.url.substr(pos);
}
auto *iter = std::find_if(std::begin(gAudioFileIndicator), std::end(gAudioFileIndicator),
[&extension](const AudioFileIndicator &judge) -> bool {
return judge.extension == extension;
});
if (iter != std::end(gAudioFileIndicator)) {
// ALOGV("isSmallFile: found: %s: ", iter->extension.c_str());
return info.length < iter->smallSizeIndicator;
}
// ALOGV("isSmallFile: not found return default value");
return info.length < gAudioFileIndicator[0].smallSizeIndicator;
}
float AudioPlayerProvider::getDurationFromFile(const ccstd::string &filePath) {
std::lock_guard<std::mutex> lck(_pcmCacheMutex);
auto iter = _pcmCache.find(filePath);
if (iter != _pcmCache.end()) {
return iter->second.duration;
}
return 0;
}
void AudioPlayerProvider::clearPcmCache(const ccstd::string &audioFilePath) {
std::lock_guard<std::mutex> lck(_pcmCacheMutex);
auto iter = _pcmCache.find(audioFilePath);
if (iter != _pcmCache.end()) {
ALOGV("clear pcm cache: (%s)", audioFilePath.c_str());
_pcmCache.erase(iter);
} else {
ALOGW("Couldn't find the pcm cache: (%s)", audioFilePath.c_str());
}
}
void AudioPlayerProvider::clearAllPcmCaches() {
std::lock_guard<std::mutex> lck(_pcmCacheMutex);
_pcmCache.clear();
}
PcmAudioPlayer *AudioPlayerProvider::obtainPcmAudioPlayer(const ccstd::string &url,
const PcmData &pcmData) {
PcmAudioPlayer *pcmPlayer = nullptr;
if (pcmData.isValid()) {
pcmPlayer = ccnew PcmAudioPlayer(_mixController, _callerThreadUtils);
if (pcmPlayer != nullptr) {
pcmPlayer->prepare(url, pcmData);
}
} else {
ALOGE("obtainPcmAudioPlayer failed, pcmData isn't valid!");
}
return pcmPlayer;
}
UrlAudioPlayer *AudioPlayerProvider::createUrlAudioPlayer(
const AudioPlayerProvider::AudioFileInfo &info) {
if (info.url.empty()) {
ALOGE("createUrlAudioPlayer failed, url is empty!");
return nullptr;
}
#if CC_PLATFORM == CC_PLATFORM_ANDROID
SLuint32 locatorType = info.assetFd->getFd() > 0 ? SL_DATALOCATOR_ANDROIDFD : SL_DATALOCATOR_URI;
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
SLuint32 locatorType = SL_DATALOCATOR_URI;
#endif
auto *urlPlayer = new (std::nothrow) UrlAudioPlayer(_engineItf, _outputMixObject, _callerThreadUtils);
bool ret = urlPlayer->prepare(info.url, locatorType, info.assetFd, info.start, info.length);
if (!ret) {
SL_SAFE_DELETE(urlPlayer);
}
return urlPlayer;
}
void AudioPlayerProvider::pause() {
if (_mixController != nullptr) {
_mixController->pause();
}
if (_pcmAudioService != nullptr) {
_pcmAudioService->pause();
}
}
void AudioPlayerProvider::resume() {
if (_mixController != nullptr) {
_mixController->resume();
}
if (_pcmAudioService != nullptr) {
_pcmAudioService->resume();
}
}
void AudioPlayerProvider::registerPcmData(const ccstd::string &audioFilePath, PcmData &data) {
std::lock_guard<std::mutex> lck(_pcmCacheMutex);
if (_pcmCache.find(audioFilePath) != _pcmCache.end()) {
CC_LOG_DEBUG("file %s pcm data is already cached.", audioFilePath.c_str());
return;
}
_pcmCache.emplace(audioFilePath, data);
}
bool AudioPlayerProvider::getPcmHeader(const ccstd::string &audioFilePath, PCMHeader &header) {
std::lock_guard<std::mutex> lck(_pcmCacheMutex);
auto &&iter = _pcmCache.find(audioFilePath);
if (iter != _pcmCache.end()) {
ALOGV("get pcm header from cache, url: %s", audioFilePath.c_str());
// On Android, all pcm buffer is resampled to sign16.
header.bytesPerFrame = iter->second.bitsPerSample / 8;
header.channelCount = iter->second.numChannels;
header.dataFormat = AudioDataFormat::SIGNED_16;
header.sampleRate = iter->second.sampleRate;
header.totalFrames = iter->second.numFrames;
return true;
}
return false;
}
bool AudioPlayerProvider::getPcmData(const ccstd::string &audioFilePath, PcmData &data) {
std::lock_guard<std::mutex> lck(_pcmCacheMutex);
auto &&iter = _pcmCache.find(audioFilePath);
if (iter != _pcmCache.end()) {
ALOGV("get pcm buffer from cache, url: %s", audioFilePath.c_str());
// On Android, all pcm buffer is resampled to sign16.
data = iter->second;
return true;
}
return false;
}
} // namespace cc

View File

@@ -0,0 +1,122 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
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 <condition_variable>
#include <memory>
#include "audio/android/IAudioPlayer.h"
#include "audio/android/OpenSLHelper.h"
#include "audio/android/PcmData.h"
#include "audio/include/AudioDef.h"
#include "base/std/container/unordered_map.h"
namespace cc {
// Manage PcmAudioPlayer& UrlAudioPlayer
class PcmAudioPlayer;
class PcmAudioService;
class UrlAudioPlayer;
class AudioMixerController;
class ICallerThreadUtils;
class AssetFd;
class LegacyThreadPool;
class AudioPlayerProvider {
public:
AudioPlayerProvider(SLEngineItf engineItf, SLObjectItf outputMixObject, int deviceSampleRate,
int bufferSizeInFrames, const FdGetterCallback &fdGetterCallback,
ICallerThreadUtils *callerThreadUtils);
virtual ~AudioPlayerProvider();
bool isFileCached(const ccstd::string &audioFilePath);
IAudioPlayer *getAudioPlayer(const ccstd::string &audioFilePath);
bool getPcmHeader(const ccstd::string &audioFilePath, PCMHeader &header);
bool getPcmData(const ccstd::string &audioFilePath, PcmData &data);
using PreloadCallback = std::function<void(bool, PcmData)>;
void preloadEffect(const ccstd::string &audioFilePath, const PreloadCallback &callback);
void registerPcmData(const ccstd::string &audioFilePath, PcmData &data);
float getDurationFromFile(const ccstd::string &filePath);
void clearPcmCache(const ccstd::string &audioFilePath);
void clearAllPcmCaches();
void pause();
void resume();
private:
struct AudioFileInfo {
ccstd::string url;
std::shared_ptr<AssetFd> assetFd;
off_t start{};
off_t length;
AudioFileInfo()
: assetFd(nullptr) {}
inline bool isValid() const {
return !url.empty() && length > 0;
}
};
PcmAudioPlayer *obtainPcmAudioPlayer(const ccstd::string &url, const PcmData &pcmData);
UrlAudioPlayer *createUrlAudioPlayer(const AudioFileInfo &info);
void preloadEffect(const AudioFileInfo &info, const PreloadCallback &callback, bool isPreloadInPlay2d);
AudioFileInfo getFileInfo(const ccstd::string &audioFilePath);
bool isSmallFile(const AudioFileInfo &info);
SLEngineItf _engineItf;
SLObjectItf _outputMixObject;
int _deviceSampleRate;
int _bufferSizeInFrames;
FdGetterCallback _fdGetterCallback;
ICallerThreadUtils *_callerThreadUtils;
ccstd::unordered_map<ccstd::string, PcmData> _pcmCache;
std::mutex _pcmCacheMutex;
struct PreloadCallbackParam {
PreloadCallback callback;
bool isPreloadInPlay2d;
};
ccstd::unordered_map<ccstd::string, ccstd::vector<PreloadCallbackParam>> _preloadCallbackMap;
std::mutex _preloadCallbackMutex;
std::mutex _preloadWaitMutex;
std::condition_variable _preloadWaitCond;
PcmAudioService *_pcmAudioService;
AudioMixerController *_mixController;
LegacyThreadPool *_threadPool;
};
} // namespace cc

View File

@@ -0,0 +1,792 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "AudioResampler"
//#define LOG_NDEBUG 0
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <new>
#include "audio/android/cutils/log.h"
#include "audio/android/utils/Utils.h"
//#include <cutils/properties.h>
#include "audio/android/AudioResampler.h"
#include "audio/common/utils/include/primitives.h"
//#include "audio/android/AudioResamplerSinc.h"
#include "audio/android/AudioResamplerCubic.h"
#include "base/memory/Memory.h"
//#include "AudioResamplerDyn.h"
//cjh #ifdef __arm__
// #define ASM_ARM_RESAMP1 // enable asm optimisation for ResamplerOrder1
//#endif
namespace cc {
// ----------------------------------------------------------------------------
class AudioResamplerOrder1 : public AudioResampler {
public:
AudioResamplerOrder1(int inChannelCount, int32_t sampleRate) : AudioResampler(inChannelCount, sampleRate, LOW_QUALITY), mX0L(0), mX0R(0) {
}
virtual size_t resample(int32_t *out, size_t outFrameCount,
AudioBufferProvider *provider);
private:
// number of bits used in interpolation multiply - 15 bits avoids overflow
static const int kNumInterpBits = 15;
// bits to shift the phase fraction down to avoid overflow
static const int kPreInterpShift = kNumPhaseBits - kNumInterpBits;
void init() {}
size_t resampleMono16(int32_t *out, size_t outFrameCount,
AudioBufferProvider *provider);
size_t resampleStereo16(int32_t *out, size_t outFrameCount,
AudioBufferProvider *provider);
#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1
void AsmMono16Loop(int16_t *in, int32_t *maxOutPt, int32_t maxInIdx,
size_t &outputIndex, int32_t *out, size_t &inputIndex, int32_t vl, int32_t vr,
uint32_t &phaseFraction, uint32_t phaseIncrement);
void AsmStereo16Loop(int16_t *in, int32_t *maxOutPt, int32_t maxInIdx,
size_t &outputIndex, int32_t *out, size_t &inputIndex, int32_t vl, int32_t vr,
uint32_t &phaseFraction, uint32_t phaseIncrement);
#endif // ASM_ARM_RESAMP1
static inline int32_t Interp(int32_t x0, int32_t x1, uint32_t f) {
return x0 + (((x1 - x0) * (int32_t)(f >> kPreInterpShift)) >> kNumInterpBits);
}
static inline void Advance(size_t *index, uint32_t *frac, uint32_t inc) {
*frac += inc;
*index += (size_t)(*frac >> kNumPhaseBits);
*frac &= kPhaseMask;
}
int mX0L;
int mX0R;
};
/*static*/
const double AudioResampler::kPhaseMultiplier = 1L << AudioResampler::kNumPhaseBits;
bool AudioResampler::qualityIsSupported(src_quality quality) {
switch (quality) {
case DEFAULT_QUALITY:
case LOW_QUALITY:
case MED_QUALITY:
case HIGH_QUALITY:
case VERY_HIGH_QUALITY:
return true;
default:
return false;
}
}
// ----------------------------------------------------------------------------
static pthread_once_t once_control = PTHREAD_ONCE_INIT;
static AudioResampler::src_quality defaultQuality = AudioResampler::DEFAULT_QUALITY;
void AudioResampler::init_routine() {
// int resamplerQuality = getSystemProperty("af.resampler.quality");
// if (resamplerQuality > 0) {
// defaultQuality = (src_quality) resamplerQuality;
// ALOGD("forcing AudioResampler quality to %d", defaultQuality);
// if (defaultQuality < DEFAULT_QUALITY || defaultQuality > VERY_HIGH_QUALITY) {
// defaultQuality = DEFAULT_QUALITY;
// }
// }
}
uint32_t AudioResampler::qualityMHz(src_quality quality) {
switch (quality) {
default:
case DEFAULT_QUALITY:
case LOW_QUALITY:
return 3;
case MED_QUALITY:
return 6;
case HIGH_QUALITY:
return 20;
case VERY_HIGH_QUALITY:
return 34;
// case DYN_LOW_QUALITY:
// return 4;
// case DYN_MED_QUALITY:
// return 6;
// case DYN_HIGH_QUALITY:
// return 12;
}
}
static const uint32_t maxMHz = 130; // an arbitrary number that permits 3 VHQ, should be tunable
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static uint32_t currentMHz = 0;
AudioResampler *AudioResampler::create(audio_format_t format, int inChannelCount,
int32_t sampleRate, src_quality quality) {
bool atFinalQuality;
if (quality == DEFAULT_QUALITY) {
// read the resampler default quality property the first time it is needed
int ok = pthread_once(&once_control, init_routine);
if (ok != 0) {
ALOGE("%s pthread_once failed: %d", __func__, ok);
}
quality = defaultQuality;
atFinalQuality = false;
} else {
atFinalQuality = true;
}
/* if the caller requests DEFAULT_QUALITY and af.resampler.property
* has not been set, the target resampler quality is set to DYN_MED_QUALITY,
* and allowed to "throttle" down to DYN_LOW_QUALITY if necessary
* due to estimated CPU load of having too many active resamplers
* (the code below the if).
*/
if (quality == DEFAULT_QUALITY) {
//cjh quality = DYN_MED_QUALITY;
}
// naive implementation of CPU load throttling doesn't account for whether resampler is active
pthread_mutex_lock(&mutex);
for (;;) {
uint32_t deltaMHz = qualityMHz(quality);
uint32_t newMHz = currentMHz + deltaMHz;
if ((qualityIsSupported(quality) && newMHz <= maxMHz) || atFinalQuality) {
ALOGV("resampler load %u -> %u MHz due to delta +%u MHz from quality %d",
currentMHz, newMHz, deltaMHz, quality);
currentMHz = newMHz;
break;
}
// not enough CPU available for proposed quality level, so try next lowest level
switch (quality) {
default:
case LOW_QUALITY:
atFinalQuality = true;
break;
case MED_QUALITY:
quality = LOW_QUALITY;
break;
case HIGH_QUALITY:
quality = MED_QUALITY;
break;
case VERY_HIGH_QUALITY:
quality = HIGH_QUALITY;
break;
// case DYN_LOW_QUALITY:
// atFinalQuality = true;
// break;
// case DYN_MED_QUALITY:
// quality = DYN_LOW_QUALITY;
// break;
// case DYN_HIGH_QUALITY:
// quality = DYN_MED_QUALITY;
// break;
}
}
pthread_mutex_unlock(&mutex);
AudioResampler *resampler;
switch (quality) {
default:
case LOW_QUALITY:
ALOGV("Create linear Resampler");
LOG_ALWAYS_FATAL_IF(format != AUDIO_FORMAT_PCM_16_BIT, "invalid pcm format");
resampler = ccnew AudioResamplerOrder1(inChannelCount, sampleRate);
break;
case MED_QUALITY:
ALOGV("Create cubic Resampler");
LOG_ALWAYS_FATAL_IF(format != AUDIO_FORMAT_PCM_16_BIT, "invalid pcm format");
resampler = ccnew AudioResamplerCubic(inChannelCount, sampleRate);
break;
case HIGH_QUALITY:
ALOGV("Create HIGH_QUALITY sinc Resampler");
LOG_ALWAYS_FATAL_IF(format != AUDIO_FORMAT_PCM_16_BIT, "invalid pcm format");
ALOG_ASSERT(false, "HIGH_QUALITY isn't supported");
// Cocos2d-x only uses MED_QUALITY, so we could remove Sinc relative files
// resampler = ccnew AudioResamplerSinc(inChannelCount, sampleRate);
break;
case VERY_HIGH_QUALITY:
ALOGV("Create VERY_HIGH_QUALITY sinc Resampler = %d", quality);
LOG_ALWAYS_FATAL_IF(format != AUDIO_FORMAT_PCM_16_BIT, "invalid pcm format");
// Cocos2d-x only uses MED_QUALITY, so we could remove Sinc relative files
// resampler = ccnew AudioResamplerSinc(inChannelCount, sampleRate, quality);
ALOG_ASSERT(false, "VERY_HIGH_QUALITY isn't supported");
break;
}
// initialize resampler
resampler->init();
return resampler;
}
AudioResampler::AudioResampler(int inChannelCount,
int32_t sampleRate, src_quality quality) : mChannelCount(inChannelCount),
mSampleRate(sampleRate),
mInSampleRate(sampleRate),
mInputIndex(0),
mPhaseFraction(0),
mLocalTimeFreq(0),
mPTS(AudioBufferProvider::kInvalidPTS),
mQuality(quality) {
const int maxChannels = 2; //cjh quality < DYN_LOW_QUALITY ? 2 : 8;
if (inChannelCount < 1 || inChannelCount > maxChannels) {
LOG_ALWAYS_FATAL("Unsupported sample format %d quality %d channels",
quality, inChannelCount);
}
if (sampleRate <= 0) {
LOG_ALWAYS_FATAL("Unsupported sample rate %d Hz", sampleRate);
}
// initialize common members
mVolume[0] = mVolume[1] = 0;
mBuffer.frameCount = 0;
}
AudioResampler::~AudioResampler() {
pthread_mutex_lock(&mutex);
src_quality quality = getQuality();
uint32_t deltaMHz = qualityMHz(quality);
int32_t newMHz = currentMHz - deltaMHz;
ALOGV("resampler load %u -> %d MHz due to delta -%u MHz from quality %d",
currentMHz, newMHz, deltaMHz, quality);
LOG_ALWAYS_FATAL_IF(newMHz < 0, "negative resampler load %d MHz", newMHz);
currentMHz = newMHz;
pthread_mutex_unlock(&mutex);
}
void AudioResampler::setSampleRate(int32_t inSampleRate) {
mInSampleRate = inSampleRate;
mPhaseIncrement = (uint32_t)((kPhaseMultiplier * inSampleRate) / mSampleRate);
}
void AudioResampler::setVolume(float left, float right) {
// REFINE: Implement anti-zipper filter
// convert to U4.12 for internal integer use (round down)
// integer volume values are clamped to 0 to UNITY_GAIN.
mVolume[0] = u4_12_from_float(clampFloatVol(left));
mVolume[1] = u4_12_from_float(clampFloatVol(right));
}
void AudioResampler::setLocalTimeFreq(uint64_t freq) {
mLocalTimeFreq = freq;
}
void AudioResampler::setPTS(int64_t pts) {
mPTS = pts;
}
int64_t AudioResampler::calculateOutputPTS(int outputFrameIndex) {
if (mPTS == AudioBufferProvider::kInvalidPTS) {
return AudioBufferProvider::kInvalidPTS;
} else {
return mPTS + ((outputFrameIndex * mLocalTimeFreq) / mSampleRate);
}
}
void AudioResampler::reset() {
mInputIndex = 0;
mPhaseFraction = 0;
mBuffer.frameCount = 0;
}
// ----------------------------------------------------------------------------
size_t AudioResamplerOrder1::resample(int32_t *out, size_t outFrameCount,
AudioBufferProvider *provider) {
// should never happen, but we overflow if it does
// ALOG_ASSERT(outFrameCount < 32767);
// select the appropriate resampler
switch (mChannelCount) {
case 1:
return resampleMono16(out, outFrameCount, provider);
case 2:
return resampleStereo16(out, outFrameCount, provider);
default:
LOG_ALWAYS_FATAL("invalid channel count: %d", mChannelCount);
return 0;
}
}
size_t AudioResamplerOrder1::resampleStereo16(int32_t *out, size_t outFrameCount,
AudioBufferProvider *provider) {
int32_t vl = mVolume[0];
int32_t vr = mVolume[1];
size_t inputIndex = mInputIndex;
uint32_t phaseFraction = mPhaseFraction;
uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
size_t outputSampleCount = outFrameCount * 2;
size_t inFrameCount = getInFrameCountRequired(outFrameCount);
// ALOGE("starting resample %d frames, inputIndex=%d, phaseFraction=%d, phaseIncrement=%d",
// outFrameCount, inputIndex, phaseFraction, phaseIncrement);
while (outputIndex < outputSampleCount) {
// buffer is empty, fetch a new one
while (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
provider->getNextBuffer(&mBuffer,
calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
goto resampleStereo16_exit;
}
// ALOGE("New buffer fetched: %d frames", mBuffer.frameCount);
if (mBuffer.frameCount > inputIndex) break;
inputIndex -= mBuffer.frameCount;
mX0L = mBuffer.i16[mBuffer.frameCount * 2 - 2];
mX0R = mBuffer.i16[mBuffer.frameCount * 2 - 1];
provider->releaseBuffer(&mBuffer);
// mBuffer.frameCount == 0 now so we reload a new buffer
}
int16_t *in = mBuffer.i16;
// handle boundary case
while (inputIndex == 0) {
// ALOGE("boundary case");
out[outputIndex++] += vl * Interp(mX0L, in[0], phaseFraction);
out[outputIndex++] += vr * Interp(mX0R, in[1], phaseFraction);
Advance(&inputIndex, &phaseFraction, phaseIncrement);
if (outputIndex == outputSampleCount) {
break;
}
}
// process input samples
// ALOGE("general case");
#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1
if (inputIndex + 2 < mBuffer.frameCount) {
int32_t *maxOutPt;
int32_t maxInIdx;
maxOutPt = out + (outputSampleCount - 2); // 2 because 2 frames per loop
maxInIdx = mBuffer.frameCount - 2;
AsmStereo16Loop(in, maxOutPt, maxInIdx, outputIndex, out, inputIndex, vl, vr,
phaseFraction, phaseIncrement);
}
#endif // ASM_ARM_RESAMP1
while (outputIndex < outputSampleCount && inputIndex < mBuffer.frameCount) {
out[outputIndex++] += vl * Interp(in[inputIndex * 2 - 2],
in[inputIndex * 2], phaseFraction);
out[outputIndex++] += vr * Interp(in[inputIndex * 2 - 1],
in[inputIndex * 2 + 1], phaseFraction);
Advance(&inputIndex, &phaseFraction, phaseIncrement);
}
// ALOGE("loop done - outputIndex=%d, inputIndex=%d", outputIndex, inputIndex);
// if done with buffer, save samples
if (inputIndex >= mBuffer.frameCount) {
inputIndex -= mBuffer.frameCount;
// ALOGE("buffer done, new input index %d", inputIndex);
mX0L = mBuffer.i16[mBuffer.frameCount * 2 - 2];
mX0R = mBuffer.i16[mBuffer.frameCount * 2 - 1];
provider->releaseBuffer(&mBuffer);
// verify that the releaseBuffer resets the buffer frameCount
// ALOG_ASSERT(mBuffer.frameCount == 0);
}
}
// ALOGE("output buffer full - outputIndex=%d, inputIndex=%d", outputIndex, inputIndex);
resampleStereo16_exit:
// save state
mInputIndex = inputIndex;
mPhaseFraction = phaseFraction;
return outputIndex / 2 /* channels for stereo */;
}
size_t AudioResamplerOrder1::resampleMono16(int32_t *out, size_t outFrameCount,
AudioBufferProvider *provider) {
int32_t vl = mVolume[0];
int32_t vr = mVolume[1];
size_t inputIndex = mInputIndex;
uint32_t phaseFraction = mPhaseFraction;
uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
size_t outputSampleCount = outFrameCount * 2;
size_t inFrameCount = getInFrameCountRequired(outFrameCount);
// ALOGE("starting resample %d frames, inputIndex=%d, phaseFraction=%d, phaseIncrement=%d",
// outFrameCount, inputIndex, phaseFraction, phaseIncrement);
while (outputIndex < outputSampleCount) {
// buffer is empty, fetch a new one
while (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
provider->getNextBuffer(&mBuffer,
calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
mInputIndex = inputIndex;
mPhaseFraction = phaseFraction;
goto resampleMono16_exit;
}
// ALOGE("New buffer fetched: %d frames", mBuffer.frameCount);
if (mBuffer.frameCount > inputIndex) break;
inputIndex -= mBuffer.frameCount;
mX0L = mBuffer.i16[mBuffer.frameCount - 1];
provider->releaseBuffer(&mBuffer);
// mBuffer.frameCount == 0 now so we reload a new buffer
}
int16_t *in = mBuffer.i16;
// handle boundary case
while (inputIndex == 0) {
// ALOGE("boundary case");
int32_t sample = Interp(mX0L, in[0], phaseFraction);
out[outputIndex++] += vl * sample;
out[outputIndex++] += vr * sample;
Advance(&inputIndex, &phaseFraction, phaseIncrement);
if (outputIndex == outputSampleCount) {
break;
}
}
// process input samples
// ALOGE("general case");
#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1
if (inputIndex + 2 < mBuffer.frameCount) {
int32_t *maxOutPt;
int32_t maxInIdx;
maxOutPt = out + (outputSampleCount - 2);
maxInIdx = (int32_t)mBuffer.frameCount - 2;
AsmMono16Loop(in, maxOutPt, maxInIdx, outputIndex, out, inputIndex, vl, vr,
phaseFraction, phaseIncrement);
}
#endif // ASM_ARM_RESAMP1
while (outputIndex < outputSampleCount && inputIndex < mBuffer.frameCount) {
int32_t sample = Interp(in[inputIndex - 1], in[inputIndex],
phaseFraction);
out[outputIndex++] += vl * sample;
out[outputIndex++] += vr * sample;
Advance(&inputIndex, &phaseFraction, phaseIncrement);
}
// ALOGE("loop done - outputIndex=%d, inputIndex=%d", outputIndex, inputIndex);
// if done with buffer, save samples
if (inputIndex >= mBuffer.frameCount) {
inputIndex -= mBuffer.frameCount;
// ALOGE("buffer done, new input index %d", inputIndex);
mX0L = mBuffer.i16[mBuffer.frameCount - 1];
provider->releaseBuffer(&mBuffer);
// verify that the releaseBuffer resets the buffer frameCount
// ALOG_ASSERT(mBuffer.frameCount == 0);
}
}
// ALOGE("output buffer full - outputIndex=%d, inputIndex=%d", outputIndex, inputIndex);
resampleMono16_exit:
// save state
mInputIndex = inputIndex;
mPhaseFraction = phaseFraction;
return outputIndex;
}
#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1
/*******************************************************************
*
* AsmMono16Loop
* asm optimized monotonic loop version; one loop is 2 frames
* Input:
* in : pointer on input samples
* maxOutPt : pointer on first not filled
* maxInIdx : index on first not used
* outputIndex : pointer on current output index
* out : pointer on output buffer
* inputIndex : pointer on current input index
* vl, vr : left and right gain
* phaseFraction : pointer on current phase fraction
* phaseIncrement
* Output:
* outputIndex :
* out : updated buffer
* inputIndex : index of next to use
* phaseFraction : phase fraction for next interpolation
*
*******************************************************************/
__attribute__((noinline)) void AudioResamplerOrder1::AsmMono16Loop(int16_t *in, int32_t *maxOutPt, int32_t maxInIdx,
size_t &outputIndex, int32_t *out, size_t &inputIndex, int32_t vl, int32_t vr,
uint32_t &phaseFraction, uint32_t phaseIncrement) {
(void)maxOutPt; // remove unused parameter warnings
(void)maxInIdx;
(void)outputIndex;
(void)out;
(void)inputIndex;
(void)vl;
(void)vr;
(void)phaseFraction;
(void)phaseIncrement;
(void)in;
#define MO_PARAM5 "36" // offset of parameter 5 (outputIndex)
asm(
"stmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, lr}\n"
// get parameters
" ldr r6, [sp, #" MO_PARAM5
" + 20]\n" // &phaseFraction
" ldr r6, [r6]\n" // phaseFraction
" ldr r7, [sp, #" MO_PARAM5
" + 8]\n" // &inputIndex
" ldr r7, [r7]\n" // inputIndex
" ldr r8, [sp, #" MO_PARAM5
" + 4]\n" // out
" ldr r0, [sp, #" MO_PARAM5
" + 0]\n" // &outputIndex
" ldr r0, [r0]\n" // outputIndex
" add r8, r8, r0, asl #2\n" // curOut
" ldr r9, [sp, #" MO_PARAM5
" + 24]\n" // phaseIncrement
" ldr r10, [sp, #" MO_PARAM5
" + 12]\n" // vl
" ldr r11, [sp, #" MO_PARAM5
" + 16]\n" // vr
// r0 pin, x0, Samp
// r1 in
// r2 maxOutPt
// r3 maxInIdx
// r4 x1, i1, i3, Out1
// r5 out0
// r6 frac
// r7 inputIndex
// r8 curOut
// r9 inc
// r10 vl
// r11 vr
// r12
// r13 sp
// r14
// the following loop works on 2 frames
"1:\n"
" cmp r8, r2\n" // curOut - maxCurOut
" bcs 2f\n"
#define MO_ONE_FRAME \
" add r0, r1, r7, asl #1\n" /* in + inputIndex */ \
" ldrsh r4, [r0]\n" /* in[inputIndex] */ \
" ldr r5, [r8]\n" /* out[outputIndex] */ \
" ldrsh r0, [r0, #-2]\n" /* in[inputIndex-1] */ \
" bic r6, r6, #0xC0000000\n" /* phaseFraction & ... */ \
" sub r4, r4, r0\n" /* in[inputIndex] - in[inputIndex-1] */ \
" mov r4, r4, lsl #2\n" /* <<2 */ \
" smulwt r4, r4, r6\n" /* (x1-x0)*.. */ \
" add r6, r6, r9\n" /* phaseFraction + phaseIncrement */ \
" add r0, r0, r4\n" /* x0 - (..) */ \
" mla r5, r0, r10, r5\n" /* vl*interp + out[] */ \
" ldr r4, [r8, #4]\n" /* out[outputIndex+1] */ \
" str r5, [r8], #4\n" /* out[outputIndex++] = ... */ \
" mla r4, r0, r11, r4\n" /* vr*interp + out[] */ \
" add r7, r7, r6, lsr #30\n" /* inputIndex + phaseFraction>>30 */ \
" str r4, [r8], #4\n" /* out[outputIndex++] = ... */
MO_ONE_FRAME // frame 1
MO_ONE_FRAME // frame 2
" cmp r7, r3\n" // inputIndex - maxInIdx
" bcc 1b\n"
"2:\n"
" bic r6, r6, #0xC0000000\n" // phaseFraction & ...
// save modified values
" ldr r0, [sp, #" MO_PARAM5
" + 20]\n" // &phaseFraction
" str r6, [r0]\n" // phaseFraction
" ldr r0, [sp, #" MO_PARAM5
" + 8]\n" // &inputIndex
" str r7, [r0]\n" // inputIndex
" ldr r0, [sp, #" MO_PARAM5
" + 4]\n" // out
" sub r8, r0\n" // curOut - out
" asr r8, #2\n" // new outputIndex
" ldr r0, [sp, #" MO_PARAM5
" + 0]\n" // &outputIndex
" str r8, [r0]\n" // save outputIndex
" ldmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, pc}\n");
}
/*******************************************************************
*
* AsmStereo16Loop
* asm optimized stereo loop version; one loop is 2 frames
* Input:
* in : pointer on input samples
* maxOutPt : pointer on first not filled
* maxInIdx : index on first not used
* outputIndex : pointer on current output index
* out : pointer on output buffer
* inputIndex : pointer on current input index
* vl, vr : left and right gain
* phaseFraction : pointer on current phase fraction
* phaseIncrement
* Output:
* outputIndex :
* out : updated buffer
* inputIndex : index of next to use
* phaseFraction : phase fraction for next interpolation
*
*******************************************************************/
__attribute__((noinline)) void AudioResamplerOrder1::AsmStereo16Loop(int16_t *in, int32_t *maxOutPt, int32_t maxInIdx,
size_t &outputIndex, int32_t *out, size_t &inputIndex, int32_t vl, int32_t vr,
uint32_t &phaseFraction, uint32_t phaseIncrement) {
(void)maxOutPt; // remove unused parameter warnings
(void)maxInIdx;
(void)outputIndex;
(void)out;
(void)inputIndex;
(void)vl;
(void)vr;
(void)phaseFraction;
(void)phaseIncrement;
(void)in;
#define ST_PARAM5 "40" // offset of parameter 5 (outputIndex)
asm(
"stmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, r12, lr}\n"
// get parameters
" ldr r6, [sp, #" ST_PARAM5
" + 20]\n" // &phaseFraction
" ldr r6, [r6]\n" // phaseFraction
" ldr r7, [sp, #" ST_PARAM5
" + 8]\n" // &inputIndex
" ldr r7, [r7]\n" // inputIndex
" ldr r8, [sp, #" ST_PARAM5
" + 4]\n" // out
" ldr r0, [sp, #" ST_PARAM5
" + 0]\n" // &outputIndex
" ldr r0, [r0]\n" // outputIndex
" add r8, r8, r0, asl #2\n" // curOut
" ldr r9, [sp, #" ST_PARAM5
" + 24]\n" // phaseIncrement
" ldr r10, [sp, #" ST_PARAM5
" + 12]\n" // vl
" ldr r11, [sp, #" ST_PARAM5
" + 16]\n" // vr
// r0 pin, x0, Samp
// r1 in
// r2 maxOutPt
// r3 maxInIdx
// r4 x1, i1, i3, out1
// r5 out0
// r6 frac
// r7 inputIndex
// r8 curOut
// r9 inc
// r10 vl
// r11 vr
// r12 temporary
// r13 sp
// r14
"3:\n"
" cmp r8, r2\n" // curOut - maxCurOut
" bcs 4f\n"
#define ST_ONE_FRAME \
" bic r6, r6, #0xC0000000\n" /* phaseFraction & ... */ \
\
" add r0, r1, r7, asl #2\n" /* in + 2*inputIndex */ \
\
" ldrsh r4, [r0]\n" /* in[2*inputIndex] */ \
" ldr r5, [r8]\n" /* out[outputIndex] */ \
" ldrsh r12, [r0, #-4]\n" /* in[2*inputIndex-2] */ \
" sub r4, r4, r12\n" /* in[2*InputIndex] - in[2*InputIndex-2] */ \
" mov r4, r4, lsl #2\n" /* <<2 */ \
" smulwt r4, r4, r6\n" /* (x1-x0)*.. */ \
" add r12, r12, r4\n" /* x0 - (..) */ \
" mla r5, r12, r10, r5\n" /* vl*interp + out[] */ \
" ldr r4, [r8, #4]\n" /* out[outputIndex+1] */ \
" str r5, [r8], #4\n" /* out[outputIndex++] = ... */ \
\
" ldrsh r12, [r0, #+2]\n" /* in[2*inputIndex+1] */ \
" ldrsh r0, [r0, #-2]\n" /* in[2*inputIndex-1] */ \
" sub r12, r12, r0\n" /* in[2*InputIndex] - in[2*InputIndex-2] */ \
" mov r12, r12, lsl #2\n" /* <<2 */ \
" smulwt r12, r12, r6\n" /* (x1-x0)*.. */ \
" add r12, r0, r12\n" /* x0 - (..) */ \
" mla r4, r12, r11, r4\n" /* vr*interp + out[] */ \
" str r4, [r8], #4\n" /* out[outputIndex++] = ... */ \
\
" add r6, r6, r9\n" /* phaseFraction + phaseIncrement */ \
" add r7, r7, r6, lsr #30\n" /* inputIndex + phaseFraction>>30 */
ST_ONE_FRAME // frame 1
ST_ONE_FRAME // frame 1
" cmp r7, r3\n" // inputIndex - maxInIdx
" bcc 3b\n"
"4:\n"
" bic r6, r6, #0xC0000000\n" // phaseFraction & ...
// save modified values
" ldr r0, [sp, #" ST_PARAM5
" + 20]\n" // &phaseFraction
" str r6, [r0]\n" // phaseFraction
" ldr r0, [sp, #" ST_PARAM5
" + 8]\n" // &inputIndex
" str r7, [r0]\n" // inputIndex
" ldr r0, [sp, #" ST_PARAM5
" + 4]\n" // out
" sub r8, r0\n" // curOut - out
" asr r8, #2\n" // new outputIndex
" ldr r0, [sp, #" ST_PARAM5
" + 0]\n" // &outputIndex
" str r8, [r0]\n" // save outputIndex
" ldmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, r12, pc}\n");
}
#endif // ASM_ARM_RESAMP1
// ----------------------------------------------------------------------------
} // namespace cc

View File

@@ -0,0 +1,182 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <cstdint>
#if CC_PLATFORM == CC_PLATFORM_ANDROID
#include <android/log.h>
#include <sys/system_properties.h>
#include <sys/types.h>
#endif
#include "audio/android/AudioBufferProvider.h"
//#include <cutils/compiler.h>
//#include <utils/Compat.h>
//#include <media/AudioBufferProvider.h>
//#include <system/audio.h>
#include <cassert>
#include "audio/android/audio.h"
namespace cc {
class AudioResampler {
public:
// Determines quality of SRC.
// LOW_QUALITY: linear interpolator (1st order)
// MED_QUALITY: cubic interpolator (3rd order)
// HIGH_QUALITY: fixed multi-tap FIR (e.g. 48KHz->44.1KHz)
// NOTE: high quality SRC will only be supported for
// certain fixed rate conversions. Sample rate cannot be
// changed dynamically.
enum src_quality { // NOLINT(readability-identifier-naming)
DEFAULT_QUALITY = 0,
LOW_QUALITY = 1,
MED_QUALITY = 2,
HIGH_QUALITY = 3,
VERY_HIGH_QUALITY = 4,
};
static const CONSTEXPR float UNITY_GAIN_FLOAT = 1.0F;
static AudioResampler *create(audio_format_t format, int inChannelCount,
int32_t sampleRate, src_quality quality = DEFAULT_QUALITY);
virtual ~AudioResampler();
virtual void init() = 0;
virtual void setSampleRate(int32_t inSampleRate);
virtual void setVolume(float left, float right);
virtual void setLocalTimeFreq(uint64_t freq);
// set the PTS of the next buffer output by the resampler
virtual void setPTS(int64_t pts);
// Resample int16_t samples from provider and accumulate into 'out'.
// A mono provider delivers a sequence of samples.
// A stereo provider delivers a sequence of interleaved pairs of samples.
//
// In either case, 'out' holds interleaved pairs of fixed-point Q4.27.
// That is, for a mono provider, there is an implicit up-channeling.
// Since this method accumulates, the caller is responsible for clearing 'out' initially.
//
// For a float resampler, 'out' holds interleaved pairs of float samples.
//
// Multichannel interleaved frames for n > 2 is supported for quality DYN_LOW_QUALITY,
// DYN_MED_QUALITY, and DYN_HIGH_QUALITY.
//
// Returns the number of frames resampled into the out buffer.
virtual size_t resample(int32_t *out, size_t outFrameCount,
AudioBufferProvider *provider) = 0;
virtual void reset();
virtual size_t getUnreleasedFrames() const { return mInputIndex; }
// called from destructor, so must not be virtual
src_quality getQuality() const { return mQuality; }
protected:
// number of bits for phase fraction - 30 bits allows nearly 2x downsampling
static const int kNumPhaseBits = 30; // NOLINT(readability-identifier-naming)
// phase mask for fraction
static const uint32_t kPhaseMask = (1LU << kNumPhaseBits) - 1; // NOLINT(readability-identifier-naming)
// multiplier to calculate fixed point phase increment
static const double kPhaseMultiplier; // NOLINT(readability-identifier-naming)
AudioResampler(int inChannelCount, int32_t sampleRate, src_quality quality);
// prevent copying
AudioResampler(const AudioResampler &);
AudioResampler &operator=(const AudioResampler &);
int64_t calculateOutputPTS(int outputFrameIndex);
const int32_t mChannelCount;// NOLINT(readability-identifier-naming)
const int32_t mSampleRate;// NOLINT(readability-identifier-naming)
int32_t mInSampleRate;// NOLINT(readability-identifier-naming)
AudioBufferProvider::Buffer mBuffer;// NOLINT(readability-identifier-naming)
union {
int16_t mVolume[2];// NOLINT(readability-identifier-naming)
uint32_t mVolumeRL;// NOLINT(readability-identifier-naming)
};
int16_t mTargetVolume[2];// NOLINT(readability-identifier-naming)
size_t mInputIndex;// NOLINT(readability-identifier-naming)
int32_t mPhaseIncrement;// NOLINT(readability-identifier-naming)
uint32_t mPhaseFraction;// NOLINT(readability-identifier-naming)
uint64_t mLocalTimeFreq;// NOLINT(readability-identifier-naming)
int64_t mPTS;// NOLINT(readability-identifier-naming)
// returns the inFrameCount required to generate outFrameCount frames.
//
// Placed here to be a consistent for all resamplers.
//
// Right now, we use the upper bound without regards to the current state of the
// input buffer using integer arithmetic, as follows:
//
// (static_cast<uint64_t>(outFrameCount)*mInSampleRate + (mSampleRate - 1))/mSampleRate;
//
// The double precision equivalent (float may not be precise enough):
// ceil(static_cast<double>(outFrameCount) * mInSampleRate / mSampleRate);
//
// this relies on the fact that the mPhaseIncrement is rounded down from
// #phases * mInSampleRate/mSampleRate and the fact that Sum(Floor(x)) <= Floor(Sum(x)).
// http://www.proofwiki.org/wiki/Sum_of_Floors_Not_Greater_Than_Floor_of_Sums
//
// (so long as double precision is computed accurately enough to be considered
// greater than or equal to the Floor(x) value in int32_t arithmetic; thus this
// will not necessarily hold for floats).
//
// REFINE:
// Greater accuracy and a tight bound is obtained by:
// 1) subtract and adjust for the current state of the AudioBufferProvider buffer.
// 2) using the exact integer formula where (ignoring 64b casting)
// inFrameCount = (mPhaseIncrement * (outFrameCount - 1) + mPhaseFraction) / phaseWrapLimit;
// phaseWrapLimit is the wraparound (1 << kNumPhaseBits), if not specified explicitly.
//
inline size_t getInFrameCountRequired(size_t outFrameCount) const {
return (static_cast<size_t>(outFrameCount) * mInSampleRate + (mSampleRate - 1)) / mSampleRate;
}
inline float clampFloatVol(float volume) {//NOLINT(readability-identifier-naming, readability-convert-member-functions-to-static)
float ret = 0.0F;
if (volume > UNITY_GAIN_FLOAT) {
ret = UNITY_GAIN_FLOAT;
} else if (volume >= 0.) {
ret = volume;
}
return ret; // NaN or negative volume maps to 0.
}
private:
const src_quality mQuality;// NOLINT(readability-identifier-naming)
// Return 'true' if the quality level is supported without explicit request
static bool qualityIsSupported(src_quality quality);
// For pthread_once()
static void init_routine(); // NOLINT(readability-identifier-naming)
// Return the estimated CPU load for specific resampler in MHz.
// The absolute number is irrelevant, it's the relative values that matter.
static uint32_t qualityMHz(src_quality quality);
};
// ----------------------------------------------------------------------------
} // namespace cc

View File

@@ -0,0 +1,186 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "AudioResamplerCubic"
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include "audio/android/cutils/log.h"
#include "audio/android/AudioResampler.h"
#include "audio/android/AudioResamplerCubic.h"
namespace cc {
// ----------------------------------------------------------------------------
void AudioResamplerCubic::init() {
memset(&left, 0, sizeof(state));
memset(&right, 0, sizeof(state));
}
size_t AudioResamplerCubic::resample(int32_t *out, size_t outFrameCount,
AudioBufferProvider *provider) {
// should never happen, but we overflow if it does
// ALOG_ASSERT(outFrameCount < 32767);
// select the appropriate resampler
switch (mChannelCount) {
case 1:
return resampleMono16(out, outFrameCount, provider);
case 2:
return resampleStereo16(out, outFrameCount, provider);
default:
LOG_ALWAYS_FATAL("invalid channel count: %d", mChannelCount);
return 0;
}
}
size_t AudioResamplerCubic::resampleStereo16(int32_t *out, size_t outFrameCount,
AudioBufferProvider *provider) {
int32_t vl = mVolume[0];
int32_t vr = mVolume[1];
size_t inputIndex = mInputIndex;
uint32_t phaseFraction = mPhaseFraction;
uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
size_t outputSampleCount = outFrameCount * 2;
size_t inFrameCount = getInFrameCountRequired(outFrameCount);
// fetch first buffer
if (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
provider->getNextBuffer(&mBuffer, mPTS);
if (mBuffer.raw == NULL) {
return 0;
}
// ALOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount);
}
int16_t *in = mBuffer.i16;
while (outputIndex < outputSampleCount) {
int32_t sample;
int32_t x;
// calculate output sample
x = phaseFraction >> kPreInterpShift;
out[outputIndex++] += vl * interp(&left, x);
out[outputIndex++] += vr * interp(&right, x);
// out[outputIndex++] += vr * in[inputIndex*2];
// increment phase
phaseFraction += phaseIncrement;
uint32_t indexIncrement = (phaseFraction >> kNumPhaseBits);
phaseFraction &= kPhaseMask;
// time to fetch another sample
while (indexIncrement--) {
inputIndex++;
if (inputIndex == mBuffer.frameCount) {
inputIndex = 0;
provider->releaseBuffer(&mBuffer);
mBuffer.frameCount = inFrameCount;
provider->getNextBuffer(&mBuffer,
calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
goto save_state; // ugly, but efficient
}
in = mBuffer.i16;
// ALOGW("New buffer: offset=%p, frames=%d", mBuffer.raw, mBuffer.frameCount);
}
// advance sample state
advance(&left, in[inputIndex * 2]);
advance(&right, in[inputIndex * 2 + 1]);
}
}
save_state:
// ALOGW("Done: index=%d, fraction=%u", inputIndex, phaseFraction);
mInputIndex = inputIndex;
mPhaseFraction = phaseFraction;
return outputIndex / 2 /* channels for stereo */;
}
size_t AudioResamplerCubic::resampleMono16(int32_t *out, size_t outFrameCount,
AudioBufferProvider *provider) {
int32_t vl = mVolume[0];
int32_t vr = mVolume[1];
size_t inputIndex = mInputIndex;
uint32_t phaseFraction = mPhaseFraction;
uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
size_t outputSampleCount = outFrameCount * 2;
size_t inFrameCount = getInFrameCountRequired(outFrameCount);
// fetch first buffer
if (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
provider->getNextBuffer(&mBuffer, mPTS);
if (mBuffer.raw == NULL) {
return 0;
}
// ALOGW("New buffer: offset=%p, frames=%d", mBuffer.raw, mBuffer.frameCount);
}
int16_t *in = mBuffer.i16;
while (outputIndex < outputSampleCount) {
int32_t sample;
int32_t x;
// calculate output sample
x = phaseFraction >> kPreInterpShift;
sample = interp(&left, x);
out[outputIndex++] += vl * sample;
out[outputIndex++] += vr * sample;
// increment phase
phaseFraction += phaseIncrement;
uint32_t indexIncrement = (phaseFraction >> kNumPhaseBits);
phaseFraction &= kPhaseMask;
// time to fetch another sample
while (indexIncrement--) {
inputIndex++;
if (inputIndex == mBuffer.frameCount) {
inputIndex = 0;
provider->releaseBuffer(&mBuffer);
mBuffer.frameCount = inFrameCount;
provider->getNextBuffer(&mBuffer,
calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
goto save_state; // ugly, but efficient
}
// ALOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount);
in = mBuffer.i16;
}
// advance sample state
advance(&left, in[inputIndex]);
}
}
save_state:
// ALOGW("Done: index=%d, fraction=%u", inputIndex, phaseFraction);
mInputIndex = inputIndex;
mPhaseFraction = phaseFraction;
return outputIndex;
}
// ----------------------------------------------------------------------------
} // namespace cc

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <sys/types.h>
#include "audio/android/AudioResampler.h"
#include "audio/android/AudioBufferProvider.h"
namespace cc {
// ----------------------------------------------------------------------------
class AudioResamplerCubic : public AudioResampler {
public:
AudioResamplerCubic(int inChannelCount, int32_t sampleRate) : AudioResampler(inChannelCount, sampleRate, MED_QUALITY) {
}
virtual size_t resample(int32_t *out, size_t outFrameCount,
AudioBufferProvider *provider);
private:
// number of bits used in interpolation multiply - 14 bits avoids overflow
static const int kNumInterpBits = 14;
// bits to shift the phase fraction down to avoid overflow
static const int kPreInterpShift = kNumPhaseBits - kNumInterpBits;
typedef struct {
int32_t a, b, c, y0, y1, y2, y3;
} state;
void init();
size_t resampleMono16(int32_t *out, size_t outFrameCount,
AudioBufferProvider *provider);
size_t resampleStereo16(int32_t *out, size_t outFrameCount,
AudioBufferProvider *provider);
static inline int32_t interp(state *p, int32_t x) {
return (((((p->a * x >> 14) + p->b) * x >> 14) + p->c) * x >> 14) + p->y1;
}
static inline void advance(state *p, int16_t in) {
p->y0 = p->y1;
p->y1 = p->y2;
p->y2 = p->y3;
p->y3 = in;
p->a = (3 * (p->y1 - p->y2) - p->y0 + p->y3) >> 1;
p->b = (p->y2 << 1) + p->y0 - (((5 * p->y1 + p->y3)) >> 1);
p->c = (p->y2 - p->y0) >> 1;
}
state left, right;
};
// ----------------------------------------------------------------------------
} // namespace cc

View File

@@ -0,0 +1,171 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <math.h>
#include <stdint.h>
namespace cc {
// AUDIO_RESAMPLER_DOWN_RATIO_MAX is the maximum ratio between the original
// audio sample rate and the target rate when downsampling,
// as permitted in the audio framework, e.g. AudioTrack and AudioFlinger.
// In practice, it is not recommended to downsample more than 6:1
// for best audio quality, even though the audio framework permits a larger
// downsampling ratio.
// REFINE: replace with an API
#define AUDIO_RESAMPLER_DOWN_RATIO_MAX 256
// AUDIO_RESAMPLER_UP_RATIO_MAX is the maximum suggested ratio between the original
// audio sample rate and the target rate when upsampling. It is loosely enforced by
// the system. One issue with large upsampling ratios is the approximation by
// an int32_t of the phase increments, making the resulting sample rate inexact.
#define AUDIO_RESAMPLER_UP_RATIO_MAX 65536
// AUDIO_TIMESTRETCH_SPEED_MIN and AUDIO_TIMESTRETCH_SPEED_MAX define the min and max time stretch
// speeds supported by the system. These are enforced by the system and values outside this range
// will result in a runtime error.
// Depending on the AudioPlaybackRate::mStretchMode, the effective limits might be narrower than
// the ones specified here
// AUDIO_TIMESTRETCH_SPEED_MIN_DELTA is the minimum absolute speed difference that might trigger a
// parameter update
#define AUDIO_TIMESTRETCH_SPEED_MIN 0.01f
#define AUDIO_TIMESTRETCH_SPEED_MAX 20.0f
#define AUDIO_TIMESTRETCH_SPEED_NORMAL 1.0f
#define AUDIO_TIMESTRETCH_SPEED_MIN_DELTA 0.0001f
// AUDIO_TIMESTRETCH_PITCH_MIN and AUDIO_TIMESTRETCH_PITCH_MAX define the min and max time stretch
// pitch shifting supported by the system. These are not enforced by the system and values
// outside this range might result in a pitch different than the one requested.
// Depending on the AudioPlaybackRate::mStretchMode, the effective limits might be narrower than
// the ones specified here.
// AUDIO_TIMESTRETCH_PITCH_MIN_DELTA is the minimum absolute pitch difference that might trigger a
// parameter update
#define AUDIO_TIMESTRETCH_PITCH_MIN 0.25f
#define AUDIO_TIMESTRETCH_PITCH_MAX 4.0f
#define AUDIO_TIMESTRETCH_PITCH_NORMAL 1.0f
#define AUDIO_TIMESTRETCH_PITCH_MIN_DELTA 0.0001f
//Determines the current algorithm used for stretching
enum AudioTimestretchStretchMode : int32_t {
AUDIO_TIMESTRETCH_STRETCH_DEFAULT = 0,
AUDIO_TIMESTRETCH_STRETCH_SPEECH = 1,
//REFINE: add more stretch modes/algorithms
};
//Limits for AUDIO_TIMESTRETCH_STRETCH_SPEECH mode
#define TIMESTRETCH_SONIC_SPEED_MIN 0.1f
#define TIMESTRETCH_SONIC_SPEED_MAX 6.0f
//Determines behavior of Timestretch if current algorithm can't perform
//with current parameters.
// FALLBACK_CUT_REPEAT: (internal only) for speed <1.0 will truncate frames
// for speed > 1.0 will repeat frames
// FALLBACK_MUTE: will set all processed frames to zero
// FALLBACK_FAIL: will stop program execution and log a fatal error
enum AudioTimestretchFallbackMode : int32_t {
AUDIO_TIMESTRETCH_FALLBACK_CUT_REPEAT = -1,
AUDIO_TIMESTRETCH_FALLBACK_DEFAULT = 0,
AUDIO_TIMESTRETCH_FALLBACK_MUTE = 1,
AUDIO_TIMESTRETCH_FALLBACK_FAIL = 2,
};
struct AudioPlaybackRate {
float mSpeed;
float mPitch;
enum AudioTimestretchStretchMode mStretchMode;
enum AudioTimestretchFallbackMode mFallbackMode;
};
static const AudioPlaybackRate AUDIO_PLAYBACK_RATE_DEFAULT = {
AUDIO_TIMESTRETCH_SPEED_NORMAL,
AUDIO_TIMESTRETCH_PITCH_NORMAL,
AUDIO_TIMESTRETCH_STRETCH_DEFAULT,
AUDIO_TIMESTRETCH_FALLBACK_DEFAULT};
static inline bool isAudioPlaybackRateEqual(const AudioPlaybackRate &pr1,
const AudioPlaybackRate &pr2) {
return fabs(pr1.mSpeed - pr2.mSpeed) < AUDIO_TIMESTRETCH_SPEED_MIN_DELTA &&
fabs(pr1.mPitch - pr2.mPitch) < AUDIO_TIMESTRETCH_PITCH_MIN_DELTA &&
pr1.mStretchMode == pr2.mStretchMode &&
pr1.mFallbackMode == pr2.mFallbackMode;
}
static inline bool isAudioPlaybackRateValid(const AudioPlaybackRate &playbackRate) {
if (playbackRate.mFallbackMode == AUDIO_TIMESTRETCH_FALLBACK_FAIL &&
(playbackRate.mStretchMode == AUDIO_TIMESTRETCH_STRETCH_SPEECH ||
playbackRate.mStretchMode == AUDIO_TIMESTRETCH_STRETCH_DEFAULT)) {
//test sonic specific constraints
return playbackRate.mSpeed >= TIMESTRETCH_SONIC_SPEED_MIN &&
playbackRate.mSpeed <= TIMESTRETCH_SONIC_SPEED_MAX &&
playbackRate.mPitch >= AUDIO_TIMESTRETCH_PITCH_MIN &&
playbackRate.mPitch <= AUDIO_TIMESTRETCH_PITCH_MAX;
} else {
return playbackRate.mSpeed >= AUDIO_TIMESTRETCH_SPEED_MIN &&
playbackRate.mSpeed <= AUDIO_TIMESTRETCH_SPEED_MAX &&
playbackRate.mPitch >= AUDIO_TIMESTRETCH_PITCH_MIN &&
playbackRate.mPitch <= AUDIO_TIMESTRETCH_PITCH_MAX;
}
}
// REFINE: Consider putting these inlines into a class scope
// Returns the source frames needed to resample to destination frames. This is not a precise
// value and depends on the resampler (and possibly how it handles rounding internally).
// Nevertheless, this should be an upper bound on the requirements of the resampler.
// If srcSampleRate and dstSampleRate are equal, then it returns destination frames, which
// may not be true if the resampler is asynchronous.
static inline size_t sourceFramesNeeded(
uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate) {
// +1 for rounding - always do this even if matched ratio (resampler may use phases not ratio)
// +1 for additional sample needed for interpolation
return srcSampleRate == dstSampleRate ? dstFramesRequired : size_t((uint64_t)dstFramesRequired * srcSampleRate / dstSampleRate + 1 + 1);
}
// An upper bound for the number of destination frames possible from srcFrames
// after sample rate conversion. This may be used for buffer sizing.
static inline size_t destinationFramesPossible(size_t srcFrames, uint32_t srcSampleRate,
uint32_t dstSampleRate) {
if (srcSampleRate == dstSampleRate) {
return srcFrames;
}
uint64_t dstFrames = (uint64_t)srcFrames * dstSampleRate / srcSampleRate;
return dstFrames > 2 ? static_cast<size_t>(dstFrames - 2) : 0;
}
static inline size_t sourceFramesNeededWithTimestretch(
uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate,
float speed) {
// required is the number of input frames the resampler needs
size_t required = sourceFramesNeeded(srcSampleRate, dstFramesRequired, dstSampleRate);
// to deliver this, the time stretcher requires:
return required * (double)speed + 1 + 1; // accounting for rounding dependencies
}
// Identifies sample rates that we associate with music
// and thus eligible for better resampling and fast capture.
// This is somewhat less than 44100 to allow for pitch correction
// involving resampling as well as asynchronous resampling.
#define AUDIO_PROCESSING_MUSIC_RATE 40000
static inline bool isMusicRate(uint32_t sampleRate) {
return sampleRate >= AUDIO_PROCESSING_MUSIC_RATE;
}
} // namespace cc
// ---------------------------------------------------------------------------

View File

@@ -0,0 +1,87 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
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 <functional>
#include "base/std/container/string.h"
namespace cc {
class IAudioPlayer {
public:
enum class State {
INVALID = 0,
INITIALIZED,
PLAYING,
PAUSED,
STOPPED,
OVER
};
using PlayEventCallback = std::function<void(State)>;
virtual ~IAudioPlayer(){};
virtual int getId() const = 0;
virtual void setId(int id) = 0;
virtual ccstd::string getUrl() const = 0;
virtual State getState() const = 0;
virtual void play() = 0;
virtual void pause() = 0;
virtual void resume() = 0;
virtual void stop() = 0;
virtual void rewind() = 0;
virtual void setVolume(float volume) = 0;
virtual float getVolume() const = 0;
virtual void setAudioFocus(bool isFocus) = 0;
virtual void setLoop(bool isLoop) = 0;
virtual bool isLoop() const = 0;
virtual float getDuration() const = 0;
virtual float getPosition() const = 0;
virtual bool setPosition(float pos) = 0;
// @note: STOPPED event is invoked in main thread
// OVER event is invoked in sub thread
virtual void setPlayEventCallback(const PlayEventCallback &playEventCallback) = 0;
};
} // namespace cc

View File

@@ -0,0 +1,40 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
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 <functional>
#include <thread>
namespace cc {
class ICallerThreadUtils {
public:
virtual ~ICallerThreadUtils(){};
virtual void performFunctionInCallerThread(const std::function<void()> &func) = 0;
virtual std::thread::id getCallerThreadId() = 0;
};
} // namespace cc

View File

@@ -0,0 +1,42 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/common/utils/include/minifloat.h"
namespace cc {
class IVolumeProvider {
public:
// The provider implementation is responsible for validating that the return value is in range.
virtual gain_minifloat_packed_t getVolumeLR() = 0;
protected:
IVolumeProvider() {}
virtual ~IVolumeProvider() {}
};
} // namespace cc

View File

@@ -0,0 +1,107 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/cutils/log.h"
#include <SLES/OpenSLES.h>
#if CC_PLATFORM == CC_PLATFORM_ANDROID
#include <SLES/OpenSLES_Android.h>
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
#include <SLES/OpenSLES_Platform.h>
#endif
#include <functional>
#include "base/std/container/string.h"
#define SL_SAFE_DELETE(obj) \
if ((obj) != nullptr) { \
delete (obj); \
(obj) = nullptr; \
}
#define SL_DESTROY_OBJ(OBJ) \
if ((OBJ) != nullptr) { \
(*(OBJ))->Destroy(OBJ); \
(OBJ) = nullptr; \
}
#define SL_RETURN_VAL_IF_FAILED(r, rval, ...) \
if (r != SL_RESULT_SUCCESS) { \
ALOGE(__VA_ARGS__); \
return rval; \
}
#define SL_RETURN_IF_FAILED(r, ...) \
if (r != SL_RESULT_SUCCESS) { \
ALOGE(__VA_ARGS__); \
return; \
}
#define SL_PRINT_ERROR_IF_FAILED(r, ...) \
if (r != SL_RESULT_SUCCESS) { \
ALOGE(__VA_ARGS__); \
}
typedef std::function<int(const ccstd::string &, off_t *start, off_t *length)> FdGetterCallback;
// Copied from OpenSLES_AndroidMetadata.h in android-21
// It's because android-10 doesn't contain this header file
/**
* Additional metadata keys to be used in SLMetadataExtractionItf:
* the ANDROID_KEY_PCMFORMAT_* keys follow the fields of the SLDataFormat_PCM struct, and as such
* all values corresponding to these keys are of SLuint32 type, and are defined as the fields
* of the same name in SLDataFormat_PCM. The exception is that sample rate is expressed here
* in Hz units, rather than in milliHz units.
*/
#ifndef ANDROID_KEY_PCMFORMAT_NUMCHANNELS
#define ANDROID_KEY_PCMFORMAT_NUMCHANNELS "AndroidPcmFormatNumChannels"
#endif
#ifndef ANDROID_KEY_PCMFORMAT_SAMPLERATE
#define ANDROID_KEY_PCMFORMAT_SAMPLERATE "AndroidPcmFormatSampleRate"
#endif
#ifndef ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE
#define ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE "AndroidPcmFormatBitsPerSample"
#endif
#ifndef ANDROID_KEY_PCMFORMAT_CONTAINERSIZE
#define ANDROID_KEY_PCMFORMAT_CONTAINERSIZE "AndroidPcmFormatContainerSize"
#endif
#ifndef ANDROID_KEY_PCMFORMAT_CHANNELMASK
#define ANDROID_KEY_PCMFORMAT_CHANNELMASK "AndroidPcmFormatChannelMask"
#endif
#ifndef ANDROID_KEY_PCMFORMAT_ENDIANNESS
#define ANDROID_KEY_PCMFORMAT_ENDIANNESS "AndroidPcmFormatEndianness"
#endif
#define clockNow() std::chrono::high_resolution_clock::now()
#define intervalInMS(oldTime, newTime) (static_cast<long>(std::chrono::duration_cast<std::chrono::microseconds>((newTime) - (oldTime)).count()) / 1000.f)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))

View File

@@ -0,0 +1,193 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "PcmAudioPlayer"
#include "audio/android/PcmAudioPlayer.h"
#include "audio/android/AudioMixerController.h"
#include "audio/android/ICallerThreadUtils.h"
#include "audio/android/cutils/log.h"
#include "base/memory/Memory.h"
namespace cc {
PcmAudioPlayer::PcmAudioPlayer(AudioMixerController *controller, ICallerThreadUtils *callerThreadUtils)
: _id(-1), _track(nullptr), _playEventCallback(nullptr), _controller(controller), _callerThreadUtils(callerThreadUtils) {
ALOGV("PcmAudioPlayer constructor: %p", this);
}
PcmAudioPlayer::~PcmAudioPlayer() {
ALOGV("In the destructor of PcmAudioPlayer (%p)", this);
delete _track;
}
bool PcmAudioPlayer::prepare(const ccstd::string &url, const PcmData &decResult) {
_url = url;
_decResult = decResult;
_track = ccnew Track(_decResult);
std::thread::id callerThreadId = _callerThreadUtils->getCallerThreadId();
// @note The logic may cause this issue https://github.com/cocos2d/cocos2d-x/issues/17707
// Assume that AudioEngine::stop(id) is invoked and the audio is played over meanwhile.
// Since State::OVER and State::DESTROYED are triggered in the audio mixing thread, it will
// call 'performFunctionInCallerThread' to post events to cocos's message queue.
// Therefore, the sequence in cocos's thread will be |STOP|OVER|DESTROYED|.
// Although, we remove the audio id in |STOPPED| callback, because it's asynchronous operation,
// |OVER| and |DESTROYED| callbacks will still be invoked in cocos's thread.
// HOW TO FIX: If the previous state is |STOPPED| and the current state
// is |OVER|, just skip to invoke |OVER| callback.
_track->onStateChanged = [this, callerThreadId](Track::State state) {
// It maybe in sub thread
Track::State prevState = _track->getPrevState();
auto func = [this, state, prevState]() {
// It's in caller's thread
if (state == Track::State::OVER && prevState != Track::State::STOPPED) {
if (_playEventCallback != nullptr) {
_playEventCallback(State::OVER);
}
} else if (state == Track::State::STOPPED) {
if (_playEventCallback != nullptr) {
_playEventCallback(State::STOPPED);
}
} else if (state == Track::State::DESTROYED) {
delete this;
}
};
if (callerThreadId == std::this_thread::get_id()) { // onStateChanged(Track::State::STOPPED) is in caller's (Cocos's) thread.
func();
} else { // onStateChanged(Track::State::OVER) or onStateChanged(Track::State::DESTROYED) are in audio mixing thread.
_callerThreadUtils->performFunctionInCallerThread(func);
}
};
setVolume(1.0f);
return true;
}
void PcmAudioPlayer::rewind() {
ALOGW("PcmAudioPlayer::rewind isn't supported!");
}
void PcmAudioPlayer::setVolume(float volume) {
_track->setVolume(volume);
}
float PcmAudioPlayer::getVolume() const {
return _track->getVolume();
}
void PcmAudioPlayer::setAudioFocus(bool isFocus) {
_track->setAudioFocus(isFocus);
}
void PcmAudioPlayer::setLoop(bool isLoop) {
_track->setLoop(isLoop);
}
bool PcmAudioPlayer::isLoop() const {
return _track->isLoop();
}
float PcmAudioPlayer::getDuration() const {
return _decResult.duration;
}
float PcmAudioPlayer::getPosition() const {
return _track->getPosition();
}
bool PcmAudioPlayer::setPosition(float pos) {
return _track->setPosition(pos);
}
void PcmAudioPlayer::setPlayEventCallback(const PlayEventCallback &playEventCallback) {
_playEventCallback = playEventCallback;
}
void PcmAudioPlayer::play() {
// put track to AudioMixerController
ALOGV("PcmAudioPlayer (%p) play, url: %s", this, _url.c_str());
_controller->addTrack(_track);
_track->setState(Track::State::PLAYING);
}
void PcmAudioPlayer::pause() {
ALOGV("PcmAudioPlayer (%p) pause, url: %s", this, _url.c_str());
_track->setState(Track::State::PAUSED);
}
void PcmAudioPlayer::resume() {
ALOGV("PcmAudioPlayer (%p) resume, url: %s", this, _url.c_str());
_track->setState(Track::State::RESUMED);
}
void PcmAudioPlayer::stop() {
ALOGV("PcmAudioPlayer (%p) stop, url: %s", this, _url.c_str());
_track->setState(Track::State::STOPPED);
}
IAudioPlayer::State PcmAudioPlayer::getState() const {
IAudioPlayer::State state = State::INVALID;
if (_track != nullptr) {
switch (_track->getState()) {
case Track::State::IDLE:
state = State::INITIALIZED;
break;
case Track::State::PLAYING:
state = State::PLAYING;
break;
case Track::State::RESUMED:
state = State::PLAYING;
break;
case Track::State::PAUSED:
state = State::PAUSED;
break;
case Track::State::STOPPED:
state = State::STOPPED;
break;
case Track::State::OVER:
state = State::OVER;
break;
default:
state = State::INVALID;
break;
}
}
return state;
}
} // namespace cc

View File

@@ -0,0 +1,96 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
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 <mutex>
#include "audio/android/IAudioPlayer.h"
#include "audio/android/PcmData.h"
#include "audio/android/Track.h"
namespace cc {
class ICallerThreadUtils;
class AudioMixerController;
class PcmAudioPlayer : public IAudioPlayer {
public:
bool prepare(const ccstd::string &url, const PcmData &decResult);
// Override Functions Begin
virtual int getId() const override { return _id; };
virtual void setId(int id) override { _id = id; };
virtual ccstd::string getUrl() const override { return _url; };
virtual State getState() const override;
virtual void play() override;
virtual void pause() override;
virtual void resume() override;
virtual void stop() override;
virtual void rewind() override;
virtual void setVolume(float volume) override;
virtual float getVolume() const override;
virtual void setAudioFocus(bool isFocus) override;
virtual void setLoop(bool isLoop) override;
virtual bool isLoop() const override;
virtual float getDuration() const override;
virtual float getPosition() const override;
virtual bool setPosition(float pos) override;
virtual void setPlayEventCallback(const PlayEventCallback &playEventCallback) override;
// Override Functions End
private:
PcmAudioPlayer(AudioMixerController *controller, ICallerThreadUtils *callerThreadUtils);
virtual ~PcmAudioPlayer();
private:
int _id;
ccstd::string _url;
PcmData _decResult;
Track *_track;
PlayEventCallback _playEventCallback;
AudioMixerController *_controller;
ICallerThreadUtils *_callerThreadUtils;
friend class AudioPlayerProvider;
};
} // namespace cc

View File

@@ -0,0 +1,190 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "PcmAudioService"
#include "base/Macros.h"
#include "audio/android/PcmAudioService.h"
#include "audio/android/AudioMixerController.h"
#include "audio/android/utils/Compat.h"
namespace cc {
static ccstd::vector<char> __silenceData;//NOLINT(bugprone-reserved-identifier, readability-identifier-naming)
#define AUDIO_PLAYER_BUFFER_COUNT (2)
class SLPcmAudioPlayerCallbackProxy {
public:
#if CC_PLATFORM == CC_PLATFORM_ANDROID
static void samplePlayerCallback(CCSLBufferQueueItf bq, void *context) {
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
static void samplePlayerCallback(CCSLBufferQueueItf bq, void *context, SLuint32 size) {
#endif
auto *thiz = reinterpret_cast<PcmAudioService *>(context);
thiz->bqFetchBufferCallback(bq);
}
};
PcmAudioService::PcmAudioService(SLEngineItf engineItf, SLObjectItf outputMixObject)
: _engineItf(engineItf), _outputMixObj(outputMixObject), _playObj(nullptr), _playItf(nullptr), _volumeItf(nullptr), _bufferQueueItf(nullptr), _numChannels(-1), _sampleRate(-1), _bufferSizeInBytes(0), _controller(nullptr) {
}
PcmAudioService::~PcmAudioService() {
ALOGV("PcmAudioServicee() (%p), before destroy play object", this);
SL_DESTROY_OBJ(_playObj);
ALOGV("PcmAudioServicee() end");
}
bool PcmAudioService::enqueue() {
#if CC_PLATFORM == CC_PLATFORM_OPENHARMONY
// We need to call this interface in openharmony, otherwise there will be noise
SLuint8 *buffer = nullptr;
SLuint32 size = 0;
(*_bufferQueueItf)->GetBuffer(_bufferQueueItf, &buffer, &size);
#endif
if (_controller->hasPlayingTacks()) {
if (_controller->isPaused()) {
SLresult r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, __silenceData.data(), __silenceData.size());
SL_RETURN_VAL_IF_FAILED(r, false, "enqueue silent data failed!");
} else {
_controller->mixOneFrame();
auto *current = _controller->current();
ALOG_ASSERT(current != nullptr, "current buffer is nullptr ...");
SLresult r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, current->buf, current->size);
SL_RETURN_VAL_IF_FAILED(r, false, "enqueue failed!");
}
} else {
SLresult r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, __silenceData.data(), __silenceData.size());
SL_RETURN_VAL_IF_FAILED(r, false, "enqueue silent data failed!");
}
return true;
}
void PcmAudioService::bqFetchBufferCallback(CCSLBufferQueueItf bq) {
CC_UNUSED_PARAM(bq);
// IDEA: PcmAudioService instance may be destroyed, we need to find a way to wait...
// It's in sub thread
enqueue();
}
bool PcmAudioService::init(AudioMixerController *controller, int numChannels, int sampleRate, int bufferSizeInBytes) {
_controller = controller;
_numChannels = numChannels;
_sampleRate = sampleRate;
_bufferSizeInBytes = bufferSizeInBytes;
SLuint32 channelMask = SL_SPEAKER_FRONT_CENTER;
if (numChannels > 1) {
channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
}
SLDataFormat_PCM formatPcm = {
SL_DATAFORMAT_PCM,
static_cast<SLuint32>(numChannels),
static_cast<SLuint32>(sampleRate * 1000),
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_PCMSAMPLEFORMAT_FIXED_16,
channelMask,
SL_BYTEORDER_LITTLEENDIAN};
#if CC_PLATFORM == CC_PLATFORM_ANDROID
SLDataLocator_AndroidSimpleBufferQueue locBufQueue = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
AUDIO_PLAYER_BUFFER_COUNT};
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
SLDataLocator_BufferQueue locBufQueue = {SL_DATALOCATOR_BUFFERQUEUE, AUDIO_PLAYER_BUFFER_COUNT};
#endif
SLDataSource source = {&locBufQueue, &formatPcm};
SLDataLocator_OutputMix locOutmix = {
SL_DATALOCATOR_OUTPUTMIX,
_outputMixObj};
SLDataSink sink = {&locOutmix, nullptr};
const SLInterfaceID ids[] = {
SL_IID_PLAY,
SL_IID_VOLUME,
CC_SL_IDD_BUFFER_QUEUE,
};
const SLboolean req[] = {
SL_BOOLEAN_TRUE,
SL_BOOLEAN_TRUE,
SL_BOOLEAN_TRUE,
};
SLresult r;
r = (*_engineItf)->CreateAudioPlayer(_engineItf, &_playObj, &source, &sink, sizeof(ids) / sizeof(ids[0]), ids, req);//NOLINT(bugprone-sizeof-expression)
SL_RETURN_VAL_IF_FAILED(r, false, "CreateAudioPlayer failed");
r = (*_playObj)->Realize(_playObj, SL_BOOLEAN_FALSE);
SL_RETURN_VAL_IF_FAILED(r, false, "Realize failed");
r = (*_playObj)->GetInterface(_playObj, SL_IID_PLAY, &_playItf);
SL_RETURN_VAL_IF_FAILED(r, false, "GetInterface SL_IID_PLAY failed");
r = (*_playObj)->GetInterface(_playObj, SL_IID_VOLUME, &_volumeItf);
SL_RETURN_VAL_IF_FAILED(r, false, "GetInterface SL_IID_VOLUME failed");
r = (*_playObj)->GetInterface(_playObj, CC_SL_IDD_BUFFER_QUEUE, &_bufferQueueItf);
SL_RETURN_VAL_IF_FAILED(r, false, "GetInterface CC_SL_IDD_BUFFER_QUEUE failed");
r = (*_bufferQueueItf)->RegisterCallback(_bufferQueueItf, SLPcmAudioPlayerCallbackProxy::samplePlayerCallback, this);
SL_RETURN_VAL_IF_FAILED(r, false, "_bufferQueueItf RegisterCallback failed");
if (__silenceData.empty()) {
__silenceData.resize(_numChannels * _bufferSizeInBytes, 0x00);
}
#if CC_PLATFORM == CC_PLATFORM_OPENHARMONY
// We need to call this interface in openharmony, otherwise there will be noise
SLuint8 *buffer = nullptr;
SLuint32 size = 0;
(*_bufferQueueItf)->GetBuffer(_bufferQueueItf, &buffer, &size);
#endif
r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, __silenceData.data(), __silenceData.size());
SL_RETURN_VAL_IF_FAILED(r, false, "_bufferQueueItf Enqueue failed");
r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING);
SL_RETURN_VAL_IF_FAILED(r, false, "SetPlayState failed");
return true;
}
void PcmAudioService::pause() {
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PAUSED);
SL_RETURN_IF_FAILED(r, "PcmAudioService::pause failed");
}
void PcmAudioService::resume() {
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING);
SL_RETURN_IF_FAILED(r, "PcmAudioService::resume failed");
}
} // namespace cc

View File

@@ -0,0 +1,78 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/IAudioPlayer.h"
#include "audio/android/OpenSLHelper.h"
#include "audio/android/PcmData.h"
#include <condition_variable>
#include <mutex>
#include "audio/android/utils/Compat.h"
namespace cc {
class AudioMixerController;
class PcmAudioService {
public:
inline int getChannelCount() const { return _numChannels; };
inline int getSampleRate() const { return _sampleRate; };
private:
PcmAudioService(SLEngineItf engineItf, SLObjectItf outputMixObject);
virtual ~PcmAudioService();
bool init(AudioMixerController *controller, int numChannels, int sampleRate, int bufferSizeInBytes);
bool enqueue();
void bqFetchBufferCallback(CCSLBufferQueueItf bq);
void pause();
void resume();
SLEngineItf _engineItf;
SLObjectItf _outputMixObj;
SLObjectItf _playObj;
SLPlayItf _playItf;
SLVolumeItf _volumeItf;
CCSLBufferQueueItf _bufferQueueItf;
int _numChannels;
int _sampleRate;
int _bufferSizeInBytes;
AudioMixerController *_controller;
friend class SLPcmAudioPlayerCallbackProxy;
friend class AudioPlayerProvider;
};
} // namespace cc

View File

@@ -0,0 +1,102 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "PcmBufferProvider"
#include "audio/android/PcmBufferProvider.h"
#include "audio/android/cutils/log.h"
//#define VERY_VERY_VERBOSE_LOGGING
#ifdef VERY_VERY_VERBOSE_LOGGING
#define ALOGVV ALOGV
#else
#define ALOGVV(a...) \
do { \
} while (0)
#endif
namespace cc {
PcmBufferProvider::PcmBufferProvider()
: _addr(nullptr), _numFrames(0), _frameSize(0), _nextFrame(0), _unrel(0) {
}
bool PcmBufferProvider::init(const void *addr, size_t frames, size_t frameSize) {
_addr = addr;
_numFrames = frames;
_frameSize = frameSize;
_nextFrame = 0;
_unrel = 0;
return true;
}
status_t PcmBufferProvider::getNextBuffer(Buffer *buffer,
int64_t pts /* = kInvalidPTS*/) {
(void)pts; // suppress warning
size_t requestedFrames = buffer->frameCount;
if (requestedFrames > _numFrames - _nextFrame) {
buffer->frameCount = _numFrames - _nextFrame;
}
ALOGVV(
"getNextBuffer() requested %zu frames out of %zu frames available,"
" and returned %zu frames",
requestedFrames, (size_t)(_numFrames - _nextFrame), buffer->frameCount);
_unrel = buffer->frameCount;
if (buffer->frameCount > 0) {
buffer->raw = (char *)_addr + _frameSize * _nextFrame;
return NO_ERROR;
} else {
buffer->raw = NULL;
return NOT_ENOUGH_DATA;
}
}
void PcmBufferProvider::releaseBuffer(Buffer *buffer) {
if (buffer->frameCount > _unrel) {
ALOGVV(
"ERROR releaseBuffer() released %zu frames but only %zu available "
"to release",
buffer->frameCount, _unrel);
_nextFrame += _unrel;
_unrel = 0;
} else {
ALOGVV(
"releaseBuffer() released %zu frames out of %zu frames available "
"to release",
buffer->frameCount, _unrel);
_nextFrame += buffer->frameCount;
_unrel -= buffer->frameCount;
}
buffer->frameCount = 0;
buffer->raw = NULL;
}
void PcmBufferProvider::reset() {
_nextFrame = 0;
}
} // namespace cc

View File

@@ -0,0 +1,51 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/AudioBufferProvider.h"
#include <stddef.h>
#include <stdio.h>
namespace cc {
class PcmBufferProvider : public AudioBufferProvider {
public:
PcmBufferProvider();
bool init(const void *addr, size_t frames, size_t frameSize);
virtual status_t getNextBuffer(Buffer *buffer, int64_t pts = kInvalidPTS) override;
virtual void releaseBuffer(Buffer *buffer) override;
void reset();
protected:
const void *_addr; // base address
size_t _numFrames; // total frames
size_t _frameSize; // size of each frame in bytes
size_t _nextFrame; // index of next frame to provide
size_t _unrel; // number of frames not yet released
};
} // namespace cc

View File

@@ -0,0 +1,128 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "PcmData"
#include "audio/android/PcmData.h"
#include "audio/android/OpenSLHelper.h"
namespace cc {
PcmData::PcmData() {
// ALOGV("In the constructor of PcmData (%p)", this);
reset();
}
PcmData::~PcmData() {
// ALOGV("In the destructor of PcmData (%p)", this);
}
PcmData::PcmData(const PcmData &o) {
// ALOGV("In the copy constructor of PcmData (%p)", this);
numChannels = o.numChannels;
sampleRate = o.sampleRate;
bitsPerSample = o.bitsPerSample;
containerSize = o.containerSize;
channelMask = o.channelMask;
endianness = o.endianness;
numFrames = o.numFrames;
duration = o.duration;
pcmBuffer = std::move(o.pcmBuffer);
}
PcmData::PcmData(PcmData &&o) {
// ALOGV("In the move constructor of PcmData (%p)", this);
numChannels = o.numChannels;
sampleRate = o.sampleRate;
bitsPerSample = o.bitsPerSample;
containerSize = o.containerSize;
channelMask = o.channelMask;
endianness = o.endianness;
numFrames = o.numFrames;
duration = o.duration;
pcmBuffer = std::move(o.pcmBuffer);
o.reset();
}
PcmData &PcmData::operator=(const PcmData &o) {
// ALOGV("In the copy assignment of PcmData");
numChannels = o.numChannels;
sampleRate = o.sampleRate;
bitsPerSample = o.bitsPerSample;
containerSize = o.containerSize;
channelMask = o.channelMask;
endianness = o.endianness;
numFrames = o.numFrames;
duration = o.duration;
pcmBuffer = o.pcmBuffer;
return *this;
}
PcmData &PcmData::operator=(PcmData &&o) {
// ALOGV("In the move assignment of PcmData");
numChannels = o.numChannels;
sampleRate = o.sampleRate;
bitsPerSample = o.bitsPerSample;
containerSize = o.containerSize;
channelMask = o.channelMask;
endianness = o.endianness;
numFrames = o.numFrames;
duration = o.duration;
pcmBuffer = std::move(o.pcmBuffer);
o.reset();
return *this;
}
void PcmData::reset() {
numChannels = -1;
sampleRate = -1;
bitsPerSample = -1;
containerSize = -1;
channelMask = -1;
endianness = -1;
numFrames = -1;
duration = -1.0f;
pcmBuffer = nullptr;
}
bool PcmData::isValid() const {
return numChannels > 0 && sampleRate > 0 && bitsPerSample > 0 && containerSize > 0 && numFrames > 0 && duration > 0 && pcmBuffer != nullptr;
}
ccstd::string PcmData::toString() const {
ccstd::string ret;
char buf[256] = {0};
snprintf(buf, sizeof(buf),
"numChannels: %d, sampleRate: %d, bitPerSample: %d, containerSize: %d, "
"channelMask: %d, endianness: %d, numFrames: %d, duration: %f",
numChannels, sampleRate, bitsPerSample, containerSize, channelMask, endianness,
numFrames, duration);
ret = buf;
return ret;
}
} // namespace cc

View File

@@ -0,0 +1,65 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
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 <stdio.h>
#include <memory>
#include "base/std/container/string.h"
#include "base/std/container/vector.h"
namespace cc {
struct PcmData {
std::shared_ptr<ccstd::vector<char>> pcmBuffer;
int numChannels;
int sampleRate;
int bitsPerSample;
int containerSize;
int channelMask;
int endianness;
int numFrames;
float duration; // in seconds
PcmData();
~PcmData();
PcmData(const PcmData &o);
PcmData(PcmData &&o);
PcmData &operator=(const PcmData &o);
PcmData &operator=(PcmData &&o);
void reset();
bool isValid() const;
ccstd::string toString() const;
};
} // namespace cc

View File

@@ -0,0 +1,86 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "Track"
#include "audio/android/Track.h"
#include "audio/android/cutils/log.h"
#include <math.h>
namespace cc {
Track::Track(const PcmData &pcmData)
: onStateChanged(nullptr), _pcmData(pcmData), _prevState(State::IDLE), _state(State::IDLE), _name(-1), _volume(1.0f), _isVolumeDirty(true), _isLoop(false), _isInitialized(false), _isAudioFocus(true) {
init(_pcmData.pcmBuffer->data(), _pcmData.numFrames, _pcmData.bitsPerSample / 8 * _pcmData.numChannels);
}
Track::~Track() {
ALOGV("~Track(): %p", this);
}
gain_minifloat_packed_t Track::getVolumeLR() {
float volume = _isAudioFocus ? _volume : 0.0f;
gain_minifloat_t v = gain_from_float(volume);
return gain_minifloat_pack(v, v);
}
bool Track::setPosition(float pos) {
_nextFrame = (size_t)(pos * _numFrames / _pcmData.duration);
_unrel = 0;
return true;
}
float Track::getPosition() const {
return _nextFrame * _pcmData.duration / _numFrames;
}
void Track::setVolume(float volume) {
std::lock_guard<std::mutex> lk(_volumeDirtyMutex);
if (fabs(_volume - volume) > 0.00001) {
_volume = volume;
setVolumeDirty(true);
}
}
float Track::getVolume() const {
return _volume;
}
void Track::setAudioFocus(bool isFocus) {
_isAudioFocus = isFocus;
setVolumeDirty(true);
}
void Track::setState(State state) {
std::lock_guard<std::mutex> lk(_stateMutex);
if (_state != state) {
_prevState = _state;
_state = state;
onStateChanged(_state);
}
};
} // namespace cc

101
cocos/audio/android/Track.h Normal file
View File

@@ -0,0 +1,101 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "audio/android/IVolumeProvider.h"
#include "audio/android/PcmBufferProvider.h"
#include "audio/android/PcmData.h"
#include <functional>
#include <mutex>
namespace cc {
class Track : public PcmBufferProvider, public IVolumeProvider {
public:
enum class State {
IDLE,
PLAYING,
RESUMED,
PAUSED,
STOPPED,
OVER,
DESTROYED
};
Track(const PcmData &pcmData);
virtual ~Track();
inline State getState() const { return _state; };
void setState(State state);
inline State getPrevState() const { return _prevState; };
inline bool isPlayOver() const { return _state == State::PLAYING && _nextFrame >= _numFrames; };
inline void setName(int name) { _name = name; };
inline int getName() const { return _name; };
void setVolume(float volume);
float getVolume() const;
void setAudioFocus(bool isFocus);
bool setPosition(float pos);
float getPosition() const;
virtual gain_minifloat_packed_t getVolumeLR() override;
inline void setLoop(bool isLoop) { _isLoop = isLoop; };
inline bool isLoop() const { return _isLoop; };
std::function<void(State)> onStateChanged;
private:
inline bool isVolumeDirty() const { return _isVolumeDirty; };
inline void setVolumeDirty(bool isDirty) { _isVolumeDirty = isDirty; };
inline bool isInitialized() const { return _isInitialized; };
inline void setInitialized(bool isInitialized) { _isInitialized = isInitialized; };
private:
PcmData _pcmData;
State _prevState;
State _state;
std::mutex _stateMutex;
int _name;
float _volume;
bool _isVolumeDirty;
std::mutex _volumeDirtyMutex;
bool _isLoop;
bool _isInitialized;
bool _isAudioFocus;
friend class AudioMixerController;
};
} // namespace cc

View File

@@ -0,0 +1,360 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#define LOG_TAG "UrlAudioPlayer"
#include "audio/android/UrlAudioPlayer.h"
#include "audio/android/ICallerThreadUtils.h"
#include "base/std/container/vector.h"
#include "base/Macros.h"
#include <cmath>
#include <algorithm> // for std::find
namespace {
std::mutex __playerContainerMutex;//NOLINT(bugprone-reserved-identifier,readability-identifier-naming)
ccstd::vector<cc::UrlAudioPlayer *> __playerContainer;//NOLINT(bugprone-reserved-identifier,readability-identifier-naming)
std::once_flag __onceFlag;//NOLINT(bugprone-reserved-identifier,readability-identifier-naming)
} // namespace
namespace cc {
class SLUrlAudioPlayerCallbackProxy {
public:
static void playEventCallback(SLPlayItf caller, void *context, SLuint32 playEvent) {
auto *thiz = reinterpret_cast<UrlAudioPlayer *>(context);
// We must use a mutex for the whole block of the following function invocation.
std::lock_guard<std::mutex> lk(__playerContainerMutex);
auto iter = std::find(__playerContainer.begin(), __playerContainer.end(), thiz);
if (iter != __playerContainer.end()) {
thiz->playEventCallback(caller, playEvent);
}
}
};
UrlAudioPlayer::UrlAudioPlayer(SLEngineItf engineItf, SLObjectItf outputMixObject, ICallerThreadUtils *callerThreadUtils)
: _engineItf(engineItf), _outputMixObj(outputMixObject), _callerThreadUtils(callerThreadUtils), _id(-1), _assetFd(nullptr), _playObj(nullptr), _playItf(nullptr), _seekItf(nullptr), _volumeItf(nullptr), _volume(0.0F), _duration(0.0F), _isLoop(false), _isAudioFocus(true), _state(State::INVALID), _playEventCallback(nullptr), _isDestroyed(std::make_shared<bool>(false)) {
std::call_once(__onceFlag, []() {
__playerContainer.reserve(10);
});
__playerContainerMutex.lock();
__playerContainer.push_back(this);
ALOGV("Current UrlAudioPlayer instance count: %d", (int)__playerContainer.size());
__playerContainerMutex.unlock();
_callerThreadId = callerThreadUtils->getCallerThreadId();
}
UrlAudioPlayer::~UrlAudioPlayer() {
ALOGV("~UrlAudioPlayer(): %p", this);
__playerContainerMutex.lock();
auto iter = std::find(__playerContainer.begin(), __playerContainer.end(), this);
if (iter != __playerContainer.end()) {
__playerContainer.erase(iter);
}
__playerContainerMutex.unlock();
}
void UrlAudioPlayer::playEventCallback(SLPlayItf caller, SLuint32 playEvent) {
CC_UNUSED_PARAM(caller);
// Note that it's on sub thread, please don't invoke OpenSLES API on sub thread
if (playEvent == SL_PLAYEVENT_HEADATEND) {
std::shared_ptr<bool> isDestroyed = _isDestroyed;
auto func = [this, isDestroyed]() {
// If it was destroyed, just return.
if (*isDestroyed) {
ALOGV("The UrlAudioPlayer (%p) was destroyed!", this);
return;
}
//Note that It's in the caller's thread (Cocos Thread)
// If state is already stopped, ignore the play over event.
if (_state == State::STOPPED) {
return;
}
//fix issue#8965:AudioEngine can't looping audio on Android 2.3.x
if (isLoop()) {
play();
} else {
setState(State::OVER);
if (_playEventCallback != nullptr) {
_playEventCallback(State::OVER);
}
ALOGV("UrlAudioPlayer (%p) played over, destroy self ...", this);
destroy();
delete this;
}
};
if (_callerThreadId == std::this_thread::get_id()) {
func();
} else {
_callerThreadUtils->performFunctionInCallerThread(func);
}
}
}
void UrlAudioPlayer::setPlayEventCallback(const PlayEventCallback &playEventCallback) {
_playEventCallback = playEventCallback;
}
void UrlAudioPlayer::stop() {
ALOGV("UrlAudioPlayer::stop (%p, %d)", this, getId());
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_STOPPED);
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::stop failed");
if (_state == State::PLAYING || _state == State::PAUSED) {
setLoop(false);
setState(State::STOPPED);
if (_playEventCallback != nullptr) {
_playEventCallback(State::STOPPED);
}
destroy();
delete this;
} else {
ALOGW("UrlAudioPlayer (%p, state:%d) isn't playing or paused, could not invoke stop!", this, static_cast<int>(_state));
}
}
void UrlAudioPlayer::pause() {
if (_state == State::PLAYING) {
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PAUSED);
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::pause failed");
setState(State::PAUSED);
} else {
ALOGW("UrlAudioPlayer (%p, state:%d) isn't playing, could not invoke pause!", this, static_cast<int>(_state));
}
}
void UrlAudioPlayer::resume() {
if (_state == State::PAUSED) {
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING);
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::resume failed");
setState(State::PLAYING);
} else {
ALOGW("UrlAudioPlayer (%p, state:%d) isn't paused, could not invoke resume!", this, static_cast<int>(_state));
}
}
void UrlAudioPlayer::play() {
if (_state == State::INITIALIZED || _state == State::PAUSED) {
SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING);
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::play failed");
setState(State::PLAYING);
} else {
ALOGW("UrlAudioPlayer (%p, state:%d) isn't paused or initialized, could not invoke play!", this, static_cast<int>(_state));
}
}
void UrlAudioPlayer::setVolumeToSLPlayer(float volume) {
int dbVolume = static_cast<int>(2000 * log10(volume));
if (dbVolume < SL_MILLIBEL_MIN) {
dbVolume = SL_MILLIBEL_MIN;
}
SLresult r = (*_volumeItf)->SetVolumeLevel(_volumeItf, dbVolume);
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::setVolumeToSLPlayer %d failed", dbVolume);
}
void UrlAudioPlayer::setVolume(float volume) {
_volume = volume;
if (_isAudioFocus) {
setVolumeToSLPlayer(_volume);
}
}
float UrlAudioPlayer::getVolume() const {
return _volume;
}
void UrlAudioPlayer::setAudioFocus(bool isFocus) {
_isAudioFocus = isFocus;
float volume = _isAudioFocus ? _volume : 0.0F;
setVolumeToSLPlayer(volume);
}
float UrlAudioPlayer::getDuration() const {
if (_duration > 0) {
return _duration;
}
SLmillisecond duration;
SLresult r = (*_playItf)->GetDuration(_playItf, &duration);
SL_RETURN_VAL_IF_FAILED(r, 0.0F, "UrlAudioPlayer::getDuration failed");
if (duration == SL_TIME_UNKNOWN) {
return -1.0F;
} else {// NOLINT(readability-else-after-return)
const_cast<UrlAudioPlayer *>(this)->_duration = duration / 1000.0F;
if (_duration <= 0) {
return -1.0F;
}
}
return _duration;
}
float UrlAudioPlayer::getPosition() const {
SLmillisecond millisecond;
SLresult r = (*_playItf)->GetPosition(_playItf, &millisecond);
SL_RETURN_VAL_IF_FAILED(r, 0.0F, "UrlAudioPlayer::getPosition failed");
return millisecond / 1000.0F;
}
bool UrlAudioPlayer::setPosition(float pos) {
SLmillisecond millisecond = 1000.0F * pos;
SLresult r = (*_seekItf)->SetPosition(_seekItf, millisecond, SL_SEEKMODE_ACCURATE);
SL_RETURN_VAL_IF_FAILED(r, false, "UrlAudioPlayer::setPosition %f failed", pos);
return true;
}
bool UrlAudioPlayer::prepare(const ccstd::string &url, SLuint32 locatorType, std::shared_ptr<AssetFd> assetFd, int start,
int length) {
_url = url;
_assetFd = std::move(assetFd);
#if CC_PLATFORM == CC_PLATFORM_ANDROID
const char *locatorTypeStr = "UNKNOWN";
if (locatorType == SL_DATALOCATOR_ANDROIDFD) {
locatorTypeStr = "SL_DATALOCATOR_ANDROIDFD";
} else if (locatorType == SL_DATALOCATOR_URI) {
locatorTypeStr = "SL_DATALOCATOR_URI";
} else {
ALOGE("Oops, invalid locatorType: %d", (int)locatorType);
return false;
}
ALOGV("UrlAudioPlayer::prepare: %s, %s, %d, %d, %d", _url.c_str(), locatorTypeStr, _assetFd->getFd(), start,
length);
SLDataSource audioSrc;
SLDataFormat_MIME formatMime = {SL_DATAFORMAT_MIME, nullptr, SL_CONTAINERTYPE_UNSPECIFIED};
audioSrc.pFormat = &formatMime;
//Note: locFd & locUri should be outside of the following if/else block
// Although locFd & locUri are only used inside if/else block, its lifecycle
// will be destroyed right after '}' block. And since we pass a pointer to
// 'audioSrc.pLocator=&locFd/&locUri', pLocator will point to an invalid address
// while invoking Engine::createAudioPlayer interface. So be care of change the position
// of these two variables.
SLDataLocator_AndroidFD locFd;
SLDataLocator_URI locUri;
if (locatorType == SL_DATALOCATOR_ANDROIDFD) {
locFd = {locatorType, _assetFd->getFd(), start, length};
audioSrc.pLocator = &locFd;
} else if (locatorType == SL_DATALOCATOR_URI) {
locUri = {locatorType, (SLchar *)_url.c_str()}; // NOLINT(google-readability-casting)
audioSrc.pLocator = &locUri;
ALOGV("locUri: locatorType: %d", (int)locUri.locatorType);
}
// configure audio sink
SLDataLocator_OutputMix locOutmix = {SL_DATALOCATOR_OUTPUTMIX, _outputMixObj};
SLDataSink audioSnk = {&locOutmix, nullptr};
// create audio player
const SLInterfaceID ids[3] = {SL_IID_SEEK, SL_IID_PREFETCHSTATUS, SL_IID_VOLUME};
const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
SLresult result = (*_engineItf)->CreateAudioPlayer(_engineItf, &_playObj, &audioSrc, &audioSnk, 3, ids, req);
SL_RETURN_VAL_IF_FAILED(result, false, "CreateAudioPlayer failed");
// realize the player
result = (*_playObj)->Realize(_playObj, SL_BOOLEAN_FALSE);
SL_RETURN_VAL_IF_FAILED(result, false, "Realize failed");
// get the play interface
result = (*_playObj)->GetInterface(_playObj, SL_IID_PLAY, &_playItf);
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_PLAY failed");
// get the seek interface
result = (*_playObj)->GetInterface(_playObj, SL_IID_SEEK, &_seekItf);
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_SEEK failed");
// get the volume interface
result = (*_playObj)->GetInterface(_playObj, SL_IID_VOLUME, &_volumeItf);
SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_VOLUME failed");
result = (*_playItf)->RegisterCallback(_playItf,
SLUrlAudioPlayerCallbackProxy::playEventCallback, this);
SL_RETURN_VAL_IF_FAILED(result, false, "RegisterCallback failed");
result = (*_playItf)->SetCallbackEventsMask(_playItf, SL_PLAYEVENT_HEADATEND);
SL_RETURN_VAL_IF_FAILED(result, false, "SetCallbackEventsMask SL_PLAYEVENT_HEADATEND failed");
setState(State::INITIALIZED);
setVolume(1.0F);
#endif
return true;
}
void UrlAudioPlayer::rewind() {
// Not supported currently. since cocos audio engine will new -> prepare -> play again.
}
void UrlAudioPlayer::setLoop(bool isLoop) {
_isLoop = isLoop;
SLboolean loopEnable = _isLoop ? SL_BOOLEAN_TRUE : SL_BOOLEAN_FALSE;
SLresult r = (*_seekItf)->SetLoop(_seekItf, loopEnable, 0, SL_TIME_UNKNOWN);
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::setLoop %d failed", _isLoop ? 1 : 0);
}
bool UrlAudioPlayer::isLoop() const {
return _isLoop;
}
void UrlAudioPlayer::stopAll() {
// To avoid break the for loop, we need to copy a new map
__playerContainerMutex.lock();
auto temp = __playerContainer;
__playerContainerMutex.unlock();
for (auto &&player : temp) {
player->stop();
}
}
void UrlAudioPlayer::destroy() {
if (!*_isDestroyed) {
*_isDestroyed = true;
ALOGV("UrlAudioPlayer::destroy() %p", this);
SL_DESTROY_OBJ(_playObj);
ALOGV("UrlAudioPlayer::destroy end");
}
}
} // namespace cc

View File

@@ -0,0 +1,127 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
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 <memory>
#include <mutex>
#include <thread>
#include "audio/android/AssetFd.h"
#include "audio/android/IAudioPlayer.h"
#include "audio/android/OpenSLHelper.h"
namespace cc {
class ICallerThreadUtils;
class AssetFd;
class UrlAudioPlayer : public IAudioPlayer {
public:
// Override Functions Begin
virtual int getId() const override { return _id; };
virtual void setId(int id) override { _id = id; };
virtual ccstd::string getUrl() const override { return _url; };
virtual State getState() const override { return _state; };
virtual void play() override;
virtual void pause() override;
virtual void resume() override;
virtual void stop() override;
virtual void rewind() override;
virtual void setVolume(float volume) override;
virtual float getVolume() const override;
virtual void setAudioFocus(bool isFocus) override;
virtual void setLoop(bool isLoop) override;
virtual bool isLoop() const override;
virtual float getDuration() const override;
virtual float getPosition() const override;
virtual bool setPosition(float pos) override;
virtual void setPlayEventCallback(const PlayEventCallback &playEventCallback) override;
// Override Functions EndOv
private:
UrlAudioPlayer(SLEngineItf engineItf, SLObjectItf outputMixObject, ICallerThreadUtils *callerThreadUtils);
virtual ~UrlAudioPlayer();
bool prepare(const ccstd::string &url, SLuint32 locatorType, std::shared_ptr<AssetFd> assetFd, int start, int length);
static void stopAll();
void destroy();
inline void setState(State state) { _state = state; };
void playEventCallback(SLPlayItf caller, SLuint32 playEvent);
void setVolumeToSLPlayer(float volume);
private:
SLEngineItf _engineItf;
SLObjectItf _outputMixObj;
ICallerThreadUtils *_callerThreadUtils;
int _id;
ccstd::string _url;
std::shared_ptr<AssetFd> _assetFd;
SLObjectItf _playObj;
SLPlayItf _playItf;
SLSeekItf _seekItf;
SLVolumeItf _volumeItf;
float _volume;
float _duration;
bool _isLoop;
bool _isAudioFocus;
State _state;
PlayEventCallback _playEventCallback;
std::thread::id _callerThreadId;
std::shared_ptr<bool> _isDestroyed;
friend class SLUrlAudioPlayerCallbackProxy;
friend class AudioPlayerProvider;
};
} // namespace cc

491
cocos/audio/android/audio.h Normal file
View File

@@ -0,0 +1,491 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
// ----------------------------------------------------------------------------
#include <stdint.h>
#include "audio/android/cutils/bitops.h"
#define PROPERTY_VALUE_MAX 256
#define CONSTEXPR constexpr
#ifdef __cplusplus
#define CC_LIKELY(exp) (__builtin_expect(!!(exp), true))
#define CC_UNLIKELY(exp) (__builtin_expect(!!(exp), false))
#else
#define CC_LIKELY(exp) (__builtin_expect(!!(exp), 1))
#define CC_UNLIKELY(exp) (__builtin_expect(!!(exp), 0))
#endif
/* special audio session values
* (XXX: should this be living in the audio effects land?)
*/
typedef enum {
/* session for effects attached to a particular output stream
* (value must be less than 0)
*/
AUDIO_SESSION_OUTPUT_STAGE = -1,
/* session for effects applied to output mix. These effects can
* be moved by audio policy manager to another output stream
* (value must be 0)
*/
AUDIO_SESSION_OUTPUT_MIX = 0,
/* application does not specify an explicit session ID to be used,
* and requests a new session ID to be allocated
* REFINE: use unique values for AUDIO_SESSION_OUTPUT_MIX and AUDIO_SESSION_ALLOCATE,
* after all uses have been updated from 0 to the appropriate symbol, and have been tested.
*/
AUDIO_SESSION_ALLOCATE = 0,
} audio_session_t;
/* Audio sub formats (see enum audio_format). */
/* PCM sub formats */
typedef enum {
/* All of these are in native byte order */
AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1, /* DO NOT CHANGE - PCM signed 16 bits */
AUDIO_FORMAT_PCM_SUB_8_BIT = 0x2, /* DO NOT CHANGE - PCM unsigned 8 bits */
AUDIO_FORMAT_PCM_SUB_32_BIT = 0x3, /* PCM signed .31 fixed point */
AUDIO_FORMAT_PCM_SUB_8_24_BIT = 0x4, /* PCM signed 8.23 fixed point */
AUDIO_FORMAT_PCM_SUB_FLOAT = 0x5, /* PCM single-precision floating point */
AUDIO_FORMAT_PCM_SUB_24_BIT_PACKED = 0x6, /* PCM signed .23 fixed point packed in 3 bytes */
} audio_format_pcm_sub_fmt_t;
/* The audio_format_*_sub_fmt_t declarations are not currently used */
/* MP3 sub format field definition : can use 11 LSBs in the same way as MP3
* frame header to specify bit rate, stereo mode, version...
*/
typedef enum {
AUDIO_FORMAT_MP3_SUB_NONE = 0x0,
} audio_format_mp3_sub_fmt_t;
/* AMR NB/WB sub format field definition: specify frame block interleaving,
* bandwidth efficient or octet aligned, encoding mode for recording...
*/
typedef enum {
AUDIO_FORMAT_AMR_SUB_NONE = 0x0,
} audio_format_amr_sub_fmt_t;
/* AAC sub format field definition: specify profile or bitrate for recording... */
typedef enum {
AUDIO_FORMAT_AAC_SUB_MAIN = 0x1,
AUDIO_FORMAT_AAC_SUB_LC = 0x2,
AUDIO_FORMAT_AAC_SUB_SSR = 0x4,
AUDIO_FORMAT_AAC_SUB_LTP = 0x8,
AUDIO_FORMAT_AAC_SUB_HE_V1 = 0x10,
AUDIO_FORMAT_AAC_SUB_SCALABLE = 0x20,
AUDIO_FORMAT_AAC_SUB_ERLC = 0x40,
AUDIO_FORMAT_AAC_SUB_LD = 0x80,
AUDIO_FORMAT_AAC_SUB_HE_V2 = 0x100,
AUDIO_FORMAT_AAC_SUB_ELD = 0x200,
} audio_format_aac_sub_fmt_t;
/* VORBIS sub format field definition: specify quality for recording... */
typedef enum {
AUDIO_FORMAT_VORBIS_SUB_NONE = 0x0,
} audio_format_vorbis_sub_fmt_t;
/* Audio format consists of a main format field (upper 8 bits) and a sub format
* field (lower 24 bits).
*
* The main format indicates the main codec type. The sub format field
* indicates options and parameters for each format. The sub format is mainly
* used for record to indicate for instance the requested bitrate or profile.
* It can also be used for certain formats to give informations not present in
* the encoded audio stream (e.g. octet alignment for AMR).
*/
typedef enum {
AUDIO_FORMAT_INVALID = 0xFFFFFFFFUL,
AUDIO_FORMAT_DEFAULT = 0,
AUDIO_FORMAT_PCM = 0x00000000UL, /* DO NOT CHANGE */
AUDIO_FORMAT_MP3 = 0x01000000UL,
AUDIO_FORMAT_AMR_NB = 0x02000000UL,
AUDIO_FORMAT_AMR_WB = 0x03000000UL,
AUDIO_FORMAT_AAC = 0x04000000UL,
AUDIO_FORMAT_HE_AAC_V1 = 0x05000000UL, /* Deprecated, Use AUDIO_FORMAT_AAC_HE_V1*/
AUDIO_FORMAT_HE_AAC_V2 = 0x06000000UL, /* Deprecated, Use AUDIO_FORMAT_AAC_HE_V2*/
AUDIO_FORMAT_VORBIS = 0x07000000UL,
AUDIO_FORMAT_OPUS = 0x08000000UL,
AUDIO_FORMAT_AC3 = 0x09000000UL,
AUDIO_FORMAT_E_AC3 = 0x0A000000UL,
AUDIO_FORMAT_DTS = 0x0B000000UL,
AUDIO_FORMAT_DTS_HD = 0x0C000000UL,
AUDIO_FORMAT_MAIN_MASK = 0xFF000000UL,
AUDIO_FORMAT_SUB_MASK = 0x00FFFFFFUL,
/* Aliases */
/* note != AudioFormat.ENCODING_PCM_16BIT */
AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM |
AUDIO_FORMAT_PCM_SUB_16_BIT),
/* note != AudioFormat.ENCODING_PCM_8BIT */
AUDIO_FORMAT_PCM_8_BIT = (AUDIO_FORMAT_PCM |
AUDIO_FORMAT_PCM_SUB_8_BIT),
AUDIO_FORMAT_PCM_32_BIT = (AUDIO_FORMAT_PCM |
AUDIO_FORMAT_PCM_SUB_32_BIT),
AUDIO_FORMAT_PCM_8_24_BIT = (AUDIO_FORMAT_PCM |
AUDIO_FORMAT_PCM_SUB_8_24_BIT),
AUDIO_FORMAT_PCM_FLOAT = (AUDIO_FORMAT_PCM |
AUDIO_FORMAT_PCM_SUB_FLOAT),
AUDIO_FORMAT_PCM_24_BIT_PACKED = (AUDIO_FORMAT_PCM |
AUDIO_FORMAT_PCM_SUB_24_BIT_PACKED),
AUDIO_FORMAT_AAC_MAIN = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_MAIN),
AUDIO_FORMAT_AAC_LC = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_LC),
AUDIO_FORMAT_AAC_SSR = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_SSR),
AUDIO_FORMAT_AAC_LTP = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_LTP),
AUDIO_FORMAT_AAC_HE_V1 = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_HE_V1),
AUDIO_FORMAT_AAC_SCALABLE = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_SCALABLE),
AUDIO_FORMAT_AAC_ERLC = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_ERLC),
AUDIO_FORMAT_AAC_LD = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_LD),
AUDIO_FORMAT_AAC_HE_V2 = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_HE_V2),
AUDIO_FORMAT_AAC_ELD = (AUDIO_FORMAT_AAC |
AUDIO_FORMAT_AAC_SUB_ELD),
} audio_format_t;
/* For the channel mask for position assignment representation */
enum {
/* These can be a complete audio_channel_mask_t. */
AUDIO_CHANNEL_NONE = 0x0,
AUDIO_CHANNEL_INVALID = 0xC0000000,
/* These can be the bits portion of an audio_channel_mask_t
* with representation AUDIO_CHANNEL_REPRESENTATION_POSITION.
* Using these bits as a complete audio_channel_mask_t is deprecated.
*/
/* output channels */
AUDIO_CHANNEL_OUT_FRONT_LEFT = 0x1,
AUDIO_CHANNEL_OUT_FRONT_RIGHT = 0x2,
AUDIO_CHANNEL_OUT_FRONT_CENTER = 0x4,
AUDIO_CHANNEL_OUT_LOW_FREQUENCY = 0x8,
AUDIO_CHANNEL_OUT_BACK_LEFT = 0x10,
AUDIO_CHANNEL_OUT_BACK_RIGHT = 0x20,
AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x40,
AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x80,
AUDIO_CHANNEL_OUT_BACK_CENTER = 0x100,
AUDIO_CHANNEL_OUT_SIDE_LEFT = 0x200,
AUDIO_CHANNEL_OUT_SIDE_RIGHT = 0x400,
AUDIO_CHANNEL_OUT_TOP_CENTER = 0x800,
AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT = 0x1000,
AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER = 0x2000,
AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT = 0x4000,
AUDIO_CHANNEL_OUT_TOP_BACK_LEFT = 0x8000,
AUDIO_CHANNEL_OUT_TOP_BACK_CENTER = 0x10000,
AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT = 0x20000,
/* REFINE: should these be considered complete channel masks, or only bits? */
AUDIO_CHANNEL_OUT_MONO = AUDIO_CHANNEL_OUT_FRONT_LEFT,
AUDIO_CHANNEL_OUT_STEREO = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
AUDIO_CHANNEL_OUT_FRONT_RIGHT),
AUDIO_CHANNEL_OUT_QUAD = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
AUDIO_CHANNEL_OUT_BACK_LEFT |
AUDIO_CHANNEL_OUT_BACK_RIGHT),
AUDIO_CHANNEL_OUT_QUAD_BACK = AUDIO_CHANNEL_OUT_QUAD,
/* like AUDIO_CHANNEL_OUT_QUAD_BACK with *_SIDE_* instead of *_BACK_* */
AUDIO_CHANNEL_OUT_QUAD_SIDE = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
AUDIO_CHANNEL_OUT_SIDE_LEFT |
AUDIO_CHANNEL_OUT_SIDE_RIGHT),
AUDIO_CHANNEL_OUT_5POINT1 = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
AUDIO_CHANNEL_OUT_FRONT_CENTER |
AUDIO_CHANNEL_OUT_LOW_FREQUENCY |
AUDIO_CHANNEL_OUT_BACK_LEFT |
AUDIO_CHANNEL_OUT_BACK_RIGHT),
AUDIO_CHANNEL_OUT_5POINT1_BACK = AUDIO_CHANNEL_OUT_5POINT1,
/* like AUDIO_CHANNEL_OUT_5POINT1_BACK with *_SIDE_* instead of *_BACK_* */
AUDIO_CHANNEL_OUT_5POINT1_SIDE = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
AUDIO_CHANNEL_OUT_FRONT_CENTER |
AUDIO_CHANNEL_OUT_LOW_FREQUENCY |
AUDIO_CHANNEL_OUT_SIDE_LEFT |
AUDIO_CHANNEL_OUT_SIDE_RIGHT),
// matches the correct AudioFormat.CHANNEL_OUT_7POINT1_SURROUND definition for 7.1
AUDIO_CHANNEL_OUT_7POINT1 = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
AUDIO_CHANNEL_OUT_FRONT_CENTER |
AUDIO_CHANNEL_OUT_LOW_FREQUENCY |
AUDIO_CHANNEL_OUT_BACK_LEFT |
AUDIO_CHANNEL_OUT_BACK_RIGHT |
AUDIO_CHANNEL_OUT_SIDE_LEFT |
AUDIO_CHANNEL_OUT_SIDE_RIGHT),
AUDIO_CHANNEL_OUT_ALL = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
AUDIO_CHANNEL_OUT_FRONT_RIGHT |
AUDIO_CHANNEL_OUT_FRONT_CENTER |
AUDIO_CHANNEL_OUT_LOW_FREQUENCY |
AUDIO_CHANNEL_OUT_BACK_LEFT |
AUDIO_CHANNEL_OUT_BACK_RIGHT |
AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER |
AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER |
AUDIO_CHANNEL_OUT_BACK_CENTER |
AUDIO_CHANNEL_OUT_SIDE_LEFT |
AUDIO_CHANNEL_OUT_SIDE_RIGHT |
AUDIO_CHANNEL_OUT_TOP_CENTER |
AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT |
AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER |
AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT |
AUDIO_CHANNEL_OUT_TOP_BACK_LEFT |
AUDIO_CHANNEL_OUT_TOP_BACK_CENTER |
AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT),
/* These are bits only, not complete values */
/* input channels */
AUDIO_CHANNEL_IN_LEFT = 0x4,
AUDIO_CHANNEL_IN_RIGHT = 0x8,
AUDIO_CHANNEL_IN_FRONT = 0x10,
AUDIO_CHANNEL_IN_BACK = 0x20,
AUDIO_CHANNEL_IN_LEFT_PROCESSED = 0x40,
AUDIO_CHANNEL_IN_RIGHT_PROCESSED = 0x80,
AUDIO_CHANNEL_IN_FRONT_PROCESSED = 0x100,
AUDIO_CHANNEL_IN_BACK_PROCESSED = 0x200,
AUDIO_CHANNEL_IN_PRESSURE = 0x400,
AUDIO_CHANNEL_IN_X_AXIS = 0x800,
AUDIO_CHANNEL_IN_Y_AXIS = 0x1000,
AUDIO_CHANNEL_IN_Z_AXIS = 0x2000,
AUDIO_CHANNEL_IN_VOICE_UPLINK = 0x4000,
AUDIO_CHANNEL_IN_VOICE_DNLINK = 0x8000,
/* REFINE: should these be considered complete channel masks, or only bits, or deprecated? */
AUDIO_CHANNEL_IN_MONO = AUDIO_CHANNEL_IN_FRONT,
AUDIO_CHANNEL_IN_STEREO = (AUDIO_CHANNEL_IN_LEFT | AUDIO_CHANNEL_IN_RIGHT),
AUDIO_CHANNEL_IN_FRONT_BACK = (AUDIO_CHANNEL_IN_FRONT | AUDIO_CHANNEL_IN_BACK),
AUDIO_CHANNEL_IN_ALL = (AUDIO_CHANNEL_IN_LEFT |
AUDIO_CHANNEL_IN_RIGHT |
AUDIO_CHANNEL_IN_FRONT |
AUDIO_CHANNEL_IN_BACK |
AUDIO_CHANNEL_IN_LEFT_PROCESSED |
AUDIO_CHANNEL_IN_RIGHT_PROCESSED |
AUDIO_CHANNEL_IN_FRONT_PROCESSED |
AUDIO_CHANNEL_IN_BACK_PROCESSED |
AUDIO_CHANNEL_IN_PRESSURE |
AUDIO_CHANNEL_IN_X_AXIS |
AUDIO_CHANNEL_IN_Y_AXIS |
AUDIO_CHANNEL_IN_Z_AXIS |
AUDIO_CHANNEL_IN_VOICE_UPLINK |
AUDIO_CHANNEL_IN_VOICE_DNLINK),
};
/* A channel mask per se only defines the presence or absence of a channel, not the order.
* But see AUDIO_INTERLEAVE_* below for the platform convention of order.
*
* audio_channel_mask_t is an opaque type and its internal layout should not
* be assumed as it may change in the future.
* Instead, always use the functions declared in this header to examine.
*
* These are the current representations:
*
* AUDIO_CHANNEL_REPRESENTATION_POSITION
* is a channel mask representation for position assignment.
* Each low-order bit corresponds to the spatial position of a transducer (output),
* or interpretation of channel (input).
* The user of a channel mask needs to know the context of whether it is for output or input.
* The constants AUDIO_CHANNEL_OUT_* or AUDIO_CHANNEL_IN_* apply to the bits portion.
* It is not permitted for no bits to be set.
*
* AUDIO_CHANNEL_REPRESENTATION_INDEX
* is a channel mask representation for index assignment.
* Each low-order bit corresponds to a selected channel.
* There is no platform interpretation of the various bits.
* There is no concept of output or input.
* It is not permitted for no bits to be set.
*
* All other representations are reserved for future use.
*
* Warning: current representation distinguishes between input and output, but this will not the be
* case in future revisions of the platform. Wherever there is an ambiguity between input and output
* that is currently resolved by checking the channel mask, the implementer should look for ways to
* fix it with additional information outside of the mask.
*/
typedef uint32_t audio_channel_mask_t;
/* Maximum number of channels for all representations */
#define AUDIO_CHANNEL_COUNT_MAX 30
/* log(2) of maximum number of representations, not part of public API */
#define AUDIO_CHANNEL_REPRESENTATION_LOG2 2
/* Representations */
typedef enum {
AUDIO_CHANNEL_REPRESENTATION_POSITION = 0, // must be zero for compatibility
// 1 is reserved for future use
AUDIO_CHANNEL_REPRESENTATION_INDEX = 2,
// 3 is reserved for future use
} audio_channel_representation_t;
/* The return value is undefined if the channel mask is invalid. */
static inline uint32_t audio_channel_mask_get_bits(audio_channel_mask_t channel) {
return channel & ((1 << AUDIO_CHANNEL_COUNT_MAX) - 1);
}
/* The return value is undefined if the channel mask is invalid. */
static inline audio_channel_representation_t audio_channel_mask_get_representation(
audio_channel_mask_t channel) {
// The right shift should be sufficient, but also "and" for safety in case mask is not 32 bits
return (audio_channel_representation_t)((channel >> AUDIO_CHANNEL_COUNT_MAX) & ((1 << AUDIO_CHANNEL_REPRESENTATION_LOG2) - 1));
}
/* Returns the number of channels from an output channel mask,
* used in the context of audio output or playback.
* If a channel bit is set which could _not_ correspond to an output channel,
* it is excluded from the count.
* Returns zero if the representation is invalid.
*/
static inline uint32_t audio_channel_count_from_out_mask(audio_channel_mask_t channel) {
uint32_t bits = audio_channel_mask_get_bits(channel);
switch (audio_channel_mask_get_representation(channel)) {
case AUDIO_CHANNEL_REPRESENTATION_POSITION:
// REFINE: We can now merge with from_in_mask and remove anding
bits &= AUDIO_CHANNEL_OUT_ALL;
// fall through
case AUDIO_CHANNEL_REPRESENTATION_INDEX:
return popcount(bits);
default:
return 0;
}
}
static inline bool audio_is_valid_format(audio_format_t format) {
switch (format & AUDIO_FORMAT_MAIN_MASK) {
case AUDIO_FORMAT_PCM:
switch (format) {
case AUDIO_FORMAT_PCM_16_BIT:
case AUDIO_FORMAT_PCM_8_BIT:
case AUDIO_FORMAT_PCM_32_BIT:
case AUDIO_FORMAT_PCM_8_24_BIT:
case AUDIO_FORMAT_PCM_FLOAT:
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
return true;
default:
return false;
}
/* not reached */
case AUDIO_FORMAT_MP3:
case AUDIO_FORMAT_AMR_NB:
case AUDIO_FORMAT_AMR_WB:
case AUDIO_FORMAT_AAC:
case AUDIO_FORMAT_HE_AAC_V1:
case AUDIO_FORMAT_HE_AAC_V2:
case AUDIO_FORMAT_VORBIS:
case AUDIO_FORMAT_OPUS:
case AUDIO_FORMAT_AC3:
case AUDIO_FORMAT_E_AC3:
case AUDIO_FORMAT_DTS:
case AUDIO_FORMAT_DTS_HD:
return true;
default:
return false;
}
}
static inline bool audio_is_linear_pcm(audio_format_t format) {
return ((format & AUDIO_FORMAT_MAIN_MASK) == AUDIO_FORMAT_PCM);
}
static inline size_t audio_bytes_per_sample(audio_format_t format) {
size_t size = 0;
switch (format) {
case AUDIO_FORMAT_PCM_32_BIT:
case AUDIO_FORMAT_PCM_8_24_BIT:
size = sizeof(int32_t);
break;
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
size = sizeof(uint8_t) * 3;
break;
case AUDIO_FORMAT_PCM_16_BIT:
size = sizeof(int16_t);
break;
case AUDIO_FORMAT_PCM_8_BIT:
size = sizeof(uint8_t);
break;
case AUDIO_FORMAT_PCM_FLOAT:
size = sizeof(float);
break;
default:
break;
}
return size;
}
/* Not part of public API */
static inline audio_channel_mask_t audio_channel_mask_from_representation_and_bits(
audio_channel_representation_t representation, uint32_t bits) {
return (audio_channel_mask_t)((representation << AUDIO_CHANNEL_COUNT_MAX) | bits);
}
/* Derive an output channel mask for position assignment from a channel count.
* This is to be used when the content channel mask is unknown. The 1, 2, 4, 5, 6, 7 and 8 channel
* cases are mapped to the standard game/home-theater layouts, but note that 4 is mapped to quad,
* and not stereo + FC + mono surround. A channel count of 3 is arbitrarily mapped to stereo + FC
* for continuity with stereo.
* Returns the matching channel mask,
* or AUDIO_CHANNEL_NONE if the channel count is zero,
* or AUDIO_CHANNEL_INVALID if the channel count exceeds that of the
* configurations for which a default output channel mask is defined.
*/
static inline audio_channel_mask_t audio_channel_out_mask_from_count(uint32_t channel_count) {
uint32_t bits;
switch (channel_count) {
case 0:
return AUDIO_CHANNEL_NONE;
case 1:
bits = AUDIO_CHANNEL_OUT_MONO;
break;
case 2:
bits = AUDIO_CHANNEL_OUT_STEREO;
break;
case 3:
bits = AUDIO_CHANNEL_OUT_STEREO | AUDIO_CHANNEL_OUT_FRONT_CENTER;
break;
case 4: // 4.0
bits = AUDIO_CHANNEL_OUT_QUAD;
break;
case 5: // 5.0
bits = AUDIO_CHANNEL_OUT_QUAD | AUDIO_CHANNEL_OUT_FRONT_CENTER;
break;
case 6: // 5.1
bits = AUDIO_CHANNEL_OUT_5POINT1;
break;
case 7: // 6.1
bits = AUDIO_CHANNEL_OUT_5POINT1 | AUDIO_CHANNEL_OUT_BACK_CENTER;
break;
case 8:
bits = AUDIO_CHANNEL_OUT_7POINT1;
break;
// IDEA: FCC_8
default:
return AUDIO_CHANNEL_INVALID;
}
return audio_channel_mask_from_representation_and_bits(
AUDIO_CHANNEL_REPRESENTATION_POSITION, bits);
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef COCOS_CUTILS_BITOPS_H
#define COCOS_CUTILS_BITOPS_H
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#if CC_PLATFORM == CC_PLATFORM_ANDROID
#include <sys/cdefs.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
static inline int popcount(unsigned int x) {
return __builtin_popcount(x);
}
static inline int popcountl(unsigned long x) {
return __builtin_popcountl(x);
}
static inline int popcountll(unsigned long long x) {
return __builtin_popcountll(x);
}
#ifdef __cplusplus
}
#endif
#endif /* COCOS_CUTILS_BITOPS_H */

View File

@@ -0,0 +1,597 @@
/*
* Copyright (C) 2005-2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// C/C++ logging functions. See the logging documentation for API details.
//
// We'd like these to be available from C code (in case we import some from
// somewhere), so this has a C interface.
//
// The output will be correct when the log file is shared between multiple
// threads and/or multiple processes so long as the operating system
// supports O_APPEND. These calls have mutex-protected data structures
// and so are NOT reentrant. Do not use LOG in a signal handler.
//
#ifndef COCOS_CUTILS_LOG_H
#define COCOS_CUTILS_LOG_H
#include <stdarg.h>
#include <stdio.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#if CC_PLATFORM == CC_PLATFORM_ANDROID
#include <android/log.h>
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
// TODO(qgh):May be implemented in later versions
// #include <Hilog/log.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
// ---------------------------------------------------------------------
/*
* Normally we strip ALOGV (VERBOSE messages) from release builds.
* You can modify this (for example with "#define LOG_NDEBUG 0"
* at the top of your source file) to change that behavior.
*/
#ifndef LOG_NDEBUG
#if defined(CC_DEBUG) && CC_DEBUG > 0
#define LOG_NDEBUG 0
#else
#define LOG_NDEBUG 1
#endif
#endif
/*
* This is the local tag used for the following simplified
* logging macros. You can change this preprocessor definition
* before using the other macros to change the tag.
*/
#ifndef LOG_TAG
#define LOG_TAG NULL
#endif
// ---------------------------------------------------------------------
#ifndef __predict_false
#define __predict_false(exp) __builtin_expect((exp) != 0, 0)
#endif
/*
* -DLINT_RLOG in sources that you want to enforce that all logging
* goes to the radio log buffer. If any logging goes to any of the other
* log buffers, there will be a compile or link error to highlight the
* problem. This is not a replacement for a full audit of the code since
* this only catches compiled code, not ifdef'd debug code. Options to
* defining this, either temporarily to do a spot check, or permanently
* to enforce, in all the communications trees; We have hopes to ensure
* that by supplying just the radio log buffer that the communications
* teams will have their one-stop shop for triaging issues.
*/
#ifndef LINT_RLOG
/*
* Simplified macro to send a verbose log message using the current LOG_TAG.
*/
#ifndef ALOGV
#define __ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
#if LOG_NDEBUG
#define ALOGV(...) \
do { \
if (0) { \
__ALOGV(__VA_ARGS__); \
} \
} while (0)
#else
#define ALOGV(...) __ALOGV(__VA_ARGS__)
#endif
#endif
#ifndef ALOGV_IF
#if LOG_NDEBUG
#define ALOGV_IF(cond, ...) ((void)0)
#else
#define ALOGV_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
: (void)0)
#endif
#endif
/*
* Simplified macro to send a debug log message using the current LOG_TAG.
*/
#ifndef ALOGD
#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#endif
#ifndef ALOGD_IF
#define ALOGD_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
: (void)0)
#endif
/*
* Simplified macro to send an info log message using the current LOG_TAG.
*/
#ifndef ALOGI
#define ALOGI(...) ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
#endif
#ifndef ALOGI_IF
#define ALOGI_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \
: (void)0)
#endif
/*
* Simplified macro to send a warning log message using the current LOG_TAG.
*/
#ifndef ALOGW
#define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
#endif
#ifndef ALOGW_IF
#define ALOGW_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \
: (void)0)
#endif
/*
* Simplified macro to send an error log message using the current LOG_TAG.
*/
#ifndef ALOGE
#define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
#endif
#ifndef ALOGE_IF
#define ALOGE_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
: (void)0)
#endif
// ---------------------------------------------------------------------
/*
* Conditional based on whether the current LOG_TAG is enabled at
* verbose priority.
*/
#ifndef IF_ALOGV
#if LOG_NDEBUG
#define IF_ALOGV() if (false)
#else
#define IF_ALOGV() IF_ALOG(LOG_VERBOSE, LOG_TAG)
#endif
#endif
/*
* Conditional based on whether the current LOG_TAG is enabled at
* debug priority.
*/
#ifndef IF_ALOGD
#define IF_ALOGD() IF_ALOG(LOG_DEBUG, LOG_TAG)
#endif
/*
* Conditional based on whether the current LOG_TAG is enabled at
* info priority.
*/
#ifndef IF_ALOGI
#define IF_ALOGI() IF_ALOG(LOG_INFO, LOG_TAG)
#endif
/*
* Conditional based on whether the current LOG_TAG is enabled at
* warn priority.
*/
#ifndef IF_ALOGW
#define IF_ALOGW() IF_ALOG(LOG_WARN, LOG_TAG)
#endif
/*
* Conditional based on whether the current LOG_TAG is enabled at
* error priority.
*/
#ifndef IF_ALOGE
#define IF_ALOGE() IF_ALOG(LOG_ERROR, LOG_TAG)
#endif
// ---------------------------------------------------------------------
/*
* Simplified macro to send a verbose system log message using the current LOG_TAG.
*/
#ifndef SLOGV
#define __SLOGV(...) \
((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
#if LOG_NDEBUG
#define SLOGV(...) \
do { \
if (0) { \
__SLOGV(__VA_ARGS__); \
} \
} while (0)
#else
#define SLOGV(...) __SLOGV(__VA_ARGS__)
#endif
#endif
#ifndef SLOGV_IF
#if LOG_NDEBUG
#define SLOGV_IF(cond, ...) ((void)0)
#else
#define SLOGV_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
: (void)0)
#endif
#endif
/*
* Simplified macro to send a debug system log message using the current LOG_TAG.
*/
#ifndef SLOGD
#define SLOGD(...) \
((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#endif
#ifndef SLOGD_IF
#define SLOGD_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
: (void)0)
#endif
/*
* Simplified macro to send an info system log message using the current LOG_TAG.
*/
#ifndef SLOGI
#define SLOGI(...) \
((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
#endif
#ifndef SLOGI_IF
#define SLOGI_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) \
: (void)0)
#endif
/*
* Simplified macro to send a warning system log message using the current LOG_TAG.
*/
#ifndef SLOGW
#define SLOGW(...) \
((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
#endif
#ifndef SLOGW_IF
#define SLOGW_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
: (void)0)
#endif
/*
* Simplified macro to send an error system log message using the current LOG_TAG.
*/
#ifndef SLOGE
#define SLOGE(...) \
((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
#endif
#ifndef SLOGE_IF
#define SLOGE_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
: (void)0)
#endif
#endif /* !LINT_RLOG */
// ---------------------------------------------------------------------
/*
* Simplified macro to send a verbose radio log message using the current LOG_TAG.
*/
#ifndef RLOGV
#define __RLOGV(...) \
((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
#if LOG_NDEBUG
#define RLOGV(...) \
do { \
if (0) { \
__RLOGV(__VA_ARGS__); \
} \
} while (0)
#else
#define RLOGV(...) __RLOGV(__VA_ARGS__)
#endif
#endif
#ifndef RLOGV_IF
#if LOG_NDEBUG
#define RLOGV_IF(cond, ...) ((void)0)
#else
#define RLOGV_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
: (void)0)
#endif
#endif
/*
* Simplified macro to send a debug radio log message using the current LOG_TAG.
*/
#ifndef RLOGD
#define RLOGD(...) \
((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#endif
#ifndef RLOGD_IF
#define RLOGD_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
: (void)0)
#endif
/*
* Simplified macro to send an info radio log message using the current LOG_TAG.
*/
#ifndef RLOGI
#define RLOGI(...) \
((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
#endif
#ifndef RLOGI_IF
#define RLOGI_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) \
: (void)0)
#endif
/*
* Simplified macro to send a warning radio log message using the current LOG_TAG.
*/
#ifndef RLOGW
#define RLOGW(...) \
((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
#endif
#ifndef RLOGW_IF
#define RLOGW_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
: (void)0)
#endif
/*
* Simplified macro to send an error radio log message using the current LOG_TAG.
*/
#ifndef RLOGE
#define RLOGE(...) \
((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
#endif
#ifndef RLOGE_IF
#define RLOGE_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
: (void)0)
#endif
// ---------------------------------------------------------------------
/*
* Log a fatal error. If the given condition fails, this stops program
* execution like a normal assertion, but also generating the given message.
* It is NOT stripped from release builds. Note that the condition test
* is -inverted- from the normal assert() semantics.
*/
#ifndef LOG_ALWAYS_FATAL_IF
#if CC_PLATFORM == CC_PLATFORM_ANDROID
#define LOG_ALWAYS_FATAL_IF(cond, ...) \
((__predict_false(cond)) \
? ((void)android_printAssert(#cond, LOG_TAG, ##__VA_ARGS__)) \
: (void)0)
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
#define LOG_ALWAYS_FATAL_IF(cond, ...)
#endif
#endif
#ifndef LOG_ALWAYS_FATAL
#if CC_PLATFORM == CC_PLATFORM_ANDROID
#define LOG_ALWAYS_FATAL(...) \
(((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__)))
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
#define LOG_ALWAYS_FATAL(...)
#endif
#endif
/*
* Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
* are stripped out of release builds.
*/
#if LOG_NDEBUG
#ifndef LOG_FATAL_IF
#define LOG_FATAL_IF(cond, ...) ((void)0)
#endif
#ifndef LOG_FATAL
#define LOG_FATAL(...) ((void)0)
#endif
#else
#ifndef LOG_FATAL_IF
#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__)
#endif
#ifndef LOG_FATAL
#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
#endif
#endif
/*
* Assertion that generates a log message when the assertion fails.
* Stripped out of release builds. Uses the current LOG_TAG.
*/
#ifndef ALOG_ASSERT
#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__)
//#define ALOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond)
#endif
// ---------------------------------------------------------------------
/*
* Basic log message macro.
*
* Example:
* ALOG(LOG_WARN, NULL, "Failed with error %d", errno);
*
* The second argument may be NULL or "" to indicate the "global" tag.
*/
#ifndef ALOG
#define ALOG(priority, tag, ...) \
LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
#endif
/*
* Log macro that allows you to specify a number for the priority.
*/
#ifndef LOG_PRI
#if CC_PLATFORM == CC_PLATFORM_ANDROID
#define LOG_PRI(priority, tag, ...) \
android_printLog(priority, tag, __VA_ARGS__)
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
#define LOG_PRI(priority, tag, ...) ((void)0)
#endif
#endif
/*
* Log macro that allows you to pass in a varargs ("args" is a va_list).
*/
#ifndef LOG_PRI_VA
#define LOG_PRI_VA(priority, tag, fmt, args) \
android_vprintLog(priority, NULL, tag, fmt, args)
#endif
/*
* Conditional given a desired logging priority and tag.
*/
#ifndef IF_ALOG
#define IF_ALOG(priority, tag) \
if (android_testLog(ANDROID_##priority, tag))
#endif
// ---------------------------------------------------------------------
/*
* ===========================================================================
*
* The stuff in the rest of this file should not be used directly.
*/
#define android_printLog(prio, tag, ...) \
__android_log_print(prio, tag, __VA_ARGS__)
#define android_vprintLog(prio, cond, tag, ...) \
__android_log_vprint(prio, tag, __VA_ARGS__)
/* XXX Macros to work around syntax errors in places where format string
* arg is not passed to ALOG_ASSERT, LOG_ALWAYS_FATAL or LOG_ALWAYS_FATAL_IF
* (happens only in debug builds).
*/
/* Returns 2nd arg. Used to substitute default value if caller's vararg list
* is empty.
*/
#define __android_second(dummy, second, ...) second
/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise
* returns nothing.
*/
#define __android_rest(first, ...) , ##__VA_ARGS__
#define android_printAssert(cond, tag, ...) \
__android_log_assert(cond, tag, \
__android_second(0, ##__VA_ARGS__, NULL) __android_rest(__VA_ARGS__))
#define android_writeLog(prio, tag, text) \
__android_log_write(prio, tag, text)
#define android_bWriteLog(tag, payload, len) \
__android_log_bwrite(tag, payload, len)
#define android_btWriteLog(tag, type, payload, len) \
__android_log_btwrite(tag, type, payload, len)
#define android_errorWriteLog(tag, subTag) \
__android_log_error_write(tag, subTag, -1, NULL, 0)
#define android_errorWriteWithInfoLog(tag, subTag, uid, data, dataLen) \
__android_log_error_write(tag, subTag, uid, data, dataLen)
/*
* IF_ALOG uses android_testLog, but IF_ALOG can be overridden.
* android_testLog will remain constant in its purpose as a wrapper
* for Android logging filter policy, and can be subject to
* change. It can be reused by the developers that override
* IF_ALOG as a convenient means to reimplement their policy
* over Android.
*/
#if LOG_NDEBUG /* Production */
#define android_testLog(prio, tag) \
(__android_log_is_loggable(prio, tag, ANDROID_LOG_DEBUG) != 0)
#else
#define android_testLog(prio, tag) \
(__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE) != 0)
#endif
/*
* Use the per-tag properties "log.tag.<tagname>" to generate a runtime
* result of non-zero to expose a log. prio is ANDROID_LOG_VERBOSE to
* ANDROID_LOG_FATAL. default_prio if no property. Undefined behavior if
* any other value.
*/
int __android_log_is_loggable(int prio, const char *tag, int default_prio);
int __android_log_security(); /* Device Owner is present */
int __android_log_error_write(int tag, const char *subTag, int32_t uid, const char *data,
uint32_t dataLen);
/*
* Send a simple string to the log.
*/
int __android_log_buf_write(int bufID, int prio, const char *tag, const char *text);
int __android_log_buf_print(int bufID, int prio, const char *tag, const char *fmt, ...)
#if defined(__GNUC__)
__attribute__((__format__(printf, 4, 5)))
#endif
;
#ifdef __cplusplus
}
#endif
#endif /* COCOS_CUTILS_LOG_H */

View File

@@ -0,0 +1,525 @@
/*
* Copyright (C) 2014 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#define LOG_TAG "mp3reader"
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h> // Resolves that memset, memcpy aren't found while APP_PLATFORM >= 22 on Android
#include <vector>
#include "audio/android/cutils/log.h"
#include "audio/android/mp3reader.h"
#include "pvmp3decoder_api.h"
static uint32_t U32_AT(const uint8_t *ptr) {
return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
}
static bool parseHeader(
uint32_t header, size_t *frame_size,
uint32_t *out_sampling_rate = NULL, uint32_t *out_channels = NULL,
uint32_t *out_bitrate = NULL, uint32_t *out_num_samples = NULL) {
*frame_size = 0;
if (out_sampling_rate) {
*out_sampling_rate = 0;
}
if (out_channels) {
*out_channels = 0;
}
if (out_bitrate) {
*out_bitrate = 0;
}
if (out_num_samples) {
*out_num_samples = 1152;
}
if ((header & 0xffe00000) != 0xffe00000) {
return false;
}
unsigned version = (header >> 19) & 3;
if (version == 0x01) {
return false;
}
unsigned layer = (header >> 17) & 3;
if (layer == 0x00) {
return false;
}
unsigned bitrate_index = (header >> 12) & 0x0f;
if (bitrate_index == 0 || bitrate_index == 0x0f) {
// Disallow "free" bitrate.
return false;
}
unsigned sampling_rate_index = (header >> 10) & 3;
if (sampling_rate_index == 3) {
return false;
}
static const int kSamplingRateV1[] = {44100, 48000, 32000};
int sampling_rate = kSamplingRateV1[sampling_rate_index];
if (version == 2 /* V2 */) {
sampling_rate /= 2;
} else if (version == 0 /* V2.5 */) {
sampling_rate /= 4;
}
unsigned padding = (header >> 9) & 1;
if (layer == 3) {
// layer I
static const int kBitrateV1[] = {
32, 64, 96, 128, 160, 192, 224, 256,
288, 320, 352, 384, 416, 448};
static const int kBitrateV2[] = {
32, 48, 56, 64, 80, 96, 112, 128,
144, 160, 176, 192, 224, 256};
int bitrate =
(version == 3 /* V1 */)
? kBitrateV1[bitrate_index - 1]
: kBitrateV2[bitrate_index - 1];
if (out_bitrate) {
*out_bitrate = bitrate;
}
*frame_size = (12000 * bitrate / sampling_rate + padding) * 4;
if (out_num_samples) {
*out_num_samples = 384;
}
} else {
// layer II or III
static const int kBitrateV1L2[] = {
32, 48, 56, 64, 80, 96, 112, 128,
160, 192, 224, 256, 320, 384};
static const int kBitrateV1L3[] = {
32, 40, 48, 56, 64, 80, 96, 112,
128, 160, 192, 224, 256, 320};
static const int kBitrateV2[] = {
8, 16, 24, 32, 40, 48, 56, 64,
80, 96, 112, 128, 144, 160};
int bitrate;
if (version == 3 /* V1 */) {
bitrate = (layer == 2 /* L2 */)
? kBitrateV1L2[bitrate_index - 1]
: kBitrateV1L3[bitrate_index - 1];
if (out_num_samples) {
*out_num_samples = 1152;
}
} else {
// V2 (or 2.5)
bitrate = kBitrateV2[bitrate_index - 1];
if (out_num_samples) {
*out_num_samples = (layer == 1 /* L3 */) ? 576 : 1152;
}
}
if (out_bitrate) {
*out_bitrate = bitrate;
}
if (version == 3 /* V1 */) {
*frame_size = 144000 * bitrate / sampling_rate + padding;
} else {
// V2 or V2.5
size_t tmp = (layer == 1 /* L3 */) ? 72000 : 144000;
*frame_size = tmp * bitrate / sampling_rate + padding;
}
}
if (out_sampling_rate) {
*out_sampling_rate = sampling_rate;
}
if (out_channels) {
int channel_mode = (header >> 6) & 3;
*out_channels = (channel_mode == 3) ? 1 : 2;
}
return true;
}
// Mask to extract the version, layer, sampling rate parts of the MP3 header,
// which should be same for all MP3 frames.
static const uint32_t kMask = 0xfffe0c00;
static ssize_t sourceReadAt(mp3_callbacks *callback, void *source, off64_t offset, void *data, size_t size) {
int retVal = callback->seek(source, offset, SEEK_SET);
if (retVal != EXIT_SUCCESS) {
return 0;
} else {
return callback->read(data, 1, size, source);
}
}
// Resync to next valid MP3 frame in the file.
static bool resync(
mp3_callbacks *callback, void *source, uint32_t match_header,
off64_t *inout_pos, uint32_t *out_header) {
if (*inout_pos == 0) {
// Skip an optional ID3 header if syncing at the very beginning
// of the datasource.
for (;;) {
uint8_t id3header[10];
int retVal = sourceReadAt(callback, source, *inout_pos, id3header,
sizeof(id3header));
if (retVal < (ssize_t)sizeof(id3header)) {
// If we can't even read these 10 bytes, we might as well bail
// out, even if there _were_ 10 bytes of valid mp3 audio data...
return false;
}
if (memcmp("ID3", id3header, 3)) {
break;
}
// Skip the ID3v2 header.
size_t len =
((id3header[6] & 0x7f) << 21) | ((id3header[7] & 0x7f) << 14) | ((id3header[8] & 0x7f) << 7) | (id3header[9] & 0x7f);
len += 10;
*inout_pos += len;
ALOGV("skipped ID3 tag, new starting offset is %lld (0x%016llx)",
(long long)*inout_pos, (long long)*inout_pos);
}
}
off64_t pos = *inout_pos;
bool valid = false;
const int32_t kMaxReadBytes = 1024;
const int32_t kMaxBytesChecked = 128 * 1024;
uint8_t buf[kMaxReadBytes];
ssize_t bytesToRead = kMaxReadBytes;
ssize_t totalBytesRead = 0;
ssize_t remainingBytes = 0;
bool reachEOS = false;
uint8_t *tmp = buf;
do {
if (pos >= (off64_t)(*inout_pos + kMaxBytesChecked)) {
// Don't scan forever.
ALOGV("giving up at offset %lld", (long long)pos);
break;
}
if (remainingBytes < 4) {
if (reachEOS) {
break;
} else {
memcpy(buf, tmp, remainingBytes);
bytesToRead = kMaxReadBytes - remainingBytes;
/*
* The next read position should start from the end of
* the last buffer, and thus should include the remaining
* bytes in the buffer.
*/
totalBytesRead = sourceReadAt(callback, source, pos + remainingBytes,
buf + remainingBytes, bytesToRead);
if (totalBytesRead <= 0) {
break;
}
reachEOS = (totalBytesRead != bytesToRead);
remainingBytes += totalBytesRead;
tmp = buf;
continue;
}
}
uint32_t header = U32_AT(tmp);
if (match_header != 0 && (header & kMask) != (match_header & kMask)) {
++pos;
++tmp;
--remainingBytes;
continue;
}
size_t frame_size;
uint32_t sample_rate, num_channels, bitrate;
if (!parseHeader(
header, &frame_size,
&sample_rate, &num_channels, &bitrate)) {
++pos;
++tmp;
--remainingBytes;
continue;
}
// ALOGV("found possible 1st frame at %lld (header = 0x%08x)", (long long)pos, header);
// We found what looks like a valid frame,
// now find its successors.
off64_t test_pos = pos + frame_size;
valid = true;
const int FRAME_MATCH_REQUIRED = 3;
for (int j = 0; j < FRAME_MATCH_REQUIRED; ++j) {
uint8_t tmp[4];
ssize_t retval = sourceReadAt(callback, source, test_pos, tmp, sizeof(tmp));
if (retval < (ssize_t)sizeof(tmp)) {
valid = false;
break;
}
uint32_t test_header = U32_AT(tmp);
ALOGV("subsequent header is %08x", test_header);
if ((test_header & kMask) != (header & kMask)) {
valid = false;
break;
}
size_t test_frame_size;
if (!parseHeader(test_header, &test_frame_size)) {
valid = false;
break;
}
ALOGV("found subsequent frame #%d at %lld", j + 2, (long long)test_pos);
test_pos += test_frame_size;
}
if (valid) {
*inout_pos = pos;
if (out_header != NULL) {
*out_header = header;
}
} else {
ALOGV("no dice, no valid sequence of frames found.");
}
++pos;
++tmp;
--remainingBytes;
} while (!valid);
return valid;
}
Mp3Reader::Mp3Reader() : mSource(NULL), mCallback(NULL) {
}
// Initialize the MP3 reader.
bool Mp3Reader::init(mp3_callbacks *callback, void *source) {
mSource = source;
mCallback = callback;
// Open the file.
// mFp = fopen(file, "rb");
// if (mFp == NULL) return false;
// Sync to the first valid frame.
off64_t pos = 0;
uint32_t header;
bool success = resync(callback, source, 0 /*match_header*/, &pos, &header);
if (!success) {
ALOGE("%s, resync failed", __FUNCTION__);
return false;
}
mCurrentPos = pos;
mFixedHeader = header;
size_t frame_size;
return parseHeader(header, &frame_size, &mSampleRate,
&mNumChannels, &mBitrate);
}
// Get the next valid MP3 frame.
bool Mp3Reader::getFrame(void *buffer, uint32_t *size) {
size_t frame_size;
uint32_t bitrate;
uint32_t num_samples;
uint32_t sample_rate;
for (;;) {
ssize_t n = sourceReadAt(mCallback, mSource, mCurrentPos, buffer, 4);
if (n < 4) {
return false;
}
uint32_t header = U32_AT((const uint8_t *)buffer);
if ((header & kMask) == (mFixedHeader & kMask) && parseHeader(
header, &frame_size, &sample_rate, NULL /*out_channels*/,
&bitrate, &num_samples)) {
break;
}
// Lost sync.
off64_t pos = mCurrentPos;
if (!resync(mCallback, mSource, mFixedHeader, &pos, NULL /*out_header*/)) {
// Unable to resync. Signalling end of stream.
return false;
}
mCurrentPos = pos;
// Try again with the new position.
}
ssize_t n = sourceReadAt(mCallback, mSource, mCurrentPos, buffer, frame_size);
if (n < (ssize_t)frame_size) {
return false;
}
*size = frame_size;
mCurrentPos += frame_size;
return true;
}
// Close the MP3 reader.
void Mp3Reader::close() {
assert(mCallback != NULL);
mCallback->close(mSource);
}
Mp3Reader::~Mp3Reader() {
}
enum {
kInputBufferSize = 10 * 1024,
kOutputBufferSize = 4608 * 2,
};
int decodeMP3(mp3_callbacks *cb, void *source, std::vector<char> &pcmBuffer, int *numChannels, int *sampleRate, int *numFrames) {
// Initialize the config.
tPVMP3DecoderExternal config;
config.equalizerType = flat;
config.crcEnabled = false;
// Allocate the decoder memory.
uint32_t memRequirements = pvmp3_decoderMemRequirements();
void *decoderBuf = malloc(memRequirements);
assert(decoderBuf != NULL);
// Initialize the decoder.
pvmp3_InitDecoder(&config, decoderBuf);
// Open the input file.
Mp3Reader mp3Reader;
bool success = mp3Reader.init(cb, source);
if (!success) {
ALOGE("mp3Reader.init: Encountered error reading\n");
free(decoderBuf);
return EXIT_FAILURE;
}
// Open the output file.
// SF_INFO sfInfo;
// memset(&sfInfo, 0, sizeof(SF_INFO));
// sfInfo.channels = mp3Reader.getNumChannels();
// sfInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
// sfInfo.samplerate = mp3Reader.getSampleRate();
// SNDFILE *handle = sf_open(argv[2], SFM_WRITE, &sfInfo);
// if (handle == NULL) {
// ALOGE("Encountered error writing %s\n", argv[2]);
// mp3Reader.close();
// free(decoderBuf);
// return EXIT_FAILURE;
// }
// Allocate input buffer.
uint8_t *inputBuf = static_cast<uint8_t *>(malloc(kInputBufferSize));
assert(inputBuf != NULL);
// Allocate output buffer.
int16_t *outputBuf = static_cast<int16_t *>(malloc(kOutputBufferSize));
assert(outputBuf != NULL);
// Decode loop.
int retVal = EXIT_SUCCESS;
while (1) {
// Read input from the file.
uint32_t bytesRead;
bool success = mp3Reader.getFrame(inputBuf, &bytesRead);
if (!success) break;
*numChannels = mp3Reader.getNumChannels();
*sampleRate = mp3Reader.getSampleRate();
// Set the input config.
config.inputBufferCurrentLength = bytesRead;
config.inputBufferMaxLength = 0;
config.inputBufferUsedLength = 0;
config.pInputBuffer = inputBuf;
config.pOutputBuffer = outputBuf;
config.outputFrameSize = kOutputBufferSize / sizeof(int16_t);
ERROR_CODE decoderErr;
decoderErr = pvmp3_framedecoder(&config, decoderBuf);
if (decoderErr != NO_DECODING_ERROR) {
ALOGE("Decoder encountered error=%d", decoderErr);
retVal = EXIT_FAILURE;
break;
}
pcmBuffer.insert(pcmBuffer.end(), (char *)outputBuf, ((char *)outputBuf) + config.outputFrameSize * 2);
*numFrames += config.outputFrameSize / mp3Reader.getNumChannels();
}
// Close input reader and output writer.
mp3Reader.close();
// sf_close(handle);
// Free allocated memory.
free(inputBuf);
free(outputBuf);
free(decoderBuf);
return retVal;
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2014 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef MP3READER_H_
#define MP3READER_H_
typedef struct {
size_t (*read)(void *ptr, size_t size, size_t nmemb, void *datasource);
int (*seek)(void *datasource, int64_t offset, int whence);
int (*close)(void *datasource);
long (*tell)(void *datasource);
} mp3_callbacks;
class Mp3Reader {
public:
Mp3Reader();
bool init(mp3_callbacks *callback, void *source);
bool getFrame(void *buffer, uint32_t *size);
uint32_t getSampleRate() { return mSampleRate; }
uint32_t getNumChannels() { return mNumChannels; }
void close();
~Mp3Reader();
private:
void *mSource;
mp3_callbacks *mCallback;
uint32_t mFixedHeader;
off64_t mCurrentPos;
uint32_t mSampleRate;
uint32_t mNumChannels;
uint32_t mBitrate;
};
int decodeMP3(mp3_callbacks *cb, void *source, std::vector<char> &pcmBuffer, int *numChannels, int *sampleRate, int *numFrames);
#endif /* MP3READER_H_ */

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef COCOS_LIB_UTILS_COMPAT_H
#define COCOS_LIB_UTILS_COMPAT_H
#if CC_PLATFORM == CC_PLATFORM_ANDROID
#include <unistd.h>
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#elif CC_PLATFORM == CC_PLATFORM_WINDOWS
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
#include <SLES/OpenSLES_OpenHarmony.h>
#include <SLES/OpenSLES_Platform.h>
#endif
#if defined(__APPLE__)
/* Mac OS has always had a 64-bit off_t, so it doesn't have off64_t. */
typedef off_t off64_t;
static inline off64_t lseek64(int fd, off64_t offset, int whence) {
return lseek(fd, offset, whence);
}
static inline ssize_t pread64(int fd, void *buf, size_t nbytes, off64_t offset) {
return pread(fd, buf, nbytes, offset);
}
static inline ssize_t pwrite64(int fd, const void *buf, size_t nbytes, off64_t offset) {
return pwrite(fd, buf, nbytes, offset);
}
#endif /* __APPLE__ */
#if defined(_WIN32)
#define O_CLOEXEC O_NOINHERIT
#define O_NOFOLLOW 0
#define DEFFILEMODE 0666
#endif /* _WIN32 */
#if defined(_WIN32)
#define ZD "%ld"
#define ZD_TYPE long
#else
#define ZD "%zd"
#define ZD_TYPE ssize_t
#endif
/*
* Needed for cases where something should be constexpr if possible, but not
* being constexpr is fine if in pre-C++11 code (such as a const static float
* member variable).
*/
#if __cplusplus >= 201103L
#define CONSTEXPR constexpr
#else
#define CONSTEXPR
#endif
/*
* TEMP_FAILURE_RETRY is defined by some, but not all, versions of
* <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
* not already defined, then define it here.
*/
#ifndef TEMP_FAILURE_RETRY
/* Used to retry syscalls that can return EINTR. */
#define TEMP_FAILURE_RETRY(exp) ({ \
typeof (exp) _rc; \
do { \
_rc = (exp); \
} while (_rc == -1 && errno == EINTR); \
_rc; })
#endif
#if defined(_WIN32)
#define OS_PATH_SEPARATOR '\\'
#else
#define OS_PATH_SEPARATOR '/'
#endif
#if CC_PLATFORM == CC_PLATFORM_ANDROID
typedef SLAndroidSimpleBufferQueueItf CCSLBufferQueueItf;
#define CC_SL_IDD_BUFFER_QUEUE SL_IID_ANDROIDSIMPLEBUFFERQUEUE
#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY
typedef SLOHBufferQueueItf CCSLBufferQueueItf;
#define CC_SL_IDD_BUFFER_QUEUE SL_IID_OH_BUFFERQUEUE
#define __unused
#endif
#endif /* COCOS_LIB_UTILS_COMPAT_H */

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef COCOS_ERRORS_H
#define COCOS_ERRORS_H
#include <sys/types.h>
#include <errno.h>
namespace cc {
// use this type to return error codes
#ifdef _WIN32
typedef int status_t;
#else
typedef int32_t status_t;
#endif
/* the MS C runtime lacks a few error codes */
/*
* Error codes.
* All error codes are negative values.
*/
// Win32 #defines NO_ERROR as well. It has the same value, so there's no
// real conflict, though it's a bit awkward.
#ifdef _WIN32
#undef NO_ERROR
#endif
enum {
OK = 0, // Everything's swell.
NO_ERROR = 0, // No errors.
UNKNOWN_ERROR = (-2147483647 - 1), // INT32_MIN value
NO_MEMORY = -ENOMEM,
INVALID_OPERATION = -ENOSYS,
BAD_VALUE = -EINVAL,
BAD_TYPE = (UNKNOWN_ERROR + 1),
NAME_NOT_FOUND = -ENOENT,
PERMISSION_DENIED = -EPERM,
NO_INIT = -ENODEV,
ALREADY_EXISTS = -EEXIST,
DEAD_OBJECT = -EPIPE,
FAILED_TRANSACTION = (UNKNOWN_ERROR + 2),
#if !defined(_WIN32)
BAD_INDEX = -EOVERFLOW,
NOT_ENOUGH_DATA = -ENODATA,
WOULD_BLOCK = -EWOULDBLOCK,
TIMED_OUT = -ETIMEDOUT,
UNKNOWN_TRANSACTION = -EBADMSG,
#else
BAD_INDEX = -E2BIG,
NOT_ENOUGH_DATA = (UNKNOWN_ERROR + 3),
WOULD_BLOCK = (UNKNOWN_ERROR + 4),
TIMED_OUT = (UNKNOWN_ERROR + 5),
UNKNOWN_TRANSACTION = (UNKNOWN_ERROR + 6),
#endif
FDS_NOT_ALLOWED = (UNKNOWN_ERROR + 7),
UNEXPECTED_NULL = (UNKNOWN_ERROR + 8),
};
// Restore define; enumeration is in "android" namespace, so the value defined
// there won't work for Win32 code in a different namespace.
#ifdef _WIN32
#define NO_ERROR 0L
#endif
} // namespace cc
// ---------------------------------------------------------------------------
#endif // COCOS_ERRORS_H

View File

@@ -0,0 +1,34 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
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/android/utils/Utils.h"
#include "platform/BasePlatform.h"
namespace cc {
int getSDKVersion() {
return BasePlatform::getPlatform()->getSdkVersion();
}
} // end of namespace cc

View File

@@ -0,0 +1,31 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
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 "base/std/container/string.h"
namespace cc {
extern int getSDKVersion();
}