diff options
Diffstat (limited to 'media/libstagefright/rtsp')
-rw-r--r-- | media/libstagefright/rtsp/AAMRAssembler.cpp | 1 | ||||
-rw-r--r-- | media/libstagefright/rtsp/AAVCAssembler.cpp | 7 | ||||
-rw-r--r-- | media/libstagefright/rtsp/AH263Assembler.cpp | 30 | ||||
-rw-r--r-- | media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp | 13 | ||||
-rw-r--r-- | media/libstagefright/rtsp/ARTPConnection.cpp | 3 | ||||
-rw-r--r-- | media/libstagefright/rtsp/ARTSPConnection.cpp | 38 | ||||
-rw-r--r-- | media/libstagefright/rtsp/ARTSPConnection.h | 6 | ||||
-rw-r--r-- | media/libstagefright/rtsp/Android.mk | 3 | ||||
-rw-r--r-- | media/libstagefright/rtsp/MyHandler.h | 480 | ||||
-rw-r--r-- | media/libstagefright/rtsp/SDPLoader.cpp | 154 |
10 files changed, 637 insertions, 98 deletions
diff --git a/media/libstagefright/rtsp/AAMRAssembler.cpp b/media/libstagefright/rtsp/AAMRAssembler.cpp index fb8abc5..9e8725a 100644 --- a/media/libstagefright/rtsp/AAMRAssembler.cpp +++ b/media/libstagefright/rtsp/AAMRAssembler.cpp @@ -145,7 +145,6 @@ ARTPAssembler::AssemblyStatus AAMRAssembler::addPacket( unsigned payloadHeader = buffer->data()[0]; unsigned CMR = payloadHeader >> 4; - CHECK_EQ(payloadHeader & 0x0f, 0u); // RR Vector<uint8_t> tableOfContents; 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/AH263Assembler.cpp b/media/libstagefright/rtsp/AH263Assembler.cpp index d0313cc..75cd911 100644 --- a/media/libstagefright/rtsp/AH263Assembler.cpp +++ b/media/libstagefright/rtsp/AH263Assembler.cpp @@ -13,6 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +//#define LOG_NDEBUG 0 +#define LOG_TAG "AH263Assembler" +#include <utils/Log.h> #include "AH263Assembler.h" @@ -100,11 +103,34 @@ ARTPAssembler::AssemblyStatus AH263Assembler::addPacket( } unsigned payloadHeader = U16_AT(buffer->data()); - CHECK_EQ(payloadHeader >> 11, 0u); // RR=0 unsigned P = (payloadHeader >> 10) & 1; unsigned V = (payloadHeader >> 9) & 1; unsigned PLEN = (payloadHeader >> 3) & 0x3f; - // unsigned PEBIT = payloadHeader & 7; + unsigned PEBIT = payloadHeader & 7; + + // V=0 + if (V != 0u) { + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + ALOGW("Packet discarded due to VRC (V != 0)"); + return MALFORMED_PACKET; + } + + // PLEN=0 + if (PLEN != 0u) { + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + ALOGW("Packet discarded (PLEN != 0)"); + return MALFORMED_PACKET; + } + + // PEBIT=0 + if (PEBIT != 0u) { + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + ALOGW("Packet discarded (PEBIT != 0)"); + return MALFORMED_PACKET; + } size_t skip = V + PLEN + (P ? 0 : 2); diff --git a/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp index f9a44f0..aa8ffc6 100644 --- a/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp +++ b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp @@ -311,9 +311,7 @@ static status_t parseStreamMuxConfig( case 2: { - // reserved - TRESPASS(); - break; + return ERROR_UNSUPPORTED; } case 3: @@ -460,6 +458,15 @@ AMPEG4AudioAssembler::AMPEG4AudioAssembler( &mFixedFrameLength, &mOtherDataPresent, &mOtherDataLenBits); + if (err == ERROR_UNSUPPORTED) { + ALOGW("Failed to parse stream mux config, using default values for playback."); + mMuxConfigPresent = false; + mNumSubFrames = 0; + mFrameLengthType = 0; + mOtherDataPresent = false; + mOtherDataLenBits = 0; + return; + } CHECK_EQ(err, (status_t)NO_ERROR); } 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 539a888..492bd4a 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; @@ -235,7 +239,7 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) { // right here, since we currently have no way of asking the user // for this information. - ALOGE("Malformed rtsp url %s", url.c_str()); + ALOGE("Malformed rtsp url <URL suppressed>"); reply->setInt32("result", ERROR_MALFORMED); reply->post(); @@ -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); @@ -830,6 +840,7 @@ status_t ARTSPConnection::findPendingRequest( if (i < 0) { // This is an unsolicited server->client message. + *index = -1; return OK; } @@ -1031,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 cac6888..e7580c2 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 */, @@ -156,7 +149,7 @@ struct MyHandler : public AHandler { mSessionURL.append(StringPrintf("%u", port)); mSessionURL.append(path); - ALOGI("rewritten session url: '%s'", mSessionURL.c_str()); + ALOGV("rewritten session url: '%s'", mSessionURL.c_str()); } mSessionHost = host; @@ -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': @@ -433,6 +509,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; @@ -456,6 +535,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 @@ -475,6 +556,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 @@ -497,6 +580,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; @@ -569,23 +697,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; + } } } @@ -595,7 +727,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); @@ -607,14 +741,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: "); @@ -652,6 +786,8 @@ struct MyHandler : public AHandler { parsePlayResponse(response); sp<AMessage> timeout = new AMessage('tiou', id()); + mCheckTimeoutGeneration++; + timeout->setInt32("tioucheck", mCheckTimeoutGeneration); timeout->post(kStartupTimeoutUs); } } @@ -724,7 +860,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); @@ -741,7 +879,8 @@ struct MyHandler : public AHandler { mNumAccessUnitsReceived = 0; mReceivedFirstRTCPPacket = false; mReceivedFirstRTPPacket = false; - mSeekable = false; + mPausing = false; + mSeekable = true; sp<AMessage> reply = new AMessage('tear', id()); @@ -862,9 +1001,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; } @@ -892,6 +1038,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) { @@ -913,8 +1168,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: "); @@ -923,8 +1187,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; } @@ -936,6 +1198,7 @@ struct MyHandler : public AHandler { TrackInfo *info = &mTracks.editItemAt(i); postQueueSeekDiscontinuity(i); + info->mEOSReceived = false; info->mRTPAnchor = 0; info->mNTPAnchorUs = -1; @@ -944,11 +1207,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: "); @@ -968,7 +1238,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)); @@ -990,6 +1263,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); @@ -1004,6 +1284,7 @@ struct MyHandler : public AHandler { (new AMessage('abor', id()))->post(); } + mPausing = false; mSeekPending = false; sp<AMessage> msg = mNotify->dup(); @@ -1026,8 +1307,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."); @@ -1101,7 +1391,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; @@ -1176,8 +1466,6 @@ struct MyHandler : public AHandler { ++n; } - - mSeekable = true; } sp<MetaData> getTrackFormat(size_t index, int32_t *timeScale) { @@ -1207,6 +1495,7 @@ private: uint32_t mRTPAnchor; int64_t mNTPAnchorUs; int32_t mTimeScale; + bool mEOSReceived; uint32_t mNormalPlayTimeRTP; int64_t mNormalPlayTimeUs; @@ -1229,6 +1518,7 @@ private: AString mSessionURL; AString mSessionHost; AString mBaseURL; + AString mControlURL; AString mSessionID; bool mSetupTracksSuccessful; bool mSeekPending; @@ -1242,6 +1532,7 @@ private: int64_t mNumAccessUnitsReceived; bool mCheckPending; int32_t mCheckGeneration; + int32_t mCheckTimeoutGeneration; bool mTryTCPInterleaving; bool mTryFakeRTCP; bool mReceivedFirstRTCPPacket; @@ -1249,9 +1540,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); @@ -1279,6 +1574,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; @@ -1295,6 +1592,7 @@ private: formatDesc.c_str(), ×cale, &numChannels); info->mTimeScale = timescale; + info->mEOSReceived = false; ALOGV("track #%d URL=%s", mTracks.size(), trackURL.c_str()); @@ -1322,6 +1620,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="); @@ -1387,6 +1687,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); @@ -1417,30 +1748,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) { @@ -1461,6 +1806,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..3c7d82a --- /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> ¬ify, 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)) { + ALOGV("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 |