
프로토콜 리버스 엔지니어링, TLS 상호 인증, H.264 비디오 프로젝션, 터치 입력 주입 및 USB AOA를 통한 센서 데이터 스트리밍을 갖춘 오픈소스 Android Auto 휴대폰 측 구현
An open-source implementation of the Android Auto phone-side app. This app runs on your phone and projects to a car's head unit over USB, replacing Google's proprietary com.google.android.projection.gearhead APK.
이 프로젝트는 초기 개발 단계에 있습니다. 프로토콜 핸드셰이크와 비디오 투사는 실제 헤드 유닛에서 작동합니다. 휴대폰 화면이 연결 해제되기 전까지 수 초 동안 차량의 헤드 유닛에 성공적으로 표시됩니다(비디오 안정성이 개선 중입니다).
사용에 따른 책임은 사용자 본인에게 있습니다. 이 소프트웨어는 어떠한 종류의 보증 없이 "있는 그대로" 제공됩니다.
USB Plug-in → MainActivity → ProjectionService ↓ UsbAoaTransport (USB AOA accessory mode) ↓ MessageFramer (16KB frame fragmentation) ↓ InBandTls (TLSv1.2 via SSLEngine) ↓ ProtocolEngine (AAP state machine) ↓ ┌───────────┼───────────┐ Video Input Audio (H.264) (touch/keys) (PCM)
## 알려진 버그
- **채널 할당이 순서를 가정함** — SERVICE_DISCOVERY_RESPONSE의 첫 번째 `av_channel`을 비디오로, 두 번째를 오디오로 할당합니다. 이는 차량 헤드 유닛(채널 1 = 비디오)에서는 작동하지만 openauto(채널 4 = 오디오, 비디오 아님)에서는 실패합니다. 수정: `av_channel` 내부의 `stream_type` 필드를 파싱하여 `VIDEO(3)`와 `AUDIO(1)`을 구분합니다.
- **비디오 안정성** — 장시간 스트리밍 후 헤드 유닛 USB 버퍼 오버플로로 인해 연결이 끊깁니다. 아래 테스트 결과를 참조하세요.
- **헤드 유닛의 중복 장치 표시** — 헤드 유닛의 스마트폰 페이지에는 두 가지 기능을 모두 가진 단일 항목 대신 앱이 두 개의 별도 항목(하나는 Android Auto, 하나는 Bluetooth)으로 표시됩니다. 이는 Android 12+가 실제 Bluetooth MAC 주소에 대한 액세스를 차단(`02:00:00:00:00:00` 반환)하기 때문입니다. 우회 방법: `adb shell "echo $(adb shell settings get secure bluetooth_address) > /sdcard/Android/data/org.openandroidauto/files/bt_address.txt"`를 통해 실제 주소를 구성 파일에 기록합니다. 사용자가 BT MAC을 수동으로 입력할 수 있는 UI 설정 화면이 필요합니다.
- **음성 비서 버튼 처리되지 않음** — 운전자가 헤드 유닛의 음성/비서 버튼을 누르면 VOICE_SESSION_REQUEST를 수신하고 음성 비서(Dicio 또는 시스템 기본값)를 실행하려 시도합니다. 그러나 실행된 비서는 아직 헤드 유닛 마이크에서 오디오를 받지 못합니다.
### 비디오 안정성 테스트 결과
테스트 패턴(컬러 바) 800x480, I-프레임 간격 1초:
| FPS | 비트레이트 | 프래그먼트 | 지속 시간 | 프레임 | 상태 |
|-----|---------|----------|----------|--------|--------|
| 30 | 2Mbps | No | ~3s | ~90 | ❌ 너무 빠름 |
| 15 | 2Mbps | No | ~33s | ~500 | ⚠️ 더 나음 |
| 10 | 2Mbps | No | ~93s | ~930 | ⚠️ 양호 |
| 30 | 2Mbps | Yes (2KB) | 5-25s | 150-750 | ⚠️ 변동 |
| 30 | 500Kbps | Yes (2KB) | ~54s | ~1691 | ⚠️ 더 나음 |
| 15 | 250Kbps | Yes (2KB) | ~67s+ | 1000+ | ⚠️ 양호 |
| 30 | 250Kbps | Yes (2KB), I=5s | ~20s | ~600 | ❌ 긴 I-프레임에서 더 나쁨 |
| 15 | 250Kbps | No | ~13s | ~200 | ❌ 여기서는 프래그먼테이션이 도움이 되었음 |
근본 원인: 지속적인 높은 처리량에서 헤드 유닛 USB 수신 버퍼가 오버플로됩니다. 데이터 전송률이 낮을수록 연결 시간이 길어집니다.
**확인된 최상의 구성:** 10fps, 2Mbps, 프래그먼테이션 없음 = 93초. 암호화 전 프래그먼트화 구현이 손상되어 있습니다(헤드 유닛이 재조립할 수 없음) — 추가 조사가 필요합니다.
## 빌드```bash
./gradlew assembleDebug
플랫폼 35가 포함된 Android SDK가 필요합니다.
./gradlew testDebugUnitTest
프로토콜, 프레이밍, TLS, 채널 로직, 비디오 상태 머신, 센서 처리 및 터치 입력을 다루는 113개의 유닛 및 통합 테스트.
### openauto(Docker)를 이용한 통합 테스트
openauto는 Android Auto 프로토콜 전체를 구현하는 서드파티 헤드 유닛 에뮬레이터입니다. 실제 차량 없이도 프로토콜 구현을 검증하는 데 사용합니다.
#### 사전 요구사항
- Docker 설치 및 실행 중
- ADB(USB 또는 무선)로 연결된 휴대폰
- 휴대폰에 앱 설치: `./gradlew assembleDebug && adb install -r app/build/outputs/apk/debug/app-debug.apk`
#### 1. openauto Docker 이미지 빌드 (1회)```bash
cd thirdparty/openauto
docker build -f Dockerfile.headless -t openauto-headless .
이것은 Debian 컨테이너에서 모든 종속성(Qt5, boost, protobuf, OpenSSL)과 함께 openauto를 빌드합니다. 첫 빌드에는 약 5분이 소요됩니다.
docker run --rm -p 5100:5000 -e QT_QPA_PLATFORM=offscreen
openauto-headless timeout 60 /src/build/bin/autoapp
openauto는 컨테이너 내부의 포트 5000에서 수신 대기하며, 호스트의 포트 5100에 매핑됩니다. 헤드리스 모드(디스플레이 필요 없음)로 실행됩니다.
#### 3. ADB 역방향 포트 포워딩 설정```bash
adb reverse tcp:5000 tcp:5100
이렇게 하면 휴대폰의 localhost:5000이 컴퓨터의 localhost:5100(openauto)으로 터널링됩니다. USB 액세서리가 발견되지 않으면 우리 앱은 TCP 클라이언트로 localhost:5000에 연결합니다.
adb shell am start -n org.openandroidauto/.MainActivity
앱은 다음을 수행합니다:
1. USB 액세서리를 찾지 못합니다
2. `localhost:5000`에 연결합니다(adb reverse를 통한 openauto)
3. 전체 프로토콜 핸드셰이크를 수행합니다(VERSION → TLS → AUTH → SERVICE_DISCOVERY)
4. 채널을 엽니다(비디오, 오디오, 입력, 센서)
5. 비디오 스트리밍을 시작합니다(테스트 패턴)
#### 5. openauto 로그에서 확인
openauto 출력에서 다음이 표시되어야 합니다:```
[OpenAuto] handleNewClient() - Handle WIFI Client Connection
[OpenAuto] [AndroidAutoEntity] Send Version Request.
[OpenAuto] [AndroidAutoEntity] onVersionResponse()
[OpenAuto] [AndroidAutoEntity] Beginning SSL handshake.
[OpenAuto] [AndroidAutoEntity] Handshake completed.
[OpenAuto] [AndroidAutoEntity] onServiceDiscoveryRequest()
[OpenAuto] [AndroidAutoEntity] onAudioFocusRequest()
[OpenAuto] [AudioMediaSinkService] onChannelOpenRequest()
[OpenAuto] [VideoMediaSinkService] onChannelOpenRequest() (if video focus granted)
adb pull /sdcard/Android/data/org.openandroidauto/files/aa_log.txt cat aa_log.txt
로그 파일은 USB 전환 사이에도 휴대폰에 유지됩니다(실제 자동차 헤드 유닛으로 테스트할 때 유용합니다).