From 35213f1420c669f43314cb75eadea450d21a75cb Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Wed, 29 Aug 2012 11:41:50 -0700 Subject: Initial checkin of support for acting as a wifi display source Change-Id: I08f17efa0c7d007e17408feb7d4fbef0a19f531a --- include/media/IMediaPlayerService.h | 2 + include/media/stagefright/ACodec.h | 11 + media/libmedia/IMediaPlayerService.cpp | 15 + media/libmediaplayerservice/Android.mk | 64 +- media/libmediaplayerservice/MediaPlayerService.cpp | 23 + media/libmediaplayerservice/MediaPlayerService.h | 3 + media/libmediaplayerservice/RemoteDisplay.cpp | 56 ++ media/libmediaplayerservice/RemoteDisplay.h | 54 ++ media/libstagefright/ACodec.cpp | 67 +- .../libstagefright/wifi-display/ANetworkSession.h | 2 + media/libstagefright/wifi-display/Android.mk | 60 +- media/libstagefright/wifi-display/ParsedMessage.h | 2 + .../wifi-display/source/Converter.cpp | 281 ++++++ .../libstagefright/wifi-display/source/Converter.h | 93 ++ .../wifi-display/source/PlaybackSession.cpp | 952 +++++++++++++++++++++ .../wifi-display/source/PlaybackSession.h | 135 +++ .../wifi-display/source/RepeaterSource.cpp | 140 +++ .../wifi-display/source/RepeaterSource.h | 55 ++ .../wifi-display/source/Serializer.cpp | 366 ++++++++ .../wifi-display/source/Serializer.h | 84 ++ .../wifi-display/source/TSPacketizer.cpp | 694 +++++++++++++++ .../wifi-display/source/TSPacketizer.h | 76 ++ .../wifi-display/source/WifiDisplaySource.cpp | 944 ++++++++++++++++++++ .../wifi-display/source/WifiDisplaySource.h | 175 ++++ media/libstagefright/wifi-display/wfd.cpp | 154 ++++ 25 files changed, 4470 insertions(+), 38 deletions(-) create mode 100644 media/libmediaplayerservice/RemoteDisplay.cpp create mode 100644 media/libmediaplayerservice/RemoteDisplay.h create mode 100644 media/libstagefright/wifi-display/source/Converter.cpp create mode 100644 media/libstagefright/wifi-display/source/Converter.h create mode 100644 media/libstagefright/wifi-display/source/PlaybackSession.cpp create mode 100644 media/libstagefright/wifi-display/source/PlaybackSession.h create mode 100644 media/libstagefright/wifi-display/source/RepeaterSource.cpp create mode 100644 media/libstagefright/wifi-display/source/RepeaterSource.h create mode 100644 media/libstagefright/wifi-display/source/Serializer.cpp create mode 100644 media/libstagefright/wifi-display/source/Serializer.h create mode 100644 media/libstagefright/wifi-display/source/TSPacketizer.cpp create mode 100644 media/libstagefright/wifi-display/source/TSPacketizer.h create mode 100644 media/libstagefright/wifi-display/source/WifiDisplaySource.cpp create mode 100644 media/libstagefright/wifi-display/source/WifiDisplaySource.h create mode 100644 media/libstagefright/wifi-display/wfd.cpp diff --git a/include/media/IMediaPlayerService.h b/include/media/IMediaPlayerService.h index 76c45a0..dbcdf92 100644 --- a/include/media/IMediaPlayerService.h +++ b/include/media/IMediaPlayerService.h @@ -50,6 +50,8 @@ public: virtual sp getOMX() = 0; virtual sp makeCrypto() = 0; + virtual status_t enableRemoteDisplay(bool enable) = 0; + // codecs and audio devices usage tracking for the battery app enum BatteryDataBits { // tracking audio codec diff --git a/include/media/stagefright/ACodec.h b/include/media/stagefright/ACodec.h index 2371619..500dde6 100644 --- a/include/media/stagefright/ACodec.h +++ b/include/media/stagefright/ACodec.h @@ -25,6 +25,8 @@ #include #include +#define TRACK_BUFFER_TIMING 0 + namespace android { struct ABuffer; @@ -127,6 +129,15 @@ private: sp mGraphicBuffer; }; +#if TRACK_BUFFER_TIMING + struct BufferStats { + int64_t mEmptyBufferTimeUs; + int64_t mFillBufferDoneTimeUs; + }; + + KeyedVector mBufferStats; +#endif + sp mNotify; sp mUninitializedState; 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(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 #include "Crypto.h" +#include "RemoteDisplay.h" namespace { using android::media::Metadata; @@ -278,6 +279,28 @@ sp 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& 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 decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat); virtual sp getOMX(); virtual sp makeCrypto(); + virtual status_t enableRemoteDisplay(bool enable); virtual status_t dump(int fd, const Vector& args); @@ -423,6 +425,7 @@ private: int32_t mNextConnId; sp mOMX; sp mCrypto; + sp 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 +#include +#include + +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 mNetLooper; + sp mLooper; + sp mNetSession; + sp 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 &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 &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 &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 &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 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 + +#include "Converter.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android { + +Converter::Converter( + const sp ¬ify, + const sp &codecLooper, + const sp &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 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 &accessUnit) { + sp 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 notify = mNotify->dup(); + notify->setInt32("what", kWhatError); + notify->setInt32("err", err); + notify->post(); +} + +void Converter::onMessageReceived(const sp &msg) { + switch (msg->what()) { + case kWhatFeedAccessUnit: + { + sp 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 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 notify = mNotify->dup(); + notify->setInt32("what", kWhatEOS); + notify->post(); + } else { + sp 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 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 + +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 ¬ify, + const sp &codecLooper, + const sp &format); + + status_t initCheck() const; + + sp getOutputFormat() const; + + void feedAccessUnit(const sp &accessUnit); + void signalEOS(); + + enum { + kWhatAccessUnit, + kWhatEOS, + kWhatError, + }; + +protected: + virtual ~Converter(); + virtual void onMessageReceived(const sp &msg); + +private: + enum { + kWhatFeedAccessUnit, + kWhatInputEOS, + kWhatDoMoreWork + }; + + status_t mInitCheck; + sp mNotify; + sp mCodecLooper; + sp mInputFormat; + sp mOutputFormat; + + sp mEncoder; + + Vector > mEncoderInputBuffers; + Vector > mEncoderOutputBuffers; + + List mAvailEncoderInputIndices; + + List > 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 + +#include "PlaybackSession.h" + +#include "Converter.h" +#include "RepeaterSource.h" +#include "Serializer.h" +#include "TSPacketizer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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); + Track(const sp &format); + + sp getFormat(); + + const sp &converter() const; + ssize_t packetizerTrackIndex() const; + + void setPacketizerTrackIndex(size_t index); + +protected: + virtual ~Track(); + +private: + sp mConverter; + sp mFormat; + ssize_t mPacketizerTrackIndex; + + DISALLOW_EVIL_CONSTRUCTORS(Track); +}; + +WifiDisplaySource::PlaybackSession::Track::Track(const sp &converter) + : mConverter(converter), + mPacketizerTrackIndex(-1) { +} + +WifiDisplaySource::PlaybackSession::Track::Track(const sp &format) + : mFormat(format), + mPacketizerTrackIndex(-1) { +} + +WifiDisplaySource::PlaybackSession::Track::~Track() { +} + +sp WifiDisplaySource::PlaybackSession::Track::getFormat() { + if (mFormat != NULL) { + return mFormat; + } + + return mConverter->getOutputFormat(); +} + +const sp &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 &netSession, + const sp ¬ify) + : 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 rtpNotify = new AMessage(kWhatRTPNotify, id()); + sp 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 sm = defaultServiceManager(); + sp binder = sm->getService(String16("SurfaceFlinger")); + sp service = interface_cast(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 &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 notify = mNotify->dup(); + notify->setInt32("what", kWhatSessionDead); + notify->post(); + break; + } + + case ANetworkSession::kWhatDatagram: + { + int32_t sessionID; + CHECK(msg->findInt32("sessionID", &sessionID)); + + sp 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 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 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 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 = 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 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 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 = + 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 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::CreateFromURI( + "/system/etc/inception_1500.mp4"); + + CHECK(dataSource != NULL); + + sp extractor = MediaExtractor::Create(dataSource); + CHECK(extractor != NULL); + + bool haveAudio = false; + bool haveVideo = false; + for (size_t i = 0; i < extractor->countTracks(); ++i) { + sp 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 source = extractor->getTrack(i); + + ssize_t index = mSerializer->addSource(source); + CHECK_GE(index, 0); + + sp 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 source = new SurfaceMediaSource(info.w, info.h); + sp source = new SurfaceMediaSource(720, 1280); + + sp sm = defaultServiceManager(); + sp binder = sm->getService(String16("SurfaceFlinger")); + sp service = interface_cast(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 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 notify = new AMessage(kWhatConverterNotify, id()); + notify->setSize("trackIndex", index); + + sp 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 &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 &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 buffer = new ABuffer(1500); + buffer->setRange(0, 0); + + addSR(buffer); + addSDES(buffer); + + if (mUseInterleavedTCP) { + sp 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 notify = mNotify->dup(); + notify->setInt32("what", kWhatBinaryData); + + sp 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 &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 >::iterator it = mHistory.begin(); + bool found = false; + while (it != mHistory.end()) { + const sp &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 &netSession, const sp ¬ify); + + 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 &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 mNetSession; + sp mNotify; + + int64_t mLastLifesignUs; + + sp mSerializerLooper; + sp mSerializer; + sp mPacketizer; + sp mCodecLooper; + + KeyedVector > mTracks; + + sp 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 > mHistory; + size_t mHistoryLength; + + void onSendSR(); + void addSR(const sp &buffer); + void addSDES(const sp &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 &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 + +#include "RepeaterSource.h" + +#include +#include +#include +#include +#include + +namespace android { + +RepeaterSource::RepeaterSource(const sp &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(this); + mLooper->registerHandler(mReflector); + + postRead(); + + return OK; +} + +status_t RepeaterSource::stop() { + if (mLooper != NULL) { + mLooper->stop(); + mLooper.clear(); + + mReflector.clear(); + } + + return mSource->stop(); +} + +sp 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 &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 +#include +#include + +namespace android { + +// This MediaSource delivers frames at a constant rate by repeating buffers +// if necessary. +struct RepeaterSource : public MediaSource { + RepeaterSource(const sp &source, double rateHz); + + virtual status_t start(MetaData *params); + virtual status_t stop(); + virtual sp getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options); + + void onMessageReceived(const sp &msg); + +protected: + virtual ~RepeaterSource(); + +private: + enum { + kWhatRead, + }; + + Mutex mLock; + Condition mCondition; + + sp mSource; + double mRateHz; + + sp mLooper; + sp > 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 + +#include "Serializer.h" + +#include +#include +#include +#include +#include +#include + +namespace android { + +struct Serializer::Track : public RefBase { + Track(const sp &source); + + status_t start(); + status_t stop(); + + void readBufferIfNecessary(); + + bool reachedEOS() const; + int64_t bufferTimeUs() const; + + sp drainBuffer(); + +protected: + virtual ~Track(); + +private: + sp mSource; + + bool mStarted; + status_t mFinalResult; + MediaBuffer *mBuffer; + int64_t mBufferTimeUs; + + DISALLOW_EVIL_CONSTRUCTORS(Track); +}; + +Serializer::Track::Track(const sp &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 Serializer::Track::drainBuffer() { + sp 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 ¬ify) + : mThrottle(throttle), + mNotify(notify), + mPollGeneration(0), + mStartTimeUs(-1ll) { +} + +Serializer::~Serializer() { +} + +status_t Serializer::postSynchronouslyAndReturnError( + const sp &msg) { + sp 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 &source) { + sp msg = new AMessage(kWhatAddSource, id()); + msg->setPointer("source", source.get()); + + sp 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 &msg) { + switch (msg->what()) { + case kWhatAddSource: + { + ssize_t index = onAddSource(msg); + + sp 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 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 &msg) { + void *obj; + CHECK(msg->findPointer("source", &obj)); + + sp source = static_cast(obj); + + sp 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 = 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 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 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 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 +#include + +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 ¬ify); + + ssize_t addSource(const sp &source); + + status_t start(); + status_t stop(); + +protected: + virtual void onMessageReceived(const sp &what); + virtual ~Serializer(); + +private: + enum { + kWhatAddSource, + kWhatStart, + kWhatStop, + kWhatPoll + }; + + struct Track; + + bool mThrottle; + sp mNotify; + Vector > mTracks; + + int32_t mPollGeneration; + + int64_t mStartTimeUs; + + status_t postSynchronouslyAndReturnError(const sp &msg); + + ssize_t onAddSource(const sp &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 + +#include "TSPacketizer.h" +#include "include/avc_utils.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace android { + +struct TSPacketizer::Track : public RefBase { + Track(const sp &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 prependCSD(const sp &accessUnit) const; + sp prependADTSHeader(const sp &accessUnit) const; + +protected: + virtual ~Track(); + +private: + sp mFormat; + + unsigned mPID; + unsigned mStreamType; + unsigned mStreamID; + unsigned mContinuityCounter; + + AString mMIME; + Vector > mCSD; + + DISALLOW_EVIL_CONSTRUCTORS(Track); +}; + +TSPacketizer::Track::Track( + const sp &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 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 TSPacketizer::Track::prependCSD( + const sp &accessUnit) const { + size_t size = 0; + for (size_t i = 0; i < mCSD.size(); ++i) { + size += mCSD.itemAt(i)->size(); + } + + sp dup = new ABuffer(accessUnit->size() + size); + size_t offset = 0; + for (size_t i = 0; i < mCSD.size(); ++i) { + const sp &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 TSPacketizer::Track::prependADTSHeader( + const sp &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 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 &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 = 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 = new Track(format, PID, streamType, streamID); + return mTracks.add(track); +} + +status_t TSPacketizer::packetize( + size_t trackIndex, + const sp &_accessUnit, + sp *packets, + uint32_t flags) { + sp accessUnit = _accessUnit; + + packets->clear(); + + if (trackIndex >= mTracks.size()) { + return -ERANGE; + } + + int64_t timeUs; + CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs)); + + const sp &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 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 = 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 +#include +#include +#include + +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 &format); + + enum { + EMIT_PAT_AND_PMT = 1, + EMIT_PCR = 2, + }; + status_t packetize( + size_t trackIndex, const sp &accessUnit, + sp *packets, + uint32_t flags); + +protected: + virtual ~TSPacketizer(); + +private: + enum { + kPID_PMT = 0x100, + kPID_PCR = 0x1000, + }; + + struct Track; + + Vector > 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 + +#include "WifiDisplaySource.h" +#include "PlaybackSession.h" +#include "ParsedMessage.h" + +#include +#include +#include +#include + +namespace android { + +WifiDisplaySource::WifiDisplaySource(const sp &netSession) + : mNetSession(netSession), + mSessionID(0), + mReaperPending(false), + mNextCSeq(1) { +} + +WifiDisplaySource::~WifiDisplaySource() { +} + +status_t WifiDisplaySource::start(int32_t port) { + sp msg = new AMessage(kWhatStart, id()); + msg->setInt32("port", port); + + sp 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 msg = new AMessage(kWhatStop, id()); + + sp 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 &msg) { + switch (msg->what()) { + case kWhatStart: + { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + int32_t port; + CHECK(msg->findInt32("port", &port)); + + sp notify = new AMessage(kWhatRTSPNotify, id()); + + status_t err = mNetSession->createRTSPServer( + port, notify, &mSessionID); + + sp 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 = + mPlaybackSessions.valueAt(i); + + looper()->unregisterHandler(playbackSession->id()); + mPlaybackSessions.removeItemsAt(i); + } + + status_t err = OK; + + sp 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 = + 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 = + 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 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 &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 &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 &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 &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 &msg) { + int32_t sessionID; + CHECK(msg->findInt32("sessionID", &sessionID)); + + sp obj; + CHECK(msg->findObject("data", &obj)); + + sp data = + static_cast(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 &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 &data) { + int32_t playbackSessionID; + sp 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 &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 notify = new AMessage(kWhatPlaybackSessionNotify, id()); + notify->setInt32("playbackSessionID", playbackSessionID); + notify->setInt32("sessionID", sessionID); + + sp 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 &data) { + int32_t playbackSessionID; + sp 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 &data) { + int32_t playbackSessionID; + sp 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 &data) { + int32_t playbackSessionID; + sp 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 &data) { + int32_t playbackSessionID; + sp 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 &data) { + int32_t playbackSessionID; +#if 0 + // XXX the dongle does not include a "Session:" header in this request. + sp 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 = 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::findPlaybackSession( + const sp &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 + +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 &netSession); + + status_t start(int32_t port); + status_t stop(); + +protected: + virtual ~WifiDisplaySource(); + virtual void onMessageReceived(const sp &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 &msg); + + static const int64_t kReaperIntervalUs = 1000000ll; + + static const int64_t kPlaybackSessionTimeoutSecs = 30; + + static const int64_t kPlaybackSessionTimeoutUs = + kPlaybackSessionTimeoutSecs * 1000000ll; + + sp mNetSession; + int32_t mSessionID; + + struct ClientInfo { + AString mRemoteIP; + AString mLocalIP; + int32_t mLocalPort; + }; + KeyedVector mClientIPs; + + bool mReaperPending; + + int32_t mNextCSeq; + + KeyedVector mResponseHandlers; + + KeyedVector > 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 &msg); + + status_t onReceiveM3Response( + int32_t sessionID, const sp &msg); + + status_t onReceiveM4Response( + int32_t sessionID, const sp &msg); + + status_t onReceiveM5Response( + int32_t sessionID, const sp &msg); + + void registerResponseHandler( + int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func); + + void onReceiveClientData(const sp &msg); + + void onDescribeRequest( + int32_t sessionID, + int32_t cseq, + const sp &data); + + void onOptionsRequest( + int32_t sessionID, + int32_t cseq, + const sp &data); + + void onSetupRequest( + int32_t sessionID, + int32_t cseq, + const sp &data); + + void onPlayRequest( + int32_t sessionID, + int32_t cseq, + const sp &data); + + void onPauseRequest( + int32_t sessionID, + int32_t cseq, + const sp &data); + + void onTeardownRequest( + int32_t sessionID, + int32_t cseq, + const sp &data); + + void onGetParameterRequest( + int32_t sessionID, + int32_t cseq, + const sp &data); + + void onSetParameterRequest( + int32_t sessionID, + int32_t cseq, + const sp &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 findPlaybackSession( + const sp &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 + +#define SUPPORT_SINK 0 + +#if SUPPORT_SINK +#include "sink/WifiDisplaySink.h" +#endif + +#include +#include +#include +#include +#include + +namespace android { + +static void enableDisableRemoteDisplay(bool enable) { + sp sm = defaultServiceManager(); + sp binder = sm->getService(String16("media.player")); + + sp service = + interface_cast(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 session = new ANetworkSession; + session->start(); + + sp looper = new ALooper; + + sp 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; +} -- cgit v1.1