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),開源。

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