Androidドライブレスusbオーディオ実装
Androidシステムがusbマイクで音声を収集する機能を実現し、複数のanroidデバイスを互換化できる.
構想案は2つある:1.libusbライブラリを介して、usbドライバに直接アクセスし、usbプロトコルのオーディオデータを分析します.2.オーディオデバイスのpcmノードにtinyalsaでアクセスし、ノードを介してオーディオデータを直接取得する.
第2の方式のオーディオノードidは制御不能であり、複数のandroidデバイスを適応できないため、第1の方式を採用した.
libusbライブラリをコードで呼び出し、usbドライバでデータを取得し、apk呼び出しのためにjniライブラリにカプセル化します.
背景知識
各usbデバイスには独自のVIDアドレスとPIDアドレスがあり、このアドレスに基づいて指定されたusbデバイスを見つけることができます.
usbにはconfigがあり、その下には複数のinterfaceがあり、interfaceの下には複数のendpointがあります.インターネットのclass値とsubclass値によってインターネットタイプを区別することができ、例えばvideoのclass値は14、audioのclass値は1などであり、これによって複合機器のインターネットを識別することができる.その後、各interfaceの下には複数のendpointがあり、endpointにはaddressが存在し、これはデータ伝送のチャネルである.各endpointには、私が持っているこのusbマイクのような異なるデータフォーマットがあり、各endpointは、2チャネル/16ビット/48 Kなどのフォーマットに対応しています.しかし、endpointが複数のフォーマットに対応しているものもあります.
USbの伝送タイプは,制御伝送(Control Transfer),割り込み伝送(Interrupt Transfer),一括伝送(Bulk Transfer),同期伝送の4種類である.オーディオデバイスは、制御転送と同期転送にのみ使用されます.
コード#コード#
1.初期化
まずinitを行い、vid、pidに基づいてusbデバイスを検索してopenを行い、その後、無駆動設計を使用するためにオーディオalsaとusb駆動を解除する必要がある.最後にすべてのconfigをスキャンし、必要なclassに基づいて必要なinterfaceを見つけ、選択設定を行います.
2.構成
サンプリングレート、interfaceの下にあるAlternateSettingsなどの関連プロパティを設定します.
これらの関連パラメータは,uacプロトコル解析に基づいて判断することができ,ここでは直接パケット取得パラメータをキャプチャし,直接設定する.WindowsパッケージはBus Houndツール、linux駆動パッケージツールはusbmonを使用できます.
3.データの取得
データはtranfersで転送され、libusb_を経由します.fill_iso_transferは充填され、endpointaddressは前のconfigスキャンで取得され、callback関数はデータコールバックインタフェースであり、その後libusb_submit_Transferはコールバックに対応し、データはコールバックインタフェースで取得されます.同時にlibusb_を作成するにはhandle_eventsが処理するスレッドで、このスレッドはバックグラウンドでスケジューリングされ、callbackコールバックを保証します.
もう一つ注意すべき点はendpoint_bytes_per_packetは各パケット長であり、endpointでサポートされている最大パケット長よりも小さい値でなければなりません.packets_per_Transferはパケット数であり,この値には別の意味があり,サンプリング遅延時間でもある.例えばendpoint_bytes_per_packetは384に設定され、packets_per_Transferが10に設定されている場合、callbackの時間は10 ms後に3840 byteデータを返します.
まとめ
このプログラムは、指定されたusbマイクデバイスデータを簡単に取得します.config/interface Descriptorをスキャンするなど、関連デバイスの説明を取得し、オーディオデバイスが存在するかどうかを区別することができます.UAC関連プロトコルに従ってカプセル化し,interfaceとendpointのusbプロトコルextra拡張部を取得でき,この部分情報に基づいて解析はデバイスサポート構成,endpoint長アドレスなどを取得できる.
同じプロジェクトで、usbcamera関連のドライブレス設計に対してgit上のオープンソースプロジェクトを参照できます.このプロジェクトはUVCプロトコルをカプセル化した.UVCCamera
構想案は2つある:1.libusbライブラリを介して、usbドライバに直接アクセスし、usbプロトコルのオーディオデータを分析します.2.オーディオデバイスのpcmノードにtinyalsaでアクセスし、ノードを介してオーディオデータを直接取得する.
第2の方式のオーディオノードidは制御不能であり、複数のandroidデバイスを適応できないため、第1の方式を採用した.
libusbライブラリをコードで呼び出し、usbドライバでデータを取得し、apk呼び出しのためにjniライブラリにカプセル化します.
背景知識
各usbデバイスには独自のVIDアドレスとPIDアドレスがあり、このアドレスに基づいて指定されたusbデバイスを見つけることができます.
usbにはconfigがあり、その下には複数のinterfaceがあり、interfaceの下には複数のendpointがあります.インターネットのclass値とsubclass値によってインターネットタイプを区別することができ、例えばvideoのclass値は14、audioのclass値は1などであり、これによって複合機器のインターネットを識別することができる.その後、各interfaceの下には複数のendpointがあり、endpointにはaddressが存在し、これはデータ伝送のチャネルである.各endpointには、私が持っているこのusbマイクのような異なるデータフォーマットがあり、各endpointは、2チャネル/16ビット/48 Kなどのフォーマットに対応しています.しかし、endpointが複数のフォーマットに対応しているものもあります.
USbの伝送タイプは,制御伝送(Control Transfer),割り込み伝送(Interrupt Transfer),一括伝送(Bulk Transfer),同期伝送の4種類である.オーディオデバイスは、制御転送と同期転送にのみ使用されます.
コード#コード#
1.初期化
まずinitを行い、vid、pidに基づいてusbデバイスを検索してopenを行い、その後、無駆動設計を使用するためにオーディオalsaとusb駆動を解除する必要がある.最後にすべてのconfigをスキャンし、必要なclassに基づいて必要なinterfaceを見つけ、選択設定を行います.
int UsbAudio::open(int vid, int pid, int fd, const char *usbfs)
{
int r; //for return values
ssize_t cnt; //holding number of devices in list
printf("start open %d
", errno);
if (mUsbFs)
free(mUsbFs);
mUsbFs = strdup(usbfs);
printf("before 11111 errno:%d
", errno);
r = libusb_init2(&ctx, usbfs); //initialize a library session
if(r < 0) {
printf("Init Error
"); //there was an error
return -1;
}
usb_dev = libusb_find_device(ctx, vid, pid, NULL, fd);
if (usb_dev) {
libusb_set_device_fd(usb_dev, fd); // assign fd to libusb_device for non-rooted Android devices
libusb_ref_device(usb_dev);
}
printf("before 444444 errno:%d
", errno);
r = libusb_open(usb_dev, &dev_handle);
if(r != LIBUSB_SUCCESS)
{
printf("open device err %d
", errno);
return -1;
}
//libusb_reset_device(dev_handle);
printf("before scan_audio_interface errno:%d
", errno);
if (scan_audio_interface(usb_dev) < 0)
{
printf("scan_audio_interface err: errno:%d
", errno);
cancel();
return -1;
}
printf("before interface_claim_if errno:%d
", errno);
interface_claim_if(dev_handle, interfaceNumber);
}
int UsbAudio::interface_claim_if(libusb_device_handle *dev, int interface_number)
{
int r = 0;
r = libusb_kernel_driver_active(dev, interface_number);
printf("libusb_kernel_driver_active2 %d
", r);
if(r == 1)
{ //find out if kernel driver is attached
printf("Kernel Driver Active
");
if(libusb_detach_kernel_driver(dev, interface_number) == 0) //detach it
printf("Kernel Driver Detached!
");
}
printf("kernel detach errno:%d
", errno);
r = libusb_claim_interface(dev, interface_number); //claim interface 0 (the first) of device (mine had jsut 1)
if(r != 0) {
printf("Cannot Claim Interface
");
return 1;
}
printf("claim_interface errno:%d
", errno);
return 0;
}
2.構成
サンプリングレート、interfaceの下にあるAlternateSettingsなどの関連プロパティを設定します.
ret = libusb_set_interface_alt_setting(dev_handle, interfaceNumber, 0);
if (ret != 0) {
printf("libusb_set_interface_alt_setting failed: %d: %s
", interfaceNumber, libusb_error_name(ret));
return -1;
}
printf("Select the altsetting, interfaceNumber:%d, altsetting:%d
", interfaceNumber, AlternateSetting);
ret = libusb_set_interface_alt_setting(dev_handle, interfaceNumber, AlternateSetting);
if (ret != 0) {
printf("libusb_set_interface_alt_setting failed: %d, %d: %s
", interfaceNumber,
AlternateSetting, libusb_error_name(ret));
return -1;
}
ret = libusb_control_transfer(dev_handle, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_ENDPOINT,
0x01, 0x0100, EndpointAddress,
rate, sizeof(rate), 0);
if (ret == sizeof(rate))
{
printf("set mic config success:0x%x:0x%x:0x%x
",
rate[0], rate[1], rate[2]);
}
else
{
printf("set mic config fail %d
", ret);
return ret;
}
これらの関連パラメータは,uacプロトコル解析に基づいて判断することができ,ここでは直接パケット取得パラメータをキャプチャし,直接設定する.WindowsパッケージはBus Houndツール、linux駆動パッケージツールはusbmonを使用できます.
12.0 CTL 01 0b 00 00 03 00 00 00 SET INTERFACE 1963.1.0
12.0 CTL 01 0b 04 00 03 00 00 00 SET INTERFACE 1964.1.0
12.0 CTL 22 01 00 01 82 00 03 00 SET CUR 1965.1.0
12.0 OUT 80 bb 00 ... 1965.2.0
12.2 ISOC 00 00 00 00 00 00 00 00 ........ 1966.1.0
00 00 00 00 00 00 00 00 ........ 1966.1.8
00 00 00 00 00 00 00 00 ........ 1966.1.16
00 00 00 00 00 00 00 00 ........ 1966.1.24
12.2 ISOC 55 00 55 00 5c 00 5c 00 U.U.\.\. 1967.1.0
5c 00 5c 00 5f 00 5f 00 \.\._._. 1967.1.8
5c 00 5c 00 58 00 58 00 \.\.X.X. 1967.1.16
61 00 61 00 65 00 65 00 a.a.e.e. 1967.1.24
12.2 ISOC d6 ff d6 ff df ff df ff ........ 1968.1.0
e7 ff e7 ff ed ff ed ff ........ 1968.1.8
e7 ff e7 ff e9 ff e9 ff ........ 1968.1.16
e2 ff e2 ff e3 ff e3 ff ........ 1968.1.24
3.データの取得
データはtranfersで転送され、libusb_を経由します.fill_iso_transferは充填され、endpointaddressは前のconfigスキャンで取得され、callback関数はデータコールバックインタフェースであり、その後libusb_submit_Transferはコールバックに対応し、データはコールバックインタフェースで取得されます.同時にlibusb_を作成するにはhandle_eventsが処理するスレッドで、このスレッドはバックグラウンドでスケジューリングされ、callbackコールバックを保証します.
endpoint_bytes_per_packet = PackSize;
packets_per_transfer = PackNum;
total_transfer_size = PackSize*PackNum;
printf("Set up the transfers
");
printf("before fill EndpointAddress:%d, per_packet:%d, packets:%d, total_transfer_size:%d
",
EndpointAddress, endpoint_bytes_per_packet, packets_per_transfer, total_transfer_size);
for (transfer_id = 0; transfer_id < LIBUAC_NUM_TRANSFER_BUFS; ++transfer_id)
{
printf("fill transfer_id:%d
", transfer_id);
transfer = libusb_alloc_transfer(packets_per_transfer);
transfers[transfer_id] = transfer;
transfer_bufs[transfer_id] = (unsigned char *)malloc(total_transfer_size);
memset(transfer_bufs[transfer_id], 0, total_transfer_size);
libusb_fill_iso_transfer(transfer, dev_handle,
EndpointAddress,
transfer_bufs[transfer_id], total_transfer_size,
packets_per_transfer, _uac_stream_callback,
(void *)NULL, 1000);
libusb_set_iso_packet_lengths(transfer, endpoint_bytes_per_packet);
}
printf("before submit errno:%d
", errno);
for (transfer_id = 0; transfer_id < LIBUAC_NUM_TRANSFER_BUFS; transfer_id++) {
printf("submit transfer_id:%d
", transfer_id);
ret = libusb_submit_transfer(transfers[transfer_id]);
if (ret != 0) {
printf("libusb_submit_transfer failed: %d, errno:%d
", ret, errno);
break;
}
printf("submit transfer_id:%d finish
", transfer_id);
}
printf("after submit errno:%d
", errno);
thread_running = 1;
//pthread_create(&user_thread, NULL, fill_user_frame, NULL);
pthread_create(&event_thread, NULL, _uac_handle_events, (void*) ctx);
void *_uac_handle_events(void *args)
{
libusb_context *handle_ctx = (libusb_context *)args;
printf("%s start
", __func__);
while(UsbAudio::thread_running)
{
if (libusb_handle_events(handle_ctx) != LIBUSB_SUCCESS) {
printf("libusb_handle_events err
");
break;
}
}
printf("libusb_handle_events exit
");
}
void _uac_stream_callback(struct libusb_transfer *transfer)
{
//printf("do callback
");
switch (transfer->status)
{
case LIBUSB_TRANSFER_COMPLETED:
if (transfer->num_iso_packets) {
/* This is an isochronous mode transfer, so each packet has a payload transfer */
_uac_process_payload_iso(transfer);
}
break;
case LIBUSB_TRANSFER_NO_DEVICE:
UsbAudio::running = 0; // this needs for unexpected disconnect of cable otherwise hangup
// pass through to following lines
case LIBUSB_TRANSFER_CANCELLED:
case LIBUSB_TRANSFER_ERROR:
break;
case LIBUSB_TRANSFER_TIMED_OUT:
case LIBUSB_TRANSFER_STALL:
case LIBUSB_TRANSFER_OVERFLOW:
break;
}
if (UsbAudio::thread_running)
{
//printf("libusb_submit_transfer next.
");
if (libusb_submit_transfer(transfer) < 0) {
printf("libusb_submit_transfer err.
");
}
}
}
void _uac_process_payload_iso(struct libusb_transfer *transfer) {
/* per packet */
unsigned char *pktbuf;
size_t header_len;
unsigned char header_info;
struct libusb_iso_packet_descriptor *pkt;
int packet_id;
unsigned char* recv = (unsigned char*)malloc(PACKET_SIZE*NUM_PACKETS);//(PACKET_SIZE * transfer->num_iso_packets);
unsigned char* recv_next = UsbAudio::holdbuf;
int len = 0;
if (transfer->type != LIBUSB_TRANSFER_TYPE_ISOCHRONOUS)
{
printf("not isoc packet
");
return;
}
//printf("record pcm,isonum %d, len %d, actlen %d
", transfer->num_iso_packets,transfer->length,transfer->actual_length);
for (packet_id = 0; packet_id < transfer->num_iso_packets; ++packet_id) {
pkt = &transfer->iso_packet_desc[packet_id];
if (pkt->status != LIBUSB_TRANSFER_COMPLETED) {
printf("bad packet:status=%d,actual_length=%d", pkt->status, pkt->actual_length);
continue;
}
pktbuf = libusb_get_iso_packet_buffer_simple(transfer, packet_id);
if(pktbuf == NULL)
{
printf("receive pktbuf null
");
}
memcpy(recv_next, pktbuf, pkt->length);
recv_next += pkt->length;
len += pkt->length;
} // for
//
if (fwrite(recv, 1, len, pFile) < 0) {
perror("Unable to write to descriptor");
}
free(recv);
if (len > UsbAudio::PackSize * transfer->num_iso_packets) {
printf("Error: incoming transfer had more data than we thought.
");
return;
}
}
もう一つ注意すべき点はendpoint_bytes_per_packetは各パケット長であり、endpointでサポートされている最大パケット長よりも小さい値でなければなりません.packets_per_Transferはパケット数であり,この値には別の意味があり,サンプリング遅延時間でもある.例えばendpoint_bytes_per_packetは384に設定され、packets_per_Transferが10に設定されている場合、callbackの時間は10 ms後に3840 byteデータを返します.
まとめ
このプログラムは、指定されたusbマイクデバイスデータを簡単に取得します.config/interface Descriptorをスキャンするなど、関連デバイスの説明を取得し、オーディオデバイスが存在するかどうかを区別することができます.UAC関連プロトコルに従ってカプセル化し,interfaceとendpointのusbプロトコルextra拡張部を取得でき,この部分情報に基づいて解析はデバイスサポート構成,endpoint長アドレスなどを取得できる.
同じプロジェクトで、usbcamera関連のドライブレス設計に対してgit上のオープンソースプロジェクトを参照できます.このプロジェクトはUVCプロトコルをカプセル化した.UVCCamera