no message
This commit is contained in:
45
cocos/audio/android/AssetFd.cpp
Normal file
45
cocos/audio/android/AssetFd.cpp
Normal 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
|
||||
42
cocos/audio/android/AssetFd.h
Normal file
42
cocos/audio/android/AssetFd.h
Normal 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
|
||||
77
cocos/audio/android/AudioBufferProvider.h
Normal file
77
cocos/audio/android/AudioBufferProvider.h
Normal 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
|
||||
260
cocos/audio/android/AudioDecoder.cpp
Normal file
260
cocos/audio/android/AudioDecoder.cpp
Normal 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
|
||||
61
cocos/audio/android/AudioDecoder.h
Normal file
61
cocos/audio/android/AudioDecoder.h
Normal 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
|
||||
75
cocos/audio/android/AudioDecoderMp3.cpp
Normal file
75
cocos/audio/android/AudioDecoderMp3.cpp
Normal 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
|
||||
41
cocos/audio/android/AudioDecoderMp3.h
Normal file
41
cocos/audio/android/AudioDecoderMp3.h
Normal 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
|
||||
101
cocos/audio/android/AudioDecoderOgg.cpp
Normal file
101
cocos/audio/android/AudioDecoderOgg.cpp
Normal 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, ¤tSection);
|
||||
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
|
||||
44
cocos/audio/android/AudioDecoderOgg.h
Normal file
44
cocos/audio/android/AudioDecoderOgg.h
Normal 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
|
||||
78
cocos/audio/android/AudioDecoderProvider.cpp
Normal file
78
cocos/audio/android/AudioDecoderProvider.cpp
Normal 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
|
||||
40
cocos/audio/android/AudioDecoderProvider.h
Normal file
40
cocos/audio/android/AudioDecoderProvider.h
Normal 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
|
||||
588
cocos/audio/android/AudioDecoderSLES.cpp
Normal file
588
cocos/audio/android/AudioDecoderSLES.cpp
Normal 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
|
||||
96
cocos/audio/android/AudioDecoderSLES.h
Normal file
96
cocos/audio/android/AudioDecoderSLES.h
Normal 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
|
||||
106
cocos/audio/android/AudioDecoderWav.cpp
Normal file
106
cocos/audio/android/AudioDecoderWav.cpp
Normal 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
|
||||
46
cocos/audio/android/AudioDecoderWav.h
Normal file
46
cocos/audio/android/AudioDecoderWav.h
Normal 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
|
||||
507
cocos/audio/android/AudioEngine-inl.cpp
Normal file
507
cocos/audio/android/AudioEngine-inl.cpp
Normal 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;
|
||||
}
|
||||
105
cocos/audio/android/AudioEngine-inl.h
Normal file
105
cocos/audio/android/AudioEngine-inl.h
Normal 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
|
||||
2053
cocos/audio/android/AudioMixer.cpp
Normal file
2053
cocos/audio/android/AudioMixer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
379
cocos/audio/android/AudioMixer.h
Normal file
379
cocos/audio/android/AudioMixer.h
Normal 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
|
||||
296
cocos/audio/android/AudioMixerController.cpp
Normal file
296
cocos/audio/android/AudioMixerController.cpp
Normal 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
|
||||
84
cocos/audio/android/AudioMixerController.h
Normal file
84
cocos/audio/android/AudioMixerController.h
Normal 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
|
||||
445
cocos/audio/android/AudioMixerOps.h
Normal file
445
cocos/audio/android/AudioMixerOps.h
Normal 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
|
||||
520
cocos/audio/android/AudioPlayerProvider.cpp
Normal file
520
cocos/audio/android/AudioPlayerProvider.cpp
Normal 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 &¶ms = preloadIter->second;
|
||||
ALOGV("preload (%s) callback count: %d", audioFilePath.c_str(), (int)params.size());
|
||||
PcmData result = decoder->getResult();
|
||||
for (auto &¶m : 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
|
||||
122
cocos/audio/android/AudioPlayerProvider.h
Normal file
122
cocos/audio/android/AudioPlayerProvider.h
Normal 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
|
||||
792
cocos/audio/android/AudioResampler.cpp
Normal file
792
cocos/audio/android/AudioResampler.cpp
Normal 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
|
||||
182
cocos/audio/android/AudioResampler.h
Normal file
182
cocos/audio/android/AudioResampler.h
Normal 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
|
||||
186
cocos/audio/android/AudioResamplerCubic.cpp
Normal file
186
cocos/audio/android/AudioResamplerCubic.cpp
Normal 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
|
||||
65
cocos/audio/android/AudioResamplerCubic.h
Normal file
65
cocos/audio/android/AudioResamplerCubic.h
Normal 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
|
||||
171
cocos/audio/android/AudioResamplerPublic.h
Normal file
171
cocos/audio/android/AudioResamplerPublic.h
Normal 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
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
87
cocos/audio/android/IAudioPlayer.h
Normal file
87
cocos/audio/android/IAudioPlayer.h
Normal 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
|
||||
40
cocos/audio/android/ICallerThreadUtils.h
Normal file
40
cocos/audio/android/ICallerThreadUtils.h
Normal 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
|
||||
42
cocos/audio/android/IVolumeProvider.h
Normal file
42
cocos/audio/android/IVolumeProvider.h
Normal 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
|
||||
107
cocos/audio/android/OpenSLHelper.h
Normal file
107
cocos/audio/android/OpenSLHelper.h
Normal 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]))
|
||||
193
cocos/audio/android/PcmAudioPlayer.cpp
Normal file
193
cocos/audio/android/PcmAudioPlayer.cpp
Normal 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
|
||||
96
cocos/audio/android/PcmAudioPlayer.h
Normal file
96
cocos/audio/android/PcmAudioPlayer.h
Normal 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
|
||||
190
cocos/audio/android/PcmAudioService.cpp
Normal file
190
cocos/audio/android/PcmAudioService.cpp
Normal 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
|
||||
78
cocos/audio/android/PcmAudioService.h
Normal file
78
cocos/audio/android/PcmAudioService.h
Normal 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
|
||||
102
cocos/audio/android/PcmBufferProvider.cpp
Normal file
102
cocos/audio/android/PcmBufferProvider.cpp
Normal 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
|
||||
51
cocos/audio/android/PcmBufferProvider.h
Normal file
51
cocos/audio/android/PcmBufferProvider.h
Normal 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
|
||||
128
cocos/audio/android/PcmData.cpp
Normal file
128
cocos/audio/android/PcmData.cpp
Normal 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
|
||||
65
cocos/audio/android/PcmData.h
Normal file
65
cocos/audio/android/PcmData.h
Normal 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
|
||||
86
cocos/audio/android/Track.cpp
Normal file
86
cocos/audio/android/Track.cpp
Normal 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
101
cocos/audio/android/Track.h
Normal 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
|
||||
360
cocos/audio/android/UrlAudioPlayer.cpp
Normal file
360
cocos/audio/android/UrlAudioPlayer.cpp
Normal 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
|
||||
127
cocos/audio/android/UrlAudioPlayer.h
Normal file
127
cocos/audio/android/UrlAudioPlayer.h
Normal 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
491
cocos/audio/android/audio.h
Normal 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);
|
||||
}
|
||||
48
cocos/audio/android/cutils/bitops.h
Normal file
48
cocos/audio/android/cutils/bitops.h
Normal 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 */
|
||||
597
cocos/audio/android/cutils/log.h
Normal file
597
cocos/audio/android/cutils/log.h
Normal 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 */
|
||||
525
cocos/audio/android/mp3reader.cpp
Normal file
525
cocos/audio/android/mp3reader.cpp
Normal 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;
|
||||
}
|
||||
61
cocos/audio/android/mp3reader.h
Normal file
61
cocos/audio/android/mp3reader.h
Normal 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_ */
|
||||
107
cocos/audio/android/utils/Compat.h
Normal file
107
cocos/audio/android/utils/Compat.h
Normal 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 */
|
||||
88
cocos/audio/android/utils/Errors.h
Normal file
88
cocos/audio/android/utils/Errors.h
Normal 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
|
||||
34
cocos/audio/android/utils/Utils.cpp
Normal file
34
cocos/audio/android/utils/Utils.cpp
Normal 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
|
||||
31
cocos/audio/android/utils/Utils.h
Normal file
31
cocos/audio/android/utils/Utils.h
Normal 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();
|
||||
}
|
||||
Reference in New Issue
Block a user