Merge "Fix 1:1 dynamic resampler rate setting"
diff --git a/include/media/AudioSystem.h b/include/media/AudioSystem.h
index fd86737..28fdfd4 100644
--- a/include/media/AudioSystem.h
+++ b/include/media/AudioSystem.h
@@ -118,6 +118,7 @@
 
     static bool routedToA2dpOutput(audio_stream_type_t streamType);
 
+    // return status NO_ERROR implies *buffSize > 0
     static status_t getInputBufferSize(uint32_t sampleRate, audio_format_t format,
         audio_channel_mask_t channelMask, size_t* buffSize);
 
diff --git a/include/media/IMediaHTTPConnection.h b/include/media/IMediaHTTPConnection.h
index e048b64..2a63eb7 100644
--- a/include/media/IMediaHTTPConnection.h
+++ b/include/media/IMediaHTTPConnection.h
@@ -38,6 +38,7 @@
     virtual ssize_t readAt(off64_t offset, void *data, size_t size) = 0;
     virtual off64_t getSize() = 0;
     virtual status_t getMIMEType(String8 *mimeType) = 0;
+    virtual status_t getUri(String8 *uri) = 0;
 
 private:
     DISALLOW_EVIL_CONSTRUCTORS(IMediaHTTPConnection);
diff --git a/include/media/IOMX.h b/include/media/IOMX.h
index 3db2c38..f6f9e7a 100644
--- a/include/media/IOMX.h
+++ b/include/media/IOMX.h
@@ -144,6 +144,7 @@
         INTERNAL_OPTION_REPEAT_PREVIOUS_FRAME_DELAY,  // data is an int64_t
         INTERNAL_OPTION_MAX_TIMESTAMP_GAP, // data is int64_t
         INTERNAL_OPTION_START_TIME, // data is an int64_t
+        INTERNAL_OPTION_TIME_LAPSE, // data is an int64_t[2]
     };
     virtual status_t setInternalOption(
             node_id node,
diff --git a/include/media/nbaio/NBLog.h b/include/media/nbaio/NBLog.h
index 6d59ea7..bcbbc04 100644
--- a/include/media/nbaio/NBLog.h
+++ b/include/media/nbaio/NBLog.h
@@ -25,6 +25,8 @@
 
 namespace android {
 
+class String8;
+
 class NBLog {
 
 public:
@@ -187,6 +189,10 @@
     const Shared* const mShared; // raw pointer to shared memory
     const sp<IMemory> mIMemory; // ref-counted version
     int32_t     mFront;         // index of oldest acknowledged Entry
+    int     mFd;                // file descriptor
+    int     mIndent;            // indentation level
+
+    void    dumpLine(const String8& timestamp, String8& body);
 
     static const size_t kSquashTimestamp = 5; // squash this many or more adjacent timestamps
 };
diff --git a/include/media/stagefright/ACodec.h b/include/media/stagefright/ACodec.h
index e284109..36f2a67 100644
--- a/include/media/stagefright/ACodec.h
+++ b/include/media/stagefright/ACodec.h
@@ -207,6 +207,9 @@
     int64_t mRepeatFrameDelayUs;
     int64_t mMaxPtsGapUs;
 
+    int64_t mTimePerFrameUs;
+    int64_t mTimePerCaptureUs;
+
     bool mCreateInputBuffersSuspended;
 
     status_t setCyclicIntraMacroblockRefresh(const sp<AMessage> &msg, int32_t mode);
diff --git a/include/media/stagefright/CameraSource.h b/include/media/stagefright/CameraSource.h
index 69cfbd0..dd0a106 100644
--- a/include/media/stagefright/CameraSource.h
+++ b/include/media/stagefright/CameraSource.h
@@ -172,7 +172,7 @@
                  const sp<IGraphicBufferProducer>& surface,
                  bool storeMetaDataInVideoBuffers);
 
-    virtual void startCameraRecording();
+    virtual status_t startCameraRecording();
     virtual void releaseRecordingFrame(const sp<IMemory>& frame);
 
     // Returns true if need to skip the current frame.
diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk
index fc4b2a5..e0acae6 100644
--- a/media/libmedia/Android.mk
+++ b/media/libmedia/Android.mk
@@ -60,16 +60,12 @@
 
 LOCAL_SRC_FILES += ../libnbaio/roundup.c
 
-# for <cutils/atomic-inline.h>
-LOCAL_CFLAGS += -DANDROID_SMP=$(if $(findstring true,$(TARGET_CPU_SMP)),1,0)
-LOCAL_SRC_FILES += SingleStateQueue.cpp
-LOCAL_CFLAGS += -DSINGLE_STATE_QUEUE_INSTANTIATIONS='"SingleStateQueueInstantiations.cpp"'
-# Consider a separate a library for SingleStateQueueInstantiations.
-
 LOCAL_SHARED_LIBRARIES := \
 	libui liblog libcutils libutils libbinder libsonivox libicuuc libicui18n libexpat \
         libcamera_client libstagefright_foundation \
-        libgui libdl libaudioutils
+        libgui libdl libaudioutils libnbaio
+
+LOCAL_STATIC_LIBRARIES += libinstantssq
 
 LOCAL_WHOLE_STATIC_LIBRARY := libmedia_helper
 
@@ -84,3 +80,15 @@
     $(call include-path-for, audio-utils)
 
 include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+
+# for <cutils/atomic-inline.h>
+LOCAL_CFLAGS += -DANDROID_SMP=$(if $(findstring true,$(TARGET_CPU_SMP)),1,0)
+LOCAL_SRC_FILES += SingleStateQueue.cpp
+LOCAL_CFLAGS += -DSINGLE_STATE_QUEUE_INSTANTIATIONS='"SingleStateQueueInstantiations.cpp"'
+
+LOCAL_MODULE := libinstantssq
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp
index 6ca499b..ce35c31 100644
--- a/media/libmedia/AudioRecord.cpp
+++ b/media/libmedia/AudioRecord.cpp
@@ -41,30 +41,22 @@
         return BAD_VALUE;
     }
 
-    // default to 0 in case of error
-    *frameCount = 0;
-
-    size_t size = 0;
+    size_t size;
     status_t status = AudioSystem::getInputBufferSize(sampleRate, format, channelMask, &size);
     if (status != NO_ERROR) {
-        ALOGE("AudioSystem could not query the input buffer size; status %d", status);
-        return NO_INIT;
+        ALOGE("AudioSystem could not query the input buffer size for sampleRate %u, format %#x, "
+              "channelMask %#x; status %d", sampleRate, format, channelMask, status);
+        return status;
     }
 
-    if (size == 0) {
+    // We double the size of input buffer for ping pong use of record buffer.
+    // Assumes audio_is_linear_pcm(format)
+    if ((*frameCount = (size * 2) / (popcount(channelMask) * audio_bytes_per_sample(format))) == 0) {
         ALOGE("Unsupported configuration: sampleRate %u, format %#x, channelMask %#x",
             sampleRate, format, channelMask);
         return BAD_VALUE;
     }
 
-    // We double the size of input buffer for ping pong use of record buffer.
-    size <<= 1;
-
-    // Assumes audio_is_linear_pcm(format)
-    uint32_t channelCount = popcount(channelMask);
-    size /= channelCount * audio_bytes_per_sample(format);
-
-    *frameCount = size;
     return NO_ERROR;
 }
 
@@ -133,6 +125,11 @@
         transfer_type transferType,
         audio_input_flags_t flags)
 {
+    ALOGV("set(): inputSource %d, sampleRate %u, format %#x, channelMask %#x, frameCount %d, "
+          "notificationFrames %d, sessionId %d, transferType %d, flags %#x",
+          inputSource, sampleRate, format, channelMask, frameCountInt, notificationFrames,
+          sessionId, transferType, flags);
+
     switch (transferType) {
     case TRANSFER_DEFAULT:
         if (cbf == NULL || threadCanCallJava) {
@@ -163,9 +160,6 @@
     }
     size_t frameCount = frameCountInt;
 
-    ALOGV("set(): sampleRate %u, channelMask %#x, frameCount %u", sampleRate, channelMask,
-            frameCount);
-
     AutoMutex lock(mLock);
 
     if (mAudioRecord != 0) {
@@ -209,15 +203,19 @@
     uint32_t channelCount = popcount(channelMask);
     mChannelCount = channelCount;
 
-    // Assumes audio_is_linear_pcm(format), else sizeof(uint8_t)
-    mFrameSize = channelCount * audio_bytes_per_sample(format);
+    if (audio_is_linear_pcm(format)) {
+        mFrameSize = channelCount * audio_bytes_per_sample(format);
+    } else {
+        mFrameSize = sizeof(uint8_t);
+    }
 
     // validate framecount
-    size_t minFrameCount = 0;
+    size_t minFrameCount;
     status_t status = AudioRecord::getMinFrameCount(&minFrameCount,
             sampleRate, format, channelMask);
     if (status != NO_ERROR) {
-        ALOGE("getMinFrameCount() failed; status %d", status);
+        ALOGE("getMinFrameCount() failed for sampleRate %u, format %#x, channelMask %#x; status %d",
+                sampleRate, format, channelMask, status);
         return status;
     }
     ALOGV("AudioRecord::set() minFrameCount = %d", minFrameCount);
@@ -462,7 +460,9 @@
     audio_io_handle_t input = AudioSystem::getInput(mInputSource, mSampleRate, mFormat,
             mChannelMask, mSessionId);
     if (input == 0) {
-        ALOGE("Could not get audio input for record source %d", mInputSource);
+        ALOGE("Could not get audio input for record source %d, sample rate %u, format %#x, "
+              "channel mask %#x, session %d",
+              mInputSource, mSampleRate, mFormat, mChannelMask, mSessionId);
         return BAD_VALUE;
     }
     {
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index 5c62260..46025c0 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -195,6 +195,11 @@
         int uid,
         pid_t pid)
 {
+    ALOGV("set(): streamType %d, sampleRate %u, format %#x, channelMask %#x, frameCount %d, "
+          "flags #%x, notificationFrames %d, sessionId %d, transferType %d",
+          streamType, sampleRate, format, channelMask, frameCountInt, flags, notificationFrames,
+          sessionId, transferType);
+
     switch (transferType) {
     case TRANSFER_DEFAULT:
         if (sharedBuffer != 0) {
diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp
index 7b15e68..e696323 100644
--- a/media/libmedia/IAudioFlinger.cpp
+++ b/media/libmedia/IAudioFlinger.cpp
@@ -988,7 +988,7 @@
                                                  &latency,
                                                  flags,
                                                  hasOffloadInfo ? &offloadInfo : NULL);
-            ALOGV("OPEN_OUTPUT output, %p", output);
+            ALOGV("OPEN_OUTPUT output, %d", output);
             reply->writeInt32((int32_t) output);
             reply->writeInt32(devices);
             reply->writeInt32(samplingRate);
diff --git a/media/libmedia/IMediaHTTPConnection.cpp b/media/libmedia/IMediaHTTPConnection.cpp
index 622d9cf..22c470a 100644
--- a/media/libmedia/IMediaHTTPConnection.cpp
+++ b/media/libmedia/IMediaHTTPConnection.cpp
@@ -33,6 +33,7 @@
     READ_AT,
     GET_SIZE,
     GET_MIME_TYPE,
+    GET_URI
 };
 
 struct BpMediaHTTPConnection : public BpInterface<IMediaHTTPConnection> {
@@ -147,6 +148,26 @@
         return OK;
     }
 
+    virtual status_t getUri(String8 *uri) {
+        *uri = String8("");
+
+        Parcel data, reply;
+        data.writeInterfaceToken(
+                IMediaHTTPConnection::getInterfaceDescriptor());
+
+        remote()->transact(GET_URI, data, &reply);
+
+        int32_t exceptionCode = reply.readExceptionCode();
+
+        if (exceptionCode) {
+            return UNKNOWN_ERROR;
+        }
+
+        *uri = String8(reply.readString16());
+
+        return OK;
+    }
+
 private:
     sp<IMemory> mMemory;
 };
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index d377acd..845a589 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -690,10 +690,10 @@
             return setParamTimeLapseEnable(timeLapseEnable);
         }
     } else if (key == "time-between-time-lapse-frame-capture") {
-        int64_t timeBetweenTimeLapseFrameCaptureMs;
-        if (safe_strtoi64(value.string(), &timeBetweenTimeLapseFrameCaptureMs)) {
+        int64_t timeBetweenTimeLapseFrameCaptureUs;
+        if (safe_strtoi64(value.string(), &timeBetweenTimeLapseFrameCaptureUs)) {
             return setParamTimeBetweenTimeLapseFrameCapture(
-                    1000LL * timeBetweenTimeLapseFrameCaptureMs);
+                    timeBetweenTimeLapseFrameCaptureUs);
         }
     } else {
         ALOGE("setParameter: failed to find key %s", key.string());
@@ -1436,6 +1436,17 @@
         format->setInt32("stride", mVideoWidth);
         format->setInt32("slice-height", mVideoWidth);
         format->setInt32("color-format", OMX_COLOR_FormatAndroidOpaque);
+
+        // set up time lapse/slow motion for surface source
+        if (mCaptureTimeLapse) {
+            if (mTimeBetweenTimeLapseFrameCaptureUs <= 0) {
+                ALOGE("Invalid mTimeBetweenTimeLapseFrameCaptureUs value: %lld",
+                    mTimeBetweenTimeLapseFrameCaptureUs);
+                return BAD_VALUE;
+            }
+            format->setInt64("time-lapse",
+                    mTimeBetweenTimeLapseFrameCaptureUs);
+        }
     }
 
     format->setInt32("bitrate", mVideoBitRate);
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index d47ac98..a750ad0 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -1006,7 +1006,14 @@
                                     &NuPlayer::performScanSources));
                     }
 
-                    flushDecoder(audio, formatChange);
+                    sp<AMessage> newFormat = mSource->getFormat(audio);
+                    sp<Decoder> &decoder = audio ? mAudioDecoder : mVideoDecoder;
+                    if (formatChange && !decoder->supportsSeamlessFormatChange(newFormat)) {
+                        flushDecoder(audio, /* needShutdown = */ true);
+                    } else {
+                        flushDecoder(audio, /* needShutdown = */ false);
+                        err = OK;
+                    }
                 } else {
                     // This stream is unaffected by the discontinuity
 
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
index 22f699e..2423fd5 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
@@ -67,6 +67,7 @@
     // queue.
     bool needDedicatedLooper = !strncasecmp(mime.c_str(), "video/", 6);
 
+    mFormat = format;
     mCodec = new ACodec;
 
     if (needDedicatedLooper && mCodecLooper == NULL) {
@@ -147,5 +148,65 @@
     }
 }
 
+bool NuPlayer::Decoder::supportsSeamlessAudioFormatChange(const sp<AMessage> &targetFormat) const {
+    if (targetFormat == NULL) {
+        return true;
+    }
+
+    AString mime;
+    if (!targetFormat->findString("mime", &mime)) {
+        return false;
+    }
+
+    if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_AUDIO_AAC)) {
+        // field-by-field comparison
+        const char * keys[] = { "channel-count", "sample-rate", "is-adts" };
+        for (unsigned int i = 0; i < sizeof(keys) / sizeof(keys[0]); i++) {
+            int32_t oldVal, newVal;
+            if (!mFormat->findInt32(keys[i], &oldVal) || !targetFormat->findInt32(keys[i], &newVal)
+                    || oldVal != newVal) {
+                return false;
+            }
+        }
+
+        sp<ABuffer> oldBuf, newBuf;
+        if (mFormat->findBuffer("csd-0", &oldBuf) && targetFormat->findBuffer("csd-0", &newBuf)) {
+            if (oldBuf->size() != newBuf->size()) {
+                return false;
+            }
+            return !memcmp(oldBuf->data(), newBuf->data(), oldBuf->size());
+        }
+    }
+    return false;
+}
+
+bool NuPlayer::Decoder::supportsSeamlessFormatChange(const sp<AMessage> &targetFormat) const {
+    if (mFormat == NULL) {
+        return false;
+    }
+
+    if (targetFormat == NULL) {
+        return true;
+    }
+
+    AString oldMime, newMime;
+    if (!mFormat->findString("mime", &oldMime)
+            || !targetFormat->findString("mime", &newMime)
+            || !(oldMime == newMime)) {
+        return false;
+    }
+
+    bool audio = !strncasecmp(oldMime.c_str(), "audio/", strlen("audio/"));
+    bool seamless;
+    if (audio) {
+        seamless = supportsSeamlessAudioFormatChange(targetFormat);
+    } else {
+        seamless = mCodec != NULL && mCodec->isConfiguredForAdaptivePlayback();
+    }
+
+    ALOGV("%s seamless support for %s", seamless ? "yes" : "no", oldMime.c_str());
+    return seamless;
+}
+
 }  // namespace android
 
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h
index a876148..78ea74a 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h
@@ -36,6 +36,8 @@
     void signalResume();
     void initiateShutdown();
 
+    bool supportsSeamlessFormatChange(const sp<AMessage> &to) const;
+
 protected:
     virtual ~Decoder();
 
@@ -49,6 +51,7 @@
     sp<AMessage> mNotify;
     sp<NativeWindowWrapper> mNativeWindow;
 
+    sp<AMessage> mFormat;
     sp<ACodec> mCodec;
     sp<ALooper> mCodecLooper;
 
@@ -59,6 +62,8 @@
 
     void onFillThisBuffer(const sp<AMessage> &msg);
 
+    bool supportsSeamlessAudioFormatChange(const sp<AMessage> &targetFormat) const;
+
     DISALLOW_EVIL_CONSTRUCTORS(Decoder);
 };
 
diff --git a/media/libnbaio/Android.mk b/media/libnbaio/Android.mk
index 69c75b8..9707c4a 100644
--- a/media/libnbaio/Android.mk
+++ b/media/libnbaio/Android.mk
@@ -31,9 +31,8 @@
     libcommon_time_client \
     libcutils \
     libutils \
-    liblog \
-    libmedia
-# This dependency on libmedia is for SingleStateQueueInstantiations.
-# Consider a separate a library for SingleStateQueueInstantiations.
+    liblog
+
+LOCAL_STATIC_LIBRARIES += libinstantssq
 
 include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libnbaio/NBLog.cpp b/media/libnbaio/NBLog.cpp
index 190824d..96738a7 100644
--- a/media/libnbaio/NBLog.cpp
+++ b/media/libnbaio/NBLog.cpp
@@ -26,6 +26,7 @@
 #include <cutils/atomic.h>
 #include <media/nbaio/NBLog.h>
 #include <utils/Log.h>
+#include <utils/String8.h>
 
 namespace android {
 
@@ -337,25 +338,25 @@
         }
         i -= length + 3;
     }
-    if (i > 0) {
-        lost += i;
-        if (fd >= 0) {
-            fdprintf(fd, "%*swarning: lost %zu bytes worth of events\n", indent, "", lost);
-        } else {
-            ALOGI("%*swarning: lost %u bytes worth of events\n", indent, "", lost);
-        }
+    mFd = fd;
+    mIndent = indent;
+    String8 timestamp, body;
+    lost += i;
+    if (lost > 0) {
+        body.appendFormat("warning: lost %u bytes worth of events", lost);
+        // TODO timestamp empty here, only other choice to wait for the first timestamp event in the
+        //      log to push it out.  Consider keeping the timestamp/body between calls to readAt().
+        dumpLine(timestamp, body);
     }
     size_t width = 1;
     while (maxSec >= 10) {
         ++width;
         maxSec /= 10;
     }
-    char prefix[32];
     if (maxSec >= 0) {
-        snprintf(prefix, sizeof(prefix), "[%*s] ", width + 4, "");
-    } else {
-        prefix[0] = '\0';
+        timestamp.appendFormat("[%*s]", width + 4, "");
     }
+    bool deferredTimestamp = false;
     while (i < avail) {
         event = (Event) copy[i];
         length = copy[i + 1];
@@ -363,11 +364,8 @@
         size_t advance = length + 3;
         switch (event) {
         case EVENT_STRING:
-            if (fd >= 0) {
-                fdprintf(fd, "%*s%s%.*s\n", indent, "", prefix, length, (const char *) data);
-            } else {
-                ALOGI("%*s%s%.*s", indent, "", prefix, length, (const char *) data);
-            } break;
+            body.appendFormat("%.*s", length, (const char *) data);
+            break;
         case EVENT_TIMESTAMP: {
             // already checked that length == sizeof(struct timespec);
             memcpy(&ts, data, sizeof(struct timespec));
@@ -400,45 +398,53 @@
                 prevNsec = tsNext.tv_nsec;
             }
             size_t n = (j - i) / (sizeof(struct timespec) + 3);
+            if (deferredTimestamp) {
+                dumpLine(timestamp, body);
+                deferredTimestamp = false;
+            }
+            timestamp.clear();
             if (n >= kSquashTimestamp) {
-                if (fd >= 0) {
-                    fdprintf(fd, "%*s[%d.%03d to .%.03d by .%.03d to .%.03d]\n", indent, "",
-                            (int) ts.tv_sec, (int) (ts.tv_nsec / 1000000),
-                            (int) ((ts.tv_nsec + deltaTotal) / 1000000),
-                            (int) (deltaMin / 1000000), (int) (deltaMax / 1000000));
-                } else {
-                    ALOGI("%*s[%d.%03d to .%.03d by .%.03d to .%.03d]\n", indent, "",
-                            (int) ts.tv_sec, (int) (ts.tv_nsec / 1000000),
-                            (int) ((ts.tv_nsec + deltaTotal) / 1000000),
-                            (int) (deltaMin / 1000000), (int) (deltaMax / 1000000));
-                }
+                timestamp.appendFormat("[%d.%03d to .%.03d by .%.03d to .%.03d]",
+                        (int) ts.tv_sec, (int) (ts.tv_nsec / 1000000),
+                        (int) ((ts.tv_nsec + deltaTotal) / 1000000),
+                        (int) (deltaMin / 1000000), (int) (deltaMax / 1000000));
                 i = j;
                 advance = 0;
                 break;
             }
-            if (fd >= 0) {
-                fdprintf(fd, "%*s[%d.%03d]\n", indent, "", (int) ts.tv_sec,
-                        (int) (ts.tv_nsec / 1000000));
-            } else {
-                ALOGI("%*s[%d.%03d]", indent, "", (int) ts.tv_sec,
-                        (int) (ts.tv_nsec / 1000000));
-            }
+            timestamp.appendFormat("[%d.%03d]", (int) ts.tv_sec,
+                    (int) (ts.tv_nsec / 1000000));
+            deferredTimestamp = true;
             } break;
         case EVENT_RESERVED:
         default:
-            if (fd >= 0) {
-                fdprintf(fd, "%*s%swarning: unknown event %d\n", indent, "", prefix, event);
-            } else {
-                ALOGI("%*s%swarning: unknown event %d", indent, "", prefix, event);
-            }
+            body.appendFormat("warning: unknown event %d", event);
             break;
         }
         i += advance;
+
+        if (!body.isEmpty()) {
+            dumpLine(timestamp, body);
+            deferredTimestamp = false;
+        }
+    }
+    if (deferredTimestamp) {
+        dumpLine(timestamp, body);
     }
     // FIXME it would be more efficient to put a char mCopy[256] as a member variable of the dumper
     delete[] copy;
 }
 
+void NBLog::Reader::dumpLine(const String8& timestamp, String8& body)
+{
+    if (mFd >= 0) {
+        fdprintf(mFd, "%.*s%s %s\n", mIndent, "", timestamp.string(), body.string());
+    } else {
+        ALOGI("%.*s%s %s", mIndent, "", timestamp.string(), body.string());
+    }
+    body.clear();
+}
+
 bool NBLog::Reader::isIMemory(const sp<IMemory>& iMemory) const
 {
     return iMemory != 0 && mIMemory != 0 && iMemory->pointer() == mIMemory->pointer();
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index ac78d6c..4450d62 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -374,7 +374,9 @@
       mStoreMetaDataInOutputBuffers(false),
       mMetaDataBuffersToSubmit(0),
       mRepeatFrameDelayUs(-1ll),
-      mMaxPtsGapUs(-1l),
+      mMaxPtsGapUs(-1ll),
+      mTimePerCaptureUs(-1ll),
+      mTimePerFrameUs(-1ll),
       mCreateInputBuffersSuspended(false) {
     mUninitializedState = new UninitializedState(this);
     mLoadedState = new LoadedState(this);
@@ -1119,7 +1121,11 @@
         }
 
         if (!msg->findInt64("max-pts-gap-to-encoder", &mMaxPtsGapUs)) {
-            mMaxPtsGapUs = -1l;
+            mMaxPtsGapUs = -1ll;
+        }
+
+        if (!msg->findInt64("time-lapse", &mTimePerCaptureUs)) {
+            mTimePerCaptureUs = -1ll;
         }
 
         if (!msg->findInt32(
@@ -1916,6 +1922,7 @@
             return INVALID_OPERATION;
         }
         frameRate = (float)tmp;
+        mTimePerFrameUs = (int64_t) (1000000.0f / frameRate);
     }
 
     video_def->xFramerate = (OMX_U32)(frameRate * 65536.0f);
@@ -3939,7 +3946,7 @@
         }
     }
 
-    if (err == OK && mCodec->mMaxPtsGapUs > 0l) {
+    if (err == OK && mCodec->mMaxPtsGapUs > 0ll) {
         err = mCodec->mOMX->setInternalOption(
                 mCodec->mNode,
                 kPortIndexInput,
@@ -3951,8 +3958,27 @@
             ALOGE("[%s] Unable to configure max timestamp gap (err %d)",
                     mCodec->mComponentName.c_str(),
                     err);
-          }
-      }
+        }
+    }
+
+    if (err == OK && mCodec->mTimePerCaptureUs > 0ll
+            && mCodec->mTimePerFrameUs > 0ll) {
+        int64_t timeLapse[2];
+        timeLapse[0] = mCodec->mTimePerFrameUs;
+        timeLapse[1] = mCodec->mTimePerCaptureUs;
+        err = mCodec->mOMX->setInternalOption(
+                mCodec->mNode,
+                kPortIndexInput,
+                IOMX::INTERNAL_OPTION_TIME_LAPSE,
+                &timeLapse[0],
+                sizeof(timeLapse));
+
+        if (err != OK) {
+            ALOGE("[%s] Unable to configure time lapse (err %d)",
+                    mCodec->mComponentName.c_str(),
+                    err);
+        }
+    }
 
     if (err == OK && mCodec->mCreateInputBuffersSuspended) {
         bool suspend = true;
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index f3ff792..b31e9e8 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -586,14 +586,15 @@
     }
 }
 
-void CameraSource::startCameraRecording() {
+status_t CameraSource::startCameraRecording() {
     ALOGV("startCameraRecording");
     // Reset the identity to the current thread because media server owns the
     // camera and recording is started by the applications. The applications
     // will connect to the camera in ICameraRecordingProxy::startRecording.
     int64_t token = IPCThreadState::self()->clearCallingIdentity();
+    status_t err;
     if (mNumInputBuffers > 0) {
-        status_t err = mCamera->sendCommand(
+        err = mCamera->sendCommand(
             CAMERA_CMD_SET_VIDEO_BUFFER_COUNT, mNumInputBuffers, 0);
 
         // This could happen for CameraHAL1 clients; thus the failure is
@@ -604,17 +605,25 @@
         }
     }
 
+    err = OK;
     if (mCameraFlags & FLAGS_HOT_CAMERA) {
         mCamera->unlock();
         mCamera.clear();
-        CHECK_EQ((status_t)OK,
-            mCameraRecordingProxy->startRecording(new ProxyListener(this)));
+        if ((err = mCameraRecordingProxy->startRecording(
+                new ProxyListener(this))) != OK) {
+            ALOGE("Failed to start recording, received error: %s (%d)",
+                    strerror(-err), err);
+        }
     } else {
         mCamera->setListener(new CameraSourceListener(this));
         mCamera->startRecording();
-        CHECK(mCamera->recordingEnabled());
+        if (!mCamera->recordingEnabled()) {
+            err = -EINVAL;
+            ALOGE("Failed to start recording");
+        }
     }
     IPCThreadState::self()->restoreCallingIdentity(token);
+    return err;
 }
 
 status_t CameraSource::start(MetaData *meta) {
@@ -646,10 +655,12 @@
         }
     }
 
-    startCameraRecording();
+    status_t err;
+    if ((err = startCameraRecording()) == OK) {
+        mStarted = true;
+    }
 
-    mStarted = true;
-    return OK;
+    return err;
 }
 
 void CameraSource::stopCameraRecording() {
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 4756b3e..2a3fa04 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -488,12 +488,12 @@
             break;
         }
         uint32_t chunk_type = ntohl(hdr[1]);
-        if (chunk_type == FOURCC('s', 'i', 'd', 'x')) {
-            // parse the sidx box too
-            continue;
-        } else if (chunk_type == FOURCC('m', 'o', 'o', 'f')) {
+        if (chunk_type == FOURCC('m', 'o', 'o', 'f')) {
             // store the offset of the first segment
             mMoofOffset = offset;
+        } else if (chunk_type != FOURCC('m', 'd', 'a', 't')) {
+            // keep parsing until we get to the data
+            continue;
         }
         break;
     }
@@ -913,6 +913,8 @@
 
         case FOURCC('e', 'l', 's', 't'):
         {
+            *offset += chunk_size;
+
             // See 14496-12 8.6.6
             uint8_t version;
             if (mDataSource->readAt(data_offset, &version, 1) < 1) {
@@ -975,12 +977,13 @@
                     mLastTrack->meta->setInt32(kKeyEncoderPadding, paddingsamples);
                 }
             }
-            *offset += chunk_size;
             break;
         }
 
         case FOURCC('f', 'r', 'm', 'a'):
         {
+            *offset += chunk_size;
+
             uint32_t original_fourcc;
             if (mDataSource->readAt(data_offset, &original_fourcc, 4) < 4) {
                 return ERROR_IO;
@@ -994,12 +997,13 @@
                 mLastTrack->meta->setInt32(kKeyChannelCount, num_channels);
                 mLastTrack->meta->setInt32(kKeySampleRate, sample_rate);
             }
-            *offset += chunk_size;
             break;
         }
 
         case FOURCC('t', 'e', 'n', 'c'):
         {
+            *offset += chunk_size;
+
             if (chunk_size < 32) {
                 return ERROR_MALFORMED;
             }
@@ -1044,23 +1048,25 @@
             mLastTrack->meta->setInt32(kKeyCryptoMode, defaultAlgorithmId);
             mLastTrack->meta->setInt32(kKeyCryptoDefaultIVSize, defaultIVSize);
             mLastTrack->meta->setData(kKeyCryptoKey, 'tenc', defaultKeyId, 16);
-            *offset += chunk_size;
             break;
         }
 
         case FOURCC('t', 'k', 'h', 'd'):
         {
+            *offset += chunk_size;
+
             status_t err;
             if ((err = parseTrackHeader(data_offset, chunk_data_size)) != OK) {
                 return err;
             }
 
-            *offset += chunk_size;
             break;
         }
 
         case FOURCC('p', 's', 's', 'h'):
         {
+            *offset += chunk_size;
+
             PsshInfo pssh;
 
             if (mDataSource->readAt(data_offset + 4, &pssh.uuid, 16) < 16) {
@@ -1086,12 +1092,13 @@
             }
             mPssh.push_back(pssh);
 
-            *offset += chunk_size;
             break;
         }
 
         case FOURCC('m', 'd', 'h', 'd'):
         {
+            *offset += chunk_size;
+
             if (chunk_data_size < 4) {
                 return ERROR_MALFORMED;
             }
@@ -1172,7 +1179,6 @@
             mLastTrack->meta->setCString(
                     kKeyMediaLanguage, lang_code);
 
-            *offset += chunk_size;
             break;
         }
 
@@ -1339,11 +1345,12 @@
                 mLastTrack->sampleTable->setChunkOffsetParams(
                         chunk_type, data_offset, chunk_data_size);
 
+            *offset += chunk_size;
+
             if (err != OK) {
                 return err;
             }
 
-            *offset += chunk_size;
             break;
         }
 
@@ -1353,11 +1360,12 @@
                 mLastTrack->sampleTable->setSampleToChunkParams(
                         data_offset, chunk_data_size);
 
+            *offset += chunk_size;
+
             if (err != OK) {
                 return err;
             }
 
-            *offset += chunk_size;
             break;
         }
 
@@ -1368,6 +1376,8 @@
                 mLastTrack->sampleTable->setSampleSizeParams(
                         chunk_type, data_offset, chunk_data_size);
 
+            *offset += chunk_size;
+
             if (err != OK) {
                 return err;
             }
@@ -1408,7 +1418,6 @@
                 }
                 mLastTrack->meta->setInt32(kKeyMaxInputSize, max_size);
             }
-            *offset += chunk_size;
 
             // NOTE: setting another piece of metadata invalidates any pointers (such as the
             // mimetype) previously obtained, so don't cache them.
@@ -1432,6 +1441,8 @@
 
         case FOURCC('s', 't', 't', 's'):
         {
+            *offset += chunk_size;
+
             status_t err =
                 mLastTrack->sampleTable->setTimeToSampleParams(
                         data_offset, chunk_data_size);
@@ -1440,12 +1451,13 @@
                 return err;
             }
 
-            *offset += chunk_size;
             break;
         }
 
         case FOURCC('c', 't', 't', 's'):
         {
+            *offset += chunk_size;
+
             status_t err =
                 mLastTrack->sampleTable->setCompositionTimeToSampleParams(
                         data_offset, chunk_data_size);
@@ -1454,12 +1466,13 @@
                 return err;
             }
 
-            *offset += chunk_size;
             break;
         }
 
         case FOURCC('s', 't', 's', 's'):
         {
+            *offset += chunk_size;
+
             status_t err =
                 mLastTrack->sampleTable->setSyncSampleParams(
                         data_offset, chunk_data_size);
@@ -1468,13 +1481,14 @@
                 return err;
             }
 
-            *offset += chunk_size;
             break;
         }
 
         // @xyz
         case FOURCC('\xA9', 'x', 'y', 'z'):
         {
+            *offset += chunk_size;
+
             // Best case the total data length inside "@xyz" box
             // would be 8, for instance "@xyz" + "\x00\x04\x15\xc7" + "0+0/",
             // where "\x00\x04" is the text string length with value = 4,
@@ -1503,12 +1517,13 @@
 
             buffer[location_length] = '\0';
             mFileMetaData->setCString(kKeyLocation, buffer);
-            *offset += chunk_size;
             break;
         }
 
         case FOURCC('e', 's', 'd', 's'):
         {
+            *offset += chunk_size;
+
             if (chunk_data_size < 4) {
                 return ERROR_MALFORMED;
             }
@@ -1546,12 +1561,13 @@
                 }
             }
 
-            *offset += chunk_size;
             break;
         }
 
         case FOURCC('a', 'v', 'c', 'C'):
         {
+            *offset += chunk_size;
+
             sp<ABuffer> buffer = new ABuffer(chunk_data_size);
 
             if (mDataSource->readAt(
@@ -1562,12 +1578,12 @@
             mLastTrack->meta->setData(
                     kKeyAVCC, kTypeAVCC, buffer->data(), chunk_data_size);
 
-            *offset += chunk_size;
             break;
         }
 
         case FOURCC('d', '2', '6', '3'):
         {
+            *offset += chunk_size;
             /*
              * d263 contains a fixed 7 bytes part:
              *   vendor - 4 bytes
@@ -1593,7 +1609,6 @@
 
             mLastTrack->meta->setData(kKeyD263, kTypeD263, buffer, chunk_data_size);
 
-            *offset += chunk_size;
             break;
         }
 
@@ -1601,11 +1616,13 @@
         {
             uint8_t buffer[4];
             if (chunk_data_size < (off64_t)sizeof(buffer)) {
+                *offset += chunk_size;
                 return ERROR_MALFORMED;
             }
 
             if (mDataSource->readAt(
                         data_offset, buffer, 4) < 4) {
+                *offset += chunk_size;
                 return ERROR_IO;
             }
 
@@ -1639,6 +1656,8 @@
         case FOURCC('n', 'a', 'm', 'e'):
         case FOURCC('d', 'a', 't', 'a'):
         {
+            *offset += chunk_size;
+
             if (mPath.size() == 6 && underMetaDataPath(mPath)) {
                 status_t err = parseITunesMetaData(data_offset, chunk_data_size);
 
@@ -1647,12 +1666,13 @@
                 }
             }
 
-            *offset += chunk_size;
             break;
         }
 
         case FOURCC('m', 'v', 'h', 'd'):
         {
+            *offset += chunk_size;
+
             if (chunk_data_size < 24) {
                 return ERROR_MALFORMED;
             }
@@ -1680,7 +1700,6 @@
 
             mFileMetaData->setCString(kKeyDate, s.string());
 
-            *offset += chunk_size;
             break;
         }
 
@@ -1701,6 +1720,8 @@
 
         case FOURCC('h', 'd', 'l', 'r'):
         {
+            *offset += chunk_size;
+
             uint32_t buffer;
             if (mDataSource->readAt(
                         data_offset + 8, &buffer, 4) < 4) {
@@ -1715,7 +1736,6 @@
                 mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_TEXT_3GPP);
             }
 
-            *offset += chunk_size;
             break;
         }
 
@@ -1740,6 +1760,8 @@
                 delete[] buffer;
                 buffer = NULL;
 
+                // advance read pointer so we don't end up reading this again
+                *offset += chunk_size;
                 return ERROR_IO;
             }
 
@@ -1754,6 +1776,8 @@
 
         case FOURCC('c', 'o', 'v', 'r'):
         {
+            *offset += chunk_size;
+
             if (mFileMetaData != NULL) {
                 ALOGV("chunk_data_size = %lld and data_offset = %lld",
                         chunk_data_size, data_offset);
@@ -1768,7 +1792,6 @@
                     buffer->data() + kSkipBytesOfDataBox, chunk_data_size - kSkipBytesOfDataBox);
             }
 
-            *offset += chunk_size;
             break;
         }
 
@@ -1779,25 +1802,27 @@
         case FOURCC('a', 'l', 'b', 'm'):
         case FOURCC('y', 'r', 'r', 'c'):
         {
+            *offset += chunk_size;
+
             status_t err = parse3GPPMetaData(data_offset, chunk_data_size, depth);
 
             if (err != OK) {
                 return err;
             }
 
-            *offset += chunk_size;
             break;
         }
 
         case FOURCC('I', 'D', '3', '2'):
         {
+            *offset += chunk_size;
+
             if (chunk_data_size < 6) {
                 return ERROR_MALFORMED;
             }
 
             parseID3v2MetaData(data_offset + 6);
 
-            *offset += chunk_size;
             break;
         }
 
@@ -1921,9 +1946,10 @@
             ALOGW("sub-sidx boxes not supported yet");
         }
         bool sap = d3 & 0x80000000;
-        bool saptype = d3 >> 28;
-        if (!sap || saptype > 2) {
-            ALOGW("not a stream access point, or unsupported type");
+        uint32_t saptype = (d3 >> 28) & 7;
+        if (!sap || (saptype != 1 && saptype != 2)) {
+            // type 1 and 2 are sync samples
+            ALOGW("not a stream access point, or unsupported type: %08x", d3);
         }
         total_duration += d2;
         offset += 12;
@@ -2899,9 +2925,20 @@
                 }
             }
             if (chunk_type == FOURCC('m', 'o', 'o', 'f')) {
-                // *offset points to the mdat box following this moof
-                parseChunk(offset); // doesn't actually parse it, just updates offset
-                mNextMoofOffset = *offset;
+                // *offset points to the box following this moof. Find the next moof from there.
+
+                while (true) {
+                    if (mDataSource->readAt(*offset, hdr, 8) < 8) {
+                        return ERROR_END_OF_STREAM;
+                    }
+                    chunk_size = ntohl(hdr[0]);
+                    chunk_type = ntohl(hdr[1]);
+                    if (chunk_type == FOURCC('m', 'o', 'o', 'f')) {
+                        mNextMoofOffset = *offset;
+                        break;
+                    }
+                    *offset += chunk_size;
+                }
             }
             break;
         }
@@ -3706,7 +3743,7 @@
                 const SidxEntry *se = &mSegments[i];
                 if (totalTime + se->mDurationUs > seekTimeUs) {
                     // The requested time is somewhere in this segment
-                    if ((mode == ReadOptions::SEEK_NEXT_SYNC) ||
+                    if ((mode == ReadOptions::SEEK_NEXT_SYNC && seekTimeUs > totalTime) ||
                         (mode == ReadOptions::SEEK_CLOSEST_SYNC &&
                         (seekTimeUs - totalTime) > (totalTime + se->mDurationUs - seekTimeUs))) {
                         // requested next sync, or closest sync and it was closer to the end of
@@ -3719,11 +3756,19 @@
                 totalTime += se->mDurationUs;
                 totalOffset += se->mSize;
             }
-        mCurrentMoofOffset = totalOffset;
-        mCurrentSamples.clear();
-        mCurrentSampleIndex = 0;
-        parseChunk(&totalOffset);
-        mCurrentTime = totalTime * mTimescale / 1000000ll;
+            mCurrentMoofOffset = totalOffset;
+            mCurrentSamples.clear();
+            mCurrentSampleIndex = 0;
+            parseChunk(&totalOffset);
+            mCurrentTime = totalTime * mTimescale / 1000000ll;
+        } else {
+            // without sidx boxes, we can only seek to 0
+            mCurrentMoofOffset = mFirstMoofOffset;
+            mCurrentSamples.clear();
+            mCurrentSampleIndex = 0;
+            off64_t tmp = mCurrentMoofOffset;
+            parseChunk(&tmp);
+            mCurrentTime = 0;
         }
 
         if (mBuffer != NULL) {
@@ -3743,16 +3788,18 @@
         newBuffer = true;
 
         if (mCurrentSampleIndex >= mCurrentSamples.size()) {
-            // move to next fragment
-            Sample lastSample = mCurrentSamples[mCurrentSamples.size() - 1];
-            off64_t nextMoof = mNextMoofOffset; // lastSample.offset + lastSample.size;
+            // move to next fragment if there is one
+            if (mNextMoofOffset <= mCurrentMoofOffset) {
+                return ERROR_END_OF_STREAM;
+            }
+            off64_t nextMoof = mNextMoofOffset;
             mCurrentMoofOffset = nextMoof;
             mCurrentSamples.clear();
             mCurrentSampleIndex = 0;
             parseChunk(&nextMoof);
-                if (mCurrentSampleIndex >= mCurrentSamples.size()) {
-                    return ERROR_END_OF_STREAM;
-                }
+            if (mCurrentSampleIndex >= mCurrentSamples.size()) {
+                return ERROR_END_OF_STREAM;
+            }
         }
 
         const Sample *smpl = &mCurrentSamples[mCurrentSampleIndex];
diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp
index 216a329..451e907 100644
--- a/media/libstagefright/Utils.cpp
+++ b/media/libstagefright/Utils.cpp
@@ -452,6 +452,11 @@
         }
     }
 
+    int32_t timeScale;
+    if (msg->findInt32("time-scale", &timeScale)) {
+        meta->setInt32(kKeyTimeScale, timeScale);
+    }
+
     // XXX TODO add whatever other keys there are
 
 #if 0
diff --git a/media/libstagefright/http/MediaHTTP.cpp b/media/libstagefright/http/MediaHTTP.cpp
index 157d967..2d29913 100644
--- a/media/libstagefright/http/MediaHTTP.cpp
+++ b/media/libstagefright/http/MediaHTTP.cpp
@@ -171,6 +171,10 @@
 }
 
 String8 MediaHTTP::getUri() {
+    String8 uri;
+    if (OK == mHTTPConnection->getUri(&uri)) {
+        return uri;
+    }
     return String8(mLastURI.c_str());
 }
 
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index f0a1c36..95779c4 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -61,14 +61,14 @@
       mRealTimeBaseUs(0ll),
       mReconfigurationInProgress(false),
       mDisconnectReplyID(0) {
-    mPacketSources.add(
-            STREAMTYPE_AUDIO, new AnotherPacketSource(NULL /* meta */));
 
-    mPacketSources.add(
-            STREAMTYPE_VIDEO, new AnotherPacketSource(NULL /* meta */));
+    mStreams[kAudioIndex] = StreamItem("audio");
+    mStreams[kVideoIndex] = StreamItem("video");
+    mStreams[kSubtitleIndex] = StreamItem("subtitle");
 
-    mPacketSources.add(
-            STREAMTYPE_SUBTITLES, new AnotherPacketSource(NULL /* meta */));
+    for (size_t i = 0; i < kMaxStreams; ++i) {
+        mPacketSources.add(indexToType(i), new AnotherPacketSource(NULL /* meta */));
+    }
 }
 
 LiveSession::~LiveSession() {
@@ -369,6 +369,12 @@
     return 1;
 }
 
+// static
+LiveSession::StreamType LiveSession::indexToType(int idx) {
+    CHECK(idx >= 0 && idx < kMaxStreams);
+    return (StreamType)(1 << idx);
+}
+
 void LiveSession::onConnect(const sp<AMessage> &msg) {
     AString url;
     CHECK(msg->findString("url", &url));
@@ -527,7 +533,8 @@
         const char *url, sp<ABuffer> *out,
         int64_t range_offset, int64_t range_length,
         uint32_t block_size, /* download block size */
-        sp<DataSource> *source /* to return and reuse source */) {
+        sp<DataSource> *source, /* to return and reuse source */
+        String8 *actualUrl) {
     off64_t size;
     sp<DataSource> temp_source;
     if (source == NULL) {
@@ -623,6 +630,12 @@
     }
 
     *out = buffer;
+    if (actualUrl != NULL) {
+        *actualUrl = (*source)->getUri();
+        if (actualUrl->isEmpty()) {
+            *actualUrl = url;
+        }
+    }
 
     return OK;
 }
@@ -634,7 +647,8 @@
     *unchanged = false;
 
     sp<ABuffer> buffer;
-    status_t err = fetchFile(url, &buffer);
+    String8 actualUrl;
+    status_t err = fetchFile(url, &buffer, 0, -1, 0, NULL, &actualUrl);
 
     if (err != OK) {
         return NULL;
@@ -665,7 +679,7 @@
 #endif
 
     sp<M3UParser> playlist =
-        new M3UParser(url, buffer->data(), buffer->size());
+        new M3UParser(actualUrl.string(), buffer->data(), buffer->size());
 
     if (playlist->initCheck() != OK) {
         ALOGE("failed to parse .m3u8 playlist");
@@ -850,19 +864,11 @@
 
     uint32_t streamMask = 0;
 
-    AString audioURI;
-    if (mPlaylist->getAudioURI(item.mPlaylistIndex, &audioURI)) {
-        streamMask |= STREAMTYPE_AUDIO;
-    }
-
-    AString videoURI;
-    if (mPlaylist->getVideoURI(item.mPlaylistIndex, &videoURI)) {
-        streamMask |= STREAMTYPE_VIDEO;
-    }
-
-    AString subtitleURI;
-    if (mPlaylist->getSubtitleURI(item.mPlaylistIndex, &subtitleURI)) {
-        streamMask |= STREAMTYPE_SUBTITLES;
+    AString URIs[kMaxStreams];
+    for (size_t i = 0; i < kMaxStreams; ++i) {
+        if (mPlaylist->getTypeURI(item.mPlaylistIndex, mStreams[i].mType, &URIs[i])) {
+            streamMask |= indexToType(i);
+        }
     }
 
     // Step 1, stop and discard fetchers that are no longer needed.
@@ -874,10 +880,10 @@
 
         // If we're seeking all current fetchers are discarded.
         if (timeUs < 0ll) {
-            if (((streamMask & STREAMTYPE_AUDIO) && uri == audioURI)
-                    || ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI)
-                    || ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI)) {
-                discardFetcher = false;
+            for (size_t j = 0; j < kMaxStreams; ++j) {
+                if ((streamMask & indexToType(j)) && uri == URIs[j]) {
+                    discardFetcher = false;
+                }
             }
         }
 
@@ -891,14 +897,10 @@
     sp<AMessage> msg = new AMessage(kWhatChangeConfiguration2, id());
     msg->setInt32("streamMask", streamMask);
     msg->setInt64("timeUs", timeUs);
-    if (streamMask & STREAMTYPE_AUDIO) {
-        msg->setString("audioURI", audioURI.c_str());
-    }
-    if (streamMask & STREAMTYPE_VIDEO) {
-        msg->setString("videoURI", videoURI.c_str());
-    }
-    if (streamMask & STREAMTYPE_SUBTITLES) {
-        msg->setString("subtitleURI", subtitleURI.c_str());
+    for (size_t i = 0; i < kMaxStreams; ++i) {
+        if (streamMask & indexToType(i)) {
+            msg->setString(mStreams[i].uriKey().c_str(), URIs[i].c_str());
+        }
     }
 
     // Every time a fetcher acknowledges the stopAsync or pauseAsync request
@@ -929,18 +931,13 @@
     uint32_t streamMask;
     CHECK(msg->findInt32("streamMask", (int32_t *)&streamMask));
 
-    AString audioURI, videoURI, subtitleURI;
-    if (streamMask & STREAMTYPE_AUDIO) {
-        CHECK(msg->findString("audioURI", &audioURI));
-        ALOGV("audioURI = '%s'", audioURI.c_str());
-    }
-    if (streamMask & STREAMTYPE_VIDEO) {
-        CHECK(msg->findString("videoURI", &videoURI));
-        ALOGV("videoURI = '%s'", videoURI.c_str());
-    }
-    if (streamMask & STREAMTYPE_SUBTITLES) {
-        CHECK(msg->findString("subtitleURI", &subtitleURI));
-        ALOGV("subtitleURI = '%s'", subtitleURI.c_str());
+    AString URIs[kMaxStreams];
+    for (size_t i = 0; i < kMaxStreams; ++i) {
+        if (streamMask & indexToType(i)) {
+            const AString &uriKey = mStreams[i].uriKey();
+            CHECK(msg->findString(uriKey.c_str(), &URIs[i]));
+            ALOGV("%s = '%s'", uriKey.c_str(), URIs[i].c_str());
+        }
     }
 
     // Determine which decoders to shutdown on the player side,
@@ -950,15 +947,12 @@
     // 2) its streamtype was already active and still is but the URI
     //    has changed.
     uint32_t changedMask = 0;
-    if (((mStreamMask & streamMask & STREAMTYPE_AUDIO)
-                && !(audioURI == mAudioURI))
-        || (mStreamMask & ~streamMask & STREAMTYPE_AUDIO)) {
-        changedMask |= STREAMTYPE_AUDIO;
-    }
-    if (((mStreamMask & streamMask & STREAMTYPE_VIDEO)
-                && !(videoURI == mVideoURI))
-        || (mStreamMask & ~streamMask & STREAMTYPE_VIDEO)) {
-        changedMask |= STREAMTYPE_VIDEO;
+    for (size_t i = 0; i < kMaxStreams && i != kSubtitleIndex; ++i) {
+        if (((mStreamMask & streamMask & indexToType(i))
+                && !(URIs[i] == mStreams[i].mUri))
+                || (mStreamMask & ~streamMask & indexToType(i))) {
+            changedMask |= indexToType(i);
+        }
     }
 
     if (changedMask == 0) {
@@ -990,15 +984,10 @@
     uint32_t streamMask;
     CHECK(msg->findInt32("streamMask", (int32_t *)&streamMask));
 
-    AString audioURI, videoURI, subtitleURI;
-    if (streamMask & STREAMTYPE_AUDIO) {
-        CHECK(msg->findString("audioURI", &audioURI));
-    }
-    if (streamMask & STREAMTYPE_VIDEO) {
-        CHECK(msg->findString("videoURI", &videoURI));
-    }
-    if (streamMask & STREAMTYPE_SUBTITLES) {
-        CHECK(msg->findString("subtitleURI", &subtitleURI));
+    for (size_t i = 0; i < kMaxStreams; ++i) {
+        if (streamMask & indexToType(i)) {
+            CHECK(msg->findString(mStreams[i].uriKey().c_str(), &mStreams[i].mUri));
+        }
     }
 
     int64_t timeUs;
@@ -1010,9 +999,6 @@
     mRealTimeBaseUs = ALooper::GetNowUs() - timeUs;
 
     mStreamMask = streamMask;
-    mAudioURI = audioURI;
-    mVideoURI = videoURI;
-    mSubtitleURI = subtitleURI;
 
     // Resume all existing fetchers and assign them packet sources.
     for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
@@ -1020,22 +1006,12 @@
 
         uint32_t resumeMask = 0;
 
-        sp<AnotherPacketSource> audioSource;
-        if ((streamMask & STREAMTYPE_AUDIO) && uri == audioURI) {
-            audioSource = mPacketSources.valueFor(STREAMTYPE_AUDIO);
-            resumeMask |= STREAMTYPE_AUDIO;
-        }
-
-        sp<AnotherPacketSource> videoSource;
-        if ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI) {
-            videoSource = mPacketSources.valueFor(STREAMTYPE_VIDEO);
-            resumeMask |= STREAMTYPE_VIDEO;
-        }
-
-        sp<AnotherPacketSource> subtitleSource;
-        if ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI) {
-            subtitleSource = mPacketSources.valueFor(STREAMTYPE_SUBTITLES);
-            resumeMask |= STREAMTYPE_SUBTITLES;
+        sp<AnotherPacketSource> sources[kMaxStreams];
+        for (size_t j = 0; j < kMaxStreams; ++j) {
+            if ((streamMask & indexToType(j)) && uri == mStreams[j].mUri) {
+                sources[j] = mPacketSources.valueFor(indexToType(j));
+                resumeMask |= indexToType(j);
+            }
         }
 
         CHECK_NE(resumeMask, 0u);
@@ -1045,7 +1021,7 @@
         streamMask &= ~resumeMask;
 
         mFetcherInfos.valueAt(i).mFetcher->startAsync(
-                audioSource, videoSource, subtitleSource);
+                sources[kAudioIndex], sources[kVideoIndex], sources[kSubtitleIndex]);
     }
 
     // streamMask now only contains the types that need a new fetcher created.
@@ -1054,52 +1030,33 @@
         ALOGV("creating new fetchers for mask 0x%08x", streamMask);
     }
 
-    while (streamMask != 0) {
-        StreamType streamType = (StreamType)(streamMask & ~(streamMask - 1));
+    for (size_t i = 0; i < kMaxStreams; i++) {
+        if (!(indexToType(i) & streamMask)) {
+            continue;
+        }
 
         AString uri;
-        switch (streamType) {
-            case STREAMTYPE_AUDIO:
-                uri = audioURI;
-                break;
-            case STREAMTYPE_VIDEO:
-                uri = videoURI;
-                break;
-            case STREAMTYPE_SUBTITLES:
-                uri = subtitleURI;
-                break;
-            default:
-                TRESPASS();
-        }
+        uri = mStreams[i].mUri;
 
         sp<PlaylistFetcher> fetcher = addFetcher(uri.c_str());
         CHECK(fetcher != NULL);
 
-        sp<AnotherPacketSource> audioSource;
-        if ((streamMask & STREAMTYPE_AUDIO) && uri == audioURI) {
-            audioSource = mPacketSources.valueFor(STREAMTYPE_AUDIO);
-            audioSource->clear();
+        sp<AnotherPacketSource> sources[kMaxStreams];
+        // TRICKY: looping from i as earlier streams are already removed from streamMask
+        for (size_t j = i; j < kMaxStreams; ++j) {
+            if ((streamMask & indexToType(j)) && uri == mStreams[j].mUri) {
+                sources[j] = mPacketSources.valueFor(indexToType(j));
+                sources[j]->clear();
 
-            streamMask &= ~STREAMTYPE_AUDIO;
+                streamMask &= ~indexToType(j);
+            }
         }
 
-        sp<AnotherPacketSource> videoSource;
-        if ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI) {
-            videoSource = mPacketSources.valueFor(STREAMTYPE_VIDEO);
-            videoSource->clear();
-
-            streamMask &= ~STREAMTYPE_VIDEO;
-        }
-
-        sp<AnotherPacketSource> subtitleSource;
-        if ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI) {
-            subtitleSource = mPacketSources.valueFor(STREAMTYPE_SUBTITLES);
-            subtitleSource->clear();
-
-            streamMask &= ~STREAMTYPE_SUBTITLES;
-        }
-
-        fetcher->startAsync(audioSource, videoSource, subtitleSource, timeUs);
+        fetcher->startAsync(
+                sources[kAudioIndex],
+                sources[kVideoIndex],
+                sources[kSubtitleIndex],
+                timeUs);
     }
 
     // All fetchers have now been started, the configuration change
diff --git a/media/libstagefright/httplive/LiveSession.h b/media/libstagefright/httplive/LiveSession.h
index 00569be..c4d125c 100644
--- a/media/libstagefright/httplive/LiveSession.h
+++ b/media/libstagefright/httplive/LiveSession.h
@@ -44,10 +44,17 @@
             uint32_t flags,
             const sp<IMediaHTTPService> &httpService);
 
+    enum StreamIndex {
+        kAudioIndex    = 0,
+        kVideoIndex    = 1,
+        kSubtitleIndex = 2,
+        kMaxStreams    = 3,
+    };
+
     enum StreamType {
-        STREAMTYPE_AUDIO        = 1,
-        STREAMTYPE_VIDEO        = 2,
-        STREAMTYPE_SUBTITLES    = 4,
+        STREAMTYPE_AUDIO        = 1 << kAudioIndex,
+        STREAMTYPE_VIDEO        = 1 << kVideoIndex,
+        STREAMTYPE_SUBTITLES    = 1 << kSubtitleIndex,
     };
     status_t dequeueAccessUnit(StreamType stream, sp<ABuffer> *accessUnit);
 
@@ -107,6 +114,19 @@
         bool mIsPrepared;
     };
 
+    struct StreamItem {
+        const char *mType;
+        AString mUri;
+        StreamItem() : mType("") {}
+        StreamItem(const char *type) : mType(type) {}
+        AString uriKey() {
+            AString key(mType);
+            key.append("URI");
+            return key;
+        }
+    };
+    StreamItem mStreams[kMaxStreams];
+
     sp<AMessage> mNotify;
     uint32_t mFlags;
     sp<IMediaHTTPService> mHTTPService;
@@ -124,7 +144,6 @@
     sp<M3UParser> mPlaylist;
 
     KeyedVector<AString, FetcherInfo> mFetcherInfos;
-    AString mAudioURI, mVideoURI, mSubtitleURI;
     uint32_t mStreamMask;
 
     KeyedVector<StreamType, sp<AnotherPacketSource> > mPacketSources;
@@ -164,7 +183,8 @@
             /* download block size */
             uint32_t block_size = 0,
             /* reuse DataSource if doing partial fetch */
-            sp<DataSource> *source = NULL);
+            sp<DataSource> *source = NULL,
+            String8 *actualUrl = NULL);
 
     sp<M3UParser> fetchPlaylist(
             const char *url, uint8_t *curPlaylistHash, bool *unchanged);
@@ -172,6 +192,7 @@
     size_t getBandwidthIndex();
 
     static int SortByBandwidth(const BandwidthItem *, const BandwidthItem *);
+    static StreamType indexToType(int idx);
 
     void changeConfiguration(
             int64_t timeUs, size_t bandwidthIndex, bool pickTrack = false);
diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp
index 39d80fc..587a6d5 100644
--- a/media/libstagefright/httplive/M3UParser.cpp
+++ b/media/libstagefright/httplive/M3UParser.cpp
@@ -24,6 +24,7 @@
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
 #include <media/mediaplayer.h>
 
 namespace android {
@@ -352,9 +353,27 @@
     if (!meta->findString(key, &groupID)) {
         *uri = mItems.itemAt(index).mURI;
 
-        // Assume media without any more specific attribute contains
-        // audio and video, but no subtitles.
-        return !strcmp("audio", key) || !strcmp("video", key);
+        AString codecs;
+        if (!meta->findString("codecs", &codecs)) {
+            // Assume media without any more specific attribute contains
+            // audio and video, but no subtitles.
+            return !strcmp("audio", key) || !strcmp("video", key);
+        } else {
+            // Split the comma separated list of codecs.
+            size_t offset = 0;
+            ssize_t commaPos = -1;
+            codecs.append(',');
+            while ((commaPos = codecs.find(",", offset)) >= 0) {
+                AString codec(codecs, offset, commaPos - offset);
+                // return true only if a codec of type `key` ("audio"/"video")
+                // is found.
+                if (codecIsType(codec, key)) {
+                    return true;
+                }
+                offset = commaPos + 1;
+            }
+            return false;
+        }
     }
 
     sp<MediaGroup> group = mMediaGroups.valueFor(groupID);
@@ -369,18 +388,6 @@
     return true;
 }
 
-bool M3UParser::getAudioURI(size_t index, AString *uri) const {
-    return getTypeURI(index, "audio", uri);
-}
-
-bool M3UParser::getVideoURI(size_t index, AString *uri) const {
-    return getTypeURI(index, "video", uri);
-}
-
-bool M3UParser::getSubtitleURI(size_t index, AString *uri) const {
-    return getTypeURI(index, "subtitles", uri);
-}
-
 static bool MakeURL(const char *baseURL, const char *url, AString *out) {
     out->clear();
 
@@ -694,12 +701,22 @@
                 *meta = new AMessage;
             }
             (*meta)->setInt32("bandwidth", x);
+        } else if (!strcasecmp("codecs", key.c_str())) {
+            if (!isQuotedString(val)) {
+                ALOGE("Expected quoted string for %s attribute, "
+                      "got '%s' instead.",
+                      key.c_str(), val.c_str());;
+
+                return ERROR_MALFORMED;
+            }
+
+            key.tolower();
+            const AString &codecs = unquoteString(val);
+            (*meta)->setString(key.c_str(), codecs.c_str());
         } else if (!strcasecmp("audio", key.c_str())
                 || !strcasecmp("video", key.c_str())
                 || !strcasecmp("subtitles", key.c_str())) {
-            if (val.size() < 2
-                    || val.c_str()[0] != '"'
-                    || val.c_str()[val.size() - 1] != '"') {
+            if (!isQuotedString(val)) {
                 ALOGE("Expected quoted string for %s attribute, "
                       "got '%s' instead.",
                       key.c_str(), val.c_str());
@@ -707,7 +724,7 @@
                 return ERROR_MALFORMED;
             }
 
-            AString groupID(val, 1, val.size() - 2);
+            const AString &groupID = unquoteString(val);
             ssize_t groupIndex = mMediaGroups.indexOfKey(groupID);
 
             if (groupIndex < 0) {
@@ -1096,4 +1113,121 @@
     return OK;
 }
 
+// static
+bool M3UParser::isQuotedString(const AString &str) {
+    if (str.size() < 2
+            || str.c_str()[0] != '"'
+            || str.c_str()[str.size() - 1] != '"') {
+        return false;
+    }
+    return true;
+}
+
+// static
+AString M3UParser::unquoteString(const AString &str) {
+     if (!isQuotedString(str)) {
+         return str;
+     }
+     return AString(str, 1, str.size() - 2);
+}
+
+// static
+bool M3UParser::codecIsType(const AString &codec, const char *type) {
+    if (codec.size() < 4) {
+        return false;
+    }
+    const char *c = codec.c_str();
+    switch (FOURCC(c[0], c[1], c[2], c[3])) {
+        // List extracted from http://www.mp4ra.org/codecs.html
+        case 'ac-3':
+        case 'alac':
+        case 'dra1':
+        case 'dtsc':
+        case 'dtse':
+        case 'dtsh':
+        case 'dtsl':
+        case 'ec-3':
+        case 'enca':
+        case 'g719':
+        case 'g726':
+        case 'm4ae':
+        case 'mlpa':
+        case 'mp4a':
+        case 'raw ':
+        case 'samr':
+        case 'sawb':
+        case 'sawp':
+        case 'sevc':
+        case 'sqcp':
+        case 'ssmv':
+        case 'twos':
+        case 'agsm':
+        case 'alaw':
+        case 'dvi ':
+        case 'fl32':
+        case 'fl64':
+        case 'ima4':
+        case 'in24':
+        case 'in32':
+        case 'lpcm':
+        case 'Qclp':
+        case 'QDM2':
+        case 'QDMC':
+        case 'ulaw':
+        case 'vdva':
+            return !strcmp("audio", type);
+
+        case 'avc1':
+        case 'avc2':
+        case 'avcp':
+        case 'drac':
+        case 'encv':
+        case 'mjp2':
+        case 'mp4v':
+        case 'mvc1':
+        case 'mvc2':
+        case 'resv':
+        case 's263':
+        case 'svc1':
+        case 'vc-1':
+        case 'CFHD':
+        case 'civd':
+        case 'DV10':
+        case 'dvh5':
+        case 'dvh6':
+        case 'dvhp':
+        case 'DVOO':
+        case 'DVOR':
+        case 'DVTV':
+        case 'DVVT':
+        case 'flic':
+        case 'gif ':
+        case 'h261':
+        case 'h263':
+        case 'HD10':
+        case 'jpeg':
+        case 'M105':
+        case 'mjpa':
+        case 'mjpb':
+        case 'png ':
+        case 'PNTG':
+        case 'rle ':
+        case 'rpza':
+        case 'Shr0':
+        case 'Shr1':
+        case 'Shr2':
+        case 'Shr3':
+        case 'Shr4':
+        case 'SVQ1':
+        case 'SVQ3':
+        case 'tga ':
+        case 'tiff':
+        case 'WRLE':
+            return !strcmp("video", type);
+
+        default:
+            return false;
+    }
+}
+
 }  // namespace android
diff --git a/media/libstagefright/httplive/M3UParser.h b/media/libstagefright/httplive/M3UParser.h
index 5248004..ccd6556 100644
--- a/media/libstagefright/httplive/M3UParser.h
+++ b/media/libstagefright/httplive/M3UParser.h
@@ -45,9 +45,7 @@
     status_t getTrackInfo(Parcel* reply) const;
     ssize_t getSelectedIndex() const;
 
-    bool getAudioURI(size_t index, AString *uri) const;
-    bool getVideoURI(size_t index, AString *uri) const;
-    bool getSubtitleURI(size_t index, AString *uri) const;
+    bool getTypeURI(size_t index, const char *key, AString *uri) const;
 
 protected:
     virtual ~M3UParser();
@@ -95,11 +93,13 @@
 
     status_t parseMedia(const AString &line);
 
-    bool getTypeURI(size_t index, const char *key, AString *uri) const;
-
     static status_t ParseInt32(const char *s, int32_t *x);
     static status_t ParseDouble(const char *s, double *x);
 
+    static bool isQuotedString(const AString &str);
+    static AString unquoteString(const AString &str);
+    static bool codecIsType(const AString &codec, const char *type);
+
     DISALLOW_EVIL_CONSTRUCTORS(M3UParser);
 };
 
diff --git a/media/libstagefright/omx/GraphicBufferSource.cpp b/media/libstagefright/omx/GraphicBufferSource.cpp
index 7672204..b81b116 100644
--- a/media/libstagefright/omx/GraphicBufferSource.cpp
+++ b/media/libstagefright/omx/GraphicBufferSource.cpp
@@ -51,7 +51,11 @@
     mLatestSubmittedBufferId(-1),
     mLatestSubmittedBufferFrameNum(0),
     mLatestSubmittedBufferUseCount(0),
-    mRepeatBufferDeferred(false) {
+    mRepeatBufferDeferred(false),
+    mTimePerCaptureUs(-1ll),
+    mTimePerFrameUs(-1ll),
+    mPrevCaptureUs(-1ll),
+    mPrevFrameUs(-1ll) {
 
     ALOGV("GraphicBufferSource w=%u h=%u c=%u",
             bufferWidth, bufferHeight, bufferCount);
@@ -560,7 +564,30 @@
 int64_t GraphicBufferSource::getTimestamp(const BufferQueue::BufferItem &item) {
     int64_t timeUs = item.mTimestamp / 1000;
 
-    if (mMaxTimestampGapUs > 0ll) {
+    if (mTimePerCaptureUs > 0ll) {
+        // Time lapse or slow motion mode
+        if (mPrevCaptureUs < 0ll) {
+            // first capture
+            mPrevCaptureUs = timeUs;
+            mPrevFrameUs = timeUs;
+        } else {
+            // snap to nearest capture point
+            int64_t nFrames = (timeUs + mTimePerCaptureUs / 2 - mPrevCaptureUs)
+                    / mTimePerCaptureUs;
+            if (nFrames <= 0) {
+                // skip this frame as it's too close to previous capture
+                ALOGV("skipping frame, timeUs %lld", timeUs);
+                return -1;
+            }
+            mPrevCaptureUs = mPrevCaptureUs + nFrames * mTimePerCaptureUs;
+            mPrevFrameUs += mTimePerFrameUs * nFrames;
+        }
+
+        ALOGV("timeUs %lld, captureUs %lld, frameUs %lld",
+                timeUs, mPrevCaptureUs, mPrevFrameUs);
+
+        return mPrevFrameUs;
+    } else if (mMaxTimestampGapUs > 0ll) {
         /* Cap timestamp gap between adjacent frames to specified max
          *
          * In the scenario of cast mirroring, encoding could be suspended for
@@ -574,7 +601,7 @@
             if (originalTimeUs < mPrevOriginalTimeUs) {
                 // Drop the frame if it's going backward in time. Bad timestamp
                 // could disrupt encoder's rate control completely.
-                ALOGV("Dropping frame that's going backward in time");
+                ALOGW("Dropping frame that's going backward in time");
                 return -1;
             }
             int64_t timestampGapUs = originalTimeUs - mPrevOriginalTimeUs;
@@ -593,6 +620,12 @@
 status_t GraphicBufferSource::submitBuffer_l(
         const BufferQueue::BufferItem &item, int cbi) {
     ALOGV("submitBuffer_l cbi=%d", cbi);
+
+    int64_t timeUs = getTimestamp(item);
+    if (timeUs < 0ll) {
+        return UNKNOWN_ERROR;
+    }
+
     CodecBuffer& codecBuffer(mCodecBuffers.editItemAt(cbi));
     codecBuffer.mGraphicBuffer = mBufferSlot[item.mBuf];
     codecBuffer.mBuf = item.mBuf;
@@ -606,12 +639,6 @@
     memcpy(data, &type, 4);
     memcpy(data + 4, &handle, sizeof(buffer_handle_t));
 
-    int64_t timeUs = getTimestamp(item);
-    if (timeUs < 0ll) {
-        ALOGE("Dropping frame with bad timestamp");
-        return UNKNOWN_ERROR;
-    }
-
     status_t err = mNodeInstance->emptyDirectBuffer(header, 0,
             4 + sizeof(buffer_handle_t), OMX_BUFFERFLAG_ENDOFFRAME,
             timeUs);
@@ -711,7 +738,7 @@
             // If this is the first time we're seeing this buffer, add it to our
             // slot table.
             if (item.mGraphicBuffer != NULL) {
-                ALOGV("fillCodecBuffer_l: setting mBufferSlot %d", item.mBuf);
+                ALOGV("onFrameAvailable: setting mBufferSlot %d", item.mBuf);
                 mBufferSlot[item.mBuf] = item.mGraphicBuffer;
             }
             mBufferQueue->releaseBuffer(item.mBuf, item.mFrameNumber,
@@ -782,6 +809,19 @@
             (skipFramesBeforeUs > 0) ? (skipFramesBeforeUs * 1000) : -1ll;
 }
 
+status_t GraphicBufferSource::setTimeLapseUs(int64_t* data) {
+    Mutex::Autolock autoLock(mMutex);
+
+    if (mExecuting || data[0] <= 0ll || data[1] <= 0ll) {
+        return INVALID_OPERATION;
+    }
+
+    mTimePerFrameUs = data[0];
+    mTimePerCaptureUs = data[1];
+
+    return OK;
+}
+
 void GraphicBufferSource::onMessageReceived(const sp<AMessage> &msg) {
     switch (msg->what()) {
         case kWhatRepeatLastFrame:
diff --git a/media/libstagefright/omx/GraphicBufferSource.h b/media/libstagefright/omx/GraphicBufferSource.h
index 153f2a0..fba42b7 100644
--- a/media/libstagefright/omx/GraphicBufferSource.h
+++ b/media/libstagefright/omx/GraphicBufferSource.h
@@ -118,6 +118,13 @@
     // of suspension on input.
     status_t setMaxTimestampGapUs(int64_t maxGapUs);
 
+    // Sets the time lapse (or slow motion) parameters.
+    // data[0] is the time (us) between two frames for playback
+    // data[1] is the time (us) between two frames for capture
+    // When set, the sample's timestamp will be modified to playback framerate,
+    // and capture timestamp will be modified to capture rate.
+    status_t setTimeLapseUs(int64_t* data);
+
     // Sets the start time us (in system time), samples before which should
     // be dropped and not submitted to encoder
     void setSkipFramesBeforeUs(int64_t startTimeUs);
@@ -250,6 +257,12 @@
     // no codec buffer was available at the time.
     bool mRepeatBufferDeferred;
 
+    // Time lapse / slow motion configuration
+    int64_t mTimePerCaptureUs;
+    int64_t mTimePerFrameUs;
+    int64_t mPrevCaptureUs;
+    int64_t mPrevFrameUs;
+
     void onMessageReceived(const sp<AMessage> &msg);
 
     DISALLOW_EVIL_CONSTRUCTORS(GraphicBufferSource);
diff --git a/media/libstagefright/omx/OMXNodeInstance.cpp b/media/libstagefright/omx/OMXNodeInstance.cpp
index aa96389..0fb38fa 100644
--- a/media/libstagefright/omx/OMXNodeInstance.cpp
+++ b/media/libstagefright/omx/OMXNodeInstance.cpp
@@ -851,6 +851,7 @@
         case IOMX::INTERNAL_OPTION_REPEAT_PREVIOUS_FRAME_DELAY:
         case IOMX::INTERNAL_OPTION_MAX_TIMESTAMP_GAP:
         case IOMX::INTERNAL_OPTION_START_TIME:
+        case IOMX::INTERNAL_OPTION_TIME_LAPSE:
         {
             const sp<GraphicBufferSource> &bufferSource =
                 getGraphicBufferSource();
@@ -884,7 +885,7 @@
                 int64_t maxGapUs = *(int64_t *)data;
 
                 return bufferSource->setMaxTimestampGapUs(maxGapUs);
-            } else { // IOMX::INTERNAL_OPTION_START_TIME
+            } else if (type == IOMX::INTERNAL_OPTION_START_TIME) {
                 if (size != sizeof(int64_t)) {
                     return INVALID_OPERATION;
                 }
@@ -892,6 +893,12 @@
                 int64_t skipFramesBeforeUs = *(int64_t *)data;
 
                 bufferSource->setSkipFramesBeforeUs(skipFramesBeforeUs);
+            } else { // IOMX::INTERNAL_OPTION_TIME_LAPSE
+                if (size != sizeof(int64_t) * 2) {
+                    return INVALID_OPERATION;
+                }
+
+                bufferSource->setTimeLapseUs((int64_t *)data);
             }
 
             return OK;
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index b74fa89..7615086 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -183,6 +183,7 @@
         (void) property_get("af.tee", value, "0");
         teeEnabled = atoi(value);
     }
+    // FIXME symbolic constants here
     if (teeEnabled & 1) {
         mTeeSinkInputEnabled = true;
     }
@@ -1810,7 +1811,7 @@
             kind = TEE_SINK_NEW;
         } else if (mRecordTeeSink->getStrongCount() != 1) {
             kind = TEE_SINK_NO;
-        } else if (format == mRecordTeeSink->format()) {
+        } else if (Format_isEqual(format, mRecordTeeSink->format())) {
             kind = TEE_SINK_OLD;
         } else {
             kind = TEE_SINK_NEW;
@@ -1847,8 +1848,6 @@
         // pre processing modules
         RecordThread *thread = new RecordThread(this,
                                   input,
-                                  reqSamplingRate,
-                                  reqChannelMask,
                                   id,
                                   primaryOutputDevice_l(),
                                   *pDevices
@@ -2096,7 +2095,7 @@
                                     int triggerSession,
                                     int listenerSession,
                                     sync_event_callback_t callBack,
-                                    void *cookie)
+                                    wp<RefBase> cookie)
 {
     Mutex::Autolock _l(mLock);
 
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 4799beb..21d05d4 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -253,7 +253,7 @@
                   int triggerSession,
                   int listenerSession,
                   sync_event_callback_t callBack,
-                  void *cookie)
+                  wp<RefBase> cookie)
         : mType(type), mTriggerSession(triggerSession), mListenerSession(listenerSession),
           mCallback(callBack), mCookie(cookie)
         {}
@@ -266,14 +266,14 @@
         AudioSystem::sync_event_t type() const { return mType; }
         int triggerSession() const { return mTriggerSession; }
         int listenerSession() const { return mListenerSession; }
-        void *cookie() const { return mCookie; }
+        wp<RefBase> cookie() const { return mCookie; }
 
     private:
           const AudioSystem::sync_event_t mType;
           const int mTriggerSession;
           const int mListenerSession;
           sync_event_callback_t mCallback;
-          void * const mCookie;
+          const wp<RefBase> mCookie;
           mutable Mutex mLock;
     };
 
@@ -281,7 +281,7 @@
                                         int triggerSession,
                                         int listenerSession,
                                         sync_event_callback_t callBack,
-                                        void *cookie);
+                                        wp<RefBase> cookie);
 
 private:
     class AudioHwDevice;    // fwd declaration for findSuitableHwDev_l
@@ -638,7 +638,7 @@
     // 0x200000 stereo 16-bit PCM frames = 47.5 seconds at 44.1 kHz, 8 megabytes
     static const size_t kTeeSinkInputFramesDefault = 0x200000;
     static const size_t kTeeSinkOutputFramesDefault = 0x200000;
-    static const size_t kTeeSinkTrackFramesDefault = 0x1000;
+    static const size_t kTeeSinkTrackFramesDefault = 0x200000;
 #endif
 
     // This method reads from a variable without mLock, but the variable is updated under mLock.  So
diff --git a/services/audioflinger/AudioResampler.cpp b/services/audioflinger/AudioResampler.cpp
index b206116..ca98f16 100644
--- a/services/audioflinger/AudioResampler.cpp
+++ b/services/audioflinger/AudioResampler.cpp
@@ -341,7 +341,7 @@
     uint32_t phaseIncrement = mPhaseIncrement;
     size_t outputIndex = 0;
     size_t outputSampleCount = outFrameCount * 2;
-    size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate;
+    size_t inFrameCount = getInFrameCountRequired(outFrameCount);
 
     // ALOGE("starting resample %d frames, inputIndex=%d, phaseFraction=%d, phaseIncrement=%d",
     //      outFrameCount, inputIndex, phaseFraction, phaseIncrement);
@@ -439,7 +439,7 @@
     uint32_t phaseIncrement = mPhaseIncrement;
     size_t outputIndex = 0;
     size_t outputSampleCount = outFrameCount * 2;
-    size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate;
+    size_t inFrameCount = getInFrameCountRequired(outFrameCount);
 
     // ALOGE("starting resample %d frames, inputIndex=%d, phaseFraction=%d, phaseIncrement=%d",
     //      outFrameCount, inputIndex, phaseFraction, phaseIncrement);
diff --git a/services/audioflinger/AudioResampler.h b/services/audioflinger/AudioResampler.h
index dc33f29..0592855 100644
--- a/services/audioflinger/AudioResampler.h
+++ b/services/audioflinger/AudioResampler.h
@@ -110,6 +110,38 @@
     uint64_t mLocalTimeFreq;
     int64_t mPTS;
 
+    // returns the inFrameCount required to generate outFrameCount frames.
+    //
+    // Placed here to be a consistent for all resamplers.
+    //
+    // Right now, we use the upper bound without regards to the current state of the
+    // input buffer using integer arithmetic, as follows:
+    //
+    // (static_cast<uint64_t>(outFrameCount)*mInSampleRate + (mSampleRate - 1))/mSampleRate;
+    //
+    // The double precision equivalent (float may not be precise enough):
+    // ceil(static_cast<double>(outFrameCount) * mInSampleRate / mSampleRate);
+    //
+    // this relies on the fact that the mPhaseIncrement is rounded down from
+    // #phases * mInSampleRate/mSampleRate and the fact that Sum(Floor(x)) <= Floor(Sum(x)).
+    // http://www.proofwiki.org/wiki/Sum_of_Floors_Not_Greater_Than_Floor_of_Sums
+    //
+    // (so long as double precision is computed accurately enough to be considered
+    // greater than or equal to the Floor(x) value in int32_t arithmetic; thus this
+    // will not necessarily hold for floats).
+    //
+    // TODO:
+    // Greater accuracy and a tight bound is obtained by:
+    // 1) subtract and adjust for the current state of the AudioBufferProvider buffer.
+    // 2) using the exact integer formula where (ignoring 64b casting)
+    //  inFrameCount = (mPhaseIncrement * (outFrameCount - 1) + mPhaseFraction) / phaseWrapLimit;
+    //  phaseWrapLimit is the wraparound (1 << kNumPhaseBits), if not specified explicitly.
+    //
+    inline size_t getInFrameCountRequired(size_t outFrameCount) {
+        return (static_cast<uint64_t>(outFrameCount)*mInSampleRate
+                + (mSampleRate - 1))/mSampleRate;
+    }
+
 private:
     const src_quality mQuality;
 
diff --git a/services/audioflinger/AudioResamplerCubic.cpp b/services/audioflinger/AudioResamplerCubic.cpp
index 1f9714b..8f14ff9 100644
--- a/services/audioflinger/AudioResamplerCubic.cpp
+++ b/services/audioflinger/AudioResamplerCubic.cpp
@@ -60,7 +60,7 @@
     uint32_t phaseIncrement = mPhaseIncrement;
     size_t outputIndex = 0;
     size_t outputSampleCount = outFrameCount * 2;
-    size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate;
+    size_t inFrameCount = getInFrameCountRequired(outFrameCount);
 
     // fetch first buffer
     if (mBuffer.frameCount == 0) {
@@ -128,7 +128,7 @@
     uint32_t phaseIncrement = mPhaseIncrement;
     size_t outputIndex = 0;
     size_t outputSampleCount = outFrameCount * 2;
-    size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate;
+    size_t inFrameCount = getInFrameCountRequired(outFrameCount);
 
     // fetch first buffer
     if (mBuffer.frameCount == 0) {
diff --git a/services/audioflinger/AudioResamplerDyn.cpp b/services/audioflinger/AudioResamplerDyn.cpp
index 2997c5c..7e4ca0c 100644
--- a/services/audioflinger/AudioResamplerDyn.cpp
+++ b/services/audioflinger/AudioResamplerDyn.cpp
@@ -470,7 +470,7 @@
     const uint32_t phaseIncrement = mPhaseIncrement;
     size_t outputIndex = 0;
     size_t outputSampleCount = outFrameCount * 2;   // stereo output
-    size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate;
+    size_t inFrameCount = getInFrameCountRequired(outFrameCount);
     const uint32_t phaseWrapLimit = c.mL << c.mShift;
 
     // NOTE: be very careful when modifying the code here. register
diff --git a/services/audioflinger/AudioResamplerSinc.cpp b/services/audioflinger/AudioResamplerSinc.cpp
index 207f26b..d0a7a58 100644
--- a/services/audioflinger/AudioResamplerSinc.cpp
+++ b/services/audioflinger/AudioResamplerSinc.cpp
@@ -540,7 +540,7 @@
     uint32_t phaseIncrement = mPhaseIncrement;
     size_t outputIndex = 0;
     size_t outputSampleCount = outFrameCount * 2;
-    size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate;
+    size_t inFrameCount = getInFrameCountRequired(outFrameCount);
 
     while (outputIndex < outputSampleCount) {
         // buffer is empty, fetch a new one
diff --git a/services/audioflinger/RecordTracks.h b/services/audioflinger/RecordTracks.h
index fc3171f..3ec9889 100644
--- a/services/audioflinger/RecordTracks.h
+++ b/services/audioflinger/RecordTracks.h
@@ -47,6 +47,9 @@
     static  void        appendDumpHeader(String8& result);
             void        dump(char* buffer, size_t size, bool active);
 
+            void        handleSyncStartEvent(const sp<SyncEvent>& event);
+            void        clearSyncStartEvent();
+
 private:
     friend class AudioFlinger;  // for mState
 
@@ -59,4 +62,33 @@
     // releaseBuffer() not overridden
 
     bool                mOverflow;  // overflow on most recent attempt to fill client buffer
+
+           // updated by RecordThread::readInputParameters_l()
+            AudioResampler                      *mResampler;
+
+            // interleaved stereo pairs of fixed-point signed Q19.12
+            int32_t                             *mRsmpOutBuffer;
+            // current allocated frame count for the above, which may be larger than needed
+            size_t                              mRsmpOutFrameCount;
+
+            size_t                              mRsmpInUnrel;   // unreleased frames remaining from
+                                                                // most recent getNextBuffer
+                                                                // for debug only
+
+            // rolling counter that is never cleared
+            int32_t                             mRsmpInFront;   // next available frame
+
+            AudioBufferProvider::Buffer mSink;  // references client's buffer sink in shared memory
+
+            // sync event triggering actual audio capture. Frames read before this event will
+            // be dropped and therefore not read by the application.
+            sp<SyncEvent>                       mSyncStartEvent;
+
+            // number of captured frames to drop after the start sync event has been received.
+            // when < 0, maximum frames to drop before starting capture even if sync event is
+            // not received
+            ssize_t                             mFramesToDrop;
+
+            // used by resampler to find source frames
+            ResamplerBufferProvider *mResamplerBufferProvider;
 };
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index b064e89..3e8c133 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -274,7 +274,8 @@
         mType(type),
         mAudioFlinger(audioFlinger),
         // mSampleRate, mFrameCount, mChannelMask, mChannelCount, mFrameSize, mFormat, mBufferSize
-        // are set by PlaybackThread::readOutputParameters() or RecordThread::readInputParameters()
+        // are set by PlaybackThread::readOutputParameters_l() or
+        // RecordThread::readInputParameters_l()
         mParamStatus(NO_ERROR),
         //FIXME: mStandby should be true here. Is this some kind of hack?
         mStandby(false), mOutDevice(outDevice), mInDevice(inDevice),
@@ -1108,7 +1109,7 @@
         }
     }
 
-    readOutputParameters();
+    readOutputParameters_l();
 
     // mStreamTypes[AUDIO_STREAM_CNT] is initialized by stream_type_t default constructor
     // There is no AUDIO_STREAM_MIN, and ++ operator does not compile
@@ -1677,7 +1678,7 @@
     return 0;
 }
 
-void AudioFlinger::PlaybackThread::readOutputParameters()
+void AudioFlinger::PlaybackThread::readOutputParameters_l()
 {
     // unfortunately we have no way of recovering from errors here, hence the LOG_FATAL
     mSampleRate = mOutput->stream->common.get_sample_rate(&mOutput->stream->common);
@@ -1765,7 +1766,7 @@
 
     // force reconfiguration of effect chains and engines to take new buffer size and audio
     // parameters into account
-    // Note that mLock is not held when readOutputParameters() is called from the constructor
+    // Note that mLock is not held when readOutputParameters_l() is called from the constructor
     // but in this case nothing is done below as no audio sessions have effect yet so it doesn't
     // matter.
     // create a copy of mEffectChains as calling moveEffectChain_l() can reorder some effect chains
@@ -3485,7 +3486,7 @@
                                                        keyValuePair.string());
             }
             if (status == NO_ERROR && reconfig) {
-                readOutputParameters();
+                readOutputParameters_l();
                 delete mAudioMixer;
                 mAudioMixer = new AudioMixer(mNormalFrameCount, mSampleRate);
                 for (size_t i = 0; i < mTracks.size() ; i++) {
@@ -3827,7 +3828,7 @@
                                                        keyValuePair.string());
             }
             if (status == NO_ERROR && reconfig) {
-                readOutputParameters();
+                readOutputParameters_l();
                 sendIoConfigEvent_l(AudioSystem::OUTPUT_CONFIG_CHANGED);
             }
         }
@@ -4450,8 +4451,6 @@
 
 AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger,
                                          AudioStreamIn *input,
-                                         uint32_t sampleRate,
-                                         audio_channel_mask_t channelMask,
                                          audio_io_handle_t id,
                                          audio_devices_t outDevice,
                                          audio_devices_t inDevice
@@ -4460,14 +4459,9 @@
 #endif
                                          ) :
     ThreadBase(audioFlinger, id, outDevice, inDevice, RECORD),
-    mInput(input), mActiveTracksGen(0), mResampler(NULL), mRsmpOutBuffer(NULL), mRsmpInBuffer(NULL),
-    // mRsmpInFrames, mRsmpInFramesP2, mRsmpInUnrel, mRsmpInFront, and mRsmpInRear
-    //      are set by readInputParameters()
-    // mRsmpInIndex LEGACY
-    mReqChannelCount(popcount(channelMask)),
-    mReqSampleRate(sampleRate)
-    // mBytesRead is only meaningful while active, and so is cleared in start()
-    // (but might be better to also clear here for dump?)
+    mInput(input), mActiveTracksGen(0), mRsmpInBuffer(NULL),
+    // mRsmpInFrames and mRsmpInFramesP2 are set by readInputParameters_l()
+    mRsmpInRear(0)
 #ifdef TEE_SINK
     , mTeeSink(teeSink)
 #endif
@@ -4475,7 +4469,7 @@
     snprintf(mName, kNameLength, "AudioIn_%X", id);
     mNBLogWriter = audioFlinger->newWriter_l(kLogSize, mName);
 
-    readInputParameters();
+    readInputParameters_l();
 }
 
 
@@ -4483,8 +4477,6 @@
 {
     mAudioFlinger->unregisterWriter(mNBLogWriter);
     delete[] mRsmpInBuffer;
-    delete mResampler;
-    delete[] mRsmpOutBuffer;
 }
 
 void AudioFlinger::RecordThread::onFirstRef()
@@ -4498,12 +4490,6 @@
 
     inputStandBy();
 
-    // used to verify we've read at least once before evaluating how many bytes were read
-    bool readOnce = false;
-
-    // used to request a deferred sleep, to be executed later while mutex is unlocked
-    bool doSleep = false;
-
 reacquire_wakelock:
     sp<RecordTrack> activeTrack;
     int activeTracksGen;
@@ -4527,17 +4513,22 @@
         }
     }
 
-    // start recording
+    // used to request a deferred sleep, to be executed later while mutex is unlocked
+    uint32_t sleepUs = 0;
+
+    // loop while there is work to do
     for (;;) {
-        TrackBase::track_state activeTrackState;
         Vector< sp<EffectChain> > effectChains;
 
         // sleep with mutex unlocked
-        if (doSleep) {
-            doSleep = false;
-            usleep(kRecordThreadSleepUs);
+        if (sleepUs > 0) {
+            usleep(sleepUs);
+            sleepUs = 0;
         }
 
+        // activeTracks accumulates a copy of a subset of mActiveTracks
+        Vector< sp<RecordTrack> > activeTracks;
+
         { // scope for mLock
             Mutex::Autolock _l(mLock);
 
@@ -4571,236 +4562,307 @@
                     tmp.add(mActiveTracks[i]->uid());
                 }
                 updateWakeLockUids_l(tmp);
-                // FIXME an arbitrary choice
-                activeTrack = mActiveTracks[0];
             }
 
-            if (activeTrack->isTerminated()) {
-                removeTrack_l(activeTrack);
-                mActiveTracks.remove(activeTrack);
-                mActiveTracksGen++;
-                continue;
-            }
+            bool doBroadcast = false;
+            for (size_t i = 0; i < size; ) {
 
-            activeTrackState = activeTrack->mState;
-            switch (activeTrackState) {
-            case TrackBase::PAUSING:
-                standbyIfNotAlreadyInStandby();
-                mActiveTracks.remove(activeTrack);
-                mActiveTracksGen++;
-                mStartStopCond.broadcast();
-                doSleep = true;
-                continue;
-
-            case TrackBase::RESUMING:
-                mStandby = false;
-                if (mReqChannelCount != activeTrack->channelCount()) {
+                activeTrack = mActiveTracks[i];
+                if (activeTrack->isTerminated()) {
+                    removeTrack_l(activeTrack);
                     mActiveTracks.remove(activeTrack);
                     mActiveTracksGen++;
-                    mStartStopCond.broadcast();
+                    size--;
                     continue;
                 }
-                if (readOnce) {
-                    mStartStopCond.broadcast();
-                    // record start succeeds only if first read from audio input succeeds
-                    if (mBytesRead < 0) {
-                        mActiveTracks.remove(activeTrack);
-                        mActiveTracksGen++;
-                        continue;
-                    }
+
+                TrackBase::track_state activeTrackState = activeTrack->mState;
+                switch (activeTrackState) {
+
+                case TrackBase::PAUSING:
+                    mActiveTracks.remove(activeTrack);
+                    mActiveTracksGen++;
+                    doBroadcast = true;
+                    size--;
+                    continue;
+
+                case TrackBase::STARTING_1:
+                    sleepUs = 10000;
+                    i++;
+                    continue;
+
+                case TrackBase::STARTING_2:
+                    doBroadcast = true;
+                    mStandby = false;
                     activeTrack->mState = TrackBase::ACTIVE;
+                    break;
+
+                case TrackBase::ACTIVE:
+                    break;
+
+                case TrackBase::IDLE:
+                    i++;
+                    continue;
+
+                default:
+                    LOG_FATAL("Unexpected activeTrackState %d", activeTrackState);
                 }
-                break;
 
-            case TrackBase::ACTIVE:
-                break;
+                activeTracks.add(activeTrack);
+                i++;
 
-            case TrackBase::IDLE:
-                doSleep = true;
-                continue;
-
-            default:
-                LOG_FATAL("Unexpected activeTrackState %d", activeTrackState);
             }
+            if (doBroadcast) {
+                mStartStopCond.broadcast();
+            }
+
+            // sleep if there are no active tracks to process
+            if (activeTracks.size() == 0) {
+                if (sleepUs == 0) {
+                    sleepUs = kRecordThreadSleepUs;
+                }
+                continue;
+            }
+            sleepUs = 0;
 
             lockEffectChains_l(effectChains);
         }
 
-        // thread mutex is now unlocked, mActiveTracks unknown, activeTrack != 0, kept, immutable
-        // activeTrack->mState unknown, activeTrackState immutable and is ACTIVE or RESUMING
+        // thread mutex is now unlocked, mActiveTracks unknown, activeTracks.size() > 0
 
-        for (size_t i = 0; i < effectChains.size(); i ++) {
+        size_t size = effectChains.size();
+        for (size_t i = 0; i < size; i++) {
             // thread mutex is not locked, but effect chain is locked
             effectChains[i]->process_l();
         }
 
-        AudioBufferProvider::Buffer buffer;
-        buffer.frameCount = mFrameCount;
-        status_t status = activeTrack->getNextBuffer(&buffer);
-        if (status == NO_ERROR) {
-            readOnce = true;
-            size_t framesOut = buffer.frameCount;
-            if (mResampler == NULL) {
-                // no resampling
-                while (framesOut) {
-                    size_t framesIn = mFrameCount - mRsmpInIndex;
-                    if (framesIn > 0) {
-                        int8_t *src = (int8_t *)mRsmpInBuffer + mRsmpInIndex * mFrameSize;
-                        int8_t *dst = buffer.i8 + (buffer.frameCount - framesOut) *
-                                activeTrack->mFrameSize;
-                        if (framesIn > framesOut) {
-                            framesIn = framesOut;
-                        }
-                        mRsmpInIndex += framesIn;
-                        framesOut -= framesIn;
-                        if (mChannelCount == mReqChannelCount) {
-                            memcpy(dst, src, framesIn * mFrameSize);
-                        } else {
-                            if (mChannelCount == 1) {
-                                upmix_to_stereo_i16_from_mono_i16((int16_t *)dst,
-                                        (int16_t *)src, framesIn);
-                            } else {
-                                downmix_to_mono_i16_from_stereo_i16((int16_t *)dst,
-                                        (int16_t *)src, framesIn);
-                            }
-                        }
-                    }
-                    if (framesOut > 0 && mFrameCount == mRsmpInIndex) {
-                        void *readInto;
-                        if (framesOut == mFrameCount && mChannelCount == mReqChannelCount) {
-                            readInto = buffer.raw;
-                            framesOut = 0;
-                        } else {
-                            readInto = mRsmpInBuffer;
-                            mRsmpInIndex = 0;
-                        }
-                        mBytesRead = mInput->stream->read(mInput->stream, readInto, mBufferSize);
-                        if (mBytesRead <= 0) {
-                            // TODO: verify that it's benign to use a stale track state
-                            if ((mBytesRead < 0) && (activeTrackState == TrackBase::ACTIVE))
-                            {
-                                ALOGE("Error reading audio input");
-                                // Force input into standby so that it tries to
-                                // recover at next read attempt
-                                inputStandBy();
-                                doSleep = true;
-                            }
-                            mRsmpInIndex = mFrameCount;
-                            framesOut = 0;
-                            buffer.frameCount = 0;
-                        }
-#ifdef TEE_SINK
-                        else if (mTeeSink != 0) {
-                            (void) mTeeSink->write(readInto,
-                                    mBytesRead >> Format_frameBitShift(mTeeSink->format()));
-                        }
-#endif
-                    }
-                }
-            } else {
-                // resampling
+        // Read from HAL to keep up with fastest client if multiple active tracks, not slowest one.
+        // Only the client(s) that are too slow will overrun. But if even the fastest client is too
+        // slow, then this RecordThread will overrun by not calling HAL read often enough.
+        // If destination is non-contiguous, first read past the nominal end of buffer, then
+        // copy to the right place.  Permitted because mRsmpInBuffer was over-allocated.
 
-                // avoid busy-waiting if client doesn't keep up
-                bool madeProgress = false;
-
-                // keep mRsmpInBuffer full so resampler always has sufficient input
-                for (;;) {
-                    int32_t rear = mRsmpInRear;
-                    ssize_t filled = rear - mRsmpInFront;
-                    ALOG_ASSERT(0 <= filled && (size_t) filled <= mRsmpInFramesP2);
-                    // exit once there is enough data in buffer for resampler
-                    if ((size_t) filled >= mRsmpInFrames) {
-                        break;
-                    }
-                    size_t avail = mRsmpInFramesP2 - filled;
-                    // Only try to read full HAL buffers.
-                    // But if the HAL read returns a partial buffer, use it.
-                    if (avail < mFrameCount) {
-                        ALOGE("insufficient space to read: avail %d < mFrameCount %d",
-                                avail, mFrameCount);
-                        break;
-                    }
-                    // If 'avail' is non-contiguous, first read past the nominal end of buffer, then
-                    // copy to the right place.  Permitted because mRsmpInBuffer was over-allocated.
-                    rear &= mRsmpInFramesP2 - 1;
-                    mBytesRead = mInput->stream->read(mInput->stream,
-                            &mRsmpInBuffer[rear * mChannelCount], mBufferSize);
-                    if (mBytesRead <= 0) {
-                        ALOGE("read failed: mBytesRead=%d < %u", mBytesRead, mBufferSize);
-                        break;
-                    }
-                    ALOG_ASSERT((size_t) mBytesRead <= mBufferSize);
-                    size_t framesRead = mBytesRead / mFrameSize;
-                    ALOG_ASSERT(framesRead > 0);
-                    madeProgress = true;
-                    // If 'avail' was non-contiguous, we now correct for reading past end of buffer.
-                    size_t part1 = mRsmpInFramesP2 - rear;
-                    if (framesRead > part1) {
-                        memcpy(mRsmpInBuffer, &mRsmpInBuffer[mRsmpInFramesP2 * mChannelCount],
-                                (framesRead - part1) * mFrameSize);
-                    }
-                    mRsmpInRear += framesRead;
-                }
-
-                if (!madeProgress) {
-                    ALOGV("Did not make progress");
-                    usleep(((mFrameCount * 1000) / mSampleRate) * 1000);
-                }
-
-                // resampler accumulates, but we only have one source track
-                memset(mRsmpOutBuffer, 0, framesOut * FCC_2 * sizeof(int32_t));
-                mResampler->resample(mRsmpOutBuffer, framesOut,
-                        this /* AudioBufferProvider* */);
-                // ditherAndClamp() works as long as all buffers returned by
-                // activeTrack->getNextBuffer() are 32 bit aligned which should be always true.
-                if (mReqChannelCount == 1) {
-                    // temporarily type pun mRsmpOutBuffer from Q19.12 to int16_t
-                    ditherAndClamp(mRsmpOutBuffer, mRsmpOutBuffer, framesOut);
-                    // the resampler always outputs stereo samples:
-                    // do post stereo to mono conversion
-                    downmix_to_mono_i16_from_stereo_i16(buffer.i16, (int16_t *)mRsmpOutBuffer,
-                            framesOut);
-                } else {
-                    ditherAndClamp((int32_t *)buffer.raw, mRsmpOutBuffer, framesOut);
-                }
-                // now done with mRsmpOutBuffer
-
-            }
-            if (mFramestoDrop == 0) {
-                activeTrack->releaseBuffer(&buffer);
-            } else {
-                if (mFramestoDrop > 0) {
-                    mFramestoDrop -= buffer.frameCount;
-                    if (mFramestoDrop <= 0) {
-                        clearSyncStartEvent();
-                    }
-                } else {
-                    mFramestoDrop += buffer.frameCount;
-                    if (mFramestoDrop >= 0 || mSyncStartEvent == 0 ||
-                            mSyncStartEvent->isCancelled()) {
-                        ALOGW("Synced record %s, session %d, trigger session %d",
-                              (mFramestoDrop >= 0) ? "timed out" : "cancelled",
-                              activeTrack->sessionId(),
-                              (mSyncStartEvent != 0) ? mSyncStartEvent->triggerSession() : 0);
-                        clearSyncStartEvent();
-                    }
-                }
-            }
-            activeTrack->clearOverflow();
+        int32_t rear = mRsmpInRear & (mRsmpInFramesP2 - 1);
+        ssize_t bytesRead = mInput->stream->read(mInput->stream,
+                &mRsmpInBuffer[rear * mChannelCount], mBufferSize);
+        if (bytesRead <= 0) {
+            ALOGE("read failed: bytesRead=%d < %u", bytesRead, mBufferSize);
+            // Force input into standby so that it tries to recover at next read attempt
+            inputStandBy();
+            sleepUs = kRecordThreadSleepUs;
+            continue;
         }
-        // client isn't retrieving buffers fast enough
-        else {
-            if (!activeTrack->setOverflow()) {
-                nsecs_t now = systemTime();
-                if ((now - lastWarning) > kWarningThrottleNs) {
-                    ALOGW("RecordThread: buffer overflow");
-                    lastWarning = now;
+        ALOG_ASSERT((size_t) bytesRead <= mBufferSize);
+        size_t framesRead = bytesRead / mFrameSize;
+        ALOG_ASSERT(framesRead > 0);
+        if (mTeeSink != 0) {
+            (void) mTeeSink->write(&mRsmpInBuffer[rear * mChannelCount], framesRead);
+        }
+        // If destination is non-contiguous, we now correct for reading past end of buffer.
+        size_t part1 = mRsmpInFramesP2 - rear;
+        if (framesRead > part1) {
+            memcpy(mRsmpInBuffer, &mRsmpInBuffer[mRsmpInFramesP2 * mChannelCount],
+                    (framesRead - part1) * mFrameSize);
+        }
+        rear = mRsmpInRear += framesRead;
+
+        size = activeTracks.size();
+        // loop over each active track
+        for (size_t i = 0; i < size; i++) {
+            activeTrack = activeTracks[i];
+
+            enum {
+                OVERRUN_UNKNOWN,
+                OVERRUN_TRUE,
+                OVERRUN_FALSE
+            } overrun = OVERRUN_UNKNOWN;
+
+            // loop over getNextBuffer to handle circular sink
+            for (;;) {
+
+                activeTrack->mSink.frameCount = ~0;
+                status_t status = activeTrack->getNextBuffer(&activeTrack->mSink);
+                size_t framesOut = activeTrack->mSink.frameCount;
+                LOG_ALWAYS_FATAL_IF((status == OK) != (framesOut > 0));
+
+                int32_t front = activeTrack->mRsmpInFront;
+                ssize_t filled = rear - front;
+                size_t framesIn;
+
+                if (filled < 0) {
+                    // should not happen, but treat like a massive overrun and re-sync
+                    framesIn = 0;
+                    activeTrack->mRsmpInFront = rear;
+                    overrun = OVERRUN_TRUE;
+                } else if ((size_t) filled <= mRsmpInFrames) {
+                    framesIn = (size_t) filled;
+                } else {
+                    // client is not keeping up with server, but give it latest data
+                    framesIn = mRsmpInFrames;
+                    activeTrack->mRsmpInFront = front = rear - framesIn;
+                    overrun = OVERRUN_TRUE;
+                }
+
+                if (framesOut == 0 || framesIn == 0) {
+                    break;
+                }
+
+                if (activeTrack->mResampler == NULL) {
+                    // no resampling
+                    if (framesIn > framesOut) {
+                        framesIn = framesOut;
+                    } else {
+                        framesOut = framesIn;
+                    }
+                    int8_t *dst = activeTrack->mSink.i8;
+                    while (framesIn > 0) {
+                        front &= mRsmpInFramesP2 - 1;
+                        size_t part1 = mRsmpInFramesP2 - front;
+                        if (part1 > framesIn) {
+                            part1 = framesIn;
+                        }
+                        int8_t *src = (int8_t *)mRsmpInBuffer + (front * mFrameSize);
+                        if (mChannelCount == activeTrack->mChannelCount) {
+                            memcpy(dst, src, part1 * mFrameSize);
+                        } else if (mChannelCount == 1) {
+                            upmix_to_stereo_i16_from_mono_i16((int16_t *)dst, (int16_t *)src,
+                                    part1);
+                        } else {
+                            downmix_to_mono_i16_from_stereo_i16((int16_t *)dst, (int16_t *)src,
+                                    part1);
+                        }
+                        dst += part1 * activeTrack->mFrameSize;
+                        front += part1;
+                        framesIn -= part1;
+                    }
+                    activeTrack->mRsmpInFront += framesOut;
+
+                } else {
+                    // resampling
+                    // FIXME framesInNeeded should really be part of resampler API, and should
+                    //       depend on the SRC ratio
+                    //       to keep mRsmpInBuffer full so resampler always has sufficient input
+                    size_t framesInNeeded;
+                    // FIXME only re-calculate when it changes, and optimize for common ratios
+                    double inOverOut = (double) mSampleRate / activeTrack->mSampleRate;
+                    double outOverIn = (double) activeTrack->mSampleRate / mSampleRate;
+                    framesInNeeded = ceil(framesOut * inOverOut) + 1;
+                    ALOGV("need %u frames in to produce %u out given in/out ratio of %.4g",
+                                framesInNeeded, framesOut, inOverOut);
+                    // Although we theoretically have framesIn in circular buffer, some of those are
+                    // unreleased frames, and thus must be discounted for purpose of budgeting.
+                    size_t unreleased = activeTrack->mRsmpInUnrel;
+                    framesIn = framesIn > unreleased ? framesIn - unreleased : 0;
+                    if (framesIn < framesInNeeded) {
+                        ALOGV("not enough to resample: have %u frames in but need %u in to "
+                                "produce %u out given in/out ratio of %.4g",
+                                framesIn, framesInNeeded, framesOut, inOverOut);
+                        size_t newFramesOut = framesIn > 0 ? floor((framesIn - 1) * outOverIn) : 0;
+                        LOG_ALWAYS_FATAL_IF(newFramesOut >= framesOut);
+                        if (newFramesOut == 0) {
+                            break;
+                        }
+                        framesInNeeded = ceil(newFramesOut * inOverOut) + 1;
+                        ALOGV("now need %u frames in to produce %u out given out/in ratio of %.4g",
+                                framesInNeeded, newFramesOut, outOverIn);
+                        LOG_ALWAYS_FATAL_IF(framesIn < framesInNeeded);
+                        ALOGV("success 2: have %u frames in and need %u in to produce %u out "
+                              "given in/out ratio of %.4g",
+                              framesIn, framesInNeeded, newFramesOut, inOverOut);
+                        framesOut = newFramesOut;
+                    } else {
+                        ALOGV("success 1: have %u in and need %u in to produce %u out "
+                            "given in/out ratio of %.4g",
+                            framesIn, framesInNeeded, framesOut, inOverOut);
+                    }
+
+                    // reallocate mRsmpOutBuffer as needed; we will grow but never shrink
+                    if (activeTrack->mRsmpOutFrameCount < framesOut) {
+                        // FIXME why does each track need it's own mRsmpOutBuffer? can't they share?
+                        delete[] activeTrack->mRsmpOutBuffer;
+                        // resampler always outputs stereo
+                        activeTrack->mRsmpOutBuffer = new int32_t[framesOut * FCC_2];
+                        activeTrack->mRsmpOutFrameCount = framesOut;
+                    }
+
+                    // resampler accumulates, but we only have one source track
+                    memset(activeTrack->mRsmpOutBuffer, 0, framesOut * FCC_2 * sizeof(int32_t));
+                    activeTrack->mResampler->resample(activeTrack->mRsmpOutBuffer, framesOut,
+                            // FIXME how about having activeTrack implement this interface itself?
+                            activeTrack->mResamplerBufferProvider
+                            /*this*/ /* AudioBufferProvider* */);
+                    // ditherAndClamp() works as long as all buffers returned by
+                    // activeTrack->getNextBuffer() are 32 bit aligned which should be always true.
+                    if (activeTrack->mChannelCount == 1) {
+                        // temporarily type pun mRsmpOutBuffer from Q19.12 to int16_t
+                        ditherAndClamp(activeTrack->mRsmpOutBuffer, activeTrack->mRsmpOutBuffer,
+                                framesOut);
+                        // the resampler always outputs stereo samples:
+                        // do post stereo to mono conversion
+                        downmix_to_mono_i16_from_stereo_i16(activeTrack->mSink.i16,
+                                (int16_t *)activeTrack->mRsmpOutBuffer, framesOut);
+                    } else {
+                        ditherAndClamp((int32_t *)activeTrack->mSink.raw,
+                                activeTrack->mRsmpOutBuffer, framesOut);
+                    }
+                    // now done with mRsmpOutBuffer
+
+                }
+
+                if (framesOut > 0 && (overrun == OVERRUN_UNKNOWN)) {
+                    overrun = OVERRUN_FALSE;
+                }
+
+                if (activeTrack->mFramesToDrop == 0) {
+                    if (framesOut > 0) {
+                        activeTrack->mSink.frameCount = framesOut;
+                        activeTrack->releaseBuffer(&activeTrack->mSink);
+                    }
+                } else {
+                    // FIXME could do a partial drop of framesOut
+                    if (activeTrack->mFramesToDrop > 0) {
+                        activeTrack->mFramesToDrop -= framesOut;
+                        if (activeTrack->mFramesToDrop <= 0) {
+                            activeTrack->clearSyncStartEvent();
+                        }
+                    } else {
+                        activeTrack->mFramesToDrop += framesOut;
+                        if (activeTrack->mFramesToDrop >= 0 || activeTrack->mSyncStartEvent == 0 ||
+                                activeTrack->mSyncStartEvent->isCancelled()) {
+                            ALOGW("Synced record %s, session %d, trigger session %d",
+                                  (activeTrack->mFramesToDrop >= 0) ? "timed out" : "cancelled",
+                                  activeTrack->sessionId(),
+                                  (activeTrack->mSyncStartEvent != 0) ?
+                                          activeTrack->mSyncStartEvent->triggerSession() : 0);
+                            activeTrack->clearSyncStartEvent();
+                        }
+                    }
+                }
+
+                if (framesOut == 0) {
+                    break;
                 }
             }
-            // Release the processor for a while before asking for a new buffer.
-            // This will give the application more chance to read from the buffer and
-            // clear the overflow.
-            doSleep = true;
+
+            switch (overrun) {
+            case OVERRUN_TRUE:
+                // client isn't retrieving buffers fast enough
+                if (!activeTrack->setOverflow()) {
+                    nsecs_t now = systemTime();
+                    // FIXME should lastWarning per track?
+                    if ((now - lastWarning) > kWarningThrottleNs) {
+                        ALOGW("RecordThread: buffer overflow");
+                        lastWarning = now;
+                    }
+                }
+                break;
+            case OVERRUN_FALSE:
+                activeTrack->clearOverflow();
+                break;
+            case OVERRUN_UNKNOWN:
+                break;
+            }
+
         }
 
         // enable changes in effect chain
@@ -4959,114 +5021,92 @@
     status_t status = NO_ERROR;
 
     if (event == AudioSystem::SYNC_EVENT_NONE) {
-        clearSyncStartEvent();
+        recordTrack->clearSyncStartEvent();
     } else if (event != AudioSystem::SYNC_EVENT_SAME) {
-        mSyncStartEvent = mAudioFlinger->createSyncEvent(event,
+        recordTrack->mSyncStartEvent = mAudioFlinger->createSyncEvent(event,
                                        triggerSession,
                                        recordTrack->sessionId(),
                                        syncStartEventCallback,
-                                       this);
+                                       recordTrack);
         // Sync event can be cancelled by the trigger session if the track is not in a
         // compatible state in which case we start record immediately
-        if (mSyncStartEvent->isCancelled()) {
-            clearSyncStartEvent();
+        if (recordTrack->mSyncStartEvent->isCancelled()) {
+            recordTrack->clearSyncStartEvent();
         } else {
             // do not wait for the event for more than AudioSystem::kSyncRecordStartTimeOutMs
-            mFramestoDrop = - ((AudioSystem::kSyncRecordStartTimeOutMs * mReqSampleRate) / 1000);
+            recordTrack->mFramesToDrop = -
+                    ((AudioSystem::kSyncRecordStartTimeOutMs * recordTrack->mSampleRate) / 1000);
         }
     }
 
     {
         // This section is a rendezvous between binder thread executing start() and RecordThread
         AutoMutex lock(mLock);
-        if (mActiveTracks.size() > 0) {
-            // FIXME does not work for multiple active tracks
-            if (mActiveTracks.indexOf(recordTrack) != 0) {
-                status = -EBUSY;
-            } else if (recordTrack->mState == TrackBase::PAUSING) {
+        if (mActiveTracks.indexOf(recordTrack) >= 0) {
+            if (recordTrack->mState == TrackBase::PAUSING) {
+                ALOGV("active record track PAUSING -> ACTIVE");
                 recordTrack->mState = TrackBase::ACTIVE;
+            } else {
+                ALOGV("active record track state %d", recordTrack->mState);
             }
             return status;
         }
 
-        // FIXME why? already set in constructor, 'STARTING_1' would be more accurate
-        recordTrack->mState = TrackBase::IDLE;
+        // TODO consider other ways of handling this, such as changing the state to :STARTING and
+        //      adding the track to mActiveTracks after returning from AudioSystem::startInput(),
+        //      or using a separate command thread
+        recordTrack->mState = TrackBase::STARTING_1;
         mActiveTracks.add(recordTrack);
         mActiveTracksGen++;
         mLock.unlock();
         status_t status = AudioSystem::startInput(mId);
         mLock.lock();
-        // FIXME should verify that mActiveTrack is still == recordTrack
+        // FIXME should verify that recordTrack is still in mActiveTracks
         if (status != NO_ERROR) {
             mActiveTracks.remove(recordTrack);
             mActiveTracksGen++;
-            clearSyncStartEvent();
+            recordTrack->clearSyncStartEvent();
             return status;
         }
-        // FIXME LEGACY
-        mRsmpInIndex = mFrameCount;
-        mRsmpInFront = 0;
-        mRsmpInRear = 0;
-        mRsmpInUnrel = 0;
-        mBytesRead = 0;
-        if (mResampler != NULL) {
-            mResampler->reset();
+        // Catch up with current buffer indices if thread is already running.
+        // This is what makes a new client discard all buffered data.  If the track's mRsmpInFront
+        // was initialized to some value closer to the thread's mRsmpInFront, then the track could
+        // see previously buffered data before it called start(), but with greater risk of overrun.
+
+        recordTrack->mRsmpInFront = mRsmpInRear;
+        recordTrack->mRsmpInUnrel = 0;
+        // FIXME why reset?
+        if (recordTrack->mResampler != NULL) {
+            recordTrack->mResampler->reset();
         }
-        // FIXME hijacking a playback track state name which was intended for start after pause;
-        //       here 'STARTING_2' would be more accurate
-        recordTrack->mState = TrackBase::RESUMING;
+        recordTrack->mState = TrackBase::STARTING_2;
         // signal thread to start
-        ALOGV("Signal record thread");
         mWaitWorkCV.broadcast();
-        // do not wait for mStartStopCond if exiting
-        if (exitPending()) {
-            mActiveTracks.remove(recordTrack);
-            mActiveTracksGen++;
-            status = INVALID_OPERATION;
-            goto startError;
-        }
-        // FIXME incorrect usage of wait: no explicit predicate or loop
-        mStartStopCond.wait(mLock);
         if (mActiveTracks.indexOf(recordTrack) < 0) {
             ALOGV("Record failed to start");
             status = BAD_VALUE;
             goto startError;
         }
-        ALOGV("Record started OK");
         return status;
     }
 
 startError:
     AudioSystem::stopInput(mId);
-    clearSyncStartEvent();
+    recordTrack->clearSyncStartEvent();
+    // FIXME I wonder why we do not reset the state here?
     return status;
 }
 
-void AudioFlinger::RecordThread::clearSyncStartEvent()
-{
-    if (mSyncStartEvent != 0) {
-        mSyncStartEvent->cancel();
-    }
-    mSyncStartEvent.clear();
-    mFramestoDrop = 0;
-}
-
 void AudioFlinger::RecordThread::syncStartEventCallback(const wp<SyncEvent>& event)
 {
     sp<SyncEvent> strongEvent = event.promote();
 
     if (strongEvent != 0) {
-        RecordThread *me = (RecordThread *)strongEvent->cookie();
-        me->handleSyncStartEvent(strongEvent);
-    }
-}
-
-void AudioFlinger::RecordThread::handleSyncStartEvent(const sp<SyncEvent>& event)
-{
-    if (event == mSyncStartEvent) {
-        // TODO: use actual buffer filling status instead of 2 buffers when info is available
-        // from audio HAL
-        mFramestoDrop = mFrameCount * 2;
+        sp<RefBase> ptr = strongEvent->cookie().promote();
+        if (ptr != 0) {
+            RecordTrack *recordTrack = (RecordTrack *)ptr.get();
+            recordTrack->handleSyncStartEvent(strongEvent);
+        }
     }
 }
 
@@ -5151,13 +5191,9 @@
     fdprintf(fd, "\nInput thread %p:\n", this);
 
     if (mActiveTracks.size() > 0) {
-        fdprintf(fd, "  In index: %zu\n", mRsmpInIndex);
         fdprintf(fd, "  Buffer size: %zu bytes\n", mBufferSize);
-        fdprintf(fd, "  Resampling: %d\n", (mResampler != NULL));
-        fdprintf(fd, "  Out channel count: %u\n", mReqChannelCount);
-        fdprintf(fd, "  Out sample rate: %u\n", mReqSampleRate);
     } else {
-        fdprintf(fd, "  No active record client\n");
+        fdprintf(fd, "  No active record clients\n");
     }
 
     dumpBase(fd, args);
@@ -5209,15 +5245,26 @@
 }
 
 // AudioBufferProvider interface
-status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts __unused)
+status_t AudioFlinger::RecordThread::ResamplerBufferProvider::getNextBuffer(
+        AudioBufferProvider::Buffer* buffer, int64_t pts __unused)
 {
-    int32_t rear = mRsmpInRear;
-    int32_t front = mRsmpInFront;
+    RecordTrack *activeTrack = mRecordTrack;
+    sp<ThreadBase> threadBase = activeTrack->mThread.promote();
+    if (threadBase == 0) {
+        buffer->frameCount = 0;
+        buffer->raw = NULL;
+        return NOT_ENOUGH_DATA;
+    }
+    RecordThread *recordThread = (RecordThread *) threadBase.get();
+    int32_t rear = recordThread->mRsmpInRear;
+    int32_t front = activeTrack->mRsmpInFront;
     ssize_t filled = rear - front;
-    ALOG_ASSERT(0 <= filled && (size_t) filled <= mRsmpInFramesP2);
+    // FIXME should not be P2 (don't want to increase latency)
+    // FIXME if client not keeping up, discard
+    LOG_ALWAYS_FATAL_IF(!(0 <= filled && (size_t) filled <= recordThread->mRsmpInFrames));
     // 'filled' may be non-contiguous, so return only the first contiguous chunk
-    front &= mRsmpInFramesP2 - 1;
-    size_t part1 = mRsmpInFramesP2 - front;
+    front &= recordThread->mRsmpInFramesP2 - 1;
+    size_t part1 = recordThread->mRsmpInFramesP2 - front;
     if (part1 > (size_t) filled) {
         part1 = filled;
     }
@@ -5228,29 +5275,31 @@
     }
     if (part1 == 0) {
         // Higher-level should keep mRsmpInBuffer full, and not call resampler if empty
-        ALOGE("RecordThread::getNextBuffer() starved");
+        LOG_ALWAYS_FATAL("RecordThread::getNextBuffer() starved");
         buffer->raw = NULL;
         buffer->frameCount = 0;
-        mRsmpInUnrel = 0;
+        activeTrack->mRsmpInUnrel = 0;
         return NOT_ENOUGH_DATA;
     }
 
-    buffer->raw = mRsmpInBuffer + front * mChannelCount;
+    buffer->raw = recordThread->mRsmpInBuffer + front * recordThread->mChannelCount;
     buffer->frameCount = part1;
-    mRsmpInUnrel = part1;
+    activeTrack->mRsmpInUnrel = part1;
     return NO_ERROR;
 }
 
 // AudioBufferProvider interface
-void AudioFlinger::RecordThread::releaseBuffer(AudioBufferProvider::Buffer* buffer)
+void AudioFlinger::RecordThread::ResamplerBufferProvider::releaseBuffer(
+        AudioBufferProvider::Buffer* buffer)
 {
+    RecordTrack *activeTrack = mRecordTrack;
     size_t stepCount = buffer->frameCount;
     if (stepCount == 0) {
         return;
     }
-    ALOG_ASSERT(stepCount <= mRsmpInUnrel);
-    mRsmpInUnrel -= stepCount;
-    mRsmpInFront += stepCount;
+    ALOG_ASSERT(stepCount <= activeTrack->mRsmpInUnrel);
+    activeTrack->mRsmpInUnrel -= stepCount;
+    activeTrack->mRsmpInFront += stepCount;
     buffer->raw = NULL;
     buffer->frameCount = 0;
 }
@@ -5265,11 +5314,14 @@
         AudioParameter param = AudioParameter(keyValuePair);
         int value;
         audio_format_t reqFormat = mFormat;
-        uint32_t reqSamplingRate = mReqSampleRate;
-        audio_channel_mask_t reqChannelMask = audio_channel_in_mask_from_count(mReqChannelCount);
+        uint32_t samplingRate = mSampleRate;
+        audio_channel_mask_t channelMask = audio_channel_in_mask_from_count(mChannelCount);
 
+        // TODO Investigate when this code runs. Check with audio policy when a sample rate and
+        //      channel count change can be requested. Do we mandate the first client defines the
+        //      HAL sampling rate and channel count or do we allow changes on the fly?
         if (param.getInt(String8(AudioParameter::keySamplingRate), value) == NO_ERROR) {
-            reqSamplingRate = value;
+            samplingRate = value;
             reconfig = true;
         }
         if (param.getInt(String8(AudioParameter::keyFormat), value) == NO_ERROR) {
@@ -5285,7 +5337,7 @@
             if (mask != AUDIO_CHANNEL_IN_MONO && mask != AUDIO_CHANNEL_IN_STEREO) {
                 status = BAD_VALUE;
             } else {
-                reqChannelMask = mask;
+                channelMask = mask;
                 reconfig = true;
             }
         }
@@ -5350,15 +5402,15 @@
                     reqFormat == mInput->stream->common.get_format(&mInput->stream->common) &&
                     reqFormat == AUDIO_FORMAT_PCM_16_BIT &&
                     (mInput->stream->common.get_sample_rate(&mInput->stream->common)
-                            <= (2 * reqSamplingRate)) &&
+                            <= (2 * samplingRate)) &&
                     popcount(mInput->stream->common.get_channels(&mInput->stream->common))
                             <= FCC_2 &&
-                    (reqChannelMask == AUDIO_CHANNEL_IN_MONO ||
-                            reqChannelMask == AUDIO_CHANNEL_IN_STEREO)) {
+                    (channelMask == AUDIO_CHANNEL_IN_MONO ||
+                            channelMask == AUDIO_CHANNEL_IN_STEREO)) {
                     status = NO_ERROR;
                 }
                 if (status == NO_ERROR) {
-                    readInputParameters();
+                    readInputParameters_l();
                     sendIoConfigEvent_l(AudioSystem::INPUT_CONFIG_CHANGED);
                 }
             }
@@ -5410,15 +5462,8 @@
     mAudioFlinger->audioConfigChanged_l(event, mId, param2);
 }
 
-void AudioFlinger::RecordThread::readInputParameters()
+void AudioFlinger::RecordThread::readInputParameters_l()
 {
-    delete[] mRsmpInBuffer;
-    // mRsmpInBuffer is always assigned a new[] below
-    delete[] mRsmpOutBuffer;
-    mRsmpOutBuffer = NULL;
-    delete mResampler;
-    mResampler = NULL;
-
     mSampleRate = mInput->stream->common.get_sample_rate(&mInput->stream->common);
     mChannelMask = mInput->stream->common.get_channels(&mInput->stream->common);
     mChannelCount = popcount(mChannelMask);
@@ -5429,24 +5474,20 @@
     mFrameSize = audio_stream_frame_size(&mInput->stream->common);
     mBufferSize = mInput->stream->common.get_buffer_size(&mInput->stream->common);
     mFrameCount = mBufferSize / mFrameSize;
+    // This is the formula for calculating the temporary buffer size.
     // With 3 HAL buffers, we can guarantee ability to down-sample the input by ratio of 2:1 to
     // 1 full output buffer, regardless of the alignment of the available input.
+    // The "3" is somewhat arbitrary, and could probably be larger.
+    // A larger value should allow more old data to be read after a track calls start(),
+    // without increasing latency.
     mRsmpInFrames = mFrameCount * 3;
     mRsmpInFramesP2 = roundup(mRsmpInFrames);
+    delete[] mRsmpInBuffer;
     // Over-allocate beyond mRsmpInFramesP2 to permit a HAL read past end of buffer
     mRsmpInBuffer = new int16_t[(mRsmpInFramesP2 + mFrameCount - 1) * mChannelCount];
-    mRsmpInFront = 0;
-    mRsmpInRear = 0;
-    mRsmpInUnrel = 0;
 
-    if (mSampleRate != mReqSampleRate && mChannelCount <= FCC_2 && mReqChannelCount <= FCC_2) {
-        mResampler = AudioResampler::create(16, (int) mChannelCount, mReqSampleRate);
-        mResampler->setSampleRate(mSampleRate);
-        mResampler->setVolume(AudioMixer::UNITY_GAIN, AudioMixer::UNITY_GAIN);
-        // resampler always outputs stereo
-        mRsmpOutBuffer = new int32_t[mFrameCount * FCC_2];
-    }
-    mRsmpInIndex = mFrameCount;
+    // AudioRecord mSampleRate and mChannelCount are constant due to AudioRecord API constraints.
+    // But if thread's mSampleRate or mChannelCount changes, how will that affect active tracks?
 }
 
 uint32_t AudioFlinger::RecordThread::getInputFramesLost()
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 999fea3..fa3563c 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -270,8 +270,8 @@
 
                 const sp<AudioFlinger>  mAudioFlinger;
 
-                // updated by PlaybackThread::readOutputParameters() or
-                // RecordThread::readInputParameters()
+                // updated by PlaybackThread::readOutputParameters_l() or
+                // RecordThread::readInputParameters_l()
                 uint32_t                mSampleRate;
                 size_t                  mFrameCount;       // output HAL, direct output, record
                 audio_channel_mask_t    mChannelMask;
@@ -478,7 +478,7 @@
                 status_t         getTimestamp_l(AudioTimestamp& timestamp);
 
 protected:
-    // updated by readOutputParameters()
+    // updated by readOutputParameters_l()
     size_t                          mNormalFrameCount;  // normal mixer and effects
 
     int16_t*                        mMixBuffer;         // frame size aligned mix buffer
@@ -541,7 +541,7 @@
     void        removeTrack_l(const sp<Track>& track);
     void        broadcast_l();
 
-    void        readOutputParameters();
+    void        readOutputParameters_l();
 
     virtual void dumpInternals(int fd, const Vector<String16>& args);
     void        dumpTracks(int fd, const Vector<String16>& args);
@@ -839,17 +839,28 @@
 
 
 // record thread
-class RecordThread : public ThreadBase, public AudioBufferProvider
-                        // derives from AudioBufferProvider interface for use by resampler
+class RecordThread : public ThreadBase
 {
 public:
 
+    class RecordTrack;
+    class ResamplerBufferProvider : public AudioBufferProvider
+                        // derives from AudioBufferProvider interface for use by resampler
+    {
+    public:
+        ResamplerBufferProvider(RecordTrack* recordTrack) : mRecordTrack(recordTrack) { }
+        virtual ~ResamplerBufferProvider() { }
+        // AudioBufferProvider interface
+        virtual status_t    getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts);
+        virtual void        releaseBuffer(AudioBufferProvider::Buffer* buffer);
+    private:
+        RecordTrack * const mRecordTrack;
+    };
+
 #include "RecordTracks.h"
 
             RecordThread(const sp<AudioFlinger>& audioFlinger,
                     AudioStreamIn *input,
-                    uint32_t sampleRate,
-                    audio_channel_mask_t channelMask,
                     audio_io_handle_t id,
                     audio_devices_t outDevice,
                     audio_devices_t inDevice
@@ -898,14 +909,11 @@
             AudioStreamIn* clearInput();
             virtual audio_stream_t* stream() const;
 
-    // AudioBufferProvider interface
-    virtual status_t    getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts);
-    virtual void        releaseBuffer(AudioBufferProvider::Buffer* buffer);
 
     virtual bool        checkForNewParameters_l();
     virtual String8     getParameters(const String8& keys);
     virtual void        audioConfigChanged_l(int event, int param = 0);
-            void        readInputParameters();
+            void        readInputParameters_l();
     virtual uint32_t    getInputFramesLost();
 
     virtual status_t addEffectChain_l(const sp<EffectChain>& chain);
@@ -921,14 +929,11 @@
     virtual bool     isValidSyncEvent(const sp<SyncEvent>& event) const;
 
     static void syncStartEventCallback(const wp<SyncEvent>& event);
-           void handleSyncStartEvent(const sp<SyncEvent>& event);
 
     virtual size_t      frameCount() const { return mFrameCount; }
             bool        hasFastRecorder() const { return false; }
 
 private:
-            void    clearSyncStartEvent();
-
             // Enter standby if not already in standby, and set mStandby flag
             void    standbyIfNotAlreadyInStandby();
 
@@ -944,34 +949,13 @@
             int                                 mActiveTracksGen;
             Condition                           mStartStopCond;
 
-            // updated by RecordThread::readInputParameters()
-            AudioResampler                      *mResampler;
-            // interleaved stereo pairs of fixed-point signed Q19.12
-            int32_t                             *mRsmpOutBuffer;
-
             // resampler converts input at HAL Hz to output at AudioRecord client Hz
             int16_t                             *mRsmpInBuffer; // see new[] for details on the size
             size_t                              mRsmpInFrames;  // size of resampler input in frames
             size_t                              mRsmpInFramesP2;// size rounded up to a power-of-2
-            size_t                              mRsmpInUnrel;   // unreleased frames remaining from
-                                                                // most recent getNextBuffer
-            // these are rolling counters that are never cleared
-            int32_t                             mRsmpInFront;   // next available frame
+
+            // rolling index that is never cleared
             int32_t                             mRsmpInRear;    // last filled frame + 1
-            size_t                              mRsmpInIndex;   // FIXME legacy
-
-            // client's requested configuration, which may differ from the HAL configuration
-            const uint32_t                      mReqChannelCount;
-            const uint32_t                      mReqSampleRate;
-
-            ssize_t                             mBytesRead;
-            // sync event triggering actual audio capture. Frames read before this event will
-            // be dropped and therefore not read by the application.
-            sp<SyncEvent>                       mSyncStartEvent;
-            // number of captured frames to drop after the start sync event has been received.
-            // when < 0, maximum frames to drop before starting capture even if sync event is
-            // not received
-            ssize_t                             mFramestoDrop;
 
             // For dumpsys
             const sp<NBAIO_Sink>                mTeeSink;
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index 05fde7c..58705c4 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -34,7 +34,9 @@
         RESUMING,
         ACTIVE,
         PAUSING,
-        PAUSED
+        PAUSED,
+        STARTING_1,     // for RecordTrack only
+        STARTING_2,     // for RecordTrack only
     };
 
                         TrackBase(ThreadBase *thread,
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index e5152b8..92ed46a 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -1785,17 +1785,34 @@
             int uid)
     :   TrackBase(thread, client, sampleRate, format,
                   channelMask, frameCount, 0 /*sharedBuffer*/, sessionId, uid, false /*isOut*/),
-        mOverflow(false)
+        mOverflow(false), mResampler(NULL), mRsmpOutBuffer(NULL), mRsmpOutFrameCount(0),
+        // See real initialization of mRsmpInFront at RecordThread::start()
+        mRsmpInUnrel(0), mRsmpInFront(0), mFramesToDrop(0), mResamplerBufferProvider(NULL)
 {
     ALOGV("RecordTrack constructor");
     if (mCblk != NULL) {
         mServerProxy = new AudioRecordServerProxy(mCblk, mBuffer, frameCount, mFrameSize);
     }
+
+    uint32_t channelCount = popcount(channelMask);
+    // FIXME I don't understand either of the channel count checks
+    if (thread->mSampleRate != sampleRate && thread->mChannelCount <= FCC_2 &&
+            channelCount <= FCC_2) {
+        // sink SR
+        mResampler = AudioResampler::create(16, thread->mChannelCount, sampleRate);
+        // source SR
+        mResampler->setSampleRate(thread->mSampleRate);
+        mResampler->setVolume(AudioMixer::UNITY_GAIN, AudioMixer::UNITY_GAIN);
+        mResamplerBufferProvider = new ResamplerBufferProvider(this);
+    }
 }
 
 AudioFlinger::RecordThread::RecordTrack::~RecordTrack()
 {
     ALOGV("%s", __func__);
+    delete mResampler;
+    delete[] mRsmpOutBuffer;
+    delete mResamplerBufferProvider;
 }
 
 // AudioBufferProvider interface
@@ -1868,12 +1885,12 @@
 
 /*static*/ void AudioFlinger::RecordThread::RecordTrack::appendDumpHeader(String8& result)
 {
-    result.append("    Active Client Fmt Chn mask Session S   Server fCount\n");
+    result.append("    Active Client Fmt Chn mask Session S   Server fCount Resampling\n");
 }
 
 void AudioFlinger::RecordThread::RecordTrack::dump(char* buffer, size_t size, bool active)
 {
-    snprintf(buffer, size, "    %6s %6u %3u %08X %7u %1d %08X %6zu\n",
+    snprintf(buffer, size, "    %6s %6u %3u %08X %7u %1d %08X %6zu %10d\n",
             active ? "yes" : "no",
             (mClient == 0) ? getpid_cached : mClient->pid(),
             mFormat,
@@ -1881,7 +1898,32 @@
             mSessionId,
             mState,
             mCblk->mServer,
-            mFrameCount);
+            mFrameCount,
+            mResampler != NULL);
+
+}
+
+void AudioFlinger::RecordThread::RecordTrack::handleSyncStartEvent(const sp<SyncEvent>& event)
+{
+    if (event == mSyncStartEvent) {
+        ssize_t framesToDrop = 0;
+        sp<ThreadBase> threadBase = mThread.promote();
+        if (threadBase != 0) {
+            // TODO: use actual buffer filling status instead of 2 buffers when info is available
+            // from audio HAL
+            framesToDrop = threadBase->mFrameCount * 2;
+        }
+        mFramesToDrop = framesToDrop;
+    }
+}
+
+void AudioFlinger::RecordThread::RecordTrack::clearSyncStartEvent()
+{
+    if (mSyncStartEvent != 0) {
+        mSyncStartEvent->cancel();
+        mSyncStartEvent.clear();
+    }
+    mFramesToDrop = 0;
 }
 
 }; // namespace android
diff --git a/services/audioflinger/test-resample.cpp b/services/audioflinger/test-resample.cpp
index 66fcd90..3ab3ba9 100644
--- a/services/audioflinger/test-resample.cpp
+++ b/services/audioflinger/test-resample.cpp
@@ -27,6 +27,7 @@
 #include <time.h>
 #include <math.h>
 #include <audio_utils/sndfile.h>
+#include <utils/Vector.h>
 
 using namespace android;
 
@@ -34,7 +35,7 @@
 
 static int usage(const char* name) {
     fprintf(stderr,"Usage: %s [-p] [-h] [-v] [-s] [-q {dq|lq|mq|hq|vhq|dlq|dmq|dhq}]"
-                   " [-i input-sample-rate] [-o output-sample-rate] [<input-file>]"
+                   " [-i input-sample-rate] [-o output-sample-rate] [-O csv] [-P csv] [<input-file>]"
                    " <output-file>\n", name);
     fprintf(stderr,"    -p    enable profiling\n");
     fprintf(stderr,"    -h    create wav file\n");
@@ -51,9 +52,50 @@
     fprintf(stderr,"              dhq : dynamic high quality\n");
     fprintf(stderr,"    -i    input file sample rate (ignored if input file is specified)\n");
     fprintf(stderr,"    -o    output file sample rate\n");
+    fprintf(stderr,"    -O    # frames output per call to resample() in CSV format\n");
+    fprintf(stderr,"    -P    # frames provided per call to resample() in CSV format\n");
     return -1;
 }
 
+// Convert a list of integers in CSV format to a Vector of those values.
+// Returns the number of elements in the list, or -1 on error.
+int parseCSV(const char *string, Vector<int>& values)
+{
+    // pass 1: count the number of values and do syntax check
+    size_t numValues = 0;
+    bool hadDigit = false;
+    for (const char *p = string; ; ) {
+        switch (*p++) {
+        case '0': case '1': case '2': case '3': case '4':
+        case '5': case '6': case '7': case '8': case '9':
+            hadDigit = true;
+            break;
+        case '\0':
+            if (hadDigit) {
+                // pass 2: allocate and initialize vector of values
+                values.resize(++numValues);
+                values.editItemAt(0) = atoi(p = optarg);
+                for (size_t i = 1; i < numValues; ) {
+                    if (*p++ == ',') {
+                        values.editItemAt(i++) = atoi(p);
+                    }
+                }
+                return numValues;
+            }
+            // fall through
+        case ',':
+            if (hadDigit) {
+                hadDigit = false;
+                numValues++;
+                break;
+            }
+            // fall through
+        default:
+            return -1;
+        }
+    }
+}
+
 int main(int argc, char* argv[]) {
 
     const char* const progname = argv[0];
@@ -64,9 +106,11 @@
     int input_freq = 0;
     int output_freq = 0;
     AudioResampler::src_quality quality = AudioResampler::DEFAULT_QUALITY;
+    Vector<int> Ovalues;
+    Vector<int> Pvalues;
 
     int ch;
-    while ((ch = getopt(argc, argv, "pfhvsq:i:o:")) != -1) {
+    while ((ch = getopt(argc, argv, "pfhvsq:i:o:O:P:")) != -1) {
         switch (ch) {
         case 'p':
             profileResample = true;
@@ -111,6 +155,18 @@
         case 'o':
             output_freq = atoi(optarg);
             break;
+        case 'O':
+            if (parseCSV(optarg, Ovalues) < 0) {
+                fprintf(stderr, "incorrect syntax for -O option\n");
+                return -1;
+            }
+            break;
+        case 'P':
+            if (parseCSV(optarg, Pvalues) < 0) {
+                fprintf(stderr, "incorrect syntax for -P option\n");
+                return -1;
+            }
+            break;
         case '?':
         default:
             usage(progname);
@@ -177,12 +233,14 @@
         const int       mChannels;
         size_t          mNextFrame; // index of next frame to provide
         size_t          mUnrel;     // number of frames not yet released
+        const Vector<int> mPvalues; // number of frames provided per call
+        size_t          mNextPidx;  // index of next entry in mPvalues to use
     public:
-        Provider(const void* addr, size_t size, int channels)
+        Provider(const void* addr, size_t size, int channels, const Vector<int>& Pvalues)
           : mAddr((int16_t*) addr),
             mNumFrames(size / (channels*sizeof(int16_t))),
             mChannels(channels),
-            mNextFrame(0), mUnrel(0) {
+            mNextFrame(0), mUnrel(0), mPvalues(Pvalues), mNextPidx(0) {
         }
         virtual status_t getNextBuffer(Buffer* buffer,
                 int64_t pts = kInvalidPTS) {
@@ -191,6 +249,16 @@
             if (requestedFrames > mNumFrames - mNextFrame) {
                 buffer->frameCount = mNumFrames - mNextFrame;
             }
+            if (!mPvalues.isEmpty()) {
+                size_t provided = mPvalues[mNextPidx++];
+                printf("mPvalue[%d]=%u not %u\n", mNextPidx-1, provided, buffer->frameCount);
+                if (provided < buffer->frameCount) {
+                    buffer->frameCount = provided;
+                }
+                if (mNextPidx >= mPvalues.size()) {
+                    mNextPidx = 0;
+                }
+            }
             if (gVerbose) {
                 printf("getNextBuffer() requested %u frames out of %u frames available,"
                         " and returned %u frames\n",
@@ -225,7 +293,7 @@
         void reset() {
             mNextFrame = 0;
         }
-    } provider(input_vaddr, input_size, channels);
+    } provider(input_vaddr, input_size, channels, Pvalues);
 
     size_t input_frames = input_size / (channels * sizeof(int16_t));
     if (gVerbose) {
@@ -343,7 +411,20 @@
     if (gVerbose) {
         printf("resample() %u output frames\n", out_frames);
     }
-    resampler->resample((int*) output_vaddr, out_frames, &provider);
+    if (Ovalues.isEmpty()) {
+        Ovalues.push(out_frames);
+    }
+    for (size_t i = 0, j = 0; i < out_frames; ) {
+        size_t thisFrames = Ovalues[j++];
+        if (j >= Ovalues.size()) {
+            j = 0;
+        }
+        if (thisFrames == 0 || thisFrames > out_frames - i) {
+            thisFrames = out_frames - i;
+        }
+        resampler->resample((int*) output_vaddr + 2*i, thisFrames, &provider);
+        i += thisFrames;
+    }
     if (gVerbose) {
         printf("resample() complete\n");
     }
diff --git a/services/camera/libcameraservice/api1/Camera2Client.cpp b/services/camera/libcameraservice/api1/Camera2Client.cpp
index 0a88a75..80b7cd4 100644
--- a/services/camera/libcameraservice/api1/Camera2Client.cpp
+++ b/services/camera/libcameraservice/api1/Camera2Client.cpp
@@ -407,12 +407,6 @@
         l.mParameters.state = Parameters::DISCONNECTED;
     }
 
-    mStreamingProcessor->deletePreviewStream();
-    mStreamingProcessor->deleteRecordingStream();
-    mJpegProcessor->deleteStream();
-    mCallbackProcessor->deleteStream();
-    mZslProcessor->deleteStream();
-
     mStreamingProcessor->requestExit();
     mFrameProcessor->requestExit();
     mCaptureSequencer->requestExit();
@@ -429,6 +423,14 @@
     mZslProcessorThread->join();
     mCallbackProcessor->join();
 
+    ALOGV("Camera %d: Deleting streams", mCameraId);
+
+    mStreamingProcessor->deletePreviewStream();
+    mStreamingProcessor->deleteRecordingStream();
+    mJpegProcessor->deleteStream();
+    mCallbackProcessor->deleteStream();
+    mZslProcessor->deleteStream();
+
     ALOGV("Camera %d: Disconnecting device", mCameraId);
 
     mDevice->disconnect();