手機鏡像投射電腦Scrcpy教學及用Python撰寫腳本自動化投射

軟體是開源的Scrcpy,這個開源程式功能非常強大,對於想要直播手機遊戲的玩家來說,這是非常好的程式,不僅免費連功能也是非常夠用,重點是還可以搭配python來自定義投射方式...

簡述

在TunaunoClicker中是用鏡像投射的方式來辨識手機畫面,用的軟體是開源的Scrcpy,這個開源程式功能非常強大,對於想要直播手機遊戲的玩家來說,這是非常好的程式,不僅免費連功能也是非常夠用,重點是還可以搭配python來自定義投射方式。

Scrcpy可以用usb或wifi來進行投射,本篇會教大家如何用usb及wifi來投射,並且也會教大家如何用python來自定義投射方式且自動化。

⚠️注意事項⚠️

Android 裝置至少需要 API 21 (Android 5.0)。

正文

前置作業

Step1(下載軟體)

首先下載Scrcpy主程式及SDK Platform Tools。

以上為下載網址,SDK Platform Tools點選進去後往下拉到下載內容,點擊下載 Windows 版 SDK Platform-Tools,Scrcpy點選進去後往下拉到Assets下載scrcpy-win64-(版本都會更新載最新的即可)。

Step2(加入環境變數)

可以到這裡參考如何加入環境變數。

Step3(開啟手機的USB偵錯功能)

可以到這裡參考如何加入環境變數。

usb投射

一切準備就緒之後就可以開始使用啦,先將手機與電腦連接,並且開啟cmd(在搜尋打上cmd),在cmd輸入

scrcpy

如果沒接手機或沒開啟usb偵錯的話會出現

scrcpy v2.2 <https://github.com/Genymobile/scrcpy>
* daemon not running; starting now at tcp:5037
* daemon started successfully
ERROR: Could not find any ADB device
ERROR: Server connection failed

如果有接會出現

scrcpy v2.2 <https://github.com/Genymobile/scrcpy>
INFO: ADB device found:
INFO:     -->   (usb)           R5******11N            device  SM_G9860
C:\Users\bb929\Downloads\scrcpy-win64-v2.2\scrcpy-server: 1 file pushed, 0 skipped. 5.4 MB/s (64363 bytes in 0.011s)
[server] INFO: Device: [samsung] samsung SM-G9860 (Android 13)
INFO: Renderer: direct3d
INFO: Texture: 1440x3200

然後就可以透過usb使用啦~

wifi投射

Step1(設定網路)

首先要先確認電腦及手機是要在同一個區網下進行,確認好之後找出手機的ip位址。

以三星S23為例,在設定中找到關於手機。

在關於手機中點選”狀態資訊”,再狀態資訊中找到ip位址,並且記錄下來。

進入開發人員選項(開啟方式),往下滑到偵錯將usb偵錯及無線偵錯開啟,開啟wifi偵錯時按允許(如果是信任的wifi環境可以勾選一律允許)。

⚠️注意事項⚠️

記得要將手機連接電腦,網路設定好後就可以拔除

得知手機ip位址後,開啟cmd並輸入

adb tcpip 5555

cmd會回傳

restarting in TCP mode port: 5555

將adb的tcpip的port設定5555,並且再輸入

adb connect 192.168.0.200:5555

其中192.168.0.200要換成您自己的手機ip,cmd會回傳

connected to 192.168.0.200:5555

表示連接成功。

Step2(打開Scrcpy)

如果手機有連接到電腦的話直接輸入

scrcpy

會回傳

scrcpy v2.2 <https://github.com/Genymobile/scrcpy>
ERROR: Multiple (2) ADB devices:
ERROR:     -->   (usb)           R5******11N            device  SM_G9860
ERROR:     --> (tcpip)    192.168.0.200:5555            device  SM_G9860
ERROR: Select a device via -s (--serial), -d (--select-usb) or -e (--select-tcpip)
ERROR: Server connection failed

代表說目前有兩個裝置在線(一個是透過usb,另一個則是透過tcpip),如果拔除usb直接在輸入”scrcpy”就可以正常用wifi執行。

或是可以輸入

scrcpy -s 192.168.0.200:5555

直接選擇要開啟哪個設備。

自動設定網路

還記得在快速開啟常用程式中介紹到如何利用bat來做到自動開啟應用程式嗎?

在根據那個方法可以自動設定網路,在.txt檔裡面貼上以下指令(記得ip要換自己的喔)

adb tcpip 5555
adb connect 192.168.0.200:5555
scrcpy -s 192.168.0.200:5555

再將.txt改成.bat,在重新命名任何你想取的名稱,就可以直接開啟檔案就直接設定好網路並且直接用wifi方式投影。

詳細過程請參閱快速開啟常用程式

斷開連接

如果使用完畢後請將網路設定清除,將手機連接至電腦,並開啟cmd輸入

adb disconnect

會回傳

disconnected everything

表示清除完畢

查找目前連接設備

如果想要知道目前有哪些設備連接可以在cmd輸入

scrcpy --list-displays

python自動化開啟

如果想要用python來寫一個自動開啟現有的裝置並且從螢幕最左邊開始顯示並且根據螢幕寬度顯示下一個手機畫面,可以參照以下範例

import subprocess
import pyautogui
import msvcrt

# 儲存每個Scrcpy實例的進程和對應的裝置ID
scrcpy_processes = {}

#希望螢幕的高為多少
scrcpyHigh = 800

#順移的pixel為多少
windowPixel = 0

def get_screen_resolution(device_id):
    try:
        result = subprocess.check_output(["adb", "-s", device_id, "shell", "wm", "size"]).decode().strip()
        resolution = result.split()[-1]  # 提取分辨率部分

        # 按 'x' 字元拆分解析度字串,提取高度和寬度
        width, height = map(int, resolution.split('x'))
        return width, height
    except subprocess.CalledProcessError:
        return None, None


# 啟動Scrcpy
def start_scrcpy(device_id, window_x, window_y):
    process = subprocess.Popen(["scrcpy", "-s", device_id, "--window-title", device_id, "--window-x", str(window_x), "--window-y", str(window_y), "--always-on-top","-m",str(scrcpyHigh)])
    return process

# 停止Scrcpy
def stop_scrcpy(process, device_id):
    if process:
        process.terminate()

# 監控設備連線狀態
def get_connected_devices():
    try:
        result = subprocess.check_output(["adb", "devices"]).decode()
    
        connected_devices = []
        for line in result.splitlines()[1:]:
            parts = line.split()
            if len(parts) >= 2:
                connected_devices.append(parts[0])
        return connected_devices
    except subprocess.CalledProcessError:
        return []
    


if __name__ == "__main__":
    # 計算每個 Scrcpy 視窗的初始位置
    window_x = 0
    window_y = 0

    max_scrcpy_instances = 6

    while True:
        # 取得目前已連接裝置列表
        connected_devices = get_connected_devices()

        open_instances = 0
        # 停止Scrcpy實例(如果裝置已連線)
        for device_id, scrcpy_process in list(scrcpy_processes.items()):
            if device_id not in connected_devices:
                stop_scrcpy(scrcpy_process, device_id)
                del scrcpy_processes[device_id]
            else:
                open_instances += 1

        

        # 啟動Scrcpy實例(如果裝置已連線)
        for device_id in connected_devices:
            if device_id not in scrcpy_processes and open_instances < max_scrcpy_instances:
                scrcpy_processes[device_id] = start_scrcpy(device_id, window_x, window_y)
                open_instances += 1

                phoneWidth,phoneHigh = get_screen_resolution(device_id)

                windowPixel = int(scrcpyHigh/phoneHigh*phoneWidth)

                # 等待一段時間確保視窗已經創建
                pyautogui.sleep(2)

                # 計算下一個視窗的初始位置
                window_x += windowPixel

                # 如果視窗超出螢幕寬度,重設 x,並調整 y
                if window_x + windowPixel > pyautogui.size().width:
                    window_x = 0
                

以上這個範例是可以根據目前有幾個adb設備在線就從螢幕最左邊開始依據顯示,如果有同台手機且一個是tcpip另一個是usb則兩個都會顯示,如果斷開adb連線則會自動關閉畫面。

程式碼說明

這段副程式是提取手機畫面的大小,雖然每台手機的高都會固定800,但是每台手機的比例不同,因此寬度就有所不同。

def get_screen_resolution(device_id):
    try:
        result = subprocess.check_output(["adb", "-s", device_id, "shell", "wm", "size"]).decode().strip()
        resolution = result.split()[-1]  # 提取分辨率部分

        # 按 'x' 字元拆分解析度字串,提取高度和寬度
        width, height = map(int, resolution.split('x'))
        return width, height
    except subprocess.CalledProcessError:
        return None, None

啟動及停止Scrcpy

# 啟動Scrcpy
def start_scrcpy(device_id, window_x, window_y):
    process = subprocess.Popen(["scrcpy", "-s", device_id, "--window-title", device_id, "--window-x", str(window_x), "--window-y", str(window_y), "--always-on-top","-m",str(scrcpyHigh)])
    return process

# 停止Scrcpy
def stop_scrcpy(process, device_id):
    if process:
        process.terminate()

監控設備連線狀態

# 監控設備連線狀態
def get_connected_devices():
    try:
        result = subprocess.check_output(["adb", "devices"]).decode()
    
        connected_devices = []
        for line in result.splitlines()[1:]:
            parts = line.split()
            if len(parts) >= 2:
                connected_devices.append(parts[0])
        return connected_devices
    except subprocess.CalledProcessError:
        return []

停止Scrcpy實例(如果裝置已連線)

        for device_id, scrcpy_process in list(scrcpy_processes.items()):
            if device_id not in connected_devices:
                stop_scrcpy(scrcpy_process, device_id)
                del scrcpy_processes[device_id]
            else:
                open_instances += 1

啟動Scrcpy實例(如果裝置已連線)

for device_id in connected_devices:
            if device_id not in scrcpy_processes and open_instances < max_scrcpy_instances:
                scrcpy_processes[device_id] = start_scrcpy(device_id, window_x, window_y)
                open_instances += 1

                phoneWidth,phoneHigh = get_screen_resolution(device_id)

                windowPixel = int(scrcpyHigh/phoneHigh*phoneWidth)

                # 等待一段時間確保視窗已經創建
                pyautogui.sleep(2)

                # 計算下一個視窗的初始位置
                window_x += windowPixel

                # 如果視窗超出螢幕寬度,重設 x,並調整 y
                if window_x + windowPixel > pyautogui.size().width:
                    window_x = 0

其中最重要的程式碼是這行

process = subprocess.Popen(["scrcpy", "-s", device_id, "--window-title", device_id, "--window-x", str(window_x), "--window-y", str(window_y), "--always-on-top","-m",str(scrcpyHigh)])

這裡就是直接按照從cmd開啟Scrcpy的方式一樣,根據指令可以設定開啟時窗口名稱、連接id、視窗大小及是否常駐顯示於螢幕最上方。

根據以上這行可以更彈性的使用Scrcpy,想怎麼開啟或是畫面怎麼呈現都是非常自由,剩下就看各位怎麼發揮溜~

小測驗

如果今天想改成自動設定網路且有tcpip的設備就自動開啟的話要怎麼做呢?

就留給各位去練習囉!

進階使用

如果覺得Scrcpy只能usb及wifi螢幕投射功能的話,那就太小看他了,其實他還有很多的使用技巧,想要更了解詳情可以到Scrcpy,往下滑到User documentation有很多花樣可以玩。

其中還可以直接投射手機的鏡頭,不過這要是v2.2版本以上才有支援,不然會有錯誤。

⚠️注意事項⚠️

要Android 12 或更高版本的裝置才支援相機投射

相機權限問題

不過經過實測發現,如果直接使用v2.2版本會出現類似相機權限的問題,以下為錯誤資訊

[server] INFO: Device: [samsung] samsung SM-S9180 (Android 14)
[server] ERROR: java.lang.reflect.InvocationTargetException
java.lang.AssertionError: java.lang.reflect.InvocationTargetException
        at com.genymobile.scrcpy.wrappers.ServiceManager.getCameraManager(ServiceManager.java:145)
        at com.genymobile.scrcpy.LogUtils.buildCameraListMessage(LogUtils.java:89)
        at com.genymobile.scrcpy.Server.internalMain(Server.java:228)
        at com.genymobile.scrcpy.Server.main(Server.java:190)
        at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
        at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:415)
Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Constructor.newInstance0(Native Method)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
        at com.genymobile.scrcpy.wrappers.ServiceManager.getCameraManager(ServiceManager.java:143)
        ... 5 more
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int android.content.Context.checkSelfPermission(java.lang.String)' on a null object reference
        at android.content.ContextWrapper.checkSelfPermission(ContextWrapper.java:982)
        at android.hardware.camera2.CameraManager.<init>(CameraManager.java:229)
        ... 8 more

可能是初始化相機在2.2版本沒有寫好,換到2.3.1版本的時候卻可以執行,但是降級到2.2版本也是能用,所以問題應該就是出現在權限問題,因為如果又換一台沒有在2.3.1版本的手機在2.2版本下執行的話就又會跳出以上問題。

所以遇到類似上面的錯誤訊息的話可以將版本更新到v2.3.1以上。

投射相機

如果要查詢有哪些可以使用的相機,可以在cmd輸入

scrcpy --list-cameras

會回傳

[server] INFO: Device: [samsung] samsung SM-S9180 (Android 14)
[server] INFO: List of cameras:
    --camera-id=0    (back, 4080x3060, fps=[10, 11, 15, 24, 30])
    --camera-id=1    (front, 4000x3000, fps=[10, 15, 24, 30])
    --camera-id=2    (back, 4000x3000, fps=[10, 15, 24, 30])
    --camera-id=3    (front, 3392x2544, fps=[10, 15, 24, 30])

可以看到有id=0、1、2、3的相機,分別是前置鏡頭及後鏡頭,那為甚麼會多兩個呢?

因為解析度不同,而且經過實測看起來差別是分別有廣角的前後置鏡頭跟沒廣角的前後置鏡頭。

如果想指定開啟哪個相機可以輸入

scrcpy --video-source=camera --camera-id=0

可以選擇想要投射的相機id,將id=0的0換成其他相機id就可以換了。

常有問題

為何會出現’scrcpy’ 不是內部或外部命令、可執行的程式或批次檔或’adb’ 不是內部或外部命令、可執行的程式或批次檔。

因為沒有確實的把資料夾放進環境變數,請詳細參閱這裡的步驟有沒有問題。

為何設定adb tcpip 5555時會顯示”error: no devices/emulators found”?

因為在網路設定時要先將手機插上電腦,並且要開啟usb偵錯。

為何開啟相機投射會出現錯誤?

請參考這裡

一定只能設定5555嗎?

不一定,你想設甚麼都沒問題,只是不要影響到其他會用到的port就好。

結論

Scrcpy的使用場景非常多,應該算是目前安卓最流行的投影程式,很可惜的是在ios沒有像這樣如此強大的開源程式可以用,不過就是封閉環境,安卓如此開放,安全性也是相對不足,雖然scrcpy可以用tcpip的方式投影,但是過程完全沒加密,因此很容易被別人盜取,因此在使用時需要非常小心,盡量不要在不信任的wifi環境開啟此功能。

用python的方式寫一個腳本開啟關閉也是可以應用在很多場景,各位小夥伴可以依造自己的需求去打造酷酷的程式吧!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *