Initial checkin of preliminary rtsp support for stagefright.
Change-Id: I0722aa888098c0c1361c97a4c1b123d910afc207
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 11f3016..d7ca635 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -771,9 +771,14 @@
}
}
- // Use PV_PLAYER for rtsp for now
if (!strncasecmp(url, "rtsp://", 7)) {
- return PV_PLAYER;
+ char value[PROPERTY_VALUE_MAX];
+ if (!property_get("media.stagefright.enable-rtsp", value, NULL)
+ || (strcmp(value, "1") && strcasecmp(value, "true"))) {
+ // For now, we're going to use PV for rtsp-based playback
+ // by default until we can clear up a few more issues.
+ return PV_PLAYER;
+ }
}
return getDefaultPlayerType();
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index f67826e..7608ec8 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -82,6 +82,7 @@
libvpx \
libstagefright_mpeg2ts \
libstagefright_httplive \
+ libstagefright_rtsp \
LOCAL_SHARED_LIBRARIES += \
libstagefright_amrnb_common \
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 88c8ee4..4c9856d 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -20,7 +20,9 @@
#include <dlfcn.h>
+#include "include/ARTSPController.h"
#include "include/AwesomePlayer.h"
+#include "include/LiveSource.h"
#include "include/Prefetcher.h"
#include "include/SoftwareRenderer.h"
@@ -39,7 +41,7 @@
#include <surfaceflinger/ISurface.h>
-#include "include/LiveSource.h"
+#include <media/stagefright/foundation/ALooper.h>
namespace android {
@@ -393,6 +395,8 @@
mVideoBuffer = NULL;
}
+ mRTSPController.clear();
+
if (mVideoSource != NULL) {
mVideoSource->stop();
@@ -1148,7 +1152,22 @@
sp<MediaExtractor> extractor =
MediaExtractor::Create(dataSource, MEDIA_MIMETYPE_CONTAINER_MPEG2TS);
+ } else if (!strncasecmp("rtsp://", mUri.string(), 7)) {
+ if (mLooper == NULL) {
+ mLooper = new ALooper;
+ mLooper->start();
+ }
+ mRTSPController = new ARTSPController(mLooper);
+ status_t err = mRTSPController->connect(mUri.string());
+ LOGI("ARTSPController::connect returned %d", err);
+
+ if (err != OK) {
+ mRTSPController.clear();
+ return err;
+ }
+
+ sp<MediaExtractor> extractor = mRTSPController.get();
return setDataSource_l(extractor);
} else {
dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders);
diff --git a/media/libstagefright/include/ARTSPController.h b/media/libstagefright/include/ARTSPController.h
new file mode 100644
index 0000000..55efd41
--- /dev/null
+++ b/media/libstagefright/include/ARTSPController.h
@@ -0,0 +1,53 @@
+/*
+ * 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_RTSP_CONTROLLER_H_
+
+#define A_RTSP_CONTROLLER_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/MediaExtractor.h>
+
+namespace android {
+
+struct ALooper;
+struct MyHandler;
+
+struct ARTSPController : public MediaExtractor {
+ ARTSPController(const sp<ALooper> &looper);
+
+ status_t connect(const char *url);
+ void disconnect();
+
+ virtual size_t countTracks();
+ virtual sp<MediaSource> getTrack(size_t index);
+
+ virtual sp<MetaData> getTrackMetaData(
+ size_t index, uint32_t flags);
+
+protected:
+ virtual ~ARTSPController();
+
+private:
+ sp<ALooper> mLooper;
+ sp<MyHandler> mHandler;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ARTSPController);
+};
+
+} // namespace android
+
+#endif // A_RTSP_CONTROLLER_H_
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 9455743..182aa06 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -36,6 +36,9 @@
struct Prefetcher;
struct TimeSource;
+struct ALooper;
+struct ARTSPController;
+
struct AwesomeRenderer : public RefBase {
AwesomeRenderer() {}
@@ -169,6 +172,9 @@
sp<Prefetcher> mPrefetcher;
sp<HTTPDataSource> mConnectingDataSource;
+ sp<ALooper> mLooper;
+ sp<ARTSPController> mRTSPController;
+
struct SuspensionState {
String8 mUri;
KeyedVector<String8, String8> mUriHeaders;
diff --git a/media/libstagefright/rtsp/AAVCAssembler.cpp b/media/libstagefright/rtsp/AAVCAssembler.cpp
new file mode 100644
index 0000000..3dfb200
--- /dev/null
+++ b/media/libstagefright/rtsp/AAVCAssembler.cpp
@@ -0,0 +1,385 @@
+/*
+ * 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 "AAVCAssembler.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 <stdint.h>
+
+#define BE_VERBOSE 0
+
+namespace android {
+
+// static
+AAVCAssembler::AAVCAssembler(const sp<AMessage> ¬ify)
+ : mNotifyMsg(notify),
+ mAccessUnitRTPTime(0),
+ mNextExpectedSeqNoValid(false),
+ mNextExpectedSeqNo(0),
+ mAccessUnitDamaged(false) {
+}
+
+AAVCAssembler::~AAVCAssembler() {
+}
+
+ARTPAssembler::AssemblyStatus AAVCAssembler::addNALUnit(
+ 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 BE_VERBOSE
+ LOG(VERBOSE) << "Not the sequence number I expected";
+#endif
+
+ return WRONG_SEQUENCE_NUMBER;
+ }
+
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ if (size < 1 || (data[0] & 0x80)) {
+ // Corrupt.
+
+ LOG(ERROR) << "Ignoring corrupt buffer.";
+ queue->erase(queue->begin());
+
+ ++mNextExpectedSeqNo;
+ return MALFORMED_PACKET;
+ }
+
+ unsigned nalType = data[0] & 0x1f;
+ if (nalType >= 1 && nalType <= 23) {
+ addSingleNALUnit(buffer);
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+ return OK;
+ } else if (nalType == 28) {
+ // FU-A
+ return addFragmentedNALUnit(queue);
+ } else if (nalType == 24) {
+ // STAP-A
+ bool success = addSingleTimeAggregationPacket(buffer);
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return success ? OK : MALFORMED_PACKET;
+ } else {
+ LOG(ERROR) << "Ignoring unsupported buffer (nalType=" << nalType << ")";
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return MALFORMED_PACKET;
+ }
+}
+
+void AAVCAssembler::addSingleNALUnit(const sp<ABuffer> &buffer) {
+#if BE_VERBOSE
+ LOG(VERBOSE) << "addSingleNALUnit of size " << buffer->size();
+ hexdump(buffer->data(), buffer->size());
+#endif
+
+ uint32_t rtpTime;
+ CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ if (!mNALUnits.empty() && rtpTime != mAccessUnitRTPTime) {
+ submitAccessUnit();
+ }
+ mAccessUnitRTPTime = rtpTime;
+
+ mNALUnits.push_back(buffer);
+}
+
+bool AAVCAssembler::addSingleTimeAggregationPacket(const sp<ABuffer> &buffer) {
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ if (size < 3) {
+ LOG(ERROR) << "Discarding too small STAP-A packet.";
+ return false;
+ }
+
+ ++data;
+ --size;
+ while (size >= 2) {
+ size_t nalSize = (data[0] << 8) | data[1];
+
+ if (size < nalSize + 2) {
+ LOG(ERROR) << "Discarding malformed STAP-A packet.";
+ return false;
+ }
+
+ sp<ABuffer> unit = new ABuffer(nalSize);
+ memcpy(unit->data(), &data[2], nalSize);
+
+ PropagateTimes(buffer, unit);
+
+ addSingleNALUnit(unit);
+
+ data += 2 + nalSize;
+ size -= 2 + nalSize;
+ }
+
+ if (size != 0) {
+ LOG(WARNING) << "Unexpected padding at end of STAP-A packet.";
+ }
+
+ return true;
+}
+
+ARTPAssembler::AssemblyStatus AAVCAssembler::addFragmentedNALUnit(
+ List<sp<ABuffer> > *queue) {
+ CHECK(!queue->empty());
+
+ sp<ABuffer> buffer = *queue->begin();
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ CHECK(size > 0);
+ unsigned indicator = data[0];
+
+ CHECK((indicator & 0x1f) == 28);
+
+ if (size < 2) {
+ LOG(ERROR) << "Ignoring malformed FU buffer (size = " << size << ")";
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+ return MALFORMED_PACKET;
+ }
+
+ if (!(data[1] & 0x80)) {
+ // Start bit not set on the first buffer.
+
+#if BE_VERBOSE
+ LOG(ERROR) << "Start bit not set on first buffer";
+#endif
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+ return MALFORMED_PACKET;
+ }
+
+ uint32_t nalType = data[1] & 0x1f;
+ uint32_t nri = (data[0] >> 5) & 3;
+
+ uint32_t expectedSeqNo = (uint32_t)buffer->int32Data() + 1;
+ size_t totalSize = size - 2;
+ size_t totalCount = 1;
+ bool complete = false;
+
+ if (data[1] & 0x40) {
+ // Huh? End bit also set on the first buffer.
+
+#if BE_VERBOSE
+ LOG(WARNING) << "Grrr. This isn't fragmented at all.";
+#endif
+
+ complete = true;
+ } else {
+ List<sp<ABuffer> >::iterator it = ++queue->begin();
+ while (it != queue->end()) {
+#if BE_VERBOSE
+ LOG(VERBOSE) << "sequence length " << totalCount;
+#endif
+
+ const sp<ABuffer> &buffer = *it;
+
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ if ((uint32_t)buffer->int32Data() != expectedSeqNo) {
+#if BE_VERBOSE
+ LOG(VERBOSE) << "sequence not complete, expected seqNo "
+ << expectedSeqNo << ", got "
+ << (uint32_t)buffer->int32Data();
+#endif
+
+ return WRONG_SEQUENCE_NUMBER;
+ }
+
+ if (size < 2
+ || data[0] != indicator
+ || (data[1] & 0x1f) != nalType
+ || (data[1] & 0x80)) {
+ LOG(ERROR) << "Ignoring malformed FU buffer.\n";
+
+ // Delete the whole start of the FU.
+
+ it = queue->begin();
+ for (size_t i = 0; i <= totalCount; ++i) {
+ it = queue->erase(it);
+ }
+
+ mNextExpectedSeqNo = expectedSeqNo + 1;
+
+ return MALFORMED_PACKET;
+ }
+
+ totalSize += size - 2;
+ ++totalCount;
+
+ expectedSeqNo = expectedSeqNo + 1;
+
+ if (data[1] & 0x40) {
+ // This is the last fragment.
+ complete = true;
+ break;
+ }
+
+ ++it;
+ }
+ }
+
+ if (!complete) {
+ return NOT_ENOUGH_DATA;
+ }
+
+ mNextExpectedSeqNo = expectedSeqNo;
+
+ // We found all the fragments that make up the complete NAL unit.
+
+ // Leave room for the header. So far totalSize did not include the
+ // header byte.
+ ++totalSize;
+
+ sp<ABuffer> unit = new ABuffer(totalSize);
+ PropagateTimes(buffer, unit);
+
+ unit->data()[0] = (nri << 5) | nalType;
+
+ size_t offset = 1;
+ List<sp<ABuffer> >::iterator it = queue->begin();
+ for (size_t i = 0; i < totalCount; ++i) {
+ const sp<ABuffer> &buffer = *it;
+
+#if BE_VERBOSE
+ LOG(VERBOSE) << "piece #" << (i + 1) << "/" << totalCount;
+ hexdump(buffer->data(), buffer->size());
+#endif
+
+ memcpy(unit->data() + offset, buffer->data() + 2, buffer->size() - 2);
+ offset += buffer->size() - 2;
+
+ it = queue->erase(it);
+ }
+
+ unit->setRange(0, totalSize);
+
+ addSingleNALUnit(unit);
+
+#if BE_VERBOSE
+ LOG(VERBOSE) << "successfully assembled a NAL unit from fragments.";
+#endif
+
+ return OK;
+}
+
+void AAVCAssembler::submitAccessUnit() {
+ CHECK(!mNALUnits.empty());
+
+#if BE_VERBOSE
+ LOG(VERBOSE) << "Access unit complete (" << mNALUnits.size() << " nal units)";
+#endif
+
+ uint64_t ntpTime;
+ CHECK((*mNALUnits.begin())->meta()->findInt64(
+ "ntp-time", (int64_t *)&ntpTime));
+
+ size_t totalSize = 0;
+ for (List<sp<ABuffer> >::iterator it = mNALUnits.begin();
+ it != mNALUnits.end(); ++it) {
+ totalSize += 4 + (*it)->size();
+ }
+
+ sp<ABuffer> accessUnit = new ABuffer(totalSize);
+ size_t offset = 0;
+ for (List<sp<ABuffer> >::iterator it = mNALUnits.begin();
+ it != mNALUnits.end(); ++it) {
+ memcpy(accessUnit->data() + offset, "\x00\x00\x00\x01", 4);
+ offset += 4;
+
+ sp<ABuffer> nal = *it;
+ memcpy(accessUnit->data() + offset, nal->data(), nal->size());
+ offset += nal->size();
+ }
+
+ accessUnit->meta()->setInt64("ntp-time", ntpTime);
+
+#if 0
+ printf(mAccessUnitDamaged ? "X" : ".");
+ fflush(stdout);
+#endif
+
+ if (mAccessUnitDamaged) {
+ accessUnit->meta()->setInt32("damaged", true);
+ }
+
+ mNALUnits.clear();
+ mAccessUnitDamaged = false;
+
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setObject("access-unit", accessUnit);
+ msg->post();
+}
+
+ARTPAssembler::AssemblyStatus AAVCAssembler::assembleMore(
+ const sp<ARTPSource> &source) {
+ AssemblyStatus status = addNALUnit(source);
+ if (status == MALFORMED_PACKET) {
+ mAccessUnitDamaged = true;
+ }
+ return status;
+}
+
+void AAVCAssembler::packetLost() {
+ CHECK(mNextExpectedSeqNoValid);
+ ++mNextExpectedSeqNo;
+
+ mAccessUnitDamaged = true;
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/AAVCAssembler.h b/media/libstagefright/rtsp/AAVCAssembler.h
new file mode 100644
index 0000000..1e97520
--- /dev/null
+++ b/media/libstagefright/rtsp/AAVCAssembler.h
@@ -0,0 +1,61 @@
+/*
+ * 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_AVC_ASSEMBLER_H_
+
+#define A_AVC_ASSEMBLER_H_
+
+#include "ARTPAssembler.h"
+
+#include <utils/List.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ABuffer;
+struct AMessage;
+
+struct AAVCAssembler : public ARTPAssembler {
+ AAVCAssembler(const sp<AMessage> ¬ify);
+
+protected:
+ virtual ~AAVCAssembler();
+
+ virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
+ virtual void packetLost();
+
+private:
+ sp<AMessage> mNotifyMsg;
+
+ uint32_t mAccessUnitRTPTime;
+ bool mNextExpectedSeqNoValid;
+ uint32_t mNextExpectedSeqNo;
+ bool mAccessUnitDamaged;
+ List<sp<ABuffer> > mNALUnits;
+
+ AssemblyStatus addNALUnit(const sp<ARTPSource> &source);
+ void addSingleNALUnit(const sp<ABuffer> &buffer);
+ AssemblyStatus addFragmentedNALUnit(List<sp<ABuffer> > *queue);
+ bool addSingleTimeAggregationPacket(const sp<ABuffer> &buffer);
+
+ void submitAccessUnit();
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAVCAssembler);
+};
+
+} // namespace android
+
+#endif // A_AVC_ASSEMBLER_H_
diff --git a/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp
new file mode 100644
index 0000000..0549d84
--- /dev/null
+++ b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp
@@ -0,0 +1,166 @@
+/*
+ * 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 "AMPEG4AudioAssembler.h"
+
+#include "ARTPSource.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+namespace android {
+
+AMPEG4AudioAssembler::AMPEG4AudioAssembler(const sp<AMessage> ¬ify)
+ : mNotifyMsg(notify),
+ mAccessUnitRTPTime(0),
+ mNextExpectedSeqNoValid(false),
+ mNextExpectedSeqNo(0),
+ mAccessUnitDamaged(false) {
+}
+
+AMPEG4AudioAssembler::~AMPEG4AudioAssembler() {
+}
+
+ARTPAssembler::AssemblyStatus AMPEG4AudioAssembler::assembleMore(
+ const sp<ARTPSource> &source) {
+ AssemblyStatus status = addPacket(source);
+ if (status == MALFORMED_PACKET) {
+ mAccessUnitDamaged = true;
+ }
+ return status;
+}
+
+ARTPAssembler::AssemblyStatus AMPEG4AudioAssembler::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;
+
+ mPackets.push_back(buffer);
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return OK;
+}
+
+void AMPEG4AudioAssembler::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;
+
+ size_t n = 0;
+ while (unit->data()[n] == 0xff) {
+ ++n;
+ }
+ ++n;
+
+ totalSize += unit->size() - n;
+ ++it;
+ }
+
+ sp<ABuffer> accessUnit = new ABuffer(totalSize);
+ size_t offset = 0;
+ it = mPackets.begin();
+ while (it != mPackets.end()) {
+ const sp<ABuffer> &unit = *it;
+
+ size_t n = 0;
+ while (unit->data()[n] == 0xff) {
+ ++n;
+ }
+ ++n;
+
+ memcpy((uint8_t *)accessUnit->data() + offset,
+ unit->data() + n, unit->size() - n);
+
+ offset += unit->size() - n;
+
+ ++it;
+ }
+
+ accessUnit->meta()->setInt64("ntp-time", ntpTime);
+
+ if (mAccessUnitDamaged) {
+ accessUnit->meta()->setInt32("damaged", true);
+ }
+
+ mPackets.clear();
+ mAccessUnitDamaged = false;
+
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setObject("access-unit", accessUnit);
+ msg->post();
+}
+
+void AMPEG4AudioAssembler::packetLost() {
+ CHECK(mNextExpectedSeqNoValid);
+ ++mNextExpectedSeqNo;
+
+ mAccessUnitDamaged = true;
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/AMPEG4AudioAssembler.h b/media/libstagefright/rtsp/AMPEG4AudioAssembler.h
new file mode 100644
index 0000000..5c2a2dd
--- /dev/null
+++ b/media/libstagefright/rtsp/AMPEG4AudioAssembler.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 A_MPEG4_AUDIO_ASSEMBLER_H_
+
+#define A_MPEG4_AUDIO_ASSEMBLER_H_
+
+#include "ARTPAssembler.h"
+
+#include <utils/List.h>
+
+#include <stdint.h>
+
+namespace android {
+
+struct AMessage;
+
+struct AMPEG4AudioAssembler : public ARTPAssembler {
+ AMPEG4AudioAssembler(const sp<AMessage> ¬ify);
+
+protected:
+ virtual ~AMPEG4AudioAssembler();
+
+ virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
+ 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(AMPEG4AudioAssembler);
+};
+
+} // namespace android
+
+#endif // A_MPEG4_AUDIO_ASSEMBLER_H_
diff --git a/media/libstagefright/rtsp/APacketSource.cpp b/media/libstagefright/rtsp/APacketSource.cpp
new file mode 100644
index 0000000..2869d54
--- /dev/null
+++ b/media/libstagefright/rtsp/APacketSource.cpp
@@ -0,0 +1,345 @@
+/*
+ * 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 "APacketSource.h"
+
+#include "ASessionDescription.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/foundation/base64.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <utils/Vector.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 (colonPos == NULL) {
+ return false;
+ }
+
+ s = colonPos + 1;
+ }
+}
+
+static sp<ABuffer> decodeHex(const AString &s) {
+ if ((s.size() % 2) != 0) {
+ return NULL;
+ }
+
+ size_t outLen = s.size() / 2;
+ sp<ABuffer> buffer = new ABuffer(outLen);
+ uint8_t *out = buffer->data();
+
+ uint8_t accum = 0;
+ for (size_t i = 0; i < s.size(); ++i) {
+ char c = s.c_str()[i];
+ unsigned value;
+ if (c >= '0' && c <= '9') {
+ value = c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ value = c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ value = c - 'A' + 10;
+ } else {
+ return NULL;
+ }
+
+ accum = (accum << 4) | value;
+
+ if (i & 1) {
+ *out++ = accum;
+
+ accum = 0;
+ }
+ }
+
+ return buffer;
+}
+
+static sp<ABuffer> MakeAVCCodecSpecificData(const char *params) {
+ AString val;
+ CHECK(GetAttribute(params, "profile-level-id", &val));
+
+ sp<ABuffer> profileLevelID = decodeHex(val);
+ CHECK(profileLevelID != NULL);
+ CHECK_EQ(profileLevelID->size(), 3u);
+
+ Vector<sp<ABuffer> > paramSets;
+
+ size_t numSeqParameterSets = 0;
+ size_t totalSeqParameterSetSize = 0;
+ size_t numPicParameterSets = 0;
+ size_t totalPicParameterSetSize = 0;
+
+ CHECK(GetAttribute(params, "sprop-parameter-sets", &val));
+ size_t start = 0;
+ for (;;) {
+ ssize_t commaPos = val.find(",", start);
+ size_t end = (commaPos < 0) ? val.size() : commaPos;
+
+ AString nalString(val, start, end - start);
+ sp<ABuffer> nal = decodeBase64(nalString);
+ CHECK(nal != NULL);
+ CHECK_GT(nal->size(), 0u);
+ CHECK_LE(nal->size(), 65535u);
+
+ uint8_t nalType = nal->data()[0] & 0x1f;
+ if (numSeqParameterSets == 0) {
+ CHECK_EQ((unsigned)nalType, 7u);
+ } else if (numPicParameterSets > 0) {
+ CHECK_EQ((unsigned)nalType, 8u);
+ }
+ if (nalType == 7) {
+ ++numSeqParameterSets;
+ totalSeqParameterSetSize += nal->size();
+ } else {
+ CHECK_EQ((unsigned)nalType, 8u);
+ ++numPicParameterSets;
+ totalPicParameterSetSize += nal->size();
+ }
+
+ paramSets.push(nal);
+
+ if (commaPos < 0) {
+ break;
+ }
+
+ start = commaPos + 1;
+ }
+
+ CHECK_LT(numSeqParameterSets, 32u);
+ CHECK_LE(numPicParameterSets, 255u);
+
+ size_t csdSize =
+ 1 + 3 + 1 + 1
+ + 2 * numSeqParameterSets + totalSeqParameterSetSize
+ + 1 + 2 * numPicParameterSets + totalPicParameterSetSize;
+
+ sp<ABuffer> csd = new ABuffer(csdSize);
+ uint8_t *out = csd->data();
+
+ *out++ = 0x01; // configurationVersion
+ memcpy(out, profileLevelID->data(), 3);
+ out += 3;
+ *out++ = (0x3f << 2) | 1; // lengthSize == 2 bytes
+ *out++ = 0xe0 | numSeqParameterSets;
+
+ for (size_t i = 0; i < numSeqParameterSets; ++i) {
+ sp<ABuffer> nal = paramSets.editItemAt(i);
+
+ *out++ = nal->size() >> 8;
+ *out++ = nal->size() & 0xff;
+
+ memcpy(out, nal->data(), nal->size());
+
+ out += nal->size();
+ }
+
+ *out++ = numPicParameterSets;
+
+ for (size_t i = 0; i < numPicParameterSets; ++i) {
+ sp<ABuffer> nal = paramSets.editItemAt(i + numSeqParameterSets);
+
+ *out++ = nal->size() >> 8;
+ *out++ = nal->size() & 0xff;
+
+ memcpy(out, nal->data(), nal->size());
+
+ out += nal->size();
+ }
+
+ hexdump(csd->data(), csd->size());
+
+ return csd;
+}
+
+sp<ABuffer> MakeAACCodecSpecificData(const char *params) {
+ AString val;
+ CHECK(GetAttribute(params, "config", &val));
+
+ sp<ABuffer> config = decodeHex(val);
+ CHECK(config != NULL);
+ CHECK_GE(config->size(), 4u);
+
+ const uint8_t *data = config->data();
+ uint32_t x = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
+ x = (x >> 1) & 0xffff;
+
+ static const uint8_t kStaticESDS[] = {
+ 0x03, 22,
+ 0x00, 0x00, // ES_ID
+ 0x00, // streamDependenceFlag, URL_Flag, OCRstreamFlag
+
+ 0x04, 17,
+ 0x40, // Audio ISO/IEC 14496-3
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x05, 2,
+ // AudioSpecificInfo follows
+ };
+
+ sp<ABuffer> csd = new ABuffer(sizeof(kStaticESDS) + 2);
+ memcpy(csd->data(), kStaticESDS, sizeof(kStaticESDS));
+ csd->data()[sizeof(kStaticESDS)] = (x >> 8) & 0xff;
+ csd->data()[sizeof(kStaticESDS) + 1] = x & 0xff;
+
+ hexdump(csd->data(), csd->size());
+
+ return csd;
+}
+
+APacketSource::APacketSource(
+ const sp<ASessionDescription> &sessionDesc, size_t index)
+ : mFormat(new MetaData),
+ mEOSResult(OK) {
+ unsigned long PT;
+ AString desc;
+ AString params;
+ sessionDesc->getFormatType(index, &PT, &desc, ¶ms);
+
+ int64_t durationUs;
+ if (sessionDesc->getDurationUs(&durationUs)) {
+ mFormat->setInt64(kKeyDuration, durationUs);
+ } else {
+ mFormat->setInt64(kKeyDuration, 60 * 60 * 1000000ll);
+ }
+
+ if (!strncmp(desc.c_str(), "H264/", 5)) {
+ mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+
+ int32_t width, height;
+ sessionDesc->getDimensions(index, PT, &width, &height);
+
+ mFormat->setInt32(kKeyWidth, width);
+ mFormat->setInt32(kKeyHeight, height);
+
+ sp<ABuffer> codecSpecificData =
+ MakeAVCCodecSpecificData(params.c_str());
+
+ mFormat->setData(
+ kKeyAVCC, 0,
+ codecSpecificData->data(), codecSpecificData->size());
+
+ } else if (!strncmp(desc.c_str(), "MP4A-LATM", 9)) {
+ mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
+
+ int32_t sampleRate, numChannels;
+ ASessionDescription::ParseFormatDesc(
+ desc.c_str(), &sampleRate, &numChannels);
+
+ mFormat->setInt32(kKeySampleRate, sampleRate);
+ mFormat->setInt32(kKeyChannelCount, numChannels);
+
+ sp<ABuffer> codecSpecificData =
+ MakeAACCodecSpecificData(params.c_str());
+
+ mFormat->setData(
+ kKeyESDS, 0,
+ codecSpecificData->data(), codecSpecificData->size());
+ } else {
+ TRESPASS();
+ }
+}
+
+APacketSource::~APacketSource() {
+}
+
+status_t APacketSource::start(MetaData *params) {
+ return OK;
+}
+
+status_t APacketSource::stop() {
+ return OK;
+}
+
+sp<MetaData> APacketSource::getFormat() {
+ return mFormat;
+}
+
+status_t APacketSource::read(
+ MediaBuffer **out, const ReadOptions *) {
+ *out = NULL;
+
+ Mutex::Autolock autoLock(mLock);
+ while (mEOSResult == OK && mBuffers.empty()) {
+ mCondition.wait(mLock);
+ }
+
+ if (!mBuffers.empty()) {
+ const sp<ABuffer> buffer = *mBuffers.begin();
+
+ uint64_t ntpTime;
+ CHECK(buffer->meta()->findInt64(
+ "ntp-time", (int64_t *)&ntpTime));
+
+ 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;
+
+ mBuffers.erase(mBuffers.begin());
+ return OK;
+ }
+
+ return mEOSResult;
+}
+
+void APacketSource::queueAccessUnit(const sp<ABuffer> &buffer) {
+ int32_t damaged;
+ if (buffer->meta()->findInt32("damaged", &damaged) && damaged) {
+ // LOG(VERBOSE) << "discarding damaged AU";
+ return;
+ }
+
+ Mutex::Autolock autoLock(mLock);
+ mBuffers.push_back(buffer);
+ mCondition.signal();
+}
+
+void APacketSource::signalEOS(status_t result) {
+ CHECK(result != OK);
+
+ Mutex::Autolock autoLock(mLock);
+ mEOSResult = result;
+ mCondition.signal();
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/APacketSource.h b/media/libstagefright/rtsp/APacketSource.h
new file mode 100644
index 0000000..4040eee
--- /dev/null
+++ b/media/libstagefright/rtsp/APacketSource.h
@@ -0,0 +1,61 @@
+/*
+ * 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_PACKET_SOURCE_H_
+
+#define A_PACKET_SOURCE_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/MediaSource.h>
+#include <utils/threads.h>
+#include <utils/List.h>
+
+namespace android {
+
+struct ABuffer;
+struct ASessionDescription;
+
+struct APacketSource : public MediaSource {
+ APacketSource(const sp<ASessionDescription> &sessionDesc, size_t index);
+
+ virtual status_t start(MetaData *params = NULL);
+ virtual status_t stop();
+ virtual sp<MetaData> getFormat();
+
+ virtual status_t read(
+ MediaBuffer **buffer, const ReadOptions *options = NULL);
+
+ void queueAccessUnit(const sp<ABuffer> &buffer);
+ void signalEOS(status_t result);
+
+protected:
+ virtual ~APacketSource();
+
+private:
+ Mutex mLock;
+ Condition mCondition;
+
+ sp<MetaData> mFormat;
+ List<sp<ABuffer> > mBuffers;
+ status_t mEOSResult;
+
+ DISALLOW_EVIL_CONSTRUCTORS(APacketSource);
+};
+
+
+} // namespace android
+
+#endif // A_PACKET_SOURCE_H_
diff --git a/media/libstagefright/rtsp/ARTPAssembler.cpp b/media/libstagefright/rtsp/ARTPAssembler.cpp
new file mode 100644
index 0000000..24225b8
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPAssembler.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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 "ARTPAssembler.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include <stdint.h>
+
+namespace android {
+
+static int64_t getNowUs() {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+
+ return (int64_t)tv.tv_usec + tv.tv_sec * 1000000ll;
+}
+
+ARTPAssembler::ARTPAssembler()
+ : mFirstFailureTimeUs(-1) {
+}
+
+void ARTPAssembler::PropagateTimes(
+ const sp<ABuffer> &from, const sp<ABuffer> &to) {
+ uint32_t rtpTime;
+ CHECK(from->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ uint64_t ntpTime = 0;
+ CHECK(from->meta()->findInt64("ntp-time", (int64_t *)&ntpTime));
+
+ to->meta()->setInt32("rtp-time", rtpTime);
+ to->meta()->setInt64("ntp-time", ntpTime);
+}
+
+void ARTPAssembler::onPacketReceived(const sp<ARTPSource> &source) {
+ AssemblyStatus status;
+ for (;;) {
+ status = assembleMore(source);
+
+ if (status == WRONG_SEQUENCE_NUMBER) {
+ if (mFirstFailureTimeUs >= 0) {
+ if (getNowUs() - mFirstFailureTimeUs > 10000ll) {
+ mFirstFailureTimeUs = -1;
+
+ // LOG(VERBOSE) << "waited too long for packet.";
+ packetLost();
+ continue;
+ }
+ } else {
+ mFirstFailureTimeUs = getNowUs();
+ }
+ break;
+ } else {
+ mFirstFailureTimeUs = -1;
+
+ if (status == NOT_ENOUGH_DATA) {
+ break;
+ }
+ }
+ }
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/ARTPAssembler.h b/media/libstagefright/rtsp/ARTPAssembler.h
new file mode 100644
index 0000000..892bd65
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPAssembler.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 A_RTP_ASSEMBLER_H_
+
+#define A_RTP_ASSEMBLER_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ABuffer;
+struct ARTPSource;
+
+struct ARTPAssembler : public RefBase {
+ enum AssemblyStatus {
+ MALFORMED_PACKET,
+ WRONG_SEQUENCE_NUMBER,
+ NOT_ENOUGH_DATA,
+ OK
+ };
+
+ ARTPAssembler();
+
+ void onPacketReceived(const sp<ARTPSource> &source);
+
+protected:
+ static void PropagateTimes(
+ const sp<ABuffer> &from, const sp<ABuffer> &to);
+
+ virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source) = 0;
+ virtual void packetLost() = 0;
+
+private:
+ int64_t mFirstFailureTimeUs;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ARTPAssembler);
+};
+
+} // namespace android
+
+#endif // A_RTP_ASSEMBLER_H_
diff --git a/media/libstagefright/rtsp/ARTPConnection.cpp b/media/libstagefright/rtsp/ARTPConnection.cpp
new file mode 100644
index 0000000..a4413f0
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPConnection.cpp
@@ -0,0 +1,499 @@
+/*
+ * 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 "ARTPConnection.h"
+
+#include "ARTPSource.h"
+#include "ASessionDescription.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/AString.h>
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#define VERBOSE 0
+
+#if VERBOSE
+#include "hexdump.h"
+#endif
+
+namespace android {
+
+static uint16_t u16at(const uint8_t *data) {
+ return data[0] << 8 | data[1];
+}
+
+static uint32_t u32at(const uint8_t *data) {
+ return u16at(data) << 16 | u16at(&data[2]);
+}
+
+static uint64_t u64at(const uint8_t *data) {
+ return (uint64_t)(u32at(data)) << 32 | u32at(&data[4]);
+}
+
+// static
+const int64_t ARTPConnection::kSelectTimeoutUs = 1000ll;
+
+struct ARTPConnection::StreamInfo {
+ int mRTPSocket;
+ int mRTCPSocket;
+ sp<ASessionDescription> mSessionDesc;
+ size_t mIndex;
+ sp<AMessage> mNotifyMsg;
+};
+
+ARTPConnection::ARTPConnection()
+ : mPollEventPending(false) {
+}
+
+ARTPConnection::~ARTPConnection() {
+}
+
+void ARTPConnection::addStream(
+ int rtpSocket, int rtcpSocket,
+ const sp<ASessionDescription> &sessionDesc,
+ size_t index,
+ const sp<AMessage> ¬ify) {
+ sp<AMessage> msg = new AMessage(kWhatAddStream, id());
+ msg->setInt32("rtp-socket", rtpSocket);
+ msg->setInt32("rtcp-socket", rtcpSocket);
+ msg->setObject("session-desc", sessionDesc);
+ msg->setSize("index", index);
+ msg->setMessage("notify", notify);
+ msg->post();
+}
+
+void ARTPConnection::removeStream(int rtpSocket, int rtcpSocket) {
+ sp<AMessage> msg = new AMessage(kWhatRemoveStream, id());
+ msg->setInt32("rtp-socket", rtpSocket);
+ msg->setInt32("rtcp-socket", rtcpSocket);
+ msg->post();
+}
+
+static void bumpSocketBufferSize(int s) {
+ int size = 256 * 1024;
+ CHECK_EQ(setsockopt(s, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)), 0);
+}
+
+// static
+void ARTPConnection::MakePortPair(
+ int *rtpSocket, int *rtcpSocket, unsigned *rtpPort) {
+ *rtpSocket = socket(AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(*rtpSocket, 0);
+
+ bumpSocketBufferSize(*rtpSocket);
+
+ *rtcpSocket = socket(AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(*rtcpSocket, 0);
+
+ bumpSocketBufferSize(*rtcpSocket);
+
+ unsigned start = (rand() * 1000)/ RAND_MAX + 15550;
+ start &= ~1;
+
+ for (unsigned port = start; port < 65536; port += 2) {
+ 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);
+
+ if (bind(*rtpSocket,
+ (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ continue;
+ }
+
+ addr.sin_port = htons(port + 1);
+
+ if (bind(*rtcpSocket,
+ (const struct sockaddr *)&addr, sizeof(addr)) == 0) {
+ *rtpPort = port;
+ return;
+ }
+ }
+
+ TRESPASS();
+}
+
+void ARTPConnection::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatAddStream:
+ {
+ onAddStream(msg);
+ break;
+ }
+
+ case kWhatRemoveStream:
+ {
+ onRemoveStream(msg);
+ break;
+ }
+
+ case kWhatPollStreams:
+ {
+ onPollStreams();
+ break;
+ }
+
+ default:
+ {
+ TRESPASS();
+ break;
+ }
+ }
+}
+
+void ARTPConnection::onAddStream(const sp<AMessage> &msg) {
+ mStreams.push_back(StreamInfo());
+ StreamInfo *info = &*--mStreams.end();
+
+ int32_t s;
+ CHECK(msg->findInt32("rtp-socket", &s));
+ info->mRTPSocket = s;
+ CHECK(msg->findInt32("rtcp-socket", &s));
+ info->mRTCPSocket = s;
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("session-desc", &obj));
+ info->mSessionDesc = static_cast<ASessionDescription *>(obj.get());
+
+ CHECK(msg->findSize("index", &info->mIndex));
+ CHECK(msg->findMessage("notify", &info->mNotifyMsg));
+
+ postPollEvent();
+}
+
+void ARTPConnection::onRemoveStream(const sp<AMessage> &msg) {
+ int32_t rtpSocket, rtcpSocket;
+ CHECK(msg->findInt32("rtp-socket", &rtpSocket));
+ CHECK(msg->findInt32("rtcp-socket", &rtcpSocket));
+
+ List<StreamInfo>::iterator it = mStreams.begin();
+ while (it != mStreams.end()
+ && (it->mRTPSocket != rtpSocket || it->mRTCPSocket != rtcpSocket)) {
+ ++it;
+ }
+
+ if (it == mStreams.end()) {
+ TRESPASS();
+ }
+
+ mStreams.erase(it);
+}
+
+void ARTPConnection::postPollEvent() {
+ if (mPollEventPending) {
+ return;
+ }
+
+ sp<AMessage> msg = new AMessage(kWhatPollStreams, id());
+ msg->post();
+
+ mPollEventPending = true;
+}
+
+void ARTPConnection::onPollStreams() {
+ mPollEventPending = false;
+
+ if (mStreams.empty()) {
+ return;
+ }
+
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = kSelectTimeoutUs;
+
+ fd_set rs;
+ FD_ZERO(&rs);
+
+ int maxSocket = -1;
+ for (List<StreamInfo>::iterator it = mStreams.begin();
+ it != mStreams.end(); ++it) {
+ FD_SET(it->mRTPSocket, &rs);
+ FD_SET(it->mRTCPSocket, &rs);
+
+ if (it->mRTPSocket > maxSocket) {
+ maxSocket = it->mRTPSocket;
+ }
+ if (it->mRTCPSocket > maxSocket) {
+ maxSocket = it->mRTCPSocket;
+ }
+ }
+
+ int res = select(maxSocket + 1, &rs, NULL, NULL, &tv);
+ CHECK_GE(res, 0);
+
+ if (res > 0) {
+ for (List<StreamInfo>::iterator it = mStreams.begin();
+ it != mStreams.end(); ++it) {
+ if (FD_ISSET(it->mRTPSocket, &rs)) {
+ receive(&*it, true);
+ }
+ if (FD_ISSET(it->mRTCPSocket, &rs)) {
+ receive(&*it, false);
+ }
+ }
+ }
+
+ postPollEvent();
+}
+
+status_t ARTPConnection::receive(StreamInfo *s, bool receiveRTP) {
+ sp<ABuffer> buffer = new ABuffer(65536);
+
+ struct sockaddr_in from;
+ socklen_t fromSize = sizeof(from);
+
+ ssize_t nbytes = recvfrom(
+ receiveRTP ? s->mRTPSocket : s->mRTCPSocket,
+ buffer->data(),
+ buffer->capacity(),
+ 0,
+ (struct sockaddr *)&from,
+ &fromSize);
+
+ if (nbytes < 0) {
+ return -1;
+ }
+
+ buffer->setRange(0, nbytes);
+
+ status_t err;
+ if (receiveRTP) {
+ err = parseRTP(s, buffer);
+ } else {
+ err = parseRTCP(s, buffer);
+ }
+
+ return err;
+}
+
+status_t ARTPConnection::parseRTP(StreamInfo *s, const sp<ABuffer> &buffer) {
+ size_t size = buffer->size();
+
+ if (size < 12) {
+ // Too short to be a valid RTP header.
+ return -1;
+ }
+
+ const uint8_t *data = buffer->data();
+
+ if ((data[0] >> 6) != 2) {
+ // Unsupported version.
+ return -1;
+ }
+
+ if (data[0] & 0x20) {
+ // Padding present.
+
+ size_t paddingLength = data[size - 1];
+
+ if (paddingLength + 12 > size) {
+ // If we removed this much padding we'd end up with something
+ // that's too short to be a valid RTP header.
+ return -1;
+ }
+
+ size -= paddingLength;
+ }
+
+ int numCSRCs = data[0] & 0x0f;
+
+ size_t payloadOffset = 12 + 4 * numCSRCs;
+
+ if (size < payloadOffset) {
+ // Not enough data to fit the basic header and all the CSRC entries.
+ return -1;
+ }
+
+ if (data[0] & 0x10) {
+ // Header eXtension present.
+
+ if (size < payloadOffset + 4) {
+ // Not enough data to fit the basic header, all CSRC entries
+ // and the first 4 bytes of the extension header.
+
+ return -1;
+ }
+
+ const uint8_t *extensionData = &data[payloadOffset];
+
+ size_t extensionLength =
+ 4 * (extensionData[2] << 8 | extensionData[3]);
+
+ if (size < payloadOffset + 4 + extensionLength) {
+ return -1;
+ }
+
+ payloadOffset += 4 + extensionLength;
+ }
+
+ 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);
+ }
+
+ uint32_t rtpTime = u32at(&data[4]);
+
+ sp<AMessage> meta = buffer->meta();
+ meta->setInt32("ssrc", srcId);
+ meta->setInt32("rtp-time", rtpTime);
+ meta->setInt32("PT", data[1] & 0x7f);
+ 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);
+
+ return OK;
+}
+
+status_t ARTPConnection::parseRTCP(StreamInfo *s, const sp<ABuffer> &buffer) {
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ while (size > 0) {
+ if (size < 8) {
+ // Too short to be a valid RTCP header
+ return -1;
+ }
+
+ if ((data[0] >> 6) != 2) {
+ // Unsupported version.
+ return -1;
+ }
+
+ if (data[0] & 0x20) {
+ // Padding present.
+
+ size_t paddingLength = data[size - 1];
+
+ if (paddingLength + 12 > size) {
+ // If we removed this much padding we'd end up with something
+ // that's too short to be a valid RTP header.
+ return -1;
+ }
+
+ size -= paddingLength;
+ }
+
+ size_t headerLength = 4 * (data[2] << 8 | data[3]) + 4;
+
+ if (size < headerLength) {
+ // Only received a partial packet?
+ return -1;
+ }
+
+ switch (data[1]) {
+ case 200:
+ {
+ parseSR(s, data, headerLength);
+ break;
+ }
+
+ default:
+ {
+#if VERBOSE
+ printf("Unknown RTCP packet type %d of size %ld\n",
+ data[1], headerLength);
+
+ hexdump(data, headerLength);
+#endif
+ break;
+ }
+ }
+
+ data += headerLength;
+ size -= headerLength;
+ }
+
+ return OK;
+}
+
+status_t ARTPConnection::parseSR(
+ StreamInfo *s, const uint8_t *data, size_t size) {
+ size_t RC = data[0] & 0x1f;
+
+ if (size < (7 + RC * 6) * 4) {
+ // Packet too short for the minimal SR header.
+ return -1;
+ }
+
+ uint32_t id = u32at(&data[4]);
+ 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);
+#endif
+
+ sp<ARTPSource> source;
+ ssize_t index = mSources.indexOfKey(id);
+ if (index < 0) {
+ index = mSources.size();
+
+ source = new ARTPSource(
+ id, s->mSessionDesc, s->mIndex, s->mNotifyMsg);
+
+ mSources.add(id, source);
+ } else {
+ source = mSources.valueAt(index);
+ }
+
+ source->timeUpdate(rtpTime, ntpTime);
+
+ return 0;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/rtsp/ARTPConnection.h b/media/libstagefright/rtsp/ARTPConnection.h
new file mode 100644
index 0000000..c77e3a4
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPConnection.h
@@ -0,0 +1,83 @@
+/*
+ * 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_CONNECTION_H_
+
+#define A_RTP_CONNECTION_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+#include <utils/List.h>
+
+namespace android {
+
+struct ABuffer;
+struct ARTPSource;
+struct ASessionDescription;
+
+struct ARTPConnection : public AHandler {
+ ARTPConnection();
+
+ void addStream(
+ int rtpSocket, int rtcpSocket,
+ const sp<ASessionDescription> &sessionDesc, size_t index,
+ const sp<AMessage> ¬ify);
+
+ void removeStream(int rtpSocket, int rtcpSocket);
+
+ // Creates a pair of UDP datagram sockets bound to adjacent ports
+ // (the rtpSocket is bound to an even port, the rtcpSocket to the
+ // next higher port).
+ static void MakePortPair(
+ int *rtpSocket, int *rtcpSocket, unsigned *rtpPort);
+
+protected:
+ virtual ~ARTPConnection();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatAddStream,
+ kWhatRemoveStream,
+ kWhatPollStreams,
+ };
+
+ static const int64_t kSelectTimeoutUs;
+
+ struct StreamInfo;
+ List<StreamInfo> mStreams;
+
+ KeyedVector<uint32_t, sp<ARTPSource> > mSources;
+
+ bool mPollEventPending;
+
+ void onAddStream(const sp<AMessage> &msg);
+ void onRemoveStream(const sp<AMessage> &msg);
+ void onPollStreams();
+
+ 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);
+
+ void postPollEvent();
+
+ DISALLOW_EVIL_CONSTRUCTORS(ARTPConnection);
+};
+
+} // namespace android
+
+#endif // A_RTP_CONNECTION_H_
diff --git a/media/libstagefright/rtsp/ARTPSource.cpp b/media/libstagefright/rtsp/ARTPSource.cpp
new file mode 100644
index 0000000..f05daa8
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPSource.cpp
@@ -0,0 +1,250 @@
+/*
+ * 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 "ARTPSource.h"
+
+#include "AAVCAssembler.h"
+#include "AMPEG4AudioAssembler.h"
+#include "ASessionDescription.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#define VERBOSE 0
+
+namespace android {
+
+ARTPSource::ARTPSource(
+ uint32_t id,
+ const sp<ASessionDescription> &sessionDesc, size_t index,
+ const sp<AMessage> ¬ify)
+ : mID(id),
+ mHighestSeqNumber(0),
+ mNumBuffersReceived(0),
+ mNumTimes(0) {
+ unsigned long PT;
+ AString desc;
+ AString params;
+ sessionDesc->getFormatType(index, &PT, &desc, ¶ms);
+
+ if (!strncmp(desc.c_str(), "H264/", 5)) {
+ mAssembler = new AAVCAssembler(notify);
+ } else if (!strncmp(desc.c_str(), "MP4A-LATM", 9)) {
+ mAssembler = new AMPEG4AudioAssembler(notify);
+ } else {
+ TRESPASS();
+ }
+}
+
+static uint32_t AbsDiff(uint32_t seq1, uint32_t seq2) {
+ return seq1 > seq2 ? seq1 - seq2 : seq2 - seq1;
+}
+
+void ARTPSource::processRTPPacket(const sp<ABuffer> &buffer) {
+ if (queuePacket(buffer) && mNumTimes == 2 && mAssembler != NULL) {
+ mAssembler->onPacketReceived(this);
+ }
+
+ dump();
+}
+
+void ARTPSource::timeUpdate(uint32_t rtpTime, uint64_t ntpTime) {
+#if VERBOSE
+ LOG(VERBOSE) << "timeUpdate";
+#endif
+
+ if (mNumTimes == 2) {
+ mNTPTime[0] = mNTPTime[1];
+ mRTPTime[0] = mRTPTime[1];
+ mNumTimes = 1;
+ }
+ mNTPTime[mNumTimes] = ntpTime;
+ mRTPTime[mNumTimes++] = rtpTime;
+
+ if (mNumTimes == 2) {
+ for (List<sp<ABuffer> >::iterator it = mQueue.begin();
+ it != mQueue.end(); ++it) {
+ sp<AMessage> meta = (*it)->meta();
+
+ uint32_t rtpTime;
+ CHECK(meta->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ meta->setInt64("ntp-time", RTP2NTP(rtpTime));
+ }
+ }
+}
+
+bool ARTPSource::queuePacket(const sp<ABuffer> &buffer) {
+ uint32_t seqNum = (uint32_t)buffer->int32Data();
+
+ if (mNumTimes == 2) {
+ sp<AMessage> meta = buffer->meta();
+
+ uint32_t rtpTime;
+ CHECK(meta->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ meta->setInt64("ntp-time", RTP2NTP(rtpTime));
+ }
+
+ if (mNumBuffersReceived++ == 0) {
+ mHighestSeqNumber = seqNum;
+ mQueue.push_back(buffer);
+ return true;
+ }
+
+ // Only the lower 16-bit of the sequence numbers are transmitted,
+ // derive the high-order bits by choosing the candidate closest
+ // to the highest sequence number (extended to 32 bits) received so far.
+
+ uint32_t seq1 = seqNum | (mHighestSeqNumber & 0xffff0000);
+ uint32_t seq2 = seqNum | ((mHighestSeqNumber & 0xffff0000) + 0x10000);
+ uint32_t seq3 = seqNum | ((mHighestSeqNumber & 0xffff0000) - 0x10000);
+ uint32_t diff1 = AbsDiff(seq1, mHighestSeqNumber);
+ uint32_t diff2 = AbsDiff(seq2, mHighestSeqNumber);
+ uint32_t diff3 = AbsDiff(seq3, mHighestSeqNumber);
+
+ if (diff1 < diff2) {
+ if (diff1 < diff3) {
+ // diff1 < diff2 ^ diff1 < diff3
+ seqNum = seq1;
+ } else {
+ // diff3 <= diff1 < diff2
+ seqNum = seq3;
+ }
+ } else if (diff2 < diff3) {
+ // diff2 <= diff1 ^ diff2 < diff3
+ seqNum = seq2;
+ } else {
+ // diff3 <= diff2 <= diff1
+ seqNum = seq3;
+ }
+
+ if (seqNum > mHighestSeqNumber) {
+ mHighestSeqNumber = seqNum;
+ }
+
+ buffer->setInt32Data(seqNum);
+
+ List<sp<ABuffer> >::iterator it = mQueue.begin();
+ while (it != mQueue.end() && (uint32_t)(*it)->int32Data() < seqNum) {
+ ++it;
+ }
+
+ if (it != mQueue.end() && (uint32_t)(*it)->int32Data() == seqNum) {
+ LOG(WARNING) << "Discarding duplicate buffer";
+ return false;
+ }
+
+ mQueue.insert(it, buffer);
+
+ return true;
+}
+
+void ARTPSource::dump() const {
+ if ((mNumBuffersReceived % 128) != 0) {
+ return;
+ }
+
+#if 0
+ if (mAssembler == NULL) {
+ char tmp[20];
+ sprintf(tmp, "0x%08x", mID);
+
+ int32_t numMissing = 0;
+
+ if (!mQueue.empty()) {
+ List<sp<ABuffer> >::const_iterator it = mQueue.begin();
+ uint32_t expectedSeqNum = (uint32_t)(*it)->int32Data();
+ ++expectedSeqNum;
+ ++it;
+
+ for (; it != mQueue.end(); ++it) {
+ uint32_t seqNum = (uint32_t)(*it)->int32Data();
+ CHECK_GE(seqNum, expectedSeqNum);
+
+ if (seqNum != expectedSeqNum) {
+ numMissing += seqNum - expectedSeqNum;
+ expectedSeqNum = seqNum;
+ }
+
+ ++expectedSeqNum;
+ }
+ }
+
+ LOG(VERBOSE) << "[" << tmp << "] Missing " << numMissing
+ << " / " << (mNumBuffersReceived + numMissing) << " packets. ("
+ << (100.0 * numMissing / (mNumBuffersReceived + numMissing))
+ << " %%)";
+ }
+#endif
+
+#if 0
+ AString out;
+
+ out.append(tmp);
+ out.append(" [");
+
+ List<sp<ABuffer> >::const_iterator it = mQueue.begin();
+ while (it != mQueue.end()) {
+ uint32_t start = (uint32_t)(*it)->int32Data();
+
+ out.append(start);
+
+ ++it;
+ uint32_t expected = start + 1;
+
+ while (it != mQueue.end()) {
+ uint32_t seqNum = (uint32_t)(*it)->int32Data();
+
+ if (seqNum != expected) {
+ if (expected > start + 1) {
+ out.append("-");
+ out.append(expected - 1);
+ }
+ out.append(", ");
+ break;
+ }
+
+ ++it;
+ ++expected;
+ }
+
+ if (it == mQueue.end()) {
+ if (expected > start + 1) {
+ out.append("-");
+ out.append(expected - 1);
+ }
+ }
+ }
+
+ out.append("]");
+
+ LOG(VERBOSE) << out;
+#endif
+}
+
+uint64_t ARTPSource::RTP2NTP(uint32_t rtpTime) const {
+ CHECK_EQ(mNumTimes, 2u);
+
+ return mNTPTime[0] + (double)(mNTPTime[1] - mNTPTime[0])
+ * ((double)rtpTime - (double)mRTPTime[0])
+ / (double)(mRTPTime[1] - mRTPTime[0]);
+}
+
+} // namespace android
+
+
diff --git a/media/libstagefright/rtsp/ARTPSource.h b/media/libstagefright/rtsp/ARTPSource.h
new file mode 100644
index 0000000..b93cd56
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPSource.h
@@ -0,0 +1,67 @@
+/*
+ * 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_SOURCE_H_
+
+#define A_RTP_SOURCE_H_
+
+#include <stdint.h>
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/List.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ABuffer;
+struct AMessage;
+struct ARTPAssembler;
+struct ASessionDescription;
+
+struct ARTPSource : public RefBase {
+ ARTPSource(
+ uint32_t id,
+ const sp<ASessionDescription> &sessionDesc, size_t index,
+ const sp<AMessage> ¬ify);
+
+ void processRTPPacket(const sp<ABuffer> &buffer);
+ void timeUpdate(uint32_t rtpTime, uint64_t ntpTime);
+
+ List<sp<ABuffer> > *queue() { return &mQueue; }
+
+private:
+ uint32_t mID;
+ uint32_t mHighestSeqNumber;
+ int32_t mNumBuffersReceived;
+
+ List<sp<ABuffer> > mQueue;
+ sp<ARTPAssembler> mAssembler;
+
+ size_t mNumTimes;
+ uint64_t mNTPTime[2];
+ uint32_t mRTPTime[2];
+
+ uint64_t RTP2NTP(uint32_t rtpTime) const;
+
+ bool queuePacket(const sp<ABuffer> &buffer);
+ void dump() const;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ARTPSource);
+};
+
+} // namespace android
+
+#endif // A_RTP_SOURCE_H_
diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp
new file mode 100644
index 0000000..e9162c0
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTSPConnection.cpp
@@ -0,0 +1,549 @@
+/*
+ * 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 "ARTSPConnection.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <sys/socket.h>
+
+namespace android {
+
+// static
+const int64_t ARTSPConnection::kSelectTimeoutUs = 1000ll;
+
+ARTSPConnection::ARTSPConnection()
+ : mState(DISCONNECTED),
+ mSocket(-1),
+ mConnectionID(0),
+ mNextCSeq(0),
+ mReceiveResponseEventPending(false) {
+}
+
+ARTSPConnection::~ARTSPConnection() {
+ if (mSocket >= 0) {
+ LOG(ERROR) << "Connection is still open, closing the socket.";
+ close(mSocket);
+ mSocket = -1;
+ }
+}
+
+void ARTSPConnection::connect(const char *url, const sp<AMessage> &reply) {
+ sp<AMessage> msg = new AMessage(kWhatConnect, id());
+ msg->setString("url", url);
+ msg->setMessage("reply", reply);
+ msg->post();
+}
+
+void ARTSPConnection::disconnect(const sp<AMessage> &reply) {
+ sp<AMessage> msg = new AMessage(kWhatDisconnect, id());
+ msg->setMessage("reply", reply);
+ msg->post();
+}
+
+void ARTSPConnection::sendRequest(
+ const char *request, const sp<AMessage> &reply) {
+ sp<AMessage> msg = new AMessage(kWhatSendRequest, id());
+ msg->setString("request", request);
+ msg->setMessage("reply", reply);
+ msg->post();
+}
+
+void ARTSPConnection::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatConnect:
+ onConnect(msg);
+ break;
+
+ case kWhatDisconnect:
+ onDisconnect(msg);
+ break;
+
+ case kWhatCompleteConnection:
+ onCompleteConnection(msg);
+ break;
+
+ case kWhatSendRequest:
+ onSendRequest(msg);
+ break;
+
+ case kWhatReceiveResponse:
+ onReceiveResponse();
+ break;
+
+ default:
+ TRESPASS();
+ break;
+ }
+}
+
+// static
+bool ARTSPConnection::ParseURL(
+ const char *url, AString *host, unsigned *port, AString *path) {
+ host->clear();
+ *port = 0;
+ path->clear();
+
+ if (strncasecmp("rtsp://", url, 7)) {
+ return false;
+ }
+
+ const char *slashPos = strchr(&url[7], '/');
+
+ if (slashPos == NULL) {
+ host->setTo(&url[7]);
+ path->setTo("/");
+ } else {
+ host->setTo(&url[7], slashPos - &url[7]);
+ path->setTo(slashPos);
+ }
+
+ char *colonPos = strchr(host->c_str(), ':');
+
+ if (colonPos != NULL) {
+ unsigned long x;
+ if (!ParseSingleUnsignedLong(colonPos + 1, &x) || x >= 65536) {
+ return false;
+ }
+
+ *port = x;
+
+ size_t colonOffset = colonPos - host->c_str();
+ size_t trailing = host->size() - colonOffset;
+ host->erase(colonOffset, trailing);
+ } else {
+ *port = 554;
+ }
+
+ return true;
+}
+
+void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
+ ++mConnectionID;
+
+ if (mState != DISCONNECTED) {
+ close(mSocket);
+ mSocket = -1;
+
+ flushPendingRequests();
+ }
+
+ mState = CONNECTING;
+
+ mSocket = socket(AF_INET, SOCK_STREAM, 0);
+
+ // Make socket non-blocking.
+ int flags = fcntl(mSocket, F_GETFL, 0);
+ CHECK_NE(flags, -1);
+ CHECK_NE(fcntl(mSocket, F_SETFL, flags | O_NONBLOCK), -1);
+
+ AString url;
+ CHECK(msg->findString("url", &url));
+
+ AString host, path;
+ unsigned port;
+ CHECK(ParseURL(url.c_str(), &host, &port, &path));
+
+ struct hostent *ent = gethostbyname(host.c_str());
+ CHECK(ent != NULL);
+
+ struct sockaddr_in remote;
+ memset(remote.sin_zero, 0, sizeof(remote.sin_zero));
+ remote.sin_family = AF_INET;
+ remote.sin_addr.s_addr = *(in_addr_t *)ent->h_addr;
+ remote.sin_port = htons(port);
+
+ int err = ::connect(
+ mSocket, (const struct sockaddr *)&remote, sizeof(remote));
+
+ sp<AMessage> reply;
+ CHECK(msg->findMessage("reply", &reply));
+
+ reply->setInt32("server-ip", ntohl(remote.sin_addr.s_addr));
+
+ if (err < 0) {
+ if (errno == EINPROGRESS) {
+ sp<AMessage> msg = new AMessage(kWhatCompleteConnection, id());
+ msg->setMessage("reply", reply);
+ msg->setInt32("connection-id", mConnectionID);
+ msg->post();
+ return;
+ }
+
+ reply->setInt32("result", -errno);
+ mState = DISCONNECTED;
+
+ close(mSocket);
+ mSocket = -1;
+ } else {
+ reply->setInt32("result", OK);
+ mState = CONNECTED;
+ mNextCSeq = 1;
+
+ postReceiveReponseEvent();
+ }
+
+ reply->post();
+}
+
+void ARTSPConnection::onDisconnect(const sp<AMessage> &msg) {
+ if (mState == CONNECTED || mState == CONNECTING) {
+ close(mSocket);
+ mSocket = -1;
+
+ flushPendingRequests();
+ }
+
+ sp<AMessage> reply;
+ CHECK(msg->findMessage("reply", &reply));
+
+ reply->setInt32("result", OK);
+ mState = DISCONNECTED;
+
+ reply->post();
+}
+
+void ARTSPConnection::onCompleteConnection(const sp<AMessage> &msg) {
+ sp<AMessage> reply;
+ CHECK(msg->findMessage("reply", &reply));
+
+ int32_t connectionID;
+ CHECK(msg->findInt32("connection-id", &connectionID));
+
+ if ((connectionID != mConnectionID) || mState != CONNECTING) {
+ // While we were attempting to connect, the attempt was
+ // cancelled.
+ reply->setInt32("result", -ECONNABORTED);
+ reply->post();
+ return;
+ }
+
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = kSelectTimeoutUs;
+
+ fd_set ws;
+ FD_ZERO(&ws);
+ FD_SET(mSocket, &ws);
+
+ int res = select(mSocket + 1, NULL, &ws, NULL, &tv);
+ CHECK_GE(res, 0);
+
+ if (res == 0) {
+ // Timed out. Not yet connected.
+
+ msg->post();
+ return;
+ }
+
+ int err;
+ socklen_t optionLen = sizeof(err);
+ CHECK_EQ(getsockopt(mSocket, SOL_SOCKET, SO_ERROR, &err, &optionLen), 0);
+ CHECK_EQ(optionLen, (socklen_t)sizeof(err));
+
+ if (err != 0) {
+ LOG(ERROR) << "err = " << err << " (" << strerror(err) << ")";
+
+ reply->setInt32("result", -err);
+
+ mState = DISCONNECTED;
+ close(mSocket);
+ mSocket = -1;
+ } else {
+ reply->setInt32("result", OK);
+ mState = CONNECTED;
+ mNextCSeq = 1;
+
+ postReceiveReponseEvent();
+ }
+
+ reply->post();
+}
+
+void ARTSPConnection::onSendRequest(const sp<AMessage> &msg) {
+ sp<AMessage> reply;
+ CHECK(msg->findMessage("reply", &reply));
+
+ if (mState != CONNECTED) {
+ reply->setInt32("result", -ENOTCONN);
+ reply->post();
+ return;
+ }
+
+ AString request;
+ CHECK(msg->findString("request", &request));
+
+ // Find the boundary between headers and the body.
+ ssize_t i = request.find("\r\n\r\n");
+ CHECK_GE(i, 0);
+
+ int32_t cseq = mNextCSeq++;
+
+ AString cseqHeader = "CSeq: ";
+ cseqHeader.append(cseq);
+ cseqHeader.append("\r\n");
+
+ request.insert(cseqHeader, i + 2);
+
+ LOG(VERBOSE) << request;
+
+ size_t numBytesSent = 0;
+ while (numBytesSent < request.size()) {
+ ssize_t n =
+ send(mSocket, request.c_str() + numBytesSent,
+ request.size() - numBytesSent, 0);
+
+ if (n == 0) {
+ // Server closed the connection.
+ TRESPASS();
+ } else if (n < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+
+ TRESPASS();
+ }
+
+ numBytesSent += (size_t)n;
+ }
+
+ mPendingRequests.add(cseq, reply);
+}
+
+void ARTSPConnection::onReceiveResponse() {
+ mReceiveResponseEventPending = false;
+
+ if (mState != CONNECTED) {
+ return;
+ }
+
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = kSelectTimeoutUs;
+
+ fd_set rs;
+ FD_ZERO(&rs);
+ FD_SET(mSocket, &rs);
+
+ int res = select(mSocket + 1, &rs, NULL, NULL, &tv);
+ CHECK_GE(res, 0);
+
+ if (res == 1) {
+ if (!receiveRTSPReponse()) {
+ // Something horrible, irreparable has happened.
+ flushPendingRequests();
+ return;
+ }
+ }
+
+ postReceiveReponseEvent();
+}
+
+void ARTSPConnection::flushPendingRequests() {
+ for (size_t i = 0; i < mPendingRequests.size(); ++i) {
+ sp<AMessage> reply = mPendingRequests.valueAt(i);
+
+ reply->setInt32("result", -ECONNABORTED);
+ reply->post();
+ }
+
+ mPendingRequests.clear();
+}
+
+void ARTSPConnection::postReceiveReponseEvent() {
+ if (mReceiveResponseEventPending) {
+ return;
+ }
+
+ sp<AMessage> msg = new AMessage(kWhatReceiveResponse, id());
+ msg->post();
+
+ mReceiveResponseEventPending = true;
+}
+
+bool ARTSPConnection::receiveLine(AString *line) {
+ line->clear();
+
+ bool sawCR = false;
+ for (;;) {
+ char c;
+ ssize_t n = recv(mSocket, &c, 1, 0);
+ if (n == 0) {
+ // Server closed the connection.
+ return false;
+ } else if (n < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+
+ TRESPASS();
+ }
+
+ if (sawCR && c == '\n') {
+ line->erase(line->size() - 1, 1);
+ return true;
+ }
+
+ line->append(&c, 1);
+
+ sawCR = (c == '\r');
+ }
+}
+
+bool ARTSPConnection::receiveRTSPReponse() {
+ sp<ARTSPResponse> response = new ARTSPResponse;
+
+ if (!receiveLine(&response->mStatusLine)) {
+ return false;
+ }
+
+ LOG(INFO) << "status: " << response->mStatusLine;
+
+ ssize_t space1 = response->mStatusLine.find(" ");
+ if (space1 < 0) {
+ return false;
+ }
+ ssize_t space2 = response->mStatusLine.find(" ", space1 + 1);
+ if (space2 < 0) {
+ return false;
+ }
+
+ AString statusCodeStr(
+ response->mStatusLine, space1 + 1, space2 - space1 - 1);
+
+ if (!ParseSingleUnsignedLong(
+ statusCodeStr.c_str(), &response->mStatusCode)
+ || response->mStatusCode < 100 || response->mStatusCode > 999) {
+ return false;
+ }
+
+ AString line;
+ for (;;) {
+ if (!receiveLine(&line)) {
+ break;
+ }
+
+ if (line.empty()) {
+ break;
+ }
+
+ LOG(VERBOSE) << "line: " << line;
+
+ ssize_t colonPos = line.find(":");
+ if (colonPos < 0) {
+ // Malformed header line.
+ return false;
+ }
+
+ AString key(line, 0, colonPos);
+ key.trim();
+ key.tolower();
+
+ line.erase(0, colonPos + 1);
+ line.trim();
+
+ response->mHeaders.add(key, line);
+ }
+
+ unsigned long contentLength = 0;
+
+ ssize_t i = response->mHeaders.indexOfKey("content-length");
+
+ if (i >= 0) {
+ AString value = response->mHeaders.valueAt(i);
+ if (!ParseSingleUnsignedLong(value.c_str(), &contentLength)) {
+ return false;
+ }
+ }
+
+ if (contentLength > 0) {
+ response->mContent = new ABuffer(contentLength);
+
+ size_t numBytesRead = 0;
+ while (numBytesRead < contentLength) {
+ ssize_t n = recv(
+ mSocket, response->mContent->data() + numBytesRead,
+ contentLength - numBytesRead, 0);
+
+ if (n == 0) {
+ // Server closed the connection.
+ TRESPASS();
+ } else if (n < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+
+ TRESPASS();
+ }
+
+ numBytesRead += (size_t)n;
+ }
+ }
+
+ return notifyResponseListener(response);
+}
+
+// static
+bool ARTSPConnection::ParseSingleUnsignedLong(
+ const char *from, unsigned long *x) {
+ char *end;
+ *x = strtoul(from, &end, 10);
+
+ if (end == from || *end != '\0') {
+ return false;
+ }
+
+ return true;
+}
+
+bool ARTSPConnection::notifyResponseListener(
+ const sp<ARTSPResponse> &response) {
+ ssize_t i = response->mHeaders.indexOfKey("cseq");
+
+ if (i < 0) {
+ return true;
+ }
+
+ AString value = response->mHeaders.valueAt(i);
+
+ unsigned long cseq;
+ if (!ParseSingleUnsignedLong(value.c_str(), &cseq)) {
+ return false;
+ }
+
+ i = mPendingRequests.indexOfKey(cseq);
+
+ if (i < 0) {
+ // Unsolicited response?
+ TRESPASS();
+ }
+
+ sp<AMessage> reply = mPendingRequests.valueAt(i);
+ mPendingRequests.removeItemsAt(i);
+
+ reply->setInt32("result", OK);
+ reply->setObject("response", response);
+ reply->post();
+
+ return true;
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/ARTSPConnection.h b/media/libstagefright/rtsp/ARTSPConnection.h
new file mode 100644
index 0000000..3577a2f
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTSPConnection.h
@@ -0,0 +1,97 @@
+/*
+ * 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_RTSP_CONNECTION_H_
+
+#define A_RTSP_CONNECTION_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/foundation/AString.h>
+
+namespace android {
+
+struct ABuffer;
+
+struct ARTSPResponse : public RefBase {
+ unsigned long mStatusCode;
+ AString mStatusLine;
+ KeyedVector<AString,AString> mHeaders;
+ sp<ABuffer> mContent;
+};
+
+struct ARTSPConnection : public AHandler {
+ ARTSPConnection();
+
+ void connect(const char *url, const sp<AMessage> &reply);
+ void disconnect(const sp<AMessage> &reply);
+
+ void sendRequest(const char *request, const sp<AMessage> &reply);
+
+protected:
+ virtual ~ARTSPConnection();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum State {
+ DISCONNECTED,
+ CONNECTING,
+ CONNECTED,
+ };
+
+ enum {
+ kWhatConnect = 'conn',
+ kWhatDisconnect = 'disc',
+ kWhatCompleteConnection = 'comc',
+ kWhatSendRequest = 'sreq',
+ kWhatReceiveResponse = 'rres',
+ };
+
+ static const int64_t kSelectTimeoutUs;
+
+ State mState;
+ int mSocket;
+ int32_t mConnectionID;
+ int32_t mNextCSeq;
+ bool mReceiveResponseEventPending;
+
+ KeyedVector<int32_t, sp<AMessage> > mPendingRequests;
+
+ void onConnect(const sp<AMessage> &msg);
+ void onDisconnect(const sp<AMessage> &msg);
+ void onCompleteConnection(const sp<AMessage> &msg);
+ void onSendRequest(const sp<AMessage> &msg);
+ void onReceiveResponse();
+
+ void flushPendingRequests();
+ void postReceiveReponseEvent();
+
+ // Return false iff something went unrecoverably wrong.
+ bool receiveRTSPReponse();
+ bool receiveLine(AString *line);
+ bool notifyResponseListener(const sp<ARTSPResponse> &response);
+
+ static bool ParseURL(
+ const char *url, AString *host, unsigned *port, AString *path);
+
+ static bool ParseSingleUnsignedLong(
+ const char *from, unsigned long *x);
+
+ DISALLOW_EVIL_CONSTRUCTORS(ARTSPConnection);
+};
+
+} // namespace android
+
+#endif // A_RTSP_CONNECTION_H_
diff --git a/media/libstagefright/rtsp/ARTSPController.cpp b/media/libstagefright/rtsp/ARTSPController.cpp
new file mode 100644
index 0000000..7b87d42
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTSPController.cpp
@@ -0,0 +1,75 @@
+/*
+ * 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 "ARTSPController.h"
+
+#include "MyHandler.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+ARTSPController::ARTSPController(const sp<ALooper> &looper)
+ : mLooper(looper) {
+}
+
+ARTSPController::~ARTSPController() {
+}
+
+status_t ARTSPController::connect(const char *url) {
+ if (mHandler != NULL) {
+ return ERROR_ALREADY_CONNECTED;
+ }
+
+ mHandler = new MyHandler(url, mLooper);
+ sleep(10);
+
+ return OK;
+}
+
+void ARTSPController::disconnect() {
+ if (mHandler == NULL) {
+ return;
+ }
+
+ mHandler.clear();
+}
+
+size_t ARTSPController::countTracks() {
+ if (mHandler == NULL) {
+ return 0;
+ }
+
+ return mHandler->countTracks();
+}
+
+sp<MediaSource> ARTSPController::getTrack(size_t index) {
+ CHECK(mHandler != NULL);
+
+ return mHandler->getPacketSource(index);
+}
+
+sp<MetaData> ARTSPController::getTrackMetaData(
+ size_t index, uint32_t flags) {
+ CHECK(mHandler != NULL);
+
+ return mHandler->getPacketSource(index)->getFormat();
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/ASessionDescription.cpp b/media/libstagefright/rtsp/ASessionDescription.cpp
new file mode 100644
index 0000000..ca4c55e
--- /dev/null
+++ b/media/libstagefright/rtsp/ASessionDescription.cpp
@@ -0,0 +1,270 @@
+/*
+ * 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 "ASessionDescription.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AString.h>
+
+#include <stdlib.h>
+
+namespace android {
+
+ASessionDescription::ASessionDescription()
+ : mIsValid(false) {
+}
+
+ASessionDescription::~ASessionDescription() {
+}
+
+bool ASessionDescription::setTo(const void *data, size_t size) {
+ mIsValid = parse(data, size);
+
+ if (!mIsValid) {
+ mTracks.clear();
+ mFormats.clear();
+ }
+
+ return mIsValid;
+}
+
+bool ASessionDescription::parse(const void *data, size_t size) {
+ mTracks.clear();
+ mFormats.clear();
+
+ mTracks.push(Attribs());
+ mFormats.push(AString("[root]"));
+
+ AString desc((const char *)data, size);
+ LOG(VERBOSE) << desc;
+
+ size_t i = 0;
+ for (;;) {
+ ssize_t eolPos = desc.find("\r\n", i);
+ if (eolPos < 0) {
+ break;
+ }
+
+ AString line(desc, i, eolPos - i);
+
+ if (line.size() < 2 || line.c_str()[1] != '=') {
+ return false;
+ }
+
+ switch (line.c_str()[0]) {
+ case 'v':
+ {
+ if (strcmp(line.c_str(), "v=0")) {
+ return false;
+ }
+ break;
+ }
+
+ case 'a':
+ case 'b':
+ {
+ AString key, value;
+
+ ssize_t colonPos = line.find(":", 2);
+ if (colonPos < 0) {
+ key = line;
+ } else {
+ key.setTo(line, 0, colonPos);
+
+ if (key == "a=fmtp" || key == "a=rtpmap"
+ || key == "a=framesize") {
+ ssize_t spacePos = line.find(" ", colonPos + 1);
+ if (spacePos < 0) {
+ return false;
+ }
+
+ key.setTo(line, 0, spacePos);
+
+ colonPos = spacePos;
+ }
+
+ value.setTo(line, colonPos + 1, line.size() - colonPos - 1);
+ }
+
+ key.trim();
+ value.trim();
+
+ LOG(VERBOSE) << "adding '" << key << "' => '" << value << "'";
+
+ mTracks.editItemAt(mTracks.size() - 1).add(key, value);
+ break;
+ }
+
+ case 'm':
+ {
+ LOG(VERBOSE) << "new section '" << AString(line, 2, line.size() - 2) << "'";
+
+ mTracks.push(Attribs());
+ mFormats.push(AString(line, 2, line.size() - 2));
+ break;
+ }
+ }
+
+ i = eolPos + 2;
+ }
+
+ return true;
+}
+
+bool ASessionDescription::isValid() const {
+ return mIsValid;
+}
+
+size_t ASessionDescription::countTracks() const {
+ return mTracks.size();
+}
+
+void ASessionDescription::getFormat(size_t index, AString *value) const {
+ CHECK_GE(index, 0u);
+ CHECK_LT(index, mTracks.size());
+
+ *value = mFormats.itemAt(index);
+}
+
+bool ASessionDescription::findAttribute(
+ size_t index, const char *key, AString *value) const {
+ CHECK_GE(index, 0u);
+ CHECK_LT(index, mTracks.size());
+
+ value->clear();
+
+ const Attribs &track = mTracks.itemAt(index);
+ ssize_t i = track.indexOfKey(AString(key));
+
+ if (i < 0) {
+ return false;
+ }
+
+ *value = track.valueAt(i);
+
+ return true;
+}
+
+void ASessionDescription::getFormatType(
+ size_t index, unsigned long *PT,
+ AString *desc, AString *params) const {
+ AString format;
+ getFormat(index, &format);
+
+ char *lastSpacePos = strrchr(format.c_str(), ' ');
+ CHECK(lastSpacePos != NULL);
+
+ char *end;
+ unsigned long x = strtoul(lastSpacePos + 1, &end, 10);
+ CHECK_GT(end, lastSpacePos + 1);
+ CHECK_EQ(*end, '\0');
+
+ *PT = x;
+
+ char key[20];
+ sprintf(key, "a=rtpmap:%lu", x);
+
+ CHECK(findAttribute(index, key, desc));
+
+ sprintf(key, "a=fmtp:%lu", x);
+ if (!findAttribute(index, key, params)) {
+ params->clear();
+ }
+}
+
+void ASessionDescription::getDimensions(
+ size_t index, unsigned long PT,
+ int32_t *width, int32_t *height) const {
+ char key[20];
+ sprintf(key, "a=framesize:%lu", PT);
+ AString value;
+ CHECK(findAttribute(index, key, &value));
+
+ const char *s = value.c_str();
+ char *end;
+ *width = strtoul(s, &end, 10);
+ CHECK_GT(end, s);
+ CHECK_EQ(*end, '-');
+
+ s = end + 1;
+ *height = strtoul(s, &end, 10);
+ CHECK_GT(end, s);
+ CHECK_EQ(*end, '\0');
+}
+
+bool ASessionDescription::getDurationUs(int64_t *durationUs) const {
+ *durationUs = 0;
+
+ CHECK(mIsValid);
+
+ AString value;
+ if (!findAttribute(0, "a=range", &value)) {
+ return false;
+ }
+
+ if (value == "npt=now-") {
+ return false;
+ }
+
+ if (strncmp(value.c_str(), "npt=", 4)) {
+ return false;
+ }
+
+ const char *s = value.c_str() + 4;
+ char *end;
+ double from = strtod(s, &end);
+ CHECK_GT(end, s);
+ CHECK_EQ(*end, '-');
+
+ s = end + 1;
+ double to = strtod(s, &end);
+ CHECK_GT(end, s);
+ CHECK_EQ(*end, '\0');
+
+ CHECK_GE(to, from);
+
+ *durationUs = (int64_t)((to - from) * 1E6);
+
+ return true;
+}
+
+// static
+void ASessionDescription::ParseFormatDesc(
+ const char *desc, int32_t *timescale, int32_t *numChannels) {
+ const char *slash1 = strchr(desc, '/');
+ CHECK(slash1 != NULL);
+
+ const char *s = slash1 + 1;
+ char *end;
+ unsigned long x = strtoul(s, &end, 10);
+ CHECK_GT(end, s);
+ CHECK(*end == '\0' || *end == '/');
+
+ *timescale = x;
+ *numChannels = 1;
+
+ if (*end == '/') {
+ s = end + 1;
+ unsigned long x = strtoul(s, &end, 10);
+ CHECK_GT(end, s);
+ CHECK_EQ(*end, '\0');
+
+ *numChannels = x;
+ }
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/rtsp/ASessionDescription.h b/media/libstagefright/rtsp/ASessionDescription.h
new file mode 100644
index 0000000..b26980f
--- /dev/null
+++ b/media/libstagefright/rtsp/ASessionDescription.h
@@ -0,0 +1,75 @@
+/*
+ * 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_SESSION_DESCRIPTION_H_
+
+#define A_SESSION_DESCRIPTION_H_
+
+#include <sys/types.h>
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct AString;
+
+struct ASessionDescription : public RefBase {
+ ASessionDescription();
+
+ bool setTo(const void *data, size_t size);
+ bool isValid() const;
+
+ // Actually, 1 + number of tracks, as index 0 is reserved for the
+ // session description root-level attributes.
+ size_t countTracks() const;
+ void getFormat(size_t index, AString *value) const;
+
+ void getFormatType(
+ size_t index, unsigned long *PT,
+ AString *desc, AString *params) const;
+
+ void getDimensions(
+ size_t index, unsigned long PT,
+ int32_t *width, int32_t *height) const;
+
+ bool getDurationUs(int64_t *durationUs) const;
+
+ static void ParseFormatDesc(
+ const char *desc, int32_t *timescale, int32_t *numChannels);
+
+ bool findAttribute(size_t index, const char *key, AString *value) const;
+
+protected:
+ virtual ~ASessionDescription();
+
+private:
+ typedef KeyedVector<AString,AString> Attribs;
+
+ bool mIsValid;
+ Vector<Attribs> mTracks;
+ Vector<AString> mFormats;
+
+ bool parse(const void *data, size_t size);
+
+ DISALLOW_EVIL_CONSTRUCTORS(ASessionDescription);
+};
+
+} // namespace android
+
+#endif // A_SESSION_DESCRIPTION_H_
diff --git a/media/libstagefright/rtsp/Android.mk b/media/libstagefright/rtsp/Android.mk
new file mode 100644
index 0000000..4608fa0
--- /dev/null
+++ b/media/libstagefright/rtsp/Android.mk
@@ -0,0 +1,28 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ ARTSPController.cpp \
+ AAVCAssembler.cpp \
+ AMPEG4AudioAssembler.cpp \
+ APacketSource.cpp \
+ ARTPAssembler.cpp \
+ ARTPConnection.cpp \
+ ARTPSource.cpp \
+ ARTSPConnection.cpp \
+ ASessionDescription.cpp \
+
+LOCAL_C_INCLUDES:= \
+ $(JNI_H_INCLUDE) \
+ $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
+ $(TOP)/frameworks/base/media/libstagefright/include \
+
+LOCAL_MODULE:= libstagefright_rtsp
+
+ifeq ($(TARGET_ARCH),arm)
+ LOCAL_CFLAGS += -Wno-psabi
+endif
+
+include $(BUILD_STATIC_LIBRARY)
+
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
new file mode 100644
index 0000000..74bb798
--- /dev/null
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -0,0 +1,442 @@
+/*
+ * 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 MY_HANDLER_H_
+
+#define MY_HANDLER_H_
+
+#include "APacketSource.h"
+#include "ARTPConnection.h"
+#include "ARTSPConnection.h"
+#include "ASessionDescription.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+struct MyHandler : public AHandler {
+ MyHandler(const char *url, const sp<ALooper> &looper)
+ : mLooper(looper),
+ mConn(new ARTSPConnection),
+ mRTPConn(new ARTPConnection),
+ mSessionURL(url),
+ mSetupTracksSuccessful(false),
+ mFirstAccessUnit(true),
+ mFirstAccessUnitNTP(-1) {
+ mLooper->registerHandler(this);
+ mLooper->registerHandler(mConn);
+ mLooper->registerHandler(mRTPConn);
+ sp<AMessage> reply = new AMessage('conn', id());
+ mConn->connect(mSessionURL.c_str(), reply);
+ }
+
+ virtual void onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case 'conn':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "connection request completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ if (result == OK) {
+ AString request;
+ request = "DESCRIBE ";
+ request.append(mSessionURL);
+ request.append(" RTSP/1.0\r\n");
+ request.append("Accept: application/sdp\r\n");
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('desc', id());
+ mConn->sendRequest(request.c_str(), reply);
+ }
+ break;
+ }
+
+ case 'disc':
+ {
+ LOG(INFO) << "disconnect completed";
+
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ case 'desc':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "DESCRIBE completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ if (result == OK) {
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response =
+ static_cast<ARTSPResponse *>(obj.get());
+
+ if (response->mStatusCode == 302) {
+ ssize_t i = response->mHeaders.indexOfKey("location");
+ CHECK_GE(i, 0);
+
+ mSessionURL = response->mHeaders.valueAt(i);
+
+ AString request;
+ request = "DESCRIBE ";
+ request.append(mSessionURL);
+ request.append(" RTSP/1.0\r\n");
+ request.append("Accept: application/sdp\r\n");
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('desc', id());
+ mConn->sendRequest(request.c_str(), reply);
+ break;
+ }
+
+ CHECK_EQ(response->mStatusCode, 200u);
+
+ mSessionDesc = new ASessionDescription;
+
+ mSessionDesc->setTo(
+ response->mContent->data(),
+ response->mContent->size());
+
+ CHECK(mSessionDesc->isValid());
+
+ ssize_t i = response->mHeaders.indexOfKey("content-base");
+ if (i >= 0) {
+ mBaseURL = response->mHeaders.valueAt(i);
+ } else {
+ i = response->mHeaders.indexOfKey("content-location");
+ if (i >= 0) {
+ mBaseURL = response->mHeaders.valueAt(i);
+ } else {
+ mBaseURL = mSessionURL;
+ }
+ }
+
+ CHECK_GT(mSessionDesc->countTracks(), 1u);
+ setupTrack(1);
+ } else {
+ sp<AMessage> reply = new AMessage('disc', id());
+ mConn->disconnect(reply);
+ }
+ break;
+ }
+
+ case 'setu':
+ {
+ size_t index;
+ CHECK(msg->findSize("index", &index));
+
+ size_t trackIndex;
+ CHECK(msg->findSize("track-index", &trackIndex));
+
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "SETUP(" << index << ") completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ TrackInfo *track = &mTracks.editItemAt(trackIndex);
+
+ if (result == OK) {
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response =
+ static_cast<ARTSPResponse *>(obj.get());
+
+ CHECK_EQ(response->mStatusCode, 200u);
+
+ ssize_t i = response->mHeaders.indexOfKey("session");
+ CHECK_GE(i, 0);
+
+ if (index == 1) {
+ mSessionID = response->mHeaders.valueAt(i);
+ i = mSessionID.find(";");
+ if (i >= 0) {
+ // Remove options, i.e. ";timeout=90"
+ mSessionID.erase(i, mSessionID.size() - i);
+ }
+ }
+
+ sp<AMessage> notify = new AMessage('accu', id());
+ notify->setSize("track-index", trackIndex);
+
+ mRTPConn->addStream(
+ track->mRTPSocket, track->mRTCPSocket,
+ 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) {
+ AString request = "PLAY ";
+ request.append(mSessionURL);
+ request.append(" RTSP/1.0\r\n");
+
+ request.append("Session: ");
+ request.append(mSessionID);
+ request.append("\r\n");
+
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('play', id());
+ mConn->sendRequest(request.c_str(), reply);
+ } else {
+ sp<AMessage> reply = new AMessage('disc', id());
+ mConn->disconnect(reply);
+ }
+ break;
+ }
+
+ case 'play':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "PLAY completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ if (result == OK) {
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response =
+ static_cast<ARTSPResponse *>(obj.get());
+
+ CHECK_EQ(response->mStatusCode, 200u);
+
+ sp<AMessage> msg = new AMessage('abor', id());
+ msg->post(60000000ll);
+ } else {
+ sp<AMessage> reply = new AMessage('disc', id());
+ mConn->disconnect(reply);
+ }
+
+ break;
+ }
+
+ case 'abor':
+ {
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ mTracks.editItemAt(i).mPacketSource->signalEOS(
+ ERROR_END_OF_STREAM);
+ }
+
+ sp<AMessage> reply = new AMessage('tear', id());
+
+ AString request;
+ request = "TEARDOWN ";
+
+ // XXX should use aggregate url from SDP here...
+ request.append(mSessionURL);
+ request.append(" RTSP/1.0\r\n");
+
+ request.append("Session: ");
+ request.append(mSessionID);
+ request.append("\r\n");
+
+ request.append("\r\n");
+
+ mConn->sendRequest(request.c_str(), reply);
+ break;
+ }
+
+ case 'tear':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "TEARDOWN completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ sp<AMessage> reply = new AMessage('disc', id());
+ mConn->disconnect(reply);
+ break;
+ }
+
+ case 'quit':
+ {
+ mLooper->stop();
+ break;
+ }
+
+ case 'accu':
+ {
+ size_t trackIndex;
+ CHECK(msg->findSize("track-index", &trackIndex));
+
+ 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 (mFirstAccessUnit) {
+ mFirstAccessUnit = false;
+ mFirstAccessUnitNTP = ntpTime;
+ }
+ if (ntpTime > mFirstAccessUnitNTP) {
+ ntpTime -= mFirstAccessUnitNTP;
+ } else {
+ ntpTime = 0;
+ }
+
+ accessUnit->meta()->setInt64("ntp-time", ntpTime);
+
+ TrackInfo *track = &mTracks.editItemAt(trackIndex);
+ track->mPacketSource->queueAccessUnit(accessUnit);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ break;
+ }
+ }
+
+ sp<APacketSource> getPacketSource(size_t index) {
+ CHECK_GE(index, 0u);
+ CHECK_LT(index, mTracks.size());
+
+ return mTracks.editItemAt(index).mPacketSource;
+ }
+
+ size_t countTracks() const {
+ return mTracks.size();
+ }
+
+private:
+ sp<ALooper> mLooper;
+ sp<ARTSPConnection> mConn;
+ sp<ARTPConnection> mRTPConn;
+ sp<ASessionDescription> mSessionDesc;
+ AString mSessionURL;
+ AString mBaseURL;
+ AString mSessionID;
+ bool mSetupTracksSuccessful;
+ bool mFirstAccessUnit;
+ uint64_t mFirstAccessUnitNTP;
+
+ struct TrackInfo {
+ int mRTPSocket;
+ int mRTCPSocket;
+
+ sp<APacketSource> mPacketSource;
+ };
+ Vector<TrackInfo> mTracks;
+
+ void setupTrack(size_t index) {
+ AString url;
+ CHECK(mSessionDesc->findAttribute(index, "a=control", &url));
+
+ AString trackURL;
+ CHECK(MakeURL(mBaseURL.c_str(), url.c_str(), &trackURL));
+
+ mTracks.push(TrackInfo());
+ TrackInfo *info = &mTracks.editItemAt(mTracks.size() - 1);
+
+ unsigned rtpPort;
+ ARTPConnection::MakePortPair(
+ &info->mRTPSocket, &info->mRTCPSocket, &rtpPort);
+
+ AString request = "SETUP ";
+ request.append(trackURL);
+ request.append(" RTSP/1.0\r\n");
+
+ request.append("Transport: RTP/AVP/UDP;unicast;client_port=");
+ request.append(rtpPort);
+ request.append("-");
+ request.append(rtpPort + 1);
+ request.append("\r\n");
+
+ if (index > 1) {
+ request.append("Session: ");
+ request.append(mSessionID);
+ request.append("\r\n");
+ }
+
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('setu', id());
+ reply->setSize("index", index);
+ reply->setSize("track-index", mTracks.size() - 1);
+ mConn->sendRequest(request.c_str(), reply);
+ }
+
+ static bool MakeURL(const char *baseURL, const char *url, AString *out) {
+ out->clear();
+
+ if (strncasecmp("rtsp://", baseURL, 7)) {
+ // Base URL must be absolute
+ return false;
+ }
+
+ if (!strncasecmp("rtsp://", url, 7)) {
+ // "url" is already an absolute URL, ignore base URL.
+ out->setTo(url);
+ return true;
+ }
+
+ size_t n = strlen(baseURL);
+ if (baseURL[n - 1] == '/') {
+ out->setTo(baseURL);
+ out->append(url);
+ } else {
+ char *slashPos = strrchr(baseURL, '/');
+
+ if (slashPos > &baseURL[6]) {
+ out->setTo(baseURL, slashPos - baseURL);
+ } else {
+ out->setTo(baseURL);
+ }
+
+ out->append("/");
+ out->append(url);
+ }
+
+ return true;
+ }
+
+ DISALLOW_EVIL_CONSTRUCTORS(MyHandler);
+};
+
+} // namespace android
+
+#endif // MY_HANDLER_H_
diff --git a/media/libstagefright/rtsp/MyTransmitter.h b/media/libstagefright/rtsp/MyTransmitter.h
new file mode 100644
index 0000000..009a3b1
--- /dev/null
+++ b/media/libstagefright/rtsp/MyTransmitter.h
@@ -0,0 +1,981 @@
+/*
+ * 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 MY_TRANSMITTER_H_
+
+#define MY_TRANSMITTER_H_
+
+#include "ARTPConnection.h"
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include <openssl/md5.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/base64.h>
+#include <media/stagefright/foundation/hexdump.h>
+
+#ifdef ANDROID
+#include "VideoSource.h"
+
+#include <media/stagefright/OMXClient.h>
+#include <media/stagefright/OMXCodec.h>
+#endif
+
+namespace android {
+
+#define TRACK_SUFFIX "trackid=1"
+#define PT 96
+#define PT_STR "96"
+
+#define USERNAME "bcast"
+#define PASSWORD "test"
+
+static int uniformRand(int limit) {
+ return ((double)rand() * limit) / RAND_MAX;
+}
+
+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 (colonPos == NULL) {
+ return false;
+ }
+
+ s = colonPos + 1;
+ }
+}
+
+struct MyTransmitter : public AHandler {
+ MyTransmitter(const char *url, const sp<ALooper> &looper)
+ : mServerURL(url),
+ mLooper(looper),
+ mConn(new ARTSPConnection),
+ mConnected(false),
+ mAuthType(NONE),
+ mRTPSocket(-1),
+ mRTCPSocket(-1),
+ mSourceID(rand()),
+ mSeqNo(uniformRand(65536)),
+ mRTPTimeBase(rand()),
+ mNumSamplesSent(0),
+ mNumRTPSent(0),
+ mNumRTPOctetsSent(0),
+ mLastRTPTime(0),
+ mLastNTPTime(0) {
+ mStreamURL = mServerURL;
+ mStreamURL.append("/bazong.sdp");
+
+ mTrackURL = mStreamURL;
+ mTrackURL.append("/");
+ mTrackURL.append(TRACK_SUFFIX);
+
+ mLooper->registerHandler(this);
+ mLooper->registerHandler(mConn);
+
+ sp<AMessage> reply = new AMessage('conn', id());
+ mConn->connect(mServerURL.c_str(), reply);
+
+#ifdef ANDROID
+ int width = 640;
+ int height = 480;
+
+ sp<MediaSource> source = new VideoSource(width, height);
+
+ sp<MetaData> encMeta = new MetaData;
+ encMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+ encMeta->setInt32(kKeyWidth, width);
+ encMeta->setInt32(kKeyHeight, height);
+
+ OMXClient client;
+ client.connect();
+
+ mEncoder = OMXCodec::Create(
+ client.interface(), encMeta,
+ true /* createEncoder */, source);
+
+ mEncoder->start();
+
+ MediaBuffer *buffer;
+ CHECK_EQ(mEncoder->read(&buffer), (status_t)OK);
+ CHECK(buffer != NULL);
+
+ makeH264SPropParamSets(buffer);
+
+ buffer->release();
+ buffer = NULL;
+#endif
+ }
+
+ uint64_t ntpTime() {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+
+ uint64_t nowUs = tv.tv_sec * 1000000ll + tv.tv_usec;
+
+ 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 issueAnnounce() {
+ AString sdp;
+ sdp = "v=0\r\n";
+
+ sdp.append("o=- ");
+
+ uint64_t ntp = ntpTime();
+ 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 with ANNOUNCE\r\n"
+ "c=IN IP4 ");
+
+ struct in_addr addr;
+ addr.s_addr = htonl(mServerIP);
+
+ sdp.append(inet_ntoa(addr));
+
+ sdp.append(
+ "\r\n"
+ "t=0 0\r\n"
+ "a=range:npt=now-\r\n");
+
+#ifdef ANDROID
+ sp<MetaData> meta = mEncoder->getFormat();
+ int32_t width, height;
+ CHECK(meta->findInt32(kKeyWidth, &width));
+ CHECK(meta->findInt32(kKeyHeight, &height));
+
+ sdp.append(
+ "m=video 0 RTP/AVP " PT_STR "\r\n"
+ "b=AS 320000\r\n"
+ "a=rtpmap:" PT_STR " H264/90000\r\n");
+
+ 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");
+
+ sdp.append(
+ "a=fmtp:" PT_STR " profile-level-id=42C015;sprop-parameter-sets=");
+
+ sdp.append(mSeqParamSet);
+ sdp.append(",");
+ sdp.append(mPicParamSet);
+ sdp.append(";packetization-mode=1\r\n");
+#else
+ sdp.append(
+ "m=audio 0 RTP/AVP " PT_STR "\r\n"
+ "a=rtpmap:" PT_STR " L8/8000/1\r\n");
+#endif
+
+ sdp.append("a=control:" TRACK_SUFFIX "\r\n");
+
+ AString request;
+ request.append("ANNOUNCE ");
+ request.append(mStreamURL);
+ request.append(" RTSP/1.0\r\n");
+
+ addAuthentication(&request, "ANNOUNCE", mStreamURL.c_str());
+
+ request.append("Content-Type: application/sdp\r\n");
+ request.append("Content-Length: ");
+ request.append(sdp.size());
+ request.append("\r\n");
+
+ request.append("\r\n");
+ request.append(sdp);
+
+ sp<AMessage> reply = new AMessage('anno', id());
+ mConn->sendRequest(request.c_str(), reply);
+ }
+
+ void H(const AString &s, AString *out) {
+ out->clear();
+
+ MD5_CTX m;
+ MD5_Init(&m);
+ MD5_Update(&m, s.c_str(), s.size());
+
+ uint8_t key[16];
+ MD5_Final(key, &m);
+
+ for (size_t i = 0; i < 16; ++i) {
+ char nibble = key[i] >> 4;
+ if (nibble <= 9) {
+ nibble += '0';
+ } else {
+ nibble += 'a' - 10;
+ }
+ out->append(&nibble, 1);
+
+ nibble = key[i] & 0x0f;
+ if (nibble <= 9) {
+ nibble += '0';
+ } else {
+ nibble += 'a' - 10;
+ }
+ out->append(&nibble, 1);
+ }
+ }
+
+ void authenticate(const sp<ARTSPResponse> &response) {
+ ssize_t i = response->mHeaders.indexOfKey("www-authenticate");
+ CHECK_GE(i, 0);
+
+ AString value = response->mHeaders.valueAt(i);
+
+ if (!strncmp(value.c_str(), "Basic", 5)) {
+ mAuthType = BASIC;
+ } else {
+ CHECK(!strncmp(value.c_str(), "Digest", 6));
+ mAuthType = DIGEST;
+
+ i = value.find("nonce=");
+ CHECK_GE(i, 0);
+ CHECK_EQ(value.c_str()[i + 6], '\"');
+ ssize_t j = value.find("\"", i + 7);
+ CHECK_GE(j, 0);
+
+ mNonce.setTo(value, i + 7, j - i - 7);
+ }
+
+ issueAnnounce();
+ }
+
+ void addAuthentication(
+ AString *request, const char *method, const char *url) {
+ if (mAuthType == NONE) {
+ return;
+ }
+
+ if (mAuthType == BASIC) {
+ request->append("Authorization: Basic YmNhc3Q6dGVzdAo=\r\n");
+ return;
+ }
+
+ CHECK_EQ((int)mAuthType, (int)DIGEST);
+
+ AString A1;
+ A1.append(USERNAME);
+ A1.append(":");
+ A1.append("Streaming Server");
+ A1.append(":");
+ A1.append(PASSWORD);
+
+ AString A2;
+ A2.append(method);
+ A2.append(":");
+ A2.append(url);
+
+ AString HA1, HA2;
+ H(A1, &HA1);
+ H(A2, &HA2);
+
+ AString tmp;
+ tmp.append(HA1);
+ tmp.append(":");
+ tmp.append(mNonce);
+ tmp.append(":");
+ tmp.append(HA2);
+
+ AString digest;
+ H(tmp, &digest);
+
+ request->append("Authorization: Digest ");
+ request->append("nonce=\"");
+ request->append(mNonce);
+ request->append("\", ");
+ request->append("username=\"" USERNAME "\", ");
+ request->append("uri=\"");
+ request->append(url);
+ request->append("\", ");
+ request->append("response=\"");
+ request->append(digest);
+ request->append("\"");
+ request->append("\r\n");
+ }
+
+ virtual void onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case 'conn':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "connection request completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ if (result != OK) {
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ mConnected = true;
+
+ CHECK(msg->findInt32("server-ip", (int32_t *)&mServerIP));
+
+ issueAnnounce();
+ break;
+ }
+
+ case 'anno':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "ANNOUNCE completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response;
+
+ if (result == OK) {
+ response = static_cast<ARTSPResponse *>(obj.get());
+ CHECK(response != NULL);
+
+ if (response->mStatusCode == 401) {
+ if (mAuthType != NONE) {
+ LOG(INFO) << "FAILED to authenticate";
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ authenticate(response);
+ break;
+ }
+ }
+
+ if (result != OK || response->mStatusCode != 200) {
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ unsigned rtpPort;
+ ARTPConnection::MakePortPair(&mRTPSocket, &mRTCPSocket, &rtpPort);
+
+ // (new AMessage('poll', id()))->post();
+
+ AString request;
+ request.append("SETUP ");
+ request.append(mTrackURL);
+ request.append(" RTSP/1.0\r\n");
+
+ addAuthentication(&request, "SETUP", mTrackURL.c_str());
+
+ request.append("Transport: RTP/AVP;unicast;client_port=");
+ request.append(rtpPort);
+ request.append("-");
+ request.append(rtpPort + 1);
+ request.append(";mode=record\r\n");
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('setu', id());
+ mConn->sendRequest(request.c_str(), reply);
+ break;
+ }
+
+#if 0
+ case 'poll':
+ {
+ fd_set rs;
+ FD_ZERO(&rs);
+ FD_SET(mRTCPSocket, &rs);
+
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ int res = select(mRTCPSocket + 1, &rs, NULL, NULL, &tv);
+
+ if (res == 1) {
+ sp<ABuffer> buffer = new ABuffer(65536);
+ ssize_t n = recv(mRTCPSocket, buffer->data(), buffer->size(), 0);
+
+ if (n <= 0) {
+ LOG(ERROR) << "recv returned " << n;
+ } else {
+ LOG(INFO) << "recv returned " << n << " bytes of data.";
+
+ hexdump(buffer->data(), n);
+ }
+ }
+
+ msg->post(50000);
+ break;
+ }
+#endif
+
+ case 'setu':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "SETUP completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response;
+
+ if (result == OK) {
+ response = static_cast<ARTSPResponse *>(obj.get());
+ CHECK(response != NULL);
+ }
+
+ if (result != OK || response->mStatusCode != 200) {
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ ssize_t i = response->mHeaders.indexOfKey("session");
+ CHECK_GE(i, 0);
+ mSessionID = response->mHeaders.valueAt(i);
+ i = mSessionID.find(";");
+ if (i >= 0) {
+ // Remove options, i.e. ";timeout=90"
+ mSessionID.erase(i, mSessionID.size() - i);
+ }
+
+ i = response->mHeaders.indexOfKey("transport");
+ CHECK_GE(i, 0);
+ AString transport = response->mHeaders.valueAt(i);
+
+ LOG(INFO) << "transport = '" << transport << "'";
+
+ AString value;
+ CHECK(GetAttribute(transport.c_str(), "server_port", &value));
+
+ unsigned rtpPort, rtcpPort;
+ CHECK_EQ(sscanf(value.c_str(), "%u-%u", &rtpPort, &rtcpPort), 2);
+
+ CHECK(GetAttribute(transport.c_str(), "source", &value));
+
+ memset(mRemoteAddr.sin_zero, 0, sizeof(mRemoteAddr.sin_zero));
+ mRemoteAddr.sin_family = AF_INET;
+ mRemoteAddr.sin_addr.s_addr = inet_addr(value.c_str());
+ mRemoteAddr.sin_port = htons(rtpPort);
+
+ mRemoteRTCPAddr = mRemoteAddr;
+ mRemoteRTCPAddr.sin_port = htons(rtpPort + 1);
+
+ CHECK_EQ(0, connect(mRTPSocket,
+ (const struct sockaddr *)&mRemoteAddr,
+ sizeof(mRemoteAddr)));
+
+ CHECK_EQ(0, connect(mRTCPSocket,
+ (const struct sockaddr *)&mRemoteRTCPAddr,
+ sizeof(mRemoteRTCPAddr)));
+
+ uint32_t x = ntohl(mRemoteAddr.sin_addr.s_addr);
+ LOG(INFO) << "sending data to "
+ << (x >> 24)
+ << "."
+ << ((x >> 16) & 0xff)
+ << "."
+ << ((x >> 8) & 0xff)
+ << "."
+ << (x & 0xff)
+ << ":"
+ << rtpPort;
+
+ AString request;
+ request.append("RECORD ");
+ request.append(mStreamURL);
+ request.append(" RTSP/1.0\r\n");
+
+ addAuthentication(&request, "RECORD", mStreamURL.c_str());
+
+ request.append("Session: ");
+ request.append(mSessionID);
+ request.append("\r\n");
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('reco', id());
+ mConn->sendRequest(request.c_str(), reply);
+ break;
+ }
+
+ case 'reco':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "RECORD completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response;
+
+ if (result == OK) {
+ response = static_cast<ARTSPResponse *>(obj.get());
+ CHECK(response != NULL);
+ }
+
+ if (result != OK) {
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ (new AMessage('more', id()))->post();
+ (new AMessage('sr ', id()))->post();
+ (new AMessage('aliv', id()))->post(30000000ll);
+ break;
+ }
+
+ case 'aliv':
+ {
+ if (!mConnected) {
+ break;
+ }
+
+ AString request;
+ request.append("OPTIONS ");
+ request.append(mStreamURL);
+ request.append(" RTSP/1.0\r\n");
+
+ addAuthentication(&request, "RECORD", mStreamURL.c_str());
+
+ request.append("Session: ");
+ request.append(mSessionID);
+ request.append("\r\n");
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('opts', id());
+ mConn->sendRequest(request.c_str(), reply);
+ break;
+ }
+
+ case 'opts':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "OPTIONS completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ if (!mConnected) {
+ break;
+ }
+
+ (new AMessage('aliv', id()))->post(30000000ll);
+ break;
+ }
+
+ case 'more':
+ {
+ if (!mConnected) {
+ break;
+ }
+
+ sp<ABuffer> buffer = new ABuffer(65536);
+ 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[8] = mSourceID >> 24;
+ data[9] = (mSourceID >> 16) & 0xff;
+ data[10] = (mSourceID >> 8) & 0xff;
+ data[11] = mSourceID & 0xff;
+
+#ifdef ANDROID
+ MediaBuffer *mediaBuf = NULL;
+ for (;;) {
+ CHECK_EQ(mEncoder->read(&mediaBuf), (status_t)OK);
+ if (mediaBuf->range_length() > 0) {
+ break;
+ }
+ mediaBuf->release();
+ mediaBuf = NULL;
+ }
+
+ 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();
+
+ CHECK(!memcmp("\x00\x00\x00\x01", mediaData, 4));
+
+ CHECK_LE(mediaBuf->range_length() - 4 + 12, buffer->size());
+
+ memcpy(&data[12],
+ mediaData + 4, mediaBuf->range_length() - 4);
+
+ buffer->setRange(0, mediaBuf->range_length() - 4 + 12);
+
+ mediaBuf->release();
+ mediaBuf = NULL;
+#else
+ uint32_t rtpTime = mRTPTimeBase + mNumRTPSent * 128;
+ memset(&data[12], 0, 128);
+ buffer->setRange(0, 12 + 128);
+#endif
+
+ data[4] = rtpTime >> 24;
+ data[5] = (rtpTime >> 16) & 0xff;
+ data[6] = (rtpTime >> 8) & 0xff;
+ data[7] = rtpTime & 0xff;
+
+ ssize_t n = send(
+ mRTPSocket, data, buffer->size(), 0);
+ if (n < 0) {
+ LOG(ERROR) << "send failed (" << strerror(errno) << ")";
+ }
+ CHECK_EQ(n, (ssize_t)buffer->size());
+
+ ++mSeqNo;
+
+ ++mNumRTPSent;
+ mNumRTPOctetsSent += buffer->size() - 12;
+
+ mLastRTPTime = rtpTime;
+ mLastNTPTime = ntpTime();
+
+#ifdef ANDROID
+ if (mNumRTPSent < 60 * 25) { // 60 secs worth
+ msg->post(40000);
+#else
+ if (mNumRTPOctetsSent < 8000 * 60) {
+ msg->post(1000000ll * 128 / 8000);
+#endif
+ } else {
+ LOG(INFO) << "That's enough, pausing.";
+
+ AString request;
+ request.append("PAUSE ");
+ request.append(mStreamURL);
+ request.append(" RTSP/1.0\r\n");
+
+ addAuthentication(&request, "PAUSE", mStreamURL.c_str());
+
+ request.append("Session: ");
+ request.append(mSessionID);
+ request.append("\r\n");
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('paus', id());
+ mConn->sendRequest(request.c_str(), reply);
+ }
+ break;
+ }
+
+ case 'sr ':
+ {
+ if (!mConnected) {
+ break;
+ }
+
+ sp<ABuffer> buffer = new ABuffer(65536);
+ buffer->setRange(0, 0);
+
+ addSR(buffer);
+ addSDES(buffer);
+
+ uint8_t *data = buffer->data();
+ ssize_t n = send(
+ mRTCPSocket, data, buffer->size(), 0);
+ CHECK_EQ(n, (ssize_t)buffer->size());
+
+ msg->post(3000000);
+ break;
+ }
+
+ case 'paus':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "PAUSE completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response;
+
+ AString request;
+ request.append("TEARDOWN ");
+ request.append(mStreamURL);
+ request.append(" RTSP/1.0\r\n");
+
+ addAuthentication(&request, "TEARDOWN", mStreamURL.c_str());
+
+ request.append("Session: ");
+ request.append(mSessionID);
+ request.append("\r\n");
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('tear', id());
+ mConn->sendRequest(request.c_str(), reply);
+ break;
+ }
+
+ case 'tear':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "TEARDOWN completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response;
+
+ if (result == OK) {
+ response = static_cast<ARTSPResponse *>(obj.get());
+ CHECK(response != NULL);
+ }
+
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ case 'disc':
+ {
+ LOG(INFO) << "disconnect completed";
+
+ mConnected = false;
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ case 'quit':
+ {
+ if (mConnected) {
+ mConn->disconnect(new AMessage('disc', id()));
+ break;
+ }
+
+ if (mRTPSocket >= 0) {
+ close(mRTPSocket);
+ mRTPSocket = -1;
+ }
+
+ if (mRTCPSocket >= 0) {
+ close(mRTCPSocket);
+ mRTCPSocket = -1;
+ }
+
+#ifdef ANDROID
+ mEncoder->stop();
+ mEncoder.clear();
+#endif
+
+ mLooper->stop();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+ }
+
+protected:
+ virtual ~MyTransmitter() {
+ }
+
+private:
+ enum AuthType {
+ NONE,
+ BASIC,
+ DIGEST
+ };
+
+ AString mServerURL;
+ AString mTrackURL;
+ AString mStreamURL;
+
+ sp<ALooper> mLooper;
+ sp<ARTSPConnection> mConn;
+ bool mConnected;
+ uint32_t mServerIP;
+ AuthType mAuthType;
+ AString mNonce;
+ AString mSessionID;
+ int mRTPSocket, mRTCPSocket;
+ uint32_t mSourceID;
+ uint32_t mSeqNo;
+ uint32_t mRTPTimeBase;
+ struct sockaddr_in mRemoteAddr;
+ struct sockaddr_in mRemoteRTCPAddr;
+ size_t mNumSamplesSent;
+ uint32_t mNumRTPSent;
+ uint32_t mNumRTPOctetsSent;
+ uint32_t mLastRTPTime;
+ uint64_t mLastNTPTime;
+
+#ifdef ANDROID
+ sp<MediaSource> mEncoder;
+ AString mSeqParamSet;
+ AString mPicParamSet;
+
+ void 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);
+ CHECK(!memcmp(kStartCode, data, 4));
+
+ data += 4;
+ size -= 4;
+
+ size_t startCodePos = 0;
+ while (startCodePos + 3 < size
+ && memcmp(kStartCode, &data[startCodePos], 4)) {
+ ++startCodePos;
+ }
+
+ CHECK_LT(startCodePos + 3, size);
+
+ encodeBase64(data, startCodePos, &mSeqParamSet);
+
+ encodeBase64(&data[startCodePos + 4], size - startCodePos - 4,
+ &mPicParamSet);
+ }
+#endif
+
+ void 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 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 = "andih@laptop";
+ 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);
+ }
+
+ DISALLOW_EVIL_CONSTRUCTORS(MyTransmitter);
+};
+
+} // namespace android
+
+#endif // MY_TRANSMITTER_H_
diff --git a/media/libstagefright/rtsp/VideoSource.h b/media/libstagefright/rtsp/VideoSource.h
new file mode 100644
index 0000000..ae0c85b
--- /dev/null
+++ b/media/libstagefright/rtsp/VideoSource.h
@@ -0,0 +1,98 @@
+/*
+ * 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 VIDEO_SOURCE_H_
+
+#define VIDEO_SOURCE_H_
+
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+class VideoSource : public MediaSource {
+ static const int32_t kFramerate = 24; // fps
+
+public:
+ VideoSource(int width, int height)
+ : mWidth(width),
+ mHeight(height),
+ mSize((width * height * 3) / 2) {
+ mGroup.add_buffer(new MediaBuffer(mSize));
+ }
+
+ virtual sp<MetaData> getFormat() {
+ sp<MetaData> meta = new MetaData;
+ meta->setInt32(kKeyWidth, mWidth);
+ meta->setInt32(kKeyHeight, mHeight);
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW);
+
+ return meta;
+ }
+
+ virtual status_t start(MetaData *params) {
+ mNumFramesOutput = 0;
+ return OK;
+ }
+
+ virtual status_t stop() {
+ return OK;
+ }
+
+ virtual status_t read(
+ MediaBuffer **buffer, const MediaSource::ReadOptions *options) {
+ if (mNumFramesOutput == kFramerate * 100) {
+ // Stop returning data after 10 secs.
+ return ERROR_END_OF_STREAM;
+ }
+
+ // printf("VideoSource::read\n");
+ status_t err = mGroup.acquire_buffer(buffer);
+ if (err != OK) {
+ return err;
+ }
+
+ char x = (char)((double)rand() / RAND_MAX * 255);
+ memset((*buffer)->data(), x, mSize);
+ (*buffer)->set_range(0, mSize);
+ (*buffer)->meta_data()->clear();
+ (*buffer)->meta_data()->setInt64(
+ kKeyTime, (mNumFramesOutput * 1000000) / kFramerate);
+ ++mNumFramesOutput;
+
+ // printf("VideoSource::read - returning buffer\n");
+ // LOG(INFO)("VideoSource::read - returning buffer");
+ return OK;
+ }
+
+protected:
+ virtual ~VideoSource() {}
+
+private:
+ MediaBufferGroup mGroup;
+ int mWidth, mHeight;
+ size_t mSize;
+ int64_t mNumFramesOutput;;
+
+ VideoSource(const VideoSource &);
+ VideoSource &operator=(const VideoSource &);
+};
+
+} // namespace android
+
+#endif // VIDEO_SOURCE_H_