Merge change I9120be33

* changes:
  Add OnLoadCompleteListener to SoundPool.
This commit is contained in:
Android (Google) Code Review
2009-12-23 09:45:23 -08:00
6 changed files with 215 additions and 63 deletions

View File

@@ -26,6 +26,10 @@ import android.content.Context;
import android.content.res.AssetFileDescriptor;
import java.io.IOException;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
/**
* The SoundPool class manages and plays audio resources for applications.
*
@@ -103,9 +107,20 @@ public class SoundPool
static { System.loadLibrary("soundpool"); }
private final static String TAG = "SoundPool";
private final static boolean DEBUG = false;
private int mNativeContext; // accessed by native methods
private EventHandler mEventHandler;
private OnLoadCompleteListener mOnLoadCompleteListener;
private final Object mLock;
// SoundPool messages
//
// must match SoundPool.h
private static final int SAMPLE_LOADED = 1;
/**
* Constructor. Constructs a SoundPool object with the following
* characteristics:
@@ -120,7 +135,23 @@ public class SoundPool
* @return a SoundPool object, or null if creation failed
*/
public SoundPool(int maxStreams, int streamType, int srcQuality) {
native_setup(new WeakReference<SoundPool>(this), maxStreams, streamType, srcQuality);
// do native setup
if (native_setup(new WeakReference(this), maxStreams, streamType, srcQuality) != 0) {
throw new RuntimeException("Native setup failed");
}
mLock = new Object();
// setup message handler
Looper looper;
if ((looper = Looper.myLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else if ((looper = Looper.getMainLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
}
}
/**
@@ -145,12 +176,11 @@ public class SoundPool
ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
if (fd != null) {
id = _load(fd.getFileDescriptor(), 0, f.length(), priority);
//Log.v(TAG, "close fd");
fd.close();
}
}
} catch (java.io.IOException e) {
Log.d(TAG, "error loading " + path);
Log.e(TAG, "error loading " + path);
}
return id;
}
@@ -176,7 +206,6 @@ public class SoundPool
if (afd != null) {
id = _load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), priority);
try {
//Log.v(TAG, "close fd");
afd.close();
} catch (java.io.IOException ex) {
//Log.d(TAG, "close failed:", ex);
@@ -358,6 +387,76 @@ public class SoundPool
*/
public native final void setRate(int streamID, float rate);
/**
* Interface definition for a callback to be invoked when all the
* sounds are loaded.
*
* @hide
*/
public interface OnLoadCompleteListener
{
/**
* Called when a sound has completed loading.
*
* @param soundPool SoundPool object from the load() method
* @param soundPool the sample ID of the sound loaded.
* @param status the status of the load operation (0 = success)
*/
public void onLoadComplete(SoundPool soundPool, int sampleId, int status);
}
/**
* Sets the callback hook for the OnLoadCompleteListener.
*
* @hide
*/
public void setOnLoadCompleteListener(OnLoadCompleteListener listener)
{
synchronized(mLock) {
mOnLoadCompleteListener = listener;
}
}
private class EventHandler extends Handler
{
private SoundPool mSoundPool;
public EventHandler(SoundPool soundPool, Looper looper) {
super(looper);
mSoundPool = soundPool;
}
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case SAMPLE_LOADED:
if (DEBUG) Log.d(TAG, "Sample " + msg.arg1 + " loaded");
synchronized(mLock) {
if (mOnLoadCompleteListener != null) {
mOnLoadCompleteListener.onLoadComplete(mSoundPool, msg.arg1, msg.arg2);
}
}
break;
default:
Log.e(TAG, "Unknown message type " + msg.what);
return;
}
}
}
// post event from native code to message handler
private static void postEventFromNative(Object weakRef, int msg, int arg1, int arg2, Object obj)
{
SoundPool soundPool = (SoundPool)((WeakReference)weakRef).get();
if (soundPool == null)
return;
if (soundPool.mEventHandler != null) {
Message m = soundPool.mEventHandler.obtainMessage(msg, arg1, arg2, obj);
soundPool.mEventHandler.sendMessage(m);
}
}
/**
* Release the SoundPool resources.
*
@@ -367,8 +466,7 @@ public class SoundPool
*/
public native final void release();
private native final void native_setup(Object mediaplayer_this,
int maxStreams, int streamType, int srcQuality);
private native final int native_setup(Object weakRef, int maxStreams, int streamType, int srcQuality);
protected void finalize() { release(); }
}

View File

@@ -61,6 +61,9 @@ SoundPool::SoundPool(int maxChannels, int streamType, int srcQuality)
mNextSampleID = 0;
mNextChannelID = 0;
mCallback = 0;
mUserData = 0;
mChannelPool = new SoundChannel[mMaxChannels];
for (int i = 0; i < mMaxChannels; ++i) {
mChannelPool[i].init(this);
@@ -141,7 +144,7 @@ void SoundPool::quit()
bool SoundPool::startThreads()
{
createThread(beginThread, this);
createThreadEtc(beginThread, this, "SoundPoolThread");
if (mDecodeThread == NULL)
mDecodeThread = new SoundPoolThread(this);
return mDecodeThread != NULL;
@@ -372,6 +375,21 @@ void SoundPool::done(SoundChannel* 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) {
@@ -422,7 +440,7 @@ Sample::~Sample()
delete mUrl;
}
void Sample::doLoad()
status_t Sample::doLoad()
{
uint32_t sampleRate;
int numChannels;
@@ -439,19 +457,19 @@ void Sample::doLoad()
}
if (p == 0) {
LOGE("Unable to load sample: %s", mUrl);
return;
return -1;
}
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;
return - 1;
}
if ((numChannels < 1) || (numChannels > 2)) {
LOGE("Sample channel count (%d) out of range", numChannels);
return;
return - 1;
}
//_dumpBuffer(p->pointer(), p->size());
@@ -464,6 +482,7 @@ void Sample::doLoad()
mNumChannels = numChannels;
mFormat = format;
mState = READY;
return 0;
}

View File

@@ -24,8 +24,6 @@
#include <media/AudioTrack.h>
#include <cutils/atomic.h>
#include <nativehelper/jni.h>
namespace android {
static const int IDLE_PRIORITY = -1;
@@ -43,10 +41,11 @@ public:
int mMsg;
int mArg1;
int mArg2;
enum MessageType { INVALID, SAMPLE_LOADED };
};
// JNI for calling back Java SoundPool object
extern void android_soundpool_SoundPool_notify(jobject ref, const SoundPoolEvent *event);
// callback function prototype
typedef void SoundPoolCallback(SoundPoolEvent event, SoundPool* soundPool, void* user);
// tracks samples used by application
class Sample : public RefBase {
@@ -62,7 +61,7 @@ public:
size_t size() { return mSize; }
int state() { return mState; }
uint8_t* data() { return static_cast<uint8_t*>(mData->pointer()); }
void doLoad();
status_t doLoad();
void startLoad() { mState = LOADING; }
sp<IMemory> getIMemory() { return mData; }
@@ -182,6 +181,10 @@ public:
// called from AudioTrack thread
void done(SoundChannel* channel);
// callback function
void setCallback(SoundPoolCallback* callback, void* user);
void* getUserData() { return mUserData; }
private:
SoundPool() {} // no default constructor
bool startThreads();
@@ -191,6 +194,7 @@ private:
SoundChannel* findNextChannel (int channelID);
SoundChannel* allocateChannel(int priority);
void moveToFront(SoundChannel* channel);
void notify(SoundPoolEvent event);
void dump();
// restart thread
@@ -214,6 +218,11 @@ private:
int mNextSampleID;
int mNextChannelID;
bool mQuit;
// callback
Mutex mCallbackLock;
SoundPoolCallback* mCallback;
void* mUserData;
};
} // end namespace android

View File

@@ -22,50 +22,54 @@
namespace android {
void SoundPoolThread::MessageQueue::write(SoundPoolMsg msg) {
LOGV("MessageQueue::write - acquiring lock\n");
void SoundPoolThread::write(SoundPoolMsg msg) {
Mutex::Autolock lock(&mLock);
while (mQueue.size() >= maxMessages) {
LOGV("MessageQueue::write - wait\n");
while (mMsgQueue.size() >= maxMessages) {
mCondition.wait(mLock);
}
LOGV("MessageQueue::write - push message\n");
mQueue.push(msg);
mCondition.signal();
// if thread is quitting, don't add to queue
if (mRunning) {
mMsgQueue.push(msg);
mCondition.signal();
}
}
const SoundPoolMsg SoundPoolThread::MessageQueue::read() {
LOGV("MessageQueue::read - acquiring lock\n");
const SoundPoolMsg SoundPoolThread::read() {
Mutex::Autolock lock(&mLock);
while (mQueue.size() == 0) {
LOGV("MessageQueue::read - wait\n");
while (mMsgQueue.size() == 0) {
mCondition.wait(mLock);
}
SoundPoolMsg msg = mQueue[0];
LOGV("MessageQueue::read - retrieve message\n");
mQueue.removeAt(0);
SoundPoolMsg msg = mMsgQueue[0];
mMsgQueue.removeAt(0);
mCondition.signal();
return msg;
}
void SoundPoolThread::MessageQueue::quit() {
void SoundPoolThread::quit() {
Mutex::Autolock lock(&mLock);
mQueue.clear();
mQueue.push(SoundPoolMsg(SoundPoolMsg::KILL, 0));
mCondition.signal();
mCondition.wait(mLock);
if (mRunning) {
mRunning = false;
mMsgQueue.clear();
mMsgQueue.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);
mMsgQueue.setCapacity(maxMessages);
if (createThread(beginThread, this)) {
mRunning = true;
}
}
SoundPoolThread::~SoundPoolThread()
{
quit();
}
int SoundPoolThread::beginThread(void* arg) {
@@ -77,7 +81,7 @@ int SoundPoolThread::beginThread(void* arg) {
int SoundPoolThread::run() {
LOGV("run");
for (;;) {
SoundPoolMsg msg = mMessages.read();
SoundPoolMsg msg = read();
LOGV("Got message m=%d, mData=%d", msg.mMessageType, msg.mData);
switch (msg.mMessageType) {
case SoundPoolMsg::KILL:
@@ -95,14 +99,16 @@ int SoundPoolThread::run() {
}
void SoundPoolThread::loadSample(int sampleID) {
mMessages.write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE, 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) {
sample->doLoad();
status = sample->doLoad();
}
mSoundPool->notify(SoundPoolEvent(SoundPoolEvent::SAMPLE_LOADED, sampleID, status));
}
} // end namespace android

View File

@@ -27,7 +27,7 @@ namespace android {
class SoundPoolMsg {
public:
enum MessageType { INVALID, KILL, LOAD_SAMPLE, PLAY_SAMPLE, SAMPLE_DONE };
enum MessageType { INVALID, KILL, LOAD_SAMPLE };
SoundPoolMsg() : mMessageType(INVALID), mData(0) {}
SoundPoolMsg(MessageType MessageType, int data) :
mMessageType(MessageType), mData(data) {}
@@ -45,29 +45,22 @@ public:
SoundPoolThread(SoundPool* SoundPool);
~SoundPoolThread();
void loadSample(int sampleID);
void quit() { mMessages.quit(); }
void quit();
void write(SoundPoolMsg msg);
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);
const SoundPoolMsg read();
SoundPool* mSoundPool;
MessageQueue mMessages;
Mutex mLock;
Condition mCondition;
Vector<SoundPoolMsg> mMsgQueue;
SoundPool* mSoundPool;
bool mRunning;
};
} // end namespace android

View File

@@ -17,7 +17,7 @@
#include <stdio.h>
//#define LOG_NDEBUG 0
#define LOG_TAG "SoundPool"
#define LOG_TAG "SoundPool-JNI"
#include <utils/Log.h>
#include <nativehelper/jni.h>
@@ -29,6 +29,7 @@ using namespace android;
static struct fields_t {
jfieldID mNativeContext;
jmethodID mPostEvent;
jclass mSoundPoolClass;
} fields;
@@ -149,19 +150,29 @@ android_media_SoundPool_setRate(JNIEnv *env, jobject thiz, jint channelID,
ap->setRate(channelID, rate);
}
static void
android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz,
jobject weak_this, jint maxChannels, jint streamType, jint srcQuality)
static void android_media_callback(SoundPoolEvent event, SoundPool* soundPool, void* user)
{
LOGV("callback: (%d, %d, %d, %p, %p)", event.mMsg, event.mArg1, event.mArg2, soundPool, user);
JNIEnv *env = AndroidRuntime::getJNIEnv();
env->CallStaticVoidMethod(fields.mSoundPoolClass, fields.mPostEvent, user, event.mMsg, event.mArg1, event.mArg2, NULL);
}
static jint
android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef, jint maxChannels, jint streamType, jint srcQuality)
{
LOGV("android_media_SoundPool_native_setup");
SoundPool *ap = new SoundPool(maxChannels, streamType, srcQuality);
if (ap == NULL) {
jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
return;
return -1;
}
// save pointer to SoundPool C++ object in opaque field in Java object
env->SetIntField(thiz, fields.mNativeContext, (int)ap);
// set callback with weak reference
jobject globalWeakRef = env->NewGlobalRef(weakRef);
ap->setCallback(android_media_callback, globalWeakRef);
return 0;
}
static void
@@ -170,6 +181,15 @@ android_media_SoundPool_release(JNIEnv *env, jobject thiz)
LOGV("android_media_SoundPool_release");
SoundPool *ap = MusterSoundPool(env, thiz);
if (ap != NULL) {
// release weak reference
jobject weakRef = (jobject) ap->getUserData();
if (weakRef != NULL) {
env->DeleteGlobalRef(weakRef);
}
// clear callback and native context
ap->setCallback(NULL, NULL);
env->SetIntField(thiz, fields.mNativeContext, 0);
delete ap;
}
@@ -224,7 +244,7 @@ static JNINativeMethod gMethods[] = {
(void *)android_media_SoundPool_setRate
},
{ "native_setup",
"(Ljava/lang/Object;III)V",
"(Ljava/lang/Object;III)I",
(void*)android_media_SoundPool_native_setup
},
{ "release",
@@ -259,6 +279,13 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
goto bail;
}
fields.mPostEvent = env->GetStaticMethodID(clazz, "postEventFromNative",
"(Ljava/lang/Object;IIILjava/lang/Object;)V");
if (fields.mPostEvent == NULL) {
LOGE("Can't find android/media/SoundPool.postEventFromNative");
return -1;
}
if (AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)) < 0)
goto bail;