Make class names fit the Java API and documentation. Rename Channel -> Stream. Rename Sample -> Sound. Rename SoundPoolThread -> SoundDecoder. Move track start and stop to worker thread. Fix up types to ensure future compatibility with Java ints which are 32 bits always. Upgrade 16 bit types to 32 bits (e.g. sample id, sample rate, etc.) Move sound related code into the SoundManager class. Move stream related code into the StreamManager class. Clean up locking, split SoundPool lock into the following locks 1) mApiLock 2) mStreamManagerLock 3) mSoundManagerLock 4) per Stream mLock. 5) mCallbackLock Group locked data and make private in associated classes to ensure restricted access, yet maximum concurrency. Fix race conditions waiting for next event to play in stream. Ensure track commands are handled consistently when stream is stolen. Test: SoundPoolOggTest Bug: 140807581 Change-Id: I8fcb374ee6329eb5474b973624584ca5080da862
242 lines
11 KiB
C++
242 lines
11 KiB
C++
/*
|
|
* 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(dup(fd))
|
|
, 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, 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;
|
|
uint8_t* 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;
|
|
}
|
|
int sampleSize = AMediaExtractor_readSampleData(ex.get(), buf, bufsize);
|
|
ALOGV("%s: read %d", __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 int status = AMediaCodec_dequeueOutputBuffer(codec.get(), &info, 1);
|
|
ALOGV("%s: dequeueoutput returned: %d", __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: %d", __func__, status);
|
|
break;
|
|
} else {
|
|
ALOGV("%s: unexpected info code: %d", __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_t 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_8) {
|
|
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
|