Raspberry pi zero WH で作った LDAC 対応 USB to Bluetooth オーディオトランスミッターを Windows にも対応させる備忘録

Environment
Photo by hyt.

Raspberry pi zero WH で作った LDAC 対応 USB to Bluetooth オーディオトランスミッターを Windows にも対応させる備忘録です.


2023年10月1日追記:Raspberry pi OS の最新安定版カーネルだとこの問題は解消しているみたいです.詳しくは,

Linux USB Gadget UAC2 の現状覚え書き
Linux USB Gadget UAC2 の現状覚え書きです. いつもの通り結論から書くと,Linux USB Gadget の UAC2 ですが, Windows もデフォルトでサポートするようになったみたい 複数のサンプリングレートの...

をご覧ください.


以前の記事,

Raspberry pi zero を USB to Bluetooth トランスミッターにする備忘録
Raspberry pi zero を USB to Bluetooth トランスミッターにする備忘録です. 前回記事で,Raspberry pi zero の Raspbian を LDAC,apt-X,AAC などの高音質なコーデックに...

で,Raspberry pi zero WH で USB to Bluetooth オーディオトランスミッターを作ってみたのですが,残念ながらWindows10 ではうまく動きませんでした.

今回,これをなんとかすることができましたので,ご紹介したいと思います.

対応方法

今回は usb_f_uac2.ko という Raspbian (Linux) を USB 接続 UAC2 音源として認識させるためのカーネルモジュールを改変します.なぜ必要かは以下のページの議論を追えば書いてありますが,要は,usb_f_uac2.ko に Windows で動作させるのに十分なコードが追加されてないからみたいです.

UAC2 gadget not recognized on Windows 10. · Issue #24 · linux-usb-gadgets/libusbgx
Using gadget-import (initial config retrieved via gadget-uac2 + gadget-export) to initialize a UAC2 audio device, but it...

さらに残念なことに,現在最新のカーネルでも全く対応は進んでいないようです.だから,これはどうにもならないなと思っていたのですが,ググっると Rockchip 上で動く Linux だと動いているような記述があります.実際,Rockchip の公開しているソースソースコード

usb: gadget: f_uac2: fix some issues for Windows recognized · rockchip-linux/kernel@5e962a0
We find that the UAC2 gadget can't be recognized on Windows 10. It's because that the descriptors of UAC2 doesn't meet t...

を見ると,Windows で動くように一時的な対応を施したと書いてある.と言うことは,このコードを持ってくれば Raspbian の場合も Windows の UAC2 ドライバで使える使えるんじゃね,と思った訳です.

はじめる前に

はじめる前に注意として,今回は,作業を終えるのにほぼ,

24時間

くらいは掛かります.カーネルのコンパイルを伴うからです.

最初は usb_f_uac2.ko のみの部分的なコンパイルで済ませようとアレコレ試したのですが,Linux カーネルの整合性対策の壁に阻まれ叶いませんでした.

Linuxのカーネルモジュールの整合性検証の仕組み – ビットログ

もちろん,クロスコンパイル環境を作るなりすれば相当程度時間を短縮できるとは思いますが,今回は面倒なので試していません.と言うか,Raspberry pi zero WH はシングルコアとは言え,CPU は 1GHz で動くので,長くても数時間で終わるだろうと思って始めたのですが,大間違いでした.カーネルって,でっかいんですね(当たり前!).

カーネルコンパイルの前準備

今回は,以下の記事を参考にしました.kernel の ver. は 5.4.59 です.

秋の夜長の Raspberry Pi 3 カーネル・セルフ・ビルド - new_western_elec
そんなわけで、RaspberryPi 3でカーネルをセルフビルドすると、何時間で終わるのでしょうか?セルフビルドの良いところは、ビルド後の運用が素早いという点と、クロス環境(Linuxマシン)を用意する必要がない点です。 最初のビルド時間は...
カーネルモジュールのコンパイル - RTL8812AUとMT7610U (Raspberry Pi) [メモ] - Qiita
概要元記事 Compiling Raspberry Pi kernel modulesRTL8812AUって、標準で対応してない、と探してたら、↑の記事を発見.環境Raspberry Pi 3…

まず,コンパイルするのに必要なパッケージをいくつか持ってきます.

$ 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

モジュールの利用

モジュールの利用の仕方については基本的には以前の記事,

Raspberry pi zero を USB to Bluetooth トランスミッターにする備忘録
Raspberry pi zero を USB to Bluetooth トランスミッターにする備忘録です. 前回記事で,Raspberry pi zero の Raspbian を LDAC,apt-X,AAC などの高音質なコーデックに...

をご覧ください.以下は,この記事から書き換えと追加が必要な部分を示します.

まず,変更が必要な部分は /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 のソースコードでまるごと塗り替える)なので,きちんとパッチを作った方が良いかなとは思っています.

以上!

タイトルとURLをコピーしました