diff options
author | Eric Laurent <elaurent@google.com> | 2012-03-26 10:47:22 -0700 |
---|---|---|
committer | Eric Laurent <elaurent@google.com> | 2012-03-26 10:54:41 -0700 |
commit | 2e66a7896c9a9da3a15fc6cff9be28b4174d8719 (patch) | |
tree | bc6d8e86ae45de8f4b1a36eda9d11f774bde9bea | |
parent | ef36d496477d1e2ae99c3cd43eee5cf7e82b0883 (diff) | |
download | frameworks_av-2e66a7896c9a9da3a15fc6cff9be28b4174d8719.zip frameworks_av-2e66a7896c9a9da3a15fc6cff9be28b4174d8719.tar.gz frameworks_av-2e66a7896c9a9da3a15fc6cff9be28b4174d8719.tar.bz2 |
reorganize SoundPool and JetPlayer code.
Reorganize SoundPool and JetPlayer code to be ready for the
creation of libmedia_native.
Split SoundPool between libsoundpool (JNI) and libmedia(sound pool implementation).
Remove dependencies on nativehelper/jni.h from JetPlayer.
Change-Id: I130c6014173b714329929dd82c5dfb70b757a610
-rw-r--r-- | include/media/JetPlayer.h | 5 | ||||
-rw-r--r-- | media/libmedia/Android.mk | 4 | ||||
-rw-r--r-- | media/libmedia/JetPlayer.cpp | 2 | ||||
-rw-r--r-- | media/libmedia/SoundPool.cpp | 908 | ||||
-rw-r--r-- | media/libmedia/SoundPool.h | 238 | ||||
-rw-r--r-- | media/libmedia/SoundPoolThread.cpp | 114 | ||||
-rw-r--r-- | media/libmedia/SoundPoolThread.h | 66 |
7 files changed, 1332 insertions, 5 deletions
diff --git a/include/media/JetPlayer.h b/include/media/JetPlayer.h index 38a3e44..0616bf0 100644 --- a/include/media/JetPlayer.h +++ b/include/media/JetPlayer.h @@ -18,7 +18,6 @@ #define JETPLAYER_H_ #include <utils/threads.h> -#include <nativehelper/jni.h> #include <libsonivox/jet.h> #include <libsonivox/eas_types.h> @@ -40,7 +39,7 @@ public: static const int JET_NUMQUEUEDSEGMENT_UPDATE = 3; static const int JET_PAUSE_UPDATE = 4; - JetPlayer(jobject javaJetPlayer, + JetPlayer(void *javaJetPlayer, int maxTracks = 32, int trackBufferSize = 1200); ~JetPlayer(); @@ -75,7 +74,7 @@ private: jetevent_callback mEventCallback; - jobject mJavaJetPlayerRef; + void* mJavaJetPlayerRef; Mutex mMutex; // mutex to sync the render and playback thread with the JET calls pid_t mTid; Condition mCondition; diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk index 21e8f29..4cae467 100644 --- a/media/libmedia/Android.mk +++ b/media/libmedia/Android.mk @@ -43,7 +43,9 @@ LOCAL_SRC_FILES:= \ IEffectClient.cpp \ AudioEffect.cpp \ Visualizer.cpp \ - MemoryLeakTrackUtil.cpp + MemoryLeakTrackUtil.cpp \ + SoundPool.cpp \ + SoundPoolThread.cpp LOCAL_SHARED_LIBRARIES := \ libui libcutils libutils libbinder libsonivox libicuuc libexpat \ diff --git a/media/libmedia/JetPlayer.cpp b/media/libmedia/JetPlayer.cpp index 7fa6bb7..52aee49 100644 --- a/media/libmedia/JetPlayer.cpp +++ b/media/libmedia/JetPlayer.cpp @@ -30,7 +30,7 @@ static const int MIX_NUM_BUFFERS = 4; static const S_EAS_LIB_CONFIG* pLibConfig = NULL; //------------------------------------------------------------------------------------------------- -JetPlayer::JetPlayer(jobject javaJetPlayer, int maxTracks, int trackBufferSize) : +JetPlayer::JetPlayer(void *javaJetPlayer, int maxTracks, int trackBufferSize) : mEventCallback(NULL), mJavaJetPlayerRef(javaJetPlayer), mTid(-1), diff --git a/media/libmedia/SoundPool.cpp b/media/libmedia/SoundPool.cpp new file mode 100644 index 0000000..306c57d --- /dev/null +++ b/media/libmedia/SoundPool.cpp @@ -0,0 +1,908 @@ +/* + * 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 "SoundPool" +#include <utils/Log.h> + +//#define USE_SHARED_MEM_BUFFER + +// XXX needed for timing latency +#include <utils/Timers.h> + +#include <media/AudioTrack.h> +#include <media/mediaplayer.h> + +#include <system/audio.h> + +#include "SoundPool.h" +#include "SoundPoolThread.h" + +namespace android +{ + +int kDefaultBufferCount = 4; +uint32_t kMaxSampleRate = 48000; +uint32_t kDefaultSampleRate = 44100; +uint32_t kDefaultFrameCount = 1200; + +SoundPool::SoundPool(int maxChannels, audio_stream_type_t streamType, int srcQuality) +{ + ALOGV("SoundPool constructor: maxChannels=%d, streamType=%d, srcQuality=%d", + maxChannels, streamType, srcQuality); + + // check limits + mMaxChannels = maxChannels; + if (mMaxChannels < 1) { + mMaxChannels = 1; + } + else if (mMaxChannels > 32) { + mMaxChannels = 32; + } + ALOGW_IF(maxChannels != mMaxChannels, "App requested %d channels", maxChannels); + + mQuit = false; + mDecodeThread = 0; + mStreamType = streamType; + mSrcQuality = srcQuality; + mAllocated = 0; + mNextSampleID = 0; + mNextChannelID = 0; + + mCallback = 0; + mUserData = 0; + + mChannelPool = new SoundChannel[mMaxChannels]; + for (int i = 0; i < mMaxChannels; ++i) { + mChannelPool[i].init(this); + mChannels.push_back(&mChannelPool[i]); + } + + // start decode thread + startThreads(); +} + +SoundPool::~SoundPool() +{ + ALOGV("SoundPool destructor"); + mDecodeThread->quit(); + quit(); + + Mutex::Autolock lock(&mLock); + + mChannels.clear(); + if (mChannelPool) + delete [] mChannelPool; + // clean up samples + ALOGV("clear samples"); + mSamples.clear(); + + if (mDecodeThread) + delete mDecodeThread; +} + +void SoundPool::addToRestartList(SoundChannel* channel) +{ + Mutex::Autolock lock(&mRestartLock); + if (!mQuit) { + mRestart.push_back(channel); + mCondition.signal(); + } +} + +void SoundPool::addToStopList(SoundChannel* channel) +{ + Mutex::Autolock lock(&mRestartLock); + if (!mQuit) { + mStop.push_back(channel); + mCondition.signal(); + } +} + +int SoundPool::beginThread(void* arg) +{ + SoundPool* p = (SoundPool*)arg; + return p->run(); +} + +int SoundPool::run() +{ + mRestartLock.lock(); + while (!mQuit) { + mCondition.wait(mRestartLock); + ALOGV("awake"); + if (mQuit) break; + + while (!mStop.empty()) { + SoundChannel* channel; + ALOGV("Getting channel from stop list"); + List<SoundChannel* >::iterator iter = mStop.begin(); + channel = *iter; + mStop.erase(iter); + mRestartLock.unlock(); + if (channel != 0) { + Mutex::Autolock lock(&mLock); + channel->stop(); + } + mRestartLock.lock(); + if (mQuit) break; + } + + while (!mRestart.empty()) { + SoundChannel* channel; + ALOGV("Getting channel from list"); + List<SoundChannel*>::iterator iter = mRestart.begin(); + channel = *iter; + mRestart.erase(iter); + mRestartLock.unlock(); + if (channel != 0) { + Mutex::Autolock lock(&mLock); + channel->nextEvent(); + } + mRestartLock.lock(); + if (mQuit) break; + } + } + + mStop.clear(); + mRestart.clear(); + mCondition.signal(); + mRestartLock.unlock(); + ALOGV("goodbye"); + return 0; +} + +void SoundPool::quit() +{ + mRestartLock.lock(); + mQuit = true; + mCondition.signal(); + mCondition.wait(mRestartLock); + ALOGV("return from quit"); + mRestartLock.unlock(); +} + +bool SoundPool::startThreads() +{ + createThreadEtc(beginThread, this, "SoundPool"); + if (mDecodeThread == NULL) + mDecodeThread = new SoundPoolThread(this); + return mDecodeThread != NULL; +} + +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].nextChannelID() == channelID) { + return &mChannelPool[i]; + } + } + return NULL; +} + +int SoundPool::load(const char* path, int priority) +{ + ALOGV("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) +{ + ALOGV("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) +{ + ALOGV("doLoad: loading sample sampleID=%d", sample->sampleID()); + sample->startLoad(); + mDecodeThread->loadSample(sample->sampleID()); +} + +bool SoundPool::unload(int sampleID) +{ + ALOGV("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) +{ + ALOGV("play sampleID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f", + sampleID, leftVolume, rightVolume, priority, loop, rate); + sp<Sample> sample; + SoundChannel* channel; + int channelID; + + Mutex::Autolock lock(&mLock); + + if (mQuit) { + return 0; + } + // is sample ready? + sample = findSample(sampleID); + if ((sample == 0) || (sample->state() != Sample::READY)) { + ALOGW(" sample %d not READY", sampleID); + return 0; + } + + dump(); + + // allocate a channel + channel = allocateChannel_l(priority); + + // no channel allocated - return 0 + if (!channel) { + ALOGV("No channel allocated"); + return 0; + } + + channelID = ++mNextChannelID; + + ALOGV("play channel %p state = %d", channel, channel->state()); + channel->play(sample, channelID, leftVolume, rightVolume, priority, loop, rate); + return channelID; +} + +SoundChannel* SoundPool::allocateChannel_l(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); + ALOGV("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_l(SoundChannel* channel) +{ + for (List<SoundChannel*>::iterator iter = mChannels.begin(); iter != mChannels.end(); ++iter) { + if (*iter == channel) { + mChannels.erase(iter); + mChannels.push_front(channel); + break; + } + } +} + +void SoundPool::pause(int channelID) +{ + ALOGV("pause(%d)", channelID); + Mutex::Autolock lock(&mLock); + SoundChannel* channel = findChannel(channelID); + if (channel) { + channel->pause(); + } +} + +void SoundPool::autoPause() +{ + ALOGV("autoPause()"); + Mutex::Autolock lock(&mLock); + for (int i = 0; i < mMaxChannels; ++i) { + SoundChannel* channel = &mChannelPool[i]; + channel->autoPause(); + } +} + +void SoundPool::resume(int channelID) +{ + ALOGV("resume(%d)", channelID); + Mutex::Autolock lock(&mLock); + SoundChannel* channel = findChannel(channelID); + if (channel) { + channel->resume(); + } +} + +void SoundPool::autoResume() +{ + ALOGV("autoResume()"); + Mutex::Autolock lock(&mLock); + for (int i = 0; i < mMaxChannels; ++i) { + SoundChannel* channel = &mChannelPool[i]; + channel->autoResume(); + } +} + +void SoundPool::stop(int channelID) +{ + ALOGV("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) +{ + Mutex::Autolock lock(&mLock); + SoundChannel* channel = findChannel(channelID); + if (channel) { + channel->setVolume(leftVolume, rightVolume); + } +} + +void SoundPool::setPriority(int channelID, int priority) +{ + ALOGV("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) +{ + ALOGV("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) +{ + ALOGV("setRate(%d, %f)", channelID, rate); + Mutex::Autolock lock(&mLock); + SoundChannel* channel = findChannel(channelID); + if (channel) { + channel->setRate(rate); + } +} + +// call with lock held +void SoundPool::done_l(SoundChannel* channel) +{ + ALOGV("done_l(%d)", channel->channelID()); + // if "stolen", play next event + if (channel->nextChannelID() != 0) { + ALOGV("add to restart list"); + addToRestartList(channel); + } + + // return to idle state + else { + ALOGV("move to front"); + moveToFront_l(channel); + } +} + +void SoundPool::setCallback(SoundPoolCallback* callback, void* user) +{ + Mutex::Autolock lock(&mCallbackLock); + mCallback = callback; + mUserData = user; +} + +void SoundPool::notify(SoundPoolEvent event) +{ + Mutex::Autolock lock(&mCallbackLock); + if (mCallback != NULL) { + mCallback(event, this, mUserData); + } +} + +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); + ALOGV("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; + ALOGV("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() +{ + ALOGV("Sample::destructor sampleID=%d, fd=%d", mSampleID, mFd); + if (mFd > 0) { + ALOGV("close(%d)", mFd); + ::close(mFd); + } + mData.clear(); + delete mUrl; +} + +status_t Sample::doLoad() +{ + uint32_t sampleRate; + int numChannels; + audio_format_t format; + sp<IMemory> p; + ALOGV("Start decode"); + if (mUrl) { + p = MediaPlayer::decode(mUrl, &sampleRate, &numChannels, &format); + } else { + p = MediaPlayer::decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format); + ALOGV("close(%d)", mFd); + ::close(mFd); + mFd = -1; + } + if (p == 0) { + ALOGE("Unable to load sample: %s", mUrl); + return -1; + } + ALOGV("pointer = %p, size = %u, sampleRate = %u, numChannels = %d", + p->pointer(), p->size(), sampleRate, numChannels); + + if (sampleRate > kMaxSampleRate) { + ALOGE("Sample rate (%u) out of range", sampleRate); + return - 1; + } + + if ((numChannels < 1) || (numChannels > 2)) { + ALOGE("Sample channel count (%d) out of range", numChannels); + return - 1; + } + + //_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; + mFormat = format; + mState = READY; + return 0; +} + + +void SoundChannel::init(SoundPool* soundPool) +{ + mSoundPool = soundPool; +} + +// call with sound pool lock held +void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftVolume, + float rightVolume, int priority, int loop, float rate) +{ + AudioTrack* oldTrack; + AudioTrack* newTrack; + status_t status; + + { // scope for the lock + Mutex::Autolock lock(&mLock); + + ALOGV("SoundChannel::play %p: sampleID=%d, channelID=%d, leftVolume=%f, rightVolume=%f," + " priority=%d, loop=%d, rate=%f", + this, sample->sampleID(), nextChannelID, leftVolume, rightVolume, + priority, loop, rate); + + // if not idle, this voice is being stolen + if (mState != IDLE) { + ALOGV("channel %d stolen - event queued for channel %d", channelID(), nextChannelID); + mNextEvent.set(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate); + stop_l(); + return; + } + + // initialize track + int afFrameCount; + int afSampleRate; + audio_stream_type_t streamType = mSoundPool->streamType(); + if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) { + afFrameCount = kDefaultFrameCount; + } + if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) { + afSampleRate = kDefaultSampleRate; + } + int numChannels = sample->numChannels(); + uint32_t sampleRate = uint32_t(float(sample->sampleRate()) * rate + 0.5); + uint32_t totalFrames = (kDefaultBufferCount * afFrameCount * sampleRate) / afSampleRate; + uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount; + uint32_t frameCount = 0; + + if (loop) { + frameCount = sample->size()/numChannels/ + ((sample->format() == AUDIO_FORMAT_PCM_16_BIT) ? sizeof(int16_t) : sizeof(uint8_t)); + } + +#ifndef USE_SHARED_MEM_BUFFER + // Ensure minimum audio buffer size in case of short looped sample + if(frameCount < totalFrames) { + frameCount = totalFrames; + } +#endif + + // mToggle toggles each time a track is started on a given channel. + // The toggle is concatenated with the SoundChannel 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) + unsigned long toggle = mToggle ^ 1; + void *userData = (void *)((unsigned long)this | toggle); + uint32_t channels = (numChannels == 2) ? + AUDIO_CHANNEL_OUT_STEREO : AUDIO_CHANNEL_OUT_MONO; + + // do not create a new audio track if current track is compatible with sample parameters +#ifdef USE_SHARED_MEM_BUFFER + newTrack = new AudioTrack(streamType, sampleRate, sample->format(), + channels, sample->getIMemory(), AUDIO_POLICY_OUTPUT_FLAG_NONE, callback, userData); +#else + newTrack = new AudioTrack(streamType, sampleRate, sample->format(), + channels, frameCount, AUDIO_POLICY_OUTPUT_FLAG_NONE, callback, userData, + bufferFrames); +#endif + oldTrack = mAudioTrack; + status = newTrack->initCheck(); + if (status != NO_ERROR) { + ALOGE("Error creating AudioTrack"); + goto exit; + } + ALOGV("setVolume %p", newTrack); + newTrack->setVolume(leftVolume, rightVolume); + newTrack->setLoop(0, frameCount, loop); + + // From now on, AudioTrack callbacks received with previous toggle value will be ignored. + mToggle = toggle; + mAudioTrack = newTrack; + mPos = 0; + mSample = sample; + mChannelID = nextChannelID; + mPriority = priority; + mLoop = loop; + mLeftVolume = leftVolume; + mRightVolume = rightVolume; + mNumChannels = numChannels; + mRate = rate; + clearNextEvent(); + mState = PLAYING; + mAudioTrack->start(); + mAudioBufferSize = newTrack->frameCount()*newTrack->frameSize(); + } + +exit: + ALOGV("delete oldTrack %p", oldTrack); + delete oldTrack; + if (status != NO_ERROR) { + delete newTrack; + mAudioTrack = NULL; + } +} + +void SoundChannel::nextEvent() +{ + sp<Sample> sample; + int nextChannelID; + float leftVolume; + float rightVolume; + int priority; + int loop; + float rate; + + // check for valid event + { + Mutex::Autolock lock(&mLock); + nextChannelID = mNextEvent.channelID(); + if (nextChannelID == 0) { + ALOGV("stolen channel has no event"); + return; + } + + sample = mNextEvent.sample(); + leftVolume = mNextEvent.leftVolume(); + rightVolume = mNextEvent.rightVolume(); + priority = mNextEvent.priority(); + loop = mNextEvent.loop(); + rate = mNextEvent.rate(); + } + + ALOGV("Starting stolen channel %d -> %d", channelID(), nextChannelID); + play(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate); +} + +void SoundChannel::callback(int event, void* user, void *info) +{ + SoundChannel* channel = static_cast<SoundChannel*>((void *)((unsigned long)user & ~1)); + + channel->process(event, info, (unsigned long)user & 1); +} + +void SoundChannel::process(int event, void *info, unsigned long toggle) +{ + //ALOGV("process(%d)", mChannelID); + + Mutex::Autolock lock(&mLock); + + AudioTrack::Buffer* b = NULL; + if (event == AudioTrack::EVENT_MORE_DATA) { + b = static_cast<AudioTrack::Buffer *>(info); + } + + if (mToggle != toggle) { + ALOGV("process wrong toggle %p channel %d", this, mChannelID); + if (b != NULL) { + b->size = 0; + } + return; + } + + sp<Sample> sample = mSample; + +// ALOGV("SoundChannel::process event %d", event); + + if (event == AudioTrack::EVENT_MORE_DATA) { + + // check for stop state + if (b->size == 0) return; + + if (mState == IDLE) { + b->size = 0; + return; + } + + if (sample != 0) { + // fill buffer + uint8_t* q = (uint8_t*) b->i8; + size_t count = 0; + + if (mPos < (int)sample->size()) { + uint8_t* p = sample->data() + mPos; + count = sample->size() - mPos; + if (count > b->size) { + count = b->size; + } + memcpy(q, p, count); +// ALOGV("fill: q=%p, p=%p, mPos=%u, b->size=%u, count=%d", q, p, mPos, b->size, count); + } else if (mPos < mAudioBufferSize) { + count = mAudioBufferSize - mPos; + if (count > b->size) { + count = b->size; + } + memset(q, 0, count); +// ALOGV("fill extra: q=%p, mPos=%u, b->size=%u, count=%d", q, mPos, b->size, count); + } + + mPos += count; + b->size = count; + //ALOGV("buffer=%p, [0]=%d", b->i16, b->i16[0]); + } + } else if (event == AudioTrack::EVENT_UNDERRUN) { + ALOGV("process %p channel %d EVENT_UNDERRUN", this, mChannelID); + mSoundPool->addToStopList(this); + } else if (event == AudioTrack::EVENT_LOOP_END) { + ALOGV("End loop %p channel %d count %d", this, mChannelID, *(int *)info); + } +} + + +// call with lock held +bool SoundChannel::doStop_l() +{ + if (mState != IDLE) { + setVolume_l(0, 0); + ALOGV("stop"); + mAudioTrack->stop(); + mSample.clear(); + mState = IDLE; + mPriority = IDLE_PRIORITY; + return true; + } + return false; +} + +// call with lock held and sound pool lock held +void SoundChannel::stop_l() +{ + if (doStop_l()) { + mSoundPool->done_l(this); + } +} + +// call with sound pool lock held +void SoundChannel::stop() +{ + bool stopped; + { + Mutex::Autolock lock(&mLock); + stopped = doStop_l(); + } + + if (stopped) { + mSoundPool->done_l(this); + } +} + +//FIXME: Pause is a little broken right now +void SoundChannel::pause() +{ + Mutex::Autolock lock(&mLock); + if (mState == PLAYING) { + ALOGV("pause track"); + mState = PAUSED; + mAudioTrack->pause(); + } +} + +void SoundChannel::autoPause() +{ + Mutex::Autolock lock(&mLock); + if (mState == PLAYING) { + ALOGV("pause track"); + mState = PAUSED; + mAutoPaused = true; + mAudioTrack->pause(); + } +} + +void SoundChannel::resume() +{ + Mutex::Autolock lock(&mLock); + if (mState == PAUSED) { + ALOGV("resume track"); + mState = PLAYING; + mAutoPaused = false; + mAudioTrack->start(); + } +} + +void SoundChannel::autoResume() +{ + Mutex::Autolock lock(&mLock); + if (mAutoPaused && (mState == PAUSED)) { + ALOGV("resume track"); + mState = PLAYING; + mAutoPaused = false; + mAudioTrack->start(); + } +} + +void SoundChannel::setRate(float rate) +{ + Mutex::Autolock lock(&mLock); + if (mAudioTrack != NULL && mSample != 0) { + uint32_t sampleRate = uint32_t(float(mSample->sampleRate()) * rate + 0.5); + mAudioTrack->setSampleRate(sampleRate); + mRate = rate; + } +} + +// call with lock held +void SoundChannel::setVolume_l(float leftVolume, float rightVolume) +{ + mLeftVolume = leftVolume; + mRightVolume = rightVolume; + if (mAudioTrack != NULL) + mAudioTrack->setVolume(leftVolume, rightVolume); +} + +void SoundChannel::setVolume(float leftVolume, float rightVolume) +{ + Mutex::Autolock lock(&mLock); + setVolume_l(leftVolume, rightVolume); +} + +void SoundChannel::setLoop(int loop) +{ + Mutex::Autolock lock(&mLock); + if (mAudioTrack != NULL && mSample != 0) { + uint32_t loopEnd = mSample->size()/mNumChannels/ + ((mSample->format() == AUDIO_FORMAT_PCM_16_BIT) ? sizeof(int16_t) : sizeof(uint8_t)); + mAudioTrack->setLoop(0, loopEnd, loop); + mLoop = loop; + } +} + +SoundChannel::~SoundChannel() +{ + ALOGV("SoundChannel destructor %p", this); + { + Mutex::Autolock lock(&mLock); + clearNextEvent(); + doStop_l(); + } + // do not call AudioTrack destructor with mLock held as it will wait for the AudioTrack + // callback thread to exit which may need to execute process() and acquire the mLock. + delete mAudioTrack; +} + +void SoundChannel::dump() +{ + ALOGV("mState = %d mChannelID=%d, mNumChannels=%d, mPos = %d, mPriority=%d, mLoop=%d", + mState, mChannelID, mNumChannels, mPos, mPriority, mLoop); +} + +void SoundEvent::set(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; +} + +} // end namespace android diff --git a/media/libmedia/SoundPool.h b/media/libmedia/SoundPool.h new file mode 100644 index 0000000..002b045 --- /dev/null +++ b/media/libmedia/SoundPool.h @@ -0,0 +1,238 @@ +/* + * 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> + +namespace android { + +static const int IDLE_PRIORITY = -1; + +// forward declarations +class SoundEvent; +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; + 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, 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; } + audio_format_t format() { return mFormat; } + size_t size() { return mSize; } + int state() { return mState; } + uint8_t* data() { return static_cast<uint8_t*>(mData->pointer()); } + status_t doLoad(); + void startLoad() { mState = LOADING; } + sp<IMemory> getIMemory() { return mData; } + + // hack + void init(int numChannels, int sampleRate, audio_format_t format, size_t size, sp<IMemory> data ) { + mNumChannels = numChannels; mSampleRate = sampleRate; mFormat = format; 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; + audio_format_t mFormat; + int mFd; + int64_t mOffset; + int64_t mLength; + char* mUrl; + sp<IMemory> mData; +}; + +// 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() : mAudioTrack(NULL), mState(IDLE), mNumChannels(1), + mPos(0), mToggle(0), mAutoPaused(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 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(); + +private: + static void callback(int event, void* user, void *info); + void process(int event, void *info, unsigned long toggle); + bool doStop_l(); + + SoundPool* mSoundPool; + AudioTrack* mAudioTrack; + SoundEvent mNextEvent; + Mutex mLock; + int mState; + int mNumChannels; + int mPos; + int mAudioBufferSize; + unsigned long mToggle; + bool mAutoPaused; +}; + +// application object for managing a pool of sounds +class SoundPool { + friend class SoundPoolThread; + friend class SoundChannel; +public: + SoundPool(int maxChannels, audio_stream_type_t 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 autoPause(); + void resume(int channelID); + 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); + audio_stream_type_t streamType() const { return mStreamType; } + int srcQuality() const { return mSrcQuality; } + + // called from SoundPoolThread + void sampleLoaded(int sampleID); + + // called from AudioTrack thread + void done_l(SoundChannel* channel); + + // callback function + void setCallback(SoundPoolCallback* callback, void* user); + void* getUserData() { return mUserData; } + +private: + SoundPool() {} // no default constructor + bool startThreads(); + void doLoad(sp<Sample>& sample); + sp<Sample> findSample(int sampleID) { return mSamples.valueFor(sampleID); } + SoundChannel* findChannel (int channelID); + SoundChannel* findNextChannel (int channelID); + SoundChannel* allocateChannel_l(int priority); + 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(); + + 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_stream_type_t mStreamType; + int mSrcQuality; + int mAllocated; + int mNextSampleID; + int mNextChannelID; + bool mQuit; + + // callback + Mutex mCallbackLock; + SoundPoolCallback* mCallback; + void* mUserData; +}; + +} // end namespace android + +#endif /*SOUNDPOOL_H_*/ diff --git a/media/libmedia/SoundPoolThread.cpp b/media/libmedia/SoundPoolThread.cpp new file mode 100644 index 0000000..ba3b482 --- /dev/null +++ b/media/libmedia/SoundPoolThread.cpp @@ -0,0 +1,114 @@ +/* + * 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 diff --git a/media/libmedia/SoundPoolThread.h b/media/libmedia/SoundPoolThread.h new file mode 100644 index 0000000..d388388 --- /dev/null +++ b/media/libmedia/SoundPoolThread.h @@ -0,0 +1,66 @@ +/* + * 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: + SoundPoolThread(SoundPool* SoundPool); + ~SoundPoolThread(); + void loadSample(int sampleID); + void quit(); + void write(SoundPoolMsg msg); + +private: + static const size_t maxMessages = 5; + + 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_*/ |