soundpool: reuse channel for same sample if available

Reuse channel for same sample if the channel completed
current playback and is not reallocated to another sample,
i.e. not stolen by other sample.

authored-by: Ashish Jain <ashishj@codeaurora.org>

Change-Id: Ibe7ee318c7dc11f3c4fd3a2f57d861318b10973b
Signed-off-by: Glenn Kasten <gkasten@google.com>
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index a73209b..8957b3c 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -256,7 +256,7 @@
     dump();
 
     // allocate a channel
-    channel = allocateChannel_l(priority);
+    channel = allocateChannel_l(priority, sampleID);
 
     // no channel allocated - return 0
     if (!channel) {
@@ -271,13 +271,25 @@
     return channelID;
 }
 
-SoundChannel* SoundPool::allocateChannel_l(int priority)
+SoundChannel* SoundPool::allocateChannel_l(int priority, int sampleID)
 {
     List<SoundChannel*>::iterator iter;
     SoundChannel* channel = NULL;
 
-    // allocate a channel
+    // check if channel for given sampleID still available
     if (!mChannels.empty()) {
+        for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
+            if (sampleID == (*iter)->getPrevSampleID() && (*iter)->state() == SoundChannel::IDLE) {
+                channel = *iter;
+                mChannels.erase(iter);
+                ALOGV("Allocated recycled channel for same sampleID");
+                break;
+            }
+        }
+    }
+
+    // allocate any channel
+    if (!channel && !mChannels.empty()) {
         iter = mChannels.begin();
         if (priority >= (*iter)->priority()) {
             channel = *iter;
@@ -648,6 +660,7 @@
 void SoundChannel::init(SoundPool* soundPool)
 {
     mSoundPool = soundPool;
+    mPrevSampleID = -1;
 }
 
 // call with sound pool lock held
@@ -656,7 +669,7 @@
 {
     sp<AudioTrack> oldTrack;
     sp<AudioTrack> newTrack;
-    status_t status;
+    status_t status = NO_ERROR;
 
     { // scope for the lock
         Mutex::Autolock lock(&mLock);
@@ -701,38 +714,41 @@
         }
 #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);
-        audio_channel_mask_t channelMask = audio_channel_out_mask_from_count(numChannels);
+        if (!mAudioTrack.get() || mPrevSampleID != sample->sampleID()) {
+            // 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);
+            audio_channel_mask_t channelMask = audio_channel_out_mask_from_count(numChannels);
 
-        // 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(),
-                channelMask, sample->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, callback, userData);
-#else
-        uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
-        newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
-                channelMask, frameCount, AUDIO_OUTPUT_FLAG_FAST, callback, userData,
-                bufferFrames);
-#endif
-        oldTrack = mAudioTrack;
-        status = newTrack->initCheck();
-        if (status != NO_ERROR) {
-            ALOGE("Error creating AudioTrack");
-            goto exit;
+            // 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(),
+                    channelMask, sample->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, callback, userData);
+    #else
+            uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
+            newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
+                    channelMask, frameCount, AUDIO_OUTPUT_FLAG_FAST, callback, userData,
+                    bufferFrames);
+    #endif
+            oldTrack = mAudioTrack;
+            status = newTrack->initCheck();
+            if (status != NO_ERROR) {
+                ALOGE("Error creating AudioTrack");
+                goto exit;
+            }
+            // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
+            mToggle = toggle;
+            mAudioTrack = newTrack;
+        } else {
+            newTrack = mAudioTrack;
+            ALOGV("reusing track %p for sample %d", mAudioTrack.get(), sample->sampleID());
         }
-        ALOGV("setVolume %p", newTrack.get());
         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;
@@ -875,6 +891,7 @@
         setVolume_l(0, 0);
         ALOGV("stop");
         mAudioTrack->stop();
+        mPrevSampleID = mSample->sampleID();
         mSample.clear();
         mState = IDLE;
         mPriority = IDLE_PRIORITY;
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index 9d9cbdf..f520406 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -136,6 +136,7 @@
     void nextEvent();
     int nextChannelID() { return mNextEvent.channelID(); }
     void dump();
+    int getPrevSampleID(void) { return mPrevSampleID; }
 
 private:
     static void callback(int event, void* user, void *info);
@@ -152,6 +153,7 @@
     int                 mAudioBufferSize;
     unsigned long       mToggle;
     bool                mAutoPaused;
+    int                 mPrevSampleID;
 };
 
 // application object for managing a pool of sounds
@@ -193,7 +195,7 @@
     sp<Sample> findSample(int sampleID) { return mSamples.valueFor(sampleID); }
     SoundChannel* findChannel (int channelID);
     SoundChannel* findNextChannel (int channelID);
-    SoundChannel* allocateChannel_l(int priority);
+    SoundChannel* allocateChannel_l(int priority, int sampleID);
     void moveToFront_l(SoundChannel* channel);
     void notify(SoundPoolEvent event);
     void dump();