Android从USB声卡录制高质量音频-----使用libusb读取USB声卡数据

为了获取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;
    }

 

  • 0
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
libusb-1.0.22是一个开源的用户空间USB库,可以用于Linux、macOS、Windows等操作系统。它提供了一个简单的编程接口,使开发者能够方便地与USB设备进行通信。 libusb-1.0.22支持USB 1.1和USB 2.0标准,并且还提供了对USB 3.0和USB 3.1的初步支持。它可以用于控制USB设备的各种功能,如数据读取和写入、设备的配置和控制、USB接口的复位等。开发者可以使用libusb-1.0.22库来开发各种用途的USB设备驱动程序、USB设备管理工具以及其他需要与USB设备进行交互的应用程序。 libusb-1.0.22的主要特点包括: 1. 跨平台支持:libusb-1.0.22可以在不同的操作系统上使用,开发者可以使用相同的代码来编写USB应用程序,而无需考虑底层操作系统的差异。 2. 简单易用:libusb-1.0.22提供了一组简单的API函数,使得开发者能够轻松地进行USB设备的操作和通信。开发者只需了解USB设备的通信协议和规范,就可以使用libusb-1.0.22来实现与USB设备的交互。 3. 功能丰富:libusb-1.0.22支持各种USB设备的操作,包括控制传输、批量传输、中断传输以及同步传输。此外,libusb-1.0.22还支持USB设备的热插拔和事件通知,方便开发者进行设备的动态管理。 总之,libusb-1.0.22是一个强大而灵活的USB库,可以帮助开发者更简便地实现USB设备的控制和通信功能。无论是开发USB设备驱动程序还是与USB设备进行交互的应用程序,libusb-1.0.22都是一个很好的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值