本文主要記錄對spydroid源碼的閱讀筆記
源代碼:https://github.com/fyhertz/spydroid-ipcamera
使用步驟 局域網用手機實現視頻監控
1.下載運行測試 apk :https://fir.im/qnhb
2.設置裏把 HTTP server 和 RTSP server 都打開
3.下載vlc (播放rtsp流) ,填入地址,地址爲手機首頁顯示的局域網地址
4.右擊 播放 就可以啦
一個問題:由於手機攝像投角度的問題,VLC 直接播放的是沒有旋轉處理的畫面,即攝像頭原始數據notes:
1.廣告(增加收入?) :https://www.google.com/admob/ 2.sdk 23以上不再支持org.apache.http,非要用高版本sdk,則 spydroid/build.gradle 添加 android { useLibrary 'org.apache.http.legacy' } 3.電池狀態監聽 net.majorkernelpanic.spydroid.SpydroidApplication.mBatteryInfoReceiver registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); 4.指定跳到系統桌面 Intent setIntent = new Intent(Intent.ACTION_MAIN);//指定跳到系統桌面 setIntent.addCategory(Intent.CATEGORY_HOME); setIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //setIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); //清除上一步緩存 startActivity(setIntent); 5.保持屏幕常亮 新姿勢 需要電源管理的權限 <uses-permission android:name="android.permission.WAKE_LOCK"/> PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); wakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, "WAKE_LOCK"); wakeLock.acquire(); // onStop() // A WakeLock should only be released when isHeld() is true ! if (mWakeLock.isHeld()) mWakeLock.release(); 附:關於int flags 各種鎖的類型對CPU 、屏幕、鍵盤的影響: PARTIAL_WAKE_LOCK: 保持CPU 運轉,屏幕和鍵盤燈有可能是關閉的。 SCREEN_DIM_WAKE_LOCK: 保持CPU 運轉,允許保持屏幕顯示但有可能是灰的,允許關閉鍵盤燈 SCREEN_BRIGHT_WAKE_LOCK:保持CPU 運轉,允許保持屏幕高亮顯示,允許關閉鍵盤燈 FULL_WAKE_LOCK: 保持CPU 運轉,保持屏幕高亮顯示,鍵盤燈也保持亮度 ACQUIRE_CAUSES_WAKEUP: 正常喚醒鎖實際上並不打開照明。相反,一旦打開他們會一直仍然保持(例如來世user的activity)。 當獲得wakelock,這個標誌會使屏幕或/和鍵盤立即打開。一個典型的使用就是可以立即看到那些對用戶重要的通知。 ON_AFTER_RELEASE: 設置了這個標誌,當wakelock釋放時用戶activity計時器會被重置, 導致照明持續一段時間。如果你在wacklock條件中循環,這個可以用來減少閃爍 6. Notification & PendingIntent Intent notificationIntent = new Intent(this, SpydroidActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT); NotificationCompat.Builder builder = new NotificationCompat.Builder(this); Notification notification = builder.setContentIntent(pendingIntent) .setWhen(System.currentTimeMillis()) .setTicker(getText(R.string.notification_title)) .setSmallIcon(R.drawable.icon) .setContentTitle(getText(R.string.notification_title)) .setContentText(getText(R.string.notification_content)).build(); notification.flags |= Notification.FLAG_ONGOING_EVENT; ((NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE)).notify(0,notification); 7. PreferenceActivity 設置 以 Notification 的打開關閉爲例: 佈局 : xml/preferences.xml:91 android:key="notification_enabled" 改變的監聽 :net/majorkernelpanic/spydroid/SpydroidApplication.java:150 if (key.equals("notification_enabled")) 8.ViewPager 的 title android.support.v4.view.PagerTitleStrip layout use in layout/spydroid.xml:27 FragmentPagerAdapter only needs @Override public CharSequence getPageTitle(int position) 9.bindService() 切記 需要 unbindService(conn); bindService(new Intent(xxx.this,Service.class),conn,flag)-->Service:onCreate()-->Service:onBind()-->conn:onServiceConnected() 如果多次調用 bindService ,onBind()方法只會執行一次 Google API:https://developer.android.com/guide/components/bound-services.html 10.Intent.FLAG_ACTIVITY_NO_HISTORY net/majorkernelpanic/spydroid/ui/AboutFragment.java:62 用這個FLAG啓動的Activity,一旦退出,它不會存在於棧中,比方說!原來是A,B,C這個時候再C中以這個FLAG啓動D的,D再啓動E,這個時候棧中情況爲A,B,C,E。 以web 打開網頁或者應用商店 Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse("https://code.google.com/p/spydroid-ipcamera/")); // Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id="+appPackageName)); intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); startActivity(intent); 11.程序運行時,進入net.majorkernelpanic.spydroid.ui.SpydroidActivity,該activity 運行時候,開啓http server ,rtsp server 12.rtsp server: net.majorkernelpanic.streaming.rtsp.RtspServer.start start 裏 開啓一個線程負責監聽客戶端的請求(VLC) mListenerThread = new RequestListener(); 當有客戶端請求時,開啓一個WorkerThread 線程 new WorkerThread(mServer.accept()).start();一個線程session代表一個請求 其實就是服務器的主線程負責接收客戶的連接, 每次接收到一個客戶連接, 就會創建一個工作線程, 由它負責與客戶的通信. 13.http server:net.majorkernelpanic.http.TinyHttpServer.start 開啓一個線程負責監聽 mHttpRequestListener = new HttpRequestListener(mHttpPort); 14. 12 和 13 裏面都使用的通信方式 : ServerSocket 這兩部分的代碼邏輯類似,只是使用的協議不同,都是充當服務器使用 在客戶/服務器通信模式中, 服務器端需要創建監聽端口的 ServerSocket, ServerSocket 負責接收客戶連接請求 Socket socket = serverSocket.accept();//從連接隊列中取出一個連接,如果沒有則等待 15. 目前只測試了rtsp協議是OK的,所以先重點分享 rtsp 目前爲止已經大概搞清楚了數據傳輸方式,使用ServerSocket,接下來看看如何把數據傳輸出去 net.majorkernelpanic.streaming.rtsp.RtspServer.Response 這個是作爲結果返回回去的,有個很重要的處理接收的方法 net.majorkernelpanic.streaming.rtsp.RtspServer.WorkerThread.processRequest,裏面使用了Session類 net.majorkernelpanic.streaming.Session 這個類用來包裝音頻或者視頻, 然後可以找到 H264Stream extends VideoStream ,AACStream extends AudioStream 在 net.majorkernelpanic.streaming.rtsp.RtspServer.WorkerThread.processRequest #428 裏調用了handleRequest #437 調用net.majorkernelpanic.streaming.rtsp.UriParser.parse 配置session, Session 中含有 AudioStream VideoStream ,在方法 net.majorkernelpanic.streaming.Session.getSessionDescription 中獲取 描述信息,而後被賦值給 response.content; 感覺獲取到的音頻或者視頻幀數據就在 getSessionDescription()裏被處理的 16. audio : AACStream --> AACStream.encodeWithMediaCodec --> 開啓一個線程AudioRecord獲取音頻數據送個MediaCodec編碼 解碼過程和解碼數據封裝爲 MediaCodecInputStream ,處理數據的過程封裝爲 AbstractPacketizer 這樣一個抽象類 這個類裏包含了一個 net.majorkernelpanic.streaming.rtp.RtpSocket 這樣一個維護 一個先進先出的隊列的一個線程, 這個RtpSocket 負責將數據處理爲 rtsp 數據,SenderReport 負責處理待發送的數據 SenderReport類包含着處理好的待發送出去的數據 等待着serverSocket發送數據 看來傳播數據的部分在這裏 request.method.equalsIgnoreCase("SETUP") 17. video :H264Stream --> VideoStream.encodeWithMediaCodec --> VideoStream.encodeWithMediaCodecMethod1 與 音頻的處理類似,也是封裝爲 MediaCodecInputStream ,只是 AbstractPacketizer的實現類變爲 H264Packetizer 大致的過程就是這個樣子,MediaCodec處理攝像頭獲取的數據,編碼後的output數據經 MediaCodecInputStream 處理後包裝成 H264Packetizer 進一步包裝爲 Session ,然後就可以作爲 response 數據響應客戶端的請求