summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorAndreas Huber <andih@google.com>2012-08-29 11:41:50 -0700
committerAndreas Huber <andih@google.com>2012-08-29 15:06:57 -0700
commitd7bee3a9d2ad76d073d91f0ee36d5ac5f9df480c (patch)
tree3c4c7a83313d169b13c79c9660afeb804d27b975 /media
parenteb941f9a0c8474324732a99387cc6d8cb4ab01ef (diff)
downloadframeworks_av-d7bee3a9d2ad76d073d91f0ee36d5ac5f9df480c.zip
frameworks_av-d7bee3a9d2ad76d073d91f0ee36d5ac5f9df480c.tar.gz
frameworks_av-d7bee3a9d2ad76d073d91f0ee36d5ac5f9df480c.tar.bz2
Initial checkin of support for acting as a wifi display source
Change-Id: I08f17efa0c7d007e17408feb7d4fbef0a19f531a
Diffstat (limited to 'media')
-rw-r--r--media/libmedia/IMediaPlayerService.cpp15
-rw-r--r--media/libmediaplayerservice/Android.mk64
-rw-r--r--media/libmediaplayerservice/MediaPlayerService.cpp23
-rw-r--r--media/libmediaplayerservice/MediaPlayerService.h3
-rw-r--r--media/libmediaplayerservice/RemoteDisplay.cpp56
-rw-r--r--media/libmediaplayerservice/RemoteDisplay.h54
-rw-r--r--media/libstagefright/ACodec.cpp67
-rw-r--r--media/libstagefright/wifi-display/ANetworkSession.h2
-rw-r--r--media/libstagefright/wifi-display/Android.mk60
-rw-r--r--media/libstagefright/wifi-display/ParsedMessage.h2
-rw-r--r--media/libstagefright/wifi-display/source/Converter.cpp281
-rw-r--r--media/libstagefright/wifi-display/source/Converter.h93
-rw-r--r--media/libstagefright/wifi-display/source/PlaybackSession.cpp952
-rw-r--r--media/libstagefright/wifi-display/source/PlaybackSession.h135
-rw-r--r--media/libstagefright/wifi-display/source/RepeaterSource.cpp140
-rw-r--r--media/libstagefright/wifi-display/source/RepeaterSource.h55
-rw-r--r--media/libstagefright/wifi-display/source/Serializer.cpp366
-rw-r--r--media/libstagefright/wifi-display/source/Serializer.h84
-rw-r--r--media/libstagefright/wifi-display/source/TSPacketizer.cpp694
-rw-r--r--media/libstagefright/wifi-display/source/TSPacketizer.h76
-rw-r--r--media/libstagefright/wifi-display/source/WifiDisplaySource.cpp944
-rw-r--r--media/libstagefright/wifi-display/source/WifiDisplaySource.h175
-rw-r--r--media/libstagefright/wifi-display/wfd.cpp154
23 files changed, 4457 insertions, 38 deletions
diff --git a/media/libmedia/IMediaPlayerService.cpp b/media/libmedia/IMediaPlayerService.cpp
index 9120617..41969b1 100644
--- a/media/libmedia/IMediaPlayerService.cpp
+++ b/media/libmedia/IMediaPlayerService.cpp
@@ -38,6 +38,7 @@ enum {
CREATE_METADATA_RETRIEVER,
GET_OMX,
MAKE_CRYPTO,
+ ENABLE_REMOTE_DISPLAY,
ADD_BATTERY_DATA,
PULL_BATTERY_DATA
};
@@ -120,6 +121,14 @@ public:
return interface_cast<ICrypto>(reply.readStrongBinder());
}
+ virtual status_t enableRemoteDisplay(bool enable) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
+ data.writeInt32(enable);
+ remote()->transact(ENABLE_REMOTE_DISPLAY, data, &reply);
+ return reply.readInt32();
+ }
+
virtual void addBatteryData(uint32_t params) {
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
@@ -206,6 +215,12 @@ status_t BnMediaPlayerService::onTransact(
reply->writeStrongBinder(crypto->asBinder());
return NO_ERROR;
} break;
+ case ENABLE_REMOTE_DISPLAY: {
+ CHECK_INTERFACE(IMediaPlayerService, data, reply);
+ bool enable = data.readInt32();
+ reply->writeInt32(enableRemoteDisplay(enable));
+ return NO_ERROR;
+ } break;
case ADD_BATTERY_DATA: {
CHECK_INTERFACE(IMediaPlayerService, data, reply);
uint32_t params = data.readInt32();
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index 1373d3c..c7227b0 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -9,45 +9,47 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
ActivityManager.cpp \
Crypto.cpp \
- MediaRecorderClient.cpp \
MediaPlayerFactory.cpp \
MediaPlayerService.cpp \
+ MediaRecorderClient.cpp \
MetadataRetrieverClient.cpp \
- TestPlayerStub.cpp \
- MidiMetadataRetriever.cpp \
MidiFile.cpp \
+ MidiMetadataRetriever.cpp \
+ RemoteDisplay.cpp \
StagefrightPlayer.cpp \
- StagefrightRecorder.cpp
-
-LOCAL_SHARED_LIBRARIES := \
- libcutils \
- libutils \
- libbinder \
- libvorbisidec \
- libsonivox \
- libmedia \
- libmedia_native \
- libcamera_client \
- libstagefright \
- libstagefright_omx \
- libstagefright_foundation \
- libgui \
- libdl
-
-LOCAL_STATIC_LIBRARIES := \
- libstagefright_nuplayer \
- libstagefright_rtsp \
-
-LOCAL_C_INCLUDES := \
- $(call include-path-for, graphics corecg) \
- $(TOP)/frameworks/av/media/libstagefright/include \
- $(TOP)/frameworks/av/media/libstagefright/rtsp \
- $(TOP)/frameworks/native/include/media/openmax \
- $(TOP)/external/tremolo/Tremolo \
+ StagefrightRecorder.cpp \
+ TestPlayerStub.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ libcamera_client \
+ libcutils \
+ libdl \
+ libgui \
+ libmedia \
+ libmedia_native \
+ libsonivox \
+ libstagefright \
+ libstagefright_foundation \
+ libstagefright_omx \
+ libstagefright_wfd \
+ libutils \
+ libvorbisidec \
+
+LOCAL_STATIC_LIBRARIES := \
+ libstagefright_nuplayer \
+ libstagefright_rtsp \
+
+LOCAL_C_INCLUDES := \
+ $(call include-path-for, graphics corecg) \
+ $(TOP)/frameworks/av/media/libstagefright/include \
+ $(TOP)/frameworks/av/media/libstagefright/rtsp \
+ $(TOP)/frameworks/av/media/libstagefright/wifi-display \
+ $(TOP)/frameworks/native/include/media/openmax \
+ $(TOP)/external/tremolo/Tremolo \
LOCAL_MODULE:= libmediaplayerservice
include $(BUILD_SHARED_LIBRARY)
include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 6346363..5fe446f 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -70,6 +70,7 @@
#include <OMX.h>
#include "Crypto.h"
+#include "RemoteDisplay.h"
namespace {
using android::media::Metadata;
@@ -278,6 +279,28 @@ sp<ICrypto> MediaPlayerService::makeCrypto() {
return new Crypto;
}
+status_t MediaPlayerService::enableRemoteDisplay(bool enable) {
+ Mutex::Autolock autoLock(mLock);
+
+ if (enable && mRemoteDisplay == NULL) {
+ mRemoteDisplay = new RemoteDisplay;
+
+ status_t err = mRemoteDisplay->start();
+
+ if (err != OK) {
+ mRemoteDisplay.clear();
+ return err;
+ }
+
+ return OK;
+ } else if (!enable && mRemoteDisplay != NULL) {
+ mRemoteDisplay->stop();
+ mRemoteDisplay.clear();
+ }
+
+ return OK;
+}
+
status_t MediaPlayerService::AudioCache::dump(int fd, const Vector<String16>& args) const
{
const size_t SIZE = 256;
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index 6ede9a4..8fbc5d5 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -42,6 +42,7 @@ class IMediaRecorder;
class IMediaMetadataRetriever;
class IOMX;
class MediaRecorderClient;
+struct RemoteDisplay;
#define CALLBACK_ANTAGONIZER 0
#if CALLBACK_ANTAGONIZER
@@ -247,6 +248,7 @@ public:
virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat);
virtual sp<IOMX> getOMX();
virtual sp<ICrypto> makeCrypto();
+ virtual status_t enableRemoteDisplay(bool enable);
virtual status_t dump(int fd, const Vector<String16>& args);
@@ -423,6 +425,7 @@ private:
int32_t mNextConnId;
sp<IOMX> mOMX;
sp<ICrypto> mCrypto;
+ sp<RemoteDisplay> mRemoteDisplay;
};
// ----------------------------------------------------------------------------
diff --git a/media/libmediaplayerservice/RemoteDisplay.cpp b/media/libmediaplayerservice/RemoteDisplay.cpp
new file mode 100644
index 0000000..855824a
--- /dev/null
+++ b/media/libmediaplayerservice/RemoteDisplay.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#include "RemoteDisplay.h"
+
+#include "ANetworkSession.h"
+#include "source/WifiDisplaySource.h"
+
+namespace android {
+
+RemoteDisplay::RemoteDisplay()
+ : mInitCheck(NO_INIT),
+ mLooper(new ALooper),
+ mNetSession(new ANetworkSession),
+ mSource(new WifiDisplaySource(mNetSession)) {
+ mLooper->registerHandler(mSource);
+}
+
+RemoteDisplay::~RemoteDisplay() {
+}
+
+status_t RemoteDisplay::start() {
+ mNetSession->start();
+ mLooper->start();
+
+ // XXX replace with 8554 for bcom dongle (it doesn't respect the
+ // default port or the one advertised in the wfd IE).
+ mSource->start(WifiDisplaySource::kWifiDisplayDefaultPort);
+
+ return OK;
+}
+
+status_t RemoteDisplay::stop() {
+ mSource->stop();
+
+ mLooper->stop();
+ mNetSession->stop();
+
+ return OK;
+}
+
+} // namespace android
+
diff --git a/media/libmediaplayerservice/RemoteDisplay.h b/media/libmediaplayerservice/RemoteDisplay.h
new file mode 100644
index 0000000..6b37afb
--- /dev/null
+++ b/media/libmediaplayerservice/RemoteDisplay.h
@@ -0,0 +1,54 @@
+/*
+ * 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 REMOTE_DISPLAY_H_
+
+#define REMOTE_DISPLAY_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ALooper;
+struct ANetworkSession;
+struct WifiDisplaySource;
+
+struct RemoteDisplay : public RefBase {
+ RemoteDisplay();
+
+ status_t start();
+ status_t stop();
+
+protected:
+ virtual ~RemoteDisplay();
+
+private:
+ status_t mInitCheck;
+
+ sp<ALooper> mNetLooper;
+ sp<ALooper> mLooper;
+ sp<ANetworkSession> mNetSession;
+ sp<WifiDisplaySource> mSource;
+
+ DISALLOW_EVIL_CONSTRUCTORS(RemoteDisplay);
+};
+
+} // namespace android
+
+#endif // REMOTE_DISPLAY_H_
+
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index c37d2ca..3dd5d60 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -861,6 +861,20 @@ status_t ACodec::configureCodec(
return INVALID_OPERATION;
}
+ int32_t storeMeta;
+ if (encoder
+ && msg->findInt32("store-metadata-in-buffers", &storeMeta)
+ && storeMeta != 0) {
+ err = mOMX->storeMetaDataInBuffers(mNode, kPortIndexInput, OMX_TRUE);
+
+ if (err != OK) {
+ ALOGE("[%s] storeMetaDataInBuffers failed w/ err %d",
+ mComponentName.c_str(), err);
+
+ return err;
+ }
+ }
+
if (!strncasecmp(mime, "video/", 6)) {
if (encoder) {
err = setupVideoEncoder(mime, msg);
@@ -2424,6 +2438,21 @@ bool ACodec::BaseState::onOMXEmptyBufferDone(IOMX::buffer_id bufferID) {
CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_COMPONENT);
info->mStatus = BufferInfo::OWNED_BY_US;
+ const sp<AMessage> &bufferMeta = info->mData->meta();
+ void *mediaBuffer;
+ if (bufferMeta->findPointer("mediaBuffer", &mediaBuffer)
+ && mediaBuffer != NULL) {
+ // We're in "store-metadata-in-buffers" mode, the underlying
+ // OMX component had access to data that's implicitly refcounted
+ // by this "mediaBuffer" object. Now that the OMX component has
+ // told us that it's done with the input buffer, we can decrement
+ // the mediaBuffer's reference count.
+ ((MediaBuffer *)mediaBuffer)->release();
+ mediaBuffer = NULL;
+
+ bufferMeta->setPointer("mediaBuffer", NULL);
+ }
+
PortMode mode = getPortMode(kPortIndexInput);
switch (mode) {
@@ -2531,10 +2560,10 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
}
if (buffer != info->mData) {
- if (0 && !(flags & OMX_BUFFERFLAG_CODECCONFIG)) {
- ALOGV("[%s] Needs to copy input data.",
- mCodec->mComponentName.c_str());
- }
+ ALOGV("[%s] Needs to copy input data for buffer %p. (%p != %p)",
+ mCodec->mComponentName.c_str(),
+ bufferID,
+ buffer.get(), info->mData.get());
CHECK_LE(buffer->size(), info->mData->capacity());
memcpy(info->mData->data(), buffer->data(), buffer->size());
@@ -2547,10 +2576,22 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
ALOGV("[%s] calling emptyBuffer %p w/ EOS",
mCodec->mComponentName.c_str(), bufferID);
} else {
+#if TRACK_BUFFER_TIMING
+ ALOGI("[%s] calling emptyBuffer %p w/ time %lld us",
+ mCodec->mComponentName.c_str(), bufferID, timeUs);
+#else
ALOGV("[%s] calling emptyBuffer %p w/ time %lld us",
mCodec->mComponentName.c_str(), bufferID, timeUs);
+#endif
}
+#if TRACK_BUFFER_TIMING
+ ACodec::BufferStats stats;
+ stats.mEmptyBufferTimeUs = ALooper::GetNowUs();
+ stats.mFillBufferDoneTimeUs = -1ll;
+ mCodec->mBufferStats.add(timeUs, stats);
+#endif
+
CHECK_EQ(mCodec->mOMX->emptyBuffer(
mCodec->mNode,
bufferID,
@@ -2647,6 +2688,22 @@ bool ACodec::BaseState::onOMXFillBufferDone(
mCodec->mComponentName.c_str(), bufferID, timeUs, flags);
ssize_t index;
+
+#if TRACK_BUFFER_TIMING
+ index = mCodec->mBufferStats.indexOfKey(timeUs);
+ if (index >= 0) {
+ ACodec::BufferStats *stats = &mCodec->mBufferStats.editValueAt(index);
+ stats->mFillBufferDoneTimeUs = ALooper::GetNowUs();
+
+ ALOGI("frame PTS %lld: %lld",
+ timeUs,
+ stats->mFillBufferDoneTimeUs - stats->mEmptyBufferTimeUs);
+
+ mCodec->mBufferStats.removeItemsAt(index);
+ stats = NULL;
+ }
+#endif
+
BufferInfo *info =
mCodec->findBufferByID(kPortIndexOutput, bufferID, &index);
@@ -2891,7 +2948,7 @@ bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) {
AString mime;
AString componentName;
- uint32_t quirks;
+ uint32_t quirks = 0;
if (msg->findString("componentName", &componentName)) {
ssize_t index = matchingCodecs.add();
OMXCodec::CodecNameAndQuirks *entry = &matchingCodecs.editItemAt(index);
diff --git a/media/libstagefright/wifi-display/ANetworkSession.h b/media/libstagefright/wifi-display/ANetworkSession.h
index 0402317..d4cd14f 100644
--- a/media/libstagefright/wifi-display/ANetworkSession.h
+++ b/media/libstagefright/wifi-display/ANetworkSession.h
@@ -27,6 +27,8 @@ namespace android {
struct AMessage;
+// Helper class to manage a number of live sockets (datagram and stream-based)
+// on a single thread. Clients are notified about activity through AMessages.
struct ANetworkSession : public RefBase {
ANetworkSession();
diff --git a/media/libstagefright/wifi-display/Android.mk b/media/libstagefright/wifi-display/Android.mk
index 114ff62..b035a51 100644
--- a/media/libstagefright/wifi-display/Android.mk
+++ b/media/libstagefright/wifi-display/Android.mk
@@ -3,9 +3,64 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
+ ANetworkSession.cpp \
+ ParsedMessage.cpp \
+ source/Converter.cpp \
+ source/PlaybackSession.cpp \
+ source/RepeaterSource.cpp \
+ source/Serializer.cpp \
+ source/TSPacketizer.cpp \
+ source/WifiDisplaySource.cpp \
+
+LOCAL_C_INCLUDES:= \
+ $(TOP)/frameworks/av/media/libstagefright \
+ $(TOP)/frameworks/native/include/media/openmax \
+ $(TOP)/frameworks/av/media/libstagefright/mpeg2ts \
+
+LOCAL_SHARED_LIBRARIES:= \
+ libbinder \
+ libcutils \
+ libgui \
+ libmedia \
+ libstagefright \
+ libstagefright_foundation \
+ libui \
+ libutils \
+
+LOCAL_MODULE:= libstagefright_wfd
+
+LOCAL_MODULE_TAGS:= optional
+
+include $(BUILD_SHARED_LIBRARY)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ wfd.cpp \
+
+LOCAL_SHARED_LIBRARIES:= \
+ libbinder \
+ libgui \
+ libmedia \
+ libstagefright \
+ libstagefright_foundation \
+ libstagefright_wfd \
+ libutils \
+
+LOCAL_MODULE:= wfd
+
+LOCAL_MODULE_TAGS := debug
+
+include $(BUILD_EXECUTABLE)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
udptest.cpp \
- ANetworkSession.cpp \
- ParsedMessage.cpp \
LOCAL_SHARED_LIBRARIES:= \
libbinder \
@@ -13,6 +68,7 @@ LOCAL_SHARED_LIBRARIES:= \
libmedia \
libstagefright \
libstagefright_foundation \
+ libstagefright_wfd \
libutils \
LOCAL_MODULE:= udptest
diff --git a/media/libstagefright/wifi-display/ParsedMessage.h b/media/libstagefright/wifi-display/ParsedMessage.h
index 00f578f..e9a1859 100644
--- a/media/libstagefright/wifi-display/ParsedMessage.h
+++ b/media/libstagefright/wifi-display/ParsedMessage.h
@@ -21,6 +21,8 @@
namespace android {
+// Encapsulates an "HTTP/RTSP style" response, i.e. a status line,
+// key/value pairs making up the headers and an optional body/content.
struct ParsedMessage : public RefBase {
static sp<ParsedMessage> Parse(
const char *data, size_t size, bool noMoreData, size_t *length);
diff --git a/media/libstagefright/wifi-display/source/Converter.cpp b/media/libstagefright/wifi-display/source/Converter.cpp
new file mode 100644
index 0000000..655fbae
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/Converter.cpp
@@ -0,0 +1,281 @@
+/*
+ * 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 "Converter"
+#include <utils/Log.h>
+
+#include "Converter.h"
+
+#include <gui/SurfaceTextureClient.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/MediaCodec.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+Converter::Converter(
+ const sp<AMessage> &notify,
+ const sp<ALooper> &codecLooper,
+ const sp<AMessage> &format)
+ : mInitCheck(NO_INIT),
+ mNotify(notify),
+ mCodecLooper(codecLooper),
+ mInputFormat(format),
+ mDoMoreWorkPending(false) {
+ mInitCheck = initEncoder();
+}
+
+Converter::~Converter() {
+ if (mEncoder != NULL) {
+ mEncoder->release();
+ mEncoder.clear();
+ }
+}
+
+status_t Converter::initCheck() const {
+ return mInitCheck;
+}
+
+sp<AMessage> Converter::getOutputFormat() const {
+ return mOutputFormat;
+}
+
+status_t Converter::initEncoder() {
+ AString inputMIME;
+ CHECK(mInputFormat->findString("mime", &inputMIME));
+
+ AString outputMIME;
+ bool isAudio = false;
+ if (!strcasecmp(inputMIME.c_str(), MEDIA_MIMETYPE_AUDIO_RAW)) {
+ outputMIME = MEDIA_MIMETYPE_AUDIO_AAC;
+ isAudio = true;
+ } else if (!strcasecmp(inputMIME.c_str(), MEDIA_MIMETYPE_VIDEO_RAW)) {
+ outputMIME = MEDIA_MIMETYPE_VIDEO_AVC;
+ } else {
+ TRESPASS();
+ }
+
+ mEncoder = MediaCodec::CreateByType(
+ mCodecLooper, outputMIME.c_str(), true /* encoder */);
+
+ if (mEncoder == NULL) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ mOutputFormat = mInputFormat->dup();
+ mOutputFormat->setString("mime", outputMIME.c_str());
+
+ if (isAudio) {
+ mOutputFormat->setInt32("bitrate", 64000); // 64 kBit/sec
+ } else {
+ mOutputFormat->setInt32("bitrate", 3000000); // 3Mbit/sec
+ mOutputFormat->setInt32("frame-rate", 30);
+ mOutputFormat->setInt32("i-frame-interval", 3); // Iframes every 3 secs
+ }
+
+ ALOGV("output format is '%s'", mOutputFormat->debugString(0).c_str());
+
+ status_t err = mEncoder->configure(
+ mOutputFormat,
+ NULL /* nativeWindow */,
+ NULL /* crypto */,
+ MediaCodec::CONFIGURE_FLAG_ENCODE);
+
+ if (err != OK) {
+ return err;
+ }
+
+ err = mEncoder->start();
+
+ if (err != OK) {
+ return err;
+ }
+
+ err = mEncoder->getInputBuffers(&mEncoderInputBuffers);
+
+ if (err != OK) {
+ return err;
+ }
+
+ return mEncoder->getOutputBuffers(&mEncoderOutputBuffers);
+}
+
+void Converter::feedAccessUnit(const sp<ABuffer> &accessUnit) {
+ sp<AMessage> msg = new AMessage(kWhatFeedAccessUnit, id());
+ msg->setBuffer("accessUnit", accessUnit);
+ msg->post();
+}
+
+void Converter::signalEOS() {
+ (new AMessage(kWhatInputEOS, id()))->post();
+}
+
+void Converter::notifyError(status_t err) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatError);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+void Converter::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatFeedAccessUnit:
+ {
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ mInputBufferQueue.push_back(accessUnit);
+
+ feedEncoderInputBuffers();
+
+ scheduleDoMoreWork();
+ break;
+ }
+
+ case kWhatInputEOS:
+ {
+ mInputBufferQueue.push_back(NULL);
+
+ feedEncoderInputBuffers();
+
+ scheduleDoMoreWork();
+ break;
+ }
+
+ case kWhatDoMoreWork:
+ {
+ mDoMoreWorkPending = false;
+ status_t err = doMoreWork();
+
+ if (err != OK) {
+ notifyError(err);
+ } else {
+ scheduleDoMoreWork();
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void Converter::scheduleDoMoreWork() {
+ if (mDoMoreWorkPending) {
+ return;
+ }
+
+ mDoMoreWorkPending = true;
+ (new AMessage(kWhatDoMoreWork, id()))->post(1000ll);
+}
+
+status_t Converter::feedEncoderInputBuffers() {
+ while (!mInputBufferQueue.empty()
+ && !mAvailEncoderInputIndices.empty()) {
+ sp<ABuffer> buffer = *mInputBufferQueue.begin();
+ mInputBufferQueue.erase(mInputBufferQueue.begin());
+
+ size_t bufferIndex = *mAvailEncoderInputIndices.begin();
+ mAvailEncoderInputIndices.erase(mAvailEncoderInputIndices.begin());
+
+ int64_t timeUs = 0ll;
+ uint32_t flags = 0;
+
+ if (buffer != NULL) {
+ CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
+
+ memcpy(mEncoderInputBuffers.itemAt(bufferIndex)->data(),
+ buffer->data(),
+ buffer->size());
+
+ void *mediaBuffer;
+ if (buffer->meta()->findPointer("mediaBuffer", &mediaBuffer)
+ && mediaBuffer != NULL) {
+ mEncoderInputBuffers.itemAt(bufferIndex)->meta()
+ ->setPointer("mediaBuffer", mediaBuffer);
+
+ buffer->meta()->setPointer("mediaBuffer", NULL);
+ }
+ } else {
+ flags = MediaCodec::BUFFER_FLAG_EOS;
+ }
+
+ status_t err = mEncoder->queueInputBuffer(
+ bufferIndex, 0, (buffer == NULL) ? 0 : buffer->size(),
+ timeUs, flags);
+
+ if (err != OK) {
+ return err;
+ }
+ }
+
+ return OK;
+}
+
+status_t Converter::doMoreWork() {
+ size_t bufferIndex;
+ status_t err = mEncoder->dequeueInputBuffer(&bufferIndex);
+
+ if (err == OK) {
+ mAvailEncoderInputIndices.push_back(bufferIndex);
+ feedEncoderInputBuffers();
+ }
+
+ size_t offset;
+ size_t size;
+ int64_t timeUs;
+ uint32_t flags;
+ err = mEncoder->dequeueOutputBuffer(
+ &bufferIndex, &offset, &size, &timeUs, &flags);
+
+ if (err == OK) {
+ if (flags & MediaCodec::BUFFER_FLAG_EOS) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatEOS);
+ notify->post();
+ } else {
+ sp<ABuffer> buffer = new ABuffer(size);
+ buffer->meta()->setInt64("timeUs", timeUs);
+
+ memcpy(buffer->data(),
+ mEncoderOutputBuffers.itemAt(bufferIndex)->base() + offset,
+ size);
+
+ if (flags & MediaCodec::BUFFER_FLAG_CODECCONFIG) {
+ mOutputFormat->setBuffer("csd-0", buffer);
+ } else {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatAccessUnit);
+ notify->setBuffer("accessUnit", buffer);
+ notify->post();
+ }
+ }
+
+ err = mEncoder->releaseOutputBuffer(bufferIndex);
+ } else if (err == -EAGAIN) {
+ err = OK;
+ }
+
+ return err;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/source/Converter.h b/media/libstagefright/wifi-display/source/Converter.h
new file mode 100644
index 0000000..6700a32
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/Converter.h
@@ -0,0 +1,93 @@
+/*
+ * 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 CONVERTER_H_
+
+#define CONVERTER_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct ABuffer;
+struct MediaCodec;
+
+// Utility class that receives media access units and converts them into
+// media access unit of a different format.
+// Right now this'll convert raw video into H.264 and raw audio into AAC.
+struct Converter : public AHandler {
+ Converter(
+ const sp<AMessage> &notify,
+ const sp<ALooper> &codecLooper,
+ const sp<AMessage> &format);
+
+ status_t initCheck() const;
+
+ sp<AMessage> getOutputFormat() const;
+
+ void feedAccessUnit(const sp<ABuffer> &accessUnit);
+ void signalEOS();
+
+ enum {
+ kWhatAccessUnit,
+ kWhatEOS,
+ kWhatError,
+ };
+
+protected:
+ virtual ~Converter();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatFeedAccessUnit,
+ kWhatInputEOS,
+ kWhatDoMoreWork
+ };
+
+ status_t mInitCheck;
+ sp<AMessage> mNotify;
+ sp<ALooper> mCodecLooper;
+ sp<AMessage> mInputFormat;
+ sp<AMessage> mOutputFormat;
+
+ sp<MediaCodec> mEncoder;
+
+ Vector<sp<ABuffer> > mEncoderInputBuffers;
+ Vector<sp<ABuffer> > mEncoderOutputBuffers;
+
+ List<size_t> mAvailEncoderInputIndices;
+
+ List<sp<ABuffer> > mInputBufferQueue;
+
+ bool mDoMoreWorkPending;
+
+ status_t initEncoder();
+
+ status_t feedEncoderInputBuffers();
+
+ void scheduleDoMoreWork();
+ status_t doMoreWork();
+
+ void notifyError(status_t err);
+
+ DISALLOW_EVIL_CONSTRUCTORS(Converter);
+};
+
+} // namespace android
+
+#endif // CONVERTER_H_
+
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.cpp b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
new file mode 100644
index 0000000..5095c15
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
@@ -0,0 +1,952 @@
+/*
+ * 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 "PlaybackSession"
+#include <utils/Log.h>
+
+#include "PlaybackSession.h"
+
+#include "Converter.h"
+#include "RepeaterSource.h"
+#include "Serializer.h"
+#include "TSPacketizer.h"
+
+#include <binder/IServiceManager.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.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/DataSource.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/MPEG2TSWriter.h>
+#include <media/stagefright/SurfaceMediaSource.h>
+#include <media/stagefright/Utils.h>
+#include <ui/DisplayInfo.h>
+
+#include <OMX_IVCommon.h>
+
+#define FAKE_VIDEO 0
+
+namespace android {
+
+static size_t kMaxRTPPacketSize = 1500;
+static size_t kMaxNumTSPacketsPerRTPPacket = (kMaxRTPPacketSize - 12) / 188;
+
+struct WifiDisplaySource::PlaybackSession::Track : public RefBase {
+ Track(const sp<Converter> &converter);
+ Track(const sp<AMessage> &format);
+
+ sp<AMessage> getFormat();
+
+ const sp<Converter> &converter() const;
+ ssize_t packetizerTrackIndex() const;
+
+ void setPacketizerTrackIndex(size_t index);
+
+protected:
+ virtual ~Track();
+
+private:
+ sp<Converter> mConverter;
+ sp<AMessage> mFormat;
+ ssize_t mPacketizerTrackIndex;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Track);
+};
+
+WifiDisplaySource::PlaybackSession::Track::Track(const sp<Converter> &converter)
+ : mConverter(converter),
+ mPacketizerTrackIndex(-1) {
+}
+
+WifiDisplaySource::PlaybackSession::Track::Track(const sp<AMessage> &format)
+ : mFormat(format),
+ mPacketizerTrackIndex(-1) {
+}
+
+WifiDisplaySource::PlaybackSession::Track::~Track() {
+}
+
+sp<AMessage> WifiDisplaySource::PlaybackSession::Track::getFormat() {
+ if (mFormat != NULL) {
+ return mFormat;
+ }
+
+ return mConverter->getOutputFormat();
+}
+
+const sp<Converter> &WifiDisplaySource::PlaybackSession::Track::converter() const {
+ return mConverter;
+}
+
+ssize_t WifiDisplaySource::PlaybackSession::Track::packetizerTrackIndex() const {
+ return mPacketizerTrackIndex;
+}
+
+void WifiDisplaySource::PlaybackSession::Track::setPacketizerTrackIndex(size_t index) {
+ CHECK_LT(mPacketizerTrackIndex, 0);
+ mPacketizerTrackIndex = index;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+WifiDisplaySource::PlaybackSession::PlaybackSession(
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> &notify)
+ : mNetSession(netSession),
+ mNotify(notify),
+ mLastLifesignUs(),
+ mTSQueue(new ABuffer(12 + kMaxNumTSPacketsPerRTPPacket * 188)),
+ mPrevTimeUs(-1ll),
+ mUseInterleavedTCP(false),
+ mRTPChannel(0),
+ mRTCPChannel(0),
+ mRTPPort(0),
+ mRTPSessionID(0),
+ mRTCPSessionID(0),
+ mRTPSeqNo(0),
+ mLastNTPTime(0),
+ mLastRTPTime(0),
+ mNumRTPSent(0),
+ mNumRTPOctetsSent(0),
+ mNumSRsSent(0),
+ mSendSRPending(false),
+ mFirstPacketTimeUs(-1ll),
+ mHistoryLength(0) {
+ mTSQueue->setRange(0, 12);
+}
+
+status_t WifiDisplaySource::PlaybackSession::init(
+ const char *clientIP, int32_t clientRtp, int32_t clientRtcp,
+ bool useInterleavedTCP) {
+ status_t err = setupPacketizer();
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (useInterleavedTCP) {
+ mUseInterleavedTCP = true;
+ mRTPChannel = clientRtp;
+ mRTCPChannel = clientRtcp;
+ mRTPPort = 0;
+ mRTPSessionID = 0;
+ mRTCPSessionID = 0;
+
+ updateLiveness();
+ return OK;
+ }
+
+ mUseInterleavedTCP = false;
+ mRTPChannel = 0;
+ mRTCPChannel = 0;
+
+ int serverRtp;
+
+ sp<AMessage> rtpNotify = new AMessage(kWhatRTPNotify, id());
+ sp<AMessage> rtcpNotify = new AMessage(kWhatRTCPNotify, id());
+ for (serverRtp = 15550;; serverRtp += 2) {
+ int32_t rtpSession;
+ err = mNetSession->createUDPSession(
+ serverRtp, clientIP, clientRtp,
+ rtpNotify, &rtpSession);
+
+ if (err != OK) {
+ ALOGI("failed to create RTP socket on port %d", serverRtp);
+ continue;
+ }
+
+ if (clientRtcp < 0) {
+ // No RTCP.
+
+ mRTPPort = serverRtp;
+ mRTPSessionID = rtpSession;
+ mRTCPSessionID = 0;
+
+ ALOGI("rtpSessionId = %d", rtpSession);
+ break;
+ }
+
+ int32_t rtcpSession;
+ err = mNetSession->createUDPSession(
+ serverRtp + 1, clientIP, clientRtcp,
+ rtcpNotify, &rtcpSession);
+
+ if (err == OK) {
+ mRTPPort = serverRtp;
+ mRTPSessionID = rtpSession;
+ mRTCPSessionID = rtcpSession;
+
+ ALOGI("rtpSessionID = %d, rtcpSessionID = %d", rtpSession, rtcpSession);
+ break;
+ }
+
+ ALOGI("failed to create RTCP socket on port %d", serverRtp + 1);
+ mNetSession->destroySession(rtpSession);
+ }
+
+ if (mRTPPort == 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ updateLiveness();
+
+ return OK;
+}
+
+WifiDisplaySource::PlaybackSession::~PlaybackSession() {
+ mTracks.clear();
+
+ if (mCodecLooper != NULL) {
+ mCodecLooper->stop();
+ mCodecLooper.clear();
+ }
+
+ mPacketizer.clear();
+
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> binder = sm->getService(String16("SurfaceFlinger"));
+ sp<ISurfaceComposer> service = interface_cast<ISurfaceComposer>(binder);
+ CHECK(service != NULL);
+
+ service->connectDisplay(NULL);
+
+ if (mSerializer != NULL) {
+ mSerializer->stop();
+
+ looper()->unregisterHandler(mSerializer->id());
+ mSerializer.clear();
+ }
+
+ if (mSerializerLooper != NULL) {
+ mSerializerLooper->stop();
+ mSerializerLooper.clear();
+ }
+
+ if (mRTCPSessionID != 0) {
+ mNetSession->destroySession(mRTCPSessionID);
+ }
+
+ if (mRTPSessionID != 0) {
+ mNetSession->destroySession(mRTPSessionID);
+ }
+}
+
+int32_t WifiDisplaySource::PlaybackSession::getRTPPort() const {
+ return mRTPPort;
+}
+
+int64_t WifiDisplaySource::PlaybackSession::getLastLifesignUs() const {
+ return mLastLifesignUs;
+}
+
+void WifiDisplaySource::PlaybackSession::updateLiveness() {
+ mLastLifesignUs = ALooper::GetNowUs();
+}
+
+status_t WifiDisplaySource::PlaybackSession::play() {
+ updateLiveness();
+
+ if (mRTCPSessionID != 0) {
+ scheduleSendSR();
+ }
+
+ return mSerializer->start();
+}
+
+status_t WifiDisplaySource::PlaybackSession::pause() {
+ updateLiveness();
+
+ return OK;
+}
+
+void WifiDisplaySource::PlaybackSession::onMessageReceived(
+ const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatRTPNotify:
+ case kWhatRTCPNotify:
+ {
+ 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));
+
+ if (msg->what() == kWhatRTPNotify
+ && !errorOccuredDuringSend) {
+ // This is ok, we don't expect to receive anything on
+ // the RTP socket.
+ break;
+ }
+
+ 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;
+ }
+
+ // Inform WifiDisplaySource of our premature death (wish).
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatSessionDead);
+ notify->post();
+ break;
+ }
+
+ case ANetworkSession::kWhatDatagram:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ sp<ABuffer> data;
+ CHECK(msg->findBuffer("data", &data));
+
+ status_t err;
+ if (msg->what() == kWhatRTCPNotify) {
+ err = parseRTCP(data);
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+ break;
+ }
+
+ case kWhatSendSR:
+ {
+ mSendSRPending = false;
+
+ if (mRTCPSessionID == 0) {
+ break;
+ }
+
+ onSendSR();
+
+ scheduleSendSR();
+ break;
+ }
+
+ case kWhatSerializerNotify:
+ {
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ if (what == Serializer::kWhatEOS) {
+ ALOGI("input eos");
+
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+#if FAKE_VIDEO
+ sp<AMessage> msg = new AMessage(kWhatConverterNotify, id());
+ msg->setInt32("what", Converter::kWhatEOS);
+ msg->setSize("trackIndex", i);
+ msg->post();
+#else
+ mTracks.valueAt(i)->converter()->signalEOS();
+#endif
+ }
+ } else {
+ CHECK_EQ(what, Serializer::kWhatAccessUnit);
+
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+#if FAKE_VIDEO
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ void *mbuf;
+ CHECK(accessUnit->meta()->findPointer("mediaBuffer", &mbuf));
+
+ ((MediaBuffer *)mbuf)->release();
+ mbuf = NULL;
+
+ sp<AMessage> msg = new AMessage(kWhatConverterNotify, id());
+ msg->setInt32("what", Converter::kWhatAccessUnit);
+ msg->setSize("trackIndex", trackIndex);
+ msg->setBuffer("accessUnit", accessUnit);
+ msg->post();
+#else
+ mTracks.valueFor(trackIndex)->converter()
+ ->feedAccessUnit(accessUnit);
+#endif
+ }
+ break;
+ }
+
+ case kWhatConverterNotify:
+ {
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ if (what == Converter::kWhatAccessUnit) {
+ const sp<Track> &track = mTracks.valueFor(trackIndex);
+
+ uint32_t flags = 0;
+
+ ssize_t packetizerTrackIndex = track->packetizerTrackIndex();
+ if (packetizerTrackIndex < 0) {
+ flags = TSPacketizer::EMIT_PAT_AND_PMT;
+
+ packetizerTrackIndex =
+ mPacketizer->addTrack(track->getFormat());
+
+ if (packetizerTrackIndex >= 0) {
+ track->setPacketizerTrackIndex(packetizerTrackIndex);
+ }
+ }
+
+ if (packetizerTrackIndex >= 0) {
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ if (mPrevTimeUs < 0ll || mPrevTimeUs + 100000ll >= timeUs) {
+ flags |= TSPacketizer::EMIT_PCR;
+ mPrevTimeUs = timeUs;
+ }
+
+ sp<ABuffer> packets;
+ mPacketizer->packetize(
+ packetizerTrackIndex, accessUnit, &packets, flags);
+
+ for (size_t offset = 0;
+ offset < packets->size(); offset += 188) {
+ bool lastTSPacket = (offset + 188 >= packets->size());
+
+ appendTSData(
+ packets->data() + offset,
+ 188,
+ true /* timeDiscontinuity */,
+ lastTSPacket /* flush */);
+ }
+ }
+ } else if (what == Converter::kWhatEOS) {
+ CHECK_EQ(what, Converter::kWhatEOS);
+
+ ALOGI("output EOS on track %d", trackIndex);
+
+ ssize_t index = mTracks.indexOfKey(trackIndex);
+ CHECK_GE(index, 0);
+
+#if !FAKE_VIDEO
+ const sp<Converter> &converter =
+ mTracks.valueAt(index)->converter();
+ looper()->unregisterHandler(converter->id());
+#endif
+
+ mTracks.removeItemsAt(index);
+
+ if (mTracks.isEmpty()) {
+ ALOGI("Reached EOS");
+ }
+ } else {
+ CHECK_EQ(what, Converter::kWhatError);
+
+ status_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ ALOGE("converter signaled error %d", err);
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+status_t WifiDisplaySource::PlaybackSession::setupPacketizer() {
+ sp<AMessage> msg = new AMessage(kWhatSerializerNotify, id());
+
+ mSerializerLooper = new ALooper;
+ mSerializerLooper->start();
+
+ mSerializer = new Serializer(
+#if FAKE_VIDEO
+ true /* throttled */
+#else
+ false /* throttled */
+#endif
+ , msg);
+ mSerializerLooper->registerHandler(mSerializer);
+
+ mPacketizer = new TSPacketizer;
+
+#if FAKE_VIDEO
+ DataSource::RegisterDefaultSniffers();
+
+ sp<DataSource> dataSource =
+ DataSource::CreateFromURI(
+ "/system/etc/inception_1500.mp4");
+
+ CHECK(dataSource != NULL);
+
+ sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
+ CHECK(extractor != NULL);
+
+ bool haveAudio = false;
+ bool haveVideo = false;
+ for (size_t i = 0; i < extractor->countTracks(); ++i) {
+ sp<MetaData> meta = extractor->getTrackMetaData(i);
+
+ const char *mime;
+ CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+ bool useTrack = false;
+ if (!strncasecmp(mime, "audio/", 6) && !haveAudio) {
+ useTrack = true;
+ haveAudio = true;
+ } else if (!strncasecmp(mime, "video/", 6) && !haveVideo) {
+ useTrack = true;
+ haveVideo = true;
+ }
+
+ if (!useTrack) {
+ continue;
+ }
+
+ sp<MediaSource> source = extractor->getTrack(i);
+
+ ssize_t index = mSerializer->addSource(source);
+ CHECK_GE(index, 0);
+
+ sp<AMessage> format;
+ status_t err = convertMetaDataToMessage(source->getFormat(), &format);
+ CHECK_EQ(err, (status_t)OK);
+
+ mTracks.add(index, new Track(format));
+ }
+ CHECK(haveAudio || haveVideo);
+#else
+ mCodecLooper = new ALooper;
+ mCodecLooper->start();
+
+ DisplayInfo info;
+ SurfaceComposerClient::getDisplayInfo(0, &info);
+
+ // sp<SurfaceMediaSource> source = new SurfaceMediaSource(info.w, info.h);
+ sp<SurfaceMediaSource> source = new SurfaceMediaSource(720, 1280);
+
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> binder = sm->getService(String16("SurfaceFlinger"));
+ sp<ISurfaceComposer> service = interface_cast<ISurfaceComposer>(binder);
+ CHECK(service != NULL);
+
+ service->connectDisplay(source->getBufferQueue());
+
+#if 0
+ {
+ ALOGI("reading buffer");
+
+ CHECK_EQ((status_t)OK, source->start());
+ MediaBuffer *mbuf;
+ CHECK_EQ((status_t)OK, source->read(&mbuf));
+ mbuf->release();
+ mbuf = NULL;
+
+ ALOGI("got buffer");
+ }
+#endif
+
+#if 0
+ ssize_t index = mSerializer->addSource(source);
+#else
+ ssize_t index = mSerializer->addSource(
+ new RepeaterSource(source, 30.0 /* rateHz */));
+#endif
+
+ CHECK_GE(index, 0);
+
+ sp<AMessage> format;
+ status_t err = convertMetaDataToMessage(source->getFormat(), &format);
+ CHECK_EQ(err, (status_t)OK);
+
+ format->setInt32("store-metadata-in-buffers", true);
+
+ format->setInt32(
+ "color-format", OMX_COLOR_FormatAndroidOpaque);
+
+ sp<AMessage> notify = new AMessage(kWhatConverterNotify, id());
+ notify->setSize("trackIndex", index);
+
+ sp<Converter> converter =
+ new Converter(notify, mCodecLooper, format);
+
+ looper()->registerHandler(converter);
+
+ mTracks.add(index, new Track(converter));
+#endif
+
+ return OK;
+}
+
+void WifiDisplaySource::PlaybackSession::scheduleSendSR() {
+ if (mSendSRPending) {
+ return;
+ }
+
+ mSendSRPending = true;
+ (new AMessage(kWhatSendSR, id()))->post(kSendSRIntervalUs);
+}
+
+void WifiDisplaySource::PlaybackSession::addSR(const sp<ABuffer> &buffer) {
+ uint8_t *data = buffer->data() + buffer->size();
+
+ // TODO: Use macros/utility functions to clean up all the bitshifts below.
+
+ data[0] = 0x80 | 0;
+ data[1] = 200; // SR
+ data[2] = 0;
+ data[3] = 6;
+ data[4] = kSourceID >> 24;
+ data[5] = (kSourceID >> 16) & 0xff;
+ data[6] = (kSourceID >> 8) & 0xff;
+ data[7] = kSourceID & 0xff;
+
+ data[8] = mLastNTPTime >> (64 - 8);
+ data[9] = (mLastNTPTime >> (64 - 16)) & 0xff;
+ data[10] = (mLastNTPTime >> (64 - 24)) & 0xff;
+ data[11] = (mLastNTPTime >> 32) & 0xff;
+ data[12] = (mLastNTPTime >> 24) & 0xff;
+ data[13] = (mLastNTPTime >> 16) & 0xff;
+ data[14] = (mLastNTPTime >> 8) & 0xff;
+ data[15] = mLastNTPTime & 0xff;
+
+ data[16] = (mLastRTPTime >> 24) & 0xff;
+ data[17] = (mLastRTPTime >> 16) & 0xff;
+ data[18] = (mLastRTPTime >> 8) & 0xff;
+ data[19] = mLastRTPTime & 0xff;
+
+ data[20] = mNumRTPSent >> 24;
+ data[21] = (mNumRTPSent >> 16) & 0xff;
+ data[22] = (mNumRTPSent >> 8) & 0xff;
+ data[23] = mNumRTPSent & 0xff;
+
+ data[24] = mNumRTPOctetsSent >> 24;
+ data[25] = (mNumRTPOctetsSent >> 16) & 0xff;
+ data[26] = (mNumRTPOctetsSent >> 8) & 0xff;
+ data[27] = mNumRTPOctetsSent & 0xff;
+
+ buffer->setRange(buffer->offset(), buffer->size() + 28);
+}
+
+void WifiDisplaySource::PlaybackSession::addSDES(const sp<ABuffer> &buffer) {
+ uint8_t *data = buffer->data() + buffer->size();
+ data[0] = 0x80 | 1;
+ data[1] = 202; // SDES
+ data[4] = kSourceID >> 24;
+ data[5] = (kSourceID >> 16) & 0xff;
+ data[6] = (kSourceID >> 8) & 0xff;
+ data[7] = kSourceID & 0xff;
+
+ size_t offset = 8;
+
+ data[offset++] = 1; // CNAME
+
+ static const char *kCNAME = "someone@somewhere";
+ data[offset++] = strlen(kCNAME);
+
+ memcpy(&data[offset], kCNAME, strlen(kCNAME));
+ offset += strlen(kCNAME);
+
+ data[offset++] = 7; // NOTE
+
+ static const char *kNOTE = "Hell's frozen over.";
+ data[offset++] = strlen(kNOTE);
+
+ memcpy(&data[offset], kNOTE, strlen(kNOTE));
+ offset += strlen(kNOTE);
+
+ data[offset++] = 0;
+
+ if ((offset % 4) > 0) {
+ size_t count = 4 - (offset % 4);
+ switch (count) {
+ case 3:
+ data[offset++] = 0;
+ case 2:
+ data[offset++] = 0;
+ case 1:
+ data[offset++] = 0;
+ }
+ }
+
+ size_t numWords = (offset / 4) - 1;
+ data[2] = numWords >> 8;
+ data[3] = numWords & 0xff;
+
+ buffer->setRange(buffer->offset(), buffer->size() + offset);
+}
+
+// static
+uint64_t WifiDisplaySource::PlaybackSession::GetNowNTP() {
+ uint64_t nowUs = ALooper::GetNowUs();
+
+ nowUs += ((70ll * 365 + 17) * 24) * 60 * 60 * 1000000ll;
+
+ uint64_t hi = nowUs / 1000000ll;
+ uint64_t lo = ((1ll << 32) * (nowUs % 1000000ll)) / 1000000ll;
+
+ return (hi << 32) | lo;
+}
+
+void WifiDisplaySource::PlaybackSession::onSendSR() {
+ sp<ABuffer> buffer = new ABuffer(1500);
+ buffer->setRange(0, 0);
+
+ addSR(buffer);
+ addSDES(buffer);
+
+ if (mUseInterleavedTCP) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatBinaryData);
+ notify->setInt32("channel", mRTCPChannel);
+ notify->setBuffer("data", buffer);
+ notify->post();
+ } else {
+ mNetSession->sendRequest(
+ mRTCPSessionID, buffer->data(), buffer->size());
+ }
+
+ ++mNumSRsSent;
+}
+
+ssize_t WifiDisplaySource::PlaybackSession::appendTSData(
+ const void *data, size_t size, bool timeDiscontinuity, bool flush) {
+ CHECK_EQ(size, 188);
+
+ CHECK_LE(mTSQueue->size() + size, mTSQueue->capacity());
+
+ memcpy(mTSQueue->data() + mTSQueue->size(), data, size);
+ mTSQueue->setRange(0, mTSQueue->size() + size);
+
+ if (flush || mTSQueue->size() == mTSQueue->capacity()) {
+ // flush
+
+ int64_t nowUs = ALooper::GetNowUs();
+ if (mFirstPacketTimeUs < 0ll) {
+ mFirstPacketTimeUs = nowUs;
+ }
+
+ // 90kHz time scale
+ uint32_t rtpTime = ((nowUs - mFirstPacketTimeUs) * 9ll) / 100ll;
+
+ uint8_t *rtp = mTSQueue->data();
+ rtp[0] = 0x80;
+ rtp[1] = 33 | (timeDiscontinuity ? (1 << 7) : 0); // M-bit
+ rtp[2] = (mRTPSeqNo >> 8) & 0xff;
+ rtp[3] = mRTPSeqNo & 0xff;
+ rtp[4] = rtpTime >> 24;
+ rtp[5] = (rtpTime >> 16) & 0xff;
+ rtp[6] = (rtpTime >> 8) & 0xff;
+ rtp[7] = rtpTime & 0xff;
+ rtp[8] = kSourceID >> 24;
+ rtp[9] = (kSourceID >> 16) & 0xff;
+ rtp[10] = (kSourceID >> 8) & 0xff;
+ rtp[11] = kSourceID & 0xff;
+
+ ++mRTPSeqNo;
+ ++mNumRTPSent;
+ mNumRTPOctetsSent += mTSQueue->size() - 12;
+
+ mLastRTPTime = rtpTime;
+ mLastNTPTime = GetNowNTP();
+
+ if (mUseInterleavedTCP) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatBinaryData);
+
+ sp<ABuffer> data = new ABuffer(mTSQueue->size());
+ memcpy(data->data(), rtp, mTSQueue->size());
+
+ notify->setInt32("channel", mRTPChannel);
+ notify->setBuffer("data", data);
+ notify->post();
+ } else {
+ mNetSession->sendRequest(
+ mRTPSessionID, rtp, mTSQueue->size());
+ }
+
+ mTSQueue->setInt32Data(mRTPSeqNo - 1);
+ mHistory.push_back(mTSQueue);
+ ++mHistoryLength;
+
+ if (mHistoryLength > kMaxHistoryLength) {
+ mTSQueue = *mHistory.begin();
+ mHistory.erase(mHistory.begin());
+
+ --mHistoryLength;
+ } else {
+ mTSQueue = new ABuffer(12 + kMaxNumTSPacketsPerRTPPacket * 188);
+ }
+
+ mTSQueue->setRange(0, 12);
+ }
+
+ return size;
+}
+
+status_t WifiDisplaySource::PlaybackSession::parseRTCP(
+ const sp<ABuffer> &buffer) {
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ while (size > 0) {
+ if (size < 8) {
+ // Too short to be a valid RTCP header
+ return ERROR_MALFORMED;
+ }
+
+ 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;
+ }
+
+ size_t headerLength = 4 * (data[2] << 8 | data[3]) + 4;
+
+ if (size < headerLength) {
+ // Only received a partial packet?
+ return ERROR_MALFORMED;
+ }
+
+ switch (data[1]) {
+ case 200:
+ case 201: // RR
+ case 202: // SDES
+ case 203:
+ case 204: // APP
+ break;
+
+ case 205: // TSFB (transport layer specific feedback)
+ parseTSFB(data, headerLength);
+ break;
+
+ case 206: // PSFB (payload specific feedback)
+ hexdump(data, headerLength);
+ break;
+
+ default:
+ {
+ ALOGW("Unknown RTCP packet type %u of size %d",
+ (unsigned)data[1], headerLength);
+ break;
+ }
+ }
+
+ data += headerLength;
+ size -= headerLength;
+ }
+
+ return OK;
+}
+
+status_t WifiDisplaySource::PlaybackSession::parseTSFB(
+ const uint8_t *data, size_t size) {
+ if ((data[0] & 0x1f) != 1) {
+ return ERROR_UNSUPPORTED; // We only support NACK for now.
+ }
+
+ uint32_t srcId = U32_AT(&data[8]);
+ if (srcId != kSourceID) {
+ return ERROR_MALFORMED;
+ }
+
+ for (size_t i = 12; i < size; i += 4) {
+ uint16_t seqNo = U16_AT(&data[i]);
+ uint16_t blp = U16_AT(&data[i + 2]);
+
+ List<sp<ABuffer> >::iterator it = mHistory.begin();
+ bool found = false;
+ while (it != mHistory.end()) {
+ const sp<ABuffer> &buffer = *it;
+
+ uint16_t bufferSeqNo = buffer->int32Data() & 0xffff;
+
+ if (bufferSeqNo == seqNo) {
+ mNetSession->sendRequest(
+ mRTPSessionID, buffer->data(), buffer->size());
+
+ found = true;
+ break;
+ }
+
+ ++it;
+ }
+
+ if (found) {
+ ALOGI("retransmitting seqNo %d", seqNo);
+ } else {
+ ALOGI("seqNo %d no longer available", seqNo);
+ }
+ }
+
+ return OK;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.h b/media/libstagefright/wifi-display/source/PlaybackSession.h
new file mode 100644
index 0000000..d256fc4
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.h
@@ -0,0 +1,135 @@
+/*
+ * 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 PLAYBACK_SESSION_H_
+
+#define PLAYBACK_SESSION_H_
+
+#include "WifiDisplaySource.h"
+
+namespace android {
+
+struct ABuffer;
+struct Serializer;
+struct TSPacketizer;
+
+// Encapsulates the state of an RTP/RTCP session in the context of wifi
+// display.
+struct WifiDisplaySource::PlaybackSession : public AHandler {
+ PlaybackSession(
+ const sp<ANetworkSession> &netSession, const sp<AMessage> &notify);
+
+ status_t init(
+ const char *clientIP, int32_t clientRtp, int32_t clientRtcp,
+ bool useInterleavedTCP);
+
+ int32_t getRTPPort() const;
+
+ int64_t getLastLifesignUs() const;
+ void updateLiveness();
+
+ status_t play();
+ status_t pause();
+
+ enum {
+ kWhatSessionDead,
+ kWhatBinaryData,
+ };
+
+protected:
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+ virtual ~PlaybackSession();
+
+private:
+ struct Track;
+
+ enum {
+ kWhatSendSR,
+ kWhatRTPNotify,
+ kWhatRTCPNotify,
+ kWhatSerializerNotify,
+ kWhatConverterNotify,
+ kWhatUpdateSurface,
+ };
+
+ static const int64_t kSendSRIntervalUs = 10000000ll;
+ static const uint32_t kSourceID = 0xdeadbeef;
+ static const size_t kMaxHistoryLength = 128;
+
+ sp<ANetworkSession> mNetSession;
+ sp<AMessage> mNotify;
+
+ int64_t mLastLifesignUs;
+
+ sp<ALooper> mSerializerLooper;
+ sp<Serializer> mSerializer;
+ sp<TSPacketizer> mPacketizer;
+ sp<ALooper> mCodecLooper;
+
+ KeyedVector<size_t, sp<Track> > mTracks;
+
+ sp<ABuffer> mTSQueue;
+ int64_t mPrevTimeUs;
+
+ bool mUseInterleavedTCP;
+
+ // in TCP mode
+ int32_t mRTPChannel;
+ int32_t mRTCPChannel;
+
+ // in UDP mode
+ int32_t mRTPPort;
+ int32_t mRTPSessionID;
+ int32_t mRTCPSessionID;
+
+
+ uint32_t mRTPSeqNo;
+
+ uint64_t mLastNTPTime;
+ uint32_t mLastRTPTime;
+ uint32_t mNumRTPSent;
+ uint32_t mNumRTPOctetsSent;
+ uint32_t mNumSRsSent;
+
+ bool mSendSRPending;
+
+ int64_t mFirstPacketTimeUs;
+
+ List<sp<ABuffer> > mHistory;
+ size_t mHistoryLength;
+
+ void onSendSR();
+ void addSR(const sp<ABuffer> &buffer);
+ void addSDES(const sp<ABuffer> &buffer);
+ static uint64_t GetNowNTP();
+
+ status_t setupPacketizer();
+
+ ssize_t appendTSData(
+ const void *data, size_t size, bool timeDiscontinuity, bool flush);
+
+ void scheduleSendSR();
+
+ status_t parseRTCP(const sp<ABuffer> &buffer);
+ status_t parseTSFB(const uint8_t *data, size_t size);
+
+ DISALLOW_EVIL_CONSTRUCTORS(PlaybackSession);
+};
+
+} // namespace android
+
+#endif // PLAYBACK_SESSION_H_
+
diff --git a/media/libstagefright/wifi-display/source/RepeaterSource.cpp b/media/libstagefright/wifi-display/source/RepeaterSource.cpp
new file mode 100644
index 0000000..8af4fdf
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/RepeaterSource.cpp
@@ -0,0 +1,140 @@
+//#define LOG_NDEBUG 0
+#define LOG_TAG "RepeaterSource"
+#include <utils/Log.h>
+
+#include "RepeaterSource.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+RepeaterSource::RepeaterSource(const sp<MediaSource> &source, double rateHz)
+ : mSource(source),
+ mRateHz(rateHz),
+ mBuffer(NULL),
+ mResult(OK),
+ mStartTimeUs(-1ll),
+ mFrameCount(0) {
+}
+
+RepeaterSource::~RepeaterSource() {
+ stop();
+}
+
+status_t RepeaterSource::start(MetaData *params) {
+ status_t err = mSource->start(params);
+
+ if (err != OK) {
+ return err;
+ }
+
+ mBuffer = NULL;
+ mResult = OK;
+ mStartTimeUs = -1ll;
+ mFrameCount = 0;
+
+ mLooper = new ALooper;
+ mLooper->start();
+
+ mReflector = new AHandlerReflector<RepeaterSource>(this);
+ mLooper->registerHandler(mReflector);
+
+ postRead();
+
+ return OK;
+}
+
+status_t RepeaterSource::stop() {
+ if (mLooper != NULL) {
+ mLooper->stop();
+ mLooper.clear();
+
+ mReflector.clear();
+ }
+
+ return mSource->stop();
+}
+
+sp<MetaData> RepeaterSource::getFormat() {
+ return mSource->getFormat();
+}
+
+status_t RepeaterSource::read(
+ MediaBuffer **buffer, const ReadOptions *options) {
+ int64_t seekTimeUs;
+ ReadOptions::SeekMode seekMode;
+ CHECK(options == NULL || !options->getSeekTo(&seekTimeUs, &seekMode));
+
+ int64_t bufferTimeUs = -1ll;
+
+ if (mStartTimeUs < 0ll) {
+ Mutex::Autolock autoLock(mLock);
+ while (mBuffer == NULL && mResult == OK) {
+ mCondition.wait(mLock);
+ }
+
+ mStartTimeUs = ALooper::GetNowUs();
+ bufferTimeUs = mStartTimeUs;
+ } else {
+ bufferTimeUs = mStartTimeUs + (mFrameCount * 1000000ll) / mRateHz;
+
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t delayUs = bufferTimeUs - nowUs;
+
+ if (delayUs > 0ll) {
+ usleep(delayUs);
+ }
+ }
+
+ Mutex::Autolock autoLock(mLock);
+ if (mResult != OK) {
+ CHECK(mBuffer == NULL);
+ return mResult;
+ }
+
+ mBuffer->add_ref();
+ *buffer = mBuffer;
+ (*buffer)->meta_data()->setInt64(kKeyTime, bufferTimeUs);
+
+ ++mFrameCount;
+
+ return OK;
+}
+
+void RepeaterSource::postRead() {
+ (new AMessage(kWhatRead, mReflector->id()))->post();
+}
+
+void RepeaterSource::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatRead:
+ {
+ MediaBuffer *buffer;
+ status_t err = mSource->read(&buffer);
+
+ Mutex::Autolock autoLock(mLock);
+ if (mBuffer != NULL) {
+ mBuffer->release();
+ mBuffer = NULL;
+ }
+ mBuffer = buffer;
+ mResult = err;
+
+ mCondition.broadcast();
+
+ if (err == OK) {
+ postRead();
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+} // namespace android
diff --git a/media/libstagefright/wifi-display/source/RepeaterSource.h b/media/libstagefright/wifi-display/source/RepeaterSource.h
new file mode 100644
index 0000000..31eb5cd
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/RepeaterSource.h
@@ -0,0 +1,55 @@
+#ifndef REPEATER_SOURCE_H_
+
+#define REPEATER_SOURCE_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AHandlerReflector.h>
+#include <media/stagefright/MediaSource.h>
+
+namespace android {
+
+// This MediaSource delivers frames at a constant rate by repeating buffers
+// if necessary.
+struct RepeaterSource : public MediaSource {
+ RepeaterSource(const sp<MediaSource> &source, double rateHz);
+
+ virtual status_t start(MetaData *params);
+ virtual status_t stop();
+ virtual sp<MetaData> getFormat();
+
+ virtual status_t read(
+ MediaBuffer **buffer, const ReadOptions *options);
+
+ void onMessageReceived(const sp<AMessage> &msg);
+
+protected:
+ virtual ~RepeaterSource();
+
+private:
+ enum {
+ kWhatRead,
+ };
+
+ Mutex mLock;
+ Condition mCondition;
+
+ sp<MediaSource> mSource;
+ double mRateHz;
+
+ sp<ALooper> mLooper;
+ sp<AHandlerReflector<RepeaterSource> > mReflector;
+
+ MediaBuffer *mBuffer;
+ status_t mResult;
+
+ int64_t mStartTimeUs;
+ int32_t mFrameCount;
+
+ void postRead();
+
+ DISALLOW_EVIL_CONSTRUCTORS(RepeaterSource);
+};
+
+} // namespace android
+
+#endif // REPEATER_SOURCE_H_
diff --git a/media/libstagefright/wifi-display/source/Serializer.cpp b/media/libstagefright/wifi-display/source/Serializer.cpp
new file mode 100644
index 0000000..bd53fc8
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/Serializer.cpp
@@ -0,0 +1,366 @@
+/*
+ * 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 "Serializer"
+#include <utils/Log.h>
+
+#include "Serializer.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+struct Serializer::Track : public RefBase {
+ Track(const sp<MediaSource> &source);
+
+ status_t start();
+ status_t stop();
+
+ void readBufferIfNecessary();
+
+ bool reachedEOS() const;
+ int64_t bufferTimeUs() const;
+
+ sp<ABuffer> drainBuffer();
+
+protected:
+ virtual ~Track();
+
+private:
+ sp<MediaSource> mSource;
+
+ bool mStarted;
+ status_t mFinalResult;
+ MediaBuffer *mBuffer;
+ int64_t mBufferTimeUs;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Track);
+};
+
+Serializer::Track::Track(const sp<MediaSource> &source)
+ : mSource(source),
+ mStarted(false),
+ mFinalResult(OK),
+ mBuffer(NULL),
+ mBufferTimeUs(-1ll) {
+}
+
+Serializer::Track::~Track() {
+ stop();
+}
+
+status_t Serializer::Track::start() {
+ if (mStarted) {
+ return OK;
+ }
+
+ status_t err = mSource->start();
+
+ if (err == OK) {
+ mStarted = true;
+ }
+
+ return err;
+}
+
+status_t Serializer::Track::stop() {
+ if (!mStarted) {
+ return OK;
+ }
+
+ if (mBuffer != NULL) {
+ mBuffer->release();
+ mBuffer = NULL;
+
+ mBufferTimeUs = -1ll;
+ }
+
+ status_t err = mSource->stop();
+
+ mStarted = false;
+
+ return err;
+}
+
+void Serializer::Track::readBufferIfNecessary() {
+ if (mBuffer != NULL) {
+ return;
+ }
+
+ mFinalResult = mSource->read(&mBuffer);
+
+ if (mFinalResult != OK) {
+ ALOGI("read failed w/ err %d", mFinalResult);
+ return;
+ }
+
+ CHECK(mBuffer->meta_data()->findInt64(kKeyTime, &mBufferTimeUs));
+}
+
+bool Serializer::Track::reachedEOS() const {
+ return mFinalResult != OK;
+}
+
+int64_t Serializer::Track::bufferTimeUs() const {
+ return mBufferTimeUs;
+}
+
+sp<ABuffer> Serializer::Track::drainBuffer() {
+ sp<ABuffer> accessUnit = new ABuffer(mBuffer->range_length());
+
+ memcpy(accessUnit->data(),
+ (const uint8_t *)mBuffer->data() + mBuffer->range_offset(),
+ mBuffer->range_length());
+
+ accessUnit->meta()->setInt64("timeUs", mBufferTimeUs);
+ accessUnit->meta()->setPointer("mediaBuffer", mBuffer);
+
+ mBuffer = NULL;
+ mBufferTimeUs = -1ll;
+
+ return accessUnit;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+Serializer::Serializer(bool throttle, const sp<AMessage> &notify)
+ : mThrottle(throttle),
+ mNotify(notify),
+ mPollGeneration(0),
+ mStartTimeUs(-1ll) {
+}
+
+Serializer::~Serializer() {
+}
+
+status_t Serializer::postSynchronouslyAndReturnError(
+ const sp<AMessage> &msg) {
+ sp<AMessage> response;
+ status_t err = msg->postAndAwaitResponse(&response);
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (!response->findInt32("err", &err)) {
+ err = OK;
+ }
+
+ return err;
+}
+
+ssize_t Serializer::addSource(const sp<MediaSource> &source) {
+ sp<AMessage> msg = new AMessage(kWhatAddSource, id());
+ msg->setPointer("source", source.get());
+
+ sp<AMessage> response;
+ status_t err = msg->postAndAwaitResponse(&response);
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (!response->findInt32("err", &err)) {
+ size_t index;
+ CHECK(response->findSize("index", &index));
+
+ return index;
+ }
+
+ return err;
+}
+
+status_t Serializer::start() {
+ return postSynchronouslyAndReturnError(new AMessage(kWhatStart, id()));
+}
+
+status_t Serializer::stop() {
+ return postSynchronouslyAndReturnError(new AMessage(kWhatStop, id()));
+}
+
+void Serializer::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatAddSource:
+ {
+ ssize_t index = onAddSource(msg);
+
+ sp<AMessage> response = new AMessage;
+
+ if (index < 0) {
+ response->setInt32("err", index);
+ } else {
+ response->setSize("index", index);
+ }
+
+ uint32_t replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ response->postReply(replyID);
+ break;
+ }
+
+ case kWhatStart:
+ case kWhatStop:
+ {
+ status_t err = (msg->what() == kWhatStart) ? onStart() : onStop();
+
+ sp<AMessage> response = new AMessage;
+ response->setInt32("err", err);
+
+ uint32_t replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ response->postReply(replyID);
+ break;
+ }
+
+ case kWhatPoll:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mPollGeneration) {
+ break;
+ }
+
+ int64_t delayUs = onPoll();
+ if (delayUs >= 0ll) {
+ schedulePoll(delayUs);
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+ssize_t Serializer::onAddSource(const sp<AMessage> &msg) {
+ void *obj;
+ CHECK(msg->findPointer("source", &obj));
+
+ sp<MediaSource> source = static_cast<MediaSource *>(obj);
+
+ sp<Track> track = new Track(source);
+ return mTracks.add(track);
+}
+
+status_t Serializer::onStart() {
+ status_t err = OK;
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ err = mTracks.itemAt(i)->start();
+
+ if (err != OK) {
+ break;
+ }
+ }
+
+ if (err == OK) {
+#if 0
+ schedulePoll();
+#else
+ // XXX the dongle doesn't appear to have setup the RTP connection
+ // fully at the time PLAY is called. We have to delay sending data
+ // for a little bit.
+ schedulePoll(500000ll);
+#endif
+ }
+
+ return err;
+}
+
+status_t Serializer::onStop() {
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ mTracks.itemAt(i)->stop();
+ }
+
+ cancelPoll();
+
+ return OK;
+}
+
+int64_t Serializer::onPoll() {
+ int64_t minTimeUs = -1ll;
+ ssize_t minTrackIndex = -1;
+
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ const sp<Track> &track = mTracks.itemAt(i);
+
+ track->readBufferIfNecessary();
+
+ if (!track->reachedEOS()) {
+ int64_t timeUs = track->bufferTimeUs();
+
+ if (minTrackIndex < 0 || timeUs < minTimeUs) {
+ minTimeUs = timeUs;
+ minTrackIndex = i;
+ }
+ }
+ }
+
+ if (minTrackIndex < 0) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatEOS);
+ notify->post();
+
+ return -1ll;
+ }
+
+ if (mThrottle) {
+ int64_t nowUs = ALooper::GetNowUs();
+
+ if (mStartTimeUs < 0ll) {
+ mStartTimeUs = nowUs;
+ }
+
+ int64_t lateByUs = nowUs - (minTimeUs + mStartTimeUs);
+
+ if (lateByUs < 0ll) {
+ // Too early
+ return -lateByUs;
+ }
+ }
+
+ sp<AMessage> notify = mNotify->dup();
+
+ notify->setInt32("what", kWhatAccessUnit);
+ notify->setSize("trackIndex", minTrackIndex);
+
+ notify->setBuffer(
+ "accessUnit", mTracks.itemAt(minTrackIndex)->drainBuffer());
+
+ notify->post();
+
+ return 0ll;
+}
+
+void Serializer::schedulePoll(int64_t delayUs) {
+ sp<AMessage> msg = new AMessage(kWhatPoll, id());
+ msg->setInt32("generation", mPollGeneration);
+ msg->post(delayUs);
+}
+
+void Serializer::cancelPoll() {
+ ++mPollGeneration;
+}
+
+} // namespace android
diff --git a/media/libstagefright/wifi-display/source/Serializer.h b/media/libstagefright/wifi-display/source/Serializer.h
new file mode 100644
index 0000000..07950fa
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/Serializer.h
@@ -0,0 +1,84 @@
+/*
+ * 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 SERIALIZER_H_
+
+#define SERIALIZER_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct AMessage;
+struct MediaSource;
+
+// After adding a number of MediaSource objects and starting the Serializer,
+// it'll emit their access units in order of increasing timestamps.
+struct Serializer : public AHandler {
+ enum {
+ kWhatEOS,
+ kWhatAccessUnit
+ };
+
+ // In throttled operation, data is emitted at a pace corresponding
+ // to the incoming media timestamps.
+ Serializer(bool throttle, const sp<AMessage> &notify);
+
+ ssize_t addSource(const sp<MediaSource> &source);
+
+ status_t start();
+ status_t stop();
+
+protected:
+ virtual void onMessageReceived(const sp<AMessage> &what);
+ virtual ~Serializer();
+
+private:
+ enum {
+ kWhatAddSource,
+ kWhatStart,
+ kWhatStop,
+ kWhatPoll
+ };
+
+ struct Track;
+
+ bool mThrottle;
+ sp<AMessage> mNotify;
+ Vector<sp<Track> > mTracks;
+
+ int32_t mPollGeneration;
+
+ int64_t mStartTimeUs;
+
+ status_t postSynchronouslyAndReturnError(const sp<AMessage> &msg);
+
+ ssize_t onAddSource(const sp<AMessage> &msg);
+ status_t onStart();
+ status_t onStop();
+ int64_t onPoll();
+
+ void schedulePoll(int64_t delayUs = 0ll);
+ void cancelPoll();
+
+ DISALLOW_EVIL_CONSTRUCTORS(Serializer);
+};
+
+} // namespace android
+
+#endif // SERIALIZER_H_
+
diff --git a/media/libstagefright/wifi-display/source/TSPacketizer.cpp b/media/libstagefright/wifi-display/source/TSPacketizer.cpp
new file mode 100644
index 0000000..b9a3e9b
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/TSPacketizer.cpp
@@ -0,0 +1,694 @@
+/*
+ * 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 "TSPacketizer"
+#include <utils/Log.h>
+
+#include "TSPacketizer.h"
+#include "include/avc_utils.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/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+
+#include <arpa/inet.h>
+
+namespace android {
+
+struct TSPacketizer::Track : public RefBase {
+ Track(const sp<AMessage> &format,
+ unsigned PID, unsigned streamType, unsigned streamID);
+
+ unsigned PID() const;
+ unsigned streamType() const;
+ unsigned streamID() const;
+
+ // Returns the previous value.
+ unsigned incrementContinuityCounter();
+
+ bool isAudio() const;
+ bool isVideo() const;
+
+ bool isH264() const;
+ bool lacksADTSHeader() const;
+
+ sp<ABuffer> prependCSD(const sp<ABuffer> &accessUnit) const;
+ sp<ABuffer> prependADTSHeader(const sp<ABuffer> &accessUnit) const;
+
+protected:
+ virtual ~Track();
+
+private:
+ sp<AMessage> mFormat;
+
+ unsigned mPID;
+ unsigned mStreamType;
+ unsigned mStreamID;
+ unsigned mContinuityCounter;
+
+ AString mMIME;
+ Vector<sp<ABuffer> > mCSD;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Track);
+};
+
+TSPacketizer::Track::Track(
+ const sp<AMessage> &format,
+ unsigned PID, unsigned streamType, unsigned streamID)
+ : mFormat(format),
+ mPID(PID),
+ mStreamType(streamType),
+ mStreamID(streamID),
+ mContinuityCounter(0) {
+ CHECK(format->findString("mime", &mMIME));
+
+ if (!strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_VIDEO_AVC)
+ || !strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_AUDIO_AAC)) {
+ for (size_t i = 0;; ++i) {
+ sp<ABuffer> csd;
+ if (!format->findBuffer(StringPrintf("csd-%d", i).c_str(), &csd)) {
+ break;
+ }
+
+ mCSD.push(csd);
+ }
+ }
+}
+
+TSPacketizer::Track::~Track() {
+}
+
+unsigned TSPacketizer::Track::PID() const {
+ return mPID;
+}
+
+unsigned TSPacketizer::Track::streamType() const {
+ return mStreamType;
+}
+
+unsigned TSPacketizer::Track::streamID() const {
+ return mStreamID;
+}
+
+unsigned TSPacketizer::Track::incrementContinuityCounter() {
+ unsigned prevCounter = mContinuityCounter;
+
+ if (++mContinuityCounter == 16) {
+ mContinuityCounter = 0;
+ }
+
+ return prevCounter;
+}
+
+bool TSPacketizer::Track::isAudio() const {
+ return !strncasecmp("audio/", mMIME.c_str(), 6);
+}
+
+bool TSPacketizer::Track::isVideo() const {
+ return !strncasecmp("video/", mMIME.c_str(), 6);
+}
+
+bool TSPacketizer::Track::isH264() const {
+ return !strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_VIDEO_AVC);
+}
+
+bool TSPacketizer::Track::lacksADTSHeader() const {
+ if (strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_AUDIO_AAC)) {
+ return false;
+ }
+
+ int32_t isADTS;
+ if (mFormat->findInt32("is-adts", &isADTS) && isADTS != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+sp<ABuffer> TSPacketizer::Track::prependCSD(
+ const sp<ABuffer> &accessUnit) const {
+ size_t size = 0;
+ for (size_t i = 0; i < mCSD.size(); ++i) {
+ size += mCSD.itemAt(i)->size();
+ }
+
+ sp<ABuffer> dup = new ABuffer(accessUnit->size() + size);
+ size_t offset = 0;
+ for (size_t i = 0; i < mCSD.size(); ++i) {
+ const sp<ABuffer> &csd = mCSD.itemAt(i);
+
+ memcpy(dup->data() + offset, csd->data(), csd->size());
+ offset += csd->size();
+ }
+
+ memcpy(dup->data() + offset, accessUnit->data(), accessUnit->size());
+
+ return dup;
+}
+
+sp<ABuffer> TSPacketizer::Track::prependADTSHeader(
+ const sp<ABuffer> &accessUnit) const {
+ CHECK_EQ(mCSD.size(), 1u);
+
+ const uint8_t *codec_specific_data = mCSD.itemAt(0)->data();
+
+ const uint32_t aac_frame_length = accessUnit->size() + 7;
+
+ sp<ABuffer> dup = new ABuffer(aac_frame_length);
+
+ unsigned profile = (codec_specific_data[0] >> 3) - 1;
+
+ unsigned sampling_freq_index =
+ ((codec_specific_data[0] & 7) << 1)
+ | (codec_specific_data[1] >> 7);
+
+ unsigned channel_configuration =
+ (codec_specific_data[1] >> 3) & 0x0f;
+
+ uint8_t *ptr = dup->data();
+
+ *ptr++ = 0xff;
+ *ptr++ = 0xf1; // b11110001, ID=0, layer=0, protection_absent=1
+
+ *ptr++ =
+ profile << 6
+ | sampling_freq_index << 2
+ | ((channel_configuration >> 2) & 1); // private_bit=0
+
+ // original_copy=0, home=0, copyright_id_bit=0, copyright_id_start=0
+ *ptr++ =
+ (channel_configuration & 3) << 6
+ | aac_frame_length >> 11;
+ *ptr++ = (aac_frame_length >> 3) & 0xff;
+ *ptr++ = (aac_frame_length & 7) << 5;
+
+ // adts_buffer_fullness=0, number_of_raw_data_blocks_in_frame=0
+ *ptr++ = 0;
+
+ memcpy(ptr, accessUnit->data(), accessUnit->size());
+
+ return dup;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSPacketizer::TSPacketizer()
+ : mPATContinuityCounter(0),
+ mPMTContinuityCounter(0) {
+ initCrcTable();
+}
+
+TSPacketizer::~TSPacketizer() {
+}
+
+ssize_t TSPacketizer::addTrack(const sp<AMessage> &format) {
+ AString mime;
+ CHECK(format->findString("mime", &mime));
+
+ unsigned PIDStart;
+ bool isVideo = !strncasecmp("video/", mime.c_str(), 6);
+ bool isAudio = !strncasecmp("audio/", mime.c_str(), 6);
+
+ if (isVideo) {
+ PIDStart = 0x1011;
+ } else if (isAudio) {
+ PIDStart = 0x1100;
+ } else {
+ return ERROR_UNSUPPORTED;
+ }
+
+ unsigned streamType;
+ unsigned streamIDStart;
+ unsigned streamIDStop;
+
+ if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_VIDEO_AVC)) {
+ streamType = 0x1b;
+ streamIDStart = 0xe0;
+ streamIDStop = 0xef;
+ } else if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_AUDIO_AAC)) {
+ streamType = 0x0f;
+ streamIDStart = 0xc0;
+ streamIDStop = 0xdf;
+ } else {
+ return ERROR_UNSUPPORTED;
+ }
+
+ size_t numTracksOfThisType = 0;
+ unsigned PID = PIDStart;
+
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ const sp<Track> &track = mTracks.itemAt(i);
+
+ if (track->streamType() == streamType) {
+ ++numTracksOfThisType;
+ }
+
+ if ((isAudio && track->isAudio()) || (isVideo && track->isVideo())) {
+ ++PID;
+ }
+ }
+
+ unsigned streamID = streamIDStart + numTracksOfThisType;
+ if (streamID > streamIDStop) {
+ return -ERANGE;
+ }
+
+ sp<Track> track = new Track(format, PID, streamType, streamID);
+ return mTracks.add(track);
+}
+
+status_t TSPacketizer::packetize(
+ size_t trackIndex,
+ const sp<ABuffer> &_accessUnit,
+ sp<ABuffer> *packets,
+ uint32_t flags) {
+ sp<ABuffer> accessUnit = _accessUnit;
+
+ packets->clear();
+
+ if (trackIndex >= mTracks.size()) {
+ return -ERANGE;
+ }
+
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ const sp<Track> &track = mTracks.itemAt(trackIndex);
+
+ if (track->isH264()) {
+ if (IsIDR(accessUnit)) {
+ // prepend codec specific data, i.e. SPS and PPS.
+ accessUnit = track->prependCSD(accessUnit);
+ }
+ } else if (track->lacksADTSHeader()) {
+ accessUnit = track->prependADTSHeader(accessUnit);
+ }
+
+ // 0x47
+ // transport_error_indicator = b0
+ // payload_unit_start_indicator = b1
+ // transport_priority = b0
+ // PID
+ // transport_scrambling_control = b00
+ // adaptation_field_control = b??
+ // continuity_counter = b????
+ // -- payload follows
+ // packet_startcode_prefix = 0x000001
+ // stream_id
+ // PES_packet_length = 0x????
+ // reserved = b10
+ // PES_scrambling_control = b00
+ // PES_priority = b0
+ // data_alignment_indicator = b1
+ // copyright = b0
+ // original_or_copy = b0
+ // PTS_DTS_flags = b10 (PTS only)
+ // ESCR_flag = b0
+ // ES_rate_flag = b0
+ // DSM_trick_mode_flag = b0
+ // additional_copy_info_flag = b0
+ // PES_CRC_flag = b0
+ // PES_extension_flag = b0
+ // PES_header_data_length = 0x05
+ // reserved = b0010 (PTS)
+ // PTS[32..30] = b???
+ // reserved = b1
+ // PTS[29..15] = b??? ???? ???? ???? (15 bits)
+ // reserved = b1
+ // PTS[14..0] = b??? ???? ???? ???? (15 bits)
+ // reserved = b1
+ // the first fragment of "buffer" follows
+
+ size_t numTSPackets;
+ if (accessUnit->size() <= 170) {
+ numTSPackets = 1;
+ } else {
+ numTSPackets = 1 + ((accessUnit->size() - 170) + 183) / 184;
+ }
+
+ if (flags & EMIT_PAT_AND_PMT) {
+ numTSPackets += 2;
+ }
+
+ if (flags & EMIT_PCR) {
+ ++numTSPackets;
+ }
+
+ sp<ABuffer> buffer = new ABuffer(numTSPackets * 188);
+ uint8_t *packetDataStart = buffer->data();
+
+ if (flags & EMIT_PAT_AND_PMT) {
+ // Program Association Table (PAT):
+ // 0x47
+ // transport_error_indicator = b0
+ // payload_unit_start_indicator = b1
+ // transport_priority = b0
+ // PID = b0000000000000 (13 bits)
+ // transport_scrambling_control = b00
+ // adaptation_field_control = b01 (no adaptation field, payload only)
+ // continuity_counter = b????
+ // skip = 0x00
+ // --- payload follows
+ // table_id = 0x00
+ // section_syntax_indicator = b1
+ // must_be_zero = b0
+ // reserved = b11
+ // section_length = 0x00d
+ // transport_stream_id = 0x0000
+ // reserved = b11
+ // version_number = b00001
+ // current_next_indicator = b1
+ // section_number = 0x00
+ // last_section_number = 0x00
+ // one program follows:
+ // program_number = 0x0001
+ // reserved = b111
+ // program_map_PID = kPID_PMT (13 bits!)
+ // CRC = 0x????????
+
+ if (++mPATContinuityCounter == 16) {
+ mPATContinuityCounter = 0;
+ }
+
+ uint8_t *ptr = packetDataStart;
+ *ptr++ = 0x47;
+ *ptr++ = 0x40;
+ *ptr++ = 0x00;
+ *ptr++ = 0x10 | mPATContinuityCounter;
+ *ptr++ = 0x00;
+
+ const uint8_t *crcDataStart = ptr;
+ *ptr++ = 0x00;
+ *ptr++ = 0xb0;
+ *ptr++ = 0x0d;
+ *ptr++ = 0x00;
+ *ptr++ = 0x00;
+ *ptr++ = 0xc3;
+ *ptr++ = 0x00;
+ *ptr++ = 0x00;
+ *ptr++ = 0x00;
+ *ptr++ = 0x01;
+ *ptr++ = 0xe0 | (kPID_PMT >> 8);
+ *ptr++ = kPID_PMT & 0xff;
+
+ CHECK_EQ(ptr - crcDataStart, 12);
+ uint32_t crc = htonl(crc32(crcDataStart, ptr - crcDataStart));
+ memcpy(ptr, &crc, 4);
+ ptr += 4;
+
+ size_t sizeLeft = packetDataStart + 188 - ptr;
+ memset(ptr, 0xff, sizeLeft);
+
+ packetDataStart += 188;
+
+ // Program Map (PMT):
+ // 0x47
+ // transport_error_indicator = b0
+ // payload_unit_start_indicator = b1
+ // transport_priority = b0
+ // PID = kPID_PMT (13 bits)
+ // transport_scrambling_control = b00
+ // adaptation_field_control = b01 (no adaptation field, payload only)
+ // continuity_counter = b????
+ // skip = 0x00
+ // -- payload follows
+ // table_id = 0x02
+ // section_syntax_indicator = b1
+ // must_be_zero = b0
+ // reserved = b11
+ // section_length = 0x???
+ // program_number = 0x0001
+ // reserved = b11
+ // version_number = b00001
+ // current_next_indicator = b1
+ // section_number = 0x00
+ // last_section_number = 0x00
+ // reserved = b111
+ // PCR_PID = kPCR_PID (13 bits)
+ // reserved = b1111
+ // program_info_length = 0x000
+ // one or more elementary stream descriptions follow:
+ // stream_type = 0x??
+ // reserved = b111
+ // elementary_PID = b? ???? ???? ???? (13 bits)
+ // reserved = b1111
+ // ES_info_length = 0x000
+ // CRC = 0x????????
+
+ if (++mPMTContinuityCounter == 16) {
+ mPMTContinuityCounter = 0;
+ }
+
+ size_t section_length = 5 * mTracks.size() + 4 + 9;
+
+ ptr = packetDataStart;
+ *ptr++ = 0x47;
+ *ptr++ = 0x40 | (kPID_PMT >> 8);
+ *ptr++ = kPID_PMT & 0xff;
+ *ptr++ = 0x10 | mPMTContinuityCounter;
+ *ptr++ = 0x00;
+
+ crcDataStart = ptr;
+ *ptr++ = 0x02;
+ *ptr++ = 0xb0 | (section_length >> 8);
+ *ptr++ = section_length & 0xff;
+ *ptr++ = 0x00;
+ *ptr++ = 0x01;
+ *ptr++ = 0xc3;
+ *ptr++ = 0x00;
+ *ptr++ = 0x00;
+ *ptr++ = 0xe0 | (kPID_PCR >> 8);
+ *ptr++ = kPID_PCR & 0xff;
+ *ptr++ = 0xf0;
+ *ptr++ = 0x00;
+
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ const sp<Track> &track = mTracks.itemAt(i);
+
+ *ptr++ = track->streamType();
+ *ptr++ = 0xe0 | (track->PID() >> 8);
+ *ptr++ = track->PID() & 0xff;
+ *ptr++ = 0xf0;
+ *ptr++ = 0x00;
+ }
+
+ CHECK_EQ(ptr - crcDataStart, 12 + mTracks.size() * 5);
+ crc = htonl(crc32(crcDataStart, ptr - crcDataStart));
+ memcpy(ptr, &crc, 4);
+ ptr += 4;
+
+ sizeLeft = packetDataStart + 188 - ptr;
+ memset(ptr, 0xff, sizeLeft);
+
+ packetDataStart += 188;
+ }
+
+ if (flags & EMIT_PCR) {
+ // PCR stream
+ // 0x47
+ // transport_error_indicator = b0
+ // payload_unit_start_indicator = b1
+ // transport_priority = b0
+ // PID = kPCR_PID (13 bits)
+ // transport_scrambling_control = b00
+ // adaptation_field_control = b10 (adaptation field only, no payload)
+ // continuity_counter = b0000 (does not increment)
+ // adaptation_field_length = 183
+ // discontinuity_indicator = b0
+ // random_access_indicator = b0
+ // elementary_stream_priority_indicator = b0
+ // PCR_flag = b1
+ // OPCR_flag = b0
+ // splicing_point_flag = b0
+ // transport_private_data_flag = b0
+ // adaptation_field_extension_flag = b0
+ // program_clock_reference_base = b?????????????????????????????????
+ // reserved = b111111
+ // program_clock_reference_extension = b?????????
+
+#if 0
+ int64_t nowUs = ALooper::GetNowUs();
+#else
+ int64_t nowUs = timeUs;
+#endif
+
+ uint64_t PCR = nowUs * 27; // PCR based on a 27MHz clock
+ uint64_t PCR_base = PCR / 300;
+ uint32_t PCR_ext = PCR % 300;
+
+ uint8_t *ptr = packetDataStart;
+ *ptr++ = 0x47;
+ *ptr++ = 0x40 | (kPID_PCR >> 8);
+ *ptr++ = kPID_PCR & 0xff;
+ *ptr++ = 0x20;
+ *ptr++ = 0xb7; // adaptation_field_length
+ *ptr++ = 0x10;
+ *ptr++ = (PCR_base >> 25) & 0xff;
+ *ptr++ = (PCR_base >> 17) & 0xff;
+ *ptr++ = (PCR_base >> 9) & 0xff;
+ *ptr++ = ((PCR_base & 1) << 7) | 0x7e | ((PCR_ext >> 8) & 1);
+ *ptr++ = (PCR_ext & 0xff);
+
+ size_t sizeLeft = packetDataStart + 188 - ptr;
+ memset(ptr, 0xff, sizeLeft);
+
+ packetDataStart += 188;
+ }
+
+ uint32_t PTS = (timeUs * 9ll) / 100ll;
+
+ size_t PES_packet_length = accessUnit->size() + 8;
+ bool padding = (accessUnit->size() < (188 - 18));
+
+ if (PES_packet_length >= 65536) {
+ // This really should only happen for video.
+ CHECK(track->isVideo());
+
+ // It's valid to set this to 0 for video according to the specs.
+ PES_packet_length = 0;
+ }
+
+ uint8_t *ptr = packetDataStart;
+ *ptr++ = 0x47;
+ *ptr++ = 0x40 | (track->PID() >> 8);
+ *ptr++ = track->PID() & 0xff;
+ *ptr++ = (padding ? 0x30 : 0x10) | track->incrementContinuityCounter();
+
+ if (padding) {
+ size_t paddingSize = 188 - 18 - accessUnit->size();
+ *ptr++ = paddingSize - 1;
+ if (paddingSize >= 2) {
+ *ptr++ = 0x00;
+ memset(ptr, 0xff, paddingSize - 2);
+ ptr += paddingSize - 2;
+ }
+ }
+
+ *ptr++ = 0x00;
+ *ptr++ = 0x00;
+ *ptr++ = 0x01;
+ *ptr++ = track->streamID();
+ *ptr++ = PES_packet_length >> 8;
+ *ptr++ = PES_packet_length & 0xff;
+ *ptr++ = 0x84;
+ *ptr++ = 0x80;
+ *ptr++ = 0x05;
+ *ptr++ = 0x20 | (((PTS >> 30) & 7) << 1) | 1;
+ *ptr++ = (PTS >> 22) & 0xff;
+ *ptr++ = (((PTS >> 15) & 0x7f) << 1) | 1;
+ *ptr++ = (PTS >> 7) & 0xff;
+ *ptr++ = ((PTS & 0x7f) << 1) | 1;
+
+ // 18 bytes of TS/PES header leave 188 - 18 = 170 bytes for the payload
+
+ size_t sizeLeft = packetDataStart + 188 - ptr;
+ size_t copy = accessUnit->size();
+ if (copy > sizeLeft) {
+ copy = sizeLeft;
+ }
+
+ memcpy(ptr, accessUnit->data(), copy);
+ ptr += copy;
+ CHECK_EQ(sizeLeft, copy);
+ memset(ptr, 0xff, sizeLeft - copy);
+
+ packetDataStart += 188;
+
+ size_t offset = copy;
+ while (offset < accessUnit->size()) {
+ bool padding = (accessUnit->size() - offset) < (188 - 4);
+
+ // for subsequent fragments of "buffer":
+ // 0x47
+ // transport_error_indicator = b0
+ // payload_unit_start_indicator = b0
+ // transport_priority = b0
+ // PID = b0 0001 1110 ???? (13 bits) [0x1e0 + 1 + sourceIndex]
+ // transport_scrambling_control = b00
+ // adaptation_field_control = b??
+ // continuity_counter = b????
+ // the fragment of "buffer" follows.
+
+ uint8_t *ptr = packetDataStart;
+ *ptr++ = 0x47;
+ *ptr++ = 0x00 | (track->PID() >> 8);
+ *ptr++ = track->PID() & 0xff;
+
+ *ptr++ = (padding ? 0x30 : 0x10) | track->incrementContinuityCounter();
+
+ if (padding) {
+ size_t paddingSize = 188 - 4 - (accessUnit->size() - offset);
+ *ptr++ = paddingSize - 1;
+ if (paddingSize >= 2) {
+ *ptr++ = 0x00;
+ memset(ptr, 0xff, paddingSize - 2);
+ ptr += paddingSize - 2;
+ }
+ }
+
+ // 4 bytes of TS header leave 188 - 4 = 184 bytes for the payload
+
+ size_t sizeLeft = packetDataStart + 188 - ptr;
+ size_t copy = accessUnit->size() - offset;
+ if (copy > sizeLeft) {
+ copy = sizeLeft;
+ }
+
+ memcpy(ptr, accessUnit->data() + offset, copy);
+ ptr += copy;
+ CHECK_EQ(sizeLeft, copy);
+ memset(ptr, 0xff, sizeLeft - copy);
+
+ offset += copy;
+ packetDataStart += 188;
+ }
+
+ CHECK(packetDataStart == buffer->data() + buffer->capacity());
+
+ *packets = buffer;
+
+ return OK;
+}
+
+void TSPacketizer::initCrcTable() {
+ uint32_t poly = 0x04C11DB7;
+
+ for (int i = 0; i < 256; i++) {
+ uint32_t crc = i << 24;
+ for (int j = 0; j < 8; j++) {
+ crc = (crc << 1) ^ ((crc & 0x80000000) ? (poly) : 0);
+ }
+ mCrcTable[i] = crc;
+ }
+}
+
+uint32_t TSPacketizer::crc32(const uint8_t *start, size_t size) const {
+ uint32_t crc = 0xFFFFFFFF;
+ const uint8_t *p;
+
+ for (p = start; p < start + size; ++p) {
+ crc = (crc << 8) ^ mCrcTable[((crc >> 24) ^ *p) & 0xFF];
+ }
+
+ return crc;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/source/TSPacketizer.h b/media/libstagefright/wifi-display/source/TSPacketizer.h
new file mode 100644
index 0000000..9dbeb27
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/TSPacketizer.h
@@ -0,0 +1,76 @@
+/*
+ * 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 TS_PACKETIZER_H_
+
+#define TS_PACKETIZER_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct ABuffer;
+struct AMessage;
+
+// Forms the packets of a transport stream given access units.
+// Emits metadata tables (PAT and PMT) and timestamp stream (PCR) based
+// on flags.
+struct TSPacketizer : public RefBase {
+ TSPacketizer();
+
+ // Returns trackIndex or error.
+ ssize_t addTrack(const sp<AMessage> &format);
+
+ enum {
+ EMIT_PAT_AND_PMT = 1,
+ EMIT_PCR = 2,
+ };
+ status_t packetize(
+ size_t trackIndex, const sp<ABuffer> &accessUnit,
+ sp<ABuffer> *packets,
+ uint32_t flags);
+
+protected:
+ virtual ~TSPacketizer();
+
+private:
+ enum {
+ kPID_PMT = 0x100,
+ kPID_PCR = 0x1000,
+ };
+
+ struct Track;
+
+ Vector<sp<Track> > mTracks;
+
+ unsigned mPATContinuityCounter;
+ unsigned mPMTContinuityCounter;
+
+ uint32_t mCrcTable[256];
+
+ void initCrcTable();
+ uint32_t crc32(const uint8_t *start, size_t size) const;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TSPacketizer);
+};
+
+} // namespace android
+
+#endif // TS_PACKETIZER_H_
+
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
new file mode 100644
index 0000000..3f75bc3
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
@@ -0,0 +1,944 @@
+/*
+ * 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 "WifiDisplaySource"
+#include <utils/Log.h>
+
+#include "WifiDisplaySource.h"
+#include "PlaybackSession.h"
+#include "ParsedMessage.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+WifiDisplaySource::WifiDisplaySource(const sp<ANetworkSession> &netSession)
+ : mNetSession(netSession),
+ mSessionID(0),
+ mReaperPending(false),
+ mNextCSeq(1) {
+}
+
+WifiDisplaySource::~WifiDisplaySource() {
+}
+
+status_t WifiDisplaySource::start(int32_t port) {
+ sp<AMessage> msg = new AMessage(kWhatStart, id());
+ msg->setInt32("port", port);
+
+ sp<AMessage> response;
+ status_t err = msg->postAndAwaitResponse(&response);
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (!response->findInt32("err", &err)) {
+ err = OK;
+ }
+
+ return err;
+}
+
+status_t WifiDisplaySource::stop() {
+ sp<AMessage> msg = new AMessage(kWhatStop, id());
+
+ sp<AMessage> response;
+ status_t err = msg->postAndAwaitResponse(&response);
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (!response->findInt32("err", &err)) {
+ err = OK;
+ }
+
+ return err;
+}
+
+void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatStart:
+ {
+ uint32_t replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ int32_t port;
+ CHECK(msg->findInt32("port", &port));
+
+ sp<AMessage> notify = new AMessage(kWhatRTSPNotify, id());
+
+ status_t err = mNetSession->createRTSPServer(
+ port, notify, &mSessionID);
+
+ sp<AMessage> response = new AMessage;
+ response->setInt32("err", err);
+ response->postReply(replyID);
+ 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));
+
+ mNetSession->destroySession(sessionID);
+
+ mClientIPs.removeItem(sessionID);
+ break;
+ }
+
+ case ANetworkSession::kWhatClientConnected:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ ClientInfo info;
+ CHECK(msg->findString("client-ip", &info.mRemoteIP));
+ CHECK(msg->findString("server-ip", &info.mLocalIP));
+ CHECK(msg->findInt32("server-port", &info.mLocalPort));
+
+ ALOGI("We now have a client (%d) connected.", sessionID);
+
+ mClientIPs.add(sessionID, info);
+
+ status_t err = sendM1(sessionID);
+ CHECK_EQ(err, (status_t)OK);
+ break;
+ }
+
+ case ANetworkSession::kWhatData:
+ {
+ onReceiveClientData(msg);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+ break;
+ }
+
+ case kWhatStop:
+ {
+ uint32_t replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ for (size_t i = mPlaybackSessions.size(); i-- > 0;) {
+ const sp<PlaybackSession> &playbackSession =
+ mPlaybackSessions.valueAt(i);
+
+ looper()->unregisterHandler(playbackSession->id());
+ mPlaybackSessions.removeItemsAt(i);
+ }
+
+ status_t err = OK;
+
+ sp<AMessage> response = new AMessage;
+ response->setInt32("err", err);
+ response->postReply(replyID);
+ break;
+ }
+
+ case kWhatReapDeadClients:
+ {
+ mReaperPending = false;
+
+ for (size_t i = mPlaybackSessions.size(); i-- > 0;) {
+ const sp<PlaybackSession> &playbackSession =
+ mPlaybackSessions.valueAt(i);
+
+ if (playbackSession->getLastLifesignUs()
+ + kPlaybackSessionTimeoutUs < ALooper::GetNowUs()) {
+ ALOGI("playback session %d timed out, reaping.",
+ mPlaybackSessions.keyAt(i));
+
+ looper()->unregisterHandler(playbackSession->id());
+ mPlaybackSessions.removeItemsAt(i);
+ }
+ }
+
+ if (!mPlaybackSessions.isEmpty()) {
+ scheduleReaper();
+ }
+ break;
+ }
+
+ case kWhatPlaybackSessionNotify:
+ {
+ int32_t playbackSessionID;
+ CHECK(msg->findInt32("playbackSessionID", &playbackSessionID));
+
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ ssize_t index = mPlaybackSessions.indexOfKey(playbackSessionID);
+ if (index >= 0) {
+ const sp<PlaybackSession> &playbackSession =
+ mPlaybackSessions.valueAt(index);
+
+ if (what == PlaybackSession::kWhatSessionDead) {
+ ALOGI("playback sessions %d wants to quit.",
+ playbackSessionID);
+
+ looper()->unregisterHandler(playbackSession->id());
+ mPlaybackSessions.removeItemsAt(index);
+ } else {
+ CHECK_EQ(what, PlaybackSession::kWhatBinaryData);
+
+ int32_t channel;
+ CHECK(msg->findInt32("channel", &channel));
+
+ sp<ABuffer> data;
+ CHECK(msg->findBuffer("data", &data));
+
+ CHECK_LE(channel, 0xffu);
+ CHECK_LE(data->size(), 0xffffu);
+
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ char header[4];
+ header[0] = '$';
+ header[1] = channel;
+ header[2] = data->size() >> 8;
+ header[3] = data->size() & 0xff;
+
+ mNetSession->sendRequest(
+ sessionID, header, sizeof(header));
+
+ mNetSession->sendRequest(
+ sessionID, data->data(), data->size());
+ }
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void WifiDisplaySource::registerResponseHandler(
+ int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func) {
+ ResponseID id;
+ id.mSessionID = sessionID;
+ id.mCSeq = cseq;
+ mResponseHandlers.add(id, func);
+}
+
+status_t WifiDisplaySource::sendM1(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, &WifiDisplaySource::onReceiveM1Response);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySource::sendM3(int32_t sessionID) {
+ AString body =
+ "wfd_video_formats\r\n"
+ "wfd_audio_codecs\r\n"
+ "wfd_client_rtp_ports\r\n";
+
+ AString request = "GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
+ AppendCommonResponse(&request, mNextCSeq);
+
+ request.append("Content-Type: text/parameters\r\n");
+ request.append(StringPrintf("Content-Length: %d\r\n", body.size()));
+ request.append("\r\n");
+ request.append(body);
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM3Response);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySource::sendM4(int32_t sessionID) {
+ // wfd_video_formats:
+ // 1 byte "native"
+ // 1 byte "preferred-display-mode-supported" 0 or 1
+ // one or more avc codec structures
+ // 1 byte profile
+ // 1 byte level
+ // 4 byte CEA mask
+ // 4 byte VESA mask
+ // 4 byte HH mask
+ // 1 byte latency
+ // 2 byte min-slice-slice
+ // 2 byte slice-enc-params
+ // 1 byte framerate-control-support
+ // max-hres (none or 2 byte)
+ // max-vres (none or 2 byte)
+
+ const ClientInfo &info = mClientIPs.valueFor(sessionID);
+
+ AString body = StringPrintf(
+ "wfd_video_formats: "
+ "30 00 02 02 00000040 00000000 00000000 00 0000 0000 00 none none\r\n"
+ "wfd_audio_codecs: AAC 00000001 00\r\n" // 2 ch AAC 48kHz
+ "wfd_presentation_URL: rtsp://%s:%d/wfd1.0/streamid=0 none\r\n"
+ "wfd_client_rtp_ports: RTP/AVP/UDP;unicast 19000 0 mode=play\r\n",
+ info.mLocalIP.c_str(), info.mLocalPort);
+
+ AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
+ AppendCommonResponse(&request, mNextCSeq);
+
+ request.append("Content-Type: text/parameters\r\n");
+ request.append(StringPrintf("Content-Length: %d\r\n", body.size()));
+ request.append("\r\n");
+ request.append(body);
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM4Response);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySource::sendM5(int32_t sessionID) {
+ AString body = "wfd_trigger_method: SETUP\r\n";
+
+ AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
+ AppendCommonResponse(&request, mNextCSeq);
+
+ request.append("Content-Type: text/parameters\r\n");
+ request.append(StringPrintf("Content-Length: %d\r\n", body.size()));
+ request.append("\r\n");
+ request.append(body);
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM5Response);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySource::onReceiveM1Response(
+ 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 WifiDisplaySource::onReceiveM3Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ return sendM4(sessionID);
+}
+
+status_t WifiDisplaySource::onReceiveM4Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ return sendM5(sessionID);
+}
+
+status_t WifiDisplaySource::onReceiveM5Response(
+ 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;
+}
+
+void WifiDisplaySource::scheduleReaper() {
+ if (mReaperPending) {
+ return;
+ }
+
+ mReaperPending = true;
+ (new AMessage(kWhatReapDeadClients, id()))->post(kReaperIntervalUs);
+}
+
+void WifiDisplaySource::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);
+
+ if (err != OK) {
+ ALOGW("Response handler for session %d, cseq %d returned "
+ "err %d (%s)",
+ sessionID, cseq, err, strerror(-err));
+ }
+ } else {
+ AString version;
+ data->getRequestField(2, &version);
+ if (!(version == AString("RTSP/1.0"))) {
+ sendErrorResponse(sessionID, "505 RTSP Version not supported", cseq);
+ return;
+ }
+
+ if (method == "DESCRIBE") {
+ onDescribeRequest(sessionID, cseq, data);
+ } else if (method == "OPTIONS") {
+ onOptionsRequest(sessionID, cseq, data);
+ } else if (method == "SETUP") {
+ onSetupRequest(sessionID, cseq, data);
+ } else if (method == "PLAY") {
+ onPlayRequest(sessionID, cseq, data);
+ } else if (method == "PAUSE") {
+ onPauseRequest(sessionID, cseq, data);
+ } else if (method == "TEARDOWN") {
+ onTeardownRequest(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 WifiDisplaySource::onDescribeRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ int64_t nowUs = ALooper::GetNowUs();
+
+ AString sdp;
+ sdp.append("v=0\r\n");
+
+ sdp.append(StringPrintf(
+ "o=- %lld %lld IN IP4 0.0.0.0\r\n", nowUs, nowUs));
+
+ sdp.append(
+ "o=- 0 0 IN IP4 127.0.0.0\r\n"
+ "s=Sample\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "b=AS:502\r\n"
+ "t=0 0\r\n"
+ "a=control:*\r\n"
+ "a=range:npt=now-\r\n"
+ "m=video 0 RTP/AVP 33\r\n"
+ "a=rtpmap:33 MP2T/90000\r\n"
+ "a=control:\r\n");
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq);
+
+ response.append("Content-Type: application/sdp\r\n");
+
+ // response.append("Content-Base: rtsp://0.0.0.0:7236\r\n");
+ response.append(StringPrintf("Content-Length: %d\r\n", sdp.size()));
+ response.append("\r\n");
+ response.append(sdp);
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySource::onOptionsRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ int32_t playbackSessionID;
+ sp<PlaybackSession> playbackSession =
+ findPlaybackSession(data, &playbackSessionID);
+
+ if (playbackSession != NULL) {
+ playbackSession->updateLiveness();
+ }
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq);
+
+ response.append(
+ "Public: org.wfa.wfd1.0, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, "
+ "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 = sendM3(sessionID);
+ CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySource::onSetupRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ AString transport;
+ if (!data->findString("transport", &transport)) {
+ sendErrorResponse(sessionID, "400 Bad Request", cseq);
+ return;
+ }
+
+ bool useInterleavedTCP = false;
+
+ int clientRtp, clientRtcp;
+ if (transport.startsWith("RTP/AVP/TCP;")) {
+ AString interleaved;
+ if (!ParsedMessage::GetAttribute(
+ transport.c_str(), "interleaved", &interleaved)
+ || sscanf(interleaved.c_str(), "%d-%d",
+ &clientRtp, &clientRtcp) != 2) {
+ sendErrorResponse(sessionID, "400 Bad Request", cseq);
+ return;
+ }
+
+ useInterleavedTCP = true;
+ } else if (transport.startsWith("RTP/AVP;unicast;")
+ || transport.startsWith("RTP/AVP/UDP;unicast;")) {
+ bool badRequest = false;
+
+ AString clientPort;
+ if (!ParsedMessage::GetAttribute(
+ transport.c_str(), "client_port", &clientPort)) {
+ badRequest = true;
+ } else if (sscanf(clientPort.c_str(), "%d-%d",
+ &clientRtp, &clientRtcp) == 2) {
+ } else if (sscanf(clientPort.c_str(), "%d", &clientRtp) == 1) {
+ // No RTCP.
+ clientRtcp = -1;
+ } else {
+ badRequest = true;
+ }
+
+ if (badRequest) {
+ sendErrorResponse(sessionID, "400 Bad Request", cseq);
+ return;
+ }
+#if 1
+ // The LG dongle doesn't specify client_port=xxx apparently.
+ } else if (transport == "RTP/AVP/UDP;unicast") {
+ clientRtp = 19000;
+ clientRtcp = clientRtp + 1;
+#endif
+ } else {
+ sendErrorResponse(sessionID, "461 Unsupported Transport", cseq);
+ return;
+ }
+
+ int32_t playbackSessionID = makeUniquePlaybackSessionID();
+
+ sp<AMessage> notify = new AMessage(kWhatPlaybackSessionNotify, id());
+ notify->setInt32("playbackSessionID", playbackSessionID);
+ notify->setInt32("sessionID", sessionID);
+
+ sp<PlaybackSession> playbackSession =
+ new PlaybackSession(mNetSession, notify);
+
+ looper()->registerHandler(playbackSession);
+
+ AString uri;
+ data->getRequestField(1, &uri);
+
+ if (strncasecmp("rtsp://", uri.c_str(), 7)) {
+ sendErrorResponse(sessionID, "400 Bad Request", cseq);
+ return;
+ }
+
+ if (!(uri.startsWith("rtsp://") && uri.endsWith("/wfd1.0/streamid=0"))) {
+ sendErrorResponse(sessionID, "404 Not found", cseq);
+ return;
+ }
+
+ const ClientInfo &info = mClientIPs.valueFor(sessionID);
+
+ status_t err = playbackSession->init(
+ info.mRemoteIP.c_str(),
+ clientRtp,
+ clientRtcp,
+ useInterleavedTCP);
+
+ if (err != OK) {
+ looper()->unregisterHandler(playbackSession->id());
+ playbackSession.clear();
+ }
+
+ switch (err) {
+ case OK:
+ break;
+ case -ENOENT:
+ sendErrorResponse(sessionID, "404 Not Found", cseq);
+ return;
+ default:
+ sendErrorResponse(sessionID, "403 Forbidden", cseq);
+ return;
+ }
+
+ mPlaybackSessions.add(playbackSessionID, playbackSession);
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq, playbackSessionID);
+
+ if (useInterleavedTCP) {
+ response.append(
+ StringPrintf(
+ "Transport: RTP/AVP/TCP;interleaved=%d-%d;",
+ clientRtp, clientRtcp));
+ } else {
+ int32_t serverRtp = playbackSession->getRTPPort();
+
+ if (clientRtcp >= 0) {
+ response.append(
+ StringPrintf(
+ "Transport: RTP/AVP;unicast;client_port=%d-%d;"
+ "server_port=%d-%d\r\n",
+ clientRtp, clientRtcp, serverRtp, serverRtp + 1));
+ } else {
+ response.append(
+ StringPrintf(
+ "Transport: RTP/AVP;unicast;client_port=%d;"
+ "server_port=%d\r\n",
+ clientRtp, serverRtp));
+ }
+ }
+
+ response.append("\r\n");
+
+ err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+
+#if 0
+ // XXX the dongle does not currently send keep-alives.
+ scheduleReaper();
+#endif
+}
+
+void WifiDisplaySource::onPlayRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ int32_t playbackSessionID;
+ sp<PlaybackSession> playbackSession =
+ findPlaybackSession(data, &playbackSessionID);
+
+ if (playbackSession == NULL) {
+ sendErrorResponse(sessionID, "454 Session Not Found", cseq);
+ return;
+ }
+
+ status_t err = playbackSession->play();
+ CHECK_EQ(err, (status_t)OK);
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq, playbackSessionID);
+ response.append("Range: npt=now-\r\n");
+ response.append("\r\n");
+
+ err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySource::onPauseRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ int32_t playbackSessionID;
+ sp<PlaybackSession> playbackSession =
+ findPlaybackSession(data, &playbackSessionID);
+
+ if (playbackSession == NULL) {
+ sendErrorResponse(sessionID, "454 Session Not Found", cseq);
+ return;
+ }
+
+ status_t err = playbackSession->pause();
+ CHECK_EQ(err, (status_t)OK);
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq, playbackSessionID);
+ response.append("\r\n");
+
+ err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySource::onTeardownRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ int32_t playbackSessionID;
+ sp<PlaybackSession> playbackSession =
+ findPlaybackSession(data, &playbackSessionID);
+
+ if (playbackSession == NULL) {
+ sendErrorResponse(sessionID, "454 Session Not Found", cseq);
+ return;
+ }
+
+ looper()->unregisterHandler(playbackSession->id());
+ mPlaybackSessions.removeItem(playbackSessionID);
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq, playbackSessionID);
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySource::onGetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ int32_t playbackSessionID;
+ sp<PlaybackSession> playbackSession =
+ findPlaybackSession(data, &playbackSessionID);
+
+ if (playbackSession == NULL) {
+ sendErrorResponse(sessionID, "454 Session Not Found", cseq);
+ return;
+ }
+
+ playbackSession->updateLiveness();
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq, playbackSessionID);
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySource::onSetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ int32_t playbackSessionID;
+#if 0
+ // XXX the dongle does not include a "Session:" header in this request.
+ sp<PlaybackSession> playbackSession =
+ findPlaybackSession(data, &playbackSessionID);
+
+ if (playbackSession == NULL) {
+ sendErrorResponse(sessionID, "454 Session Not Found", cseq);
+ return;
+ }
+#else
+ CHECK_EQ(mPlaybackSessions.size(), 1u);
+ playbackSessionID = mPlaybackSessions.keyAt(0);
+ sp<PlaybackSession> playbackSession = mPlaybackSessions.valueAt(0);
+#endif
+
+ playbackSession->updateLiveness();
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq, playbackSessionID);
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+}
+
+// static
+void WifiDisplaySource::AppendCommonResponse(
+ AString *response, int32_t cseq, int32_t playbackSessionID) {
+ 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("Server: Mine/1.0\r\n");
+
+ if (cseq >= 0) {
+ response->append(StringPrintf("CSeq: %d\r\n", cseq));
+ }
+
+ if (playbackSessionID >= 0ll) {
+ response->append(
+ StringPrintf(
+ "Session: %d;timeout=%lld\r\n",
+ playbackSessionID, kPlaybackSessionTimeoutSecs));
+ }
+}
+
+void WifiDisplaySource::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);
+}
+
+int32_t WifiDisplaySource::makeUniquePlaybackSessionID() const {
+ for (;;) {
+ int32_t playbackSessionID = rand();
+
+ for (size_t i = 0; i < mPlaybackSessions.size(); ++i) {
+ if (mPlaybackSessions.keyAt(i) == playbackSessionID) {
+ continue;
+ }
+ }
+
+ return playbackSessionID;
+ }
+}
+
+sp<WifiDisplaySource::PlaybackSession> WifiDisplaySource::findPlaybackSession(
+ const sp<ParsedMessage> &data, int32_t *playbackSessionID) const {
+ if (!data->findInt32("session", playbackSessionID)) {
+ *playbackSessionID = 0;
+ return NULL;
+ }
+
+ ssize_t index = mPlaybackSessions.indexOfKey(*playbackSessionID);
+ if (index < 0) {
+ return NULL;
+ }
+
+ return mPlaybackSessions.valueAt(index);
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.h b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
new file mode 100644
index 0000000..95c3560
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
@@ -0,0 +1,175 @@
+/*
+ * 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_SOURCE_H_
+
+#define WIFI_DISPLAY_SOURCE_H_
+
+#include "ANetworkSession.h"
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct ParsedMessage;
+
+// Represents the RTSP server acting as a wifi display source.
+// Manages incoming connections, sets up Playback sessions as necessary.
+struct WifiDisplaySource : public AHandler {
+ static const unsigned kWifiDisplayDefaultPort = 7236;
+
+ WifiDisplaySource(const sp<ANetworkSession> &netSession);
+
+ status_t start(int32_t port);
+ status_t stop();
+
+protected:
+ virtual ~WifiDisplaySource();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ struct PlaybackSession;
+
+ enum {
+ kWhatStart,
+ kWhatRTSPNotify,
+ kWhatStop,
+ kWhatReapDeadClients,
+ kWhatPlaybackSessionNotify,
+ };
+
+ 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 (WifiDisplaySource::*HandleRTSPResponseFunc)(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ static const int64_t kReaperIntervalUs = 1000000ll;
+
+ static const int64_t kPlaybackSessionTimeoutSecs = 30;
+
+ static const int64_t kPlaybackSessionTimeoutUs =
+ kPlaybackSessionTimeoutSecs * 1000000ll;
+
+ sp<ANetworkSession> mNetSession;
+ int32_t mSessionID;
+
+ struct ClientInfo {
+ AString mRemoteIP;
+ AString mLocalIP;
+ int32_t mLocalPort;
+ };
+ KeyedVector<int32_t, ClientInfo> mClientIPs;
+
+ bool mReaperPending;
+
+ int32_t mNextCSeq;
+
+ KeyedVector<ResponseID, HandleRTSPResponseFunc> mResponseHandlers;
+
+ KeyedVector<int32_t, sp<PlaybackSession> > mPlaybackSessions;
+
+ status_t sendM1(int32_t sessionID);
+ status_t sendM3(int32_t sessionID);
+ status_t sendM4(int32_t sessionID);
+ status_t sendM5(int32_t sessionID);
+
+ status_t onReceiveM1Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t onReceiveM3Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t onReceiveM4Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t onReceiveM5Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ void registerResponseHandler(
+ int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func);
+
+ void onReceiveClientData(const sp<AMessage> &msg);
+
+ void onDescribeRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ void onOptionsRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ void onSetupRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ void onPlayRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ void onPauseRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ void onTeardownRequest(
+ 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 sendErrorResponse(
+ int32_t sessionID,
+ const char *errorDetail,
+ int32_t cseq);
+
+ static void AppendCommonResponse(
+ AString *response, int32_t cseq, int32_t playbackSessionID = -1ll);
+
+ void scheduleReaper();
+
+ int32_t makeUniquePlaybackSessionID() const;
+
+ sp<PlaybackSession> findPlaybackSession(
+ const sp<ParsedMessage> &data, int32_t *playbackSessionID) const;
+
+ DISALLOW_EVIL_CONSTRUCTORS(WifiDisplaySource);
+};
+
+} // namespace android
+
+#endif // WIFI_DISPLAY_SOURCE_H_
diff --git a/media/libstagefright/wifi-display/wfd.cpp b/media/libstagefright/wifi-display/wfd.cpp
new file mode 100644
index 0000000..32cdf3f
--- /dev/null
+++ b/media/libstagefright/wifi-display/wfd.cpp
@@ -0,0 +1,154 @@
+/*
+ * 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 "wfd"
+#include <utils/Log.h>
+
+#define SUPPORT_SINK 0
+
+#if SUPPORT_SINK
+#include "sink/WifiDisplaySink.h"
+#endif
+
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+#include <media/IMediaPlayerService.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/foundation/ADebug.h>
+
+namespace android {
+
+static void enableDisableRemoteDisplay(bool enable) {
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> binder = sm->getService(String16("media.player"));
+
+ sp<IMediaPlayerService> service =
+ interface_cast<IMediaPlayerService>(binder);
+
+ CHECK(service.get() != NULL);
+
+ service->enableRemoteDisplay(enable);
+}
+
+} // namespace android
+
+static void usage(const char *me) {
+ fprintf(stderr,
+ "usage:\n"
+#if SUPPORT_SINK
+ " %s -c host[:port]\tconnect to wifi source\n"
+ " -u uri \tconnect to an rtsp uri\n"
+#endif
+ " -e \tenable remote display\n"
+ " -d \tdisable remote display\n",
+ me);
+}
+
+int main(int argc, char **argv) {
+ using namespace android;
+
+ ProcessState::self()->startThreadPool();
+
+ DataSource::RegisterDefaultSniffers();
+
+ AString connectToHost;
+ int32_t connectToPort = -1;
+ AString uri;
+
+ int res;
+ while ((res = getopt(argc, argv, "hc:l:u:ed")) >= 0) {
+ switch (res) {
+#if SUPPORT_SINK
+ 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;
+ }
+#endif
+
+ case 'e':
+ case 'd':
+ {
+ enableDisableRemoteDisplay(res == 'e');
+ exit(0);
+ break;
+ }
+
+ case '?':
+ case 'h':
+ default:
+ usage(argv[0]);
+ exit(1);
+ }
+ }
+
+#if SUPPORT_SINK
+ 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<ANetworkSession> session = new ANetworkSession;
+ session->start();
+
+ sp<ALooper> looper = new ALooper;
+
+ sp<WifiDisplaySink> sink = new WifiDisplaySink(session);
+ looper->registerHandler(sink);
+
+ if (connectToPort >= 0) {
+ sink->start(connectToHost.c_str(), connectToPort);
+ } else {
+ sink->start(uri.c_str());
+ }
+
+ looper->start(true /* runOnCallingThread */);
+#endif
+
+ return 0;
+}