Merge "SoundPool: Refactor class"

This commit is contained in:
Andy Hung
2019-10-23 18:25:05 +00:00
committed by Android (Google) Code Review
15 changed files with 2381 additions and 1432 deletions

View File

@@ -3,11 +3,16 @@ cc_library_shared {
srcs: [
"android_media_SoundPool.cpp",
"Sound.cpp",
"SoundDecoder.cpp",
"SoundManager.cpp",
"SoundPool.cpp",
"SoundPoolThread.cpp",
"Stream.cpp",
"StreamManager.cpp",
],
shared_libs: [
"libaudioutils",
"liblog",
"libcutils",
"libutils",

View File

@@ -0,0 +1,241 @@
/*
* 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

View File

@@ -0,0 +1,93 @@
/*
* 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.
*/
#pragma once
#include <android-base/unique_fd.h>
#include <binder/MemoryBase.h>
#include <binder/MemoryHeapBase.h>
#include <system/audio.h>
namespace android::soundpool {
class SoundDecoder;
/**
* Sound is a resource used by SoundPool, referenced by soundID.
*
* After loading, it is effectively const so no locking required.
* However, in order to guarantee that all the values have been
* written properly and read properly, we use the mState as an atomic synchronization
* point. So if getState() shows READY, then all the other getters may
* be safely read.
*
* Technical details:
* We access the mState atomic value through memory_order_seq_cst
*
* https://en.cppreference.com/w/cpp/atomic/memory_order
*
* which provides memory barriers. So if the last value written by the SoundDecoder
* is mState, then the compiler ensures no other prior writes by SoundDecoder will be
* reordered afterwards, and memory barrier is placed (as necessary) to ensure the
* cache is visible to other processors.
*
* Likewise, if the first value read by SoundPool is mState,
* the compiler ensures no reads for that thread will be reordered before mState is read,
* and a memory barrier is placed (as necessary) to ensure that the cache is properly
* updated with other processor's writes before reading.
*
* See https://developer.android.com/training/articles/smp for discussions about
* the variant load-acquire, store-release semantics.
*/
class Sound {
friend SoundDecoder; // calls doLoad().
public:
enum sound_state : int32_t { LOADING, READY, DECODE_ERROR };
// A sound starts in the LOADING state and transitions only once
// to either READY or DECODE_ERROR when doLoad() is called.
Sound(int soundID, int fd, int64_t offset, int64_t length);
~Sound();
int32_t getSoundID() const { return mSoundID; }
int32_t getChannelCount() const { return mChannelCount; }
uint32_t getSampleRate() const { return mSampleRate; }
audio_format_t getFormat() const { return mFormat; }
audio_channel_mask_t getChannelMask() const { return mChannelMask; }
size_t getSizeInBytes() const { return mSizeInBytes; }
sound_state getState() const { return mState; }
uint8_t* getData() const { return static_cast<uint8_t*>(mData->unsecurePointer()); }
sp<IMemory> getIMemory() const { return mData; }
private:
status_t doLoad(); // only SoundDecoder accesses this.
size_t mSizeInBytes = 0;
const int32_t mSoundID;
uint32_t mSampleRate = 0;
std::atomic<sound_state> mState = LOADING; // used as synchronization point
int32_t mChannelCount = 0;
audio_format_t mFormat = AUDIO_FORMAT_INVALID;
audio_channel_mask_t mChannelMask = AUDIO_CHANNEL_NONE;
base::unique_fd mFd; // initialized in constructor, reset to -1 after loading
const int64_t mOffset; // int64_t to match java long, see off64_t
const int64_t mLength; // int64_t to match java long, see off64_t
sp<IMemory> mData;
sp<MemoryHeapBase> mHeap;
};
} // namespace android::soundpool

View File

@@ -0,0 +1,115 @@
/*
* 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::SoundDecoder"
#include "utils/Log.h"
#include "SoundDecoder.h"
namespace android::soundpool {
// Maximum Samples that can be background decoded before we block the caller.
static constexpr size_t kMaxQueueSize = 128;
// The amount of time we wait for a new Sound decode request
// before the SoundDecoder thread closes.
static constexpr int32_t kWaitTimeBeforeCloseMs = 1000;
SoundDecoder::SoundDecoder(SoundManager* soundManager, size_t threads)
: mSoundManager(soundManager)
{
ALOGV("%s(%p, %zu)", __func__, soundManager, threads);
// ThreadPool is created, but we don't launch any threads.
mThreadPool = std::make_unique<ThreadPool>(
std::min(threads, (size_t)std::thread::hardware_concurrency()),
"SoundDecoder_");
}
SoundDecoder::~SoundDecoder()
{
ALOGV("%s()", __func__);
quit();
}
void SoundDecoder::quit()
{
ALOGV("%s()", __func__);
{
std::lock_guard lock(mLock);
mQuit = true;
mQueueSpaceAvailable.notify_all(); // notify all load waiters
mQueueDataAvailable.notify_all(); // notify all worker threads
}
mThreadPool->quit();
}
void SoundDecoder::run(int32_t id __unused /* ALOGV only */)
{
ALOGV("%s(%d): entering", __func__, id);
std::unique_lock lock(mLock);
while (!mQuit) {
if (mSoundIDs.size() == 0) {
ALOGV("%s(%d): waiting", __func__, id);
mQueueDataAvailable.wait_for(
lock, std::chrono::duration<int32_t, std::milli>(kWaitTimeBeforeCloseMs));
if (mSoundIDs.size() == 0) {
break; // no new sound, exit this thread.
}
continue;
}
const int32_t soundID = mSoundIDs.front();
mSoundIDs.pop_front();
mQueueSpaceAvailable.notify_one();
ALOGV("%s(%d): processing soundID: %d size: %zu", __func__, id, soundID, mSoundIDs.size());
lock.unlock();
std::shared_ptr<Sound> sound = mSoundManager->findSound(soundID);
status_t status = NO_INIT;
if (sound.get() != nullptr) {
status = sound->doLoad();
}
ALOGV("%s(%d): notifying loaded soundID:%d status:%d", __func__, id, soundID, status);
mSoundManager->notify(SoundPoolEvent(SoundPoolEvent::SOUND_LOADED, soundID, status));
lock.lock();
}
ALOGV("%s(%d): exiting", __func__, id);
}
void SoundDecoder::loadSound(int32_t soundID)
{
ALOGV("%s(%d)", __func__, soundID);
size_t pendingSounds;
{
std::unique_lock lock(mLock);
while (mSoundIDs.size() == kMaxQueueSize) {
if (mQuit) return;
ALOGV("%s: waiting soundID: %d size: %zu", __func__, soundID, mSoundIDs.size());
mQueueSpaceAvailable.wait(lock);
}
if (mQuit) return;
mSoundIDs.push_back(soundID);
mQueueDataAvailable.notify_one();
ALOGV("%s: adding soundID: %d size: %zu", __func__, soundID, mSoundIDs.size());
pendingSounds = mSoundIDs.size();
}
// Launch threads as needed. The "as needed" is weakly consistent as we release mLock.
if (pendingSounds > mThreadPool->getActiveThreadCount()) {
const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); });
ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
}
}
} // end namespace android::soundpool

View File

@@ -0,0 +1,51 @@
/*
* 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.
*/
#pragma once
#include "SoundPool.h"
#include <deque>
#include <mutex>
namespace android::soundpool {
/**
* SoundDecoder handles background decoding tasks.
*/
class SoundDecoder {
public:
SoundDecoder(SoundManager* soundManager, size_t threads);
~SoundDecoder();
void loadSound(int32_t soundID);
void quit();
private:
void run(int32_t id); // The decode thread function.
SoundManager* const mSoundManager; // set in constructor, has own lock
std::unique_ptr<ThreadPool> mThreadPool; // set in constructor, has own lock
std::mutex mLock;
std::condition_variable mQueueSpaceAvailable;
std::condition_variable mQueueDataAvailable;
std::deque<int32_t> mSoundIDs; // GUARDED_BY(mLock);
bool mQuit = false; // GUARDED_BY(mLock);
};
} // end namespace android::soundpool

View File

@@ -0,0 +1,104 @@
/*
* 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::SoundManager"
#include <utils/Log.h>
#include "SoundManager.h"
#include <thread>
#include "SoundDecoder.h"
namespace android::soundpool {
static const size_t kDecoderThreads = std::thread::hardware_concurrency() >= 4 ? 2 : 1;
SoundManager::SoundManager()
: mDecoder{std::make_unique<SoundDecoder>(this, kDecoderThreads)}
{
ALOGV("%s()", __func__);
}
SoundManager::~SoundManager()
{
ALOGV("%s()", __func__);
mDecoder->quit();
std::lock_guard lock(mSoundManagerLock);
mSounds.clear();
}
int32_t SoundManager::load(int fd, int64_t offset, int64_t length, int32_t priority __unused)
{
ALOGV("%s(fd=%d, offset=%lld, length=%lld, priority=%d)",
__func__, fd, (long long)offset, (long long)length, priority);
int32_t soundID;
{
std::lock_guard lock(mSoundManagerLock);
// mNextSoundID is always positive and does not "integer overflow"
do {
mNextSoundID = mNextSoundID == INT32_MAX ? 1 : mNextSoundID + 1;
} while (findSound_l(mNextSoundID) != nullptr);
soundID = mNextSoundID;
auto sound = std::make_shared<Sound>(soundID, fd, offset, length);
mSounds.emplace(soundID, sound);
}
// mDecoder->loadSound() must be called outside of mSoundManagerLock.
// mDecoder->loadSound() may block on mDecoder message queue space;
// the message queue emptying may block on SoundManager::findSound().
//
// It is theoretically possible that sound loads might decode out-of-order.
mDecoder->loadSound(soundID);
return soundID;
}
bool SoundManager::unload(int32_t soundID)
{
ALOGV("%s(soundID=%d)", __func__, soundID);
std::lock_guard lock(mSoundManagerLock);
return mSounds.erase(soundID) > 0; // erase() returns number of sounds removed.
}
std::shared_ptr<Sound> SoundManager::findSound(int32_t soundID) const
{
std::lock_guard lock(mSoundManagerLock);
return findSound_l(soundID);
}
std::shared_ptr<Sound> SoundManager::findSound_l(int32_t soundID) const
{
auto it = mSounds.find(soundID);
return it != mSounds.end() ? it->second : nullptr;
}
void SoundManager::setCallback(SoundPool *soundPool, SoundPoolCallback* callback, void* user)
{
mCallbackHandler.setCallback(soundPool, callback, user);
}
void SoundManager::notify(SoundPoolEvent event)
{
mCallbackHandler.notify(event);
}
void* SoundManager::getUserData() const
{
return mCallbackHandler.getUserData();
}
} // namespace android::soundpool

View File

@@ -0,0 +1,110 @@
/*
* 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.
*/
#pragma once
#include "Sound.h"
#include <mutex>
#include <unordered_map>
namespace android {
class SoundPool;
// for queued events
class SoundPoolEvent {
public:
explicit SoundPoolEvent(int msg, int arg1 = 0, int arg2 = 0) :
mMsg(msg), mArg1(arg1), mArg2(arg2) {}
const int mMsg; // MessageType
const int mArg1; // soundID
const int mArg2; // status
enum MessageType { INVALID, SOUND_LOADED };
};
// callback function prototype
typedef void SoundPoolCallback(SoundPoolEvent event, SoundPool* soundPool, void* user);
} // namespace android
namespace android::soundpool {
// This class manages Sounds for the SoundPool.
class SoundManager {
public:
SoundManager();
~SoundManager();
// Matches corresponding SoundPool API functions
int32_t load(int fd, int64_t offset, int64_t length, int32_t priority);
bool unload(int32_t soundID);
void setCallback(SoundPool* soundPool, SoundPoolCallback* callback, void* user);
void* getUserData() const;
// SoundPool and SoundDecoder access
std::shared_ptr<Sound> findSound(int32_t soundID) const;
// from the SoundDecoder
void notify(SoundPoolEvent event);
private:
// CallbackHandler is used to manage notifications back to the app when a sound
// has been loaded. It uses a recursive lock to allow setting the callback
// during the callback.
class CallbackHandler {
public:
void setCallback(SoundPool *soundPool, SoundPoolCallback* callback, void* userData)
{
std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
mSoundPool = soundPool;
mCallback = callback;
mUserData = userData;
}
void notify(SoundPoolEvent event) const
{
std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
if (mCallback != nullptr) {
mCallback(event, mSoundPool, mUserData);
// Note: mCallback may call setCallback().
// so mCallback, mUserData may have changed.
}
}
void* getUserData() const
{
std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
return mUserData;
}
private:
mutable std::recursive_mutex mCallbackLock; // allow mCallback to setCallback().
SoundPool* mSoundPool = nullptr; // GUARDED_BY(mCallbackLock)
SoundPoolCallback* mCallback = nullptr; // GUARDED_BY(mCallbackLock)
void* mUserData = nullptr; // GUARDED_BY(mCallbackLock)
};
std::shared_ptr<Sound> findSound_l(int32_t soundID) const;
// The following variables are initialized in constructor and can be accessed anytime.
CallbackHandler mCallbackHandler; // has its own lock
const std::unique_ptr<SoundDecoder> mDecoder; // has its own lock
mutable std::mutex mSoundManagerLock;
std::unordered_map<int, std::shared_ptr<Sound>> mSounds; // GUARDED_BY(mSoundManagerLock)
int32_t mNextSoundID = 0; // GUARDED_BY(mSoundManagerLock)
};
} // namespace android::soundpool

File diff suppressed because it is too large Load Diff

View File

@@ -14,227 +14,59 @@
* limitations under the License.
*/
#ifndef SOUNDPOOL_H_
#define SOUNDPOOL_H_
#pragma once
#include <utils/threads.h>
#include <utils/List.h>
#include <utils/Vector.h>
#include <utils/KeyedVector.h>
#include <media/AudioTrack.h>
#include <binder/MemoryHeapBase.h>
#include <binder/MemoryBase.h>
#include "SoundManager.h"
#include "StreamManager.h"
namespace android {
static const int IDLE_PRIORITY = -1;
// forward declarations
class SoundEvent;
class SoundPoolThread;
class SoundPool;
// for queued events
class SoundPoolEvent {
public:
explicit SoundPoolEvent(int msg, int arg1=0, int arg2=0) :
mMsg(msg), mArg1(arg1), mArg2(arg2) {}
int mMsg;
int mArg1;
int mArg2;
enum MessageType { INVALID, SAMPLE_LOADED };
};
// callback function prototype
typedef void SoundPoolCallback(SoundPoolEvent event, SoundPool* soundPool, void* user);
// tracks samples used by application
class Sample : public RefBase {
public:
enum sample_state { UNLOADED, LOADING, READY, UNLOADING };
Sample(int sampleID, int fd, int64_t offset, int64_t length);
~Sample();
int sampleID() { return mSampleID; }
int numChannels() { return mNumChannels; }
int sampleRate() { return mSampleRate; }
audio_format_t format() { return mFormat; }
audio_channel_mask_t channelMask() { return mChannelMask; }
size_t size() { return mSize; }
int state() { return mState; }
uint8_t* data() { return static_cast<uint8_t*>(mData->unsecurePointer()); }
status_t doLoad();
void startLoad() { mState = LOADING; }
sp<IMemory> getIMemory() { return mData; }
private:
void init();
size_t mSize;
volatile int32_t mRefCount;
uint16_t mSampleID;
uint16_t mSampleRate;
uint8_t mState;
uint8_t mNumChannels;
audio_format_t mFormat;
audio_channel_mask_t mChannelMask;
int mFd;
int64_t mOffset;
int64_t mLength;
sp<IMemory> mData;
sp<MemoryHeapBase> mHeap;
};
// stores pending events for stolen channels
class SoundEvent
{
public:
SoundEvent() : mChannelID(0), mLeftVolume(0), mRightVolume(0),
mPriority(IDLE_PRIORITY), mLoop(0), mRate(0) {}
void set(const sp<Sample>& sample, int channelID, float leftVolume,
float rightVolume, int priority, int loop, float rate);
sp<Sample> sample() { return mSample; }
int channelID() { return mChannelID; }
float leftVolume() { return mLeftVolume; }
float rightVolume() { return mRightVolume; }
int priority() { return mPriority; }
int loop() { return mLoop; }
float rate() { return mRate; }
void clear() { mChannelID = 0; mSample.clear(); }
protected:
sp<Sample> mSample;
int mChannelID;
float mLeftVolume;
float mRightVolume;
int mPriority;
int mLoop;
float mRate;
};
// for channels aka AudioTracks
class SoundChannel : public SoundEvent {
public:
enum state { IDLE, RESUMING, STOPPING, PAUSED, PLAYING };
SoundChannel() : mState(IDLE), mNumChannels(1),
mPos(0), mToggle(0), mAutoPaused(false), mMuted(false) {}
~SoundChannel();
void init(SoundPool* soundPool);
void play(const sp<Sample>& sample, int channelID, float leftVolume, float rightVolume,
int priority, int loop, float rate);
void setVolume_l(float leftVolume, float rightVolume);
void setVolume(float leftVolume, float rightVolume);
void mute(bool muting);
void stop_l();
void stop();
void pause();
void autoPause();
void resume();
void autoResume();
void setRate(float rate);
int state() { return mState; }
void setPriority(int priority) { mPriority = priority; }
void setLoop(int loop);
int numChannels() { return mNumChannels; }
void clearNextEvent() { mNextEvent.clear(); }
void nextEvent();
int nextChannelID() { return mNextEvent.channelID(); }
void dump();
int getPrevSampleID(void) { return mPrevSampleID; }
private:
static void callback(int event, void* user, void *info);
void process(int event, void *info, unsigned long toggle);
bool doStop_l();
SoundPool* mSoundPool;
sp<AudioTrack> mAudioTrack;
SoundEvent mNextEvent;
Mutex mLock;
int mState;
int mNumChannels;
int mPos;
int mAudioBufferSize;
unsigned long mToggle;
bool mAutoPaused;
int mPrevSampleID;
bool mMuted;
};
// application object for managing a pool of sounds
/**
* Native class for Java SoundPool, manages a pool of sounds.
*
* See the Android SoundPool Java documentation for description of valid values.
* https://developer.android.com/reference/android/media/SoundPool
*/
class SoundPool {
friend class SoundPoolThread;
friend class SoundChannel;
public:
SoundPool(int maxChannels, const audio_attributes_t* pAttributes);
SoundPool(int32_t maxStreams, const audio_attributes_t* attributes);
~SoundPool();
int load(int fd, int64_t offset, int64_t length, int priority);
bool unload(int sampleID);
int play(int sampleID, float leftVolume, float rightVolume, int priority,
int loop, float rate);
void pause(int channelID);
void mute(bool muting);
// SoundPool Java API support
int32_t load(int fd, int64_t offset, int64_t length, int32_t priority);
bool unload(int32_t soundID);
int32_t play(int32_t soundID, float leftVolume, float rightVolume, int32_t priority,
int32_t loop, float rate);
void pause(int32_t streamID);
void autoPause();
void resume(int channelID);
void resume(int32_t streamID);
void autoResume();
void stop(int channelID);
void setVolume(int channelID, float leftVolume, float rightVolume);
void setPriority(int channelID, int priority);
void setLoop(int channelID, int loop);
void setRate(int channelID, float rate);
const audio_attributes_t* attributes() { return &mAttributes; }
// called from SoundPoolThread
void sampleLoaded(int sampleID);
sp<Sample> findSample(int sampleID);
// called from AudioTrack thread
void done_l(SoundChannel* channel);
// callback function
void stop(int32_t streamID);
void setVolume(int32_t streamID, float leftVolume, float rightVolume);
void setPriority(int32_t streamID, int32_t priority);
void setLoop(int32_t streamID, int32_t loop);
void setRate(int32_t streamID, float rate);
void setCallback(SoundPoolCallback* callback, void* user);
void* getUserData() { return mUserData; }
void* getUserData() const;
// not exposed in the public Java API, used for internal playerSetVolume() muting.
void mute(bool muting);
private:
SoundPool() {} // no default constructor
bool startThreads();
sp<Sample> findSample_l(int sampleID);
SoundChannel* findChannel (int channelID);
SoundChannel* findNextChannel (int channelID);
SoundChannel* allocateChannel_l(int priority, int sampleID);
void moveToFront_l(SoundChannel* channel);
void notify(SoundPoolEvent event);
void dump();
// restart thread
void addToRestartList(SoundChannel* channel);
void addToStopList(SoundChannel* channel);
static int beginThread(void* arg);
int run();
void quit();
// Constructor initialized variables
// Can access without lock as they are internally locked,
// though care needs to be taken that the final result composed of
// individually consistent actions are consistent.
soundpool::SoundManager mSoundManager;
soundpool::StreamManager mStreamManager;
Mutex mLock;
Mutex mRestartLock;
Condition mCondition;
SoundPoolThread* mDecodeThread;
SoundChannel* mChannelPool;
List<SoundChannel*> mChannels;
List<SoundChannel*> mRestart;
List<SoundChannel*> mStop;
DefaultKeyedVector< int, sp<Sample> > mSamples;
int mMaxChannels;
audio_attributes_t mAttributes;
int mAllocated;
int mNextSampleID;
int mNextChannelID;
bool mQuit;
bool mMuted;
// callback
Mutex mCallbackLock;
SoundPoolCallback* mCallback;
void* mUserData;
// mApiLock serializes SoundPool application calls (configurable by kUseApiLock).
// It only locks at the SoundPool layer and not below. At this level,
// mApiLock is only required for autoPause() and autoResume() to prevent zippering
// of the individual pauses and resumes, and mute() for self-interaction with itself.
// It is optional for all other apis.
mutable std::mutex mApiLock;
};
} // end namespace android
#endif /*SOUNDPOOL_H_*/

View File

@@ -1,114 +0,0 @@
/*
* 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_NDEBUG 0
#define LOG_TAG "SoundPoolThread"
#include "utils/Log.h"
#include "SoundPoolThread.h"
namespace android {
void SoundPoolThread::write(SoundPoolMsg msg) {
Mutex::Autolock lock(&mLock);
while (mMsgQueue.size() >= maxMessages) {
mCondition.wait(mLock);
}
// if thread is quitting, don't add to queue
if (mRunning) {
mMsgQueue.push(msg);
mCondition.signal();
}
}
const SoundPoolMsg SoundPoolThread::read() {
Mutex::Autolock lock(&mLock);
while (mMsgQueue.size() == 0) {
mCondition.wait(mLock);
}
SoundPoolMsg msg = mMsgQueue[0];
mMsgQueue.removeAt(0);
mCondition.signal();
return msg;
}
void SoundPoolThread::quit() {
Mutex::Autolock lock(&mLock);
if (mRunning) {
mRunning = false;
mMsgQueue.clear();
mMsgQueue.push(SoundPoolMsg(SoundPoolMsg::KILL, 0));
mCondition.signal();
mCondition.wait(mLock);
}
ALOGV("return from quit");
}
SoundPoolThread::SoundPoolThread(SoundPool* soundPool) :
mSoundPool(soundPool)
{
mMsgQueue.setCapacity(maxMessages);
if (createThreadEtc(beginThread, this, "SoundPoolThread")) {
mRunning = true;
}
}
SoundPoolThread::~SoundPoolThread()
{
quit();
}
int SoundPoolThread::beginThread(void* arg) {
ALOGV("beginThread");
SoundPoolThread* soundPoolThread = (SoundPoolThread*)arg;
return soundPoolThread->run();
}
int SoundPoolThread::run() {
ALOGV("run");
for (;;) {
SoundPoolMsg msg = read();
ALOGV("Got message m=%d, mData=%d", msg.mMessageType, msg.mData);
switch (msg.mMessageType) {
case SoundPoolMsg::KILL:
ALOGV("goodbye");
return NO_ERROR;
case SoundPoolMsg::LOAD_SAMPLE:
doLoadSample(msg.mData);
break;
default:
ALOGW("run: Unrecognized message %d\n",
msg.mMessageType);
break;
}
}
}
void SoundPoolThread::loadSample(int sampleID) {
write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE, sampleID));
}
void SoundPoolThread::doLoadSample(int sampleID) {
sp <Sample> sample = mSoundPool->findSample(sampleID);
status_t status = -1;
if (sample != 0) {
status = sample->doLoad();
}
mSoundPool->notify(SoundPoolEvent(SoundPoolEvent::SAMPLE_LOADED, sampleID, status));
}
} // end namespace android

View File

@@ -1,66 +0,0 @@
/*
* 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 SOUNDPOOLTHREAD_H_
#define SOUNDPOOLTHREAD_H_
#include <utils/threads.h>
#include <utils/Vector.h>
#include <media/AudioTrack.h>
#include "SoundPool.h"
namespace android {
class SoundPoolMsg {
public:
enum MessageType { INVALID, KILL, LOAD_SAMPLE };
SoundPoolMsg() : mMessageType(INVALID), mData(0) {}
SoundPoolMsg(MessageType MessageType, int data) :
mMessageType(MessageType), mData(data) {}
uint16_t mMessageType;
uint16_t mData;
};
/*
* This class handles background requests from the SoundPool
*/
class SoundPoolThread {
public:
explicit SoundPoolThread(SoundPool* SoundPool);
~SoundPoolThread();
void loadSample(int sampleID);
void quit();
void write(SoundPoolMsg msg);
private:
static const size_t maxMessages = 128;
static int beginThread(void* arg);
int run();
void doLoadSample(int sampleID);
const SoundPoolMsg read();
Mutex mLock;
Condition mCondition;
Vector<SoundPoolMsg> mMsgQueue;
SoundPool* mSoundPool;
bool mRunning;
};
} // end namespace android
#endif /*SOUNDPOOLTHREAD_H_*/

View File

@@ -0,0 +1,448 @@
/*
* 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::Stream"
#include <utils/Log.h>
#include "Stream.h"
#include "StreamManager.h"
namespace android::soundpool {
Stream::~Stream()
{
ALOGV("%s(%p)", __func__, this);
}
void Stream::autoPause()
{
std::lock_guard lock(mLock);
if (mState == PLAYING) {
ALOGV("%s: track streamID: %d", __func__, (int)mStreamID);
mState = PAUSED;
mAutoPaused = true;
if (mAudioTrack != nullptr) {
mAudioTrack->pause();
}
}
}
void Stream::autoResume()
{
std::lock_guard lock(mLock);
if (mAutoPaused) {
if (mState == PAUSED) {
ALOGV("%s: track streamID: %d", __func__, (int)mStreamID);
mState = PLAYING;
if (mAudioTrack != nullptr) {
mAudioTrack->start();
}
}
mAutoPaused = false; // New for R: always reset autopause (consistent with API spec).
}
}
void Stream::mute(bool muting)
{
std::lock_guard lock(mLock);
mMuted = muting;
if (mAudioTrack != nullptr) {
if (mMuted) {
mAudioTrack->setVolume(0.0f, 0.0f);
} else {
mAudioTrack->setVolume(mLeftVolume, mRightVolume);
}
}
}
void Stream::pause(int32_t streamID)
{
std::lock_guard lock(mLock);
if (streamID == mStreamID) {
if (mState == PLAYING) {
ALOGV("%s: track streamID: %d", __func__, streamID);
mState = PAUSED;
if (mAudioTrack != nullptr) {
mAudioTrack->pause();
}
}
}
}
void Stream::resume(int32_t streamID)
{
std::lock_guard lock(mLock);
if (streamID == mStreamID) {
if (mState == PAUSED) {
ALOGV("%s: track streamID: %d", __func__, streamID);
mState = PLAYING;
if (mAudioTrack != nullptr) {
mAudioTrack->start();
}
mAutoPaused = false; // TODO: is this right? (ambiguous per spec), move outside?
}
}
}
void Stream::setRate(int32_t streamID, float rate)
{
std::lock_guard lock(mLock);
if (streamID == mStreamID) {
mRate = rate;
if (mAudioTrack != nullptr && mSound != nullptr) {
const uint32_t sampleRate = uint32_t(float(mSound->getSampleRate()) * rate + 0.5);
mAudioTrack->setSampleRate(sampleRate);
}
}
}
void Stream::setVolume_l(float leftVolume, float rightVolume)
{
mLeftVolume = leftVolume;
mRightVolume = rightVolume;
if (mAudioTrack != nullptr && !mMuted) {
mAudioTrack->setVolume(leftVolume, rightVolume);
}
}
void Stream::setVolume(int32_t streamID, float leftVolume, float rightVolume)
{
std::lock_guard lock(mLock);
if (streamID == mStreamID) {
setVolume_l(leftVolume, rightVolume);
}
}
void Stream::setPriority(int32_t streamID, int32_t priority)
{
std::lock_guard lock(mLock);
if (streamID == mStreamID) {
mPriority = priority;
}
}
void Stream::setLoop(int32_t streamID, int32_t loop)
{
std::lock_guard lock(mLock);
if (streamID == mStreamID) {
if (mAudioTrack != nullptr && mSound != nullptr) {
const uint32_t loopEnd = mSound->getSizeInBytes() / mSound->getChannelCount() /
(mSound->getFormat() == AUDIO_FORMAT_PCM_16_BIT
? sizeof(int16_t) : sizeof(uint8_t));
mAudioTrack->setLoop(0, loopEnd, loop);
}
mLoop = loop;
}
}
void Stream::setPlay(
int32_t streamID, const std::shared_ptr<Sound> &sound, int32_t soundID,
float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate)
{
std::lock_guard lock(mLock);
// We must be idle, or we must be repurposing a pending Stream.
LOG_ALWAYS_FATAL_IF(mState != IDLE && mAudioTrack != nullptr, "State %d must be IDLE", mState);
mSound = sound;
mSoundID = soundID;
mLeftVolume = leftVolume;
mRightVolume = rightVolume;
mPriority = priority;
mLoop = loop;
mRate = rate;
mState = PLAYING;
mAutoPaused = false; // New for R (consistent with Java API spec).
mStreamID = streamID; // prefer this to be the last, as it is an atomic sync point
}
void Stream::setStopTimeNs(int64_t stopTimeNs)
{
std::lock_guard lock(mLock);
mStopTimeNs = stopTimeNs;
}
bool Stream::requestStop(int32_t streamID)
{
std::lock_guard lock(mLock);
if (streamID == mStreamID) {
if (mAudioTrack != nullptr) {
if (mState == PLAYING && !mMuted && (mLeftVolume != 0.f || mRightVolume != 0.f)) {
setVolume_l(0.f, 0.f);
mStopTimeNs = systemTime() + kStopWaitTimeNs;
} else {
mStopTimeNs = systemTime();
}
return true; // must be queued on the restart list.
}
stop_l();
}
return false;
}
void Stream::stop()
{
std::lock_guard lock(mLock);
stop_l();
}
void Stream::stop_l()
{
if (mState != IDLE) {
if (mAudioTrack != nullptr) {
mAudioTrack->stop();
}
mSound.reset();
mState = IDLE;
}
}
void Stream::clearAudioTrack()
{
// This will invoke the destructor which waits for the AudioTrack thread to join,
// and is currently the only safe way to ensure there are no callbacks afterwards.
mAudioTrack.clear();
}
Stream* Stream::getPairStream() const
{
return mStreamManager->getPairStream(this);
}
Stream* Stream::playPairStream() {
Stream* pairStream = getPairStream();
LOG_ALWAYS_FATAL_IF(pairStream == nullptr, "No pair stream!");
sp<AudioTrack> releaseTracks[2];
{
// TODO: Do we really want to force a simultaneous synchronization between
// the stream and its pair?
// note locking order - the paired stream is obtained before the queued stream.
// we can invert the locking order, but it is slightly more optimal to do it this way.
std::lock_guard lockp(pairStream->mLock);
if (pairStream->mSound == nullptr) {
return nullptr; // no pair sound
}
{
std::lock_guard lock(mLock);
LOG_ALWAYS_FATAL_IF(mState != IDLE, "State: %d must be IDLE", mState);
// TODO: do we want a specific set() here?
pairStream->mAudioTrack = mAudioTrack;
pairStream->mSoundID = mSoundID; // optimization to reuse AudioTrack.
pairStream->mToggle = mToggle;
pairStream->mAutoPaused = mAutoPaused; // save autopause state
pairStream->mMuted = mMuted;
mAudioTrack.clear(); // the pair owns the audiotrack.
mSound.reset();
mSoundID = 0;
}
// TODO: do we need a specific play_l() anymore?
const int pairState = pairStream->mState;
pairStream->play_l(pairStream->mSound, pairStream->mStreamID,
pairStream->mLeftVolume, pairStream->mRightVolume, pairStream->mPriority,
pairStream->mLoop, pairStream->mRate, releaseTracks);
if (pairStream->mState == IDLE) {
return nullptr; // AudioTrack error
}
if (pairState == PAUSED) { // reestablish pause
pairStream->mState = PAUSED;
pairStream->mAudioTrack->pause();
}
}
// release tracks outside of Stream lock
return pairStream;
}
void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID,
float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate,
sp<AudioTrack> releaseTracks[2])
{
// These tracks are released without the lock.
sp<AudioTrack> &oldTrack = releaseTracks[0];
sp<AudioTrack> &newTrack = releaseTracks[1];
status_t status = NO_ERROR;
{
ALOGV("%s(%p)(soundID=%d, streamID=%d, leftVolume=%f, rightVolume=%f,"
" priority=%d, loop=%d, rate=%f)",
__func__, this, sound->getSoundID(), nextStreamID, leftVolume, rightVolume,
priority, loop, rate);
// initialize track
const audio_stream_type_t streamType =
AudioSystem::attributesToStreamType(*mStreamManager->getAttributes());
const int32_t channelCount = sound->getChannelCount();
const uint32_t sampleRate = uint32_t(float(sound->getSampleRate()) * rate + 0.5);
size_t frameCount = 0;
if (loop) {
const audio_format_t format = sound->getFormat();
const size_t frameSize = audio_is_linear_pcm(format)
? channelCount * audio_bytes_per_sample(format) : 1;
frameCount = sound->getSizeInBytes() / frameSize;
}
// check if the existing track has the same sound id.
if (mAudioTrack != nullptr && mSoundID == sound->getSoundID()) {
// the sample rate may fail to change if the audio track is a fast track.
if (mAudioTrack->setSampleRate(sampleRate) == NO_ERROR) {
newTrack = mAudioTrack;
ALOGV("%s: reusing track %p for sound %d",
__func__, mAudioTrack.get(), sound->getSoundID());
}
}
if (newTrack == 0) {
// mToggle toggles each time a track is started on a given stream.
// The toggle is concatenated with the Stream address and passed to AudioTrack
// as callback user data. This enables the detection of callbacks received from the old
// audio track while the new one is being started and avoids processing them with
// wrong audio audio buffer size (mAudioBufferSize)
auto toggle = mToggle ^ 1;
void* userData = (void*)((uintptr_t)this | toggle);
audio_channel_mask_t soundChannelMask = sound->getChannelMask();
// When sound contains a valid channel mask, use it as is.
// Otherwise, use stream count to calculate channel mask.
audio_channel_mask_t channelMask = soundChannelMask != AUDIO_CHANNEL_NONE
? soundChannelMask : audio_channel_out_mask_from_count(channelCount);
// do not create a new audio track if current track is compatible with sound parameters
newTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(),
channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST,
staticCallback, userData,
0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
AudioTrack::TRANSFER_DEFAULT,
nullptr /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/,
mStreamManager->getAttributes());
oldTrack = mAudioTrack;
status = newTrack->initCheck();
if (status != NO_ERROR) {
ALOGE("%s: error creating AudioTrack", __func__);
// newTrack goes out of scope, so reference count drops to zero
goto exit;
}
// From now on, AudioTrack callbacks received with previous toggle value will be ignored.
mToggle = toggle;
mAudioTrack = newTrack;
ALOGV("%s: using new track %p for sound %d",
__func__, newTrack.get(), sound->getSoundID());
}
if (mMuted) {
newTrack->setVolume(0.0f, 0.0f);
} else {
newTrack->setVolume(leftVolume, rightVolume);
}
newTrack->setLoop(0, frameCount, loop);
mAudioTrack->start();
mSound = sound;
mSoundID = sound->getSoundID();
mPriority = priority;
mLoop = loop;
mLeftVolume = leftVolume;
mRightVolume = rightVolume;
mRate = rate;
mState = PLAYING;
mStopTimeNs = 0;
mStreamID = nextStreamID; // prefer this to be the last, as it is an atomic sync point
}
exit:
ALOGV("%s: delete oldTrack %p", __func__, oldTrack.get());
if (status != NO_ERROR) {
// TODO: should we consider keeping the soundID if the old track is OK?
// Do not attempt to restart this track (should we remove the stream id?)
mState = IDLE;
mSoundID = 0;
mSound.reset();
mAudioTrack.clear(); // actual release from releaseTracks[]
}
}
/* static */
void Stream::staticCallback(int event, void* user, void* info)
{
const uintptr_t userAsInt = (uintptr_t)user;
Stream* stream = reinterpret_cast<Stream*>(userAsInt & ~1);
stream->callback(event, info, userAsInt & 1, 0 /* tries */);
}
void Stream::callback(int event, void* info, int toggle, int tries)
{
ALOGV("%s streamID %d", __func__, (int)mStreamID);
int32_t activeStreamIDToRestart = 0;
{
std::unique_lock lock(mLock);
if (mAudioTrack == nullptr) {
// The AudioTrack is either with this stream or its pair.
// if this swaps a few times, the toggle is bound to be wrong, so we fail then.
//
// TODO: Modify AudioTrack callbacks to avoid the hacky toggle and retry
// logic here.
if (tries < 3) {
lock.unlock();
getPairStream()->callback(event, info, toggle, tries + 1);
} else {
ALOGW("%s streamID %d cannot find track", __func__, (int)mStreamID);
}
return;
}
if (mToggle != toggle) {
ALOGD("%s streamID %d wrong toggle", __func__, (int)mStreamID);
return;
}
switch (event) {
case AudioTrack::EVENT_MORE_DATA:
ALOGW("%s streamID %d Invalid EVENT_MORE_DATA for static track",
__func__, (int)mStreamID);
break;
case AudioTrack::EVENT_UNDERRUN:
ALOGW("%s streamID %d Invalid EVENT_UNDERRUN for static track",
__func__, (int)mStreamID);
break;
case AudioTrack::EVENT_BUFFER_END:
ALOGV("%s streamID %d EVENT_BUFFER_END", __func__, (int)mStreamID);
if (mState != IDLE) {
activeStreamIDToRestart = mStreamID;
mStopTimeNs = systemTime();
}
break;
case AudioTrack::EVENT_LOOP_END:
ALOGV("%s streamID %d EVENT_LOOP_END", __func__, (int)mStreamID);
break;
case AudioTrack::EVENT_NEW_IAUDIOTRACK:
ALOGV("%s streamID %d NEW_IAUDIOTRACK", __func__, (int)mStreamID);
break;
default:
ALOGW("%s streamID %d Invalid event %d", __func__, (int)mStreamID, event);
break;
}
} // lock ends here. This is on the callback thread, no need to be precise.
if (activeStreamIDToRestart > 0) {
// Restart only if a particular streamID is still current and active.
ALOGV("%s: moveToRestartQueue %d", __func__, activeStreamIDToRestart);
mStreamManager->moveToRestartQueue(this, activeStreamIDToRestart);
}
}
void Stream::dump() const
{
ALOGV("mPairStream=%p, mState=%d, mStreamID=%d, mSoundID=%d, mPriority=%d, mLoop=%d",
getPairStream(), mState, (int)mStreamID, mSoundID, mPriority, mLoop);
}
} // namespace android::soundpool

View File

@@ -0,0 +1,150 @@
/*
* 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.
*/
#pragma once
#include "Sound.h"
#include <audio_utils/clock.h>
#include <media/AudioTrack.h>
namespace android::soundpool {
// This is the amount of time to wait after stop is called when stealing an
// AudioTrack to allow the sound to ramp down. If this is 0, glitches
// may occur when stealing an AudioTrack.
inline constexpr int64_t kStopWaitTimeNs = 20 * NANOS_PER_MILLISECOND;
inline constexpr size_t kCacheLineSize = 64; /* std::hardware_constructive_interference_size */
class StreamManager; // forward decl
/**
* A Stream is associated with a StreamID exposed to the app to play a Sound.
*
* The Stream uses monitor locking strategy on mLock.
* https://en.wikipedia.org/wiki/Monitor_(synchronization)
*
* where public methods are guarded by a lock (as needed)
*
* For Java equivalent APIs, see
* https://developer.android.com/reference/android/media/SoundPool
*
* Streams are paired by the StreamManager, so one stream in the pair may be "stopping"
* while the other stream of the pair has been prepared to run
* (and the streamID returned to the app) pending its pair to be stopped.
* The pair of a Stream may be obtained by calling getPairStream(),
* where this->getPairStream()->getPairStream() == this; (pair is a commutative relationship).
*
* playPairStream() and getPairPriority() access the paired stream.
* See also StreamManager.h for details of physical layout implications of paired streams.
*/
class alignas(kCacheLineSize) Stream {
public:
enum state { IDLE, PAUSED, PLAYING };
// The PAUSED, PLAYING state directly corresponds to the AudioTrack state of an active Stream.
//
// The IDLE state indicates an inactive Stream. An IDLE Stream may have a non-nullptr
// AudioTrack, which may be recycled for use if the SoundID matches the next Stream playback.
//
// PAUSED -> PLAYING through resume() (see also autoResume())
// PLAYING -> PAUSED through pause() (see also autoPause())
//
// IDLE is the initial state of a Stream and also when a stream becomes inactive.
// {PAUSED, PLAYING} -> IDLE through stop() (or if the Sound finishes playing)
// IDLE -> PLAYING through play(). (there is no way to start a Stream in paused mode).
~Stream();
void setStreamManager(StreamManager* streamManager) { // non-nullptr
mStreamManager = streamManager; // set in StreamManager constructor, not changed
}
// The following methods are monitor locked by mLock.
//
// For methods taking a streamID:
// if the streamID matches the Stream's mStreamID, then method proceeds
// else the command is ignored with no effect.
// returns true if the stream needs to be explicitly stopped.
bool requestStop(int32_t streamID);
void stop(); // explicit stop(), typically called from the worker thread.
void clearAudioTrack();
void pause(int32_t streamID);
void autoPause(); // see the Java SoundPool.autoPause documentation for details.
void resume(int32_t streamID);
void autoResume();
void mute(bool muting);
void dump() const;
// returns the pair stream if successful, nullptr otherwise
Stream* playPairStream();
// These parameters are explicitly checked in the SoundPool class
// so never deviate from the Java API specified values.
void setVolume(int32_t streamID, float leftVolume, float rightVolume);
void setRate(int32_t streamID, float rate);
void setPriority(int32_t streamID, int priority);
void setLoop(int32_t streamID, int loop);
void setPlay(int32_t streamID, const std::shared_ptr<Sound> &sound, int32_t soundID,
float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate);
void setStopTimeNs(int64_t stopTimeNs); // systemTime() clock monotonic.
// The following getters are not locked and have weak consistency.
// These are considered advisory only - being stale is of nuisance.
int32_t getPriority() const { return mPriority; }
int32_t getPairPriority() const { return getPairStream()->getPriority(); }
int64_t getStopTimeNs() const { return mStopTimeNs; }
int32_t getStreamID() const { return mStreamID; } // Can change with setPlay()
int32_t getSoundID() const { return mSoundID; } // Can change with play_l()
bool hasSound() const { return mSound.get() != nullptr; }
Stream* getPairStream() const; // this never changes. See top of header.
private:
void play_l(const std::shared_ptr<Sound>& sound, int streamID,
float leftVolume, float rightVolume, int priority, int loop, float rate,
sp<AudioTrack> releaseTracks[2]);
void stop_l();
void setVolume_l(float leftVolume, float rightVolume);
// For use with AudioTrack callback.
static void staticCallback(int event, void* user, void* info);
void callback(int event, void* info, int toggle, int tries);
// StreamManager should be set on construction and not changed.
// release mLock before calling into StreamManager
StreamManager* mStreamManager = nullptr;
mutable std::mutex mLock;
std::atomic_int32_t mStreamID = 0; // Note: valid streamIDs are always positive.
int mState = IDLE;
std::shared_ptr<Sound> mSound; // Non-null if playing.
int32_t mSoundID = 0; // The sound ID associated with the AudioTrack.
float mLeftVolume = 0.f;
float mRightVolume = 0.f;
int32_t mPriority = INT32_MIN;
int32_t mLoop = 0;
float mRate = 0.f;
bool mAutoPaused = false;
bool mMuted = false;
sp<AudioTrack> mAudioTrack;
int mToggle = 0;
int64_t mStopTimeNs = 0; // if nonzero, time to wait for stop.
};
} // namespace android::soundpool

View File

@@ -0,0 +1,407 @@
/*
* 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::StreamManager"
#include <utils/Log.h>
#include "StreamManager.h"
#include <audio_utils/clock.h>
#include <audio_utils/roundup.h>
namespace android::soundpool {
// kMaxStreams is number that should be less than the current AudioTrack max per UID of 40.
// It is the maximum number of AudioTrack resources allowed in the SoundPool.
// We suggest a value at least 4 or greater to allow CTS tests to pass.
static constexpr int32_t kMaxStreams = 32;
// kStealActiveStream_OldestFirst = false historically (Q and earlier)
// Changing to true could break app expectations but could change behavior beneficially.
// In R, we change this to true, as it is the correct way per SoundPool documentation.
static constexpr bool kStealActiveStream_OldestFirst = true;
// kPlayOnCallingThread = true prior to R.
// Changing to false means calls to play() are almost instantaneous instead of taking around
// ~10ms to launch the AudioTrack. It is perhaps 100x faster.
static constexpr bool kPlayOnCallingThread = false;
// Amount of time for a StreamManager thread to wait before closing.
static constexpr int64_t kWaitTimeBeforeCloseNs = 9 * NANOS_PER_SECOND;
////////////
StreamMap::StreamMap(int32_t streams) {
ALOGV("%s(%d)", __func__, streams);
if (streams > kMaxStreams) {
ALOGW("%s: requested %d streams, clamping to %d", __func__, streams, kMaxStreams);
streams = kMaxStreams;
} else if (streams < 1) {
ALOGW("%s: requested %d streams, clamping to 1", __func__, streams);
streams = 1;
}
mStreamPoolSize = streams * 2;
mStreamPool.reset(new Stream[mStreamPoolSize]);
// we use a perfect hash table with 2x size to map StreamIDs to Stream pointers.
mPerfectHash = std::make_unique<PerfectHash<int32_t, Stream *>>(roundup(mStreamPoolSize * 2));
}
Stream* StreamMap::findStream(int32_t streamID) const
{
Stream *stream = lookupStreamFromId(streamID);
return stream != nullptr && stream->getStreamID() == streamID ? stream : nullptr;
}
size_t StreamMap::streamPosition(const Stream* stream) const
{
ptrdiff_t index = stream - mStreamPool.get();
LOG_ALWAYS_FATAL_IF(index < 0 || index >= mStreamPoolSize,
"%s: stream position out of range: %td", __func__, index);
return (size_t)index;
}
Stream* StreamMap::lookupStreamFromId(int32_t streamID) const
{
return streamID > 0 ? mPerfectHash->getValue(streamID).load() : nullptr;
}
int32_t StreamMap::getNextIdForStream(Stream* stream) const {
// even though it is const, it mutates the internal hash table.
const int32_t id = mPerfectHash->generateKey(
stream,
[] (Stream *stream) {
return stream == nullptr ? 0 : stream->getStreamID();
}, /* getKforV() */
stream->getStreamID() /* oldID */);
return id;
}
////////////
StreamManager::StreamManager(
int32_t streams, size_t threads, const audio_attributes_t* attributes)
: StreamMap(streams)
, mAttributes(*attributes)
{
ALOGV("%s(%d, %zu, ...)", __func__, streams, threads);
forEach([this](Stream *stream) {
stream->setStreamManager(this);
if ((streamPosition(stream) & 1) == 0) { // put the first stream of pair as available.
mAvailableStreams.insert(stream);
}
});
mThreadPool = std::make_unique<ThreadPool>(
std::min(threads, (size_t)std::thread::hardware_concurrency()),
"SoundPool_");
}
StreamManager::~StreamManager()
{
ALOGV("%s", __func__);
{
std::unique_lock lock(mStreamManagerLock);
mQuit = true;
mStreamManagerCondition.notify_all();
}
mThreadPool->quit();
// call stop on the stream pool
forEach([](Stream *stream) { stream->stop(); });
// This invokes the destructor on the AudioTracks -
// we do it here to ensure that AudioTrack callbacks will not occur
// afterwards.
forEach([](Stream *stream) { stream->clearAudioTrack(); });
}
int32_t StreamManager::queueForPlay(const std::shared_ptr<Sound> &sound,
int32_t soundID, float leftVolume, float rightVolume,
int32_t priority, int32_t loop, float rate)
{
ALOGV("%s(sound=%p, soundID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f)",
__func__, sound.get(), soundID, leftVolume, rightVolume, priority, loop, rate);
bool launchThread = false;
int32_t streamID = 0;
{ // for lock
std::unique_lock lock(mStreamManagerLock);
Stream *newStream = nullptr;
bool fromAvailableQueue = false;
ALOGV("%s: mStreamManagerLock lock acquired", __func__);
sanityCheckQueue_l();
// find an available stream, prefer one that has matching sound id.
if (mAvailableStreams.size() > 0) {
newStream = *mAvailableStreams.begin();
for (auto stream : mAvailableStreams) {
if (stream->getSoundID() == soundID) {
newStream = stream;
break;
}
}
if (newStream != nullptr) {
newStream->setStopTimeNs(systemTime());
}
fromAvailableQueue = true;
}
// also look in the streams restarting (if the paired stream doesn't have a pending play)
if (newStream == nullptr || newStream->getSoundID() != soundID) {
for (auto [unused , stream] : mRestartStreams) {
if (!stream->getPairStream()->hasSound()) {
if (stream->getSoundID() == soundID) {
newStream = stream;
break;
} else if (newStream == nullptr) {
newStream = stream;
}
}
}
}
// no available streams, look for one to steal from the active list
if (newStream == nullptr) {
for (auto stream : mActiveStreams) {
if (stream->getPriority() <= priority) {
if (newStream == nullptr
|| newStream->getPriority() > stream->getPriority()) {
newStream = stream;
}
}
}
if (newStream != nullptr) { // we need to mute as it is still playing.
(void)newStream->requestStop(newStream->getStreamID());
}
}
// none found, look for a stream that is restarting, evict one.
if (newStream == nullptr) {
for (auto [unused, stream] : mRestartStreams) {
if (stream->getPairPriority() <= priority) {
newStream = stream;
break;
}
}
}
// DO NOT LOOK into mProcessingStreams as those are held by the StreamManager threads.
if (newStream == nullptr) {
ALOGD("%s: unable to find stream, returning 0", __func__);
return 0; // unable to find available stream
}
Stream *pairStream = newStream->getPairStream();
streamID = getNextIdForStream(pairStream);
pairStream->setPlay(
streamID, sound, soundID, leftVolume, rightVolume, priority, loop, rate);
if (fromAvailableQueue && kPlayOnCallingThread) {
removeFromQueues_l(newStream);
mProcessingStreams.emplace(newStream);
lock.unlock();
if (Stream* nextStream = newStream->playPairStream()) {
lock.lock();
ALOGV("%s: starting streamID:%d", __func__, nextStream->getStreamID());
addToActiveQueue_l(nextStream);
} else {
lock.lock();
mAvailableStreams.insert(newStream);
streamID = 0;
}
mProcessingStreams.erase(newStream);
} else {
launchThread = moveToRestartQueue_l(newStream) && needMoreThreads_l();
}
sanityCheckQueue_l();
ALOGV("%s: mStreamManagerLock released", __func__);
} // lock
if (launchThread) {
const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); });
ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
}
ALOGV("%s: returning %d", __func__, streamID);
return streamID;
}
void StreamManager::moveToRestartQueue(
Stream* stream, int32_t activeStreamIDToMatch)
{
ALOGV("%s(stream(ID)=%d, activeStreamIDToMatch=%d)",
__func__, stream->getStreamID(), activeStreamIDToMatch);
bool restart;
{
std::lock_guard lock(mStreamManagerLock);
sanityCheckQueue_l();
if (mProcessingStreams.count(stream) > 0 ||
mProcessingStreams.count(stream->getPairStream()) > 0) {
ALOGD("%s: attempting to restart processing stream(%d)",
__func__, stream->getStreamID());
restart = false;
} else {
moveToRestartQueue_l(stream, activeStreamIDToMatch);
restart = needMoreThreads_l();
}
sanityCheckQueue_l();
}
if (restart) {
const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); });
ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
}
}
bool StreamManager::moveToRestartQueue_l(
Stream* stream, int32_t activeStreamIDToMatch)
{
ALOGV("%s(stream(ID)=%d, activeStreamIDToMatch=%d)",
__func__, stream->getStreamID(), activeStreamIDToMatch);
if (activeStreamIDToMatch > 0 && stream->getStreamID() != activeStreamIDToMatch) {
return false;
}
const ssize_t found = removeFromQueues_l(stream, activeStreamIDToMatch);
if (found < 0) return false;
LOG_ALWAYS_FATAL_IF(found > 1, "stream on %zd > 1 stream lists", found);
addToRestartQueue_l(stream);
mStreamManagerCondition.notify_one();
return true;
}
ssize_t StreamManager::removeFromQueues_l(
Stream* stream, int32_t activeStreamIDToMatch) {
size_t found = 0;
for (auto it = mActiveStreams.begin(); it != mActiveStreams.end(); ++it) {
if (*it == stream) {
mActiveStreams.erase(it); // we erase the iterator and break (otherwise it not safe).
++found;
break;
}
}
// activeStreamIDToMatch is nonzero indicates we proceed only if found.
if (found == 0 && activeStreamIDToMatch > 0) {
return -1; // special code: not present on active streams, ignore restart request
}
for (auto it = mRestartStreams.begin(); it != mRestartStreams.end(); ++it) {
if (it->second == stream) {
mRestartStreams.erase(it);
++found;
break;
}
}
found += mAvailableStreams.erase(stream);
// streams on mProcessingStreams are undergoing processing by the StreamManager thread
// and do not participate in normal stream migration.
return found;
}
void StreamManager::addToRestartQueue_l(Stream *stream) {
mRestartStreams.emplace(stream->getStopTimeNs(), stream);
}
void StreamManager::addToActiveQueue_l(Stream *stream) {
if (kStealActiveStream_OldestFirst) {
mActiveStreams.push_back(stream); // oldest to newest
} else {
mActiveStreams.push_front(stream); // newest to oldest
}
}
void StreamManager::run(int32_t id)
{
ALOGV("%s(%d) entering", __func__, id);
int64_t waitTimeNs = kWaitTimeBeforeCloseNs;
std::unique_lock lock(mStreamManagerLock);
while (!mQuit) {
mStreamManagerCondition.wait_for(
lock, std::chrono::duration<int64_t, std::nano>(waitTimeNs));
ALOGV("%s(%d) awake", __func__, id);
sanityCheckQueue_l();
if (mQuit || (mRestartStreams.empty() && waitTimeNs == kWaitTimeBeforeCloseNs)) {
break; // end the thread
}
waitTimeNs = kWaitTimeBeforeCloseNs;
while (!mQuit && !mRestartStreams.empty()) {
const nsecs_t nowNs = systemTime();
auto it = mRestartStreams.begin();
Stream* const stream = it->second;
const int64_t diffNs = stream->getStopTimeNs() - nowNs;
if (diffNs > 0) {
waitTimeNs = std::min(waitTimeNs, diffNs);
break;
}
mRestartStreams.erase(it);
mProcessingStreams.emplace(stream);
lock.unlock();
stream->stop();
ALOGV("%s(%d) stopping streamID:%d", __func__, id, stream->getStreamID());
if (Stream* nextStream = stream->playPairStream()) {
ALOGV("%s(%d) starting streamID:%d", __func__, id, nextStream->getStreamID());
lock.lock();
if (nextStream->getStopTimeNs() > 0) {
// the next stream was stopped before we can move it to the active queue.
ALOGV("%s(%d) stopping started streamID:%d",
__func__, id, nextStream->getStreamID());
moveToRestartQueue_l(nextStream);
} else {
addToActiveQueue_l(nextStream);
}
} else {
lock.lock();
mAvailableStreams.insert(stream);
}
mProcessingStreams.erase(stream);
sanityCheckQueue_l();
}
}
ALOGV("%s(%d) exiting", __func__, id);
}
void StreamManager::dump() const
{
forEach([](const Stream *stream) { stream->dump(); });
}
void StreamManager::sanityCheckQueue_l() const
{
// We want to preserve the invariant that each stream pair is exactly on one of the queues.
const size_t availableStreams = mAvailableStreams.size();
const size_t restartStreams = mRestartStreams.size();
const size_t activeStreams = mActiveStreams.size();
const size_t processingStreams = mProcessingStreams.size();
const size_t managedStreams = availableStreams + restartStreams + activeStreams
+ processingStreams;
const size_t totalStreams = getStreamMapSize() >> 1;
LOG_ALWAYS_FATAL_IF(managedStreams != totalStreams,
"%s: mAvailableStreams:%zu + mRestartStreams:%zu + "
"mActiveStreams:%zu + mProcessingStreams:%zu = %zu != total streams %zu",
__func__, availableStreams, restartStreams, activeStreams, processingStreams,
managedStreams, totalStreams);
ALOGV("%s: mAvailableStreams:%zu + mRestartStreams:%zu + "
"mActiveStreams:%zu + mProcessingStreams:%zu = %zu (total streams: %zu)",
__func__, availableStreams, restartStreams, activeStreams, processingStreams,
managedStreams, totalStreams);
}
} // namespace android::soundpool

View File

@@ -0,0 +1,467 @@
/*
* 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.
*/
#pragma once
#include "Stream.h"
#include <condition_variable>
#include <future>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <unordered_set>
#include <vector>
#include <utils/AndroidThreads.h>
namespace android::soundpool {
// TODO: Move helper classes to a utility file, with separate test.
/**
* JavaThread is used like std::thread but for threads that may call the JVM.
*
* std::thread does not easily attach to the JVM. We need JVM capable threads
* from createThreadEtc() since android binder call optimization may attempt to
* call back into Java if the SoundPool runs in system server.
*
*
* No locking is required - the member variables are inherently thread-safe.
*/
class JavaThread {
public:
JavaThread(std::function<void()> f, const char *name)
: mF{std::move(f)} {
createThreadEtc(staticFunction, this, name);
}
JavaThread(JavaThread &&) = delete; // uses "this" ptr, not moveable.
void join() const {
mFuture.wait();
}
bool isClosed() const {
return mIsClosed;
}
private:
static int staticFunction(void *data) {
JavaThread *jt = static_cast<JavaThread *>(data);
jt->mF();
jt->mIsClosed = true;
jt->mPromise.set_value();
return 0;
}
// No locking is provided as these variables are initialized in the constructor
// and the members referenced are thread-safe objects.
// (mFuture.wait() can block multiple threads.)
// Note the order of member variables is reversed for destructor.
const std::function<void()> mF;
// Used in join() to block until the thread completes.
// See https://en.cppreference.com/w/cpp/thread/promise for the void specialization of
// promise.
std::promise<void> mPromise;
std::future<void> mFuture{mPromise.get_future()};
std::atomic_bool mIsClosed = false;
};
/**
* The ThreadPool manages thread lifetimes of SoundPool worker threads.
*
* TODO: the (eventual) goal of ThreadPool is to transparently and cooperatively
* maximize CPU utilization while avoiding starvation of other applications.
* Some possibilities:
*
* We should create worker threads when we have SoundPool work and the system is idle.
* CPU cycles are "use-it-or-lose-it" when the system is idle.
*
* We should adjust the priority of worker threads so that the second (and subsequent) worker
* threads have lower priority (should we try to promote priority also?).
*
* We should throttle the spawning of new worker threads, spacing over time, to avoid
* creating too many new threads all at once, on initialization.
*/
class ThreadPool {
public:
ThreadPool(size_t maxThreadCount, std::string name)
: mMaxThreadCount(maxThreadCount)
, mName{std::move(name)} { }
~ThreadPool() { quit(); }
size_t getActiveThreadCount() const { return mActiveThreadCount; }
size_t getMaxThreadCount() const { return mMaxThreadCount; }
void quit() {
std::list<std::unique_ptr<JavaThread>> threads;
{
std::lock_guard lock(mThreadLock);
if (mQuit) return; // already joined.
mQuit = true;
threads = std::move(mThreads);
mThreads.clear();
}
// mQuit set under lock, no more threads will be created.
for (auto &thread : threads) {
thread->join();
thread.reset();
}
LOG_ALWAYS_FATAL_IF(mActiveThreadCount != 0,
"Invalid Active Threads: %zu", (size_t)mActiveThreadCount);
}
// returns a non-zero id if successful, the id is to help logging messages.
int32_t launch(std::function<void(int32_t /* id */)> f) {
std::list<std::unique_ptr<JavaThread>> threadsToRelease; // release outside of lock.
std::lock_guard lock(mThreadLock);
if (mQuit) return 0; // ignore if we have quit
// clean up threads.
for (auto it = mThreads.begin(); it != mThreads.end(); ) {
if ((*it)->isClosed()) {
threadsToRelease.emplace_back(std::move(*it));
it = mThreads.erase(it);
} else {
++it;
}
}
const size_t threadCount = mThreads.size();
if (threadCount < mMaxThreadCount) {
// if the id wraps, we don't care about collisions. it's just for logging.
mNextThreadId = mNextThreadId == INT32_MAX ? 1 : ++mNextThreadId;
const int32_t id = mNextThreadId;
mThreads.emplace_back(std::make_unique<JavaThread>(
[this, id, mf = std::move(f)] { mf(id); --mActiveThreadCount; },
(mName + std::to_string(id)).c_str()));
++mActiveThreadCount;
return id;
}
return 0;
}
// TODO: launch only if load average is low.
// This gets the load average
// See also std::thread::hardware_concurrency() for the concurrent capability.
static double getLoadAvg() {
double loadAvg[1];
if (getloadavg(loadAvg, std::size(loadAvg)) > 0) {
return loadAvg[0];
}
return -1.;
}
private:
const size_t mMaxThreadCount;
const std::string mName;
std::atomic_size_t mActiveThreadCount = 0;
std::mutex mThreadLock;
bool mQuit = false; // GUARDED_BY(mThreadLock)
int32_t mNextThreadId = 0; // GUARDED_BY(mThreadLock)
std::list<std::unique_ptr<JavaThread>> mThreads; // GUARDED_BY(mThreadLock)
};
/**
* A Perfect HashTable for IDs (key) to pointers (value).
*
* There are no collisions. Why? because we generate the IDs for you to look up :-).
*
* The goal of this hash table is to map an integer ID handle > 0 to a pointer.
* We give these IDs in monotonic order (though we may skip if it were to cause a collision).
*
* The size of the hashtable must be large enough to accommodate the max number of keys.
* We suggest 2x.
*
* Readers are lockless
* Single writer could be lockless, but we allow multiple writers through an internal lock.
*
* For the Key type K, valid keys generated are > 0 (signed or unsigned)
* For the Value type V, values are pointers - nullptr means empty.
*/
template <typename K, typename V>
class PerfectHash {
public:
PerfectHash(size_t hashCapacity)
: mHashCapacity(hashCapacity)
, mK2V{new std::atomic<V>[hashCapacity]()} {
}
// Generate a key for a value V.
// There is a testing function getKforV() which checks what the value reports as its key.
//
// Calls back into getKforV under lock.
//
// We expect that the hashCapacity is 2x the number of stored keys in order
// to have one or two tries to find an empty slot
K generateKey(V value, std::function<K(V)> getKforV, K oldKey = 0) {
std::lock_guard lock(mHashLock);
// try to remove the old key.
if (oldKey > 0) { // key valid
const V v = getValue(oldKey);
if (v != nullptr) { // value still valid
const K atPosition = getKforV(v);
if (atPosition < 0 || // invalid value
atPosition == oldKey || // value's key still valid and matches old key
((atPosition ^ oldKey) & (mHashCapacity - 1)) != 0) { // stale key entry
getValue(oldKey) = nullptr; // invalidate
}
} // else if value is invalid, no need to invalidate.
}
// check if we are invalidating only.
if (value == nullptr) return 0;
// now insert the new value and return the key.
size_t tries = 0;
for (; tries < mHashCapacity; ++tries) {
mNextKey = mNextKey == std::numeric_limits<K>::max() ? 1 : mNextKey + 1;
const V v = getValue(mNextKey);
//ALOGD("tries: %zu, key:%d value:%p", tries, (int)mNextKey, v);
if (v == nullptr) break; // empty
const K atPosition = getKforV(v);
//ALOGD("tries: %zu key atPosition:%d", tries, (int)atPosition);
if (atPosition < 0 || // invalid value
((atPosition ^ mNextKey) & (mHashCapacity - 1)) != 0) { // stale key entry
break;
}
}
LOG_ALWAYS_FATAL_IF(tries == mHashCapacity, "hash table overflow!");
//ALOGD("%s: found after %zu tries", __func__, tries);
getValue(mNextKey) = value;
return mNextKey;
}
std::atomic<V> &getValue(K key) { return mK2V[key & (mHashCapacity - 1)]; }
const std::atomic_int32_t &getValue(K key) const { return mK2V[key & (mHashCapacity - 1)]; }
private:
mutable std::mutex mHashLock;
const size_t mHashCapacity; // size of mK2V no lock needed.
std::unique_ptr<std::atomic<V>[]> mK2V; // no lock needed for read access.
K mNextKey{}; // GUARDED_BY(mHashLock)
};
/**
* StreamMap contains the all the valid streams available to SoundPool.
*
* There is no Lock required for this class because the streams are
* allocated in the constructor, the lookup is lockless, and the Streams
* returned are locked internally.
*
* The lookup uses a perfect hash.
* It is possible to use a lockless hash table or to use a stripe-locked concurrent
* hashmap for essentially lock-free lookup.
*
* This follows Map-Reduce parallelism model.
* https://en.wikipedia.org/wiki/MapReduce
*
* Conceivably the forEach could be parallelized using std::for_each with a
* std::execution::par policy.
*
* https://en.cppreference.com/w/cpp/algorithm/for_each
*/
class StreamMap {
public:
explicit StreamMap(int32_t streams);
// Returns the stream associated with streamID or nullptr if not found.
// This need not be locked.
// The stream ID will never migrate to another Stream, but it may change
// underneath you. The Stream operations that take a streamID will confirm
// that the streamID matches under the Stream lock before executing otherwise
// it ignores the command as stale.
Stream* findStream(int32_t streamID) const;
// Iterates through the stream pool applying the function f.
// Since this enumerates over every single stream, it is unlocked.
//
// See related: https://en.cppreference.com/w/cpp/algorithm/for_each
void forEach(std::function<void(const Stream *)>f) const {
for (size_t i = 0; i < mStreamPoolSize; ++i) {
f(&mStreamPool[i]);
}
}
void forEach(std::function<void(Stream *)>f) {
for (size_t i = 0; i < mStreamPoolSize; ++i) {
f(&mStreamPool[i]);
}
}
// Returns the pair stream for a given Stream.
// This need not be locked as it is a property of the pointer address.
Stream* getPairStream(const Stream* stream) const {
const size_t index = streamPosition(stream);
return &mStreamPool[index ^ 1];
}
// find the position of the stream in mStreamPool array.
size_t streamPosition(const Stream* stream) const; // no lock needed
size_t getStreamMapSize() const {
return mStreamPoolSize;
}
// find the next valid ID for a stream and store in hash table.
int32_t getNextIdForStream(Stream* stream) const;
private:
// use the hash table to attempt to find the stream.
// nullptr is returned if the lookup fails.
Stream* lookupStreamFromId(int32_t streamID) const;
// The stream pool is initialized in the constructor, effectively const.
// no locking required for access.
//
// The constructor parameter "streams" results in streams pairs of streams.
// We have twice as many streams because we wish to return a streamID "handle"
// back to the app immediately, while we may be stopping the other stream in the
// pair to get its AudioTrack :-).
//
// Of the stream pair, only one of the streams may have an AudioTrack.
// The fixed association of a stream pair allows callbacks from the AudioTrack
// to be associated properly to either one or the other of the stream pair.
//
// TODO: The stream pair arrangement can be removed if we have better AudioTrack
// callback handling (being able to remove and change the callback after construction).
//
// Streams may be accessed anytime off of the stream pool
// as there is internal locking on each stream.
std::unique_ptr<Stream[]> mStreamPool; // no lock needed for access.
size_t mStreamPoolSize; // no lock needed for access.
// In order to find the Stream from a StreamID, we could do a linear lookup in mStreamPool.
// As an alternative, one could use stripe-locked or lock-free concurrent hashtables.
//
// When considering linear search vs hashmap, verify the typical use-case size.
// Linear search is faster than std::unordered_map (circa 2018) for less than 40 elements.
// [ Skarupke, M. (2018), "You Can Do Better than std::unordered_map: New and Recent
// Improvements to Hash Table Performance." C++Now 2018. cppnow.org, see
// https://www.youtube.com/watch?v=M2fKMP47slQ ]
//
// Here, we use a PerfectHash of Id to Stream *, since we can control the
// StreamID returned to the user. This allows O(1) read access to mStreamPool lock-free.
//
// We prefer that the next stream ID is monotonic for aesthetic reasons
// (if we didn't care about monotonicity, a simple method is to apply a generation count
// to each stream in the unused upper bits of its index in mStreamPool for the id).
//
std::unique_ptr<PerfectHash<int32_t, Stream *>> mPerfectHash;
};
/**
* StreamManager is used to manage the streams (accessed by StreamID from Java).
*
* Locking order (proceeds from application to component).
* SoundPool mApiLock (if needed) -> StreamManager mStreamManagerLock
* -> pair Stream mLock -> queued Stream mLock
*/
class StreamManager : public StreamMap {
public:
// Note: the SoundPool pointer is only used for stream initialization.
// It is not stored in StreamManager.
StreamManager(int32_t streams, size_t threads, const audio_attributes_t* attributes);
~StreamManager();
// Returns positive streamID on success, 0 on failure. This is locked.
int32_t queueForPlay(const std::shared_ptr<Sound> &sound,
int32_t soundID, float leftVolume, float rightVolume,
int32_t priority, int32_t loop, float rate);
///////////////////////////////////////////////////////////////////////
// Called from soundpool::Stream
const audio_attributes_t* getAttributes() const { return &mAttributes; }
// Moves the stream to the restart queue (called upon BUFFER_END of the static track)
// this is locked internally.
// If activeStreamIDToMatch is nonzero, it will only move to the restart queue
// if the streamIDToMatch is found on the active queue.
void moveToRestartQueue(Stream* stream, int32_t activeStreamIDToMatch = 0);
private:
void run(int32_t id); // worker thread, takes lock internally.
void dump() const; // no lock needed
// returns true if more worker threads are needed.
bool needMoreThreads_l() {
return mRestartStreams.size() > 0 &&
(mThreadPool->getActiveThreadCount() == 0
|| std::distance(mRestartStreams.begin(),
mRestartStreams.upper_bound(systemTime()))
> (ptrdiff_t)mThreadPool->getActiveThreadCount());
}
// returns true if the stream was added.
bool moveToRestartQueue_l(Stream* stream, int32_t activeStreamIDToMatch = 0);
// returns number of queues the stream was removed from (should be 0 or 1);
// a special code of -1 is returned if activeStreamIDToMatch is > 0 and
// the stream wasn't found on the active queue.
ssize_t removeFromQueues_l(Stream* stream, int32_t activeStreamIDToMatch = 0);
void addToRestartQueue_l(Stream *stream);
void addToActiveQueue_l(Stream *stream);
void sanityCheckQueue_l() const;
const audio_attributes_t mAttributes;
std::unique_ptr<ThreadPool> mThreadPool; // locked internally
// mStreamManagerLock is used to lock access for transitions between the
// 4 stream queues by the Manager Thread or by the user initiated play().
// A stream pair has exactly one stream on exactly one of the queues.
std::mutex mStreamManagerLock;
std::condition_variable mStreamManagerCondition;
bool mQuit = false; // GUARDED_BY(mStreamManagerLock)
// There are constructor arg "streams" pairs of streams, only one of each
// pair on the 4 stream queues below. The other stream in the pair serves as
// placeholder to accumulate user changes, pending actual availability of the
// AudioTrack, as it may be in use, requiring stop-then-restart.
//
// The 4 queues are implemented in the appropriate STL container based on perceived
// optimality.
// 1) mRestartStreams: Streams awaiting stop.
// The paired stream may be active (but with no AudioTrack), and will be restarted
// with an active AudioTrack when the current stream is stopped.
std::multimap<int64_t /* stopTimeNs */, Stream*>
mRestartStreams; // GUARDED_BY(mStreamManagerLock)
// 2) mActiveStreams: Streams that are active.
// The paired stream will be inactive.
// This is in order of specified by kStealActiveStream_OldestFirst
std::list<Stream*> mActiveStreams; // GUARDED_BY(mStreamManagerLock)
// 3) mAvailableStreams: Streams that are inactive.
// The paired stream will also be inactive.
// No particular order.
std::unordered_set<Stream*> mAvailableStreams; // GUARDED_BY(mStreamManagerLock)
// 4) mProcessingStreams: Streams that are being processed by the ManagerThreads
// When on this queue, the stream and its pair are not available for stealing.
// Each ManagerThread will have at most one stream on the mProcessingStreams queue.
// The paired stream may be active or restarting.
// No particular order.
std::unordered_set<Stream*> mProcessingStreams; // GUARDED_BY(mStreamManagerLock)
};
} // namespace android::soundpool