Android/linux从usb声卡获取音频(使用libusb库)---libusb库获取“纯麦”音频数据(三)

Android/linux从usb声卡获取音频(使用libusb库)---环境,lsusb命令的介绍(一)
Android/linux从usb声卡获取音频(使用libusb库)---设备环境的确认(二)
Android/linux从usb声卡获取音频(使用libusb库)---libusb库获取“纯麦”音频数据(三)
Android/linux从usb声卡获取音频(使用libusb库)---libusb库获取“纯麦”音频数据,附(四)
Android/linux从usb声卡获取音频(使用libusb库)---监听“纯麦”(五)
啰嗦了太多,先敬上代码:(在ubuntu 18.04上开发实测,数据会保存到文件 ,pcm流文件,48000,2通道,16it,用Cool Edit Pro软件可播放 如果直接把数据怼到alas输出,即可实现实时监听)

/*
* canok. 2019 JMTek, LLC.
*/
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include "libusb.h"

//设备相关信息, 和你设备相关
#define VID 0x0c76 
#define PID 0x1915
#define EP_ISO_IN 0x82 //端点地址
#define IFACE_NUM 1  //usb “接口”编号 Configuration Descriptor: 中的bNumInterfaces 值表示该配置中接口数量,每一个配置中的接口有自己的接口编号 bInterfaceNumber ,


#define NUM_TRANSFERS 10  //这个可以改,if you like
#define PACKET_SIZE 192  //lsusb 列出来的这个传输最大支持 200
#define NUM_PACKETS 10   //这个也可以改 if you like

//数据传输完成后,transfer传输任务会调用此回调函数。我们在这里拿走数据,并且继续把添加新的transfer任务,循环读取
static FILE* fout=NULL;
static int bFisrt = 1;
static void cb_xfr(struct libusb_transfer *xfr)
{
	if(bFisrt)
	{
		bFisrt = 0;
		fout = fopen("./output_data.pcm","w+");
		if(fout == NULL)
		{
			printf("canok:: erro to openfile[%d%s] \n",__LINE__,__FUNCTION__);
		}
	}

	int  i =0;
	if(fout)
	{//取出数据给到文件
		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)
			{
				printf("canok:: erro transfer status %d %s [%d%s]\n",pack->status,libusb_error_name(pack->status),__LINE__,__FUNCTION__);
				break;
			}

			const uint8_t *data = libusb_get_iso_packet_buffer_simple(xfr, i);
			printf("get out data %d [%d%s]\n",pack->actual_length,__LINE__,__FUNCTION__);
			fwrite(data,1,pack->actual_length,fout);
		}

	}	

	//把transfer任务重新在提交上去
	if (libusb_submit_transfer(xfr) < 0) {
		printf("error re-submitting !!!!!!!exit ----------[%d%s]\n",__LINE__,__FUNCTION__);
		exit(1);
	}
	else
	{
		printf("re-submint ok !\n");
	}

}
static struct libusb_device_handle *devh = NULL;
int main()
{
	int ret;
	//-----------------库的初始化
	ret = libusb_init(NULL);
	if(ret <0)
	{
		printf("can:: init erro:%d [%d%s]\n",ret,__LINE__,__FUNCTION__);
		return -1;
	}

	//------------------打开设备
	//每一个设备都有自己独有的 VID vendor 厂家ID, PID product 产品ID, 相当于usb设备的生份证
	//先通过lsusb命令 可以查看确定自己设备的VID PID
	//devh = libusb_open_device_with_vid_pid(NULL, 0x05ba, 0x000a);
	devh = libusb_open_device_with_vid_pid(NULL, VID, PID);
	if(devh == NULL)
	{
		printf("can:: open erro [%d%s] \n",__LINE__,__FUNCTION__);
		return -1;
	}

	//先做一个check,确保设备没有占用,在最小demo情况下,可以先不考虑这种复杂情况
	ret = libusb_kernel_driver_active(devh,IFACE_NUM);
	if(ret == 1)
        {
		printf("acticve ,to deteach .");
		ret = libusb_detach_kernel_driver(devh,IFACE_NUM);
		if(ret <0)
		{
			printf("canok:: erro to detach kernel!!![%d%s]\n",__LINE__,__FUNCTION__);
			return -1;
		}
	}


	//------------------请求使用一个 interface 第二个参数是 interface 编号
	ret = libusb_claim_interface(devh,IFACE_NUM);
	if(ret < 0)
	{
		printf("can:: erro claming interface %s %d [%d%s]\n",libusb_error_name(ret),ret,__LINE__,__FUNCTION__);
		return -1;
	}

	//-------------------同一个接口可以有多个接口描述符,用bAlternateSetting来识别.
	//在Interface Descriptor 中的bAlternateSetting 值
	libusb_set_interface_alt_setting(devh, IFACE_NUM, 1);

	//-------------------开始启动 transfer
	uint8_t buf[PACKET_SIZE*NUM_PACKETS];
	struct libusb_transfer* xfr[NUM_TRANSFERS];
	//demo里面有一段注释,意思是说,可以一次性提交多个transfer,这样当总线上有数据在传输时,同时可以有已经从usb总线传输完成的
	//transfer 在调用 callback, 这样可以提高 usb总线利用率,所以这里提交了 NUM_TRANSFER个 transfer
	//每一个 transfer传输 NUM_PACKETS 个包,每个包 大小为PACKET_SIZE . 可以理解为一个transfer 即一个传输任务
	int i =0;
	for(i=0; i<NUM_TRANSFERS; i++)
	{
		xfr[i] = libusb_alloc_transfer(NUM_PACKETS);
		if(!xfr[i])
		{
			printf("can:: alloc transfer err [%d%s]o\n",__LINE__,__FUNCTION__);
			return -1;
		}
                //填充transfer ,比如transfer的回调,cb_xfr, 这个transfer在执行完成后,会调用这个回调函数,我们就可以在回调里面
		//把数据拿走。 并且重新 提交transfer任务,这样就反复循环。
		libusb_fill_iso_transfer(xfr[i],devh,EP_ISO_IN,buf,PACKET_SIZE*NUM_PACKETS,NUM_PACKETS,cb_xfr,NULL,1000);
		libusb_set_iso_packet_lengths(xfr[i], PACKET_SIZE);

		//正式提交任务
		ret = libusb_submit_transfer(xfr[i]);
		if(ret ==0)
		{
			printf("canok:: transfer submint ok ! start capture[%d%s]\n",__LINE__,__FUNCTION__);
		}
		else
		{
			printf("canok:: transfer submint erro %d %s [%d%s]\n",ret,libusb_error_name(ret),__LINE__,__FUNCTION__);
		}
	}

	//------------------主循环 ,驱动事件
	while(1)
	{
		ret = libusb_handle_events(NULL);
		if(ret != LIBUSB_SUCCESS)
		{
			printf("can:: handle event erro ,exit! [%d%s]\n",__LINE__,__FUNCTION__);
			break;
		}
	}

	//逆初始化
	libusb_release_interface(devh,0);

	if(devh)
	{
		libusb_close(devh);
	}

	libusb_exit(NULL);

	//好像没有释放 transfer?????
	return 0;
}

上述demo,两个函数,main()和cb_xfr(), 方便理解,所以的调用libusb接口的都尽量在一个main函数中,减少demo自身的函数定义。而cb_xfr函数,是libusb接口要求的一个数据回调函数。(demo就应该这样简单明了。^_^)大体调用了这么几个部分:
1.0 libusb_init() //初始化
2.0 libusb_open_device_with_vid_pid(NULL, VID, PID); //打开设备
3.0 libusb_kernel_driver_active(devh,IFACE_NUM); //检查是否被占用,如被占用,调用libusb_detach_kernel_driver(devh,IFACE_NUM); 进行分离,然后我们才能使用
4.0 libusb_claim_interface(devh,IFACE_NUM);//请求使用一个 interface
5.0 libusb_set_interface_alt_setting(devh, IFACE_NUM, 1); //同一个接口可以有多个接口描述符,用bAlternateSetting来识别 这是切换复用功能?????
6.0 libusb_alloc_transfer + libusb_fill_iso_transfer +  libusb_submit_transfer //申请创建 transfer任务,填充tranfer任务体,提交启动transfer任务。 任务中可以设置一个回调,任务完成会触发回调,我们就在里面拿走数据,然后把该任务体transfer重新提交上去,循环。
7.0 while(1){libusb_handle_events(NULL)} //任务驱动主循环。类似于live555中的事件驱动主循环。
8.0 剩下的,逆初始化,该释放的释放,该关闭的关闭。
这个文件直接放在example目录下,暂取名 JMTek.c 已经对libusb源码进行过 configure + make + make install 的情况下,在进入到example目录下,执行make,可以编译出官方给的几个demo:
dpfp
dpfp_threaded
fxload
listdevs
sam3u_benchmark
testlibusb
xusb
hotplugtest
不过很遗憾,上述的几个demo可不一定能直接运行,个人喜欢从官方给的demo来学习,结果直接运行dpfp,直接错误。让人很是郁闷,仔细去看,源码中第一行就有解释:libusb example program to manipulate U.are.U 4000B fingerprint scanner
libusb示例程序,用于操纵U.are.U 4000B指纹扫描仪,当然,我们没有接上这个扫描仪设备,用不了。同样的看一下其他的 xusb sam3u_benchmark ,都是针对特定设备写的demo, 比如xusb 里面可以读U盘(这个还通用,接了我的u盘,测试ok),还有操控 xbox手柄和索尼的一个什么输入设备的demo。

执行添加的这个JMTek.c可以这么编译# gcc JMTek.c -I../libusb/ -lusb-1.0 -o JMTek (如果不嫌麻烦可以自行往makefile中添加)
最后,如果需要android上使用,在android/jni/example.mk 目录上添加上这个文件的编译即可,封装JNI什么的,小事。
附上:libusb官方文档链接
https://libusb.info/ 官网,可下载源码 。libusb库,它按照usb协议,把对usb设备的驱动的调用开发封装了一个库(常见的ioctr),开源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章