summaryrefslogtreecommitdiffstats
path: root/media/libstagefright/rtsp/MyTransmitter.h
diff options
context:
space:
mode:
authorAndreas Huber <andih@google.com>2010-06-07 15:19:40 -0700
committerAndreas Huber <andih@google.com>2010-06-07 15:34:45 -0700
commitcf7b9c7aae758ac0b99833915053c63c2ac46e09 (patch)
tree2480a2da149f7ba8fe651876a6173947ec8c6a16 /media/libstagefright/rtsp/MyTransmitter.h
parente71d10e7ad55ccbcb0756c007caef1c959090384 (diff)
downloadframeworks_av-cf7b9c7aae758ac0b99833915053c63c2ac46e09.zip
frameworks_av-cf7b9c7aae758ac0b99833915053c63c2ac46e09.tar.gz
frameworks_av-cf7b9c7aae758ac0b99833915053c63c2ac46e09.tar.bz2
Initial checkin of preliminary rtsp support for stagefright.
Change-Id: I0722aa888098c0c1361c97a4c1b123d910afc207
Diffstat (limited to 'media/libstagefright/rtsp/MyTransmitter.h')
-rw-r--r--media/libstagefright/rtsp/MyTransmitter.h981
1 files changed, 981 insertions, 0 deletions
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_