Merge "Add new API to Animator to allow seeking of animations"
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index b8ba3f6..2e87394 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -24,7 +24,6 @@
     if ((uid < AID_SYSTEM) || (gid < AID_SYSTEM)) {
         LOGE("invalid uid/gid: %d %d\n", uid, gid);
         return -1;
-        
     }
 
     if (encrypted_fs_flag == USE_UNENCRYPTED_FS) {
@@ -143,6 +142,7 @@
     if (statfs(PKG_DIR_PREFIX, &sfs) == 0) {
         return sfs.f_bavail * sfs.f_bsize;
     } else {
+        LOGE("Couldn't statfs " PKG_DIR_PREFIX ": %s\n", strerror(errno));
         return -1;
     }
 }
@@ -171,7 +171,7 @@
     /* First try encrypted dir */
     d = opendir(PKG_SEC_DIR_PREFIX);
     if (d == NULL) {
-        LOGE("cannot open %s\n", PKG_SEC_DIR_PREFIX);
+        LOGE("cannot open %s: %s\n", PKG_SEC_DIR_PREFIX, strerror(errno));
     } else {
         dfd = dirfd(d);
 
@@ -203,7 +203,7 @@
     /* Next try unencrypted dir... */
     d = opendir(PKG_DIR_PREFIX);
     if (d == NULL) {
-        LOGE("cannot open %s\n", PKG_DIR_PREFIX);
+        LOGE("cannot open %s: %s\n", PKG_DIR_PREFIX, strerror(errno));
         return -1;
     }
     dfd = dirfd(d);
@@ -279,6 +279,7 @@
 
     LOGI("move %s -> %s\n", src_dex, dst_dex);
     if (rename(src_dex, dst_dex) < 0) {
+        LOGE("Couldn't move %s: %s\n", src_dex, strerror(errno));
         return -1;
     } else {
         return 0;
@@ -294,6 +295,7 @@
 
     LOGI("unlink %s\n", dex_path);
     if (unlink(dex_path) < 0) {
+        LOGE("Couldn't unlink %s: %s\n", dex_path, strerror(errno));
         return -1;
     } else {
         return 0;
diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c
index 555c19e..a5e4b5a 100644
--- a/cmds/installd/utils.c
+++ b/cmds/installd/utils.c
@@ -98,11 +98,13 @@
 
             subfd = openat(dfd, name, O_RDONLY | O_DIRECTORY);
             if (subfd < 0) {
+                LOGE("Couldn't openat %s: %s\n", name, strerror(errno));
                 result = -1;
                 continue;
             }
             subdir = fdopendir(subfd);
             if (subdir == NULL) {
+                LOGE("Couldn't fdopendir %s: %s\n", name, strerror(errno));
                 close(subfd);
                 result = -1;
                 continue;
@@ -112,10 +114,12 @@
             }
             closedir(subdir);
             if (unlinkat(dfd, name, AT_REMOVEDIR) < 0) {
+                LOGE("Couldn't unlinkat %s: %s\n", name, strerror(errno));
                 result = -1;
             }
         } else {
             if (unlinkat(dfd, name, 0) < 0) {
+                LOGE("Couldn't unlinkat %s: %s\n", name, strerror(errno));
                 result = -1;
             }
         }
@@ -133,12 +137,14 @@
 
     d = opendir(pathname);
     if (d == NULL) {
+        LOGE("Couldn't opendir %s: %s\n", pathname, strerror(errno));
         return -errno;
     }
     res = _delete_dir_contents(d, ignore);
     closedir(d);
     if (also_delete_dir) {
         if (rmdir(pathname)) {
+            LOGE("Couldn't rmdir %s: %s\n", pathname, strerror(errno));
             res = -1;
         }
     }
@@ -152,10 +158,12 @@
 
     fd = openat(dfd, name, O_RDONLY | O_DIRECTORY);
     if (fd < 0) {
+        LOGE("Couldn't openat %s: %s\n", name, strerror(errno));
         return -1;
     }
     d = fdopendir(fd);
     if (d == NULL) {
+        LOGE("Couldn't fdopendir %s: %s\n", name, strerror(errno));
         close(fd);
         return -1;
     }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 15a446b..b14555a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -610,6 +610,16 @@
     public static final int MOVE_FAILED_INTERNAL_ERROR = -6;
 
     /**
+     * Error code that is passed to the {@link IPackageMoveObserver} by
+     * {@link #movePackage(android.net.Uri, IPackageMoveObserver)} if the
+     * specified package already has an operation pending in the
+     * {@link PackageHandler} queue.
+     * 
+     * @hide
+     */
+    public static final int MOVE_FAILED_OPERATION_PENDING = -7;
+
+    /**
      * Flag parameter for {@link #movePackage} to indicate that
      * the package should be moved to internal storage if its
      * been installed on external media.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 8c9cc4e..155c86c 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2799,7 +2799,10 @@
 
         // Additional data supplied by callers.
         public Object mExtras;
-        
+
+        // Whether an operation is currently pending on this package
+        public boolean mOperationPending;
+
         /*
          *  Applications hardware preferences
          */
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 418a9d6..48d5345 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -30,7 +30,6 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteException;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.RemoteException;
@@ -233,14 +232,31 @@
      * </p>
      * <p>
      * Clients should send directory requests to Contacts Provider and let it
-     * forward them to the respective providers rather than constructing directory provider
-     * URIs by themselves.  This level of indirection allows Contacts Provider to
-     * implement additional system-level features and optimizations.
-     * Also, directory providers may reject requests coming from other
-     * clients than the Contacts Provider itself.
+     * forward them to the respective providers rather than constructing
+     * directory provider URIs by themselves. This level of indirection allows
+     * Contacts Provider to implement additional system-level features and
+     * optimizations. Access to Contacts Provider is protected by the
+     * READ_CONTACTS permission, but access to the directory provider is not.
+     * Therefore directory providers must reject requests coming from clients
+     * other than the Contacts Provider itself. An easy way to prevent such
+     * unauthorized access is to check the name of the calling package:
+     * <pre>
+     * private boolean isCallerAllowed() {
+     *   PackageManager pm = getContext().getPackageManager();
+     *   for (String packageName: pm.getPackagesForUid(Binder.getCallingUid())) {
+     *     if (packageName.equals("com.android.providers.contacts")) {
+     *       return true;
+     *     }
+     *   }
+     *   return false;
+     * }
+     * </pre>
      * </p>
      * <p>
-     * The Directory table always has at least these two rows:
+     * The Directory table is read-only and is maintained by the Contacts Provider
+     * automatically.
+     * </p>
+     * <p>It always has at least these two rows:
      * <ul>
      * <li>
      * The local directory. It has {@link Directory#_ID Directory._ID} =
@@ -254,18 +270,54 @@
      * </li>
      * </ul>
      * </p>
+     * <p>Custom Directories are discovered by the Contacts Provider following this procedure:
+     * <ul>
+     * <li>It finds all installed content providers with meta data identifying them
+     * as directory providers in AndroidManifest.xml:
+     * <code>
+     * &lt;meta-data android:name="android.content.ContactDirectory"
+     *               android:value="true" /&gt;
+     * </code>
      * <p>
-     * Other directories should register themselves by explicitly adding rows to this table.
+     * This tag should be placed inside the corresponding content provider declaration.
+     * </p>
+     * </li>
+     * <li>
+     * Then Contacts Provider sends a {@link Directory#CONTENT_URI Directory.CONTENT_URI}
+     * query to each of the directory authorities.  A directory provider must implement
+     * this query and return a list of directories.  Each directory returned by
+     * the provider must have a unique combination for the {@link #ACCOUNT_NAME} and
+     * {@link #ACCOUNT_TYPE} columns (nulls are allowed).  Since directory IDs are assigned
+     * automatically, the _ID field will not be part of the query projection.
+     * </li>
+     * <li>Contacts Provider compiles directory lists received from all directory
+     * providers into one, assigns each individual directory a globally unique ID and
+     * stores all directory records in the Directory table.
+     * </li>
+     * </ul>
+     * </p>
+     * <p>Contacts Provider automatically interrogates newly installed or replaced packages.
+     * Thus simply installing a package containing a directory provider is sufficient
+     * to have that provider registered.  A package supplying a directory provider does
+     * not have to contain launchable activities.
      * </p>
      * <p>
-     * When a row is inserted in this table, it is automatically associated with the package
-     * (apk) that made the request.  If the package is later uninstalled, all directory rows
-     * it inserted are automatically removed.
+     * Every row in the Directory table is automatically associated with the corresponding package
+     * (apk).  If the package is later uninstalled, all corresponding directory rows
+     * are automatically removed from the Contacts Provider.
      * </p>
      * <p>
-     * A directory row can be optionally associated with an account.
-     * If the account is later removed, the corresponding directory rows are
-     * automatically removed.
+     * When the list of directories handled by a directory provider changes
+     * (for instance when the user adds a new Directory account), the directory provider
+     * should call {@link #notifyDirectoryChange} to notify the Contacts Provider of the change.
+     * In response, the Contacts Provider will requery the directory provider to obtain the
+     * new list of directories.
+     * </p>
+     * <p>
+     * A directory row can be optionally associated with an existing account
+     * (see {@link android.accounts.AccountManager}). If the account is later removed,
+     * the corresponding directory rows are
+     * automatically removed from the Contacts Provider.
      * </p>
      *
      * @hide
@@ -317,11 +369,10 @@
         public static final long LOCAL_INVISIBLE = 1;
 
         /**
-         * The name of the package that owns this directory. This field is
-         * required in an insert request and must match the name of the package
-         * making the request. If the package is later uninstalled, the
-         * directories it owns are automatically removed from this table. Only
-         * the specified package is allowed to modify or delete this row later.
+         * The name of the package that owns this directory. Contacts Provider
+         * fill it in with the name of the package containing the directory provider.
+         * If the package is later uninstalled, the directories it owns are
+         * automatically removed from this table.
          *
          * <p>TYPE: TEXT</p>
          *
@@ -349,8 +400,15 @@
         public static final String DISPLAY_NAME = "displayName";
 
         /**
-         * The authority to which the request should forwarded in order to access
-         * this directory.
+         * <p>
+         * The authority of the Directory Provider. Contacts Provider will
+         * use this authority to forward requests to the directory provider.
+         * A directory provider can leave this column empty - Contacts Provider will fill it in.
+         * </p>
+         * <p>
+         * Clients of this API should not send requests directly to this authority.
+         * All directory requests must be routed through Contacts Provider.
+         * </p>
          *
          * <p>TYPE: text</p>
          *
@@ -444,6 +502,22 @@
          * @hide
          */
         public static final int SHORTCUT_SUPPORT_FULL = 2;
+
+        /**
+         * Notifies the system of a change in the list of directories handled by
+         * a particular directory provider. The Contacts provider will turn around
+         * and send a query to the directory provider for the full list of directories,
+         * which will replace the previous list.
+         *
+         * @hide
+         */
+        public static void notifyDirectoryChange(ContentResolver resolver) {
+            // This is done to trigger a query by Contacts Provider back to the directory provider.
+            // No data needs to be sent back, because the provider can infer the calling
+            // package from binder.
+            ContentValues contentValues = new ContentValues();
+            resolver.update(Directory.CONTENT_URI, contentValues, null, null);
+        }
     }
 
     /**
diff --git a/core/jni/android_bluetooth_common.cpp b/core/jni/android_bluetooth_common.cpp
index 53ac625..a38d3b2 100644
--- a/core/jni/android_bluetooth_common.cpp
+++ b/core/jni/android_bluetooth_common.cpp
@@ -46,6 +46,7 @@
     {"Paired", DBUS_TYPE_BOOLEAN},
     {"Connected", DBUS_TYPE_BOOLEAN},
     {"Trusted", DBUS_TYPE_BOOLEAN},
+    {"Blocked", DBUS_TYPE_BOOLEAN},
     {"Alias", DBUS_TYPE_STRING},
     {"Nodes", DBUS_TYPE_ARRAY},
     {"Adapter", DBUS_TYPE_OBJECT_PATH},
diff --git a/graphics/java/android/renderscript/Matrix2f.java b/graphics/java/android/renderscript/Matrix2f.java
index a575ca9..99d23db 100644
--- a/graphics/java/android/renderscript/Matrix2f.java
+++ b/graphics/java/android/renderscript/Matrix2f.java
@@ -96,6 +96,11 @@
         tmp.loadScale(x, y);
         multiply(tmp);
     }
+    public void transpose() {
+        float temp = mMat[1];
+        mMat[1] = mMat[2];
+        mMat[2] = temp;
+    }
 
     final float[] mMat;
 }
diff --git a/graphics/java/android/renderscript/Matrix3f.java b/graphics/java/android/renderscript/Matrix3f.java
index d01ac68..961bc5d 100644
--- a/graphics/java/android/renderscript/Matrix3f.java
+++ b/graphics/java/android/renderscript/Matrix3f.java
@@ -165,6 +165,15 @@
         tmp.loadTranslate(x, y);
         multiply(tmp);
     }
+    public void transpose() {
+        for(int i = 0; i < 2; ++i) {
+            for(int j = i + 1; j < 3; ++j) {
+                float temp = mMat[i*3 + j];
+                mMat[i*3 + j] = mMat[j*3 + i];
+                mMat[j*3 + i] = temp;
+            }
+        }
+    }
 
     final float[] mMat;
 }
diff --git a/graphics/java/android/renderscript/Matrix4f.java b/graphics/java/android/renderscript/Matrix4f.java
index ebd5bde..e854cd9 100644
--- a/graphics/java/android/renderscript/Matrix4f.java
+++ b/graphics/java/android/renderscript/Matrix4f.java
@@ -179,6 +179,15 @@
         tmp.loadTranslate(x, y, z);
         multiply(tmp);
     }
+    public void transpose() {
+        for(int i = 0; i < 3; ++i) {
+            for(int j = i + 1; j < 4; ++j) {
+                float temp = mMat[i*4 + j];
+                mMat[i*4 + j] = mMat[j*4 + i];
+                mMat[j*4 + i] = temp;
+            }
+        }
+    }
 
     final float[] mMat;
 }
diff --git a/graphics/java/android/renderscript/ProgramFragment.java b/graphics/java/android/renderscript/ProgramFragment.java
index d06d768..04091a3 100644
--- a/graphics/java/android/renderscript/ProgramFragment.java
+++ b/graphics/java/android/renderscript/ProgramFragment.java
@@ -66,6 +66,7 @@
         public static final int MAX_TEXTURE = 2;
         RenderScript mRS;
         boolean mPointSpriteEnable;
+        boolean mVaryingColorEnable;
 
         public enum EnvMode {
             REPLACE (1),
@@ -120,9 +121,14 @@
             return this;
         }
 
+        public Builder setVaryingColor(boolean enable) {
+            mVaryingColorEnable = enable;
+            return this;
+        }
+
         public ProgramFragment create() {
             mRS.validate();
-            int[] tmp = new int[MAX_TEXTURE * 2 + 1];
+            int[] tmp = new int[MAX_TEXTURE * 2 + 2];
             if (mSlots[0] != null) {
                 tmp[0] = mSlots[0].env.mID;
                 tmp[1] = mSlots[0].format.mID;
@@ -132,6 +138,7 @@
                 tmp[3] = mSlots[1].format.mID;
             }
             tmp[4] = mPointSpriteEnable ? 1 : 0;
+            tmp[5] = mVaryingColorEnable ? 1 : 0;
             int id = mRS.nProgramFragmentCreate(tmp);
             ProgramFragment pf = new ProgramFragment(id, mRS);
             pf.mTextureCount = MAX_TEXTURE;
diff --git a/include/media/mediarecorder.h b/include/media/mediarecorder.h
index b21bc4d..291b18a 100644
--- a/include/media/mediarecorder.h
+++ b/include/media/mediarecorder.h
@@ -73,6 +73,9 @@
     OUTPUT_FORMAT_AAC_ADIF = 5,
     OUTPUT_FORMAT_AAC_ADTS = 6,
 
+    /* Stream over a socket, limited to a single stream */
+    OUTPUT_FORMAT_RTP_AVP = 7,
+
     OUTPUT_FORMAT_LIST_END // must be last - used to validate format type
 };
 
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index e631c7f..ab1fa4f 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -46,6 +46,7 @@
     kKeyIsSyncFrame       = 'sync',  // int32_t (bool)
     kKeyIsCodecConfig     = 'conf',  // int32_t (bool)
     kKeyTime              = 'time',  // int64_t (usecs)
+    kKeyNTPTime           = 'ntpT',  // uint64_t (ntp-timestamp)
     kKeyTargetTime        = 'tarT',  // int64_t (usecs)
     kKeyDuration          = 'dura',  // int64_t (usecs)
     kKeyColorFormat       = 'colf',
diff --git a/libs/rs/java/Fountain/src/com/android/fountain/FountainRS.java b/libs/rs/java/Fountain/src/com/android/fountain/FountainRS.java
index bff7273..297ea07 100644
--- a/libs/rs/java/Fountain/src/com/android/fountain/FountainRS.java
+++ b/libs/rs/java/Fountain/src/com/android/fountain/FountainRS.java
@@ -34,6 +34,10 @@
         mRS = rs;
         mRes = res;
 
+        ProgramFragment.Builder pfb = new ProgramFragment.Builder(rs);
+        pfb.setVaryingColor(true);
+        rs.contextBindProgramFragment(pfb.create());
+
         ScriptField_Point points = new ScriptField_Point(mRS, PART_COUNT);
 
         Mesh.AllocationBuilder smb = new Mesh.AllocationBuilder(mRS);
diff --git a/libs/rs/java/Fountain/src/com/android/fountain/fountain.rs b/libs/rs/java/Fountain/src/com/android/fountain/fountain.rs
index f87ef59..812cb7a 100644
--- a/libs/rs/java/Fountain/src/com/android/fountain/fountain.rs
+++ b/libs/rs/java/Fountain/src/com/android/fountain/fountain.rs
@@ -3,6 +3,8 @@
 
 #pragma rs java_package_name(com.android.fountain)
 
+#pragma stateFragment(parent)
+
 #include "rs_graphics.rsh"
 
 static int newPart = 0;
diff --git a/libs/rs/java/ModelViewer/src/com/android/modelviewer/modelviewer.rs b/libs/rs/java/ModelViewer/src/com/android/modelviewer/modelviewer.rs
index 41594eb..adb609c 100644
--- a/libs/rs/java/ModelViewer/src/com/android/modelviewer/modelviewer.rs
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/modelviewer.rs
@@ -62,7 +62,7 @@
 
     rsgDrawMesh(gTestMesh);
 
-    color(0.3f, 0.3f, 0.3f, 1.0f);
+    rsgFontColor(0.3f, 0.3f, 0.3f, 1.0f);
     rsgDrawText("Renderscript model test", 30, 695);
 
     rsgBindFont(gItalic);
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index 1c03954..dfdeb98 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -263,38 +263,18 @@
 {
     char buffer[128];
     sprintf(buffer, "Frame %i ms, Script %i ms", mTimeMSLastFrame, mTimeMSLastScript);
-    float oldR = mStateVertex.color[0];
-    float oldG = mStateVertex.color[1];
-    float oldB = mStateVertex.color[2];
-    float oldA = mStateVertex.color[3];
+    float oldR, oldG, oldB, oldA;
+    mStateFont.getFontColor(&oldR, &oldG, &oldB, &oldA);
 
     float shadowCol = 0.2f;
-    mStateVertex.color[0] = shadowCol;
-    mStateVertex.color[1] = shadowCol;
-    mStateVertex.color[2] = shadowCol;
-    mStateVertex.color[3] = 1.0f;
-    if (!checkVersion2_0()) {
-        glColor4f(shadowCol, shadowCol, shadowCol, 1.0f);
-    }
+    mStateFont.setFontColor(shadowCol, shadowCol, shadowCol, 1.0f);
     mStateFont.renderText(buffer, 5, getHeight() - 5);
 
     float textCol = 0.9f;
-    mStateVertex.color[0] = textCol;
-    mStateVertex.color[1] = textCol;
-    mStateVertex.color[2] = textCol;
-    mStateVertex.color[3] = 1.0f;
-    if (!checkVersion2_0()) {
-        glColor4f(textCol, textCol, textCol, 1.0f);
-    }
+    mStateFont.setFontColor(textCol, textCol, textCol, 1.0f);
     mStateFont.renderText(buffer, 4, getHeight() - 6);
 
-    mStateVertex.color[0] = oldR;
-    mStateVertex.color[1] = oldG;
-    mStateVertex.color[2] = oldB;
-    mStateVertex.color[3] = oldA;
-    if (!checkVersion2_0()) {
-        glColor4f(oldR, oldG, oldB, oldA);
-    }
+    mStateFont.setFontColor(oldR, oldG, oldB, oldA);
 }
 
 void * Context::threadProc(void *vrsc)
diff --git a/libs/rs/rsFont.cpp b/libs/rs/rsFont.cpp
index e58d8b1..833bee0 100644
--- a/libs/rs/rsFont.cpp
+++ b/libs/rs/rsFont.cpp
@@ -259,7 +259,7 @@
     mCurrentQuadIndex = 0;
     mRSC = NULL;
     mLibrary = NULL;
-    setFontColor(0.0f, 0.0f, 0.0f, 1.0f);
+    setFontColor(0.1f, 0.1f, 0.1f, 1.0f);
 }
 
 FontState::~FontState()
@@ -379,12 +379,12 @@
 
 void FontState::initRenderState()
 {
-    uint32_t tmp[5] = {
+    uint32_t tmp[] = {
         RS_TEX_ENV_MODE_REPLACE, 1,
         RS_TEX_ENV_MODE_NONE, 0,
-        0
+        0, 0
     };
-    ProgramFragment *pf = new ProgramFragment(mRSC, tmp, 5);
+    ProgramFragment *pf = new ProgramFragment(mRSC, tmp, 6);
     mFontShaderF.set(pf);
     mFontShaderF->init(mRSC);
 
@@ -521,6 +521,11 @@
     ObjectBaseRef<const ProgramStore> tmpPS(mRSC->getFragmentStore());
     mRSC->setFragmentStore(mFontProgramStore.get());
 
+    if(mFontColorDirty) {
+        mFontShaderF->setConstantColor(mFontColor[0], mFontColor[1], mFontColor[2], mFontColor[3]);
+        mFontColorDirty = false;
+    }
+
     if (!mRSC->setupCheck()) {
         mRSC->setVertex((ProgramVertex *)tmpV.get());
         mRSC->setRaster((ProgramRaster *)tmpR.get());
@@ -669,6 +674,13 @@
     mFontColorDirty = true;
 }
 
+void FontState::getFontColor(float *r, float *g, float *b, float *a) const {
+    *r = mFontColor[0];
+    *g = mFontColor[1];
+    *b = mFontColor[2];
+    *a = mFontColor[3];
+}
+
 void FontState::deinit(Context *rsc)
 {
     mInitialized = false;
diff --git a/libs/rs/rsFont.h b/libs/rs/rsFont.h
index 2d9a34a..ab229be 100644
--- a/libs/rs/rsFont.h
+++ b/libs/rs/rsFont.h
@@ -117,6 +117,7 @@
     void renderText(Allocation *alloc, uint32_t start, int len, int x, int y);
 
     void setFontColor(float r, float g, float b, float a);
+    void getFontColor(float *r, float *g, float *b, float *a) const;
 
 protected:
 
diff --git a/libs/rs/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp
index cbe33c7..056863c 100644
--- a/libs/rs/rsProgramFragment.cpp
+++ b/libs/rs/rsProgramFragment.cpp
@@ -38,13 +38,21 @@
 {
     mAllocFile = __FILE__;
     mAllocLine = __LINE__;
-    rsAssert(paramLength = 5);
+    rsAssert(paramLength == 6);
+
+    mConstantColor[0] = 1.f;
+    mConstantColor[1] = 1.f;
+    mConstantColor[2] = 1.f;
+    mConstantColor[3] = 1.f;
 
     mEnvModes[0] = (RsTexEnvMode)params[0];
     mTextureFormats[0] = params[1];
     mEnvModes[1] = (RsTexEnvMode)params[2];
     mTextureFormats[1] = params[3];
     mPointSpriteEnable = params[4] != 0;
+    mVaryingColor = false;
+    if (paramLength > 5)
+        mVaryingColor = params[5] != 0;
 
     mTextureEnableMask = 0;
     if (mEnvModes[0]) {
@@ -53,7 +61,17 @@
     if (mEnvModes[1]) {
         mTextureEnableMask |= 2;
     }
-    init(rsc);
+
+    mUniformCount = 0;
+    mUniformNames[mUniformCount++].setTo("uni_Tex0");
+    mUniformNames[mUniformCount++].setTo("uni_Tex1");
+
+    mConstantColorUniformIndex = -1;
+    //if (!mVaryingColor) {
+        mConstantColorUniformIndex = mUniformCount;
+        mUniformNames[mUniformCount++].setTo("uni_Color");
+    //}
+    createShader();
 }
 
 ProgramFragment::ProgramFragment(Context *rsc, const char * shaderText,
@@ -64,7 +82,19 @@
     mAllocFile = __FILE__;
     mAllocLine = __LINE__;
 
-    init(rsc);
+    mConstantColor[0] = 1.f;
+    mConstantColor[1] = 1.f;
+    mConstantColor[2] = 1.f;
+    mConstantColor[3] = 1.f;
+
+    LOGE("Custom FP");
+
+    mUniformCount = 2;
+    mUniformNames[0].setTo("uni_Tex0");
+    mUniformNames[1].setTo("uni_Tex1");
+
+    createShader();
+
     mTextureEnableMask = (1 << mTextureCount) -1;
 }
 
@@ -73,79 +103,17 @@
 {
 }
 
+void ProgramFragment::setConstantColor(float r, float g, float b, float a)
+{
+    mConstantColor[0] = r;
+    mConstantColor[1] = g;
+    mConstantColor[2] = b;
+    mConstantColor[3] = a;
+    mDirty = true;
+}
+
 void ProgramFragment::setupGL(const Context *rsc, ProgramFragmentState *state)
 {
-    if ((state->mLast.get() == this) && !mDirty) {
-        return;
-    }
-    state->mLast.set(this);
-
-    for (uint32_t ct=0; ct < MAX_TEXTURE; ct++) {
-        glActiveTexture(GL_TEXTURE0 + ct);
-        if (!(mTextureEnableMask & (1 << ct)) || !mTextures[ct].get()) {
-            glDisable(GL_TEXTURE_2D);
-            continue;
-        }
-
-        glEnable(GL_TEXTURE_2D);
-        if (rsc->checkVersion1_1()) {
-#ifndef ANDROID_RS_BUILD_FOR_HOST // These are GLES only
-            if (mPointSpriteEnable) {
-                glEnable(GL_POINT_SPRITE_OES);
-            } else {
-                glDisable(GL_POINT_SPRITE_OES);
-            }
-            glTexEnvi(GL_POINT_SPRITE_OES, GL_COORD_REPLACE_OES, mPointSpriteEnable);
-#endif //ANDROID_RS_BUILD_FOR_HOST
-
-        }
-        mTextures[ct]->uploadCheck(rsc);
-        glBindTexture(GL_TEXTURE_2D, mTextures[ct]->getTextureID());
-
-        switch(mEnvModes[ct]) {
-        case RS_TEX_ENV_MODE_NONE:
-            rsAssert(0);
-            break;
-        case RS_TEX_ENV_MODE_REPLACE:
-            glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
-            break;
-        case RS_TEX_ENV_MODE_MODULATE:
-            glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
-            break;
-        case RS_TEX_ENV_MODE_DECAL:
-            glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
-            break;
-        }
-
-        if (mSamplers[ct].get()) {
-            mSamplers[ct]->setupGL(rsc, mTextures[ct]->getType()->getIsNp2());
-        } else {
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
-        }
-
-        // Gross hack.
-        if (ct == 2) {
-            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
-
-            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD);
-            glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
-            glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE);
-            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
-            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
-
-            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_ADD);
-            glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PREVIOUS);
-            glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_TEXTURE);
-            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
-            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
-        }
-    }
-    glActiveTexture(GL_TEXTURE0);
-    mDirty = false;
-    rsc->checkError("ProgramFragment::setupGL");
 }
 
 void ProgramFragment::setupGL2(const Context *rsc, ProgramFragmentState *state, ShaderCache *sc)
@@ -158,6 +126,14 @@
     state->mLast.set(this);
 
     rsc->checkError("ProgramFragment::setupGL2 start");
+
+    if (!mVaryingColor &&
+        (sc->fragUniformSlot(mConstantColorUniformIndex) >= 0)) {
+        //LOGE("mConstantColorUniformIndex %i %i", mConstantColorUniformIndex, sc->fragUniformSlot(mConstantColorUniformIndex));
+        glUniform4fv(sc->fragUniformSlot(mConstantColorUniformIndex), 1, mConstantColor);
+        rsc->checkError("ProgramFragment::color setup");
+    }
+
     for (uint32_t ct=0; ct < MAX_TEXTURE; ct++) {
         glActiveTexture(GL_TEXTURE0 + ct);
         if (!(mTextureEnableMask & (1 << ct)) || !mTextures[ct].get()) {
@@ -195,6 +171,7 @@
     mShader.setTo("precision mediump float;\n");
     mShader.append("varying vec4 varColor;\n");
     mShader.append("varying vec4 varTex0;\n");
+    mShader.append("uniform vec4 uni_Color;\n");
 
     if (mUserShader.length() > 1) {
         for (uint32_t ct=0; ct < mTextureCount; ct++) {
@@ -221,7 +198,11 @@
 
 
         mShader.append("void main() {\n");
-        mShader.append("  vec4 col = varColor;\n");
+        if (mVaryingColor) {
+            mShader.append("  vec4 col = varColor;\n");
+        } else {
+            mShader.append("  vec4 col = uni_Color;\n");
+        }
 
         if (mTextureEnableMask) {
             if (mPointSpriteEnable) {
@@ -291,11 +272,6 @@
 
 void ProgramFragment::init(Context *rsc)
 {
-    mUniformCount = 2;
-    mUniformNames[0].setTo("uni_Tex0");
-    mUniformNames[1].setTo("uni_Tex1");
-
-    createShader();
 }
 
 void ProgramFragment::serialize(OStream *stream) const
@@ -321,12 +297,12 @@
 
 void ProgramFragmentState::init(Context *rsc)
 {
-    uint32_t tmp[5] = {
+    uint32_t tmp[] = {
         RS_TEX_ENV_MODE_NONE, 0,
         RS_TEX_ENV_MODE_NONE, 0,
-        0
+        0, 0
     };
-    ProgramFragment *pf = new ProgramFragment(rsc, tmp, 5);
+    ProgramFragment *pf = new ProgramFragment(rsc, tmp, 6);
     mDefault.set(pf);
     pf->init(rsc);
 }
diff --git a/libs/rs/rsProgramFragment.h b/libs/rs/rsProgramFragment.h
index e5bbe1b..7c1598e 100644
--- a/libs/rs/rsProgramFragment.h
+++ b/libs/rs/rsProgramFragment.h
@@ -44,6 +44,8 @@
     virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_PROGRAM_FRAGMENT; }
     static ProgramFragment *createFromStream(Context *rsc, IStream *stream);
 
+    void setConstantColor(float, float, float, float);
+
 protected:
     // Hacks to create a program for now
     uint32_t mTextureFormats[MAX_TEXTURE];
@@ -51,6 +53,10 @@
     RsTexEnvMode mEnvModes[MAX_TEXTURE];
     uint32_t mTextureEnableMask;
     bool mPointSpriteEnable;
+    bool mVaryingColor;
+
+    float mConstantColor[4];
+    int32_t mConstantColorUniformIndex;
 };
 
 class ProgramFragmentState
diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp
index 5558007..60de04a 100644
--- a/libs/rs/rsProgramVertex.cpp
+++ b/libs/rs/rsProgramVertex.cpp
@@ -221,7 +221,6 @@
     }
 
     rsc->checkError("ProgramVertex::setupGL2 start");
-    glVertexAttrib4f(1, state->color[0], state->color[1], state->color[2], state->color[3]);
 
     const float *f = static_cast<const float *>(mConstants[0]->getPtr());
 
@@ -405,11 +404,6 @@
     pv->init(rsc);
     pv->bindAllocation(alloc, 0);
 
-    color[0] = 1.f;
-    color[1] = 1.f;
-    color[2] = 1.f;
-    color[3] = 1.f;
-
     updateSize(rsc);
 #endif //ANDROID_RS_BUILD_FOR_HOST
 
diff --git a/libs/rs/rsProgramVertex.h b/libs/rs/rsProgramVertex.h
index cb93eaf..1c8b9c8 100644
--- a/libs/rs/rsProgramVertex.h
+++ b/libs/rs/rsProgramVertex.h
@@ -83,9 +83,6 @@
     ObjectBaseRef<Allocation> mDefaultAlloc;
 
     ObjectBaseRef<Type> mAllocType;
-
-
-    float color[4];
 };
 
 
diff --git a/libs/rs/rsScriptC_LibGL.cpp b/libs/rs/rsScriptC_LibGL.cpp
index 22b0945..f5e59534 100644
--- a/libs/rs/rsScriptC_LibGL.cpp
+++ b/libs/rs/rsScriptC_LibGL.cpp
@@ -110,6 +110,13 @@
 }
 
 
+static void SC_pfConstantColor(RsProgramFragment vpf, float r, float g, float b, float a)
+{
+    //GET_TLS();
+    ProgramFragment *pf = static_cast<ProgramFragment *>(vpf);
+    pf->setConstantColor(r, g, b, a);
+}
+
 
 //////////////////////////////////////////////////////////////////////////////
 // Drawing
@@ -253,13 +260,8 @@
 static void SC_color(float r, float g, float b, float a)
 {
     GET_TLS();
-    rsc->mStateVertex.color[0] = r;
-    rsc->mStateVertex.color[1] = g;
-    rsc->mStateVertex.color[2] = b;
-    rsc->mStateVertex.color[3] = a;
-    if (!rsc->checkVersion2_0()) {
-        glColor4f(r, g, b, a);
-    }
+    ProgramFragment *pf = (ProgramFragment *)rsc->getFragment();
+    pf->setConstantColor(r, g, b, a);
 }
 
 static void SC_uploadToTexture2(RsAllocation va, uint32_t baseMipLevel)
@@ -371,6 +373,8 @@
     { "_Z31rsgProgramVertexLoadModelMatrixPK12rs_matrix4x4", (void *)&SC_vpLoadModelMatrix },
     { "_Z33rsgProgramVertexLoadTextureMatrixPK12rs_matrix4x4", (void *)&SC_vpLoadTextureMatrix },
 
+    { "_Z31rsgProgramFragmentConstantColor19rs_program_fragmentffff", (void *)&SC_pfConstantColor },
+
     { "_Z11rsgGetWidthv", (void *)&SC_getWidth },
     { "_Z12rsgGetHeightv", (void *)&SC_getHeight },
 
diff --git a/libs/rs/scriptc/rs_core.rsh b/libs/rs/scriptc/rs_core.rsh
index 463550f..01b039f 100644
--- a/libs/rs/scriptc/rs_core.rsh
+++ b/libs/rs/scriptc/rs_core.rsh
@@ -476,6 +476,124 @@
     return ret;
 }
 
+// Returns true if the matrix was successfully inversed
+static bool __attribute__((overloadable))
+rsMatrixInverse(rs_matrix4x4 *m) {
+    rs_matrix4x4 result;
+
+    int i, j;
+    for (i = 0; i < 4; ++i) {
+        for (j = 0; j < 4; ++j) {
+            // computeCofactor for int i, int j
+            int c0 = (i+1) % 4;
+            int c1 = (i+2) % 4;
+            int c2 = (i+3) % 4;
+            int r0 = (j+1) % 4;
+            int r1 = (j+2) % 4;
+            int r2 = (j+3) % 4;
+
+            float minor = (m->m[c0 + 4*r0] * (m->m[c1 + 4*r1] * m->m[c2 + 4*r2] - m->m[c1 + 4*r2] * m->m[c2 + 4*r1]))
+                         - (m->m[c0 + 4*r1] * (m->m[c1 + 4*r0] * m->m[c2 + 4*r2] - m->m[c1 + 4*r2] * m->m[c2 + 4*r0]))
+                         + (m->m[c0 + 4*r2] * (m->m[c1 + 4*r0] * m->m[c2 + 4*r1] - m->m[c1 + 4*r1] * m->m[c2 + 4*r0]));
+
+            float cofactor = (i+j) & 1 ? -minor : minor;
+
+            result.m[4*i + j] = cofactor;
+        }
+    }
+
+    // Dot product of 0th column of source and 0th row of result
+    float det = m->m[0]*result.m[0] + m->m[4]*result.m[1] +
+                 m->m[8]*result.m[2] + m->m[12]*result.m[3];
+
+    if (fabs(det) < 1e-6) {
+        return false;
+    }
+
+    det = 1.0f / det;
+    for (i = 0; i < 16; ++i) {
+        m->m[i] = result.m[i] * det;
+    }
+
+    return true;
+}
+
+// Returns true if the matrix was successfully inversed
+static bool __attribute__((overloadable))
+rsMatrixInverseTranspose(rs_matrix4x4 *m) {
+    rs_matrix4x4 result;
+
+    int i, j;
+    for (i = 0; i < 4; ++i) {
+        for (j = 0; j < 4; ++j) {
+            // computeCofactor for int i, int j
+            int c0 = (i+1) % 4;
+            int c1 = (i+2) % 4;
+            int c2 = (i+3) % 4;
+            int r0 = (j+1) % 4;
+            int r1 = (j+2) % 4;
+            int r2 = (j+3) % 4;
+
+            float minor = (m->m[c0 + 4*r0] * (m->m[c1 + 4*r1] * m->m[c2 + 4*r2] - m->m[c1 + 4*r2] * m->m[c2 + 4*r1]))
+                         - (m->m[c0 + 4*r1] * (m->m[c1 + 4*r0] * m->m[c2 + 4*r2] - m->m[c1 + 4*r2] * m->m[c2 + 4*r0]))
+                         + (m->m[c0 + 4*r2] * (m->m[c1 + 4*r0] * m->m[c2 + 4*r1] - m->m[c1 + 4*r1] * m->m[c2 + 4*r0]));
+
+            float cofactor = (i+j) & 1 ? -minor : minor;
+
+            result.m[4*j + i] = cofactor;
+        }
+    }
+
+    // Dot product of 0th column of source and 0th column of result
+    float det = m->m[0]*result.m[0] + m->m[4]*result.m[4] +
+                 m->m[8]*result.m[8] + m->m[12]*result.m[12];
+
+    if (fabs(det) < 1e-6) {
+        return false;
+    }
+
+    det = 1.0f / det;
+    for (i = 0; i < 16; ++i) {
+        m->m[i] = result.m[i] * det;
+    }
+
+    return true;
+}
+
+static void __attribute__((overloadable))
+rsMatrixTranspose(rs_matrix4x4 *m) {
+    int i, j;
+    float temp;
+    for (i = 0; i < 3; ++i) {
+        for (j = i + 1; j < 4; ++j) {
+            temp = m->m[i*4 + j];
+            m->m[i*4 + j] = m->m[j*4 + i];
+            m->m[j*4 + i] = temp;
+        }
+    }
+}
+
+static void __attribute__((overloadable))
+rsMatrixTranspose(rs_matrix3x3 *m) {
+    int i, j;
+    float temp;
+    for (i = 0; i < 2; ++i) {
+        for (j = i + 1; j < 3; ++j) {
+            temp = m->m[i*3 + j];
+            m->m[i*3 + j] = m->m[j*4 + i];
+            m->m[j*3 + i] = temp;
+        }
+    }
+}
+
+static void __attribute__((overloadable))
+rsMatrixTranspose(rs_matrix2x2 *m) {
+    float temp = m->m[1];
+    m->m[1] = m->m[2];
+    m->m[2] = temp;
+}
+
+
 /////////////////////////////////////////////////////
 // int ops
 /////////////////////////////////////////////////////
diff --git a/libs/rs/scriptc/rs_graphics.rsh b/libs/rs/scriptc/rs_graphics.rsh
index 4f53963..fd0491c 100644
--- a/libs/rs/scriptc/rs_graphics.rsh
+++ b/libs/rs/scriptc/rs_graphics.rsh
@@ -26,6 +26,9 @@
 extern void __attribute__((overloadable))
     rsgProgramVertexLoadTextureMatrix(const rs_matrix4x4 *);
 
+extern void __attribute__((overloadable))
+    rsgProgramFragmentConstantColor(rs_program_fragment, float, float, float, float);
+
 extern uint __attribute__((overloadable))
     rsgGetWidth(void);
 extern uint __attribute__((overloadable))
@@ -76,6 +79,8 @@
 
 ///////////////////////////////////////////////////////
 // misc
+
+// Depricated
 extern void __attribute__((overloadable))
     color(float, float, float, float);
 
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 2b0bd0e..94f5c7a 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -188,6 +188,9 @@
         public static final int AAC_ADIF = 5;
         /** @hide AAC ADTS file format */
         public static final int AAC_ADTS = 6;
+
+        /** @hide Stream over a socket, limited to a single stream */
+        public static final int OUTPUT_FORMAT_RTP_AVP = 7;
     };
 
     /**
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index 1c99ae5..f3229c0 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -568,7 +568,8 @@
         locked = true;
     }
 
-    if (mPlayer == 0) {
+    // Allows calls from JNI in idle state to notify errors
+    if (!(msg == MEDIA_ERROR && mCurrentState == MEDIA_PLAYER_IDLE) && mPlayer == 0) {
         LOGV("notify(%d, %d, %d) callback on disconnected mediaplayer", msg, ext1, ext2);
         if (locked) mLock.unlock();   // release the lock when done.
         return;
diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp
index 5adc116..9d53c257e 100644
--- a/media/libmedia/mediarecorder.cpp
+++ b/media/libmedia/mediarecorder.cpp
@@ -181,7 +181,7 @@
         LOGE("setOutputFormat called in an invalid state: %d", mCurrentState);
         return INVALID_OPERATION;
     }
-    if (mIsVideoSourceSet && of >= OUTPUT_FORMAT_AUDIO_ONLY_START) { //first non-video output format
+    if (mIsVideoSourceSet && of >= OUTPUT_FORMAT_AUDIO_ONLY_START && of != OUTPUT_FORMAT_RTP_AVP) { //first non-video output format
         LOGE("output format (%d) is meant for audio recording only and incompatible with video recording", of);
         return INVALID_OPERATION;
     }
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index 8f010c9..e1edd16 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -31,9 +31,13 @@
 	libandroid_runtime    			\
 	libstagefright        			\
 	libstagefright_omx    			\
-	libstagefright_color_conversion \
+	libstagefright_color_conversion         \
+	libstagefright_foundation               \
 	libsurfaceflinger_client
 
+LOCAL_STATIC_LIBRARIES := \
+        libstagefright_rtsp
+
 ifneq ($(BUILD_WITHOUT_PV),true)
 LOCAL_SHARED_LIBRARIES += \
 	libopencore_player    \
@@ -51,6 +55,7 @@
 	$(call include-path-for, graphics corecg)                       \
 	$(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
 	$(TOP)/frameworks/base/media/libstagefright/include             \
+	$(TOP)/frameworks/base/media/libstagefright/rtsp                \
         $(TOP)/external/tremolo/Tremolo
 
 LOCAL_MODULE:= libmediaplayerservice
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 7d6754b..805f86b 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -41,6 +41,8 @@
 #include <unistd.h>
 #include <ctype.h>
 
+#include "ARTPWriter.h"
+
 namespace android {
 
 StagefrightRecorder::StagefrightRecorder()
@@ -684,6 +686,9 @@
         case OUTPUT_FORMAT_AAC_ADTS:
             return startAACRecording();
 
+        case OUTPUT_FORMAT_RTP_AVP:
+            return startRTPRecording();
+
         default:
             LOGE("Unsupported output file format: %d", mOutputFormat);
             return UNKNOWN_ERROR;
@@ -816,6 +821,39 @@
     return OK;
 }
 
+status_t StagefrightRecorder::startRTPRecording() {
+    CHECK_EQ(mOutputFormat, OUTPUT_FORMAT_RTP_AVP);
+
+    if ((mAudioSource != AUDIO_SOURCE_LIST_END
+                && mVideoSource != VIDEO_SOURCE_LIST_END)
+            || (mAudioSource == AUDIO_SOURCE_LIST_END
+                && mVideoSource == VIDEO_SOURCE_LIST_END)) {
+        // Must have exactly one source.
+        return BAD_VALUE;
+    }
+
+    if (mOutputFd < 0) {
+        return BAD_VALUE;
+    }
+
+    sp<MediaSource> source;
+
+    if (mAudioSource != AUDIO_SOURCE_LIST_END) {
+        source = createAudioSource();
+    } else {
+        status_t err = setupVideoEncoder(&source);
+        if (err != OK) {
+            return err;
+        }
+    }
+
+    mWriter = new ARTPWriter(dup(mOutputFd));
+    mWriter->addSource(source);
+    mWriter->setListener(mListener);
+
+    return mWriter->start();
+}
+
 void StagefrightRecorder::clipVideoFrameRate() {
     LOGV("clipVideoFrameRate: encoder %d", mVideoEncoder);
     int minFrameRate = mEncoderProfiles->getVideoEncoderParamByName(
@@ -949,7 +987,9 @@
     }
 }
 
-status_t StagefrightRecorder::setupVideoEncoder(const sp<MediaWriter>& writer) {
+status_t StagefrightRecorder::setupVideoEncoder(sp<MediaSource> *source) {
+    source->clear();
+
     status_t err = setupCameraSource();
     if (err != OK) return err;
 
@@ -1017,7 +1057,8 @@
         return UNKNOWN_ERROR;
     }
 
-    writer->addSource(encoder);
+    *source = encoder;
+
     return OK;
 }
 
@@ -1055,8 +1096,10 @@
     }
     if (mVideoSource == VIDEO_SOURCE_DEFAULT
             || mVideoSource == VIDEO_SOURCE_CAMERA) {
-        err = setupVideoEncoder(writer);
+        sp<MediaSource> encoder;
+        err = setupVideoEncoder(&encoder);
         if (err != OK) return err;
+        writer->addSource(encoder);
         totalBitRate += mVideoBitRate;
     }
 
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 03f0b61..a8be27d 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -105,10 +105,11 @@
     status_t startMPEG4Recording();
     status_t startAMRRecording();
     status_t startAACRecording();
+    status_t startRTPRecording();
     sp<MediaSource> createAudioSource();
     status_t setupCameraSource();
     status_t setupAudioEncoder(const sp<MediaWriter>& writer);
-    status_t setupVideoEncoder(const sp<MediaWriter>& writer);
+    status_t setupVideoEncoder(sp<MediaSource> *source);
 
     // Encoding parameter handling utilities
     status_t setParameter(const String8 &key, const String8 &value);
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 0708eec..77a1476 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -46,7 +46,8 @@
 	$(JNI_H_INCLUDE) \
         $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
         $(TOP)/external/opencore/android \
-        $(TOP)/external/tremolo
+        $(TOP)/external/tremolo \
+        $(TOP)/frameworks/base/media/libstagefright/rtsp
 
 LOCAL_SHARED_LIBRARIES := \
         libbinder         \
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 236a62b4..436f098 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -27,6 +27,11 @@
 #include "include/NuCachedSource2.h"
 #include "include/ThrottledSource.h"
 
+#include "ARTPSession.h"
+#include "APacketSource.h"
+#include "ASessionDescription.h"
+#include "UDPPusher.h"
+
 #include <binder/IPCThreadState.h>
 #include <media/stagefright/AudioPlayer.h>
 #include <media/stagefright/DataSource.h>
@@ -389,6 +394,9 @@
     }
 
     mRTSPController.clear();
+    mRTPPusher.clear();
+    mRTCPPusher.clear();
+    mRTPSession.clear();
 
     if (mVideoSource != NULL) {
         mVideoSource->stop();
@@ -845,10 +853,24 @@
 }
 
 status_t AwesomePlayer::initVideoDecoder() {
+    uint32_t flags = 0;
+#if 1
+    if (mRTPSession != NULL) {
+        // XXX hack.
+
+        const char *mime;
+        CHECK(mVideoTrack->getFormat()->findCString(kKeyMIMEType, &mime));
+        if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
+            flags |= OMXCodec::kPreferSoftwareCodecs;
+        }
+    }
+#endif
+
     mVideoSource = OMXCodec::Create(
             mClient.interface(), mVideoTrack->getFormat(),
             false, // createEncoder
-            mVideoTrack);
+            mVideoTrack,
+            NULL, flags);
 
     if (mVideoSource != NULL) {
         int64_t durationUs;
@@ -1200,6 +1222,158 @@
             MediaExtractor::Create(dataSource, MEDIA_MIMETYPE_CONTAINER_MPEG2TS);
 
         return setDataSource_l(extractor);
+    } else if (!strcmp("rtsp://gtalk", mUri.string())) {
+        if (mLooper == NULL) {
+            mLooper = new ALooper;
+            mLooper->start();
+        }
+
+#if 0
+        mRTPPusher = new UDPPusher("/data/misc/rtpout.bin", 5434);
+        mLooper->registerHandler(mRTPPusher);
+
+        mRTCPPusher = new UDPPusher("/data/misc/rtcpout.bin", 5435);
+        mLooper->registerHandler(mRTCPPusher);
+#endif
+
+        mRTPSession = new ARTPSession;
+        mLooper->registerHandler(mRTPSession);
+
+#if 0
+        // My H264 SDP
+        static const char *raw =
+            "v=0\r\n"
+            "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+            "s=QuickTime\r\n"
+            "t=0 0\r\n"
+            "a=range:npt=0-315\r\n"
+            "a=isma-compliance:2,2.0,2\r\n"
+            "m=video 5434 RTP/AVP 97\r\n"
+            "c=IN IP4 127.0.0.1\r\n"
+            "b=AS:30\r\n"
+            "a=rtpmap:97 H264/90000\r\n"
+            "a=fmtp:97 packetization-mode=1;profile-level-id=42000C;"
+              "sprop-parameter-sets=Z0IADJZUCg+I,aM44gA==\r\n"
+            "a=mpeg4-esid:201\r\n"
+            "a=cliprect:0,0,240,320\r\n"
+            "a=framesize:97 320-240\r\n";
+#elif 0
+        // My H263 SDP
+        static const char *raw =
+            "v=0\r\n"
+            "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+            "s=QuickTime\r\n"
+            "t=0 0\r\n"
+            "a=range:npt=0-315\r\n"
+            "a=isma-compliance:2,2.0,2\r\n"
+            "m=video 5434 RTP/AVP 97\r\n"
+            "c=IN IP4 127.0.0.1\r\n"
+            "b=AS:30\r\n"
+            "a=rtpmap:97 H263-1998/90000\r\n"
+            "a=cliprect:0,0,240,320\r\n"
+            "a=framesize:97 320-240\r\n";
+#elif 0
+        // My AMR SDP
+        static const char *raw =
+            "v=0\r\n"
+            "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+            "s=QuickTime\r\n"
+            "t=0 0\r\n"
+            "a=range:npt=0-315\r\n"
+            "a=isma-compliance:2,2.0,2\r\n"
+            "m=audio 5434 RTP/AVP 97\r\n"
+            "c=IN IP4 127.0.0.1\r\n"
+            "b=AS:30\r\n"
+            "a=rtpmap:97 AMR/8000/1\r\n"
+            "a=fmtp:97 octet-align\r\n";
+#elif 1
+        // My GTalk H.264 SDP
+        static const char *raw =
+            "v=0\r\n"
+            "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+            "s=QuickTime\r\n"
+            "t=0 0\r\n"
+            "a=range:npt=0-315\r\n"
+            "a=isma-compliance:2,2.0,2\r\n"
+            "m=video 5434 RTP/AVP 97\r\n"
+            "c=IN IP4 127.0.0.1\r\n"
+            "b=AS:30\r\n"
+            "a=rtpmap:97 H264/90000\r\n"
+            "a=fmtp:97 packetization-mode=1;profile-level-id=42E00D;"
+              "sprop-parameter-sets=J0LgDZWgUG/lQA==,KM4DnoA=\r\n"
+            "a=mpeg4-esid:201\r\n"
+            "a=cliprect:0,0,200,320\r\n"
+            "a=framesize:97 320-200\r\n";
+#elif 0
+        // GTalk H263 SDP
+        static const char *raw =
+            "v=0\r\n"
+            "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+            "s=QuickTime\r\n"
+            "t=0 0\r\n"
+            "a=range:npt=0-315\r\n"
+            "a=isma-compliance:2,2.0,2\r\n"
+            "m=video 5434 RTP/AVP 98\r\n"
+            "c=IN IP4 127.0.0.1\r\n"
+            "b=AS:30\r\n"
+            "a=rtpmap:98 H263-1998/90000\r\n"
+            "a=cliprect:0,0,200,320\r\n"
+            "a=framesize:98 320-200\r\n";
+#else
+    // sholes H264 SDP
+    static const char *raw =
+        "v=0\r\n"
+        "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+        "s=QuickTime\r\n"
+        "t=0 0\r\n"
+        "a=range:npt=now-\r\n"
+        "m=video 5434 RTP/AVP 96\r\n"
+        "c=IN IP4 127.0.0.1\r\n"
+        "b=AS:320000\r\n"
+        "a=rtpmap:96 H264/90000\r\n"
+        "a=fmtp:96 packetization-mode=1;profile-level-id=42001E;"
+          "sprop-parameter-sets=Z0KACukCg+QgAAB9AAAOpgCA,aM48gA==\r\n"
+        "a=cliprect:0,0,240,320\r\n"
+        "a=framesize:96 320-240\r\n";
+#endif
+
+        sp<ASessionDescription> desc = new ASessionDescription;
+        CHECK(desc->setTo(raw, strlen(raw)));
+
+        CHECK_EQ(mRTPSession->setup(desc), (status_t)OK);
+
+        if (mRTPPusher != NULL) {
+            mRTPPusher->start();
+        }
+
+        if (mRTCPPusher != NULL) {
+            mRTCPPusher->start();
+        }
+
+        CHECK_EQ(mRTPSession->countTracks(), 1u);
+        sp<MediaSource> source = mRTPSession->trackAt(0);
+
+#if 0
+        bool eos;
+        while (((APacketSource *)source.get())
+                ->getQueuedDuration(&eos) < 5000000ll && !eos) {
+            usleep(100000ll);
+        }
+#endif
+
+        const char *mime;
+        CHECK(source->getFormat()->findCString(kKeyMIMEType, &mime));
+
+        if (!strncasecmp("video/", mime, 6)) {
+            setVideoSource(source);
+        } else {
+            CHECK(!strncasecmp("audio/", mime, 6));
+            setAudioSource(source);
+        }
+
+        mExtractorFlags = MediaExtractor::CAN_PAUSE;
+
+        return OK;
     } else if (!strncasecmp("rtsp://", mUri.string(), 7)) {
         if (mLooper == NULL) {
             mLooper = new ALooper;
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 3fba2d9..d19fbe5 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -1429,6 +1429,10 @@
         return err;
     }
 
+    CODEC_LOGI("allocating %lu buffers of size %lu on %s port",
+            def.nBufferCountActual, def.nBufferSize,
+            portIndex == kPortIndexInput ? "input" : "output");
+
     size_t totalSize = def.nBufferCountActual * def.nBufferSize;
     mDealer[portIndex] = new MemoryDealer(totalSize, "OMXCodec");
 
diff --git a/media/libstagefright/OggExtractor.cpp b/media/libstagefright/OggExtractor.cpp
index c267a23..16f094b 100644
--- a/media/libstagefright/OggExtractor.cpp
+++ b/media/libstagefright/OggExtractor.cpp
@@ -361,7 +361,7 @@
                 memcpy(tmp->data(), buffer->data(), buffer->range_length());
                 tmp->set_range(0, buffer->range_length());
                 buffer->release();
-            } else {
+            } else if (mVi.rate) {
                 // XXX Not only is this not technically the correct time for
                 // this packet, we also stamp every packet in this page
                 // with the same time. This needs fixing later.
diff --git a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
index 050e3da..7154ba5 100644
--- a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
+++ b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
@@ -53,7 +53,9 @@
       mNumSamplesOutput(0),
       mPendingSeekTimeUs(-1),
       mPendingSeekMode(MediaSource::ReadOptions::SEEK_CLOSEST_SYNC),
-      mTargetTimeUs(-1) {
+      mTargetTimeUs(-1),
+      mSPSSeen(false),
+      mPPSSeen(false) {
     memset(mHandle, 0, sizeof(tagAVCHandle));
     mHandle->AVCObject = NULL;
     mHandle->userData = this;
@@ -165,6 +167,8 @@
     mPendingSeekTimeUs = -1;
     mPendingSeekMode = ReadOptions::SEEK_CLOSEST_SYNC;
     mTargetTimeUs = -1;
+    mSPSSeen = false;
+    mPPSSeen = false;
     mStarted = true;
 
     return OK;
@@ -314,184 +318,196 @@
 
     if (res != AVCDEC_SUCCESS) {
         LOGE("cannot determine nal type");
-    } else switch (nalType) {
-        case AVC_NALTYPE_SPS:
-        {
-            res = PVAVCDecSeqParamSet(
-                    mHandle, const_cast<uint8_t *>(fragPtr),
-                    fragSize);
-
-            if (res != AVCDEC_SUCCESS) {
-                break;
-            }
-
-            AVCDecObject *pDecVid = (AVCDecObject *)mHandle->AVCObject;
-
-            int32_t width =
-                (pDecVid->seqParams[0]->pic_width_in_mbs_minus1 + 1) * 16;
-
-            int32_t height =
-                (pDecVid->seqParams[0]->pic_height_in_map_units_minus1 + 1) * 16;
-
-            int32_t crop_left, crop_right, crop_top, crop_bottom;
-            if (pDecVid->seqParams[0]->frame_cropping_flag)
+    } else if (nalType == AVC_NALTYPE_SPS || nalType == AVC_NALTYPE_PPS
+                || (mSPSSeen && mPPSSeen)) {
+        switch (nalType) {
+            case AVC_NALTYPE_SPS:
             {
-                crop_left = 2 * pDecVid->seqParams[0]->frame_crop_left_offset;
-                crop_right =
-                    width - (2 * pDecVid->seqParams[0]->frame_crop_right_offset + 1);
+                mSPSSeen = true;
 
-                if (pDecVid->seqParams[0]->frame_mbs_only_flag)
-                {
-                    crop_top = 2 * pDecVid->seqParams[0]->frame_crop_top_offset;
-                    crop_bottom =
-                        height -
-                        (2 * pDecVid->seqParams[0]->frame_crop_bottom_offset + 1);
+                res = PVAVCDecSeqParamSet(
+                        mHandle, const_cast<uint8_t *>(fragPtr),
+                        fragSize);
+
+                if (res != AVCDEC_SUCCESS) {
+                    break;
                 }
-                else
+
+                AVCDecObject *pDecVid = (AVCDecObject *)mHandle->AVCObject;
+
+                int32_t width =
+                    (pDecVid->seqParams[0]->pic_width_in_mbs_minus1 + 1) * 16;
+
+                int32_t height =
+                    (pDecVid->seqParams[0]->pic_height_in_map_units_minus1 + 1) * 16;
+
+                int32_t crop_left, crop_right, crop_top, crop_bottom;
+                if (pDecVid->seqParams[0]->frame_cropping_flag)
                 {
-                    crop_top = 4 * pDecVid->seqParams[0]->frame_crop_top_offset;
-                    crop_bottom =
-                        height -
-                        (4 * pDecVid->seqParams[0]->frame_crop_bottom_offset + 1);
-                }
-            } else {
-                crop_bottom = height - 1;
-                crop_right = width - 1;
-                crop_top = crop_left = 0;
-            }
+                    crop_left = 2 * pDecVid->seqParams[0]->frame_crop_left_offset;
+                    crop_right =
+                        width - (2 * pDecVid->seqParams[0]->frame_crop_right_offset + 1);
 
-            int32_t aligned_width = (crop_right - crop_left + 1 + 15) & ~15;
-            int32_t aligned_height = (crop_bottom - crop_top + 1 + 15) & ~15;
-
-            int32_t oldWidth, oldHeight;
-            CHECK(mFormat->findInt32(kKeyWidth, &oldWidth));
-            CHECK(mFormat->findInt32(kKeyHeight, &oldHeight));
-
-            if (oldWidth != aligned_width || oldHeight != aligned_height) {
-                mFormat->setInt32(kKeyWidth, aligned_width);
-                mFormat->setInt32(kKeyHeight, aligned_height);
-
-                err = INFO_FORMAT_CHANGED;
-            } else {
-                *out = new MediaBuffer(0);
-                err = OK;
-            }
-            break;
-        }
-
-        case AVC_NALTYPE_PPS:
-        {
-            res = PVAVCDecPicParamSet(
-                    mHandle, const_cast<uint8_t *>(fragPtr),
-                    fragSize);
-
-            if (res != AVCDEC_SUCCESS) {
-                break;
-            }
-
-            *out = new MediaBuffer(0);
-
-            err = OK;
-            break;
-        }
-
-        case AVC_NALTYPE_SLICE:
-        case AVC_NALTYPE_IDR:
-        {
-            res = PVAVCDecodeSlice(
-                    mHandle, const_cast<uint8_t *>(fragPtr),
-                    fragSize);
-
-            if (res == AVCDEC_PICTURE_OUTPUT_READY) {
-                int32_t index;
-                int32_t Release;
-                AVCFrameIO Output;
-                Output.YCbCr[0] = Output.YCbCr[1] = Output.YCbCr[2] = NULL;
-
-                CHECK_EQ(PVAVCDecGetOutput(mHandle, &index, &Release, &Output),
-                         AVCDEC_SUCCESS);
-
-                CHECK(index >= 0);
-                CHECK(index < (int32_t)mFrames.size());
-
-                MediaBuffer *mbuf = mFrames.editItemAt(index);
-
-                bool skipFrame = false;
-
-                if (mTargetTimeUs >= 0) {
-                    int64_t timeUs;
-                    CHECK(mbuf->meta_data()->findInt64(kKeyTime, &timeUs));
-                    CHECK(timeUs <= mTargetTimeUs);
-
-                    if (timeUs < mTargetTimeUs) {
-                        // We're still waiting for the frame with the matching
-                        // timestamp and we won't return the current one.
-                        skipFrame = true;
-
-                        LOGV("skipping frame at %lld us", timeUs);
-                    } else {
-                        LOGV("found target frame at %lld us", timeUs);
-
-                        mTargetTimeUs = -1;
+                    if (pDecVid->seqParams[0]->frame_mbs_only_flag)
+                    {
+                        crop_top = 2 * pDecVid->seqParams[0]->frame_crop_top_offset;
+                        crop_bottom =
+                            height -
+                            (2 * pDecVid->seqParams[0]->frame_crop_bottom_offset + 1);
                     }
+                    else
+                    {
+                        crop_top = 4 * pDecVid->seqParams[0]->frame_crop_top_offset;
+                        crop_bottom =
+                            height -
+                            (4 * pDecVid->seqParams[0]->frame_crop_bottom_offset + 1);
+                    }
+                } else {
+                    crop_bottom = height - 1;
+                    crop_right = width - 1;
+                    crop_top = crop_left = 0;
                 }
 
-                if (!skipFrame) {
-                    *out = mbuf;
-                    (*out)->set_range(0, (*out)->size());
-                    (*out)->add_ref();
+                int32_t aligned_width = (crop_right - crop_left + 1 + 15) & ~15;
+                int32_t aligned_height = (crop_bottom - crop_top + 1 + 15) & ~15;
+
+                int32_t oldWidth, oldHeight;
+                CHECK(mFormat->findInt32(kKeyWidth, &oldWidth));
+                CHECK(mFormat->findInt32(kKeyHeight, &oldHeight));
+
+                if (oldWidth != aligned_width || oldHeight != aligned_height) {
+                    mFormat->setInt32(kKeyWidth, aligned_width);
+                    mFormat->setInt32(kKeyHeight, aligned_height);
+
+                    err = INFO_FORMAT_CHANGED;
                 } else {
                     *out = new MediaBuffer(0);
+                    err = OK;
                 }
-
-                // Do _not_ release input buffer yet.
-
-                releaseFragment = false;
-                err = OK;
                 break;
             }
 
-            if (res == AVCDEC_PICTURE_READY || res == AVCDEC_SUCCESS) {
+            case AVC_NALTYPE_PPS:
+            {
+                mPPSSeen = true;
+
+                res = PVAVCDecPicParamSet(
+                        mHandle, const_cast<uint8_t *>(fragPtr),
+                        fragSize);
+
+                if (res != AVCDEC_SUCCESS) {
+                    break;
+                }
+
                 *out = new MediaBuffer(0);
 
                 err = OK;
-            } else {
-                LOGV("failed to decode frame (res = %d)", res);
-            }
-            break;
-        }
-
-        case AVC_NALTYPE_SEI:
-        {
-            res = PVAVCDecSEI(
-                    mHandle, const_cast<uint8_t *>(fragPtr),
-                    fragSize);
-
-            if (res != AVCDEC_SUCCESS) {
                 break;
             }
 
-            *out = new MediaBuffer(0);
+            case AVC_NALTYPE_SLICE:
+            case AVC_NALTYPE_IDR:
+            {
+                res = PVAVCDecodeSlice(
+                        mHandle, const_cast<uint8_t *>(fragPtr),
+                        fragSize);
 
-            err = OK;
-            break;
+                if (res == AVCDEC_PICTURE_OUTPUT_READY) {
+                    int32_t index;
+                    int32_t Release;
+                    AVCFrameIO Output;
+                    Output.YCbCr[0] = Output.YCbCr[1] = Output.YCbCr[2] = NULL;
+
+                    CHECK_EQ(PVAVCDecGetOutput(mHandle, &index, &Release, &Output),
+                             AVCDEC_SUCCESS);
+
+                    CHECK(index >= 0);
+                    CHECK(index < (int32_t)mFrames.size());
+
+                    MediaBuffer *mbuf = mFrames.editItemAt(index);
+
+                    bool skipFrame = false;
+
+                    if (mTargetTimeUs >= 0) {
+                        int64_t timeUs;
+                        CHECK(mbuf->meta_data()->findInt64(kKeyTime, &timeUs));
+                        CHECK(timeUs <= mTargetTimeUs);
+
+                        if (timeUs < mTargetTimeUs) {
+                            // We're still waiting for the frame with the matching
+                            // timestamp and we won't return the current one.
+                            skipFrame = true;
+
+                            LOGV("skipping frame at %lld us", timeUs);
+                        } else {
+                            LOGV("found target frame at %lld us", timeUs);
+
+                            mTargetTimeUs = -1;
+                        }
+                    }
+
+                    if (!skipFrame) {
+                        *out = mbuf;
+                        (*out)->set_range(0, (*out)->size());
+                        (*out)->add_ref();
+                    } else {
+                        *out = new MediaBuffer(0);
+                    }
+
+                    // Do _not_ release input buffer yet.
+
+                    releaseFragment = false;
+                    err = OK;
+                    break;
+                }
+
+                if (res == AVCDEC_PICTURE_READY || res == AVCDEC_SUCCESS) {
+                    *out = new MediaBuffer(0);
+
+                    err = OK;
+                } else {
+                    LOGV("failed to decode frame (res = %d)", res);
+                }
+                break;
+            }
+
+            case AVC_NALTYPE_SEI:
+            {
+                res = PVAVCDecSEI(
+                        mHandle, const_cast<uint8_t *>(fragPtr),
+                        fragSize);
+
+                if (res != AVCDEC_SUCCESS) {
+                    break;
+                }
+
+                *out = new MediaBuffer(0);
+
+                err = OK;
+                break;
+            }
+
+            case AVC_NALTYPE_AUD:
+            case AVC_NALTYPE_FILL:
+            {
+                *out = new MediaBuffer(0);
+
+                err = OK;
+                break;
+            }
+
+            default:
+            {
+                LOGE("Should not be here, unknown nalType %d", nalType);
+                CHECK(!"Should not be here");
+                break;
+            }
         }
+    } else {
+        // We haven't seen SPS or PPS yet.
 
-        case AVC_NALTYPE_AUD:
-        case AVC_NALTYPE_FILL:
-        {
-            *out = new MediaBuffer(0);
-
-            err = OK;
-            break;
-        }
-
-        default:
-        {
-            LOGE("Should not be here, unknown nalType %d", nalType);
-            CHECK(!"Should not be here");
-            break;
-        }
+        *out = new MediaBuffer(0);
+        err = OK;
     }
 
     if (releaseFragment) {
diff --git a/media/libstagefright/include/AVCDecoder.h b/media/libstagefright/include/AVCDecoder.h
index 7810171..898c90a 100644
--- a/media/libstagefright/include/AVCDecoder.h
+++ b/media/libstagefright/include/AVCDecoder.h
@@ -62,6 +62,9 @@
 
     int64_t mTargetTimeUs;
 
+    bool mSPSSeen;
+    bool mPPSSeen;
+
     void addCodecSpecificData(const uint8_t *data, size_t size);
 
     static int32_t ActivateSPSWrapper(
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 8d0877c..55e2c36 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -38,6 +38,8 @@
 
 struct ALooper;
 struct ARTSPController;
+struct ARTPSession;
+struct UDPPusher;
 
 struct AwesomeRenderer : public RefBase {
     AwesomeRenderer() {}
@@ -178,6 +180,8 @@
 
     sp<ALooper> mLooper;
     sp<ARTSPController> mRTSPController;
+    sp<ARTPSession> mRTPSession;
+    sp<UDPPusher> mRTPPusher, mRTCPPusher;
 
     struct SuspensionState {
         String8 mUri;
diff --git a/media/libstagefright/rtsp/AAMRAssembler.cpp b/media/libstagefright/rtsp/AAMRAssembler.cpp
new file mode 100644
index 0000000..c56578b
--- /dev/null
+++ b/media/libstagefright/rtsp/AAMRAssembler.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2010 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 "AAMRAssembler.h"
+
+#include "ARTPSource.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+static bool GetAttribute(const char *s, const char *key, AString *value) {
+    value->clear();
+
+    size_t keyLen = strlen(key);
+
+    for (;;) {
+        const char *colonPos = strchr(s, ';');
+
+        size_t len =
+            (colonPos == NULL) ? strlen(s) : colonPos - s;
+
+        if (len >= keyLen + 1 && s[keyLen] == '=' && !strncmp(s, key, keyLen)) {
+            value->setTo(&s[keyLen + 1], len - keyLen - 1);
+            return true;
+        }
+        if (len == keyLen && !strncmp(s, key, keyLen)) {
+            value->setTo("1");
+            return true;
+        }
+
+        if (colonPos == NULL) {
+            return false;
+        }
+
+        s = colonPos + 1;
+    }
+}
+
+AAMRAssembler::AAMRAssembler(
+        const sp<AMessage> &notify, bool isWide, const AString &params)
+    : mIsWide(isWide),
+      mNotifyMsg(notify),
+      mNextExpectedSeqNoValid(false),
+      mNextExpectedSeqNo(0) {
+    AString value;
+    CHECK(GetAttribute(params.c_str(), "octet-align", &value) && value == "1");
+    CHECK(!GetAttribute(params.c_str(), "crc", &value) || value == "0");
+    CHECK(!GetAttribute(params.c_str(), "interleaving", &value));
+}
+
+AAMRAssembler::~AAMRAssembler() {
+}
+
+ARTPAssembler::AssemblyStatus AAMRAssembler::assembleMore(
+        const sp<ARTPSource> &source) {
+    return addPacket(source);
+}
+
+static size_t getFrameSize(bool isWide, unsigned FT) {
+    static const size_t kFrameSizeNB[8] = {
+        95, 103, 118, 134, 148, 159, 204, 244
+    };
+    static const size_t kFrameSizeWB[9] = {
+        132, 177, 253, 285, 317, 365, 397, 461, 477
+    };
+
+    size_t frameSize = isWide ? kFrameSizeWB[FT] : kFrameSizeNB[FT];
+
+    // Round up bits to bytes and add 1 for the header byte.
+    frameSize = (frameSize + 7) / 8 + 1;
+
+    return frameSize;
+}
+
+ARTPAssembler::AssemblyStatus AAMRAssembler::addPacket(
+        const sp<ARTPSource> &source) {
+    List<sp<ABuffer> > *queue = source->queue();
+
+    if (queue->empty()) {
+        return NOT_ENOUGH_DATA;
+    }
+
+    if (mNextExpectedSeqNoValid) {
+        List<sp<ABuffer> >::iterator it = queue->begin();
+        while (it != queue->end()) {
+            if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) {
+                break;
+            }
+
+            it = queue->erase(it);
+        }
+
+        if (queue->empty()) {
+            return NOT_ENOUGH_DATA;
+        }
+    }
+
+    sp<ABuffer> buffer = *queue->begin();
+
+    if (!mNextExpectedSeqNoValid) {
+        mNextExpectedSeqNoValid = true;
+        mNextExpectedSeqNo = (uint32_t)buffer->int32Data();
+    } else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) {
+#if VERBOSE
+        LOG(VERBOSE) << "Not the sequence number I expected";
+#endif
+
+        return WRONG_SEQUENCE_NUMBER;
+    }
+
+    // hexdump(buffer->data(), buffer->size());
+
+    if (buffer->size() < 1) {
+        queue->erase(queue->begin());
+        ++mNextExpectedSeqNo;
+
+        LOG(VERBOSE) << "AMR packet too short.";
+
+        return MALFORMED_PACKET;
+    }
+
+    unsigned payloadHeader = buffer->data()[0];
+    unsigned CMR = payloadHeader >> 4;
+    CHECK_EQ(payloadHeader & 0x0f, 0u);  // RR
+
+    Vector<uint8_t> tableOfContents;
+
+    size_t offset = 1;
+    size_t totalSize = 0;
+    for (;;) {
+        if (offset >= buffer->size()) {
+            queue->erase(queue->begin());
+            ++mNextExpectedSeqNo;
+
+            LOG(VERBOSE) << "Unable to parse TOC.";
+
+            return MALFORMED_PACKET;
+        }
+
+        uint8_t toc = buffer->data()[offset++];
+
+        unsigned FT = (toc >> 3) & 0x0f;
+        if ((toc & 3) != 0
+                || (mIsWide && FT > 8)
+                || (!mIsWide && FT > 7)) {
+            queue->erase(queue->begin());
+            ++mNextExpectedSeqNo;
+
+            LOG(VERBOSE) << "Illegal TOC entry.";
+
+            return MALFORMED_PACKET;
+        }
+
+        totalSize += getFrameSize(mIsWide, (toc >> 3) & 0x0f);
+
+        tableOfContents.push(toc);
+
+        if (0 == (toc & 0x80)) {
+            break;
+        }
+    }
+
+    uint64_t ntpTime;
+    CHECK(buffer->meta()->findInt64(
+                "ntp-time", (int64_t *)&ntpTime));
+
+    sp<ABuffer> accessUnit = new ABuffer(totalSize);
+    accessUnit->meta()->setInt64("ntp-time", ntpTime);
+
+    size_t dstOffset = 0;
+    for (size_t i = 0; i < tableOfContents.size(); ++i) {
+        uint8_t toc = tableOfContents[i];
+
+        size_t frameSize = getFrameSize(mIsWide, (toc >> 3) & 0x0f);
+
+        if (offset + frameSize - 1 > buffer->size()) {
+            queue->erase(queue->begin());
+            ++mNextExpectedSeqNo;
+
+            LOG(VERBOSE) << "AMR packet too short.";
+
+            return MALFORMED_PACKET;
+        }
+
+        accessUnit->data()[dstOffset++] = toc;
+        memcpy(accessUnit->data() + dstOffset,
+               buffer->data() + offset, frameSize - 1);
+
+        offset += frameSize - 1;
+        dstOffset += frameSize - 1;
+    }
+
+    sp<AMessage> msg = mNotifyMsg->dup();
+    msg->setObject("access-unit", accessUnit);
+    msg->post();
+
+    queue->erase(queue->begin());
+    ++mNextExpectedSeqNo;
+
+    return OK;
+}
+
+void AAMRAssembler::packetLost() {
+    CHECK(mNextExpectedSeqNoValid);
+    ++mNextExpectedSeqNo;
+}
+
+void AAMRAssembler::onByeReceived() {
+    sp<AMessage> msg = mNotifyMsg->dup();
+    msg->setInt32("eos", true);
+    msg->post();
+}
+
+}  // namespace android
diff --git a/media/libstagefright/rtsp/AAMRAssembler.h b/media/libstagefright/rtsp/AAMRAssembler.h
new file mode 100644
index 0000000..d55e109
--- /dev/null
+++ b/media/libstagefright/rtsp/AAMRAssembler.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 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 A_AMR_ASSEMBLER_H_
+
+#define A_AMR_ASSEMBLER_H_
+
+#include "ARTPAssembler.h"
+
+#include <utils/List.h>
+
+#include <stdint.h>
+
+namespace android {
+
+struct AMessage;
+struct AString;
+
+struct AAMRAssembler : public ARTPAssembler {
+    AAMRAssembler(
+            const sp<AMessage> &notify, bool isWide,
+            const AString &params);
+
+protected:
+    virtual ~AAMRAssembler();
+
+    virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
+    virtual void onByeReceived();
+    virtual void packetLost();
+
+private:
+    bool mIsWide;
+
+    sp<AMessage> mNotifyMsg;
+    bool mNextExpectedSeqNoValid;
+    uint32_t mNextExpectedSeqNo;
+
+    AssemblyStatus addPacket(const sp<ARTPSource> &source);
+
+    DISALLOW_EVIL_CONSTRUCTORS(AAMRAssembler);
+};
+
+}  // namespace android
+
+#endif  // A_AMR_ASSEMBLER_H_
+
diff --git a/media/libstagefright/rtsp/AAVCAssembler.cpp b/media/libstagefright/rtsp/AAVCAssembler.cpp
index 3dfb200..b22de2c 100644
--- a/media/libstagefright/rtsp/AAVCAssembler.cpp
+++ b/media/libstagefright/rtsp/AAVCAssembler.cpp
@@ -377,9 +377,17 @@
 
 void AAVCAssembler::packetLost() {
     CHECK(mNextExpectedSeqNoValid);
+    LOG(VERBOSE) << "packetLost (expected " << mNextExpectedSeqNo << ")";
+
     ++mNextExpectedSeqNo;
 
     mAccessUnitDamaged = true;
 }
 
+void AAVCAssembler::onByeReceived() {
+    sp<AMessage> msg = mNotifyMsg->dup();
+    msg->setInt32("eos", true);
+    msg->post();
+}
+
 }  // namespace android
diff --git a/media/libstagefright/rtsp/AAVCAssembler.h b/media/libstagefright/rtsp/AAVCAssembler.h
index 1e97520..bf389ec 100644
--- a/media/libstagefright/rtsp/AAVCAssembler.h
+++ b/media/libstagefright/rtsp/AAVCAssembler.h
@@ -35,6 +35,7 @@
     virtual ~AAVCAssembler();
 
     virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
+    virtual void onByeReceived();
     virtual void packetLost();
 
 private:
diff --git a/media/libstagefright/rtsp/AH263Assembler.cpp b/media/libstagefright/rtsp/AH263Assembler.cpp
new file mode 100644
index 0000000..8b59998
--- /dev/null
+++ b/media/libstagefright/rtsp/AH263Assembler.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2010 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 "AH263Assembler.h"
+
+#include "ARTPSource.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+AH263Assembler::AH263Assembler(const sp<AMessage> &notify)
+    : mNotifyMsg(notify),
+      mAccessUnitRTPTime(0),
+      mNextExpectedSeqNoValid(false),
+      mNextExpectedSeqNo(0),
+      mAccessUnitDamaged(false) {
+}
+
+AH263Assembler::~AH263Assembler() {
+}
+
+ARTPAssembler::AssemblyStatus AH263Assembler::assembleMore(
+        const sp<ARTPSource> &source) {
+    AssemblyStatus status = addPacket(source);
+    if (status == MALFORMED_PACKET) {
+        mAccessUnitDamaged = true;
+    }
+    return status;
+}
+
+ARTPAssembler::AssemblyStatus AH263Assembler::addPacket(
+        const sp<ARTPSource> &source) {
+    List<sp<ABuffer> > *queue = source->queue();
+
+    if (queue->empty()) {
+        return NOT_ENOUGH_DATA;
+    }
+
+    if (mNextExpectedSeqNoValid) {
+        List<sp<ABuffer> >::iterator it = queue->begin();
+        while (it != queue->end()) {
+            if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) {
+                break;
+            }
+
+            it = queue->erase(it);
+        }
+
+        if (queue->empty()) {
+            return NOT_ENOUGH_DATA;
+        }
+    }
+
+    sp<ABuffer> buffer = *queue->begin();
+
+    if (!mNextExpectedSeqNoValid) {
+        mNextExpectedSeqNoValid = true;
+        mNextExpectedSeqNo = (uint32_t)buffer->int32Data();
+    } else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) {
+#if VERBOSE
+        LOG(VERBOSE) << "Not the sequence number I expected";
+#endif
+
+        return WRONG_SEQUENCE_NUMBER;
+    }
+
+    uint32_t rtpTime;
+    CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+    if (mPackets.size() > 0 && rtpTime != mAccessUnitRTPTime) {
+        submitAccessUnit();
+    }
+    mAccessUnitRTPTime = rtpTime;
+
+    // hexdump(buffer->data(), buffer->size());
+
+    if (buffer->size() < 2) {
+        queue->erase(queue->begin());
+        ++mNextExpectedSeqNo;
+
+        return MALFORMED_PACKET;
+    }
+
+    unsigned payloadHeader = U16_AT(buffer->data());
+    CHECK_EQ(payloadHeader >> 11, 0u);  // RR=0
+    unsigned P = (payloadHeader >> 10) & 1;
+    CHECK_EQ((payloadHeader >> 9) & 1, 0u);  // V=0
+    CHECK_EQ((payloadHeader >> 3) & 0x3f, 0u);  // PLEN=0
+    CHECK_EQ(payloadHeader & 7, 0u);  // PEBIT=0
+
+    if (P) {
+        buffer->data()[0] = 0x00;
+        buffer->data()[1] = 0x00;
+    } else {
+        buffer->setRange(2, buffer->size() - 2);
+    }
+
+    mPackets.push_back(buffer);
+
+    queue->erase(queue->begin());
+    ++mNextExpectedSeqNo;
+
+    return OK;
+}
+
+void AH263Assembler::submitAccessUnit() {
+    CHECK(!mPackets.empty());
+
+#if VERBOSE
+    LOG(VERBOSE) << "Access unit complete (" << mPackets.size() << " packets)";
+#endif
+
+    uint64_t ntpTime;
+    CHECK((*mPackets.begin())->meta()->findInt64(
+                "ntp-time", (int64_t *)&ntpTime));
+
+    size_t totalSize = 0;
+    List<sp<ABuffer> >::iterator it = mPackets.begin();
+    while (it != mPackets.end()) {
+        const sp<ABuffer> &unit = *it;
+
+        totalSize += unit->size();
+        ++it;
+    }
+
+    sp<ABuffer> accessUnit = new ABuffer(totalSize);
+    size_t offset = 0;
+    it = mPackets.begin();
+    while (it != mPackets.end()) {
+        const sp<ABuffer> &unit = *it;
+
+        memcpy((uint8_t *)accessUnit->data() + offset,
+               unit->data(), unit->size());
+
+        offset += unit->size();
+
+        ++it;
+    }
+
+    accessUnit->meta()->setInt64("ntp-time", ntpTime);
+
+#if 0
+    printf(mAccessUnitDamaged ? "X" : ".");
+    fflush(stdout);
+#endif
+
+    if (mAccessUnitDamaged) {
+        accessUnit->meta()->setInt32("damaged", true);
+    }
+
+    mPackets.clear();
+    mAccessUnitDamaged = false;
+
+    sp<AMessage> msg = mNotifyMsg->dup();
+    msg->setObject("access-unit", accessUnit);
+    msg->post();
+}
+
+void AH263Assembler::packetLost() {
+    CHECK(mNextExpectedSeqNoValid);
+    ++mNextExpectedSeqNo;
+
+    mAccessUnitDamaged = true;
+}
+
+void AH263Assembler::onByeReceived() {
+    sp<AMessage> msg = mNotifyMsg->dup();
+    msg->setInt32("eos", true);
+    msg->post();
+}
+
+}  // namespace android
+
diff --git a/media/libstagefright/rtsp/AH263Assembler.h b/media/libstagefright/rtsp/AH263Assembler.h
new file mode 100644
index 0000000..2b6c625
--- /dev/null
+++ b/media/libstagefright/rtsp/AH263Assembler.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 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 A_H263_ASSEMBLER_H_
+
+#define A_H263_ASSEMBLER_H_
+
+#include "ARTPAssembler.h"
+
+#include <utils/List.h>
+
+#include <stdint.h>
+
+namespace android {
+
+struct AMessage;
+
+struct AH263Assembler : public ARTPAssembler {
+    AH263Assembler(const sp<AMessage> &notify);
+
+protected:
+    virtual ~AH263Assembler();
+
+    virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
+    virtual void onByeReceived();
+    virtual void packetLost();
+
+private:
+    sp<AMessage> mNotifyMsg;
+    uint32_t mAccessUnitRTPTime;
+    bool mNextExpectedSeqNoValid;
+    uint32_t mNextExpectedSeqNo;
+    bool mAccessUnitDamaged;
+    List<sp<ABuffer> > mPackets;
+
+    AssemblyStatus addPacket(const sp<ARTPSource> &source);
+    void submitAccessUnit();
+
+    DISALLOW_EVIL_CONSTRUCTORS(AH263Assembler);
+};
+
+}  // namespace android
+
+#endif  // A_H263_ASSEMBLER_H_
diff --git a/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp
index 7e55106..6e46361 100644
--- a/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp
+++ b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp
@@ -168,4 +168,10 @@
     mAccessUnitDamaged = true;
 }
 
+void AMPEG4AudioAssembler::onByeReceived() {
+    sp<AMessage> msg = mNotifyMsg->dup();
+    msg->setInt32("eos", true);
+    msg->post();
+}
+
 }  // namespace android
diff --git a/media/libstagefright/rtsp/AMPEG4AudioAssembler.h b/media/libstagefright/rtsp/AMPEG4AudioAssembler.h
index 5c2a2dd..bf9f204 100644
--- a/media/libstagefright/rtsp/AMPEG4AudioAssembler.h
+++ b/media/libstagefright/rtsp/AMPEG4AudioAssembler.h
@@ -35,6 +35,7 @@
     virtual ~AMPEG4AudioAssembler();
 
     virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
+    virtual void onByeReceived();
     virtual void packetLost();
 
 private:
diff --git a/media/libstagefright/rtsp/APacketSource.cpp b/media/libstagefright/rtsp/APacketSource.cpp
index 2869d54..88336ba 100644
--- a/media/libstagefright/rtsp/APacketSource.cpp
+++ b/media/libstagefright/rtsp/APacketSource.cpp
@@ -226,8 +226,11 @@
 
 APacketSource::APacketSource(
         const sp<ASessionDescription> &sessionDesc, size_t index)
-    : mFormat(new MetaData),
-      mEOSResult(OK) {
+    : mInitCheck(NO_INIT),
+      mFormat(new MetaData),
+      mEOSResult(OK),
+      mFirstAccessUnit(true),
+      mFirstAccessUnitNTP(0) {
     unsigned long PT;
     AString desc;
     AString params;
@@ -240,6 +243,7 @@
         mFormat->setInt64(kKeyDuration, 60 * 60 * 1000000ll);
     }
 
+    mInitCheck = OK;
     if (!strncmp(desc.c_str(), "H264/", 5)) {
         mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
 
@@ -255,8 +259,16 @@
         mFormat->setData(
                 kKeyAVCC, 0,
                 codecSpecificData->data(), codecSpecificData->size());
+    } else if (!strncmp(desc.c_str(), "H263-2000/", 10)
+            || !strncmp(desc.c_str(), "H263-1998/", 10)) {
+        mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263);
 
-    } else if (!strncmp(desc.c_str(), "MP4A-LATM", 9)) {
+        int32_t width, height;
+        sessionDesc->getDimensions(index, PT, &width, &height);
+
+        mFormat->setInt32(kKeyWidth, width);
+        mFormat->setInt32(kKeyHeight, height);
+    } else if (!strncmp(desc.c_str(), "MP4A-LATM/", 10)) {
         mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
 
         int32_t sampleRate, numChannels;
@@ -272,15 +284,48 @@
         mFormat->setData(
                 kKeyESDS, 0,
                 codecSpecificData->data(), codecSpecificData->size());
+    } else if (!strncmp(desc.c_str(), "AMR/", 4)) {
+        mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AMR_NB);
+
+        int32_t sampleRate, numChannels;
+        ASessionDescription::ParseFormatDesc(
+                desc.c_str(), &sampleRate, &numChannels);
+
+        mFormat->setInt32(kKeySampleRate, sampleRate);
+        mFormat->setInt32(kKeyChannelCount, numChannels);
+
+        if (sampleRate != 8000 || numChannels != 1) {
+            mInitCheck = ERROR_UNSUPPORTED;
+        }
+    } else if (!strncmp(desc.c_str(), "AMR-WB/", 7)) {
+        mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AMR_WB);
+
+        int32_t sampleRate, numChannels;
+        ASessionDescription::ParseFormatDesc(
+                desc.c_str(), &sampleRate, &numChannels);
+
+        mFormat->setInt32(kKeySampleRate, sampleRate);
+        mFormat->setInt32(kKeyChannelCount, numChannels);
+
+        if (sampleRate != 16000 || numChannels != 1) {
+            mInitCheck = ERROR_UNSUPPORTED;
+        }
     } else {
-        TRESPASS();
+        mInitCheck = ERROR_UNSUPPORTED;
     }
 }
 
 APacketSource::~APacketSource() {
 }
 
+status_t APacketSource::initCheck() const {
+    return mInitCheck;
+}
+
 status_t APacketSource::start(MetaData *params) {
+    mFirstAccessUnit = true;
+    mFirstAccessUnitNTP = 0;
+
     return OK;
 }
 
@@ -308,10 +353,23 @@
         CHECK(buffer->meta()->findInt64(
                     "ntp-time", (int64_t *)&ntpTime));
 
+        MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
+        mediaBuffer->meta_data()->setInt64(kKeyNTPTime, ntpTime);
+
+        if (mFirstAccessUnit) {
+            mFirstAccessUnit = false;
+            mFirstAccessUnitNTP = ntpTime;
+        }
+        if (ntpTime > mFirstAccessUnitNTP) {
+            ntpTime -= mFirstAccessUnitNTP;
+        } else {
+            ntpTime = 0;
+        }
+
         int64_t timeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32));
 
-        MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
         mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
+
         memcpy(mediaBuffer->data(), buffer->data(), buffer->size());
         *out = mediaBuffer;
 
@@ -342,4 +400,31 @@
     mCondition.signal();
 }
 
+int64_t APacketSource::getQueuedDuration(bool *eos) {
+    Mutex::Autolock autoLock(mLock);
+
+    *eos = (mEOSResult != OK);
+
+    if (mBuffers.empty()) {
+        return 0;
+    }
+
+    sp<ABuffer> buffer = *mBuffers.begin();
+
+    uint64_t ntpTime;
+    CHECK(buffer->meta()->findInt64(
+                "ntp-time", (int64_t *)&ntpTime));
+
+    int64_t firstTimeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32));
+
+    buffer = *--mBuffers.end();
+
+    CHECK(buffer->meta()->findInt64(
+                "ntp-time", (int64_t *)&ntpTime));
+
+    int64_t lastTimeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32));
+
+    return lastTimeUs - firstTimeUs;
+}
+
 }  // namespace android
diff --git a/media/libstagefright/rtsp/APacketSource.h b/media/libstagefright/rtsp/APacketSource.h
index 4040eee..647da6e 100644
--- a/media/libstagefright/rtsp/APacketSource.h
+++ b/media/libstagefright/rtsp/APacketSource.h
@@ -31,6 +31,8 @@
 struct APacketSource : public MediaSource {
     APacketSource(const sp<ASessionDescription> &sessionDesc, size_t index);
 
+    status_t initCheck() const;
+
     virtual status_t start(MetaData *params = NULL);
     virtual status_t stop();
     virtual sp<MetaData> getFormat();
@@ -41,10 +43,14 @@
     void queueAccessUnit(const sp<ABuffer> &buffer);
     void signalEOS(status_t result);
 
+    int64_t getQueuedDuration(bool *eos);
+
 protected:
     virtual ~APacketSource();
 
 private:
+    status_t mInitCheck;
+
     Mutex mLock;
     Condition mCondition;
 
@@ -52,6 +58,9 @@
     List<sp<ABuffer> > mBuffers;
     status_t mEOSResult;
 
+    bool mFirstAccessUnit;
+    uint64_t mFirstAccessUnitNTP;
+
     DISALLOW_EVIL_CONSTRUCTORS(APacketSource);
 };
 
diff --git a/media/libstagefright/rtsp/ARTPAssembler.h b/media/libstagefright/rtsp/ARTPAssembler.h
index 892bd65..e598088 100644
--- a/media/libstagefright/rtsp/ARTPAssembler.h
+++ b/media/libstagefright/rtsp/ARTPAssembler.h
@@ -37,6 +37,7 @@
     ARTPAssembler();
 
     void onPacketReceived(const sp<ARTPSource> &source);
+    virtual void onByeReceived() = 0;
 
 protected:
     static void PropagateTimes(
diff --git a/media/libstagefright/rtsp/ARTPConnection.cpp b/media/libstagefright/rtsp/ARTPConnection.cpp
index a4413f0..9abdab4 100644
--- a/media/libstagefright/rtsp/ARTPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTPConnection.cpp
@@ -23,18 +23,17 @@
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/foundation/hexdump.h>
 
 #include <arpa/inet.h>
 #include <sys/socket.h>
 
-#define VERBOSE         0
-
-#if VERBOSE
-#include "hexdump.h"
-#endif
+#define IGNORE_RTCP_TIME        0
 
 namespace android {
 
+static const size_t kMaxUDPSize = 1500;
+
 static uint16_t u16at(const uint8_t *data) {
     return data[0] << 8 | data[1];
 }
@@ -56,10 +55,15 @@
     sp<ASessionDescription> mSessionDesc;
     size_t mIndex;
     sp<AMessage> mNotifyMsg;
+    KeyedVector<uint32_t, sp<ARTPSource> > mSources;
+
+    int32_t mNumRTCPPacketsReceived;
+    struct sockaddr_in mRemoteRTCPAddr;
 };
 
 ARTPConnection::ARTPConnection()
-    : mPollEventPending(false) {
+    : mPollEventPending(false),
+      mLastReceiverReportTimeUs(-1) {
 }
 
 ARTPConnection::~ARTPConnection() {
@@ -176,6 +180,9 @@
     CHECK(msg->findSize("index", &info->mIndex));
     CHECK(msg->findMessage("notify", &info->mNotifyMsg));
 
+    info->mNumRTCPPacketsReceived = 0;
+    memset(&info->mRemoteRTCPAddr, 0, sizeof(info->mRemoteRTCPAddr));
+
     postPollEvent();
 }
 
@@ -252,21 +259,59 @@
     }
 
     postPollEvent();
+
+    int64_t nowUs = ALooper::GetNowUs();
+    if (mLastReceiverReportTimeUs <= 0
+            || mLastReceiverReportTimeUs + 5000000ll <= nowUs) {
+        sp<ABuffer> buffer = new ABuffer(kMaxUDPSize);
+        for (List<StreamInfo>::iterator it = mStreams.begin();
+             it != mStreams.end(); ++it) {
+            StreamInfo *s = &*it;
+
+            if (s->mNumRTCPPacketsReceived == 0) {
+                // We have never received any RTCP packets on this stream,
+                // we don't even know where to send a report.
+                continue;
+            }
+
+            buffer->setRange(0, 0);
+
+            for (size_t i = 0; i < s->mSources.size(); ++i) {
+                sp<ARTPSource> source = s->mSources.valueAt(i);
+
+                source->addReceiverReport(buffer);
+                source->addFIR(buffer);
+            }
+
+            if (buffer->size() > 0) {
+                LOG(VERBOSE) << "Sending RR...";
+
+                ssize_t n = sendto(
+                        s->mRTCPSocket, buffer->data(), buffer->size(), 0,
+                        (const struct sockaddr *)&s->mRemoteRTCPAddr,
+                        sizeof(s->mRemoteRTCPAddr));
+                CHECK_EQ(n, (ssize_t)buffer->size());
+
+                mLastReceiverReportTimeUs = nowUs;
+            }
+        }
+    }
 }
 
 status_t ARTPConnection::receive(StreamInfo *s, bool receiveRTP) {
     sp<ABuffer> buffer = new ABuffer(65536);
 
-    struct sockaddr_in from;
-    socklen_t fromSize = sizeof(from);
+    socklen_t remoteAddrLen =
+        (!receiveRTP && s->mNumRTCPPacketsReceived == 0)
+            ? sizeof(s->mRemoteRTCPAddr) : 0;
 
     ssize_t nbytes = recvfrom(
             receiveRTP ? s->mRTPSocket : s->mRTCPSocket,
             buffer->data(),
             buffer->capacity(),
             0,
-            (struct sockaddr *)&from,
-            &fromSize);
+            remoteAddrLen > 0 ? (struct sockaddr *)&s->mRemoteRTCPAddr : NULL,
+            remoteAddrLen > 0 ? &remoteAddrLen : NULL);
 
     if (nbytes < 0) {
         return -1;
@@ -278,6 +323,7 @@
     if (receiveRTP) {
         err = parseRTP(s, buffer);
     } else {
+        ++s->mNumRTCPPacketsReceived;
         err = parseRTCP(s, buffer);
     }
 
@@ -346,18 +392,7 @@
 
     uint32_t srcId = u32at(&data[8]);
 
-    sp<ARTPSource> source;
-    ssize_t index = mSources.indexOfKey(srcId);
-    if (index < 0) {
-        index = mSources.size();
-
-        source = new ARTPSource(
-                srcId, s->mSessionDesc, s->mIndex, s->mNotifyMsg);
-
-        mSources.add(srcId, source);
-    } else {
-        source = mSources.valueAt(index);
-    }
+    sp<ARTPSource> source = findSource(s, srcId);
 
     uint32_t rtpTime = u32at(&data[4]);
 
@@ -368,24 +403,6 @@
     meta->setInt32("M", data[1] >> 7);
 
     buffer->setInt32Data(u16at(&data[2]));
-
-#if VERBOSE
-    printf("RTP = {\n"
-           "  PT: %d\n"
-           "  sequence number: %d\n"
-           "  RTP-time: 0x%08x\n"
-           "  M: %d\n"
-           "  SSRC: 0x%08x\n"
-           "}\n",
-           data[1] & 0x7f,
-           u16at(&data[2]),
-           rtpTime,
-           data[1] >> 7,
-           srcId);
-
-    // hexdump(&data[payloadOffset], size - payloadOffset);
-#endif
-
     buffer->setRange(payloadOffset, size - payloadOffset);
 
     source->processRTPPacket(buffer);
@@ -436,14 +453,27 @@
                 break;
             }
 
+            case 201:  // RR
+            case 202:  // SDES
+            case 204:  // APP
+                break;
+
+            case 205:  // TSFB (transport layer specific feedback)
+            case 206:  // PSFB (payload specific feedback)
+                // hexdump(data, headerLength);
+                break;
+
+            case 203:
+            {
+                parseBYE(s, data, headerLength);
+                break;
+            }
+
             default:
             {
-#if VERBOSE
-                printf("Unknown RTCP packet type %d of size %ld\n",
-                       data[1], headerLength);
-
-                hexdump(data, headerLength);
-#endif
+                LOG(WARNING) << "Unknown RTCP packet type "
+                             << (unsigned)data[1]
+                             << " of size " << headerLength;
                 break;
             }
         }
@@ -455,6 +485,24 @@
     return OK;
 }
 
+status_t ARTPConnection::parseBYE(
+        StreamInfo *s, const uint8_t *data, size_t size) {
+    size_t SC = data[0] & 0x3f;
+
+    if (SC == 0 || size < (4 + SC * 4)) {
+        // Packet too short for the minimal BYE header.
+        return -1;
+    }
+
+    uint32_t id = u32at(&data[4]);
+
+    sp<ARTPSource> source = findSource(s, id);
+
+    source->byeReceived();
+
+    return OK;
+}
+
 status_t ARTPConnection::parseSR(
         StreamInfo *s, const uint8_t *data, size_t size) {
     size_t RC = data[0] & 0x1f;
@@ -468,32 +516,44 @@
     uint64_t ntpTime = u64at(&data[8]);
     uint32_t rtpTime = u32at(&data[16]);
 
-#if VERBOSE
-    printf("SR = {\n"
-           "  SSRC:      0x%08x\n"
-           "  NTP-time:  0x%016llx\n"
-           "  RTP-time:  0x%08x\n"
-           "}\n",
-           id, ntpTime, rtpTime);
+#if 0
+    LOG(INFO) << StringPrintf(
+            "XXX timeUpdate: ssrc=0x%08x, rtpTime %u == ntpTime %.3f",
+            id,
+            rtpTime, (ntpTime >> 32) + (double)(ntpTime & 0xffffffff) / (1ll << 32));
 #endif
 
-    sp<ARTPSource> source;
-    ssize_t index = mSources.indexOfKey(id);
-    if (index < 0) {
-        index = mSources.size();
+    sp<ARTPSource> source = findSource(s, id);
 
-        source = new ARTPSource(
-                id, s->mSessionDesc, s->mIndex, s->mNotifyMsg);
-
-        mSources.add(id, source);
-    } else {
-        source = mSources.valueAt(index);
-    }
-
+#if !IGNORE_RTCP_TIME
     source->timeUpdate(rtpTime, ntpTime);
+#endif
 
     return 0;
 }
 
+sp<ARTPSource> ARTPConnection::findSource(StreamInfo *info, uint32_t srcId) {
+    sp<ARTPSource> source;
+    ssize_t index = info->mSources.indexOfKey(srcId);
+    if (index < 0) {
+        index = info->mSources.size();
+
+        source = new ARTPSource(
+                srcId, info->mSessionDesc, info->mIndex, info->mNotifyMsg);
+
+#if IGNORE_RTCP_TIME
+        // For H.263 gtalk to work...
+        source->timeUpdate(0, 0);
+        source->timeUpdate(30, 0x100000000ll);
+#endif
+
+        info->mSources.add(srcId, source);
+    } else {
+        source = info->mSources.valueAt(index);
+    }
+
+    return source;
+}
+
 }  // namespace android
 
diff --git a/media/libstagefright/rtsp/ARTPConnection.h b/media/libstagefright/rtsp/ARTPConnection.h
index c77e3a4..49839ad 100644
--- a/media/libstagefright/rtsp/ARTPConnection.h
+++ b/media/libstagefright/rtsp/ARTPConnection.h
@@ -59,19 +59,22 @@
     struct StreamInfo;
     List<StreamInfo> mStreams;
 
-    KeyedVector<uint32_t, sp<ARTPSource> > mSources;
-
     bool mPollEventPending;
+    int64_t mLastReceiverReportTimeUs;
 
     void onAddStream(const sp<AMessage> &msg);
     void onRemoveStream(const sp<AMessage> &msg);
     void onPollStreams();
+    void onSendReceiverReports();
 
     status_t receive(StreamInfo *info, bool receiveRTP);
 
     status_t parseRTP(StreamInfo *info, const sp<ABuffer> &buffer);
     status_t parseRTCP(StreamInfo *info, const sp<ABuffer> &buffer);
     status_t parseSR(StreamInfo *info, const uint8_t *data, size_t size);
+    status_t parseBYE(StreamInfo *info, const uint8_t *data, size_t size);
+
+    sp<ARTPSource> findSource(StreamInfo *info, uint32_t id);
 
     void postPollEvent();
 
diff --git a/media/libstagefright/rtsp/ARTPSession.cpp b/media/libstagefright/rtsp/ARTPSession.cpp
new file mode 100644
index 0000000..0e0f45a
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPSession.cpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2010 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 "ARTPSession.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+
+#include <ctype.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include "APacketSource.h"
+#include "ARTPConnection.h"
+#include "ASessionDescription.h"
+
+namespace android {
+
+ARTPSession::ARTPSession()
+    : mInitCheck(NO_INIT) {
+}
+
+status_t ARTPSession::setup(const sp<ASessionDescription> &desc) {
+    CHECK_EQ(mInitCheck, (status_t)NO_INIT);
+
+    mDesc = desc;
+
+    mRTPConn = new ARTPConnection;
+    looper()->registerHandler(mRTPConn);
+
+    for (size_t i = 1; i < mDesc->countTracks(); ++i) {
+        AString connection;
+        if (!mDesc->findAttribute(i, "c=", &connection)) {
+            // No per-stream connection information, try global fallback.
+            if (!mDesc->findAttribute(0, "c=", &connection)) {
+                LOG(ERROR) << "Unable to find connection attribtue.";
+                return mInitCheck;
+            }
+        }
+        if (!(connection == "IN IP4 127.0.0.1")) {
+            LOG(ERROR) << "We only support localhost connections for now.";
+            return mInitCheck;
+        }
+
+        unsigned port;
+        if (!validateMediaFormat(i, &port) || (port & 1) != 0) {
+            LOG(ERROR) << "Invalid media format.";
+            return mInitCheck;
+        }
+
+        sp<APacketSource> source = new APacketSource(mDesc, i);
+        if (source->initCheck() != OK) {
+            LOG(ERROR) << "Unsupported format.";
+            return mInitCheck;
+        }
+
+        int rtpSocket = MakeUDPSocket(port);
+        int rtcpSocket = MakeUDPSocket(port + 1);
+
+        mTracks.push(TrackInfo());
+        TrackInfo *info = &mTracks.editItemAt(mTracks.size() - 1);
+        info->mRTPSocket = rtpSocket;
+        info->mRTCPSocket = rtcpSocket;
+
+        sp<AMessage> notify = new AMessage(kWhatAccessUnitComplete, id());
+        notify->setSize("track-index", mTracks.size() - 1);
+
+        mRTPConn->addStream(rtpSocket, rtcpSocket, mDesc, i, notify);
+
+        info->mPacketSource = source;
+    }
+
+    mInitCheck = OK;
+
+    return OK;
+}
+
+// static
+int ARTPSession::MakeUDPSocket(unsigned port) {
+    int s = socket(AF_INET, SOCK_DGRAM, 0);
+    CHECK_GE(s, 0);
+
+    struct sockaddr_in addr;
+    memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
+    addr.sin_family = AF_INET;
+    addr.sin_addr.s_addr = INADDR_ANY;
+    addr.sin_port = htons(port);
+
+    CHECK_EQ(0, bind(s, (const struct sockaddr *)&addr, sizeof(addr)));
+
+    return s;
+}
+
+ARTPSession::~ARTPSession() {
+    for (size_t i = 0; i < mTracks.size(); ++i) {
+        TrackInfo *info = &mTracks.editItemAt(i);
+
+        info->mPacketSource->signalEOS(UNKNOWN_ERROR);
+
+        close(info->mRTPSocket);
+        close(info->mRTCPSocket);
+    }
+}
+
+void ARTPSession::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatAccessUnitComplete:
+        {
+            size_t trackIndex;
+            CHECK(msg->findSize("track-index", &trackIndex));
+
+            int32_t eos;
+            if (msg->findInt32("eos", &eos) && eos) {
+                TrackInfo *info = &mTracks.editItemAt(trackIndex);
+                info->mPacketSource->signalEOS(ERROR_END_OF_STREAM);
+                break;
+            }
+
+            sp<RefBase> obj;
+            CHECK(msg->findObject("access-unit", &obj));
+
+            sp<ABuffer> accessUnit = static_cast<ABuffer *>(obj.get());
+
+            uint64_t ntpTime;
+            CHECK(accessUnit->meta()->findInt64(
+                        "ntp-time", (int64_t *)&ntpTime));
+
+#if 0
+#if 0
+            printf("access unit complete size=%d\tntp-time=0x%016llx\n",
+                   accessUnit->size(), ntpTime);
+#else
+            LOG(INFO) << "access unit complete, "
+                      << "size=" << accessUnit->size() << ", "
+                      << "ntp-time=" << ntpTime;
+            hexdump(accessUnit->data(), accessUnit->size());
+#endif
+#endif
+
+#if 0
+            CHECK_GE(accessUnit->size(), 5u);
+            CHECK(!memcmp("\x00\x00\x00\x01", accessUnit->data(), 4));
+            unsigned x = accessUnit->data()[4];
+
+            LOG(INFO) << "access unit complete: "
+                      << StringPrintf("nalType=0x%02x, nalRefIdc=0x%02x",
+                                      x & 0x1f, (x & 0x60) >> 5);
+#endif
+
+            accessUnit->meta()->setInt64("ntp-time", ntpTime);
+
+#if 0
+            int32_t damaged;
+            if (accessUnit->meta()->findInt32("damaged", &damaged)
+                    && damaged != 0) {
+                LOG(INFO) << "ignoring damaged AU";
+            } else
+#endif
+            {
+                TrackInfo *info = &mTracks.editItemAt(trackIndex);
+                info->mPacketSource->queueAccessUnit(accessUnit);
+            }
+            break;
+        }
+
+        default:
+            TRESPASS();
+            break;
+    }
+}
+
+bool ARTPSession::validateMediaFormat(size_t index, unsigned *port) const {
+    AString format;
+    mDesc->getFormat(index, &format);
+
+    ssize_t i = format.find(" ");
+    if (i < 0) {
+        return false;
+    }
+
+    ++i;
+    size_t j = i;
+    while (isdigit(format.c_str()[j])) {
+        ++j;
+    }
+    if (format.c_str()[j] != ' ') {
+        return false;
+    }
+
+    AString portString(format, i, j - i);
+
+    char *end;
+    unsigned long x = strtoul(portString.c_str(), &end, 10);
+    if (end == portString.c_str() || *end != '\0') {
+        return false;
+    }
+
+    if (x == 0 || x > 65535) {
+        return false;
+    }
+
+    *port = x;
+
+    return true;
+}
+
+size_t ARTPSession::countTracks() {
+    return mTracks.size();
+}
+
+sp<MediaSource> ARTPSession::trackAt(size_t index) {
+    CHECK_LT(index, mTracks.size());
+    return mTracks.editItemAt(index).mPacketSource;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/rtsp/ARTPSession.h b/media/libstagefright/rtsp/ARTPSession.h
new file mode 100644
index 0000000..9bff74c
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPSession.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010 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 A_RTP_SESSION_H_
+
+#define A_RTP_SESSION_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct APacketSource;
+struct ARTPConnection;
+struct ASessionDescription;
+struct MediaSource;
+
+struct ARTPSession : public AHandler {
+    ARTPSession();
+
+    status_t setup(const sp<ASessionDescription> &desc);
+
+    size_t countTracks();
+    sp<MediaSource> trackAt(size_t index);
+
+protected:
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
+    virtual ~ARTPSession();
+
+private:
+    enum {
+        kWhatAccessUnitComplete = 'accu'
+    };
+
+    struct TrackInfo {
+        int mRTPSocket;
+        int mRTCPSocket;
+
+        sp<APacketSource> mPacketSource;
+    };
+
+    status_t mInitCheck;
+    sp<ASessionDescription> mDesc;
+    sp<ARTPConnection> mRTPConn;
+
+    Vector<TrackInfo> mTracks;
+
+    bool validateMediaFormat(size_t index, unsigned *port) const;
+    static int MakeUDPSocket(unsigned port);
+
+    DISALLOW_EVIL_CONSTRUCTORS(ARTPSession);
+};
+
+}  // namespace android
+
+#endif  // A_RTP_SESSION_H_
diff --git a/media/libstagefright/rtsp/ARTPSource.cpp b/media/libstagefright/rtsp/ARTPSource.cpp
index f05daa8..2aa0c1f 100644
--- a/media/libstagefright/rtsp/ARTPSource.cpp
+++ b/media/libstagefright/rtsp/ARTPSource.cpp
@@ -16,7 +16,9 @@
 
 #include "ARTPSource.h"
 
+#include "AAMRAssembler.h"
 #include "AAVCAssembler.h"
+#include "AH263Assembler.h"
 #include "AMPEG4AudioAssembler.h"
 #include "ASessionDescription.h"
 
@@ -24,10 +26,12 @@
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
 
-#define VERBOSE         0
+#define BE_VERBOSE      0
 
 namespace android {
 
+static const uint32_t kSourceID = 0xdeadbeef;
+
 ARTPSource::ARTPSource(
         uint32_t id,
         const sp<ASessionDescription> &sessionDesc, size_t index,
@@ -35,7 +39,12 @@
     : mID(id),
       mHighestSeqNumber(0),
       mNumBuffersReceived(0),
-      mNumTimes(0) {
+      mNumTimes(0),
+      mLastNTPTime(0),
+      mLastNTPTimeUpdateUs(0),
+      mIssueFIRRequests(false),
+      mLastFIRRequestUs(-1),
+      mNextFIRSeqNo((rand() * 256.0) / RAND_MAX) {
     unsigned long PT;
     AString desc;
     AString params;
@@ -43,8 +52,16 @@
 
     if (!strncmp(desc.c_str(), "H264/", 5)) {
         mAssembler = new AAVCAssembler(notify);
-    } else if (!strncmp(desc.c_str(), "MP4A-LATM", 9)) {
+        mIssueFIRRequests = true;
+    } else if (!strncmp(desc.c_str(), "MP4A-LATM/", 10)) {
         mAssembler = new AMPEG4AudioAssembler(notify);
+    } else if (!strncmp(desc.c_str(), "H263-1998/", 10)
+            || !strncmp(desc.c_str(), "H263-2000/", 10)) {
+        mAssembler = new AH263Assembler(notify);
+    } else if (!strncmp(desc.c_str(), "AMR/", 4)) {
+        mAssembler = new AAMRAssembler(notify, false /* isWide */, params);
+    } else  if (!strncmp(desc.c_str(), "AMR-WB/", 7)) {
+        mAssembler = new AAMRAssembler(notify, true /* isWide */, params);
     } else {
         TRESPASS();
     }
@@ -55,7 +72,9 @@
 }
 
 void ARTPSource::processRTPPacket(const sp<ABuffer> &buffer) {
-    if (queuePacket(buffer) && mNumTimes == 2 && mAssembler != NULL) {
+    if (queuePacket(buffer)
+            && mNumTimes == 2
+            && mAssembler != NULL) {
         mAssembler->onPacketReceived(this);
     }
 
@@ -63,10 +82,13 @@
 }
 
 void ARTPSource::timeUpdate(uint32_t rtpTime, uint64_t ntpTime) {
-#if VERBOSE
+#if BE_VERBOSE
     LOG(VERBOSE) << "timeUpdate";
 #endif
 
+    mLastNTPTime = ntpTime;
+    mLastNTPTimeUpdateUs = ALooper::GetNowUs();
+
     if (mNumTimes == 2) {
         mNTPTime[0] = mNTPTime[1];
         mRTPTime[0] = mRTPTime[1];
@@ -89,6 +111,13 @@
 }
 
 bool ARTPSource::queuePacket(const sp<ABuffer> &buffer) {
+#if 1
+    if (mNumTimes != 2) {
+        // Drop incoming packets until we've established a time base.
+        return false;
+    }
+#endif
+
     uint32_t seqNum = (uint32_t)buffer->int32Data();
 
     if (mNumTimes == 2) {
@@ -194,7 +223,7 @@
 
 #if 0
     AString out;
-    
+
     out.append(tmp);
     out.append(" [");
 
@@ -245,6 +274,120 @@
             / (double)(mRTPTime[1] - mRTPTime[0]);
 }
 
+void ARTPSource::byeReceived() {
+    mAssembler->onByeReceived();
+}
+
+void ARTPSource::addFIR(const sp<ABuffer> &buffer) {
+    if (!mIssueFIRRequests) {
+        return;
+    }
+
+    int64_t nowUs = ALooper::GetNowUs();
+    if (mLastFIRRequestUs >= 0 && mLastFIRRequestUs + 5000000ll > nowUs) {
+        // Send FIR requests at most every 5 secs.
+        return;
+    }
+
+    mLastFIRRequestUs = nowUs;
+
+    if (buffer->size() + 20 > buffer->capacity()) {
+        LOG(WARNING) << "RTCP buffer too small to accomodate FIR.";
+        return;
+    }
+
+    uint8_t *data = buffer->data() + buffer->size();
+
+    data[0] = 0x80 | 4;
+    data[1] = 206;  // PSFB
+    data[2] = 0;
+    data[3] = 4;
+    data[4] = kSourceID >> 24;
+    data[5] = (kSourceID >> 16) & 0xff;
+    data[6] = (kSourceID >> 8) & 0xff;
+    data[7] = kSourceID & 0xff;
+
+    data[8] = 0x00;  // SSRC of media source (unused)
+    data[9] = 0x00;
+    data[10] = 0x00;
+    data[11] = 0x00;
+
+    data[12] = mID >> 24;
+    data[13] = (mID >> 16) & 0xff;
+    data[14] = (mID >> 8) & 0xff;
+    data[15] = mID & 0xff;
+
+    data[16] = mNextFIRSeqNo++;  // Seq Nr.
+
+    data[17] = 0x00;  // Reserved
+    data[18] = 0x00;
+    data[19] = 0x00;
+
+    buffer->setRange(buffer->offset(), buffer->size() + 20);
+
+    LOG(VERBOSE) << "Added FIR request.";
+}
+
+void ARTPSource::addReceiverReport(const sp<ABuffer> &buffer) {
+    if (buffer->size() + 32 > buffer->capacity()) {
+        LOG(WARNING) << "RTCP buffer too small to accomodate RR.";
+        return;
+    }
+
+    uint8_t *data = buffer->data() + buffer->size();
+
+    data[0] = 0x80 | 1;
+    data[1] = 201;  // RR
+    data[2] = 0;
+    data[3] = 7;
+    data[4] = kSourceID >> 24;
+    data[5] = (kSourceID >> 16) & 0xff;
+    data[6] = (kSourceID >> 8) & 0xff;
+    data[7] = kSourceID & 0xff;
+
+    data[8] = mID >> 24;
+    data[9] = (mID >> 16) & 0xff;
+    data[10] = (mID >> 8) & 0xff;
+    data[11] = mID & 0xff;
+
+    data[12] = 0x00;  // fraction lost
+
+    data[13] = 0x00;  // cumulative lost
+    data[14] = 0x00;
+    data[15] = 0x00;
+
+    data[16] = mHighestSeqNumber >> 24;
+    data[17] = (mHighestSeqNumber >> 16) & 0xff;
+    data[18] = (mHighestSeqNumber >> 8) & 0xff;
+    data[19] = mHighestSeqNumber & 0xff;
+
+    data[20] = 0x00;  // Interarrival jitter
+    data[21] = 0x00;
+    data[22] = 0x00;
+    data[23] = 0x00;
+
+    uint32_t LSR = 0;
+    uint32_t DLSR = 0;
+    if (mLastNTPTime != 0) {
+        LSR = (mLastNTPTime >> 16) & 0xffffffff;
+
+        DLSR = (uint32_t)
+            ((ALooper::GetNowUs() - mLastNTPTimeUpdateUs) * 65536.0 / 1E6);
+    }
+
+    data[24] = LSR >> 24;
+    data[25] = (LSR >> 16) & 0xff;
+    data[26] = (LSR >> 8) & 0xff;
+    data[27] = LSR & 0xff;
+
+    data[28] = DLSR >> 24;
+    data[29] = (DLSR >> 16) & 0xff;
+    data[30] = (DLSR >> 8) & 0xff;
+    data[31] = DLSR & 0xff;
+
+    buffer->setRange(buffer->offset(), buffer->size() + 32);
+}
+
 }  // namespace android
 
 
diff --git a/media/libstagefright/rtsp/ARTPSource.h b/media/libstagefright/rtsp/ARTPSource.h
index b93cd56..8e483a8 100644
--- a/media/libstagefright/rtsp/ARTPSource.h
+++ b/media/libstagefright/rtsp/ARTPSource.h
@@ -39,9 +39,13 @@
 
     void processRTPPacket(const sp<ABuffer> &buffer);
     void timeUpdate(uint32_t rtpTime, uint64_t ntpTime);
+    void byeReceived();
 
     List<sp<ABuffer> > *queue() { return &mQueue; }
 
+    void addReceiverReport(const sp<ABuffer> &buffer);
+    void addFIR(const sp<ABuffer> &buffer);
+
 private:
     uint32_t mID;
     uint32_t mHighestSeqNumber;
@@ -54,6 +58,13 @@
     uint64_t mNTPTime[2];
     uint32_t mRTPTime[2];
 
+    uint64_t mLastNTPTime;
+    int64_t mLastNTPTimeUpdateUs;
+
+    bool mIssueFIRRequests;
+    int64_t mLastFIRRequestUs;
+    uint8_t mNextFIRSeqNo;
+
     uint64_t RTP2NTP(uint32_t rtpTime) const;
 
     bool queuePacket(const sp<ABuffer> &buffer);
diff --git a/media/libstagefright/rtsp/ARTPWriter.cpp b/media/libstagefright/rtsp/ARTPWriter.cpp
new file mode 100644
index 0000000..cc23856
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPWriter.cpp
@@ -0,0 +1,813 @@
+#include "ARTPWriter.h"
+
+#include <fcntl.h>
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <utils/ByteOrder.h>
+
+#define PT      97
+#define PT_STR  "97"
+
+namespace android {
+
+// static const size_t kMaxPacketSize = 65507;  // maximum payload in UDP over IP
+static const size_t kMaxPacketSize = 1500;
+
+static int UniformRand(int limit) {
+    return ((double)rand() * limit) / RAND_MAX;
+}
+
+ARTPWriter::ARTPWriter(int fd)
+    : mFlags(0),
+      mFd(fd),
+      mLooper(new ALooper),
+      mReflector(new AHandlerReflector<ARTPWriter>(this)) {
+    CHECK_GE(fd, 0);
+
+    mLooper->registerHandler(mReflector);
+    mLooper->start();
+
+    mSocket = socket(AF_INET, SOCK_DGRAM, 0);
+    CHECK_GE(mSocket, 0);
+
+    memset(mRTPAddr.sin_zero, 0, sizeof(mRTPAddr.sin_zero));
+    mRTPAddr.sin_family = AF_INET;
+
+#if 1
+    mRTPAddr.sin_addr.s_addr = INADDR_ANY;
+#else
+    mRTPAddr.sin_addr.s_addr = inet_addr("172.19.19.74");
+#endif
+
+    mRTPAddr.sin_port = htons(5634);
+    CHECK_EQ(0, ntohs(mRTPAddr.sin_port) & 1);
+
+    mRTCPAddr = mRTPAddr;
+    mRTCPAddr.sin_port = htons(ntohs(mRTPAddr.sin_port) | 1);
+
+#if LOG_TO_FILES
+    mRTPFd = open(
+            "/data/misc/rtpout.bin",
+            O_WRONLY | O_CREAT | O_TRUNC,
+            0644);
+    CHECK_GE(mRTPFd, 0);
+
+    mRTCPFd = open(
+            "/data/misc/rtcpout.bin",
+            O_WRONLY | O_CREAT | O_TRUNC,
+            0644);
+    CHECK_GE(mRTCPFd, 0);
+#endif
+}
+
+ARTPWriter::~ARTPWriter() {
+#if LOG_TO_FILES
+    close(mRTCPFd);
+    mRTCPFd = -1;
+
+    close(mRTPFd);
+    mRTPFd = -1;
+#endif
+
+    close(mSocket);
+    mSocket = -1;
+
+    close(mFd);
+    mFd = -1;
+}
+
+status_t ARTPWriter::addSource(const sp<MediaSource> &source) {
+    mSource = source;
+    return OK;
+}
+
+bool ARTPWriter::reachedEOS() {
+    Mutex::Autolock autoLock(mLock);
+    return (mFlags & kFlagEOS) != 0;
+}
+
+status_t ARTPWriter::start(MetaData *params) {
+    Mutex::Autolock autoLock(mLock);
+    if (mFlags & kFlagStarted) {
+        return INVALID_OPERATION;
+    }
+
+    mFlags &= ~kFlagEOS;
+    mSourceID = rand();
+    mSeqNo = UniformRand(65536);
+    mRTPTimeBase = rand();
+    mNumRTPSent = 0;
+    mNumRTPOctetsSent = 0;
+    mLastRTPTime = 0;
+    mLastNTPTime = 0;
+    mNumSRsSent = 0;
+
+    const char *mime;
+    CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
+
+    mMode = INVALID;
+    if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
+        mMode = H264;
+    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_H263)) {
+        mMode = H263;
+    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)) {
+        mMode = AMR_NB;
+    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
+        mMode = AMR_WB;
+    } else {
+        TRESPASS();
+    }
+
+    (new AMessage(kWhatStart, mReflector->id()))->post();
+
+    while (!(mFlags & kFlagStarted)) {
+        mCondition.wait(mLock);
+    }
+
+    return OK;
+}
+
+void ARTPWriter::stop() {
+    Mutex::Autolock autoLock(mLock);
+    if (!(mFlags & kFlagStarted)) {
+        return;
+    }
+
+    (new AMessage(kWhatStop, mReflector->id()))->post();
+
+    while (mFlags & kFlagStarted) {
+        mCondition.wait(mLock);
+    }
+}
+
+void ARTPWriter::pause() {
+}
+
+static void StripStartcode(MediaBuffer *buffer) {
+    if (buffer->range_length() < 4) {
+        return;
+    }
+
+    const uint8_t *ptr =
+        (const uint8_t *)buffer->data() + buffer->range_offset();
+
+    if (!memcmp(ptr, "\x00\x00\x00\x01", 4)) {
+        buffer->set_range(
+                buffer->range_offset() + 4, buffer->range_length() - 4);
+    }
+}
+
+void ARTPWriter::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatStart:
+        {
+            CHECK_EQ(mSource->start(), (status_t)OK);
+
+#if 0
+            if (mMode == H264) {
+                MediaBuffer *buffer;
+                CHECK_EQ(mSource->read(&buffer), (status_t)OK);
+
+                StripStartcode(buffer);
+                makeH264SPropParamSets(buffer);
+                buffer->release();
+                buffer = NULL;
+            }
+
+            dumpSessionDesc();
+#endif
+
+            {
+                Mutex::Autolock autoLock(mLock);
+                mFlags |= kFlagStarted;
+                mCondition.signal();
+            }
+
+            (new AMessage(kWhatRead, mReflector->id()))->post();
+            (new AMessage(kWhatSendSR, mReflector->id()))->post();
+            break;
+        }
+
+        case kWhatStop:
+        {
+            CHECK_EQ(mSource->stop(), (status_t)OK);
+
+            sendBye();
+
+            {
+                Mutex::Autolock autoLock(mLock);
+                mFlags &= ~kFlagStarted;
+                mCondition.signal();
+            }
+            break;
+        }
+
+        case kWhatRead:
+        {
+            {
+                Mutex::Autolock autoLock(mLock);
+                if (!(mFlags & kFlagStarted)) {
+                    break;
+                }
+            }
+
+            onRead(msg);
+            break;
+        }
+
+        case kWhatSendSR:
+        {
+            {
+                Mutex::Autolock autoLock(mLock);
+                if (!(mFlags & kFlagStarted)) {
+                    break;
+                }
+            }
+
+            onSendSR(msg);
+            break;
+        }
+
+        default:
+            TRESPASS();
+            break;
+    }
+}
+
+void ARTPWriter::onRead(const sp<AMessage> &msg) {
+    MediaBuffer *mediaBuf;
+    status_t err = mSource->read(&mediaBuf);
+
+    if (err != OK) {
+        LOG(INFO) << "reached EOS.";
+
+        Mutex::Autolock autoLock(mLock);
+        mFlags |= kFlagEOS;
+        return;
+    }
+
+    if (mediaBuf->range_length() > 0) {
+        LOG(VERBOSE) << "read buffer of size " << mediaBuf->range_length();
+
+        if (mMode == H264) {
+            StripStartcode(mediaBuf);
+            sendAVCData(mediaBuf);
+        } else if (mMode == H263) {
+            sendH263Data(mediaBuf);
+        } else if (mMode == AMR_NB || mMode == AMR_WB) {
+            sendAMRData(mediaBuf);
+        }
+    }
+
+    mediaBuf->release();
+    mediaBuf = NULL;
+
+    msg->post();
+}
+
+void ARTPWriter::onSendSR(const sp<AMessage> &msg) {
+    sp<ABuffer> buffer = new ABuffer(65536);
+    buffer->setRange(0, 0);
+
+    addSR(buffer);
+    addSDES(buffer);
+
+    send(buffer, true /* isRTCP */);
+
+    ++mNumSRsSent;
+    msg->post(3000000);
+}
+
+void ARTPWriter::send(const sp<ABuffer> &buffer, bool isRTCP) {
+    ssize_t n = sendto(
+            mSocket, buffer->data(), buffer->size(), 0,
+            (const struct sockaddr *)(isRTCP ? &mRTCPAddr : &mRTPAddr),
+            sizeof(mRTCPAddr));
+
+    CHECK_EQ(n, (ssize_t)buffer->size());
+
+#if LOG_TO_FILES
+    int fd = isRTCP ? mRTCPFd : mRTPFd;
+
+    uint32_t ms = tolel(ALooper::GetNowUs() / 1000ll);
+    uint32_t length = tolel(buffer->size());
+    write(fd, &ms, sizeof(ms));
+    write(fd, &length, sizeof(length));
+    write(fd, buffer->data(), buffer->size());
+#endif
+}
+
+void ARTPWriter::addSR(const sp<ABuffer> &buffer) {
+    uint8_t *data = buffer->data() + buffer->size();
+
+    data[0] = 0x80 | 0;
+    data[1] = 200;  // SR
+    data[2] = 0;
+    data[3] = 6;
+    data[4] = mSourceID >> 24;
+    data[5] = (mSourceID >> 16) & 0xff;
+    data[6] = (mSourceID >> 8) & 0xff;
+    data[7] = mSourceID & 0xff;
+
+    data[8] = mLastNTPTime >> (64 - 8);
+    data[9] = (mLastNTPTime >> (64 - 16)) & 0xff;
+    data[10] = (mLastNTPTime >> (64 - 24)) & 0xff;
+    data[11] = (mLastNTPTime >> 32) & 0xff;
+    data[12] = (mLastNTPTime >> 24) & 0xff;
+    data[13] = (mLastNTPTime >> 16) & 0xff;
+    data[14] = (mLastNTPTime >> 8) & 0xff;
+    data[15] = mLastNTPTime & 0xff;
+
+    data[16] = (mLastRTPTime >> 24) & 0xff;
+    data[17] = (mLastRTPTime >> 16) & 0xff;
+    data[18] = (mLastRTPTime >> 8) & 0xff;
+    data[19] = mLastRTPTime & 0xff;
+
+    data[20] = mNumRTPSent >> 24;
+    data[21] = (mNumRTPSent >> 16) & 0xff;
+    data[22] = (mNumRTPSent >> 8) & 0xff;
+    data[23] = mNumRTPSent & 0xff;
+
+    data[24] = mNumRTPOctetsSent >> 24;
+    data[25] = (mNumRTPOctetsSent >> 16) & 0xff;
+    data[26] = (mNumRTPOctetsSent >> 8) & 0xff;
+    data[27] = mNumRTPOctetsSent & 0xff;
+
+    buffer->setRange(buffer->offset(), buffer->size() + 28);
+}
+
+void ARTPWriter::addSDES(const sp<ABuffer> &buffer) {
+    uint8_t *data = buffer->data() + buffer->size();
+    data[0] = 0x80 | 1;
+    data[1] = 202;  // SDES
+    data[4] = mSourceID >> 24;
+    data[5] = (mSourceID >> 16) & 0xff;
+    data[6] = (mSourceID >> 8) & 0xff;
+    data[7] = mSourceID & 0xff;
+
+    size_t offset = 8;
+
+    data[offset++] = 1;  // CNAME
+
+    static const char *kCNAME = "someone@somewhere";
+    data[offset++] = strlen(kCNAME);
+
+    memcpy(&data[offset], kCNAME, strlen(kCNAME));
+    offset += strlen(kCNAME);
+
+    data[offset++] = 7;  // NOTE
+
+    static const char *kNOTE = "Hell's frozen over.";
+    data[offset++] = strlen(kNOTE);
+
+    memcpy(&data[offset], kNOTE, strlen(kNOTE));
+    offset += strlen(kNOTE);
+
+    data[offset++] = 0;
+
+    if ((offset % 4) > 0) {
+        size_t count = 4 - (offset % 4);
+        switch (count) {
+            case 3:
+                data[offset++] = 0;
+            case 2:
+                data[offset++] = 0;
+            case 1:
+                data[offset++] = 0;
+        }
+    }
+
+    size_t numWords = (offset / 4) - 1;
+    data[2] = numWords >> 8;
+    data[3] = numWords & 0xff;
+
+    buffer->setRange(buffer->offset(), buffer->size() + offset);
+}
+
+// static
+uint64_t ARTPWriter::GetNowNTP() {
+    uint64_t nowUs = ALooper::GetNowUs();
+
+    nowUs += ((70ll * 365 + 17) * 24) * 60 * 60 * 1000000ll;
+
+    uint64_t hi = nowUs / 1000000ll;
+    uint64_t lo = ((1ll << 32) * (nowUs % 1000000ll)) / 1000000ll;
+
+    return (hi << 32) | lo;
+}
+
+void ARTPWriter::dumpSessionDesc() {
+    AString sdp;
+    sdp = "v=0\r\n";
+
+    sdp.append("o=- ");
+
+    uint64_t ntp = GetNowNTP();
+    sdp.append(ntp);
+    sdp.append(" ");
+    sdp.append(ntp);
+    sdp.append(" IN IP4 127.0.0.0\r\n");
+
+    sdp.append(
+          "s=Sample\r\n"
+          "i=Playing around\r\n"
+          "c=IN IP4 ");
+
+    struct in_addr addr;
+    addr.s_addr = ntohl(INADDR_LOOPBACK);
+
+    sdp.append(inet_ntoa(addr));
+
+    sdp.append(
+          "\r\n"
+          "t=0 0\r\n"
+          "a=range:npt=now-\r\n");
+
+    sp<MetaData> meta = mSource->getFormat();
+
+    if (mMode == H264 || mMode == H263) {
+        sdp.append("m=video ");
+    } else {
+        sdp.append("m=audio ");
+    }
+
+    sdp.append(StringPrintf("%d", ntohs(mRTPAddr.sin_port)));
+    sdp.append(
+          " RTP/AVP " PT_STR "\r\n"
+          "b=AS 320000\r\n"
+          "a=rtpmap:" PT_STR " ");
+
+    if (mMode == H264) {
+        sdp.append("H264/90000");
+    } else if (mMode == H263) {
+        sdp.append("H263-1998/90000");
+    } else if (mMode == AMR_NB || mMode == AMR_WB) {
+        int32_t sampleRate, numChannels;
+        CHECK(mSource->getFormat()->findInt32(kKeySampleRate, &sampleRate));
+        CHECK(mSource->getFormat()->findInt32(kKeyChannelCount, &numChannels));
+
+        CHECK_EQ(numChannels, 1);
+        CHECK_EQ(sampleRate, (mMode == AMR_NB) ? 8000 : 16000);
+
+        sdp.append(mMode == AMR_NB ? "AMR" : "AMR-WB");
+        sdp.append(StringPrintf("/%d/%d", sampleRate, numChannels));
+    } else {
+        TRESPASS();
+    }
+
+    sdp.append("\r\n");
+
+    if (mMode == H264 || mMode == H263) {
+        int32_t width, height;
+        CHECK(meta->findInt32(kKeyWidth, &width));
+        CHECK(meta->findInt32(kKeyHeight, &height));
+
+        sdp.append("a=cliprect 0,0,");
+        sdp.append(height);
+        sdp.append(",");
+        sdp.append(width);
+        sdp.append("\r\n");
+
+        sdp.append(
+              "a=framesize:" PT_STR " ");
+        sdp.append(width);
+        sdp.append("-");
+        sdp.append(height);
+        sdp.append("\r\n");
+    }
+
+    if (mMode == H264) {
+        sdp.append(
+              "a=fmtp:" PT_STR " profile-level-id=");
+        sdp.append(mProfileLevel);
+        sdp.append(";sprop-parameter-sets=");
+
+        sdp.append(mSeqParamSet);
+        sdp.append(",");
+        sdp.append(mPicParamSet);
+        sdp.append(";packetization-mode=1\r\n");
+    } else if (mMode == AMR_NB || mMode == AMR_WB) {
+        sdp.append("a=fmtp:" PT_STR " octed-align\r\n");
+    }
+
+    LOG(INFO) << sdp;
+}
+
+void ARTPWriter::makeH264SPropParamSets(MediaBuffer *buffer) {
+    static const char kStartCode[] = "\x00\x00\x00\x01";
+
+    const uint8_t *data =
+        (const uint8_t *)buffer->data() + buffer->range_offset();
+    size_t size = buffer->range_length();
+
+    CHECK_GE(size, 0u);
+
+    size_t startCodePos = 0;
+    while (startCodePos + 3 < size
+            && memcmp(kStartCode, &data[startCodePos], 4)) {
+        ++startCodePos;
+    }
+
+    CHECK_LT(startCodePos + 3, size);
+
+    CHECK_EQ((unsigned)data[0], 0x67u);
+
+    mProfileLevel =
+        StringPrintf("%02X%02X%02X", data[1], data[2], data[3]);
+
+    encodeBase64(data, startCodePos, &mSeqParamSet);
+
+    encodeBase64(&data[startCodePos + 4], size - startCodePos - 4,
+                 &mPicParamSet);
+}
+
+void ARTPWriter::sendBye() {
+    sp<ABuffer> buffer = new ABuffer(8);
+    uint8_t *data = buffer->data();
+    *data++ = (2 << 6) | 1;
+    *data++ = 203;
+    *data++ = 0;
+    *data++ = 1;
+    *data++ = mSourceID >> 24;
+    *data++ = (mSourceID >> 16) & 0xff;
+    *data++ = (mSourceID >> 8) & 0xff;
+    *data++ = mSourceID & 0xff;
+    buffer->setRange(0, 8);
+
+    send(buffer, true /* isRTCP */);
+}
+
+void ARTPWriter::sendAVCData(MediaBuffer *mediaBuf) {
+    // 12 bytes RTP header + 2 bytes for the FU-indicator and FU-header.
+    CHECK_GE(kMaxPacketSize, 12u + 2u);
+
+    int64_t timeUs;
+    CHECK(mediaBuf->meta_data()->findInt64(kKeyTime, &timeUs));
+
+    uint32_t rtpTime = mRTPTimeBase + (timeUs * 9 / 100ll);
+
+    const uint8_t *mediaData =
+        (const uint8_t *)mediaBuf->data() + mediaBuf->range_offset();
+
+    sp<ABuffer> buffer = new ABuffer(kMaxPacketSize);
+    if (mediaBuf->range_length() + 12 <= buffer->capacity()) {
+        // The data fits into a single packet
+        uint8_t *data = buffer->data();
+        data[0] = 0x80;
+        data[1] = (1 << 7) | PT;  // M-bit
+        data[2] = (mSeqNo >> 8) & 0xff;
+        data[3] = mSeqNo & 0xff;
+        data[4] = rtpTime >> 24;
+        data[5] = (rtpTime >> 16) & 0xff;
+        data[6] = (rtpTime >> 8) & 0xff;
+        data[7] = rtpTime & 0xff;
+        data[8] = mSourceID >> 24;
+        data[9] = (mSourceID >> 16) & 0xff;
+        data[10] = (mSourceID >> 8) & 0xff;
+        data[11] = mSourceID & 0xff;
+
+        memcpy(&data[12],
+               mediaData, mediaBuf->range_length());
+
+        buffer->setRange(0, mediaBuf->range_length() + 12);
+
+        send(buffer, false /* isRTCP */);
+
+        ++mSeqNo;
+        ++mNumRTPSent;
+        mNumRTPOctetsSent += buffer->size() - 12;
+    } else {
+        // FU-A
+
+        unsigned nalType = mediaData[0];
+        size_t offset = 1;
+
+        bool firstPacket = true;
+        while (offset < mediaBuf->range_length()) {
+            size_t size = mediaBuf->range_length() - offset;
+            bool lastPacket = true;
+            if (size + 12 + 2 > buffer->capacity()) {
+                lastPacket = false;
+                size = buffer->capacity() - 12 - 2;
+            }
+
+            uint8_t *data = buffer->data();
+            data[0] = 0x80;
+            data[1] = (lastPacket ? (1 << 7) : 0x00) | PT;  // M-bit
+            data[2] = (mSeqNo >> 8) & 0xff;
+            data[3] = mSeqNo & 0xff;
+            data[4] = rtpTime >> 24;
+            data[5] = (rtpTime >> 16) & 0xff;
+            data[6] = (rtpTime >> 8) & 0xff;
+            data[7] = rtpTime & 0xff;
+            data[8] = mSourceID >> 24;
+            data[9] = (mSourceID >> 16) & 0xff;
+            data[10] = (mSourceID >> 8) & 0xff;
+            data[11] = mSourceID & 0xff;
+
+            data[12] = 28 | (nalType & 0xe0);
+
+            CHECK(!firstPacket || !lastPacket);
+
+            data[13] =
+                (firstPacket ? 0x80 : 0x00)
+                | (lastPacket ? 0x40 : 0x00)
+                | (nalType & 0x1f);
+
+            memcpy(&data[14], &mediaData[offset], size);
+
+            buffer->setRange(0, 14 + size);
+
+            send(buffer, false /* isRTCP */);
+
+            ++mSeqNo;
+            ++mNumRTPSent;
+            mNumRTPOctetsSent += buffer->size() - 12;
+
+            firstPacket = false;
+            offset += size;
+        }
+    }
+
+    mLastRTPTime = rtpTime;
+    mLastNTPTime = GetNowNTP();
+}
+
+void ARTPWriter::sendH263Data(MediaBuffer *mediaBuf) {
+    CHECK_GE(kMaxPacketSize, 12u + 2u);
+
+    int64_t timeUs;
+    CHECK(mediaBuf->meta_data()->findInt64(kKeyTime, &timeUs));
+
+    uint32_t rtpTime = mRTPTimeBase + (timeUs * 9 / 100ll);
+
+    const uint8_t *mediaData =
+        (const uint8_t *)mediaBuf->data() + mediaBuf->range_offset();
+
+    // hexdump(mediaData, mediaBuf->range_length());
+
+    CHECK_EQ((unsigned)mediaData[0], 0u);
+    CHECK_EQ((unsigned)mediaData[1], 0u);
+
+    size_t offset = 2;
+    size_t size = mediaBuf->range_length();
+
+    while (offset < size) {
+        sp<ABuffer> buffer = new ABuffer(kMaxPacketSize);
+        // CHECK_LE(mediaBuf->range_length() -2 + 14, buffer->capacity());
+
+        size_t remaining = size - offset;
+        bool lastPacket = (remaining + 14 <= buffer->capacity());
+        if (!lastPacket) {
+            remaining = buffer->capacity() - 14;
+        }
+
+        uint8_t *data = buffer->data();
+        data[0] = 0x80;
+        data[1] = (lastPacket ? 0x80 : 0x00) | PT;  // M-bit
+        data[2] = (mSeqNo >> 8) & 0xff;
+        data[3] = mSeqNo & 0xff;
+        data[4] = rtpTime >> 24;
+        data[5] = (rtpTime >> 16) & 0xff;
+        data[6] = (rtpTime >> 8) & 0xff;
+        data[7] = rtpTime & 0xff;
+        data[8] = mSourceID >> 24;
+        data[9] = (mSourceID >> 16) & 0xff;
+        data[10] = (mSourceID >> 8) & 0xff;
+        data[11] = mSourceID & 0xff;
+
+        data[12] = (offset == 2) ? 0x04 : 0x00;  // P=?, V=0
+        data[13] = 0x00;  // PLEN = PEBIT = 0
+
+        memcpy(&data[14], &mediaData[offset], remaining);
+        offset += remaining;
+
+        buffer->setRange(0, remaining + 14);
+
+        send(buffer, false /* isRTCP */);
+
+        ++mSeqNo;
+        ++mNumRTPSent;
+        mNumRTPOctetsSent += buffer->size() - 12;
+    }
+
+    mLastRTPTime = rtpTime;
+    mLastNTPTime = GetNowNTP();
+}
+
+static size_t getFrameSize(bool isWide, unsigned FT) {
+    static const size_t kFrameSizeNB[8] = {
+        95, 103, 118, 134, 148, 159, 204, 244
+    };
+    static const size_t kFrameSizeWB[9] = {
+        132, 177, 253, 285, 317, 365, 397, 461, 477
+    };
+
+    size_t frameSize = isWide ? kFrameSizeWB[FT] : kFrameSizeNB[FT];
+
+    // Round up bits to bytes and add 1 for the header byte.
+    frameSize = (frameSize + 7) / 8 + 1;
+
+    return frameSize;
+}
+
+void ARTPWriter::sendAMRData(MediaBuffer *mediaBuf) {
+    const uint8_t *mediaData =
+        (const uint8_t *)mediaBuf->data() + mediaBuf->range_offset();
+
+    size_t mediaLength = mediaBuf->range_length();
+
+    CHECK_GE(kMaxPacketSize, 12u + 1u + mediaLength);
+
+    const bool isWide = (mMode == AMR_WB);
+
+    int64_t timeUs;
+    CHECK(mediaBuf->meta_data()->findInt64(kKeyTime, &timeUs));
+    uint32_t rtpTime = mRTPTimeBase + (timeUs / (isWide ? 250 : 125));
+
+    // hexdump(mediaData, mediaLength);
+
+    Vector<uint8_t> tableOfContents;
+    size_t srcOffset = 0;
+    while (srcOffset < mediaLength) {
+        uint8_t toc = mediaData[srcOffset];
+
+        unsigned FT = (toc >> 3) & 0x0f;
+        CHECK((isWide && FT <= 8) || (!isWide && FT <= 7));
+
+        tableOfContents.push(toc);
+        srcOffset += getFrameSize(isWide, FT);
+    }
+    CHECK_EQ(srcOffset, mediaLength);
+
+    sp<ABuffer> buffer = new ABuffer(kMaxPacketSize);
+    CHECK_LE(mediaLength + 12 + 1, buffer->capacity());
+
+    // The data fits into a single packet
+    uint8_t *data = buffer->data();
+    data[0] = 0x80;
+    data[1] = PT;
+    if (mNumRTPSent == 0) {
+        // Signal start of talk-spurt.
+        data[1] |= 0x80;  // M-bit
+    }
+    data[2] = (mSeqNo >> 8) & 0xff;
+    data[3] = mSeqNo & 0xff;
+    data[4] = rtpTime >> 24;
+    data[5] = (rtpTime >> 16) & 0xff;
+    data[6] = (rtpTime >> 8) & 0xff;
+    data[7] = rtpTime & 0xff;
+    data[8] = mSourceID >> 24;
+    data[9] = (mSourceID >> 16) & 0xff;
+    data[10] = (mSourceID >> 8) & 0xff;
+    data[11] = mSourceID & 0xff;
+
+    data[12] = 0xf0;  // CMR=15, RR=0
+
+    size_t dstOffset = 13;
+
+    for (size_t i = 0; i < tableOfContents.size(); ++i) {
+        uint8_t toc = tableOfContents[i];
+
+        if (i + 1 < tableOfContents.size()) {
+            toc |= 0x80;
+        } else {
+            toc &= ~0x80;
+        }
+
+        data[dstOffset++] = toc;
+    }
+
+    srcOffset = 0;
+    for (size_t i = 0; i < tableOfContents.size(); ++i) {
+        uint8_t toc = tableOfContents[i];
+        unsigned FT = (toc >> 3) & 0x0f;
+        size_t frameSize = getFrameSize(isWide, FT);
+
+        ++srcOffset;  // skip toc
+        memcpy(&data[dstOffset], &mediaData[srcOffset], frameSize - 1);
+        srcOffset += frameSize - 1;
+        dstOffset += frameSize - 1;
+    }
+
+    buffer->setRange(0, dstOffset);
+
+    send(buffer, false /* isRTCP */);
+
+    ++mSeqNo;
+    ++mNumRTPSent;
+    mNumRTPOctetsSent += buffer->size() - 12;
+
+    mLastRTPTime = rtpTime;
+    mLastNTPTime = GetNowNTP();
+}
+
+}  // namespace android
+
diff --git a/media/libstagefright/rtsp/ARTPWriter.h b/media/libstagefright/rtsp/ARTPWriter.h
new file mode 100644
index 0000000..b1b8b45
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPWriter.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2010 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 A_RTP_WRITER_H_
+
+#define A_RTP_WRITER_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AHandlerReflector.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/foundation/base64.h>
+#include <media/stagefright/MediaWriter.h>
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#define LOG_TO_FILES    0
+
+namespace android {
+
+struct ABuffer;
+struct MediaBuffer;
+
+struct ARTPWriter : public MediaWriter {
+    ARTPWriter(int fd);
+
+    virtual status_t addSource(const sp<MediaSource> &source);
+    virtual bool reachedEOS();
+    virtual status_t start(MetaData *params);
+    virtual void stop();
+    virtual void pause();
+
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
+protected:
+    virtual ~ARTPWriter();
+
+private:
+    enum {
+        kWhatStart  = 'strt',
+        kWhatStop   = 'stop',
+        kWhatRead   = 'read',
+        kWhatSendSR = 'sr  ',
+    };
+
+    enum {
+        kFlagStarted  = 1,
+        kFlagEOS      = 2,
+    };
+
+    Mutex mLock;
+    Condition mCondition;
+    uint32_t mFlags;
+
+    int mFd;
+
+#if LOG_TO_FILES
+    int mRTPFd;
+    int mRTCPFd;
+#endif
+
+    sp<MediaSource> mSource;
+    sp<ALooper> mLooper;
+    sp<AHandlerReflector<ARTPWriter> > mReflector;
+
+    int mSocket;
+    struct sockaddr_in mRTPAddr;
+    struct sockaddr_in mRTCPAddr;
+
+    AString mProfileLevel;
+    AString mSeqParamSet;
+    AString mPicParamSet;
+
+    uint32_t mSourceID;
+    uint32_t mSeqNo;
+    uint32_t mRTPTimeBase;
+    uint32_t mNumRTPSent;
+    uint32_t mNumRTPOctetsSent;
+    uint32_t mLastRTPTime;
+    uint64_t mLastNTPTime;
+
+    int32_t mNumSRsSent;
+
+    enum {
+        INVALID,
+        H264,
+        H263,
+        AMR_NB,
+        AMR_WB,
+    } mMode;
+
+    static uint64_t GetNowNTP();
+
+    void onRead(const sp<AMessage> &msg);
+    void onSendSR(const sp<AMessage> &msg);
+
+    void addSR(const sp<ABuffer> &buffer);
+    void addSDES(const sp<ABuffer> &buffer);
+
+    void makeH264SPropParamSets(MediaBuffer *buffer);
+    void dumpSessionDesc();
+
+    void sendBye();
+    void sendAVCData(MediaBuffer *mediaBuf);
+    void sendH263Data(MediaBuffer *mediaBuf);
+    void sendAMRData(MediaBuffer *mediaBuf);
+
+    void send(const sp<ABuffer> &buffer, bool isRTCP);
+
+    DISALLOW_EVIL_CONSTRUCTORS(ARTPWriter);
+};
+
+}  // namespace android
+
+#endif  // A_RTP_WRITER_H_
diff --git a/media/libstagefright/rtsp/ASessionDescription.cpp b/media/libstagefright/rtsp/ASessionDescription.cpp
index 25b9ce2..8187e0c 100644
--- a/media/libstagefright/rtsp/ASessionDescription.cpp
+++ b/media/libstagefright/rtsp/ASessionDescription.cpp
@@ -49,7 +49,7 @@
     mFormats.push(AString("[root]"));
 
     AString desc((const char *)data, size);
-    LOG(VERBOSE) << desc;
+    LOG(INFO) << desc;
 
     size_t i = 0;
     for (;;) {
@@ -116,6 +116,24 @@
                 mFormats.push(AString(line, 2, line.size() - 2));
                 break;
             }
+
+            default:
+            {
+                AString key, value;
+
+                ssize_t equalPos = line.find("=");
+
+                key = AString(line, 0, equalPos + 1);
+                value = AString(line, equalPos + 1, line.size() - equalPos - 1);
+
+                key.trim();
+                value.trim();
+
+                LOG(VERBOSE) << "adding '" << key << "' => '" << value << "'";
+
+                mTracks.editItemAt(mTracks.size() - 1).add(key, value);
+                break;
+            }
         }
 
         i = eolPos + 2;
diff --git a/media/libstagefright/rtsp/Android.mk b/media/libstagefright/rtsp/Android.mk
index 4608fa0..7f3659f 100644
--- a/media/libstagefright/rtsp/Android.mk
+++ b/media/libstagefright/rtsp/Android.mk
@@ -3,15 +3,20 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:=       \
-        ARTSPController.cpp         \
+        AAMRAssembler.cpp           \
         AAVCAssembler.cpp           \
+        AH263Assembler.cpp          \
         AMPEG4AudioAssembler.cpp    \
         APacketSource.cpp           \
         ARTPAssembler.cpp           \
         ARTPConnection.cpp          \
+        ARTPSession.cpp             \
         ARTPSource.cpp              \
+        ARTPWriter.cpp              \
         ARTSPConnection.cpp         \
+        ARTSPController.cpp         \
         ASessionDescription.cpp     \
+        UDPPusher.cpp               \
 
 LOCAL_C_INCLUDES:= \
 	$(JNI_H_INCLUDE) \
@@ -26,3 +31,28 @@
 
 include $(BUILD_STATIC_LIBRARY)
 
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:=         \
+        rtp_test.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+	libstagefright liblog libutils libbinder libstagefright_foundation
+
+LOCAL_STATIC_LIBRARIES := \
+        libstagefright_rtsp
+
+LOCAL_C_INCLUDES:= \
+	$(JNI_H_INCLUDE) \
+	frameworks/base/media/libstagefright \
+	$(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include
+
+LOCAL_CFLAGS += -Wno-multichar
+
+LOCAL_MODULE_TAGS := debug
+
+LOCAL_MODULE:= rtp_test
+
+include $(BUILD_EXECUTABLE)
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
index 15901cd..8be8914 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -38,9 +38,7 @@
           mConn(new ARTSPConnection),
           mRTPConn(new ARTPConnection),
           mSessionURL(url),
-          mSetupTracksSuccessful(false),
-          mFirstAccessUnit(true),
-          mFirstAccessUnitNTP(-1) {
+          mSetupTracksSuccessful(false) {
 
         mNetLooper->start(false /* runOnCallingThread */,
                           false /* canCallJava */,
@@ -161,8 +159,11 @@
                 size_t index;
                 CHECK(msg->findSize("index", &index));
 
+                TrackInfo *track = NULL;
                 size_t trackIndex;
-                CHECK(msg->findSize("track-index", &trackIndex));
+                if (msg->findSize("track-index", &trackIndex)) {
+                    track = &mTracks.editItemAt(trackIndex);
+                }
 
                 int32_t result;
                 CHECK(msg->findInt32("result", &result));
@@ -170,9 +171,16 @@
                 LOG(INFO) << "SETUP(" << index << ") completed with result "
                      << result << " (" << strerror(-result) << ")";
 
-                TrackInfo *track = &mTracks.editItemAt(trackIndex);
+                if (result != OK) {
+                    if (track) {
+                        close(track->mRTPSocket);
+                        close(track->mRTCPSocket);
 
-                if (result == OK) {
+                        mTracks.removeItemsAt(trackIndex);
+                    }
+                } else {
+                    CHECK(track != NULL);
+
                     sp<RefBase> obj;
                     CHECK(msg->findObject("response", &obj));
                     sp<ARTSPResponse> response =
@@ -200,24 +208,13 @@
                             mSessionDesc, index,
                             notify);
 
-                    track->mPacketSource =
-                        new APacketSource(mSessionDesc, index);
-
                     mSetupTracksSuccessful = true;
-
-                    ++index;
-                    if (index < mSessionDesc->countTracks()) {
-                        setupTrack(index);
-                        break;
-                    }
-                } else {
-                    close(track->mRTPSocket);
-                    close(track->mRTCPSocket);
-
-                    mTracks.removeItemsAt(mTracks.size() - 1);
                 }
 
-                if (mSetupTracksSuccessful) {
+                ++index;
+                if (index < mSessionDesc->countTracks()) {
+                    setupTrack(index);
+                } else if (mSetupTracksSuccessful) {
                     AString request = "PLAY ";
                     request.append(mSessionURL);
                     request.append(" RTSP/1.0\r\n");
@@ -321,16 +318,6 @@
                 CHECK(accessUnit->meta()->findInt64(
                             "ntp-time", (int64_t *)&ntpTime));
 
-                if (mFirstAccessUnit) {
-                    mFirstAccessUnit = false;
-                    mFirstAccessUnitNTP = ntpTime;
-                }
-                if (ntpTime > mFirstAccessUnitNTP) {
-                    ntpTime -= mFirstAccessUnitNTP;
-                } else {
-                    ntpTime = 0;
-                }
-
                 accessUnit->meta()->setInt64("ntp-time", ntpTime);
 
 #if 0
@@ -374,8 +361,6 @@
     AString mBaseURL;
     AString mSessionID;
     bool mSetupTracksSuccessful;
-    bool mFirstAccessUnit;
-    uint64_t mFirstAccessUnitNTP;
 
     struct TrackInfo {
         int mRTPSocket;
@@ -386,6 +371,19 @@
     Vector<TrackInfo> mTracks;
 
     void setupTrack(size_t index) {
+        sp<APacketSource> source =
+            new APacketSource(mSessionDesc, index);
+        if (source->initCheck() != OK) {
+            LOG(WARNING) << "Unsupported format. Ignoring track #"
+                         << index << ".";
+
+            sp<AMessage> reply = new AMessage('setu', id());
+            reply->setSize("index", index);
+            reply->setInt32("result", ERROR_UNSUPPORTED);
+            reply->post();
+            return;
+        }
+
         AString url;
         CHECK(mSessionDesc->findAttribute(index, "a=control", &url));
 
@@ -394,6 +392,7 @@
 
         mTracks.push(TrackInfo());
         TrackInfo *info = &mTracks.editItemAt(mTracks.size() - 1);
+        info->mPacketSource = source;
 
         unsigned rtpPort;
         ARTPConnection::MakePortPair(
diff --git a/media/libstagefright/rtsp/UDPPusher.cpp b/media/libstagefright/rtsp/UDPPusher.cpp
new file mode 100644
index 0000000..28a343f
--- /dev/null
+++ b/media/libstagefright/rtsp/UDPPusher.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2010 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 "UDPPusher.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <utils/ByteOrder.h>
+
+#include <sys/socket.h>
+
+namespace android {
+
+UDPPusher::UDPPusher(const char *filename, unsigned port)
+    : mFile(fopen(filename, "rb")),
+      mFirstTimeMs(0),
+      mFirstTimeUs(0) {
+    CHECK(mFile != NULL);
+
+    mSocket = socket(AF_INET, SOCK_DGRAM, 0);
+
+    struct sockaddr_in addr;
+    memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
+    addr.sin_family = AF_INET;
+    addr.sin_addr.s_addr = INADDR_ANY;
+    addr.sin_port = 0;
+
+    CHECK_EQ(0, bind(mSocket, (const struct sockaddr *)&addr, sizeof(addr)));
+
+    memset(mRemoteAddr.sin_zero, 0, sizeof(mRemoteAddr.sin_zero));
+    mRemoteAddr.sin_family = AF_INET;
+    mRemoteAddr.sin_addr.s_addr = INADDR_ANY;
+    mRemoteAddr.sin_port = htons(port);
+}
+
+UDPPusher::~UDPPusher() {
+    close(mSocket);
+    mSocket = -1;
+
+    fclose(mFile);
+    mFile = NULL;
+}
+
+void UDPPusher::start() {
+    uint32_t timeMs;
+    CHECK_EQ(fread(&timeMs, 1, sizeof(timeMs), mFile), sizeof(timeMs));
+    mFirstTimeMs = fromlel(timeMs);
+    mFirstTimeUs = ALooper::GetNowUs();
+
+    (new AMessage(kWhatPush, id()))->post();
+}
+
+bool UDPPusher::onPush() {
+    uint32_t length;
+    if (fread(&length, 1, sizeof(length), mFile) < sizeof(length)) {
+        LOG(INFO) << "No more data to push.";
+        return false;
+    }
+
+    length = fromlel(length);
+
+    CHECK_GT(length, 0u);
+
+    sp<ABuffer> buffer = new ABuffer(length);
+    if (fread(buffer->data(), 1, length, mFile) < length) {
+        LOG(ERROR) << "File truncated?.";
+        return false;
+    }
+
+    ssize_t n = sendto(
+            mSocket, buffer->data(), buffer->size(), 0,
+            (const struct sockaddr *)&mRemoteAddr, sizeof(mRemoteAddr));
+
+    CHECK_EQ(n, (ssize_t)buffer->size());
+
+    uint32_t timeMs;
+    if (fread(&timeMs, 1, sizeof(timeMs), mFile) < sizeof(timeMs)) {
+        LOG(INFO) << "No more data to push.";
+        return false;
+    }
+
+    timeMs = fromlel(timeMs);
+    CHECK_GE(timeMs, mFirstTimeMs);
+
+    timeMs -= mFirstTimeMs;
+    int64_t whenUs = mFirstTimeUs + timeMs * 1000ll;
+    int64_t nowUs = ALooper::GetNowUs();
+    (new AMessage(kWhatPush, id()))->post(whenUs - nowUs);
+
+    return true;
+}
+
+void UDPPusher::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatPush:
+        {
+            if (!onPush() && !(ntohs(mRemoteAddr.sin_port) & 1)) {
+                LOG(INFO) << "emulating BYE packet";
+
+                sp<ABuffer> buffer = new ABuffer(8);
+                uint8_t *data = buffer->data();
+                *data++ = (2 << 6) | 1;
+                *data++ = 203;
+                *data++ = 0;
+                *data++ = 1;
+                *data++ = 0x8f;
+                *data++ = 0x49;
+                *data++ = 0xc0;
+                *data++ = 0xd0;
+                buffer->setRange(0, 8);
+
+                struct sockaddr_in tmp = mRemoteAddr;
+                tmp.sin_port = htons(ntohs(mRemoteAddr.sin_port) | 1);
+
+                ssize_t n = sendto(
+                        mSocket, buffer->data(), buffer->size(), 0,
+                        (const struct sockaddr *)&tmp,
+                        sizeof(tmp));
+
+                CHECK_EQ(n, (ssize_t)buffer->size());
+            }
+            break;
+        }
+
+        default:
+            TRESPASS();
+            break;
+    }
+}
+
+}  // namespace android
+
diff --git a/media/libstagefright/rtsp/UDPPusher.h b/media/libstagefright/rtsp/UDPPusher.h
new file mode 100644
index 0000000..2bde533
--- /dev/null
+++ b/media/libstagefright/rtsp/UDPPusher.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 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 UDP_PUSHER_H_
+
+#define UDP_PUSHER_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+#include <stdio.h>
+#include <arpa/inet.h>
+
+namespace android {
+
+struct UDPPusher : public AHandler {
+    UDPPusher(const char *filename, unsigned port);
+
+    void start();
+
+protected:
+    virtual ~UDPPusher();
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+    enum {
+        kWhatPush = 'push'
+    };
+
+    FILE *mFile;
+    int mSocket;
+    struct sockaddr_in mRemoteAddr;
+
+    uint32_t mFirstTimeMs;
+    int64_t mFirstTimeUs;
+
+    bool onPush();
+
+    DISALLOW_EVIL_CONSTRUCTORS(UDPPusher);
+};
+
+}  // namespace android
+
+#endif  // UDP_PUSHER_H_
diff --git a/media/libstagefright/rtsp/rtp_test.cpp b/media/libstagefright/rtsp/rtp_test.cpp
new file mode 100644
index 0000000..cec6c0c
--- /dev/null
+++ b/media/libstagefright/rtsp/rtp_test.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2010 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 <binder/ProcessState.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXClient.h>
+#include <media/stagefright/OMXCodec.h>
+#include <media/stagefright/foundation/base64.h>
+
+#include "ARTPSession.h"
+#include "ASessionDescription.h"
+#include "UDPPusher.h"
+
+using namespace android;
+
+int main(int argc, char **argv) {
+    android::ProcessState::self()->startThreadPool();
+
+    DataSource::RegisterDefaultSniffers();
+
+    const char *rtpFilename = NULL;
+    const char *rtcpFilename = NULL;
+
+    if (argc == 3) {
+        rtpFilename = argv[1];
+        rtcpFilename = argv[2];
+    } else if (argc != 1) {
+        fprintf(stderr, "usage: %s [ rtpFilename rtcpFilename ]\n", argv[0]);
+        return 1;
+    }
+
+#if 0
+    static const uint8_t kSPS[] = {
+        0x67, 0x42, 0x80, 0x0a, 0xe9, 0x02, 0x83, 0xe4, 0x20, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x0e, 0xa6, 0x00, 0x80
+    };
+    static const uint8_t kPPS[] = {
+        0x68, 0xce, 0x3c, 0x80
+    };
+    AString out1, out2;
+    encodeBase64(kSPS, sizeof(kSPS), &out1);
+    encodeBase64(kPPS, sizeof(kPPS), &out2);
+    printf("params=%s,%s\n", out1.c_str(), out2.c_str());
+#endif
+
+    sp<ALooper> looper = new ALooper;
+
+    sp<UDPPusher> rtp_pusher;
+    sp<UDPPusher> rtcp_pusher;
+
+    if (rtpFilename != NULL) {
+        rtp_pusher = new UDPPusher(rtpFilename, 5434);
+        looper->registerHandler(rtp_pusher);
+
+        rtcp_pusher = new UDPPusher(rtcpFilename, 5435);
+        looper->registerHandler(rtcp_pusher);
+    }
+
+    sp<ARTPSession> session = new ARTPSession;
+    looper->registerHandler(session);
+
+#if 0
+    // My H264 SDP
+    static const char *raw =
+        "v=0\r\n"
+        "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+        "s=QuickTime\r\n"
+        "t=0 0\r\n"
+        "a=range:npt=0-315\r\n"
+        "a=isma-compliance:2,2.0,2\r\n"
+        "m=video 5434 RTP/AVP 97\r\n"
+        "c=IN IP4 127.0.0.1\r\n"
+        "b=AS:30\r\n"
+        "a=rtpmap:97 H264/90000\r\n"
+        "a=fmtp:97 packetization-mode=1;profile-level-id=42000C;"
+          "sprop-parameter-sets=Z0IADJZUCg+I,aM44gA==\r\n"
+        "a=mpeg4-esid:201\r\n"
+        "a=cliprect:0,0,240,320\r\n"
+        "a=framesize:97 320-240\r\n";
+#elif 0
+    // My H263 SDP
+    static const char *raw =
+        "v=0\r\n"
+        "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+        "s=QuickTime\r\n"
+        "t=0 0\r\n"
+        "a=range:npt=0-315\r\n"
+        "a=isma-compliance:2,2.0,2\r\n"
+        "m=video 5434 RTP/AVP 97\r\n"
+        "c=IN IP4 127.0.0.1\r\n"
+        "b=AS:30\r\n"
+        "a=rtpmap:97 H263-1998/90000\r\n"
+        "a=cliprect:0,0,240,320\r\n"
+        "a=framesize:97 320-240\r\n";
+#elif 0
+    // My AMR SDP
+    static const char *raw =
+        "v=0\r\n"
+        "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+        "s=QuickTime\r\n"
+        "t=0 0\r\n"
+        "a=range:npt=0-315\r\n"
+        "a=isma-compliance:2,2.0,2\r\n"
+        "m=audio 5434 RTP/AVP 97\r\n"
+        "c=IN IP4 127.0.0.1\r\n"
+        "b=AS:30\r\n"
+        "a=rtpmap:97 AMR/8000/1\r\n"
+        "a=fmtp:97 octet-align\r\n";
+#elif 1
+    // GTalk's H264 SDP
+    static const char *raw =
+        "v=0\r\n"
+        "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+        "s=QuickTime\r\n"
+        "t=0 0\r\n"
+        "a=range:npt=now-\r\n"
+        "m=video 5434 RTP/AVP 96\r\n"
+        "c=IN IP4 127.0.0.1\r\n"
+        "b=AS:320000\r\n"
+        "a=rtpmap:96 H264/90000\r\n"
+        "a=fmtp:96 packetization-mode=1;profile-level-id=42001E;"
+          "sprop-parameter-sets=Z0IAHpZUBaHogA==,aM44gA==\r\n"
+        "a=cliprect:0,0,480,270\r\n"
+        "a=framesize:96 720-480\r\n";
+#else
+    // sholes H264 SDP
+    static const char *raw =
+        "v=0\r\n"
+        "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+        "s=QuickTime\r\n"
+        "t=0 0\r\n"
+        "a=range:npt=now-\r\n"
+        "m=video 5434 RTP/AVP 96\r\n"
+        "c=IN IP4 127.0.0.1\r\n"
+        "b=AS:320000\r\n"
+        "a=rtpmap:96 H264/90000\r\n"
+        "a=fmtp:96 packetization-mode=1;profile-level-id=42001E;"
+          "sprop-parameter-sets=Z0KACukCg+QgAAB9AAAOpgCA,aM48gA==\r\n"
+        "a=cliprect:0,0,240,320\r\n"
+        "a=framesize:96 320-240\r\n";
+#endif
+
+    sp<ASessionDescription> desc = new ASessionDescription;
+    CHECK(desc->setTo(raw, strlen(raw)));
+
+    CHECK_EQ(session->setup(desc), (status_t)OK);
+
+    if (rtp_pusher != NULL) {
+        rtp_pusher->start();
+    }
+
+    if (rtcp_pusher != NULL) {
+        rtcp_pusher->start();
+    }
+
+    looper->start(false /* runOnCallingThread */);
+
+    CHECK_EQ(session->countTracks(), 1u);
+    sp<MediaSource> source = session->trackAt(0);
+
+    OMXClient client;
+    CHECK_EQ(client.connect(), (status_t)OK);
+
+    sp<MediaSource> decoder = OMXCodec::Create(
+            client.interface(),
+            source->getFormat(), false /* createEncoder */,
+            source,
+            NULL,
+            0);  // OMXCodec::kPreferSoftwareCodecs);
+    CHECK(decoder != NULL);
+
+    CHECK_EQ(decoder->start(), (status_t)OK);
+
+    for (;;) {
+        MediaBuffer *buffer;
+        status_t err = decoder->read(&buffer);
+
+        if (err != OK) {
+            if (err == INFO_FORMAT_CHANGED) {
+                int32_t width, height;
+                CHECK(decoder->getFormat()->findInt32(kKeyWidth, &width));
+                CHECK(decoder->getFormat()->findInt32(kKeyHeight, &height));
+                printf("INFO_FORMAT_CHANGED %d x %d\n", width, height);
+                continue;
+            }
+
+            LOG(ERROR) << "decoder returned error "
+                       << StringPrintf("0x%08x", err);
+            break;
+        }
+
+#if 1
+        if (buffer->range_length() != 0) {
+            int64_t timeUs;
+            CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs));
+
+            printf("decoder returned frame of size %d at time %.2f secs\n",
+                   buffer->range_length(), timeUs / 1E6);
+        }
+#endif
+
+        buffer->release();
+        buffer = NULL;
+    }
+
+    CHECK_EQ(decoder->stop(), (status_t)OK);
+
+    looper->stop();
+
+    return 0;
+}
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 7cc254b..8a876a2 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -5867,8 +5867,17 @@
         if ((newPackage.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
             retCode = mInstaller.movedex(newPackage.mScanPath, newPackage.mPath);
             if (retCode != 0) {
-                Slog.e(TAG, "Couldn't rename dex file: " + newPackage.mPath);
-                return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+                if (mNoDexOpt) {
+                    /*
+                     * If we're in an engineering build, programs are lazily run
+                     * through dexopt. If the .dex file doesn't exist yet, it
+                     * will be created when the program is run next.
+                     */
+                    Slog.i(TAG, "dex file doesn't exist, skipping move: " + newPackage.mPath);
+                } else {
+                    Slog.e(TAG, "Couldn't rename dex file: " + newPackage.mPath);
+                    return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+                }
             }
         }
         return PackageManager.INSTALL_SUCCEEDED;
@@ -7202,6 +7211,9 @@
                     pw.print("    pkgFlags=0x"); pw.print(Integer.toHexString(ps.pkgFlags));
                             pw.print(" installStatus="); pw.print(ps.installStatus);
                             pw.print(" enabled="); pw.println(ps.enabled);
+                    if (ps.pkg.mOperationPending) {
+                        pw.println("    mOperationPending=true");
+                    }
                     if (ps.disabledComponents.size() > 0) {
                         pw.println("    disabledComponents:");
                         for (String s : ps.disabledComponents) {
@@ -9890,6 +9902,9 @@
                        (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0) {
                    Slog.w(TAG, "Cannot move forward locked app.");
                    returnCode = PackageManager.MOVE_FAILED_FORWARD_LOCKED;
+               } else if (pkg.mOperationPending) {
+                   Slog.w(TAG, "Attempt to move package which has pending operations");
+                   returnCode = PackageManager.MOVE_FAILED_OPERATION_PENDING;
                } else {
                    // Find install location first
                    if ((flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 &&
@@ -9906,6 +9921,9 @@
                            returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION;
                        }
                    }
+                   if (returnCode == PackageManager.MOVE_SUCCEEDED) {
+                       pkg.mOperationPending = true;
+                   }
                }
            }
            if (returnCode != PackageManager.MOVE_SUCCEEDED) {
@@ -10018,6 +10036,18 @@
                        mp.srcArgs.doPostDeleteLI(true);
                    }
                }
+
+               // Allow more operations on this file if we didn't fail because
+               // an operation was already pending for this package.
+               if (returnCode != PackageManager.MOVE_FAILED_OPERATION_PENDING) {
+                   synchronized (mPackages) {
+                       PackageParser.Package pkg = mPackages.get(mp.packageName);
+                       if (pkg != null) {
+                           pkg.mOperationPending = false;
+                       }
+                   }
+               }
+
                IPackageMoveObserver observer = mp.observer;
                if (observer != null) {
                    try {