最近的項(xiàng)目里面需要對(duì)UVC攝像頭進(jìn)行操控,簡(jiǎn)單的了解了下相關(guān)的知識(shí)。
首先UVC全稱為USB video(device) class,是微軟與另外幾家設(shè)備廠商聯(lián)合推出的為USB視頻捕獲設(shè)備定義的協(xié)議標(biāo)準(zhǔn),目前已成為USB org標(biāo)準(zhǔn)之一。在USB中文網(wǎng)上其實(shí)有比較詳細(xì)的描述,但是新手直接上來(lái)就看這個(gè)協(xié)議其實(shí)是比較懵逼的。所以可以參考UVCCamera這個(gè)安卓項(xiàng)目的源碼去輔助理解。
USB描述符
當(dāng)我們連接到一個(gè)UVC設(shè)備之后其實(shí)第一步應(yīng)該先獲取它的描述符來(lái)看看它具體支持哪些操作。UVCCamera里是使用libusb獲取到USB的設(shè)備描述符之后在uvc_scan_control里面解析的。實(shí)際上我們也可以利用安卓應(yīng)用層的UsbDeviceConnection.getRawDescriptors接口獲取到USB的描述符然后再java層解析:
val manager = context.getSystemService(AppCompatActivity.USB_SERVICE) as UsbManager
val deviceIterator: Iterator<UsbDevice> = manager.deviceList.values.iterator()
while (deviceIterator.hasNext()) {
val device = deviceIterator.next()
if (device.vendorId == targetVid && device.productId == targetPid) {
val connect = manager.openDevice(device)
val desc = connect.rawDescriptors
// 解析usb描述符
connect.close()
return
}
}
這里拿到的是一個(gè)byte數(shù)組,我們先要理解什么是usb描述符才能去解析它。usb描述符其實(shí)就是描述usb的屬性和用途的,四種主要描述符的邏輯結(jié)構(gòu)大概如下:
- 設(shè)備描述符: 每一個(gè)USB設(shè)備只有一個(gè)設(shè)備描述符,主要向主機(jī)說(shuō)明設(shè)備類型、端點(diǎn)0最大包長(zhǎng)、設(shè)備版本、配置數(shù)量等等
- 配置描述符: 每一個(gè)USB設(shè)備至少有一個(gè)或者多個(gè)配置描述符,但是主機(jī)同一時(shí)間只能選擇某一種配置,標(biāo)準(zhǔn)配置描述符主要向主機(jī)描述當(dāng)前配置下的設(shè)備屬性、所需電流、支持的接口數(shù)、配置描述符集合長(zhǎng)度等等。
- 接口描述符 : 每一個(gè)USB配置下至少有一個(gè)或者多個(gè)接口描述符,接口描述符主要說(shuō)明設(shè)備類型、此接口下使用的端點(diǎn)數(shù)(不包括0號(hào)端點(diǎn)),一個(gè)接口就是實(shí)現(xiàn)一種功能,實(shí)現(xiàn)這種功能可能需要端點(diǎn)0就夠了,可能還需要其它的端點(diǎn)配合。
- 端點(diǎn)描述符: 每一個(gè)USB接口下可以有多個(gè)端點(diǎn)描述符,端點(diǎn)描述符用來(lái)描述符端點(diǎn)的各種屬性。
端點(diǎn)是實(shí)現(xiàn)USB設(shè)備功能的物理緩沖區(qū)實(shí)體,USB主機(jī)和設(shè)備是通過(guò)端點(diǎn)進(jìn)行數(shù)據(jù)交互的
描述符解析
所有類型的描述符的前兩個(gè)字節(jié)定義都是一樣的,第一個(gè)字節(jié)指定描述符的長(zhǎng)度,而第二個(gè)字節(jié)表示描述符類型。所以我們拿到rawDescriptors之后可以用下面的代碼去遍歷解析:
var index = 0
val descriptorTypes = mapOf(
0x01.toByte() to "DEVICE",
0x02.toByte() to "CONFIG",
0x04.toByte() to "INTERFACE",
0x05.toByte() to "ENDPOINT",
)
while (index < desc.size) {
descriptorTypes[desc[index + 1]]?.let {
val indent = " ".repeat(desc[index + 1].toInt())
Log.d(TAG, "${indent}$it")
}
index += desc[index]
}
我這個(gè)調(diào)試設(shè)備的描述符解析如下:
DEVICE
CONFIG
INTERFACE
ENDPOINT
INTERFACE
ENDPOINT
INTERFACE
ENDPOINT
ENDPOINT
可以看到它有一個(gè)設(shè)備描述符,這個(gè)設(shè)備描述符下有個(gè)一個(gè)配置描述符,這個(gè)配置描述符下有三個(gè)接口描述符,每個(gè)接口描述符下又有一到兩個(gè)端點(diǎn)描述符。
知道了描述符的類型就能在USB標(biāo)準(zhǔn)里找到它具體的數(shù)據(jù)結(jié)構(gòu)去解析,例如設(shè)備描述符的定義如下:
struct libusb_device_descriptor {
/** Size of this descriptor (in bytes) */
uint8_t bLength;
/** Descriptor type. Will have value
* \ref libusb_descriptor_type::LIBUSB_DT_DEVICE LIBUSB_DT_DEVICE in this
* context. */
uint8_t bDescriptorType;
/** USB specification release number in binary-coded decimal. A value of
* 0x0200 indicates USB 2.0, 0x0110 indicates USB 1.1, etc. */
uint16_t bcdUSB;
/** USB-IF class code for the device. See \ref libusb_class_code. */
uint8_t bDeviceClass;
/** USB-IF subclass code for the device, qualified by the bDeviceClass
* value */
uint8_t bDeviceSubClass;
/** USB-IF protocol code for the device, qualified by the bDeviceClass and
* bDeviceSubClass values */
uint8_t bDeviceProtocol;
/** Maximum packet size for endpoint 0 */
uint8_t bMaxPacketSize0;
/** USB-IF vendor ID */
uint16_t idVendor;
/** USB-IF product ID */
uint16_t idProduct;
/** Device release number in binary-coded decimal */
uint16_t bcdDevice;
/** Index of string descriptor describing manufacturer */
uint8_t iManufacturer;
/** Index of string descriptor describing product */
uint8_t iProduct;
/** Index of string descriptor containing device serial number */
uint8_t iSerialNumber;
/** Number of possible configurations */
uint8_t bNumConfigurations;
};
描述符類型定義的id可以參考lsusb的libusb_descriptor_type枚舉:
enum libusb_descriptor_type {
/** Device descriptor. See libusb_device_descriptor. */
LIBUSB_DT_DEVICE = 0x01,
/** Configuration descriptor. See libusb_config_descriptor. */
LIBUSB_DT_CONFIG = 0x02,
/** String descriptor */
LIBUSB_DT_STRING = 0x03,
/** Interface descriptor. See libusb_interface_descriptor. */
LIBUSB_DT_INTERFACE = 0x04,
/** Endpoint descriptor. See libusb_endpoint_descriptor. */
LIBUSB_DT_ENDPOINT = 0x05,
/** Interface Association Descriptor.
* See libusb_interface_association_descriptor */
LIBUSB_DT_INTERFACE_ASSOCIATION = 0x0b,
/** BOS descriptor */
LIBUSB_DT_BOS = 0x0f,
/** Device Capability descriptor */
LIBUSB_DT_DEVICE_CAPABILITY = 0x10,
/** HID descriptor */
LIBUSB_DT_HID = 0x21,
/** HID report descriptor */
LIBUSB_DT_REPORT = 0x22,
/** Physical descriptor */
LIBUSB_DT_PHYSICAL = 0x23,
/** Hub descriptor */
LIBUSB_DT_HUB = 0x29,
/** SuperSpeed Hub descriptor */
LIBUSB_DT_SUPERSPEED_HUB = 0x2a,
/** SuperSpeed Endpoint Companion descriptor */
LIBUSB_DT_SS_ENDPOINT_COMPANION = 0x30
};
可以看到描述符的類型其實(shí)不止上面四種,還有很多其他的類型。例如我就能從UVC 相機(jī)終端描述符里面的bmControls字段解析出相機(jī)具體支持的操作:
mControls:使用位圖來(lái)表示支持的視頻流。
- D0:掃描模式 //掃描模式(逐行掃描或隔行掃描)
- D1:自動(dòng)曝光模式
- D2:自動(dòng)曝光優(yōu)先級(jí)
- D3:曝光時(shí)間(絕對(duì)值)
- D4:曝光時(shí)間(相對(duì))
- D5:焦點(diǎn)(絕對(duì))
- D6:焦點(diǎn)(相對(duì))
- ...
在libusb里面這個(gè)UVC相機(jī)終端描述符會(huì)作為接口描述符的拓展信息保存:
static int parse_interface(libusb_context *ctx,
struct libusb_interface *usb_interface, const uint8_t *buffer, int size)
{
...
begin = buffer;
/* Skip over any interface, class or vendor descriptors */
while (size >= DESC_HEADER_LENGTH) {
...
/* If we find another "proper" descriptor then we're done */
if (header->bDescriptorType == LIBUSB_DT_INTERFACE ||
header->bDescriptorType == LIBUSB_DT_ENDPOINT ||
header->bDescriptorType == LIBUSB_DT_CONFIG ||
header->bDescriptorType == LIBUSB_DT_DEVICE)
break;
buffer += header->bLength;
parsed += header->bLength;
size -= header->bLength;
}
/* Copy any unknown descriptors into a storage area for */
/* drivers to later parse */
ptrdiff_t len = buffer - begin;
if (len > 0) {
void *extra = malloc((size_t)len);
...
memcpy(extra, begin, (size_t)len);
ifp->extra = extra;
ifp->extra_length = (int)len;
}
...
}
所以在uvc_scan_control里面就從接口描述符的extra信息里面去解析UVC的相關(guān)描述符:
uvc_error_t uvc_scan_control(uvc_device_t *dev, uvc_device_info_t *info) {
...
for (interface_idx = 0; interface_idx < info->config->bNumInterfaces; ++interface_idx) {
if_desc = &info->config->interface[interface_idx].altsetting[0];
MARK("interface_idx=%d:bInterfaceClass=%02x,bInterfaceSubClass=%02x", interface_idx, if_desc->bInterfaceClass, if_desc->bInterfaceSubClass);
// select first found Video control
if (if_desc->bInterfaceClass == LIBUSB_CLASS_VIDEO/*14*/ && if_desc->bInterfaceSubClass == 1) // Video, Control
break;
...
}
...
buffer = if_desc->extra;
buffer_left = if_desc->extra_length;
while (buffer_left >= 3) { // parseX needs to see buf[0,2] = length,type
block_size = buffer[0];
parse_ret = uvc_parse_vc(dev, info, buffer, block_size);
if (parse_ret != UVC_SUCCESS) {
ret = parse_ret;
break;
}
buffer_left -= block_size;
buffer += block_size;
}
...
}
uvc_error_t uvc_parse_vc(uvc_device_t *dev, uvc_device_info_t *info,
const unsigned char *block, size_t block_size) {
int descriptor_subtype;
uvc_error_t ret = UVC_SUCCESS;
UVC_ENTER();
if (block[1] != LIBUSB_DT_CS_INTERFACE/*36*/) { // not a CS_INTERFACE descriptor??
UVC_EXIT(UVC_SUCCESS);
return UVC_SUCCESS; // UVC_ERROR_INVALID_DEVICE;
}
descriptor_subtype = block[2];
switch (descriptor_subtype) {
case UVC_VC_HEADER:
ret = uvc_parse_vc_header(dev, info, block, block_size);
break;
case UVC_VC_INPUT_TERMINAL:
ret = uvc_parse_vc_input_terminal(dev, info, block, block_size);
break;
case UVC_VC_OUTPUT_TERMINAL:
break;
case UVC_VC_SELECTOR_UNIT:
break;
case UVC_VC_PROCESSING_UNIT:
ret = uvc_parse_vc_processing_unit(dev, info, block, block_size);
break;
case UVC_VC_EXTENSION_UNIT:
ret = uvc_parse_vc_extension_unit(dev, info, block, block_size);
break;
default:
LOGW("UVC_ERROR_INVALID_DEVICE:descriptor_subtype=0x%02x", descriptor_subtype);
ret = UVC_ERROR_INVALID_DEVICE;
}
UVC_EXIT(ret);
return ret;
}
只要找到bInterfaceClass等于14,bInterfaceSubClass等于1的視頻控制接口,然后在它的拓展信息里面找到UVC_VC_INPUT_TERMINAL(0x02)
類型的描述符就是我們需要的UVC 相機(jī)終端描述符
USB通訊
前面有說(shuō)到端點(diǎn)是實(shí)現(xiàn)USB設(shè)備功能的物理緩沖區(qū)實(shí)體,USB主機(jī)和設(shè)備是通過(guò)端點(diǎn)進(jìn)行數(shù)據(jù)交互的
,之前做HID設(shè)備通訊的時(shí)候流程是找到bInterfaceClass
為UsbConstants.USB_CLASS_HID(0x03)
類型的接口,在它下面找到輸入端點(diǎn)去寫入請(qǐng)求,然后找到輸出端點(diǎn)去讀取設(shè)備響應(yīng)。
但是UVC的攝像頭控制并不是用視頻控制接口去讀寫,而是直接使用USB設(shè)備不屬于任何接口的0號(hào)端口去進(jìn)行通訊。
例如uvc_get_pantilt_abs里面在傳輸數(shù)據(jù)的時(shí)候就沒(méi)有指定端口號(hào):
uvc_error_t uvc_get_pantilt_abs(uvc_device_handle_t *devh, int32_t *pan, int32_t *tilt,
enum uvc_req_code req_code) {
uint8_t data[8];
uvc_error_t ret;
ret = libusb_control_transfer(devh->usb_devh, REQ_TYPE_GET, req_code,
UVC_CT_PANTILT_ABSOLUTE_CONTROL << 8,
devh->info->ctrl_if.input_term_descs->request,
data, sizeof(data), CTRL_TIMEOUT_MILLIS);
if (LIKELY(ret == sizeof(data))) {
*pan = DW_TO_INT(data);
*tilt = DW_TO_INT(data + 4);
return UVC_SUCCESS;
} else {
return ret;
}
}
因?yàn)樵?a target="_blank">libusb_control_transfer里面調(diào)用libusb_fill_control_transfer去填充信息的時(shí)候就會(huì)把端口指定為0號(hào)端口
int API_EXPORTED libusb_control_transfer(libusb_device_handle *dev_handle,
uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex,
unsigned char *data, uint16_t wLength, unsigned int timeout)
{
...
libusb_fill_control_transfer(transfer, dev_handle, buffer,
sync_transfer_cb, &completed, timeout); // 填充transfer信息
transfer->flags = LIBUSB_TRANSFER_FREE_BUFFER;
r = libusb_submit_transfer(transfer); // 發(fā)送請(qǐng)求
if (UNLIKELY(r < 0)) {
libusb_free_transfer(transfer);
return r;
}
sync_transfer_wait_for_completion(transfer); // 等待回復(fù)
...
}
static inline void libusb_fill_control_transfer(
struct libusb_transfer *transfer, libusb_device_handle *dev_handle,
unsigned char *buffer, libusb_transfer_cb_fn callback, void *user_data,
unsigned int timeout)
{
struct libusb_control_setup *setup = (struct libusb_control_setup *)(void *) buffer;
transfer->dev_handle = dev_handle;
transfer->endpoint = 0; // 指定0號(hào)端口
...
}
涉及到使用USB進(jìn)行通訊的4種方式:
- 控制傳輸 - 設(shè)備接入主機(jī)時(shí),需要通過(guò)控制傳輸去獲取USB設(shè)備的描述符以及對(duì)設(shè)備進(jìn)行識(shí)別,在設(shè)備的枚舉過(guò)程中都是使用控制傳輸進(jìn)行數(shù)據(jù)交換。
- 同步傳輸 - 也叫等時(shí)傳輸,用于要求數(shù)據(jù)連續(xù)、實(shí)時(shí)且數(shù)據(jù)量大的場(chǎng)合,其對(duì)傳輸延時(shí)十分敏感,類似用于USB攝像設(shè)備,USB語(yǔ)音設(shè)備等等。
- 中斷傳輸 - 用于數(shù)據(jù)量小的數(shù)據(jù)不連續(xù)的但實(shí)時(shí)性高的場(chǎng)合的一種傳輸方式,主要應(yīng)用于人機(jī)交互設(shè)備(HID)中的USB鼠標(biāo)和USB鍵盤等。
- 批量傳輸 - 用于數(shù)據(jù)量大但對(duì)時(shí)間要求又不高的場(chǎng)合的一種傳輸方式,類似用于USB打印機(jī)和USB掃描儀等等。
控制傳輸
控制傳輸是usb設(shè)備一定會(huì)支持的傳輸方式,因?yàn)槊枋龇褪峭ㄟ^(guò)這種方式獲取的.
在安卓應(yīng)用層我們可以通過(guò)調(diào)用UsbDeviceConnection.controlTransfer來(lái)實(shí)現(xiàn),參考UVCCamera里面uvc_get_pantilt_abs里面獲取PanTilt值的c代碼,在java層可以用下面代碼獲取
private const val CONTROL_REQ_TYPE_GET = 0xa1
private const val UVC_GET_CUR = 0x81
val connection = usbManager.openDevice(device)
// 先claim bInterfaceClass為CC_VIDEO(0x0E) bInterfaceSubClass為SC_VIDEOCONTROL(0x01)的攝像頭控制接口
val vcInterface = UsbUtils.getInterface(device, UsbConstants.USB_CLASS_VIDEO, USB_SUBCLASS_VIDEO_CONTROL)
connection.claimInterface(vcInterface, true)
// 然后發(fā)送控制指令獲取PanTilt絕對(duì)值
val buff = ByteArray(8)
val index = getPanTiltControlIndex(connection)
val value = CT_PANTILT_ABSOLUTE_CONTROL.shl(8)
connection.controlTransfer(CONTROL_REQ_TYPE_GET, UVC_GET_CUR, value, index, buff, buff.size, 100)
// buff前四個(gè)byte組合起來(lái)是pan值
// buff后四個(gè)byte組合起來(lái)是tilt值
val pan = bytes[0].toUByte().toInt().shl(0) or
bytes[1].toUByte().toInt().shl(8) or
bytes[2].toUByte().toInt().shl(16) or
bytes[3].toUByte().toInt().shl(24)
val tilt = bytes[4].toUByte().toInt().shl(0) or
bytes[5].toUByte().toInt().shl(8) or
bytes[6].toUByte().toInt().shl(16) or
bytes[7].toUByte().toInt().shl(24)
connection.releaseInterface(usbInterface)
connection.close()
這里解釋下上面的值如何來(lái)的,首先看GET_CUR的文檔:
requestType | request | value | index | buffer | length |
---|---|---|---|---|---|
10100001(接口或?qū)嶓w) — — — — — 10100010(端點(diǎn)) |
GET_CUR GET_MIN GET_MAX GET_RES GET_LEN GET_INFO GET_DEF |
UVC中大多數(shù)情況下取值都為控制選擇器CS(高字節(jié)),低字節(jié)為零。當(dāng)實(shí)體ID取不同值時(shí)則該字段取值也會(huì)有所不同 | 實(shí)體ID(高字節(jié))、接口(低字節(jié)) — — — — — 端點(diǎn)(低字節(jié)) |
用來(lái)接收數(shù)據(jù)或者發(fā)送數(shù)據(jù)的buffer | buffer的大小 |
value
例如我們現(xiàn)在要獲取PanTilt的絕對(duì)值,那么在value字段部分文檔里面可以看到當(dāng)Entity ID值為Camera Terminal時(shí):
ControlSelector | Value |
---|---|
... | ... |
CT_PANTILT_ABSOLUTE_CONTROL | 0x0D |
... | ... |
又因?yàn)?code>value的值為控制選擇器CS(高字節(jié)),低字節(jié)為零。所以value的值應(yīng)該是0x0D << 8
index
而Entity ID值為Camera Terminal
指的是終端描述符的bTerminalID, 由于它屬于控制接口描述符的extra信息,所以還需要指的該接口的bInterfaceNunber:
val USB_DESC_TYPE_INTERFACE_LEN = 9.toByte()
val USB_DESC_TYPE_INTERFACE = 0x04.toByte()
val USB_DESC_TYPE_CS_INTERFACE = 0x24.toByte()
val USB_DESC_SUB_TYPE_VC_INPUT_TERMINAL = 0x02.toByte()
val USB_SUBCLASS_VIDEO_CONTROL = 0x01
private fun getPanTiltControlIndex(connection: UsbDeviceConnection): Int {
val desc = connection.rawDescriptors ?: return -1
var index = 0
var isInVideoControlInterface = false
var interfaceNumber = 0
while (index < desc.size) {
if (desc[index] == USB_DESC_TYPE_INTERFACE_LEN
&& desc[index + 1] == USB_DESC_TYPE_INTERFACE
&& desc[index + 5] == UsbConstants.USB_CLASS_VIDEO.toByte()
&& desc[index + 6] == USB_SUBCLASS_VIDEO_CONTROL.toByte()
) {
// 找到bInterfaceClass為CC_VIDEO(0x0E) bInterfaceSubClass為SC_VIDEOCONTROL(0x01)的攝像頭控制接口的interfaceNumber
isInVideoControlInterface = true
interfaceNumber = desc[index + 2].toInt()
} else if (isInVideoControlInterface) {
if (desc[index + 1] != USB_DESC_TYPE_CS_INTERFACE) {
return -1
}
if (desc[index + 2] == USB_DESC_SUB_TYPE_VC_INPUT_TERMINAL) {
// 在攝像頭控制接口下找到bDescriptorType為CS_INTERFACE(0x24) bDescriptorSubtype為VC_INPUT_TERMINAL(0x02)的攝像頭終端描述符
// 獲取它的bTerminalID用來(lái)和前面獲取到的攝像頭控制接口的interfaceNumber拼接成index
return desc[index + 3].toInt().shl(8).or(interfaceNumber)
}
}
index += desc[index]
}
return -1
}
request
我們要獲取的是當(dāng)前值所以request是GET_CUR(0x81),其他值的定如下:
名稱 | 值 | 說(shuō)明 |
---|---|---|
RC_UNDEFINED | 0x00 | 未定義 |
SET_CUR | 0x01 | 設(shè)置屬性 |
GET_CUR | 0x81 | 獲取當(dāng)前屬性 |
GET_MIN | 0x82 | 獲取最小設(shè)置屬性 |
GET_MAX | 0x83 | 獲取最大設(shè)置屬性 |
GET_RES | 0x84 | 獲取分辨率屬性 |
GET_LEN | 0x85 | 獲取數(shù)據(jù)長(zhǎng)度屬性 |
GET_INF | 0x86 | 獲取設(shè)備支持的特定類請(qǐng)求屬性 |
GET_DEF | 0x87 | 獲取默認(rèn)屬性 |
requestType
最后再來(lái)看requestType,由于index需要選擇的是實(shí)體ID(高字節(jié))、接口(低字節(jié))
所以requestType應(yīng)該是10100001(接口或?qū)嶓w)
。它的值這么奇怪是因?yàn)閞equestType的每個(gè)bit都是有意義的:
由于命令接受者為接口,所以我們?cè)诎l(fā)送控制指令前還是需要找到這個(gè)接口用claimInterface去鎖定它。
使用uvc控制指令的坑
似乎是因?yàn)槭褂冒沧康腃amera2等接口去讀取攝像頭畫面的時(shí)候會(huì)使用到這個(gè)控制接口,所以如果在預(yù)覽的時(shí)候去claimInterface鎖定它就會(huì)造成畫面卡死。
看起來(lái)似乎需要完全使用uvc自己從視頻流接口讀取畫面,而不能一半用uvc去控制攝像頭另一半用安卓原生api去獲取預(yù)覽畫面?;蛘哂萌∏傻姆椒ㄔ诎l(fā)送控制指令的時(shí)候先停止預(yù)覽,發(fā)送完再開(kāi)始。
其他三種傳輸
其他三種傳輸都是需要找到對(duì)應(yīng)的端點(diǎn)才能進(jìn)行通訊的,所以需要先獲取到端點(diǎn)信息.用UsbInterface.getEndpoint去遍歷接口下的端點(diǎn),然后判斷端點(diǎn)的類型和讀寫方向:
for (i in 0 until usbInterface.endpointCount) {
val usbEndpoint = usbInterface.getEndpoint(i)
when (usbEndpoint.type) {
UsbConstants.USB_ENDPOINT_XFER_BULK -> {
// 批量傳輸
if (usbEndpoint.direction == UsbConstants.USB_DIR_OUT) {
// 可寫入端點(diǎn)
} else if (usbEndpoint.direction == UsbConstants.USB_DIR_IN) {
// 可讀取端點(diǎn)
}
}
UsbConstants.USB_ENDPOINT_XFER_ISOC -> {
// 中斷傳輸
}
UsbConstants.USB_ENDPOINT_XFER_INT -> {
// 同步傳輸
}
}
}
他們最終都是通過(guò)UsbDeviceConnection.bulkTransfer去調(diào)用的,例如可以先寫入請(qǐng)求在讀取響應(yīng):
val requestBuffer = ByteArray(256)
// 將數(shù)據(jù)保存到requestBuffer
// 然后往寫入端點(diǎn)寫入請(qǐng)求數(shù)據(jù)
connection.bulkTransfer(outPoint, sendBuff, sendBuff.size, timeout)
// 從讀取端點(diǎn)讀取響應(yīng)
val responseBuffer = ByteArray(256)
connection.bulkTransfer(inPoint, responseBuffer, responseBuffer.size, timeout)