android系统 usb2.0 双屏异显 多屏异显

双屏异显原理及实现详解

基于 Android 13 POS + AM8268N USB转HDMI + MiraPlug + Presentation API 的实测验证


背景

android手持主机,采用MTK方案,希望通过usb2.0 口扩展屏幕,且实现主屏幕和扩展屏幕的不同显示。
AM8268N USB转HDMI,是一个usb拓展坞,JD购买,型号: HY41-T4(双HMDI版)

一、整体架构

┌─────────────────────────────────────────────────────────────────────────┐
│                         POS 主机 (Android 13)                            │
│                                                                         │
│  ┌─────────────┐     ┌──────────────────────┐                          │
│  │ Display 0   │     │ Display N (虚拟)      │   N=动态ID (4/6/...)     │
│  │ 720×1600    │     │ 496×1088 @60fps       │                          │
│  │ 主屏Activity│     │ Presentation (独立UI)  │                          │
│  └──────┬──────┘     └───────────┬──────────┘                          │
│         │                        │                                      │
│         │           ┌────────────▼───────────┐                          │
│         │           │  VirtualDisplay Surface  │  layerStack=N           │
│         │           └────────────┬───────────┘                          │
│         │                        │                                      │
│         │              SurfaceFlinger 合成                               │
│         │                        │                                      │
│  ┌──────▼────────────────────────▼──────────┐                          │
│  │        MiraPlug (com.actionsmicro.usbdisplay)                       │
│  │                                                                     │
│  │  ┌──────────────────────────────────────────────────────┐           │
│  │  │  MediaProjection                            │                      │
│  │  │  ├─ 路径A: 抓取主屏(Display 0) → JPEG压缩    │ 镜像模式       │
│  │  │  ├─ 路径B: 抓取VirtualDisplay(N) → 副屏内容  │ 异显模式       │
│  │  │  └─ DisplayPass Protocol 封装               │                      │
│  │  └──────────────────────────────────────────────────────┘           │
│  └──────────────────────────┬───────────────────────┘                  │
│                             │ USB Bulk Transfer                         │
└─────────────────────────────┼───────────────────────────────────────────┘
                              │
                    ┌─────────▼──────────┐
                    │   USB Hub (触点底座) │
                    └─────────┬──────────┘
                              │
                    ┌─────────▼──────────┐
                    │  AM8268N 芯片      │
                    │  (USB 2.0→HDMI)    │
                    │  JPEG硬解 → HDMI TX │
                    └─────────┬──────────┘
                              │ HDMI
                    ┌─────────▼──────────┐
                    │   外接显示器       │
                    │   496×1088 竖屏    │
                    └────────────────────┘

二、核心链路:AM8268N USB转HDMI

2.1 AM8268N 芯片

参数规格
厂商炬力北方 (Actions Micro)
封装QFN68, 内置512Mb DDR2
输入USB 2.0 (Host端)
输出HDMI 1.2, 最高 1080P@60Hz
协议DisplayPass Protocol (私有)
解码能力JPEG硬解 + H.264硬解
Android支持9.0+ (官网确认)

2.2 数据传输原理

AM8268N 不走标准HDMI Alt Mode,也不走DisplayPort over USB-C。它的工作方式是:

Android Host                          AM8268N
    │                                     │
    │  逐帧 JPEG 压缩 (软件/硬件)          │
    │  ──────────────────────────────►     │
    │  USB Bulk Transfer (帧数据)          │  JPEG硬解码
    │  ──────────────────────────────►     │
    │  DisplayPass 控制指令                │  HDMI TX → 显示器
    │  ──────────────────────────────►     │
    │                                     │

关键特性:

  • 不是像素流:传输的是压缩后的JPEG帧(通常30-50KB/帧),而非原始RGB像素
  • 不是视频流:是逐帧非连续传输,由Host端按需推送
  • USB 2.0 带宽足够:30fps × 50KB = 1.5MB/s,USB 2.0实际带宽约30MB/s,余量充足
  • 延迟可控:JPEG压缩→USB传输→硬解→HDMI输出,总延迟约30-80ms

三、MiraPlug 的角色

3.1 包信息

包名: com.actionsmicro.usbdisplay
UID:  10107
核心Activity:
  - MainActivity         : 用户交互界面(开始/停止投屏)
  - ProjectionActivity   : 屏幕捕获服务(MediaProjection)
  - UsbDeviceActivity    : USB设备连接处理

3.2 启动流程(从logcat追踪得出)

1. 用户点击"开始投屏"
     │
2. MainActivity → start ProjectionActivity
     │
3. 系统弹出 MediaProjectionPermissionActivity(Android安全机制)
   "是否允许录制屏幕?" → 用户点"立即开始"
     │
4. ProjectionActivity 获得 MediaProjection 授权
     │
5. USB_DEVICE_ATTACHED 广播 → 检测AM8268N
     │
6. createVirtualDisplay("SCREENCAST_VIRTUAL", 496×1088, FLAG_PRESENTATION)
     │
7. Display N 注册到系统,开始投屏

3.3 VirtualDisplay 创建细节

dumpsys display 分析,MiraPlug 创建的VirtualDisplay关键参数:

DisplayManager.createVirtualDisplay(
    "SCREENCAST_VIRTUAL",           // name
    496, 1088,                       // width, height
    240,                             // densityDpi (hdpi)
    targetSurface,                   // 输出Surface → USB发送
    VIRTUAL_DISPLAY_FLAG_PRESENTATION // 支持Presentation API
);

重要发现:Display ID 是动态的

状态Display ID原因
MiraPlug未运行无虚拟DisplayVirtualDisplay未创建
第一次启动投屏Display 4系统按序分配
重新启动投屏Display 6销毁旧的,创建新的,ID递增
再重启Display 8…持续递增

因此应用中不能硬编码 Display ID,必须通过 DisplayManager.DISPLAY_CATEGORY_PRESENTATION 动态查找。


四、双屏异显的核心:Android Presentation API

4.1 原理

Presentation 是 Android 4.2 (API 17) 引入的API,允许在非主屏上显示独立窗口:

class SecondScreenPresentation(context: Context, display: Display) 
    : Presentation(context, display) {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.second_screen) // 独立的布局
    }
}

4.2 渲染管线

┌───────────────────┐     ┌───────────────────┐
│   Display 0       │     │   Display N       │
│   layerStack=0    │     │   layerStack=N    │
│                   │     │                   │
│  MainActivity     │     │  Presentation     │
│  (Window #1)      │     │  (Window #1)      │
│  ↓                │     │  ↓                │
│  SurfaceFlinger   │     │  SurfaceFlinger   │
│  (layerStack 0)   │     │  (layerStack N)   │
│  ↓                │     │  ↓                │
│  物理屏幕Surface   │     │  VirtualDisplay   │
│                   │     │  Surface          │
└───────────────────┘     └────────┬──────────┘
                                   │
                          MiraPlug 读取此Surface
                          → JPEG压缩 → USB → AM8268N → HDMI

关键点:

  • Display 0 和 Display N 各自拥有独立的 layerStack
  • SurfaceFlinger 在每个 layerStack 上独立合成
  • Presentation 的 UI 渲染到 VirtualDisplay 的 Surface 上
  • MiraPlug 读取这个 Surface 的内容发送到 AM8268N

4.3 为什么 mHasContent=false 也能工作?

dumpsys display 显示 VirtualDisplay 的 mHasContent=false。这是因为 MiraPlug 不通过 layerStack 渲染镜像内容(它用 MediaProjection 直接抓主屏 Surface),但 Presentation 窗口直接渲染到 VirtualDisplay 的 Surface,SurfaceFlinger 在独立的 layerStack 上合成,MiraPlug 读取的是合成后的最终 Surface 输出。

4.4 FLAG_PRESENTATION 的作用

VIRTUAL_DISPLAY_FLAG_PRESENTATION
  • 使 Display 出现在 DisplayManager.DISPLAY_CATEGORY_PRESENTATION 分类中
  • 允许在该 Display 上创建 Presentation 窗口
  • Presentation 的 Context 自动关联到此 Display
  • 资源(metrics, density)基于此 Display 配置

五、关键实现代码

5.1 动态查找副屏

val dm = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val presentationDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)

// 优先匹配包含"SCREENCAST"的(MiraPlug注册的)
val externalDisplay = presentationDisplays.firstOrNull { 
    it.name?.contains("SCREENCAST") == true 
} ?: presentationDisplays.firstOrNull()

5.2 监听 Display 变化

dm.registerDisplayListener(object : DisplayManager.DisplayListener {
    override fun onDisplayAdded(displayId: Int) {
        // MiraPlug启动 → 重新扫描
        scanDisplays()
    }
    override fun onDisplayRemoved(displayId: Int) {
        // MiraPlug断开 → 关闭Presentation
        if (externalDisplay?.displayId == displayId) {
            dismissPresentation()
        }
    }
    override fun onDisplayChanged(displayId: Int) {}
}, null)

5.3 显示/关闭副屏

fun showOnExternal() {
    presentation = SecondScreenPresentation(context, externalDisplay!!)
    presentation?.show()
}

fun hideExternal() {
    presentation?.dismiss()
    presentation = null
}

六、与传统方案对比

维度传统HDMI Alt ModeAM8268N + Presentation
物理接口USB-C DP Alt Mode / 独立HDMI口任意USB 2.0口
SoC要求需原生HDMI或DP输出无要求(USB即可)
成本HDMI控制器+连接器AM8268N ¥8~12
传输方式原始像素流JPEG压缩帧
分辨率支持4K@60 (DP1.4)1080P@60 (HDMI 1.2)
延迟<1ms30-80ms
适用场景视频播放、游戏静态/半静态内容(营业厅叫号等)
双屏异显原生支持Presentation API
驱动复杂度Kernel层Display驱动用户态APK即可

七、适用场景与限制

适用场景

  • 营业厅叫号系统(主屏操作,副屏显示号码)
  • POS收银(主屏收银,副屏显示客户信息)
  • 信息展示(主屏管理,副屏展示公告/广告)
  • 身份核验(主屏录入,副屏显示比对结果)

限制

  • 副屏不支持触摸输入(AM8268N 无触控回传通道)
  • 动态画面(视频、动画)质量受JPEG压缩影响
  • 延迟30-80ms,不适合实时交互
  • 依赖MiraPlug进程运行(VirtualDisplay由它创建)
  • AM8268N 无音频通道

八、实测数据

项目
POS系统Android 13 (API 33)
主屏分辨率720×1600, 240dpi
副屏分辨率496×1088, 240dpi
副屏类型VIRTUAL, FLAG_PRESENTATION
副屏ownercom.actionsmicro.usbdisplay (uid=10107)
连接方式POS 触点底座 → USB Hub → AM8268N → HDMI
ADB连接WiFi: 192.168.31.54:5555
编译环境Android SDK 36, Gradle 8.13, JDK 21
工程路径android_demo\DualScreenTest
APK大小~23MB (Debug, 含Compose依赖)

文档生成日期: 2026-06-09

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值