Raspberry pi zero WH で作った LDAC 対応 USB to Bluetooth オーディオトランスミッターを Windows にも対応させる備忘録です.
2023年10月1日追記:Raspberry pi OS の最新安定版カーネルだとこの問題は解消しているみたいです.詳しくは,
をご覧ください.
以前の記事,
で,Raspberry pi zero WH で USB to Bluetooth オーディオトランスミッターを作ってみたのですが,残念ながらWindows10 ではうまく動きませんでした.
今回,これをなんとかすることができましたので,ご紹介したいと思います.
対応方法
今回は usb_f_uac2.ko という Raspbian (Linux) を USB 接続 UAC2 音源として認識させるためのカーネルモジュールを改変します.なぜ必要かは以下のページの議論を追えば書いてありますが,要は,usb_f_uac2.ko に Windows で動作させるのに十分なコードが追加されてないからみたいです.
さらに残念なことに,現在最新のカーネルでも全く対応は進んでいないようです.だから,これはどうにもならないなと思っていたのですが,ググっると Rockchip 上で動く Linux だと動いているような記述があります.実際,Rockchip の公開しているソースソースコード
を見ると,Windows で動くように一時的な対応を施したと書いてある.と言うことは,このコードを持ってくれば Raspbian の場合も Windows の UAC2 ドライバで使える使えるんじゃね,と思った訳です.
はじめる前に
はじめる前に注意として,今回は,作業を終えるのにほぼ,
24時間
くらいは掛かります.カーネルのコンパイルを伴うからです.
最初は usb_f_uac2.ko のみの部分的なコンパイルで済ませようとアレコレ試したのですが,Linux カーネルの整合性対策の壁に阻まれ叶いませんでした.
もちろん,クロスコンパイル環境を作るなりすれば相当程度時間を短縮できるとは思いますが,今回は面倒なので試していません.と言うか,Raspberry pi zero WH はシングルコアとは言え,CPU は 1GHz で動くので,長くても数時間で終わるだろうと思って始めたのですが,大間違いでした.カーネルって,でっかいんですね(当たり前!).
カーネルコンパイルの前準備
今回は,以下の記事を参考にしました.kernel の ver. は 5.4.59 です.
まず,コンパイルするのに必要なパッケージをいくつか持ってきます.
$ sudo apt install bison flex bc libssl-dev
次に,Raspbian のカーネルを最新版にします.なお,この作業は一般的には推奨されないものであることにご注意ください.
$ sudo rpi-update
次に,最新版のカーネルのソースコードを取得し,コンパイルするために展開しておきます.
$ FIRMWARE_REV=`cat /boot/.firmware_revision` $ KERNEL_REV=`curl -L https://github.com/Hexxeh/rpi-firmware/raw/${FIRMWARE_REV}/git_hash` $ echo Firmware Rev: ${FIRMWARE_REV} $ echo kernel Rev : ${KERNEL_REV} $ curl -L https://github.com/raspberrypi/linux/archive/${KERNEL_REV}.tar.gz >rpi-linux.tar.gz $ sudo -s # HOME=/home/pi # cd /usr/src # mkdir rpi-linux # cd rpi-linux # tar --strip-components 1 -xf ${HOME}/rpi-linux.tar.gz
さらに,現在利用中のカーネルでどのような機能が有効なのかの一覧も取得し,モジュールをコンパイルする前準備を行います.
# modprobe configs # gunzip -c /proc/config.gz >.config
これでカーネルをコンパイルする前準備が整いました.
f_uac2.c と u_uac2.h の改変
次は,usb_f_uac2.ko のソースコードを改変します.
まず,モジュールが含まれているソースコードの位置に移動します.usb_f_uac2.ko の元となるコードは f_uac2.c と u_uac2.h なので,これらが含まれる drivers/usb/gadget/function が対応する個所です.
# cd drivers/usb/gadget/function
ソースコードを改変します.本来ならばパッチを用意するのだと思いますし,私の場合もはじめはそうしようと思っていたのですが,Rockchip のカーネルは ver 4.4 で Raspbian のものと比べるとかなり古いせいか,f_uac2.c にもかなり大きな改変があり,パッチ作るのがかなり面倒だったので,以下の通りまるごと ver 4.4 のコードで置き換えました.
# mv f_uac2.c f_uac2.c.orig # mv u_uac2.h u_uac2.c.orig # wget https://raw.githubusercontent.com/rockchip-linux/kernel/develop-4.4/drivers/usb/gadget/function/f_uac2.c # wget https://raw.githubusercontent.com/rockchip-linux/kernel/develop-4.4/drivers/usb/gadget/function/u_uac2.h
また,このままだとコンパイルが通らないことから,f_uac2.c を以下の通り改変しました.
< usb_assign_descriptors(fn, fs_audio_desc, hs_audio_desc, NULL); > usb_assign_descriptors(fn, fs_audio_desc, hs_audio_desc, NULL, NULL);
また,改変したソースコードを元にしたカーネルであることが分かるよう,Makefile を以下の通り書き換えました.
# cd /usr/src/rpi-linux # vi Makefile VERSION = 4 PATCHLEVEL = 19 SUBLEVEL = 108 EXTRAVERSION = -usb_f_uac2_mod NAME = "People's Front" ....
結局パッチを作りました.こちらの方が安定してます.
--- f_uac2.c.orig 2020-03-28 18:07:31.903026316 +0900 +++ f_uac2.c 2020-03-28 18:13:06.702301125 +0900 @@ -174,7 +174,7 @@ static struct uac2_input_terminal_descri .bDescriptorSubtype = UAC_INPUT_TERMINAL, .bTerminalID = IO_IN_IT_ID, - .wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_UNDEFINED), + .wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_MICROPHONE), .bAssocTerminal = 0, .bCSourceID = USB_IN_CLK_ID, .iChannelNames = 0, @@ -202,7 +202,7 @@ static struct uac2_output_terminal_descr .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, .bTerminalID = IO_OUT_OT_ID, - .wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_UNDEFINED), + .wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_SPEAKER), .bAssocTerminal = 0, .bSourceID = USB_OUT_IT_ID, .bCSourceID = USB_OUT_CLK_ID, @@ -213,10 +213,10 @@ static struct uac2_ac_header_descriptor .bLength = sizeof ac_hdr_desc, .bDescriptorType = USB_DT_CS_INTERFACE, - .bDescriptorSubtype = UAC_MS_HEADER, + .bDescriptorSubtype = UAC_HEADER, .bcdADC = cpu_to_le16(0x200), + .bDescriptorSubtype = UAC_HEADER, .bcdADC = cpu_to_le16(0x200), .bCategory = UAC2_FUNCTION_IO_BOX, - .wTotalLength = cpu_to_le16(sizeof in_clk_src_desc + .wTotalLength = cpu_to_le16(sizeof ac_hdr_desc + sizeof in_clk_src_desc + sizeof out_clk_src_desc + sizeof usb_out_it_desc + sizeof io_in_it_desc + sizeof usb_in_ot_desc + sizeof io_out_ot_desc), @@ -274,7 +274,7 @@ static struct usb_endpoint_descriptor fs .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_OUT, - .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, + .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE, .wMaxPacketSize = cpu_to_le16(1023), .bInterval = 1, }; @@ -283,7 +283,7 @@ static struct usb_endpoint_descriptor hs .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, - .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, + .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE, .wMaxPacketSize = cpu_to_le16(1024), .bInterval = 4, }; @@ -351,7 +351,7 @@ static struct usb_endpoint_descriptor fs .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN, - .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, + .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_SYNC, .wMaxPacketSize = cpu_to_le16(1023), .bInterval = 1, }; @@ -360,7 +360,7 @@ static struct usb_endpoint_descriptor hs .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, - .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, + .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_SYNC, .wMaxPacketSize = cpu_to_le16(1024), .bInterval = 4, };
あとは,この書き換えたコードを元にカーネルをコンパイルしました.
# make -j1 zImage modules dtbs
Raspberry pi zero WH は 1-core の CPU なので,-j4 とかしてもコンパイルは速くはなりませんのでご注意ください.
改変カーネルへの置き換え
元のカーネルから改変カーネルへの置き換えは以下の通り実行しました.
# make -j1 zImage modules dtbs # make modules_install # cp arch/arm/boot/dts/overlays/*.dtbo /boot/overlays/ # cp arch/arm/boot/dts/overlays/README /boot/overlays/ # mv /boot/kernel.img /boot/kernel-bakup.img # cp arch/arm/boot/zImage /boot/kernel.img
リブート後,改変カーネルが有効かどうかを確かめるには uname -r の表示を見れば良いと思います.
$ uname -r 4.19.108-usb_f_uac2_mod
モジュールの利用
モジュールの利用の仕方については基本的には以前の記事,
をご覧ください.以下は,この記事から書き換えと追加が必要な部分を示します.
まず,変更が必要な部分は /usr/local/bin/on.usb_dac で,以下のように書き換えました.
$ sudo vi /usr/local/bin/on.usb_dac #!/bin/bash ASINK_LN=`pacmd list-sinks|grep -n "a2dp.sink>"|cut -d":" -f1` SINK_NAME=`pacmd list-sinks|head -n $ASINK_LN|tail -n 1|awk -F'[<>]' '{print $2}'` SINK_DP=`pacmd list-sinks|tail -n +$ASINK_LN|grep "sample spec"|head -n 1|awk -F'[^0-9]+' '{print $2}'` SINK_SR=`pacmd list-sinks|tail -n +$ASINK_LN|grep "sample spec"|head -n 1|awk -F'[^0-9]+' '{print $4}'` /usr/local/bin/on.uac2_audio $SINK_SR $(($SINK_DP /8)) ASOURCE_LN=`pacmd list-sources|grep -n "* index"|cut -d":" -f1` SOURCE_NAME=`pacmd list-sources|head -n $(($ASOURCE_LN +1))|tail -n 1|awk -F'[<>]' '{print $2}'` echo "SINK NAME:" $SINK_NAME echo "SINK AUDIO DEPTH:" $SINK_DP echo "SINK SMPLING RATE:" $SINK_SR echo "SOURCE NAME:" $SOURCE_NAME pactl load-module module-loopback source=$SOURCE_NAME sink=$SINK_NAME
以前の記事は usb_f_uac2.ko を modprobe -r g_audio で呼び出していましたが,これはレガシーな方法で,現在は推奨されなくなっているようです.代わりに GadgetFS の仕組みを用いて usb_f_uac2.ko を呼び出します.上の改変はこれを行うスクリプト /usr/local/bin/on.uac2_audio を使うような変更を行っているだけです.
肝の GadgetFS を利用して usb_f_uac2.ko を呼び出すスクリプトは以下の通りとなります.実行権限を忘れずにつける(chmod +x /usr/local/bin/on.uac2_audio)ようにしてください.
$ sudo vi /usr/local/bin/on.uac2_audio #!/bin/bash sudo su - <<EOF modprobe libcomposite mkdir /sys/kernel/config/usb_gadget/g1 mkdir /sys/kernel/config/usb_gadget/g1/configs/c.1 mkdir /sys/kernel/config/usb_gadget/g1/functions/uac2.0 echo $1 > /sys/kernel/config/usb_gadget/g1/functions/uac2.0/c_srate echo $2 > /sys/kernel/config/usb_gadget/g1/functions/uac2.0/c_ssize mkdir /sys/kernel/config/usb_gadget/g1/strings/0x409 mkdir /sys/kernel/config/usb_gadget/g1/configs/c.1/strings/0x409 echo "UAC2 Audio Gadget" > /sys/kernel/config/usb_gadget/g1/strings/0x409/product echo "uac2" > /sys/kernel/config/usb_gadget/g1/configs/c.1/strings/0x409/configuration echo 120 > /sys/kernel/config/usb_gadget/g1/configs/c.1/MaxPower ln -s /sys/kernel/config/usb_gadget/g1/functions/uac2.0 /sys/kernel/config/usb_gadget/g1/configs/c.1 echo 0x0101 > /sys/kernel/config/usb_gadget/g1/idProduct echo 0x1d6b > /sys/kernel/config/usb_gadget/g1/idVendor echo 0x0200 > /sys/kernel/config/usb_gadget/g1/bcdDevice echo "00000000de912db3" > /sys/kernel/config/usb_gadget/g1/strings/0x409/serialnumber echo "RASPBERRY PI FOUNDATION" > /sys/kernel/config/usb_gadget/g1/strings/0x409/manufacturer ls /sys/class/udc > /sys/kernel/config/usb_gadget/g1/UDC EOF
前回の記事もそうですが,これ,適当に組んだ(泥縄式に組んだ)動けば良い,という程度のものなので,エラー処理等全くしていないことにご注意ください.
なお,Windows 10 からは UAC2 Audio Gadget として認識されますが,対応のさせ方としてがかなり乱暴(Rockchip のソースコードでまるごと塗り替える)なので,きちんとパッチを作った方が良いかなとは思っています.
以上!