wifi display代码 分析
转自:http://blog.csdn.net/lilian0118/article/details/23168531
这一章中我们来看Wifi Display连接过程的建立,包含P2P的部分和RTSP的部分,首先来大致看一下Wifi Display规范相关的东西。
HIDC: Human Interface Device Class (遵循HID标准的设备类)
UIBC: User Input Back Channel (UIBC分为两种,一种是Generic,包含鼠标、键盘等;另一种是HIDC,HID是一个规范,只有遵循HID的标准,都可以叫做HID设备,包含USB鼠标、键盘、蓝牙、红外等)
PES: Packetized Elementary Stream (数字电视基本码流)
HDCP: High-bandwidth Digital Content Protection (加密方式,用于加密传输的MPEG2-TS流)
MPEG2-TS: Moving Picture Experts Group 2 Transport Stream (Wifi display之间传输的是MPEG2-TS流)
RTSP: Real-Time Streaming Protocol (Wifi display通过RTSP协议来交互两边的能力)
RTP: Real-time Transport Protocol (Wifi display通过RTP来传输MPEG2-TS流)
Wi-Fi P2P: Wi-Fi Direct
TDLS: Tunneled Direct Link Setup (另一种方式建立两台设备之间的直连,与P2P类似,但要借助一台AP)
另一种比较重要的概念是在Wifi Display中分为Source和Sink两种角色,如下图。Source是用于encode并输出TS流;Sink用于decode并显示TS流。相当于Server/Client架构中,Source就是Server,用于提供服务;Sink就是Client。当然,我们这篇文章主要介绍在Android上Wifi display Source的流程。
从上面的架构图我们可以看到,Wifi display是建立在TCP/UDP上面的应用层协议,L2链路层是通过P2P和TDLS两种方式建立,TDLS是optional的。在L2层建立连接后,Source就会在一个特定的port上listen,等待client的TCP连接。当与Client建立了TCP连接后,就会有M1~M7七个消息的交互,用户获取对方设备的能力,包括视频编码能力、Audio输出能力、是否支持HDCP加密等等。在获取这些能力之后,Source就会选择一种视频编码格式以及Audio格式用于这次会话当中。当一个RTSP会话建立后,双方就会决定出用于传输TS流的RTP port,RTP协议是基于UDP的。当这些都准备好后,Sink设备就会发送M7消息,也就是Play给Source,双方就可以开始传输数据了。
关于M1~M7是什么,我们后面再来介绍。首先我们来介绍在Android WifiDisplay中如何建立P2P的连接。
WifiDisplay之P2P的建立
- private void pairWifiDisplay(WifiDisplay display) {
- if (display.canConnect()) {
- mDisplayManager.connectWifiDisplay(display.getDeviceAddress());
- }
- }
WifiDisplaySettings通过AIDL调用到DisplayManagerService的connectWifiDisplay方法,关于AIDL的调用过程这里不讲了,直接到DisplayManagerService的connectWifiDisplay方法来看:
- public void connectWifiDisplay(String address) {
- if (address == null) {
- throw new IllegalArgumentException("address must not be null");
- }
- mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
- "Permission required to connect to a wifi display");
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mSyncRoot) {
- if (mWifiDisplayAdapter != null) {
- mWifiDisplayAdapter.requestConnectLocked(address);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
首先做参数的检查,即MAC地址不能为空,然后做权限检查,调用这个方法的application必须要在manifest中声明有CONFIGURE_WIFI_DISPLAY权限,最后直接调用WifiDisplayAdapter的requestConnectLocked方法:
- public void requestConnectLocked(final String address) {
- if (DEBUG) {
- Slog.d(TAG, "requestConnectLocked: address=" + address);
- }
- getHandler().post(new Runnable() {
- @Override
- public void run() {
- if (mDisplayController != null) {
- mDisplayController.requestConnect(address);
- }
- }
- });
- }
这里比较简单,直接调用WifiDisplayController的requestConnect方法。前面都是直接的调用,最终做事情的还是WifiDisplayController。
- public void requestConnect(String address) {
- for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
- if (device.deviceAddress.equals(address)) {
- connect(device);
- }
- }
- }
- private void connect(final WifiP2pDevice device) {
- if (mDesiredDevice != null
- && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) {
- if (DEBUG) {
- Slog.d(TAG, "connect: nothing to do, already connecting to "
- + describeWifiP2pDevice(device));
- }
- return;
- }
- if (mConnectedDevice != null
- && !mConnectedDevice.deviceAddress.equals(device.deviceAddress)
- && mDesiredDevice == null) {
- if (DEBUG) {
- Slog.d(TAG, "connect: nothing to do, already connected to "
- + describeWifiP2pDevice(device) + " and not part way through "
- + "connecting to a different device.");
- }
- return;
- }
- if (!mWfdEnabled) {
- Slog.i(TAG, "Ignoring request to connect to Wifi display because the "
- +" feature is currently disabled: " + device.deviceName);
- return;
- }
- mDesiredDevice = device;
- mConnectionRetriesLeft = CONNECT_MAX_RETRIES;
- updateConnection();
- }
requestConnect先从mAvaiableWifiDsiplayPeers中通过Mac地址找到所有连接的WifiP2pDevice,然后调用connect方法,在connect方法中会做一系列的判断,看首先是否有正在连接中或者断开中的设备,如果有就直接返回;再看有没有已经连接上的设备,如果有,也直接返回,然后赋值mDesiredDevice为这次要连接的设备,最后调用updateConnection来更新连接状态并发起连接。updateConnection的代码比较长,我们分段来分析:
- private void updateConnection() {
- n style="white-space:pre"> </span>//更新是否需要scan或者停止scan
- updateScanState();
- n style="white-space:pre"> </span>//如果有已经连接上的RemoteDisplay,先断开。这里先不看
- if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
- }
- // 接上面的一步,段开这个group
- if (mDisconnectingDevice != null) {
- return; // wait for asynchronous callback
- }
- if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
- }
- // 如果有正在连接的设备,先停止连接之前的设备
- if (mCancelingDevice != null) {
- return; // wait for asynchronous callback
- }
- if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
- }
- // 当断开之前的连接或者启动匿名GROUP时,这里就结束了
- if (mDesiredDevice == null) {
- }
- // 开始连接,这是我们要看的重点
- if (mConnectedDevice == null && mConnectingDevice == null) {
- Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);
- mConnectingDevice = mDesiredDevice;
- WifiP2pConfig config = new WifiP2pConfig();
- WpsInfo wps = new WpsInfo();
- if (mWifiDisplayWpsConfig != WpsInfo.INVALID) {
- wps.setup = mWifiDisplayWpsConfig;
- } else if (mConnectingDevice.wpsPbcSupported()) {
- wps.setup = WpsInfo.PBC;
- } else if (mConnectingDevice.wpsDisplaySupported()) {
- wps.setup = WpsInfo.KEYPAD;
- } else {
- wps.setup = WpsInfo.DISPLAY;
- }
- config.wps = wps;
- config.deviceAddress = mConnectingDevice.deviceAddress;
- config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT;
- WifiDisplay display = createWifiDisplay(mConnectingDevice);
- advertiseDisplay(display, null, 0, 0, 0);
- final WifiP2pDevice newDevice = mDesiredDevice;
- mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {
- @Override
- public void onSuccess() {
- Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);
- mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);
- }
- @Override
- public void onFailure(int reason) {
- if (mConnectingDevice == newDevice) {
- Slog.i(TAG, "Failed to initiate connection to Wifi display: "
- + newDevice.deviceName + ", reason=" + reason);
- mConnectingDevice = null;
- handleConnectionFailure(false);
- }
- }
- });
- return;
- }<span style="font-family: Arial, Helvetica, sans-serif;"> </span>
这段函数比较长,我们先看我们需要的,剩下的后面再来分析。首先赋值给mConnectingDevice表示当前正在连接的设备,然后构造一个WifiP2pConfig对象,这个对象包含这次连接的设备的Mac地址、wps方式以及我们自己的GROUP_OWNER intent值,然后调用advertieseDisplay方法来通知WifiDisplayAdapter相关状态的改变,WifiDisplayAdapter会发送相应的broadcast出来,这是WifiDisplaySettings可以接收这些broadcast,然后在UI上更新相应的状态。关于advertieseDisplay的实现,我们后面再来分析。
- } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
- NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
- WifiP2pManager.EXTRA_NETWORK_INFO);
- if (DEBUG) {
- Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="
- + networkInfo);
- }
- handleConnectionChanged(networkInfo);
- private void handleConnectionChanged(NetworkInfo networkInfo) {
- mNetworkInfo = networkInfo;
- if (mWfdEnabled && networkInfo.isConnected()) {
- if (mDesiredDevice != null || mWifiDisplayCertMode) {
- mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
- @Override
- public void onGroupInfoAvailable(WifiP2pGroup info) {
- if (DEBUG) {
- Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
- }
- if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {
- Slog.i(TAG, "Aborting connection to Wifi display because "
- + "the current P2P group does not contain the device "
- + "we expected to find: " + mConnectingDevice.deviceName
- + ", group info was: " + describeWifiP2pGroup(info));
- handleConnectionFailure(false);
- return;
- }
- if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {
- disconnect();
- return;
- }
- if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
- Slog.i(TAG, "Connected to Wifi display: "
- + mConnectingDevice.deviceName);
- mHandler.removeCallbacks(mConnectionTimeout);
- mConnectedDeviceGroupInfo = info;
- mConnectedDevice = mConnectingDevice;
- mConnectingDevice = null;
- updateConnection();
- }
- }
- });
- }
- }
当WifiDisplayController收到WIFI_P2P_CONNECTION_CHANGED_ACTION广播后,会调用handleConnectionChanged来获取当前P2P Group相关的信息,如果获取到的P2P Group信息里面没有mConnectingDevice或者mDesiredDevice的信息,则表示连接出错了,直接退出。如果当前连接信息与前面设置的mConnectingDevice一直,则表示连接P2P成功,这里首先会移除前面设置的连接timeout的callback,然后设置mConnectedDevice为当前连接的设备,并设置mConnectingDevice为空,最后调用updateConnection来更新连接状态信息。我们又回到updateConnection这个函数了,但这次进入的分支与之前连接请求的分支又不同了,我们来看代码:
- private void updateConnection() {
- // 更新是否需要scan或者停止scan
- updateScanState();
- // 如果有连接上的RemoteDisplay,这里先断开
- if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
- }
- // 接着上面的一步,先断开之前连接的设备
- if (mDisconnectingDevice != null) {
- return; // wait for asynchronous callback
- }
- if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
- }
- // 如果有正在连接的设备,先断开之前连接的设备
- if (mCancelingDevice != null) {
- return; // wait for asynchronous callback
- }
- if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
- }
- // 当断开之前的连接或者匿名GO时,这里就结束了
- if (mDesiredDevice == null) {
- }
- // 如果有连接请求,则进入此
- if (mConnectedDevice == null && mConnectingDevice == null) {
- }
- // 当连接上P2P后,就进入到此
- if (mConnectedDevice != null && mRemoteDisplay == null) {
- Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
- if (addr == null) {
- Slog.i(TAG, "Failed to get local interface address for communicating "
- + "with Wifi display: " + mConnectedDevice.deviceName);
- handleConnectionFailure(false);
- return; // done
- }
- mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE);
- final WifiP2pDevice oldDevice = mConnectedDevice;
- final int port = getPortNumber(mConnectedDevice);
- final String iface = addr.getHostAddress() + ":" + port;
- mRemoteDisplayInterface = iface;
- Slog.i(TAG, "Listening for RTSP connection on " + iface
- + " from Wifi display: " + mConnectedDevice.deviceName);
- mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
- @Override
- public void onDisplayConnected(Surface surface,
- int width, int height, int flags, int session) {
- if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
- Slog.i(TAG, "Opened RTSP connection with Wifi display: "
- + mConnectedDevice.deviceName);
- mRemoteDisplayConnected = true;
- mHandler.removeCallbacks(mRtspTimeout);
- if (mWifiDisplayCertMode) {
- mListener.onDisplaySessionInfo(
- getSessionInfo(mConnectedDeviceGroupInfo, session));
- }
- final WifiDisplay display = createWifiDisplay(mConnectedDevice);
- advertiseDisplay(display, surface, width, height, flags);
- }
- }
- @Override
- public void onDisplayDisconnected() {
- if (mConnectedDevice == oldDevice) {
- Slog.i(TAG, "Closed RTSP connection with Wifi display: "
- + mConnectedDevice.deviceName);
- mHandler.removeCallbacks(mRtspTimeout);
- disconnect();
- }
- }
- @Override
- public void onDisplayError(int error) {
- if (mConnectedDevice == oldDevice) {
- Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
- + error + ": " + mConnectedDevice.deviceName);
- mHandler.removeCallbacks(mRtspTimeout);
- handleConnectionFailure(false);
- }
- }
- }, mHandler);
- // Use extended timeout value for certification, as some tests require user inputs
- int rtspTimeout = mWifiDisplayCertMode ?
- RTSP_TIMEOUT_SECONDS_CERT_MODE : RTSP_TIMEOUT_SECONDS;
- mHandler.postDelayed(mRtspTimeout, rtspTimeout * 1000);
- }
- }
WifiDisplay之RTSP server的创建
- public static RemoteDisplay listen(String iface, Listener listener, Handler handler) {
- if (iface == null) {
- throw new IllegalArgumentException("iface must not be null");
- }
- if (listener == null) {
- throw new IllegalArgumentException("listener must not be null");
- }
- if (handler == null) {
- throw new IllegalArgumentException("handler must not be null");
- }
- RemoteDisplay display = new RemoteDisplay(listener, handler);
- display.startListening(iface);
- return display;
- }
这里首先进行参数的检查,然后创建一个RemoteDisplay对象(这里不能直接创建RemoteDisplay对象,因为它的构造函数是private的),接着调用RemoteDisplay的startListening方法:
- private void startListening(String iface) {
- mPtr = nativeListen(iface);
- if (mPtr == 0) {
- throw new IllegalStateException("Could not start listening for "
- + "remote display connection on \"" + iface + "\"");
- }
- mGuard.open("dispose");
- }
nativeListen会调用JNI中的实现,相关代码在android_media_RemoteDisplay.cpp中。注意上面的mGuard是CloseGuard对象,是一种用于显示释放一些资源的机制。
- static jint nativeListen(JNIEnv* env, jobject remoteDisplayObj, jstring ifaceStr) {
- ScopedUtfChars iface(env, ifaceStr);
- sp<IServiceManager> sm = defaultServiceManager();
- sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(
- sm->getService(String16("media.player")));
- if (service == NULL) {
- ALOGE("Could not obtain IMediaPlayerService from service manager");
- return 0;
- }
- sp<NativeRemoteDisplayClient> client(new NativeRemoteDisplayClient(env, remoteDisplayObj));
- sp<IRemoteDisplay> display = service->listenForRemoteDisplay(
- client, String8(iface.c_str()));
- if (display == NULL) {
- ALOGE("Media player service rejected request to listen for remote display '%s'.",
- iface.c_str());
- return 0;
- }
- NativeRemoteDisplay* wrapper = new NativeRemoteDisplay(display, client);
- return reinterpret_cast<jint>(wrapper);
- }
上面的代码中先从ServiceManager中获取MediaPlayerService的Bpbinder引用,然后由传入的第二个参数remoteDisplayObj,也就是RemoteDisplay对象构造一个NativeRemoteDisplayClient,在framework中,我们经常看到像这样的用法,类似于设计模式中的包装模式,例如在framework中对Java层的BnBinder也是做了一层封装JavaBBinder。在NativeRemoteDisplayClient中通过JNI的反向调用,就可以直接回调RemoteDisplay中的一些函数,实现回调方法了,下面来看它的实现:
- class NativeRemoteDisplayClient : public BnRemoteDisplayClient {
- public:
- NativeRemoteDisplayClient(JNIEnv* env, jobject remoteDisplayObj) :
- mRemoteDisplayObjGlobal(env->NewGlobalRef(remoteDisplayObj)) {
- }
- protected:
- ~NativeRemoteDisplayClient() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->DeleteGlobalRef(mRemoteDisplayObjGlobal);
- }
- public:
- virtual void onDisplayConnected(const sp<IGraphicBufferProducer>& bufferProducer,
- uint32_t width, uint32_t height, uint32_t flags, uint32_t session) {
- env->CallVoidMethod(mRemoteDisplayObjGlobal,
- gRemoteDisplayClassInfo.notifyDisplayConnected,
- surfaceObj, width, height, flags, session);
- }
- virtual void onDisplayDisconnected() {
- }
- virtual void onDisplayError(int32_t error) {
- }
- private:
- jobject mRemoteDisplayObjGlobal;
- static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
- }
- }
- };
在NativeRemoteDisplayClient的构造函数中,把RemoteDisplay对象先保存到mRemoteDisplayObjGlobal中,可以看到上面主要实现了三个回调函数,onDisplayConnected、onDisplayDisconnected、onDisplayError,这三个回调函数对应到RemoteDisplay类的notifyDisplayConnected、notifyDisplayDisconnected和notifyDisplayError三个方法。接着回到nativeListen中,接着会调用MediaPlayerService的listenForRemoteDisplay方法去监听socket连接,这个方法是返回一个RemoteDisplay对象,当然经过binder的调用,最终返回到nativeListen的是BpRemoteDisplay对象,然后会由这个BpRemoteDisplay对象构造一个NativeRemoteDisplay对象并把它的指针地址返回给上层RemoteDisplay使用。
- class NativeRemoteDisplay {
- public:
- NativeRemoteDisplay(const sp<IRemoteDisplay>& display,
- const sp<NativeRemoteDisplayClient>& client) :
- mDisplay(display), mClient(client) {
- }
- ~NativeRemoteDisplay() {
- mDisplay->dispose();
- }
- void pause() {
- mDisplay->pause();
- }
- void resume() {
- mDisplay->resume();
- }
- private:
- sp<IRemoteDisplay> mDisplay;
- sp<NativeRemoteDisplayClient> mClient;
- };
来看一下这时Java层的RemoteDisplay和Native层RemoteDisplay之间的关系:
- sp<IRemoteDisplay> MediaPlayerService::listenForRemoteDisplay(
- const sp<IRemoteDisplayClient>& client, const String8& iface) {
- if (!checkPermission("android.permission.CONTROL_WIFI_DISPLAY")) {
- return NULL;
- }
- return new RemoteDisplay(client, iface.string());
- }
首先进行权限的检查,然后创建一个RemoteDisplay对象(注意现在已经在C++层了),这里看RemoteDisplay.cpp文件。RemoteDisplay继承于BnRemoteDisplay,并实现BnRemoteDisplay中的一些方法,有兴趣的可以去看一下IRemoteDisplay的实现。接下来来看RemoteDisplay的构造函数:
- RemoteDisplay::RemoteDisplay(
- const sp<IRemoteDisplayClient> &client,
- const char *iface)
- : mLooper(new ALooper),
- mNetSession(new ANetworkSession) {
- mLooper->setName("wfd_looper");
- mSource = new WifiDisplaySource(mNetSession, client);
- mLooper->registerHandler(mSource);
- mNetSession->start();
- mLooper->start();
- mSource->start(iface);
- }
- ALooper::ALooper()
- : mRunningLocally(false) {
- }
- void ALooper::setName(const char *name) {
- mName = name;
- }
- ALooper::handler_id ALooper::registerHandler(const sp<AHandler> &handler) {
- return gLooperRoster.registerHandler(this, handler);
- }
这三个方法都比较简单,我们看LooperRoster的registerHandler方法:
- ALooper::handler_id ALooperRoster::registerHandler(
- const sp<ALooper> looper, const sp<AHandler> &handler) {
- Mutex::Autolock autoLock(mLock);
- if (handler->id() != 0) {
- CHECK(!"A handler must only be registered once.");
- return INVALID_OPERATION;
- }
- HandlerInfo info;
- info.mLooper = looper;
- info.mHandler = handler;
- ALooper::handler_id handlerID = mNextHandlerID++;
- mHandlers.add(handlerID, info);
- handler->setID(handlerID);
- return handlerID;
- }
这里为每一个注册的AHandler分配一个handlerID,并且把注册的AHandler保存在mHandlers列表中,后面使用时,就可以快速的通过HandlerID找到对应的AHandler以及ALooper了。注意这里HandlerInfo结构中的mLooper和mHander都是是wp,是一个弱引用,在使用中必须调用其promote方法获取sp指针才能使用。再回到RemoteDisplay的构造函数中看ALooper的start方法:
- status_t ALooper::start(
- bool runOnCallingThread, bool canCallJava, int32_t priority) {
- if (runOnCallingThread) {
- }
- Mutex::Autolock autoLock(mLock);
- mThread = new LooperThread(this, canCallJava);
- status_t err = mThread->run(
- mName.empty() ? "ALooper" : mName.c_str(), priority);
- if (err != OK) {
- mThread.clear();
- }
- return err;
- }
这里的runOnCallingThread会根据默认形参为false,所以会新建一个LooperThread来不断的做循环,LooperThread是继承于Thread,并实现它的readyToRun和threadLoop方法,在threadLoop方法中去调用ALooper的loop方法,代码如下:
- virtual bool threadLoop() {
- return mLooper->loop();
- }
- ALooper::loop() {
- Event event;
- {
- Mutex::Autolock autoLock(mLock);
- if (mEventQueue.empty()) {
- mQueueChangedCondition.wait(mLock);
- return true;
- }
- int64_t whenUs = (*mEventQueue.begin()).mWhenUs;
- int64_t nowUs = GetNowUs();
- if (whenUs > nowUs) {
- int64_t delayUs = whenUs - nowUs;
- mQueueChangedCondition.waitRelative(mLock, delayUs * 1000ll);
- return true;
- }
- event = *mEventQueue.begin();
- mEventQueue.erase(mEventQueue.begin());
- }
- gLooperRoster.deliverMessage(event.mMessage);
- return true;
在loop方法中,不断的从mEventQueue取出消息,并dispatch给LooperRoster处理,mEventQueue是一个list链表,其元素都是Event结构,Event结构又包含消息处理的时间以及消息本身AMessage。再来看ALooperRoster的deliverMessage方法:
- void ALooperRoster::deliverMessage(const sp<AMessage> &msg) {
- sp<AHandler> handler;
- {
- Mutex::Autolock autoLock(mLock);
- ssize_t index = mHandlers.indexOfKey(msg->target());
- if (index < 0) {
- ALOGW("failed to deliver message. Target handler not registered.");
- return;
- }
- const HandlerInfo &info = mHandlers.valueAt(index);
- handler = info.mHandler.promote();
- if (handler == NULL) {
- ALOGW("failed to deliver message. "
- "Target handler %d registered, but object gone.",
- msg->target());
- mHandlers.removeItemsAt(index);
- return;
- }
- }
- handler->onMessageReceived(msg);
- }
这里首先通过AMessage的target找到需要哪个AHandler处理,然后调用这个AHandler的onMessageReceived去处理这个消息。注意前面的info.mHandler.promote()用于当前AHandler的强引用指针,也可以用来判断当前AHandler是否还存活在。由前面的知识我们知道,这里会调用到WifiDisplaySource的onMessageReceived方法,至于这些消息如何被处理,我们后面再来分析。再回到RemoteDisplay的构造函数中,ANetworkSession用于处理与网络请求相关的工作,比如创建socket,从socket中收发数据,当然这些工作都是由WifiDisplaySource控制的,我们先来看ANetworkSession的构造方法和start方法:
- ANetworkSession::ANetworkSession()
- : mNextSessionID(1) {
- mPipeFd[0] = mPipeFd[1] = -1;
- }
- status_t ANetworkSession::start() {
- if (mThread != NULL) {
- return INVALID_OPERATION;
- }
- int res = pipe(mPipeFd);
- if (res != 0) {
- mPipeFd[0] = mPipeFd[1] = -1;
- return -errno;
- }
- mThread = new NetworkThread(this);
- status_t err = mThread->run("ANetworkSession", ANDROID_PRIORITY_AUDIO);
- if (err != OK) {
- mThread.clear();
- close(mPipeFd[0]);
- close(mPipeFd[1]);
- mPipeFd[0] = mPipeFd[1] = -1;
- return err;
- }
- return OK;
- }
在start方法中,首先创建一个管道,这里创建的管理主要用于让ANetworkSession不断的做select循环,当有事务要处理时,就从select中跳出来处理,我们后面会分析到具体的代码。接着创建一个NetworkThread,NetworkThread也是继承于Thread,并实现threadLoop方法,在threadLoop方法中只是简单的调用ANetworkSession的threadLoop方法,我们来分析threadLoop方法:
- void ANetworkSession::threadLoop() {
- fd_set rs, ws;
- FD_ZERO(&rs);
- FD_ZERO(&ws);
- FD_SET(mPipeFd[0], &rs);
- int maxFd = mPipeFd[0];
- {
- Mutex::Autolock autoLock(mLock);
- for (size_t i = 0; i < mSessions.size(); ++i) {
- const sp<Session> &session = mSessions.valueAt(i);
- int s = session->socket();
- if (s < 0) {
- continue;
- }
- if (session->wantsToRead()) {
- FD_SET(s, &rs);
- if (s > maxFd) {
- maxFd = s;
- }
- }
- if (session->wantsToWrite()) {
- FD_SET(s, &ws);
- if (s > maxFd) {
- maxFd = s;
- }
- }
- }
- }
- int res = select(maxFd + 1, &rs, &ws, NULL, NULL /* tv */);
- if (res == 0) {
- return;
- }
- if (res < 0) {
- if (errno == EINTR) {
- return;
- }
- ALOGE("select failed w/ error %d (%s)", errno, strerror(errno));
- return;
- }
- }
这个函数比较长,我们分段来看,首先看select前半段部分,首先将mPipeFd[0]作为select监听的一个fd。然后循环的从mSessions中取出各个子Session(Session即为一个回话,在RTSP中当双方连接好TCP连接,并交互完Setup以后,就表示一个回话建立成功了,在RTSP中,可以在一对Server & Client之间建立多个回话,用于传输不同的数据),并通过socket类型添加到ReadFd和WirteFd中,最后调用select去等待是否有可读或者可写的事件发生。mSessions是一个KeyedVector,保存所有的Session及其SessionID,方便查找。关于Session何时创建,如何创建,我们后面再来分析。
- WifiDisplaySource::WifiDisplaySource(
- const sp<ANetworkSession> &netSession,
- const sp<IRemoteDisplayClient> &client,
- const char *path)
- : mState(INITIALIZED),
- mNetSession(netSession),
- mClient(client),
- mSessionID(0),
- mStopReplyID(0),
- mChosenRTPPort(-1),
- mUsingPCMAudio(false),
- mClientSessionID(0),
- mReaperPending(false),
- mNextCSeq(1),
- mUsingHDCP(false),
- mIsHDCP2_0(false),
- mHDCPPort(0),
- mHDCPInitializationComplete(false),
- mSetupTriggerDeferred(false),
- mPlaybackSessionEstablished(false) {
- if (path != NULL) {
- mMediaPath.setTo(path);
- }
- mSupportedSourceVideoFormats.disableAll();
- mSupportedSourceVideoFormats.setNativeResolution(
- VideoFormats::RESOLUTION_CEA, 5); // 1280x720 p30
- // Enable all resolutions up to 1280x720p30
- mSupportedSourceVideoFormats.enableResolutionUpto(
- VideoFormats::RESOLUTION_CEA, 5,
- VideoFormats::PROFILE_CHP, // Constrained High Profile
- VideoFormats::LEVEL_32); // Level 3.2
- }
- VideoFormats::VideoFormats() {
- memcpy(mConfigs, mResolutionTable, sizeof(mConfigs));
- for (size_t i = 0; i < kNumResolutionTypes; ++i) {
- mResolutionEnabled[i] = 0;
- }
- setNativeResolution(RESOLUTION_CEA, 0); // default to 640x480 p60
- }
mResolutionTable是按照Wifi Display 规范定义好的一个3*32数组,里面的元素是config_t类型:
- struct config_t {
- size_t width, height, framesPerSecond;
- bool interlaced;
- unsigned char profile, level;
- };
config_t包含了长、宽、帧率、隔行视频、profile和H.264 level。然后在构造函数中,对mResolutionEnabled[]数组全部置为0,mResolutionEnabled数组有三个元素,分别对应CEA、VESA、HH被选取的位,如果在mConfigs数组中相应的格式被选取,就会置mResolutionEnabled对应的位为1;相反取消支持一种格式时,相应的位就被置为0。在来看setNativeResolution:
- void VideoFormats::setNativeResolution(ResolutionType type, size_t index) {
- CHECK_LT(type, kNumResolutionTypes);
- CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));
- mNativeType = type;
- mNativeIndex = index;
- setResolutionEnabled(type, index);
- }
首先做参数检查,检查输入的type和index是否合法,然后调用setResolutionEnabled去设置mResolutionEnabled和mConfigs中的相应的值:
- void VideoFormats::setResolutionEnabled(
- ResolutionType type, size_t index, bool enabled) {
- CHECK_LT(type, kNumResolutionTypes);
- CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));
- if (enabled) {
- mResolutionEnabled[type] |= (1ul << index);
- mConfigs[type][index].profile = (1ul << PROFILE_CBP);
- mConfigs[type][index].level = (1ul << LEVEL_31);
- } else {
- mResolutionEnabled[type] &= ~(1ul << index);
- mConfigs[type][index].profile = 0;
- mConfigs[type][index].level = 0;
- }
- }
这里首先还是做参数的检查,由默认形参我们知道,enable是true,则设置mResolutionEnabled相应type中的对应格式为1,并设置mConfigs中的profile和level值为CBP和Level 3.1。这里设置640*480 p60是因为在Wifi Display规范中,这个格式是必须要强制支持的,在Miracast认证中,这种格式也会被测试到。然后回到WifiDisplaySource的构造函数中,接下来会调用setNativeResolution去设置当前系统支持的默认格式为1280*720 p30,并调用enableResolutionUpto去将1280*720 p30以上的格式都设置为支持:
- void VideoFormats::enableResolutionUpto(
- ResolutionType type, size_t index,
- ProfileType profile, LevelType level) {
- size_t width, height, fps, score;
- bool interlaced;
- if (!GetConfiguration(type, index, &width, &height,
- &fps, &interlaced)) {
- ALOGE("Maximum resolution not found!");
- return;
- }
- score = width * height * fps * (!interlaced + 1);
- for (size_t i = 0; i < kNumResolutionTypes; ++i) {
- for (size_t j = 0; j < 32; j++) {
- if (GetConfiguration((ResolutionType)i, j,
- &width, &height, &fps, &interlaced)
- && score >= width * height * fps * (!interlaced + 1)) {
- setResolutionEnabled((ResolutionType)i, j);
- setProfileLevel((ResolutionType)i, j, profile, level);
- }
- }
- }
- }
这里采用width * height * fps * (!interlaced + 1)的方式去计算一个score值,然后遍历所有的mResolutionTable中的值去检查是否计算到的值比当前score要高,如果大于当前score值,就将这种分辨率enable,并设置mConfigs中对应分辨率的profile和H.264 level为CHP和Level 3.2。到这里WifiDisplaySource的构造函数分析完了,接着回到RemoteDisplay构造函数中,它会调用WifiDisplaySource的start方法,参数是的"ip:rtspPort":
- status_t WifiDisplaySource::start(const char *iface) {
- CHECK_EQ(mState, INITIALIZED);
- sp<AMessage> msg = new AMessage(kWhatStart, id());
- msg->setString("iface", iface);
- sp<AMessage> response;
- return PostAndAwaitResponse(msg, &response);
- }
- static status_t PostAndAwaitResponse(
- const sp<AMessage> &msg, sp<AMessage> *response) {
- status_t err = msg->postAndAwaitResponse(response);
- if (err != OK) {
- return err;
- }
- if (response == NULL || !(*response)->findInt32("err", &err)) {
- err = OK;
- }
- return err;
- }
在start函数中,构造一个AMessage,消息种类是kWhatStart,id()返回在ALooperRoster注册的handlerID值,ALooperRoster通过handlerID值可以快速找到对应的AHandler,我们知道,这里的id()返回WifiDisplaySource这个AHander的id值,这个消息最终也会被WifiDisplaySource的onMessageReceived方法处理。首先来看AMessage的postAndAwaitResponse方法:
- status_t AMessage::postAndAwaitResponse(sp<AMessage> *response) {
- return gLooperRoster.postAndAwaitResponse(this, response);
- }
- status_t ALooperRoster::postAndAwaitResponse(
- const sp<AMessage> &msg, sp<AMessage> *response) {
- Mutex::Autolock autoLock(mLock);
- uint32_t replyID = mNextReplyID++;
- msg->setInt32("replyID", replyID);
- status_t err = postMessage_l(msg, 0 /* delayUs */);
- if (err != OK) {
- response->clear();
- return err;
- }
- ssize_t index;
- while ((index = mReplies.indexOfKey(replyID)) < 0) {
- mRepliesCondition.wait(mLock);
- }
- *response = mReplies.valueAt(index);
- mReplies.removeItemsAt(index);
- return OK;
- }
首先会为每个需要reply的消息赋予一个replyID,后面会根据这个replyID去mReplies找到对应的response。再来看postMessage_l的实现:
- status_t ALooperRoster::postMessage_l(
- const sp<AMessage> &msg, int64_t delayUs) {
- ssize_t index = mHandlers.indexOfKey(msg->target());
- if (index < 0) {
- ALOGW("failed to post message '%s'. Target handler not registered.",
- msg->debugString().c_str());
- return -ENOENT;
- }
- const HandlerInfo &info = mHandlers.valueAt(index);
- sp<ALooper> looper = info.mLooper.promote();
- if (looper == NULL) {
- ALOGW("failed to post message. "
- "Target handler %d still registered, but object gone.",
- msg->target());
- mHandlers.removeItemsAt(index);
- return -ENOENT;
- }
- looper->post(msg, delayUs);
- return OK;
- }
首先从mHandler数组中找到当前AMessage对应的ALooper,然后调用ALooper的post方法,来看一下实现:
- void ALooper::post(const sp<AMessage> &msg, int64_t delayUs) {
- Mutex::Autolock autoLock(mLock);
- int64_t whenUs;
- if (delayUs > 0) {
- whenUs = GetNowUs() + delayUs;
- } else {
- whenUs = GetNowUs();
- }
- List<Event>::iterator it = mEventQueue.begin();
- while (it != mEventQueue.end() && (*it).mWhenUs <= whenUs) {
- ++it;
- }
- Event event;
- event.mWhenUs = whenUs;
- event.mMessage = msg;
- if (it == mEventQueue.begin()) {
- mQueueChangedCondition.signal();
- }
- mEventQueue.insert(it, event);
- }
delayUs用于做延时消息使用,会加上当前时间作为消息应该被处理的时间。然后依次比较mEventQueue链表中的所有消息,并把当前消息插入到比whenUs大的前面一个位置。如果这是mEventQueue中的第一个消息,则发出一个signal通知等待的线程。前面我们知道在ALooper的loop方法中会循环的从mEventQueue获取消息并dispatch出去给WifiDisplaySource的onMessageReceived去处理,我们接着来看这部分的实现。这里绕这么大一圈,最后WifiDisplaySource发送的消息还是给自己处理,主要是为了避开主线程处理的事务太多,通过消息机制,让更多的繁杂的活都在Thread中去完成。
- void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) {
- switch (msg->what()) {
- case kWhatStart:
- {
- uint32_t replyID;
- CHECK(msg->senderAwaitsResponse(&replyID));
- AString iface;
- CHECK(msg->findString("iface", &iface));
- status_t err = OK;
- ssize_t colonPos = iface.find(":");
- unsigned long port;
- if (colonPos >= 0) {
- const char *s = iface.c_str() + colonPos + 1;
- char *end;
- port = strtoul(s, &end, 10);
- if (end == s || *end != '\0' || port > 65535) {
- err = -EINVAL;
- } else {
- iface.erase(colonPos, iface.size() - colonPos);
- }
- } else {
- port = kWifiDisplayDefaultPort;
- }
- if (err == OK) {
- if (inet_aton(iface.c_str(), &mInterfaceAddr) != 0) {
- sp<AMessage> notify = new AMessage(kWhatRTSPNotify, id());
- err = mNetSession->createRTSPServer(
- mInterfaceAddr, port, notify, &mSessionID);
- } else {
- err = -EINVAL;
- }
- }
- mState = AWAITING_CLIENT_CONNECTION;
- sp<AMessage> response = new AMessage;
- response->setInt32("err", err);
- response->postReply(replyID);
- break;
- }
首先通过AMessage获取到replayID和iface,然后把iface分割成ip和port,分别保存在mInterfaceAddr和port中。在调用ANetSession的createRTSPServer去创建一个RTSP server,最后构造一个response对象并返回。我们先来看createRTSPServer方法:
- status_t ANetworkSession::createRTSPServer(
- const struct in_addr &addr, unsigned port,
- const sp<AMessage> ¬ify, int32_t *sessionID) {
- return createClientOrServer(
- kModeCreateRTSPServer,
- &addr,
- port,
- NULL /* remoteHost */,
- 0 /* remotePort */,
- notify,
- sessionID);
- }
- status_t ANetworkSession::createClientOrServer(
- Mode mode,
- const struct in_addr *localAddr,
- unsigned port,
- const char *remoteHost,
- unsigned remotePort,
- const sp<AMessage> ¬ify,
- int32_t *sessionID) {
- Mutex::Autolock autoLock(mLock);
- *sessionID = 0;
- status_t err = OK;
- int s, res;
- sp<Session> session;
- s = socket(
- AF_INET,
- (mode == kModeCreateUDPSession) ? SOCK_DGRAM : SOCK_STREAM,
- 0);
- if (s < 0) {
- err = -errno;
- goto bail;
- }
- if (mode == kModeCreateRTSPServer
- || mode == kModeCreateTCPDatagramSessionPassive) {
- const int yes = 1;
- res = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
- if (res < 0) {
- err = -errno;
- goto bail2;
- }
- }
- err = MakeSocketNonBlocking(s);
- if (err != OK) {
- goto bail2;
- }
- struct sockaddr_in addr;
- memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
- addr.sin_family = AF_INET;
- } else if (localAddr != NULL) {
- addr.sin_addr = *localAddr;
- addr.sin_port = htons(port);
- res = bind(s, (const struct sockaddr *)&addr, sizeof(addr));
- if (res == 0) {
- if (mode == kModeCreateRTSPServer
- || mode == kModeCreateTCPDatagramSessionPassive) {
- res = listen(s, 4);
- } else {
- if (res < 0) {
- err = -errno;
- goto bail2;
- }
- Session::State state;
- switch (mode) {
- case kModeCreateRTSPServer:
- state = Session::LISTENING_RTSP;
- break;
- default:
- CHECK_EQ(mode, kModeCreateUDPSession);
- state = Session::DATAGRAM;
- break;
- }
- session = new Session(
- mNextSessionID++,
- state,
- s,
- notify);
- mSessions.add(session->sessionID(), session);
- interrupt();
- *sessionID = session->sessionID();
- goto bail;
- bail2:
- close(s);
- s = -1;
- bail:
- return err;
- }
- void ANetworkSession::interrupt() {
- static const char dummy = 0;
- ssize_t n;
- do {
- n = write(mPipeFd[1], &dummy, 1);
- } while (n < 0 && errno == EINTR);
- if (n < 0) {
- ALOGW("Error writing to pipe (%s)", strerror(errno));
- }
- }
interrupt方法向pipe中写入一个空消息,前面我们已经介绍过threadLoop了,这里就会把刚刚创建的socket加入到监听的readFd中。到这里,关于WifiDisplay连接的建立就讲完了,后面会再从收到Sink的TCP连接请求开始讲起。最后贴一份从WifiDisplaySettings到ANetworkSession如何创建socket的时序图:
wifi display代码 分析的更多相关文章
- SonarQube-5.6.3 代码分析平台搭建使用
python代码分析 官网主页: http://docs.sonarqube.org/display/PLUG/Python+Plugin Windows下安装使用: 快速使用: 1.下载jdk ht ...
- 集成TFS Build生成与SonarQube获取代码分析结果
软件项目在开发过程中,往往由于任务重.时间紧等原因忽略软件代码的质量和规范检查,只注重软件功能的开发和交付.等软件交付上线以后,由于代码质量导致的问题会耗费开发和运维人员的大量时间.研发表明,项目上线 ...
- Qualcomm Android display架构分析
Android display架构分析(一) http://blog.csdn.net/BonderWu/archive/2010/08/12/5805961.aspx http://hi.baidu ...
- 使用 Gradle 插件进行代码分析(转)
代码分析在大多数项目中通常是作为最后一个步骤(如果做了的话)完成的.其通常难以配置及与现有代码整合. 本文旨在勾勒出使用 Gradle 整合 PMD 与 FindBugs 的步骤,并将其与一个现有的 ...
- Android Wi-Fi Display(Miracast)介绍
地址:http://blog.csdn.net/innost/article/details/8474683 Android Wi-Fi Display(Miracast)介绍 2012年11月中旬, ...
- 高通Android display架构分析
目录(?)[-] Kernel Space Display架构介绍 函数和数据结构介绍 函数和数据结构介绍 函数和数据结构介绍 数据流分析 初始化过程分析 User Space display接口 K ...
- 2018-2019-2 20165221『网络对抗技术』Exp4:恶意代码分析
2018-2019-2 20165221『网络对抗技术』Exp4:恶意代码分析 实验要求: 是监控你自己系统的运行状态,看有没有可疑的程序在运行. 是分析一个恶意软件,就分析Exp2或Exp3中生成后 ...
- Exp4 恶意代码分析 20164323段钊阳
网络对抗技术 20164323 Exp4 恶意代码分析 1.如果在工作中怀疑一台主机上有恶意代码,但只是猜想,所有想监控下系统一天天的到底在干些什么.请设计下你想监控的操作有哪些,用什么方法来监控. ...
- crt0.S(_main)代码分析
crt0,S(_main)代码分析 --- 1. 设置sp寄存器地址 //设置SP栈指针 #if defined(CONFIG_SPL_BUILD) && defined(CONFIG ...
随机推荐
- JavaScript判断是否是手机mobile登录
在页面代码中加入以下js,即可利用JavaScript判断是否是手机mobile登录! <script type="text/javascript" src="${ ...
- LAMP环境的安装
感觉一下子喜欢上了ubuntu.界面特别舒服.打算物理机装ubuntu了都. 00x1 LINUX linux我安装过了就不演示了,百度经验的一篇文章:http://jingyan.baidu.com ...
- OpenCV成长之路 01、图像的读写与显示
一.工具篇 工欲善其事,必先利其器.学习OpenCV,肯定少不于基本的编程工具与OpenCV库.在Windows平台下你可以选择Visual Studio.CodeBlock等,当然你也可以选择在Li ...
- OpenCV摄像头人脸识别
注: 从外设摄像装置中获取图像帧,把每帧的图片与人脸特征进行匹配,用方框框住识别出来的人脸 需要用到的函数: CvHaarClassifierCascade* cvLoadHaarClassifier ...
- (转)Singleton 单例模式(懒汉方式和饿汉方式)
原文地址:http://www.cnblogs.com/kkgreen/archive/2011/09/05/2166868.html 单例模式的概念: 单例模式的意思就是只有一个实例.单例模式确保某 ...
- Android 内容提供器(Content Provider)介绍
内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性.目前,使用内容 ...
- OpenGL官方教程——着色器语言概述
OpenGL官方教程——着色器语言概述 OpenGL官方教程——着色器语言概述 可编程图形硬件管线(流水线) 可编程顶点处理器 可编程几何处理器 可编程片元处理器 语言 可编程图形硬件管线(流水线) ...
- 关于QQ使用的一些代码
http://wiki.open.qq.com/wiki/website/网站接入wiki索引
- 流程图制作在云上 https://www.processon.com/
流程图制作在云上 : https://www.processon.com/
- Android SDK打包
2015年6月18日 14:38:49 星期四 eclipse: 1. 将写好的代码上传版本库 2. 删除 /bin/* 3. eclipse->project->clean... 4. ...