Android开放配件 (AOA) 协议

发布时间 2023-11-29 12:32:39作者: sheldon_blogs

一、背景

  自Android 3.1之后的版本,Google引入了USB Accessories的概念,并提供了相关的开发库。Android3.1之后的版本不仅可以让Android设备作为USB Host的角色支持USB鼠标、键盘、游戏手柄等,还可以以USB Device的角色与一些具有USB Host功能,但却扮演着配件角色的设备相连,Google把这种设备称为“Accessory”(附件)。这类Accessory可能是如下设备:机器人控制器、Dock(基座)、诊断设备、音响设备、配电设备、读卡器等等。

  Google引入USB Accessory概念的原因应该主要有如下:

  • 非常多的Android设备不具有USB Host的功能而只具有USB Device功能(例如绝大部分Android手机),或者即使具备USB Host的功能,也承担不起对USB外设供电的任务,因为便携式Android设备本身的电池容量就很有限。
  • 原来的Android设备,作为USB Device所实现的功能相对比较简单,内置的功能只有U盘或ADB调试设备等,Google希望提供应用层的USB开发库,让更多的软硬件厂商来开发新的功能,比如说安装一个APK应用,然后通过USB连接到一个与电视机配套的Dock上,就可以让一台Android手机变身为一个电视机遥控器。

  以此借助一套标准的AOA(Android Open Accessory)协议,方便Android设备和外围设备通过USB进行交互,实现各种 Android 设备功能扩展。

 

二、Usb Accessory的设计实现

  Android 设备可以通过主机模式和配件模式和各种USB设备通信。

  • 主机模式 Host Mode : Android设备充当USB HOST,USB配件充当 USB DEVICE,Android 设备负责给总线供电及枚举。
  • 配件模式 Accessory Mode :USB配件充当USB HOST,Android设备充当USB DEVICE,USB配件为 Android 设备提供电源并进行枚举,与主机模式相反。

  两种模式如下图所示:

 

 软件架构图:

 

AOA 协议有 1.0 和 2.0 两个版本,2.0版本是对对1.0版本的补充,增加了对Audio和HID类Accessory设备的支持:

版本产品 ID通信说明
AOAv1 0x2D00 配件 提供两个批量端点,用于与 Android 应用通信。
0x2D01 配件 + adb 在配件开发过程中用于调试。仅当用户在 Android 设备设置中启用了“USB 调试”时才可用。
AOAv2 0x2D02 音频 将音频从 Android 设备流式传输至配件。
0x2D03 音频 + adb  
0x2D04 配件 + 音频  
0x2D05 配件 + 音频 + adb

AOA 协议将上述3种接口组合出6种 USB 接口层设备,这些USB设备的厂商 ID 统一为 0x18D1 (Google Inc)。如需确定连接的 Android 设备是否支持配件和支持的协议版本,该配件必须发送 getProtocol() 命令并检查结果。仅支持 AOAv1 功能的 Android 设备必须返回 1 作为协议版本,支持 AOAv2 的额外功能的设备必须返回 2 作为协议版本。AOAv2 向后兼容 AOAv1,因此基于原始配件协议设计的配件将可以兼容更高版本的 Android 设备。

注:音频输出在 Android 8.0 中已被弃用。谷歌官网 https://source.android.com/docs/core/interaction/accessories/aoa2?hl=zh-cn 中有详细介绍 

这里简单介绍一下USB Composite(复合)设备,对于大部分USB Device设备来说,它仅仅只有一个功能,比如大部分U盘,单个的USB鼠标等,但是也有些USB设备不止实现一个功能,比如某些USB上网卡有无线上网的功能,同时还有U盘存储的功能,又比如有的鼠标和键盘二合一设备,它只有一个USB接口,却同时支持了鼠标和键盘两个功能。这种一个USB接口扩展出多个设备功能的实现方法有两种,一种是在设备外部或内部加Hub扩展;另一种就是以Usb Composite Device方式实现(一般称为复合设备)。复合设备其实只是一个USB设备,只有一个USB设备地址,它实现多个功能的原因主要在于它扩展实现了多个USB接口,每个接口具有不同的设备类型。

Usb accessory模式也基于复合设备驱动实现,具体源码在内核的 drivers/usb/gadget/function/ 目录下,有 f_accessory.c 、f_audio_source.c、f_hid.c 等等,详细的驱动剖析可参考: Android USB之复合设备(gadget)详解 。

 

三、使用Usb Accessory模式

1.Device端, 也就是当前Android设备作为配件。

  原生就有配置,只要设置属性:setprop sys.usb.config accessory ,即可开启Accessory模式,对应的usb口要支持OTG功能,并且切成device模式。

  然后可以向驱动节点: /dev/usb_accessory  写入数据即可,会通过usb往外发送给Host端。参考如下3种测试方法:

echo "123" > /dev/usb_accessory  //echo字符串
cat /sdcard/DCIM/test.mp4 > /dev/usb_accessory  //cat文件,注意默认是一次写入4k大小,跟驱动缓存大小有关。
busybox dd if=/sdcard/DCIM/test.mp4 of=/dev/usb_accessory bs=128K count=100  // dd方式写入,可以随意指定每次写入的大小。

  也可以写个简单的demo,核心就是读写 /dev/usb_accessory 节点:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#define DRIVER_NAME "/dev/usb_accessory"

#define MAX_USBFS_BUFFER_SIZE   16384

int open_accessory()
{
    int fd = open(DRIVER_NAME, O_RDWR);
    if (fd < 0) {
        fprintf(stderr, "Error: could not open %s\n", DRIVER_NAME);
        return -1;
    }
    return fd;
}

static void usb_accessory_read(int fd)
{
    char c;
    char buf[MAX_USBFS_BUFFER_SIZE];
    int n;
    while((n = read(fd, buf, MAX_USBFS_BUFFER_SIZE)) >= 0)
    {
        fprintf(stderr, "Error: %d\n", n);
        write(1, buf, n);
        putchar('\n');
    }
}

static void usb_accessory_write(int fd, char* buff, int length)
{
    char write_buff[MAX_USBFS_BUFFER_SIZE + 1] = {0x00};

    length = (length >= MAX_USBFS_BUFFER_SIZE) ? MAX_USBFS_BUFFER_SIZE : length;

    fprintf(stderr, "Error: length %d\n", length);
    memcpy(write_buff, buff, length);
    int result = write(fd, write_buff, length);
    fprintf(stderr, "Error: result %d\n", result);
}

int main()
{
    usb_accessory_write(open_accessory(), "123456789", 9);
    //usb_accessory_read(open_accessory());
    return 0;
}

 

2.Host端,也就是当前Android设备作为主设备,可以读写连接过来的配件。

  host端不需要额外的usb config, 只要确认同样支持对应版本AOA协议的版本, 测试demo可以是app 使用标准的API,也可以在native层基于libusbhost库实现。