日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

【译】Introducing scrcpy

發(fā)布時(shí)間:2025/3/15 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【译】Introducing scrcpy 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

我開(kāi)發(fā)了一個(gè)應(yīng)用程序來(lái)顯示和控制連接在USB上的Android設(shè)備。?它不需要任何root訪問(wèn)權(quán)限。?它適用于GNU / Linux,Windows和Mac OS。

它側(cè)重于:

  • 亮度?(原生,僅顯示設(shè)備屏幕)
  • 表演?(30~60fps)
  • 質(zhì)量?(1920×1080或以上)
  • 低延遲?(70~100ms)
  • 啟動(dòng)時(shí)間短?(顯示第一張圖像約1秒)
  • 非侵入性?(設(shè)備上沒(méi)有安裝任何東西)

就像我之前的項(xiàng)目gnirehtet一樣?,?Genymobile接受了開(kāi)源:?scrcpy?。

您可以構(gòu)建,安裝和運(yùn)行它。

scrcpy如何工作?

應(yīng)用程序在設(shè)備上執(zhí)行服務(wù)器。?客戶端和服務(wù)器通過(guò)adb隧道上的套接字進(jìn)行通信。

服務(wù)器流式傳輸設(shè)備屏幕的H.264視頻。?客戶端解碼視頻幀并顯示它們。

客戶端捕獲輸入(鍵盤(pán)和鼠標(biāo))事件,將它們發(fā)送到服務(wù)器,服務(wù)器將它們注入設(shè)備。

文檔提供了更多詳細(xì)信息。

在這里,我將詳細(xì)介紹應(yīng)用程序可能感興趣的應(yīng)用程序的幾個(gè)技術(shù)方面。

最大限度地減少延遲

沒(méi)有緩沖

編碼,傳輸和解碼視頻流需要時(shí)間。?為了減少延遲,我們必須避免任何額外的延遲。

例如,讓我們使用screenrecord流式傳輸屏幕并使用VLC播放:

adb exec-out screenrecord --output-format=h264 - | vlc - --demux h264

最初,它可以工作,但很快就會(huì)延遲并且?guī)黄茐摹?原因是VLC將PTS與幀相關(guān)聯(lián),并緩沖流以在某個(gè)目標(biāo)時(shí)間播放幀。

因此,它有時(shí)會(huì)在stderr上打印出這樣的錯(cuò)誤:

ES_OUT_SET_(GROUP_)PCR is called too late (pts_delay increased to 300 ms)

就在我開(kāi)始這個(gè)項(xiàng)目之前,與WebRTC一起工作的同事Philippe建議我“手動(dòng)”解碼(使用FFmpeg?)并渲染幀,以避免任何額外的延遲。?這使我免于浪費(fèi)時(shí)間,這是正確的解決方案。

解碼視頻流以使用FFmpeg檢索單個(gè)幀非常簡(jiǎn)單?。

跳過(guò)幀

如果由于任何原因,渲染被延遲,則丟棄解碼的幀,以便scrcpy始終顯示最后一個(gè)解碼的幀。

請(qǐng)注意,可以使用配置標(biāo)志更改此行為:

mesonconf x -Dskip_frames=false

在Android上運(yùn)行Java main

捕獲設(shè)備屏幕需要一些權(quán)限,這些權(quán)限授予shell?。

通過(guò)從adb shell調(diào)用app_process?,可以在Android上執(zhí)行Java代碼作為adb shell?。

你好,世界!

這是一個(gè)簡(jiǎn)單的Java應(yīng)用程序:

public class HelloWorld { public static void main ( String ... args ) { System . out . println ( "Hello, world!" ); } }

讓我們編譯并解釋它:

javac -source 1.7 -target 1.7 HelloWorld.java "$ANDROID_HOME"/build-tools/27.0.2/dx \ --dex --output classes.dex HelloWorld.class

然后,我們將classes.dex推送到Android設(shè)備:

adb push classes.dex /data/local/tmp/

并執(zhí)行它:

$ adb shell CLASSPATH=/data/local/tmp/classes.dex app_process / HelloWorld Hello, world!

訪問(wèn)Android框架

應(yīng)用程序可以在運(yùn)行時(shí)訪問(wèn)Android框架。

例如,讓我們使用android.os.SystemClock?:

import android.os.SystemClock ; public class HelloWorld { public static void main ( String ... args ) { System . out . print ( "Hello," ); SystemClock . sleep ( 1000 ); System . out . println ( " world!" ); } }

我們將我們的類(lèi)與android.jar?:

javac -source 1.7 -target 1.7 \ -cp "$ANDROID_HOME"/platforms/android-27/android.jar HelloWorld.java

然后像以前一樣運(yùn)行它。

請(qǐng)注意,scrcpy還需要從框架中訪問(wèn)隱藏的方法?。?在這種情況下,鏈接android.jar是不夠的,所以它使用反射?。

就像一個(gè)APK

如果classes.dex嵌入在zip / jar中,則執(zhí)行也有效:

jar cvf hello.jar classes.dex adb push hello.jar /data/local/tmp/ adb shell CLASSPATH=/data/local/tmp/hello.jar app_process / HelloWorld

你知道一個(gè)包含classes.dex的zip的例子嗎??一個(gè)APK?!

因此,它適用于任何已安裝的APK,其中包含一個(gè)帶有main方法的類(lèi):

$ adb install myapp.apk … $ adb shell pm path my.app.package package:/data/app/my.app.package-1/base.apk $ adb shell CLASSPATH=/data/app/my.app.package-1/base.apk \ app_process / HelloWorld

在scrcpy中

為了簡(jiǎn)化構(gòu)建系統(tǒng),我決定使用gradle將服務(wù)器構(gòu)建為APK,即使它不是真正的Android應(yīng)用程序:?gradle提供運(yùn)行測(cè)試,檢查樣式??等的任務(wù)。

以這種方式調(diào)用,服務(wù)器被授權(quán)捕獲設(shè)備屏幕。

改善啟動(dòng)時(shí)間

快速安裝

用戶無(wú)需在設(shè)備上安裝任何內(nèi)容:在啟動(dòng)時(shí),客戶端負(fù)責(zé)在設(shè)備上執(zhí)行服務(wù)器。

我們看到我們可以從APK執(zhí)行服務(wù)器的主要方法:

  • 安裝,或
  • 推送到/data/local/tmp?。

哪一個(gè)選擇?

$ time adb install server.apk … real 0m0,963s … $ time adb push server.apk /data/local/tmp/ … real 0m0,022s …

所以我決定推。

請(qǐng)注意,?/data/local/tmp是shell可讀寫(xiě)的,但不是/data/local/tmp可寫(xiě)的,因此惡意應(yīng)用程序可能無(wú)法在客戶端執(zhí)行之前替換服務(wù)器。

并行

如果你執(zhí)行了Hello,那么世界!?在上一節(jié)中,您可能已經(jīng)注意到運(yùn)行app_process需要一些時(shí)間:?Hello, World!?在一些延遲之前(0.5到1秒之間)不打印。

在客戶端中,初始化SDL也需要一些時(shí)間。

因此,這些初始化步驟已經(jīng)并行化?。

清理設(shè)備

使用后,我們要從設(shè)備中刪除服務(wù)器(?/data/local/tmp/scrcpy-server.jar?)。

我們可以在退出時(shí)將其刪除,但之后,它將在設(shè)備斷開(kāi)連接時(shí)保留。

相反,一旦app_process打開(kāi)服務(wù)器,?scrcpy?unlink?s(?rm?)就可以了。?因此,文件僅存在不到1秒(甚至在顯示屏幕之前它也被刪除)。

當(dāng)最后一個(gè)關(guān)聯(lián)的打開(kāi)文件描述符關(guān)閉時(shí)(最遲,當(dāng)app_process死亡時(shí)),實(shí)際上刪除了文件本身(不是它的名字)。

處理文本輸入

處理從鍵盤(pán)接收的輸入比我想象的更復(fù)雜。

活動(dòng)

有兩種“鍵盤(pán)”事件:

  • 關(guān)鍵事件,
  • 文字輸入事件。

鍵事件提供?掃描碼?(鍵盤(pán)上鍵的物理位置)和鍵碼?(取決于鍵盤(pán)布局)。?scrcpy只使用密鑰?代碼?(它不需要物理密鑰的位置)。

但是,關(guān)鍵事件不足以處理文本輸入?:

有時(shí)可能需要多次按鍵才能產(chǎn)生角色。?有時(shí)一次按鍵可以產(chǎn)生多個(gè)字符。

即使是簡(jiǎn)單的字符也可能無(wú)法通過(guò)鍵事件輕松處理,因?yàn)樗鼈內(nèi)Q于布局。?例如,在法語(yǔ)鍵盤(pán)上輸入.?(點(diǎn))生成Shift?+?;?。

因此,?scrcpy僅針對(duì)一組有限的密鑰將密鑰事件轉(zhuǎn)發(fā)給設(shè)備。?其余的由文本輸入事件處理。

注入文字

在Android方面,我們可能不直接注入文本(注入由相關(guān)構(gòu)造函數(shù)創(chuàng)建的KeyEvent不起作用)。?相反,我們可以使用getEvents(char[])檢索為char[]生成的KeyEvent列表。

例如:

char [] chars = { '?' }; KeyEvent [] events = charMap . getEvents ( chars );

在這里,使用4個(gè)事件的數(shù)組初始化事件:

  • 按KEYCODE_SHIFT_LEFT
  • 按KEYCODE_SLASH
  • 發(fā)布KEYCODE_SLASH
  • 發(fā)布KEYCODE_SHIFT_LEFT
  • 正確地注入這些事件會(huì)產(chǎn)生char?'?'?。

    處理重音字符

    不幸的是,以前的方法僅適用于ASCII字符:

    char [] chars = { 'é' }; KeyEvent [] events = charMap . getEvents ( chars ); // events is null!!!

    我首先想到?jīng)]有辦法從那里注入這樣的事件,直到我與Philippe討論(是的,和之前一樣),誰(shuí)知道解決方案:當(dāng)我們使用組合變音死鍵字符分解字符時(shí)它起作用。

    具體而言,我們注入"\u0301e"而不是注入"é"?"\u0301e"?:

    char [] chars = { '\u0301' , 'e' }; KeyEvent [] events = charMap . getEvents ( chars ); // now, there are events

    因此,為了支持重音字符,?scrcpy嘗試使用KeyComposition?分解字符。

    編輯:重音字符不適用于虛擬鍵盤(pán)Gboard(默認(rèn)的谷歌鍵盤(pán)),但使用默認(rèn)(AOSP)鍵盤(pán)和SwiftKey。

    設(shè)置一個(gè)窗口圖標(biāo)

    應(yīng)用程序窗口可能有一個(gè)圖標(biāo),用于標(biāo)題欄(對(duì)于某些桌面環(huán)境)和/或桌面任務(wù)欄中。

    必須通過(guò)SDL_SetWindowIcon從SDL_Surface設(shè)置窗口圖標(biāo)。?使用圖標(biāo)內(nèi)容創(chuàng)建表面取決于開(kāi)發(fā)人員。?例如,我們可以決定從PNG文件加載圖標(biāo),或者直接從內(nèi)存中的原始像素加載圖標(biāo)。

    相反,另一位同事Aurélien建議我使用XPM圖像格式,這也是一個(gè)有效的C源代碼:?icon.xpm?。

    請(qǐng)注意,圖像不是icon_xpm聲明的變量icon_xpm的內(nèi)容:它是整個(gè)文件!?因此,?icon.xpm既可以在Gimp中直接打開(kāi),也可以包含在C源代碼中:

    #include "icon.xpm"

    作為一個(gè)好處,我們直接“識(shí)別”源代碼中的圖標(biāo),我們可以輕松地對(duì)其進(jìn)行修補(bǔ):在調(diào)試模式下,?圖標(biāo)顏色會(huì)發(fā)生變化。

    結(jié)論

    開(kāi)發(fā)這個(gè)項(xiàng)目是一個(gè)令人敬畏和激勵(lì)的經(jīng)驗(yàn)。?我學(xué)到了很多東西(之前從未使用過(guò)SDL或者libav / FFmpeg?)。

    由此產(chǎn)生的應(yīng)用程序比我最初預(yù)期的更好,我很高興能夠開(kāi)源它。

    討論reddit和黑客新聞?。

    https://blog.rom1v.com/2018/03/introducing-scrcpy/

    總結(jié)

    以上是生活随笔為你收集整理的【译】Introducing scrcpy的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。