Android usb学习笔记:Android AOA协议设备端 流程总结
Posted vonchenchen1
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android usb学习笔记:Android AOA协议设备端 流程总结相关的知识,希望对你有一定的参考价值。
背景
前段时间的项目开发中,由于wifi稳定性的限制,项目采用了android手机与嵌入式设备通过usb直接连接的方式进行通信。其中Android的usb层使用了Android自身的AOA模式,嵌入式端借助libusb库与Android端通信。在应用层简单实现了一个tcp连接,最终可以抽象为双方socket端口与端口间的通信过程。探索的过程比较曲折,其间受到两位同事也是前辈的帮助指导,收获颇多。
实现
AOA协议实现流程简述
下面是我对AOA通信过程的一些理解
1.Android连接设备,设备可以通过轮询或者注册热插拔事件的方式,检测当前插入的usb设备,查询这个设备是否处在AOA模式,如果不是则开启AOA模式。也就是设备向Android设备写入相应的usb控制信息,写入成功后,我们的Android设备就开启了AOA模式,这时嵌入式端就拿到了Android设备usb的读写描述符,可以对其进行数据传输。
2.Android端接收到嵌入式端写入的信息后,就把自己设置为accessory模式,这时Android会发送一条系统广播,在广播接受者中查询当前是否有accessory连接。如果可以拿到这个accessory,那么就能获取到相应的读写流,也就可以通过这两个流对设备进行读写。
这里设备端底层使用了libusb库,下面是项目地址,可以直接在mac或者linux下编译。在开发Android端代码时网上的参考代码有不少bug,这里给出一份相对比较稳定的Android端例程。
libusb项目链接 https://github.com/libusb/libusb.git
设备aoa模式参考项目链接 https://github.com/timotto/AOA-Proxy.git
本例程设备端项目链接 https://git.oschina.net/vonchenchen/aoa_proxy.git
本例程Android端项目链接 https://git.oschina.net/vonchenchen/aoa_android.git
例程的编译与使用
在编译设备端项目之前需要先编译并安装libusb库。安装完成libusb库后,运行本例程设备端项目中的configure文件,参考日志信息安装其他依赖库,然后执行make,编译完成后将会生成aoaproxy文件,也就是我们能最后生成的可执行文件。
Android端例程可以直接在Android Studio打开运行,直接装入手机即可。
在设备端执行
sudo ./aoaproxy
如果看到如下日志,则说明程序已经正常启动
start_service
start connect device
prepare to connect device
这时,插入android设备,应用会自动启动,设备控制台开始每隔一秒打印如下信息
Start send
recived len 12
usb recive len 12
receive hello
这样就说明例子程序运行起来了。这里每隔1秒设备将向Android端发送一个Hello字符串,Android端收到数据后会原样返回这些数据,这时设备端收到数据后会将这些信息打印在控制台上。
代码分析
设备端代码主要分为一下几个文件,文件对应功能如下
aoaproxy.c —– 主程序
accessory.c —— AOA底层开启
a2spipe.c ——usb与本地server交互管道
tcp.c ——-tcp连接
local_service.c ——- 本地 server 用于数据接收和发送,可以在单独进程中开启
下面我们将分布对这几个文件进行介绍。
main函数
int main(int argc, char** argv)
int r;
int opt;
int xcount=0;
while ((opt = getopt(argc, argv, "dfh:p:x:")) != -1)
//参数
....
ctx = NULL;
connectedDevices = NULL;
//开启本地socketserver
create_start_service();
if (do_fork)
do_fork_foo();
//注册信号
initSigHandler();
// it is important to init usb after the fork!
if (0 > initUsb())
logError("Failed to initialize USB\\n");
return 1;
//定时任务轮询
if(autoscan)
struct itimerval timer;
timer.it_value.tv_sec = 1;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 1;
timer.it_interval.tv_usec = 0;
setitimer (ITIMER_REAL, &timer, NULL);
//usb设备列表
libusb_device **devs = NULL;
while(!do_exit)
if (doUpdateUsbInventory == 1)
doUpdateUsbInventory = 0;
//清空设备
cleanupDeadDevices();
//尝试链接设备
updateUsbInventory(devs);
//阻塞等待usb事件
r = libusb_handle_events(ctx);
if (r)
if (r == LIBUSB_ERROR_INTERRUPTED)
// ignore
else
if(!do_exit)
logDebug("libusb_handle_events_timeout: %d\\n", r);
break;
if (devs != NULL)
libusb_free_device_list(devs, 1);
if(autoscan)
struct itimerval timer;
memset (&timer, 0, sizeof(timer));
setitimer (ITIMER_REAL, &timer, NULL);
shutdownEverything();
return EXIT_SUCCESS;
这里比较重要的是updateUsbInventory函数。
updateUsbInventory
下面看一下updateUsbInventory方法做什么的
static int updateUsbInventory(libusb_device **devs)
static ssize_t cnt = 0;
static ssize_t lastCnt = 0;
// static libusb_device **devs;
static libusb_device **lastDevs = NULL;
//获取usb设备列表
cnt = libusb_get_device_list(ctx, &devs);
if(cnt < 0)
logError("Failed to list devices\\n");
return -1;
ssize_t i, j;
int foundBefore;
for(i = 0; i < cnt; i++)
foundBefore = 0;
if ( lastDevs != NULL)
for(j=0;j < lastCnt; j++)
if (devs[i] == lastDevs[j])
foundBefore = 1;
break;
if (!foundBefore)
logDebug("start connect device\\n");
//连接设备 连接本地服务端
if(connectDevice(devs[i]) >= 0)
libusb_ref_device(devs[i]);
if (lastDevs != NULL)
// if (cnt != lastCnt)
// fprintf(LOG_DEB, "number of USB devices changed from %d to %d\\n", lastCnt, cnt);
for (i=0;i<lastCnt;i++)
foundBefore = 0;
for(j=0;j<cnt;j++)
if (devs[j] == lastDevs[i])
foundBefore = 1;
break;
if(!foundBefore)
struct listentry *hit = connectedDevices;
while(hit != NULL)
if ( hit->usbDevice == lastDevs[i])
disconnectDevice(lastDevs[i]);
libusb_unref_device(lastDevs[i]);
break;
hit = hit->next;
libusb_free_device_list(lastDevs, 1);
lastDevs = devs;
lastCnt = cnt;
return 0;
connectDevice
这个函数比较关键,开启了android的accessory,同时也和本地服务器进行连接,这样就打通了usb和本地server的通道。
static int connectDevice(libusb_device *device)
logDebug("prepare to connect device \\n");
struct libusb_device_descriptor desc;
//获取usb设备描述信息
int r = libusb_get_device_descriptor(device, &desc);
if (r < 0)
logError("failed to get device descriptor: %d", r);
return -1;
switch(desc.bDeviceClass)
case 0x09:
logDebug("device 0x%04X:%04X has wrong deviceClass: 0x%02x",
desc.idVendor, desc.idProduct,
desc.bDeviceClass);
return -1;
struct t_excludeList *e = exclude;
while(e != NULL)
logDebug("comparing device [%04x:%04x] and [%04x:%04x]",
desc.idVendor, desc.idProduct, e->vid, e->pid);
if(e->vid == desc.idVendor && e->pid == desc.idProduct)
logDebug("device is on exclude list", desc.idVendor, desc.idProduct);
return -1;
e = e->next;
//检查当前设备是否处于accessory模式
if(!isDroidInAcc(device))
logDebug("attempting AOA on device 0x%04X:%04X\\n",
desc.idVendor, desc.idProduct);
//写入要启动的应用的信息 开启android的accessory模式
switchDroidToAcc(device, 1, haveAudio);
return -1;
//entry管理socket与usb
struct listentry *entry = malloc(sizeof(struct listentry));
if (entry == NULL)
logError("Not enough RAM");
return -2;
bzero(entry, sizeof(struct listentry));
//entry拿到usb句柄device
entry->usbDevice = device;
//entry拿到socket句柄
#ifdef SOCKET_RETRY
//连接本地socketserver, 返回socket客户端的描述符
while((r = connectTcpSocket(hostname, portno)) <= 0)
logError("failed to setup socket: %d, retrying\\n", r);
sleep(1);
//记录本地soket链接的描述符
entry->sockfd = r;
entry->socketDead = 0;
#else
r = connectTcpSocket(hostname, portno);
if (r < 0)
fprintf(LOG_ERR, "failed to setup socket: %d\\n", r);
free(entry);
return -4;
entry->sockfd = r;
entry->socketDead = 0;
#endif
//如果android设备已经是aoa模式,打开usb
logDebug("start setup droid \\n");
//找到accessory接口并用接口信息初始化entry->droid
r = setupDroid(device, &entry->droid);
if (r < 0)
logError("failed to setup droid: %d\\n", r);
free(entry);
return -3;
//将entry加入链表
entry->next = NULL;
if (connectedDevices == NULL)
entry->prev = NULL;
connectedDevices = entry;
else
struct listentry *last = connectedDevices;
while(last->next != NULL)
last = last->next;
entry->prev = last;
last->next = entry;
//建立usb与socket互相通信的任务
r = startUSBPipe(entry);
if (r < 0)
logError("failed to start pipe: %d", r);
disconnectDevice(device);
return -5;
if (haveAudio && entry->droid.audioendp)
startAudio(entry);
logDebug("new Android connected");
return 0;
上述代码中首先检测当前接口是否为accessory模式,如果不是则将其设置为accessory模式,但是此处将所有设备都设置为accessory,可能有些设备并非android设备。同时connectTcpSocket方法开启了tcp连接。这里用entry记录socket和usb信息,并将其放入全局链表connectedDevices维护。
这里不经会让我们产生疑问,到底usb收发数据是在哪里,又是在什么地方与tcp server进行交互,我们继续往下看。
entry中维护了usb状态,同时也有socket,entry被放入startUSBPipe函数,下面着重看一下startUSBPipe的实现。
startUSBPipe
static int startUSBPipe(struct listentry *device)
int r;
if(initUsbXferThread(&device->usbRxThread) < 0)
logError("failed to allocate usb rx transfer\\n");
return -1;
if(initUsbXferThread(&device->socketRxThread) < 0)
logError("failed to allocate usb tx transfer\\n");
destroyUsbXferThread(&device->usbRxThread);
return -1;
//写入到usb任务
r = pthread_create(&device->usbRxThread.thread, NULL, (void*)&a2s_usbRxThread, (void*)device);
if (r < 0)
logError("failed to start usb rx thread\\n");
return -1;
//读出到socket任务
r = pthread_create(&device->socketRxThread.thread, NULL, (void*)&a2s_socketRxThread, (void*)device);
if (r < 0)
// other thread is stopped in disconnectDevice method
logError("failed to start socket rx thread\\n");
return -1;
return 0;
这里开启了两个线程,分别是usb数据写入server任务和server写入usb任务。下面分别看一下这两个任务。
a2s_usbRxThread
//usb写入socket任务
void *a2s_usbRxThread( void *d )
logDebug("a2s_usbRxThread started\\n");
struct listentry *device = (struct listentry*)d;
unsigned char buffer[device->droid.inpacketsize];
int rxBytes = 0;
int txBytes;
int sent;
int r;
//初始化usbRxThread.xfr ,关联数据buffer 传输完毕后回调a2s_usbrx_cb 解锁device->usbRxThread.condition
libusb_fill_bulk_transfer(device->usbRxThread.xfr, device->droid.usbHandle, device->droid.inendp,
buffer, sizeof(buffer),
(libusb_transfer_cb_fn)&a2s_usbrx_cb, (void*)&device->usbRxThread, 0);
while(!device->usbRxThread.stop && !device->usbDead && !device->socketDead)
pthread_mutex_lock( &device->usbRxThread.mutex );
device->usbRxThread.usbActive = 1;
// logDebug("a2s_usbRxThread reading...\\n");
//请求数据
r = libusb_submit_transfer(device->usbRxThread.xfr);
if (r < 0)
logError("a2s usbrx submit transfer failed\\n");
device->usbDead = 1;
device->usbRxThread.usbActive = 0;
pthread_mutex_unlock( &device->usbRxThread.mutex );
break;
// waitUsbXferThread(&device->usbRxThread);
// logDebug("a2s_usbRxThread waiting...\\n");
//等待接收数据
pthread_cond_wait( &device->usbRxThread.condition, &device->usbRxThread.mutex);
// logDebug("a2s_usbRxThread wait over\\n");
if (device->usbRxThread.usbActive)
logError("wait, unlock but usbActive!\\n");
pthread_mutex_unlock( &device->usbRxThread.mutex );
if (device->usbRxThread.stop || device->usbDead || device->socketDead)
break;
//查看usb接收数据的状态
switch(device->usbRxThread.xfr->status)
case LIBUSB_TRANSFER_COMPLETED:
// logDebug("a2s_usbRxThread writing...\\n");
rxBytes = device->usbRxThread.xfr->actual_length;
logDebug("usb recive len %d \\n", rxBytes);
sent = 0;
txBytes = 0;
while(sent < rxBytes && !device->usbRxThread.stop)
//将usb接收到的数据全部写入到socket
txBytes = write(device->sockfd, buffer + sent, rxBytes - sent);
if (txBytes <= 0)
logError("a2s usbrx socket tx failed\\n");
device->socketDead = 1;
device->usbRxThread.stop = 1;
else
sent += txBytes;
break;
case LIBUSB_TRANSFER_NO_DEVICE:
device->usbDead = 1;
device->usbRxThread.stop = 1;
break;
default:
// logDebug("a2s_usbRxThread usb error %d, ignoring\\n", device->usbRxThread.xfr->status);
break;
device->usbRxThread.stopped = 1;
logDebug("a2s_usbRxThread finished\\n");
pthread_exit(0);
return NULL;
libusb_fill_bulk_transfer
这个方法使用entry中的数据,开启usb通路,如果接收到了usb数据,就会回调a2s_usbrx_cb。
之后调用
libusb_submit_transfer
请求接收数据。请求完数据线程被锁,如果a2s_usbrx_cb被回调,则会发送一个信号量,线程锁打开。此时usb数据已经传入到了缓冲区,entry中存储的socket的描述符,直接将buffer写入这个描述符,server就会收到usb信息。数据发送任务也是同理。到此,usb和server通道就已经打通。
另外,AOA模式可以直接打开应用,下面看一下AOA模式是如何打开的。
isDroidInAcc
这个函数用于设备检测是否处于AOA模式
int isDroidInAcc(libusb_device *dev)
struct libusb_device_descriptor desc;
int r = libusb_get_device_descriptor(dev, &desc);
if (r < 0)
logError("failed to get device descriptor\\n");
// fprintf(LOG_ERR, ERR_USB_DEVDESC);
return 0;
if (desc.idVendor == VID_GOOGLE)
switch(desc.idProduct)
case PID_AOA_ACC:
case PID_AOA_ACC_ADB:
case PID_AOA_ACC_AU:
case PID_AOA_ACC_AU_ADB:
return 1;
case PID_AOA_AU:
case PID_AOA_AU_ADB:
logDebug("device is audio-only\\n");
// logDebug( "device is audio-only\\n");
break;
default:
break;
return 0;
通过libusb_device获取当前usb设备的状态信息,用来判断usb的厂商和模式等信息。
switchDroidToAcc
这个函数是将设备设置为Accessory模式,这里可以配置我们的设备连接手机后启动哪个android设备。
void switchDroidToAcc(libusb_device *dev, int force, int audio)
struct libusb_device_handle* handle;
unsigned char ioBuffer[2];
int r;
int deviceProtocol;
//打开设备
if(0 > libusb_open(dev, &handle))
logError("Failed to connect to device\\n");
return;
//写入控制信息
if(libusb_kernel_driver_active(handle, 0) > 0)
if(!force)
logError("kernel driver active, ignoring device");
libusb_close(handle);
return;
if(libusb_detach_kernel_driver(handle, 0)!=0)
logError("failed to detach kernel driver, ignoring device");
libusb_close(handle);
return;
if(0> (r = libusb_control_transfer(handle,
0xC0, //bmRequestType
51, //Get Protocol
0,
0,
ioBuffer,
2,
2000)))
logError("get protocol call failed %d \\n", r);
libusb_close(handle);
return;
deviceProtocol = ioBuffer[1] << 8 | ioBuffer[0];
if (deviceProtocol < AOA_PROTOCOL_MIN || deviceProtocol > AOA_PROTOCOL_MAX)
// logDebug("Unsupported AOA protocol %d\\n", deviceProtocol);
logDebug( "Unsupported AOA protocol %d\\n", deviceProtocol);
libusb_close(handle);
return;
//这些量用于指定启动app,我们在app中也会写入同样的信息
const char *setupStrings[6];
setupStrings[0] = vendor;
setupStrings[1] = model;
setupStrings[2] = description;
setupStrings[3] = version;
setupStrings[4] = uri;
setupStrings[5] = serial;
int i;
for(i=0;i<6;i++)
if(0 > (r = libusb_control_transfer(handle,
0x40,
52,
0,
(uint16_t)i,
(unsigned char*)setupStrings[i],
strlen(setupStrings[i]),2000)))
logDebug( "send string %d call failed\\n", i);
libusb_close(handle);
return;
if (deviceProtocol >= 2)
if(0 > (r = libusb_control_transfer(handle,
0x40, //厂商的请求
58,
#ifdef USE_AUDIO
audio, // 0=no audio, 1=2ch,16bit PCM, 44.1kHz
#else
0,
#endif
0,
NULL,
0,
2000)))
logDebug( "set audio call failed\\n");
libusb_close(handle);
return;
if(0 > (r = libusb_control_transfer(handle,0x40,53,0,0,NULL,0,2000)))
logDebug( "start accessory call failed\\n");
libusb_close(handle);
return;
libusb_close(handle);
server例程
下面是我们自己实现的socket server,用于接收和发送数据,这里我们会向usb发送“hello“字符串,然后打印收到的数据。当然,在使用时可以把上面的例子作为单独进程开启,作为单独模块,而数据发送和接收在我们自己的应用的进程中。
//数据接收线程
void *recvThread(void *arg)
int length;
struct thread_param *param = (struct thread_param *)arg;
int socket = param->socket_id;
printf("recvThread %d\\n", socket);
char buffer[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);
while(loop_flag)
length = recv(socket,buffer,BUFFER_SIZE,0 );
if (length < 0)
printf("Server Recieve Data Failed!\\n");
loop_flag = 0;
break;
//打印接收到的数据内容
printf("receive %s\\n", buffer);
//数据发送线程 每隔一秒发送
void *sendThread(void *arg)
struct thread_param *param = (struct thread_param *)arg;
int socket = param->socket_id;
printf("sendThread %d\\n", socket);
char buffer[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);
while(loop_flag)
printf("\\nStart send\\n");
int length = 12;
buffer[0] = 'h';
buffer[1] = 'e';
buffer[2] = 'l';
buffer[3] = 'l';
buffer[4] = 'o';
buffer[5] = 0;
//发送buffer中的字符串到new_server_socket,实际是给客户端
if(send(socket,buffer,length,0)<0)
printf("Send faield\\n");
loop_flag = 0;
break;
sleep(1);
//开启自定义服务
int start_service(int argc, char **argv)
printf("start_service\\n");
//设置一个socket地址结构server_addr,代表服务器internet地址, 端口
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr)); //把一段内存区的内容全部设置为0
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htons(INADDR_ANY);
server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
//创建用于internet的流协议(TCP)socket,用server_socket代表服务器socket
int server_socket = socket(PF_INET,SOCK_STREAM,0);
if( server_socket < 0)
printf("Create Socket Failed!");
exit(1);
int opt =1;
setsockopt(server_socket,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
//把socket和socket地址结构联系起来
if( bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr)))
printf("Server Bind Port : %d Failed!", HELLO_WORLD_SERVER_PORT);
exit(1);
//server_socket用于监听
if ( listen(server_socket, LENGTH_OF_LISTEN_QUEUE) )
printf("Server Listen Failed!");
exit(1);
while (1)
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
int new_server_socket = accept(server_socket,(struct sockaddr*)&client_addr,&length);
if ( new_server_socket < 0)
printf("Server Accept Failed!\\n");
break;
loop_flag = 1;
threadParam.socket_id = new_server_socket;
pthread_create(&recvThreadId, NULL, recvThread, &threadParam);
pthread_create(&sendThreadId, NULL, sendThread, &threadParam);
char buffer[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);
while(1)
sleep(1);
if(loop_flag == 0)
break;
printf("close server socket \\n");
//关闭与客户端的连接
close(new_server_socket);
//关闭监听用的socket
close(server_socket);
return 0;
void create_start_service()
pthread_create(&mainThreadId, NULL, start_service, &mainThreadParam);
总结
上文介绍了Android AOA协议设备端实现的基本流程,首先介绍了项目的使用方法,之后梳理了本地server和usb通信的流程,最后介绍了设备端开启app的方法,下一篇文章我们将分析android端代码如何对接设备端。
以上是关于Android usb学习笔记:Android AOA协议设备端 流程总结的主要内容,如果未能解决你的问题,请参考以下文章