最近公司一个项目,主要是想实现高质量音频的录制。虽然说目前大部分手机的录音质量都是可以的,但是想要录制高质量,立体声的音频,一般的手机还是望成莫及的。为了实现192K的立体声录音,电子组的同事直接搞了一个USB声卡过来让我获取其中的音频数据。
心想USB声卡应该算是一个通用的器件,大部分系统都应该可以直接使用了吧。于是找了几款手机,通过OTG线连接测试一下,测试结果发现只有TypeC接口的手机可以直接使用,而MicroUSB接口的手机则无法使用它录音。没办法,只好自己通过USB读取声卡的音频数据。
USB协议是一项很复杂的协议,有兴趣的可以自行查找相关资料学习。这里,我简单提一下和我们USB声卡相关的几点:
- 每一个usb设备都有自己固定的VID和PID地址,根据这个地址可以寻找到指定的usb设备。这个VID和PID可以在枚举USB设备的时候获取到。可参考UsbDevice中的方法。
- USB设备有config,下面有多个interface,interface下面有多个endpoint。interface的Class值可以区分interfce类型,音频设备的interface Class值是1。每一个interface下面有多个endpoint,每一个endpoint有一个唯一的Address,这个就是数据通信的通道了。
- USB的传输类型有四种,分别是控制传输(Control Transfer)、中断传输(Interrupt Transfer)、批量传输(Bulk Transfer)、同步传输(Isochronous Transfer)。音频设备只用到控制传输和同步传输。
提到USB通信,我首先想到的是可以利用AndroidSDK提供的USB接口进行数据通信。使用SDK通过的API操作USB很简单,主要的类是UsbManager、UsbEndpoint、UsbInterface、UsbDeviceConnection、UsbDevice。请看我的例子代码:
public class MainActivity extends AppCompatActivity {
private UsbManager mUsbManager;
public static final String ACTION_DEVICE_PERMISSION = "com.linc.USB_PERMISSION";
private PendingIntent mPermissionIntent;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
// 用于USB授权的Intent
mPermissionIntent = PendingIntent.getBroadcast(this,0,new Intent(ACTION_DEVICE_PERMISSION),0);
// 枚举USB设备
HashMap<String,UsbDevice> deviceHashMap = mUsbManager.getDeviceList();
Iterator<UsbDevice> iterator = deviceHashMap.values().iterator();
while (iterator.hasNext()) {
UsbDevice device = iterator.next();
// 判断APP对该USB设备是否有权限,若没有权限则申请USB设备权限
if(mUsbManager.hasPermission(device)) {
//do your work
initCommunication(device);
} else {
mUsbManager.requestPermission(device,mPermissionIntent);
}
Log.e("TAG", "\ndevice name: "+device.getDeviceName()+"\ndevice product name:"
+device.getProductName()+"\nvendor id:"+device.getVendorId()+
"\ndevice serial: "+device.getSerialNumber()+
"\ndevice serial: "+device.getProductId());
}
}
@Override
protected void onResume() {
super.onResume();
// 注册USB设备插拔、授权等广播,以监听到之后处理相应的操作。
IntentFilter usbFilter = new IntentFilter();
usbFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
usbFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
usbFilter.addAction(ACTION_DEVICE_PERMISSION);
registerReceiver(mUsbReceiver, usbFilter);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mUsbReceiver);
}
/*==============以下获取特定的USB设备信息=============*/
private UsbEndpoint mUsbEndpointIn;
private UsbEndpoint mUsbEndpointOut;
private UsbInterface mUsbInterfaceInt;
private UsbInterface musbInterfaceOut;
UsbDeviceConnection mUsbDeviceConnection;
/**
*
* 遍历USB设备各个节点,获取节点信息,并且打开USB连接
*/
private void initCommunication(UsbDevice device) {
tvInfo.append("initCommunication in\n");
tvInfo.append("initCommunication in right device\n");
int interfaceCount = device.getInterfaceCount();
tvInfo.append("interfaceCount " + interfaceCount + "\n");
for (int interfaceIndex = 0; interfaceIndex < interfaceCount; interfaceIndex++) {
UsbInterface usbInterface = device.getInterface(interfaceIndex);
tvInfo.append("=========>>"+ usbInterface.getInterfaceClass() + usbInterface.getInterfaceSubclass() + "<<===================\n");
if ((UsbConstants.USB_CLASS_AUDIO != usbInterface.getInterfaceClass())
&& (UsbConstants.USB_CLASS_HID != usbInterface.getInterfaceClass())) {
tvInfo.append("usbInterface.getInterfaceClass() " + usbInterface.getInterfaceClass() + usbInterface.getInterfaceSubclass() + "\n");
continue;
}
tvInfo.append("========="+ usbInterface.getEndpointCount() + "===================\n");
for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
UsbEndpoint ep = usbInterface.getEndpoint(i);
tvInfo.append("usbInterface.getEndpoint(i) " + ep.getType() + "\n");
if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_ISOC) {
if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
mUsbEndpointIn = ep;
mUsbInterfaceInt = usbInterface;
tvInfo.append("mUsbEndpointIn \n");
} else {
mUsbEndpointOut = ep;
musbInterfaceOut = usbInterface;
tvInfo.append("mUsbEndpointOut \n");
}
}
}
}
if ((null == mUsbEndpointIn) || (null == mUsbEndpointOut)) {
tvInfo.append("endpoint is null\n");
mUsbEndpointIn = null;
mUsbEndpointOut = null;
// mUsbInterface = null;
} else {
tvInfo.append("\nendpoint out: " + mUsbEndpointOut + ",endpoint in: " +
mUsbEndpointIn.getAddress()+"\n");
// mUsbInterface = usbInterface;
mUsbDeviceConnection = mUsbManager.openDevice(device);
mUsbDeviceConnection.claimInterface(mUsbInterfaceInt, true);
mUsbDeviceConnection.claimInterface(musbInterfaceOut, true);
tvInfo.append("mUsbDeviceConnection: " + byteToString(mUsbDeviceConnection.getRawDescriptors()) + "\n");
}
}
/**将byte数组转换为可读性高的字符串*/
private String byteToString(byte[] b){
String result = "";
for (int i = 0; i < b.length; i++){
result = String.format("%s %02x", result, b[i]);
}
return result;
}
// USB设备插拔、授权的Receiver。
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
tvInfo.append("BroadcastReceiver in\n");
if(UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
tvInfo.append("ACTION_USB_DEVICE_ATTACHED\n");
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
initCommunication(device);
} else if(UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
tvInfo.append("ACTION_USB_DEVICE_DETACHED\n");
}else if (ACTION_DEVICE_PERMISSION.equals(action)){
synchronized (this) {
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if (device != null) {
tvInfo.append("usb EXTRA_PERMISSION_GRANTED");
}
} else {
tvInfo.append("usb EXTRA_PERMISSION_GRANTED null!!!");
}
}
}
}
};
}
例子实现的功能就是发现USB设备获取信息,并打开连接通道。通道打开成功之后,就可以调用USB传输方法传输数据了。SDK提供了上述四种传输类型中的两种:
/*===========控制传输=============*/
public int controlTransfer(int requestType, int request, int value, int index,
byte[] buffer, int offset, int length, int timeout)
/*=============批量传输=================*/
public int bulkTransfer(UsbEndpoint endpoint,
byte[] buffer, int length, int timeout)
传输方向是由endpoint决定的,一个endpoint只有一个传输方向。
Android官方文档直接指出不支持同步传输(Isochronous Transfer),而这正是我们USB声卡的传输方式,使用SDK的方式是行不通了,因此,我们需要使用JNI的方式获取音频数据。下一篇将直接介绍使用JNI的方法来连接USB。