2021년 9월 22일 수요일

scrcpy (안드로이드 단말을 PC에 연결해서 화면 보기, 스크린 미러링) #4 소스 분석


앞에서 jar파일의 main이 sever.java여기에 있고 실행시키는것까지 확인하였습니다.

server.java에서는 아마도(?) 소켓을 만들고 대기하고 있을것입니다.

전형적인 PME(Property Method Event)프로그래밍 방식에서는 무한 루프에 빠지는 형태를 취하지는 않습니다. 그러나 여기 소스는 android의 cmd line 형태를 취하고 있고 전혀 android activity 구조를 가지고 있지 않습니다.


scrcpy 함수에서 아래 함수를 볼 수 있는데

screenEncoder.streamScreen(device, connection.getVideoFd());

이 부분이 화면을 압축하여 지속적으로 전달하기 위한 함수 입니다.

내부에는 주요 함수들도 많지만, 특히 아래함수가 주요 함수로 보면됩니다.

private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException {





다음으로는 입력에대한 처리입니다.

scrcpy 소스 근처의 controller 쪽을 참고 하면됩니다.

                final Controller controller = new Controller(device, connection);


                // asynchronous

                controllerThread = startController(controller);

thread를 만들고 control 함수를 실행시키게 되는데 이부분의 소스는 Controller.java가 담당합니다.

public void control() throws IOException {
// on start, power on the device
if (!Device.isScreenOn()) {
device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER);
// dirty hack
// After POWER is injected, the device is powered on asynchronously.
// To turn the device screen off while mirroring, the client will send a message that
// would be handled before the device is actually powered on, so its effect would
// be "canceled" once the device is turned back on.
// Adding this delay prevents to handle the message before the device is actually
// powered on.
SystemClock.sleep(500);
}
while (true) {
handleEvent();
}
}

별건 없고 hadleEvenet게 주업무를 담당하고 있습니다.

키를 누르거나 터치를 하게 되면 아래 함수에 의해서 처리가 되는데


private void handleEvent() throws IOException {
ControlMessage msg = connection.receiveControlMessage();
switch (msg.getType()) {
case ControlMessage.TYPE_INJECT_KEYCODE:


누군가가 메세지를 보내준걸 읽게 됩니다.

public ControlMessage receiveControlMessage() throws IOException {
ControlMessage msg = reader.next();
while (msg == null) {
reader.readFrom(controlInputStream);
msg = reader.next();
}
return msg;
}

그렇다면 그 누군가가 누구일까요? 바로 PC의 connection온 데이터 겠죠. 모든 입력은 PC쪽에서 발생하게 되고 server jar 쪽으로 전달되게 됩니다.

아래와 같은 함수로 touch가 눌린것처럼 행동하게 됩니다.

injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getButtons());

좀 더 따라가보면

아래 형태로 inject 를 하게 됩니다.

public static boolean injectEvent(InputEvent inputEvent, int displayId) {
if (!supportsInputEvents(displayId)) {
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
}
if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) {
return false;
}
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}

여기에서 inputmanager는 이렇고,

public InputManager getInputManager() {
if (inputManager == null) {
inputManager = new InputManager(getService("input", "android.hardware.input.IInputManager"));
}
return inputManager;
}

여기에 전달하는 inputevent는 아래와 같습니다

Controller.java 소스를 참고 하면 됩니다.

MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source,
0);
return device.injectEvent(event);







댓글 없음:

댓글 쓰기