am 72215491: am 1a475921: am 223fd5c9: audio: new routing strategies and stream types

* commit '72215491c60fbcdb9a2f0be782e24e39cca249c5':
  audio: new routing strategies and stream types
diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk
index 561ce02..78c89bb 100644
--- a/cmds/stagefright/Android.mk
+++ b/cmds/stagefright/Android.mk
@@ -169,6 +169,44 @@
 
 include $(CLEAR_VARS)
 
+LOCAL_NDK_STL_VARIANT := stlport_static
+
+LOCAL_SRC_FILES:= \
+	filters/argbtorgba.rs \
+	filters/nightvision.rs \
+	filters/saturation.rs \
+	mediafilter.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+	libstagefright liblog libutils libbinder libstagefright_foundation \
+	libmedia libgui libcutils libui
+
+LOCAL_C_INCLUDES:= \
+	$(TOP)/bionic \
+	$(TOP)/external/stlport/stlport \
+	$(TOP)/frameworks/av/media/libstagefright \
+	$(TOP)/frameworks/native/include/media/openmax \
+	$(TOP)/frameworks/rs/cpp \
+	$(TOP)/frameworks/rs \
+
+intermediates := $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,)
+LOCAL_C_INCLUDES += $(intermediates)
+
+LOCAL_STATIC_LIBRARIES:= \
+	libstagefright_mediafilter
+
+LOCAL_CFLAGS += -Wno-multichar
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE:= mediafilter
+
+include $(BUILD_EXECUTABLE)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
 LOCAL_SRC_FILES:=               \
         muxer.cpp            \
 
diff --git a/cmds/stagefright/codec.cpp b/cmds/stagefright/codec.cpp
index fd02bcc..d987250 100644
--- a/cmds/stagefright/codec.cpp
+++ b/cmds/stagefright/codec.cpp
@@ -45,9 +45,10 @@
     fprintf(stderr, "usage: %s [-a] use audio\n"
                     "\t\t[-v] use video\n"
                     "\t\t[-p] playback\n"
-                    "\t\t[-S] allocate buffers from a surface\n",
+                    "\t\t[-S] allocate buffers from a surface\n"
+                    "\t\t[-R] render output to surface (enables -S)\n"
+                    "\t\t[-T] use render timestamps (enables -R)\n",
                     me);
-
     exit(1);
 }
 
@@ -71,7 +72,9 @@
         const char *path,
         bool useAudio,
         bool useVideo,
-        const android::sp<android::Surface> &surface) {
+        const android::sp<android::Surface> &surface,
+        bool renderSurface,
+        bool useTimestamp) {
     using namespace android;
 
     static int64_t kTimeout = 500ll;
@@ -136,6 +139,7 @@
     CHECK(!stateByTrack.isEmpty());
 
     int64_t startTimeUs = ALooper::GetNowUs();
+    int64_t startTimeRender = -1;
 
     for (size_t i = 0; i < stateByTrack.size(); ++i) {
         CodecState *state = &stateByTrack.editValueAt(i);
@@ -260,7 +264,23 @@
                 ++state->mNumBuffersDecoded;
                 state->mNumBytesDecoded += size;
 
-                err = state->mCodec->releaseOutputBuffer(index);
+                if (surface == NULL || !renderSurface) {
+                    err = state->mCodec->releaseOutputBuffer(index);
+                } else if (useTimestamp) {
+                    if (startTimeRender == -1) {
+                        // begin rendering 2 vsyncs (~33ms) after first decode
+                        startTimeRender =
+                                systemTime(SYSTEM_TIME_MONOTONIC) + 33000000
+                                - (presentationTimeUs * 1000);
+                    }
+                    presentationTimeUs =
+                            (presentationTimeUs * 1000) + startTimeRender;
+                    err = state->mCodec->renderOutputBufferAndRelease(
+                            index, presentationTimeUs);
+                } else {
+                    err = state->mCodec->renderOutputBufferAndRelease(index);
+                }
+
                 CHECK_EQ(err, (status_t)OK);
 
                 if (flags & MediaCodec::BUFFER_FLAG_EOS) {
@@ -320,34 +340,42 @@
     bool useVideo = false;
     bool playback = false;
     bool useSurface = false;
+    bool renderSurface = false;
+    bool useTimestamp = false;
 
     int res;
-    while ((res = getopt(argc, argv, "havpSD")) >= 0) {
+    while ((res = getopt(argc, argv, "havpSDRT")) >= 0) {
         switch (res) {
             case 'a':
             {
                 useAudio = true;
                 break;
             }
-
             case 'v':
             {
                 useVideo = true;
                 break;
             }
-
             case 'p':
             {
                 playback = true;
                 break;
             }
-
+            case 'T':
+            {
+                useTimestamp = true;
+            }
+            // fall through
+            case 'R':
+            {
+                renderSurface = true;
+            }
+            // fall through
             case 'S':
             {
                 useSurface = true;
                 break;
             }
-
             case '?':
             case 'h':
             default:
@@ -422,7 +450,8 @@
         player->stop();
         player->reset();
     } else {
-        decode(looper, argv[0], useAudio, useVideo, surface);
+        decode(looper, argv[0], useAudio, useVideo, surface, renderSurface,
+                useTimestamp);
     }
 
     if (playback || (useSurface && useVideo)) {
diff --git a/cmds/stagefright/filters/argbtorgba.rs b/cmds/stagefright/filters/argbtorgba.rs
new file mode 100644
index 0000000..229ff8c
--- /dev/null
+++ b/cmds/stagefright/filters/argbtorgba.rs
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.rs.cppbasic)
+#pragma rs_fp_relaxed
+
+void root(const uchar4 *v_in, uchar4 *v_out) {
+    v_out->x = v_in->y;
+    v_out->y = v_in->z;
+    v_out->z = v_in->w;
+    v_out->w = v_in->x;
+}
\ No newline at end of file
diff --git a/cmds/stagefright/filters/nightvision.rs b/cmds/stagefright/filters/nightvision.rs
new file mode 100644
index 0000000..f61413c
--- /dev/null
+++ b/cmds/stagefright/filters/nightvision.rs
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.rs.cppbasic)
+#pragma rs_fp_relaxed
+
+const static float3 gMonoMult = {0.299f, 0.587f, 0.114f};
+const static float3 gNightVisionMult = {0.5f, 1.f, 0.5f};
+
+// calculates luminance of pixel, then biases color balance toward green
+void root(const uchar4 *v_in, uchar4 *v_out) {
+    v_out->x = v_in->x; // don't modify A
+
+    // get RGB, scale 0-255 uchar to 0-1.0 float
+    float3 rgb = {v_in->y * 0.003921569f, v_in->z * 0.003921569f,
+            v_in->w * 0.003921569f};
+
+    // apply filter
+    float3 result = dot(rgb, gMonoMult) * gNightVisionMult;
+
+    v_out->y = (uchar)clamp((result.r * 255.f + 0.5f), 0.f, 255.f);
+    v_out->z = (uchar)clamp((result.g * 255.f + 0.5f), 0.f, 255.f);
+    v_out->w = (uchar)clamp((result.b * 255.f + 0.5f), 0.f, 255.f);
+}
diff --git a/cmds/stagefright/filters/saturation.rs b/cmds/stagefright/filters/saturation.rs
new file mode 100644
index 0000000..1de9dd8
--- /dev/null
+++ b/cmds/stagefright/filters/saturation.rs
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.rs.cppbasic)
+#pragma rs_fp_relaxed
+
+const static float3 gMonoMult = {0.299f, 0.587f, 0.114f};
+
+// global variables (parameters accessible to application code)
+float gSaturation = 1.0f;
+
+void root(const uchar4 *v_in, uchar4 *v_out) {
+    v_out->x = v_in->x; // don't modify A
+
+    // get RGB, scale 0-255 uchar to 0-1.0 float
+    float3 rgb = {v_in->y * 0.003921569f, v_in->z * 0.003921569f,
+            v_in->w * 0.003921569f};
+
+    // apply saturation filter
+    float3 result = dot(rgb, gMonoMult);
+    result = mix(result, rgb, gSaturation);
+
+    v_out->y = (uchar)clamp((result.r * 255.f + 0.5f), 0.f, 255.f);
+    v_out->z = (uchar)clamp((result.g * 255.f + 0.5f), 0.f, 255.f);
+    v_out->w = (uchar)clamp((result.b * 255.f + 0.5f), 0.f, 255.f);
+}
diff --git a/cmds/stagefright/mediafilter.cpp b/cmds/stagefright/mediafilter.cpp
new file mode 100644
index 0000000..f77b38b
--- /dev/null
+++ b/cmds/stagefright/mediafilter.cpp
@@ -0,0 +1,785 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "mediafilterTest"
+
+#include <inttypes.h>
+
+#include <binder/ProcessState.h>
+#include <filters/ColorConvert.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/Surface.h>
+#include <media/ICrypto.h>
+#include <media/IMediaHTTPService.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaCodec.h>
+#include <media/stagefright/NuMediaExtractor.h>
+#include <media/stagefright/RenderScriptWrapper.h>
+#include <OMX_IVCommon.h>
+#include <ui/DisplayInfo.h>
+
+#include "RenderScript.h"
+#include "ScriptC_argbtorgba.h"
+#include "ScriptC_nightvision.h"
+#include "ScriptC_saturation.h"
+
+// test parameters
+static const bool kTestFlush = true;        // Note: true will drop 1 out of
+static const int kFlushAfterFrames = 25;    // kFlushAfterFrames output frames
+static const int64_t kTimeout = 500ll;
+
+// built-in filter parameters
+static const int32_t kInvert = false;   // ZeroFilter param
+static const float kBlurRadius = 15.0f; // IntrinsicBlurFilter param
+static const float kSaturation = 0.0f;  // SaturationFilter param
+
+static void usage(const char *me) {
+    fprintf(stderr, "usage: [flags] %s\n"
+                    "\t[-b] use IntrinsicBlurFilter\n"
+                    "\t[-c] use argb to rgba conversion RSFilter\n"
+                    "\t[-n] use night vision RSFilter\n"
+                    "\t[-r] use saturation RSFilter\n"
+                    "\t[-s] use SaturationFilter\n"
+                    "\t[-z] use ZeroFilter (copy filter)\n"
+                    "\t[-R] render output to surface (enables -S)\n"
+                    "\t[-S] allocate buffers from a surface\n"
+                    "\t[-T] use render timestamps (enables -R)\n",
+                    me);
+    exit(1);
+}
+
+namespace android {
+
+struct SaturationRSFilter : RenderScriptWrapper::RSFilterCallback {
+    void init(RSC::sp<RSC::RS> context) {
+        mScript = new ScriptC_saturation(context);
+        mScript->set_gSaturation(3.f);
+    }
+
+    virtual status_t processBuffers(
+            RSC::Allocation *inBuffer, RSC::Allocation *outBuffer) {
+        mScript->forEach_root(inBuffer, outBuffer);
+
+        return OK;
+    }
+
+    status_t handleSetParameters(const sp<AMessage> &msg) {
+        return OK;
+    }
+
+private:
+    RSC::sp<ScriptC_saturation> mScript;
+};
+
+struct NightVisionRSFilter : RenderScriptWrapper::RSFilterCallback {
+    void init(RSC::sp<RSC::RS> context) {
+        mScript = new ScriptC_nightvision(context);
+    }
+
+    virtual status_t processBuffers(
+            RSC::Allocation *inBuffer, RSC::Allocation *outBuffer) {
+        mScript->forEach_root(inBuffer, outBuffer);
+
+        return OK;
+    }
+
+    status_t handleSetParameters(const sp<AMessage> &msg) {
+        return OK;
+    }
+
+private:
+    RSC::sp<ScriptC_nightvision> mScript;
+};
+
+struct ARGBToRGBARSFilter : RenderScriptWrapper::RSFilterCallback {
+    void init(RSC::sp<RSC::RS> context) {
+        mScript = new ScriptC_argbtorgba(context);
+    }
+
+    virtual status_t processBuffers(
+            RSC::Allocation *inBuffer, RSC::Allocation *outBuffer) {
+        mScript->forEach_root(inBuffer, outBuffer);
+
+        return OK;
+    }
+
+    status_t handleSetParameters(const sp<AMessage> &msg) {
+        return OK;
+    }
+
+private:
+    RSC::sp<ScriptC_argbtorgba> mScript;
+};
+
+struct CodecState {
+    sp<MediaCodec> mCodec;
+    Vector<sp<ABuffer> > mInBuffers;
+    Vector<sp<ABuffer> > mOutBuffers;
+    bool mSignalledInputEOS;
+    bool mSawOutputEOS;
+    int64_t mNumBuffersDecoded;
+};
+
+struct DecodedFrame {
+    size_t index;
+    size_t offset;
+    size_t size;
+    int64_t presentationTimeUs;
+    uint32_t flags;
+};
+
+enum FilterType {
+    FILTERTYPE_ZERO,
+    FILTERTYPE_INTRINSIC_BLUR,
+    FILTERTYPE_SATURATION,
+    FILTERTYPE_RS_SATURATION,
+    FILTERTYPE_RS_NIGHT_VISION,
+    FILTERTYPE_RS_ARGB_TO_RGBA,
+};
+
+size_t inputFramesSinceFlush = 0;
+void tryCopyDecodedBuffer(
+        List<DecodedFrame> *decodedFrameIndices,
+        CodecState *filterState,
+        CodecState *vidState) {
+    if (decodedFrameIndices->empty()) {
+        return;
+    }
+
+    size_t filterIndex;
+    status_t err = filterState->mCodec->dequeueInputBuffer(
+            &filterIndex, kTimeout);
+    if (err != OK) {
+        return;
+    }
+
+    ++inputFramesSinceFlush;
+
+    DecodedFrame frame = *decodedFrameIndices->begin();
+
+    // only consume a buffer if we are not going to flush, since we expect
+    // the dequeue -> flush -> queue operation to cause an error and
+    // not produce an output frame
+    if (!kTestFlush || inputFramesSinceFlush < kFlushAfterFrames) {
+        decodedFrameIndices->erase(decodedFrameIndices->begin());
+    }
+    size_t outIndex = frame.index;
+
+    const sp<ABuffer> &srcBuffer =
+        vidState->mOutBuffers.itemAt(outIndex);
+    const sp<ABuffer> &destBuffer =
+        filterState->mInBuffers.itemAt(filterIndex);
+
+    sp<AMessage> srcFormat, destFormat;
+    vidState->mCodec->getOutputFormat(&srcFormat);
+    filterState->mCodec->getInputFormat(&destFormat);
+
+    int32_t srcWidth, srcHeight, srcStride, srcSliceHeight;
+    int32_t srcColorFormat, destColorFormat;
+    int32_t destWidth, destHeight, destStride, destSliceHeight;
+    CHECK(srcFormat->findInt32("stride", &srcStride)
+            && srcFormat->findInt32("slice-height", &srcSliceHeight)
+            && srcFormat->findInt32("width", &srcWidth)
+            && srcFormat->findInt32("height", & srcHeight)
+            && srcFormat->findInt32("color-format", &srcColorFormat));
+    CHECK(destFormat->findInt32("stride", &destStride)
+            && destFormat->findInt32("slice-height", &destSliceHeight)
+            && destFormat->findInt32("width", &destWidth)
+            && destFormat->findInt32("height", & destHeight)
+            && destFormat->findInt32("color-format", &destColorFormat));
+
+    CHECK(srcWidth <= destStride && srcHeight <= destSliceHeight);
+
+    convertYUV420spToARGB(
+            srcBuffer->data(),
+            srcBuffer->data() + srcStride * srcSliceHeight,
+            srcWidth,
+            srcHeight,
+            destBuffer->data());
+
+    // copy timestamp
+    int64_t timeUs;
+    CHECK(srcBuffer->meta()->findInt64("timeUs", &timeUs));
+    destBuffer->meta()->setInt64("timeUs", timeUs);
+
+    if (kTestFlush && inputFramesSinceFlush >= kFlushAfterFrames) {
+        inputFramesSinceFlush = 0;
+
+        // check that queueing a buffer that was dequeued before flush
+        // fails with expected error EACCES
+        filterState->mCodec->flush();
+
+        err = filterState->mCodec->queueInputBuffer(
+                filterIndex, 0 /* offset */, destBuffer->size(),
+                timeUs, frame.flags);
+
+        if (err == OK) {
+            ALOGE("FAIL: queue after flush returned OK");
+        } else if (err != -EACCES) {
+            ALOGE("queueInputBuffer after flush returned %d, "
+                    "expected -EACCES (-13)", err);
+        }
+    } else {
+        err = filterState->mCodec->queueInputBuffer(
+                filterIndex, 0 /* offset */, destBuffer->size(),
+                timeUs, frame.flags);
+        CHECK(err == OK);
+
+        err = vidState->mCodec->releaseOutputBuffer(outIndex);
+        CHECK(err == OK);
+    }
+}
+
+size_t outputFramesSinceFlush = 0;
+void tryDrainOutputBuffer(
+        CodecState *filterState,
+        const sp<Surface> &surface, bool renderSurface,
+        bool useTimestamp, int64_t *startTimeRender) {
+    size_t index;
+    size_t offset;
+    size_t size;
+    int64_t presentationTimeUs;
+    uint32_t flags;
+    status_t err = filterState->mCodec->dequeueOutputBuffer(
+            &index, &offset, &size, &presentationTimeUs, &flags,
+            kTimeout);
+
+    if (err != OK) {
+        return;
+    }
+
+    ++outputFramesSinceFlush;
+
+    if (kTestFlush && outputFramesSinceFlush >= kFlushAfterFrames) {
+        filterState->mCodec->flush();
+    }
+
+    if (surface == NULL || !renderSurface) {
+        err = filterState->mCodec->releaseOutputBuffer(index);
+    } else if (useTimestamp) {
+        if (*startTimeRender == -1) {
+            // begin rendering 2 vsyncs after first decode
+            *startTimeRender = systemTime(SYSTEM_TIME_MONOTONIC)
+                    + 33000000 - (presentationTimeUs * 1000);
+        }
+        presentationTimeUs =
+                (presentationTimeUs * 1000) + *startTimeRender;
+        err = filterState->mCodec->renderOutputBufferAndRelease(
+                index, presentationTimeUs);
+    } else {
+        err = filterState->mCodec->renderOutputBufferAndRelease(index);
+    }
+
+    if (kTestFlush && outputFramesSinceFlush >= kFlushAfterFrames) {
+        outputFramesSinceFlush = 0;
+
+        // releasing the buffer dequeued before flush should cause an error
+        // if so, the frame will also be skipped in output stream
+        if (err == OK) {
+            ALOGE("FAIL: release after flush returned OK");
+        } else if (err != -EACCES) {
+            ALOGE("releaseOutputBuffer after flush returned %d, "
+                    "expected -EACCES (-13)", err);
+        }
+    } else {
+        CHECK(err == OK);
+    }
+
+    if (flags & MediaCodec::BUFFER_FLAG_EOS) {
+        ALOGV("reached EOS on output.");
+        filterState->mSawOutputEOS = true;
+    }
+}
+
+static int decode(
+        const sp<ALooper> &looper,
+        const char *path,
+        const sp<Surface> &surface,
+        bool renderSurface,
+        bool useTimestamp,
+        FilterType filterType) {
+
+    static int64_t kTimeout = 500ll;
+
+    sp<NuMediaExtractor> extractor = new NuMediaExtractor;
+    if (extractor->setDataSource(NULL /* httpService */, path) != OK) {
+        fprintf(stderr, "unable to instantiate extractor.\n");
+        return 1;
+    }
+
+    KeyedVector<size_t, CodecState> stateByTrack;
+
+    CodecState *vidState = NULL;
+    for (size_t i = 0; i < extractor->countTracks(); ++i) {
+        sp<AMessage> format;
+        status_t err = extractor->getTrackFormat(i, &format);
+        CHECK(err == OK);
+
+        AString mime;
+        CHECK(format->findString("mime", &mime));
+        bool isVideo = !strncasecmp(mime.c_str(), "video/", 6);
+        if (!isVideo) {
+            continue;
+        }
+
+        ALOGV("selecting track %zu", i);
+
+        err = extractor->selectTrack(i);
+        CHECK(err == OK);
+
+        CodecState *state =
+            &stateByTrack.editValueAt(stateByTrack.add(i, CodecState()));
+
+        vidState = state;
+
+        state->mNumBuffersDecoded = 0;
+
+        state->mCodec = MediaCodec::CreateByType(
+                looper, mime.c_str(), false /* encoder */);
+
+        CHECK(state->mCodec != NULL);
+
+        err = state->mCodec->configure(
+                format, NULL /* surface */, NULL /* crypto */, 0 /* flags */);
+
+        CHECK(err == OK);
+
+        state->mSignalledInputEOS = false;
+        state->mSawOutputEOS = false;
+
+        break;
+    }
+    CHECK(!stateByTrack.isEmpty());
+    CHECK(vidState != NULL);
+    sp<AMessage> vidFormat;
+    vidState->mCodec->getOutputFormat(&vidFormat);
+
+    // set filter to use ARGB8888
+    vidFormat->setInt32("color-format", OMX_COLOR_Format32bitARGB8888);
+    // set app cache directory path
+    vidFormat->setString("cacheDir", "/system/bin");
+
+    // create RenderScript context for RSFilters
+    RSC::sp<RSC::RS> context = new RSC::RS();
+    context->init("/system/bin");
+
+    sp<RenderScriptWrapper::RSFilterCallback> rsFilter;
+
+    // create renderscript wrapper for RSFilters
+    sp<RenderScriptWrapper> rsWrapper = new RenderScriptWrapper;
+    rsWrapper->mContext = context.get();
+
+    CodecState *filterState = new CodecState();
+    filterState->mNumBuffersDecoded = 0;
+
+    sp<AMessage> params = new AMessage();
+
+    switch (filterType) {
+        case FILTERTYPE_ZERO:
+        {
+            filterState->mCodec = MediaCodec::CreateByComponentName(
+                    looper, "android.filter.zerofilter");
+            params->setInt32("invert", kInvert);
+            break;
+        }
+        case FILTERTYPE_INTRINSIC_BLUR:
+        {
+            filterState->mCodec = MediaCodec::CreateByComponentName(
+                    looper, "android.filter.intrinsicblur");
+            params->setFloat("blur-radius", kBlurRadius);
+            break;
+        }
+        case FILTERTYPE_SATURATION:
+        {
+            filterState->mCodec = MediaCodec::CreateByComponentName(
+                    looper, "android.filter.saturation");
+            params->setFloat("saturation", kSaturation);
+            break;
+        }
+        case FILTERTYPE_RS_SATURATION:
+        {
+            SaturationRSFilter *satFilter = new SaturationRSFilter;
+            satFilter->init(context);
+            rsFilter = satFilter;
+            rsWrapper->mCallback = rsFilter;
+            vidFormat->setObject("rs-wrapper", rsWrapper);
+
+            filterState->mCodec = MediaCodec::CreateByComponentName(
+                    looper, "android.filter.RenderScript");
+            break;
+        }
+        case FILTERTYPE_RS_NIGHT_VISION:
+        {
+            NightVisionRSFilter *nightVisionFilter = new NightVisionRSFilter;
+            nightVisionFilter->init(context);
+            rsFilter = nightVisionFilter;
+            rsWrapper->mCallback = rsFilter;
+            vidFormat->setObject("rs-wrapper", rsWrapper);
+
+            filterState->mCodec = MediaCodec::CreateByComponentName(
+                    looper, "android.filter.RenderScript");
+            break;
+        }
+        case FILTERTYPE_RS_ARGB_TO_RGBA:
+        {
+            ARGBToRGBARSFilter *argbToRgbaFilter = new ARGBToRGBARSFilter;
+            argbToRgbaFilter->init(context);
+            rsFilter = argbToRgbaFilter;
+            rsWrapper->mCallback = rsFilter;
+            vidFormat->setObject("rs-wrapper", rsWrapper);
+
+            filterState->mCodec = MediaCodec::CreateByComponentName(
+                    looper, "android.filter.RenderScript");
+            break;
+        }
+        default:
+        {
+            LOG_ALWAYS_FATAL("mediacodec.cpp error: unrecognized FilterType");
+            break;
+        }
+    }
+    CHECK(filterState->mCodec != NULL);
+
+    status_t err = filterState->mCodec->configure(
+            vidFormat /* format */, surface, NULL /* crypto */, 0 /* flags */);
+    CHECK(err == OK);
+
+    filterState->mSignalledInputEOS = false;
+    filterState->mSawOutputEOS = false;
+
+    int64_t startTimeUs = ALooper::GetNowUs();
+    int64_t startTimeRender = -1;
+
+    for (size_t i = 0; i < stateByTrack.size(); ++i) {
+        CodecState *state = &stateByTrack.editValueAt(i);
+
+        sp<MediaCodec> codec = state->mCodec;
+
+        CHECK_EQ((status_t)OK, codec->start());
+
+        CHECK_EQ((status_t)OK, codec->getInputBuffers(&state->mInBuffers));
+        CHECK_EQ((status_t)OK, codec->getOutputBuffers(&state->mOutBuffers));
+
+        ALOGV("got %zu input and %zu output buffers",
+                state->mInBuffers.size(), state->mOutBuffers.size());
+    }
+
+    CHECK_EQ((status_t)OK, filterState->mCodec->setParameters(params));
+
+    if (kTestFlush) {
+        status_t flushErr = filterState->mCodec->flush();
+        if (flushErr == OK) {
+            ALOGE("FAIL: Flush before start returned OK");
+        } else {
+            ALOGV("Flush before start returned status %d, usually ENOSYS (-38)",
+                    flushErr);
+        }
+    }
+
+    CHECK_EQ((status_t)OK, filterState->mCodec->start());
+    CHECK_EQ((status_t)OK, filterState->mCodec->getInputBuffers(
+            &filterState->mInBuffers));
+    CHECK_EQ((status_t)OK, filterState->mCodec->getOutputBuffers(
+            &filterState->mOutBuffers));
+
+    if (kTestFlush) {
+        status_t flushErr = filterState->mCodec->flush();
+        if (flushErr != OK) {
+            ALOGE("FAIL: Flush after start returned %d, expect OK (0)",
+                    flushErr);
+        } else {
+            ALOGV("Flush immediately after start OK");
+        }
+    }
+
+    List<DecodedFrame> decodedFrameIndices;
+
+    // loop until decoder reaches EOS
+    bool sawInputEOS = false;
+    bool sawOutputEOSOnAllTracks = false;
+    while (!sawOutputEOSOnAllTracks) {
+        if (!sawInputEOS) {
+            size_t trackIndex;
+            status_t err = extractor->getSampleTrackIndex(&trackIndex);
+
+            if (err != OK) {
+                ALOGV("saw input eos");
+                sawInputEOS = true;
+            } else {
+                CodecState *state = &stateByTrack.editValueFor(trackIndex);
+
+                size_t index;
+                err = state->mCodec->dequeueInputBuffer(&index, kTimeout);
+
+                if (err == OK) {
+                    ALOGV("filling input buffer %zu", index);
+
+                    const sp<ABuffer> &buffer = state->mInBuffers.itemAt(index);
+
+                    err = extractor->readSampleData(buffer);
+                    CHECK(err == OK);
+
+                    int64_t timeUs;
+                    err = extractor->getSampleTime(&timeUs);
+                    CHECK(err == OK);
+
+                    uint32_t bufferFlags = 0;
+
+                    err = state->mCodec->queueInputBuffer(
+                            index, 0 /* offset */, buffer->size(),
+                            timeUs, bufferFlags);
+
+                    CHECK(err == OK);
+
+                    extractor->advance();
+                } else {
+                    CHECK_EQ(err, -EAGAIN);
+                }
+            }
+        } else {
+            for (size_t i = 0; i < stateByTrack.size(); ++i) {
+                CodecState *state = &stateByTrack.editValueAt(i);
+
+                if (!state->mSignalledInputEOS) {
+                    size_t index;
+                    status_t err =
+                        state->mCodec->dequeueInputBuffer(&index, kTimeout);
+
+                    if (err == OK) {
+                        ALOGV("signalling input EOS on track %zu", i);
+
+                        err = state->mCodec->queueInputBuffer(
+                                index, 0 /* offset */, 0 /* size */,
+                                0ll /* timeUs */, MediaCodec::BUFFER_FLAG_EOS);
+
+                        CHECK(err == OK);
+
+                        state->mSignalledInputEOS = true;
+                    } else {
+                        CHECK_EQ(err, -EAGAIN);
+                    }
+                }
+            }
+        }
+
+        sawOutputEOSOnAllTracks = true;
+        for (size_t i = 0; i < stateByTrack.size(); ++i) {
+            CodecState *state = &stateByTrack.editValueAt(i);
+
+            if (state->mSawOutputEOS) {
+                continue;
+            } else {
+                sawOutputEOSOnAllTracks = false;
+            }
+
+            DecodedFrame frame;
+            status_t err = state->mCodec->dequeueOutputBuffer(
+                    &frame.index, &frame.offset, &frame.size,
+                    &frame.presentationTimeUs, &frame.flags, kTimeout);
+
+            if (err == OK) {
+                ALOGV("draining decoded buffer %zu, time = %lld us",
+                        frame.index, frame.presentationTimeUs);
+
+                ++(state->mNumBuffersDecoded);
+
+                decodedFrameIndices.push_back(frame);
+
+                if (frame.flags & MediaCodec::BUFFER_FLAG_EOS) {
+                    ALOGV("reached EOS on decoder output.");
+                    state->mSawOutputEOS = true;
+                }
+
+            } else if (err == INFO_OUTPUT_BUFFERS_CHANGED) {
+                ALOGV("INFO_OUTPUT_BUFFERS_CHANGED");
+                CHECK_EQ((status_t)OK, state->mCodec->getOutputBuffers(
+                        &state->mOutBuffers));
+
+                ALOGV("got %zu output buffers", state->mOutBuffers.size());
+            } else if (err == INFO_FORMAT_CHANGED) {
+                sp<AMessage> format;
+                CHECK_EQ((status_t)OK, state->mCodec->getOutputFormat(&format));
+
+                ALOGV("INFO_FORMAT_CHANGED: %s",
+                        format->debugString().c_str());
+            } else {
+                CHECK_EQ(err, -EAGAIN);
+            }
+
+            tryCopyDecodedBuffer(&decodedFrameIndices, filterState, vidState);
+
+            tryDrainOutputBuffer(
+                    filterState, surface, renderSurface,
+                    useTimestamp, &startTimeRender);
+        }
+    }
+
+    // after EOS on decoder, let filter reach EOS
+    while (!filterState->mSawOutputEOS) {
+        tryCopyDecodedBuffer(&decodedFrameIndices, filterState, vidState);
+
+        tryDrainOutputBuffer(
+                filterState, surface, renderSurface,
+                useTimestamp, &startTimeRender);
+    }
+
+    int64_t elapsedTimeUs = ALooper::GetNowUs() - startTimeUs;
+
+    for (size_t i = 0; i < stateByTrack.size(); ++i) {
+        CodecState *state = &stateByTrack.editValueAt(i);
+
+        CHECK_EQ((status_t)OK, state->mCodec->release());
+
+        printf("track %zu: %" PRId64 " frames decoded and filtered, "
+                "%.2f fps.\n", i, state->mNumBuffersDecoded,
+                state->mNumBuffersDecoded * 1E6 / elapsedTimeUs);
+    }
+
+    return 0;
+}
+
+}  // namespace android
+
+int main(int argc, char **argv) {
+    using namespace android;
+
+    const char *me = argv[0];
+
+    bool useSurface = false;
+    bool renderSurface = false;
+    bool useTimestamp = false;
+    FilterType filterType = FILTERTYPE_ZERO;
+
+    int res;
+    while ((res = getopt(argc, argv, "bcnrszTRSh")) >= 0) {
+        switch (res) {
+            case 'b':
+            {
+                filterType = FILTERTYPE_INTRINSIC_BLUR;
+                break;
+            }
+            case 'c':
+            {
+                filterType = FILTERTYPE_RS_ARGB_TO_RGBA;
+                break;
+            }
+            case 'n':
+            {
+                filterType = FILTERTYPE_RS_NIGHT_VISION;
+                break;
+            }
+            case 'r':
+            {
+                filterType = FILTERTYPE_RS_SATURATION;
+                break;
+            }
+            case 's':
+            {
+                filterType = FILTERTYPE_SATURATION;
+                break;
+            }
+            case 'z':
+            {
+                filterType = FILTERTYPE_ZERO;
+                break;
+            }
+            case 'T':
+            {
+                useTimestamp = true;
+            }
+            // fall through
+            case 'R':
+            {
+                renderSurface = true;
+            }
+            // fall through
+            case 'S':
+            {
+                useSurface = true;
+                break;
+            }
+            case '?':
+            case 'h':
+            default:
+            {
+                usage(me);
+                break;
+            }
+        }
+    }
+
+    argc -= optind;
+    argv += optind;
+
+    if (argc != 1) {
+        usage(me);
+    }
+
+    ProcessState::self()->startThreadPool();
+
+    DataSource::RegisterDefaultSniffers();
+
+    android::sp<ALooper> looper = new ALooper;
+    looper->start();
+
+    android::sp<SurfaceComposerClient> composerClient;
+    android::sp<SurfaceControl> control;
+    android::sp<Surface> surface;
+
+    if (useSurface) {
+        composerClient = new SurfaceComposerClient;
+        CHECK_EQ((status_t)OK, composerClient->initCheck());
+
+        android::sp<IBinder> display(SurfaceComposerClient::getBuiltInDisplay(
+                ISurfaceComposer::eDisplayIdMain));
+        DisplayInfo info;
+        SurfaceComposerClient::getDisplayInfo(display, &info);
+        ssize_t displayWidth = info.w;
+        ssize_t displayHeight = info.h;
+
+        ALOGV("display is %zd x %zd", displayWidth, displayHeight);
+
+        control = composerClient->createSurface(
+                String8("A Surface"), displayWidth, displayHeight,
+                PIXEL_FORMAT_RGBA_8888, 0);
+
+        CHECK(control != NULL);
+        CHECK(control->isValid());
+
+        SurfaceComposerClient::openGlobalTransaction();
+        CHECK_EQ((status_t)OK, control->setLayer(INT_MAX));
+        CHECK_EQ((status_t)OK, control->show());
+        SurfaceComposerClient::closeGlobalTransaction();
+
+        surface = control->getSurface();
+        CHECK(surface != NULL);
+    }
+
+    decode(looper, argv[0], surface, renderSurface, useTimestamp, filterType);
+
+    if (useSurface) {
+        composerClient->dispose();
+    }
+
+    looper->stop();
+
+    return 0;
+}
diff --git a/include/media/EffectsFactoryApi.h b/include/media/EffectsFactoryApi.h
index b1ed7b0..64a3212 100644
--- a/include/media/EffectsFactoryApi.h
+++ b/include/media/EffectsFactoryApi.h
@@ -171,6 +171,8 @@
 ////////////////////////////////////////////////////////////////////////////////
 int EffectIsNullUuid(const effect_uuid_t *pEffectUuid);
 
+int EffectDumpEffects(int fd);
+
 #if __cplusplus
 }  // extern "C"
 #endif
diff --git a/include/media/stagefright/MediaFilter.h b/include/media/stagefright/MediaFilter.h
new file mode 100644
index 0000000..7b3f700
--- /dev/null
+++ b/include/media/stagefright/MediaFilter.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEDIA_FILTER_H_
+#define MEDIA_FILTER_H_
+
+#include <media/stagefright/CodecBase.h>
+
+namespace android {
+
+struct ABuffer;
+struct GraphicBufferListener;
+struct MemoryDealer;
+struct SimpleFilter;
+
+struct MediaFilter : public CodecBase {
+    MediaFilter();
+
+    virtual void setNotificationMessage(const sp<AMessage> &msg);
+
+    virtual void initiateAllocateComponent(const sp<AMessage> &msg);
+    virtual void initiateConfigureComponent(const sp<AMessage> &msg);
+    virtual void initiateCreateInputSurface();
+    virtual void initiateStart();
+    virtual void initiateShutdown(bool keepComponentAllocated = false);
+
+    virtual void signalFlush();
+    virtual void signalResume();
+
+    virtual void signalRequestIDRFrame();
+    virtual void signalSetParameters(const sp<AMessage> &msg);
+    virtual void signalEndOfInputStream();
+
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
+    struct PortDescription : public CodecBase::PortDescription {
+        virtual size_t countBuffers();
+        virtual IOMX::buffer_id bufferIDAt(size_t index) const;
+        virtual sp<ABuffer> bufferAt(size_t index) const;
+
+    protected:
+        PortDescription();
+
+    private:
+        friend struct MediaFilter;
+
+        Vector<IOMX::buffer_id> mBufferIDs;
+        Vector<sp<ABuffer> > mBuffers;
+
+        void addBuffer(IOMX::buffer_id id, const sp<ABuffer> &buffer);
+
+        DISALLOW_EVIL_CONSTRUCTORS(PortDescription);
+    };
+
+protected:
+    virtual ~MediaFilter();
+
+private:
+    struct BufferInfo {
+        enum Status {
+            OWNED_BY_US,
+            OWNED_BY_UPSTREAM,
+        };
+
+        IOMX::buffer_id mBufferID;
+        int32_t mGeneration;
+        int32_t mOutputFlags;
+        Status mStatus;
+
+        sp<ABuffer> mData;
+    };
+
+    enum State {
+      UNINITIALIZED,
+      INITIALIZED,
+      CONFIGURED,
+      STARTED,
+    };
+
+    enum {
+        kWhatInputBufferFilled       = 'inpF',
+        kWhatOutputBufferDrained     = 'outD',
+        kWhatShutdown                = 'shut',
+        kWhatFlush                   = 'flus',
+        kWhatResume                  = 'resm',
+        kWhatAllocateComponent       = 'allo',
+        kWhatConfigureComponent      = 'conf',
+        kWhatCreateInputSurface      = 'cisf',
+        kWhatSignalEndOfInputStream  = 'eois',
+        kWhatStart                   = 'star',
+        kWhatSetParameters           = 'setP',
+        kWhatProcessBuffers          = 'proc',
+    };
+
+    enum {
+        kPortIndexInput  = 0,
+        kPortIndexOutput = 1
+    };
+
+    // member variables
+    AString mComponentName;
+    State mState;
+    status_t mInputEOSResult;
+    int32_t mWidth, mHeight;
+    int32_t mStride, mSliceHeight;
+    int32_t mColorFormatIn, mColorFormatOut;
+    size_t mMaxInputSize, mMaxOutputSize;
+    int32_t mGeneration;
+    sp<AMessage> mNotify;
+    sp<AMessage> mInputFormat;
+    sp<AMessage> mOutputFormat;
+
+    sp<MemoryDealer> mDealer[2];
+    Vector<BufferInfo> mBuffers[2];
+    Vector<BufferInfo*> mAvailableInputBuffers;
+    Vector<BufferInfo*> mAvailableOutputBuffers;
+    bool mPortEOS[2];
+
+    sp<SimpleFilter> mFilter;
+    sp<GraphicBufferListener> mGraphicBufferListener;
+
+    // helper functions
+    void signalProcessBuffers();
+    void signalError(status_t error);
+
+    status_t allocateBuffersOnPort(OMX_U32 portIndex);
+    BufferInfo *findBufferByID(
+            uint32_t portIndex, IOMX::buffer_id bufferID,
+            ssize_t *index = NULL);
+    void postFillThisBuffer(BufferInfo *info);
+    void postDrainThisBuffer(BufferInfo *info);
+    void postEOS();
+    void sendFormatChange();
+    void requestFillEmptyInput();
+    void processBuffers();
+
+    void onAllocateComponent(const sp<AMessage> &msg);
+    void onConfigureComponent(const sp<AMessage> &msg);
+    void onStart();
+    void onInputBufferFilled(const sp<AMessage> &msg);
+    void onOutputBufferDrained(const sp<AMessage> &msg);
+    void onShutdown(const sp<AMessage> &msg);
+    void onFlush();
+    void onSetParameters(const sp<AMessage> &msg);
+    void onCreateInputSurface();
+    void onInputFrameAvailable();
+    void onSignalEndOfInputStream();
+
+    DISALLOW_EVIL_CONSTRUCTORS(MediaFilter);
+};
+
+}  // namespace android
+
+#endif  // MEDIA_FILTER_H_
diff --git a/include/media/stagefright/RenderScriptWrapper.h b/include/media/stagefright/RenderScriptWrapper.h
new file mode 100644
index 0000000..b42649e
--- /dev/null
+++ b/include/media/stagefright/RenderScriptWrapper.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RENDERSCRIPT_WRAPPER_H_
+#define RENDERSCRIPT_WRAPPER_H_
+
+#include <RenderScript.h>
+
+namespace android {
+
+struct RenderScriptWrapper : public RefBase {
+public:
+    struct RSFilterCallback : public RefBase {
+    public:
+        // called by RSFilter to process each input buffer
+        virtual status_t processBuffers(
+                RSC::Allocation* inBuffer,
+                RSC::Allocation* outBuffer) = 0;
+
+        virtual status_t handleSetParameters(const sp<AMessage> &msg) = 0;
+    };
+
+    sp<RSFilterCallback> mCallback;
+    RSC::sp<RSC::RS> mContext;
+};
+
+}   // namespace android
+
+#endif  // RENDERSCRIPT_WRAPPER_H_
diff --git a/media/libeffects/factory/EffectsFactory.c b/media/libeffects/factory/EffectsFactory.c
index 6d30d64..c310fe2 100644
--- a/media/libeffects/factory/EffectsFactory.c
+++ b/media/libeffects/factory/EffectsFactory.c
@@ -28,6 +28,7 @@
 
 static list_elem_t *gEffectList; // list of effect_entry_t: all currently created effects
 static list_elem_t *gLibraryList; // list of lib_entry_t: all currently loaded libraries
+static list_elem_t *gSkippedEffects; // list of effects skipped because of duplicate uuid
 // list of effect_descriptor and list of sub effects : all currently loaded
 // It does not contain effects without sub effects.
 static list_sub_elem_t *gSubEffectList;
@@ -63,10 +64,10 @@
                lib_entry_t **lib,
                effect_descriptor_t **desc);
 // To search a subeffect in the gSubEffectList
-int findSubEffect(const effect_uuid_t *uuid,
+static int findSubEffect(const effect_uuid_t *uuid,
                lib_entry_t **lib,
                effect_descriptor_t **desc);
-static void dumpEffectDescriptor(effect_descriptor_t *desc, char *str, size_t len);
+static void dumpEffectDescriptor(effect_descriptor_t *desc, char *str, size_t len, int indent);
 static int stringToUuid(const char *str, effect_uuid_t *uuid);
 static int uuidToString(const effect_uuid_t *uuid, char *str, size_t maxLen);
 
@@ -237,8 +238,8 @@
     }
 
 #if (LOG_NDEBUG == 0)
-    char str[256];
-    dumpEffectDescriptor(pDescriptor, str, 256);
+    char str[512];
+    dumpEffectDescriptor(pDescriptor, str, sizeof(str), 0 /* indent */);
     ALOGV("EffectQueryEffect() desc:%s", str);
 #endif
     pthread_mutex_unlock(&gLibLock);
@@ -503,15 +504,31 @@
     audio_effect_library_t *desc;
     list_elem_t *e;
     lib_entry_t *l;
+    char path[PATH_MAX];
+    char *str;
+    size_t len;
 
     node = config_find(root, PATH_TAG);
     if (node == NULL) {
         return -EINVAL;
     }
+    // audio_effects.conf always specifies 32 bit lib path: convert to 64 bit path if needed
+    strlcpy(path, node->value, PATH_MAX);
+#ifdef __LP64__
+    str = strstr(path, "/lib/");
+    if (str == NULL)
+        return -EINVAL;
+    len = str - path;
+    path[len] = '\0';
+    strlcat(path, "/lib64/", PATH_MAX);
+    strlcat(path, node->value + len + strlen("/lib/"), PATH_MAX);
+#endif
+    if (strlen(path) >= PATH_MAX - 1)
+        return -EINVAL;
 
-    hdl = dlopen(node->value, RTLD_NOW);
+    hdl = dlopen(path, RTLD_NOW);
     if (hdl == NULL) {
-        ALOGW("loadLibrary() failed to open %s", node->value);
+        ALOGW("loadLibrary() failed to open %s", path);
         goto error;
     }
 
@@ -535,7 +552,7 @@
     // add entry for library in gLibraryList
     l = malloc(sizeof(lib_entry_t));
     l->name = strndup(name, PATH_MAX);
-    l->path = strndup(node->value, PATH_MAX);
+    l->path = strndup(path, PATH_MAX);
     l->handle = hdl;
     l->desc = desc;
     l->effects = NULL;
@@ -547,7 +564,7 @@
     e->next = gLibraryList;
     gLibraryList = e;
     pthread_mutex_unlock(&gLibLock);
-    ALOGV("getLibrary() linked library %p for path %s", l, node->value);
+    ALOGV("getLibrary() linked library %p for path %s", l, path);
 
     return 0;
 
@@ -595,8 +612,8 @@
         return -EINVAL;
     }
 #if (LOG_NDEBUG==0)
-    char s[256];
-    dumpEffectDescriptor(d, s, 256);
+    char s[512];
+    dumpEffectDescriptor(d, s, sizeof(s), 0 /* indent */);
     ALOGV("addSubEffect() read descriptor %p:%s",d, s);
 #endif
     if (EFFECT_API_VERSION_MAJOR(d->apiVersion) !=
@@ -660,6 +677,13 @@
         ALOGW("loadEffect() invalid uuid %s", node->value);
         return -EINVAL;
     }
+    lib_entry_t *tmp;
+    bool skip = false;
+    if (findEffect(NULL, &uuid, &tmp, NULL) == 0) {
+        ALOGW("skipping duplicate uuid %s %s", node->value,
+                node->next ? "and its sub-effects" : "");
+        skip = true;
+    }
 
     d = malloc(sizeof(effect_descriptor_t));
     if (l->desc->get_descriptor(&uuid, d) != 0) {
@@ -670,8 +694,8 @@
         return -EINVAL;
     }
 #if (LOG_NDEBUG==0)
-    char s[256];
-    dumpEffectDescriptor(d, s, 256);
+    char s[512];
+    dumpEffectDescriptor(d, s, sizeof(s), 0 /* indent */);
     ALOGV("loadEffect() read descriptor %p:%s",d, s);
 #endif
     if (EFFECT_API_VERSION_MAJOR(d->apiVersion) !=
@@ -682,8 +706,14 @@
     }
     e = malloc(sizeof(list_elem_t));
     e->object = d;
-    e->next = l->effects;
-    l->effects = e;
+    if (skip) {
+        e->next = gSkippedEffects;
+        gSkippedEffects = e;
+        return -EINVAL;
+    } else {
+        e->next = l->effects;
+        l->effects = e;
+    }
 
     // After the UUID node in the config_tree, if node->next is valid,
     // that would be sub effect node.
@@ -876,22 +906,30 @@
     return ret;
 }
 
-void dumpEffectDescriptor(effect_descriptor_t *desc, char *str, size_t len) {
+void dumpEffectDescriptor(effect_descriptor_t *desc, char *str, size_t len, int indent) {
     char s[256];
+    char ss[256];
+    char idt[indent + 1];
 
-    snprintf(str, len, "\nEffect Descriptor %p:\n", desc);
-    strncat(str, "- TYPE: ", len);
-    uuidToString(&desc->uuid, s, 256);
-    snprintf(str, len, "- UUID: %s\n", s);
-    uuidToString(&desc->type, s, 256);
-    snprintf(str, len, "- TYPE: %s\n", s);
-    sprintf(s, "- apiVersion: %08X\n- flags: %08X\n",
-            desc->apiVersion, desc->flags);
-    strncat(str, s, len);
-    sprintf(s, "- name: %s\n", desc->name);
-    strncat(str, s, len);
-    sprintf(s, "- implementor: %s\n", desc->implementor);
-    strncat(str, s, len);
+    memset(idt, ' ', indent);
+    idt[indent] = 0;
+
+    str[0] = 0;
+
+    snprintf(s, sizeof(s), "%s%s / %s\n", idt, desc->name, desc->implementor);
+    strlcat(str, s, len);
+
+    uuidToString(&desc->uuid, s, sizeof(s));
+    snprintf(ss, sizeof(ss), "%s  UUID: %s\n", idt, s);
+    strlcat(str, ss, len);
+
+    uuidToString(&desc->type, s, sizeof(s));
+    snprintf(ss, sizeof(ss), "%s  TYPE: %s\n", idt, s);
+    strlcat(str, ss, len);
+
+    sprintf(s, "%s  apiVersion: %08X\n%s  flags: %08X\n", idt,
+            desc->apiVersion, idt, desc->flags);
+    strlcat(str, s, len);
 }
 
 int stringToUuid(const char *str, effect_uuid_t *uuid)
@@ -934,3 +972,40 @@
     return 0;
 }
 
+int EffectDumpEffects(int fd) {
+    char s[512];
+    list_elem_t *e = gLibraryList;
+    lib_entry_t *l = NULL;
+    effect_descriptor_t *d = NULL;
+    int found = 0;
+    int ret = 0;
+
+    while (e) {
+        l = (lib_entry_t *)e->object;
+        list_elem_t *efx = l->effects;
+        dprintf(fd, "Library %s\n", l->name);
+        if (!efx) {
+            dprintf(fd, "  (no effects)\n");
+        }
+        while (efx) {
+            d = (effect_descriptor_t *)efx->object;
+            dumpEffectDescriptor(d, s, sizeof(s), 2);
+            dprintf(fd, "%s", s);
+            efx = efx->next;
+        }
+        e = e->next;
+    }
+
+    e = gSkippedEffects;
+    if (e) {
+        dprintf(fd, "Skipped effects\n");
+        while(e) {
+            d = (effect_descriptor_t *)e->object;
+            dumpEffectDescriptor(d, s, sizeof(s), 2 /* indent */);
+            dprintf(fd, "%s", s);
+            e = e->next;
+        }
+    }
+    return ret;
+}
+
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index d050c78..d65df14 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -351,6 +351,14 @@
         case STATE_PREPARED:
         case STATE_STOPPED_AND_PREPARED:
         {
+            int curpos = 0;
+            if (mPositionUs > 0) {
+                curpos = (mPositionUs + 500ll) / 1000;
+            }
+            if (curpos == msec) {
+                // nothing to do, and doing something anyway could result in deadlock (b/15323063)
+                break;
+            }
             mStartupSeekTimeUs = seekTimeUs;
             // pretend that the seek completed. It will actually happen when starting playback.
             // TODO: actually perform the seek here, so the player is ready to go at the new
diff --git a/media/libnbaio/MonoPipeReader.cpp b/media/libnbaio/MonoPipeReader.cpp
index de82229..e4d3ed8 100644
--- a/media/libnbaio/MonoPipeReader.cpp
+++ b/media/libnbaio/MonoPipeReader.cpp
@@ -39,7 +39,7 @@
         return NEGOTIATE;
     }
     ssize_t ret = android_atomic_acquire_load(&mPipe->mRear) - mPipe->mFront;
-    ALOG_ASSERT((0 <= ret) && (ret <= mMaxFrames));
+    ALOG_ASSERT((0 <= ret) && ((size_t) ret <= mPipe->mMaxFrames));
     return ret;
 }
 
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 193f8a7..1810031 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -103,6 +103,7 @@
         libstagefright_color_conversion \
         libstagefright_aacenc \
         libstagefright_matroska \
+        libstagefright_mediafilter \
         libstagefright_webm \
         libstagefright_timedtext \
         libvpx \
@@ -110,13 +111,14 @@
         libstagefright_mpeg2ts \
         libstagefright_id3 \
         libFLAC \
-        libmedia_helper
+        libmedia_helper \
 
 LOCAL_SHARED_LIBRARIES += \
         libstagefright_enc_common \
         libstagefright_avc_common \
         libstagefright_foundation \
-        libdl
+        libdl \
+        libRScpp \
 
 LOCAL_CFLAGS += -Wno-multichar
 
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 11069e4..3622eb7 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -36,6 +36,7 @@
 #include <media/stagefright/MediaCodecList.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaFilter.h>
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/NativeWindowWrapper.h>
 #include <private/android_filesystem_config.h>
@@ -189,7 +190,16 @@
     // quickly, violating the OpenMAX specs, until that is remedied
     // we need to invest in an extra looper to free the main event
     // queue.
-    mCodec = new ACodec;
+
+    if (nameIsType || !strncasecmp(name.c_str(), "omx.", 4)) {
+        mCodec = new ACodec;
+    } else if (!nameIsType
+            && !strncasecmp(name.c_str(), "android.filter.", 15)) {
+        mCodec = new MediaFilter;
+    } else {
+        return NAME_NOT_FOUND;
+    }
+
     bool needDedicatedLooper = false;
     if (nameIsType && !strncasecmp(name.c_str(), "video/", 6)) {
         needDedicatedLooper = true;
diff --git a/media/libstagefright/colorconversion/SoftwareRenderer.cpp b/media/libstagefright/colorconversion/SoftwareRenderer.cpp
index 1899b40..6474906 100644
--- a/media/libstagefright/colorconversion/SoftwareRenderer.cpp
+++ b/media/libstagefright/colorconversion/SoftwareRenderer.cpp
@@ -98,32 +98,48 @@
     mCropWidth = mCropRight - mCropLeft + 1;
     mCropHeight = mCropBottom - mCropTop + 1;
 
-    int halFormat;
-    size_t bufWidth, bufHeight;
+    // by default convert everything to RGB565
+    int halFormat = HAL_PIXEL_FORMAT_RGB_565;
+    size_t bufWidth = mCropWidth;
+    size_t bufHeight = mCropHeight;
 
-    switch (mColorFormat) {
-        case OMX_COLOR_FormatYUV420Planar:
-        case OMX_TI_COLOR_FormatYUV420PackedSemiPlanar:
-        {
-            if (!runningInEmulator()) {
+    // hardware has YUV12 and RGBA8888 support, so convert known formats
+    if (!runningInEmulator()) {
+        switch (mColorFormat) {
+            case OMX_COLOR_FormatYUV420Planar:
+            case OMX_TI_COLOR_FormatYUV420PackedSemiPlanar:
+            {
                 halFormat = HAL_PIXEL_FORMAT_YV12;
                 bufWidth = (mCropWidth + 1) & ~1;
                 bufHeight = (mCropHeight + 1) & ~1;
                 break;
             }
-
-            // fall through.
+            case OMX_COLOR_Format24bitRGB888:
+            {
+                halFormat = HAL_PIXEL_FORMAT_RGB_888;
+                bufWidth = (mCropWidth + 1) & ~1;
+                bufHeight = (mCropHeight + 1) & ~1;
+                break;
+            }
+            case OMX_COLOR_Format32bitARGB8888:
+            case OMX_COLOR_Format32BitRGBA8888:
+            {
+                halFormat = HAL_PIXEL_FORMAT_RGBA_8888;
+                bufWidth = (mCropWidth + 1) & ~1;
+                bufHeight = (mCropHeight + 1) & ~1;
+                break;
+            }
+            default:
+            {
+                break;
+            }
         }
+    }
 
-        default:
-            halFormat = HAL_PIXEL_FORMAT_RGB_565;
-            bufWidth = mCropWidth;
-            bufHeight = mCropHeight;
-
-            mConverter = new ColorConverter(
-                    mColorFormat, OMX_COLOR_Format16bitRGB565);
-            CHECK(mConverter->isValid());
-            break;
+    if (halFormat == HAL_PIXEL_FORMAT_RGB_565) {
+        mConverter = new ColorConverter(
+                mColorFormat, OMX_COLOR_Format16bitRGB565);
+        CHECK(mConverter->isValid());
     }
 
     CHECK(mNativeWindow != NULL);
@@ -200,6 +216,8 @@
     CHECK_EQ(0, mapper.lock(
                 buf->handle, GRALLOC_USAGE_SW_WRITE_OFTEN, bounds, &dst));
 
+    // TODO move the other conversions also into ColorConverter, and
+    // fix cropping issues (when mCropLeft/Top != 0 or mWidth != mCropWidth)
     if (mConverter) {
         mConverter->convert(
                 data,
@@ -210,7 +228,8 @@
                 0, 0, mCropWidth - 1, mCropHeight - 1);
     } else if (mColorFormat == OMX_COLOR_FormatYUV420Planar) {
         const uint8_t *src_y = (const uint8_t *)data;
-        const uint8_t *src_u = (const uint8_t *)data + mWidth * mHeight;
+        const uint8_t *src_u =
+                (const uint8_t *)data + mWidth * mHeight;
         const uint8_t *src_v = src_u + (mWidth / 2 * mHeight / 2);
 
         uint8_t *dst_y = (uint8_t *)dst;
@@ -236,14 +255,10 @@
             dst_u += dst_c_stride;
             dst_v += dst_c_stride;
         }
-    } else {
-        CHECK_EQ(mColorFormat, OMX_TI_COLOR_FormatYUV420PackedSemiPlanar);
-
-        const uint8_t *src_y =
-            (const uint8_t *)data;
-
-        const uint8_t *src_uv =
-            (const uint8_t *)data + mWidth * (mHeight - mCropTop / 2);
+    } else if (mColorFormat == OMX_TI_COLOR_FormatYUV420PackedSemiPlanar) {
+        const uint8_t *src_y = (const uint8_t *)data;
+        const uint8_t *src_uv = (const uint8_t *)data
+                + mWidth * (mHeight - mCropTop / 2);
 
         uint8_t *dst_y = (uint8_t *)dst;
 
@@ -271,6 +286,40 @@
             dst_u += dst_c_stride;
             dst_v += dst_c_stride;
         }
+    } else if (mColorFormat == OMX_COLOR_Format24bitRGB888) {
+        uint8_t* srcPtr = (uint8_t*)data;
+        uint8_t* dstPtr = (uint8_t*)dst;
+
+        for (size_t y = 0; y < (size_t)mCropHeight; ++y) {
+            memcpy(dstPtr, srcPtr, mCropWidth * 3);
+            srcPtr += mWidth * 3;
+            dstPtr += buf->stride * 3;
+        }
+    } else if (mColorFormat == OMX_COLOR_Format32bitARGB8888) {
+        uint8_t *srcPtr, *dstPtr;
+
+        for (size_t y = 0; y < (size_t)mCropHeight; ++y) {
+            srcPtr = (uint8_t*)data + mWidth * 4 * y;
+            dstPtr = (uint8_t*)dst + buf->stride * 4 * y;
+            for (size_t x = 0; x < (size_t)mCropWidth; ++x) {
+                uint8_t a = *srcPtr++;
+                for (size_t i = 0; i < 3; ++i) {   // copy RGB
+                    *dstPtr++ = *srcPtr++;
+                }
+                *dstPtr++ = a;  // alpha last (ARGB to RGBA)
+            }
+        }
+    } else if (mColorFormat == OMX_COLOR_Format32BitRGBA8888) {
+        uint8_t* srcPtr = (uint8_t*)data;
+        uint8_t* dstPtr = (uint8_t*)dst;
+
+        for (size_t y = 0; y < (size_t)mCropHeight; ++y) {
+            memcpy(dstPtr, srcPtr, mCropWidth * 4);
+            srcPtr += mWidth * 4;
+            dstPtr += buf->stride * 4;
+        }
+    } else {
+        LOG_ALWAYS_FATAL("bad color format %#x", mColorFormat);
     }
 
     CHECK_EQ(0, mapper.unlock(buf->handle));
diff --git a/media/libstagefright/filters/Android.mk b/media/libstagefright/filters/Android.mk
new file mode 100644
index 0000000..36ab444
--- /dev/null
+++ b/media/libstagefright/filters/Android.mk
@@ -0,0 +1,27 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+        ColorConvert.cpp          \
+        GraphicBufferListener.cpp \
+        IntrinsicBlurFilter.cpp   \
+        MediaFilter.cpp           \
+        RSFilter.cpp              \
+        SaturationFilter.cpp      \
+        saturationARGB.rs         \
+        SimpleFilter.cpp          \
+        ZeroFilter.cpp
+
+LOCAL_C_INCLUDES := \
+        $(TOP)/frameworks/native/include/media/openmax \
+        $(TOP)/frameworks/rs/cpp \
+        $(TOP)/frameworks/rs \
+
+intermediates := $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,)
+LOCAL_C_INCLUDES += $(intermediates)
+
+LOCAL_CFLAGS += -Wno-multichar
+
+LOCAL_MODULE:= libstagefright_mediafilter
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/media/libstagefright/filters/ColorConvert.cpp b/media/libstagefright/filters/ColorConvert.cpp
new file mode 100644
index 0000000..a5039f9
--- /dev/null
+++ b/media/libstagefright/filters/ColorConvert.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ColorConvert.h"
+
+#ifndef max
+#define max(a,b) ((a) > (b) ? (a) : (b))
+#endif
+#ifndef min
+#define min(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+namespace android {
+
+void YUVToRGB(
+        int32_t y, int32_t u, int32_t v,
+        int32_t* r, int32_t* g, int32_t* b) {
+    y -= 16;
+    u -= 128;
+    v -= 128;
+
+    *b = 1192 * y + 2066 * u;
+    *g = 1192 * y - 833 * v - 400 * u;
+    *r = 1192 * y + 1634 * v;
+
+    *r = min(262143, max(0, *r));
+    *g = min(262143, max(0, *g));
+    *b = min(262143, max(0, *b));
+
+    *r >>= 10;
+    *g >>= 10;
+    *b >>= 10;
+}
+
+void convertYUV420spToARGB(
+        uint8_t *pY, uint8_t *pUV, int32_t width, int32_t height,
+        uint8_t *dest) {
+    const int32_t bytes_per_pixel = 2;
+
+    for (int32_t i = 0; i < height; i++) {
+        for (int32_t j = 0; j < width; j++) {
+            int32_t y = *(pY + i * width + j);
+            int32_t u = *(pUV + (i/2) * width + bytes_per_pixel * (j/2));
+            int32_t v = *(pUV + (i/2) * width + bytes_per_pixel * (j/2) + 1);
+
+            int32_t r, g, b;
+            YUVToRGB(y, u, v, &r, &g, &b);
+
+            *dest++ = 0xFF;
+            *dest++ = r;
+            *dest++ = g;
+            *dest++ = b;
+        }
+    }
+}
+
+void convertYUV420spToRGB888(
+        uint8_t *pY, uint8_t *pUV, int32_t width, int32_t height,
+        uint8_t *dest) {
+    const int32_t bytes_per_pixel = 2;
+
+    for (int32_t i = 0; i < height; i++) {
+        for (int32_t j = 0; j < width; j++) {
+            int32_t y = *(pY + i * width + j);
+            int32_t u = *(pUV + (i/2) * width + bytes_per_pixel * (j/2));
+            int32_t v = *(pUV + (i/2) * width + bytes_per_pixel * (j/2) + 1);
+
+            int32_t r, g, b;
+            YUVToRGB(y, u, v, &r, &g, &b);
+
+            *dest++ = r;
+            *dest++ = g;
+            *dest++ = b;
+        }
+    }
+}
+
+// HACK - not even slightly optimized
+// TODO: remove when RGBA support is added to SoftwareRenderer
+void convertRGBAToARGB(
+        uint8_t *src, int32_t width, int32_t height, uint32_t stride,
+        uint8_t *dest) {
+    for (size_t i = 0; i < height; ++i) {
+        for (size_t j = 0; j < width; ++j) {
+            uint8_t r = *src++;
+            uint8_t g = *src++;
+            uint8_t b = *src++;
+            uint8_t a = *src++;
+            *dest++ = a;
+            *dest++ = r;
+            *dest++ = g;
+            *dest++ = b;
+        }
+        src += (stride - width) * 4;
+    }
+}
+
+}   // namespace android
diff --git a/media/libstagefright/filters/ColorConvert.h b/media/libstagefright/filters/ColorConvert.h
new file mode 100644
index 0000000..13faa02
--- /dev/null
+++ b/media/libstagefright/filters/ColorConvert.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COLOR_CONVERT_H_
+#define COLOR_CONVERT_H_
+
+#include <inttypes.h>
+
+namespace android {
+
+void YUVToRGB(
+        int32_t y, int32_t u, int32_t v,
+        int32_t* r, int32_t* g, int32_t* b);
+
+void convertYUV420spToARGB(
+        uint8_t *pY, uint8_t *pUV, int32_t width, int32_t height,
+        uint8_t *dest);
+
+void convertYUV420spToRGB888(
+        uint8_t *pY, uint8_t *pUV, int32_t width, int32_t height,
+        uint8_t *dest);
+
+// TODO: remove when RGBA support is added to SoftwareRenderer
+void convertRGBAToARGB(
+        uint8_t *src, int32_t width, int32_t height, uint32_t stride,
+        uint8_t *dest);
+
+}   // namespace android
+
+#endif  // COLOR_CONVERT_H_
diff --git a/media/libstagefright/filters/GraphicBufferListener.cpp b/media/libstagefright/filters/GraphicBufferListener.cpp
new file mode 100644
index 0000000..fa38192
--- /dev/null
+++ b/media/libstagefright/filters/GraphicBufferListener.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "GraphicBufferListener"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaErrors.h>
+
+#include "GraphicBufferListener.h"
+
+namespace android {
+
+status_t GraphicBufferListener::init(
+        const sp<AMessage> &notify,
+        size_t bufferWidth, size_t bufferHeight, size_t bufferCount) {
+    mNotify = notify;
+
+    String8 name("GraphicBufferListener");
+    BufferQueue::createBufferQueue(&mProducer, &mConsumer);
+    mConsumer->setConsumerName(name);
+    mConsumer->setDefaultBufferSize(bufferWidth, bufferHeight);
+    mConsumer->setConsumerUsageBits(GRALLOC_USAGE_SW_READ_OFTEN);
+
+    status_t err = mConsumer->setMaxAcquiredBufferCount(bufferCount);
+    if (err != NO_ERROR) {
+        ALOGE("Unable to set BQ max acquired buffer count to %u: %d",
+                bufferCount, err);
+        return err;
+    }
+
+    wp<BufferQueue::ConsumerListener> listener =
+        static_cast<BufferQueue::ConsumerListener*>(this);
+    sp<BufferQueue::ProxyConsumerListener> proxy =
+        new BufferQueue::ProxyConsumerListener(listener);
+
+    err = mConsumer->consumerConnect(proxy, false);
+    if (err != NO_ERROR) {
+        ALOGE("Error connecting to BufferQueue: %s (%d)",
+                strerror(-err), err);
+        return err;
+    }
+
+    ALOGV("init() successful.");
+
+    return OK;
+}
+
+void GraphicBufferListener::onFrameAvailable(const BufferItem& /* item */) {
+    ALOGV("onFrameAvailable() called");
+
+    {
+        Mutex::Autolock autoLock(mMutex);
+        mNumFramesAvailable++;
+    }
+
+    sp<AMessage> notify = mNotify->dup();
+    mNotify->setWhat(kWhatFrameAvailable);
+    mNotify->post();
+}
+
+void GraphicBufferListener::onBuffersReleased() {
+    ALOGV("onBuffersReleased() called");
+    // nothing to do
+}
+
+void GraphicBufferListener::onSidebandStreamChanged() {
+    ALOGW("GraphicBufferListener cannot consume sideband streams.");
+    // nothing to do
+}
+
+BufferQueue::BufferItem GraphicBufferListener::getBufferItem() {
+    BufferQueue::BufferItem item;
+
+    {
+        Mutex::Autolock autoLock(mMutex);
+        if (mNumFramesAvailable <= 0) {
+            ALOGE("getBuffer() called with no frames available");
+            return item;
+        }
+        mNumFramesAvailable--;
+    }
+
+    status_t err = mConsumer->acquireBuffer(&item, 0);
+    if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
+        // shouldn't happen, since we track num frames available
+        ALOGE("frame was not available");
+        item.mBuf = -1;
+        return item;
+    } else if (err != OK) {
+        ALOGE("acquireBuffer returned err=%d", err);
+        item.mBuf = -1;
+        return item;
+    }
+
+    // Wait for it to become available.
+    err = item.mFence->waitForever("GraphicBufferListener::getBufferItem");
+    if (err != OK) {
+        ALOGW("failed to wait for buffer fence: %d", err);
+        // keep going
+    }
+
+    // If this is the first time we're seeing this buffer, add it to our
+    // slot table.
+    if (item.mGraphicBuffer != NULL) {
+        ALOGV("setting mBufferSlot %d", item.mBuf);
+        mBufferSlot[item.mBuf] = item.mGraphicBuffer;
+    }
+
+    return item;
+}
+
+sp<GraphicBuffer> GraphicBufferListener::getBuffer(
+        BufferQueue::BufferItem item) {
+    sp<GraphicBuffer> buf;
+    if (item.mBuf < 0 || item.mBuf >= BufferQueue::NUM_BUFFER_SLOTS) {
+        ALOGE("getBuffer() received invalid BufferItem: mBuf==%d", item.mBuf);
+        return buf;
+    }
+
+    buf = mBufferSlot[item.mBuf];
+    CHECK(buf.get() != NULL);
+
+    return buf;
+}
+
+status_t GraphicBufferListener::releaseBuffer(
+        BufferQueue::BufferItem item) {
+    if (item.mBuf < 0 || item.mBuf >= BufferQueue::NUM_BUFFER_SLOTS) {
+        ALOGE("getBuffer() received invalid BufferItem: mBuf==%d", item.mBuf);
+        return ERROR_OUT_OF_RANGE;
+    }
+
+    mConsumer->releaseBuffer(item.mBuf, item.mFrameNumber,
+            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE);
+
+    return OK;
+}
+
+}   // namespace android
diff --git a/media/libstagefright/filters/GraphicBufferListener.h b/media/libstagefright/filters/GraphicBufferListener.h
new file mode 100644
index 0000000..b3e0ee3
--- /dev/null
+++ b/media/libstagefright/filters/GraphicBufferListener.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GRAPHIC_BUFFER_LISTENER_H_
+#define GRAPHIC_BUFFER_LISTENER_H_
+
+#include <gui/BufferQueue.h>
+
+namespace android {
+
+struct AMessage;
+
+struct GraphicBufferListener : public BufferQueue::ConsumerListener {
+public:
+    GraphicBufferListener() {};
+
+    status_t init(
+            const sp<AMessage> &notify,
+            size_t bufferWidth, size_t bufferHeight, size_t bufferCount);
+
+    virtual void onFrameAvailable(const BufferItem& item);
+    virtual void onBuffersReleased();
+    virtual void onSidebandStreamChanged();
+
+    // Returns the handle to the producer side of the BufferQueue.  Buffers
+    // queued on this will be received by GraphicBufferListener.
+    sp<IGraphicBufferProducer> getIGraphicBufferProducer() const {
+        return mProducer;
+    }
+
+    BufferQueue::BufferItem getBufferItem();
+    sp<GraphicBuffer> getBuffer(BufferQueue::BufferItem item);
+    status_t releaseBuffer(BufferQueue::BufferItem item);
+
+    enum {
+        kWhatFrameAvailable = 'frav',
+    };
+
+private:
+    sp<AMessage> mNotify;
+    size_t mNumFramesAvailable;
+
+    mutable Mutex mMutex;
+
+    // Our BufferQueue interfaces. mProducer is passed to the producer through
+    // getIGraphicBufferProducer, and mConsumer is used internally to retrieve
+    // the buffers queued by the producer.
+    sp<IGraphicBufferProducer> mProducer;
+    sp<IGraphicBufferConsumer> mConsumer;
+
+    // Cache of GraphicBuffers from the buffer queue.
+    sp<GraphicBuffer> mBufferSlot[BufferQueue::NUM_BUFFER_SLOTS];
+};
+
+}   // namespace android
+
+#endif  // GRAPHIC_BUFFER_LISTENER_H
diff --git a/media/libstagefright/filters/IntrinsicBlurFilter.cpp b/media/libstagefright/filters/IntrinsicBlurFilter.cpp
new file mode 100644
index 0000000..cbcf699
--- /dev/null
+++ b/media/libstagefright/filters/IntrinsicBlurFilter.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "IntrinsicBlurFilter"
+
+#include <utils/Log.h>
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include "IntrinsicBlurFilter.h"
+
+namespace android {
+
+status_t IntrinsicBlurFilter::configure(const sp<AMessage> &msg) {
+    status_t err = SimpleFilter::configure(msg);
+    if (err != OK) {
+        return err;
+    }
+
+    if (!msg->findString("cacheDir", &mCacheDir)) {
+        ALOGE("Failed to find cache directory in config message.");
+        return NAME_NOT_FOUND;
+    }
+
+    return OK;
+}
+
+status_t IntrinsicBlurFilter::start() {
+    // TODO: use a single RS context object for entire application
+    mRS = new RSC::RS();
+
+    if (!mRS->init(mCacheDir.c_str())) {
+        ALOGE("Failed to initialize RenderScript context.");
+        return NO_INIT;
+    }
+
+    // 32-bit elements for ARGB8888
+    RSC::sp<const RSC::Element> e = RSC::Element::U8_4(mRS);
+
+    RSC::Type::Builder tb(mRS, e);
+    tb.setX(mWidth);
+    tb.setY(mHeight);
+    RSC::sp<const RSC::Type> t = tb.create();
+
+    mAllocIn = RSC::Allocation::createTyped(mRS, t);
+    mAllocOut = RSC::Allocation::createTyped(mRS, t);
+
+    mBlur = RSC::ScriptIntrinsicBlur::create(mRS, e);
+    mBlur->setRadius(mBlurRadius);
+    mBlur->setInput(mAllocIn);
+
+    return OK;
+}
+
+void IntrinsicBlurFilter::reset() {
+    mBlur.clear();
+    mAllocOut.clear();
+    mAllocIn.clear();
+    mRS.clear();
+}
+
+status_t IntrinsicBlurFilter::setParameters(const sp<AMessage> &msg) {
+    sp<AMessage> params;
+    CHECK(msg->findMessage("params", &params));
+
+    float blurRadius;
+    if (params->findFloat("blur-radius", &blurRadius)) {
+        mBlurRadius = blurRadius;
+    }
+
+    return OK;
+}
+
+status_t IntrinsicBlurFilter::processBuffers(
+        const sp<ABuffer> &srcBuffer, const sp<ABuffer> &outBuffer) {
+    mAllocIn->copy1DRangeFrom(0, mWidth * mHeight, srcBuffer->data());
+    mBlur->forEach(mAllocOut);
+    mAllocOut->copy1DRangeTo(0, mWidth * mHeight, outBuffer->data());
+
+    return OK;
+}
+
+}   // namespace android
diff --git a/media/libstagefright/filters/IntrinsicBlurFilter.h b/media/libstagefright/filters/IntrinsicBlurFilter.h
new file mode 100644
index 0000000..4707ab7
--- /dev/null
+++ b/media/libstagefright/filters/IntrinsicBlurFilter.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INTRINSIC_BLUR_FILTER_H_
+#define INTRINSIC_BLUR_FILTER_H_
+
+#include "RenderScript.h"
+#include "SimpleFilter.h"
+
+namespace android {
+
+struct IntrinsicBlurFilter : public SimpleFilter {
+public:
+    IntrinsicBlurFilter() : mBlurRadius(1.f) {};
+
+    virtual status_t configure(const sp<AMessage> &msg);
+    virtual status_t start();
+    virtual void reset();
+    virtual status_t setParameters(const sp<AMessage> &msg);
+    virtual status_t processBuffers(
+            const sp<ABuffer> &srcBuffer, const sp<ABuffer> &outBuffer);
+
+protected:
+    virtual ~IntrinsicBlurFilter() {};
+
+private:
+    AString mCacheDir;
+    RSC::sp<RSC::RS> mRS;
+    RSC::sp<RSC::Allocation> mAllocIn;
+    RSC::sp<RSC::Allocation> mAllocOut;
+    RSC::sp<RSC::ScriptIntrinsicBlur> mBlur;
+    float mBlurRadius;
+};
+
+}   // namespace android
+
+#endif  // INTRINSIC_BLUR_FILTER_H_
diff --git a/media/libstagefright/filters/MediaFilter.cpp b/media/libstagefright/filters/MediaFilter.cpp
new file mode 100644
index 0000000..c5289b6
--- /dev/null
+++ b/media/libstagefright/filters/MediaFilter.cpp
@@ -0,0 +1,816 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaFilter"
+
+#include <inttypes.h>
+#include <utils/Trace.h>
+
+#include <binder/MemoryDealer.h>
+
+#include <media/stagefright/BufferProducerWrapper.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaFilter.h>
+
+#include "ColorConvert.h"
+#include "GraphicBufferListener.h"
+#include "IntrinsicBlurFilter.h"
+#include "RSFilter.h"
+#include "SaturationFilter.h"
+#include "ZeroFilter.h"
+
+namespace android {
+
+// parameter: number of input and output buffers
+static const size_t kBufferCountActual = 4;
+
+MediaFilter::MediaFilter()
+    : mState(UNINITIALIZED),
+      mGeneration(0),
+      mGraphicBufferListener(NULL) {
+}
+
+MediaFilter::~MediaFilter() {
+}
+
+//////////////////// PUBLIC FUNCTIONS //////////////////////////////////////////
+
+void MediaFilter::setNotificationMessage(const sp<AMessage> &msg) {
+    mNotify = msg;
+}
+
+void MediaFilter::initiateAllocateComponent(const sp<AMessage> &msg) {
+    msg->setWhat(kWhatAllocateComponent);
+    msg->setTarget(id());
+    msg->post();
+}
+
+void MediaFilter::initiateConfigureComponent(const sp<AMessage> &msg) {
+    msg->setWhat(kWhatConfigureComponent);
+    msg->setTarget(id());
+    msg->post();
+}
+
+void MediaFilter::initiateCreateInputSurface() {
+    (new AMessage(kWhatCreateInputSurface, id()))->post();
+}
+
+void MediaFilter::initiateStart() {
+    (new AMessage(kWhatStart, id()))->post();
+}
+
+void MediaFilter::initiateShutdown(bool keepComponentAllocated) {
+    sp<AMessage> msg = new AMessage(kWhatShutdown, id());
+    msg->setInt32("keepComponentAllocated", keepComponentAllocated);
+    msg->post();
+}
+
+void MediaFilter::signalFlush() {
+    (new AMessage(kWhatFlush, id()))->post();
+}
+
+void MediaFilter::signalResume() {
+    (new AMessage(kWhatResume, id()))->post();
+}
+
+// nothing to do
+void MediaFilter::signalRequestIDRFrame() {
+    return;
+}
+
+void MediaFilter::signalSetParameters(const sp<AMessage> &params) {
+    sp<AMessage> msg = new AMessage(kWhatSetParameters, id());
+    msg->setMessage("params", params);
+    msg->post();
+}
+
+void MediaFilter::signalEndOfInputStream() {
+    (new AMessage(kWhatSignalEndOfInputStream, id()))->post();
+}
+
+void MediaFilter::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatAllocateComponent:
+        {
+            onAllocateComponent(msg);
+            break;
+        }
+        case kWhatConfigureComponent:
+        {
+            onConfigureComponent(msg);
+            break;
+        }
+        case kWhatStart:
+        {
+            onStart();
+            break;
+        }
+        case kWhatProcessBuffers:
+        {
+            processBuffers();
+            break;
+        }
+        case kWhatInputBufferFilled:
+        {
+            onInputBufferFilled(msg);
+            break;
+        }
+        case kWhatOutputBufferDrained:
+        {
+            onOutputBufferDrained(msg);
+            break;
+        }
+        case kWhatShutdown:
+        {
+            onShutdown(msg);
+            break;
+        }
+        case kWhatFlush:
+        {
+            onFlush();
+            break;
+        }
+        case kWhatResume:
+        {
+            // nothing to do
+            break;
+        }
+        case kWhatSetParameters:
+        {
+            onSetParameters(msg);
+            break;
+        }
+        case kWhatCreateInputSurface:
+        {
+            onCreateInputSurface();
+            break;
+        }
+        case GraphicBufferListener::kWhatFrameAvailable:
+        {
+            onInputFrameAvailable();
+            break;
+        }
+        case kWhatSignalEndOfInputStream:
+        {
+            onSignalEndOfInputStream();
+            break;
+        }
+        default:
+        {
+            ALOGE("Message not handled:\n%s", msg->debugString().c_str());
+            break;
+        }
+    }
+}
+
+//////////////////// PORT DESCRIPTION //////////////////////////////////////////
+
+MediaFilter::PortDescription::PortDescription() {
+}
+
+void MediaFilter::PortDescription::addBuffer(
+        IOMX::buffer_id id, const sp<ABuffer> &buffer) {
+    mBufferIDs.push_back(id);
+    mBuffers.push_back(buffer);
+}
+
+size_t MediaFilter::PortDescription::countBuffers() {
+    return mBufferIDs.size();
+}
+
+IOMX::buffer_id MediaFilter::PortDescription::bufferIDAt(size_t index) const {
+    return mBufferIDs.itemAt(index);
+}
+
+sp<ABuffer> MediaFilter::PortDescription::bufferAt(size_t index) const {
+    return mBuffers.itemAt(index);
+}
+
+//////////////////// HELPER FUNCTIONS //////////////////////////////////////////
+
+void MediaFilter::signalProcessBuffers() {
+    (new AMessage(kWhatProcessBuffers, id()))->post();
+}
+
+void MediaFilter::signalError(status_t error) {
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", CodecBase::kWhatError);
+    notify->setInt32("err", error);
+    notify->post();
+}
+
+status_t MediaFilter::allocateBuffersOnPort(OMX_U32 portIndex) {
+    CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput);
+    const bool isInput = portIndex == kPortIndexInput;
+    const size_t bufferSize = isInput ? mMaxInputSize : mMaxOutputSize;
+
+    CHECK(mDealer[portIndex] == NULL);
+    CHECK(mBuffers[portIndex].isEmpty());
+
+    ALOGV("Allocating %zu buffers of size %zu on %s port",
+            kBufferCountActual, bufferSize,
+            isInput ? "input" : "output");
+
+    size_t totalSize = kBufferCountActual * bufferSize;
+
+    mDealer[portIndex] = new MemoryDealer(totalSize, "MediaFilter");
+
+    for (size_t i = 0; i < kBufferCountActual; ++i) {
+        sp<IMemory> mem = mDealer[portIndex]->allocate(bufferSize);
+        CHECK(mem.get() != NULL);
+
+        BufferInfo info;
+        info.mStatus = BufferInfo::OWNED_BY_US;
+        info.mBufferID = i;
+        info.mGeneration = mGeneration;
+        info.mOutputFlags = 0;
+        info.mData = new ABuffer(mem->pointer(), bufferSize);
+        info.mData->meta()->setInt64("timeUs", 0);
+
+        mBuffers[portIndex].push_back(info);
+
+        if (!isInput) {
+            mAvailableOutputBuffers.push(
+                    &mBuffers[portIndex].editItemAt(i));
+        }
+    }
+
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", CodecBase::kWhatBuffersAllocated);
+
+    notify->setInt32("portIndex", portIndex);
+
+    sp<PortDescription> desc = new PortDescription;
+
+    for (size_t i = 0; i < mBuffers[portIndex].size(); ++i) {
+        const BufferInfo &info = mBuffers[portIndex][i];
+
+        desc->addBuffer(info.mBufferID, info.mData);
+    }
+
+    notify->setObject("portDesc", desc);
+    notify->post();
+
+    return OK;
+}
+
+MediaFilter::BufferInfo* MediaFilter::findBufferByID(
+        uint32_t portIndex, IOMX::buffer_id bufferID,
+        ssize_t *index) {
+    for (size_t i = 0; i < mBuffers[portIndex].size(); ++i) {
+        BufferInfo *info = &mBuffers[portIndex].editItemAt(i);
+
+        if (info->mBufferID == bufferID) {
+            if (index != NULL) {
+                *index = i;
+            }
+            return info;
+        }
+    }
+
+    TRESPASS();
+
+    return NULL;
+}
+
+void MediaFilter::postFillThisBuffer(BufferInfo *info) {
+    ALOGV("postFillThisBuffer on buffer %d", info->mBufferID);
+    if (mPortEOS[kPortIndexInput]) {
+        return;
+    }
+
+    CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_US);
+
+    info->mGeneration = mGeneration;
+
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", CodecBase::kWhatFillThisBuffer);
+    notify->setInt32("buffer-id", info->mBufferID);
+
+    info->mData->meta()->clear();
+    notify->setBuffer("buffer", info->mData);
+
+    sp<AMessage> reply = new AMessage(kWhatInputBufferFilled, id());
+    reply->setInt32("buffer-id", info->mBufferID);
+
+    notify->setMessage("reply", reply);
+
+    info->mStatus = BufferInfo::OWNED_BY_UPSTREAM;
+    notify->post();
+}
+
+void MediaFilter::postDrainThisBuffer(BufferInfo *info) {
+    CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_US);
+
+    info->mGeneration = mGeneration;
+
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", CodecBase::kWhatDrainThisBuffer);
+    notify->setInt32("buffer-id", info->mBufferID);
+    notify->setInt32("flags", info->mOutputFlags);
+    notify->setBuffer("buffer", info->mData);
+
+    sp<AMessage> reply = new AMessage(kWhatOutputBufferDrained, id());
+    reply->setInt32("buffer-id", info->mBufferID);
+
+    notify->setMessage("reply", reply);
+
+    notify->post();
+
+    info->mStatus = BufferInfo::OWNED_BY_UPSTREAM;
+}
+
+void MediaFilter::postEOS() {
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", CodecBase::kWhatEOS);
+    notify->setInt32("err", ERROR_END_OF_STREAM);
+    notify->post();
+
+    ALOGV("Sent kWhatEOS.");
+}
+
+void MediaFilter::sendFormatChange() {
+    sp<AMessage> notify = mNotify->dup();
+
+    notify->setInt32("what", kWhatOutputFormatChanged);
+
+    AString mime;
+    CHECK(mOutputFormat->findString("mime", &mime));
+    notify->setString("mime", mime.c_str());
+
+    notify->setInt32("stride", mStride);
+    notify->setInt32("slice-height", mSliceHeight);
+    notify->setInt32("color-format", mColorFormatOut);
+    notify->setRect("crop", 0, 0, mStride - 1, mSliceHeight - 1);
+    notify->setInt32("width", mWidth);
+    notify->setInt32("height", mHeight);
+
+    notify->post();
+}
+
+void MediaFilter::requestFillEmptyInput() {
+    if (mPortEOS[kPortIndexInput]) {
+        return;
+    }
+
+    for (size_t i = 0; i < mBuffers[kPortIndexInput].size(); ++i) {
+        BufferInfo *info = &mBuffers[kPortIndexInput].editItemAt(i);
+
+        if (info->mStatus == BufferInfo::OWNED_BY_US) {
+            postFillThisBuffer(info);
+        }
+    }
+}
+
+void MediaFilter::processBuffers() {
+    if (mAvailableInputBuffers.empty() || mAvailableOutputBuffers.empty()) {
+        ALOGV("Skipping process (buffers unavailable)");
+        return;
+    }
+
+    if (mPortEOS[kPortIndexOutput]) {
+        // TODO notify caller of queueInput error when it is supported
+        // in MediaCodec
+        ALOGW("Tried to process a buffer after EOS.");
+        return;
+    }
+
+    BufferInfo *inputInfo = mAvailableInputBuffers[0];
+    mAvailableInputBuffers.removeAt(0);
+    BufferInfo *outputInfo = mAvailableOutputBuffers[0];
+    mAvailableOutputBuffers.removeAt(0);
+
+    status_t err;
+    err = mFilter->processBuffers(inputInfo->mData, outputInfo->mData);
+    if (err != (status_t)OK) {
+        outputInfo->mData->meta()->setInt32("err", err);
+    }
+
+    int64_t timeUs;
+    CHECK(inputInfo->mData->meta()->findInt64("timeUs", &timeUs));
+    outputInfo->mData->meta()->setInt64("timeUs", timeUs);
+    outputInfo->mOutputFlags = 0;
+    int32_t eos = 0;
+    if (inputInfo->mData->meta()->findInt32("eos", &eos) && eos != 0) {
+        outputInfo->mOutputFlags |= OMX_BUFFERFLAG_EOS;
+        mPortEOS[kPortIndexOutput] = true;
+        outputInfo->mData->meta()->setInt32("eos", eos);
+        postEOS();
+        ALOGV("Output stream saw EOS.");
+    }
+
+    ALOGV("Processed input buffer %u [%zu], output buffer %u [%zu]",
+                inputInfo->mBufferID, inputInfo->mData->size(),
+                outputInfo->mBufferID, outputInfo->mData->size());
+
+    if (mGraphicBufferListener != NULL) {
+        delete inputInfo;
+    } else {
+        postFillThisBuffer(inputInfo);
+    }
+    postDrainThisBuffer(outputInfo);
+
+    // prevent any corner case where buffers could get stuck in queue
+    signalProcessBuffers();
+}
+
+void MediaFilter::onAllocateComponent(const sp<AMessage> &msg) {
+    CHECK_EQ(mState, UNINITIALIZED);
+
+    CHECK(msg->findString("componentName", &mComponentName));
+    const char* name = mComponentName.c_str();
+    if (!strcasecmp(name, "android.filter.zerofilter")) {
+        mFilter = new ZeroFilter;
+    } else if (!strcasecmp(name, "android.filter.saturation")) {
+        mFilter = new SaturationFilter;
+    } else if (!strcasecmp(name, "android.filter.intrinsicblur")) {
+        mFilter = new IntrinsicBlurFilter;
+    } else if (!strcasecmp(name, "android.filter.RenderScript")) {
+        mFilter = new RSFilter;
+    } else {
+        ALOGE("Unrecognized filter name: %s", name);
+        signalError(NAME_NOT_FOUND);
+        return;
+    }
+
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", kWhatComponentAllocated);
+    // HACK - need "OMX.google" to use MediaCodec's software renderer
+    notify->setString("componentName", "OMX.google.MediaFilter");
+    notify->post();
+    mState = INITIALIZED;
+    ALOGV("Handled kWhatAllocateComponent.");
+}
+
+void MediaFilter::onConfigureComponent(const sp<AMessage> &msg) {
+    // TODO: generalize to allow audio filters as well as video
+
+    CHECK_EQ(mState, INITIALIZED);
+
+    // get params - at least mime, width & height
+    AString mime;
+    CHECK(msg->findString("mime", &mime));
+    if (strcasecmp(mime.c_str(), MEDIA_MIMETYPE_VIDEO_RAW)) {
+        ALOGE("Bad mime: %s", mime.c_str());
+        signalError(BAD_VALUE);
+        return;
+    }
+
+    CHECK(msg->findInt32("width", &mWidth));
+    CHECK(msg->findInt32("height", &mHeight));
+    if (!msg->findInt32("stride", &mStride)) {
+        mStride = mWidth;
+    }
+    if (!msg->findInt32("slice-height", &mSliceHeight)) {
+        mSliceHeight = mHeight;
+    }
+
+    mMaxInputSize = mWidth * mHeight * 4;   // room for ARGB8888
+    int32_t maxInputSize;
+    if (msg->findInt32("max-input-size", &maxInputSize)
+            && (size_t)maxInputSize > mMaxInputSize) {
+        mMaxInputSize = maxInputSize;
+    }
+
+    if (!msg->findInt32("color-format", &mColorFormatIn)) {
+        // default to OMX_COLOR_Format32bitARGB8888
+        mColorFormatIn = OMX_COLOR_Format32bitARGB8888;
+        msg->setInt32("color-format", mColorFormatIn);
+    }
+    mColorFormatOut = mColorFormatIn;
+
+    mMaxOutputSize = mWidth * mHeight * 4;  // room for ARGB8888
+
+    AString cacheDir;
+    if (!msg->findString("cacheDir", &cacheDir)) {
+        ALOGE("Failed to find cache directory in config message.");
+        signalError(NAME_NOT_FOUND);
+        return;
+    }
+
+    status_t err;
+    err = mFilter->configure(msg);
+    if (err != (status_t)OK) {
+        ALOGE("Failed to configure filter component, err %d", err);
+        signalError(err);
+        return;
+    }
+
+    mInputFormat = new AMessage();
+    mInputFormat->setString("mime", mime.c_str());
+    mInputFormat->setInt32("stride", mStride);
+    mInputFormat->setInt32("slice-height", mSliceHeight);
+    mInputFormat->setInt32("color-format", mColorFormatIn);
+    mInputFormat->setRect("crop", 0, 0, mStride, mSliceHeight);
+    mInputFormat->setInt32("width", mWidth);
+    mInputFormat->setInt32("height", mHeight);
+
+    mOutputFormat = new AMessage();
+    mOutputFormat->setString("mime", mime.c_str());
+    mOutputFormat->setInt32("stride", mStride);
+    mOutputFormat->setInt32("slice-height", mSliceHeight);
+    mOutputFormat->setInt32("color-format", mColorFormatOut);
+    mOutputFormat->setRect("crop", 0, 0, mStride, mSliceHeight);
+    mOutputFormat->setInt32("width", mWidth);
+    mOutputFormat->setInt32("height", mHeight);
+
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", kWhatComponentConfigured);
+    notify->setString("componentName", "MediaFilter");
+    notify->setMessage("input-format", mInputFormat);
+    notify->setMessage("output-format", mOutputFormat);
+    notify->post();
+    mState = CONFIGURED;
+    ALOGV("Handled kWhatConfigureComponent.");
+
+    sendFormatChange();
+}
+
+void MediaFilter::onStart() {
+    CHECK_EQ(mState, CONFIGURED);
+
+    allocateBuffersOnPort(kPortIndexInput);
+
+    allocateBuffersOnPort(kPortIndexOutput);
+
+    status_t err = mFilter->start();
+    if (err != (status_t)OK) {
+        ALOGE("Failed to start filter component, err %d", err);
+        signalError(err);
+        return;
+    }
+
+    mPortEOS[kPortIndexInput] = false;
+    mPortEOS[kPortIndexOutput] = false;
+    mInputEOSResult = OK;
+    mState = STARTED;
+
+    requestFillEmptyInput();
+    ALOGV("Handled kWhatStart.");
+}
+
+void MediaFilter::onInputBufferFilled(const sp<AMessage> &msg) {
+    IOMX::buffer_id bufferID;
+    CHECK(msg->findInt32("buffer-id", (int32_t*)&bufferID));
+    BufferInfo *info = findBufferByID(kPortIndexInput, bufferID);
+
+    if (mState != STARTED) {
+        // we're not running, so we'll just keep that buffer...
+        info->mStatus = BufferInfo::OWNED_BY_US;
+        return;
+    }
+
+    if (info->mGeneration != mGeneration) {
+        ALOGV("Caught a stale input buffer [ID %d]", bufferID);
+        // buffer is stale (taken before a flush/shutdown) - repost it
+        CHECK_EQ(info->mStatus, BufferInfo::OWNED_BY_US);
+        postFillThisBuffer(info);
+        return;
+    }
+
+    CHECK_EQ(info->mStatus, BufferInfo::OWNED_BY_UPSTREAM);
+    info->mStatus = BufferInfo::OWNED_BY_US;
+
+    sp<ABuffer> buffer;
+    int32_t err = OK;
+    bool eos = false;
+
+    if (!msg->findBuffer("buffer", &buffer)) {
+        // these are unfilled buffers returned by client
+        CHECK(msg->findInt32("err", &err));
+
+        if (err == OK) {
+            // buffers with no errors are returned on MediaCodec.flush
+            ALOGV("saw unfilled buffer (MediaCodec.flush)");
+            postFillThisBuffer(info);
+            return;
+        } else {
+            ALOGV("saw error %d instead of an input buffer", err);
+            eos = true;
+        }
+
+        buffer.clear();
+    }
+
+    int32_t isCSD;
+    if (buffer != NULL && buffer->meta()->findInt32("csd", &isCSD)
+            && isCSD != 0) {
+        // ignore codec-specific data buffers
+        ALOGW("MediaFilter received a codec-specific data buffer");
+        postFillThisBuffer(info);
+        return;
+    }
+
+    int32_t tmp;
+    if (buffer != NULL && buffer->meta()->findInt32("eos", &tmp) && tmp) {
+        eos = true;
+        err = ERROR_END_OF_STREAM;
+    }
+
+    mAvailableInputBuffers.push_back(info);
+    processBuffers();
+
+    if (eos) {
+        mPortEOS[kPortIndexInput] = true;
+        mInputEOSResult = err;
+    }
+
+    ALOGV("Handled kWhatInputBufferFilled. [ID %u]", bufferID);
+}
+
+void MediaFilter::onOutputBufferDrained(const sp<AMessage> &msg) {
+    IOMX::buffer_id bufferID;
+    CHECK(msg->findInt32("buffer-id", (int32_t*)&bufferID));
+    BufferInfo *info = findBufferByID(kPortIndexOutput, bufferID);
+
+    if (mState != STARTED) {
+        // we're not running, so we'll just keep that buffer...
+        info->mStatus = BufferInfo::OWNED_BY_US;
+        return;
+    }
+
+    if (info->mGeneration != mGeneration) {
+        ALOGV("Caught a stale output buffer [ID %d]", bufferID);
+        // buffer is stale (taken before a flush/shutdown) - keep it
+        CHECK_EQ(info->mStatus, BufferInfo::OWNED_BY_US);
+        return;
+    }
+
+    CHECK_EQ(info->mStatus, BufferInfo::OWNED_BY_UPSTREAM);
+    info->mStatus = BufferInfo::OWNED_BY_US;
+
+    mAvailableOutputBuffers.push_back(info);
+
+    processBuffers();
+
+    ALOGV("Handled kWhatOutputBufferDrained. [ID %u]",
+            bufferID);
+}
+
+void MediaFilter::onShutdown(const sp<AMessage> &msg) {
+    mGeneration++;
+
+    if (mState != UNINITIALIZED) {
+        mFilter->reset();
+    }
+
+    int32_t keepComponentAllocated;
+    CHECK(msg->findInt32("keepComponentAllocated", &keepComponentAllocated));
+    if (!keepComponentAllocated || mState == UNINITIALIZED) {
+        mState = UNINITIALIZED;
+    } else {
+        mState = INITIALIZED;
+    }
+
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", CodecBase::kWhatShutdownCompleted);
+    notify->post();
+}
+
+void MediaFilter::onFlush() {
+    mGeneration++;
+
+    mAvailableInputBuffers.clear();
+    for (size_t i = 0; i < mBuffers[kPortIndexInput].size(); ++i) {
+        BufferInfo *info = &mBuffers[kPortIndexInput].editItemAt(i);
+        info->mStatus = BufferInfo::OWNED_BY_US;
+    }
+    mAvailableOutputBuffers.clear();
+    for (size_t i = 0; i < mBuffers[kPortIndexOutput].size(); ++i) {
+        BufferInfo *info = &mBuffers[kPortIndexOutput].editItemAt(i);
+        info->mStatus = BufferInfo::OWNED_BY_US;
+        mAvailableOutputBuffers.push_back(info);
+    }
+
+    mPortEOS[kPortIndexInput] = false;
+    mPortEOS[kPortIndexOutput] = false;
+    mInputEOSResult = OK;
+
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", CodecBase::kWhatFlushCompleted);
+    notify->post();
+    ALOGV("Posted kWhatFlushCompleted");
+
+    // MediaCodec returns all input buffers after flush, so in
+    // onInputBufferFilled we call postFillThisBuffer on them
+}
+
+void MediaFilter::onSetParameters(const sp<AMessage> &msg) {
+    CHECK(mState != STARTED);
+
+    status_t err = mFilter->setParameters(msg);
+    if (err != (status_t)OK) {
+        ALOGE("setParameters returned err %d", err);
+    }
+}
+
+void MediaFilter::onCreateInputSurface() {
+    CHECK(mState == CONFIGURED);
+
+    mGraphicBufferListener = new GraphicBufferListener;
+
+    sp<AMessage> notify = new AMessage();
+    notify->setTarget(id());
+    status_t err = mGraphicBufferListener->init(
+            notify, mStride, mSliceHeight, kBufferCountActual);
+
+    if (err != OK) {
+        ALOGE("Failed to init mGraphicBufferListener: %d", err);
+        signalError(err);
+        return;
+    }
+
+    sp<AMessage> reply = mNotify->dup();
+    reply->setInt32("what", CodecBase::kWhatInputSurfaceCreated);
+    reply->setObject(
+            "input-surface",
+            new BufferProducerWrapper(
+                    mGraphicBufferListener->getIGraphicBufferProducer()));
+    reply->post();
+}
+
+void MediaFilter::onInputFrameAvailable() {
+    BufferQueue::BufferItem item = mGraphicBufferListener->getBufferItem();
+    sp<GraphicBuffer> buf = mGraphicBufferListener->getBuffer(item);
+
+    // get pointer to graphic buffer
+    void* bufPtr;
+    buf->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &bufPtr);
+
+    // HACK - there is no OMX_COLOR_FORMATTYPE value for RGBA, so the format
+    // conversion is hardcoded until we add this.
+    // TODO: check input format and convert only if necessary
+    // copy RGBA graphic buffer into temporary ARGB input buffer
+    BufferInfo *inputInfo = new BufferInfo;
+    inputInfo->mData = new ABuffer(buf->getWidth() * buf->getHeight() * 4);
+    ALOGV("Copying surface data into temp buffer.");
+    convertRGBAToARGB(
+            (uint8_t*)bufPtr, buf->getWidth(), buf->getHeight(),
+            buf->getStride(), inputInfo->mData->data());
+    inputInfo->mBufferID = item.mBuf;
+    inputInfo->mGeneration = mGeneration;
+    inputInfo->mOutputFlags = 0;
+    inputInfo->mStatus = BufferInfo::OWNED_BY_US;
+    inputInfo->mData->meta()->setInt64("timeUs", item.mTimestamp / 1000);
+
+    mAvailableInputBuffers.push_back(inputInfo);
+
+    mGraphicBufferListener->releaseBuffer(item);
+
+    signalProcessBuffers();
+}
+
+void MediaFilter::onSignalEndOfInputStream() {
+    // if using input surface, need to send an EOS output buffer
+    if (mGraphicBufferListener != NULL) {
+        Vector<BufferInfo> *outputBufs = &mBuffers[kPortIndexOutput];
+        BufferInfo* eosBuf;
+        bool foundBuf = false;
+        for (size_t i = 0; i < kBufferCountActual; i++) {
+            eosBuf = &outputBufs->editItemAt(i);
+            if (eosBuf->mStatus == BufferInfo::OWNED_BY_US) {
+                foundBuf = true;
+                break;
+            }
+        }
+
+        if (!foundBuf) {
+            ALOGE("onSignalEndOfInputStream failed to find an output buffer");
+            return;
+        }
+
+        eosBuf->mOutputFlags = OMX_BUFFERFLAG_EOS;
+        eosBuf->mGeneration = mGeneration;
+        eosBuf->mData->setRange(0, 0);
+        postDrainThisBuffer(eosBuf);
+        ALOGV("Posted EOS on output buffer %zu", eosBuf->mBufferID);
+    }
+
+    mPortEOS[kPortIndexOutput] = true;
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", CodecBase::kWhatSignaledInputEOS);
+    notify->post();
+
+    ALOGV("Output stream saw EOS.");
+}
+
+}   // namespace android
diff --git a/media/libstagefright/filters/RSFilter.cpp b/media/libstagefright/filters/RSFilter.cpp
new file mode 100644
index 0000000..b569945
--- /dev/null
+++ b/media/libstagefright/filters/RSFilter.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "RSFilter"
+
+#include <utils/Log.h>
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include "RSFilter.h"
+
+namespace android {
+
+RSFilter::RSFilter() {
+
+}
+
+RSFilter::~RSFilter() {
+
+}
+
+status_t RSFilter::configure(const sp<AMessage> &msg) {
+    status_t err = SimpleFilter::configure(msg);
+    if (err != OK) {
+        return err;
+    }
+
+    if (!msg->findString("cacheDir", &mCacheDir)) {
+        ALOGE("Failed to find cache directory in config message.");
+        return NAME_NOT_FOUND;
+    }
+
+    sp<RenderScriptWrapper> wrapper;
+    if (!msg->findObject("rs-wrapper", (sp<RefBase>*)&wrapper)) {
+        ALOGE("Failed to find RenderScriptWrapper in config message.");
+        return NAME_NOT_FOUND;
+    }
+
+    mRS = wrapper->mContext;
+    mCallback = wrapper->mCallback;
+
+    return OK;
+}
+
+status_t RSFilter::start() {
+    // 32-bit elements for ARGB8888
+    RSC::sp<const RSC::Element> e = RSC::Element::U8_4(mRS);
+
+    RSC::Type::Builder tb(mRS, e);
+    tb.setX(mWidth);
+    tb.setY(mHeight);
+    RSC::sp<const RSC::Type> t = tb.create();
+
+    mAllocIn = RSC::Allocation::createTyped(mRS, t);
+    mAllocOut = RSC::Allocation::createTyped(mRS, t);
+
+    return OK;
+}
+
+void RSFilter::reset() {
+    mCallback.clear();
+    mAllocOut.clear();
+    mAllocIn.clear();
+    mRS.clear();
+}
+
+status_t RSFilter::setParameters(const sp<AMessage> &msg) {
+    return mCallback->handleSetParameters(msg);
+}
+
+status_t RSFilter::processBuffers(
+        const sp<ABuffer> &srcBuffer, const sp<ABuffer> &outBuffer) {
+    mAllocIn->copy1DRangeFrom(0, mWidth * mHeight, srcBuffer->data());
+    mCallback->processBuffers(mAllocIn.get(), mAllocOut.get());
+    mAllocOut->copy1DRangeTo(0, mWidth * mHeight, outBuffer->data());
+
+    return OK;
+}
+
+}   // namespace android
diff --git a/media/libstagefright/filters/RSFilter.h b/media/libstagefright/filters/RSFilter.h
new file mode 100644
index 0000000..c5b5074
--- /dev/null
+++ b/media/libstagefright/filters/RSFilter.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RS_FILTER_H_
+#define RS_FILTER_H_
+
+#include <media/stagefright/RenderScriptWrapper.h>
+#include <RenderScript.h>
+
+#include "SimpleFilter.h"
+
+namespace android {
+
+struct AString;
+
+struct RSFilter : public SimpleFilter {
+public:
+    RSFilter();
+
+    virtual status_t configure(const sp<AMessage> &msg);
+    virtual status_t start();
+    virtual void reset();
+    virtual status_t setParameters(const sp<AMessage> &msg);
+    virtual status_t processBuffers(
+            const sp<ABuffer> &srcBuffer, const sp<ABuffer> &outBuffer);
+
+protected:
+    virtual ~RSFilter();
+
+private:
+    AString mCacheDir;
+    sp<RenderScriptWrapper::RSFilterCallback> mCallback;
+    RSC::sp<RSC::RS> mRS;
+    RSC::sp<RSC::Allocation> mAllocIn;
+    RSC::sp<RSC::Allocation> mAllocOut;
+};
+
+}   // namespace android
+
+#endif  // RS_FILTER_H_
diff --git a/media/libstagefright/filters/SaturationFilter.cpp b/media/libstagefright/filters/SaturationFilter.cpp
new file mode 100644
index 0000000..ba5f75a
--- /dev/null
+++ b/media/libstagefright/filters/SaturationFilter.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SaturationFilter"
+
+#include <utils/Log.h>
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include "SaturationFilter.h"
+
+namespace android {
+
+status_t SaturationFilter::configure(const sp<AMessage> &msg) {
+    status_t err = SimpleFilter::configure(msg);
+    if (err != OK) {
+        return err;
+    }
+
+    if (!msg->findString("cacheDir", &mCacheDir)) {
+        ALOGE("Failed to find cache directory in config message.");
+        return NAME_NOT_FOUND;
+    }
+
+    return OK;
+}
+
+status_t SaturationFilter::start() {
+    // TODO: use a single RS context object for entire application
+    mRS = new RSC::RS();
+
+    if (!mRS->init(mCacheDir.c_str())) {
+        ALOGE("Failed to initialize RenderScript context.");
+        return NO_INIT;
+    }
+
+    // 32-bit elements for ARGB8888
+    RSC::sp<const RSC::Element> e = RSC::Element::U8_4(mRS);
+
+    RSC::Type::Builder tb(mRS, e);
+    tb.setX(mWidth);
+    tb.setY(mHeight);
+    RSC::sp<const RSC::Type> t = tb.create();
+
+    mAllocIn = RSC::Allocation::createTyped(mRS, t);
+    mAllocOut = RSC::Allocation::createTyped(mRS, t);
+
+    mScript = new ScriptC_saturationARGB(mRS);
+
+    mScript->set_gSaturation(mSaturation);
+
+    return OK;
+}
+
+void SaturationFilter::reset() {
+    mScript.clear();
+    mAllocOut.clear();
+    mAllocIn.clear();
+    mRS.clear();
+}
+
+status_t SaturationFilter::setParameters(const sp<AMessage> &msg) {
+    sp<AMessage> params;
+    CHECK(msg->findMessage("params", &params));
+
+    float saturation;
+    if (params->findFloat("saturation", &saturation)) {
+        mSaturation = saturation;
+    }
+
+    return OK;
+}
+
+status_t SaturationFilter::processBuffers(
+        const sp<ABuffer> &srcBuffer, const sp<ABuffer> &outBuffer) {
+    mAllocIn->copy1DRangeFrom(0, mWidth * mHeight, srcBuffer->data());
+    mScript->forEach_root(mAllocIn, mAllocOut);
+    mAllocOut->copy1DRangeTo(0, mWidth * mHeight, outBuffer->data());
+
+    return OK;
+}
+
+}   // namespace android
diff --git a/media/libstagefright/filters/SaturationFilter.h b/media/libstagefright/filters/SaturationFilter.h
new file mode 100644
index 0000000..0545021
--- /dev/null
+++ b/media/libstagefright/filters/SaturationFilter.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SATURATION_FILTER_H_
+#define SATURATION_FILTER_H_
+
+#include <RenderScript.h>
+
+#include "ScriptC_saturationARGB.h"
+#include "SimpleFilter.h"
+
+namespace android {
+
+struct SaturationFilter : public SimpleFilter {
+public:
+    SaturationFilter() : mSaturation(1.f) {};
+
+    virtual status_t configure(const sp<AMessage> &msg);
+    virtual status_t start();
+    virtual void reset();
+    virtual status_t setParameters(const sp<AMessage> &msg);
+    virtual status_t processBuffers(
+            const sp<ABuffer> &srcBuffer, const sp<ABuffer> &outBuffer);
+
+protected:
+    virtual ~SaturationFilter() {};
+
+private:
+    AString mCacheDir;
+    RSC::sp<RSC::RS> mRS;
+    RSC::sp<RSC::Allocation> mAllocIn;
+    RSC::sp<RSC::Allocation> mAllocOut;
+    RSC::sp<ScriptC_saturationARGB> mScript;
+    float mSaturation;
+};
+
+}   // namespace android
+
+#endif  // SATURATION_FILTER_H_
diff --git a/media/libstagefright/filters/SimpleFilter.cpp b/media/libstagefright/filters/SimpleFilter.cpp
new file mode 100644
index 0000000..6c1ca2c
--- /dev/null
+++ b/media/libstagefright/filters/SimpleFilter.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include "SimpleFilter.h"
+
+namespace android {
+
+status_t SimpleFilter::configure(const sp<AMessage> &msg) {
+    CHECK(msg->findInt32("width", &mWidth));
+    CHECK(msg->findInt32("height", &mHeight));
+    if (!msg->findInt32("stride", &mStride)) {
+        mStride = mWidth;
+    }
+    if (!msg->findInt32("slice-height", &mSliceHeight)) {
+        mSliceHeight = mHeight;
+    }
+    CHECK(msg->findInt32("color-format", &mColorFormatIn));
+    mColorFormatOut = mColorFormatIn;
+
+    return OK;
+}
+
+}   // namespace android
diff --git a/media/libstagefright/filters/SimpleFilter.h b/media/libstagefright/filters/SimpleFilter.h
new file mode 100644
index 0000000..4cd37ef
--- /dev/null
+++ b/media/libstagefright/filters/SimpleFilter.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_FILTER_H_
+#define SIMPLE_FILTER_H_
+
+#include <stdint.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+struct ABuffer;
+struct AMessage;
+
+namespace android {
+
+struct SimpleFilter : public RefBase {
+public:
+    SimpleFilter() : mWidth(0), mHeight(0), mStride(0), mSliceHeight(0),
+            mColorFormatIn(0), mColorFormatOut(0) {};
+
+    virtual status_t configure(const sp<AMessage> &msg);
+
+    virtual status_t start() = 0;
+    virtual void reset() = 0;
+    virtual status_t setParameters(const sp<AMessage> &msg) = 0;
+    virtual status_t processBuffers(
+            const sp<ABuffer> &srcBuffer, const sp<ABuffer> &outBuffer) = 0;
+
+protected:
+    int32_t mWidth, mHeight;
+    int32_t mStride, mSliceHeight;
+    int32_t mColorFormatIn, mColorFormatOut;
+
+    virtual ~SimpleFilter() {};
+};
+
+}   // namespace android
+
+#endif  // SIMPLE_FILTER_H_
diff --git a/media/libstagefright/filters/ZeroFilter.cpp b/media/libstagefright/filters/ZeroFilter.cpp
new file mode 100644
index 0000000..3f1243c
--- /dev/null
+++ b/media/libstagefright/filters/ZeroFilter.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "ZeroFilter"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include "ZeroFilter.h"
+
+namespace android {
+
+status_t ZeroFilter::setParameters(const sp<AMessage> &msg) {
+    sp<AMessage> params;
+    CHECK(msg->findMessage("params", &params));
+
+    int32_t invert;
+    if (params->findInt32("invert", &invert)) {
+        mInvertData = (invert != 0);
+    }
+
+    return OK;
+}
+
+status_t ZeroFilter::processBuffers(
+        const sp<ABuffer> &srcBuffer, const sp<ABuffer> &outBuffer) {
+    // assuming identical input & output buffers, since we're a copy filter
+    if (mInvertData) {
+        uint32_t* src = (uint32_t*)srcBuffer->data();
+        uint32_t* dest = (uint32_t*)outBuffer->data();
+        for (size_t i = 0; i < srcBuffer->size() / 4; ++i) {
+            *(dest++) = *(src++) ^ 0xFFFFFFFF;
+        }
+    } else {
+        memcpy(outBuffer->data(), srcBuffer->data(), srcBuffer->size());
+    }
+    outBuffer->setRange(0, srcBuffer->size());
+
+    return OK;
+}
+
+}   // namespace android
diff --git a/media/libstagefright/filters/ZeroFilter.h b/media/libstagefright/filters/ZeroFilter.h
new file mode 100644
index 0000000..bd34dfb
--- /dev/null
+++ b/media/libstagefright/filters/ZeroFilter.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ZERO_FILTER_H_
+#define ZERO_FILTER_H_
+
+#include "SimpleFilter.h"
+
+namespace android {
+
+struct ZeroFilter : public SimpleFilter {
+public:
+    ZeroFilter() : mInvertData(false) {};
+
+    virtual status_t start() { return OK; };
+    virtual void reset() {};
+    virtual status_t setParameters(const sp<AMessage> &msg);
+    virtual status_t processBuffers(
+            const sp<ABuffer> &srcBuffer, const sp<ABuffer> &outBuffer);
+
+protected:
+    virtual ~ZeroFilter() {};
+
+private:
+    bool mInvertData;
+};
+
+}   // namespace android
+
+#endif  // ZERO_FILTER_H_
diff --git a/media/libstagefright/filters/saturation.rs b/media/libstagefright/filters/saturation.rs
new file mode 100644
index 0000000..2c867ac
--- /dev/null
+++ b/media/libstagefright/filters/saturation.rs
@@ -0,0 +1,40 @@
+// Sample script for RGB888 support (compare to saturationARGB.rs)
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.rs.cppbasic)
+#pragma rs_fp_relaxed
+
+const static float3 gMonoMult = {0.299f, 0.587f, 0.114f};
+
+// global variables (parameters accessible to application code)
+float gSaturation = 1.0f;
+
+void root(const uchar3 *v_in, uchar3 *v_out) {
+    // scale 0-255 uchar to 0-1.0 float
+    float3 in = {v_in->r * 0.003921569f, v_in->g * 0.003921569f,
+            v_in->b * 0.003921569f};
+
+    // apply saturation filter
+    float3 result = dot(in, gMonoMult);
+    result = mix(result, in, gSaturation);
+
+    // convert to uchar, copied from rsPackColorTo8888
+    v_out->x = (uchar)clamp((result.r * 255.f + 0.5f), 0.f, 255.f);
+    v_out->y = (uchar)clamp((result.g * 255.f + 0.5f), 0.f, 255.f);
+    v_out->z = (uchar)clamp((result.b * 255.f + 0.5f), 0.f, 255.f);
+}
diff --git a/media/libstagefright/filters/saturationARGB.rs b/media/libstagefright/filters/saturationARGB.rs
new file mode 100644
index 0000000..1de9dd8
--- /dev/null
+++ b/media/libstagefright/filters/saturationARGB.rs
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.rs.cppbasic)
+#pragma rs_fp_relaxed
+
+const static float3 gMonoMult = {0.299f, 0.587f, 0.114f};
+
+// global variables (parameters accessible to application code)
+float gSaturation = 1.0f;
+
+void root(const uchar4 *v_in, uchar4 *v_out) {
+    v_out->x = v_in->x; // don't modify A
+
+    // get RGB, scale 0-255 uchar to 0-1.0 float
+    float3 rgb = {v_in->y * 0.003921569f, v_in->z * 0.003921569f,
+            v_in->w * 0.003921569f};
+
+    // apply saturation filter
+    float3 result = dot(rgb, gMonoMult);
+    result = mix(result, rgb, gSaturation);
+
+    v_out->y = (uchar)clamp((result.r * 255.f + 0.5f), 0.f, 255.f);
+    v_out->z = (uchar)clamp((result.g * 255.f + 0.5f), 0.f, 255.f);
+    v_out->w = (uchar)clamp((result.b * 255.f + 0.5f), 0.f, 255.f);
+}
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index ef1cd3d..3c8f03e 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -604,8 +604,6 @@
     // having to interpolate.
     // The final AAC frame may well extend into the next RangeInfo but
     // that's ok.
-    // TODO: the logic commented above is skipped because codec cannot take
-    // arbitrary sized input buffers;
     size_t offset = 0;
     while (offset < info.mLength) {
         if (offset + 7 > mBuffer->size()) {
@@ -670,12 +668,9 @@
         size_t headerSize = protection_absent ? 7 : 9;
 
         offset += aac_frame_length;
-        // TODO: move back to concatenation when codec can support arbitrary input buffers.
-        // For now only queue a single buffer
-        break;
     }
 
-    int64_t timeUs = fetchTimestampAAC(offset);
+    int64_t timeUs = fetchTimestamp(offset);
 
     sp<ABuffer> accessUnit = new ABuffer(offset);
     memcpy(accessUnit->data(), mBuffer->data(), offset);
@@ -722,45 +717,6 @@
     return timeUs;
 }
 
-// TODO: avoid interpolating timestamps once codec supports arbitrary sized input buffers
-int64_t ElementaryStreamQueue::fetchTimestampAAC(size_t size) {
-    int64_t timeUs = -1;
-    bool first = true;
-
-    size_t samplesize = size;
-    while (size > 0) {
-        CHECK(!mRangeInfos.empty());
-
-        RangeInfo *info = &*mRangeInfos.begin();
-
-        if (first) {
-            timeUs = info->mTimestampUs;
-            first = false;
-        }
-
-        if (info->mLength > size) {
-            int32_t sampleRate;
-            CHECK(mFormat->findInt32(kKeySampleRate, &sampleRate));
-            info->mLength -= size;
-            size_t numSamples = 1024 * size / samplesize;
-            info->mTimestampUs += numSamples * 1000000ll / sampleRate;
-            size = 0;
-        } else {
-            size -= info->mLength;
-
-            mRangeInfos.erase(mRangeInfos.begin());
-            info = NULL;
-        }
-
-    }
-
-    if (timeUs == 0ll) {
-        ALOGV("Returning 0 timestamp");
-    }
-
-    return timeUs;
-}
-
 struct NALPosition {
     size_t nalOffset;
     size_t nalSize;
diff --git a/media/libstagefright/mpeg2ts/ESQueue.h b/media/libstagefright/mpeg2ts/ESQueue.h
index eb4b1c9..45b4624 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.h
+++ b/media/libstagefright/mpeg2ts/ESQueue.h
@@ -77,7 +77,6 @@
     // consume a logical (compressed) access unit of size "size",
     // returns its timestamp in us (or -1 if no time information).
     int64_t fetchTimestamp(size_t size);
-    int64_t fetchTimestampAAC(size_t size);
 
     DISALLOW_EVIL_CONSTRUCTORS(ElementaryStreamQueue);
 };
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 71e6f83..aee805a 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -401,6 +401,9 @@
             String8 result(kClientLockedString);
             write(fd, result.string(), result.size());
         }
+
+        EffectDumpEffects(fd);
+
         dumpClients(fd, args);
         if (clientLocked) {
             mClientLock.unlock();
diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp
index fd28ea1..0d4b358 100644
--- a/services/audioflinger/AudioMixer.cpp
+++ b/services/audioflinger/AudioMixer.cpp
@@ -430,6 +430,10 @@
     mState.mLog = log;
 }
 
+static inline audio_format_t selectMixerInFormat(audio_format_t inputFormat __unused) {
+    return kUseFloat && kUseNewMixer ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
+}
+
 int AudioMixer::getTrackName(audio_channel_mask_t channelMask,
         audio_format_t format, int sessionId)
 {
@@ -492,24 +496,23 @@
         t->mInputBufferProvider = NULL;
         t->mReformatBufferProvider = NULL;
         t->downmixerBufferProvider = NULL;
+        t->mPostDownmixReformatBufferProvider = NULL;
         t->mMixerFormat = AUDIO_FORMAT_PCM_16_BIT;
         t->mFormat = format;
-        t->mMixerInFormat = kUseFloat && kUseNewMixer
-                ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
+        t->mMixerInFormat = selectMixerInFormat(format);
+        t->mDownmixRequiresFormat = AUDIO_FORMAT_INVALID; // no format required
         t->mMixerChannelMask = audio_channel_mask_from_representation_and_bits(
                 AUDIO_CHANNEL_REPRESENTATION_POSITION, AUDIO_CHANNEL_OUT_STEREO);
         t->mMixerChannelCount = audio_channel_count_from_out_mask(t->mMixerChannelMask);
         // Check the downmixing (or upmixing) requirements.
-        status_t status = initTrackDownmix(t, n);
+        status_t status = t->prepareForDownmix();
         if (status != OK) {
             ALOGE("AudioMixer::getTrackName invalid channelMask (%#x)", channelMask);
             return -1;
         }
-        // initTrackDownmix() may change the input format requirement.
-        // If you desire floating point input to the mixer, it may change
-        // to integer because the downmixer requires integer to process.
+        // prepareForDownmix() may change mDownmixRequiresFormat
         ALOGVV("mMixerFormat:%#x  mMixerInFormat:%#x\n", t->mMixerFormat, t->mMixerInFormat);
-        prepareTrackForReformat(t, n);
+        t->prepareForReformat();
         mTrackNames |= 1 << n;
         return TRACK0 + n;
     }
@@ -526,7 +529,7 @@
  }
 
 // Called when channel masks have changed for a track name
-// TODO: Fix Downmixbufferprofider not to (possibly) change mixer input format,
+// TODO: Fix DownmixerBufferProvider not to (possibly) change mixer input format,
 // which will simplify this logic.
 bool AudioMixer::setChannelMasks(int name,
         audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask) {
@@ -551,21 +554,18 @@
 
     // channel masks have changed, does this track need a downmixer?
     // update to try using our desired format (if we aren't already using it)
-    const audio_format_t prevMixerInFormat = track.mMixerInFormat;
-    track.mMixerInFormat = kUseFloat && kUseNewMixer
-            ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
-    const status_t status = initTrackDownmix(&mState.tracks[name], name);
+    const audio_format_t prevDownmixerFormat = track.mDownmixRequiresFormat;
+    const status_t status = mState.tracks[name].prepareForDownmix();
     ALOGE_IF(status != OK,
-            "initTrackDownmix error %d, track channel mask %#x, mixer channel mask %#x",
+            "prepareForDownmix error %d, track channel mask %#x, mixer channel mask %#x",
             status, track.channelMask, track.mMixerChannelMask);
 
-    const bool mixerInFormatChanged = prevMixerInFormat != track.mMixerInFormat;
-    if (mixerInFormatChanged) {
-        prepareTrackForReformat(&track, name); // because of downmixer, track format may change!
+    if (prevDownmixerFormat != track.mDownmixRequiresFormat) {
+        track.prepareForReformat(); // because of downmixer, track format may change!
     }
 
-    if (track.resampler && (mixerInFormatChanged || mixerChannelCountChanged)) {
-        // resampler input format or channels may have changed.
+    if (track.resampler && mixerChannelCountChanged) {
+        // resampler channels may have changed.
         const uint32_t resetToSampleRate = track.sampleRate;
         delete track.resampler;
         track.resampler = NULL;
@@ -576,99 +576,122 @@
     return true;
 }
 
-status_t AudioMixer::initTrackDownmix(track_t* pTrack, int trackName)
-{
-    // Only remix (upmix or downmix) if the track and mixer/device channel masks
-    // are not the same and not handled internally, as mono -> stereo currently is.
-    if (pTrack->channelMask != pTrack->mMixerChannelMask
-            && !(pTrack->channelMask == AUDIO_CHANNEL_OUT_MONO
-                    && pTrack->mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO)) {
-        return prepareTrackForDownmix(pTrack, trackName);
-    }
-    // no remix necessary
-    unprepareTrackForDownmix(pTrack, trackName);
-    return NO_ERROR;
-}
+void AudioMixer::track_t::unprepareForDownmix() {
+    ALOGV("AudioMixer::unprepareForDownmix(%p)", this);
 
-void AudioMixer::unprepareTrackForDownmix(track_t* pTrack, int trackName __unused) {
-    ALOGV("AudioMixer::unprepareTrackForDownmix(%d)", trackName);
-
-    if (pTrack->downmixerBufferProvider != NULL) {
+    mDownmixRequiresFormat = AUDIO_FORMAT_INVALID;
+    if (downmixerBufferProvider != NULL) {
         // this track had previously been configured with a downmixer, delete it
         ALOGV(" deleting old downmixer");
-        delete pTrack->downmixerBufferProvider;
-        pTrack->downmixerBufferProvider = NULL;
-        reconfigureBufferProviders(pTrack);
+        delete downmixerBufferProvider;
+        downmixerBufferProvider = NULL;
+        reconfigureBufferProviders();
     } else {
         ALOGV(" nothing to do, no downmixer to delete");
     }
 }
 
-status_t AudioMixer::prepareTrackForDownmix(track_t* pTrack, int trackName)
+status_t AudioMixer::track_t::prepareForDownmix()
 {
-    ALOGV("AudioMixer::prepareTrackForDownmix(%d) with mask 0x%x", trackName, pTrack->channelMask);
+    ALOGV("AudioMixer::prepareForDownmix(%p) with mask 0x%x",
+            this, channelMask);
 
     // discard the previous downmixer if there was one
-    unprepareTrackForDownmix(pTrack, trackName);
+    unprepareForDownmix();
+    // Only remix (upmix or downmix) if the track and mixer/device channel masks
+    // are not the same and not handled internally, as mono -> stereo currently is.
+    if (channelMask == mMixerChannelMask
+            || (channelMask == AUDIO_CHANNEL_OUT_MONO
+                    && mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO)) {
+        return NO_ERROR;
+    }
     if (DownmixerBufferProvider::isMultichannelCapable()) {
-        DownmixerBufferProvider* pDbp = new DownmixerBufferProvider(pTrack->channelMask,
-                pTrack->mMixerChannelMask,
-                AUDIO_FORMAT_PCM_16_BIT /* TODO: use pTrack->mMixerInFormat, now only PCM 16 */,
-                pTrack->sampleRate, pTrack->sessionId, kCopyBufferFrameCount);
+        DownmixerBufferProvider* pDbp = new DownmixerBufferProvider(channelMask,
+                mMixerChannelMask,
+                AUDIO_FORMAT_PCM_16_BIT /* TODO: use mMixerInFormat, now only PCM 16 */,
+                sampleRate, sessionId, kCopyBufferFrameCount);
 
         if (pDbp->isValid()) { // if constructor completed properly
-            pTrack->mMixerInFormat = AUDIO_FORMAT_PCM_16_BIT; // PCM 16 bit required for downmix
-            pTrack->downmixerBufferProvider = pDbp;
-            reconfigureBufferProviders(pTrack);
+            mDownmixRequiresFormat = AUDIO_FORMAT_PCM_16_BIT; // PCM 16 bit required for downmix
+            downmixerBufferProvider = pDbp;
+            reconfigureBufferProviders();
             return NO_ERROR;
         }
         delete pDbp;
     }
 
     // Effect downmixer does not accept the channel conversion.  Let's use our remixer.
-    RemixBufferProvider* pRbp = new RemixBufferProvider(pTrack->channelMask,
-            pTrack->mMixerChannelMask, pTrack->mMixerInFormat, kCopyBufferFrameCount);
+    RemixBufferProvider* pRbp = new RemixBufferProvider(channelMask,
+            mMixerChannelMask, mMixerInFormat, kCopyBufferFrameCount);
     // Remix always finds a conversion whereas Downmixer effect above may fail.
-    pTrack->downmixerBufferProvider = pRbp;
-    reconfigureBufferProviders(pTrack);
+    downmixerBufferProvider = pRbp;
+    reconfigureBufferProviders();
     return NO_ERROR;
 }
 
-void AudioMixer::unprepareTrackForReformat(track_t* pTrack, int trackName __unused) {
-    ALOGV("AudioMixer::unprepareTrackForReformat(%d)", trackName);
-    if (pTrack->mReformatBufferProvider != NULL) {
-        delete pTrack->mReformatBufferProvider;
-        pTrack->mReformatBufferProvider = NULL;
-        reconfigureBufferProviders(pTrack);
+void AudioMixer::track_t::unprepareForReformat() {
+    ALOGV("AudioMixer::unprepareForReformat(%p)", this);
+    bool requiresReconfigure = false;
+    if (mReformatBufferProvider != NULL) {
+        delete mReformatBufferProvider;
+        mReformatBufferProvider = NULL;
+        requiresReconfigure = true;
+    }
+    if (mPostDownmixReformatBufferProvider != NULL) {
+        delete mPostDownmixReformatBufferProvider;
+        mPostDownmixReformatBufferProvider = NULL;
+        requiresReconfigure = true;
+    }
+    if (requiresReconfigure) {
+        reconfigureBufferProviders();
     }
 }
 
-status_t AudioMixer::prepareTrackForReformat(track_t* pTrack, int trackName)
+status_t AudioMixer::track_t::prepareForReformat()
 {
-    ALOGV("AudioMixer::prepareTrackForReformat(%d) with format %#x", trackName, pTrack->mFormat);
-    // discard the previous reformatter if there was one
-    unprepareTrackForReformat(pTrack, trackName);
-    // only configure reformatter if needed
-    if (pTrack->mFormat != pTrack->mMixerInFormat) {
-        pTrack->mReformatBufferProvider = new ReformatBufferProvider(
-                audio_channel_count_from_out_mask(pTrack->channelMask),
-                pTrack->mFormat, pTrack->mMixerInFormat,
+    ALOGV("AudioMixer::prepareForReformat(%p) with format %#x", this, mFormat);
+    // discard previous reformatters
+    unprepareForReformat();
+    // only configure reformatters as needed
+    const audio_format_t targetFormat = mDownmixRequiresFormat != AUDIO_FORMAT_INVALID
+            ? mDownmixRequiresFormat : mMixerInFormat;
+    bool requiresReconfigure = false;
+    if (mFormat != targetFormat) {
+        mReformatBufferProvider = new ReformatBufferProvider(
+                audio_channel_count_from_out_mask(channelMask),
+                mFormat,
+                targetFormat,
                 kCopyBufferFrameCount);
-        reconfigureBufferProviders(pTrack);
+        requiresReconfigure = true;
+    }
+    if (targetFormat != mMixerInFormat) {
+        mPostDownmixReformatBufferProvider = new ReformatBufferProvider(
+                audio_channel_count_from_out_mask(mMixerChannelMask),
+                targetFormat,
+                mMixerInFormat,
+                kCopyBufferFrameCount);
+        requiresReconfigure = true;
+    }
+    if (requiresReconfigure) {
+        reconfigureBufferProviders();
     }
     return NO_ERROR;
 }
 
-void AudioMixer::reconfigureBufferProviders(track_t* pTrack)
+void AudioMixer::track_t::reconfigureBufferProviders()
 {
-    pTrack->bufferProvider = pTrack->mInputBufferProvider;
-    if (pTrack->mReformatBufferProvider) {
-        pTrack->mReformatBufferProvider->setBufferProvider(pTrack->bufferProvider);
-        pTrack->bufferProvider = pTrack->mReformatBufferProvider;
+    bufferProvider = mInputBufferProvider;
+    if (mReformatBufferProvider) {
+        mReformatBufferProvider->setBufferProvider(bufferProvider);
+        bufferProvider = mReformatBufferProvider;
     }
-    if (pTrack->downmixerBufferProvider) {
-        pTrack->downmixerBufferProvider->setBufferProvider(pTrack->bufferProvider);
-        pTrack->bufferProvider = pTrack->downmixerBufferProvider;
+    if (downmixerBufferProvider) {
+        downmixerBufferProvider->setBufferProvider(bufferProvider);
+        bufferProvider = downmixerBufferProvider;
+    }
+    if (mPostDownmixReformatBufferProvider) {
+        mPostDownmixReformatBufferProvider->setBufferProvider(bufferProvider);
+        bufferProvider = mPostDownmixReformatBufferProvider;
     }
 }
 
@@ -687,9 +710,9 @@
     delete track.resampler;
     track.resampler = NULL;
     // delete the downmixer
-    unprepareTrackForDownmix(&mState.tracks[name], name);
+    mState.tracks[name].unprepareForDownmix();
     // delete the reformatter
-    unprepareTrackForReformat(&mState.tracks[name], name);
+    mState.tracks[name].unprepareForReformat();
 
     mTrackNames &= ~(1<<name);
 }
@@ -828,7 +851,7 @@
                 ALOG_ASSERT(audio_is_linear_pcm(format), "Invalid format %#x", format);
                 track.mFormat = format;
                 ALOGV("setParameter(TRACK, FORMAT, %#x)", format);
-                prepareTrackForReformat(&track, name);
+                track.prepareForReformat();
                 invalidateState(1 << name);
             }
             } break;
@@ -1032,10 +1055,13 @@
     if (mState.tracks[name].mReformatBufferProvider != NULL) {
         mState.tracks[name].mReformatBufferProvider->reset();
     } else if (mState.tracks[name].downmixerBufferProvider != NULL) {
+        mState.tracks[name].downmixerBufferProvider->reset();
+    } else if (mState.tracks[name].mPostDownmixReformatBufferProvider != NULL) {
+        mState.tracks[name].mPostDownmixReformatBufferProvider->reset();
     }
 
     mState.tracks[name].mInputBufferProvider = bufferProvider;
-    reconfigureBufferProviders(&mState.tracks[name]);
+    mState.tracks[name].reconfigureBufferProviders();
 }
 
 
diff --git a/services/audioflinger/AudioMixer.h b/services/audioflinger/AudioMixer.h
index f4f142b..c2ff985 100644
--- a/services/audioflinger/AudioMixer.h
+++ b/services/audioflinger/AudioMixer.h
@@ -205,17 +205,34 @@
         int32_t*           auxBuffer;
 
         // 16-byte boundary
+
+        /* Buffer providers are constructed to translate the track input data as needed.
+         *
+         * 1) mInputBufferProvider: The AudioTrack buffer provider.
+         * 2) mReformatBufferProvider: If not NULL, performs the audio reformat to
+         *    match either mMixerInFormat or mDownmixRequiresFormat, if the downmixer
+         *    requires reformat. For example, it may convert floating point input to
+         *    PCM_16_bit if that's required by the downmixer.
+         * 3) downmixerBufferProvider: If not NULL, performs the channel remixing to match
+         *    the number of channels required by the mixer sink.
+         * 4) mPostDownmixReformatBufferProvider: If not NULL, performs reformatting from
+         *    the downmixer requirements to the mixer engine input requirements.
+         */
         AudioBufferProvider*     mInputBufferProvider;    // externally provided buffer provider.
         CopyBufferProvider*      mReformatBufferProvider; // provider wrapper for reformatting.
         CopyBufferProvider*      downmixerBufferProvider; // wrapper for channel conversion.
-
-        int32_t     sessionId;
+        CopyBufferProvider*      mPostDownmixReformatBufferProvider;
 
         // 16-byte boundary
+        int32_t     sessionId;
+
         audio_format_t mMixerFormat;     // output mix format: AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
         audio_format_t mFormat;          // input track format
         audio_format_t mMixerInFormat;   // mix internal format AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
                                          // each track must be converted to this format.
+        audio_format_t mDownmixRequiresFormat;  // required downmixer format
+                                                // AUDIO_FORMAT_PCM_16_BIT if 16 bit necessary
+                                                // AUDIO_FORMAT_INVALID if no required format
 
         float          mVolume[MAX_NUM_VOLUMES];     // floating point set volume
         float          mPrevVolume[MAX_NUM_VOLUMES]; // floating point previous volume
@@ -225,7 +242,6 @@
         float          mPrevAuxLevel;                 // floating point prev aux level
         float          mAuxInc;                       // floating point aux increment
 
-        // 16-byte boundary
         audio_channel_mask_t mMixerChannelMask;
         uint32_t             mMixerChannelCount;
 
@@ -236,6 +252,12 @@
         void        adjustVolumeRamp(bool aux, bool useFloat = false);
         size_t      getUnreleasedFrames() const { return resampler != NULL ?
                                                     resampler->getUnreleasedFrames() : 0; };
+
+        status_t    prepareForDownmix();
+        void        unprepareForDownmix();
+        status_t    prepareForReformat();
+        void        unprepareForReformat();
+        void        reconfigureBufferProviders();
     };
 
     typedef void (*process_hook_t)(state_t* state, int64_t pts);
@@ -382,14 +404,6 @@
     bool setChannelMasks(int name,
             audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask);
 
-    // TODO: remove unused trackName/trackNum from functions below.
-    static status_t initTrackDownmix(track_t* pTrack, int trackName);
-    static status_t prepareTrackForDownmix(track_t* pTrack, int trackNum);
-    static void unprepareTrackForDownmix(track_t* pTrack, int trackName);
-    static status_t prepareTrackForReformat(track_t* pTrack, int trackNum);
-    static void unprepareTrackForReformat(track_t* pTrack, int trackName);
-    static void reconfigureBufferProviders(track_t* pTrack);
-
     static void track__genericResample(track_t* t, int32_t* out, size_t numFrames, int32_t* temp,
             int32_t* aux);
     static void track__nop(track_t* t, int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux);
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index cbb64c8..e04af3d 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -314,6 +314,64 @@
 //      ThreadBase
 // ----------------------------------------------------------------------------
 
+// static
+const char *AudioFlinger::ThreadBase::threadTypeToString(AudioFlinger::ThreadBase::type_t type)
+{
+    switch (type) {
+    case MIXER:
+        return "MIXER";
+    case DIRECT:
+        return "DIRECT";
+    case DUPLICATING:
+        return "DUPLICATING";
+    case RECORD:
+        return "RECORD";
+    case OFFLOAD:
+        return "OFFLOAD";
+    default:
+        return "unknown";
+    }
+}
+
+static String8 outputFlagsToString(audio_output_flags_t flags)
+{
+    static const struct mapping {
+        audio_output_flags_t    mFlag;
+        const char *            mString;
+    } mappings[] = {
+        AUDIO_OUTPUT_FLAG_DIRECT,           "DIRECT",
+        AUDIO_OUTPUT_FLAG_PRIMARY,          "PRIMARY",
+        AUDIO_OUTPUT_FLAG_FAST,             "FAST",
+        AUDIO_OUTPUT_FLAG_DEEP_BUFFER,      "DEEP_BUFFER",
+        AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD, "COMPRESS_OFFLOAAD",
+        AUDIO_OUTPUT_FLAG_NON_BLOCKING,     "NON_BLOCKING",
+        AUDIO_OUTPUT_FLAG_HW_AV_SYNC,       "HW_AV_SYNC",
+        AUDIO_OUTPUT_FLAG_NONE,             "NONE",         // must be last
+    };
+    String8 result;
+    audio_output_flags_t allFlags = AUDIO_OUTPUT_FLAG_NONE;
+    const mapping *entry;
+    for (entry = mappings; entry->mFlag != AUDIO_OUTPUT_FLAG_NONE; entry++) {
+        allFlags = (audio_output_flags_t) (allFlags | entry->mFlag);
+        if (flags & entry->mFlag) {
+            if (!result.isEmpty()) {
+                result.append("|");
+            }
+            result.append(entry->mString);
+        }
+    }
+    if (flags & ~allFlags) {
+        if (!result.isEmpty()) {
+            result.append("|");
+        }
+        result.appendFormat("0x%X", flags & ~allFlags);
+    }
+    if (result.isEmpty()) {
+        result.append(entry->mString);
+    }
+    return result;
+}
+
 AudioFlinger::ThreadBase::ThreadBase(const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
         audio_devices_t outDevice, audio_devices_t inDevice, type_t type)
     :   Thread(false /*canCallJava*/),
@@ -577,20 +635,21 @@
 
     bool locked = AudioFlinger::dumpTryLock(mLock);
     if (!locked) {
-        dprintf(fd, "thread %p maybe dead locked\n", this);
+        dprintf(fd, "thread %p may be deadlocked\n", this);
     }
 
     dprintf(fd, "  I/O handle: %d\n", mId);
     dprintf(fd, "  TID: %d\n", getTid());
     dprintf(fd, "  Standby: %s\n", mStandby ? "yes" : "no");
-    dprintf(fd, "  Sample rate: %u\n", mSampleRate);
+    dprintf(fd, "  Sample rate: %u Hz\n", mSampleRate);
     dprintf(fd, "  HAL frame count: %zu\n", mFrameCount);
+    dprintf(fd, "  HAL format: 0x%x (%s)\n", mHALFormat, formatToString(mHALFormat));
     dprintf(fd, "  HAL buffer size: %u bytes\n", mBufferSize);
-    dprintf(fd, "  Channel Count: %u\n", mChannelCount);
-    dprintf(fd, "  Channel Mask: 0x%08x (%s)\n", mChannelMask,
+    dprintf(fd, "  Channel count: %u\n", mChannelCount);
+    dprintf(fd, "  Channel mask: 0x%08x (%s)\n", mChannelMask,
             channelMaskToString(mChannelMask, mType != RECORD).string());
-    dprintf(fd, "  Format: 0x%x (%s)\n", mHALFormat, formatToString(mHALFormat));
-    dprintf(fd, "  Frame size: %zu\n", mFrameSize);
+    dprintf(fd, "  Format: 0x%x (%s)\n", mFormat, formatToString(mFormat));
+    dprintf(fd, "  Frame size: %zu bytes\n", mFrameSize);
     dprintf(fd, "  Pending config events:");
     size_t numConfig = mConfigEvents.size();
     if (numConfig) {
@@ -1314,7 +1373,7 @@
 
 void AudioFlinger::PlaybackThread::dumpInternals(int fd, const Vector<String16>& args)
 {
-    dprintf(fd, "\nOutput thread %p:\n", this);
+    dprintf(fd, "\nOutput thread %p type %d (%s):\n", this, type(), threadTypeToString(type()));
     dprintf(fd, "  Normal frame count: %zu\n", mNormalFrameCount);
     dprintf(fd, "  Last write occurred (msecs): %llu\n", ns2ms(systemTime() - mLastWriteTime));
     dprintf(fd, "  Total writes: %d\n", mNumWrites);
@@ -1325,6 +1384,10 @@
     dprintf(fd, "  Mixer buffer: %p\n", mMixerBuffer);
     dprintf(fd, "  Effect buffer: %p\n", mEffectBuffer);
     dprintf(fd, "  Fast track availMask=%#x\n", mFastTrackAvailMask);
+    AudioStreamOut *output = mOutput;
+    audio_output_flags_t flags = output != NULL ? output->flags : AUDIO_OUTPUT_FLAG_NONE;
+    String8 flagsAsString = outputFlagsToString(flags);
+    dprintf(fd, "  AudioStreamOut: %p flags %#x (%s)\n", output, flags, flagsAsString.string());
 
     dumpBase(fd, args);
 }
@@ -2119,6 +2182,7 @@
         } else {
             bytesWritten = framesWritten;
         }
+        mLatchDValid = false;
         status_t status = mNormalSink->getTimestamp(mLatchD.mTimestamp);
         if (status == NO_ERROR) {
             size_t totalFramesWritten = mNormalSink->framesWritten();
@@ -2616,7 +2680,9 @@
                 }
 
             } else {
+                ATRACE_BEGIN("sleep");
                 usleep(sleepTime);
+                ATRACE_END();
             }
         }
 
@@ -2816,6 +2882,7 @@
         NBAIO_Format format = mOutputSink->format();
         NBAIO_Format origformat = format;
         // adjust format to match that of the Fast Mixer
+        ALOGV("format changed from %d to %d", format.mFormat, fastMixerFormat);
         format.mFormat = fastMixerFormat;
         format.mFrameSize = audio_bytes_per_sample(format.mFormat) * format.mChannelCount;
 
@@ -3379,6 +3446,23 @@
         }
 
         size_t framesReady = track->framesReady();
+        if (ATRACE_ENABLED()) {
+            // I wish we had formatted trace names
+            char traceName[16];
+            strcpy(traceName, "nRdy");
+            int name = track->name();
+            if (AudioMixer::TRACK0 <= name &&
+                    name < (int) (AudioMixer::TRACK0 + AudioMixer::MAX_NUM_TRACKS)) {
+                name -= AudioMixer::TRACK0;
+                traceName[4] = (name / 10) + '0';
+                traceName[5] = (name % 10) + '0';
+            } else {
+                traceName[4] = '?';
+                traceName[5] = '?';
+            }
+            traceName[6] = '\0';
+            ATRACE_INT(traceName, framesReady);
+        }
         if ((framesReady >= minFrames) && track->isReady() &&
                 !track->isPaused() && !track->isTerminated())
         {
@@ -5029,7 +5113,9 @@
 
         // sleep with mutex unlocked
         if (sleepUs > 0) {
+            ATRACE_BEGIN("sleep");
             usleep(sleepUs);
+            ATRACE_END();
             sleepUs = 0;
         }
 
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 119e495..09383b6 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -32,6 +32,8 @@
         OFFLOAD             // Thread class is OffloadThread
     };
 
+    static const char *threadTypeToString(type_t type);
+
     ThreadBase(const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
                 audio_devices_t outDevice, audio_devices_t inDevice, type_t type);
     virtual             ~ThreadBase();
@@ -406,6 +408,7 @@
                 audio_channel_mask_t    mChannelMask;
                 uint32_t                mChannelCount;
                 size_t                  mFrameSize;
+                // not HAL frame size, this is for output sink (to pipe to fast mixer)
                 audio_format_t          mFormat;           // Source format for Recording and
                                                            // Sink format for Playback.
                                                            // Sink format may be different than
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index aa708ec..d59e68d 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -860,6 +860,7 @@
         if (mState == FLUSHED) {
             mState = IDLE;
         }
+        mPreviousValid = false;
     }
 }
 
diff --git a/services/audioflinger/tests/mixer_to_wav_tests.sh b/services/audioflinger/tests/mixer_to_wav_tests.sh
index 9b39e77..e60e6d5 100755
--- a/services/audioflinger/tests/mixer_to_wav_tests.sh
+++ b/services/audioflinger/tests/mixer_to_wav_tests.sh
@@ -63,8 +63,18 @@
 # process__genericResampling
 # track__Resample / track__genericResample
     adb shell test-mixer $1 -s 48000 \
+        -o /sdcard/tm48000grif.wav \
+        sine:2,4000,7520 chirp:2,9200 sine:1,3000,18000 \
+        sine:f,6,6000,19000  chirp:i,4,30000
+    adb pull /sdcard/tm48000grif.wav $2
+
+# Test:
+# process__genericResampling
+# track__Resample / track__genericResample
+    adb shell test-mixer $1 -s 48000 \
         -o /sdcard/tm48000gr.wav \
-        sine:2,4000,7520 chirp:2,9200 sine:1,3000,18000
+        sine:2,4000,7520 chirp:2,9200 sine:1,3000,18000 \
+        sine:6,6000,19000
     adb pull /sdcard/tm48000gr.wav $2
 
 # Test:
diff --git a/services/audioflinger/tests/test-mixer.cpp b/services/audioflinger/tests/test-mixer.cpp
index 9a4fad6..8da6245 100644
--- a/services/audioflinger/tests/test-mixer.cpp
+++ b/services/audioflinger/tests/test-mixer.cpp
@@ -39,7 +39,7 @@
     fprintf(stderr, "Usage: %s [-f] [-m] [-c channels]"
                     " [-s sample-rate] [-o <output-file>] [-a <aux-buffer-file>] [-P csv]"
                     " (<input-file> | <command>)+\n", name);
-    fprintf(stderr, "    -f    enable floating point input track\n");
+    fprintf(stderr, "    -f    enable floating point input track by default\n");
     fprintf(stderr, "    -m    enable floating point mixer output\n");
     fprintf(stderr, "    -c    number of mixer output channels\n");
     fprintf(stderr, "    -s    mixer sample-rate\n");
@@ -47,8 +47,8 @@
     fprintf(stderr, "    -a    <aux-buffer-file>\n");
     fprintf(stderr, "    -P    # frames provided per call to resample() in CSV format\n");
     fprintf(stderr, "    <input-file> is a WAV file\n");
-    fprintf(stderr, "    <command> can be 'sine:<channels>,<frequency>,<samplerate>'\n");
-    fprintf(stderr, "                     'chirp:<channels>,<samplerate>'\n");
+    fprintf(stderr, "    <command> can be 'sine:[(i|f),]<channels>,<frequency>,<samplerate>'\n");
+    fprintf(stderr, "                     'chirp:[(i|f),]<channels>,<samplerate>'\n");
 }
 
 static int writeFile(const char *filename, const void *buffer,
@@ -78,6 +78,18 @@
     return EXIT_SUCCESS;
 }
 
+const char *parseFormat(const char *s, bool *useFloat) {
+    if (!strncmp(s, "f,", 2)) {
+        *useFloat = true;
+        return s + 2;
+    }
+    if (!strncmp(s, "i,", 2)) {
+        *useFloat = false;
+        return s + 2;
+    }
+    return s;
+}
+
 int main(int argc, char* argv[]) {
     const char* const progname = argv[0];
     bool useInputFloat = false;
@@ -88,8 +100,9 @@
     std::vector<int> Pvalues;
     const char* outputFilename = NULL;
     const char* auxFilename = NULL;
-    std::vector<int32_t> Names;
-    std::vector<SignalProvider> Providers;
+    std::vector<int32_t> names;
+    std::vector<SignalProvider> providers;
+    std::vector<audio_format_t> formats;
 
     for (int ch; (ch = getopt(argc, argv, "fmc:s:o:a:P:")) != -1;) {
         switch (ch) {
@@ -138,54 +151,65 @@
     size_t outputFrames = 0;
 
     // create providers for each track
-    Providers.resize(argc);
+    names.resize(argc);
+    providers.resize(argc);
+    formats.resize(argc);
     for (int i = 0; i < argc; ++i) {
         static const char chirp[] = "chirp:";
         static const char sine[] = "sine:";
         static const double kSeconds = 1;
+        bool useFloat = useInputFloat;
 
         if (!strncmp(argv[i], chirp, strlen(chirp))) {
             std::vector<int> v;
+            const char *s = parseFormat(argv[i] + strlen(chirp), &useFloat);
 
-            parseCSV(argv[i] + strlen(chirp), v);
+            parseCSV(s, v);
             if (v.size() == 2) {
                 printf("creating chirp(%d %d)\n", v[0], v[1]);
-                if (useInputFloat) {
-                    Providers[i].setChirp<float>(v[0], 0, v[1]/2, v[1], kSeconds);
+                if (useFloat) {
+                    providers[i].setChirp<float>(v[0], 0, v[1]/2, v[1], kSeconds);
+                    formats[i] = AUDIO_FORMAT_PCM_FLOAT;
                 } else {
-                    Providers[i].setChirp<int16_t>(v[0], 0, v[1]/2, v[1], kSeconds);
+                    providers[i].setChirp<int16_t>(v[0], 0, v[1]/2, v[1], kSeconds);
+                    formats[i] = AUDIO_FORMAT_PCM_16_BIT;
                 }
-                Providers[i].setIncr(Pvalues);
+                providers[i].setIncr(Pvalues);
             } else {
                 fprintf(stderr, "malformed input '%s'\n", argv[i]);
             }
         } else if (!strncmp(argv[i], sine, strlen(sine))) {
             std::vector<int> v;
+            const char *s = parseFormat(argv[i] + strlen(sine), &useFloat);
 
-            parseCSV(argv[i] + strlen(sine), v);
+            parseCSV(s, v);
             if (v.size() == 3) {
                 printf("creating sine(%d %d %d)\n", v[0], v[1], v[2]);
-                if (useInputFloat) {
-                    Providers[i].setSine<float>(v[0], v[1], v[2], kSeconds);
+                if (useFloat) {
+                    providers[i].setSine<float>(v[0], v[1], v[2], kSeconds);
+                    formats[i] = AUDIO_FORMAT_PCM_FLOAT;
                 } else {
-                    Providers[i].setSine<int16_t>(v[0], v[1], v[2], kSeconds);
+                    providers[i].setSine<int16_t>(v[0], v[1], v[2], kSeconds);
+                    formats[i] = AUDIO_FORMAT_PCM_16_BIT;
                 }
-                Providers[i].setIncr(Pvalues);
+                providers[i].setIncr(Pvalues);
             } else {
                 fprintf(stderr, "malformed input '%s'\n", argv[i]);
             }
         } else {
             printf("creating filename(%s)\n", argv[i]);
             if (useInputFloat) {
-                Providers[i].setFile<float>(argv[i]);
+                providers[i].setFile<float>(argv[i]);
+                formats[i] = AUDIO_FORMAT_PCM_FLOAT;
             } else {
-                Providers[i].setFile<short>(argv[i]);
+                providers[i].setFile<short>(argv[i]);
+                formats[i] = AUDIO_FORMAT_PCM_16_BIT;
             }
-            Providers[i].setIncr(Pvalues);
+            providers[i].setIncr(Pvalues);
         }
         // calculate the number of output frames
-        size_t nframes = (int64_t) Providers[i].getNumFrames() * outputSampleRate
-                / Providers[i].getSampleRate();
+        size_t nframes = (int64_t) providers[i].getNumFrames() * outputSampleRate
+                / providers[i].getSampleRate();
         if (i == 0 || outputFrames > nframes) { // choose minimum for outputFrames
             outputFrames = nframes;
         }
@@ -213,22 +237,20 @@
     // create the mixer.
     const size_t mixerFrameCount = 320; // typical numbers may range from 240 or 960
     AudioMixer *mixer = new AudioMixer(mixerFrameCount, outputSampleRate);
-    audio_format_t inputFormat = useInputFloat
-            ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
     audio_format_t mixerFormat = useMixerFloat
             ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
-    float f = AudioMixer::UNITY_GAIN_FLOAT / Providers.size(); // normalize volume by # tracks
+    float f = AudioMixer::UNITY_GAIN_FLOAT / providers.size(); // normalize volume by # tracks
     static float f0; // zero
 
     // set up the tracks.
-    for (size_t i = 0; i < Providers.size(); ++i) {
-        //printf("track %d out of %d\n", i, Providers.size());
-        uint32_t channelMask = audio_channel_out_mask_from_count(Providers[i].getNumChannels());
+    for (size_t i = 0; i < providers.size(); ++i) {
+        //printf("track %d out of %d\n", i, providers.size());
+        uint32_t channelMask = audio_channel_out_mask_from_count(providers[i].getNumChannels());
         int32_t name = mixer->getTrackName(channelMask,
-                inputFormat, AUDIO_SESSION_OUTPUT_MIX);
+                formats[i], AUDIO_SESSION_OUTPUT_MIX);
         ALOG_ASSERT(name >= 0);
-        Names.push_back(name);
-        mixer->setBufferProvider(name, &Providers[i]);
+        names[i] = name;
+        mixer->setBufferProvider(name, &providers[i]);
         mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
                 (void *)outputAddr);
         mixer->setParameter(
@@ -240,7 +262,7 @@
                 name,
                 AudioMixer::TRACK,
                 AudioMixer::FORMAT,
-                (void *)(uintptr_t)inputFormat);
+                (void *)(uintptr_t)formats[i]);
         mixer->setParameter(
                 name,
                 AudioMixer::TRACK,
@@ -255,7 +277,7 @@
                 name,
                 AudioMixer::RESAMPLE,
                 AudioMixer::SAMPLE_RATE,
-                (void *)(uintptr_t)Providers[i].getSampleRate());
+                (void *)(uintptr_t)providers[i].getSampleRate());
         if (useRamp) {
             mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &f0);
             mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &f0);
@@ -277,11 +299,11 @@
     // pump the mixer to process data.
     size_t i;
     for (i = 0; i < outputFrames - mixerFrameCount; i += mixerFrameCount) {
-        for (size_t j = 0; j < Names.size(); ++j) {
-            mixer->setParameter(Names[j], AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
+        for (size_t j = 0; j < names.size(); ++j) {
+            mixer->setParameter(names[j], AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
                     (char *) outputAddr + i * outputFrameSize);
             if (auxFilename) {
-                mixer->setParameter(Names[j], AudioMixer::TRACK, AudioMixer::AUX_BUFFER,
+                mixer->setParameter(names[j], AudioMixer::TRACK, AudioMixer::AUX_BUFFER,
                         (char *) auxAddr + i * auxFrameSize);
             }
         }