Support for Gtalk video, includes AMR/H.263 assembler and packetization support, extensions to MediaRecorder to stream via RTP over a pair of UDP sockets as well as various fixes to the RTP implementation.

Change-Id: I95b8dd487061add9bade15749e563b01cd99d9a6
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/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 47a8cfc..34a86ec 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/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp
index 5adc116..9d53c25 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 dfddae0..f6f89c7 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -40,6 +40,8 @@
 #include <unistd.h>
 #include <ctype.h>
 
+#include "ARTPWriter.h"
+
 namespace android {
 
 StagefrightRecorder::StagefrightRecorder()
@@ -628,6 +630,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;
@@ -760,6 +765,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(
@@ -882,7 +920,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;
 
@@ -944,7 +984,8 @@
         return UNKNOWN_ERROR;
     }
 
-    writer->addSource(encoder);
+    *source = encoder;
+
     return OK;
 }
 
@@ -982,8 +1023,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 b4c5900..216f6bc 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -101,10 +101,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 89bfc1f..fb85287 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -45,7 +45,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/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 ca4c55e..ad813cd 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 044393f..f21c8dc 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;
+}