The problem can occur if a sample is started at the same time as the last AudioTrack callback for a playing sample is called. At this time, allocateChannel() can be called concurrently with moveToFront() which can cause an entry in mChannels being used by moveToFront() to be erased temporarily by allocateChannel(). The fix consists in making sure that the SoundPool mutex is held whenever play(), stop() or done() are called. In addition, other potential weaknesses have been removed by making sure that the channel mutex is held while starting, stopping and processing the AudioTrack call back. To that purpose, a mechanism similar to the channel restart method is implemented to avoid stopping channels from the AudioTrack call back but do it from the restart thread instead. The sound effects SounPool management in AudioService has also been improved to make sure that the samples have been loaded when a playback request is received and also to immediately release the SoundPool when the effects are unloaded without waiting for the GC to occur. The SoundPool.java class was modified to allow the use of a looper attached to the thread in which the sample loaded listener is running and not to the thread in which the SoundPool is created. The maximum number of samples that can be loaded in a SoundPool lifetime as been increased from 255 to 65535. Change-Id: I368a3bdfda4239f807f857c3e97b70f6b31b0af3
239 lines
7.8 KiB
C++
239 lines
7.8 KiB
C++
/*
|
|
* 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; }
|
|
int 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, int 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;
|
|
uint8_t mFormat : 2;
|
|
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(0), 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, 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 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);
|
|
int 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;
|
|
int mStreamType;
|
|
int mSrcQuality;
|
|
int mAllocated;
|
|
int mNextSampleID;
|
|
int mNextChannelID;
|
|
bool mQuit;
|
|
|
|
// callback
|
|
Mutex mCallbackLock;
|
|
SoundPoolCallback* mCallback;
|
|
void* mUserData;
|
|
};
|
|
|
|
} // end namespace android
|
|
|
|
#endif /*SOUNDPOOL_H_*/
|