summaryrefslogtreecommitdiffstats
path: root/media/libstagefright/wifi-display
diff options
context:
space:
mode:
authorAndreas Huber <andih@google.com>2013-04-26 08:42:50 -0700
committerAndreas Huber <andih@google.com>2013-04-26 08:42:50 -0700
commitc86ef45279185b474bd6af0a7ae407f8ab577f13 (patch)
tree110e05de886cf816f0b9265931c611f74d668b8b /media/libstagefright/wifi-display
parentc540ea00f4763758c9c40594d1e086aeefda9b70 (diff)
downloadframeworks_av-c86ef45279185b474bd6af0a7ae407f8ab577f13.zip
frameworks_av-c86ef45279185b474bd6af0a7ae407f8ab577f13.tar.gz
frameworks_av-c86ef45279185b474bd6af0a7ae407f8ab577f13.tar.bz2
Revert "Remove all traces of wifi display sink implementation and supporting code."
This reverts commit 3a9682a86ead84d6f60d3f3aa01b2b4d34af983d.
Diffstat (limited to 'media/libstagefright/wifi-display')
-rw-r--r--media/libstagefright/wifi-display/Android.mk76
-rw-r--r--media/libstagefright/wifi-display/MediaReceiver.cpp328
-rw-r--r--media/libstagefright/wifi-display/MediaReceiver.h111
-rw-r--r--media/libstagefright/wifi-display/MediaSender.cpp16
-rw-r--r--media/libstagefright/wifi-display/MediaSender.h1
-rw-r--r--media/libstagefright/wifi-display/SNTPClient.cpp174
-rw-r--r--media/libstagefright/wifi-display/SNTPClient.h62
-rw-r--r--media/libstagefright/wifi-display/TimeSyncer.cpp338
-rw-r--r--media/libstagefright/wifi-display/TimeSyncer.h109
-rw-r--r--media/libstagefright/wifi-display/nettest.cpp400
-rw-r--r--media/libstagefright/wifi-display/rtp/RTPAssembler.cpp328
-rw-r--r--media/libstagefright/wifi-display/rtp/RTPAssembler.h92
-rw-r--r--media/libstagefright/wifi-display/rtp/RTPReceiver.cpp1153
-rw-r--r--media/libstagefright/wifi-display/rtp/RTPReceiver.h125
-rw-r--r--media/libstagefright/wifi-display/rtp/RTPSender.cpp11
-rw-r--r--media/libstagefright/wifi-display/rtp/RTPSender.h1
-rw-r--r--media/libstagefright/wifi-display/rtptest.cpp565
-rw-r--r--media/libstagefright/wifi-display/sink/DirectRenderer.cpp625
-rw-r--r--media/libstagefright/wifi-display/sink/DirectRenderer.h82
-rw-r--r--media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp917
-rw-r--r--media/libstagefright/wifi-display/sink/WifiDisplaySink.h196
-rw-r--r--media/libstagefright/wifi-display/source/PlaybackSession.cpp85
-rw-r--r--media/libstagefright/wifi-display/source/WifiDisplaySource.cpp14
-rw-r--r--media/libstagefright/wifi-display/source/WifiDisplaySource.h3
-rw-r--r--media/libstagefright/wifi-display/udptest.cpp116
-rw-r--r--media/libstagefright/wifi-display/wfd.cpp125
26 files changed, 6049 insertions, 4 deletions
diff --git a/media/libstagefright/wifi-display/Android.mk b/media/libstagefright/wifi-display/Android.mk
index 061ae89..f99ef60 100644
--- a/media/libstagefright/wifi-display/Android.mk
+++ b/media/libstagefright/wifi-display/Android.mk
@@ -4,10 +4,17 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
ANetworkSession.cpp \
+ MediaReceiver.cpp \
MediaSender.cpp \
Parameters.cpp \
ParsedMessage.cpp \
+ rtp/RTPAssembler.cpp \
+ rtp/RTPReceiver.cpp \
rtp/RTPSender.cpp \
+ sink/DirectRenderer.cpp \
+ sink/WifiDisplaySink.cpp \
+ SNTPClient.cpp \
+ TimeSyncer.cpp \
source/Converter.cpp \
source/MediaPuller.cpp \
source/PlaybackSession.cpp \
@@ -60,3 +67,72 @@ LOCAL_MODULE:= wfd
LOCAL_MODULE_TAGS := debug
include $(BUILD_EXECUTABLE)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ udptest.cpp \
+
+LOCAL_SHARED_LIBRARIES:= \
+ libbinder \
+ libgui \
+ libmedia \
+ libstagefright \
+ libstagefright_foundation \
+ libstagefright_wfd \
+ libutils \
+ liblog \
+
+LOCAL_MODULE:= udptest
+
+LOCAL_MODULE_TAGS := debug
+
+include $(BUILD_EXECUTABLE)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ rtptest.cpp \
+
+LOCAL_SHARED_LIBRARIES:= \
+ libbinder \
+ libgui \
+ libmedia \
+ libstagefright \
+ libstagefright_foundation \
+ libstagefright_wfd \
+ libutils \
+ liblog \
+
+LOCAL_MODULE:= rtptest
+
+LOCAL_MODULE_TAGS := debug
+
+include $(BUILD_EXECUTABLE)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ nettest.cpp \
+
+LOCAL_SHARED_LIBRARIES:= \
+ libbinder \
+ libgui \
+ libmedia \
+ libstagefright \
+ libstagefright_foundation \
+ libstagefright_wfd \
+ libutils \
+ liblog \
+
+LOCAL_MODULE:= nettest
+
+LOCAL_MODULE_TAGS := debug
+
+include $(BUILD_EXECUTABLE)
diff --git a/media/libstagefright/wifi-display/MediaReceiver.cpp b/media/libstagefright/wifi-display/MediaReceiver.cpp
new file mode 100644
index 0000000..364acb9
--- /dev/null
+++ b/media/libstagefright/wifi-display/MediaReceiver.cpp
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2013, 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 "MediaReceiver"
+#include <utils/Log.h>
+
+#include "MediaReceiver.h"
+
+#include "ANetworkSession.h"
+#include "AnotherPacketSource.h"
+#include "rtp/RTPReceiver.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+MediaReceiver::MediaReceiver(
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> &notify)
+ : mNetSession(netSession),
+ mNotify(notify),
+ mMode(MODE_UNDEFINED),
+ mGeneration(0),
+ mInitStatus(OK),
+ mInitDoneCount(0) {
+}
+
+MediaReceiver::~MediaReceiver() {
+}
+
+ssize_t MediaReceiver::addTrack(
+ RTPReceiver::TransportMode rtpMode,
+ RTPReceiver::TransportMode rtcpMode,
+ int32_t *localRTPPort) {
+ if (mMode != MODE_UNDEFINED) {
+ return INVALID_OPERATION;
+ }
+
+ size_t trackIndex = mTrackInfos.size();
+
+ TrackInfo info;
+
+ sp<AMessage> notify = new AMessage(kWhatReceiverNotify, id());
+ notify->setInt32("generation", mGeneration);
+ notify->setSize("trackIndex", trackIndex);
+
+ info.mReceiver = new RTPReceiver(mNetSession, notify);
+ looper()->registerHandler(info.mReceiver);
+
+ info.mReceiver->registerPacketType(
+ 33, RTPReceiver::PACKETIZATION_TRANSPORT_STREAM);
+
+ info.mReceiver->registerPacketType(
+ 96, RTPReceiver::PACKETIZATION_AAC);
+
+ info.mReceiver->registerPacketType(
+ 97, RTPReceiver::PACKETIZATION_H264);
+
+ status_t err = info.mReceiver->initAsync(
+ rtpMode,
+ rtcpMode,
+ localRTPPort);
+
+ if (err != OK) {
+ looper()->unregisterHandler(info.mReceiver->id());
+ info.mReceiver.clear();
+
+ return err;
+ }
+
+ mTrackInfos.push_back(info);
+
+ return trackIndex;
+}
+
+status_t MediaReceiver::connectTrack(
+ size_t trackIndex,
+ const char *remoteHost,
+ int32_t remoteRTPPort,
+ int32_t remoteRTCPPort) {
+ if (trackIndex >= mTrackInfos.size()) {
+ return -ERANGE;
+ }
+
+ TrackInfo *info = &mTrackInfos.editItemAt(trackIndex);
+ return info->mReceiver->connect(remoteHost, remoteRTPPort, remoteRTCPPort);
+}
+
+status_t MediaReceiver::initAsync(Mode mode) {
+ if ((mode == MODE_TRANSPORT_STREAM || mode == MODE_TRANSPORT_STREAM_RAW)
+ && mTrackInfos.size() > 1) {
+ return INVALID_OPERATION;
+ }
+
+ sp<AMessage> msg = new AMessage(kWhatInit, id());
+ msg->setInt32("mode", mode);
+ msg->post();
+
+ return OK;
+}
+
+void MediaReceiver::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatInit:
+ {
+ int32_t mode;
+ CHECK(msg->findInt32("mode", &mode));
+
+ CHECK_EQ(mMode, MODE_UNDEFINED);
+ mMode = (Mode)mode;
+
+ if (mInitStatus != OK || mInitDoneCount == mTrackInfos.size()) {
+ notifyInitDone(mInitStatus);
+ }
+
+ mTSParser = new ATSParser(
+ ATSParser::ALIGNED_VIDEO_DATA
+ | ATSParser::TS_TIMESTAMPS_ARE_ABSOLUTE);
+
+ mFormatKnownMask = 0;
+ break;
+ }
+
+ case kWhatReceiverNotify:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+ if (generation != mGeneration) {
+ break;
+ }
+
+ onReceiverNotify(msg);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void MediaReceiver::onReceiverNotify(const sp<AMessage> &msg) {
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ switch (what) {
+ case RTPReceiver::kWhatInitDone:
+ {
+ ++mInitDoneCount;
+
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ if (err != OK) {
+ mInitStatus = err;
+ ++mGeneration;
+ }
+
+ if (mMode != MODE_UNDEFINED) {
+ if (mInitStatus != OK || mInitDoneCount == mTrackInfos.size()) {
+ notifyInitDone(mInitStatus);
+ }
+ }
+ break;
+ }
+
+ case RTPReceiver::kWhatError:
+ {
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ notifyError(err);
+ break;
+ }
+
+ case RTPReceiver::kWhatAccessUnit:
+ {
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ int32_t followsDiscontinuity;
+ if (!msg->findInt32(
+ "followsDiscontinuity", &followsDiscontinuity)) {
+ followsDiscontinuity = 0;
+ }
+
+ if (mMode == MODE_TRANSPORT_STREAM) {
+ if (followsDiscontinuity) {
+ mTSParser->signalDiscontinuity(
+ ATSParser::DISCONTINUITY_TIME, NULL /* extra */);
+ }
+
+ for (size_t offset = 0;
+ offset < accessUnit->size(); offset += 188) {
+ status_t err = mTSParser->feedTSPacket(
+ accessUnit->data() + offset, 188);
+
+ if (err != OK) {
+ notifyError(err);
+ break;
+ }
+ }
+
+ drainPackets(0 /* trackIndex */, ATSParser::VIDEO);
+ drainPackets(1 /* trackIndex */, ATSParser::AUDIO);
+ } else {
+ postAccessUnit(trackIndex, accessUnit, NULL);
+ }
+ break;
+ }
+
+ case RTPReceiver::kWhatPacketLost:
+ {
+ notifyPacketLost();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void MediaReceiver::drainPackets(
+ size_t trackIndex, ATSParser::SourceType type) {
+ sp<AnotherPacketSource> source =
+ static_cast<AnotherPacketSource *>(
+ mTSParser->getSource(type).get());
+
+ if (source == NULL) {
+ return;
+ }
+
+ sp<AMessage> format;
+ if (!(mFormatKnownMask & (1ul << trackIndex))) {
+ sp<MetaData> meta = source->getFormat();
+ CHECK(meta != NULL);
+
+ CHECK_EQ((status_t)OK, convertMetaDataToMessage(meta, &format));
+
+ mFormatKnownMask |= 1ul << trackIndex;
+ }
+
+ status_t finalResult;
+ while (source->hasBufferAvailable(&finalResult)) {
+ sp<ABuffer> accessUnit;
+ status_t err = source->dequeueAccessUnit(&accessUnit);
+ if (err == OK) {
+ postAccessUnit(trackIndex, accessUnit, format);
+ format.clear();
+ } else if (err != INFO_DISCONTINUITY) {
+ notifyError(err);
+ }
+ }
+
+ if (finalResult != OK) {
+ notifyError(finalResult);
+ }
+}
+
+void MediaReceiver::notifyInitDone(status_t err) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatInitDone);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+void MediaReceiver::notifyError(status_t err) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatError);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+void MediaReceiver::notifyPacketLost() {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatPacketLost);
+ notify->post();
+}
+
+void MediaReceiver::postAccessUnit(
+ size_t trackIndex,
+ const sp<ABuffer> &accessUnit,
+ const sp<AMessage> &format) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatAccessUnit);
+ notify->setSize("trackIndex", trackIndex);
+ notify->setBuffer("accessUnit", accessUnit);
+
+ if (format != NULL) {
+ notify->setMessage("format", format);
+ }
+
+ notify->post();
+}
+
+status_t MediaReceiver::informSender(
+ size_t trackIndex, const sp<AMessage> &params) {
+ if (trackIndex >= mTrackInfos.size()) {
+ return -ERANGE;
+ }
+
+ TrackInfo *info = &mTrackInfos.editItemAt(trackIndex);
+ return info->mReceiver->informSender(params);
+}
+
+} // namespace android
+
+
diff --git a/media/libstagefright/wifi-display/MediaReceiver.h b/media/libstagefright/wifi-display/MediaReceiver.h
new file mode 100644
index 0000000..afbb407
--- /dev/null
+++ b/media/libstagefright/wifi-display/MediaReceiver.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2013, 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 <media/stagefright/foundation/AHandler.h>
+
+#include "ATSParser.h"
+#include "rtp/RTPReceiver.h"
+
+namespace android {
+
+struct ABuffer;
+struct ANetworkSession;
+struct AMessage;
+struct ATSParser;
+
+// This class facilitates receiving of media data for one or more tracks
+// over RTP. Either a 1:1 track to RTP channel mapping is used or a single
+// RTP channel provides the data for a transport stream that is consequently
+// demuxed and its track's data provided to the observer.
+struct MediaReceiver : public AHandler {
+ enum {
+ kWhatInitDone,
+ kWhatError,
+ kWhatAccessUnit,
+ kWhatPacketLost,
+ };
+
+ MediaReceiver(
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> &notify);
+
+ ssize_t addTrack(
+ RTPReceiver::TransportMode rtpMode,
+ RTPReceiver::TransportMode rtcpMode,
+ int32_t *localRTPPort);
+
+ status_t connectTrack(
+ size_t trackIndex,
+ const char *remoteHost,
+ int32_t remoteRTPPort,
+ int32_t remoteRTCPPort);
+
+ enum Mode {
+ MODE_UNDEFINED,
+ MODE_TRANSPORT_STREAM,
+ MODE_TRANSPORT_STREAM_RAW,
+ MODE_ELEMENTARY_STREAMS,
+ };
+ status_t initAsync(Mode mode);
+
+ status_t informSender(size_t trackIndex, const sp<AMessage> &params);
+
+protected:
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+ virtual ~MediaReceiver();
+
+private:
+ enum {
+ kWhatInit,
+ kWhatReceiverNotify,
+ };
+
+ struct TrackInfo {
+ sp<RTPReceiver> mReceiver;
+ };
+
+ sp<ANetworkSession> mNetSession;
+ sp<AMessage> mNotify;
+
+ Mode mMode;
+ int32_t mGeneration;
+
+ Vector<TrackInfo> mTrackInfos;
+
+ status_t mInitStatus;
+ size_t mInitDoneCount;
+
+ sp<ATSParser> mTSParser;
+ uint32_t mFormatKnownMask;
+
+ void onReceiverNotify(const sp<AMessage> &msg);
+
+ void drainPackets(size_t trackIndex, ATSParser::SourceType type);
+
+ void notifyInitDone(status_t err);
+ void notifyError(status_t err);
+ void notifyPacketLost();
+
+ void postAccessUnit(
+ size_t trackIndex,
+ const sp<ABuffer> &accessUnit,
+ const sp<AMessage> &format);
+
+ DISALLOW_EVIL_CONSTRUCTORS(MediaReceiver);
+};
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/MediaSender.cpp b/media/libstagefright/wifi-display/MediaSender.cpp
index 8a3566f..33af66d 100644
--- a/media/libstagefright/wifi-display/MediaSender.cpp
+++ b/media/libstagefright/wifi-display/MediaSender.cpp
@@ -341,6 +341,22 @@ void MediaSender::onSenderNotify(const sp<AMessage> &msg) {
break;
}
+ case kWhatInformSender:
+ {
+ int64_t avgLatencyUs;
+ CHECK(msg->findInt64("avgLatencyUs", &avgLatencyUs));
+
+ int64_t maxLatencyUs;
+ CHECK(msg->findInt64("maxLatencyUs", &maxLatencyUs));
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatInformSender);
+ notify->setInt64("avgLatencyUs", avgLatencyUs);
+ notify->setInt64("maxLatencyUs", maxLatencyUs);
+ notify->post();
+ break;
+ }
+
default:
TRESPASS();
}
diff --git a/media/libstagefright/wifi-display/MediaSender.h b/media/libstagefright/wifi-display/MediaSender.h
index 64722c5..04538ea 100644
--- a/media/libstagefright/wifi-display/MediaSender.h
+++ b/media/libstagefright/wifi-display/MediaSender.h
@@ -43,6 +43,7 @@ struct MediaSender : public AHandler {
kWhatInitDone,
kWhatError,
kWhatNetworkStall,
+ kWhatInformSender,
};
MediaSender(
diff --git a/media/libstagefright/wifi-display/SNTPClient.cpp b/media/libstagefright/wifi-display/SNTPClient.cpp
new file mode 100644
index 0000000..5c0af6a
--- /dev/null
+++ b/media/libstagefright/wifi-display/SNTPClient.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2013, 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 "SNTPClient.h"
+
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/Utils.h>
+
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+namespace android {
+
+SNTPClient::SNTPClient() {
+}
+
+status_t SNTPClient::requestTime(const char *host) {
+ struct hostent *ent;
+ int64_t requestTimeNTP, requestTimeUs;
+ ssize_t n;
+ int64_t responseTimeUs, responseTimeNTP;
+ int64_t originateTimeNTP, receiveTimeNTP, transmitTimeNTP;
+ int64_t roundTripTimeNTP, clockOffsetNTP;
+
+ status_t err = UNKNOWN_ERROR;
+
+ int s = socket(AF_INET, SOCK_DGRAM, 0);
+
+ if (s < 0) {
+ err = -errno;
+
+ goto bail;
+ }
+
+ ent = gethostbyname(host);
+
+ if (ent == NULL) {
+ err = -ENOENT;
+ goto bail2;
+ }
+
+ struct sockaddr_in hostAddr;
+ memset(hostAddr.sin_zero, 0, sizeof(hostAddr.sin_zero));
+ hostAddr.sin_family = AF_INET;
+ hostAddr.sin_port = htons(kNTPPort);
+ hostAddr.sin_addr.s_addr = *(in_addr_t *)ent->h_addr;
+
+ uint8_t packet[kNTPPacketSize];
+ memset(packet, 0, sizeof(packet));
+
+ packet[0] = kNTPModeClient | (kNTPVersion << 3);
+
+ requestTimeNTP = getNowNTP();
+ requestTimeUs = ALooper::GetNowUs();
+ writeTimeStamp(&packet[kNTPTransmitTimeOffset], requestTimeNTP);
+
+ n = sendto(
+ s, packet, sizeof(packet), 0,
+ (const struct sockaddr *)&hostAddr, sizeof(hostAddr));
+
+ if (n < 0) {
+ err = -errno;
+ goto bail2;
+ }
+
+ memset(packet, 0, sizeof(packet));
+
+ do {
+ n = recv(s, packet, sizeof(packet), 0);
+ } while (n < 0 && errno == EINTR);
+
+ if (n < 0) {
+ err = -errno;
+ goto bail2;
+ }
+
+ responseTimeUs = ALooper::GetNowUs();
+
+ responseTimeNTP = requestTimeNTP + makeNTP(responseTimeUs - requestTimeUs);
+
+ originateTimeNTP = readTimeStamp(&packet[kNTPOriginateTimeOffset]);
+ receiveTimeNTP = readTimeStamp(&packet[kNTPReceiveTimeOffset]);
+ transmitTimeNTP = readTimeStamp(&packet[kNTPTransmitTimeOffset]);
+
+ roundTripTimeNTP =
+ makeNTP(responseTimeUs - requestTimeUs)
+ - (transmitTimeNTP - receiveTimeNTP);
+
+ clockOffsetNTP =
+ ((receiveTimeNTP - originateTimeNTP)
+ + (transmitTimeNTP - responseTimeNTP)) / 2;
+
+ mTimeReferenceNTP = responseTimeNTP + clockOffsetNTP;
+ mTimeReferenceUs = responseTimeUs;
+ mRoundTripTimeNTP = roundTripTimeNTP;
+
+ err = OK;
+
+bail2:
+ close(s);
+ s = -1;
+
+bail:
+ return err;
+}
+
+int64_t SNTPClient::adjustTimeUs(int64_t timeUs) const {
+ uint64_t nowNTP =
+ mTimeReferenceNTP + makeNTP(timeUs - mTimeReferenceUs);
+
+ int64_t nowUs =
+ (nowNTP >> 32) * 1000000ll
+ + ((nowNTP & 0xffffffff) * 1000000ll) / (1ll << 32);
+
+ nowUs -= ((70ll * 365 + 17) * 24) * 60 * 60 * 1000000ll;
+
+ return nowUs;
+}
+
+// static
+void SNTPClient::writeTimeStamp(uint8_t *dst, uint64_t ntpTime) {
+ *dst++ = (ntpTime >> 56) & 0xff;
+ *dst++ = (ntpTime >> 48) & 0xff;
+ *dst++ = (ntpTime >> 40) & 0xff;
+ *dst++ = (ntpTime >> 32) & 0xff;
+ *dst++ = (ntpTime >> 24) & 0xff;
+ *dst++ = (ntpTime >> 16) & 0xff;
+ *dst++ = (ntpTime >> 8) & 0xff;
+ *dst++ = ntpTime & 0xff;
+}
+
+// static
+uint64_t SNTPClient::readTimeStamp(const uint8_t *dst) {
+ return U64_AT(dst);
+}
+
+// static
+uint64_t SNTPClient::getNowNTP() {
+ struct timeval tv;
+ gettimeofday(&tv, NULL /* time zone */);
+
+ uint64_t nowUs = tv.tv_sec * 1000000ll + tv.tv_usec;
+
+ nowUs += ((70ll * 365 + 17) * 24) * 60 * 60 * 1000000ll;
+
+ return makeNTP(nowUs);
+}
+
+// static
+uint64_t SNTPClient::makeNTP(uint64_t deltaUs) {
+ uint64_t hi = deltaUs / 1000000ll;
+ uint64_t lo = ((1ll << 32) * (deltaUs % 1000000ll)) / 1000000ll;
+
+ return (hi << 32) | lo;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/SNTPClient.h b/media/libstagefright/wifi-display/SNTPClient.h
new file mode 100644
index 0000000..967d1fc
--- /dev/null
+++ b/media/libstagefright/wifi-display/SNTPClient.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013, 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 SNTP_CLIENT_H_
+
+#define SNTP_CLIENT_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+
+namespace android {
+
+// Implementation of the SNTP (Simple Network Time Protocol)
+struct SNTPClient {
+ SNTPClient();
+
+ status_t requestTime(const char *host);
+
+ // given a time obtained from ALooper::GetNowUs()
+ // return the number of us elapsed since Jan 1 1970 00:00:00 (UTC).
+ int64_t adjustTimeUs(int64_t timeUs) const;
+
+private:
+ enum {
+ kNTPPort = 123,
+ kNTPPacketSize = 48,
+ kNTPModeClient = 3,
+ kNTPVersion = 3,
+ kNTPTransmitTimeOffset = 40,
+ kNTPOriginateTimeOffset = 24,
+ kNTPReceiveTimeOffset = 32,
+ };
+
+ uint64_t mTimeReferenceNTP;
+ int64_t mTimeReferenceUs;
+ int64_t mRoundTripTimeNTP;
+
+ static void writeTimeStamp(uint8_t *dst, uint64_t ntpTime);
+ static uint64_t readTimeStamp(const uint8_t *dst);
+
+ static uint64_t getNowNTP();
+ static uint64_t makeNTP(uint64_t deltaUs);
+
+ DISALLOW_EVIL_CONSTRUCTORS(SNTPClient);
+};
+
+} // namespace android
+
+#endif // SNTP_CLIENT_H_
diff --git a/media/libstagefright/wifi-display/TimeSyncer.cpp b/media/libstagefright/wifi-display/TimeSyncer.cpp
new file mode 100644
index 0000000..cb429bc
--- /dev/null
+++ b/media/libstagefright/wifi-display/TimeSyncer.cpp
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2013, 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_NEBUG 0
+#define LOG_TAG "TimeSyncer"
+#include <utils/Log.h>
+
+#include "TimeSyncer.h"
+
+#include "ANetworkSession.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+TimeSyncer::TimeSyncer(
+ const sp<ANetworkSession> &netSession, const sp<AMessage> &notify)
+ : mNetSession(netSession),
+ mNotify(notify),
+ mIsServer(false),
+ mConnected(false),
+ mUDPSession(0),
+ mSeqNo(0),
+ mTotalTimeUs(0.0),
+ mPendingT1(0ll),
+ mTimeoutGeneration(0) {
+}
+
+TimeSyncer::~TimeSyncer() {
+}
+
+void TimeSyncer::startServer(unsigned localPort) {
+ sp<AMessage> msg = new AMessage(kWhatStartServer, id());
+ msg->setInt32("localPort", localPort);
+ msg->post();
+}
+
+void TimeSyncer::startClient(const char *remoteHost, unsigned remotePort) {
+ sp<AMessage> msg = new AMessage(kWhatStartClient, id());
+ msg->setString("remoteHost", remoteHost);
+ msg->setInt32("remotePort", remotePort);
+ msg->post();
+}
+
+void TimeSyncer::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatStartClient:
+ {
+ AString remoteHost;
+ CHECK(msg->findString("remoteHost", &remoteHost));
+
+ int32_t remotePort;
+ CHECK(msg->findInt32("remotePort", &remotePort));
+
+ sp<AMessage> notify = new AMessage(kWhatUDPNotify, id());
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->createUDPSession(
+ 0 /* localPort */,
+ remoteHost.c_str(),
+ remotePort,
+ notify,
+ &mUDPSession));
+
+ postSendPacket();
+ break;
+ }
+
+ case kWhatStartServer:
+ {
+ mIsServer = true;
+
+ int32_t localPort;
+ CHECK(msg->findInt32("localPort", &localPort));
+
+ sp<AMessage> notify = new AMessage(kWhatUDPNotify, id());
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->createUDPSession(
+ localPort, notify, &mUDPSession));
+
+ break;
+ }
+
+ case kWhatSendPacket:
+ {
+ if (mHistory.size() == 0) {
+ ALOGI("starting batch");
+ }
+
+ TimeInfo ti;
+ memset(&ti, 0, sizeof(ti));
+
+ ti.mT1 = ALooper::GetNowUs();
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->sendRequest(
+ mUDPSession, &ti, sizeof(ti)));
+
+ mPendingT1 = ti.mT1;
+ postTimeout();
+ break;
+ }
+
+ case kWhatTimedOut:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mTimeoutGeneration) {
+ break;
+ }
+
+ ALOGI("timed out, sending another request");
+ postSendPacket();
+ break;
+ }
+
+ case kWhatUDPNotify:
+ {
+ int32_t reason;
+ CHECK(msg->findInt32("reason", &reason));
+
+ switch (reason) {
+ case ANetworkSession::kWhatError:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ AString detail;
+ CHECK(msg->findString("detail", &detail));
+
+ ALOGE("An error occurred in session %d (%d, '%s/%s').",
+ sessionID,
+ err,
+ detail.c_str(),
+ strerror(-err));
+
+ mNetSession->destroySession(sessionID);
+
+ cancelTimeout();
+
+ notifyError(err);
+ break;
+ }
+
+ case ANetworkSession::kWhatDatagram:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ sp<ABuffer> packet;
+ CHECK(msg->findBuffer("data", &packet));
+
+ int64_t arrivalTimeUs;
+ CHECK(packet->meta()->findInt64(
+ "arrivalTimeUs", &arrivalTimeUs));
+
+ CHECK_EQ(packet->size(), sizeof(TimeInfo));
+
+ TimeInfo *ti = (TimeInfo *)packet->data();
+
+ if (mIsServer) {
+ if (!mConnected) {
+ AString fromAddr;
+ CHECK(msg->findString("fromAddr", &fromAddr));
+
+ int32_t fromPort;
+ CHECK(msg->findInt32("fromPort", &fromPort));
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->connectUDPSession(
+ mUDPSession, fromAddr.c_str(), fromPort));
+
+ mConnected = true;
+ }
+
+ ti->mT2 = arrivalTimeUs;
+ ti->mT3 = ALooper::GetNowUs();
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->sendRequest(
+ mUDPSession, ti, sizeof(*ti)));
+ } else {
+ if (ti->mT1 != mPendingT1) {
+ break;
+ }
+
+ cancelTimeout();
+ mPendingT1 = 0;
+
+ ti->mT4 = arrivalTimeUs;
+
+ // One way delay for a packet to travel from client
+ // to server or back (assumed to be the same either way).
+ int64_t delay =
+ (ti->mT2 - ti->mT1 + ti->mT4 - ti->mT3) / 2;
+
+ // Offset between the client clock (T1, T4) and the
+ // server clock (T2, T3) timestamps.
+ int64_t offset =
+ (ti->mT2 - ti->mT1 - ti->mT4 + ti->mT3) / 2;
+
+ mHistory.push_back(*ti);
+
+ ALOGV("delay = %lld us,\toffset %lld us",
+ delay,
+ offset);
+
+ if (mHistory.size() < kNumPacketsPerBatch) {
+ postSendPacket(1000000ll / 30);
+ } else {
+ notifyOffset();
+
+ ALOGI("batch done");
+
+ mHistory.clear();
+ postSendPacket(kBatchDelayUs);
+ }
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void TimeSyncer::postSendPacket(int64_t delayUs) {
+ (new AMessage(kWhatSendPacket, id()))->post(delayUs);
+}
+
+void TimeSyncer::postTimeout() {
+ sp<AMessage> msg = new AMessage(kWhatTimedOut, id());
+ msg->setInt32("generation", mTimeoutGeneration);
+ msg->post(kTimeoutDelayUs);
+}
+
+void TimeSyncer::cancelTimeout() {
+ ++mTimeoutGeneration;
+}
+
+void TimeSyncer::notifyError(status_t err) {
+ if (mNotify == NULL) {
+ looper()->stop();
+ return;
+ }
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatError);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+// static
+int TimeSyncer::CompareRountripTime(const TimeInfo *ti1, const TimeInfo *ti2) {
+ int64_t rt1 = ti1->mT4 - ti1->mT1;
+ int64_t rt2 = ti2->mT4 - ti2->mT1;
+
+ if (rt1 < rt2) {
+ return -1;
+ } else if (rt1 > rt2) {
+ return 1;
+ }
+
+ return 0;
+}
+
+void TimeSyncer::notifyOffset() {
+ mHistory.sort(CompareRountripTime);
+
+ int64_t sum = 0ll;
+ size_t count = 0;
+
+ // Only consider the third of the information associated with the best
+ // (smallest) roundtrip times.
+ for (size_t i = 0; i < mHistory.size() / 3; ++i) {
+ const TimeInfo *ti = &mHistory[i];
+
+#if 0
+ // One way delay for a packet to travel from client
+ // to server or back (assumed to be the same either way).
+ int64_t delay =
+ (ti->mT2 - ti->mT1 + ti->mT4 - ti->mT3) / 2;
+#endif
+
+ // Offset between the client clock (T1, T4) and the
+ // server clock (T2, T3) timestamps.
+ int64_t offset =
+ (ti->mT2 - ti->mT1 - ti->mT4 + ti->mT3) / 2;
+
+ ALOGV("(%d) RT: %lld us, offset: %lld us",
+ i, ti->mT4 - ti->mT1, offset);
+
+ sum += offset;
+ ++count;
+ }
+
+ if (mNotify == NULL) {
+ ALOGI("avg. offset is %lld", sum / count);
+ return;
+ }
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatTimeOffset);
+ notify->setInt64("offset", sum / count);
+ notify->post();
+}
+
+} // namespace android
diff --git a/media/libstagefright/wifi-display/TimeSyncer.h b/media/libstagefright/wifi-display/TimeSyncer.h
new file mode 100644
index 0000000..4e7571f
--- /dev/null
+++ b/media/libstagefright/wifi-display/TimeSyncer.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2013, 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 TIME_SYNCER_H_
+
+#define TIME_SYNCER_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct ANetworkSession;
+
+/*
+ TimeSyncer allows us to synchronize time between a client and a server.
+ The client sends a UDP packet containing its send-time to the server,
+ the server sends that packet back to the client amended with information
+ about when it was received as well as the time the reply was sent back.
+ Finally the client receives the reply and has now enough information to
+ compute the clock offset between client and server assuming that packet
+ exchange is symmetric, i.e. time for a packet client->server and
+ server->client is roughly equal.
+ This exchange is repeated a number of times and the average offset computed
+ over the 30% of packets that had the lowest roundtrip times.
+ The offset is determined every 10 secs to account for slight differences in
+ clock frequency.
+*/
+struct TimeSyncer : public AHandler {
+ enum {
+ kWhatError,
+ kWhatTimeOffset,
+ };
+ TimeSyncer(
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> &notify);
+
+ void startServer(unsigned localPort);
+ void startClient(const char *remoteHost, unsigned remotePort);
+
+protected:
+ virtual ~TimeSyncer();
+
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatStartServer,
+ kWhatStartClient,
+ kWhatUDPNotify,
+ kWhatSendPacket,
+ kWhatTimedOut,
+ };
+
+ struct TimeInfo {
+ int64_t mT1; // client timestamp at send
+ int64_t mT2; // server timestamp at receive
+ int64_t mT3; // server timestamp at send
+ int64_t mT4; // client timestamp at receive
+ };
+
+ enum {
+ kNumPacketsPerBatch = 30,
+ };
+ static const int64_t kTimeoutDelayUs = 500000ll;
+ static const int64_t kBatchDelayUs = 60000000ll; // every minute
+
+ sp<ANetworkSession> mNetSession;
+ sp<AMessage> mNotify;
+
+ bool mIsServer;
+ bool mConnected;
+ int32_t mUDPSession;
+ uint32_t mSeqNo;
+ double mTotalTimeUs;
+
+ Vector<TimeInfo> mHistory;
+
+ int64_t mPendingT1;
+ int32_t mTimeoutGeneration;
+
+ void postSendPacket(int64_t delayUs = 0ll);
+
+ void postTimeout();
+ void cancelTimeout();
+
+ void notifyError(status_t err);
+ void notifyOffset();
+
+ static int CompareRountripTime(const TimeInfo *ti1, const TimeInfo *ti2);
+
+ DISALLOW_EVIL_CONSTRUCTORS(TimeSyncer);
+};
+
+} // namespace android
+
+#endif // TIME_SYNCER_H_
diff --git a/media/libstagefright/wifi-display/nettest.cpp b/media/libstagefright/wifi-display/nettest.cpp
new file mode 100644
index 0000000..0779bf5
--- /dev/null
+++ b/media/libstagefright/wifi-display/nettest.cpp
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2013, 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_NEBUG 0
+#define LOG_TAG "nettest"
+#include <utils/Log.h>
+
+#include "ANetworkSession.h"
+#include "TimeSyncer.h"
+
+#include <binder/ProcessState.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/NuMediaExtractor.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+struct TestHandler : public AHandler {
+ TestHandler(const sp<ANetworkSession> &netSession);
+
+ void listen(int32_t port);
+ void connect(const char *host, int32_t port);
+
+protected:
+ virtual ~TestHandler();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kTimeSyncerPort = 8123,
+ };
+
+ enum {
+ kWhatListen,
+ kWhatConnect,
+ kWhatTimeSyncerNotify,
+ kWhatNetNotify,
+ kWhatSendMore,
+ kWhatStop,
+ };
+
+ sp<ANetworkSession> mNetSession;
+ sp<TimeSyncer> mTimeSyncer;
+
+ int32_t mServerSessionID;
+ int32_t mSessionID;
+
+ int64_t mTimeOffsetUs;
+ bool mTimeOffsetValid;
+
+ int32_t mCounter;
+
+ int64_t mMaxDelayMs;
+
+ void dumpDelay(int32_t counter, int64_t delayMs);
+
+ DISALLOW_EVIL_CONSTRUCTORS(TestHandler);
+};
+
+TestHandler::TestHandler(const sp<ANetworkSession> &netSession)
+ : mNetSession(netSession),
+ mServerSessionID(0),
+ mSessionID(0),
+ mTimeOffsetUs(-1ll),
+ mTimeOffsetValid(false),
+ mCounter(0),
+ mMaxDelayMs(-1ll) {
+}
+
+TestHandler::~TestHandler() {
+}
+
+void TestHandler::listen(int32_t port) {
+ sp<AMessage> msg = new AMessage(kWhatListen, id());
+ msg->setInt32("port", port);
+ msg->post();
+}
+
+void TestHandler::connect(const char *host, int32_t port) {
+ sp<AMessage> msg = new AMessage(kWhatConnect, id());
+ msg->setString("host", host);
+ msg->setInt32("port", port);
+ msg->post();
+}
+
+void TestHandler::dumpDelay(int32_t counter, int64_t delayMs) {
+ static const int64_t kMinDelayMs = 0;
+ static const int64_t kMaxDelayMs = 300;
+
+ const char *kPattern = "########################################";
+ size_t kPatternSize = strlen(kPattern);
+
+ int n = (kPatternSize * (delayMs - kMinDelayMs))
+ / (kMaxDelayMs - kMinDelayMs);
+
+ if (n < 0) {
+ n = 0;
+ } else if ((size_t)n > kPatternSize) {
+ n = kPatternSize;
+ }
+
+ if (delayMs > mMaxDelayMs) {
+ mMaxDelayMs = delayMs;
+ }
+
+ ALOGI("[%d] (%4lld ms / %4lld ms) %s",
+ counter,
+ delayMs,
+ mMaxDelayMs,
+ kPattern + kPatternSize - n);
+}
+
+void TestHandler::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatListen:
+ {
+ sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+
+ notify = new AMessage(kWhatNetNotify, id());
+
+ int32_t port;
+ CHECK(msg->findInt32("port", &port));
+
+ struct in_addr ifaceAddr;
+ ifaceAddr.s_addr = INADDR_ANY;
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->createTCPDatagramSession(
+ ifaceAddr,
+ port,
+ notify,
+ &mServerSessionID));
+ break;
+ }
+
+ case kWhatConnect:
+ {
+ sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+ mTimeSyncer->startServer(kTimeSyncerPort);
+
+ AString host;
+ CHECK(msg->findString("host", &host));
+
+ int32_t port;
+ CHECK(msg->findInt32("port", &port));
+
+ notify = new AMessage(kWhatNetNotify, id());
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->createTCPDatagramSession(
+ 0 /* localPort */,
+ host.c_str(),
+ port,
+ notify,
+ &mSessionID));
+ break;
+ }
+
+ case kWhatNetNotify:
+ {
+ int32_t reason;
+ CHECK(msg->findInt32("reason", &reason));
+
+ switch (reason) {
+ case ANetworkSession::kWhatConnected:
+ {
+ ALOGI("kWhatConnected");
+
+ (new AMessage(kWhatSendMore, id()))->post();
+ break;
+ }
+
+ case ANetworkSession::kWhatClientConnected:
+ {
+ ALOGI("kWhatClientConnected");
+
+ CHECK_EQ(mSessionID, 0);
+ CHECK(msg->findInt32("sessionID", &mSessionID));
+
+ AString clientIP;
+ CHECK(msg->findString("client-ip", &clientIP));
+
+ mTimeSyncer->startClient(clientIP.c_str(), kTimeSyncerPort);
+ break;
+ }
+
+ case ANetworkSession::kWhatDatagram:
+ {
+ sp<ABuffer> packet;
+ CHECK(msg->findBuffer("data", &packet));
+
+ CHECK_EQ(packet->size(), 12u);
+
+ int32_t counter = U32_AT(packet->data());
+ int64_t timeUs = U64_AT(packet->data() + 4);
+
+ if (mTimeOffsetValid) {
+ timeUs -= mTimeOffsetUs;
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t delayMs = (nowUs - timeUs) / 1000ll;
+
+ dumpDelay(counter, delayMs);
+ } else {
+ ALOGI("received %d", counter);
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatError:
+ {
+ ALOGE("kWhatError");
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+ break;
+ }
+
+ case kWhatTimeSyncerNotify:
+ {
+ CHECK(msg->findInt64("offset", &mTimeOffsetUs));
+ mTimeOffsetValid = true;
+ break;
+ }
+
+ case kWhatSendMore:
+ {
+ uint8_t buffer[4 + 8];
+ buffer[0] = mCounter >> 24;
+ buffer[1] = (mCounter >> 16) & 0xff;
+ buffer[2] = (mCounter >> 8) & 0xff;
+ buffer[3] = mCounter & 0xff;
+
+ int64_t nowUs = ALooper::GetNowUs();
+
+ buffer[4] = nowUs >> 56;
+ buffer[5] = (nowUs >> 48) & 0xff;
+ buffer[6] = (nowUs >> 40) & 0xff;
+ buffer[7] = (nowUs >> 32) & 0xff;
+ buffer[8] = (nowUs >> 24) & 0xff;
+ buffer[9] = (nowUs >> 16) & 0xff;
+ buffer[10] = (nowUs >> 8) & 0xff;
+ buffer[11] = nowUs & 0xff;
+
+ ++mCounter;
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->sendRequest(
+ mSessionID,
+ buffer,
+ sizeof(buffer),
+ true /* timeValid */,
+ nowUs));
+
+ msg->post(100000ll);
+ break;
+ }
+
+ case kWhatStop:
+ {
+ if (mSessionID != 0) {
+ mNetSession->destroySession(mSessionID);
+ mSessionID = 0;
+ }
+
+ if (mServerSessionID != 0) {
+ mNetSession->destroySession(mServerSessionID);
+ mServerSessionID = 0;
+ }
+
+ looper()->stop();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+} // namespace android
+
+static void usage(const char *me) {
+ fprintf(stderr,
+ "usage: %s -c host:port\tconnect to remote host\n"
+ " -l port \tlisten\n",
+ me);
+}
+
+int main(int argc, char **argv) {
+ using namespace android;
+
+ // srand(time(NULL));
+
+ ProcessState::self()->startThreadPool();
+
+ DataSource::RegisterDefaultSniffers();
+
+ int32_t connectToPort = -1;
+ AString connectToHost;
+
+ int32_t listenOnPort = -1;
+
+ int res;
+ while ((res = getopt(argc, argv, "hc:l:")) >= 0) {
+ switch (res) {
+ case 'c':
+ {
+ const char *colonPos = strrchr(optarg, ':');
+
+ if (colonPos == NULL) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ connectToHost.setTo(optarg, colonPos - optarg);
+
+ char *end;
+ connectToPort = strtol(colonPos + 1, &end, 10);
+
+ if (*end != '\0' || end == colonPos + 1
+ || connectToPort < 0 || connectToPort > 65535) {
+ fprintf(stderr, "Illegal port specified.\n");
+ exit(1);
+ }
+ break;
+ }
+
+ case 'l':
+ {
+ char *end;
+ listenOnPort = strtol(optarg, &end, 10);
+
+ if (*end != '\0' || end == optarg
+ || listenOnPort < 0 || listenOnPort > 65535) {
+ fprintf(stderr, "Illegal port specified.\n");
+ exit(1);
+ }
+ break;
+ }
+
+ case '?':
+ case 'h':
+ usage(argv[0]);
+ exit(1);
+ }
+ }
+
+ if ((listenOnPort < 0 && connectToPort < 0)
+ || (listenOnPort >= 0 && connectToPort >= 0)) {
+ fprintf(stderr,
+ "You need to select either client or server mode.\n");
+ exit(1);
+ }
+
+ sp<ANetworkSession> netSession = new ANetworkSession;
+ netSession->start();
+
+ sp<ALooper> looper = new ALooper;
+
+ sp<TestHandler> handler = new TestHandler(netSession);
+ looper->registerHandler(handler);
+
+ if (listenOnPort) {
+ handler->listen(listenOnPort);
+ }
+
+ if (connectToPort >= 0) {
+ handler->connect(connectToHost.c_str(), connectToPort);
+ }
+
+ looper->start(true /* runOnCallingThread */);
+
+ return 0;
+}
diff --git a/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp b/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp
new file mode 100644
index 0000000..7a96081
--- /dev/null
+++ b/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2013, 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 "RTPAssembler"
+#include <utils/Log.h>
+
+#include "RTPAssembler.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+RTPReceiver::Assembler::Assembler(const sp<AMessage> &notify)
+ : mNotify(notify) {
+}
+
+void RTPReceiver::Assembler::postAccessUnit(
+ const sp<ABuffer> &accessUnit, bool followsDiscontinuity) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", RTPReceiver::kWhatAccessUnit);
+ notify->setBuffer("accessUnit", accessUnit);
+ notify->setInt32("followsDiscontinuity", followsDiscontinuity);
+ notify->post();
+}
+////////////////////////////////////////////////////////////////////////////////
+
+RTPReceiver::TSAssembler::TSAssembler(const sp<AMessage> &notify)
+ : Assembler(notify),
+ mSawDiscontinuity(false) {
+}
+
+void RTPReceiver::TSAssembler::signalDiscontinuity() {
+ mSawDiscontinuity = true;
+}
+
+status_t RTPReceiver::TSAssembler::processPacket(const sp<ABuffer> &packet) {
+ int32_t rtpTime;
+ CHECK(packet->meta()->findInt32("rtp-time", &rtpTime));
+
+ packet->meta()->setInt64("timeUs", (rtpTime * 100ll) / 9);
+
+ postAccessUnit(packet, mSawDiscontinuity);
+
+ if (mSawDiscontinuity) {
+ mSawDiscontinuity = false;
+ }
+
+ return OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+RTPReceiver::H264Assembler::H264Assembler(const sp<AMessage> &notify)
+ : Assembler(notify),
+ mState(0),
+ mIndicator(0),
+ mNALType(0),
+ mAccessUnitRTPTime(0) {
+}
+
+void RTPReceiver::H264Assembler::signalDiscontinuity() {
+ reset();
+}
+
+status_t RTPReceiver::H264Assembler::processPacket(const sp<ABuffer> &packet) {
+ status_t err = internalProcessPacket(packet);
+
+ if (err != OK) {
+ reset();
+ }
+
+ return err;
+}
+
+status_t RTPReceiver::H264Assembler::internalProcessPacket(
+ const sp<ABuffer> &packet) {
+ const uint8_t *data = packet->data();
+ size_t size = packet->size();
+
+ switch (mState) {
+ case 0:
+ {
+ if (size < 1 || (data[0] & 0x80)) {
+ ALOGV("Malformed H264 RTP packet (empty or F-bit set)");
+ return ERROR_MALFORMED;
+ }
+
+ unsigned nalType = data[0] & 0x1f;
+ if (nalType >= 1 && nalType <= 23) {
+ addSingleNALUnit(packet);
+ ALOGV("added single NAL packet");
+ } else if (nalType == 28) {
+ // FU-A
+ unsigned indicator = data[0];
+ CHECK((indicator & 0x1f) == 28);
+
+ if (size < 2) {
+ ALOGV("Malformed H264 FU-A packet (single byte)");
+ return ERROR_MALFORMED;
+ }
+
+ if (!(data[1] & 0x80)) {
+ ALOGV("Malformed H264 FU-A packet (no start bit)");
+ return ERROR_MALFORMED;
+ }
+
+ mIndicator = data[0];
+ mNALType = data[1] & 0x1f;
+ uint32_t nri = (data[0] >> 5) & 3;
+
+ clearAccumulator();
+
+ uint8_t byte = mNALType | (nri << 5);
+ appendToAccumulator(&byte, 1);
+ appendToAccumulator(data + 2, size - 2);
+
+ int32_t rtpTime;
+ CHECK(packet->meta()->findInt32("rtp-time", &rtpTime));
+ mAccumulator->meta()->setInt32("rtp-time", rtpTime);
+
+ if (data[1] & 0x40) {
+ // Huh? End bit also set on the first buffer.
+ addSingleNALUnit(mAccumulator);
+ clearAccumulator();
+
+ ALOGV("added FU-A");
+ break;
+ }
+
+ mState = 1;
+ } else if (nalType == 24) {
+ // STAP-A
+
+ status_t err = addSingleTimeAggregationPacket(packet);
+ if (err != OK) {
+ return err;
+ }
+ } else {
+ ALOGV("Malformed H264 packet (unknown type %d)", nalType);
+ return ERROR_UNSUPPORTED;
+ }
+ break;
+ }
+
+ case 1:
+ {
+ if (size < 2
+ || data[0] != mIndicator
+ || (data[1] & 0x1f) != mNALType
+ || (data[1] & 0x80)) {
+ ALOGV("Malformed H264 FU-A packet (indicator, "
+ "type or start bit mismatch)");
+
+ return ERROR_MALFORMED;
+ }
+
+ appendToAccumulator(data + 2, size - 2);
+
+ if (data[1] & 0x40) {
+ addSingleNALUnit(mAccumulator);
+
+ clearAccumulator();
+ mState = 0;
+
+ ALOGV("added FU-A");
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+
+ int32_t marker;
+ CHECK(packet->meta()->findInt32("M", &marker));
+
+ if (marker) {
+ flushAccessUnit();
+ }
+
+ return OK;
+}
+
+void RTPReceiver::H264Assembler::reset() {
+ mNALUnits.clear();
+
+ clearAccumulator();
+ mState = 0;
+}
+
+void RTPReceiver::H264Assembler::clearAccumulator() {
+ if (mAccumulator != NULL) {
+ // XXX Too expensive.
+ mAccumulator.clear();
+ }
+}
+
+void RTPReceiver::H264Assembler::appendToAccumulator(
+ const void *data, size_t size) {
+ if (mAccumulator == NULL) {
+ mAccumulator = new ABuffer(size);
+ memcpy(mAccumulator->data(), data, size);
+ return;
+ }
+
+ if (mAccumulator->size() + size > mAccumulator->capacity()) {
+ sp<ABuffer> buf = new ABuffer(mAccumulator->size() + size);
+ memcpy(buf->data(), mAccumulator->data(), mAccumulator->size());
+ buf->setRange(0, mAccumulator->size());
+
+ int32_t rtpTime;
+ if (mAccumulator->meta()->findInt32("rtp-time", &rtpTime)) {
+ buf->meta()->setInt32("rtp-time", rtpTime);
+ }
+
+ mAccumulator = buf;
+ }
+
+ memcpy(mAccumulator->data() + mAccumulator->size(), data, size);
+ mAccumulator->setRange(0, mAccumulator->size() + size);
+}
+
+void RTPReceiver::H264Assembler::addSingleNALUnit(const sp<ABuffer> &packet) {
+ if (mNALUnits.empty()) {
+ int32_t rtpTime;
+ CHECK(packet->meta()->findInt32("rtp-time", &rtpTime));
+
+ mAccessUnitRTPTime = rtpTime;
+ }
+
+ mNALUnits.push_back(packet);
+}
+
+void RTPReceiver::H264Assembler::flushAccessUnit() {
+ if (mNALUnits.empty()) {
+ return;
+ }
+
+ 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) {
+ const sp<ABuffer> nalUnit = *it;
+
+ memcpy(accessUnit->data() + offset, "\x00\x00\x00\x01", 4);
+
+ memcpy(accessUnit->data() + offset + 4,
+ nalUnit->data(),
+ nalUnit->size());
+
+ offset += 4 + nalUnit->size();
+ }
+
+ mNALUnits.clear();
+
+ accessUnit->meta()->setInt64("timeUs", mAccessUnitRTPTime * 100ll / 9ll);
+ postAccessUnit(accessUnit, false /* followsDiscontinuity */);
+}
+
+status_t RTPReceiver::H264Assembler::addSingleTimeAggregationPacket(
+ const sp<ABuffer> &packet) {
+ const uint8_t *data = packet->data();
+ size_t size = packet->size();
+
+ if (size < 3) {
+ ALOGV("Malformed H264 STAP-A packet (too small)");
+ return ERROR_MALFORMED;
+ }
+
+ int32_t rtpTime;
+ CHECK(packet->meta()->findInt32("rtp-time", &rtpTime));
+
+ ++data;
+ --size;
+ while (size >= 2) {
+ size_t nalSize = (data[0] << 8) | data[1];
+
+ if (size < nalSize + 2) {
+ ALOGV("Malformed H264 STAP-A packet (incomplete NAL unit)");
+ return ERROR_MALFORMED;
+ }
+
+ sp<ABuffer> unit = new ABuffer(nalSize);
+ memcpy(unit->data(), &data[2], nalSize);
+
+ unit->meta()->setInt32("rtp-time", rtpTime);
+
+ addSingleNALUnit(unit);
+
+ data += 2 + nalSize;
+ size -= 2 + nalSize;
+ }
+
+ if (size != 0) {
+ ALOGV("Unexpected padding at end of STAP-A packet.");
+ }
+
+ ALOGV("added STAP-A");
+
+ return OK;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/rtp/RTPAssembler.h b/media/libstagefright/wifi-display/rtp/RTPAssembler.h
new file mode 100644
index 0000000..e456d32
--- /dev/null
+++ b/media/libstagefright/wifi-display/rtp/RTPAssembler.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2013, 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 RTP_ASSEMBLER_H_
+
+#define RTP_ASSEMBLER_H_
+
+#include "RTPReceiver.h"
+
+namespace android {
+
+// A helper class to reassemble the payload of RTP packets into access
+// units depending on the packetization scheme.
+struct RTPReceiver::Assembler : public RefBase {
+ Assembler(const sp<AMessage> &notify);
+
+ virtual void signalDiscontinuity() = 0;
+ virtual status_t processPacket(const sp<ABuffer> &packet) = 0;
+
+protected:
+ virtual ~Assembler() {}
+
+ void postAccessUnit(
+ const sp<ABuffer> &accessUnit, bool followsDiscontinuity);
+
+private:
+ sp<AMessage> mNotify;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Assembler);
+};
+
+struct RTPReceiver::TSAssembler : public RTPReceiver::Assembler {
+ TSAssembler(const sp<AMessage> &notify);
+
+ virtual void signalDiscontinuity();
+ virtual status_t processPacket(const sp<ABuffer> &packet);
+
+private:
+ bool mSawDiscontinuity;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TSAssembler);
+};
+
+struct RTPReceiver::H264Assembler : public RTPReceiver::Assembler {
+ H264Assembler(const sp<AMessage> &notify);
+
+ virtual void signalDiscontinuity();
+ virtual status_t processPacket(const sp<ABuffer> &packet);
+
+private:
+ int32_t mState;
+
+ uint8_t mIndicator;
+ uint8_t mNALType;
+
+ sp<ABuffer> mAccumulator;
+
+ List<sp<ABuffer> > mNALUnits;
+ int32_t mAccessUnitRTPTime;
+
+ status_t internalProcessPacket(const sp<ABuffer> &packet);
+
+ void addSingleNALUnit(const sp<ABuffer> &packet);
+ status_t addSingleTimeAggregationPacket(const sp<ABuffer> &packet);
+
+ void flushAccessUnit();
+
+ void clearAccumulator();
+ void appendToAccumulator(const void *data, size_t size);
+
+ void reset();
+
+ DISALLOW_EVIL_CONSTRUCTORS(H264Assembler);
+};
+
+} // namespace android
+
+#endif // RTP_ASSEMBLER_H_
+
diff --git a/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp b/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp
new file mode 100644
index 0000000..8fa1dae
--- /dev/null
+++ b/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp
@@ -0,0 +1,1153 @@
+/*
+ * Copyright 2013, 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 "RTPReceiver"
+#include <utils/Log.h>
+
+#include "RTPAssembler.h"
+#include "RTPReceiver.h"
+
+#include "ANetworkSession.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
+
+#define TRACK_PACKET_LOSS 0
+
+namespace android {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct RTPReceiver::Source : public AHandler {
+ Source(RTPReceiver *receiver, uint32_t ssrc);
+
+ void onPacketReceived(uint16_t seq, const sp<ABuffer> &buffer);
+
+ void addReportBlock(uint32_t ssrc, const sp<ABuffer> &buf);
+
+protected:
+ virtual ~Source();
+
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatRetransmit,
+ kWhatDeclareLost,
+ };
+
+ static const uint32_t kMinSequential = 2;
+ static const uint32_t kMaxDropout = 3000;
+ static const uint32_t kMaxMisorder = 100;
+ static const uint32_t kRTPSeqMod = 1u << 16;
+ static const int64_t kReportIntervalUs = 10000000ll;
+
+ RTPReceiver *mReceiver;
+ uint32_t mSSRC;
+ bool mFirst;
+ uint16_t mMaxSeq;
+ uint32_t mCycles;
+ uint32_t mBaseSeq;
+ uint32_t mReceived;
+ uint32_t mExpectedPrior;
+ uint32_t mReceivedPrior;
+
+ int64_t mFirstArrivalTimeUs;
+ int64_t mFirstRTPTimeUs;
+
+ // Ordered by extended seq number.
+ List<sp<ABuffer> > mPackets;
+
+ enum StatusBits {
+ STATUS_DECLARED_LOST = 1,
+ STATUS_REQUESTED_RETRANSMISSION = 2,
+ STATUS_ARRIVED_LATE = 4,
+ };
+#if TRACK_PACKET_LOSS
+ KeyedVector<int32_t, uint32_t> mLostPackets;
+#endif
+
+ void modifyPacketStatus(int32_t extSeqNo, uint32_t mask);
+
+ int32_t mAwaitingExtSeqNo;
+ bool mRequestedRetransmission;
+
+ int32_t mActivePacketType;
+ sp<Assembler> mActiveAssembler;
+
+ int64_t mNextReportTimeUs;
+
+ int32_t mNumDeclaredLost;
+ int32_t mNumDeclaredLostPrior;
+
+ int32_t mRetransmitGeneration;
+ int32_t mDeclareLostGeneration;
+ bool mDeclareLostTimerPending;
+
+ void queuePacket(const sp<ABuffer> &packet);
+ void dequeueMore();
+
+ sp<ABuffer> getNextPacket();
+ void resync();
+
+ void postRetransmitTimer(int64_t delayUs);
+ void postDeclareLostTimer(int64_t delayUs);
+ void cancelTimers();
+
+ DISALLOW_EVIL_CONSTRUCTORS(Source);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+RTPReceiver::Source::Source(RTPReceiver *receiver, uint32_t ssrc)
+ : mReceiver(receiver),
+ mSSRC(ssrc),
+ mFirst(true),
+ mMaxSeq(0),
+ mCycles(0),
+ mBaseSeq(0),
+ mReceived(0),
+ mExpectedPrior(0),
+ mReceivedPrior(0),
+ mFirstArrivalTimeUs(-1ll),
+ mFirstRTPTimeUs(-1ll),
+ mAwaitingExtSeqNo(-1),
+ mRequestedRetransmission(false),
+ mActivePacketType(-1),
+ mNextReportTimeUs(-1ll),
+ mNumDeclaredLost(0),
+ mNumDeclaredLostPrior(0),
+ mRetransmitGeneration(0),
+ mDeclareLostGeneration(0),
+ mDeclareLostTimerPending(false) {
+}
+
+RTPReceiver::Source::~Source() {
+}
+
+void RTPReceiver::Source::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatRetransmit:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mRetransmitGeneration) {
+ break;
+ }
+
+ mRequestedRetransmission = true;
+ mReceiver->requestRetransmission(mSSRC, mAwaitingExtSeqNo);
+
+ modifyPacketStatus(
+ mAwaitingExtSeqNo, STATUS_REQUESTED_RETRANSMISSION);
+ break;
+ }
+
+ case kWhatDeclareLost:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mDeclareLostGeneration) {
+ break;
+ }
+
+ cancelTimers();
+
+ ALOGV("Lost packet extSeqNo %d %s",
+ mAwaitingExtSeqNo,
+ mRequestedRetransmission ? "*" : "");
+
+ mRequestedRetransmission = false;
+ if (mActiveAssembler != NULL) {
+ mActiveAssembler->signalDiscontinuity();
+ }
+
+ modifyPacketStatus(mAwaitingExtSeqNo, STATUS_DECLARED_LOST);
+
+ // resync();
+ ++mAwaitingExtSeqNo;
+ ++mNumDeclaredLost;
+
+ mReceiver->notifyPacketLost();
+
+ dequeueMore();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void RTPReceiver::Source::onPacketReceived(
+ uint16_t seq, const sp<ABuffer> &buffer) {
+ if (mFirst) {
+ buffer->setInt32Data(mCycles | seq);
+ queuePacket(buffer);
+
+ mFirst = false;
+ mBaseSeq = seq;
+ mMaxSeq = seq;
+ ++mReceived;
+ return;
+ }
+
+ uint16_t udelta = seq - mMaxSeq;
+
+ if (udelta < kMaxDropout) {
+ // In order, with permissible gap.
+
+ if (seq < mMaxSeq) {
+ // Sequence number wrapped - count another 64K cycle
+ mCycles += kRTPSeqMod;
+ }
+
+ mMaxSeq = seq;
+
+ ++mReceived;
+ } else if (udelta <= kRTPSeqMod - kMaxMisorder) {
+ // The sequence number made a very large jump
+ return;
+ } else {
+ // Duplicate or reordered packet.
+ }
+
+ buffer->setInt32Data(mCycles | seq);
+ queuePacket(buffer);
+}
+
+void RTPReceiver::Source::queuePacket(const sp<ABuffer> &packet) {
+ int32_t newExtendedSeqNo = packet->int32Data();
+
+ if (mFirstArrivalTimeUs < 0ll) {
+ mFirstArrivalTimeUs = ALooper::GetNowUs();
+
+ uint32_t rtpTime;
+ CHECK(packet->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ mFirstRTPTimeUs = (rtpTime * 100ll) / 9ll;
+ }
+
+ if (mAwaitingExtSeqNo >= 0 && newExtendedSeqNo < mAwaitingExtSeqNo) {
+ // We're no longer interested in these. They're old.
+ ALOGV("dropping stale extSeqNo %d", newExtendedSeqNo);
+
+ modifyPacketStatus(newExtendedSeqNo, STATUS_ARRIVED_LATE);
+ return;
+ }
+
+ if (mPackets.empty()) {
+ mPackets.push_back(packet);
+ dequeueMore();
+ return;
+ }
+
+ List<sp<ABuffer> >::iterator firstIt = mPackets.begin();
+ List<sp<ABuffer> >::iterator it = --mPackets.end();
+ for (;;) {
+ int32_t extendedSeqNo = (*it)->int32Data();
+
+ if (extendedSeqNo == newExtendedSeqNo) {
+ // Duplicate packet.
+ return;
+ }
+
+ if (extendedSeqNo < newExtendedSeqNo) {
+ // Insert new packet after the one at "it".
+ mPackets.insert(++it, packet);
+ break;
+ }
+
+ if (it == firstIt) {
+ // Insert new packet before the first existing one.
+ mPackets.insert(it, packet);
+ break;
+ }
+
+ --it;
+ }
+
+ dequeueMore();
+}
+
+void RTPReceiver::Source::dequeueMore() {
+ int64_t nowUs = ALooper::GetNowUs();
+ if (mNextReportTimeUs < 0ll || nowUs >= mNextReportTimeUs) {
+ if (mNextReportTimeUs >= 0ll) {
+ uint32_t expected = (mMaxSeq | mCycles) - mBaseSeq + 1;
+
+ uint32_t expectedInterval = expected - mExpectedPrior;
+ mExpectedPrior = expected;
+
+ uint32_t receivedInterval = mReceived - mReceivedPrior;
+ mReceivedPrior = mReceived;
+
+ int64_t lostInterval =
+ (int64_t)expectedInterval - (int64_t)receivedInterval;
+
+ int32_t declaredLostInterval =
+ mNumDeclaredLost - mNumDeclaredLostPrior;
+
+ mNumDeclaredLostPrior = mNumDeclaredLost;
+
+ if (declaredLostInterval > 0) {
+ ALOGI("lost %lld packets (%.2f %%), declared %d lost\n",
+ lostInterval,
+ 100.0f * lostInterval / expectedInterval,
+ declaredLostInterval);
+ }
+ }
+
+ mNextReportTimeUs = nowUs + kReportIntervalUs;
+
+#if TRACK_PACKET_LOSS
+ for (size_t i = 0; i < mLostPackets.size(); ++i) {
+ int32_t key = mLostPackets.keyAt(i);
+ uint32_t value = mLostPackets.valueAt(i);
+
+ AString status;
+ if (value & STATUS_REQUESTED_RETRANSMISSION) {
+ status.append("retrans ");
+ }
+ if (value & STATUS_ARRIVED_LATE) {
+ status.append("arrived-late ");
+ }
+ ALOGI("Packet %d declared lost %s", key, status.c_str());
+ }
+#endif
+ }
+
+ sp<ABuffer> packet;
+ while ((packet = getNextPacket()) != NULL) {
+ if (mDeclareLostTimerPending) {
+ cancelTimers();
+ }
+
+ CHECK_GE(mAwaitingExtSeqNo, 0);
+#if TRACK_PACKET_LOSS
+ mLostPackets.removeItem(mAwaitingExtSeqNo);
+#endif
+
+ int32_t packetType;
+ CHECK(packet->meta()->findInt32("PT", &packetType));
+
+ if (packetType != mActivePacketType) {
+ mActiveAssembler = mReceiver->makeAssembler(packetType);
+ mActivePacketType = packetType;
+ }
+
+ if (mActiveAssembler != NULL) {
+ status_t err = mActiveAssembler->processPacket(packet);
+ if (err != OK) {
+ ALOGV("assembler returned error %d", err);
+ }
+ }
+
+ ++mAwaitingExtSeqNo;
+ }
+
+ if (mDeclareLostTimerPending) {
+ return;
+ }
+
+ if (mPackets.empty()) {
+ return;
+ }
+
+ CHECK_GE(mAwaitingExtSeqNo, 0);
+
+ const sp<ABuffer> &firstPacket = *mPackets.begin();
+
+ uint32_t rtpTime;
+ CHECK(firstPacket->meta()->findInt32(
+ "rtp-time", (int32_t *)&rtpTime));
+
+
+ int64_t rtpUs = (rtpTime * 100ll) / 9ll;
+
+ int64_t maxArrivalTimeUs =
+ mFirstArrivalTimeUs + rtpUs - mFirstRTPTimeUs;
+
+ nowUs = ALooper::GetNowUs();
+
+ CHECK_LT(mAwaitingExtSeqNo, firstPacket->int32Data());
+
+ ALOGV("waiting for %d, comparing against %d, %lld us left",
+ mAwaitingExtSeqNo,
+ firstPacket->int32Data(),
+ maxArrivalTimeUs - nowUs);
+
+ postDeclareLostTimer(maxArrivalTimeUs + kPacketLostAfterUs);
+
+ if (kRequestRetransmissionAfterUs > 0ll) {
+ postRetransmitTimer(
+ maxArrivalTimeUs + kRequestRetransmissionAfterUs);
+ }
+}
+
+sp<ABuffer> RTPReceiver::Source::getNextPacket() {
+ if (mPackets.empty()) {
+ return NULL;
+ }
+
+ int32_t extSeqNo = (*mPackets.begin())->int32Data();
+
+ if (mAwaitingExtSeqNo < 0) {
+ mAwaitingExtSeqNo = extSeqNo;
+ } else if (extSeqNo != mAwaitingExtSeqNo) {
+ return NULL;
+ }
+
+ sp<ABuffer> packet = *mPackets.begin();
+ mPackets.erase(mPackets.begin());
+
+ return packet;
+}
+
+void RTPReceiver::Source::resync() {
+ mAwaitingExtSeqNo = -1;
+}
+
+void RTPReceiver::Source::addReportBlock(
+ uint32_t ssrc, const sp<ABuffer> &buf) {
+ uint32_t extMaxSeq = mMaxSeq | mCycles;
+ uint32_t expected = extMaxSeq - mBaseSeq + 1;
+
+ int64_t lost = (int64_t)expected - (int64_t)mReceived;
+ if (lost > 0x7fffff) {
+ lost = 0x7fffff;
+ } else if (lost < -0x800000) {
+ lost = -0x800000;
+ }
+
+ uint32_t expectedInterval = expected - mExpectedPrior;
+ mExpectedPrior = expected;
+
+ uint32_t receivedInterval = mReceived - mReceivedPrior;
+ mReceivedPrior = mReceived;
+
+ int64_t lostInterval = expectedInterval - receivedInterval;
+
+ uint8_t fractionLost;
+ if (expectedInterval == 0 || lostInterval <=0) {
+ fractionLost = 0;
+ } else {
+ fractionLost = (lostInterval << 8) / expectedInterval;
+ }
+
+ uint8_t *ptr = buf->data() + buf->size();
+
+ ptr[0] = ssrc >> 24;
+ ptr[1] = (ssrc >> 16) & 0xff;
+ ptr[2] = (ssrc >> 8) & 0xff;
+ ptr[3] = ssrc & 0xff;
+
+ ptr[4] = fractionLost;
+
+ ptr[5] = (lost >> 16) & 0xff;
+ ptr[6] = (lost >> 8) & 0xff;
+ ptr[7] = lost & 0xff;
+
+ ptr[8] = extMaxSeq >> 24;
+ ptr[9] = (extMaxSeq >> 16) & 0xff;
+ ptr[10] = (extMaxSeq >> 8) & 0xff;
+ ptr[11] = extMaxSeq & 0xff;
+
+ // XXX TODO:
+
+ ptr[12] = 0x00; // interarrival jitter
+ ptr[13] = 0x00;
+ ptr[14] = 0x00;
+ ptr[15] = 0x00;
+
+ ptr[16] = 0x00; // last SR
+ ptr[17] = 0x00;
+ ptr[18] = 0x00;
+ ptr[19] = 0x00;
+
+ ptr[20] = 0x00; // delay since last SR
+ ptr[21] = 0x00;
+ ptr[22] = 0x00;
+ ptr[23] = 0x00;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+RTPReceiver::RTPReceiver(
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> &notify,
+ uint32_t flags)
+ : mNetSession(netSession),
+ mNotify(notify),
+ mFlags(flags),
+ mRTPMode(TRANSPORT_UNDEFINED),
+ mRTCPMode(TRANSPORT_UNDEFINED),
+ mRTPSessionID(0),
+ mRTCPSessionID(0),
+ mRTPConnected(false),
+ mRTCPConnected(false),
+ mRTPClientSessionID(0),
+ mRTCPClientSessionID(0) {
+}
+
+RTPReceiver::~RTPReceiver() {
+ if (mRTCPClientSessionID != 0) {
+ mNetSession->destroySession(mRTCPClientSessionID);
+ mRTCPClientSessionID = 0;
+ }
+
+ if (mRTPClientSessionID != 0) {
+ mNetSession->destroySession(mRTPClientSessionID);
+ mRTPClientSessionID = 0;
+ }
+
+ if (mRTCPSessionID != 0) {
+ mNetSession->destroySession(mRTCPSessionID);
+ mRTCPSessionID = 0;
+ }
+
+ if (mRTPSessionID != 0) {
+ mNetSession->destroySession(mRTPSessionID);
+ mRTPSessionID = 0;
+ }
+}
+
+status_t RTPReceiver::initAsync(
+ TransportMode rtpMode,
+ TransportMode rtcpMode,
+ int32_t *outLocalRTPPort) {
+ if (mRTPMode != TRANSPORT_UNDEFINED
+ || rtpMode == TRANSPORT_UNDEFINED
+ || rtpMode == TRANSPORT_NONE
+ || rtcpMode == TRANSPORT_UNDEFINED) {
+ return INVALID_OPERATION;
+ }
+
+ CHECK_NE(rtpMode, TRANSPORT_TCP_INTERLEAVED);
+ CHECK_NE(rtcpMode, TRANSPORT_TCP_INTERLEAVED);
+
+ sp<AMessage> rtpNotify = new AMessage(kWhatRTPNotify, id());
+
+ sp<AMessage> rtcpNotify;
+ if (rtcpMode != TRANSPORT_NONE) {
+ rtcpNotify = new AMessage(kWhatRTCPNotify, id());
+ }
+
+ CHECK_EQ(mRTPSessionID, 0);
+ CHECK_EQ(mRTCPSessionID, 0);
+
+ int32_t localRTPPort;
+
+ struct in_addr ifaceAddr;
+ ifaceAddr.s_addr = INADDR_ANY;
+
+ for (;;) {
+ localRTPPort = PickRandomRTPPort();
+
+ status_t err;
+ if (rtpMode == TRANSPORT_UDP) {
+ err = mNetSession->createUDPSession(
+ localRTPPort,
+ rtpNotify,
+ &mRTPSessionID);
+ } else {
+ CHECK_EQ(rtpMode, TRANSPORT_TCP);
+ err = mNetSession->createTCPDatagramSession(
+ ifaceAddr,
+ localRTPPort,
+ rtpNotify,
+ &mRTPSessionID);
+ }
+
+ if (err != OK) {
+ continue;
+ }
+
+ if (rtcpMode == TRANSPORT_NONE) {
+ break;
+ } else if (rtcpMode == TRANSPORT_UDP) {
+ err = mNetSession->createUDPSession(
+ localRTPPort + 1,
+ rtcpNotify,
+ &mRTCPSessionID);
+ } else {
+ CHECK_EQ(rtpMode, TRANSPORT_TCP);
+ err = mNetSession->createTCPDatagramSession(
+ ifaceAddr,
+ localRTPPort + 1,
+ rtcpNotify,
+ &mRTCPSessionID);
+ }
+
+ if (err == OK) {
+ break;
+ }
+
+ mNetSession->destroySession(mRTPSessionID);
+ mRTPSessionID = 0;
+ }
+
+ mRTPMode = rtpMode;
+ mRTCPMode = rtcpMode;
+ *outLocalRTPPort = localRTPPort;
+
+ return OK;
+}
+
+status_t RTPReceiver::connect(
+ const char *remoteHost, int32_t remoteRTPPort, int32_t remoteRTCPPort) {
+ status_t err;
+
+ if (mRTPMode == TRANSPORT_UDP) {
+ CHECK(!mRTPConnected);
+
+ err = mNetSession->connectUDPSession(
+ mRTPSessionID, remoteHost, remoteRTPPort);
+
+ if (err != OK) {
+ notifyInitDone(err);
+ return err;
+ }
+
+ ALOGI("connectUDPSession RTP successful.");
+
+ mRTPConnected = true;
+ }
+
+ if (mRTCPMode == TRANSPORT_UDP) {
+ CHECK(!mRTCPConnected);
+
+ err = mNetSession->connectUDPSession(
+ mRTCPSessionID, remoteHost, remoteRTCPPort);
+
+ if (err != OK) {
+ notifyInitDone(err);
+ return err;
+ }
+
+ scheduleSendRR();
+
+ ALOGI("connectUDPSession RTCP successful.");
+
+ mRTCPConnected = true;
+ }
+
+ if (mRTPConnected
+ && (mRTCPConnected || mRTCPMode == TRANSPORT_NONE)) {
+ notifyInitDone(OK);
+ }
+
+ return OK;
+}
+
+status_t RTPReceiver::informSender(const sp<AMessage> &params) {
+ if (!mRTCPConnected) {
+ return INVALID_OPERATION;
+ }
+
+ int64_t avgLatencyUs;
+ CHECK(params->findInt64("avgLatencyUs", &avgLatencyUs));
+
+ int64_t maxLatencyUs;
+ CHECK(params->findInt64("maxLatencyUs", &maxLatencyUs));
+
+ sp<ABuffer> buf = new ABuffer(28);
+
+ uint8_t *ptr = buf->data();
+ ptr[0] = 0x80 | 0;
+ ptr[1] = 204; // APP
+ ptr[2] = 0;
+
+ CHECK((buf->size() % 4) == 0u);
+ ptr[3] = (buf->size() / 4) - 1;
+
+ ptr[4] = kSourceID >> 24; // SSRC
+ ptr[5] = (kSourceID >> 16) & 0xff;
+ ptr[6] = (kSourceID >> 8) & 0xff;
+ ptr[7] = kSourceID & 0xff;
+ ptr[8] = 'l';
+ ptr[9] = 'a';
+ ptr[10] = 't';
+ ptr[11] = 'e';
+
+ ptr[12] = avgLatencyUs >> 56;
+ ptr[13] = (avgLatencyUs >> 48) & 0xff;
+ ptr[14] = (avgLatencyUs >> 40) & 0xff;
+ ptr[15] = (avgLatencyUs >> 32) & 0xff;
+ ptr[16] = (avgLatencyUs >> 24) & 0xff;
+ ptr[17] = (avgLatencyUs >> 16) & 0xff;
+ ptr[18] = (avgLatencyUs >> 8) & 0xff;
+ ptr[19] = avgLatencyUs & 0xff;
+
+ ptr[20] = maxLatencyUs >> 56;
+ ptr[21] = (maxLatencyUs >> 48) & 0xff;
+ ptr[22] = (maxLatencyUs >> 40) & 0xff;
+ ptr[23] = (maxLatencyUs >> 32) & 0xff;
+ ptr[24] = (maxLatencyUs >> 24) & 0xff;
+ ptr[25] = (maxLatencyUs >> 16) & 0xff;
+ ptr[26] = (maxLatencyUs >> 8) & 0xff;
+ ptr[27] = maxLatencyUs & 0xff;
+
+ mNetSession->sendRequest(mRTCPSessionID, buf->data(), buf->size());
+
+ return OK;
+}
+
+void RTPReceiver::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatRTPNotify:
+ case kWhatRTCPNotify:
+ onNetNotify(msg->what() == kWhatRTPNotify, msg);
+ break;
+
+ case kWhatSendRR:
+ {
+ onSendRR();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void RTPReceiver::onNetNotify(bool isRTP, const sp<AMessage> &msg) {
+ int32_t reason;
+ CHECK(msg->findInt32("reason", &reason));
+
+ switch (reason) {
+ case ANetworkSession::kWhatError:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ int32_t errorOccuredDuringSend;
+ CHECK(msg->findInt32("send", &errorOccuredDuringSend));
+
+ AString detail;
+ CHECK(msg->findString("detail", &detail));
+
+ ALOGE("An error occurred during %s in session %d "
+ "(%d, '%s' (%s)).",
+ errorOccuredDuringSend ? "send" : "receive",
+ sessionID,
+ err,
+ detail.c_str(),
+ strerror(-err));
+
+ mNetSession->destroySession(sessionID);
+
+ if (sessionID == mRTPSessionID) {
+ mRTPSessionID = 0;
+ } else if (sessionID == mRTCPSessionID) {
+ mRTCPSessionID = 0;
+ } else if (sessionID == mRTPClientSessionID) {
+ mRTPClientSessionID = 0;
+ } else if (sessionID == mRTCPClientSessionID) {
+ mRTCPClientSessionID = 0;
+ }
+
+ if (!mRTPConnected
+ || (mRTCPMode != TRANSPORT_NONE && !mRTCPConnected)) {
+ notifyInitDone(err);
+ break;
+ }
+
+ notifyError(err);
+ break;
+ }
+
+ case ANetworkSession::kWhatDatagram:
+ {
+ sp<ABuffer> data;
+ CHECK(msg->findBuffer("data", &data));
+
+ if (isRTP) {
+ if (mFlags & FLAG_AUTO_CONNECT) {
+ AString fromAddr;
+ CHECK(msg->findString("fromAddr", &fromAddr));
+
+ int32_t fromPort;
+ CHECK(msg->findInt32("fromPort", &fromPort));
+
+ CHECK_EQ((status_t)OK,
+ connect(
+ fromAddr.c_str(), fromPort, fromPort + 1));
+
+ mFlags &= ~FLAG_AUTO_CONNECT;
+ }
+
+ onRTPData(data);
+ } else {
+ onRTCPData(data);
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatClientConnected:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ if (isRTP) {
+ CHECK_EQ(mRTPMode, TRANSPORT_TCP);
+
+ if (mRTPClientSessionID != 0) {
+ // We only allow a single client connection.
+ mNetSession->destroySession(sessionID);
+ sessionID = 0;
+ break;
+ }
+
+ mRTPClientSessionID = sessionID;
+ mRTPConnected = true;
+ } else {
+ CHECK_EQ(mRTCPMode, TRANSPORT_TCP);
+
+ if (mRTCPClientSessionID != 0) {
+ // We only allow a single client connection.
+ mNetSession->destroySession(sessionID);
+ sessionID = 0;
+ break;
+ }
+
+ mRTCPClientSessionID = sessionID;
+ mRTCPConnected = true;
+ }
+
+ if (mRTPConnected
+ && (mRTCPConnected || mRTCPMode == TRANSPORT_NONE)) {
+ notifyInitDone(OK);
+ }
+ break;
+ }
+ }
+}
+
+void RTPReceiver::notifyInitDone(status_t err) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatInitDone);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+void RTPReceiver::notifyError(status_t err) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatError);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+void RTPReceiver::notifyPacketLost() {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatPacketLost);
+ notify->post();
+}
+
+status_t RTPReceiver::onRTPData(const sp<ABuffer> &buffer) {
+ size_t size = buffer->size();
+ if (size < 12) {
+ // Too short to be a valid RTP header.
+ return ERROR_MALFORMED;
+ }
+
+ const uint8_t *data = buffer->data();
+
+ if ((data[0] >> 6) != 2) {
+ // Unsupported version.
+ return ERROR_UNSUPPORTED;
+ }
+
+ 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 ERROR_MALFORMED;
+ }
+
+ 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 ERROR_MALFORMED;
+ }
+
+ 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 ERROR_MALFORMED;
+ }
+
+ const uint8_t *extensionData = &data[payloadOffset];
+
+ size_t extensionLength =
+ 4 * (extensionData[2] << 8 | extensionData[3]);
+
+ if (size < payloadOffset + 4 + extensionLength) {
+ return ERROR_MALFORMED;
+ }
+
+ payloadOffset += 4 + extensionLength;
+ }
+
+ uint32_t srcId = U32_AT(&data[8]);
+ uint32_t rtpTime = U32_AT(&data[4]);
+ uint16_t seqNo = U16_AT(&data[2]);
+
+ 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->setRange(payloadOffset, size - payloadOffset);
+
+ ssize_t index = mSources.indexOfKey(srcId);
+ sp<Source> source;
+ if (index < 0) {
+ source = new Source(this, srcId);
+ looper()->registerHandler(source);
+
+ mSources.add(srcId, source);
+ } else {
+ source = mSources.valueAt(index);
+ }
+
+ source->onPacketReceived(seqNo, buffer);
+
+ return OK;
+}
+
+status_t RTPReceiver::onRTCPData(const sp<ABuffer> &data) {
+ ALOGI("onRTCPData");
+ return OK;
+}
+
+void RTPReceiver::addSDES(const sp<ABuffer> &buffer) {
+ uint8_t *data = buffer->data() + buffer->size();
+ data[0] = 0x80 | 1;
+ data[1] = 202; // SDES
+ data[4] = kSourceID >> 24; // SSRC
+ data[5] = (kSourceID >> 16) & 0xff;
+ data[6] = (kSourceID >> 8) & 0xff;
+ data[7] = kSourceID & 0xff;
+
+ size_t offset = 8;
+
+ data[offset++] = 1; // CNAME
+
+ AString cname = "stagefright@somewhere";
+ data[offset++] = cname.size();
+
+ memcpy(&data[offset], cname.c_str(), cname.size());
+ offset += cname.size();
+
+ data[offset++] = 6; // TOOL
+
+ AString tool = "stagefright/1.0";
+ data[offset++] = tool.size();
+
+ memcpy(&data[offset], tool.c_str(), tool.size());
+ offset += tool.size();
+
+ 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);
+}
+
+void RTPReceiver::scheduleSendRR() {
+ (new AMessage(kWhatSendRR, id()))->post(5000000ll);
+}
+
+void RTPReceiver::onSendRR() {
+#if 0
+ sp<ABuffer> buf = new ABuffer(kMaxUDPPacketSize);
+ buf->setRange(0, 0);
+
+ uint8_t *ptr = buf->data();
+ ptr[0] = 0x80 | 0;
+ ptr[1] = 201; // RR
+ ptr[2] = 0;
+ ptr[3] = 1;
+ ptr[4] = kSourceID >> 24; // SSRC
+ ptr[5] = (kSourceID >> 16) & 0xff;
+ ptr[6] = (kSourceID >> 8) & 0xff;
+ ptr[7] = kSourceID & 0xff;
+
+ buf->setRange(0, 8);
+
+ size_t numReportBlocks = 0;
+ for (size_t i = 0; i < mSources.size(); ++i) {
+ uint32_t ssrc = mSources.keyAt(i);
+ sp<Source> source = mSources.valueAt(i);
+
+ if (numReportBlocks > 31 || buf->size() + 24 > buf->capacity()) {
+ // Cannot fit another report block.
+ break;
+ }
+
+ source->addReportBlock(ssrc, buf);
+ ++numReportBlocks;
+ }
+
+ ptr[0] |= numReportBlocks; // 5 bit
+
+ size_t sizeInWordsMinus1 = 1 + 6 * numReportBlocks;
+ ptr[2] = sizeInWordsMinus1 >> 8;
+ ptr[3] = sizeInWordsMinus1 & 0xff;
+
+ buf->setRange(0, (sizeInWordsMinus1 + 1) * 4);
+
+ addSDES(buf);
+
+ mNetSession->sendRequest(mRTCPSessionID, buf->data(), buf->size());
+#endif
+
+ scheduleSendRR();
+}
+
+status_t RTPReceiver::registerPacketType(
+ uint8_t packetType, PacketizationMode mode) {
+ mPacketTypes.add(packetType, mode);
+
+ return OK;
+}
+
+sp<RTPReceiver::Assembler> RTPReceiver::makeAssembler(uint8_t packetType) {
+ ssize_t index = mPacketTypes.indexOfKey(packetType);
+ if (index < 0) {
+ return NULL;
+ }
+
+ PacketizationMode mode = mPacketTypes.valueAt(index);
+
+ switch (mode) {
+ case PACKETIZATION_NONE:
+ case PACKETIZATION_TRANSPORT_STREAM:
+ return new TSAssembler(mNotify);
+
+ case PACKETIZATION_H264:
+ return new H264Assembler(mNotify);
+
+ default:
+ return NULL;
+ }
+}
+
+void RTPReceiver::requestRetransmission(uint32_t senderSSRC, int32_t extSeqNo) {
+ int32_t blp = 0;
+
+ sp<ABuffer> buf = new ABuffer(16);
+ buf->setRange(0, 0);
+
+ uint8_t *ptr = buf->data();
+ ptr[0] = 0x80 | 1; // generic NACK
+ ptr[1] = 205; // TSFB
+ ptr[2] = 0;
+ ptr[3] = 3;
+ ptr[8] = (senderSSRC >> 24) & 0xff;
+ ptr[9] = (senderSSRC >> 16) & 0xff;
+ ptr[10] = (senderSSRC >> 8) & 0xff;
+ ptr[11] = (senderSSRC & 0xff);
+ ptr[8] = (kSourceID >> 24) & 0xff;
+ ptr[9] = (kSourceID >> 16) & 0xff;
+ ptr[10] = (kSourceID >> 8) & 0xff;
+ ptr[11] = (kSourceID & 0xff);
+ ptr[12] = (extSeqNo >> 8) & 0xff;
+ ptr[13] = (extSeqNo & 0xff);
+ ptr[14] = (blp >> 8) & 0xff;
+ ptr[15] = (blp & 0xff);
+
+ buf->setRange(0, 16);
+
+ mNetSession->sendRequest(mRTCPSessionID, buf->data(), buf->size());
+}
+
+void RTPReceiver::Source::modifyPacketStatus(int32_t extSeqNo, uint32_t mask) {
+#if TRACK_PACKET_LOSS
+ ssize_t index = mLostPackets.indexOfKey(extSeqNo);
+ if (index < 0) {
+ mLostPackets.add(extSeqNo, mask);
+ } else {
+ mLostPackets.editValueAt(index) |= mask;
+ }
+#endif
+}
+
+void RTPReceiver::Source::postRetransmitTimer(int64_t timeUs) {
+ int64_t delayUs = timeUs - ALooper::GetNowUs();
+ sp<AMessage> msg = new AMessage(kWhatRetransmit, id());
+ msg->setInt32("generation", mRetransmitGeneration);
+ msg->post(delayUs);
+}
+
+void RTPReceiver::Source::postDeclareLostTimer(int64_t timeUs) {
+ CHECK(!mDeclareLostTimerPending);
+ mDeclareLostTimerPending = true;
+
+ int64_t delayUs = timeUs - ALooper::GetNowUs();
+ sp<AMessage> msg = new AMessage(kWhatDeclareLost, id());
+ msg->setInt32("generation", mDeclareLostGeneration);
+ msg->post(delayUs);
+}
+
+void RTPReceiver::Source::cancelTimers() {
+ ++mRetransmitGeneration;
+ ++mDeclareLostGeneration;
+ mDeclareLostTimerPending = false;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/rtp/RTPReceiver.h b/media/libstagefright/wifi-display/rtp/RTPReceiver.h
new file mode 100644
index 0000000..240ab2e
--- /dev/null
+++ b/media/libstagefright/wifi-display/rtp/RTPReceiver.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2013, 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 RTP_RECEIVER_H_
+
+#define RTP_RECEIVER_H_
+
+#include "RTPBase.h"
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct ABuffer;
+struct ANetworkSession;
+
+// An object of this class facilitates receiving of media data on an RTP
+// channel. The channel is established over a UDP or TCP connection depending
+// on which "TransportMode" was chosen. In addition different RTP packetization
+// schemes are supported such as "Transport Stream Packets over RTP",
+// or "AVC/H.264 encapsulation as specified in RFC 3984 (non-interleaved mode)"
+struct RTPReceiver : public RTPBase, public AHandler {
+ enum {
+ kWhatInitDone,
+ kWhatError,
+ kWhatAccessUnit,
+ kWhatPacketLost,
+ };
+
+ enum Flags {
+ FLAG_AUTO_CONNECT = 1,
+ };
+ RTPReceiver(
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> &notify,
+ uint32_t flags = 0);
+
+ status_t registerPacketType(
+ uint8_t packetType, PacketizationMode mode);
+
+ status_t initAsync(
+ TransportMode rtpMode,
+ TransportMode rtcpMode,
+ int32_t *outLocalRTPPort);
+
+ status_t connect(
+ const char *remoteHost,
+ int32_t remoteRTPPort,
+ int32_t remoteRTCPPort);
+
+ status_t informSender(const sp<AMessage> &params);
+
+protected:
+ virtual ~RTPReceiver();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatRTPNotify,
+ kWhatRTCPNotify,
+ kWhatSendRR,
+ };
+
+ enum {
+ kSourceID = 0xdeadbeef,
+ kPacketLostAfterUs = 100000,
+ kRequestRetransmissionAfterUs = -1,
+ };
+
+ struct Assembler;
+ struct H264Assembler;
+ struct Source;
+ struct TSAssembler;
+
+ sp<ANetworkSession> mNetSession;
+ sp<AMessage> mNotify;
+ uint32_t mFlags;
+ TransportMode mRTPMode;
+ TransportMode mRTCPMode;
+ int32_t mRTPSessionID;
+ int32_t mRTCPSessionID;
+ bool mRTPConnected;
+ bool mRTCPConnected;
+
+ int32_t mRTPClientSessionID; // in TRANSPORT_TCP mode.
+ int32_t mRTCPClientSessionID; // in TRANSPORT_TCP mode.
+
+ KeyedVector<uint8_t, PacketizationMode> mPacketTypes;
+ KeyedVector<uint32_t, sp<Source> > mSources;
+
+ void onNetNotify(bool isRTP, const sp<AMessage> &msg);
+ status_t onRTPData(const sp<ABuffer> &data);
+ status_t onRTCPData(const sp<ABuffer> &data);
+ void onSendRR();
+
+ void scheduleSendRR();
+ void addSDES(const sp<ABuffer> &buffer);
+
+ void notifyInitDone(status_t err);
+ void notifyError(status_t err);
+ void notifyPacketLost();
+
+ sp<Assembler> makeAssembler(uint8_t packetType);
+
+ void requestRetransmission(uint32_t senderSSRC, int32_t extSeqNo);
+
+ DISALLOW_EVIL_CONSTRUCTORS(RTPReceiver);
+};
+
+} // namespace android
+
+#endif // RTP_RECEIVER_H_
diff --git a/media/libstagefright/wifi-display/rtp/RTPSender.cpp b/media/libstagefright/wifi-display/rtp/RTPSender.cpp
index 095fd97..6bbe650 100644
--- a/media/libstagefright/wifi-display/rtp/RTPSender.cpp
+++ b/media/libstagefright/wifi-display/rtp/RTPSender.cpp
@@ -767,6 +767,17 @@ status_t RTPSender::parseTSFB(const uint8_t *data, size_t size) {
}
status_t RTPSender::parseAPP(const uint8_t *data, size_t size) {
+ if (!memcmp("late", &data[8], 4)) {
+ int64_t avgLatencyUs = (int64_t)U64_AT(&data[12]);
+ int64_t maxLatencyUs = (int64_t)U64_AT(&data[20]);
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatInformSender);
+ notify->setInt64("avgLatencyUs", avgLatencyUs);
+ notify->setInt64("maxLatencyUs", maxLatencyUs);
+ notify->post();
+ }
+
return OK;
}
diff --git a/media/libstagefright/wifi-display/rtp/RTPSender.h b/media/libstagefright/wifi-display/rtp/RTPSender.h
index 7dc138a..fefcab7 100644
--- a/media/libstagefright/wifi-display/rtp/RTPSender.h
+++ b/media/libstagefright/wifi-display/rtp/RTPSender.h
@@ -37,6 +37,7 @@ struct RTPSender : public RTPBase, public AHandler {
kWhatInitDone,
kWhatError,
kWhatNetworkStall,
+ kWhatInformSender,
};
RTPSender(
const sp<ANetworkSession> &netSession,
diff --git a/media/libstagefright/wifi-display/rtptest.cpp b/media/libstagefright/wifi-display/rtptest.cpp
new file mode 100644
index 0000000..764a38b
--- /dev/null
+++ b/media/libstagefright/wifi-display/rtptest.cpp
@@ -0,0 +1,565 @@
+/*
+ * Copyright 2013, 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_NEBUG 0
+#define LOG_TAG "rtptest"
+#include <utils/Log.h>
+
+#include "ANetworkSession.h"
+#include "rtp/RTPSender.h"
+#include "rtp/RTPReceiver.h"
+#include "TimeSyncer.h"
+
+#include <binder/ProcessState.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/NuMediaExtractor.h>
+#include <media/stagefright/Utils.h>
+
+#define MEDIA_FILENAME "/sdcard/Frame Counter HD 30FPS_1080p.mp4"
+
+namespace android {
+
+struct PacketSource : public RefBase {
+ PacketSource() {}
+
+ virtual sp<ABuffer> getNextAccessUnit() = 0;
+
+protected:
+ virtual ~PacketSource() {}
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(PacketSource);
+};
+
+struct MediaPacketSource : public PacketSource {
+ MediaPacketSource()
+ : mMaxSampleSize(1024 * 1024) {
+ mExtractor = new NuMediaExtractor;
+ CHECK_EQ((status_t)OK,
+ mExtractor->setDataSource(MEDIA_FILENAME));
+
+ bool haveVideo = false;
+ for (size_t i = 0; i < mExtractor->countTracks(); ++i) {
+ sp<AMessage> format;
+ CHECK_EQ((status_t)OK, mExtractor->getTrackFormat(i, &format));
+
+ AString mime;
+ CHECK(format->findString("mime", &mime));
+
+ if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime.c_str())) {
+ mExtractor->selectTrack(i);
+ haveVideo = true;
+ break;
+ }
+ }
+
+ CHECK(haveVideo);
+ }
+
+ virtual sp<ABuffer> getNextAccessUnit() {
+ int64_t timeUs;
+ status_t err = mExtractor->getSampleTime(&timeUs);
+
+ if (err != OK) {
+ return NULL;
+ }
+
+ sp<ABuffer> accessUnit = new ABuffer(mMaxSampleSize);
+ CHECK_EQ((status_t)OK, mExtractor->readSampleData(accessUnit));
+
+ accessUnit->meta()->setInt64("timeUs", timeUs);
+
+ CHECK_EQ((status_t)OK, mExtractor->advance());
+
+ return accessUnit;
+ }
+
+protected:
+ virtual ~MediaPacketSource() {
+ }
+
+private:
+ sp<NuMediaExtractor> mExtractor;
+ size_t mMaxSampleSize;
+
+ DISALLOW_EVIL_CONSTRUCTORS(MediaPacketSource);
+};
+
+struct SimplePacketSource : public PacketSource {
+ SimplePacketSource()
+ : mCounter(0) {
+ }
+
+ virtual sp<ABuffer> getNextAccessUnit() {
+ sp<ABuffer> buffer = new ABuffer(4);
+ uint8_t *dst = buffer->data();
+ dst[0] = mCounter >> 24;
+ dst[1] = (mCounter >> 16) & 0xff;
+ dst[2] = (mCounter >> 8) & 0xff;
+ dst[3] = mCounter & 0xff;
+
+ buffer->meta()->setInt64("timeUs", mCounter * 1000000ll / kFrameRate);
+
+ ++mCounter;
+
+ return buffer;
+ }
+
+protected:
+ virtual ~SimplePacketSource() {
+ }
+
+private:
+ enum {
+ kFrameRate = 30
+ };
+
+ uint32_t mCounter;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SimplePacketSource);
+};
+
+struct TestHandler : public AHandler {
+ TestHandler(const sp<ANetworkSession> &netSession);
+
+ void listen();
+ void connect(const char *host, int32_t port);
+
+protected:
+ virtual ~TestHandler();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatListen,
+ kWhatConnect,
+ kWhatReceiverNotify,
+ kWhatSenderNotify,
+ kWhatSendMore,
+ kWhatStop,
+ kWhatTimeSyncerNotify,
+ };
+
+#if 1
+ static const RTPBase::TransportMode kRTPMode = RTPBase::TRANSPORT_UDP;
+ static const RTPBase::TransportMode kRTCPMode = RTPBase::TRANSPORT_UDP;
+#else
+ static const RTPBase::TransportMode kRTPMode = RTPBase::TRANSPORT_TCP;
+ static const RTPBase::TransportMode kRTCPMode = RTPBase::TRANSPORT_NONE;
+#endif
+
+#if 1
+ static const RTPBase::PacketizationMode kPacketizationMode
+ = RTPBase::PACKETIZATION_H264;
+#else
+ static const RTPBase::PacketizationMode kPacketizationMode
+ = RTPBase::PACKETIZATION_NONE;
+#endif
+
+ sp<ANetworkSession> mNetSession;
+ sp<PacketSource> mSource;
+ sp<RTPSender> mSender;
+ sp<RTPReceiver> mReceiver;
+
+ sp<TimeSyncer> mTimeSyncer;
+ bool mTimeSyncerStarted;
+
+ int64_t mFirstTimeRealUs;
+ int64_t mFirstTimeMediaUs;
+
+ int64_t mTimeOffsetUs;
+ bool mTimeOffsetValid;
+
+ status_t readMore();
+
+ DISALLOW_EVIL_CONSTRUCTORS(TestHandler);
+};
+
+TestHandler::TestHandler(const sp<ANetworkSession> &netSession)
+ : mNetSession(netSession),
+ mTimeSyncerStarted(false),
+ mFirstTimeRealUs(-1ll),
+ mFirstTimeMediaUs(-1ll),
+ mTimeOffsetUs(-1ll),
+ mTimeOffsetValid(false) {
+}
+
+TestHandler::~TestHandler() {
+}
+
+void TestHandler::listen() {
+ sp<AMessage> msg = new AMessage(kWhatListen, id());
+ msg->post();
+}
+
+void TestHandler::connect(const char *host, int32_t port) {
+ sp<AMessage> msg = new AMessage(kWhatConnect, id());
+ msg->setString("host", host);
+ msg->setInt32("port", port);
+ msg->post();
+}
+
+static void dumpDelay(int64_t delayMs) {
+ static const int64_t kMinDelayMs = 0;
+ static const int64_t kMaxDelayMs = 300;
+
+ const char *kPattern = "########################################";
+ size_t kPatternSize = strlen(kPattern);
+
+ int n = (kPatternSize * (delayMs - kMinDelayMs))
+ / (kMaxDelayMs - kMinDelayMs);
+
+ if (n < 0) {
+ n = 0;
+ } else if ((size_t)n > kPatternSize) {
+ n = kPatternSize;
+ }
+
+ ALOGI("(%4lld ms) %s\n",
+ delayMs,
+ kPattern + kPatternSize - n);
+}
+
+void TestHandler::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatListen:
+ {
+ sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+
+ notify = new AMessage(kWhatReceiverNotify, id());
+ mReceiver = new RTPReceiver(
+ mNetSession, notify, RTPReceiver::FLAG_AUTO_CONNECT);
+ looper()->registerHandler(mReceiver);
+
+ CHECK_EQ((status_t)OK,
+ mReceiver->registerPacketType(33, kPacketizationMode));
+
+ int32_t receiverRTPPort;
+ CHECK_EQ((status_t)OK,
+ mReceiver->initAsync(
+ kRTPMode,
+ kRTCPMode,
+ &receiverRTPPort));
+
+ printf("picked receiverRTPPort %d\n", receiverRTPPort);
+
+#if 0
+ CHECK_EQ((status_t)OK,
+ mReceiver->connect(
+ "127.0.0.1", senderRTPPort, senderRTPPort + 1));
+#endif
+ break;
+ }
+
+ case kWhatConnect:
+ {
+ AString host;
+ CHECK(msg->findString("host", &host));
+
+ sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+ mTimeSyncer->startServer(8123);
+
+ int32_t receiverRTPPort;
+ CHECK(msg->findInt32("port", &receiverRTPPort));
+
+#if 1
+ mSource = new MediaPacketSource;
+#else
+ mSource = new SimplePacketSource;
+#endif
+
+ notify = new AMessage(kWhatSenderNotify, id());
+ mSender = new RTPSender(mNetSession, notify);
+
+ looper()->registerHandler(mSender);
+
+ int32_t senderRTPPort;
+ CHECK_EQ((status_t)OK,
+ mSender->initAsync(
+ host.c_str(),
+ receiverRTPPort,
+ kRTPMode,
+ kRTCPMode == RTPBase::TRANSPORT_NONE
+ ? -1 : receiverRTPPort + 1,
+ kRTCPMode,
+ &senderRTPPort));
+
+ printf("picked senderRTPPort %d\n", senderRTPPort);
+ break;
+ }
+
+ case kWhatSenderNotify:
+ {
+ ALOGI("kWhatSenderNotify");
+
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ switch (what) {
+ case RTPSender::kWhatInitDone:
+ {
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ ALOGI("RTPSender::initAsync completed w/ err %d", err);
+
+ if (err == OK) {
+ err = readMore();
+
+ if (err != OK) {
+ (new AMessage(kWhatStop, id()))->post();
+ }
+ }
+ break;
+ }
+
+ case RTPSender::kWhatError:
+ break;
+ }
+ break;
+ }
+
+ case kWhatReceiverNotify:
+ {
+ ALOGV("kWhatReceiverNotify");
+
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ switch (what) {
+ case RTPReceiver::kWhatInitDone:
+ {
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ ALOGI("RTPReceiver::initAsync completed w/ err %d", err);
+ break;
+ }
+
+ case RTPReceiver::kWhatError:
+ break;
+
+ case RTPReceiver::kWhatAccessUnit:
+ {
+#if 0
+ if (!mTimeSyncerStarted) {
+ mTimeSyncer->startClient("172.18.41.216", 8123);
+ mTimeSyncerStarted = true;
+ }
+
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ if (mTimeOffsetValid) {
+ timeUs -= mTimeOffsetUs;
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t delayMs = (nowUs - timeUs) / 1000ll;
+
+ dumpDelay(delayMs);
+ }
+#endif
+ break;
+ }
+
+ case RTPReceiver::kWhatPacketLost:
+ ALOGV("kWhatPacketLost");
+ break;
+
+ default:
+ TRESPASS();
+ }
+ break;
+ }
+
+ case kWhatSendMore:
+ {
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ CHECK_EQ((status_t)OK,
+ mSender->queueBuffer(
+ accessUnit,
+ 33,
+ kPacketizationMode));
+
+ status_t err = readMore();
+
+ if (err != OK) {
+ (new AMessage(kWhatStop, id()))->post();
+ }
+ break;
+ }
+
+ case kWhatStop:
+ {
+ if (mReceiver != NULL) {
+ looper()->unregisterHandler(mReceiver->id());
+ mReceiver.clear();
+ }
+
+ if (mSender != NULL) {
+ looper()->unregisterHandler(mSender->id());
+ mSender.clear();
+ }
+
+ mSource.clear();
+
+ looper()->stop();
+ break;
+ }
+
+ case kWhatTimeSyncerNotify:
+ {
+ CHECK(msg->findInt64("offset", &mTimeOffsetUs));
+ mTimeOffsetValid = true;
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+status_t TestHandler::readMore() {
+ sp<ABuffer> accessUnit = mSource->getNextAccessUnit();
+
+ if (accessUnit == NULL) {
+ return ERROR_END_OF_STREAM;
+ }
+
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t whenUs;
+
+ if (mFirstTimeRealUs < 0ll) {
+ mFirstTimeRealUs = whenUs = nowUs;
+ mFirstTimeMediaUs = timeUs;
+ } else {
+ whenUs = mFirstTimeRealUs + timeUs - mFirstTimeMediaUs;
+ }
+
+ accessUnit->meta()->setInt64("timeUs", whenUs);
+
+ sp<AMessage> msg = new AMessage(kWhatSendMore, id());
+ msg->setBuffer("accessUnit", accessUnit);
+ msg->post(whenUs - nowUs);
+
+ return OK;
+}
+
+} // namespace android
+
+static void usage(const char *me) {
+ fprintf(stderr,
+ "usage: %s -c host:port\tconnect to remote host\n"
+ " -l \tlisten\n",
+ me);
+}
+
+int main(int argc, char **argv) {
+ using namespace android;
+
+ // srand(time(NULL));
+
+ ProcessState::self()->startThreadPool();
+
+ DataSource::RegisterDefaultSniffers();
+
+ bool listen = false;
+ int32_t connectToPort = -1;
+ AString connectToHost;
+
+ int res;
+ while ((res = getopt(argc, argv, "hc:l")) >= 0) {
+ switch (res) {
+ case 'c':
+ {
+ const char *colonPos = strrchr(optarg, ':');
+
+ if (colonPos == NULL) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ connectToHost.setTo(optarg, colonPos - optarg);
+
+ char *end;
+ connectToPort = strtol(colonPos + 1, &end, 10);
+
+ if (*end != '\0' || end == colonPos + 1
+ || connectToPort < 1 || connectToPort > 65535) {
+ fprintf(stderr, "Illegal port specified.\n");
+ exit(1);
+ }
+ break;
+ }
+
+ case 'l':
+ {
+ listen = true;
+ break;
+ }
+
+ case '?':
+ case 'h':
+ usage(argv[0]);
+ exit(1);
+ }
+ }
+
+ if (!listen && connectToPort < 0) {
+ fprintf(stderr,
+ "You need to select either client or server mode.\n");
+ exit(1);
+ }
+
+ sp<ANetworkSession> netSession = new ANetworkSession;
+ netSession->start();
+
+ sp<ALooper> looper = new ALooper;
+
+ sp<TestHandler> handler = new TestHandler(netSession);
+ looper->registerHandler(handler);
+
+ if (listen) {
+ handler->listen();
+ }
+
+ if (connectToPort >= 0) {
+ handler->connect(connectToHost.c_str(), connectToPort);
+ }
+
+ looper->start(true /* runOnCallingThread */);
+
+ return 0;
+}
+
diff --git a/media/libstagefright/wifi-display/sink/DirectRenderer.cpp b/media/libstagefright/wifi-display/sink/DirectRenderer.cpp
new file mode 100644
index 0000000..15f9c88
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/DirectRenderer.cpp
@@ -0,0 +1,625 @@
+/*
+ * Copyright 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 "DirectRenderer"
+#include <utils/Log.h>
+
+#include "DirectRenderer.h"
+
+#include <gui/SurfaceComposerClient.h>
+#include <gui/Surface.h>
+#include <media/AudioTrack.h>
+#include <media/ICrypto.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/MediaCodec.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+/*
+ Drives the decoding process using a MediaCodec instance. Input buffers
+ queued by calls to "queueInputBuffer" are fed to the decoder as soon
+ as the decoder is ready for them, the client is notified about output
+ buffers as the decoder spits them out.
+*/
+struct DirectRenderer::DecoderContext : public AHandler {
+ enum {
+ kWhatOutputBufferReady,
+ };
+ DecoderContext(const sp<AMessage> &notify);
+
+ status_t init(
+ const sp<AMessage> &format,
+ const sp<IGraphicBufferProducer> &surfaceTex);
+
+ void queueInputBuffer(const sp<ABuffer> &accessUnit);
+
+ status_t renderOutputBufferAndRelease(size_t index);
+ status_t releaseOutputBuffer(size_t index);
+
+protected:
+ virtual ~DecoderContext();
+
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatDecoderNotify,
+ };
+
+ sp<AMessage> mNotify;
+ sp<ALooper> mDecoderLooper;
+ sp<MediaCodec> mDecoder;
+ Vector<sp<ABuffer> > mDecoderInputBuffers;
+ Vector<sp<ABuffer> > mDecoderOutputBuffers;
+ List<size_t> mDecoderInputBuffersAvailable;
+ bool mDecoderNotificationPending;
+
+ List<sp<ABuffer> > mAccessUnits;
+
+ void onDecoderNotify();
+ void scheduleDecoderNotification();
+ void queueDecoderInputBuffers();
+
+ void queueOutputBuffer(
+ size_t index, int64_t timeUs, const sp<ABuffer> &buffer);
+
+ DISALLOW_EVIL_CONSTRUCTORS(DecoderContext);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+/*
+ A "push" audio renderer. The primary function of this renderer is to use
+ an AudioTrack in push mode and making sure not to block the event loop
+ be ensuring that calls to AudioTrack::write never block. This is done by
+ estimating an upper bound of data that can be written to the AudioTrack
+ buffer without delay.
+*/
+struct DirectRenderer::AudioRenderer : public AHandler {
+ AudioRenderer(const sp<DecoderContext> &decoderContext);
+
+ void queueInputBuffer(
+ size_t index, int64_t timeUs, const sp<ABuffer> &buffer);
+
+protected:
+ virtual ~AudioRenderer();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatPushAudio,
+ };
+
+ struct BufferInfo {
+ size_t mIndex;
+ int64_t mTimeUs;
+ sp<ABuffer> mBuffer;
+ };
+
+ sp<DecoderContext> mDecoderContext;
+ sp<AudioTrack> mAudioTrack;
+
+ List<BufferInfo> mInputBuffers;
+ bool mPushPending;
+
+ size_t mNumFramesWritten;
+
+ void schedulePushIfNecessary();
+ void onPushAudio();
+
+ ssize_t writeNonBlocking(const uint8_t *data, size_t size);
+
+ DISALLOW_EVIL_CONSTRUCTORS(AudioRenderer);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+DirectRenderer::DecoderContext::DecoderContext(const sp<AMessage> &notify)
+ : mNotify(notify),
+ mDecoderNotificationPending(false) {
+}
+
+DirectRenderer::DecoderContext::~DecoderContext() {
+ if (mDecoder != NULL) {
+ mDecoder->release();
+ mDecoder.clear();
+
+ mDecoderLooper->stop();
+ mDecoderLooper.clear();
+ }
+}
+
+status_t DirectRenderer::DecoderContext::init(
+ const sp<AMessage> &format,
+ const sp<IGraphicBufferProducer> &surfaceTex) {
+ CHECK(mDecoder == NULL);
+
+ AString mime;
+ CHECK(format->findString("mime", &mime));
+
+ mDecoderLooper = new ALooper;
+ mDecoderLooper->setName("video codec looper");
+
+ mDecoderLooper->start(
+ false /* runOnCallingThread */,
+ false /* canCallJava */,
+ PRIORITY_DEFAULT);
+
+ mDecoder = MediaCodec::CreateByType(
+ mDecoderLooper, mime.c_str(), false /* encoder */);
+
+ CHECK(mDecoder != NULL);
+
+ status_t err = mDecoder->configure(
+ format,
+ surfaceTex == NULL
+ ? NULL : new Surface(surfaceTex),
+ NULL /* crypto */,
+ 0 /* flags */);
+ CHECK_EQ(err, (status_t)OK);
+
+ err = mDecoder->start();
+ CHECK_EQ(err, (status_t)OK);
+
+ err = mDecoder->getInputBuffers(
+ &mDecoderInputBuffers);
+ CHECK_EQ(err, (status_t)OK);
+
+ err = mDecoder->getOutputBuffers(
+ &mDecoderOutputBuffers);
+ CHECK_EQ(err, (status_t)OK);
+
+ scheduleDecoderNotification();
+
+ return OK;
+}
+
+void DirectRenderer::DecoderContext::queueInputBuffer(
+ const sp<ABuffer> &accessUnit) {
+ CHECK(mDecoder != NULL);
+
+ mAccessUnits.push_back(accessUnit);
+ queueDecoderInputBuffers();
+}
+
+status_t DirectRenderer::DecoderContext::renderOutputBufferAndRelease(
+ size_t index) {
+ return mDecoder->renderOutputBufferAndRelease(index);
+}
+
+status_t DirectRenderer::DecoderContext::releaseOutputBuffer(size_t index) {
+ return mDecoder->releaseOutputBuffer(index);
+}
+
+void DirectRenderer::DecoderContext::queueDecoderInputBuffers() {
+ if (mDecoder == NULL) {
+ return;
+ }
+
+ bool submittedMore = false;
+
+ while (!mAccessUnits.empty()
+ && !mDecoderInputBuffersAvailable.empty()) {
+ size_t index = *mDecoderInputBuffersAvailable.begin();
+
+ mDecoderInputBuffersAvailable.erase(
+ mDecoderInputBuffersAvailable.begin());
+
+ sp<ABuffer> srcBuffer = *mAccessUnits.begin();
+ mAccessUnits.erase(mAccessUnits.begin());
+
+ const sp<ABuffer> &dstBuffer =
+ mDecoderInputBuffers.itemAt(index);
+
+ memcpy(dstBuffer->data(), srcBuffer->data(), srcBuffer->size());
+
+ int64_t timeUs;
+ CHECK(srcBuffer->meta()->findInt64("timeUs", &timeUs));
+
+ status_t err = mDecoder->queueInputBuffer(
+ index,
+ 0 /* offset */,
+ srcBuffer->size(),
+ timeUs,
+ 0 /* flags */);
+ CHECK_EQ(err, (status_t)OK);
+
+ submittedMore = true;
+ }
+
+ if (submittedMore) {
+ scheduleDecoderNotification();
+ }
+}
+
+void DirectRenderer::DecoderContext::onMessageReceived(
+ const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatDecoderNotify:
+ {
+ onDecoderNotify();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void DirectRenderer::DecoderContext::onDecoderNotify() {
+ mDecoderNotificationPending = false;
+
+ for (;;) {
+ size_t index;
+ status_t err = mDecoder->dequeueInputBuffer(&index);
+
+ if (err == OK) {
+ mDecoderInputBuffersAvailable.push_back(index);
+ } else if (err == -EAGAIN) {
+ break;
+ } else {
+ TRESPASS();
+ }
+ }
+
+ queueDecoderInputBuffers();
+
+ for (;;) {
+ size_t index;
+ size_t offset;
+ size_t size;
+ int64_t timeUs;
+ uint32_t flags;
+ status_t err = mDecoder->dequeueOutputBuffer(
+ &index,
+ &offset,
+ &size,
+ &timeUs,
+ &flags);
+
+ if (err == OK) {
+ queueOutputBuffer(
+ index, timeUs, mDecoderOutputBuffers.itemAt(index));
+ } else if (err == INFO_OUTPUT_BUFFERS_CHANGED) {
+ err = mDecoder->getOutputBuffers(
+ &mDecoderOutputBuffers);
+ CHECK_EQ(err, (status_t)OK);
+ } else if (err == INFO_FORMAT_CHANGED) {
+ // We don't care.
+ } else if (err == -EAGAIN) {
+ break;
+ } else {
+ TRESPASS();
+ }
+ }
+
+ scheduleDecoderNotification();
+}
+
+void DirectRenderer::DecoderContext::scheduleDecoderNotification() {
+ if (mDecoderNotificationPending) {
+ return;
+ }
+
+ sp<AMessage> notify =
+ new AMessage(kWhatDecoderNotify, id());
+
+ mDecoder->requestActivityNotification(notify);
+ mDecoderNotificationPending = true;
+}
+
+void DirectRenderer::DecoderContext::queueOutputBuffer(
+ size_t index, int64_t timeUs, const sp<ABuffer> &buffer) {
+ sp<AMessage> msg = mNotify->dup();
+ msg->setInt32("what", kWhatOutputBufferReady);
+ msg->setSize("index", index);
+ msg->setInt64("timeUs", timeUs);
+ msg->setBuffer("buffer", buffer);
+ msg->post();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+DirectRenderer::AudioRenderer::AudioRenderer(
+ const sp<DecoderContext> &decoderContext)
+ : mDecoderContext(decoderContext),
+ mPushPending(false),
+ mNumFramesWritten(0) {
+ mAudioTrack = new AudioTrack(
+ AUDIO_STREAM_DEFAULT,
+ 48000.0f,
+ AUDIO_FORMAT_PCM,
+ AUDIO_CHANNEL_OUT_STEREO,
+ (int)0 /* frameCount */);
+
+ CHECK_EQ((status_t)OK, mAudioTrack->initCheck());
+
+ mAudioTrack->start();
+}
+
+DirectRenderer::AudioRenderer::~AudioRenderer() {
+}
+
+void DirectRenderer::AudioRenderer::queueInputBuffer(
+ size_t index, int64_t timeUs, const sp<ABuffer> &buffer) {
+ BufferInfo info;
+ info.mIndex = index;
+ info.mTimeUs = timeUs;
+ info.mBuffer = buffer;
+
+ mInputBuffers.push_back(info);
+ schedulePushIfNecessary();
+}
+
+void DirectRenderer::AudioRenderer::onMessageReceived(
+ const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatPushAudio:
+ {
+ onPushAudio();
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+void DirectRenderer::AudioRenderer::schedulePushIfNecessary() {
+ if (mPushPending || mInputBuffers.empty()) {
+ return;
+ }
+
+ mPushPending = true;
+
+ uint32_t numFramesPlayed;
+ CHECK_EQ(mAudioTrack->getPosition(&numFramesPlayed),
+ (status_t)OK);
+
+ uint32_t numFramesPendingPlayout = mNumFramesWritten - numFramesPlayed;
+
+ // This is how long the audio sink will have data to
+ // play back.
+ const float msecsPerFrame = 1000.0f / mAudioTrack->getSampleRate();
+
+ int64_t delayUs =
+ msecsPerFrame * numFramesPendingPlayout * 1000ll;
+
+ // Let's give it more data after about half that time
+ // has elapsed.
+ (new AMessage(kWhatPushAudio, id()))->post(delayUs / 2);
+}
+
+void DirectRenderer::AudioRenderer::onPushAudio() {
+ mPushPending = false;
+
+ while (!mInputBuffers.empty()) {
+ const BufferInfo &info = *mInputBuffers.begin();
+
+ ssize_t n = writeNonBlocking(
+ info.mBuffer->data(), info.mBuffer->size());
+
+ if (n < (ssize_t)info.mBuffer->size()) {
+ CHECK_GE(n, 0);
+
+ info.mBuffer->setRange(
+ info.mBuffer->offset() + n, info.mBuffer->size() - n);
+ break;
+ }
+
+ mDecoderContext->releaseOutputBuffer(info.mIndex);
+
+ mInputBuffers.erase(mInputBuffers.begin());
+ }
+
+ schedulePushIfNecessary();
+}
+
+ssize_t DirectRenderer::AudioRenderer::writeNonBlocking(
+ const uint8_t *data, size_t size) {
+ uint32_t numFramesPlayed;
+ status_t err = mAudioTrack->getPosition(&numFramesPlayed);
+ if (err != OK) {
+ return err;
+ }
+
+ ssize_t numFramesAvailableToWrite =
+ mAudioTrack->frameCount() - (mNumFramesWritten - numFramesPlayed);
+
+ size_t numBytesAvailableToWrite =
+ numFramesAvailableToWrite * mAudioTrack->frameSize();
+
+ if (size > numBytesAvailableToWrite) {
+ size = numBytesAvailableToWrite;
+ }
+
+ CHECK_EQ(mAudioTrack->write(data, size), (ssize_t)size);
+
+ size_t numFramesWritten = size / mAudioTrack->frameSize();
+ mNumFramesWritten += numFramesWritten;
+
+ return size;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+DirectRenderer::DirectRenderer(
+ const sp<IGraphicBufferProducer> &bufferProducer)
+ : mSurfaceTex(bufferProducer),
+ mVideoRenderPending(false),
+ mNumFramesLate(0),
+ mNumFrames(0) {
+}
+
+DirectRenderer::~DirectRenderer() {
+}
+
+void DirectRenderer::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatDecoderNotify:
+ {
+ onDecoderNotify(msg);
+ break;
+ }
+
+ case kWhatRenderVideo:
+ {
+ onRenderVideo();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void DirectRenderer::setFormat(size_t trackIndex, const sp<AMessage> &format) {
+ CHECK_LT(trackIndex, 2u);
+
+ CHECK(mDecoderContext[trackIndex] == NULL);
+
+ sp<AMessage> notify = new AMessage(kWhatDecoderNotify, id());
+ notify->setSize("trackIndex", trackIndex);
+
+ mDecoderContext[trackIndex] = new DecoderContext(notify);
+ looper()->registerHandler(mDecoderContext[trackIndex]);
+
+ CHECK_EQ((status_t)OK,
+ mDecoderContext[trackIndex]->init(
+ format, trackIndex == 0 ? mSurfaceTex : NULL));
+
+ if (trackIndex == 1) {
+ // Audio
+ mAudioRenderer = new AudioRenderer(mDecoderContext[1]);
+ looper()->registerHandler(mAudioRenderer);
+ }
+}
+
+void DirectRenderer::queueAccessUnit(
+ size_t trackIndex, const sp<ABuffer> &accessUnit) {
+ CHECK_LT(trackIndex, 2u);
+
+ if (mDecoderContext[trackIndex] == NULL) {
+ CHECK_EQ(trackIndex, 0u);
+
+ sp<AMessage> format = new AMessage;
+ format->setString("mime", "video/avc");
+ format->setInt32("width", 640);
+ format->setInt32("height", 360);
+
+ setFormat(trackIndex, format);
+ }
+
+ mDecoderContext[trackIndex]->queueInputBuffer(accessUnit);
+}
+
+void DirectRenderer::onDecoderNotify(const sp<AMessage> &msg) {
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ switch (what) {
+ case DecoderContext::kWhatOutputBufferReady:
+ {
+ size_t index;
+ CHECK(msg->findSize("index", &index));
+
+ int64_t timeUs;
+ CHECK(msg->findInt64("timeUs", &timeUs));
+
+ sp<ABuffer> buffer;
+ CHECK(msg->findBuffer("buffer", &buffer));
+
+ queueOutputBuffer(trackIndex, index, timeUs, buffer);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void DirectRenderer::queueOutputBuffer(
+ size_t trackIndex,
+ size_t index, int64_t timeUs, const sp<ABuffer> &buffer) {
+ if (trackIndex == 1) {
+ // Audio
+ mAudioRenderer->queueInputBuffer(index, timeUs, buffer);
+ return;
+ }
+
+ OutputInfo info;
+ info.mIndex = index;
+ info.mTimeUs = timeUs;
+ info.mBuffer = buffer;
+ mVideoOutputBuffers.push_back(info);
+
+ scheduleVideoRenderIfNecessary();
+}
+
+void DirectRenderer::scheduleVideoRenderIfNecessary() {
+ if (mVideoRenderPending || mVideoOutputBuffers.empty()) {
+ return;
+ }
+
+ mVideoRenderPending = true;
+
+ int64_t timeUs = (*mVideoOutputBuffers.begin()).mTimeUs;
+ int64_t nowUs = ALooper::GetNowUs();
+
+ int64_t delayUs = timeUs - nowUs;
+
+ (new AMessage(kWhatRenderVideo, id()))->post(delayUs);
+}
+
+void DirectRenderer::onRenderVideo() {
+ mVideoRenderPending = false;
+
+ int64_t nowUs = ALooper::GetNowUs();
+
+ while (!mVideoOutputBuffers.empty()) {
+ const OutputInfo &info = *mVideoOutputBuffers.begin();
+
+ if (info.mTimeUs > nowUs) {
+ break;
+ }
+
+ if (info.mTimeUs + 15000ll < nowUs) {
+ ++mNumFramesLate;
+ }
+ ++mNumFrames;
+
+ status_t err =
+ mDecoderContext[0]->renderOutputBufferAndRelease(info.mIndex);
+ CHECK_EQ(err, (status_t)OK);
+
+ mVideoOutputBuffers.erase(mVideoOutputBuffers.begin());
+ }
+
+ scheduleVideoRenderIfNecessary();
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/sink/DirectRenderer.h b/media/libstagefright/wifi-display/sink/DirectRenderer.h
new file mode 100644
index 0000000..c5a4a83
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/DirectRenderer.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef DIRECT_RENDERER_H_
+
+#define DIRECT_RENDERER_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct ABuffer;
+struct AudioTrack;
+struct IGraphicBufferProducer;
+struct MediaCodec;
+
+// Renders audio and video data queued by calls to "queueAccessUnit".
+struct DirectRenderer : public AHandler {
+ DirectRenderer(const sp<IGraphicBufferProducer> &bufferProducer);
+
+ void setFormat(size_t trackIndex, const sp<AMessage> &format);
+ void queueAccessUnit(size_t trackIndex, const sp<ABuffer> &accessUnit);
+
+protected:
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+ virtual ~DirectRenderer();
+
+private:
+ struct DecoderContext;
+ struct AudioRenderer;
+
+ enum {
+ kWhatDecoderNotify,
+ kWhatRenderVideo,
+ };
+
+ struct OutputInfo {
+ size_t mIndex;
+ int64_t mTimeUs;
+ sp<ABuffer> mBuffer;
+ };
+
+ sp<IGraphicBufferProducer> mSurfaceTex;
+
+ sp<DecoderContext> mDecoderContext[2];
+ List<OutputInfo> mVideoOutputBuffers;
+
+ bool mVideoRenderPending;
+
+ sp<AudioRenderer> mAudioRenderer;
+
+ int32_t mNumFramesLate;
+ int32_t mNumFrames;
+
+ void onDecoderNotify(const sp<AMessage> &msg);
+
+ void queueOutputBuffer(
+ size_t trackIndex,
+ size_t index, int64_t timeUs, const sp<ABuffer> &buffer);
+
+ void scheduleVideoRenderIfNecessary();
+ void onRenderVideo();
+
+ DISALLOW_EVIL_CONSTRUCTORS(DirectRenderer);
+};
+
+} // namespace android
+
+#endif // DIRECT_RENDERER_H_
diff --git a/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp b/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp
new file mode 100644
index 0000000..5db2099
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp
@@ -0,0 +1,917 @@
+/*
+ * Copyright 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 "WifiDisplaySink"
+#include <utils/Log.h>
+
+#include "WifiDisplaySink.h"
+
+#include "DirectRenderer.h"
+#include "MediaReceiver.h"
+#include "ParsedMessage.h"
+#include "TimeSyncer.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/MediaErrors.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+// static
+const AString WifiDisplaySink::sUserAgent = MakeUserAgent();
+
+WifiDisplaySink::WifiDisplaySink(
+ uint32_t flags,
+ const sp<ANetworkSession> &netSession,
+ const sp<IGraphicBufferProducer> &bufferProducer,
+ const sp<AMessage> &notify)
+ : mState(UNDEFINED),
+ mFlags(flags),
+ mNetSession(netSession),
+ mSurfaceTex(bufferProducer),
+ mNotify(notify),
+ mUsingTCPTransport(false),
+ mUsingTCPInterleaving(false),
+ mSessionID(0),
+ mNextCSeq(1),
+ mIDRFrameRequestPending(false),
+ mTimeOffsetUs(0ll),
+ mTimeOffsetValid(false),
+ mSetupDeferred(false),
+ mLatencyCount(0),
+ mLatencySumUs(0ll),
+ mLatencyMaxUs(0ll),
+ mMaxDelayMs(-1ll) {
+ // We support any and all resolutions, but prefer 720p30
+ mSinkSupportedVideoFormats.setNativeResolution(
+ VideoFormats::RESOLUTION_CEA, 5); // 1280 x 720 p30
+
+ mSinkSupportedVideoFormats.enableAll();
+}
+
+WifiDisplaySink::~WifiDisplaySink() {
+}
+
+void WifiDisplaySink::start(const char *sourceHost, int32_t sourcePort) {
+ sp<AMessage> msg = new AMessage(kWhatStart, id());
+ msg->setString("sourceHost", sourceHost);
+ msg->setInt32("sourcePort", sourcePort);
+ msg->post();
+}
+
+void WifiDisplaySink::start(const char *uri) {
+ sp<AMessage> msg = new AMessage(kWhatStart, id());
+ msg->setString("setupURI", uri);
+ msg->post();
+}
+
+// static
+bool WifiDisplaySink::ParseURL(
+ const char *url, AString *host, int32_t *port, AString *path,
+ AString *user, AString *pass) {
+ host->clear();
+ *port = 0;
+ path->clear();
+ user->clear();
+ pass->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);
+ }
+
+ ssize_t atPos = host->find("@");
+
+ if (atPos >= 0) {
+ // Split of user:pass@ from hostname.
+
+ AString userPass(*host, 0, atPos);
+ host->erase(0, atPos + 1);
+
+ ssize_t colonPos = userPass.find(":");
+
+ if (colonPos < 0) {
+ *user = userPass;
+ } else {
+ user->setTo(userPass, 0, colonPos);
+ pass->setTo(userPass, colonPos + 1, userPass.size() - colonPos - 1);
+ }
+ }
+
+ const char *colonPos = strchr(host->c_str(), ':');
+
+ if (colonPos != NULL) {
+ char *end;
+ unsigned long x = strtoul(colonPos + 1, &end, 10);
+
+ if (end == colonPos + 1 || *end != '\0' || 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 WifiDisplaySink::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatStart:
+ {
+ sleep(2); // XXX
+
+ int32_t sourcePort;
+ CHECK(msg->findString("sourceHost", &mRTSPHost));
+ CHECK(msg->findInt32("sourcePort", &sourcePort));
+
+ sp<AMessage> notify = new AMessage(kWhatRTSPNotify, id());
+
+ status_t err = mNetSession->createRTSPClient(
+ mRTSPHost.c_str(), sourcePort, notify, &mSessionID);
+ CHECK_EQ(err, (status_t)OK);
+
+ mState = CONNECTING;
+ break;
+ }
+
+ case kWhatRTSPNotify:
+ {
+ int32_t reason;
+ CHECK(msg->findInt32("reason", &reason));
+
+ switch (reason) {
+ case ANetworkSession::kWhatError:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ AString detail;
+ CHECK(msg->findString("detail", &detail));
+
+ ALOGE("An error occurred in session %d (%d, '%s/%s').",
+ sessionID,
+ err,
+ detail.c_str(),
+ strerror(-err));
+
+ if (sessionID == mSessionID) {
+ ALOGI("Lost control connection.");
+
+ // The control connection is dead now.
+ mNetSession->destroySession(mSessionID);
+ mSessionID = 0;
+
+ if (mNotify == NULL) {
+ looper()->stop();
+ } else {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatDisconnected);
+ notify->post();
+ }
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatConnected:
+ {
+ ALOGI("We're now connected.");
+ mState = CONNECTED;
+
+ if (mFlags & FLAG_SPECIAL_MODE) {
+ sp<AMessage> notify = new AMessage(
+ kWhatTimeSyncerNotify, id());
+
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+
+ mTimeSyncer->startClient(mRTSPHost.c_str(), 8123);
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatData:
+ {
+ onReceiveClientData(msg);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+ break;
+ }
+
+ case kWhatStop:
+ {
+ looper()->stop();
+ break;
+ }
+
+ case kWhatMediaReceiverNotify:
+ {
+ onMediaReceiverNotify(msg);
+ break;
+ }
+
+ case kWhatTimeSyncerNotify:
+ {
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ if (what == TimeSyncer::kWhatTimeOffset) {
+ CHECK(msg->findInt64("offset", &mTimeOffsetUs));
+ mTimeOffsetValid = true;
+
+ if (mSetupDeferred) {
+ CHECK_EQ((status_t)OK,
+ sendSetup(
+ mSessionID,
+ "rtsp://x.x.x.x:x/wfd1.0/streamid=0"));
+
+ mSetupDeferred = false;
+ }
+ }
+ break;
+ }
+
+ case kWhatReportLateness:
+ {
+ if (mLatencyCount > 0) {
+ int64_t avgLatencyUs = mLatencySumUs / mLatencyCount;
+
+ ALOGV("avg. latency = %lld ms (max %lld ms)",
+ avgLatencyUs / 1000ll,
+ mLatencyMaxUs / 1000ll);
+
+ sp<AMessage> params = new AMessage;
+ params->setInt64("avgLatencyUs", avgLatencyUs);
+ params->setInt64("maxLatencyUs", mLatencyMaxUs);
+ mMediaReceiver->informSender(0 /* trackIndex */, params);
+ }
+
+ mLatencyCount = 0;
+ mLatencySumUs = 0ll;
+ mLatencyMaxUs = 0ll;
+
+ msg->post(kReportLatenessEveryUs);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void WifiDisplaySink::dumpDelay(size_t trackIndex, int64_t timeUs) {
+ int64_t delayMs = (ALooper::GetNowUs() - timeUs) / 1000ll;
+
+ if (delayMs > mMaxDelayMs) {
+ mMaxDelayMs = delayMs;
+ }
+
+ static const int64_t kMinDelayMs = 0;
+ static const int64_t kMaxDelayMs = 300;
+
+ const char *kPattern = "########################################";
+ size_t kPatternSize = strlen(kPattern);
+
+ int n = (kPatternSize * (delayMs - kMinDelayMs))
+ / (kMaxDelayMs - kMinDelayMs);
+
+ if (n < 0) {
+ n = 0;
+ } else if ((size_t)n > kPatternSize) {
+ n = kPatternSize;
+ }
+
+ ALOGI("[%lld]: (%4lld ms / %4lld ms) %s",
+ timeUs / 1000,
+ delayMs,
+ mMaxDelayMs,
+ kPattern + kPatternSize - n);
+}
+
+void WifiDisplaySink::onMediaReceiverNotify(const sp<AMessage> &msg) {
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ switch (what) {
+ case MediaReceiver::kWhatInitDone:
+ {
+ status_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ ALOGI("MediaReceiver initialization completed w/ err %d", err);
+ break;
+ }
+
+ case MediaReceiver::kWhatError:
+ {
+ status_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ ALOGE("MediaReceiver signaled error %d", err);
+ break;
+ }
+
+ case MediaReceiver::kWhatAccessUnit:
+ {
+ if (mRenderer == NULL) {
+ mRenderer = new DirectRenderer(mSurfaceTex);
+ looper()->registerHandler(mRenderer);
+ }
+
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ if (!mTimeOffsetValid && !(mFlags & FLAG_SPECIAL_MODE)) {
+ mTimeOffsetUs = timeUs - ALooper::GetNowUs();
+ mTimeOffsetValid = true;
+ }
+
+ CHECK(mTimeOffsetValid);
+
+ // We are the timesync _client_,
+ // client time = server time - time offset.
+ timeUs -= mTimeOffsetUs;
+
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t delayUs = nowUs - timeUs;
+
+ mLatencySumUs += delayUs;
+ if (mLatencyCount == 0 || delayUs > mLatencyMaxUs) {
+ mLatencyMaxUs = delayUs;
+ }
+ ++mLatencyCount;
+
+ // dumpDelay(trackIndex, timeUs);
+
+ timeUs += 220000ll; // Assume 220 ms of latency
+ accessUnit->meta()->setInt64("timeUs", timeUs);
+
+ sp<AMessage> format;
+ if (msg->findMessage("format", &format)) {
+ mRenderer->setFormat(trackIndex, format);
+ }
+
+ mRenderer->queueAccessUnit(trackIndex, accessUnit);
+ break;
+ }
+
+ case MediaReceiver::kWhatPacketLost:
+ {
+#if 0
+ if (!mIDRFrameRequestPending) {
+ ALOGI("requesting IDR frame");
+
+ sendIDRFrameRequest(mSessionID);
+ }
+#endif
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void WifiDisplaySink::registerResponseHandler(
+ int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func) {
+ ResponseID id;
+ id.mSessionID = sessionID;
+ id.mCSeq = cseq;
+ mResponseHandlers.add(id, func);
+}
+
+status_t WifiDisplaySink::sendM2(int32_t sessionID) {
+ AString request = "OPTIONS * RTSP/1.0\r\n";
+ AppendCommonResponse(&request, mNextCSeq);
+
+ request.append(
+ "Require: org.wfa.wfd1.0\r\n"
+ "\r\n");
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySink::onReceiveM2Response);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySink::onReceiveM2Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ return OK;
+}
+
+status_t WifiDisplaySink::onReceiveSetupResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ if (!msg->findString("session", &mPlaybackSessionID)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (!ParsedMessage::GetInt32Attribute(
+ mPlaybackSessionID.c_str(),
+ "timeout",
+ &mPlaybackSessionTimeoutSecs)) {
+ mPlaybackSessionTimeoutSecs = -1;
+ }
+
+ ssize_t colonPos = mPlaybackSessionID.find(";");
+ if (colonPos >= 0) {
+ // Strip any options from the returned session id.
+ mPlaybackSessionID.erase(
+ colonPos, mPlaybackSessionID.size() - colonPos);
+ }
+
+ status_t err = configureTransport(msg);
+
+ if (err != OK) {
+ return err;
+ }
+
+ mState = PAUSED;
+
+ return sendPlay(
+ sessionID,
+ "rtsp://x.x.x.x:x/wfd1.0/streamid=0");
+}
+
+status_t WifiDisplaySink::configureTransport(const sp<ParsedMessage> &msg) {
+ if (mUsingTCPTransport && !(mFlags & FLAG_SPECIAL_MODE)) {
+ // In "special" mode we still use a UDP RTCP back-channel that
+ // needs connecting.
+ return OK;
+ }
+
+ AString transport;
+ if (!msg->findString("transport", &transport)) {
+ ALOGE("Missing 'transport' field in SETUP response.");
+ return ERROR_MALFORMED;
+ }
+
+ AString sourceHost;
+ if (!ParsedMessage::GetAttribute(
+ transport.c_str(), "source", &sourceHost)) {
+ sourceHost = mRTSPHost;
+ }
+
+ AString serverPortStr;
+ if (!ParsedMessage::GetAttribute(
+ transport.c_str(), "server_port", &serverPortStr)) {
+ ALOGE("Missing 'server_port' in Transport field.");
+ return ERROR_MALFORMED;
+ }
+
+ int rtpPort, rtcpPort;
+ if (sscanf(serverPortStr.c_str(), "%d-%d", &rtpPort, &rtcpPort) != 2
+ || rtpPort <= 0 || rtpPort > 65535
+ || rtcpPort <=0 || rtcpPort > 65535
+ || rtcpPort != rtpPort + 1) {
+ ALOGE("Invalid server_port description '%s'.",
+ serverPortStr.c_str());
+
+ return ERROR_MALFORMED;
+ }
+
+ if (rtpPort & 1) {
+ ALOGW("Server picked an odd numbered RTP port.");
+ }
+
+ return mMediaReceiver->connectTrack(
+ 0 /* trackIndex */, sourceHost.c_str(), rtpPort, rtcpPort);
+}
+
+status_t WifiDisplaySink::onReceivePlayResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ mState = PLAYING;
+
+ (new AMessage(kWhatReportLateness, id()))->post(kReportLatenessEveryUs);
+
+ return OK;
+}
+
+status_t WifiDisplaySink::onReceiveIDRFrameRequestResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ CHECK(mIDRFrameRequestPending);
+ mIDRFrameRequestPending = false;
+
+ return OK;
+}
+
+void WifiDisplaySink::onReceiveClientData(const sp<AMessage> &msg) {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("data", &obj));
+
+ sp<ParsedMessage> data =
+ static_cast<ParsedMessage *>(obj.get());
+
+ ALOGV("session %d received '%s'",
+ sessionID, data->debugString().c_str());
+
+ AString method;
+ AString uri;
+ data->getRequestField(0, &method);
+
+ int32_t cseq;
+ if (!data->findInt32("cseq", &cseq)) {
+ sendErrorResponse(sessionID, "400 Bad Request", -1 /* cseq */);
+ return;
+ }
+
+ if (method.startsWith("RTSP/")) {
+ // This is a response.
+
+ ResponseID id;
+ id.mSessionID = sessionID;
+ id.mCSeq = cseq;
+
+ ssize_t index = mResponseHandlers.indexOfKey(id);
+
+ if (index < 0) {
+ ALOGW("Received unsolicited server response, cseq %d", cseq);
+ return;
+ }
+
+ HandleRTSPResponseFunc func = mResponseHandlers.valueAt(index);
+ mResponseHandlers.removeItemsAt(index);
+
+ status_t err = (this->*func)(sessionID, data);
+ CHECK_EQ(err, (status_t)OK);
+ } else {
+ AString version;
+ data->getRequestField(2, &version);
+ if (!(version == AString("RTSP/1.0"))) {
+ sendErrorResponse(sessionID, "505 RTSP Version not supported", cseq);
+ return;
+ }
+
+ if (method == "OPTIONS") {
+ onOptionsRequest(sessionID, cseq, data);
+ } else if (method == "GET_PARAMETER") {
+ onGetParameterRequest(sessionID, cseq, data);
+ } else if (method == "SET_PARAMETER") {
+ onSetParameterRequest(sessionID, cseq, data);
+ } else {
+ sendErrorResponse(sessionID, "405 Method Not Allowed", cseq);
+ }
+ }
+}
+
+void WifiDisplaySink::onOptionsRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq);
+ response.append("Public: org.wfa.wfd1.0, GET_PARAMETER, SET_PARAMETER\r\n");
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+
+ err = sendM2(sessionID);
+ CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySink::onGetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ AString body;
+
+ if (mState == CONNECTED) {
+ mUsingTCPTransport = false;
+ mUsingTCPInterleaving = false;
+
+ char val[PROPERTY_VALUE_MAX];
+ if (property_get("media.wfd-sink.tcp-mode", val, NULL)) {
+ if (!strcasecmp("true", val) || !strcmp("1", val)) {
+ ALOGI("Using TCP unicast transport.");
+ mUsingTCPTransport = true;
+ mUsingTCPInterleaving = false;
+ } else if (!strcasecmp("interleaved", val)) {
+ ALOGI("Using TCP interleaved transport.");
+ mUsingTCPTransport = true;
+ mUsingTCPInterleaving = true;
+ }
+ } else if (mFlags & FLAG_SPECIAL_MODE) {
+ mUsingTCPTransport = true;
+ }
+
+ body = "wfd_video_formats: ";
+ body.append(mSinkSupportedVideoFormats.getFormatSpec());
+
+ body.append(
+ "\r\nwfd_audio_codecs: AAC 0000000F 00\r\n"
+ "wfd_client_rtp_ports: RTP/AVP/");
+
+ if (mUsingTCPTransport) {
+ body.append("TCP;");
+ if (mUsingTCPInterleaving) {
+ body.append("interleaved");
+ } else {
+ body.append("unicast 19000 0");
+ }
+ } else {
+ body.append("UDP;unicast 19000 0");
+ }
+
+ body.append(" mode=play\r\n");
+ }
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq);
+ response.append("Content-Type: text/parameters\r\n");
+ response.append(StringPrintf("Content-Length: %d\r\n", body.size()));
+ response.append("\r\n");
+ response.append(body);
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+}
+
+status_t WifiDisplaySink::sendSetup(int32_t sessionID, const char *uri) {
+ sp<AMessage> notify = new AMessage(kWhatMediaReceiverNotify, id());
+
+ mMediaReceiverLooper = new ALooper;
+ mMediaReceiverLooper->setName("media_receiver");
+
+ mMediaReceiverLooper->start(
+ false /* runOnCallingThread */,
+ false /* canCallJava */,
+ PRIORITY_AUDIO);
+
+ mMediaReceiver = new MediaReceiver(mNetSession, notify);
+ mMediaReceiverLooper->registerHandler(mMediaReceiver);
+
+ RTPReceiver::TransportMode rtpMode = RTPReceiver::TRANSPORT_UDP;
+ if (mUsingTCPTransport) {
+ if (mUsingTCPInterleaving) {
+ rtpMode = RTPReceiver::TRANSPORT_TCP_INTERLEAVED;
+ } else {
+ rtpMode = RTPReceiver::TRANSPORT_TCP;
+ }
+ }
+
+ int32_t localRTPPort;
+ status_t err = mMediaReceiver->addTrack(
+ rtpMode, RTPReceiver::TRANSPORT_UDP /* rtcpMode */, &localRTPPort);
+
+ if (err == OK) {
+ err = mMediaReceiver->initAsync(MediaReceiver::MODE_TRANSPORT_STREAM);
+ }
+
+ if (err != OK) {
+ mMediaReceiverLooper->unregisterHandler(mMediaReceiver->id());
+ mMediaReceiver.clear();
+
+ mMediaReceiverLooper->stop();
+ mMediaReceiverLooper.clear();
+
+ return err;
+ }
+
+ AString request = StringPrintf("SETUP %s RTSP/1.0\r\n", uri);
+
+ AppendCommonResponse(&request, mNextCSeq);
+
+ if (rtpMode == RTPReceiver::TRANSPORT_TCP_INTERLEAVED) {
+ request.append("Transport: RTP/AVP/TCP;interleaved=0-1\r\n");
+ } else if (rtpMode == RTPReceiver::TRANSPORT_TCP) {
+ if (mFlags & FLAG_SPECIAL_MODE) {
+ // This isn't quite true, since the RTP connection is through TCP
+ // and the RTCP connection through UDP...
+ request.append(
+ StringPrintf(
+ "Transport: RTP/AVP/TCP;unicast;client_port=%d-%d\r\n",
+ localRTPPort, localRTPPort + 1));
+ } else {
+ request.append(
+ StringPrintf(
+ "Transport: RTP/AVP/TCP;unicast;client_port=%d\r\n",
+ localRTPPort));
+ }
+ } else {
+ request.append(
+ StringPrintf(
+ "Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n",
+ localRTPPort,
+ localRTPPort + 1));
+ }
+
+ request.append("\r\n");
+
+ ALOGV("request = '%s'", request.c_str());
+
+ err = mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySink::onReceiveSetupResponse);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySink::sendPlay(int32_t sessionID, const char *uri) {
+ AString request = StringPrintf("PLAY %s RTSP/1.0\r\n", uri);
+
+ AppendCommonResponse(&request, mNextCSeq);
+
+ request.append(StringPrintf("Session: %s\r\n", mPlaybackSessionID.c_str()));
+ request.append("\r\n");
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySink::onReceivePlayResponse);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySink::sendIDRFrameRequest(int32_t sessionID) {
+ CHECK(!mIDRFrameRequestPending);
+
+ AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
+
+ AppendCommonResponse(&request, mNextCSeq);
+
+ AString content = "wfd_idr_request\r\n";
+
+ request.append(StringPrintf("Session: %s\r\n", mPlaybackSessionID.c_str()));
+ request.append(StringPrintf("Content-Length: %d\r\n", content.size()));
+ request.append("\r\n");
+ request.append(content);
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID,
+ mNextCSeq,
+ &WifiDisplaySink::onReceiveIDRFrameRequestResponse);
+
+ ++mNextCSeq;
+
+ mIDRFrameRequestPending = true;
+
+ return OK;
+}
+
+void WifiDisplaySink::onSetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ const char *content = data->getContent();
+
+ if (strstr(content, "wfd_trigger_method: SETUP\r\n") != NULL) {
+ if ((mFlags & FLAG_SPECIAL_MODE) && !mTimeOffsetValid) {
+ mSetupDeferred = true;
+ } else {
+ status_t err =
+ sendSetup(
+ sessionID,
+ "rtsp://x.x.x.x:x/wfd1.0/streamid=0");
+
+ CHECK_EQ(err, (status_t)OK);
+ }
+ }
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq);
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySink::sendErrorResponse(
+ int32_t sessionID,
+ const char *errorDetail,
+ int32_t cseq) {
+ AString response;
+ response.append("RTSP/1.0 ");
+ response.append(errorDetail);
+ response.append("\r\n");
+
+ AppendCommonResponse(&response, cseq);
+
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+}
+
+// static
+void WifiDisplaySink::AppendCommonResponse(AString *response, int32_t cseq) {
+ time_t now = time(NULL);
+ struct tm *now2 = gmtime(&now);
+ char buf[128];
+ strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %z", now2);
+
+ response->append("Date: ");
+ response->append(buf);
+ response->append("\r\n");
+
+ response->append(StringPrintf("User-Agent: %s\r\n", sUserAgent.c_str()));
+
+ if (cseq >= 0) {
+ response->append(StringPrintf("CSeq: %d\r\n", cseq));
+ }
+}
+
+} // namespace android
diff --git a/media/libstagefright/wifi-display/sink/WifiDisplaySink.h b/media/libstagefright/wifi-display/sink/WifiDisplaySink.h
new file mode 100644
index 0000000..adb9d89
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/WifiDisplaySink.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef WIFI_DISPLAY_SINK_H_
+
+#define WIFI_DISPLAY_SINK_H_
+
+#include "ANetworkSession.h"
+
+#include "VideoFormats.h"
+
+#include <gui/Surface.h>
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct AMessage;
+struct DirectRenderer;
+struct MediaReceiver;
+struct ParsedMessage;
+struct TimeSyncer;
+
+// Represents the RTSP client acting as a wifi display sink.
+// Connects to a wifi display source and renders the incoming
+// transport stream using a MediaPlayer instance.
+struct WifiDisplaySink : public AHandler {
+ enum {
+ kWhatDisconnected,
+ };
+
+ enum Flags {
+ FLAG_SPECIAL_MODE = 1,
+ };
+
+ // If no notification message is specified (notify == NULL)
+ // the sink will stop its looper() once the session ends,
+ // otherwise it will post an appropriate notification but leave
+ // the looper() running.
+ WifiDisplaySink(
+ uint32_t flags,
+ const sp<ANetworkSession> &netSession,
+ const sp<IGraphicBufferProducer> &bufferProducer = NULL,
+ const sp<AMessage> &notify = NULL);
+
+ void start(const char *sourceHost, int32_t sourcePort);
+ void start(const char *uri);
+
+protected:
+ virtual ~WifiDisplaySink();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum State {
+ UNDEFINED,
+ CONNECTING,
+ CONNECTED,
+ PAUSED,
+ PLAYING,
+ };
+
+ enum {
+ kWhatStart,
+ kWhatRTSPNotify,
+ kWhatStop,
+ kWhatMediaReceiverNotify,
+ kWhatTimeSyncerNotify,
+ kWhatReportLateness,
+ };
+
+ struct ResponseID {
+ int32_t mSessionID;
+ int32_t mCSeq;
+
+ bool operator<(const ResponseID &other) const {
+ return mSessionID < other.mSessionID
+ || (mSessionID == other.mSessionID
+ && mCSeq < other.mCSeq);
+ }
+ };
+
+ typedef status_t (WifiDisplaySink::*HandleRTSPResponseFunc)(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ static const int64_t kReportLatenessEveryUs = 1000000ll;
+
+ static const AString sUserAgent;
+
+ State mState;
+ uint32_t mFlags;
+ VideoFormats mSinkSupportedVideoFormats;
+ sp<ANetworkSession> mNetSession;
+ sp<IGraphicBufferProducer> mSurfaceTex;
+ sp<AMessage> mNotify;
+ sp<TimeSyncer> mTimeSyncer;
+ bool mUsingTCPTransport;
+ bool mUsingTCPInterleaving;
+ AString mRTSPHost;
+ int32_t mSessionID;
+
+ int32_t mNextCSeq;
+
+ KeyedVector<ResponseID, HandleRTSPResponseFunc> mResponseHandlers;
+
+ sp<ALooper> mMediaReceiverLooper;
+ sp<MediaReceiver> mMediaReceiver;
+ sp<DirectRenderer> mRenderer;
+
+ AString mPlaybackSessionID;
+ int32_t mPlaybackSessionTimeoutSecs;
+
+ bool mIDRFrameRequestPending;
+
+ int64_t mTimeOffsetUs;
+ bool mTimeOffsetValid;
+
+ bool mSetupDeferred;
+
+ size_t mLatencyCount;
+ int64_t mLatencySumUs;
+ int64_t mLatencyMaxUs;
+
+ int64_t mMaxDelayMs;
+
+ status_t sendM2(int32_t sessionID);
+ status_t sendSetup(int32_t sessionID, const char *uri);
+ status_t sendPlay(int32_t sessionID, const char *uri);
+ status_t sendIDRFrameRequest(int32_t sessionID);
+
+ status_t onReceiveM2Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t onReceiveSetupResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t configureTransport(const sp<ParsedMessage> &msg);
+
+ status_t onReceivePlayResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t onReceiveIDRFrameRequestResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ void registerResponseHandler(
+ int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func);
+
+ void onReceiveClientData(const sp<AMessage> &msg);
+
+ void onOptionsRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ void onGetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ void onSetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ void onMediaReceiverNotify(const sp<AMessage> &msg);
+
+ void sendErrorResponse(
+ int32_t sessionID,
+ const char *errorDetail,
+ int32_t cseq);
+
+ static void AppendCommonResponse(AString *response, int32_t cseq);
+
+ bool ParseURL(
+ const char *url, AString *host, int32_t *port, AString *path,
+ AString *user, AString *pass);
+
+ void dumpDelay(size_t trackIndex, int64_t timeUs);
+
+ DISALLOW_EVIL_CONSTRUCTORS(WifiDisplaySink);
+};
+
+} // namespace android
+
+#endif // WIFI_DISPLAY_SINK_H_
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.cpp b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
index 3d7b865..cacfcca 100644
--- a/media/libstagefright/wifi-display/source/PlaybackSession.cpp
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
@@ -559,6 +559,8 @@ void WifiDisplaySource::PlaybackSession::onMessageReceived(
converter->dropAFrame();
}
}
+ } else if (what == MediaSender::kWhatInformSender) {
+ onSinkFeedback(msg);
} else {
TRESPASS();
}
@@ -654,6 +656,89 @@ void WifiDisplaySource::PlaybackSession::onMessageReceived(
}
}
+void WifiDisplaySource::PlaybackSession::onSinkFeedback(const sp<AMessage> &msg) {
+ int64_t avgLatencyUs;
+ CHECK(msg->findInt64("avgLatencyUs", &avgLatencyUs));
+
+ int64_t maxLatencyUs;
+ CHECK(msg->findInt64("maxLatencyUs", &maxLatencyUs));
+
+ ALOGI("sink reports avg. latency of %lld ms (max %lld ms)",
+ avgLatencyUs / 1000ll,
+ maxLatencyUs / 1000ll);
+
+ if (mVideoTrackIndex >= 0) {
+ const sp<Track> &videoTrack = mTracks.valueFor(mVideoTrackIndex);
+ sp<Converter> converter = videoTrack->converter();
+
+ if (converter != NULL) {
+ int32_t videoBitrate =
+ Converter::GetInt32Property("media.wfd.video-bitrate", -1);
+
+ char val[PROPERTY_VALUE_MAX];
+ if (videoBitrate < 0
+ && property_get("media.wfd.video-bitrate", val, NULL)
+ && !strcasecmp("adaptive", val)) {
+ videoBitrate = converter->getVideoBitrate();
+
+ if (avgLatencyUs > 300000ll) {
+ videoBitrate *= 0.6;
+ } else if (avgLatencyUs < 100000ll) {
+ videoBitrate *= 1.1;
+ }
+ }
+
+ if (videoBitrate > 0) {
+ if (videoBitrate < 500000) {
+ videoBitrate = 500000;
+ } else if (videoBitrate > 10000000) {
+ videoBitrate = 10000000;
+ }
+
+ if (videoBitrate != converter->getVideoBitrate()) {
+ ALOGI("setting video bitrate to %d bps", videoBitrate);
+
+ converter->setVideoBitrate(videoBitrate);
+ }
+ }
+ }
+
+ sp<RepeaterSource> repeaterSource = videoTrack->repeaterSource();
+ if (repeaterSource != NULL) {
+ double rateHz =
+ Converter::GetInt32Property(
+ "media.wfd.video-framerate", -1);
+
+ char val[PROPERTY_VALUE_MAX];
+ if (rateHz < 0.0
+ && property_get("media.wfd.video-framerate", val, NULL)
+ && !strcasecmp("adaptive", val)) {
+ rateHz = repeaterSource->getFrameRate();
+
+ if (avgLatencyUs > 300000ll) {
+ rateHz *= 0.9;
+ } else if (avgLatencyUs < 200000ll) {
+ rateHz *= 1.1;
+ }
+ }
+
+ if (rateHz > 0) {
+ if (rateHz < 5.0) {
+ rateHz = 5.0;
+ } else if (rateHz > 30.0) {
+ rateHz = 30.0;
+ }
+
+ if (rateHz != repeaterSource->getFrameRate()) {
+ ALOGI("setting frame rate to %.2f Hz", rateHz);
+
+ repeaterSource->setFrameRate(rateHz);
+ }
+ }
+ }
+ }
+}
+
status_t WifiDisplaySource::PlaybackSession::setupMediaPacketizer(
bool enableAudio, bool enableVideo) {
DataSource::RegisterDefaultSniffers();
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
index 2b5bee9..4a49811 100644
--- a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
@@ -23,6 +23,7 @@
#include "Parameters.h"
#include "ParsedMessage.h"
#include "rtp/RTPSender.h"
+#include "TimeSyncer.h"
#include <binder/IServiceManager.h>
#include <gui/IGraphicBufferProducer.h>
@@ -164,6 +165,14 @@ void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) {
} else {
err = -EINVAL;
}
+ }
+
+ if (err == OK) {
+ sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+
+ mTimeSyncer->startServer(8123);
mState = AWAITING_CLIENT_CONNECTION;
}
@@ -539,6 +548,11 @@ void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) {
break;
}
+ case kWhatTimeSyncerNotify:
+ {
+ break;
+ }
+
default:
TRESPASS();
}
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.h b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
index 44d3e4d..3efa0b4 100644
--- a/media/libstagefright/wifi-display/source/WifiDisplaySource.h
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
@@ -30,6 +30,7 @@ namespace android {
struct IHDCP;
struct IRemoteDisplayClient;
struct ParsedMessage;
+struct TimeSyncer;
// Represents the RTSP server acting as a wifi display source.
// Manages incoming connections, sets up Playback sessions as necessary.
@@ -82,6 +83,7 @@ private:
kWhatHDCPNotify,
kWhatFinishStop2,
kWhatTeardownTriggerTimedOut,
+ kWhatTimeSyncerNotify,
};
struct ResponseID {
@@ -118,6 +120,7 @@ private:
sp<ANetworkSession> mNetSession;
sp<IRemoteDisplayClient> mClient;
AString mMediaPath;
+ sp<TimeSyncer> mTimeSyncer;
struct in_addr mInterfaceAddr;
int32_t mSessionID;
diff --git a/media/libstagefright/wifi-display/udptest.cpp b/media/libstagefright/wifi-display/udptest.cpp
new file mode 100644
index 0000000..111846d
--- /dev/null
+++ b/media/libstagefright/wifi-display/udptest.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright 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_NEBUG 0
+#define LOG_TAG "udptest"
+#include <utils/Log.h>
+
+#include "ANetworkSession.h"
+#include "TimeSyncer.h"
+
+#include <binder/ProcessState.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+namespace android {
+
+} // namespace android
+
+static void usage(const char *me) {
+ fprintf(stderr,
+ "usage: %s -c host[:port]\tconnect to test server\n"
+ " -l \tcreate a test server\n",
+ me);
+}
+
+int main(int argc, char **argv) {
+ using namespace android;
+
+ ProcessState::self()->startThreadPool();
+
+ int32_t localPort = -1;
+ int32_t connectToPort = -1;
+ AString connectToHost;
+
+ int res;
+ while ((res = getopt(argc, argv, "hc:l:")) >= 0) {
+ switch (res) {
+ case 'c':
+ {
+ const char *colonPos = strrchr(optarg, ':');
+
+ if (colonPos == NULL) {
+ connectToHost = optarg;
+ connectToPort = 49152;
+ } else {
+ connectToHost.setTo(optarg, colonPos - optarg);
+
+ char *end;
+ connectToPort = strtol(colonPos + 1, &end, 10);
+
+ if (*end != '\0' || end == colonPos + 1
+ || connectToPort < 1 || connectToPort > 65535) {
+ fprintf(stderr, "Illegal port specified.\n");
+ exit(1);
+ }
+ }
+ break;
+ }
+
+ case 'l':
+ {
+ char *end;
+ localPort = strtol(optarg, &end, 10);
+
+ if (*end != '\0' || end == optarg
+ || localPort < 1 || localPort > 65535) {
+ fprintf(stderr, "Illegal port specified.\n");
+ exit(1);
+ }
+ break;
+ }
+
+ case '?':
+ case 'h':
+ usage(argv[0]);
+ exit(1);
+ }
+ }
+
+ if (localPort < 0 && connectToPort < 0) {
+ fprintf(stderr,
+ "You need to select either client or server mode.\n");
+ exit(1);
+ }
+
+ sp<ANetworkSession> netSession = new ANetworkSession;
+ netSession->start();
+
+ sp<ALooper> looper = new ALooper;
+
+ sp<TimeSyncer> handler = new TimeSyncer(netSession, NULL /* notify */);
+ looper->registerHandler(handler);
+
+ if (localPort >= 0) {
+ handler->startServer(localPort);
+ } else {
+ handler->startClient(connectToHost.c_str(), connectToPort);
+ }
+
+ looper->start(true /* runOnCallingThread */);
+
+ return 0;
+}
+
diff --git a/media/libstagefright/wifi-display/wfd.cpp b/media/libstagefright/wifi-display/wfd.cpp
index c947765..9fee4d0 100644
--- a/media/libstagefright/wifi-display/wfd.cpp
+++ b/media/libstagefright/wifi-display/wfd.cpp
@@ -18,6 +18,7 @@
#define LOG_TAG "wfd"
#include <utils/Log.h>
+#include "sink/WifiDisplaySink.h"
#include "source/WifiDisplaySource.h"
#include <binder/ProcessState.h>
@@ -38,8 +39,12 @@ namespace android {
static void usage(const char *me) {
fprintf(stderr,
"usage:\n"
- " %s -l iface[:port]\tcreate a wifi display source\n"
- " -f(ilename) \tstream media\n",
+ " %s -c host[:port]\tconnect to wifi source\n"
+ " -u uri \tconnect to an rtsp uri\n"
+ " -l ip[:port] \tlisten on the specified port "
+ " -f(ilename) \tstream media "
+ "(create a sink)\n"
+ " -s(pecial) \trun in 'special' mode\n",
me);
}
@@ -209,14 +214,48 @@ int main(int argc, char **argv) {
DataSource::RegisterDefaultSniffers();
+ AString connectToHost;
+ int32_t connectToPort = -1;
+ AString uri;
+
AString listenOnAddr;
int32_t listenOnPort = -1;
AString path;
+ bool specialMode = false;
+
int res;
- while ((res = getopt(argc, argv, "hl:f:")) >= 0) {
+ while ((res = getopt(argc, argv, "hc:l:u:f:s")) >= 0) {
switch (res) {
+ case 'c':
+ {
+ const char *colonPos = strrchr(optarg, ':');
+
+ if (colonPos == NULL) {
+ connectToHost = optarg;
+ connectToPort = WifiDisplaySource::kWifiDisplayDefaultPort;
+ } else {
+ connectToHost.setTo(optarg, colonPos - optarg);
+
+ char *end;
+ connectToPort = strtol(colonPos + 1, &end, 10);
+
+ if (*end != '\0' || end == colonPos + 1
+ || connectToPort < 1 || connectToPort > 65535) {
+ fprintf(stderr, "Illegal port specified.\n");
+ exit(1);
+ }
+ }
+ break;
+ }
+
+ case 'u':
+ {
+ uri = optarg;
+ break;
+ }
+
case 'f':
{
path = optarg;
@@ -245,6 +284,12 @@ int main(int argc, char **argv) {
break;
}
+ case 's':
+ {
+ specialMode = true;
+ break;
+ }
+
case '?':
case 'h':
default:
@@ -253,6 +298,13 @@ int main(int argc, char **argv) {
}
}
+ if (connectToPort >= 0 && listenOnPort >= 0) {
+ fprintf(stderr,
+ "You can connect to a source or create one, "
+ "but not both at the same time.\n");
+ exit(1);
+ }
+
if (listenOnPort >= 0) {
if (path.empty()) {
createSource(listenOnAddr, listenOnPort);
@@ -263,7 +315,72 @@ int main(int argc, char **argv) {
exit(0);
}
- usage(argv[0]);
+ if (connectToPort < 0 && uri.empty()) {
+ fprintf(stderr,
+ "You need to select either source host or uri.\n");
+
+ exit(1);
+ }
+
+ if (connectToPort >= 0 && !uri.empty()) {
+ fprintf(stderr,
+ "You need to either connect to a wfd host or an rtsp url, "
+ "not both.\n");
+ exit(1);
+ }
+
+ sp<SurfaceComposerClient> composerClient = new SurfaceComposerClient;
+ CHECK_EQ(composerClient->initCheck(), (status_t)OK);
+
+ sp<IBinder> display(SurfaceComposerClient::getBuiltInDisplay(
+ ISurfaceComposer::eDisplayIdMain));
+ DisplayInfo info;
+ SurfaceComposerClient::getDisplayInfo(display, &info);
+ ssize_t displayWidth = info.w;
+ ssize_t displayHeight = info.h;
+
+ ALOGV("display is %d x %d\n", displayWidth, displayHeight);
+
+ sp<SurfaceControl> control =
+ composerClient->createSurface(
+ String8("A Surface"),
+ displayWidth,
+ displayHeight,
+ PIXEL_FORMAT_RGB_565,
+ 0);
+
+ CHECK(control != NULL);
+ CHECK(control->isValid());
+
+ SurfaceComposerClient::openGlobalTransaction();
+ CHECK_EQ(control->setLayer(INT_MAX), (status_t)OK);
+ CHECK_EQ(control->show(), (status_t)OK);
+ SurfaceComposerClient::closeGlobalTransaction();
+
+ sp<Surface> surface = control->getSurface();
+ CHECK(surface != NULL);
+
+ sp<ANetworkSession> session = new ANetworkSession;
+ session->start();
+
+ sp<ALooper> looper = new ALooper;
+
+ sp<WifiDisplaySink> sink = new WifiDisplaySink(
+ specialMode ? WifiDisplaySink::FLAG_SPECIAL_MODE : 0 /* flags */,
+ session,
+ surface->getIGraphicBufferProducer());
+
+ looper->registerHandler(sink);
+
+ if (connectToPort >= 0) {
+ sink->start(connectToHost.c_str(), connectToPort);
+ } else {
+ sink->start(uri.c_str());
+ }
+
+ looper->start(true /* runOnCallingThread */);
+
+ composerClient->dispose();
return 0;
}