diff options
Diffstat (limited to 'media/jni/soundpool')
-rw-r--r-- | media/jni/soundpool/Android.mk | 18 | ||||
-rw-r--r-- | media/jni/soundpool/SoundPool.cpp | 729 | ||||
-rw-r--r-- | media/jni/soundpool/SoundPool.h | 212 | ||||
-rw-r--r-- | media/jni/soundpool/SoundPoolThread.cpp | 108 | ||||
-rw-r--r-- | media/jni/soundpool/SoundPoolThread.h | 75 | ||||
-rw-r--r-- | media/jni/soundpool/android_media_SoundPool.cpp | 270 |
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; +} |