├── Android.mk ├── AndroidManifest.xml ├── README.md ├── jni ├── Android.mk └── JniWifiDisplaySink.cpp ├── lib ├── ANetworkSession.cpp ├── ANetworkSession.h ├── Android.mk ├── MediaReceiver.cpp ├── MediaReceiver.h ├── Parameters.cpp ├── Parameters.h ├── PlantUtils.cpp ├── PlantUtils.h ├── TimeSyncer.cpp ├── TimeSyncer.h ├── VideoFormats.cpp ├── VideoFormats.h ├── rtp │ ├── RTPAssembler.cpp │ ├── RTPAssembler.h │ ├── RTPBase.h │ ├── RTPReceiver.cpp │ └── RTPReceiver.h └── sink │ ├── DirectRenderer.cpp │ ├── DirectRenderer.h │ ├── WifiDisplaySink.cpp │ └── WifiDisplaySink.h ├── res ├── anim │ ├── frame.xml │ └── frame2.xml ├── drawable-hdpi │ ├── app_loading0.png │ ├── app_loading1.png │ ├── ic_launcher.png │ ├── progress_loading_image_01.png │ └── progress_loading_image_02.png ├── drawable-ldpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png ├── layout │ ├── activity_waiting_connection.xml │ └── activity_wifidisplay_sink.xml ├── raw │ ├── combo_sdp.xml │ ├── keyboard_sdp_bak.xml │ ├── mouse_sdp.xml │ └── mouse_sdp_temp.xml ├── values-ja │ └── strings.xml ├── values-v11 │ └── styles.xml ├── values-v14 │ └── styles.xml ├── values-zh │ └── strings.xml └── values │ ├── arrays.xml │ ├── strings.xml │ └── styles.xml ├── src └── com │ └── lc │ └── wifidisplaysink │ ├── HidDeviceAdapterService.java │ ├── HidDeviceAdapterService.java.stub │ ├── RarpImpl.java │ ├── WaitingConnectionActivity.java │ ├── WifiDisplaySink.java │ ├── WifiDisplaySinkActivity.java │ ├── WifiDisplaySinkConstants.java │ ├── WifiDisplaySinkUtils.java │ └── WifiDisplaySinkView.java └── tags /Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH:= $(call my-dir) 2 | include $(CLEAR_VARS) 3 | 4 | LOCAL_MODULE_TAGS := optional 5 | 6 | ifeq ($(PLATFORM_VERSION), 6.0.1) 7 | WFDSINK_JAVA_PATH := "$(LOCAL_PATH)/src/com/lc/wifidisplaysink" 8 | $(info $(shell cp $(WFDSINK_JAVA_PATH)/HidDeviceAdapterService.java.stub $(WFDSINK_JAVA_PATH)/HidDeviceAdapterService.java)) 9 | endif 10 | 11 | LOCAL_SRC_FILES := $(call all-subdir-java-files) 12 | 13 | LOCAL_JAVA_LIBRARIES := com.broadcom.bt javax.obex 14 | 15 | LOCAL_PACKAGE_NAME := WifiDisplaySink 16 | LOCAL_CERTIFICATE := platform 17 | 18 | LOCAL_PROGUARD_ENABLED := disabled 19 | 20 | LOCAL_JNI_SHARED_LIBRARIES := libWifiDisplaySink 21 | LOCAL_REQUIRED_MODULES := libWifiDisplaySink 22 | 23 | include $(BUILD_PACKAGE) 24 | include $(call all-makefiles-under, $(LOCAL_PATH)) 25 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | 36 | 37 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WifiDisplaySink 2 | This is a wifi display sink implementation. 3 | The native library is extended from google's old android release. 4 | The some simple activitis is added at app layer. 5 | 6 | -------------------------------------------------------------------------------- /jni/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | 5 | LOCAL_MODULE := libWifiDisplaySink 6 | LOCAL_SRC_FILES := JniWifiDisplaySink.cpp 7 | 8 | LOCAL_C_INCLUDES += \ 9 | $(JNI_H_INCLUDE) \ 10 | $(TOP)/frameworks/native/include \ 11 | $(TOP)/frameworks/native/include/media/openmax \ 12 | $(TOP)/frameworks/base/include \ 13 | $(TOP)/frameworks/av/include/media/stagefright/foundation \ 14 | $(TOP)/leadcore/packages/apps/WifiDisplaySink/lib 15 | 16 | LOCAL_SHARED_LIBRARIES:= \ 17 | libbinder \ 18 | libgui \ 19 | libmedia \ 20 | libstagefright \ 21 | libstagefright_foundation \ 22 | libstagefright_wfd2 \ 23 | libutils \ 24 | libcutils \ 25 | libandroid_runtime \ 26 | libnativehelper \ 27 | 28 | LOCAL_CERTIFICATE := platform 29 | 30 | include $(BUILD_SHARED_LIBRARY) 31 | -------------------------------------------------------------------------------- /jni/JniWifiDisplaySink.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | //#define LOG_NDEBUG 0 6 | #define LOG_TAG "JniWifiDisplaySink" 7 | #include 8 | #include 9 | 10 | #include "sink/WifiDisplaySink.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "jni.h" 28 | #include "JNIHelp.h" 29 | #include "android_runtime/android_view_Surface.h" 30 | #include "android_runtime/AndroidRuntime.h" 31 | #include "android_runtime/Log.h" 32 | 33 | 34 | using namespace android; 35 | 36 | struct fields_t { 37 | jfieldID context; 38 | jmethodID post_event; 39 | }; 40 | static fields_t fields; 41 | 42 | class JniWfdSinkListener; 43 | 44 | struct WfdNativeContext: public RefBase { 45 | sp mWifiDisplaySink; 46 | sp mSurfaceComposerClient; 47 | sp mSurfaceControl; 48 | sp mSurface; 49 | sp mLooper; 50 | sp mListener; 51 | WfdNativeContext() { 52 | mWifiDisplaySink = NULL; 53 | mSurfaceComposerClient = NULL; 54 | mSurfaceControl = NULL; 55 | mSurface = NULL; 56 | mLooper = NULL; 57 | } 58 | ~WfdNativeContext() { 59 | mWifiDisplaySink.clear(); 60 | mSurface.clear(); 61 | mSurfaceControl.clear(); 62 | mSurfaceComposerClient.clear(); 63 | mLooper.clear(); 64 | } 65 | }; 66 | 67 | static WfdNativeContext *getWfdNativeContext(JNIEnv* env, jobject thiz) 68 | { 69 | ALOGD("getWfdNativeContext"); 70 | WfdNativeContext* context = reinterpret_cast(env->GetLongField(thiz, fields.context)); 71 | return context; 72 | } 73 | 74 | static void prepareWfdNativeContext(JNIEnv* env, jobject thiz) 75 | { 76 | ALOGD("prepareWfdNativeContext"); 77 | sp context = new WfdNativeContext(); 78 | context->incStrong((void*)prepareWfdNativeContext); 79 | 80 | env->SetLongField(thiz, fields.context, (jlong)context.get()); 81 | } 82 | 83 | class JniWfdSinkListener: public WfdSinkListener 84 | { 85 | public: 86 | JniWfdSinkListener(JNIEnv* env, jobject thiz, jobject weak_thiz); 87 | ~JniWfdSinkListener(); 88 | virtual void notify(int msg, int ext1, int ext2, const Parcel *obj = NULL); 89 | private: 90 | JniWfdSinkListener(); 91 | jclass mClass; 92 | jobject mObject; 93 | }; 94 | 95 | JniWfdSinkListener::JniWfdSinkListener(JNIEnv* env, jobject thiz, jobject weak_thiz) { 96 | jclass clazz = env->GetObjectClass(thiz); 97 | if (clazz == NULL) { 98 | ALOGE("Can't find com/lc/WifiDisplaySink/WifiDisplaySink"); 99 | jniThrowException(env, "java/lang/Exception", NULL); 100 | return; 101 | } 102 | 103 | ALOGD("inc GlobalRef about thiz and weak_thiz"); 104 | mClass = (jclass)env->NewGlobalRef(clazz); 105 | mObject = env->NewGlobalRef(weak_thiz); 106 | } 107 | 108 | JniWfdSinkListener::~JniWfdSinkListener(){ 109 | ALOGD("~JniWfdSinkListener"); 110 | JNIEnv *env = AndroidRuntime::getJNIEnv(); 111 | env->DeleteGlobalRef(mObject); 112 | env->DeleteGlobalRef(mClass); 113 | } 114 | 115 | void JniWfdSinkListener::notify(int msg, int ext1, int ext2, const Parcel *obj) { 116 | JNIEnv *env = AndroidRuntime::getJNIEnv(); 117 | 118 | ALOGD("notify %d, %d, %d", msg, ext1, ext2); 119 | 120 | if (mObject == NULL) { 121 | ALOGW("callback on dead wfdsink object"); 122 | return; 123 | } 124 | 125 | env->CallStaticVoidMethod(mClass, fields.post_event, mObject, 126 | msg, ext1, ext2, NULL); 127 | 128 | if (env->ExceptionCheck()) { 129 | ALOGW("An exception occurred while notifying an event."); 130 | env->ExceptionClear(); 131 | } 132 | } 133 | 134 | static int32_t GetInt32Property( 135 | const char *propName, int32_t defaultValue) { 136 | char val[PROPERTY_VALUE_MAX]; 137 | if (property_get(propName, val, NULL)) { 138 | char *end; 139 | unsigned long x = strtoul(val, &end, 10); 140 | 141 | if (*end == '\0' && end > val && x > 0) { 142 | return x; 143 | } 144 | } 145 | 146 | return defaultValue; 147 | } 148 | 149 | static void getDisplayDimensions(ssize_t *w, ssize_t *h) { 150 | sp display(SurfaceComposerClient::getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain)); 151 | DisplayInfo info; 152 | SurfaceComposerClient::getDisplayInfo(display, &info); 153 | bool rotate = true; 154 | int dimentionRot = GetInt32Property("media.wfd.sink.dimention", 0); 155 | ALOGI("set buffer dimention: %d", dimentionRot); 156 | if (dimentionRot > 0) { 157 | *w = info.h; 158 | *h = info.w; 159 | } else { 160 | *w = info.w; 161 | *h = info.h; 162 | } 163 | } 164 | 165 | static void 166 | com_lc_wifidisplaysink_WifiDisplaySink_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, jint special, jint is_N10) 167 | { 168 | 169 | ProcessState::self()->startThreadPool(); 170 | DataSource::RegisterDefaultSniffers(); 171 | 172 | bool specialMode = special == 1; 173 | bool isN10 = is_N10 == 1; 174 | 175 | /*sp composerClient = new SurfaceComposerClient; 176 | CHECK_EQ(composerClient->initCheck(), (status_t)OK); 177 | 178 | ssize_t displayWidth = 0; 179 | ssize_t displayHeight = 0; 180 | getDisplayDimensions(&displayWidth, &displayHeight); 181 | 182 | ALOGD("Sink Display[%d, %d] Special[%d] Nexus10[%d]", displayWidth, displayHeight, specialMode, isN10); 183 | 184 | sp control = 185 | composerClient->createSurface( 186 | String8("A Sink Surface"), 187 | // displayWidth, 188 | // displayHeight, 189 | isN10 ? displayHeight : displayWidth, 190 | isN10 ? displayWidth : displayHeight, 191 | PIXEL_FORMAT_RGB_565, 192 | 0); 193 | 194 | 195 | CHECK(control != NULL); 196 | CHECK(control->isValid()); 197 | 198 | SurfaceComposerClient::openGlobalTransaction(); 199 | CHECK_EQ(control->setLayer(INT_MAX), (status_t)OK); 200 | CHECK_EQ(control->show(), (status_t)OK); 201 | SurfaceComposerClient::closeGlobalTransaction(); 202 | 203 | sp surface = control->getSurface(); 204 | CHECK(surface != NULL);*/ 205 | 206 | sp session = new ANetworkSession; 207 | session->start(); 208 | 209 | sp looper = new ALooper; 210 | 211 | sp sink = new WifiDisplaySink( 212 | looper, 213 | specialMode ? WifiDisplaySink::FLAG_SPECIAL_MODE : 0 /* flags */, 214 | session); 215 | 216 | looper->registerHandler(sink); 217 | 218 | sp listener = new JniWfdSinkListener(env, thiz, weak_this); 219 | sink->setListener(listener); 220 | 221 | prepareWfdNativeContext(env, thiz); 222 | WfdNativeContext *ctx = getWfdNativeContext(env, thiz); 223 | //ctx->mSurfaceComposerClient = composerClient; 224 | //ctx->mSurfaceControl = control; 225 | //ctx->mSurface = surface; 226 | ctx->mWifiDisplaySink = sink; 227 | ctx->mLooper = looper; 228 | //ctx->mListener = listener; 229 | 230 | looper->start(false /* runOnCallingThread */, true /*canCallJava*/ ); 231 | 232 | ALOGD("setup finished"); 233 | //composerClient->dispose(); 234 | 235 | } 236 | 237 | static void 238 | com_lc_wifidisplaysink_WifiDisplaySink_nativeInvokeSink(JNIEnv* env, jobject thiz, jstring ipaddr, jint port) { 239 | const char *ip = env->GetStringUTFChars(ipaddr, NULL); 240 | ALOGD("Source Addr[%s] Port[%d]", ip, port); 241 | 242 | WfdNativeContext *ctx = getWfdNativeContext(env, thiz); 243 | sp sink = ctx->mWifiDisplaySink; 244 | if (sink == NULL) { 245 | ALOGE("should call setup first."); 246 | return; 247 | } 248 | 249 | 250 | ALOGD("start sink"); 251 | sink->start(ip, port); 252 | } 253 | 254 | static void 255 | com_lc_wifidisplaysink_WifiDisplaySink_native_init(JNIEnv *env) { 256 | jclass clazz; 257 | 258 | clazz = env->FindClass("com/lc/wifidisplaysink/WifiDisplaySink"); 259 | if (clazz == NULL) { 260 | return; 261 | } 262 | 263 | fields.context = env->GetFieldID(clazz, "mNativeContext", "J"); 264 | ALOGD("fields.context : %lld", fields.context); 265 | if (fields.context == NULL) { 266 | return; 267 | } 268 | fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative", 269 | "(Ljava/lang/Object;IIILjava/lang/Object;)V"); 270 | if (fields.post_event == NULL) { 271 | ALOGE("postEventFromNative is NULL"); 272 | return; 273 | } 274 | 275 | env->DeleteLocalRef(clazz); 276 | } 277 | 278 | static void 279 | com_lc_wifidisplaysink_WifiDisplaySink_native_setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface) 280 | { 281 | ALOGD("setVideoSurface"); 282 | sp new_st; 283 | if (jsurface) { 284 | sp surface(android_view_Surface_getSurface(env, jsurface)); 285 | if (surface != NULL) { 286 | new_st = surface->getIGraphicBufferProducer(); 287 | if (new_st == NULL) { 288 | jniThrowException(env, "java/lang/IllegalArgumentException", 289 | "The surface does not have a binding SurfaceTexture!"); 290 | return; 291 | } 292 | WfdNativeContext *ctx = getWfdNativeContext(env, thiz); 293 | ctx->mSurface = surface; 294 | sp sink = ctx->mWifiDisplaySink; 295 | if (sink == NULL) { 296 | ALOGE("should call setup first."); 297 | return; 298 | } 299 | sink->setDisplay(new_st); 300 | } else { 301 | jniThrowException(env, "java/lang/IllegalArgumentException", 302 | "The surface has been released"); 303 | return; 304 | } 305 | } 306 | } 307 | 308 | static JNINativeMethod gMethods[] = { 309 | {"native_init", "()V", (void *)com_lc_wifidisplaysink_WifiDisplaySink_native_init}, 310 | {"native_setup", "(Ljava/lang/Object;II)V", (void *)com_lc_wifidisplaysink_WifiDisplaySink_native_setup}, 311 | { 312 | "native_invokeSink", 313 | "(Ljava/lang/String;I)V", 314 | (void *)com_lc_wifidisplaysink_WifiDisplaySink_nativeInvokeSink 315 | }, 316 | {"native_setVideoSurface", "(Landroid/view/Surface;)V", (void *)com_lc_wifidisplaysink_WifiDisplaySink_native_setVideoSurface}, 317 | }; 318 | 319 | jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { 320 | JNIEnv* env = NULL; 321 | jint result = -1; 322 | 323 | if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { 324 | ALOGE("ERROR: GetEnv failed\n"); 325 | return -1; 326 | } 327 | 328 | assert(env != NULL); 329 | int ret = AndroidRuntime::registerNativeMethods(env, 330 | "com/lc/wifidisplaysink/WifiDisplaySink", gMethods, NELEM(gMethods)); 331 | if (ret < 0) { 332 | ALOGE("ERROR: registerNativeMethods failed\n"); 333 | return ret; 334 | } else { 335 | return JNI_VERSION_1_4; 336 | } 337 | 338 | } 339 | 340 | -------------------------------------------------------------------------------- /lib/ANetworkSession.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef A_NETWORK_SESSION_H_ 18 | 19 | #define A_NETWORK_SESSION_H_ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | namespace android { 29 | 30 | struct AMessage; 31 | 32 | // Helper class to manage a number of live sockets (datagram and stream-based) 33 | // on a single thread. Clients are notified about activity through AMessages. 34 | struct ANetworkSession : public RefBase { 35 | ANetworkSession(); 36 | 37 | status_t start(); 38 | status_t stop(); 39 | 40 | status_t createRTSPClient( 41 | const char *host, unsigned port, const sp ¬ify, 42 | int32_t *sessionID); 43 | 44 | status_t createRTSPServer( 45 | const struct in_addr &addr, unsigned port, 46 | const sp ¬ify, int32_t *sessionID); 47 | 48 | status_t createUDPSession( 49 | unsigned localPort, const sp ¬ify, int32_t *sessionID); 50 | 51 | status_t createUDPSession( 52 | unsigned localPort, 53 | const char *remoteHost, 54 | unsigned remotePort, 55 | const sp ¬ify, 56 | int32_t *sessionID); 57 | 58 | status_t connectUDPSession( 59 | int32_t sessionID, const char *remoteHost, unsigned remotePort); 60 | 61 | // passive 62 | status_t createTCPDatagramSession( 63 | const struct in_addr &addr, unsigned port, 64 | const sp ¬ify, int32_t *sessionID); 65 | 66 | // active 67 | status_t createTCPDatagramSession( 68 | unsigned localPort, 69 | const char *remoteHost, 70 | unsigned remotePort, 71 | const sp ¬ify, 72 | int32_t *sessionID); 73 | 74 | status_t destroySession(int32_t sessionID); 75 | 76 | status_t sendRequest( 77 | int32_t sessionID, const void *data, ssize_t size = -1, 78 | bool timeValid = false, int64_t timeUs = -1ll); 79 | 80 | status_t switchToWebSocketMode(int32_t sessionID); 81 | 82 | enum NotificationReason { 83 | kWhatError, 84 | kWhatConnected, 85 | kWhatClientConnected, 86 | kWhatData, 87 | kWhatDatagram, 88 | kWhatBinaryData, 89 | kWhatWebSocketMessage, 90 | kWhatNetworkStall, 91 | }; 92 | 93 | protected: 94 | virtual ~ANetworkSession(); 95 | 96 | private: 97 | struct NetworkThread; 98 | struct Session; 99 | 100 | Mutex mLock; 101 | sp mThread; 102 | 103 | int32_t mNextSessionID; 104 | 105 | int mPipeFd[2]; 106 | 107 | KeyedVector > mSessions; 108 | 109 | enum Mode { 110 | kModeCreateUDPSession, 111 | kModeCreateTCPDatagramSessionPassive, 112 | kModeCreateTCPDatagramSessionActive, 113 | kModeCreateRTSPServer, 114 | kModeCreateRTSPClient, 115 | }; 116 | status_t createClientOrServer( 117 | Mode mode, 118 | const struct in_addr *addr, 119 | unsigned port, 120 | const char *remoteHost, 121 | unsigned remotePort, 122 | const sp ¬ify, 123 | int32_t *sessionID); 124 | 125 | void threadLoop(); 126 | void interrupt(); 127 | 128 | static status_t MakeSocketNonBlocking(int s); 129 | 130 | DISALLOW_EVIL_CONSTRUCTORS(ANetworkSession); 131 | }; 132 | 133 | } // namespace android 134 | 135 | #endif // A_NETWORK_SESSION_H_ 136 | -------------------------------------------------------------------------------- /lib/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH:= $(call my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | 5 | LOCAL_SRC_FILES:= \ 6 | PlantUtils.cpp \ 7 | MediaReceiver.cpp \ 8 | Parameters.cpp \ 9 | rtp/RTPAssembler.cpp \ 10 | rtp/RTPReceiver.cpp \ 11 | sink/DirectRenderer.cpp \ 12 | sink/WifiDisplaySink.cpp \ 13 | TimeSyncer.cpp \ 14 | VideoFormats.cpp \ 15 | ANetworkSession.cpp \ 16 | 17 | LOCAL_C_INCLUDES:= \ 18 | $(TOP)/frameworks/av/media/libstagefright \ 19 | $(TOP)/frameworks/native/include/media/openmax \ 20 | $(TOP)/frameworks/av/media/libstagefright/mpeg2ts \ 21 | 22 | LOCAL_SHARED_LIBRARIES:= \ 23 | libbinder \ 24 | libcutils \ 25 | liblog \ 26 | libgui \ 27 | libmedia \ 28 | libstagefright \ 29 | libstagefright_foundation \ 30 | libui \ 31 | libutils \ 32 | 33 | ifeq ($(PLATFORM_VERSION), 6.0.1) 34 | LOCAL_CFLAGS += -DANDROID6_0 35 | else 36 | ifeq ($(PLATFORM_VERSION), 5.1.1) 37 | LOCAL_CFLAGS += -DANDROID5_1 38 | else 39 | ifeq ($(PLATFORM_VERSION), 4.4.4) 40 | LOCAL_CFLAGS += -DANDROID4_4 41 | endif 42 | endif 43 | endif 44 | 45 | LOCAL_MODULE:= libstagefright_wfd2 46 | 47 | LOCAL_MODULE_TAGS:= optional 48 | 49 | include $(BUILD_SHARED_LIBRARY) 50 | -------------------------------------------------------------------------------- /lib/MediaReceiver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | //#define LOG_NDEBUG 0 18 | #define LOG_TAG "MediaReceiver" 19 | #include 20 | 21 | #include "MediaReceiver.h" 22 | #include "AnotherPacketSource.h" 23 | #include "rtp/RTPReceiver.h" 24 | 25 | #include 26 | #include 27 | #include 28 | //#include 29 | #include "ANetworkSession.h" 30 | #include 31 | #include 32 | #include "PlantUtils.h" 33 | 34 | namespace android { 35 | 36 | MediaReceiver::MediaReceiver( 37 | const sp &netSession, 38 | const sp ¬ify) 39 | : mNetSession(netSession), 40 | mNotify(notify), 41 | mMode(MODE_UNDEFINED), 42 | mGeneration(0), 43 | mInitStatus(OK), 44 | mInitDoneCount(0) { 45 | } 46 | 47 | MediaReceiver::~MediaReceiver() { 48 | } 49 | 50 | ssize_t MediaReceiver::addTrack( 51 | RTPReceiver::TransportMode rtpMode, 52 | RTPReceiver::TransportMode rtcpMode, 53 | int32_t *localRTPPort, 54 | int32_t forceRtpPort) { 55 | if (mMode != MODE_UNDEFINED) { 56 | return INVALID_OPERATION; 57 | } 58 | 59 | size_t trackIndex = mTrackInfos.size(); 60 | 61 | TrackInfo info; 62 | 63 | sp notify = PlantUtils::newAMessage(kWhatReceiverNotify, this); 64 | notify->setInt32("generation", mGeneration); 65 | notify->setSize("trackIndex", trackIndex); 66 | 67 | info.mReceiver = new RTPReceiver(mNetSession, notify); 68 | looper()->registerHandler(info.mReceiver); 69 | 70 | info.mReceiver->registerPacketType( 71 | 33, RTPReceiver::PACKETIZATION_TRANSPORT_STREAM); 72 | 73 | info.mReceiver->registerPacketType( 74 | 96, RTPReceiver::PACKETIZATION_AAC); 75 | 76 | info.mReceiver->registerPacketType( 77 | 97, RTPReceiver::PACKETIZATION_H264); 78 | 79 | status_t err = info.mReceiver->initAsync( 80 | rtpMode, 81 | rtcpMode, 82 | localRTPPort, 83 | forceRtpPort); 84 | 85 | if (err != OK) { 86 | looper()->unregisterHandler(info.mReceiver->id()); 87 | info.mReceiver.clear(); 88 | 89 | return err; 90 | } 91 | 92 | mTrackInfos.push_back(info); 93 | 94 | return trackIndex; 95 | } 96 | 97 | status_t MediaReceiver::connectTrack( 98 | size_t trackIndex, 99 | const char *remoteHost, 100 | int32_t remoteRTPPort, 101 | int32_t remoteRTCPPort) { 102 | if (trackIndex >= mTrackInfos.size()) { 103 | return -ERANGE; 104 | } 105 | 106 | TrackInfo *info = &mTrackInfos.editItemAt(trackIndex); 107 | return info->mReceiver->connect(remoteHost, remoteRTPPort, remoteRTCPPort); 108 | } 109 | 110 | status_t MediaReceiver::initAsync(Mode mode) { 111 | if ((mode == MODE_TRANSPORT_STREAM || mode == MODE_TRANSPORT_STREAM_RAW) 112 | && mTrackInfos.size() > 1) { 113 | return INVALID_OPERATION; 114 | } 115 | 116 | sp msg = PlantUtils::newAMessage(kWhatInit, this); 117 | msg->setInt32("mode", mode); 118 | msg->post(); 119 | 120 | return OK; 121 | } 122 | 123 | void MediaReceiver::onMessageReceived(const sp &msg) { 124 | switch (msg->what()) { 125 | case kWhatInit: 126 | { 127 | int32_t mode; 128 | CHECK(msg->findInt32("mode", &mode)); 129 | 130 | CHECK_EQ(mMode, MODE_UNDEFINED); 131 | mMode = (Mode)mode; 132 | 133 | if (mInitStatus != OK || mInitDoneCount == mTrackInfos.size()) { 134 | notifyInitDone(mInitStatus); 135 | } 136 | 137 | mTSParser = new ATSParser( 138 | ATSParser::ALIGNED_VIDEO_DATA 139 | | ATSParser::TS_TIMESTAMPS_ARE_ABSOLUTE); 140 | 141 | mFormatKnownMask = 0; 142 | break; 143 | } 144 | 145 | case kWhatReceiverNotify: 146 | { 147 | int32_t generation; 148 | CHECK(msg->findInt32("generation", &generation)); 149 | if (generation != mGeneration) { 150 | break; 151 | } 152 | 153 | onReceiverNotify(msg); 154 | break; 155 | } 156 | 157 | default: 158 | TRESPASS(); 159 | } 160 | } 161 | 162 | void MediaReceiver::onReceiverNotify(const sp &msg) { 163 | int32_t what; 164 | CHECK(msg->findInt32("what", &what)); 165 | 166 | switch (what) { 167 | case RTPReceiver::kWhatInitDone: 168 | { 169 | ++mInitDoneCount; 170 | 171 | int32_t err; 172 | CHECK(msg->findInt32("err", &err)); 173 | 174 | if (err != OK) { 175 | mInitStatus = err; 176 | ++mGeneration; 177 | } 178 | 179 | if (mMode != MODE_UNDEFINED) { 180 | if (mInitStatus != OK || mInitDoneCount == mTrackInfos.size()) { 181 | notifyInitDone(mInitStatus); 182 | } 183 | } 184 | break; 185 | } 186 | 187 | case RTPReceiver::kWhatError: 188 | { 189 | int32_t err; 190 | CHECK(msg->findInt32("err", &err)); 191 | 192 | notifyError(err); 193 | break; 194 | } 195 | 196 | case RTPReceiver::kWhatAccessUnit: 197 | { 198 | size_t trackIndex; 199 | CHECK(msg->findSize("trackIndex", &trackIndex)); 200 | 201 | sp accessUnit; 202 | CHECK(msg->findBuffer("accessUnit", &accessUnit)); 203 | 204 | int32_t followsDiscontinuity; 205 | if (!msg->findInt32( 206 | "followsDiscontinuity", &followsDiscontinuity)) { 207 | followsDiscontinuity = 0; 208 | } 209 | 210 | if (mMode == MODE_TRANSPORT_STREAM) { 211 | if (followsDiscontinuity) { 212 | mTSParser->signalDiscontinuity( 213 | ATSParser::DISCONTINUITY_TIME, NULL /* extra */); 214 | } 215 | 216 | for (size_t offset = 0; 217 | offset < accessUnit->size(); offset += 188) { 218 | status_t err = mTSParser->feedTSPacket( 219 | accessUnit->data() + offset, 188); 220 | 221 | if (err != OK) { 222 | notifyError(err); 223 | break; 224 | } 225 | } 226 | 227 | drainPackets(0 /* trackIndex */, ATSParser::VIDEO); 228 | drainPackets(1 /* trackIndex */, ATSParser::AUDIO); 229 | } else { 230 | postAccessUnit(trackIndex, accessUnit, NULL); 231 | } 232 | break; 233 | } 234 | 235 | case RTPReceiver::kWhatPacketLost: 236 | { 237 | notifyPacketLost(); 238 | break; 239 | } 240 | 241 | default: 242 | TRESPASS(); 243 | } 244 | } 245 | 246 | void MediaReceiver::drainPackets( 247 | size_t trackIndex, ATSParser::SourceType type) { 248 | sp source = 249 | static_cast( 250 | mTSParser->getSource(type).get()); 251 | 252 | if (source == NULL) { 253 | return; 254 | } 255 | 256 | sp format; 257 | if (!(mFormatKnownMask & (1ul << trackIndex))) { 258 | sp meta = source->getFormat(); 259 | CHECK(meta != NULL); 260 | 261 | meta->setInt32(kKeyRotation, 90); // yangxudong 262 | ALOGD("####################kKeyRotation 90"); 263 | 264 | CHECK_EQ((status_t)OK, convertMetaDataToMessage(meta, &format)); 265 | 266 | mFormatKnownMask |= 1ul << trackIndex; 267 | } 268 | 269 | status_t finalResult; 270 | while (source->hasBufferAvailable(&finalResult)) { 271 | sp accessUnit; 272 | status_t err = source->dequeueAccessUnit(&accessUnit); 273 | if (err == OK) { 274 | postAccessUnit(trackIndex, accessUnit, format); 275 | format.clear(); 276 | } else if (err != INFO_DISCONTINUITY) { 277 | notifyError(err); 278 | } 279 | } 280 | 281 | if (finalResult != OK) { 282 | notifyError(finalResult); 283 | } 284 | } 285 | 286 | void MediaReceiver::notifyInitDone(status_t err) { 287 | sp notify = mNotify->dup(); 288 | notify->setInt32("what", kWhatInitDone); 289 | notify->setInt32("err", err); 290 | notify->post(); 291 | } 292 | 293 | void MediaReceiver::notifyError(status_t err) { 294 | sp notify = mNotify->dup(); 295 | notify->setInt32("what", kWhatError); 296 | notify->setInt32("err", err); 297 | notify->post(); 298 | } 299 | 300 | void MediaReceiver::notifyPacketLost() { 301 | sp notify = mNotify->dup(); 302 | notify->setInt32("what", kWhatPacketLost); 303 | notify->post(); 304 | } 305 | 306 | void MediaReceiver::postAccessUnit( 307 | size_t trackIndex, 308 | const sp &accessUnit, 309 | const sp &format) { 310 | sp notify = mNotify->dup(); 311 | notify->setInt32("what", kWhatAccessUnit); 312 | notify->setSize("trackIndex", trackIndex); 313 | notify->setBuffer("accessUnit", accessUnit); 314 | 315 | if (format != NULL) { 316 | notify->setMessage("format", format); 317 | } 318 | 319 | notify->post(); 320 | } 321 | 322 | status_t MediaReceiver::informSender( 323 | size_t trackIndex, const sp ¶ms) { 324 | if (trackIndex >= mTrackInfos.size()) { 325 | return -ERANGE; 326 | } 327 | 328 | TrackInfo *info = &mTrackInfos.editItemAt(trackIndex); 329 | return info->mReceiver->informSender(params); 330 | } 331 | 332 | } // namespace android 333 | 334 | 335 | -------------------------------------------------------------------------------- /lib/MediaReceiver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include "ATSParser.h" 20 | #include "rtp/RTPReceiver.h" 21 | 22 | namespace android { 23 | 24 | struct ABuffer; 25 | struct ANetworkSession; 26 | struct AMessage; 27 | struct ATSParser; 28 | 29 | // This class facilitates receiving of media data for one or more tracks 30 | // over RTP. Either a 1:1 track to RTP channel mapping is used or a single 31 | // RTP channel provides the data for a transport stream that is consequently 32 | // demuxed and its track's data provided to the observer. 33 | struct MediaReceiver : public AHandler { 34 | enum { 35 | kWhatInitDone, 36 | kWhatError, 37 | kWhatAccessUnit, 38 | kWhatPacketLost, 39 | }; 40 | 41 | MediaReceiver( 42 | const sp &netSession, 43 | const sp ¬ify); 44 | 45 | ssize_t addTrack( 46 | RTPReceiver::TransportMode rtpMode, 47 | RTPReceiver::TransportMode rtcpMode, 48 | int32_t *localRTPPort, 49 | int32_t forceRtpPort); 50 | 51 | status_t connectTrack( 52 | size_t trackIndex, 53 | const char *remoteHost, 54 | int32_t remoteRTPPort, 55 | int32_t remoteRTCPPort); 56 | 57 | enum Mode { 58 | MODE_UNDEFINED, 59 | MODE_TRANSPORT_STREAM, 60 | MODE_TRANSPORT_STREAM_RAW, 61 | MODE_ELEMENTARY_STREAMS, 62 | }; 63 | status_t initAsync(Mode mode); 64 | 65 | status_t informSender(size_t trackIndex, const sp ¶ms); 66 | 67 | protected: 68 | virtual void onMessageReceived(const sp &msg); 69 | virtual ~MediaReceiver(); 70 | 71 | private: 72 | enum { 73 | kWhatInit, 74 | kWhatReceiverNotify, 75 | }; 76 | 77 | struct TrackInfo { 78 | sp mReceiver; 79 | }; 80 | 81 | sp mNetSession; 82 | sp mNotify; 83 | 84 | Mode mMode; 85 | int32_t mGeneration; 86 | 87 | Vector mTrackInfos; 88 | 89 | status_t mInitStatus; 90 | size_t mInitDoneCount; 91 | 92 | sp mTSParser; 93 | uint32_t mFormatKnownMask; 94 | 95 | void onReceiverNotify(const sp &msg); 96 | 97 | void drainPackets(size_t trackIndex, ATSParser::SourceType type); 98 | 99 | void notifyInitDone(status_t err); 100 | void notifyError(status_t err); 101 | void notifyPacketLost(); 102 | 103 | void postAccessUnit( 104 | size_t trackIndex, 105 | const sp &accessUnit, 106 | const sp &format); 107 | 108 | DISALLOW_EVIL_CONSTRUCTORS(MediaReceiver); 109 | }; 110 | 111 | } // namespace android 112 | 113 | -------------------------------------------------------------------------------- /lib/Parameters.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Parameters.h" 18 | 19 | #include 20 | 21 | namespace android { 22 | 23 | // static 24 | sp Parameters::Parse(const char *data, size_t size) { 25 | sp params = new Parameters; 26 | status_t err = params->parse(data, size); 27 | 28 | if (err != OK) { 29 | return NULL; 30 | } 31 | 32 | return params; 33 | } 34 | 35 | Parameters::Parameters() {} 36 | 37 | Parameters::~Parameters() {} 38 | 39 | status_t Parameters::parse(const char *data, size_t size) { 40 | size_t i = 0; 41 | while (i < size) { 42 | size_t nameStart = i; 43 | while (i < size && data[i] != ':') { 44 | ++i; 45 | } 46 | 47 | if (i == size || i == nameStart) { 48 | return ERROR_MALFORMED; 49 | } 50 | 51 | AString name(&data[nameStart], i - nameStart); 52 | name.trim(); 53 | name.tolower(); 54 | 55 | ++i; 56 | 57 | size_t valueStart = i; 58 | 59 | while (i + 1 < size && (data[i] != '\r' || data[i + 1] != '\n')) { 60 | ++i; 61 | } 62 | 63 | AString value(&data[valueStart], i - valueStart); 64 | value.trim(); 65 | 66 | mDict.add(name, value); 67 | 68 | while (i + 1 < size && data[i] == '\r' && data[i + 1] == '\n') { 69 | i += 2; 70 | } 71 | } 72 | 73 | return OK; 74 | } 75 | 76 | bool Parameters::findParameter(const char *name, AString *value) const { 77 | AString key = name; 78 | key.tolower(); 79 | 80 | ssize_t index = mDict.indexOfKey(key); 81 | 82 | if (index < 0) { 83 | value->clear(); 84 | 85 | return false; 86 | } 87 | 88 | *value = mDict.valueAt(index); 89 | return true; 90 | } 91 | 92 | } // namespace android 93 | -------------------------------------------------------------------------------- /lib/Parameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace android { 23 | 24 | struct Parameters : public RefBase { 25 | static sp Parse(const char *data, size_t size); 26 | 27 | bool findParameter(const char *name, AString *value) const; 28 | 29 | protected: 30 | virtual ~Parameters(); 31 | 32 | private: 33 | KeyedVector mDict; 34 | 35 | Parameters(); 36 | status_t parse(const char *data, size_t size); 37 | 38 | DISALLOW_EVIL_CONSTRUCTORS(Parameters); 39 | }; 40 | 41 | } // namespace android 42 | -------------------------------------------------------------------------------- /lib/PlantUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "PlantUtils.h" 2 | #include 3 | 4 | namespace android { 5 | 6 | sp PlantUtils::newAMessage(uint32_t what, const sp &handler) { 7 | #ifdef ANDROID6_0 8 | return new AMessage(what, handler); 9 | #else 10 | return new AMessage(what, handler->id()); 11 | #endif 12 | } 13 | 14 | AString PlantUtils::newStringPrintf(const char *format, ...) { 15 | va_list ap; 16 | va_start(ap, format); 17 | char *buffer; 18 | vasprintf(&buffer, format, ap); 19 | va_end(ap); 20 | AString result(buffer); 21 | free(buffer); 22 | buffer = NULL; 23 | return result; 24 | } 25 | 26 | } // namespace android 27 | -------------------------------------------------------------------------------- /lib/PlantUtils.h: -------------------------------------------------------------------------------- 1 | #ifndef __PLANT_UTILS__ 2 | #define __PLANT_UTILS__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | namespace android{ 11 | 12 | class PlantUtils { 13 | public: 14 | static sp newAMessage(uint32_t what, const sp &handler); 15 | static AString newStringPrintf(const char *format, ...); 16 | }; 17 | 18 | } // namespace 19 | #endif //__PLANT_UTILS__ 20 | -------------------------------------------------------------------------------- /lib/TimeSyncer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | //#define LOG_NEBUG 0 18 | #define LOG_TAG "TimeSyncer" 19 | #include 20 | 21 | #include "TimeSyncer.h" 22 | #include "PlantUtils.h" 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | namespace android { 33 | 34 | TimeSyncer::TimeSyncer( 35 | const sp &netSession, const sp ¬ify) 36 | : mNetSession(netSession), 37 | mNotify(notify), 38 | mIsServer(false), 39 | mConnected(false), 40 | mUDPSession(0), 41 | mSeqNo(0), 42 | mTotalTimeUs(0.0), 43 | mPendingT1(0ll), 44 | mTimeoutGeneration(0) { 45 | } 46 | 47 | TimeSyncer::~TimeSyncer() { 48 | } 49 | 50 | void TimeSyncer::startServer(unsigned localPort) { 51 | sp msg = PlantUtils::newAMessage(kWhatStartServer, this); 52 | msg->setInt32("localPort", localPort); 53 | msg->post(); 54 | } 55 | 56 | void TimeSyncer::startClient(const char *remoteHost, unsigned remotePort) { 57 | sp msg = PlantUtils::newAMessage(kWhatStartClient, this); 58 | msg->setString("remoteHost", remoteHost); 59 | msg->setInt32("remotePort", remotePort); 60 | msg->post(); 61 | } 62 | 63 | void TimeSyncer::onMessageReceived(const sp &msg) { 64 | switch (msg->what()) { 65 | case kWhatStartClient: 66 | { 67 | AString remoteHost; 68 | CHECK(msg->findString("remoteHost", &remoteHost)); 69 | 70 | int32_t remotePort; 71 | CHECK(msg->findInt32("remotePort", &remotePort)); 72 | 73 | sp notify = PlantUtils::newAMessage(kWhatUDPNotify, this); 74 | 75 | CHECK_EQ((status_t)OK, 76 | mNetSession->createUDPSession( 77 | 0 /* localPort */, 78 | remoteHost.c_str(), 79 | remotePort, 80 | notify, 81 | &mUDPSession)); 82 | 83 | postSendPacket(); 84 | break; 85 | } 86 | 87 | case kWhatStartServer: 88 | { 89 | mIsServer = true; 90 | 91 | int32_t localPort; 92 | CHECK(msg->findInt32("localPort", &localPort)); 93 | 94 | sp notify = PlantUtils::newAMessage(kWhatUDPNotify, this); 95 | 96 | CHECK_EQ((status_t)OK, 97 | mNetSession->createUDPSession( 98 | localPort, notify, &mUDPSession)); 99 | 100 | break; 101 | } 102 | 103 | case kWhatSendPacket: 104 | { 105 | if (mHistory.size() == 0) { 106 | ALOGI("starting batch"); 107 | } 108 | 109 | TimeInfo ti; 110 | memset(&ti, 0, sizeof(ti)); 111 | 112 | ti.mT1 = ALooper::GetNowUs(); 113 | 114 | CHECK_EQ((status_t)OK, 115 | mNetSession->sendRequest( 116 | mUDPSession, &ti, sizeof(ti))); 117 | 118 | mPendingT1 = ti.mT1; 119 | postTimeout(); 120 | break; 121 | } 122 | 123 | case kWhatTimedOut: 124 | { 125 | int32_t generation; 126 | CHECK(msg->findInt32("generation", &generation)); 127 | 128 | if (generation != mTimeoutGeneration) { 129 | break; 130 | } 131 | 132 | ALOGI("timed out, sending another request"); 133 | postSendPacket(); 134 | break; 135 | } 136 | 137 | case kWhatUDPNotify: 138 | { 139 | int32_t reason; 140 | CHECK(msg->findInt32("reason", &reason)); 141 | 142 | switch (reason) { 143 | case ANetworkSession::kWhatError: 144 | { 145 | int32_t sessionID; 146 | CHECK(msg->findInt32("sessionID", &sessionID)); 147 | 148 | int32_t err; 149 | CHECK(msg->findInt32("err", &err)); 150 | 151 | AString detail; 152 | CHECK(msg->findString("detail", &detail)); 153 | 154 | ALOGE("An error occurred in session %d (%d, '%s/%s').", 155 | sessionID, 156 | err, 157 | detail.c_str(), 158 | strerror(-err)); 159 | 160 | mNetSession->destroySession(sessionID); 161 | 162 | cancelTimeout(); 163 | 164 | notifyError(err); 165 | break; 166 | } 167 | 168 | case ANetworkSession::kWhatDatagram: 169 | { 170 | int32_t sessionID; 171 | CHECK(msg->findInt32("sessionID", &sessionID)); 172 | 173 | sp packet; 174 | CHECK(msg->findBuffer("data", &packet)); 175 | 176 | int64_t arrivalTimeUs; 177 | CHECK(packet->meta()->findInt64( 178 | "arrivalTimeUs", &arrivalTimeUs)); 179 | 180 | CHECK_EQ(packet->size(), sizeof(TimeInfo)); 181 | 182 | TimeInfo *ti = (TimeInfo *)packet->data(); 183 | 184 | if (mIsServer) { 185 | if (!mConnected) { 186 | AString fromAddr; 187 | CHECK(msg->findString("fromAddr", &fromAddr)); 188 | 189 | int32_t fromPort; 190 | CHECK(msg->findInt32("fromPort", &fromPort)); 191 | 192 | CHECK_EQ((status_t)OK, 193 | mNetSession->connectUDPSession( 194 | mUDPSession, fromAddr.c_str(), fromPort)); 195 | 196 | mConnected = true; 197 | } 198 | 199 | ti->mT2 = arrivalTimeUs; 200 | ti->mT3 = ALooper::GetNowUs(); 201 | 202 | CHECK_EQ((status_t)OK, 203 | mNetSession->sendRequest( 204 | mUDPSession, ti, sizeof(*ti))); 205 | } else { 206 | if (ti->mT1 != mPendingT1) { 207 | break; 208 | } 209 | 210 | cancelTimeout(); 211 | mPendingT1 = 0; 212 | 213 | ti->mT4 = arrivalTimeUs; 214 | 215 | // One way delay for a packet to travel from client 216 | // to server or back (assumed to be the same either way). 217 | int64_t delay = 218 | (ti->mT2 - ti->mT1 + ti->mT4 - ti->mT3) / 2; 219 | 220 | // Offset between the client clock (T1, T4) and the 221 | // server clock (T2, T3) timestamps. 222 | int64_t offset = 223 | (ti->mT2 - ti->mT1 - ti->mT4 + ti->mT3) / 2; 224 | 225 | mHistory.push_back(*ti); 226 | 227 | ALOGV("delay = %lld us,\toffset %lld us", 228 | delay, 229 | offset); 230 | 231 | if (mHistory.size() < kNumPacketsPerBatch) { 232 | postSendPacket(1000000ll / 30); 233 | } else { 234 | notifyOffset(); 235 | 236 | ALOGI("batch done"); 237 | 238 | mHistory.clear(); 239 | postSendPacket(kBatchDelayUs); 240 | } 241 | } 242 | break; 243 | } 244 | 245 | default: 246 | TRESPASS(); 247 | } 248 | 249 | break; 250 | } 251 | 252 | default: 253 | TRESPASS(); 254 | } 255 | } 256 | 257 | void TimeSyncer::postSendPacket(int64_t delayUs) { 258 | (PlantUtils::newAMessage(kWhatSendPacket, this))->post(delayUs); 259 | } 260 | 261 | void TimeSyncer::postTimeout() { 262 | sp msg = PlantUtils::newAMessage(kWhatTimedOut, this); 263 | msg->setInt32("generation", mTimeoutGeneration); 264 | msg->post(kTimeoutDelayUs); 265 | } 266 | 267 | void TimeSyncer::cancelTimeout() { 268 | ++mTimeoutGeneration; 269 | } 270 | 271 | void TimeSyncer::notifyError(status_t err) { 272 | if (mNotify == NULL) { 273 | looper()->stop(); 274 | return; 275 | } 276 | 277 | sp notify = mNotify->dup(); 278 | notify->setInt32("what", kWhatError); 279 | notify->setInt32("err", err); 280 | notify->post(); 281 | } 282 | 283 | // static 284 | int TimeSyncer::CompareRountripTime(const TimeInfo *ti1, const TimeInfo *ti2) { 285 | int64_t rt1 = ti1->mT4 - ti1->mT1; 286 | int64_t rt2 = ti2->mT4 - ti2->mT1; 287 | 288 | if (rt1 < rt2) { 289 | return -1; 290 | } else if (rt1 > rt2) { 291 | return 1; 292 | } 293 | 294 | return 0; 295 | } 296 | 297 | void TimeSyncer::notifyOffset() { 298 | mHistory.sort(CompareRountripTime); 299 | 300 | int64_t sum = 0ll; 301 | size_t count = 0; 302 | 303 | // Only consider the third of the information associated with the best 304 | // (smallest) roundtrip times. 305 | for (size_t i = 0; i < mHistory.size() / 3; ++i) { 306 | const TimeInfo *ti = &mHistory[i]; 307 | 308 | #if 0 309 | // One way delay for a packet to travel from client 310 | // to server or back (assumed to be the same either way). 311 | int64_t delay = 312 | (ti->mT2 - ti->mT1 + ti->mT4 - ti->mT3) / 2; 313 | #endif 314 | 315 | // Offset between the client clock (T1, T4) and the 316 | // server clock (T2, T3) timestamps. 317 | int64_t offset = 318 | (ti->mT2 - ti->mT1 - ti->mT4 + ti->mT3) / 2; 319 | 320 | ALOGV("(%d) RT: %lld us, offset: %lld us", 321 | i, ti->mT4 - ti->mT1, offset); 322 | 323 | sum += offset; 324 | ++count; 325 | } 326 | 327 | if (mNotify == NULL) { 328 | ALOGI("avg. offset is %lld", sum / count); 329 | return; 330 | } 331 | 332 | sp notify = mNotify->dup(); 333 | notify->setInt32("what", kWhatTimeOffset); 334 | notify->setInt64("offset", sum / count); 335 | notify->post(); 336 | } 337 | 338 | } // namespace android 339 | -------------------------------------------------------------------------------- /lib/TimeSyncer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef TIME_SYNCER_H_ 18 | 19 | #define TIME_SYNCER_H_ 20 | 21 | #include 22 | 23 | namespace android { 24 | 25 | struct ANetworkSession; 26 | 27 | /* 28 | TimeSyncer allows us to synchronize time between a client and a server. 29 | The client sends a UDP packet containing its send-time to the server, 30 | the server sends that packet back to the client amended with information 31 | about when it was received as well as the time the reply was sent back. 32 | Finally the client receives the reply and has now enough information to 33 | compute the clock offset between client and server assuming that packet 34 | exchange is symmetric, i.e. time for a packet client->server and 35 | server->client is roughly equal. 36 | This exchange is repeated a number of times and the average offset computed 37 | over the 30% of packets that had the lowest roundtrip times. 38 | The offset is determined every 10 secs to account for slight differences in 39 | clock frequency. 40 | */ 41 | struct TimeSyncer : public AHandler { 42 | enum { 43 | kWhatError, 44 | kWhatTimeOffset, 45 | }; 46 | TimeSyncer( 47 | const sp &netSession, 48 | const sp ¬ify); 49 | 50 | void startServer(unsigned localPort); 51 | void startClient(const char *remoteHost, unsigned remotePort); 52 | 53 | protected: 54 | virtual ~TimeSyncer(); 55 | 56 | virtual void onMessageReceived(const sp &msg); 57 | 58 | private: 59 | enum { 60 | kWhatStartServer, 61 | kWhatStartClient, 62 | kWhatUDPNotify, 63 | kWhatSendPacket, 64 | kWhatTimedOut, 65 | }; 66 | 67 | struct TimeInfo { 68 | int64_t mT1; // client timestamp at send 69 | int64_t mT2; // server timestamp at receive 70 | int64_t mT3; // server timestamp at send 71 | int64_t mT4; // client timestamp at receive 72 | }; 73 | 74 | enum { 75 | kNumPacketsPerBatch = 30, 76 | }; 77 | static const int64_t kTimeoutDelayUs = 500000ll; 78 | static const int64_t kBatchDelayUs = 60000000ll; // every minute 79 | 80 | sp mNetSession; 81 | sp mNotify; 82 | 83 | bool mIsServer; 84 | bool mConnected; 85 | int32_t mUDPSession; 86 | uint32_t mSeqNo; 87 | double mTotalTimeUs; 88 | 89 | Vector mHistory; 90 | 91 | int64_t mPendingT1; 92 | int32_t mTimeoutGeneration; 93 | 94 | void postSendPacket(int64_t delayUs = 0ll); 95 | 96 | void postTimeout(); 97 | void cancelTimeout(); 98 | 99 | void notifyError(status_t err); 100 | void notifyOffset(); 101 | 102 | static int CompareRountripTime(const TimeInfo *ti1, const TimeInfo *ti2); 103 | 104 | DISALLOW_EVIL_CONSTRUCTORS(TimeSyncer); 105 | }; 106 | 107 | } // namespace android 108 | 109 | #endif // TIME_SYNCER_H_ 110 | -------------------------------------------------------------------------------- /lib/VideoFormats.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | //#define LOG_NDEBUG 0 18 | #define LOG_TAG "VideoFormats" 19 | #include 20 | 21 | #include "VideoFormats.h" 22 | #include "PlantUtils.h" 23 | 24 | #include 25 | 26 | namespace android { 27 | 28 | // static 29 | const VideoFormats::config_t VideoFormats::mResolutionTable[][32] = { 30 | { 31 | // CEA Resolutions 32 | { 640, 480, 60, false, 0, 0}, 33 | { 720, 480, 60, false, 0, 0}, 34 | { 720, 480, 60, true, 0, 0}, 35 | { 720, 576, 50, false, 0, 0}, 36 | { 720, 576, 50, true, 0, 0}, 37 | { 1280, 720, 30, false, 0, 0}, 38 | { 1280, 720, 60, false, 0, 0}, 39 | { 1920, 1080, 30, false, 0, 0}, 40 | { 1920, 1080, 60, false, 0, 0}, 41 | { 1920, 1080, 60, true, 0, 0}, 42 | { 1280, 720, 25, false, 0, 0}, 43 | { 1280, 720, 50, false, 0, 0}, 44 | { 1920, 1080, 25, false, 0, 0}, 45 | { 1920, 1080, 50, false, 0, 0}, 46 | { 1920, 1080, 50, true, 0, 0}, 47 | { 1280, 720, 24, false, 0, 0}, 48 | { 1920, 1080, 24, false, 0, 0}, 49 | { 0, 0, 0, false, 0, 0}, 50 | { 0, 0, 0, false, 0, 0}, 51 | { 0, 0, 0, false, 0, 0}, 52 | { 0, 0, 0, false, 0, 0}, 53 | { 0, 0, 0, false, 0, 0}, 54 | { 0, 0, 0, false, 0, 0}, 55 | { 0, 0, 0, false, 0, 0}, 56 | { 0, 0, 0, false, 0, 0}, 57 | { 0, 0, 0, false, 0, 0}, 58 | { 0, 0, 0, false, 0, 0}, 59 | { 0, 0, 0, false, 0, 0}, 60 | { 0, 0, 0, false, 0, 0}, 61 | { 0, 0, 0, false, 0, 0}, 62 | { 0, 0, 0, false, 0, 0}, 63 | { 0, 0, 0, false, 0, 0}, 64 | }, 65 | { 66 | // VESA Resolutions 67 | { 800, 600, 30, false, 0, 0}, 68 | { 800, 600, 60, false, 0, 0}, 69 | { 1024, 768, 30, false, 0, 0}, 70 | { 1024, 768, 60, false, 0, 0}, 71 | { 1152, 864, 30, false, 0, 0}, 72 | { 1152, 864, 60, false, 0, 0}, 73 | { 1280, 768, 30, false, 0, 0}, 74 | { 1280, 768, 60, false, 0, 0}, 75 | { 1280, 800, 30, false, 0, 0}, 76 | { 1280, 800, 60, false, 0, 0}, 77 | { 1360, 768, 30, false, 0, 0}, 78 | { 1360, 768, 60, false, 0, 0}, 79 | { 1366, 768, 30, false, 0, 0}, 80 | { 1366, 768, 60, false, 0, 0}, 81 | { 1280, 1024, 30, false, 0, 0}, 82 | { 1280, 1024, 60, false, 0, 0}, 83 | { 1400, 1050, 30, false, 0, 0}, 84 | { 1400, 1050, 60, false, 0, 0}, 85 | { 1440, 900, 30, false, 0, 0}, 86 | { 1440, 900, 60, false, 0, 0}, 87 | { 1600, 900, 30, false, 0, 0}, 88 | { 1600, 900, 60, false, 0, 0}, 89 | { 1600, 1200, 30, false, 0, 0}, 90 | { 1600, 1200, 60, false, 0, 0}, 91 | { 1680, 1024, 30, false, 0, 0}, 92 | { 1680, 1024, 60, false, 0, 0}, 93 | { 1680, 1050, 30, false, 0, 0}, 94 | { 1680, 1050, 60, false, 0, 0}, 95 | { 1920, 1200, 30, false, 0, 0}, 96 | { 1920, 1200, 60, false, 0, 0}, 97 | { 0, 0, 0, false, 0, 0}, 98 | { 0, 0, 0, false, 0, 0}, 99 | }, 100 | { 101 | // HH Resolutions 102 | { 800, 480, 30, false, 0, 0}, 103 | { 800, 480, 60, false, 0, 0}, 104 | { 854, 480, 30, false, 0, 0}, 105 | { 854, 480, 60, false, 0, 0}, 106 | { 864, 480, 30, false, 0, 0}, 107 | { 864, 480, 60, false, 0, 0}, 108 | { 640, 360, 30, false, 0, 0}, 109 | { 640, 360, 60, false, 0, 0}, 110 | { 960, 540, 30, false, 0, 0}, 111 | { 960, 540, 60, false, 0, 0}, 112 | { 848, 480, 30, false, 0, 0}, 113 | { 848, 480, 60, false, 0, 0}, 114 | { 0, 0, 0, false, 0, 0}, 115 | { 0, 0, 0, false, 0, 0}, 116 | { 0, 0, 0, false, 0, 0}, 117 | { 0, 0, 0, false, 0, 0}, 118 | { 0, 0, 0, false, 0, 0}, 119 | { 0, 0, 0, false, 0, 0}, 120 | { 0, 0, 0, false, 0, 0}, 121 | { 0, 0, 0, false, 0, 0}, 122 | { 0, 0, 0, false, 0, 0}, 123 | { 0, 0, 0, false, 0, 0}, 124 | { 0, 0, 0, false, 0, 0}, 125 | { 0, 0, 0, false, 0, 0}, 126 | { 0, 0, 0, false, 0, 0}, 127 | { 0, 0, 0, false, 0, 0}, 128 | { 0, 0, 0, false, 0, 0}, 129 | { 0, 0, 0, false, 0, 0}, 130 | { 0, 0, 0, false, 0, 0}, 131 | { 0, 0, 0, false, 0, 0}, 132 | { 0, 0, 0, false, 0, 0}, 133 | { 0, 0, 0, false, 0, 0}, 134 | } 135 | }; 136 | 137 | VideoFormats::VideoFormats() { 138 | memcpy(mConfigs, mResolutionTable, sizeof(mConfigs)); 139 | 140 | for (size_t i = 0; i < kNumResolutionTypes; ++i) { 141 | mResolutionEnabled[i] = 0; 142 | } 143 | 144 | setNativeResolution(RESOLUTION_CEA, 0); // default to 640x480 p60 145 | } 146 | 147 | void VideoFormats::setNativeResolution(ResolutionType type, size_t index) { 148 | CHECK_LT(type, kNumResolutionTypes); 149 | CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL)); 150 | 151 | mNativeType = type; 152 | mNativeIndex = index; 153 | 154 | setResolutionEnabled(type, index); 155 | } 156 | 157 | void VideoFormats::getNativeResolution( 158 | ResolutionType *type, size_t *index) const { 159 | *type = mNativeType; 160 | *index = mNativeIndex; 161 | } 162 | 163 | void VideoFormats::disableAll() { 164 | for (size_t i = 0; i < kNumResolutionTypes; ++i) { 165 | mResolutionEnabled[i] = 0; 166 | for (size_t j = 0; j < 32; j++) { 167 | mConfigs[i][j].profile = mConfigs[i][j].level = 0; 168 | } 169 | } 170 | } 171 | 172 | void VideoFormats::enableAll() { 173 | for (size_t i = 0; i < kNumResolutionTypes; ++i) { 174 | mResolutionEnabled[i] = 0xffffffff; 175 | for (size_t j = 0; j < 32; j++) { 176 | mConfigs[i][j].profile = (1ul << PROFILE_CBP); 177 | mConfigs[i][j].level = (1ul << LEVEL_31); 178 | } 179 | } 180 | } 181 | 182 | void VideoFormats::enableResolutionUpto( 183 | ResolutionType type, size_t index, 184 | ProfileType profile, LevelType level) { 185 | size_t width, height, fps, score; 186 | bool interlaced; 187 | if (!GetConfiguration(type, index, &width, &height, 188 | &fps, &interlaced)) { 189 | ALOGE("Maximum resolution not found!"); 190 | return; 191 | } 192 | score = width * height * fps * (!interlaced + 1); 193 | for (size_t i = 0; i < kNumResolutionTypes; ++i) { 194 | for (size_t j = 0; j < 32; j++) { 195 | if (GetConfiguration((ResolutionType)i, j, 196 | &width, &height, &fps, &interlaced) 197 | && score >= width * height * fps * (!interlaced + 1)) { 198 | setResolutionEnabled((ResolutionType)i, j); 199 | setProfileLevel((ResolutionType)i, j, profile, level); 200 | } 201 | } 202 | } 203 | } 204 | 205 | void VideoFormats::setResolutionEnabled( 206 | ResolutionType type, size_t index, bool enabled) { 207 | CHECK_LT(type, kNumResolutionTypes); 208 | CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL)); 209 | 210 | if (enabled) { 211 | mResolutionEnabled[type] |= (1ul << index); 212 | mConfigs[type][index].profile = (1ul << PROFILE_CBP); 213 | mConfigs[type][index].level = (1ul << LEVEL_31); 214 | } else { 215 | mResolutionEnabled[type] &= ~(1ul << index); 216 | mConfigs[type][index].profile = 0; 217 | mConfigs[type][index].level = 0; 218 | } 219 | } 220 | 221 | void VideoFormats::setProfileLevel( 222 | ResolutionType type, size_t index, 223 | ProfileType profile, LevelType level) { 224 | CHECK_LT(type, kNumResolutionTypes); 225 | CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL)); 226 | 227 | mConfigs[type][index].profile = (1ul << profile); 228 | mConfigs[type][index].level = (1ul << level); 229 | } 230 | 231 | void VideoFormats::getProfileLevel( 232 | ResolutionType type, size_t index, 233 | ProfileType *profile, LevelType *level) const{ 234 | CHECK_LT(type, kNumResolutionTypes); 235 | CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL)); 236 | 237 | int i, bestProfile = -1, bestLevel = -1; 238 | 239 | for (i = 0; i < kNumProfileTypes; ++i) { 240 | if (mConfigs[type][index].profile & (1ul << i)) { 241 | bestProfile = i; 242 | } 243 | } 244 | 245 | for (i = 0; i < kNumLevelTypes; ++i) { 246 | if (mConfigs[type][index].level & (1ul << i)) { 247 | bestLevel = i; 248 | } 249 | } 250 | 251 | if (bestProfile == -1 || bestLevel == -1) { 252 | ALOGE("Profile or level not set for resolution type %d, index %d", 253 | type, index); 254 | bestProfile = PROFILE_CBP; 255 | bestLevel = LEVEL_31; 256 | } 257 | 258 | *profile = (ProfileType) bestProfile; 259 | *level = (LevelType) bestLevel; 260 | } 261 | 262 | bool VideoFormats::isResolutionEnabled( 263 | ResolutionType type, size_t index) const { 264 | CHECK_LT(type, kNumResolutionTypes); 265 | CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL)); 266 | 267 | return mResolutionEnabled[type] & (1ul << index); 268 | } 269 | 270 | // static 271 | bool VideoFormats::GetConfiguration( 272 | ResolutionType type, 273 | size_t index, 274 | size_t *width, size_t *height, size_t *framesPerSecond, 275 | bool *interlaced) { 276 | CHECK_LT(type, kNumResolutionTypes); 277 | 278 | if (index >= 32) { 279 | return false; 280 | } 281 | 282 | const config_t *config = &mResolutionTable[type][index]; 283 | 284 | if (config->width == 0) { 285 | return false; 286 | } 287 | 288 | if (width) { 289 | *width = config->width; 290 | } 291 | 292 | if (height) { 293 | *height = config->height; 294 | } 295 | 296 | if (framesPerSecond) { 297 | *framesPerSecond = config->framesPerSecond; 298 | } 299 | 300 | if (interlaced) { 301 | *interlaced = config->interlaced; 302 | } 303 | 304 | return true; 305 | } 306 | 307 | bool VideoFormats::parseH264Codec(const char *spec) { 308 | unsigned profile, level, res[3]; 309 | 310 | if (sscanf( 311 | spec, 312 | "%02x %02x %08X %08X %08X", 313 | &profile, 314 | &level, 315 | &res[0], 316 | &res[1], 317 | &res[2]) != 5) { 318 | return false; 319 | } 320 | 321 | for (size_t i = 0; i < kNumResolutionTypes; ++i) { 322 | for (size_t j = 0; j < 32; ++j) { 323 | if (res[i] & (1ul << j)){ 324 | mResolutionEnabled[i] |= (1ul << j); 325 | if (profile > mConfigs[i][j].profile) { 326 | // prefer higher profile (even if level is lower) 327 | mConfigs[i][j].profile = profile; 328 | mConfigs[i][j].level = level; 329 | } else if (profile == mConfigs[i][j].profile && 330 | level > mConfigs[i][j].level) { 331 | mConfigs[i][j].level = level; 332 | } 333 | } 334 | } 335 | } 336 | 337 | return true; 338 | } 339 | 340 | // static 341 | bool VideoFormats::GetProfileLevel( 342 | ProfileType profile, LevelType level, unsigned *profileIdc, 343 | unsigned *levelIdc, unsigned *constraintSet) { 344 | CHECK_LT(profile, kNumProfileTypes); 345 | CHECK_LT(level, kNumLevelTypes); 346 | 347 | static const unsigned kProfileIDC[kNumProfileTypes] = { 348 | 66, // PROFILE_CBP 349 | 100, // PROFILE_CHP 350 | }; 351 | 352 | static const unsigned kLevelIDC[kNumLevelTypes] = { 353 | 31, // LEVEL_31 354 | 32, // LEVEL_32 355 | 40, // LEVEL_40 356 | 41, // LEVEL_41 357 | 42, // LEVEL_42 358 | }; 359 | 360 | static const unsigned kConstraintSet[kNumProfileTypes] = { 361 | 0xc0, // PROFILE_CBP 362 | 0x0c, // PROFILE_CHP 363 | }; 364 | 365 | if (profileIdc) { 366 | *profileIdc = kProfileIDC[profile]; 367 | } 368 | 369 | if (levelIdc) { 370 | *levelIdc = kLevelIDC[level]; 371 | } 372 | 373 | if (constraintSet) { 374 | *constraintSet = kConstraintSet[profile]; 375 | } 376 | 377 | return true; 378 | } 379 | 380 | bool VideoFormats::parseFormatSpec(const char *spec) { 381 | CHECK_EQ(kNumResolutionTypes, 3); 382 | 383 | disableAll(); 384 | 385 | unsigned native, dummy; 386 | unsigned res[3]; 387 | size_t size = strlen(spec); 388 | size_t offset = 0; 389 | 390 | if (sscanf(spec, "%02x %02x ", &native, &dummy) != 2) { 391 | return false; 392 | } 393 | 394 | offset += 6; // skip native and preferred-display-mode-supported 395 | CHECK_LE(offset + 58, size); 396 | while (offset < size) { 397 | parseH264Codec(spec + offset); 398 | offset += 60; // skip H.264-codec + ", " 399 | } 400 | 401 | mNativeIndex = native >> 3; 402 | mNativeType = (ResolutionType)(native & 7); 403 | 404 | bool success; 405 | if (mNativeType >= kNumResolutionTypes) { 406 | success = false; 407 | } else { 408 | success = GetConfiguration( 409 | mNativeType, mNativeIndex, NULL, NULL, NULL, NULL); 410 | } 411 | 412 | if (!success) { 413 | ALOGW("sink advertised an illegal native resolution, fortunately " 414 | "this value is ignored for the time being..."); 415 | } 416 | 417 | return true; 418 | } 419 | 420 | AString VideoFormats::getFormatSpec(bool forM4Message) const { 421 | CHECK_EQ(kNumResolutionTypes, 3); 422 | 423 | // wfd_video_formats: 424 | // 1 byte "native" 425 | // 1 byte "preferred-display-mode-supported" 0 or 1 426 | // one or more avc codec structures 427 | // 1 byte profile 428 | // 1 byte level 429 | // 4 byte CEA mask 430 | // 4 byte VESA mask 431 | // 4 byte HH mask 432 | // 1 byte latency 433 | // 2 byte min-slice-slice 434 | // 2 byte slice-enc-params 435 | // 1 byte framerate-control-support 436 | // max-hres (none or 2 byte) 437 | // max-vres (none or 2 byte) 438 | 439 | return PlantUtils::newStringPrintf( 440 | "%02x 00 %02x %02x %08x %08x %08x 00 0000 0000 00 none none", 441 | forM4Message ? 0x00 : ((mNativeIndex << 3) | mNativeType), 442 | mConfigs[mNativeType][mNativeIndex].profile, 443 | mConfigs[mNativeType][mNativeIndex].level, 444 | mResolutionEnabled[0], 445 | mResolutionEnabled[1], 446 | mResolutionEnabled[2]); 447 | } 448 | 449 | // static 450 | bool VideoFormats::PickBestFormat( 451 | const VideoFormats &sinkSupported, 452 | const VideoFormats &sourceSupported, 453 | ResolutionType *chosenType, 454 | size_t *chosenIndex, 455 | ProfileType *chosenProfile, 456 | LevelType *chosenLevel) { 457 | #if 0 458 | // Support for the native format is a great idea, the spec includes 459 | // these features, but nobody supports it and the tests don't validate it. 460 | 461 | ResolutionType nativeType; 462 | size_t nativeIndex; 463 | sinkSupported.getNativeResolution(&nativeType, &nativeIndex); 464 | if (sinkSupported.isResolutionEnabled(nativeType, nativeIndex)) { 465 | if (sourceSupported.isResolutionEnabled(nativeType, nativeIndex)) { 466 | ALOGI("Choosing sink's native resolution"); 467 | *chosenType = nativeType; 468 | *chosenIndex = nativeIndex; 469 | return true; 470 | } 471 | } else { 472 | ALOGW("Sink advertised native resolution that it doesn't " 473 | "actually support... ignoring"); 474 | } 475 | 476 | sourceSupported.getNativeResolution(&nativeType, &nativeIndex); 477 | if (sourceSupported.isResolutionEnabled(nativeType, nativeIndex)) { 478 | if (sinkSupported.isResolutionEnabled(nativeType, nativeIndex)) { 479 | ALOGI("Choosing source's native resolution"); 480 | *chosenType = nativeType; 481 | *chosenIndex = nativeIndex; 482 | return true; 483 | } 484 | } else { 485 | ALOGW("Source advertised native resolution that it doesn't " 486 | "actually support... ignoring"); 487 | } 488 | #endif 489 | 490 | bool first = true; 491 | uint32_t bestScore = 0; 492 | size_t bestType = 0; 493 | size_t bestIndex = 0; 494 | for (size_t i = 0; i < kNumResolutionTypes; ++i) { 495 | for (size_t j = 0; j < 32; ++j) { 496 | size_t width, height, framesPerSecond; 497 | bool interlaced; 498 | if (!GetConfiguration( 499 | (ResolutionType)i, 500 | j, 501 | &width, &height, &framesPerSecond, &interlaced)) { 502 | break; 503 | } 504 | 505 | if (!sinkSupported.isResolutionEnabled((ResolutionType)i, j) 506 | || !sourceSupported.isResolutionEnabled( 507 | (ResolutionType)i, j)) { 508 | continue; 509 | } 510 | 511 | ALOGV("type %u, index %u, %u x %u %c%u supported", 512 | i, j, width, height, interlaced ? 'i' : 'p', framesPerSecond); 513 | 514 | uint32_t score = width * height * framesPerSecond; 515 | if (!interlaced) { 516 | score *= 2; 517 | } 518 | 519 | if (first || score > bestScore) { 520 | bestScore = score; 521 | bestType = i; 522 | bestIndex = j; 523 | 524 | first = false; 525 | } 526 | } 527 | } 528 | 529 | if (first) { 530 | return false; 531 | } 532 | 533 | *chosenType = (ResolutionType)bestType; 534 | *chosenIndex = bestIndex; 535 | 536 | // Pick the best profile/level supported by both sink and source. 537 | ProfileType srcProfile, sinkProfile; 538 | LevelType srcLevel, sinkLevel; 539 | sourceSupported.getProfileLevel( 540 | (ResolutionType)bestType, bestIndex, 541 | &srcProfile, &srcLevel); 542 | sinkSupported.getProfileLevel( 543 | (ResolutionType)bestType, bestIndex, 544 | &sinkProfile, &sinkLevel); 545 | *chosenProfile = srcProfile < sinkProfile ? srcProfile : sinkProfile; 546 | *chosenLevel = srcLevel < sinkLevel ? srcLevel : sinkLevel; 547 | 548 | return true; 549 | } 550 | 551 | bool VideoFormats::getResolutionType( 552 | size_t displayWidth, 553 | size_t displayHeight, 554 | ResolutionType *chosenType, 555 | size_t *chosenIndex) { 556 | 557 | for (size_t i = 0; i < kNumResolutionTypes; ++i) { 558 | for (size_t j = 0; j < 32; ++j) { 559 | size_t width, height, framesPerSecond; 560 | bool interlaced; 561 | if (!GetConfiguration( 562 | (ResolutionType)i, 563 | j, 564 | &width, &height, &framesPerSecond, &interlaced)) { 565 | break; 566 | } 567 | if (width == displayWidth && height == displayHeight) { 568 | *chosenType = (ResolutionType)i; 569 | *chosenIndex = j; 570 | return true; 571 | } 572 | } 573 | } 574 | 575 | return false; 576 | } 577 | 578 | } // namespace android 579 | 580 | -------------------------------------------------------------------------------- /lib/VideoFormats.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef VIDEO_FORMATS_H_ 18 | 19 | #define VIDEO_FORMATS_H_ 20 | 21 | #include 22 | 23 | #include 24 | 25 | namespace android { 26 | 27 | struct AString; 28 | 29 | // This class encapsulates that video resolution capabilities of a wfd source 30 | // or sink as outlined in the wfd specs. Currently three sets of resolutions 31 | // are specified, each of which supports up to 32 resolutions. 32 | // In addition to its capabilities each sink/source also publishes its 33 | // "native" resolution, presumably one that is preferred among all others 34 | // because it wouldn't require any scaling and directly corresponds to the 35 | // display capabilities/pixels. 36 | struct VideoFormats { 37 | VideoFormats(); 38 | 39 | struct config_t { 40 | size_t width, height, framesPerSecond; 41 | bool interlaced; 42 | unsigned char profile, level; 43 | }; 44 | 45 | enum ProfileType { 46 | PROFILE_CBP = 0, 47 | PROFILE_CHP, 48 | kNumProfileTypes, 49 | }; 50 | 51 | enum LevelType { 52 | LEVEL_31 = 0, 53 | LEVEL_32, 54 | LEVEL_40, 55 | LEVEL_41, 56 | LEVEL_42, 57 | kNumLevelTypes, 58 | }; 59 | 60 | enum ResolutionType { 61 | RESOLUTION_CEA, 62 | RESOLUTION_VESA, 63 | RESOLUTION_HH, 64 | kNumResolutionTypes, 65 | }; 66 | 67 | void setNativeResolution(ResolutionType type, size_t index); 68 | void getNativeResolution(ResolutionType *type, size_t *index) const; 69 | 70 | void disableAll(); 71 | void enableAll(); 72 | void enableResolutionUpto( 73 | ResolutionType type, size_t index, 74 | ProfileType profile, LevelType level); 75 | 76 | void setResolutionEnabled( 77 | ResolutionType type, size_t index, bool enabled = true); 78 | 79 | bool isResolutionEnabled(ResolutionType type, size_t index) const; 80 | 81 | void setProfileLevel( 82 | ResolutionType type, size_t index, 83 | ProfileType profile, LevelType level); 84 | 85 | void getProfileLevel( 86 | ResolutionType type, size_t index, 87 | ProfileType *profile, LevelType *level) const; 88 | 89 | static bool GetConfiguration( 90 | ResolutionType type, size_t index, 91 | size_t *width, size_t *height, size_t *framesPerSecond, 92 | bool *interlaced); 93 | 94 | static bool GetProfileLevel( 95 | ProfileType profile, LevelType level, 96 | unsigned *profileIdc, unsigned *levelIdc, 97 | unsigned *constraintSet); 98 | 99 | bool parseFormatSpec(const char *spec); 100 | AString getFormatSpec(bool forM4Message = false) const; 101 | 102 | static bool PickBestFormat( 103 | const VideoFormats &sinkSupported, 104 | const VideoFormats &sourceSupported, 105 | ResolutionType *chosenType, 106 | size_t *chosenIndex, 107 | ProfileType *chosenProfile, 108 | LevelType *chosenLevel); 109 | 110 | static bool getResolutionType( 111 | size_t displayWidth, 112 | size_t displayHeight, 113 | ResolutionType *chosenType, 114 | size_t *chosenIndex); 115 | 116 | private: 117 | bool parseH264Codec(const char *spec); 118 | ResolutionType mNativeType; 119 | size_t mNativeIndex; 120 | 121 | uint32_t mResolutionEnabled[kNumResolutionTypes]; 122 | static const config_t mResolutionTable[kNumResolutionTypes][32]; 123 | config_t mConfigs[kNumResolutionTypes][32]; 124 | 125 | DISALLOW_EVIL_CONSTRUCTORS(VideoFormats); 126 | }; 127 | 128 | } // namespace android 129 | 130 | #endif // VIDEO_FORMATS_H_ 131 | 132 | -------------------------------------------------------------------------------- /lib/rtp/RTPAssembler.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | //#define LOG_NDEBUG 0 18 | #define LOG_TAG "RTPAssembler" 19 | #include 20 | 21 | #include "RTPAssembler.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace android { 30 | 31 | RTPReceiver::Assembler::Assembler(const sp ¬ify) 32 | : mNotify(notify) { 33 | } 34 | 35 | void RTPReceiver::Assembler::postAccessUnit( 36 | const sp &accessUnit, bool followsDiscontinuity) { 37 | sp notify = mNotify->dup(); 38 | notify->setInt32("what", RTPReceiver::kWhatAccessUnit); 39 | notify->setBuffer("accessUnit", accessUnit); 40 | notify->setInt32("followsDiscontinuity", followsDiscontinuity); 41 | notify->post(); 42 | } 43 | //////////////////////////////////////////////////////////////////////////////// 44 | 45 | RTPReceiver::TSAssembler::TSAssembler(const sp ¬ify) 46 | : Assembler(notify), 47 | mSawDiscontinuity(false) { 48 | } 49 | 50 | void RTPReceiver::TSAssembler::signalDiscontinuity() { 51 | mSawDiscontinuity = true; 52 | } 53 | 54 | status_t RTPReceiver::TSAssembler::processPacket(const sp &packet) { 55 | int32_t rtpTime; 56 | CHECK(packet->meta()->findInt32("rtp-time", &rtpTime)); 57 | 58 | packet->meta()->setInt64("timeUs", (rtpTime * 100ll) / 9); 59 | 60 | postAccessUnit(packet, mSawDiscontinuity); 61 | 62 | if (mSawDiscontinuity) { 63 | mSawDiscontinuity = false; 64 | } 65 | 66 | return OK; 67 | } 68 | 69 | //////////////////////////////////////////////////////////////////////////////// 70 | 71 | RTPReceiver::H264Assembler::H264Assembler(const sp ¬ify) 72 | : Assembler(notify), 73 | mState(0), 74 | mIndicator(0), 75 | mNALType(0), 76 | mAccessUnitRTPTime(0) { 77 | } 78 | 79 | void RTPReceiver::H264Assembler::signalDiscontinuity() { 80 | reset(); 81 | } 82 | 83 | status_t RTPReceiver::H264Assembler::processPacket(const sp &packet) { 84 | status_t err = internalProcessPacket(packet); 85 | 86 | if (err != OK) { 87 | reset(); 88 | } 89 | 90 | return err; 91 | } 92 | 93 | status_t RTPReceiver::H264Assembler::internalProcessPacket( 94 | const sp &packet) { 95 | const uint8_t *data = packet->data(); 96 | size_t size = packet->size(); 97 | 98 | switch (mState) { 99 | case 0: 100 | { 101 | if (size < 1 || (data[0] & 0x80)) { 102 | ALOGV("Malformed H264 RTP packet (empty or F-bit set)"); 103 | return ERROR_MALFORMED; 104 | } 105 | 106 | unsigned nalType = data[0] & 0x1f; 107 | if (nalType >= 1 && nalType <= 23) { 108 | addSingleNALUnit(packet); 109 | ALOGV("added single NAL packet"); 110 | } else if (nalType == 28) { 111 | // FU-A 112 | unsigned indicator = data[0]; 113 | CHECK((indicator & 0x1f) == 28); 114 | 115 | if (size < 2) { 116 | ALOGV("Malformed H264 FU-A packet (single byte)"); 117 | return ERROR_MALFORMED; 118 | } 119 | 120 | if (!(data[1] & 0x80)) { 121 | ALOGV("Malformed H264 FU-A packet (no start bit)"); 122 | return ERROR_MALFORMED; 123 | } 124 | 125 | mIndicator = data[0]; 126 | mNALType = data[1] & 0x1f; 127 | uint32_t nri = (data[0] >> 5) & 3; 128 | 129 | clearAccumulator(); 130 | 131 | uint8_t byte = mNALType | (nri << 5); 132 | appendToAccumulator(&byte, 1); 133 | appendToAccumulator(data + 2, size - 2); 134 | 135 | int32_t rtpTime; 136 | CHECK(packet->meta()->findInt32("rtp-time", &rtpTime)); 137 | mAccumulator->meta()->setInt32("rtp-time", rtpTime); 138 | 139 | if (data[1] & 0x40) { 140 | // Huh? End bit also set on the first buffer. 141 | addSingleNALUnit(mAccumulator); 142 | clearAccumulator(); 143 | 144 | ALOGV("added FU-A"); 145 | break; 146 | } 147 | 148 | mState = 1; 149 | } else if (nalType == 24) { 150 | // STAP-A 151 | 152 | status_t err = addSingleTimeAggregationPacket(packet); 153 | if (err != OK) { 154 | return err; 155 | } 156 | } else { 157 | ALOGV("Malformed H264 packet (unknown type %d)", nalType); 158 | return ERROR_UNSUPPORTED; 159 | } 160 | break; 161 | } 162 | 163 | case 1: 164 | { 165 | if (size < 2 166 | || data[0] != mIndicator 167 | || (data[1] & 0x1f) != mNALType 168 | || (data[1] & 0x80)) { 169 | ALOGV("Malformed H264 FU-A packet (indicator, " 170 | "type or start bit mismatch)"); 171 | 172 | return ERROR_MALFORMED; 173 | } 174 | 175 | appendToAccumulator(data + 2, size - 2); 176 | 177 | if (data[1] & 0x40) { 178 | addSingleNALUnit(mAccumulator); 179 | 180 | clearAccumulator(); 181 | mState = 0; 182 | 183 | ALOGV("added FU-A"); 184 | } 185 | break; 186 | } 187 | 188 | default: 189 | TRESPASS(); 190 | } 191 | 192 | int32_t marker; 193 | CHECK(packet->meta()->findInt32("M", &marker)); 194 | 195 | if (marker) { 196 | flushAccessUnit(); 197 | } 198 | 199 | return OK; 200 | } 201 | 202 | void RTPReceiver::H264Assembler::reset() { 203 | mNALUnits.clear(); 204 | 205 | clearAccumulator(); 206 | mState = 0; 207 | } 208 | 209 | void RTPReceiver::H264Assembler::clearAccumulator() { 210 | if (mAccumulator != NULL) { 211 | // XXX Too expensive. 212 | mAccumulator.clear(); 213 | } 214 | } 215 | 216 | void RTPReceiver::H264Assembler::appendToAccumulator( 217 | const void *data, size_t size) { 218 | if (mAccumulator == NULL) { 219 | mAccumulator = new ABuffer(size); 220 | memcpy(mAccumulator->data(), data, size); 221 | return; 222 | } 223 | 224 | if (mAccumulator->size() + size > mAccumulator->capacity()) { 225 | sp buf = new ABuffer(mAccumulator->size() + size); 226 | memcpy(buf->data(), mAccumulator->data(), mAccumulator->size()); 227 | buf->setRange(0, mAccumulator->size()); 228 | 229 | int32_t rtpTime; 230 | if (mAccumulator->meta()->findInt32("rtp-time", &rtpTime)) { 231 | buf->meta()->setInt32("rtp-time", rtpTime); 232 | } 233 | 234 | mAccumulator = buf; 235 | } 236 | 237 | memcpy(mAccumulator->data() + mAccumulator->size(), data, size); 238 | mAccumulator->setRange(0, mAccumulator->size() + size); 239 | } 240 | 241 | void RTPReceiver::H264Assembler::addSingleNALUnit(const sp &packet) { 242 | if (mNALUnits.empty()) { 243 | int32_t rtpTime; 244 | CHECK(packet->meta()->findInt32("rtp-time", &rtpTime)); 245 | 246 | mAccessUnitRTPTime = rtpTime; 247 | } 248 | 249 | mNALUnits.push_back(packet); 250 | } 251 | 252 | void RTPReceiver::H264Assembler::flushAccessUnit() { 253 | if (mNALUnits.empty()) { 254 | return; 255 | } 256 | 257 | size_t totalSize = 0; 258 | for (List >::iterator it = mNALUnits.begin(); 259 | it != mNALUnits.end(); ++it) { 260 | totalSize += 4 + (*it)->size(); 261 | } 262 | 263 | sp accessUnit = new ABuffer(totalSize); 264 | size_t offset = 0; 265 | for (List >::iterator it = mNALUnits.begin(); 266 | it != mNALUnits.end(); ++it) { 267 | const sp nalUnit = *it; 268 | 269 | memcpy(accessUnit->data() + offset, "\x00\x00\x00\x01", 4); 270 | 271 | memcpy(accessUnit->data() + offset + 4, 272 | nalUnit->data(), 273 | nalUnit->size()); 274 | 275 | offset += 4 + nalUnit->size(); 276 | } 277 | 278 | mNALUnits.clear(); 279 | 280 | accessUnit->meta()->setInt64("timeUs", mAccessUnitRTPTime * 100ll / 9ll); 281 | postAccessUnit(accessUnit, false /* followsDiscontinuity */); 282 | } 283 | 284 | status_t RTPReceiver::H264Assembler::addSingleTimeAggregationPacket( 285 | const sp &packet) { 286 | const uint8_t *data = packet->data(); 287 | size_t size = packet->size(); 288 | 289 | if (size < 3) { 290 | ALOGV("Malformed H264 STAP-A packet (too small)"); 291 | return ERROR_MALFORMED; 292 | } 293 | 294 | int32_t rtpTime; 295 | CHECK(packet->meta()->findInt32("rtp-time", &rtpTime)); 296 | 297 | ++data; 298 | --size; 299 | while (size >= 2) { 300 | size_t nalSize = (data[0] << 8) | data[1]; 301 | 302 | if (size < nalSize + 2) { 303 | ALOGV("Malformed H264 STAP-A packet (incomplete NAL unit)"); 304 | return ERROR_MALFORMED; 305 | } 306 | 307 | sp unit = new ABuffer(nalSize); 308 | memcpy(unit->data(), &data[2], nalSize); 309 | 310 | unit->meta()->setInt32("rtp-time", rtpTime); 311 | 312 | addSingleNALUnit(unit); 313 | 314 | data += 2 + nalSize; 315 | size -= 2 + nalSize; 316 | } 317 | 318 | if (size != 0) { 319 | ALOGV("Unexpected padding at end of STAP-A packet."); 320 | } 321 | 322 | ALOGV("added STAP-A"); 323 | 324 | return OK; 325 | } 326 | 327 | } // namespace android 328 | 329 | -------------------------------------------------------------------------------- /lib/rtp/RTPAssembler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef RTP_ASSEMBLER_H_ 18 | 19 | #define RTP_ASSEMBLER_H_ 20 | 21 | #include "RTPReceiver.h" 22 | 23 | namespace android { 24 | 25 | // A helper class to reassemble the payload of RTP packets into access 26 | // units depending on the packetization scheme. 27 | struct RTPReceiver::Assembler : public RefBase { 28 | Assembler(const sp ¬ify); 29 | 30 | virtual void signalDiscontinuity() = 0; 31 | virtual status_t processPacket(const sp &packet) = 0; 32 | 33 | protected: 34 | virtual ~Assembler() {} 35 | 36 | void postAccessUnit( 37 | const sp &accessUnit, bool followsDiscontinuity); 38 | 39 | private: 40 | sp mNotify; 41 | 42 | DISALLOW_EVIL_CONSTRUCTORS(Assembler); 43 | }; 44 | 45 | struct RTPReceiver::TSAssembler : public RTPReceiver::Assembler { 46 | TSAssembler(const sp ¬ify); 47 | 48 | virtual void signalDiscontinuity(); 49 | virtual status_t processPacket(const sp &packet); 50 | 51 | private: 52 | bool mSawDiscontinuity; 53 | 54 | DISALLOW_EVIL_CONSTRUCTORS(TSAssembler); 55 | }; 56 | 57 | struct RTPReceiver::H264Assembler : public RTPReceiver::Assembler { 58 | H264Assembler(const sp ¬ify); 59 | 60 | virtual void signalDiscontinuity(); 61 | virtual status_t processPacket(const sp &packet); 62 | 63 | private: 64 | int32_t mState; 65 | 66 | uint8_t mIndicator; 67 | uint8_t mNALType; 68 | 69 | sp mAccumulator; 70 | 71 | List > mNALUnits; 72 | int32_t mAccessUnitRTPTime; 73 | 74 | status_t internalProcessPacket(const sp &packet); 75 | 76 | void addSingleNALUnit(const sp &packet); 77 | status_t addSingleTimeAggregationPacket(const sp &packet); 78 | 79 | void flushAccessUnit(); 80 | 81 | void clearAccumulator(); 82 | void appendToAccumulator(const void *data, size_t size); 83 | 84 | void reset(); 85 | 86 | DISALLOW_EVIL_CONSTRUCTORS(H264Assembler); 87 | }; 88 | 89 | } // namespace android 90 | 91 | #endif // RTP_ASSEMBLER_H_ 92 | 93 | -------------------------------------------------------------------------------- /lib/rtp/RTPBase.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef RTP_BASE_H_ 18 | 19 | #define RTP_BASE_H_ 20 | 21 | namespace android { 22 | 23 | struct RTPBase { 24 | enum PacketizationMode { 25 | PACKETIZATION_TRANSPORT_STREAM, 26 | PACKETIZATION_H264, 27 | PACKETIZATION_AAC, 28 | PACKETIZATION_NONE, 29 | }; 30 | 31 | enum TransportMode { 32 | TRANSPORT_UNDEFINED, 33 | TRANSPORT_NONE, 34 | TRANSPORT_UDP, 35 | TRANSPORT_TCP, 36 | TRANSPORT_TCP_INTERLEAVED, 37 | }; 38 | 39 | enum { 40 | // Really UDP _payload_ size 41 | kMaxUDPPacketSize = 1472, // 1472 good, 1473 bad on Android@Home 42 | }; 43 | 44 | static int32_t PickRandomRTPPort(); 45 | }; 46 | 47 | } // namespace android 48 | 49 | #endif // RTP_BASE_H_ 50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/rtp/RTPReceiver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef RTP_RECEIVER_H_ 18 | 19 | #define RTP_RECEIVER_H_ 20 | 21 | #include "RTPBase.h" 22 | 23 | #include 24 | 25 | namespace android { 26 | 27 | struct ABuffer; 28 | struct ANetworkSession; 29 | 30 | // An object of this class facilitates receiving of media data on an RTP 31 | // channel. The channel is established over a UDP or TCP connection depending 32 | // on which "TransportMode" was chosen. In addition different RTP packetization 33 | // schemes are supported such as "Transport Stream Packets over RTP", 34 | // or "AVC/H.264 encapsulation as specified in RFC 3984 (non-interleaved mode)" 35 | struct RTPReceiver : public RTPBase, public AHandler { 36 | enum { 37 | kWhatInitDone, 38 | kWhatError, 39 | kWhatAccessUnit, 40 | kWhatPacketLost, 41 | }; 42 | 43 | enum Flags { 44 | FLAG_AUTO_CONNECT = 1, 45 | }; 46 | RTPReceiver( 47 | const sp &netSession, 48 | const sp ¬ify, 49 | uint32_t flags = 0); 50 | 51 | status_t registerPacketType( 52 | uint8_t packetType, PacketizationMode mode); 53 | 54 | status_t initAsync( 55 | TransportMode rtpMode, 56 | TransportMode rtcpMode, 57 | int32_t *outLocalRTPPort, 58 | int32_t forceRtpPort); 59 | 60 | status_t connect( 61 | const char *remoteHost, 62 | int32_t remoteRTPPort, 63 | int32_t remoteRTCPPort); 64 | 65 | status_t informSender(const sp ¶ms); 66 | 67 | protected: 68 | virtual ~RTPReceiver(); 69 | virtual void onMessageReceived(const sp &msg); 70 | 71 | private: 72 | enum { 73 | kWhatRTPNotify, 74 | kWhatRTCPNotify, 75 | kWhatSendRR, 76 | }; 77 | 78 | enum { 79 | kSourceID = 0xdeadbeef, 80 | kPacketLostAfterUs = 100000, 81 | kRequestRetransmissionAfterUs = -1, 82 | }; 83 | 84 | struct Assembler; 85 | struct H264Assembler; 86 | struct Source; 87 | struct TSAssembler; 88 | 89 | sp mNetSession; 90 | sp mNotify; 91 | uint32_t mFlags; 92 | TransportMode mRTPMode; 93 | TransportMode mRTCPMode; 94 | int32_t mRTPSessionID; 95 | int32_t mRTCPSessionID; 96 | bool mRTPConnected; 97 | bool mRTCPConnected; 98 | 99 | int32_t mRTPClientSessionID; // in TRANSPORT_TCP mode. 100 | int32_t mRTCPClientSessionID; // in TRANSPORT_TCP mode. 101 | 102 | KeyedVector mPacketTypes; 103 | KeyedVector > mSources; 104 | 105 | void onNetNotify(bool isRTP, const sp &msg); 106 | status_t onRTPData(const sp &data); 107 | status_t onRTCPData(const sp &data); 108 | void onSendRR(); 109 | 110 | void scheduleSendRR(); 111 | void addSDES(const sp &buffer); 112 | 113 | void notifyInitDone(status_t err); 114 | void notifyError(status_t err); 115 | void notifyPacketLost(); 116 | 117 | sp makeAssembler(uint8_t packetType); 118 | 119 | void requestRetransmission(uint32_t senderSSRC, int32_t extSeqNo); 120 | 121 | DISALLOW_EVIL_CONSTRUCTORS(RTPReceiver); 122 | }; 123 | 124 | } // namespace android 125 | 126 | #endif // RTP_RECEIVER_H_ 127 | -------------------------------------------------------------------------------- /lib/sink/DirectRenderer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | //#define LOG_NDEBUG 0 18 | #define LOG_TAG "DirectRenderer" 19 | #include 20 | 21 | #include "DirectRenderer.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "PlantUtils.h" 36 | 37 | namespace android { 38 | 39 | /* 40 | Drives the decoding process using a MediaCodec instance. Input buffers 41 | queued by calls to "queueInputBuffer" are fed to the decoder as soon 42 | as the decoder is ready for them, the client is notified about output 43 | buffers as the decoder spits them out. 44 | */ 45 | struct DirectRenderer::DecoderContext : public AHandler { 46 | enum { 47 | kWhatOutputBufferReady, 48 | }; 49 | DecoderContext(const sp ¬ify); 50 | 51 | status_t init( 52 | const sp &format, 53 | const sp &surfaceTex); 54 | 55 | void queueInputBuffer(const sp &accessUnit); 56 | 57 | status_t renderOutputBufferAndRelease(size_t index); 58 | status_t releaseOutputBuffer(size_t index); 59 | 60 | protected: 61 | virtual ~DecoderContext(); 62 | 63 | virtual void onMessageReceived(const sp &msg); 64 | 65 | private: 66 | enum { 67 | kWhatDecoderNotify, 68 | }; 69 | 70 | sp mNotify; 71 | sp mDecoderLooper; 72 | sp mDecoder; 73 | Vector > mDecoderInputBuffers; 74 | Vector > mDecoderOutputBuffers; 75 | List mDecoderInputBuffersAvailable; 76 | bool mDecoderNotificationPending; 77 | 78 | List > mAccessUnits; 79 | 80 | void onDecoderNotify(); 81 | void scheduleDecoderNotification(); 82 | void queueDecoderInputBuffers(); 83 | 84 | void queueOutputBuffer( 85 | size_t index, int64_t timeUs, const sp &buffer); 86 | 87 | DISALLOW_EVIL_CONSTRUCTORS(DecoderContext); 88 | }; 89 | 90 | //////////////////////////////////////////////////////////////////////////////// 91 | 92 | /* 93 | A "push" audio renderer. The primary function of this renderer is to use 94 | an AudioTrack in push mode and making sure not to block the event loop 95 | be ensuring that calls to AudioTrack::write never block. This is done by 96 | estimating an upper bound of data that can be written to the AudioTrack 97 | buffer without delay. 98 | */ 99 | struct DirectRenderer::AudioRenderer : public AHandler { 100 | AudioRenderer(const sp &decoderContext); 101 | 102 | void queueInputBuffer( 103 | size_t index, int64_t timeUs, const sp &buffer); 104 | 105 | protected: 106 | virtual ~AudioRenderer(); 107 | virtual void onMessageReceived(const sp &msg); 108 | 109 | private: 110 | enum { 111 | kWhatPushAudio, 112 | }; 113 | 114 | struct BufferInfo { 115 | size_t mIndex; 116 | int64_t mTimeUs; 117 | sp mBuffer; 118 | }; 119 | 120 | sp mDecoderContext; 121 | sp mAudioTrack; 122 | 123 | List mInputBuffers; 124 | bool mPushPending; 125 | 126 | size_t mNumFramesWritten; 127 | 128 | void schedulePushIfNecessary(); 129 | void onPushAudio(); 130 | 131 | ssize_t writeNonBlocking(const uint8_t *data, size_t size); 132 | 133 | DISALLOW_EVIL_CONSTRUCTORS(AudioRenderer); 134 | }; 135 | 136 | //////////////////////////////////////////////////////////////////////////////// 137 | 138 | DirectRenderer::DecoderContext::DecoderContext(const sp ¬ify) 139 | : mNotify(notify), 140 | mDecoderNotificationPending(false) { 141 | } 142 | 143 | DirectRenderer::DecoderContext::~DecoderContext() { 144 | if (mDecoder != NULL) { 145 | mDecoder->release(); 146 | mDecoder.clear(); 147 | 148 | mDecoderLooper->stop(); 149 | mDecoderLooper.clear(); 150 | } 151 | } 152 | 153 | status_t DirectRenderer::DecoderContext::init( 154 | const sp &format, 155 | const sp &surfaceTex) { 156 | CHECK(mDecoder == NULL); 157 | 158 | AString mime; 159 | CHECK(format->findString("mime", &mime)); 160 | 161 | mDecoderLooper = new ALooper; 162 | mDecoderLooper->setName("video codec looper"); 163 | 164 | mDecoderLooper->start( 165 | false /* runOnCallingThread */, 166 | false /* canCallJava */, 167 | PRIORITY_DEFAULT); 168 | 169 | mDecoder = MediaCodec::CreateByType( 170 | mDecoderLooper, mime.c_str(), false /* encoder */); 171 | 172 | CHECK(mDecoder != NULL); 173 | 174 | status_t err = mDecoder->configure( 175 | format, 176 | surfaceTex == NULL 177 | ? NULL : new Surface(surfaceTex), 178 | NULL /* crypto */, 179 | 0 /* flags */); 180 | CHECK_EQ(err, (status_t)OK); 181 | 182 | err = mDecoder->start(); 183 | CHECK_EQ(err, (status_t)OK); 184 | 185 | err = mDecoder->getInputBuffers( 186 | &mDecoderInputBuffers); 187 | CHECK_EQ(err, (status_t)OK); 188 | 189 | err = mDecoder->getOutputBuffers( 190 | &mDecoderOutputBuffers); 191 | CHECK_EQ(err, (status_t)OK); 192 | 193 | scheduleDecoderNotification(); 194 | 195 | return OK; 196 | } 197 | 198 | void DirectRenderer::DecoderContext::queueInputBuffer( 199 | const sp &accessUnit) { 200 | CHECK(mDecoder != NULL); 201 | 202 | mAccessUnits.push_back(accessUnit); 203 | queueDecoderInputBuffers(); 204 | } 205 | 206 | status_t DirectRenderer::DecoderContext::renderOutputBufferAndRelease( 207 | size_t index) { 208 | return mDecoder->renderOutputBufferAndRelease(index); 209 | } 210 | 211 | status_t DirectRenderer::DecoderContext::releaseOutputBuffer(size_t index) { 212 | return mDecoder->releaseOutputBuffer(index); 213 | } 214 | 215 | void DirectRenderer::DecoderContext::queueDecoderInputBuffers() { 216 | if (mDecoder == NULL) { 217 | return; 218 | } 219 | 220 | bool submittedMore = false; 221 | 222 | while (!mAccessUnits.empty() 223 | && !mDecoderInputBuffersAvailable.empty()) { 224 | size_t index = *mDecoderInputBuffersAvailable.begin(); 225 | 226 | mDecoderInputBuffersAvailable.erase( 227 | mDecoderInputBuffersAvailable.begin()); 228 | 229 | sp srcBuffer = *mAccessUnits.begin(); 230 | mAccessUnits.erase(mAccessUnits.begin()); 231 | 232 | const sp &dstBuffer = 233 | mDecoderInputBuffers.itemAt(index); 234 | 235 | memcpy(dstBuffer->data(), srcBuffer->data(), srcBuffer->size()); 236 | 237 | int64_t timeUs; 238 | CHECK(srcBuffer->meta()->findInt64("timeUs", &timeUs)); 239 | 240 | status_t err = mDecoder->queueInputBuffer( 241 | index, 242 | 0 /* offset */, 243 | srcBuffer->size(), 244 | timeUs, 245 | 0 /* flags */); 246 | CHECK_EQ(err, (status_t)OK); 247 | 248 | submittedMore = true; 249 | } 250 | 251 | if (submittedMore) { 252 | scheduleDecoderNotification(); 253 | } 254 | } 255 | 256 | void DirectRenderer::DecoderContext::onMessageReceived( 257 | const sp &msg) { 258 | switch (msg->what()) { 259 | case kWhatDecoderNotify: 260 | { 261 | onDecoderNotify(); 262 | break; 263 | } 264 | 265 | default: 266 | TRESPASS(); 267 | } 268 | } 269 | 270 | void DirectRenderer::DecoderContext::onDecoderNotify() { 271 | mDecoderNotificationPending = false; 272 | 273 | for (;;) { 274 | size_t index; 275 | status_t err = mDecoder->dequeueInputBuffer(&index); 276 | 277 | if (err == OK) { 278 | mDecoderInputBuffersAvailable.push_back(index); 279 | } else if (err == -EAGAIN) { 280 | break; 281 | } else { 282 | TRESPASS(); 283 | } 284 | } 285 | 286 | queueDecoderInputBuffers(); 287 | 288 | for (;;) { 289 | size_t index; 290 | size_t offset; 291 | size_t size; 292 | int64_t timeUs; 293 | uint32_t flags; 294 | status_t err = mDecoder->dequeueOutputBuffer( 295 | &index, 296 | &offset, 297 | &size, 298 | &timeUs, 299 | &flags); 300 | 301 | if (err == OK) { 302 | queueOutputBuffer( 303 | index, timeUs, mDecoderOutputBuffers.itemAt(index)); 304 | } else if (err == INFO_OUTPUT_BUFFERS_CHANGED) { 305 | err = mDecoder->getOutputBuffers( 306 | &mDecoderOutputBuffers); 307 | CHECK_EQ(err, (status_t)OK); 308 | } else if (err == INFO_FORMAT_CHANGED) { 309 | // We don't care. 310 | } else if (err == -EAGAIN) { 311 | break; 312 | } else { 313 | TRESPASS(); 314 | } 315 | } 316 | 317 | scheduleDecoderNotification(); 318 | } 319 | 320 | void DirectRenderer::DecoderContext::scheduleDecoderNotification() { 321 | if (mDecoderNotificationPending) { 322 | return; 323 | } 324 | 325 | sp notify = 326 | PlantUtils::newAMessage(kWhatDecoderNotify, this); 327 | 328 | mDecoder->requestActivityNotification(notify); 329 | mDecoderNotificationPending = true; 330 | } 331 | 332 | void DirectRenderer::DecoderContext::queueOutputBuffer( 333 | size_t index, int64_t timeUs, const sp &buffer) { 334 | sp msg = mNotify->dup(); 335 | msg->setInt32("what", kWhatOutputBufferReady); 336 | msg->setSize("index", index); 337 | msg->setInt64("timeUs", timeUs); 338 | msg->setBuffer("buffer", buffer); 339 | msg->post(); 340 | } 341 | 342 | //////////////////////////////////////////////////////////////////////////////// 343 | 344 | DirectRenderer::AudioRenderer::AudioRenderer( 345 | const sp &decoderContext) 346 | : mDecoderContext(decoderContext), 347 | mPushPending(false), 348 | mNumFramesWritten(0) { 349 | mAudioTrack = new AudioTrack( 350 | AUDIO_STREAM_DEFAULT, 351 | 48000.0f, 352 | AUDIO_FORMAT_PCM, 353 | AUDIO_CHANNEL_OUT_STEREO, 354 | (int)0 /* frameCount */); 355 | 356 | CHECK_EQ((status_t)OK, mAudioTrack->initCheck()); 357 | 358 | mAudioTrack->start(); 359 | } 360 | 361 | DirectRenderer::AudioRenderer::~AudioRenderer() { 362 | } 363 | 364 | void DirectRenderer::AudioRenderer::queueInputBuffer( 365 | size_t index, int64_t timeUs, const sp &buffer) { 366 | BufferInfo info; 367 | info.mIndex = index; 368 | info.mTimeUs = timeUs; 369 | info.mBuffer = buffer; 370 | 371 | mInputBuffers.push_back(info); 372 | schedulePushIfNecessary(); 373 | } 374 | 375 | void DirectRenderer::AudioRenderer::onMessageReceived( 376 | const sp &msg) { 377 | switch (msg->what()) { 378 | case kWhatPushAudio: 379 | { 380 | onPushAudio(); 381 | break; 382 | } 383 | 384 | default: 385 | break; 386 | } 387 | } 388 | 389 | void DirectRenderer::AudioRenderer::schedulePushIfNecessary() { 390 | if (mPushPending || mInputBuffers.empty()) { 391 | return; 392 | } 393 | 394 | mPushPending = true; 395 | 396 | uint32_t numFramesPlayed; 397 | CHECK_EQ(mAudioTrack->getPosition(&numFramesPlayed), 398 | (status_t)OK); 399 | 400 | uint32_t numFramesPendingPlayout = mNumFramesWritten - numFramesPlayed; 401 | 402 | // This is how long the audio sink will have data to 403 | // play back. 404 | const float msecsPerFrame = 1000.0f / mAudioTrack->getSampleRate(); 405 | 406 | int64_t delayUs = 407 | msecsPerFrame * numFramesPendingPlayout * 1000ll; 408 | 409 | // Let's give it more data after about half that time 410 | // has elapsed. 411 | (PlantUtils::newAMessage(kWhatPushAudio, this))->post(delayUs / 2); 412 | } 413 | 414 | void DirectRenderer::AudioRenderer::onPushAudio() { 415 | mPushPending = false; 416 | 417 | while (!mInputBuffers.empty()) { 418 | const BufferInfo &info = *mInputBuffers.begin(); 419 | 420 | ssize_t n = writeNonBlocking( 421 | info.mBuffer->data(), info.mBuffer->size()); 422 | 423 | if (n < (ssize_t)info.mBuffer->size()) { 424 | CHECK_GE(n, 0); 425 | 426 | info.mBuffer->setRange( 427 | info.mBuffer->offset() + n, info.mBuffer->size() - n); 428 | break; 429 | } 430 | 431 | mDecoderContext->releaseOutputBuffer(info.mIndex); 432 | 433 | mInputBuffers.erase(mInputBuffers.begin()); 434 | } 435 | 436 | schedulePushIfNecessary(); 437 | } 438 | 439 | ssize_t DirectRenderer::AudioRenderer::writeNonBlocking( 440 | const uint8_t *data, size_t size) { 441 | uint32_t numFramesPlayed; 442 | status_t err = mAudioTrack->getPosition(&numFramesPlayed); 443 | if (err != OK) { 444 | return err; 445 | } 446 | 447 | ssize_t numFramesAvailableToWrite = 448 | mAudioTrack->frameCount() - (mNumFramesWritten - numFramesPlayed); 449 | 450 | size_t numBytesAvailableToWrite = 451 | numFramesAvailableToWrite * mAudioTrack->frameSize(); 452 | 453 | if (size > numBytesAvailableToWrite) { 454 | size = numBytesAvailableToWrite; 455 | } 456 | 457 | CHECK_EQ(mAudioTrack->write(data, size), (ssize_t)size); 458 | 459 | size_t numFramesWritten = size / mAudioTrack->frameSize(); 460 | mNumFramesWritten += numFramesWritten; 461 | 462 | return size; 463 | } 464 | 465 | //////////////////////////////////////////////////////////////////////////////// 466 | 467 | DirectRenderer::DirectRenderer( 468 | const sp &bufferProducer) 469 | : mSurfaceTex(bufferProducer), 470 | mVideoRenderPending(false), 471 | mNumFramesLate(0), 472 | mNumFrames(0) { 473 | } 474 | 475 | DirectRenderer::~DirectRenderer() { 476 | } 477 | 478 | void DirectRenderer::onMessageReceived(const sp &msg) { 479 | switch (msg->what()) { 480 | case kWhatDecoderNotify: 481 | { 482 | onDecoderNotify(msg); 483 | break; 484 | } 485 | 486 | case kWhatRenderVideo: 487 | { 488 | onRenderVideo(); 489 | break; 490 | } 491 | 492 | case kWhatQueueAccessUnit: 493 | onQueueAccessUnit(msg); 494 | break; 495 | 496 | case kWhatSetFormat: 497 | onSetFormat(msg); 498 | break; 499 | 500 | default: 501 | TRESPASS(); 502 | } 503 | } 504 | 505 | void DirectRenderer::setFormat(size_t trackIndex, const sp &format) { 506 | sp msg = PlantUtils::newAMessage(kWhatSetFormat, this); 507 | msg->setSize("trackIndex", trackIndex); 508 | msg->setMessage("format", format); 509 | msg->post(); 510 | } 511 | 512 | void DirectRenderer::onSetFormat(const sp &msg) { 513 | size_t trackIndex; 514 | CHECK(msg->findSize("trackIndex", &trackIndex)); 515 | 516 | sp format; 517 | CHECK(msg->findMessage("format", &format)); 518 | 519 | internalSetFormat(trackIndex, format); 520 | } 521 | 522 | void DirectRenderer::internalSetFormat( 523 | size_t trackIndex, const sp &format) { 524 | CHECK_LT(trackIndex, 2u); 525 | 526 | CHECK(mDecoderContext[trackIndex] == NULL); 527 | 528 | sp notify = PlantUtils::newAMessage(kWhatDecoderNotify, this); 529 | notify->setSize("trackIndex", trackIndex); 530 | 531 | mDecoderContext[trackIndex] = new DecoderContext(notify); 532 | looper()->registerHandler(mDecoderContext[trackIndex]); 533 | 534 | CHECK_EQ((status_t)OK, 535 | mDecoderContext[trackIndex]->init( 536 | format, trackIndex == 0 ? mSurfaceTex : NULL)); 537 | 538 | if (trackIndex == 1) { 539 | // Audio 540 | mAudioRenderer = new AudioRenderer(mDecoderContext[1]); 541 | looper()->registerHandler(mAudioRenderer); 542 | } 543 | } 544 | 545 | void DirectRenderer::queueAccessUnit( 546 | size_t trackIndex, const sp &accessUnit) { 547 | sp msg = PlantUtils::newAMessage(kWhatQueueAccessUnit, this); 548 | msg->setSize("trackIndex", trackIndex); 549 | msg->setBuffer("accessUnit", accessUnit); 550 | msg->post(); 551 | } 552 | 553 | void DirectRenderer::onQueueAccessUnit(const sp &msg) { 554 | size_t trackIndex; 555 | CHECK(msg->findSize("trackIndex", &trackIndex)); 556 | 557 | sp accessUnit; 558 | CHECK(msg->findBuffer("accessUnit", &accessUnit)); 559 | 560 | CHECK_LT(trackIndex, 2u); 561 | CHECK(mDecoderContext[trackIndex] != NULL); 562 | 563 | mDecoderContext[trackIndex]->queueInputBuffer(accessUnit); 564 | } 565 | 566 | void DirectRenderer::onDecoderNotify(const sp &msg) { 567 | size_t trackIndex; 568 | CHECK(msg->findSize("trackIndex", &trackIndex)); 569 | 570 | int32_t what; 571 | CHECK(msg->findInt32("what", &what)); 572 | 573 | switch (what) { 574 | case DecoderContext::kWhatOutputBufferReady: 575 | { 576 | size_t index; 577 | CHECK(msg->findSize("index", &index)); 578 | 579 | int64_t timeUs; 580 | CHECK(msg->findInt64("timeUs", &timeUs)); 581 | 582 | sp buffer; 583 | CHECK(msg->findBuffer("buffer", &buffer)); 584 | 585 | queueOutputBuffer(trackIndex, index, timeUs, buffer); 586 | break; 587 | } 588 | 589 | default: 590 | TRESPASS(); 591 | } 592 | } 593 | 594 | void DirectRenderer::queueOutputBuffer( 595 | size_t trackIndex, 596 | size_t index, int64_t timeUs, const sp &buffer) { 597 | if (trackIndex == 1) { 598 | // Audio 599 | mAudioRenderer->queueInputBuffer(index, timeUs, buffer); 600 | return; 601 | } 602 | 603 | OutputInfo info; 604 | info.mIndex = index; 605 | info.mTimeUs = timeUs; 606 | info.mBuffer = buffer; 607 | mVideoOutputBuffers.push_back(info); 608 | 609 | scheduleVideoRenderIfNecessary(); 610 | } 611 | 612 | void DirectRenderer::scheduleVideoRenderIfNecessary() { 613 | if (mVideoRenderPending || mVideoOutputBuffers.empty()) { 614 | return; 615 | } 616 | 617 | mVideoRenderPending = true; 618 | 619 | int64_t timeUs = (*mVideoOutputBuffers.begin()).mTimeUs; 620 | int64_t nowUs = ALooper::GetNowUs(); 621 | 622 | int64_t delayUs = timeUs - nowUs; 623 | 624 | (PlantUtils::newAMessage(kWhatRenderVideo, this))->post(delayUs); 625 | } 626 | 627 | void DirectRenderer::onRenderVideo() { 628 | mVideoRenderPending = false; 629 | 630 | int64_t nowUs = ALooper::GetNowUs(); 631 | 632 | while (!mVideoOutputBuffers.empty()) { 633 | const OutputInfo &info = *mVideoOutputBuffers.begin(); 634 | 635 | if (info.mTimeUs > nowUs) { 636 | break; 637 | } 638 | 639 | if (info.mTimeUs + 15000ll < nowUs) { 640 | ++mNumFramesLate; 641 | ALOGD("DirectRenderer::onRenderVideo, late frames(>15ms) vs total frames (%d : %d)", mNumFramesLate, mNumFrames); 642 | // TODO: skip render late frame. 643 | } 644 | ++mNumFrames; 645 | 646 | status_t err = 647 | mDecoderContext[0]->renderOutputBufferAndRelease(info.mIndex); 648 | CHECK_EQ(err, (status_t)OK); 649 | 650 | mVideoOutputBuffers.erase(mVideoOutputBuffers.begin()); 651 | } 652 | 653 | scheduleVideoRenderIfNecessary(); 654 | } 655 | 656 | } // namespace android 657 | 658 | -------------------------------------------------------------------------------- /lib/sink/DirectRenderer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef DIRECT_RENDERER_H_ 18 | 19 | #define DIRECT_RENDERER_H_ 20 | 21 | #include 22 | 23 | namespace android { 24 | 25 | struct ABuffer; 26 | struct IGraphicBufferProducer; 27 | 28 | // Renders audio and video data queued by calls to "queueAccessUnit". 29 | struct DirectRenderer : public AHandler { 30 | DirectRenderer(const sp &bufferProducer); 31 | 32 | void setFormat(size_t trackIndex, const sp &format); 33 | void queueAccessUnit(size_t trackIndex, const sp &accessUnit); 34 | 35 | protected: 36 | virtual void onMessageReceived(const sp &msg); 37 | virtual ~DirectRenderer(); 38 | 39 | private: 40 | struct DecoderContext; 41 | struct AudioRenderer; 42 | 43 | enum { 44 | kWhatDecoderNotify, 45 | kWhatRenderVideo, 46 | kWhatQueueAccessUnit, 47 | kWhatSetFormat, 48 | }; 49 | 50 | struct OutputInfo { 51 | size_t mIndex; 52 | int64_t mTimeUs; 53 | sp mBuffer; 54 | }; 55 | 56 | sp mSurfaceTex; 57 | 58 | sp mDecoderContext[2]; 59 | List mVideoOutputBuffers; 60 | 61 | bool mVideoRenderPending; 62 | 63 | sp mAudioRenderer; 64 | 65 | int32_t mNumFramesLate; 66 | int32_t mNumFrames; 67 | 68 | void onDecoderNotify(const sp &msg); 69 | 70 | void queueOutputBuffer( 71 | size_t trackIndex, 72 | size_t index, int64_t timeUs, const sp &buffer); 73 | 74 | void scheduleVideoRenderIfNecessary(); 75 | void onRenderVideo(); 76 | 77 | void onSetFormat(const sp &msg); 78 | void onQueueAccessUnit(const sp &msg); 79 | 80 | void internalSetFormat(size_t trackIndex, const sp &format); 81 | 82 | DISALLOW_EVIL_CONSTRUCTORS(DirectRenderer); 83 | }; 84 | 85 | } // namespace android 86 | 87 | #endif // DIRECT_RENDERER_H_ 88 | -------------------------------------------------------------------------------- /lib/sink/WifiDisplaySink.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef WIFI_DISPLAY_SINK_H_ 18 | 19 | #define WIFI_DISPLAY_SINK_H_ 20 | 21 | #include "VideoFormats.h" 22 | 23 | #include 24 | #include 25 | //#include 26 | #include "ANetworkSession.h" 27 | 28 | 29 | namespace android { 30 | 31 | struct AMessage; 32 | struct DirectRenderer; 33 | struct MediaReceiver; 34 | struct ParsedMessage; 35 | struct TimeSyncer; 36 | 37 | enum wfd_event_type { 38 | WFD_NOP = 0, // interface test message 39 | WFD_ERROR = 100, 40 | WFD_INFO = 200, 41 | }; 42 | 43 | enum wfd_error_type { 44 | // 0xx 45 | WFD_ERROR_UNKNOWN = 1, 46 | // 1xx 47 | WFD_ERROR_SERVER_DIED = 100, 48 | }; 49 | 50 | enum wfd_info_type { 51 | // 0xx 52 | WFD_INFO_UNKNOWN = 1, 53 | // 7xx 54 | WFD_INFO_RTSP_TEARDOWN = 700, 55 | }; 56 | 57 | class WfdSinkListener: virtual public RefBase 58 | { 59 | public: 60 | virtual void notify(int msg, int ext1, int ext2, const Parcel *obj) = 0; 61 | }; 62 | 63 | // Represents the RTSP client acting as a wifi display sink. 64 | // Connects to a wifi display source and renders the incoming 65 | // transport stream using a MediaPlayer instance. 66 | struct WifiDisplaySink : public AHandler { 67 | enum { 68 | kWhatDisconnected, 69 | }; 70 | 71 | enum Flags { 72 | FLAG_SPECIAL_MODE = 1, 73 | }; 74 | 75 | // If no notification message is specified (notify == NULL) 76 | // the sink will stop its looper() once the session ends, 77 | // otherwise it will post an appropriate notification but leave 78 | // the looper() running. 79 | WifiDisplaySink( 80 | sp looper, 81 | uint32_t flags, 82 | const sp &netSession, 83 | //const sp &bufferProducer = NULL, 84 | const sp ¬ify = NULL); 85 | 86 | status_t setListener(const sp& listener); 87 | void setDisplay(const sp& bufferProducer); 88 | void start(const char *sourceHost, int32_t sourcePort); 89 | void start(const char *uri); 90 | 91 | protected: 92 | virtual ~WifiDisplaySink(); 93 | virtual void onMessageReceived(const sp &msg); 94 | 95 | private: 96 | enum State { 97 | UNDEFINED, 98 | CONNECTING, 99 | CONNECTED, 100 | PAUSED, 101 | PLAYING, 102 | }; 103 | 104 | enum { 105 | kWhatStart, 106 | kWhatRTSPNotify, 107 | kWhatStop, 108 | kWhatMediaReceiverNotify, 109 | kWhatTimeSyncerNotify, 110 | kWhatReportLateness, 111 | }; 112 | 113 | struct ResponseID { 114 | int32_t mSessionID; 115 | int32_t mCSeq; 116 | 117 | bool operator<(const ResponseID &other) const { 118 | return mSessionID < other.mSessionID 119 | || (mSessionID == other.mSessionID 120 | && mCSeq < other.mCSeq); 121 | } 122 | }; 123 | 124 | typedef status_t (WifiDisplaySink::*HandleRTSPResponseFunc)( 125 | int32_t sessionID, const sp &msg); 126 | 127 | static const int64_t kReportLatenessEveryUs = 1000000ll; 128 | 129 | static const AString sUserAgent; 130 | 131 | sp mWfdSinkLooper; 132 | sp mListener; 133 | State mState; 134 | uint32_t mFlags; 135 | VideoFormats mSinkSupportedVideoFormats; 136 | sp mNetSession; 137 | sp mSurfaceTex; 138 | sp mNotify; 139 | sp mTimeSyncer; 140 | bool mUsingTCPTransport; 141 | bool mUsingTCPInterleaving; 142 | AString mRTSPHost; 143 | int32_t mSessionID; 144 | int32_t mLocalRtpPort; 145 | 146 | int32_t mNextCSeq; 147 | 148 | KeyedVector mResponseHandlers; 149 | 150 | sp mMediaReceiverLooper; 151 | sp mMediaReceiver; 152 | sp mRenderer; 153 | 154 | AString mPlaybackSessionID; 155 | int32_t mPlaybackSessionTimeoutSecs; 156 | 157 | bool mIDRFrameRequestPending; 158 | 159 | int64_t mTimeOffsetUs; 160 | bool mTimeOffsetValid; 161 | 162 | bool mSetupDeferred; 163 | 164 | size_t mLatencyCount; 165 | int64_t mLatencySumUs; 166 | int64_t mLatencyMaxUs; 167 | 168 | int64_t mMaxDelayMs; 169 | 170 | status_t sendM2(int32_t sessionID); 171 | status_t sendSetup(int32_t sessionID, const char *uri); 172 | status_t sendPlay(int32_t sessionID, const char *uri); 173 | status_t sendIDRFrameRequest(int32_t sessionID); 174 | 175 | status_t onReceiveM2Response( 176 | int32_t sessionID, const sp &msg); 177 | 178 | status_t onReceiveSetupResponse( 179 | int32_t sessionID, const sp &msg); 180 | 181 | status_t configureTransport(const sp &msg); 182 | 183 | status_t onReceivePlayResponse( 184 | int32_t sessionID, const sp &msg); 185 | 186 | status_t onReceiveIDRFrameRequestResponse( 187 | int32_t sessionID, const sp &msg); 188 | 189 | void registerResponseHandler( 190 | int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func); 191 | 192 | void onReceiveClientData(const sp &msg); 193 | 194 | void onOptionsRequest( 195 | int32_t sessionID, 196 | int32_t cseq, 197 | const sp &data); 198 | 199 | void onGetParameterRequest( 200 | int32_t sessionID, 201 | int32_t cseq, 202 | const sp &data); 203 | 204 | void onSetParameterRequest( 205 | int32_t sessionID, 206 | int32_t cseq, 207 | const sp &data); 208 | 209 | void onMediaReceiverNotify(const sp &msg); 210 | 211 | void sendErrorResponse( 212 | int32_t sessionID, 213 | const char *errorDetail, 214 | int32_t cseq); 215 | 216 | static void AppendCommonResponse(AString *response, int32_t cseq); 217 | 218 | bool ParseURL( 219 | const char *url, AString *host, int32_t *port, AString *path, 220 | AString *user, AString *pass); 221 | 222 | void dumpDelay(size_t trackIndex, int64_t timeUs); 223 | 224 | DISALLOW_EVIL_CONSTRUCTORS(WifiDisplaySink); 225 | }; 226 | 227 | } // namespace android 228 | 229 | #endif // WIFI_DISPLAY_SINK_H_ 230 | -------------------------------------------------------------------------------- /res/anim/frame.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /res/anim/frame2.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /res/drawable-hdpi/app_loading0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nantianyan/WifiDisplaySink/cfed568325ab0b2a759c15dc10075028254a9030/res/drawable-hdpi/app_loading0.png -------------------------------------------------------------------------------- /res/drawable-hdpi/app_loading1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nantianyan/WifiDisplaySink/cfed568325ab0b2a759c15dc10075028254a9030/res/drawable-hdpi/app_loading1.png -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nantianyan/WifiDisplaySink/cfed568325ab0b2a759c15dc10075028254a9030/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-hdpi/progress_loading_image_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nantianyan/WifiDisplaySink/cfed568325ab0b2a759c15dc10075028254a9030/res/drawable-hdpi/progress_loading_image_01.png -------------------------------------------------------------------------------- /res/drawable-hdpi/progress_loading_image_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nantianyan/WifiDisplaySink/cfed568325ab0b2a759c15dc10075028254a9030/res/drawable-hdpi/progress_loading_image_02.png -------------------------------------------------------------------------------- /res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nantianyan/WifiDisplaySink/cfed568325ab0b2a759c15dc10075028254a9030/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nantianyan/WifiDisplaySink/cfed568325ab0b2a759c15dc10075028254a9030/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nantianyan/WifiDisplaySink/cfed568325ab0b2a759c15dc10075028254a9030/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/layout/activity_waiting_connection.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 20 | 21 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /res/layout/activity_wifidisplay_sink.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 22 | 26 | 27 | -------------------------------------------------------------------------------- /res/raw/combo_sdp.xml: -------------------------------------------------------------------------------- 1 | 2 | 47 | 48 | Android HID KB for BT 49 | A KB that runs over BT 50 | Broadcom 51 | 0x0111 52 | 0x0080 53 | 0x21 54 | true 55 | true 56 | 05010906a1010507850119e029e71500250175019508810295017508810195057501050819012905910295017503910195067508150026a4000507190029a48100c005010902A1010901A10085020509190129031500250195037501810295017505810305010930093109381581257F750895038106C0C0050c0901a1018503751095021501268c0219012a8c028160c0 57 | 58 | 0x0409 59 | 0x0100 60 | 61 | false 62 | true 63 | true 64 | 0x0100 65 | 0x1f40 66 | true 67 | true 68 | 69 | 70 | -------------------------------------------------------------------------------- /res/raw/keyboard_sdp_bak.xml: -------------------------------------------------------------------------------- 1 | 2 | 47 | 48 | Android HID Keyboard for Bluetooth 49 | A keyboard that runs over Bluetooth 50 | Microsoft 51 | 0x0111 52 | 0x0080 53 | 0x0021 54 | true 55 | true 56 | 05010906a1018501050819012903150025017501950391029505910105071ae0002ae70095088102810119002a910026ff00750895068100c0050c0901a1018507050c19002aff0395017510150027ff03000081007512950181010600ff0a05ff7505251f8102750181011a01fd2afffd150126ff00750881000a02ff150026ff007508810295018101c0 57 | 58 | 0x0409 59 | 0x0100 60 | 61 | false 62 | true 63 | true 64 | 0x0100 65 | 0x1f40 66 | true 67 | true 68 | 69 | 70 | -------------------------------------------------------------------------------- /res/raw/mouse_sdp.xml: -------------------------------------------------------------------------------- 1 | 2 | 47 | 48 | Test Android Bluetooth Mouse 49 | Five Button Mouse 50 | Microsoft 51 | 0x0111 52 | 0x0080 53 | 0x0021 54 | true 55 | true 56 | 05010902a10185020901a1000501093009311581257F75089502810605091901290315002501950375018102950175058103c0c0 57 | 58 | 0x0409 59 | 0x0100 60 | 61 | false 62 | true 63 | true 64 | 0x0100 65 | 0x8000 66 | true 67 | true 68 | 69 | -------------------------------------------------------------------------------- /res/raw/mouse_sdp_temp.xml: -------------------------------------------------------------------------------- 1 | 2 | 47 | 48 | Test Android Bluetooth Mouse 49 | Five Button Mouse 50 | Microsoft 51 | 0x0111 52 | 0x0080 53 | 0x0021 54 | true 55 | true 56 | 57 | 58 | 59 | 050c0901a1010501090285140600ff0a12fe950175021500250381020a11fe75012501810295058101c005010902a1010501090285110901a10005091901290495047501250181027504950181010501093009310938950375081581257f810695018101c0c0050c0901a10185190600ff1a18ff2a22ff950b7508150026ff00b1020a23ff950175012501b1027507b101c0 60 | true 61 | true 62 | true 63 | 0x0100 64 | 0x1f40 65 | true 66 | true 67 | 68 | -------------------------------------------------------------------------------- /res/values-ja/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 有効なネットワーク接続がありません。ネットワークへ接続してください。 4 | -------------------------------------------------------------------------------- /res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 镜像 4 | 镜像 5 | 网络错误 6 | 等待连接中 ... 7 | 车载镜像 8 | 9 | -------------------------------------------------------------------------------- /res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | "(DON'T CARE) RECOMMEND" 5 | 800 x 480 - for Galaxy S2 6 | 1280 x 720 - AOSP Standard 7 | 1600 x 900 - Some Sinks used this Value? 8 | 1920 x 1080 - Full HD 9 | 10 | 11 | -1x-1 12 | 800x480 13 | 1280x720 14 | 1600x900 15 | 1920x1080 16 | 17 | 18 | 19 | "(DON'T CARE)" 20 | 10 Hz - TEST! 21 | 20 Hz - TEST!! 22 | 30 Hz - AOSP Standard 23 | 40 Hz - TEST!!!! 24 | 50 Hz - TEST!!!!! 25 | 60 Hz - TEST!!!!!! 26 | 27 | 28 | -1 29 | 10 30 | 20 31 | 30 32 | 40 33 | 50 34 | 60 35 | 36 | 37 | 38 | "(DON'T CARE)" 39 | 1000000 40 | 2000000 41 | 3000000 42 | 4000000 43 | 5000000 - AOSP Standard 44 | 8000000 45 | 10000000 46 | 12000000 - Limit of maguro 47 | 16000000 48 | 20000000 - Limit of Nexus 7 49 | 50 | 51 | -1 52 | 1000000 53 | 2000000 54 | 3000000 55 | 4000000 56 | 5000000 57 | 8000000 58 | 10000000 59 | 12000000 60 | 16000000 61 | 20000000 62 | 63 | 64 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | WifiDisplaySink 4 | WifiDisplaySink 5 | No Valid Networks. Try Connect a Network. 6 | Conecting ... 7 | Car_MiraCast 8 | 9 | -------------------------------------------------------------------------------- /res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /src/com/lc/wifidisplaysink/HidDeviceAdapterService.java: -------------------------------------------------------------------------------- 1 | package com.lc.wifidisplaysink; 2 | 3 | import java.io.IOException; 4 | import android.app.Activity; 5 | 6 | import android.os.Bundle; 7 | import android.os.Handler; 8 | import android.os.Message; 9 | import android.os.Binder; 10 | import android.os.IBinder; 11 | 12 | import android.content.BroadcastReceiver; 13 | import android.content.Context; 14 | import android.content.Intent; 15 | import android.content.IntentFilter; 16 | 17 | import android.view.WindowManager; 18 | import android.view.MotionEvent; 19 | import android.view.View; 20 | import android.widget.ImageView; 21 | 22 | import java.lang.Math; 23 | import android.util.Log; 24 | 25 | import android.app.Service; 26 | 27 | public class HidDeviceAdapterService extends Service{ 28 | private static final String TAG = "HidDeviceAdapterService"; 29 | 30 | @Override 31 | public void onCreate() { 32 | super.onCreate(); 33 | } 34 | 35 | @Override 36 | public int onStartCommand(Intent intent, int flags, int startId) { 37 | onStart(intent, startId); 38 | return START_NOT_STICKY; 39 | } 40 | 41 | @Override 42 | public void onDestroy() { 43 | Log.d(TAG, "onDestroy()"); 44 | super.onDestroy(); 45 | } 46 | 47 | //Because we know this service always 48 | //runs in the same process as its clients, we don't need to deal with 49 | //IPC. 50 | public class HidDeviceAdapterBinder extends Binder { 51 | HidDeviceAdapterService getService() { 52 | Log.d(TAG, "getService()"); 53 | return HidDeviceAdapterService.this; 54 | } 55 | } 56 | 57 | private final IBinder mBinder = new HidDeviceAdapterBinder(); 58 | 59 | @Override 60 | public IBinder onBind(Intent intent) { 61 | Log.d(TAG, "onBind()"); 62 | return mBinder; 63 | } 64 | 65 | //---------------------------------------------------- 66 | public void sendLeftClickReport() { 67 | Log.d(TAG, "sendLeftClickReport"); 68 | } 69 | 70 | public void sendMouseMoveReport(int delta_x, int delta_y) { 71 | Log.d(TAG, "sendMouseMoveReport"); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/com/lc/wifidisplaysink/HidDeviceAdapterService.java.stub: -------------------------------------------------------------------------------- 1 | package com.lc.wifidisplaysink; 2 | 3 | import java.io.IOException; 4 | import android.app.Activity; 5 | 6 | import android.os.Bundle; 7 | import android.os.Handler; 8 | import android.os.Message; 9 | import android.os.Binder; 10 | import android.os.IBinder; 11 | 12 | import android.content.BroadcastReceiver; 13 | import android.content.Context; 14 | import android.content.Intent; 15 | import android.content.IntentFilter; 16 | 17 | import android.view.WindowManager; 18 | import android.view.MotionEvent; 19 | import android.view.View; 20 | import android.widget.ImageView; 21 | 22 | import java.lang.Math; 23 | import android.util.Log; 24 | 25 | import android.app.Service; 26 | 27 | public class HidDeviceAdapterService extends Service{ 28 | private static final String TAG = "HidDeviceAdapterService"; 29 | 30 | @Override 31 | public void onCreate() { 32 | super.onCreate(); 33 | } 34 | 35 | @Override 36 | public int onStartCommand(Intent intent, int flags, int startId) { 37 | onStart(intent, startId); 38 | return START_NOT_STICKY; 39 | } 40 | 41 | @Override 42 | public void onDestroy() { 43 | Log.d(TAG, "onDestroy()"); 44 | super.onDestroy(); 45 | } 46 | 47 | //Because we know this service always 48 | //runs in the same process as its clients, we don't need to deal with 49 | //IPC. 50 | public class HidDeviceAdapterBinder extends Binder { 51 | HidDeviceAdapterService getService() { 52 | Log.d(TAG, "getService()"); 53 | return HidDeviceAdapterService.this; 54 | } 55 | } 56 | 57 | private final IBinder mBinder = new HidDeviceAdapterBinder(); 58 | 59 | @Override 60 | public IBinder onBind(Intent intent) { 61 | Log.d(TAG, "onBind()"); 62 | return mBinder; 63 | } 64 | 65 | //---------------------------------------------------- 66 | public void sendLeftClickReport() { 67 | Log.d(TAG, "sendLeftClickReport"); 68 | } 69 | 70 | public void sendMouseMoveReport(int delta_x, int delta_y) { 71 | Log.d(TAG, "sendMouseMoveReport"); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/com/lc/wifidisplaysink/RarpImpl.java: -------------------------------------------------------------------------------- 1 | package com.lc.wifidisplaysink; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.FileNotFoundException; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.io.UnsupportedEncodingException; 10 | import java.util.ArrayList; 11 | 12 | import android.util.Log; 13 | 14 | /** 15 | * RARP: MAC --> IP 16 | * /proc/net/arp 17 | * IP address HW type Flags HW address Mask Device 18 | * 192.168.49.208 0x1 0x2 a2:0b:ba:ba:c4:d1 * p2p-wlan0-8 19 | */ 20 | public class RarpImpl { 21 | 22 | private String TAG = "RarpImpl"; 23 | 24 | public final String ARP_PATH = "/proc/net/arp"; 25 | 26 | /** 27 | * rarp 28 | * @param netIf 29 | * @return IP address 30 | */ 31 | public String execRarp(String netIf) { 32 | ArrayList lines = readFile(ARP_PATH); 33 | if (lines == null || lines.size() == 0) { 34 | Log.w(TAG, "execRarp() readFile("+ARP_PATH+") returns 0"); 35 | return null; 36 | } 37 | 38 | int i = 0; 39 | for (String line : lines) { 40 | Log.d(TAG, "execRarp() [" + (i++) + "]" + line); 41 | } 42 | 43 | ArrayList arps = parseArp(lines); 44 | if (arps == null || arps.size() == 0) { 45 | Log.w(TAG, "execRarp() parseArp("+lines+") returns 0"); 46 | return null; 47 | } 48 | 49 | ArpType arp = searchArp(arps, netIf); 50 | if (arp == null) { 51 | Log.w(TAG, "execRarp() searchArp() "+netIf+" Not Found!"); 52 | return null; 53 | } 54 | 55 | return arp.mIPaddress; 56 | } 57 | 58 | /** 59 | * Arp 60 | */ 61 | public ArrayList getArpTable() { 62 | ArrayList lines = readFile(ARP_PATH); 63 | if (lines == null || lines.size() == 0) { 64 | Log.w(TAG, "getArpTable() readFile("+ARP_PATH+") returns 0"); 65 | return null; 66 | } 67 | 68 | int i = 0; 69 | for (String line : lines) { 70 | Log.d(TAG, "getArpTable() [" + (i++) + "]" + line); 71 | } 72 | 73 | ArrayList arps = parseArp(lines); 74 | if (arps == null || arps.size() == 0) { 75 | Log.w(TAG, "getArpTable() parseArp("+lines+") returns 0"); 76 | return null; 77 | } 78 | return arps; 79 | } 80 | 81 | /** 82 | * 83 | * @param path 84 | * @return null 85 | */ 86 | private ArrayList readFile(String path) { 87 | try { 88 | BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(path)), "UTF-8"), 128); 89 | ArrayList lines = new ArrayList(); 90 | String line; 91 | while ((line = br.readLine()) != null) { 92 | lines.add(line); 93 | } 94 | br.close(); 95 | return lines; 96 | } catch(FileNotFoundException e) { 97 | Log.e(TAG, "readFile() " + e); 98 | } catch(UnsupportedEncodingException e) { 99 | Log.e(TAG, "readFile() " + e); 100 | } catch (IOException e) { 101 | Log.e(TAG, "readFile() " + e); 102 | } 103 | return null; 104 | } 105 | 106 | /** 107 | * /proc/net/arp 108 | */ 109 | private ArrayList parseArp(ArrayList arpLines) { 110 | if (arpLines == null || arpLines.size() == 1) { 111 | return null; 112 | } 113 | 114 | ArrayList arps = new ArrayList(); 115 | for (String line : arpLines) { 116 | ArpType arp = parseArpLine(line); 117 | if (arp == null) { 118 | continue; 119 | } 120 | arps.add(arp); 121 | } 122 | return arps; 123 | } 124 | 125 | private ArpType parseArpLine(String line) { 126 | if (line == null) { 127 | Log.e(TAG, "parseArpLine() line is null!"); 128 | return null; 129 | } 130 | 131 | String[] seps = line.split(" +"); // 132 | if (seps == null || seps.length == 0) { 133 | Log.e(TAG, "parseArpLine() split error!"+line+"]"); 134 | return null; 135 | } 136 | 137 | int len = seps.length; 138 | // arp行 139 | if (len == 6) { 140 | ArpType arp = new ArpType(seps[0], seps[1], seps[2], seps[3], seps[4], seps[5]); 141 | Log.d(TAG, "parseArpLine() created arp["+arp.toString()+"]"); 142 | return arp; 143 | } else { 144 | // ヘッダ 145 | if (seps.length == 9 && seps[0].equals("IP") && seps[8].equals("Device")) { 146 | Log.i(TAG, "parseArpLine() this is header line. don't create arp["+line+"]"); 147 | } else { 148 | StringBuffer buf = new StringBuffer(); 149 | for (int i = 0; i < seps.length; i++) { 150 | buf.append(String.format("[%02d](%s)", i, seps[i])); 151 | } 152 | Log.e(TAG, "parseArpLine() Unknown Line! Seps["+buf.toString()+"]"); 153 | } 154 | return null; 155 | } 156 | } 157 | 158 | private ArpType searchArp(ArrayList arps, String netIf) { 159 | if (arps == null || arps.size() == 0 || netIf == null) { 160 | return null; 161 | } 162 | 163 | ArpType lastarp = null; 164 | for (ArpType arp : arps) { 165 | //if (arp.mHWaddress.equals(macAddress)) { 166 | // return arp; 167 | //} 168 | if (arp.mDevice.equals(netIf)) { 169 | if (!arp.mHWaddress.equals("00:00:00:00:00:00")) { 170 | return arp; 171 | } 172 | } 173 | lastarp = arp; 174 | } 175 | 176 | if (lastarp != null) { 177 | //return lastarp; 178 | } 179 | return null; 180 | } 181 | 182 | 183 | public class ArpType { 184 | 185 | public String mIPaddress; 186 | String mHWType; 187 | String mFlags; 188 | String mHWaddress; 189 | String mMask; 190 | String mDevice; 191 | 192 | ArpType() { 193 | } 194 | 195 | ArpType(String ipaddress, String hwtype, String flags, String hwaddress, String mask, String device) { 196 | mIPaddress = ipaddress; 197 | mHWType = hwtype; 198 | mFlags = flags; 199 | mHWaddress = hwaddress; 200 | mMask = mask; 201 | mDevice = device; 202 | } 203 | 204 | public String toString() { 205 | String ret = 206 | " IP address:" + mIPaddress + "¥n" + 207 | " HW type:" + mHWType + "¥n" + 208 | " Flags:" + mFlags + "¥n" + 209 | " HW address:" + mHWaddress + "¥n" + 210 | " Mask:" + mMask + "¥n" + 211 | " Device:" + mDevice; 212 | return ret; 213 | } 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /src/com/lc/wifidisplaysink/WaitingConnectionActivity.java: -------------------------------------------------------------------------------- 1 | package com.lc.wifidisplaysink; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.List; 6 | import java.util.Timer; 7 | import java.util.TimerTask; 8 | 9 | import android.app.Activity; 10 | import android.content.ActivityNotFoundException; 11 | import android.content.BroadcastReceiver; 12 | import android.content.Context; 13 | import android.content.Intent; 14 | import android.content.IntentFilter; 15 | import android.content.SharedPreferences; 16 | import android.content.pm.PackageManager; 17 | import android.net.NetworkInfo; 18 | import android.net.wifi.WifiInfo; 19 | import android.net.wifi.WifiManager; 20 | import android.net.wifi.p2p.WifiP2pDevice; 21 | import android.net.wifi.p2p.WifiP2pDeviceList; 22 | import android.net.wifi.p2p.WifiP2pGroup; 23 | import android.net.wifi.p2p.WifiP2pInfo; 24 | import android.net.wifi.p2p.WifiP2pManager; 25 | import android.net.wifi.p2p.WifiP2pManager.Channel; 26 | import android.net.wifi.p2p.WifiP2pManager.ChannelListener; 27 | import android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener; 28 | import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener; 29 | import android.net.wifi.p2p.WifiP2pManager.PeerListListener; 30 | import android.net.wifi.p2p.WifiP2pWfdInfo; 31 | import android.net.wifi.p2p.WifiP2pManager.ActionListener; 32 | import android.os.Build; 33 | import android.os.Bundle; 34 | import android.os.Handler; 35 | import android.provider.Settings; 36 | import android.text.Editable; 37 | import android.text.Html; 38 | import android.text.TextWatcher; 39 | import android.util.Log; 40 | import android.view.View; 41 | import android.view.WindowManager; 42 | import android.widget.AdapterView; 43 | import android.widget.AdapterView.OnItemSelectedListener; 44 | import android.widget.ArrayAdapter; 45 | import android.widget.TextView; 46 | import android.widget.Toast; 47 | import android.widget.ImageView; 48 | import android.graphics.drawable.AnimationDrawable; 49 | import android.graphics.Color; 50 | import java.lang.ref.WeakReference; 51 | import android.os.Message; 52 | import android.os.Looper; 53 | import com.lc.wifidisplaysink.WifiDisplaySink; 54 | import com.lc.wifidisplaysink.WifiDisplaySinkConstants; 55 | import com.lc.wifidisplaysink.HidDeviceAdapterService; 56 | 57 | public class WaitingConnectionActivity extends Activity { 58 | private static final String TAG = "WaitingConnectionActivity"; 59 | private BroadcastReceiver mReceiver; 60 | private WifiP2pManager mWifiP2pManager; 61 | private Channel mChannel; 62 | private List mPeers = new ArrayList(); 63 | private final Handler mHandler = new Handler(); 64 | 65 | private boolean mIsWiFiDirectEnabled; 66 | private boolean mConnected; 67 | private int mArpRetryCount = 0; 68 | private static final int MAX_ARP_RETRY_COUNT = 60; 69 | private int mP2pControlPort = -1; 70 | private String mP2pInterfaceName; 71 | private boolean mIsSinkOpened; 72 | 73 | private ImageView mConnectingImageView; 74 | private TextView mConnectingTextView; 75 | private AnimationDrawable mConnectingAnimation; 76 | 77 | private Context mContext; 78 | private WifiManager mWifiManager; 79 | 80 | private Runnable mConnectingChecker = new Runnable() { 81 | @Override 82 | public void run() { 83 | p2pDiscoverPeers(null); 84 | mHandler.postDelayed(mConnectingChecker, 1*1000*30); //TODO: to give a resonable check time. 85 | } 86 | }; 87 | 88 | private Runnable mRarpChecker = new Runnable() { 89 | @Override 90 | public void run() { 91 | RarpImpl rarp = new RarpImpl(); 92 | String sourceIp = rarp.execRarp(mP2pInterfaceName); 93 | 94 | if (sourceIp == null) { 95 | if (++mArpRetryCount > MAX_ARP_RETRY_COUNT) { 96 | Log.e(TAG, "failed to do RARP, no source IP found. still trying ...."); 97 | } 98 | mHandler.postDelayed(mRarpChecker, 1000); 99 | } else { 100 | if (!mIsSinkOpened) { 101 | //delayedInvokeSink(sourceIp, mP2pControlPort, 1); 102 | startWifiDisplaySinkActivity(sourceIp, mP2pControlPort); 103 | } 104 | } 105 | } 106 | }; 107 | 108 | @Override 109 | public void onCreate(Bundle savedInstanceState) { 110 | super.onCreate(savedInstanceState); 111 | setContentView(R.layout.activity_waiting_connection); 112 | 113 | runConnectingAnimation(); 114 | utilsCheckP2pFeature(); // final WifiManager 115 | mContext = this; 116 | 117 | Intent startIntent = new Intent(this, HidDeviceAdapterService.class); 118 | startService(startIntent); 119 | } 120 | 121 | @Override 122 | protected void onResume() { 123 | super.onResume(); 124 | 125 | mConnected = false; 126 | mIsSinkOpened = false; 127 | mIsWiFiDirectEnabled = false; 128 | 129 | registerBroadcastReceiver(); 130 | 131 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 132 | //getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 133 | } 134 | 135 | @Override 136 | protected void onPause() { 137 | super.onPause(); 138 | 139 | mHandler.removeCallbacksAndMessages(null); 140 | unRegisterBroadcastReceiver(); 141 | } 142 | 143 | @Override 144 | public void onBackPressed() { 145 | super.onBackPressed(); 146 | System.exit(0); 147 | } 148 | 149 | private void registerBroadcastReceiver() { 150 | if (mReceiver == null) { 151 | Log.d(TAG, "registerBroadcastReceiver()"); 152 | IntentFilter filter = new IntentFilter(); 153 | filter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); 154 | filter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); 155 | filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); 156 | filter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); 157 | mReceiver = new WiFiDirectBroadcastReceiver(); 158 | registerReceiver(mReceiver, filter); 159 | } 160 | } 161 | 162 | private void unRegisterBroadcastReceiver() { 163 | if (mReceiver != null) { 164 | Log.d(TAG, "unRegisterBroadcastReceiver()"); 165 | unregisterReceiver(mReceiver); 166 | mReceiver = null; 167 | } 168 | } 169 | 170 | private void runConnectingAnimation() { 171 | Log.d(TAG, "runConnectingAnimation()"); 172 | mConnectingTextView = (TextView) findViewById(R.id.connecting_textview); 173 | mConnectingImageView = (ImageView) findViewById(R.id.connecting_imageview); 174 | 175 | mConnectingAnimation = (AnimationDrawable) mConnectingImageView.getBackground(); 176 | mConnectingImageView.post(new Runnable() { 177 | @Override 178 | public void run() { 179 | mConnectingAnimation.start(); 180 | } 181 | }); 182 | 183 | mConnectingTextView.setText(R.string.connecting_textview); 184 | mConnectingTextView.setTextColor(Color.parseColor("#ffffff00")); 185 | } 186 | 187 | private void utilsToastLog(String msg1, String msg2) { 188 | String log = msg1 + System.getProperty("line.separator") + msg2; 189 | Toast.makeText(this, log, Toast.LENGTH_SHORT).show(); 190 | } 191 | 192 | private String utilsGetAndroidID() { 193 | return Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID); 194 | } 195 | 196 | private String utilsGetMACAddress() { 197 | WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); 198 | String mac = wifiInfo.getMacAddress(); 199 | return mac; 200 | } 201 | 202 | private void utilsCheckP2pFeature() { 203 | Log.d(TAG, "utilsCheckP2pFeature()"); 204 | Log.d(TAG, "ANDROID_ID: " + utilsGetAndroidID()); 205 | 206 | mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); 207 | 208 | if (mWifiManager == null) { 209 | Log.e(TAG, "we'll exit because, mWifiManager is null"); 210 | finish(); 211 | } 212 | 213 | if (!mWifiManager.isWifiEnabled()) { 214 | if (!mWifiManager.setWifiEnabled(true)) { 215 | utilsToastLog("Warning", "Cannot enable wifi"); 216 | } 217 | } 218 | 219 | Log.d(TAG, "MAC: " + utilsGetMACAddress()); 220 | 221 | if (!p2pIsSupported()) { 222 | utilsToastLog("Warning", "This Package Does Not Supports P2P Feature!!"); 223 | return; 224 | } 225 | } 226 | 227 | private boolean p2pIsSupported() { 228 | return getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT); 229 | } 230 | 231 | private boolean p2pIsNull() { 232 | if (!mIsWiFiDirectEnabled) { 233 | utilsToastLog(" Wi-Fi Direct is OFF!", "try Setting Menu"); 234 | return true; 235 | } 236 | 237 | if (mWifiP2pManager == null) { 238 | utilsToastLog(" mWifiP2pManager is NULL!", " try getSystemService"); 239 | return true; 240 | } 241 | if (mChannel == null) { 242 | utilsToastLog(" mChannel is NULL!", " try initialize"); 243 | return true; 244 | } 245 | 246 | return false; 247 | } 248 | 249 | //NOTE: should call this before other p2p operation. 250 | public void p2pInitialize() { 251 | Log.d(TAG, "p2pInitialize()"); 252 | 253 | if (mWifiP2pManager != null) { 254 | Log.d(TAG, "p2p manager have been initialized, please recheck the calling sequence."); 255 | return; 256 | } 257 | 258 | mWifiP2pManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); 259 | mChannel = mWifiP2pManager.initialize(this, getMainLooper(), new ChannelListener() { 260 | public void onChannelDisconnected() { 261 | Log.d(TAG, "ChannelListener: onChannelDisconnected()"); 262 | } 263 | }); 264 | 265 | Log.d(TAG, "P2P Channel: "+ mChannel ); 266 | 267 | mWifiP2pManager.setDeviceName(mChannel, 268 | this.getString(R.string.p2p_device_name), 269 | new WifiP2pManager.ActionListener() { 270 | public void onSuccess() { 271 | Log.d(TAG, " device rename success"); 272 | } 273 | public void onFailure(int reason) { 274 | Log.d(TAG, " Failed to set device name"); 275 | } 276 | }); 277 | 278 | mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_SINK); 279 | 280 | WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo(); 281 | wfdInfo.setWfdEnabled(true); 282 | wfdInfo.setDeviceType(WifiP2pWfdInfo.PRIMARY_SINK); 283 | wfdInfo.setSessionAvailable(true); 284 | wfdInfo.setControlPort(7236); 285 | wfdInfo.setMaxThroughput(50); 286 | 287 | mWifiP2pManager.setWFDInfo(mChannel, wfdInfo, new ActionListener() { 288 | @Override 289 | public void onSuccess() { 290 | Log.d(TAG, "Successfully set WFD info."); 291 | } 292 | @Override 293 | public void onFailure(int reason) { 294 | Log.d(TAG, "Failed to set WFD info with reason " + reason + "."); 295 | } 296 | }); 297 | } 298 | 299 | 300 | public void p2pDiscoverPeers(View view) { 301 | Log.d(TAG, "p2pDiscoverPeers()"); 302 | if (p2pIsNull()) { 303 | Log.w(TAG, "should call p2p initialize at first."); 304 | return; 305 | } 306 | 307 | mWifiP2pManager.discoverPeers(mChannel, new ActionListener() { 308 | @Override 309 | public void onSuccess() { 310 | Log.d(TAG, "Successfully discoverPeers."); 311 | } 312 | @Override 313 | public void onFailure(int reason) { 314 | Log.d(TAG, "Failed to discoverPeers with reason " + reason + "."); 315 | } 316 | }); 317 | } 318 | 319 | public boolean p2pDeviceIsConnected(WifiP2pDevice device) { 320 | if (device == null) { 321 | return false; 322 | } 323 | return device.status == WifiP2pDevice.CONNECTED; 324 | } 325 | 326 | public class WiFiDirectBroadcastReceiver extends BroadcastReceiver { 327 | 328 | @Override 329 | public void onReceive(Context context, Intent intent) { 330 | String action = intent.getAction(); 331 | Log.d(TAG, "Received Broadcast: "+action+""); 332 | 333 | if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) { 334 | onStateChanged(context, intent); 335 | } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { 336 | onPeersChanged(context, intent); 337 | } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { 338 | onConnectionChanged(context, intent); 339 | } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) { 340 | onDeviceChanged(context, intent); 341 | } 342 | } 343 | 344 | private void onStateChanged(Context context, Intent intent) { 345 | Log.d(TAG, "***onStateChanged"); 346 | 347 | mIsWiFiDirectEnabled = false; 348 | int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1); 349 | switch (state) { 350 | case WifiP2pManager.WIFI_P2P_STATE_ENABLED: 351 | Log.d(TAG, "state: WIFI_P2P_STATE_ENABLED"); 352 | mIsWiFiDirectEnabled = true; 353 | p2pInitialize(); 354 | break; 355 | case WifiP2pManager.WIFI_P2P_STATE_DISABLED: 356 | Log.d(TAG, "state: WIFI_P2P_STATE_DISABLED"); 357 | break; 358 | default: 359 | Log.d(TAG, "state: " + state); 360 | break; 361 | } 362 | } 363 | 364 | private void onPeersChanged(Context context, Intent intent) { 365 | Log.d(TAG, "***onPeersChanged"); 366 | //intent.getParcelableExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST); or requestPeers anytime 367 | } 368 | 369 | private void onConnectionChanged(Context context, Intent intent) { 370 | Log.d(TAG, "***onConnectionChanged"); 371 | 372 | Log.d(TAG, "---------------------------------"); 373 | WifiP2pInfo wifiP2pInfo = (WifiP2pInfo) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO); 374 | Log.d(TAG, "WifiP2pInfo:"); 375 | Log.d(TAG, wifiP2pInfo.toString()); 376 | 377 | NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO); 378 | Log.d(TAG, "NetworkInfo:"); 379 | Log.d(TAG, networkInfo.toString()); 380 | 381 | WifiP2pGroup wifiP2pGroupInfo = (WifiP2pGroup) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP); 382 | Log.d(TAG, "WifiP2pGroup:"); 383 | Log.d(TAG, wifiP2pGroupInfo.toString()); 384 | Log.d(TAG, "---------------------------------"); 385 | 386 | if (!networkInfo.isConnected()) { 387 | if (mConnected) { 388 | mConnected = false; 389 | Log.d(TAG, "finish"); 390 | finish(); 391 | System.exit(0); // FIXME 392 | } 393 | } 394 | 395 | if (networkInfo.isConnected()) { 396 | if (!mConnected) { 397 | mConnected = true; 398 | if (mConnectingChecker != null) { 399 | Log.d(TAG, "removeCallbacks --- mConnectingChecker"); 400 | mHandler.removeCallbacks(mConnectingChecker); 401 | mConnectingChecker = null; 402 | } 403 | if (!mIsSinkOpened) { 404 | startWfdSink(context, intent); 405 | } 406 | } 407 | } 408 | } 409 | 410 | private void onDeviceChanged(Context context, Intent intent) { 411 | Log.d(TAG, "***onDeviceChanged"); 412 | 413 | WifiP2pDevice device = (WifiP2pDevice) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE); 414 | Log.d(TAG, "WifiP2pDevice:"); 415 | Log.d(TAG, device.toString()); 416 | 417 | if (mIsWiFiDirectEnabled && !p2pDeviceIsConnected(device)) { 418 | if (!mConnected) { 419 | Log.d(TAG, "start connecting checker --- do p2pDiscoverPeers"); 420 | mHandler.postDelayed(mConnectingChecker, 100); 421 | } 422 | } 423 | } 424 | 425 | } 426 | 427 | private void startWfdSink(Context context, Intent intent) { 428 | WifiP2pInfo wifiP2pInfo = (WifiP2pInfo) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO); 429 | //NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO); 430 | WifiP2pGroup wifiP2pGroupInfo = (WifiP2pGroup) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP); 431 | 432 | mP2pControlPort = 7236; 433 | Collection p2pDevs = wifiP2pGroupInfo.getClientList(); 434 | for (WifiP2pDevice dev : p2pDevs) { 435 | WifiP2pWfdInfo wfd = dev.wfdInfo; 436 | if (wfd != null && wfd.isWfdEnabled()) { 437 | int type = wfd.getDeviceType(); 438 | if (type == WifiP2pWfdInfo.WFD_SOURCE || type == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK) { 439 | mP2pControlPort = wfd.getControlPort(); 440 | Log.d(TAG, "got source port: " + mP2pControlPort); 441 | break; 442 | } 443 | } else { 444 | continue; 445 | } 446 | } 447 | 448 | if (wifiP2pGroupInfo.isGroupOwner()) { 449 | Log.d(TAG, "GO gets password: " + wifiP2pGroupInfo.getPassphrase()); 450 | 451 | //Log.d(TAG, "isGroupOwner, G.O. don't know client IP, so check /proc/net/arp "); 452 | // it's a pity that the MAC (dev.deviceAddress) is not the one in arp table 453 | mP2pInterfaceName = wifiP2pGroupInfo.getInterface(); 454 | Log.d(TAG, "GO gets p2p interface: " + mP2pInterfaceName); 455 | mHandler.postDelayed(mRarpChecker, 1000); 456 | } else { 457 | Log.d(TAG, "Client couldn't get password"); 458 | 459 | String sourceIp = wifiP2pInfo.groupOwnerAddress.getHostAddress(); 460 | Log.d(TAG, "Client gets GO's IP: " + sourceIp); 461 | 462 | // delayedInvokeSink(sourceIp, mP2pControlPort, 1); 463 | startWifiDisplaySinkActivity(sourceIp, mP2pControlPort); 464 | 465 | } 466 | 467 | } 468 | 469 | private void delayedInvokeSink(final String ip, final int port, int delaySec) { 470 | Log.d(TAG, "delayedInvokeSink()"); 471 | mIsSinkOpened = true; 472 | 473 | mHandler.postDelayed(new Runnable() { 474 | @Override 475 | public void run() { 476 | invokeSink(ip, port); 477 | } 478 | }, delaySec*1000); 479 | } 480 | 481 | private final void startWifiDisplaySinkActivity(String sourceAddr, int sourcePort) { 482 | Log.d(TAG, "source port: " + sourcePort + " source ip addr: " + sourceAddr); 483 | 484 | Intent itent = new Intent(this, WifiDisplaySinkActivity.class); 485 | itent.putExtra(WifiDisplaySinkConstants.SOURCE_PORT, sourcePort); 486 | itent.putExtra(WifiDisplaySinkConstants.SOURCE_ADDR, sourceAddr); 487 | 488 | utilsToastLog("source port: " + sourcePort, "source ip addr: " + sourceAddr); 489 | 490 | startActivity(itent); 491 | finish(); 492 | } 493 | 494 | private void invokeSink(String ip, int port) { 495 | Log.d(TAG, "invokeSink() Source Addr["+ip+":"+port+"]"); 496 | new AvoidANRThread(ip, port).start(); 497 | //Toast.makeText(this, "invokeSink() called native_invokeSink("+ip+":"+port+")", Toast.LENGTH_SHORT).show(); 498 | } 499 | 500 | private class AvoidANRThread extends Thread { 501 | private final String ip; 502 | private final int port; 503 | private WifiDisplaySink mSink; 504 | 505 | AvoidANRThread(String _ip, int _port) { 506 | ip = _ip; 507 | port = _port; 508 | mSink = WifiDisplaySink.create(mContext); 509 | } 510 | 511 | public void run() { 512 | mSink.invoke(ip, port); 513 | } 514 | } 515 | } 516 | -------------------------------------------------------------------------------- /src/com/lc/wifidisplaysink/WifiDisplaySink.java: -------------------------------------------------------------------------------- 1 | package com.lc.wifidisplaysink; 2 | 3 | import android.content.Context; 4 | import java.lang.ref.WeakReference; 5 | import android.util.Log; 6 | import android.os.Handler; 7 | import android.os.HandlerThread; 8 | import android.os.Looper; 9 | import android.os.Message; 10 | import android.view.SurfaceHolder; 11 | import android.view.Surface; 12 | 13 | public class WifiDisplaySink{ 14 | private final static String TAG = "WifiDisplaySinkJ"; 15 | 16 | /* Do not change these values without updating their counterparts 17 | * in lib\WifiDisplaySink.h! 18 | */ 19 | private static final int WFD_NOP = 0; // interface test message 20 | private static final int WFD_ERROR = 100; 21 | private static final int WFD_INFO = 200; 22 | 23 | public static final int WFD_ERROR_UNKNOWN = 1; 24 | public static final int WFD_ERROR_SERVER_DIED = 100; 25 | 26 | public static final int WFD_INFO_UNKNOWN = 1; 27 | public static final int WFD_INFO_RTSP_TEARDOWN = 700; 28 | 29 | private EventHandler mEventHandler; 30 | private SurfaceHolder mSurfaceHolder; 31 | 32 | public static WifiDisplaySink create(Context context) { 33 | WifiDisplaySink sink = new WifiDisplaySink(); 34 | return sink; 35 | } 36 | 37 | public WifiDisplaySink() { 38 | Looper looper; 39 | if ((looper = Looper.myLooper()) != null) { 40 | Log.d(TAG, "myLooper"); 41 | mEventHandler = new EventHandler(this, looper); 42 | } else if ((looper = Looper.getMainLooper()) != null) { 43 | Log.d(TAG, "getMainLooper"); 44 | mEventHandler = new EventHandler(this, looper); 45 | } else { 46 | Log.e(TAG, "no looper!!!!"); 47 | mEventHandler = null; 48 | } 49 | 50 | native_setup(new WeakReference(this), 0, 0); 51 | } 52 | 53 | public void setDisplay(SurfaceHolder sh) { 54 | mSurfaceHolder = sh; 55 | Surface surface; 56 | if (sh != null) { 57 | surface = sh.getSurface(); 58 | } else { 59 | surface = null; 60 | } 61 | Log.d(TAG, "setDisplay surface = "+surface); 62 | native_setVideoSurface(surface); 63 | if (mSurfaceHolder != null) { 64 | mSurfaceHolder.setKeepScreenOn(true); 65 | } 66 | } 67 | 68 | public void invoke(String ip, int port) { 69 | if (mNativeContext == 0) { 70 | Log.e(TAG, "native context should have been configured."); 71 | return; 72 | } 73 | native_invokeSink(ip, port); 74 | postEventFromNative(null, WFD_NOP, 0, 0, 0); // proguard 75 | } 76 | 77 | private static void postEventFromNative(Object sink_ref, 78 | int what, int arg1, int arg2, Object obj) 79 | { 80 | if (what == WFD_NOP) { 81 | Log.d("WifiDisplaySink", "test op"); 82 | return; 83 | } 84 | 85 | WifiDisplaySink sink = (WifiDisplaySink)((WeakReference)sink_ref).get(); 86 | if (sink == null) { 87 | Log.d("WifiDisplaySink", "sink is empty"); 88 | return; 89 | } 90 | 91 | if (sink.mEventHandler != null) { 92 | Log.d("WifiDisplaySink", "postEventFromNative"); 93 | Message m = sink.mEventHandler.obtainMessage(what, arg1, arg2, obj); 94 | sink.mEventHandler.sendMessage(m); 95 | } 96 | 97 | return; 98 | /* 99 | if (what == WFD_INFO && arg1 == WFD_INFO_RTSP_TEARDOWN) { 100 | Log.d("WifiDisplaySink", "postEventFromNative, WFD_INFO_RTSP_TEARDOWN"); 101 | //native_release(); 102 | System.exit(0); // FIXME 103 | } 104 | 105 | if (what == WFD_ERROR) { 106 | Log.d("WifiDisplaySink", "postEventFromNative, WFD_ERROR, we should notify to finish the activity?"); 107 | } 108 | 109 | Log.d("WifiDisplaySink", "postEventFromNative, what:" + what); 110 | return; 111 | */ 112 | } 113 | 114 | private static native final void native_init(); 115 | private native final void native_setup(Object sink_this, int special, int isN10); 116 | private native final void native_invokeSink(String ip, int port); 117 | private native final void native_setVideoSurface(Surface surface); 118 | 119 | private long mNativeContext; 120 | private int mListenerContext; 121 | static { 122 | System.loadLibrary("WifiDisplaySink"); 123 | native_init(); 124 | } 125 | 126 | public interface OnErrorListener 127 | { 128 | /** 129 | * Called to indicate an error. 130 | * 131 | * @param sink the WifiDisplaySink the error pertains to 132 | * @param what the type of error that has occurred 133 | * @param extra an extra code, specific to the error. Typically 134 | * implementation dependent. 135 | * @return True if the method handled the error, false if it didn't. 136 | */ 137 | boolean onError(WifiDisplaySink sink, int what, int extra); 138 | } 139 | 140 | /** 141 | * Register a callback to be invoked when an error has happened 142 | * 143 | * @param listener the callback that will be run 144 | */ 145 | public void setOnErrorListener(OnErrorListener listener) 146 | { 147 | mOnErrorListener = listener; 148 | } 149 | 150 | private OnErrorListener mOnErrorListener; 151 | 152 | private class EventHandler extends Handler 153 | { 154 | private WifiDisplaySink mWifiDisplaySink; 155 | 156 | public EventHandler(WifiDisplaySink sink, Looper looper) { 157 | super(looper); 158 | mWifiDisplaySink = sink; 159 | } 160 | 161 | @Override 162 | public void handleMessage(Message msg) { 163 | if (mWifiDisplaySink.mNativeContext == 0) { 164 | Log.w(TAG, "WifiDisplaySink went away with unhandled events"); 165 | return; 166 | } 167 | switch(msg.what) { 168 | 169 | case WFD_ERROR: 170 | Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")"); 171 | Log.d("WifiDisplaySink", "postEventFromNative, WFD_ERROR, we should notify to finish the activity?"); 172 | if (mOnErrorListener != null) { 173 | Log.d(TAG, "mOnErrorListener.onError"); 174 | mOnErrorListener.onError(mWifiDisplaySink, msg.arg1, msg.arg2); 175 | } 176 | return; 177 | 178 | case WFD_INFO: 179 | if (msg.arg1 == WFD_INFO_RTSP_TEARDOWN) { 180 | Log.d("WifiDisplaySink", "postEventFromNative, WFD_INFO_RTSP_TEARDOWN"); 181 | //native_release(); 182 | System.exit(0); // FIXME 183 | } 184 | break; 185 | 186 | case WFD_NOP: // interface test message - ignore 187 | break; 188 | 189 | default: 190 | Log.e(TAG, "Unknown message type " + msg.what); 191 | return; 192 | } 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/com/lc/wifidisplaysink/WifiDisplaySinkActivity.java: -------------------------------------------------------------------------------- 1 | package com.lc.wifidisplaysink; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.List; 6 | import java.util.Timer; 7 | import java.util.TimerTask; 8 | 9 | import android.app.Activity; 10 | import android.content.ActivityNotFoundException; 11 | import android.content.BroadcastReceiver; 12 | import android.content.Context; 13 | import android.content.Intent; 14 | import android.content.IntentFilter; 15 | import android.content.SharedPreferences; 16 | import android.content.pm.PackageManager; 17 | import android.net.NetworkInfo; 18 | import android.net.wifi.WifiInfo; 19 | import android.net.wifi.WifiManager; 20 | import android.net.wifi.p2p.WifiP2pDevice; 21 | import android.net.wifi.p2p.WifiP2pDeviceList; 22 | import android.net.wifi.p2p.WifiP2pGroup; 23 | import android.net.wifi.p2p.WifiP2pInfo; 24 | import android.net.wifi.p2p.WifiP2pManager; 25 | import android.net.wifi.p2p.WifiP2pManager.Channel; 26 | import android.net.wifi.p2p.WifiP2pManager.ChannelListener; 27 | import android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener; 28 | import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener; 29 | import android.net.wifi.p2p.WifiP2pManager.PeerListListener; 30 | import android.net.wifi.p2p.WifiP2pWfdInfo; 31 | import android.net.wifi.p2p.WifiP2pManager.ActionListener; 32 | import android.os.Build; 33 | import android.os.Bundle; 34 | import android.os.Handler; 35 | import android.provider.Settings; 36 | import android.text.Editable; 37 | import android.text.Html; 38 | import android.text.TextWatcher; 39 | import android.util.Log; 40 | import android.view.View; 41 | import android.view.WindowManager; 42 | import android.widget.AdapterView; 43 | import android.widget.AdapterView.OnItemSelectedListener; 44 | import android.widget.ArrayAdapter; 45 | import android.widget.TextView; 46 | import android.widget.Toast; 47 | import android.widget.ImageView; 48 | import android.graphics.drawable.AnimationDrawable; 49 | import android.graphics.Color; 50 | import java.lang.ref.WeakReference; 51 | import android.os.Message; 52 | import android.os.Looper; 53 | import com.lc.wifidisplaysink.WifiDisplaySink; 54 | import com.lc.wifidisplaysink.WifiDisplaySinkConstants; 55 | import com.lc.wifidisplaysink.WifiDisplaySinkUtils; 56 | import com.lc.wifidisplaysink.HidDeviceAdapterService; 57 | import android.content.ServiceConnection; 58 | import android.content.ComponentName; 59 | import android.os.IBinder; 60 | 61 | public class WifiDisplaySinkActivity extends Activity { 62 | private final String TAG = "WifiDisplaySinkActivity"; 63 | 64 | private String mSourceAddr; 65 | private int mSourcePort; 66 | private WifiDisplaySink mSink; 67 | 68 | private WifiDisplaySinkView mWifiDisplaySinkView; 69 | 70 | HidDeviceAdapterService mHidDeviceAdapterService; 71 | 72 | private ServiceConnection mConn = new ServiceConnection() { 73 | public void onServiceConnected(ComponentName name, IBinder service) { 74 | Log.d(TAG, "onServiceConnected"); 75 | if (service != null) { 76 | mHidDeviceAdapterService = ((HidDeviceAdapterService.HidDeviceAdapterBinder) service).getService(); 77 | if (mHidDeviceAdapterService != null) 78 | mWifiDisplaySinkView.setHidDeviceAdapterService(mHidDeviceAdapterService); 79 | } 80 | } 81 | 82 | public void onServiceDisconnected(ComponentName name) { 83 | Log.d(TAG, "onServiceDisconnected"); 84 | mHidDeviceAdapterService = null; 85 | } 86 | }; 87 | 88 | @Override 89 | public void onCreate(Bundle savedInstanceState) { 90 | super.onCreate(savedInstanceState); 91 | setContentView(R.layout.activity_wifidisplay_sink); 92 | 93 | View rootView = findViewById(R.id.movie_view_root); 94 | 95 | mWifiDisplaySinkView = (WifiDisplaySinkView) rootView.findViewById(R.id.surface_view); 96 | 97 | 98 | if (mWifiDisplaySinkView == null) { 99 | Log.e(TAG, "mWifiDisplaySinkView is null"); 100 | WifiDisplaySinkUtils.toastLog(this, TAG, "Error: mWifiDisplaySinkView is null"); 101 | } 102 | 103 | 104 | // TODO: 105 | mSourceAddr = "192.168.49.46"; 106 | mSourcePort = 7236; 107 | 108 | mSourcePort = getIntent().getIntExtra(WifiDisplaySinkConstants.SOURCE_PORT, 7236); 109 | mSourceAddr = getIntent().getStringExtra(WifiDisplaySinkConstants.SOURCE_ADDR); 110 | Log.d(TAG, "addr:" + mSourceAddr + " | port:" + mSourcePort); 111 | 112 | mWifiDisplaySinkView.setSourceIpAddr(mSourceAddr, mSourcePort); 113 | 114 | Intent intent = new Intent(this, HidDeviceAdapterService.class); 115 | bindService(intent, mConn, Context.BIND_AUTO_CREATE); 116 | } 117 | 118 | @Override 119 | protected void onResume() { 120 | super.onResume(); 121 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 122 | } 123 | 124 | @Override 125 | protected void onPause() { 126 | super.onPause(); 127 | // System.exit(0); // ? 128 | } 129 | 130 | @Override 131 | protected void onDestroy() { 132 | unbindService(mConn); 133 | } 134 | 135 | @Override 136 | public void onBackPressed() { 137 | super.onBackPressed(); 138 | unbindService(mConn); 139 | finish(); 140 | System.exit(0); 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/com/lc/wifidisplaysink/WifiDisplaySinkConstants.java: -------------------------------------------------------------------------------- 1 | package com.lc.wifidisplaysink; 2 | 3 | public class WifiDisplaySinkConstants { 4 | 5 | // intent extra info to start wifidisplay activity 6 | public static final String SOURCE_PORT = "source_port"; 7 | public static final String SOURCE_ADDR = "source_addr"; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/com/lc/wifidisplaysink/WifiDisplaySinkUtils.java: -------------------------------------------------------------------------------- 1 | package com.lc.wifidisplaysink; 2 | 3 | import android.widget.Toast; 4 | import android.content.Context; 5 | 6 | public class WifiDisplaySinkUtils { 7 | 8 | public static void toastLog(Context ctx, String msg1, String msg2) { 9 | String log = msg1 + System.getProperty("line.separator") + msg2; 10 | Toast.makeText(ctx, log, Toast.LENGTH_SHORT).show(); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/com/lc/wifidisplaysink/WifiDisplaySinkView.java: -------------------------------------------------------------------------------- 1 | package com.lc.wifidisplaysink; 2 | 3 | import java.lang.Math; 4 | import android.view.*; 5 | //import android.view.SurfaceView; 6 | //import android.view.SurfaceHolder; 7 | import android.content.Context; 8 | import android.util.Log; 9 | import android.util.AttributeSet; 10 | 11 | import com.lc.wifidisplaysink.WifiDisplaySink; 12 | import com.lc.wifidisplaysink.WifiDisplaySink.OnErrorListener; 13 | import com.lc.wifidisplaysink.WifiDisplaySinkUtils; 14 | import com.lc.wifidisplaysink.HidDeviceAdapterService; 15 | 16 | 17 | public class WifiDisplaySinkView extends SurfaceView { 18 | private static final String TAG = "WifiDisplaySinkView"; 19 | 20 | private String mSourceAddr = null; 21 | private int mSourcePort = 7236; 22 | private SurfaceHolder mSurfaceHolder = null; 23 | private Context mContext = null; 24 | private WifiDisplaySink mSink; 25 | private WifiDisplaySink.OnErrorListener mOnErrorListener; 26 | private HidDeviceAdapterService mHidDeviceAdapterService; 27 | 28 | private int mSurfaceWidth; 29 | private int mSurfaceHeight; 30 | 31 | private void initView() { 32 | getHolder().addCallback(mSHCallback); 33 | getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 34 | setFocusable(true); 35 | setFocusableInTouchMode(true); 36 | requestFocus(); 37 | Log.d(TAG, "WifiDisplaySink.create"); 38 | mSink = WifiDisplaySink.create(mContext); 39 | 40 | } 41 | 42 | public WifiDisplaySinkView(Context context) { 43 | super(context); 44 | mContext = context; 45 | initView(); 46 | } 47 | 48 | public WifiDisplaySinkView(Context context, AttributeSet attrs) { 49 | super(context, attrs, 0); 50 | mContext = context; 51 | initView(); 52 | } 53 | 54 | public WifiDisplaySinkView(Context context, AttributeSet attrs, int defStyle) { 55 | super(context, attrs, defStyle); 56 | mContext = context; 57 | initView(); 58 | } 59 | 60 | public void setSourceIpAddr(String sourceAddr, int sourcePort) { 61 | mSourceAddr = sourceAddr; 62 | mSourcePort = sourcePort; 63 | Log.d(TAG, "setSourceIpAddr"); 64 | } 65 | 66 | public void setOnErrorListener(OnErrorListener l) 67 | { 68 | mOnErrorListener = l; 69 | } 70 | 71 | public void setHidDeviceAdapterService(HidDeviceAdapterService service) { 72 | Log.d(TAG, "setHidDeviceAdapterService"); 73 | mHidDeviceAdapterService = service; 74 | } 75 | 76 | SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() 77 | { 78 | public void surfaceChanged(SurfaceHolder holder, int format, 79 | int w, int h) 80 | { 81 | Log.d(TAG, "surfaceChanged: size [" + w + " x " + h +"]"); 82 | mSurfaceWidth = w; 83 | mSurfaceHeight = h; 84 | } 85 | 86 | public void surfaceCreated(SurfaceHolder holder) 87 | { 88 | Log.d(TAG, "surfaceCreated"); 89 | mSurfaceHolder = holder; 90 | mSink.setDisplay(mSurfaceHolder); 91 | Log.d(TAG, "WifiDisplaySink.invoke"); 92 | mSink.invoke(mSourceAddr, mSourcePort); 93 | } 94 | 95 | public void surfaceDestroyed(SurfaceHolder holder) 96 | { 97 | Log.d(TAG, "surfaceDestroyed"); 98 | // after we return from this we can't use the surface any more 99 | mSurfaceHolder = null; 100 | //release(true); 101 | } 102 | }; 103 | 104 | 105 | private int mLastX= 0xffff, mLastY= 0xffff, mCurrentX= 0xffff, mCurrentY = 0xffff; 106 | private int mDownX= 0xffff, mDownY= 0xffff, mUpX= 0xffff, mUpY = 0xffff; 107 | 108 | @Override 109 | public boolean onTouchEvent(MotionEvent event) { 110 | switch (event.getAction()) { 111 | case MotionEvent.ACTION_DOWN: 112 | Log.d(TAG,"DOWN getX()="+event.getX()+"getY()"+event.getY()); 113 | mLastX = (int)event.getX(); 114 | mLastY = (int)event.getY(); 115 | mDownX = (int)event.getX(); 116 | mDownY = (int)event.getY(); 117 | //return false; 118 | return true; 119 | case MotionEvent.ACTION_UP: 120 | Log.d(TAG,"UP getX()="+event.getX()+"getY()"+event.getY()); 121 | 122 | mUpX = (int)event.getX(); 123 | mUpY = (int)event.getY(); 124 | if(Math.abs(mUpX - mDownX) < 15 && Math.abs(mUpY - mDownY) < 15) { 125 | Log.d(TAG, "send click"); 126 | if (mHidDeviceAdapterService != null) { 127 | mHidDeviceAdapterService.sendLeftClickReport(); 128 | } 129 | } 130 | //return false; 131 | return true; 132 | case MotionEvent.ACTION_MOVE: 133 | Log.d(TAG,"MOVE getX()="+event.getX()+"getY()"+event.getY()); 134 | mCurrentX = (int)event.getX(); 135 | mCurrentY = (int)event.getY(); 136 | 137 | int dx= mCurrentX-mLastX; 138 | int dy= mCurrentY-mLastY; 139 | byte Bdx = (byte)dx; 140 | byte Bdy = (byte)dy; 141 | 142 | //Log.d(TAG, "Mouse after" + "Bdx="+ Bdx + " Bdy=" + Bdy); 143 | Log.d(TAG, "send move"); 144 | if (mHidDeviceAdapterService != null) { 145 | mHidDeviceAdapterService.sendMouseMoveReport(Bdx, Bdy); 146 | } 147 | mLastX=mCurrentX; 148 | mLastY=mCurrentY; 149 | 150 | //return false; 151 | return true; 152 | default: 153 | break; 154 | } 155 | return false; 156 | } 157 | } 158 | --------------------------------------------------------------------------------