Вторичный монитор Windows 10/11 на экране Android-устройства через USB.
Система написана с нуля: драйвер косвенного дисплея, приложение-стример и нативный Android-клиент. Сторонних зависимостей нет — только Windows SDK/WDK и платформенные API Android.
Рабочий стол Windows → виртуальный монитор (IddCx) → разделяемая видеопамять
→ аппаратный H.264 → TCP через adb (USB) → MediaCodec → SurfaceView
Версия проверена на реальном железе сквозным прогоном, а не только сборкой.
Стенд: Windows 11 26200, Intel Arc 140T iGPU, кодировщик Intel QuickSync · vivo V2509A, Android 16 (API 36), экран 1216×2640 @ 120 Гц, декодер MediaTek.
| Метрика | Измерено |
|---|---|
| Задержка Windows: публикация кадра драйвером → отправка в сокет | 10,3 мс средняя, 15,6 мс пик |
| Кодирование (Intel QuickSync, 1080p) | 9,4 мс |
| Декодирование на телефоне | 6–14 мс |
| Композиция Android при 120 Гц | ~8,3 мс |
| Сквозная оценка | ≈26 мс |
| Потери кадров за прогон (2220 кадров) | 0 на передаче, 0 на приёме |
| Глубина очереди декодера | 0–1 пакет |
Целевые «менее 30 мс» достигнуты при панели 120 Гц. На 60 Гц следует ожидать ≈34 мс: композиция Android даёт один период панели и является крупнейшим неустранимым слагаемым.
- Драйвер поднимает виртуальный монитор, Windows разбирает сгенерированный EDID
и создаёт устройство
DISPLAY\PMN0001. - Кадры передаются в разделяемой видеопамяти без единой копии через системную.
- Клиент получает поток, декодирует аппаратно и выводит на
SurfaceView. - Согласование геометрии, отчёт о нагреве, автоматический
adb forward, переподключение после разрыва.
- Активное разрешение не переключается. Драйвер объявляет системе новый
список режимов и рапортует об успехе, но Windows оставляет монитор на
1920×1080. На телефон идёт 1080p, растянутый на его экран. Смена списка
режимов не равна переключению активного режима — нужен явный
SetDisplayConfig. - Гонка при первом назначении swapchain.
IddCxSwapChainSetDeviceвозвращает0x887A0026, повторное назначение проходит нормально. Внешне незаметно. - Intra-Refresh на Intel QuickSync принимается только как флаг: шаг волны выбирает сам кодировщик. Вариант с явным числом кадров отвергается.
| Компонент | Зачем |
|---|---|
| Visual Studio 2022 Build Tools, нагрузка «Разработка классических приложений на C++» | компилятор и Windows SDK |
| Windows Driver Kit той же версии, что SDK | только для драйвера |
| CMake 3.21+ | сборка |
| Android SDK Platform-Tools | adb |
| JDK 17 + Android SDK (или Android Studio) | сборка клиента |
Прямые ссылки и разбор частых граблей — в docs/BUILD.md.
cmake -S driver -B build/driver -A x64 && cmake --build build/driver --config Release
cmake -S streamer -B build/streamer -A x64 && cmake --build build/streamer --config Release
cd android-client && .\gradlew.bat assembleDebugЭто снижает защиту машины. Secure Boot придётся выключить в UEFI, а если включён BitLocker — сначала сохранить ключ восстановления и приостановить защиту, иначе следующая загрузка потребует 48-значный пароль. Подробный безопасный порядок — в
docs/BUILD.md, раздел 2.1.
bcdedit /set testsigning onзатем перезагрузка.
Проверить состояние защиты в любой момент:
powershell -NoProfile -ExecutionPolicy Bypass -File tools\check-security.ps1powershell -NoProfile -ExecutionPolicy Bypass -File tools\install-driver.ps1Скрипт сам запросит права, создаст тестовый сертификат, подпишет пакет,
установит его и создаст узел устройства. Вывод дублируется в
build\driver-install.log.
Удалить: тот же скрипт с ключом -Remove.
adb install -r android-client\app\build\outputs\apk\debug\app-debug.apkЗапустите приложение на телефоне (оно откроет порт 6100 и будет ждать), затем на компьютере:
build\streamer\Release\phonemonitor-streamer.exeЕсли adb не в PATH, укажите путь: --adb "C:\путь\к\adb.exe".
Полный список ключей: phonemonitor-streamer.exe --help.
| Каталог | Компонент | Технологии |
|---|---|---|
driver/ |
Драйвер косвенного дисплея | UMDF 2.0, IddCx 1.10, D3D11.4, C++17 |
streamer/ |
Приложение-стример | Win32, NVENC / Media Foundation, Winsock, C++17 |
android-client/ |
Клиент | Kotlin, MediaCodec, SurfaceView, minSdk 26 |
common/ |
Общие контракты | pm_ipc.h (v4), pm_protocol.h (v1) |
tools/ |
Скрипты | установка драйвера, проверка защиты, индикатор прогресса |
docs/ |
Документация | архитектура, сборка, диагностика |
Драйвер не кодирует и не работает с сетью. WUDFHost.exe находится под
надзором PnP-менеджера: длительная операция в нём приводит к таймауту старта
устройства. Драйвер делает ровно две вещи — отдаёт системе виртуальный монитор
и копирует кадры в разделяемую видеопамять.
Кадр не покидает видеопамять на всём пути Windows. Драйвер копирует поверхность swapchain в общую текстуру, стример открывает её на том же адаптере и отдаёт прямо в кодировщик.
Синхронизация ключевым мьютексом, а не фенсом. Исходно проект использовал
общий ID3D11Fence с передачей HANDLE через DuplicateHandle. Схема
оказалась нерабочей: драйвер живёт под ограниченной учётной записью и получает
отказ в доступе при OpenProcess на пользовательский процесс, а обратное
направление требует прав администратора. IDXGIKeyedMutex принадлежит самой
текстуре, открывается вместе с ней по имени и не требует никаких привилегий.
Разрешение диктует телефон. Клиент первым пакетом сообщает геометрию экрана и предел аппаратного декодера — это разные вещи, и устройства с экраном 2400×1080 нередко имеют декодер до 1920×1080.
Задержка не прячется в буферах. VBV кодировщика — один кадр, SO_SNDBUF —
два кадра, очередь декодера — восемь NAL. Большой буфер не устраняет затор,
а превращает его в скрытую задержку.
Реакция на нагрев — на первом же сигнале. THERMAL_STATUS_LIGHT включает
режим 720p. Ждать жёсткого троттлинга нельзя: из него система не выходит, пока
не остынет.
| GPU | Путь | Intra-Refresh |
|---|---|---|
| NVIDIA | NVENC напрямую (nvEncodeAPI64.dll, динамическая загрузка) |
полоса 5 % ширины кадра, точный контроль |
| AMD | Media Foundation MFT (официальная обёртка над AMF) | через CODECAPI_AVEncVideoGradualIntraRefresh |
| Intel | Media Foundation MFT (QuickSync) | принимается флагом, шаг выбирает кодировщик |
| Где | Что смотреть |
|---|---|
C:\ProgramData\PhoneMonitor\driver.log |
журнал драйвера, каждый шаг с меткой времени |
build\driver-install.log |
результат установки драйвера |
build\security-status.log |
состояние BitLocker, Secure Boot, тестовой подписи |
| вывод стримера | сводка каждые N кадров: частота, битрейт, задержка |
adb logcat -s PhoneMonitor:V VideoDecoder:V StreamServer:V |
журнал клиента |
Разбор типовых отказов — в docs/BUILD.md, раздел 6.
Все комментарии в исходниках и весь вывод журналов — на русском языке.