为了获取USB声卡数据,在网上进行了大量的文章搜索,发现USB的库都是使用C语言写的,使用比较多的应属libusb了。
参考https://github.com/shenki/usbaudio-android-demo.git
1、libusb简介
libusb是一种高级别的,底层的API,它封装了低级别的内核与USB模块的交互,并提供了一系列适合在用户空间进行usb驱动开发的函数。libusb基于usb文件系统提供的usb接口,端点等信息,与usb设备进行通信。显然,只要开发平台上的内核支持usb文件系统,我们就可以利用libusb进行usb驱动开发。
libusb可以跨平台实现,开发的程序很容易在不同操作系统上一直。对于Linux操作系统,也很容易在不同的CPU架构间进行移植,而且不低担心内核版本造成的种种问题。相对于Linux内核驱动开发,libusb无疑是一种省时省力而又行之有效的开发工具。
libusb库使用C语言编写,在Android中使用该库需要用到JNI技术。
2、AndroidStudio NDK项目配置
这里对NDK的系统环境就不深入了,只对项目配置进行说明。
A、在app的build.gradle里面,添加NDK的编译信息(包括生成的so库名字,以及编译出来的各种平台版本)。
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
defaultConfig {
applicationId "com.real0168.stereorecorder"
minSdkVersion 19
targetSdkVersion 28
// 指定支持的手机架构
// ndk {
// abiFilters "armeabi"
// }
}
sourceSets {
main {
// 指定JNI的目录
jniLibs.srcDirs = ['jni']
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
externalNativeBuild {
ndkBuild {
// 指定JNI代码的入口MK文件路径
path 'jni/Android.mk'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'org.greenrobot:eventbus:3.1.1'
}
B、下载Libusb源文件,将其复制到工程目录下。
到“https://libusb.info/”下载源码,将源码复制到项目目录中。
在JNI文件夹中创建Android.mk和Application.mk文件。
# +++++++++++++++++++++++++++++++++++++++Android.mk++++++++++++++++++++++++++
ROOT_PATH := $(call my-dir)
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# LOCAL_MODULE := libusb-1.0
# LOCAL_SRC_FILES := libusb-1.0/lib/$(TARGET_ARCH_ABI)/libusb-1.0.so
# LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/libusb-1.0/include/libusb-1.0
# LOCAL_EXPORT_LDLIBS := -llog
# include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := usbaudio
LOCAL_SHARED_LIBRARIES := usb100
LOCAL_LDLIBS := -llog
#LOCAL_CFLAGS :=
LOCAL_SRC_FILES := usbaudio_dump.c
include $(BUILD_SHARED_LIBRARY)
include $(LOCAL_PATH)/libusb/android/jni/Android.mk
# ++++++++++++++++++++++++++++++Application.mk++++++++++++++++++++++++
APP_ABI := armeabi-v7a armeabi
APP_PLATFORM := android-16
APP_STL := stlport_static
APP_CFLAGS += -DSTDC_HEADERS
STLPORT_FORCE_REBUILD := true
C、编写libusbAPI调用接口文件。
/*
*
* Dumb userspace USB Audio receiver
* Copyright 2012 Joel Stanley <joel@jms.id.au>
*
* Based on the following:
*
* libusb example program to measure Atmel SAM3U isochronous performance
* Copyright (C) 2012 Harald Welte <laforge@gnumonks.org>
*
* Copied with the author's permission under LGPL-2.1 from
* http://git.gnumonks.org/cgi-bin/gitweb.cgi?p=sam3u-tests.git;a=blob;f=usb-benchmark-project/host/benchmark.c;h=74959f7ee88f1597286cd435f312a8ff52c56b7e
*
* An Atmel SAM3U test firmware is also available in the above repository.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <jni.h>
#include <android/log.h>
#include <libusb/android/jni/usb/libusb.h>
#include <libusb/libusb/libusbi.h>
#include <libusb/libusb/libusb.h>
#define LOGD(...) \
__android_log_print(ANDROID_LOG_DEBUG, "UsbAudioNative", __VA_ARGS__)
#define UNUSED __attribute__((unused))
/* The first PCM stereo AudioStreaming endpoint. */
#define EP_ISO_IN 0x82
#define IFACE_NUM 2
static int do_exit = 1;
static struct libusb_device_handle *devh = NULL;
static unsigned long num_bytes = 0, num_xfer = 0;
static struct timeval tv_start;
static JavaVM* java_vm = NULL;
static jclass com_qcymall_recorder_usbaudio_AudioPlayback = NULL;
static jmethodID com_qcymall_recorder_usbaudio_AudioPlayback_write;
static void cb_xfr(struct libusb_transfer *xfr)
{
unsigned int i;
int len = 0;
// Get an env handle
JNIEnv * env;
void * void_env;
bool had_to_attach = false;
jint status = (*java_vm)->GetEnv(java_vm, &void_env, JNI_VERSION_1_6);
if (status == JNI_EDETACHED) {
(*java_vm)->AttachCurrentThread(java_vm, &env, NULL);
had_to_attach = true;
} else {
env = void_env;
}
// Create a jbyteArray.
int start = 0;
jbyteArray audioByteArray = (*env)->NewByteArray(env, 200 * xfr->num_iso_packets);
for (i = 0; i < xfr->num_iso_packets; i++) {
struct libusb_iso_packet_descriptor *pack = &xfr->iso_packet_desc[i];
if (pack->status != LIBUSB_TRANSFER_COMPLETED) {
LOGD("Error (status %d: %s) :", pack->status,
libusb_error_name(pack->status));
/* This doesn't happen, so bail out if it does. */
return;
// exit(EXIT_FAILURE);
}
const uint8_t *data = libusb_get_iso_packet_buffer_simple(xfr, i);
// writeToFile22(data, pack->actual_length);
(*env)->SetByteArrayRegion(env, audioByteArray, len, pack->actual_length, data);
// LOGD("JNI DataLength = %d; len = %d", pack->length, pack->actual_length);
len += pack->actual_length;
}
// Call write()
(*env)->CallStaticVoidMethod(env, com_qcymall_recorder_usbaudio_AudioPlayback,
com_qcymall_recorder_usbaudio_AudioPlayback_write, audioByteArray, len);
(*env)->DeleteLocalRef(env, audioByteArray);
if ((*env)->ExceptionCheck(env)) {
LOGD("Exception while trying to pass sound data to java");
return;
}
num_bytes += len;
num_xfer++;
if (had_to_attach) {
(*java_vm)->DetachCurrentThread(java_vm);
}
if (libusb_submit_transfer(xfr) < 0) {
LOGD("error re-submitting URB\n");
exit(1);
}
}
#define NUM_TRANSFERS 10
#define PACKET_SIZE 200
#define NUM_PACKETS 10
static int benchmark_in(uint8_t ep)
{
static uint8_t buf[PACKET_SIZE * NUM_PACKETS];
static struct libusb_transfer *xfr[NUM_TRANSFERS];
int num_iso_pack = NUM_PACKETS;
int i;
int rc;
/* NOTE: To reach maximum possible performance the program must
* submit *multiple* transfers here, not just one.
*
* When only one transfer is submitted there is a gap in the bus
* schedule from when the transfer completes until a new transfer
* is submitted by the callback. This causes some jitter for
* isochronous transfers and loss of throughput for bulk transfers.
*
* This is avoided by queueing multiple transfers in advance, so
* that the host controller is always kept busy, and will schedule
* more transfers on the bus while the callback is running for
* transfers which have completed on the bus.
*/
for (i=0; i<NUM_TRANSFERS; i++) {
xfr[i] = libusb_alloc_transfer(num_iso_pack);
if (!xfr[i]) {
LOGD("Could not allocate transfer");
return -ENOMEM;
}
libusb_fill_iso_transfer(xfr[i], devh, ep, buf,
sizeof(buf), num_iso_pack, cb_xfr, NULL, 1000);
libusb_set_iso_packet_lengths(xfr[i], sizeof(buf)/num_iso_pack);
rc = libusb_submit_transfer(xfr[i]);
LOGD("libusb_submit_transfer %d %s", i, libusb_error_name(rc));
}
gettimeofday(&tv_start, NULL);
return 1;
}
unsigned int measure(void)
{
struct timeval tv_stop;
unsigned int diff_msec;
gettimeofday(&tv_stop, NULL);
diff_msec = (tv_stop.tv_sec - tv_start.tv_sec)*1000;
diff_msec += (tv_stop.tv_usec - tv_start.tv_usec)/1000;
printf("%lu transfers (total %lu bytes) in %u miliseconds => %lu bytes/sec\n",
num_xfer, num_bytes, diff_msec, (num_bytes*1000)/diff_msec);
return num_bytes;
}
JNIEXPORT jint JNICALL
Java_com_qcymall_recorder_usbaudio_UsbAudio_measure(JNIEnv* env UNUSED, jobject foo UNUSED) {
return measure();
}
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void* reserved UNUSED)
{
LOGD("libusbaudio: loaded");
java_vm = vm;
return JNI_VERSION_1_6;
}
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved UNUSED)
{
JNIEnv * env;
void * void_env;
(*java_vm)->GetEnv(vm, &void_env, JNI_VERSION_1_6);
env = void_env;
(*env)->DeleteGlobalRef(env, com_qcymall_recorder_usbaudio_AudioPlayback);
LOGD("libusbaudio: unloaded");
}
JNIEXPORT jint JNICALL
Java_com_qcymall_recorder_usbaudio_UsbAudio_setup(JNIEnv* env UNUSED, jobject foo UNUSED)
{
int rc;
uint8_t data[0x03];
uint8_t data2[23];
int *configCount;
rc = libusb_init(NULL);
if (rc < 0) {
LOGD("Error initializing libusb: %d %s\n", rc, libusb_error_name(rc));
return -1000 + rc;
}
// discover devices
libusb_device **list;
libusb_device *found = NULL;
ssize_t cnt = libusb_get_device_list(NULL, &list);
ssize_t i = 0;
int err = 0;
if (cnt >= 0){
for (i = 0; i < cnt; i++) {
libusb_device *device = list[i];
}
libusb_free_device_list(list, 1);
}
/* This device is the TI PCM2900C Audio CODEC default VID/PID. */
devh = libusb_open_device_with_vid_pid(NULL, 0x0c76, 0x161F);
if (!devh) {
LOGD("Error finding USB device\n");
libusb_exit(NULL);
return -1100;
}
rc = libusb_kernel_driver_active(devh, IFACE_NUM);
if (rc == 1) {
rc = libusb_detach_kernel_driver(devh, IFACE_NUM);
if (rc < 0) {
LOGD("Could not detach kernel driver: %s\n",
libusb_error_name(rc));
libusb_close(devh);
libusb_exit(NULL);
return -2000 + rc;
}
}
rc = libusb_claim_interface(devh, IFACE_NUM);
if (rc < 0) {
LOGD("Error claiming interface: %s\n", libusb_error_name(rc));
libusb_close(devh);
libusb_exit(NULL);
return -3000+rc;
}
rc = libusb_set_interface_alt_setting(devh, IFACE_NUM, 1);
if (rc < 0) {
LOGD("Error setting alt setting: %s\n", libusb_error_name(rc));
libusb_close(devh);
libusb_exit(NULL);
return -4000+rc;
}
rc = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_ENDPOINT,
0x01, 0x0100, EP_ISO_IN,
data, sizeof(data), 0);
if (rc == sizeof(data))
{
LOGD("set mic config success:0x%x:0x%x:0x%x\n",
data[0], data[1], data[2]);
}
else
{
LOGD("set mic config fail %d\n", rc);
return -5000+rc;
}
// Get write callback handle
jclass clazz = (*env)->FindClass(env, "com/qcymall/recorder/usbaudio/AudioPlayback");
if (!clazz) {
LOGD("Could not find au.id.jms.usbaudio.AudioPlayback");
libusb_close(devh);
libusb_exit(NULL);
return -6100;
}
com_qcymall_recorder_usbaudio_AudioPlayback = (*env)->NewGlobalRef(env, clazz);
com_qcymall_recorder_usbaudio_AudioPlayback_write = (*env)->GetStaticMethodID(env,
com_qcymall_recorder_usbaudio_AudioPlayback, "write", "([BI)V");
if (!com_qcymall_recorder_usbaudio_AudioPlayback_write) {
LOGD("Could not find au.id.jms.usbaudio.AudioPlayback");
(*env)->DeleteGlobalRef(env, com_qcymall_recorder_usbaudio_AudioPlayback);
libusb_close(devh);
libusb_exit(NULL);
return -7100;
}
// Good to go
do_exit = 0;
LOGD("Starting capture");
if ((rc = benchmark_in(EP_ISO_IN)) < 0) {
LOGD("Capture failed to start: %d", rc);
return -8000+rc;
}
return 0;
}
JNIEXPORT void JNICALL
Java_com_qcymall_recorder_usbaudio_UsbAudio_stop(JNIEnv* env UNUSED, jobject foo UNUSED) {
do_exit = 1;
measure();
}
JNIEXPORT bool JNICALL
Java_com_qcymall_recorder_usbaudio_UsbAudio_close(JNIEnv* env UNUSED, jobject foo UNUSED) {
// fclose(fp);
if (do_exit == 0) {
return false;
}
libusb_release_interface(devh, IFACE_NUM);
if (devh)
libusb_close(devh);
libusb_exit(NULL);
return true;
}
JNIEXPORT void JNICALL
Java_com_qcymall_recorder_usbaudio_UsbAudio_loop(JNIEnv* env UNUSED, jobject foo UNUSED) {
while (!do_exit) {
int rc = libusb_handle_events(NULL);
if (rc != LIBUSB_SUCCESS)
break;
}
}
JNIEXPORT jstring JNICALL
Java_com_qcymall_recorder_usbaudio_UsbAudio_hellow(JNIEnv *env, jobject instance) {
return (*env)->NewStringUTF(env, "hellow");
}
D、声明本地方法
实现USBAudio类,提供开始录音,结束录音等方法。
public class UsbAudio {
private static final String TAG = "UsbAudio";
static {
System.loadLibrary("usbaudio");
}
public native int setup();
public native void close();
public native void loop();
public native boolean stop();
public native int measure();
public native String hellow();
private boolean isRecord;
public int startRecord(){
if (isRecord){
return 0;
}
int result = setup();
if (result >= 0) {
isRecord = true;
new Thread(new Runnable() {
public void run() {
loop();
}
}).start();
return result;
}else{
return result;
}
}
public void stopRecord(){
LogUtil.d(TAG, "Stop pressed");
isRecord = false;
stop();
Handler h = new Handler();
h.postDelayed(new Runnable() {
@Override
public void run() {
if (!isRecord) {
close();
}
}
}, 100);
}
public boolean isRecord() {
return isRecord;
}
}
使用USBAudio开始录音之前,必须先使用JAVA的API获取一下USB设备信息,如果没有提前获取设备信息的话,可能Libusb库的初始化有问题。
public UsbDeviceConnection openDevice() {
if (mUsbDevice == null){
return null;
}
int interfaceCount = mUsbDevice.getInterfaceCount();
for (int interfaceIndex = 0; interfaceIndex < interfaceCount; interfaceIndex++) {
UsbInterface usbInterface = mUsbDevice.getInterface(interfaceIndex);
if ((UsbConstants.USB_CLASS_AUDIO != usbInterface.getInterfaceClass())
&& (UsbConstants.USB_CLASS_HID != usbInterface.getInterfaceClass())) {
continue;
}
for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
UsbEndpoint ep = usbInterface.getEndpoint(i);
if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_ISOC) {
if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
mUsbEndpointIn = ep;
mUsbInterfaceInt = usbInterface;
} else {
mUsbEndpointOut = ep;
musbInterfaceOut = usbInterface;
}
}if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT){
mUsbEndpointCtl = ep;
musbInterfaceOut = usbInterface;
}
}
}
connection = manager.openDevice(mUsbDevice);
if (connection == null){
return null;
}
boolean result = connection.claimInterface(mUsbInterfaceInt, true);
boolean result2 = connection.claimInterface(musbInterfaceOut, true);
return connection;
}