Merge change I9120be33
* changes: Add OnLoadCompleteListener to SoundPool.
This commit is contained in:
@@ -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(); }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user