summaryrefslogtreecommitdiffstats
path: root/media/libstagefright/rtsp
diff options
context:
space:
mode:
Diffstat (limited to 'media/libstagefright/rtsp')
-rw-r--r--media/libstagefright/rtsp/AAVCAssembler.cpp7
-rw-r--r--media/libstagefright/rtsp/ARTPConnection.cpp3
-rw-r--r--media/libstagefright/rtsp/ARTSPConnection.cpp35
-rw-r--r--media/libstagefright/rtsp/ARTSPConnection.h6
-rw-r--r--media/libstagefright/rtsp/Android.mk3
-rw-r--r--media/libstagefright/rtsp/MyHandler.h478
-rw-r--r--media/libstagefright/rtsp/SDPLoader.cpp154
7 files changed, 596 insertions, 90 deletions
diff --git a/media/libstagefright/rtsp/AAVCAssembler.cpp b/media/libstagefright/rtsp/AAVCAssembler.cpp
index 7ea132e..a6825eb 100644
--- a/media/libstagefright/rtsp/AAVCAssembler.cpp
+++ b/media/libstagefright/rtsp/AAVCAssembler.cpp
@@ -106,6 +106,13 @@ ARTPAssembler::AssemblyStatus AAVCAssembler::addNALUnit(
++mNextExpectedSeqNo;
return success ? OK : MALFORMED_PACKET;
+ } else if (nalType == 0) {
+ ALOGV("Ignoring undefined nal type.");
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return OK;
} else {
ALOGV("Ignoring unsupported buffer (nalType=%d)", nalType);
diff --git a/media/libstagefright/rtsp/ARTPConnection.cpp b/media/libstagefright/rtsp/ARTPConnection.cpp
index 501a970..af369b5 100644
--- a/media/libstagefright/rtsp/ARTPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTPConnection.cpp
@@ -117,7 +117,8 @@ void ARTPConnection::MakePortPair(
bumpSocketBufferSize(*rtcpSocket);
- unsigned start = (rand() * 1000)/ RAND_MAX + 15550;
+ /* rand() * 1000 may overflow int type, use long long */
+ unsigned start = (unsigned)((rand()* 1000ll)/RAND_MAX) + 15550;
start &= ~1;
for (unsigned port = start; port < 65536; port += 2) {
diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp
index 161bd4f..efde7a9 100644
--- a/media/libstagefright/rtsp/ARTSPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTSPConnection.cpp
@@ -20,13 +20,12 @@
#include "ARTSPConnection.h"
-#include <cutils/properties.h>
-
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/base64.h>
#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
#include <arpa/inet.h>
#include <fcntl.h>
@@ -41,6 +40,10 @@ namespace android {
// static
const int64_t ARTSPConnection::kSelectTimeoutUs = 1000ll;
+// static
+const AString ARTSPConnection::sUserAgent =
+ StringPrintf("User-Agent: %s\r\n", MakeUserAgent().c_str());
+
ARTSPConnection::ARTSPConnection(bool uidValid, uid_t uid)
: mUIDValid(uidValid),
mUID(uid),
@@ -50,7 +53,6 @@ ARTSPConnection::ARTSPConnection(bool uidValid, uid_t uid)
mConnectionID(0),
mNextCSeq(0),
mReceiveResponseEventPending(false) {
- MakeUserAgent(&mUserAgent);
}
ARTSPConnection::~ARTSPConnection() {
@@ -58,6 +60,7 @@ ARTSPConnection::~ARTSPConnection() {
ALOGE("Connection is still open, closing the socket.");
if (mUIDValid) {
HTTPBase::UnRegisterSocketUserTag(mSocket);
+ HTTPBase::UnRegisterSocketUserMark(mSocket);
}
close(mSocket);
mSocket = -1;
@@ -212,6 +215,7 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
if (mState != DISCONNECTED) {
if (mUIDValid) {
HTTPBase::UnRegisterSocketUserTag(mSocket);
+ HTTPBase::UnRegisterSocketUserMark(mSocket);
}
close(mSocket);
mSocket = -1;
@@ -264,6 +268,7 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
if (mUIDValid) {
HTTPBase::RegisterSocketUserTag(mSocket, mUID,
(uint32_t)*(uint32_t*) "RTSP");
+ HTTPBase::RegisterSocketUserMark(mSocket, mUID);
}
MakeSocketBlocking(mSocket, false);
@@ -293,6 +298,7 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
if (mUIDValid) {
HTTPBase::UnRegisterSocketUserTag(mSocket);
+ HTTPBase::UnRegisterSocketUserMark(mSocket);
}
close(mSocket);
mSocket = -1;
@@ -310,6 +316,7 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
void ARTSPConnection::performDisconnect() {
if (mUIDValid) {
HTTPBase::UnRegisterSocketUserTag(mSocket);
+ HTTPBase::UnRegisterSocketUserMark(mSocket);
}
close(mSocket);
mSocket = -1;
@@ -383,6 +390,7 @@ void ARTSPConnection::onCompleteConnection(const sp<AMessage> &msg) {
mState = DISCONNECTED;
if (mUIDValid) {
HTTPBase::UnRegisterSocketUserTag(mSocket);
+ HTTPBase::UnRegisterSocketUserMark(mSocket);
}
close(mSocket);
mSocket = -1;
@@ -481,7 +489,6 @@ void ARTSPConnection::onReceiveResponse() {
FD_SET(mSocket, &rs);
int res = select(mSocket + 1, &rs, NULL, NULL, &tv);
- CHECK_GE(res, 0);
if (res == 1) {
MakeSocketBlocking(mSocket, true);
@@ -563,6 +570,9 @@ bool ARTSPConnection::receiveLine(AString *line) {
if (sawCR && c == '\n') {
line->erase(line->size() - 1, 1);
return true;
+ } else if (c == '\n') {
+ // some reponse line ended with '\n', instead of '\r\n'.
+ return true;
}
line->append(&c, 1);
@@ -1032,27 +1042,12 @@ void ARTSPConnection::addAuthentication(AString *request) {
#endif
}
-// static
-void ARTSPConnection::MakeUserAgent(AString *userAgent) {
- userAgent->clear();
- userAgent->setTo("User-Agent: stagefright/1.1 (Linux;Android ");
-
-#if (PROPERTY_VALUE_MAX < 8)
-#error "PROPERTY_VALUE_MAX must be at least 8"
-#endif
-
- char value[PROPERTY_VALUE_MAX];
- property_get("ro.build.version.release", value, "Unknown");
- userAgent->append(value);
- userAgent->append(")\r\n");
-}
-
void ARTSPConnection::addUserAgent(AString *request) const {
// Find the boundary between headers and the body.
ssize_t i = request->find("\r\n\r\n");
CHECK_GE(i, 0);
- request->insert(mUserAgent, i + 2);
+ request->insert(sUserAgent, i + 2);
}
} // namespace android
diff --git a/media/libstagefright/rtsp/ARTSPConnection.h b/media/libstagefright/rtsp/ARTSPConnection.h
index 68f2d59..1fe9c99 100644
--- a/media/libstagefright/rtsp/ARTSPConnection.h
+++ b/media/libstagefright/rtsp/ARTSPConnection.h
@@ -74,6 +74,8 @@ private:
static const int64_t kSelectTimeoutUs;
+ static const AString sUserAgent;
+
bool mUIDValid;
uid_t mUID;
State mState;
@@ -89,8 +91,6 @@ private:
sp<AMessage> mObserveBinaryMessage;
- AString mUserAgent;
-
void performDisconnect();
void onConnect(const sp<AMessage> &msg);
@@ -122,8 +122,6 @@ private:
static bool ParseSingleUnsignedLong(
const char *from, unsigned long *x);
- static void MakeUserAgent(AString *userAgent);
-
DISALLOW_EVIL_CONSTRUCTORS(ARTSPConnection);
};
diff --git a/media/libstagefright/rtsp/Android.mk b/media/libstagefright/rtsp/Android.mk
index 49e2daf..e77c69c 100644
--- a/media/libstagefright/rtsp/Android.mk
+++ b/media/libstagefright/rtsp/Android.mk
@@ -17,6 +17,7 @@ LOCAL_SRC_FILES:= \
ARTPWriter.cpp \
ARTSPConnection.cpp \
ASessionDescription.cpp \
+ SDPLoader.cpp \
LOCAL_C_INCLUDES:= \
$(TOP)/frameworks/av/media/libstagefright/include \
@@ -50,7 +51,7 @@ LOCAL_C_INCLUDES:= \
LOCAL_CFLAGS += -Wno-multichar
-LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE_TAGS := optional
LOCAL_MODULE:= rtp_test
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
index 96c7683..cd77aa0 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -28,13 +28,13 @@
#include "ASessionDescription.h"
#include <ctype.h>
-#include <cutils/properties.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>
+#include <media/stagefright/Utils.h>
#include <arpa/inet.h>
#include <sys/socket.h>
@@ -52,20 +52,9 @@ static int64_t kStartupTimeoutUs = 10000000ll;
static int64_t kDefaultKeepAliveTimeoutUs = 60000000ll;
-namespace android {
-
-static void MakeUserAgentString(AString *s) {
- s->setTo("stagefright/1.1 (Linux;Android ");
-
-#if (PROPERTY_VALUE_MAX < 8)
-#error "PROPERTY_VALUE_MAX must be at least 8"
-#endif
+static int64_t kPauseDelayUs = 3000000ll;
- char value[PROPERTY_VALUE_MAX];
- property_get("ro.build.version.release", value, "Unknown");
- s->append(value);
- s->append(")");
-}
+namespace android {
static bool GetAttribute(const char *s, const char *key, AString *value) {
value->clear();
@@ -129,13 +118,17 @@ struct MyHandler : public AHandler {
mNumAccessUnitsReceived(0),
mCheckPending(false),
mCheckGeneration(0),
+ mCheckTimeoutGeneration(0),
mTryTCPInterleaving(false),
mTryFakeRTCP(false),
mReceivedFirstRTCPPacket(false),
mReceivedFirstRTPPacket(false),
- mSeekable(false),
+ mSeekable(true),
mKeepAliveTimeoutUs(kDefaultKeepAliveTimeoutUs),
- mKeepAliveGeneration(0) {
+ mKeepAliveGeneration(0),
+ mPausing(false),
+ mPauseGeneration(0),
+ mPlayResponseParsed(false) {
mNetLooper->setName("rtsp net");
mNetLooper->start(false /* runOnCallingThread */,
false /* canCallJava */,
@@ -173,6 +166,39 @@ struct MyHandler : public AHandler {
mConn->connect(mOriginalSessionURL.c_str(), reply);
}
+ void loadSDP(const sp<ASessionDescription>& desc) {
+ looper()->registerHandler(mConn);
+ (1 ? mNetLooper : looper())->registerHandler(mRTPConn);
+
+ sp<AMessage> notify = new AMessage('biny', id());
+ mConn->observeBinaryData(notify);
+
+ sp<AMessage> reply = new AMessage('sdpl', id());
+ reply->setObject("description", desc);
+ mConn->connect(mOriginalSessionURL.c_str(), reply);
+ }
+
+ AString getControlURL(sp<ASessionDescription> desc) {
+ AString sessionLevelControlURL;
+ if (mSessionDesc->findAttribute(
+ 0,
+ "a=control",
+ &sessionLevelControlURL)) {
+ if (sessionLevelControlURL.compare("*") == 0) {
+ return mBaseURL;
+ } else {
+ AString controlURL;
+ CHECK(MakeURL(
+ mBaseURL.c_str(),
+ sessionLevelControlURL.c_str(),
+ &controlURL));
+ return controlURL;
+ }
+ } else {
+ return mSessionURL;
+ }
+ }
+
void disconnect() {
(new AMessage('abor', id()))->post();
}
@@ -180,6 +206,24 @@ struct MyHandler : public AHandler {
void seek(int64_t timeUs) {
sp<AMessage> msg = new AMessage('seek', id());
msg->setInt64("time", timeUs);
+ mPauseGeneration++;
+ msg->post();
+ }
+
+ bool isSeekable() const {
+ return mSeekable;
+ }
+
+ void pause() {
+ sp<AMessage> msg = new AMessage('paus', id());
+ mPauseGeneration++;
+ msg->setInt32("pausecheck", mPauseGeneration);
+ msg->post(kPauseDelayUs);
+ }
+
+ void resume() {
+ sp<AMessage> msg = new AMessage('resu', id());
+ mPauseGeneration++;
msg->post();
}
@@ -223,8 +267,7 @@ struct MyHandler : public AHandler {
data[offset++] = 6; // TOOL
- AString tool;
- MakeUserAgentString(&tool);
+ AString tool = MakeUserAgent();
data[offset++] = tool.size();
@@ -348,6 +391,39 @@ struct MyHandler : public AHandler {
return true;
}
+ static bool isLiveStream(const sp<ASessionDescription> &desc) {
+ AString attrLiveStream;
+ if (desc->findAttribute(0, "a=LiveStream", &attrLiveStream)) {
+ ssize_t semicolonPos = attrLiveStream.find(";", 2);
+
+ const char* liveStreamValue;
+ if (semicolonPos < 0) {
+ liveStreamValue = attrLiveStream.c_str();
+ } else {
+ AString valString;
+ valString.setTo(attrLiveStream,
+ semicolonPos + 1,
+ attrLiveStream.size() - semicolonPos - 1);
+ liveStreamValue = valString.c_str();
+ }
+
+ uint32_t value = strtoul(liveStreamValue, NULL, 10);
+ if (value == 1) {
+ ALOGV("found live stream");
+ return true;
+ }
+ } else {
+ // It is a live stream if no duration is returned
+ int64_t durationUs;
+ if (!desc->getDurationUs(&durationUs)) {
+ ALOGV("No duration found, assume live stream");
+ return true;
+ }
+ }
+
+ return false;
+ }
+
virtual void onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case 'conn':
@@ -422,6 +498,9 @@ struct MyHandler : public AHandler {
if (response->mStatusCode != 200) {
result = UNKNOWN_ERROR;
+ } else if (response->mContent == NULL) {
+ result = ERROR_MALFORMED;
+ ALOGE("The response has no content.");
} else {
mSessionDesc = new ASessionDescription;
@@ -445,6 +524,8 @@ struct MyHandler : public AHandler {
}
}
+ mSeekable = !isLiveStream(mSessionDesc);
+
if (!mBaseURL.startsWith("rtsp://")) {
// Some misbehaving servers specify a relative
// URL in one of the locations above, combine
@@ -464,6 +545,8 @@ struct MyHandler : public AHandler {
mBaseURL = tmp;
}
+ mControlURL = getControlURL(mSessionDesc);
+
if (mSessionDesc->countTracks() < 2) {
// There's no actual tracks in this session.
// The first "track" is merely session meta
@@ -486,6 +569,51 @@ struct MyHandler : public AHandler {
break;
}
+ case 'sdpl':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ ALOGI("SDP connection request completed with result %d (%s)",
+ result, strerror(-result));
+
+ if (result == OK) {
+ sp<RefBase> obj;
+ CHECK(msg->findObject("description", &obj));
+ mSessionDesc =
+ static_cast<ASessionDescription *>(obj.get());
+
+ if (!mSessionDesc->isValid()) {
+ ALOGE("Failed to parse session description.");
+ result = ERROR_MALFORMED;
+ } else {
+ mBaseURL = mSessionURL;
+
+ mSeekable = !isLiveStream(mSessionDesc);
+
+ mControlURL = getControlURL(mSessionDesc);
+
+ if (mSessionDesc->countTracks() < 2) {
+ // There's no actual tracks in this session.
+ // The first "track" is merely session meta
+ // data.
+
+ ALOGW("Session doesn't contain any playable "
+ "tracks. Aborting.");
+ result = ERROR_UNSUPPORTED;
+ } else {
+ setupTrack(1);
+ }
+ }
+ }
+
+ if (result != OK) {
+ sp<AMessage> reply = new AMessage('disc', id());
+ mConn->disconnect(reply);
+ }
+ break;
+ }
+
case 'setu':
{
size_t index;
@@ -558,23 +686,27 @@ struct MyHandler : public AHandler {
i = response->mHeaders.indexOfKey("transport");
CHECK_GE(i, 0);
- if (!track->mUsingInterleavedTCP) {
- AString transport = response->mHeaders.valueAt(i);
-
- // We are going to continue even if we were
- // unable to poke a hole into the firewall...
- pokeAHole(
- track->mRTPSocket,
- track->mRTCPSocket,
- transport);
- }
+ if (track->mRTPSocket != -1 && track->mRTCPSocket != -1) {
+ if (!track->mUsingInterleavedTCP) {
+ AString transport = response->mHeaders.valueAt(i);
- mRTPConn->addStream(
- track->mRTPSocket, track->mRTCPSocket,
- mSessionDesc, index,
- notify, track->mUsingInterleavedTCP);
+ // We are going to continue even if we were
+ // unable to poke a hole into the firewall...
+ pokeAHole(
+ track->mRTPSocket,
+ track->mRTCPSocket,
+ transport);
+ }
+
+ mRTPConn->addStream(
+ track->mRTPSocket, track->mRTCPSocket,
+ mSessionDesc, index,
+ notify, track->mUsingInterleavedTCP);
- mSetupTracksSuccessful = true;
+ mSetupTracksSuccessful = true;
+ } else {
+ result = BAD_VALUE;
+ }
}
}
@@ -584,7 +716,9 @@ struct MyHandler : public AHandler {
// Clear the tag
if (mUIDValid) {
HTTPBase::UnRegisterSocketUserTag(track->mRTPSocket);
+ HTTPBase::UnRegisterSocketUserMark(track->mRTPSocket);
HTTPBase::UnRegisterSocketUserTag(track->mRTCPSocket);
+ HTTPBase::UnRegisterSocketUserMark(track->mRTCPSocket);
}
close(track->mRTPSocket);
@@ -596,14 +730,14 @@ struct MyHandler : public AHandler {
}
++index;
- if (index < mSessionDesc->countTracks()) {
+ if (result == OK && index < mSessionDesc->countTracks()) {
setupTrack(index);
} else if (mSetupTracksSuccessful) {
++mKeepAliveGeneration;
postKeepAlive();
AString request = "PLAY ";
- request.append(mSessionURL);
+ request.append(mControlURL);
request.append(" RTSP/1.0\r\n");
request.append("Session: ");
@@ -641,6 +775,8 @@ struct MyHandler : public AHandler {
parsePlayResponse(response);
sp<AMessage> timeout = new AMessage('tiou', id());
+ mCheckTimeoutGeneration++;
+ timeout->setInt32("tioucheck", mCheckTimeoutGeneration);
timeout->post(kStartupTimeoutUs);
}
}
@@ -713,7 +849,9 @@ struct MyHandler : public AHandler {
// Clear the tag
if (mUIDValid) {
HTTPBase::UnRegisterSocketUserTag(info->mRTPSocket);
+ HTTPBase::UnRegisterSocketUserMark(info->mRTPSocket);
HTTPBase::UnRegisterSocketUserTag(info->mRTCPSocket);
+ HTTPBase::UnRegisterSocketUserMark(info->mRTCPSocket);
}
close(info->mRTPSocket);
@@ -730,7 +868,8 @@ struct MyHandler : public AHandler {
mNumAccessUnitsReceived = 0;
mReceivedFirstRTCPPacket = false;
mReceivedFirstRTPPacket = false;
- mSeekable = false;
+ mPausing = false;
+ mSeekable = true;
sp<AMessage> reply = new AMessage('tear', id());
@@ -851,9 +990,16 @@ struct MyHandler : public AHandler {
int32_t eos;
if (msg->findInt32("eos", &eos)) {
ALOGI("received BYE on track index %d", trackIndex);
-#if 0
- track->mPacketSource->signalEOS(ERROR_END_OF_STREAM);
-#endif
+ if (!mAllTracksHaveTime && dataReceivedOnAllChannels()) {
+ ALOGI("No time established => fake existing data");
+
+ track->mEOSReceived = true;
+ mTryFakeRTCP = true;
+ mReceivedFirstRTCPPacket = true;
+ fakeTimestamps();
+ } else {
+ postQueueEOS(trackIndex, ERROR_END_OF_STREAM);
+ }
return;
}
@@ -881,6 +1027,115 @@ struct MyHandler : public AHandler {
break;
}
+ case 'paus':
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("pausecheck", &generation));
+ if (generation != mPauseGeneration) {
+ ALOGV("Ignoring outdated pause message.");
+ break;
+ }
+
+ if (!mSeekable) {
+ ALOGW("This is a live stream, ignoring pause request.");
+ break;
+ }
+ mCheckPending = true;
+ ++mCheckGeneration;
+ mPausing = true;
+
+ AString request = "PAUSE ";
+ request.append(mControlURL);
+ 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('pau2', id());
+ mConn->sendRequest(request.c_str(), reply);
+ break;
+ }
+
+ case 'pau2':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+ mCheckTimeoutGeneration++;
+
+ ALOGI("PAUSE completed with result %d (%s)",
+ result, strerror(-result));
+ break;
+ }
+
+ case 'resu':
+ {
+ if (mPausing && mSeekPending) {
+ // If seeking, Play will be sent from see1 instead
+ break;
+ }
+
+ if (!mPausing) {
+ // Dont send PLAY if we have not paused
+ break;
+ }
+ AString request = "PLAY ";
+ request.append(mControlURL);
+ 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('res2', id());
+ mConn->sendRequest(request.c_str(), reply);
+ break;
+ }
+
+ case 'res2':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ ALOGI("PLAY completed with result %d (%s)",
+ result, strerror(-result));
+
+ mCheckPending = false;
+ postAccessUnitTimeoutCheck();
+
+ if (result == OK) {
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response =
+ static_cast<ARTSPResponse *>(obj.get());
+
+ if (response->mStatusCode != 200) {
+ result = UNKNOWN_ERROR;
+ } else {
+ parsePlayResponse(response);
+
+ // Post new timeout in order to make sure to use
+ // fake timestamps if no new Sender Reports arrive
+ sp<AMessage> timeout = new AMessage('tiou', id());
+ mCheckTimeoutGeneration++;
+ timeout->setInt32("tioucheck", mCheckTimeoutGeneration);
+ timeout->post(kStartupTimeoutUs);
+ }
+ }
+
+ if (result != OK) {
+ ALOGE("resume failed, aborting.");
+ (new AMessage('abor', id()))->post();
+ }
+
+ mPausing = false;
+ break;
+ }
+
case 'seek':
{
if (!mSeekable) {
@@ -902,8 +1157,17 @@ struct MyHandler : public AHandler {
mCheckPending = true;
++mCheckGeneration;
+ sp<AMessage> reply = new AMessage('see1', id());
+ reply->setInt64("time", timeUs);
+
+ if (mPausing) {
+ // PAUSE already sent
+ ALOGI("Pause already sent");
+ reply->post();
+ break;
+ }
AString request = "PAUSE ";
- request.append(mSessionURL);
+ request.append(mControlURL);
request.append(" RTSP/1.0\r\n");
request.append("Session: ");
@@ -912,8 +1176,6 @@ struct MyHandler : public AHandler {
request.append("\r\n");
- sp<AMessage> reply = new AMessage('see1', id());
- reply->setInt64("time", timeUs);
mConn->sendRequest(request.c_str(), reply);
break;
}
@@ -925,6 +1187,7 @@ struct MyHandler : public AHandler {
TrackInfo *info = &mTracks.editItemAt(i);
postQueueSeekDiscontinuity(i);
+ info->mEOSReceived = false;
info->mRTPAnchor = 0;
info->mNTPAnchorUs = -1;
@@ -933,11 +1196,18 @@ struct MyHandler : public AHandler {
mAllTracksHaveTime = false;
mNTPAnchorUs = -1;
+ // Start new timeoutgeneration to avoid getting timeout
+ // before PLAY response arrive
+ sp<AMessage> timeout = new AMessage('tiou', id());
+ mCheckTimeoutGeneration++;
+ timeout->setInt32("tioucheck", mCheckTimeoutGeneration);
+ timeout->post(kStartupTimeoutUs);
+
int64_t timeUs;
CHECK(msg->findInt64("time", &timeUs));
AString request = "PLAY ";
- request.append(mSessionURL);
+ request.append(mControlURL);
request.append(" RTSP/1.0\r\n");
request.append("Session: ");
@@ -957,7 +1227,10 @@ struct MyHandler : public AHandler {
case 'see2':
{
- CHECK(mSeekPending);
+ if (mTracks.size() == 0) {
+ // We have already hit abor, break
+ break;
+ }
int32_t result;
CHECK(msg->findInt32("result", &result));
@@ -979,6 +1252,13 @@ struct MyHandler : public AHandler {
} else {
parsePlayResponse(response);
+ // Post new timeout in order to make sure to use
+ // fake timestamps if no new Sender Reports arrive
+ sp<AMessage> timeout = new AMessage('tiou', id());
+ mCheckTimeoutGeneration++;
+ timeout->setInt32("tioucheck", mCheckTimeoutGeneration);
+ timeout->post(kStartupTimeoutUs);
+
ssize_t i = response->mHeaders.indexOfKey("rtp-info");
CHECK_GE(i, 0);
@@ -993,6 +1273,7 @@ struct MyHandler : public AHandler {
(new AMessage('abor', id()))->post();
}
+ mPausing = false;
mSeekPending = false;
sp<AMessage> msg = mNotify->dup();
@@ -1015,8 +1296,17 @@ struct MyHandler : public AHandler {
case 'tiou':
{
+ int32_t timeoutGenerationCheck;
+ CHECK(msg->findInt32("tioucheck", &timeoutGenerationCheck));
+ if (timeoutGenerationCheck != mCheckTimeoutGeneration) {
+ // This is an outdated message. Ignore.
+ // This typically happens if a lot of seeks are
+ // performed, since new timeout messages now are
+ // posted at seek as well.
+ break;
+ }
if (!mReceivedFirstRTCPPacket) {
- if (mReceivedFirstRTPPacket && !mTryFakeRTCP) {
+ if (dataReceivedOnAllChannels() && !mTryFakeRTCP) {
ALOGW("We received RTP packets but no RTCP packets, "
"using fake timestamps.");
@@ -1090,7 +1380,7 @@ struct MyHandler : public AHandler {
}
void parsePlayResponse(const sp<ARTSPResponse> &response) {
- mSeekable = false;
+ mPlayResponseParsed = true;
if (mTracks.size() == 0) {
ALOGV("parsePlayResponse: late packets ignored.");
return;
@@ -1165,8 +1455,6 @@ struct MyHandler : public AHandler {
++n;
}
-
- mSeekable = true;
}
sp<MetaData> getTrackFormat(size_t index, int32_t *timeScale) {
@@ -1196,6 +1484,7 @@ private:
uint32_t mRTPAnchor;
int64_t mNTPAnchorUs;
int32_t mTimeScale;
+ bool mEOSReceived;
uint32_t mNormalPlayTimeRTP;
int64_t mNormalPlayTimeUs;
@@ -1218,6 +1507,7 @@ private:
AString mSessionURL;
AString mSessionHost;
AString mBaseURL;
+ AString mControlURL;
AString mSessionID;
bool mSetupTracksSuccessful;
bool mSeekPending;
@@ -1231,6 +1521,7 @@ private:
int64_t mNumAccessUnitsReceived;
bool mCheckPending;
int32_t mCheckGeneration;
+ int32_t mCheckTimeoutGeneration;
bool mTryTCPInterleaving;
bool mTryFakeRTCP;
bool mReceivedFirstRTCPPacket;
@@ -1238,9 +1529,13 @@ private:
bool mSeekable;
int64_t mKeepAliveTimeoutUs;
int32_t mKeepAliveGeneration;
+ bool mPausing;
+ int32_t mPauseGeneration;
Vector<TrackInfo> mTracks;
+ bool mPlayResponseParsed;
+
void setupTrack(size_t index) {
sp<APacketSource> source =
new APacketSource(mSessionDesc, index);
@@ -1268,6 +1563,8 @@ private:
info->mUsingInterleavedTCP = false;
info->mFirstSeqNumInSegment = 0;
info->mNewSegment = true;
+ info->mRTPSocket = -1;
+ info->mRTCPSocket = -1;
info->mRTPAnchor = 0;
info->mNTPAnchorUs = -1;
info->mNormalPlayTimeRTP = 0;
@@ -1284,6 +1581,7 @@ private:
formatDesc.c_str(), &timescale, &numChannels);
info->mTimeScale = timescale;
+ info->mEOSReceived = false;
ALOGV("track #%d URL=%s", mTracks.size(), trackURL.c_str());
@@ -1311,6 +1609,8 @@ private:
(uint32_t)*(uint32_t*) "RTP_");
HTTPBase::RegisterSocketUserTag(info->mRTCPSocket, mUID,
(uint32_t)*(uint32_t*) "RTP_");
+ HTTPBase::RegisterSocketUserMark(info->mRTPSocket, mUID);
+ HTTPBase::RegisterSocketUserMark(info->mRTCPSocket, mUID);
}
request.append("Transport: RTP/AVP/UDP;unicast;client_port=");
@@ -1376,6 +1676,37 @@ private:
}
}
+ bool dataReceivedOnAllChannels() {
+ TrackInfo *track;
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ track = &mTracks.editItemAt(i);
+ if (track->mPackets.empty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void handleFirstAccessUnit() {
+ if (mFirstAccessUnit) {
+ sp<AMessage> msg = mNotify->dup();
+ msg->setInt32("what", kWhatConnected);
+ msg->post();
+
+ if (mSeekable) {
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ TrackInfo *info = &mTracks.editItemAt(i);
+
+ postNormalPlayTimeMapping(
+ i,
+ info->mNormalPlayTimeRTP, info->mNormalPlayTimeUs);
+ }
+ }
+
+ mFirstAccessUnit = false;
+ }
+ }
+
void onTimeUpdate(int32_t trackIndex, uint32_t rtpTime, uint64_t ntpTime) {
ALOGV("onTimeUpdate track %d, rtpTime = 0x%08x, ntpTime = 0x%016llx",
trackIndex, rtpTime, ntpTime);
@@ -1406,30 +1737,44 @@ private:
ALOGI("Time now established for all tracks.");
}
}
+ if (mAllTracksHaveTime && dataReceivedOnAllChannels()) {
+ handleFirstAccessUnit();
+
+ // Time is now established, lets start timestamping immediately
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ TrackInfo *trackInfo = &mTracks.editItemAt(i);
+ while (!trackInfo->mPackets.empty()) {
+ sp<ABuffer> accessUnit = *trackInfo->mPackets.begin();
+ trackInfo->mPackets.erase(trackInfo->mPackets.begin());
+
+ if (addMediaTimestamp(i, trackInfo, accessUnit)) {
+ postQueueAccessUnit(i, accessUnit);
+ }
+ }
+ }
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ TrackInfo *trackInfo = &mTracks.editItemAt(i);
+ if (trackInfo->mEOSReceived) {
+ postQueueEOS(i, ERROR_END_OF_STREAM);
+ trackInfo->mEOSReceived = false;
+ }
+ }
+ }
}
void onAccessUnitComplete(
int32_t trackIndex, const sp<ABuffer> &accessUnit) {
ALOGV("onAccessUnitComplete track %d", trackIndex);
- if (mFirstAccessUnit) {
- sp<AMessage> msg = mNotify->dup();
- msg->setInt32("what", kWhatConnected);
- msg->post();
-
- if (mSeekable) {
- for (size_t i = 0; i < mTracks.size(); ++i) {
- TrackInfo *info = &mTracks.editItemAt(i);
-
- postNormalPlayTimeMapping(
- i,
- info->mNormalPlayTimeRTP, info->mNormalPlayTimeUs);
- }
- }
-
- mFirstAccessUnit = false;
+ if(!mPlayResponseParsed){
+ ALOGI("play response is not parsed, storing accessunit");
+ TrackInfo *track = &mTracks.editItemAt(trackIndex);
+ track->mPackets.push_back(accessUnit);
+ return;
}
+ handleFirstAccessUnit();
+
TrackInfo *track = &mTracks.editItemAt(trackIndex);
if (!mAllTracksHaveTime) {
@@ -1450,6 +1795,11 @@ private:
if (addMediaTimestamp(trackIndex, track, accessUnit)) {
postQueueAccessUnit(trackIndex, accessUnit);
}
+
+ if (track->mEOSReceived) {
+ postQueueEOS(trackIndex, ERROR_END_OF_STREAM);
+ track->mEOSReceived = false;
+ }
}
bool addMediaTimestamp(
diff --git a/media/libstagefright/rtsp/SDPLoader.cpp b/media/libstagefright/rtsp/SDPLoader.cpp
new file mode 100644
index 0000000..ed3fa7e
--- /dev/null
+++ b/media/libstagefright/rtsp/SDPLoader.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SDPLoader"
+#include <utils/Log.h>
+
+#include "SDPLoader.h"
+
+#include "ASessionDescription.h"
+#include "HTTPBase.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+
+#define DEFAULT_SDP_SIZE 100000
+
+namespace android {
+
+SDPLoader::SDPLoader(const sp<AMessage> &notify, uint32_t flags, bool uidValid, uid_t uid)
+ : mNotify(notify),
+ mFlags(flags),
+ mUIDValid(uidValid),
+ mUID(uid),
+ mNetLooper(new ALooper),
+ mCancelled(false),
+ mHTTPDataSource(
+ HTTPBase::Create(
+ (mFlags & kFlagIncognito)
+ ? HTTPBase::kFlagIncognito
+ : 0)) {
+ if (mUIDValid) {
+ mHTTPDataSource->setUID(mUID);
+ }
+
+ mNetLooper->setName("sdp net");
+ mNetLooper->start(false /* runOnCallingThread */,
+ false /* canCallJava */,
+ PRIORITY_HIGHEST);
+}
+
+void SDPLoader::load(const char *url, const KeyedVector<String8, String8> *headers) {
+ mNetLooper->registerHandler(this);
+
+ sp<AMessage> msg = new AMessage(kWhatLoad, id());
+ msg->setString("url", url);
+
+ if (headers != NULL) {
+ msg->setPointer(
+ "headers",
+ new KeyedVector<String8, String8>(*headers));
+ }
+
+ msg->post();
+}
+
+void SDPLoader::cancel() {
+ mCancelled = true;
+ sp<HTTPBase> HTTPDataSource = mHTTPDataSource;
+ HTTPDataSource->disconnect();
+}
+
+void SDPLoader::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatLoad:
+ onLoad(msg);
+ break;
+
+ default:
+ TRESPASS();
+ break;
+ }
+}
+
+void SDPLoader::onLoad(const sp<AMessage> &msg) {
+ status_t err = OK;
+ sp<ASessionDescription> desc = NULL;
+ AString url;
+ CHECK(msg->findString("url", &url));
+
+ KeyedVector<String8, String8> *headers = NULL;
+ msg->findPointer("headers", (void **)&headers);
+
+ if (!(mFlags & kFlagIncognito)) {
+ ALOGI("onLoad '%s'", url.c_str());
+ } else {
+ ALOGI("onLoad <URL suppressed>");
+ }
+
+ if (!mCancelled) {
+ err = mHTTPDataSource->connect(url.c_str(), headers);
+
+ if (err != OK) {
+ ALOGE("connect() returned %d", err);
+ }
+ }
+
+ if (headers != NULL) {
+ delete headers;
+ headers = NULL;
+ }
+
+ off64_t sdpSize;
+ if (err == OK && !mCancelled) {
+ err = mHTTPDataSource->getSize(&sdpSize);
+
+ if (err != OK) {
+ //We did not get the size of the sdp file, default to a large value
+ sdpSize = DEFAULT_SDP_SIZE;
+ err = OK;
+ }
+ }
+
+ sp<ABuffer> buffer = new ABuffer(sdpSize);
+
+ if (err == OK && !mCancelled) {
+ ssize_t readSize = mHTTPDataSource->readAt(0, buffer->data(), sdpSize);
+
+ if (readSize < 0) {
+ ALOGE("Failed to read SDP, error code = %ld", readSize);
+ err = UNKNOWN_ERROR;
+ } else {
+ desc = new ASessionDescription;
+
+ if (desc == NULL || !desc->setTo(buffer->data(), (size_t)readSize)) {
+ err = UNKNOWN_ERROR;
+ ALOGE("Failed to parse SDP");
+ }
+ }
+ }
+
+ mHTTPDataSource.clear();
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatSDPLoaded);
+ notify->setInt32("result", err);
+ notify->setObject("description", desc);
+ notify->post();
+}
+
+} // namespace android