summaryrefslogtreecommitdiffstats
path: root/media/jni/soundpool
diff options
context:
space:
mode:
Diffstat (limited to 'media/jni/soundpool')
-rw-r--r--media/jni/soundpool/Android.mk18
-rw-r--r--media/jni/soundpool/SoundPool.cpp729
-rw-r--r--media/jni/soundpool/SoundPool.h212
-rw-r--r--media/jni/soundpool/SoundPoolThread.cpp108
-rw-r--r--media/jni/soundpool/SoundPoolThread.h75
-rw-r--r--media/jni/soundpool/android_media_SoundPool.cpp270
6 files changed, 1412 insertions, 0 deletions
diff --git a/media/jni/soundpool/Android.mk b/media/jni/soundpool/Android.mk
new file mode 100644
index 0000000..374ddeb
--- /dev/null
+++ b/media/jni/soundpool/Android.mk
@@ -0,0 +1,18 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ android_media_SoundPool.cpp \
+ SoundPool.cpp \
+ SoundPoolThread.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libcutils \
+ libutils \
+ libandroid_runtime \
+ libnativehelper \
+ libmedia
+
+LOCAL_MODULE:= libsoundpool
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
new file mode 100644
index 0000000..8ad73d7
--- /dev/null
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -0,0 +1,729 @@
+/*
+ * 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 "SoundPoolRestartThread"
+#include <utils/Log.h>
+
+// XXX needed for timing latency
+#include <utils/Timers.h>
+
+#include <sys/resource.h>
+#include <media/AudioTrack.h>
+#include <media/mediaplayer.h>
+
+#include "SoundPool.h"
+#include "SoundPoolThread.h"
+
+namespace android
+{
+
+int kDefaultBufferCount = 4;
+uint32_t kMaxSampleRate = 44100;
+
+// handles restarting channels that have been stolen
+class SoundPoolRestartThread
+{
+public:
+ SoundPoolRestartThread() : mQuit(false) { createThread(beginThread, this); }
+ void addToRestartList(SoundChannel* channel);
+ void quit();
+
+private:
+ static int beginThread(void* arg);
+ int run();
+
+ Mutex mLock;
+ Condition mCondition;
+ List<SoundChannel*> mRestart;
+ bool mQuit;
+};
+
+void SoundPoolRestartThread::addToRestartList(SoundChannel* channel)
+{
+ Mutex::Autolock lock(&mLock);
+ mRestart.push_back(channel);
+ mCondition.signal();
+}
+
+int SoundPoolRestartThread::beginThread(void* arg)
+{
+ SoundPoolRestartThread* thread = (SoundPoolRestartThread*)arg;
+ return thread->run();
+}
+
+int SoundPoolRestartThread::run()
+{
+ mLock.lock();
+ while (!mQuit) {
+ mCondition.wait(mLock);
+ LOGV("awake");
+ if (mQuit) break;
+
+ while (!mRestart.empty()) {
+ SoundChannel* channel;
+ LOGV("Getting channel from list");
+ List<SoundChannel*>::iterator iter = mRestart.begin();
+ channel = *iter;
+ mRestart.erase(iter);
+ if (channel) {
+ SoundEvent* next = channel->nextEvent();
+ if (next) {
+ LOGV("Starting stolen channel %d -> %d", channel->channelID(), next->mChannelID);
+ channel->play(next->mSample, next->mChannelID, next->mLeftVolume,
+ next->mRightVolume, next->mPriority, next->mLoop,
+ next->mRate);
+ }
+ else {
+ LOGW("stolen channel has no event");
+ }
+ }
+ else {
+ LOGW("no stolen channel to process");
+ }
+ if (mQuit) break;
+ }
+ }
+
+ mRestart.clear();
+ mCondition.signal();
+ mLock.unlock();
+ LOGV("goodbye");
+ return 0;
+}
+
+void SoundPoolRestartThread::quit()
+{
+ mLock.lock();
+ mQuit = true;
+ mCondition.signal();
+ mCondition.wait(mLock);
+ LOGV("return from quit");
+}
+
+#undef LOG_TAG
+#define LOG_TAG "SoundPool"
+
+SoundPool::SoundPool(jobject soundPoolRef, int maxChannels, int streamType, int srcQuality) :
+ mSoundPoolRef(soundPoolRef), mRestartThread(NULL), mDecodeThread(NULL),
+ mChannelPool(NULL), mMaxChannels(maxChannels), mStreamType(streamType),
+ mSrcQuality(srcQuality), mAllocated(0), mNextSampleID(0), mNextChannelID(0)
+{
+ LOGV("SoundPool constructor: maxChannels=%d, streamType=%d, srcQuality=%d",
+ maxChannels, streamType, srcQuality);
+
+ mChannelPool = new SoundChannel[maxChannels];
+ for (int i = 0; i < maxChannels; ++i) {
+ mChannelPool[i].init(this);
+ mChannels.push_back(&mChannelPool[i]);
+ }
+
+ // start decode thread
+ startThreads();
+}
+
+SoundPool::~SoundPool()
+{
+ LOGV("SoundPool destructor");
+ mDecodeThread->quit();
+ mRestartThread->quit();
+
+ Mutex::Autolock lock(&mLock);
+ mChannels.clear();
+ if (mChannelPool)
+ delete [] mChannelPool;
+
+ // clean up samples
+ LOGV("clear samples");
+ mSamples.clear();
+
+ if (mDecodeThread)
+ delete mDecodeThread;
+ if (mRestartThread)
+ delete mRestartThread;
+}
+
+bool SoundPool::startThreads()
+{
+ if (mDecodeThread == NULL)
+ mDecodeThread = new SoundPoolThread(this);
+ if (mRestartThread == NULL)
+ mRestartThread = new SoundPoolRestartThread();
+ return (mDecodeThread && mRestartThread);
+}
+
+SoundChannel* SoundPool::findChannel(int channelID)
+{
+ for (int i = 0; i < mMaxChannels; ++i) {
+ if (mChannelPool[i].channelID() == channelID) {
+ return &mChannelPool[i];
+ }
+ }
+ return NULL;
+}
+
+SoundChannel* SoundPool::findNextChannel(int channelID)
+{
+ for (int i = 0; i < mMaxChannels; ++i) {
+ if (mChannelPool[i].nextEvent()->mChannelID == channelID) {
+ return &mChannelPool[i];
+ }
+ }
+ return NULL;
+}
+
+int SoundPool::load(const char* path, int priority)
+{
+ LOGV("load: path=%s, priority=%d", path, priority);
+ Mutex::Autolock lock(&mLock);
+ sp<Sample> sample = new Sample(++mNextSampleID, path);
+ mSamples.add(sample->sampleID(), sample);
+ doLoad(sample);
+ return sample->sampleID();
+}
+
+int SoundPool::load(int fd, int64_t offset, int64_t length, int priority)
+{
+ LOGV("load: fd=%d, offset=%lld, length=%lld, priority=%d",
+ fd, offset, length, priority);
+ Mutex::Autolock lock(&mLock);
+ sp<Sample> sample = new Sample(++mNextSampleID, fd, offset, length);
+ mSamples.add(sample->sampleID(), sample);
+ doLoad(sample);
+ return sample->sampleID();
+}
+
+void SoundPool::doLoad(sp<Sample>& sample)
+{
+ LOGV("doLoad: loading sample sampleID=%d", sample->sampleID());
+ sample->startLoad();
+ mDecodeThread->loadSample(sample->sampleID());
+}
+
+bool SoundPool::unload(int sampleID)
+{
+ LOGV("unload: sampleID=%d", sampleID);
+ Mutex::Autolock lock(&mLock);
+ return mSamples.removeItem(sampleID);
+}
+
+int SoundPool::play(int sampleID, float leftVolume, float rightVolume,
+ int priority, int loop, float rate)
+{
+ LOGV("sampleID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f",
+ sampleID, leftVolume, rightVolume, priority, loop, rate);
+ Mutex::Autolock lock(&mLock);
+
+ // is sample ready?
+ sp<Sample> sample = findSample(sampleID);
+ if ((sample == 0) || (sample->state() != Sample::READY)) {
+ LOGW(" sample %d not READY", sampleID);
+ return 0;
+ }
+
+ dump();
+
+ // allocate a channel
+ SoundChannel* channel = allocateChannel(priority);
+
+ // no channel allocated - return 0
+ if (!channel) {
+ LOGV("No channel allocated");
+ return 0;
+ }
+
+ int channelID = ++mNextChannelID;
+ LOGV("channel state = %d", channel->state());
+
+ // idle: start playback
+ if (channel->state() == SoundChannel::IDLE) {
+ LOGV("idle channel - starting playback");
+ channel->play(sample, channelID, leftVolume, rightVolume, priority,
+ loop, rate);
+ }
+
+ // stolen: stop, save event data, and let service thread restart it
+ else {
+ LOGV("channel %d stolen - event queued for channel %d", channel->channelID(), channelID);
+ channel->stop();
+ channel->setNextEvent(new SoundEvent(sample, channelID, leftVolume,
+ rightVolume, priority, loop, rate));
+ }
+
+ return channelID;
+}
+
+SoundChannel* SoundPool::allocateChannel(int priority)
+{
+ List<SoundChannel*>::iterator iter;
+ SoundChannel* channel = NULL;
+
+ // allocate a channel
+ if (!mChannels.empty()) {
+ iter = mChannels.begin();
+ if (priority >= (*iter)->priority()) {
+ channel = *iter;
+ mChannels.erase(iter);
+ LOGV("Allocated active channel");
+ }
+ }
+
+ // update priority and put it back in the list
+ if (channel) {
+ channel->setPriority(priority);
+ for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
+ if (priority < (*iter)->priority()) {
+ break;
+ }
+ }
+ mChannels.insert(iter, channel);
+ }
+ return channel;
+}
+
+// move a channel from its current position to the front of the list
+void SoundPool::moveToFront(List<SoundChannel*>& list, SoundChannel* channel)
+{
+ for (List<SoundChannel*>::iterator iter = list.begin(); iter != list.end(); ++iter) {
+ if (*iter == channel) {
+ list.erase(iter);
+ list.push_front(channel);
+ break;
+ }
+ }
+}
+
+void SoundPool::pause(int channelID)
+{
+ LOGV("pause(%d)", channelID);
+ Mutex::Autolock lock(&mLock);
+ SoundChannel* channel = findChannel(channelID);
+ if (channel) {
+ channel->pause();
+ }
+}
+
+void SoundPool::resume(int channelID)
+{
+ LOGV("resume(%d)", channelID);
+ Mutex::Autolock lock(&mLock);
+ SoundChannel* channel = findChannel(channelID);
+ if (channel) {
+ channel->resume();
+ }
+}
+
+void SoundPool::stop(int channelID)
+{
+ LOGV("stop(%d)", channelID);
+ Mutex::Autolock lock(&mLock);
+ SoundChannel* channel = findChannel(channelID);
+ if (channel) {
+ channel->stop();
+ } else {
+ channel = findNextChannel(channelID);
+ if (channel)
+ channel->clearNextEvent();
+ }
+}
+
+void SoundPool::setVolume(int channelID, float leftVolume, float rightVolume)
+{
+ LOGV("setVolume(%d, %f, %f)", channelID, leftVolume, rightVolume);
+ Mutex::Autolock lock(&mLock);
+ SoundChannel* channel = findChannel(channelID);
+ if (channel) {
+ channel->setVolume(leftVolume, rightVolume);
+ }
+}
+
+void SoundPool::setPriority(int channelID, int priority)
+{
+ LOGV("setPriority(%d, %d)", channelID, priority);
+ Mutex::Autolock lock(&mLock);
+ SoundChannel* channel = findChannel(channelID);
+ if (channel) {
+ channel->setPriority(priority);
+ }
+}
+
+void SoundPool::setLoop(int channelID, int loop)
+{
+ LOGV("setLoop(%d, %d)", channelID, loop);
+ Mutex::Autolock lock(&mLock);
+ SoundChannel* channel = findChannel(channelID);
+ if (channel) {
+ channel->setLoop(loop);
+ }
+}
+
+void SoundPool::setRate(int channelID, float rate)
+{
+ LOGV("setRate(%d, %f)", channelID, rate);
+ Mutex::Autolock lock(&mLock);
+ SoundChannel* channel = findChannel(channelID);
+ if (channel) {
+ channel->setRate(rate);
+ }
+}
+
+void SoundPool::done(SoundChannel* channel)
+{
+ LOGV("done(%d)", channel->channelID());
+
+ // if "stolen", play next event
+ SoundEvent* next = channel->nextEvent();
+ if (next) {
+ LOGV("add to restart list");
+ mRestartThread->addToRestartList(channel);
+ }
+
+ // return to idle state
+ else {
+ LOGV("move to front");
+ moveToFront(mChannels, channel);
+ }
+}
+
+void SoundPool::dump()
+{
+ for (int i = 0; i < mMaxChannels; ++i) {
+ mChannelPool[i].dump();
+ }
+}
+
+Sample::Sample(int sampleID, const char* url)
+{
+ init();
+ mSampleID = sampleID;
+ mUrl = strdup(url);
+ LOGV("create sampleID=%d, url=%s", mSampleID, mUrl);
+}
+
+Sample::Sample(int sampleID, int fd, int64_t offset, int64_t length)
+{
+ init();
+ mSampleID = sampleID;
+ mFd = dup(fd);
+ mOffset = offset;
+ mLength = length;
+ LOGV("create sampleID=%d, fd=%d, offset=%lld, length=%lld", mSampleID, mFd, mLength, mOffset);
+}
+
+void Sample::init()
+{
+ mData = 0;
+ mSize = 0;
+ mRefCount = 0;
+ mSampleID = 0;
+ mState = UNLOADED;
+ mFd = -1;
+ mOffset = 0;
+ mLength = 0;
+ mUrl = 0;
+}
+
+Sample::~Sample()
+{
+ LOGV("Sample::destructor sampleID=%d, fd=%d", mSampleID, mFd);
+ if (mFd > 0) {
+ LOGV("close(%d)", mFd);
+ ::close(mFd);
+ }
+ mData.clear();
+ delete mUrl;
+}
+
+// TODO: Remove after debug is complete
+#if 0
+static void _dumpBuffer(void* buffer, size_t bufferSize, size_t dumpSize=10, bool zeroCheck=true)
+{
+ int16_t* p = static_cast<int16_t*>(buffer);
+ if (zeroCheck) {
+ for (size_t i = 0; i < bufferSize / 2; i++) {
+ if (*p != 0) {
+ goto Dump;
+ }
+ }
+ LOGV("Sample data is all zeroes");
+ return;
+ }
+
+Dump:
+ LOGV("Sample Data");
+ while (--dumpSize) {
+ LOGV(" %04x", *p++);
+ }
+}
+#endif
+
+void Sample::doLoad()
+{
+ uint32_t sampleRate;
+ int numChannels;
+ sp<IMemory> p;
+ LOGV("Start decode");
+ if (mUrl) {
+ p = MediaPlayer::decode(mUrl, &sampleRate, &numChannels);
+ } else {
+ p = MediaPlayer::decode(mFd, mOffset, mLength, &sampleRate, &numChannels);
+ LOGV("close(%d)", mFd);
+ ::close(mFd);
+ mFd = -1;
+ }
+ if (p == 0) {
+ LOGE("Unable to load sample: %s", mUrl);
+ return;
+ }
+ LOGV("pointer = %p, size = %u, sampleRate = %u, numChannels = %d",
+ p->pointer(), p->size(), sampleRate, numChannels);
+
+ if (sampleRate > kMaxSampleRate) {
+ LOGE("Sample rate (%u) out of range", sampleRate);
+ return;
+ }
+
+ if ((numChannels < 1) || (numChannels > 2)) {
+ LOGE("Sample channel count (%d) out of range", numChannels);
+ return;
+ }
+
+ //_dumpBuffer(p->pointer(), p->size());
+ uint8_t* q = static_cast<uint8_t*>(p->pointer()) + p->size() - 10;
+ //_dumpBuffer(q, 10, 10, false);
+
+ mData = p;
+ mSize = p->size();
+ mSampleRate = sampleRate;
+ mNumChannels = numChannels;
+ mState = READY;
+}
+
+void SoundChannel::init(SoundPool* soundPool)
+{
+ mSoundPool = soundPool;
+}
+
+void SoundChannel::deleteTrack() {
+ LOGV("delete track");
+ delete mAudioTrack;
+ mAudioTrack = 0;
+ mState = IDLE;
+ return;
+}
+
+void SoundChannel::play(const sp<Sample>& sample, int channelID, float leftVolume,
+ float rightVolume, int priority, int loop, float rate)
+{
+ Mutex::Autolock lock(&mLock);
+ mSample = sample;
+ mChannelID = channelID;
+ mPriority = priority;
+ mLoop = loop;
+ doPlay(leftVolume, rightVolume, rate);
+}
+
+// must call with mutex held
+void SoundChannel::doPlay(float leftVolume, float rightVolume, float rate)
+{
+ LOGV("SoundChannel::doPlay: sampleID=%d, channelID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f",
+ mSample->sampleID(), mChannelID, leftVolume, rightVolume, mPriority, mLoop, rate);
+ deleteTrack();
+ mNumChannels = mSample->numChannels();
+ clearNextEvent();
+ mPos = 0;
+
+ // initialize track
+ uint32_t sampleRate = uint32_t(float(mSample->sampleRate()) * rate + 0.5);
+ LOGV("play: channelID=%d, sampleRate=%d\n", mChannelID, sampleRate); // create track
+
+ mAudioTrack = new AudioTrack(mSoundPool->streamType(), sampleRate, AudioSystem::PCM_16_BIT,
+ mSample->numChannels(), kDefaultBufferCount, 0, callback, this);
+ if (mAudioTrack->initCheck() != NO_ERROR) {
+ LOGE("Error creating AudioTrack");
+ deleteTrack();
+ return;
+ }
+ mLeftVolume = leftVolume;
+ mRightVolume = rightVolume;
+ mAudioTrack->setVolume(leftVolume, rightVolume);
+
+ // start playback
+ mState = PLAYING;
+ LOGV("play: start track");
+ mAudioTrack->start();
+}
+
+void SoundChannel::callback(void* user, const AudioTrack::Buffer& b)
+{
+ SoundChannel* channel = static_cast<SoundChannel*>(user);
+ channel->process(b);
+}
+
+void SoundChannel::process(const AudioTrack::Buffer& b)
+{
+ //LOGV("process(%d)", mChannelID);
+ bool more = true;
+ sp<Sample> sample = mSample;
+
+ // check for stop state
+ if (sample != 0) {
+
+ // fill buffer
+ uint8_t* q = (uint8_t*) b.i8;
+ uint8_t* p = sample->data() + mPos;
+ size_t count = sample->size() - mPos;
+ if (count > b.size) {
+ //LOGV("fill: q=%p, p=%p, mPos=%u, b.size=%u", q, p, mPos, b.size);
+ memcpy(q, p, b.size);
+ mPos += b.size;
+ }
+
+ // not enough samples to fill buffer
+ else {
+ //LOGV("partial: q=%p, p=%p, mPos=%u, count=%u", q, p, mPos, count);
+ memcpy(q, p, count);
+ size_t left = b.size - count;
+ q += count;
+
+ // loop sample
+ while (left && mLoop) {
+ if (mLoop > 0) {
+ mLoop--;
+ }
+ count = left > sample->size() ? sample->size() : left;
+ //LOGV("loop: q=%p, p=%p, count=%u, mLoop=%d", p, q, count, mLoop);
+ memcpy(q, sample->data(), count);
+ q += count;
+ mPos = count;
+ left -= count;
+
+ // done filling buffer?
+ if ((mLoop == 0) && (count == sample->size())) {
+ more = false;
+ }
+ }
+
+ // end of sample: zero-fill and stop track
+ if (left) {
+ //LOGV("zero-fill: q=%p, left=%u", q, left);
+ memset(q, 0, left);
+ more = false;
+ }
+ }
+
+ //LOGV("buffer=%p, [0]=%d", b.i16, b.i16[0]);
+ }
+
+ // clean up
+ Mutex::Autolock lock(&mLock);
+ if (!more || (mState == STOPPING) || (mState == PAUSED)) {
+ LOGV("stopping track");
+ mAudioTrack->stop();
+ if (more && (mState == PAUSED)) {
+ LOGV("volume to zero");
+ mAudioTrack->setVolume(0,0);
+ } else {
+ mSample.clear();
+ mState = IDLE;
+ mPriority = IDLE_PRIORITY;
+ mSoundPool->done(this);
+ }
+ }
+}
+
+void SoundChannel::stop()
+{
+ Mutex::Autolock lock(&mLock);
+ if (mState != IDLE) {
+ setVolume(0, 0);
+ LOGV("stop");
+ mState = STOPPING;
+ }
+}
+
+//FIXME: Pause is a little broken right now
+void SoundChannel::pause()
+{
+ Mutex::Autolock lock(&mLock);
+ if (mState == PLAYING) {
+ LOGV("pause track");
+ mState = PAUSED;
+ }
+}
+
+void SoundChannel::resume()
+{
+ Mutex::Autolock lock(&mLock);
+ if (mState == PAUSED) {
+ LOGV("resume track");
+ mState = PLAYING;
+ mAudioTrack->setVolume(mLeftVolume, mRightVolume);
+ mAudioTrack->start();
+ }
+}
+
+void SoundChannel::setRate(float rate)
+{
+ uint32_t sampleRate = uint32_t(float(mSample->sampleRate()) * rate + 0.5);
+ mAudioTrack->setSampleRate(sampleRate);
+}
+
+void SoundChannel::setVolume(float leftVolume, float rightVolume)
+{
+ Mutex::Autolock lock(&mLock);
+ mLeftVolume = leftVolume;
+ mRightVolume = rightVolume;
+ if (mAudioTrack != 0) mAudioTrack->setVolume(leftVolume, rightVolume);
+}
+
+SoundChannel::~SoundChannel()
+{
+ LOGV("SoundChannel destructor");
+ if (mAudioTrack) {
+ LOGV("stop track");
+ mAudioTrack->stop();
+ delete mAudioTrack;
+ }
+ clearNextEvent();
+ mSample.clear();
+}
+
+// always call with lock held
+void SoundChannel::clearNextEvent()
+{
+ if (mNextEvent) {
+ mNextEvent->mSample.clear();
+ delete mNextEvent;
+ mNextEvent = NULL;
+ }
+}
+
+void SoundChannel::setNextEvent(SoundEvent* nextEvent)
+{
+ Mutex::Autolock lock(&mLock);
+ clearNextEvent();
+ mNextEvent = nextEvent;
+}
+
+void SoundChannel::dump()
+{
+ LOGV("mState = %d mChannelID=%d, mNumChannels=%d, mPos = %d, mPriority=%d, mLoop=%d",
+ mState, mChannelID, mNumChannels, mPos, mPriority, mLoop);
+}
+
+} // end namespace android
+
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
new file mode 100644
index 0000000..ccd724a
--- /dev/null
+++ b/media/jni/soundpool/SoundPool.h
@@ -0,0 +1,212 @@
+/*
+ * 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 SOUNDPOOL_H_
+#define SOUNDPOOL_H_
+
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+#include <media/AudioTrack.h>
+#include <cutils/atomic.h>
+
+#include <nativehelper/jni.h>
+
+namespace android {
+
+static const int IDLE_PRIORITY = -1;
+
+// forward declarations
+class SoundEvent;
+class SoundPoolRestartThread;
+class SoundPoolThread;
+class SoundPool;
+
+// for queued events
+class SoundPoolEvent {
+public:
+ SoundPoolEvent(int msg, int arg1=0, int arg2=0) :
+ mMsg(msg), mArg1(arg1), mArg2(arg2) {}
+ int mMsg;
+ int mArg1;
+ int mArg2;
+};
+
+// JNI for calling back Java SoundPool object
+extern void android_soundpool_SoundPool_notify(jobject ref, const SoundPoolEvent *event);
+
+// tracks samples used by application
+class Sample : public RefBase {
+public:
+ enum sample_state { UNLOADED, LOADING, READY, UNLOADING };
+ Sample(int sampleID, const char* url);
+ Sample(int sampleID, int fd, int64_t offset, int64_t length);
+ ~Sample();
+ int sampleID() { return mSampleID; }
+ int numChannels() { return mNumChannels; }
+ int sampleRate() { return mSampleRate; }
+ size_t size() { return mSize; }
+ int state() { return mState; }
+ uint8_t* data() { return static_cast<uint8_t*>(mData->pointer()); }
+ void doLoad();
+ void startLoad() { mState = LOADING; }
+
+ // hack
+ void init(int numChannels, int sampleRate, size_t size, sp<IMemory> data ) {
+ mNumChannels = numChannels; mSampleRate = sampleRate; mSize = size; mData = data; }
+
+private:
+ void init();
+
+ size_t mSize;
+ volatile int32_t mRefCount;
+ uint16_t mSampleID;
+ uint16_t mSampleRate;
+ uint8_t mState : 3;
+ uint8_t mNumChannels : 2;
+ int mFd;
+ int64_t mOffset;
+ int64_t mLength;
+ char* mUrl;
+ sp<IMemory> mData;
+};
+
+// stores pending events for stolen channels
+class SoundEvent
+{
+public:
+ SoundEvent(const sp<Sample>& sample, int channelID, float leftVolume,
+ float rightVolume, int priority, int loop, float rate) :
+ mSample(sample), mChannelID(channelID), mLeftVolume(leftVolume),
+ mRightVolume(rightVolume), mPriority(priority), mLoop(loop),
+ mRate(rate) {}
+ sp<Sample> mSample;
+ int mChannelID;
+ float mLeftVolume;
+ float mRightVolume;
+ int mPriority;
+ int mLoop;
+ float mRate;
+};
+
+// for channels aka AudioTracks
+class SoundChannel {
+public:
+ enum state { IDLE, RESUMING, STOPPING, PAUSED, PLAYING };
+ SoundChannel() : mAudioTrack(0), mNextEvent(0), mChannelID(0), mState(IDLE),
+ mNumChannels(1), mPos(0), mPriority(IDLE_PRIORITY), mLoop(0) {}
+ ~SoundChannel();
+ void init(SoundPool* soundPool);
+ void deleteTrack();
+ void play(const sp<Sample>& sample, int channelID, float leftVolume, float rightVolume,
+ int priority, int loop, float rate);
+ void doPlay(float leftVolume, float rightVolume, float rate);
+ void setVolume(float leftVolume, float rightVolume);
+ void stop();
+ void pause();
+ void resume();
+ void setRate(float rate);
+ int channelID() { return mChannelID; }
+ int state() { return mState; }
+ int priority() { return mPriority; }
+ void setPriority(int priority) { mPriority = priority; }
+ void setLoop(int loop) { mLoop = loop; }
+ int numChannels() { return mNumChannels; }
+ SoundEvent* nextEvent() { return mNextEvent; }
+ void clearNextEvent();
+ void setNextEvent(SoundEvent* nextEvent);
+ void dump();
+
+private:
+ static void callback(void* user, const AudioTrack::Buffer& info);
+ void process(const AudioTrack::Buffer& b);
+
+ SoundPool* mSoundPool;
+ AudioTrack* mAudioTrack;
+ sp<Sample> mSample;
+ SoundEvent* mNextEvent;
+ Mutex mLock;
+ int mChannelID;
+ int mState;
+ int mNumChannels;
+ int mPos;
+ int mPriority;
+ int mLoop;
+ float mLeftVolume;
+ float mRightVolume;
+};
+
+// application object for managing a pool of sounds
+class SoundPool {
+ friend class SoundPoolThread;
+ friend class SoundChannel;
+public:
+ SoundPool(jobject soundPoolRef, int maxChannels, int streamType, int srcQuality);
+ ~SoundPool();
+ int load(const char* url, int priority);
+ 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 resume(int channelID);
+ 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);
+ int streamType() const { return mStreamType; }
+ int srcQuality() const { return mSrcQuality; }
+
+ // called from SoundPoolThread
+ void sampleLoaded(int sampleID);
+
+ // called from AudioTrack thread
+ void done(SoundChannel* channel);
+
+private:
+ SoundPool() {} // no default constructor
+ bool startThreads();
+ void doLoad(sp<Sample>& sample);
+ inline void notify(const SoundPoolEvent* event) {
+ android_soundpool_SoundPool_notify(mSoundPoolRef, event);
+ }
+ sp<Sample> findSample(int sampleID) { return mSamples.valueFor(sampleID); }
+ SoundChannel* findChannel (int channelID);
+ SoundChannel* findNextChannel (int channelID);
+ SoundChannel* allocateChannel(int priority);
+ void moveToFront(List<SoundChannel*>& list, SoundChannel* channel);
+ void dump();
+
+ jobject mSoundPoolRef;
+ Mutex mLock;
+ SoundPoolRestartThread* mRestartThread;
+ SoundPoolThread* mDecodeThread;
+ SoundChannel* mChannelPool;
+ List<SoundChannel*> mChannels;
+ DefaultKeyedVector< int, sp<Sample> > mSamples;
+ int mMaxChannels;
+ int mStreamType;
+ int mSrcQuality;
+ int mAllocated;
+ int mNextSampleID;
+ int mNextChannelID;
+};
+
+} // end namespace android
+
+#endif /*SOUNDPOOL_H_*/
diff --git a/media/jni/soundpool/SoundPoolThread.cpp b/media/jni/soundpool/SoundPoolThread.cpp
new file mode 100644
index 0000000..4e6798d
--- /dev/null
+++ b/media/jni/soundpool/SoundPoolThread.cpp
@@ -0,0 +1,108 @@
+/*
+ * 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::MessageQueue::write(SoundPoolMsg msg) {
+ LOGV("MessageQueue::write - acquiring lock\n");
+ Mutex::Autolock lock(&mLock);
+ while (mQueue.size() >= maxMessages) {
+ LOGV("MessageQueue::write - wait\n");
+ mCondition.wait(mLock);
+ }
+ LOGV("MessageQueue::write - push message\n");
+ mQueue.push(msg);
+ mCondition.signal();
+}
+
+const SoundPoolMsg SoundPoolThread::MessageQueue::read() {
+ LOGV("MessageQueue::read - acquiring lock\n");
+ Mutex::Autolock lock(&mLock);
+ while (mQueue.size() == 0) {
+ LOGV("MessageQueue::read - wait\n");
+ mCondition.wait(mLock);
+ }
+ SoundPoolMsg msg = mQueue[0];
+ LOGV("MessageQueue::read - retrieve message\n");
+ mQueue.removeAt(0);
+ mCondition.signal();
+ return msg;
+}
+
+void SoundPoolThread::MessageQueue::quit() {
+ Mutex::Autolock lock(&mLock);
+ mQueue.clear();
+ mQueue.push(SoundPoolMsg(SoundPoolMsg::KILL, 0));
+ mCondition.signal();
+ mCondition.wait(mLock);
+ LOGV("return from quit");
+}
+
+SoundPoolThread::SoundPoolThread(SoundPool* soundPool) :
+ mSoundPool(soundPool)
+{
+ mMessages.setCapacity(maxMessages);
+ createThread(beginThread, this);
+}
+
+SoundPoolThread::~SoundPoolThread()
+{
+}
+
+int SoundPoolThread::beginThread(void* arg) {
+ LOGV("beginThread");
+ SoundPoolThread* soundPoolThread = (SoundPoolThread*)arg;
+ return soundPoolThread->run();
+}
+
+int SoundPoolThread::run() {
+ LOGV("run");
+ for (;;) {
+ SoundPoolMsg msg = mMessages.read();
+ LOGV("Got message m=%d, mData=%d", msg.mMessageType, msg.mData);
+ switch (msg.mMessageType) {
+ case SoundPoolMsg::KILL:
+ LOGV("goodbye");
+ return NO_ERROR;
+ case SoundPoolMsg::LOAD_SAMPLE:
+ doLoadSample(msg.mData);
+ break;
+ default:
+ LOGW("run: Unrecognized message %d\n",
+ msg.mMessageType);
+ break;
+ }
+ }
+}
+
+void SoundPoolThread::loadSample(int sampleID) {
+ mMessages.write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE, sampleID));
+}
+
+void SoundPoolThread::doLoadSample(int sampleID) {
+ sp <Sample> sample = mSoundPool->findSample(sampleID);
+ if (sample != 0) {
+ sample->doLoad();
+ }
+}
+
+} // end namespace android
diff --git a/media/jni/soundpool/SoundPoolThread.h b/media/jni/soundpool/SoundPoolThread.h
new file mode 100644
index 0000000..459a764
--- /dev/null
+++ b/media/jni/soundpool/SoundPoolThread.h
@@ -0,0 +1,75 @@
+/*
+ * 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, PLAY_SAMPLE, SAMPLE_DONE };
+ SoundPoolMsg() : mMessageType(INVALID), mData(0) {}
+ SoundPoolMsg(MessageType MessageType, int data) :
+ mMessageType(MessageType), mData(data) {}
+ uint8_t mMessageType;
+ uint8_t mData;
+ uint8_t mData2;
+ uint8_t mData3;
+};
+
+/*
+ * This class handles background requests from the SoundPool
+ */
+class SoundPoolThread {
+public:
+ SoundPoolThread(SoundPool* SoundPool);
+ ~SoundPoolThread();
+ void loadSample(int sampleID);
+ void quit() { mMessages.quit(); }
+
+private:
+ static const size_t maxMessages = 5;
+
+ class MessageQueue {
+ public:
+ void write(SoundPoolMsg msg);
+ const SoundPoolMsg read();
+ void setCapacity(size_t size) { mQueue.setCapacity(size); }
+ void quit();
+ private:
+ Vector<SoundPoolMsg> mQueue;
+ Mutex mLock;
+ Condition mCondition;
+ };
+
+ static int beginThread(void* arg);
+ int run();
+ void doLoadSample(int sampleID);
+
+ SoundPool* mSoundPool;
+ MessageQueue mMessages;
+};
+
+} // end namespace android
+
+#endif /*SOUNDPOOLTHREAD_H_*/
diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
new file mode 100644
index 0000000..0ce2d6f
--- /dev/null
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+#include <stdio.h>
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool"
+
+#include <utils/Log.h>
+#include <nativehelper/jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+#include "SoundPool.h"
+
+using namespace android;
+
+static struct fields_t {
+ jfieldID mNativeContext;
+ jclass mSoundPoolClass;
+} fields;
+
+static inline SoundPool* MusterSoundPool(JNIEnv *env, jobject thiz) {
+ return (SoundPool*)env->GetIntField(thiz, fields.mNativeContext);
+}
+
+// ----------------------------------------------------------------------------
+static int
+android_media_SoundPool_load_URL(JNIEnv *env, jobject thiz, jstring path, jint priority)
+{
+ LOGV("android_media_SoundPool_load_URL");
+ SoundPool *ap = MusterSoundPool(env, thiz);
+ if (path == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return 0;
+ }
+ const char* s = env->GetStringUTFChars(path, NULL);
+ int id = ap->load(s, priority);
+ env->ReleaseStringUTFChars(path, s);
+ return id;
+}
+
+static int
+android_media_SoundPool_load_FD(JNIEnv *env, jobject thiz, jobject fileDescriptor,
+ jlong offset, jlong length, jint priority)
+{
+ LOGV("android_media_SoundPool_load_FD");
+ SoundPool *ap = MusterSoundPool(env, thiz);
+ if (ap == NULL) return 0;
+ return ap->load(getParcelFileDescriptorFD(env, fileDescriptor),
+ int64_t(offset), int64_t(length), int(priority));
+}
+
+static bool
+android_media_SoundPool_unload(JNIEnv *env, jobject thiz, jint sampleID) {
+ LOGV("android_media_SoundPool_unload\n");
+ SoundPool *ap = MusterSoundPool(env, thiz);
+ if (ap == NULL) return 0;
+ return ap->unload(sampleID);
+}
+
+static int
+android_media_SoundPool_play(JNIEnv *env, jobject thiz, jint sampleID,
+ jfloat leftVolume, jfloat rightVolume, jint priority, jint loop,
+ jfloat rate)
+{
+ LOGV("android_media_SoundPool_play\n");
+ SoundPool *ap = MusterSoundPool(env, thiz);
+ if (ap == NULL) return 0;
+ return ap->play(sampleID, leftVolume, rightVolume, priority, loop, rate);
+}
+
+static void
+android_media_SoundPool_pause(JNIEnv *env, jobject thiz, jint channelID)
+{
+ LOGV("android_media_SoundPool_pause");
+ SoundPool *ap = MusterSoundPool(env, thiz);
+ if (ap == NULL) return;
+ ap->pause(channelID);
+}
+
+static void
+android_media_SoundPool_resume(JNIEnv *env, jobject thiz, jint channelID)
+{
+ LOGV("android_media_SoundPool_resume");
+ SoundPool *ap = MusterSoundPool(env, thiz);
+ if (ap == NULL) return;
+ ap->resume(channelID);
+}
+
+static void
+android_media_SoundPool_stop(JNIEnv *env, jobject thiz, jint channelID)
+{
+ LOGV("android_media_SoundPool_stop");
+ SoundPool *ap = MusterSoundPool(env, thiz);
+ if (ap == NULL) return;
+ ap->stop(channelID);
+}
+
+static void
+android_media_SoundPool_setVolume(JNIEnv *env, jobject thiz, jint channelID,
+ float leftVolume, float rightVolume)
+{
+ LOGV("android_media_SoundPool_setVolume");
+ SoundPool *ap = MusterSoundPool(env, thiz);
+ if (ap == NULL) return;
+ ap->setVolume(channelID, leftVolume, rightVolume);
+}
+
+static void
+android_media_SoundPool_setPriority(JNIEnv *env, jobject thiz, jint channelID,
+ int priority)
+{
+ LOGV("android_media_SoundPool_setPriority");
+ SoundPool *ap = MusterSoundPool(env, thiz);
+ if (ap == NULL) return;
+ ap->setPriority(channelID, priority);
+}
+
+static void
+android_media_SoundPool_setLoop(JNIEnv *env, jobject thiz, jint channelID,
+ int loop)
+{
+ LOGV("android_media_SoundPool_setLoop");
+ SoundPool *ap = MusterSoundPool(env, thiz);
+ if (ap == NULL) return;
+ ap->setLoop(channelID, loop);
+}
+
+static void
+android_media_SoundPool_setRate(JNIEnv *env, jobject thiz, jint channelID,
+ float rate)
+{
+ LOGV("android_media_SoundPool_setRate");
+ SoundPool *ap = MusterSoundPool(env, thiz);
+ if (ap == NULL) return;
+ ap->setRate(channelID, rate);
+}
+
+static void
+android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz,
+ jobject weak_this, jint maxChannels, jint streamType, jint srcQuality)
+{
+ LOGV("android_media_SoundPool_native_setup");
+ SoundPool *ap = new SoundPool(weak_this, maxChannels, streamType, srcQuality);
+ if (ap == NULL) {
+ jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
+ return;
+ }
+
+ // save pointer to SoundPool C++ object in opaque field in Java object
+ env->SetIntField(thiz, fields.mNativeContext, (int)ap);
+}
+
+static void
+android_media_SoundPool_release(JNIEnv *env, jobject thiz)
+{
+ LOGV("android_media_SoundPool_release");
+ SoundPool *ap = MusterSoundPool(env, thiz);
+ if (ap != NULL) {
+ env->SetIntField(thiz, fields.mNativeContext, 0);
+ delete ap;
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+// Dalvik VM type signatures
+static JNINativeMethod gMethods[] = {
+ { "_load",
+ "(Ljava/lang/String;I)I",
+ (void *)android_media_SoundPool_load_URL
+ },
+ { "_load",
+ "(Ljava/io/FileDescriptor;JJI)I",
+ (void *)android_media_SoundPool_load_FD
+ },
+ { "unload",
+ "(I)Z",
+ (void *)android_media_SoundPool_unload
+ },
+ { "play",
+ "(IFFIIF)I",
+ (void *)android_media_SoundPool_play
+ },
+ { "pause",
+ "(I)V",
+ (void *)android_media_SoundPool_pause
+ },
+ { "resume",
+ "(I)V",
+ (void *)android_media_SoundPool_resume
+ },
+ { "stop",
+ "(I)V",
+ (void *)android_media_SoundPool_stop
+ },
+ { "setVolume",
+ "(IFF)V",
+ (void *)android_media_SoundPool_setVolume
+ },
+ { "setPriority",
+ "(II)V",
+ (void *)android_media_SoundPool_setPriority
+ },
+ { "setLoop",
+ "(II)V",
+ (void *)android_media_SoundPool_setLoop
+ },
+ { "setRate",
+ "(IF)V",
+ (void *)android_media_SoundPool_setRate
+ },
+ { "native_setup",
+ "(Ljava/lang/Object;III)V",
+ (void*)android_media_SoundPool_native_setup
+ },
+ { "release",
+ "()V",
+ (void*)android_media_SoundPool_release
+ }
+};
+
+static const char* const kClassPathName = "android/media/SoundPool";
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+ JNIEnv* env = NULL;
+ jint result = -1;
+ jclass clazz;
+
+ if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+ LOGE("ERROR: GetEnv failed\n");
+ goto bail;
+ }
+ assert(env != NULL);
+
+ clazz = env->FindClass(kClassPathName);
+ if (clazz == NULL) {
+ LOGE("Can't find %s", kClassPathName);
+ goto bail;
+ }
+
+ fields.mNativeContext = env->GetFieldID(clazz, "mNativeContext", "I");
+ if (fields.mNativeContext == NULL) {
+ LOGE("Can't find SoundPool.mNativeContext");
+ goto bail;
+ }
+
+ if (AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)) < 0)
+ goto bail;
+
+ /* success -- return valid version number */
+ result = JNI_VERSION_1_4;
+
+bail:
+ return result;
+}