Merge "Camera2: Fix exposure compensation step value." into jb-mr1-dev
diff --git a/include/media/mediametadataretriever.h b/include/media/mediametadataretriever.h
index 534afce..0df77c1 100644
--- a/include/media/mediametadataretriever.h
+++ b/include/media/mediametadataretriever.h
@@ -55,6 +55,7 @@
     METADATA_KEY_TIMED_TEXT_LANGUAGES      = 21,
     METADATA_KEY_IS_DRM          = 22,
     METADATA_KEY_LOCATION        = 23,
+    METADATA_KEY_VIDEO_ROTATION  = 24,
 
     // Add more here...
 };
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index 3c25a14..e91904c 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -111,6 +111,7 @@
     kKeyTrackTimeStatus   = 'tktm',  // int64_t
 
     kKeyNotRealTime       = 'ntrt',  // bool (int32_t)
+    kKeyNumBuffers        = 'nbbf',  // int32_t
 
     // Ogg files can be tagged to be automatically looping...
     kKeyAutoLoop          = 'autL',  // bool (int32_t)
diff --git a/include/media/stagefright/timedtext/TimedTextDriver.h b/include/media/stagefright/timedtext/TimedTextDriver.h
index cde551b..f23c337 100644
--- a/include/media/stagefright/timedtext/TimedTextDriver.h
+++ b/include/media/stagefright/timedtext/TimedTextDriver.h
@@ -64,6 +64,7 @@
 
     enum State {
         UNINITIALIZED,
+        PREPARED,
         PLAYING,
         PAUSED,
     };
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 2f5e9a4..dabb0e7 100755
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -3623,11 +3623,6 @@
         }
         params->setInt64(kKeyTime, startTimeUs);
     }
-    status_t err = mSource->start(params.get());
-
-    if (err != OK) {
-        return err;
-    }
 
     mCodecSpecificDataIndex = 0;
     mInitialBufferSubmit = true;
@@ -3640,6 +3635,23 @@
     mFilledBuffers.clear();
     mPaused = false;
 
+    status_t err;
+    if (mIsEncoder) {
+        // Calling init() before starting its source so that we can configure,
+        // if supported, the source to use exactly the same number of input
+        // buffers as requested by the encoder.
+        if ((err = init()) != OK) {
+            return err;
+        }
+
+        params->setInt32(kKeyNumBuffers, mPortBuffers[kPortIndexInput].size());
+        return mSource->start(params.get());
+    }
+
+    // Decoder case
+    if ((err = mSource->start(params.get())) != OK) {
+        return err;
+    }
     return init();
 }
 
diff --git a/media/libstagefright/StagefrightMetadataRetriever.cpp b/media/libstagefright/StagefrightMetadataRetriever.cpp
index 7951496..c9ef4d9 100644
--- a/media/libstagefright/StagefrightMetadataRetriever.cpp
+++ b/media/libstagefright/StagefrightMetadataRetriever.cpp
@@ -462,6 +462,7 @@
     int32_t videoWidth = -1;
     int32_t videoHeight = -1;
     int32_t audioBitrate = -1;
+    int32_t rotationAngle = -1;
 
     // The overall duration is the duration of the longest track.
     int64_t maxDurationUs = 0;
@@ -489,6 +490,9 @@
 
                 CHECK(trackMeta->findInt32(kKeyWidth, &videoWidth));
                 CHECK(trackMeta->findInt32(kKeyHeight, &videoHeight));
+                if (!trackMeta->findInt32(kKeyRotation, &rotationAngle)) {
+                    rotationAngle = 0;
+                }
             } else if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) {
                 const char *lang;
                 trackMeta->findCString(kKeyMediaLanguage, &lang);
@@ -521,6 +525,9 @@
 
         sprintf(tmp, "%d", videoHeight);
         mMetaData.add(METADATA_KEY_VIDEO_HEIGHT, String8(tmp));
+
+        sprintf(tmp, "%d", rotationAngle);
+        mMetaData.add(METADATA_KEY_VIDEO_ROTATION, String8(tmp));
     }
 
     if (numTracks == 1 && hasAudio && audioBitrate >= 0) {
diff --git a/media/libstagefright/timedtext/TimedTextDriver.cpp b/media/libstagefright/timedtext/TimedTextDriver.cpp
index 42ca1f5..54ce7ac 100644
--- a/media/libstagefright/timedtext/TimedTextDriver.cpp
+++ b/media/libstagefright/timedtext/TimedTextDriver.cpp
@@ -61,7 +61,7 @@
     source = mTextSourceVector.valueFor(index);
     mPlayer->setDataSource(source);
     if (mState == UNINITIALIZED) {
-        mState = PAUSED;
+        mState = PREPARED;
     }
     mCurrentTrackIndex = index;
     return OK;
@@ -74,42 +74,47 @@
             return INVALID_OPERATION;
         case PLAYING:
             return OK;
-        case PAUSED:
+        case PREPARED:
             mPlayer->start();
-            break;
+            mState = PLAYING;
+            return OK;
+        case PAUSED:
+            mPlayer->resume();
+            mState = PLAYING;
+            return OK;
         default:
             TRESPASS();
     }
-    mState = PLAYING;
-    return OK;
+    return UNKNOWN_ERROR;
 }
 
-// TODO: Test if pause() works properly.
-// Scenario 1: start - pause - resume
-// Scenario 2: start - seek
-// Scenario 3: start - pause - seek - resume
 status_t TimedTextDriver::pause() {
     Mutex::Autolock autoLock(mLock);
+    ALOGV("%s() is called", __FUNCTION__);
     switch (mState) {
         case UNINITIALIZED:
             return INVALID_OPERATION;
         case PLAYING:
             mPlayer->pause();
-            break;
+            mState = PAUSED;
+            return OK;
+        case PREPARED:
+            return INVALID_OPERATION;
         case PAUSED:
             return OK;
         default:
             TRESPASS();
     }
-    mState = PAUSED;
-    return OK;
+    return UNKNOWN_ERROR;
 }
 
 status_t TimedTextDriver::selectTrack(size_t index) {
     status_t ret = OK;
     Mutex::Autolock autoLock(mLock);
+    ALOGV("%s() is called", __FUNCTION__);
     switch (mState) {
         case UNINITIALIZED:
+        case PREPARED:
         case PAUSED:
             ret = selectTrack_l(index);
             break;
@@ -128,21 +133,50 @@
 }
 
 status_t TimedTextDriver::unselectTrack(size_t index) {
+    Mutex::Autolock autoLock(mLock);
+    ALOGV("%s() is called", __FUNCTION__);
     if (mCurrentTrackIndex != index) {
         return INVALID_OPERATION;
     }
-    status_t err = pause();
-    if (err != OK) {
-        return err;
+    switch (mState) {
+        case UNINITIALIZED:
+            return INVALID_OPERATION;
+        case PLAYING:
+            mPlayer->pause();
+            mState = UNINITIALIZED;
+            return OK;
+        case PREPARED:
+        case PAUSED:
+            mState = UNINITIALIZED;
+            return OK;
+        default:
+            TRESPASS();
     }
-    Mutex::Autolock autoLock(mLock);
-    mState = UNINITIALIZED;
-    return OK;
+    return UNKNOWN_ERROR;
 }
 
 status_t TimedTextDriver::seekToAsync(int64_t timeUs) {
-    mPlayer->seekToAsync(timeUs);
-    return OK;
+    Mutex::Autolock autoLock(mLock);
+    ALOGV("%s() is called", __FUNCTION__);
+    switch (mState) {
+        case UNINITIALIZED:
+            return INVALID_OPERATION;
+        case PREPARED:
+            mPlayer->seekToAsync(timeUs);
+            mPlayer->pause();
+            mState = PAUSED;
+            return OK;
+        case PAUSED:
+            mPlayer->seekToAsync(timeUs);
+            mPlayer->pause();
+            return OK;
+        case PLAYING:
+            mPlayer->seekToAsync(timeUs);
+            return OK;
+        defaut:
+            TRESPASS();
+    }
+    return UNKNOWN_ERROR;
 }
 
 status_t TimedTextDriver::addInBandTextSource(
diff --git a/media/libstagefright/timedtext/TimedTextPlayer.cpp b/media/libstagefright/timedtext/TimedTextPlayer.cpp
index 9b001cc..df7eb39 100644
--- a/media/libstagefright/timedtext/TimedTextPlayer.cpp
+++ b/media/libstagefright/timedtext/TimedTextPlayer.cpp
@@ -18,6 +18,7 @@
 #define LOG_TAG "TimedTextPlayer"
 #include <utils/Log.h>
 
+#include <limits.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/timedtext/TimedTextDriver.h>
@@ -30,12 +31,18 @@
 
 namespace android {
 
+// Event should be fired a bit earlier considering the processing time till
+// application actually gets the notification message.
 static const int64_t kAdjustmentProcessingTimeUs = 100000ll;
+static const int64_t kMaxDelayUs = 5000000ll;
 static const int64_t kWaitTimeUsToRetryRead = 100000ll;
+static const int64_t kInvalidTimeUs = INT_MIN;
 
 TimedTextPlayer::TimedTextPlayer(const wp<MediaPlayerBase> &listener)
     : mListener(listener),
       mSource(NULL),
+      mPendingSeekTimeUs(kInvalidTimeUs),
+      mPaused(false),
       mSendSubtitleGeneration(0) {
 }
 
@@ -48,15 +55,17 @@
 }
 
 void TimedTextPlayer::start() {
-    sp<AMessage> msg = new AMessage(kWhatSeek, id());
-    msg->setInt64("seekTimeUs", -1);
-    msg->post();
+    (new AMessage(kWhatStart, id()))->post();
 }
 
 void TimedTextPlayer::pause() {
     (new AMessage(kWhatPause, id()))->post();
 }
 
+void TimedTextPlayer::resume() {
+    (new AMessage(kWhatResume, id()))->post();
+}
+
 void TimedTextPlayer::seekToAsync(int64_t timeUs) {
     sp<AMessage> msg = new AMessage(kWhatSeek, id());
     msg->setInt64("seekTimeUs", timeUs);
@@ -72,10 +81,43 @@
 void TimedTextPlayer::onMessageReceived(const sp<AMessage> &msg) {
     switch (msg->what()) {
         case kWhatPause: {
+            mPaused = true;
+            break;
+        }
+        case kWhatResume: {
+            mPaused = false;
+            if (mPendingSeekTimeUs != kInvalidTimeUs) {
+                seekToAsync(mPendingSeekTimeUs);
+                mPendingSeekTimeUs = kInvalidTimeUs;
+            } else {
+                doRead();
+            }
+            break;
+        }
+        case kWhatStart: {
+            sp<MediaPlayerBase> listener = mListener.promote();
+            if (listener == NULL) {
+                ALOGE("Listener is NULL when kWhatStart is received.");
+                break;
+            }
+            mPaused = false;
+            mPendingSeekTimeUs = kInvalidTimeUs;
+            int32_t positionMs = 0;
+            listener->getCurrentPosition(&positionMs);
+            int64_t seekTimeUs = positionMs * 1000ll;
+
+            notifyListener();
             mSendSubtitleGeneration++;
+            doSeekAndRead(seekTimeUs);
             break;
         }
         case kWhatRetryRead: {
+            int32_t generation = -1;
+            CHECK(msg->findInt32("generation", &generation));
+            if (generation != mSendSubtitleGeneration) {
+                // Drop obsolete msg.
+                break;
+            }
             int64_t seekTimeUs;
             int seekMode;
             if (msg->findInt64("seekTimeUs", &seekTimeUs) &&
@@ -91,9 +133,11 @@
             break;
         }
         case kWhatSeek: {
-            int64_t seekTimeUs = 0;
+            int64_t seekTimeUs = kInvalidTimeUs;
+            // Clear a displayed timed text before seeking.
+            notifyListener();
             msg->findInt64("seekTimeUs", &seekTimeUs);
-            if (seekTimeUs < 0) {
+            if (seekTimeUs == kInvalidTimeUs) {
                 sp<MediaPlayerBase> listener = mListener.promote();
                 if (listener != NULL) {
                     int32_t positionMs = 0;
@@ -101,6 +145,11 @@
                     seekTimeUs = positionMs * 1000ll;
                 }
             }
+            if (mPaused) {
+                mPendingSeekTimeUs = seekTimeUs;
+                break;
+            }
+            mSendSubtitleGeneration++;
             doSeekAndRead(seekTimeUs);
             break;
         }
@@ -108,8 +157,19 @@
             int32_t generation;
             CHECK(msg->findInt32("generation", &generation));
             if (generation != mSendSubtitleGeneration) {
-              // Drop obsolete msg.
-              break;
+                // Drop obsolete msg.
+                break;
+            }
+            // If current time doesn't reach to the fire time,
+            // re-post the message with the adjusted delay time.
+            int64_t fireTimeUs = kInvalidTimeUs;
+            if (msg->findInt64("fireTimeUs", &fireTimeUs)) {
+                // TODO: check if fireTimeUs is not kInvalidTimeUs.
+                int64_t delayUs = delayUsFromCurrentTime(fireTimeUs);
+                if (delayUs > 0) {
+                    msg->post(delayUs);
+                    break;
+                }
             }
             sp<RefBase> obj;
             if (msg->findObject("subtitle", &obj)) {
@@ -162,12 +222,14 @@
     if (err == WOULD_BLOCK) {
         sp<AMessage> msg = new AMessage(kWhatRetryRead, id());
         if (options != NULL) {
-            int64_t seekTimeUs;
-            MediaSource::ReadOptions::SeekMode seekMode;
+            int64_t seekTimeUs = kInvalidTimeUs;
+            MediaSource::ReadOptions::SeekMode seekMode =
+                MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC;
             CHECK(options->getSeekTo(&seekTimeUs, &seekMode));
             msg->setInt64("seekTimeUs", seekTimeUs);
             msg->setInt32("seekMode", seekMode);
         }
+        msg->setInt32("generation", mSendSubtitleGeneration);
         msg->post(kWaitTimeUsToRetryRead);
         return;
     } else if (err != OK) {
@@ -185,49 +247,57 @@
 }
 
 void TimedTextPlayer::postTextEvent(const sp<ParcelEvent>& parcel, int64_t timeUs) {
-    sp<MediaPlayerBase> listener = mListener.promote();
-    if (listener != NULL) {
-        int64_t positionUs, delayUs;
-        int32_t positionMs = 0;
-        listener->getCurrentPosition(&positionMs);
-        positionUs = positionMs * 1000ll;
-
-        if (timeUs <= positionUs + kAdjustmentProcessingTimeUs) {
-            delayUs = 0;
-        } else {
-            delayUs = timeUs - positionUs - kAdjustmentProcessingTimeUs;
-        }
-        postTextEventDelayUs(parcel, delayUs);
+    int64_t delayUs = delayUsFromCurrentTime(timeUs);
+    sp<AMessage> msg = new AMessage(kWhatSendSubtitle, id());
+    msg->setInt32("generation", mSendSubtitleGeneration);
+    if (parcel != NULL) {
+        msg->setObject("subtitle", parcel);
     }
+    msg->setInt64("fireTimeUs", timeUs);
+    msg->post(delayUs);
 }
 
-void TimedTextPlayer::postTextEventDelayUs(const sp<ParcelEvent>& parcel, int64_t delayUs) {
+int64_t TimedTextPlayer::delayUsFromCurrentTime(int64_t fireTimeUs) {
     sp<MediaPlayerBase> listener = mListener.promote();
-    if (listener != NULL) {
-        sp<AMessage> msg = new AMessage(kWhatSendSubtitle, id());
-        msg->setInt32("generation", mSendSubtitleGeneration);
-        if (parcel != NULL) {
-            msg->setObject("subtitle", parcel);
+    if (listener == NULL) {
+        // TODO: it may be better to return kInvalidTimeUs
+        ALOGE("%s: Listener is NULL.", __FUNCTION__, fireTimeUs);
+        return 0;
+    }
+    int32_t positionMs = 0;
+    listener->getCurrentPosition(&positionMs);
+    int64_t positionUs = positionMs * 1000ll;
+
+    if (fireTimeUs <= positionUs + kAdjustmentProcessingTimeUs) {
+        return 0;
+    } else {
+        int64_t delayUs = fireTimeUs - positionUs - kAdjustmentProcessingTimeUs;
+        if (delayUs > kMaxDelayUs) {
+            return kMaxDelayUs;
         }
-        msg->post(delayUs);
+        return delayUs;
     }
 }
 
 void TimedTextPlayer::notifyError(int error) {
     sp<MediaPlayerBase> listener = mListener.promote();
-    if (listener != NULL) {
-        listener->sendEvent(MEDIA_INFO, MEDIA_INFO_TIMED_TEXT_ERROR, error);
+    if (listener == NULL) {
+        ALOGE("%s(error=%d): Listener is NULL.", __FUNCTION__, error);
+        return;
     }
+    listener->sendEvent(MEDIA_INFO, MEDIA_INFO_TIMED_TEXT_ERROR, error);
 }
 
 void TimedTextPlayer::notifyListener(const Parcel *parcel) {
     sp<MediaPlayerBase> listener = mListener.promote();
-    if (listener != NULL) {
-        if (parcel != NULL && (parcel->dataSize() > 0)) {
-            listener->sendEvent(MEDIA_TIMED_TEXT, 0, 0, parcel);
-        } else {  // send an empty timed text to clear the screen
-            listener->sendEvent(MEDIA_TIMED_TEXT);
-        }
+    if (listener == NULL) {
+        ALOGE("%s: Listener is NULL.", __FUNCTION__);
+        return;
+    }
+    if (parcel != NULL && (parcel->dataSize() > 0)) {
+        listener->sendEvent(MEDIA_TIMED_TEXT, 0, 0, parcel);
+    } else {  // send an empty timed text to clear the screen
+        listener->sendEvent(MEDIA_TIMED_TEXT);
     }
 }
 
diff --git a/media/libstagefright/timedtext/TimedTextPlayer.h b/media/libstagefright/timedtext/TimedTextPlayer.h
index b7e15f8..ec8ed25 100644
--- a/media/libstagefright/timedtext/TimedTextPlayer.h
+++ b/media/libstagefright/timedtext/TimedTextPlayer.h
@@ -40,6 +40,7 @@
 
     void start();
     void pause();
+    void resume();
     void seekToAsync(int64_t timeUs);
     void setDataSource(sp<TimedTextSource> source);
 
@@ -49,6 +50,8 @@
 private:
     enum {
         kWhatPause = 'paus',
+        kWhatResume = 'resm',
+        kWhatStart = 'strt',
         kWhatSeek = 'seek',
         kWhatRetryRead = 'read',
         kWhatSendSubtitle = 'send',
@@ -62,13 +65,15 @@
 
     wp<MediaPlayerBase> mListener;
     sp<TimedTextSource> mSource;
+    int64_t mPendingSeekTimeUs;
+    bool mPaused;
     int32_t mSendSubtitleGeneration;
 
     void doSeekAndRead(int64_t seekTimeUs);
     void doRead(MediaSource::ReadOptions* options = NULL);
     void onTextEvent();
     void postTextEvent(const sp<ParcelEvent>& parcel = NULL, int64_t timeUs = -1);
-    void postTextEventDelayUs(const sp<ParcelEvent>& parcel = NULL, int64_t delayUs = -1);
+    int64_t delayUsFromCurrentTime(int64_t fireTimeUs);
     void notifyError(int error = 0);
     void notifyListener(const Parcel *parcel = NULL);
 
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index fe9d09d..d65a2b6 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -868,16 +868,19 @@
             if (mBtNrecIsOff != btNrecIsOff) {
                 for (size_t i = 0; i < mRecordThreads.size(); i++) {
                     sp<RecordThread> thread = mRecordThreads.valueAt(i);
-                    RecordThread::RecordTrack *track = thread->track();
-                    if (track != NULL) {
-                        audio_devices_t device = thread->device() & AUDIO_DEVICE_IN_ALL;
-                        bool suspend = audio_is_bluetooth_sco_device(device) && btNrecIsOff;
+                    audio_devices_t device = thread->device() & AUDIO_DEVICE_IN_ALL;
+                    bool suspend = audio_is_bluetooth_sco_device(device) && btNrecIsOff;
+                    // collect all of the thread's session IDs
+                    KeyedVector<int, bool> ids = thread->sessionIds();
+                    // suspend effects associated with those session IDs
+                    for (size_t j = 0; j < ids.size(); ++j) {
+                        int sessionId = ids.keyAt(j);
                         thread->setEffectSuspended(FX_IID_AEC,
                                                    suspend,
-                                                   track->sessionId());
+                                                   sessionId);
                         thread->setEffectSuspended(FX_IID_NS,
                                                    suspend,
-                                                   track->sessionId());
+                                                   sessionId);
                     }
                 }
                 mBtNrecIsOff = btNrecIsOff;
@@ -3178,7 +3181,7 @@
             }
 
             //ALOGV("track %d u=%08x, s=%08x [NOT READY] on thread %p", name, cblk->user, cblk->server, this);
-            if ((track->sharedBuffer() != 0) || track->isTerminated() ||
+            if ((track->sharedBuffer() != 0) ||
                     track->isStopped() || track->isPaused()) {
                 // We have consumed all the buffers of this track.
                 // Remove it from the list of active tracks.
@@ -3198,8 +3201,8 @@
                 track->mUnderrunCount++;
                 // No buffers for this track. Give it a few chances to
                 // fill a buffer, then remove it from active list.
-                if (--(track->mRetryCount) <= 0) {
-                    ALOGV("BUFFER TIMEOUT: remove(%d) from active list on thread %p", name, this);
+                if (--(track->mRetryCount) <= 0 || track->isTerminated()) {
+                    ALOGV_IF(track->mRetryCount <= 0, "BUFFER TIMEOUT: remove(%d) from active list on thread %p", name, this);
                     tracksToRemove->add(track);
                     // indicate to client process that the track was disabled because of underrun;
                     // it will then automatically call start() when data is available
@@ -3698,7 +3701,7 @@
             }
 
             //ALOGV("track %d u=%08x, s=%08x [NOT READY]", track->name(), cblk->user, cblk->server);
-            if ((track->sharedBuffer() != 0) || track->isTerminated() ||
+            if ((track->sharedBuffer() != 0) ||
                     track->isStopped() || track->isPaused()) {
                 // We have consumed all the buffers of this track.
                 // Remove it from the list of active tracks.
@@ -3716,8 +3719,8 @@
             } else {
                 // No buffers for this track. Give it a few chances to
                 // fill a buffer, then remove it from active list.
-                if (--(track->mRetryCount) <= 0) {
-                    ALOGV("BUFFER TIMEOUT: remove(%d) from active list", track->name());
+                if (--(track->mRetryCount) <= 0 || track->isTerminated()) {
+                    ALOGV_IF(track->mRetryCount <= 0, "BUFFER TIMEOUT: remove(%d) from active list", track->name());
                     trackToRemove = track;
                 } else {
                     mixerStatus = MIXER_TRACKS_ENABLED;
@@ -4277,11 +4280,6 @@
 AudioFlinger::PlaybackThread::Track::~Track()
 {
     ALOGV("PlaybackThread::Track destructor");
-    sp<ThreadBase> thread = mThread.promote();
-    if (thread != 0) {
-        Mutex::Autolock _l(thread->mLock);
-        mState = TERMINATED;
-    }
 }
 
 void AudioFlinger::PlaybackThread::Track::destroy()
@@ -5301,10 +5299,7 @@
 
 AudioFlinger::RecordThread::RecordTrack::~RecordTrack()
 {
-    sp<ThreadBase> thread = mThread.promote();
-    if (thread != 0) {
-        AudioSystem::releaseInput(thread->id());
-    }
+    ALOGV("%s", __func__);
 }
 
 // AudioBufferProvider interface
@@ -5377,6 +5372,11 @@
     }
 }
 
+/*static*/ void AudioFlinger::RecordThread::RecordTrack::appendDumpHeader(String8& result)
+{
+    result.append("   Clien Fmt Chn mask   Session Buf  S SRate  Serv     User\n");
+}
+
 void AudioFlinger::RecordThread::RecordTrack::dump(char* buffer, size_t size)
 {
     snprintf(buffer, size, "   %05d %03u 0x%08x %05d   %04u %01d %05u  %08x %08x\n",
@@ -5861,6 +5861,7 @@
 
 AudioFlinger::RecordHandle::~RecordHandle() {
     stop_nonvirtual();
+    mRecordTrack->destroy();
 }
 
 sp<IMemory> AudioFlinger::RecordHandle::getCblk() const {
@@ -5896,7 +5897,7 @@
                                          audio_io_handle_t id,
                                          audio_devices_t device) :
     ThreadBase(audioFlinger, id, device, RECORD),
-    mInput(input), mTrack(NULL), mResampler(NULL), mRsmpOutBuffer(NULL), mRsmpInBuffer(NULL),
+    mInput(input), mResampler(NULL), mRsmpOutBuffer(NULL), mRsmpInBuffer(NULL),
     // mRsmpInIndex and mInputBytes set by readInputParameters()
     mReqChannelCount(popcount(channelMask)),
     mReqSampleRate(sampleRate)
@@ -5980,6 +5981,9 @@
                         mStartStopCond.broadcast();
                     }
                     mStandby = false;
+                } else if (mActiveTrack->mState == TrackBase::TERMINATED) {
+                    removeTrack_l(mActiveTrack);
+                    mActiveTrack.clear();
                 }
             }
             lockEffectChains_l(effectChains);
@@ -6168,8 +6172,8 @@
             lStatus = NO_MEMORY;
             goto Exit;
         }
+        mTracks.add(track);
 
-        mTrack = track.get();
         // disable AEC and NS if the device is a BT SCO headset supporting those pre processings
         bool suspend = audio_is_bluetooth_sco_device(mDevice & AUDIO_DEVICE_IN_ALL) &&
                         mAudioFlinger->btNrecIsOff();
@@ -6320,17 +6324,64 @@
         return BAD_VALUE;
     }
 
+    int eventSession = event->triggerSession();
+    status_t ret = NAME_NOT_FOUND;
+
     Mutex::Autolock _l(mLock);
 
-    if (mTrack != NULL && event->triggerSession() == mTrack->sessionId()) {
-        mTrack->setSyncEvent(event);
-        return NO_ERROR;
+    for (size_t i = 0; i < mTracks.size(); i++) {
+        sp<RecordTrack> track = mTracks[i];
+        if (eventSession == track->sessionId()) {
+            track->setSyncEvent(event);
+            ret = NO_ERROR;
+        }
     }
-    return NAME_NOT_FOUND;
+    return ret;
+}
+
+void AudioFlinger::RecordThread::RecordTrack::destroy()
+{
+    // see comments at AudioFlinger::PlaybackThread::Track::destroy()
+    sp<RecordTrack> keep(this);
+    {
+        sp<ThreadBase> thread = mThread.promote();
+        if (thread != 0) {
+            if (mState == ACTIVE || mState == RESUMING) {
+                AudioSystem::stopInput(thread->id());
+            }
+            AudioSystem::releaseInput(thread->id());
+            Mutex::Autolock _l(thread->mLock);
+            RecordThread *recordThread = (RecordThread *) thread.get();
+            recordThread->destroyTrack_l(this);
+        }
+    }
+}
+
+// destroyTrack_l() must be called with ThreadBase::mLock held
+void AudioFlinger::RecordThread::destroyTrack_l(const sp<RecordTrack>& track)
+{
+    track->mState = TrackBase::TERMINATED;
+    // active tracks are removed by threadLoop()
+    if (mActiveTrack != track) {
+        removeTrack_l(track);
+    }
+}
+
+void AudioFlinger::RecordThread::removeTrack_l(const sp<RecordTrack>& track)
+{
+    mTracks.remove(track);
+    // need anything related to effects here?
 }
 
 void AudioFlinger::RecordThread::dump(int fd, const Vector<String16>& args)
 {
+    dumpInternals(fd, args);
+    dumpTracks(fd, args);
+    dumpEffectChains(fd, args);
+}
+
+void AudioFlinger::RecordThread::dumpInternals(int fd, const Vector<String16>& args)
+{
     const size_t SIZE = 256;
     char buffer[SIZE];
     String8 result;
@@ -6339,11 +6390,6 @@
     result.append(buffer);
 
     if (mActiveTrack != 0) {
-        result.append("Active Track:\n");
-        result.append("   Clien Fmt Chn mask   Session Buf  S SRate  Serv     User\n");
-        mActiveTrack->dump(buffer, SIZE);
-        result.append(buffer);
-
         snprintf(buffer, SIZE, "In index: %d\n", mRsmpInIndex);
         result.append(buffer);
         snprintf(buffer, SIZE, "In size: %d\n", mInputBytes);
@@ -6354,15 +6400,41 @@
         result.append(buffer);
         snprintf(buffer, SIZE, "Out sample rate: %d\n", mReqSampleRate);
         result.append(buffer);
-
-
     } else {
-        result.append("No record client\n");
+        result.append("No active record client\n");
     }
+
     write(fd, result.string(), result.size());
 
     dumpBase(fd, args);
-    dumpEffectChains(fd, args);
+}
+
+void AudioFlinger::RecordThread::dumpTracks(int fd, const Vector<String16>& args)
+{
+    const size_t SIZE = 256;
+    char buffer[SIZE];
+    String8 result;
+
+    snprintf(buffer, SIZE, "Input thread %p tracks\n", this);
+    result.append(buffer);
+    RecordTrack::appendDumpHeader(result);
+    for (size_t i = 0; i < mTracks.size(); ++i) {
+        sp<RecordTrack> track = mTracks[i];
+        if (track != 0) {
+            track->dump(buffer, SIZE);
+            result.append(buffer);
+        }
+    }
+
+    if (mActiveTrack != 0) {
+        snprintf(buffer, SIZE, "\nInput thread %p active tracks\n", this);
+        result.append(buffer);
+        RecordTrack::appendDumpHeader(result);
+        mActiveTrack->dump(buffer, SIZE);
+        result.append(buffer);
+
+    }
+    write(fd, result.string(), result.size());
 }
 
 // AudioBufferProvider interface
@@ -6462,11 +6534,14 @@
             } else {
                 newDevice &= ~(value & AUDIO_DEVICE_IN_ALL);
                 // disable AEC and NS if the device is a BT SCO headset supporting those pre processings
-                if (mTrack != NULL) {
+                if (mTracks.size() > 0) {
                     bool suspend = audio_is_bluetooth_sco_device(
                             (audio_devices_t)value) && mAudioFlinger->btNrecIsOff();
-                    setEffectSuspended_l(FX_IID_AEC, suspend, mTrack->sessionId());
-                    setEffectSuspended_l(FX_IID_NS, suspend, mTrack->sessionId());
+                    for (size_t i = 0; i < mTracks.size(); i++) {
+                        sp<RecordTrack> track = mTracks[i];
+                        setEffectSuspended_l(FX_IID_AEC, suspend, track->sessionId());
+                        setEffectSuspended_l(FX_IID_NS, suspend, track->sessionId());
+                    }
                 }
             }
             newDevice |= value;
@@ -6605,17 +6680,28 @@
         result = EFFECT_SESSION;
     }
 
-    if (mTrack != NULL && sessionId == mTrack->sessionId()) {
-        result |= TRACK_SESSION;
+    for (size_t i = 0; i < mTracks.size(); ++i) {
+        if (sessionId == mTracks[i]->sessionId()) {
+            result |= TRACK_SESSION;
+            break;
+        }
     }
 
     return result;
 }
 
-AudioFlinger::RecordThread::RecordTrack* AudioFlinger::RecordThread::track()
+KeyedVector<int, bool> AudioFlinger::RecordThread::sessionIds()
 {
+    KeyedVector<int, bool> ids;
     Mutex::Autolock _l(mLock);
-    return mTrack;
+    for (size_t j = 0; j < mTracks.size(); ++j) {
+        sp<RecordThread::RecordTrack> track = mTracks[j];
+        int sessionId = track->sessionId();
+        if (ids.indexOfKey(sessionId) < 0) {
+            ids.add(sessionId, true);
+        }
+    }
+    return ids;
 }
 
 AudioFlinger::AudioStreamIn* AudioFlinger::RecordThread::clearInput()
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index e629533..2b6d00f 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -1381,11 +1381,14 @@
             virtual status_t    start(AudioSystem::sync_event_t event, int triggerSession);
             virtual void        stop();
 
+                    void        destroy();
+
                     // clear the buffer overflow flag
                     void        clearOverflow() { mOverflow = false; }
                     // set the buffer overflow flag and return previous value
                     bool        setOverflow() { bool tmp = mOverflow; mOverflow = true; return tmp; }
 
+            static  void        appendDumpHeader(String8& result);
                     void        dump(char* buffer, size_t size);
 
         private:
@@ -1409,6 +1412,13 @@
                         audio_devices_t device);
                 virtual     ~RecordThread();
 
+        // no addTrack_l ?
+        void        destroyTrack_l(const sp<RecordTrack>& track);
+        void        removeTrack_l(const sp<RecordTrack>& track);
+
+        void        dumpInternals(int fd, const Vector<String16>& args);
+        void        dumpTracks(int fd, const Vector<String16>& args);
+
         // Thread
         virtual bool        threadLoop();
         virtual status_t    readyToRun();
@@ -1453,7 +1463,11 @@
         virtual status_t addEffectChain_l(const sp<EffectChain>& chain);
         virtual size_t removeEffectChain_l(const sp<EffectChain>& chain);
         virtual uint32_t hasAudioSession(int sessionId);
-                RecordTrack* track();
+
+                // Return the set of unique session IDs across all tracks.
+                // The keys are the session IDs, and the associated values are meaningless.
+                // FIXME replace by Set [and implement Bag/Multiset for other uses].
+                KeyedVector<int, bool> sessionIds();
 
         virtual status_t setSyncEvent(const sp<SyncEvent>& event);
         virtual bool     isValidSyncEvent(const sp<SyncEvent>& event);
@@ -1471,7 +1485,9 @@
                 void inputStandBy();
 
                 AudioStreamIn                       *mInput;
-                RecordTrack*                        mTrack;
+                SortedVector < sp<RecordTrack> >    mTracks;
+                // mActiveTrack has dual roles:  it indicates the current active track, and
+                // is used together with mStartStopCond to indicate start()/stop() progress
                 sp<RecordTrack>                     mActiveTrack;
                 Condition                           mStartStopCond;
                 AudioResampler                      *mResampler;
diff --git a/services/camera/libcameraservice/CameraClient.cpp b/services/camera/libcameraservice/CameraClient.cpp
index 80ccb43..54829ef 100644
--- a/services/camera/libcameraservice/CameraClient.cpp
+++ b/services/camera/libcameraservice/CameraClient.cpp
@@ -445,9 +445,9 @@
     Mutex::Autolock lock(mLock);
     if (checkPidAndHardware() != NO_ERROR) return;
 
-    mCameraService->playSound(CameraService::SOUND_RECORDING);
     disableMsgType(CAMERA_MSG_VIDEO_FRAME);
     mHardware->stopRecording();
+    mCameraService->playSound(CameraService::SOUND_RECORDING);
 
     mPreviewBuffer.clear();
 }