I am struggling on how to capture the audio stream from connected USB microphone. I have tried to use the MediaCapture
with MediaRecorder.AudioSource.MIC
as source which worked but recording quality isn't quite usable for me and you can't be sure if the audio source is really USB or the built in device microphone. What I need is to use the USB audio feature but so far I am unable to make any progress.
To use third-party libraries is overkill for me since I need to only receive the stream of audio data from the microphone, the rest of the processing is already done and working, only the audio source is the issue.
What I need to do is:
- Check if there is USB microphone connected to the device.
- Find out what characteristics this device has (supported sampling rates, channels etc.)
- Record audio data
What I've done so far:
Generate a list of connected USB device which class is UsbConstants.USB_CLASS_AUDIO
private static final String ACTION_USB_PERMISSION = PACKAGE_NAME + ".USB_PERMISSION";
private UsbManager mUsbManAndroid;
private Map<String, UsbDevice> mAndroidDeviceMap;
private PendingIntent mPermissionIntent;
private ArrayList<UsbDeviceListItem> getUSBDevicesList() {
// Enumerate USB devices
mAndroidDeviceMap = mUsbManAndroid.getDeviceList();
ArrayList<UsbDeviceListItem> usbDevicesList = new ArrayList<>();
for (String key : mAndroidDeviceMap.keySet()) {
UsbDevice device = mAndroidDeviceMap.get(key);
// Check the device class
if (device.getDeviceClass() == UsbConstants.USB_CLASS_AUDIO) {
usbDevicesList.add(usbDeviceToListItem(key, device));
} else if (device.getDeviceClass() == UsbConstants.USB_CLASS_PER_INTERFACE) {
UsbInterface interface;
for (int i = 0; i < device.getInterfaceCount(); i++) {
// Check if at least one interface is audio
interface = device.getInterface(i);
if (interface != null && interface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) {
usbDevicesList.add(usbDeviceToSysBusUsbDevice(key, device));
break;
}
}
}
}
/////////////////////////////////////////////////////////
// Here goes some code to identify the device using
// linux shell commands if device SDK version is older
// than 21 (Lollipop). In older versions of Android
// we can't get device's Vendor and Device names using
// Android API, we need to use some linux shell commands.
/////////////////////////////////////////////////////////
return usbDevicesList;
}
Request permission for selected usb device from the list:
mUsbDeviceList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
UsbDeviceListItem usbDeviceItem = (UsbDeviceListItem) mUsbDeviceList.getItemAtPosition(i);
UsbDevice device = mAndroidDeviceMap.get(usbDeviceItem.getDevicePath());
manager.requestPermission(device, mPermissionIntent);
}
});
Permission broadcast receiver:
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
synchronized (this) {
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if(device != null){
streamFromUsbDevice(device)
}
}
else {
Toast.makeText(SensorActivity.this, "Permission denied for device " + device,
Toast.LENGTH_SHORT).show();
}
}
}
}
};
Sample method for reading data from the USB device
private void streamFromUSBDevice(UsbDevice device) {
UsbEndpoint endpoint;
UsbInterface usbInterface;
////////////////////////////////////////////////////////////////
// Here was code for finding the first audio interface with its
// endpoint. But because I failed to make it work I was manually
// getting them by index.
////////////////////////////////////////////////////////////////
usbInterface = device.getInterface(2);
endpoint = usbInterface.getEndpoint(0);
if (endpoint == null) {
Log.i(TAG, getString(R.string.endpoint_not_found));
notifyUser(R.string.endpoint_not_found);
return;
}
Log.i(TAG, R.string.connecting_to_usb_device);
notifyUser(R.string.connecting_to_usb_device);
UsbDeviceConnection connection = manager.openDevice(device);
connection.claimInterface(usbInterface, true);
while (true) {
if (!isRecording) {
// Close the connection to the usb device
connection.close();
notifyUser(R.string.status_idle);
break;
}
UsbRequest request = new UsbRequest();
request.initialize(connection, endpoint);
byte[] buffer = new byte[endpoint.getMaxPacketSize()];
final ByteBuffer buf = ByteBuffer.wrap(buffer);
Log.i(TAG, "Requesting queue...");
if (!request.queue(buf, buffer.length)) {
Log.e(TAG, getString(R.string.error_queue_request)));
notifyUser(R.string.error_queue_request);
isRecording = false;
break;
}
Log.i(TAG, "Requesting response...");
final UsbRequest response = connection.requestWait();
if (response == null) {
Log.e(TAG, "Null response!");
notifyUser(R.string.null_response);
isRecording = false;
break;
}
final int nRead = buf.position();
if (nRead > 0) {
Log.i(TAG, "Streaming " + nRead + " bytes to UI");
Bundle args = new Bundle();
args.putShortArray(ARG_RECORDED_DATA, byte2short(buffer));
sendMessageToUI(SHOW_AUDIO_DATA_PACKET, args, null);
} else {
Log.e(TAG, "No data in buffer!");
notifyUser(R.string_empty_buffer);
isRecording = false;
break;
}
}
}
What I am getting from this is that request.queue()
is always returning false.
I have also attempted to use the connection.bulkTransfer(endpoint, buffer, buffer.length, 0);
method but the result is always -1.
If someone was in similar situation please help.
P.S. The error I am receiving in the log is: UsbRequestJNI: request is closed in native_queue
.