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.
242 lines
11 KiB
242 lines
11 KiB
/*
|
|
* Copyright (C) 2019 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_NDEBUG 0
|
|
#define LOG_TAG "SoundPool::Sound"
|
|
#include <utils/Log.h>
|
|
|
|
#include "Sound.h"
|
|
|
|
#include <media/NdkMediaCodec.h>
|
|
#include <media/NdkMediaExtractor.h>
|
|
#include <media/NdkMediaFormat.h>
|
|
|
|
namespace android::soundpool {
|
|
|
|
constexpr uint32_t kMaxSampleRate = 192000;
|
|
constexpr size_t kDefaultHeapSize = 1024 * 1024; // 1MB (compatible with low mem devices)
|
|
|
|
Sound::Sound(int32_t soundID, int fd, int64_t offset, int64_t length)
|
|
: mSoundID(soundID)
|
|
, mFd(fcntl(fd, F_DUPFD_CLOEXEC, (int)0 /* arg */)) // dup(fd) + close on exec to prevent leaks.
|
|
, mOffset(offset)
|
|
, mLength(length)
|
|
{
|
|
ALOGV("%s(soundID=%d, fd=%d, offset=%lld, length=%lld)",
|
|
__func__, soundID, fd, (long long)offset, (long long)length);
|
|
ALOGW_IF(mFd == -1, "Unable to dup descriptor %d", fd);
|
|
}
|
|
|
|
Sound::~Sound()
|
|
{
|
|
ALOGV("%s(soundID=%d, fd=%d)", __func__, mSoundID, mFd.get());
|
|
}
|
|
|
|
static status_t decode(int fd, int64_t offset, int64_t length,
|
|
uint32_t *rate, int32_t *channelCount, audio_format_t *audioFormat,
|
|
audio_channel_mask_t *channelMask, const sp<MemoryHeapBase>& heap,
|
|
size_t *sizeInBytes) {
|
|
ALOGV("%s(fd=%d, offset=%lld, length=%lld, ...)",
|
|
__func__, fd, (long long)offset, (long long)length);
|
|
std::unique_ptr<AMediaExtractor, decltype(&AMediaExtractor_delete)> ex{
|
|
AMediaExtractor_new(), &AMediaExtractor_delete};
|
|
status_t err = AMediaExtractor_setDataSourceFd(ex.get(), fd, offset, length);
|
|
|
|
if (err != AMEDIA_OK) {
|
|
return err;
|
|
}
|
|
|
|
*audioFormat = AUDIO_FORMAT_PCM_16_BIT; // default format for audio codecs.
|
|
const size_t numTracks = AMediaExtractor_getTrackCount(ex.get());
|
|
for (size_t i = 0; i < numTracks; i++) {
|
|
std::unique_ptr<AMediaFormat, decltype(&AMediaFormat_delete)> format{
|
|
AMediaExtractor_getTrackFormat(ex.get(), i), &AMediaFormat_delete};
|
|
const char *mime;
|
|
if (!AMediaFormat_getString(format.get(), AMEDIAFORMAT_KEY_MIME, &mime)) {
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
if (strncmp(mime, "audio/", 6) == 0) {
|
|
std::unique_ptr<AMediaCodec, decltype(&AMediaCodec_delete)> codec{
|
|
AMediaCodec_createDecoderByType(mime), &AMediaCodec_delete};
|
|
if (codec == nullptr
|
|
|| AMediaCodec_configure(codec.get(), format.get(),
|
|
nullptr /* window */, nullptr /* drm */, 0 /* flags */) != AMEDIA_OK
|
|
|| AMediaCodec_start(codec.get()) != AMEDIA_OK
|
|
|| AMediaExtractor_selectTrack(ex.get(), i) != AMEDIA_OK) {
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
bool sawInputEOS = false;
|
|
bool sawOutputEOS = false;
|
|
auto writePos = static_cast<uint8_t*>(heap->getBase());
|
|
size_t available = heap->getSize();
|
|
size_t written = 0;
|
|
format.reset(AMediaCodec_getOutputFormat(codec.get())); // update format.
|
|
|
|
while (!sawOutputEOS) {
|
|
if (!sawInputEOS) {
|
|
ssize_t bufidx = AMediaCodec_dequeueInputBuffer(codec.get(), 5000);
|
|
ALOGV("%s: input buffer %zd", __func__, bufidx);
|
|
if (bufidx >= 0) {
|
|
size_t bufsize;
|
|
uint8_t * const buf = AMediaCodec_getInputBuffer(
|
|
codec.get(), bufidx, &bufsize);
|
|
if (buf == nullptr) {
|
|
ALOGE("%s: AMediaCodec_getInputBuffer returned nullptr, short decode",
|
|
__func__);
|
|
break;
|
|
}
|
|
ssize_t sampleSize = AMediaExtractor_readSampleData(ex.get(), buf, bufsize);
|
|
ALOGV("%s: read %zd", __func__, sampleSize);
|
|
if (sampleSize < 0) {
|
|
sampleSize = 0;
|
|
sawInputEOS = true;
|
|
ALOGV("%s: EOS", __func__);
|
|
}
|
|
const int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex.get());
|
|
|
|
const media_status_t mstatus = AMediaCodec_queueInputBuffer(
|
|
codec.get(), bufidx,
|
|
0 /* offset */, sampleSize, presentationTimeUs,
|
|
sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
|
|
if (mstatus != AMEDIA_OK) {
|
|
// AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
|
|
ALOGE("%s: AMediaCodec_queueInputBuffer returned status %d,"
|
|
"short decode",
|
|
__func__, (int)mstatus);
|
|
break;
|
|
}
|
|
(void)AMediaExtractor_advance(ex.get());
|
|
}
|
|
}
|
|
|
|
AMediaCodecBufferInfo info;
|
|
const ssize_t status = AMediaCodec_dequeueOutputBuffer(codec.get(), &info, 1);
|
|
ALOGV("%s: dequeueoutput returned: %zd", __func__, status);
|
|
if (status >= 0) {
|
|
if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
|
|
ALOGV("%s: output EOS", __func__);
|
|
sawOutputEOS = true;
|
|
}
|
|
ALOGV("%s: got decoded buffer size %d", __func__, info.size);
|
|
|
|
const uint8_t * const buf = AMediaCodec_getOutputBuffer(
|
|
codec.get(), status, nullptr /* out_size */);
|
|
if (buf == nullptr) {
|
|
ALOGE("%s: AMediaCodec_getOutputBuffer returned nullptr, short decode",
|
|
__func__);
|
|
break;
|
|
}
|
|
const size_t dataSize = std::min((size_t)info.size, available);
|
|
memcpy(writePos, buf + info.offset, dataSize);
|
|
writePos += dataSize;
|
|
written += dataSize;
|
|
available -= dataSize;
|
|
const media_status_t mstatus = AMediaCodec_releaseOutputBuffer(
|
|
codec.get(), status, false /* render */);
|
|
if (mstatus != AMEDIA_OK) {
|
|
// AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
|
|
ALOGE("%s: AMediaCodec_releaseOutputBuffer"
|
|
" returned status %d, short decode",
|
|
__func__, (int)mstatus);
|
|
break;
|
|
}
|
|
if (available == 0) {
|
|
// there might be more data, but there's no space for it
|
|
sawOutputEOS = true;
|
|
}
|
|
} else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
|
|
ALOGV("%s: output buffers changed", __func__);
|
|
} else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
|
|
format.reset(AMediaCodec_getOutputFormat(codec.get())); // update format
|
|
ALOGV("%s: format changed to: %s",
|
|
__func__, AMediaFormat_toString(format.get()));
|
|
} else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
|
|
ALOGV("%s: no output buffer right now", __func__);
|
|
} else if (status <= AMEDIA_ERROR_BASE) {
|
|
ALOGE("%s: decode error: %zd", __func__, status);
|
|
break;
|
|
} else {
|
|
ALOGV("%s: unexpected info code: %zd", __func__, status);
|
|
}
|
|
}
|
|
|
|
(void)AMediaCodec_stop(codec.get());
|
|
if (!AMediaFormat_getInt32(
|
|
format.get(), AMEDIAFORMAT_KEY_SAMPLE_RATE, (int32_t*) rate) ||
|
|
!AMediaFormat_getInt32(
|
|
format.get(), AMEDIAFORMAT_KEY_CHANNEL_COUNT, channelCount)) {
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
if (!AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_CHANNEL_MASK,
|
|
(int32_t*) channelMask)) {
|
|
*channelMask = AUDIO_CHANNEL_NONE;
|
|
}
|
|
*sizeInBytes = written;
|
|
return OK;
|
|
}
|
|
}
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
status_t Sound::doLoad()
|
|
{
|
|
ALOGV("%s()", __func__);
|
|
status_t status = NO_INIT;
|
|
if (mFd.get() != -1) {
|
|
mHeap = new MemoryHeapBase(kDefaultHeapSize);
|
|
|
|
ALOGV("%s: start decode", __func__);
|
|
uint32_t sampleRate;
|
|
int32_t channelCount;
|
|
audio_format_t format;
|
|
audio_channel_mask_t channelMask;
|
|
status = decode(mFd.get(), mOffset, mLength, &sampleRate, &channelCount, &format,
|
|
&channelMask, mHeap, &mSizeInBytes);
|
|
ALOGV("%s: close(%d)", __func__, mFd.get());
|
|
mFd.reset(); // close
|
|
|
|
if (status != NO_ERROR) {
|
|
ALOGE("%s: unable to load sound", __func__);
|
|
} else if (sampleRate > kMaxSampleRate) {
|
|
ALOGE("%s: sample rate (%u) out of range", __func__, sampleRate);
|
|
status = BAD_VALUE;
|
|
} else if (channelCount < 1 || channelCount > FCC_LIMIT) {
|
|
ALOGE("%s: sample channel count (%d) out of range", __func__, channelCount);
|
|
status = BAD_VALUE;
|
|
} else {
|
|
// Correctly loaded, proper parameters
|
|
ALOGV("%s: pointer = %p, sizeInBytes = %zu, sampleRate = %u, channelCount = %d",
|
|
__func__, mHeap->getBase(), mSizeInBytes, sampleRate, channelCount);
|
|
mData = new MemoryBase(mHeap, 0, mSizeInBytes);
|
|
mSampleRate = sampleRate;
|
|
mChannelCount = channelCount;
|
|
mFormat = format;
|
|
mChannelMask = channelMask;
|
|
mState = READY; // this should be last, as it is an atomic sync point
|
|
return NO_ERROR;
|
|
}
|
|
} else {
|
|
ALOGE("%s: uninitialized fd, dup failed", __func__);
|
|
}
|
|
// ERROR handling
|
|
mHeap.clear();
|
|
mState = DECODE_ERROR; // this should be last, as it is an atomic sync point
|
|
return status;
|
|
}
|
|
|
|
} // namespace android::soundpool
|