You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
260 lines
9.0 KiB
260 lines
9.0 KiB
/****************************************************************************
|
|
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
|
|
|