RTX1200のv6コネクト接続設定でハマる

帯広に住む弟が「WiFiが調子悪い。安定したWiFiはないの?」というので、YAMAHAのWLXやCiscoのMerakiGoを勧めたら、WLX212を購入した。結構高いのによく買うなぁ、と思ったが、よくよくWiFi不調の内容を聞いてみると、「テレビのネット接続が途切れる。テレビは有線接続している。」というではないか! それ、WiFi関係ない。普通の人は家庭内ネットワーク=WiFiだというのをすっかり忘れていた。
そこで、中古のRTX1200を入手し、設定してあげることに。
札幌の自宅で設定し、ゴールデンウィークに帯広に行き、ルータ交換することにした。

札幌の自宅はIPv6DTiのIPoE、IPv4はとあるISPのPPPoE。
帯広の弟宅はASAHIネットASAHIネットIPv6は同じくIPoE、IPv4DS-LiteとPPPoE両方提供されている。DS-Liteはv6コネクトという商品名。せっかくのRTXなので、DHCPで配布されたIPアドレスからはDS-Lite経由で、特定のIPアドレスL2TP/IPSec用にPPPoE経由で、IPv4接続するということにした。

RTX1200はDS-Liteに正式に対応しているため、設定例はRT-Proに載っている。

www.rtpro.yamaha.co.jp

しかし、注意点があって、RTX1200(とRTX810)はAFTRアドレスにFQDN(いわゆるURL)を指定できず、IPアドレスを指定する必要がある。
(このため、RTX1200とRTX810はv6コネクトの対応機種に掲載されていない)

v6コネクトのAFTRのIPアドレスを調べるべく、札幌のDTiのIPoE環境で、WindowsコマンドプロンプトLinuxのターミナルから
nslookup dslite.v6connect.net
と、コマンドを打っても

% nslookup dslite.v6connect.net
Server:		240b:13:8360:1100::1
Address:	240b:13:8360:1100::1#53

** server can't find dslite.v6connect.net: NXDOMAIN

と、いった感じで、IPアドレスを教えてくれません。
ネットの情報を探ると、dslite.v6connect.netのIPアドレスは[2001:c28:5:301::11]である、というような情報を見つけたので、早速指定。

tunnel select 1
 tunnel encapsulation ipip
 tunnel endpoint address 2001:c28:5:301::11
 ip tunnel mtu 1460
 ip tunnel tcp mss limit auto
 tunnel enable 1

IPv6 IPoEのRAプロキシなどの設定や、(VPN用の)PPPoEの設定なども行い、帯広に持っていくと、IPv4で通信できない。
もちろん、IPv6の通信でGoogleYouTubeは接続可能。

RTX1200の設定ページでは

■接続先の情報
IP over IP TUNNEL1 Up 2001:c28:5:301::11

と、接続はされているようだが、状態表示は

■TUNNEL1の状態表示
トラフィック
送信 パケット数 1138
受信 パケット数 0

対向側のパケットが一切送られていない状態。
思い当たる設定を色々試したが、何をやってもダメ。

初心に戻り、そもそもAFTRアドレスは合っているのだろうか? と、帯広=Asahi-netの環境でnslookupを試してみると・・・。

% nslookup dslite.v6connect.net           
Server:		240b:13:8360:1100::1
Address:	240b:13:8360:1100::1#53

Non-authoritative answer:
Name:	dslite.v6connect.net 
Address: 2001:c28:1:301::11

なんと、札幌=DTiと同じNGN網内のDNSサーバーに問い合わせしているはずなのに、帯広=Asahi-net契約での問い合わせだと、回答が帰ってきました。
そして、事前情報のIPアドレスと違うということがわかりました。
CAF番号によって収容先サーバーを変えているということでしょうか?それともサーバー更改でIPアドレスが変更したということでしょうか?
AFTRのIPアドレスを変更します。

tunnel select 1
 tunnel encapsulation ipip
 tunnel endpoint address 2001:c28:1:301::11
 ip tunnel mtu 1460
 ip tunnel tcp mss limit auto
 tunnel enable 1

めでたくIPv4通信ができるようになりました。

grub rescue > error: symbol 'grub_calloc' not found

Gentooを入れているサーバーがかなり古く、AMD Athlon 5350で、emergeに時間がかかりすぎるようになってきました。
ただ時間がかかるだけなら良いのですが、エラーのたびにやり直す必要が出てきて、'emerge -uDN @world'を完了するのに、何日もかかるように。(ほとんどがいつの間にかエラーで止まっていたのを知らずにほっといている時間なのですが・・・。)
買い換えようと思いましたが、今、自作で省電力CPUとかマザーって少ないんですね。
なので、ノート用CPUを積んだ小型ベアボーンに買い替えました。

移行作業は面倒なので、旧サーバーのSATA SSDの中身を、新サーバーのNVME SSDパーティション毎に、'e2image -ra -p /dev/sda4 /dev/nvme0n1p4'のように、e2imageコマンドを使ってコピーしました。
FAT32EFIパーティションにはe2imageコマンドは使えないので、rsyncでコピー。

この状態でSATA SSDを外し、NVME SSDからブートしてみると、grub rescueモードに突入。grubコンソールモードでもないので、とても使いにくいです。

grub rescue >

解決方法をググると、bootディレクトリをprefixにセットして、insmod normal.modでモジュールを読み込んでから、normalコマンドで通常のgrubに戻るということでした。
(normal.modではなく、linux.modと解説しているサイトもありました。)

grub rescue > set prefix=(hd0,gpt1)/boot
grub rescue > insmod normal.mod
symbol 'grub_calloc' not found
grub rescue > 

ちなみに、lsコマンドが使えるので、パーティションのリストやディレクトリ内のファイルリストを表示できます。
lsでパーティションの一覧から、/bootに該当するであろうパーティションを探します。
上記の例ではgrubが認識したhd0の第1パーティションです。
しかし、モジュールが読み込めず、symbol 'grub_calloc' not foundが出ます。このエラーでググると、grub rescueモードでの解決策はなく、リムーバブルメディアからブートして、grub-installを実行するしかありません。

早速、Gentoo LiveCDをUSBにw32diskimagerで書き込み、起動。
GentooGRUBガイドやGentoo quick install guideを元にgrub-installを実施します。

wiki.gentoo.org

$ > sudo su
root #mkdir -p /mnt/gentoo
root #mount /dev/nvme0n1p4 /mnt/gentoo
root #mount /dev/nvme0n1p1 /mnt/gentoo/boot
root #mount /dev/nvme0n1p2 /mnt/gentoo/boot/efi
root #cd /mnt/gentoo
root #mount --types proc /proc /mnt/gentoo/proc
root #mount --rbind /sys /mnt/gentoo/sys
root #mount --make-rslave /mnt/gentoo/sys
root #mount --rbind /dev /mnt/gentoo/dev
root #mount --make-rslave /mnt/gentoo/dev
root #mount --bind /run /mnt/gentoo/run
root #mount --make-slave /mnt/gentoo/run
root #chroot . /bin/bash
root #source /etc/profile 
root #grub-install --target=x86_64-efi

明示的に--target=x86_64-efiをつけないと、i386-pcディレクトリがない!って怒られます。
しかし、これでもgrub rescueモードに入ってしまいます。
いくつか試みましたが、grubが通常起動したのは、/boot内にあるgrubディレクトリと、/boot/efiディレクトリ内にあるEFIディレクトリの2つを削除してから(いきなり削除は怖いのでリネームしてから)、上記のgrub-installをデバイスマップを再作成する--recheckオプションをつけて実行したときでした。

$ > sudo su
root #mkdir -p /mnt/gentoo
root #mount /dev/nvme0n1p4 /mnt/gentoo
root #mount /dev/nvme0n1p1 /mnt/gentoo/boot
root #mount /dev/nvme0n1p2 /mnt/gentoo/boot/efi
root #cd /mnt/gentoo
root #mount --types proc /proc /mnt/gentoo/proc
root #mount --rbind /sys /mnt/gentoo/sys
root #mount --make-rslave /mnt/gentoo/sys
root #mount --rbind /dev /mnt/gentoo/dev
root #mount --make-rslave /mnt/gentoo/dev
root #mount --bind /run /mnt/gentoo/run
root #mount --make-slave /mnt/gentoo/run
root #chroot . /bin/bash
root #source /etc/profile
root #mv /boot/grub /boot/grub.old
root #mv /boot/efi/EFI  /boot/efi/EFI.old
root #grub-install --target=x86_64-efi --boot-directory=/boot --efi-directory=/boot/efi --recheck

chroot環境なので、--boot-directory=/bootと--efi-directory=/boot/efiオプションは必要ないかもしれません。
chroot環境ではない場合は、それぞれ指示しないと、リムーバブルメディアでブートした環境にgrubをインストールしてしまいます。
最後に、新たに作られた/boot/grubフォルダにはgrub.cfgがありませんから、これを作成します。

root #grub-mkconfig -o /boot/grub/grub.cfg

これで、めでたくgrubが起動しました。
次はkernelだ!

PulseAudioでRaspberry PiをBluetoothレシーバーにする

そもそものPulseAudioをどう設定したのか、かなり前に試行錯誤して記録を残し忘れてしまいました。
今はALSAを経由せず、shairport-sync(AirPlay)とgmrender-resurrect(DLNA/Upnp)はPluseAudio経由で使えています。
PulseAudioのおかげで、それぞれのアプリケーションがALSAのデバイスを開放されるのを待たずに利用できます。
ソフトウェアミキシングしているんでしょう。(その分、音が悪いということかもしれません。)

そこで、Bluetooth USBドングルを使って、Raspberry PiBluetoothレシーバーにします。
せっかくPulseAudioが使えているので、そのままPulseAudioをBluetooth対応にします。

参考にしたのはこのサイト
ifritjp.github.io

もう、このサイトの通りやればほぼできます。
が、いつくかハマりポイントが。

bluetoothctlで接続まででき、スマホで音楽再生もできるのですが、音は一切鳴りません。
pactl list sources shortを実行しても、bluezで始まる再生デバイスが表示されないのです。

# pactl list sources short
0	alsa_output.platform-bcm2835_audio.digital-stereo.monitor	module-alsa-card.c	s16le 2ch 44100Hz	SUSPENDED
1	alsa_output.platform-bcm2835_audio.analog-stereo.monitor	module-alsa-card.c	s16le 2ch 44100Hz	SUSPENDED
2	alsa_output.usb-VIA_Technologies_Inc._NFJ_USB_Audio-00.analog-stereo.monitor	module-alsa-card.c	s16le 2ch 44100Hz	SUSPENDED

どうやら、bluetooth関連のモジュールがPulseAudioに組み込まれていないからのようです。
そこで、/etc/pulse/system.paの最下行にbluetooth関連のモジュールをロードする指示を記載して再起動します。

# nano /etc/pulse/system.pa

(中略)

### Bluetooth Audio
load-module module-bluetooth-policy
load-module module-bluetooth-discover

再起動することで、Bluetoothレシーバーとして音が鳴ります。
PulseAudioデバイスも表示されます。

# pactl list sources short
0	alsa_output.platform-bcm2835_audio.digital-stereo.monitor	module-alsa-card.c	s16le 2ch 44100Hz	SUSPENDED
1	alsa_output.platform-bcm2835_audio.analog-stereo.monitor	module-alsa-card.c	s16le 2ch 44100Hz	SUSPENDED
2	alsa_output.usb-VIA_Technologies_Inc._NFJ_USB_Audio-00.analog-stereo.monitor	module-alsa-card.c	s16le 2ch 44100Hz	IDLE
7	bluez_source.80_7B_3E_8D_82_25.a2dp_source	module-bluez5-device.c	s32le 2ch 44100Hz	RUNNING

ちなみに余談ですが、PulseAudioのデフォルト再生デバイスをUSB DACにしています。
上記デバイスリストのNo.2です。
これは、/etc/pulse/default.paでset-default-sinkにデバイス名を指定します。

# nano /etc/pulse/default.pa

(中略)

### Make some devices default
set-default-sink alsa_output.usb-VIA_Technologies_Inc._NFJ_USB_Audio-00.analog-stereo
#set-default-source input

さて、ここで再生されるのはBluetooth標準のSBCコーデックでの再生です。
安いDAC、安いデジタルアンプ、安いスピーカーでもSBCの音質はペラッペラのひどい音なのがわかります。
そこで、高音質コーデックをインストールします。

参考にしたのはこのサイト
www.labohyt.net

またもやこの通りにやると対応できちゃいます。
自分はAACコーデックは不要だったので、そこはスキップしました。

# apt install pulseaudio libpulse-dev libbluetooth-dev libdbus-1-dev libsbc-dev libavcodec-dev libavutil-dev dh-autoreconf git cmake
# mkdir src
# cd src
# git clone https://github.com/EHfive/ldacBT.git
# cd ldacBT/
# git submodule update --init
# mkdir build && cd build
# cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DINSTALL_LIBDIR=/usr/local/lib -DLDAC_SOFT_FLOAT=OFF ../
# make DESTDIR=$DEST_DIR install
# MODDIR=`pkg-config --variable=modlibexecdir libpulse`
# find $MODDIR -regex ".*\(bluez5\|bluetooth\).*\.so" -exec cp {} {}.bak \;
# git clone https://github.com/EHfive/pulseaudio-modules-bt.git
# cd pulseaudio-modules-bt/
# git submodule update --init
# git -C pa/ checkout v`pkg-config libpulse --modversion|sed 's/[^0-9.]*\([0-9.]*\).*/\1/'`
# mkdir build && cd build
# cmake -DCODEC_APTX_FF=ON -DCODEC_APTX_HD_FF=ON -DCODEC_LDAC=ON ..
# make
# sudo make install
# ldconfig

違いは、AACに対応しないので、fdk-aacを導入しないのと、pulseaudio-modules-btのcmakeで-DCODEC_AAC_FDK=ONを記載していないことです。

Raspberry Piを再起動してスマホからBluetooth接続して音楽を再生すると、明らかに音がよくなりました。
ちなみに、LDACはデコードに対応していないので、Raspberry PiをはじめとするLinuxマシンをBluetoothレシーバーにすると、LDACコーデックは使えません。
aptX-HDを使っているのかな?と、思ってスマホの開発者モードから確認すると、aptXを使っていました。
なんと、私が使っているGalaxy S20はaptX-HDには対応していないと・・・。
まぁaptXでもSBCよりは十分音が良くなったので、これで良しとします。

LDACでデコードできるようにならないかなぁ・・・。

Raspberry PiでBluetooth 5.0ドングル(rtl8761bu)を使う

BluetoothのUSBドングルなんて、どれも同じでCSR社製のチップが載っているんだろうとばかり思っていました。
なので、せっかくだからBluetooth5.0のUSBドングルを買うことにしたらドハマり。
ファームウェアがアタッチされません。
dmsgを見てみると。

$ dmsg | grep hci
(中略)
[151448.550512] bluetooth hci0: Direct firmware load for rtl_bt/rtl8761bu_fw.bin failed with error -2
[151448.550540] Bluetooth: hci0: RTL: firmware file rtl_bt/rtl8761bu_fw.bin not found

当然hciconfigもdownに

$ hciconfig
hci0:	Type: Primary  Bus: USB
	BD Address: 00:00:00:00:00:00  ACL MTU: 0:0  SCO MTU: 0:0
	DOWN 
	RX bytes:42 acl:0 sco:0 events:4 errors:0
	TX bytes:12 acl:0 sco:0 commands:4 errors:0

この解決策のいとぐちはこちらのブログにありました。

maku.blog

ここからリンクされている元記事

linuxreviews.org

kernel 5.8以降でrtl_btのドライバが提供されており、RTL8761Aのファームウェアは多くのLinux distributionで提供済みですが、RTL8761Bのファームウェアはないので、GitHubからダウンロードしてね。とのこと。
で、落としてきて拡張子.binをつけて/lib/firmware/rtl_btに移動するのは良いのですが、私が購入したのはRTL8761BUのようで、ダウンロードして拡張子を変えて/lib/firmware/rtl_btに放り込むだけではNGです。

この解決方法はこんなところに書いてありました。

forums.linuxmint.com

なんのことはない、シンボリックリンクを張れと。

一応一連のコマンドを載せます。

$ curl -O https://raw.githubusercontent.com/Realtek-OpenSource/android_hardware_realtek/rtk1395/bt/rtkbt/Firmware/BT/rtl8761b_config
$ curl -O https://raw.githubusercontent.com/Realtek-OpenSource/android_hardware_realtek/rtk1395/bt/rtkbt/Firmware/BT/rtl8761b_fw
$ mv rtl8761b_config rtl8761b_config.bin
$ mv rtl8761b_fw rtl8761b_fw.bin
$ sudo cp rtl8761b_config.bin /lib/firmware/rtl_bt
$ sudo cp rtl8761b_fw.bin /lib/firmware/rtl_bt
$ cd /lib/firmware/rtl_bt
$ sudo ln -s rtl8761b_config.bin rtl8761bu_config.bin
$ sudo ln -s rtl8761b_fw.bin rtl8761bu_fw.bin

これでhciconfig hci0 upをすると、めでたくbluetoothドングルが有効になります。

BOSE QuietComfort 20のバッテリー交換

愛用していたBose QuietComfort 20のバッテリーがへたってきたので交換。
BOSEに修理をお願いすると、15,000円で新品交換できるそうですが、もうごく稀にしか使わなくなったので、15,000円ももったいなく感じます。
そこで、バッテリーを自分で交換してみることにしました。
交換例は日本にはあまりありませんが、海外ではよくあるようです。

ポイントは、旧バッテリーに直付されている充電コントローラーボードのサブ基板を外し、新しいバッテリーに移植することです。
新しいバッテリーを半田付けしただけでは、充電時に赤点滅して充電されません。

まずはコントローラーボックスを開けます。
古いイヤホンで、コントローラーボックスを覆っていたゴムラバーは破れていたので、ゴムラバーはえいや!と、剥がしました。
剥がすのが嫌な人は、綺麗に真横をカットしましょう。
コントローラーボックスのMicroUSBの横あたりからカッターを入れて隙間を作り、あとはギターのピックなどを使って、バリバリと剥がしていきます。
LEDがついている面にメイン基盤などが来ますので、LEDが無い面を蓋と考えて開きます。

オープン

メイン基板とバッテリーの赤黒白のケーブル接点は、白いグルーガンのようなもので、断線&ショート対策が施されていますが、ハンダを外すために先が細い何か(精密ドライバーとか)で、メイン基板を削らないように慎重に剥がします。

グルーガンのようなものを剥離したバッテリー接点

半田コテを使って、バッテリーケーブルをメイン基板から剥がします。

外したバッテリー

さらに、バッテリー先端の黄色い透明な絶縁テープを剥がし、バッテリー線が充電コントローラーボードのサブ基板に巻かれているのでクルクルと剥がし、サブ基板とバッテリーを半田コテを使って剥がします。

バッテリー本体とサブ基板を分離

この古いバッテリーに付いている充電コントローラーボードのサブ基板が重要で、このサブ基板じゃないと充電してくれません。

新品のバッテリーにもサブ基板が付いていたので、同じように半田コテを使って剥がし、古いサブ基板を新しいバッテリーにハンダつけし、絶縁テープで巻きます。

そして、元の通り、サブ基板から出ている赤黒白の線をメイン基板にハンダつけして完了です。

新品のバッテリーを接続

バッテリーを両面テープで固定し、コントローラーボックスを閉めます。
剥がしたコントローラーボックスは元に戻らないので、2か所をテープで巻いてみました。

ケーブルハーネステープで巻きます

充電してみたところ、赤い点滅はせず、黄色く光って充電が始まり、数時間後に満充電の緑に変わりました。

充電完了

バッテリーはAmazonからも購入できますが、急がなければAliexpressからお安く購入できます。

ja.aliexpress.com

Macbook Air 11 MID2013 6,1をNVMeタイプのSSDに載せ替えてBootCamp

知人からMacbook Air 11 MID2013 6,1をもらったので、早速SSDをNVMeタイプのものに変えて高速化。
やったぜーって喜んでいましたが、BootCampでWindows10 21H2をインストールしようにも、BSODで進まず。
内容はCritical_Process_Died。打つ手なし。
調べると、NVMeに換装した場合は、Windows10 1507まで戻らないとインストールできないようです。

そこで、1507のインストールディスクを探します。
Windowsマシンを使い、インストールメディアを作成するフリーソフトRefusを使うことで、古いインストールメディアのISOをダウンロードできるようです。

rufus.ie

ここでのハマりポイントは、「選択」ボタンの右に▼が出てダウンロードが押せるようになるはずが、▼がでません。
最初に出てくる「自動更新の確認」を有効にすると、▼が出てダウンロードできるようになります。
ISOファイルをダウンロードして、何らかの方法(ネットワーク経由かUSBメモリ)でMacに持ってきて、BootCampで書き込みます。
MID 2013モデルはBootCampインストールにUSBメモリが必要なタイプです。
書き込むと、無事にWindows 10 1507が起動します。

早速、電源のパラメータを変更します。
[コントロールパネル]→[電源の管理]→[選択中の電源プラン]→[プラン設定の変更]を選択
(コントロールパネルの出し方がわからない時は、検索バーに「コントロールパネル」と入力)
[詳細な電源設定の変更]を押す
[PCI Express]→[リンク状態の電源管理]→(電源接続時 バッテリ時両方)[設定: オフ]
おそらく、Critical_Process_Diedの原因はPCIeへの電力供給の問題なのでは?と思っています。

さて、この後、Windows Updateを走らせても、完走しないそうです。(未検証)
そこで、Windows10 Media Creation Toolを使って、手動でアップデートすることにしました。
2022年9月現在のMedia Creation Toolは21H2へ強制的にアップデートされます。
が、再起動を数回繰り返し、ロールバックして1507にもどってしまいました。

エラーコードは0xC1900101 - 0x4001Eです。
この前半0xC1900101は、SECOND_BOOT失敗の汎用コードだそうで、具体的な理由は0x4001Eのほうです。
とはいえ、ネットでも原因が掴めず、Media Creation Toolで一足飛びにアップデートするのは諦めます。

BootCampされた1507上にもrefusをダウンロードし、Windows10 1511のインストールメディアISOをダウンロード。
ISOファイルはWinsows上から右クリックしてマウントするだけで、CD-ROMを挿入した時のように普通に読むことができます。
setup.exeを実行し、アップグレードを選択。
時間はかかりましたが、あっけなく1511にアップデート完了。

今度は1511上でMedia Creation Toolを実行すると、こちらも時間はかかりますが、あっさりと完了。
無事に2022年9月現在の最新版Windows 10 21H2にアップデートされました。

今回は画像がひとつもないです。
なぜならこれを、Macで書いてるから。

Gerbera 1.11.0のインポートスクリプトをいじる

Gerbera 1.4.0相当までは、旧mediatombのインポートスクリプトがそのまま使えました。

しかし、Gerbera 1.11.0になってから、インポート方法が変わり、インポートそのものは今まで通りaddCdsObject()を使ってインポートするのですが、第二引数であるコンテナツリー指定の形式が変わったようで、今までは単なる配列を指定するだけでしたが、これからはいったんaddContainerTree()を使って変数にコンテナツリーを代入してから、addCdsObject()の第二引数にその変数を指定する方法に変わりました。

面倒そうな話ですが、こうなった理由はとても合理的で、複数値を持ったメタデータに対応するためです。
一番わかりやすいのがジャンル(genre)
音楽のジャンルが多様化しているので、テクノポップなんかはGENRE=["POP","TECNO","TECNO_POP"]みたいな感じになると思うんですが、今までだとこの曲は
Audio - Genre - POP TECNO TECNO_POP
となるか
Audio - Genre - POP
となって、TECNO以下切り捨てみたいになっていたと思います。
(ジャンルを例に出しましたが、それほどジャンル検索をしたことがないです。)

それを、
Audio - Genre - POP
Audio - Genre - TECNO
Audio - Genre - TECNO_POP
どこからでもアクセスできるようにする、という設計思想になっているようです。
理にかなっていますが、配列の中に配列を入れられるよう、コンテナツリーをaddContainerTree()を使って代入することになったようです。

更に、これらのコンテナツリーは、どのようなオブジェクトなのか、オブジェクトタイプを予め定義する必要があるようです。

んで、本題。
うちの音楽ライブラリは4つのフォルダ(ディレクトリ)に分けて格納しています。
gentoolinux.hatenablog.com
今回、HDD交換を機に、ディレクトリがツリー分下がっています。つまり、
/home/files/mp3/Album
/home/files/mp3/Single
/home/files/mp3/SuperEurobeat
/home/files/mp3/Wife
という構造に変えました。
この第4階層をAudioコンテナの次に表示し、DLNA上では
Audio - Album
Audio - Aingle
Audio - SuperEurobeat
Ausio - Wife
と、一つ階層を増やし、膨大な音楽ファイルを選びやすくしたいと思います。

これを実現するため、今まではimport.jsを改変していましたが、Gerbera 1. 11.0からはcommon.jsを改変する必要があります。
import.jsもcommon.jsも、1.11.0デフォルトのスクリプトとし、common.jsを、この1.11.0のデフォルトのものから改変していきます。
まずは、音楽ファイルのフルパスを変数tmpfilelocに格納します。パスが格納されているのはorigオブジェクトのlocationプロパティです。なので、
var tmpfileloc = orig.location;
そこから、/で分割した値をfilelocの変数に配列として代入します。
var fileloc = tmpfileloc.split('/');
最終的にディレクトリの第4階層をコンテナ名の変数u_pathに代入します。
var u_path = fileloc[4];

これらを、できるだけ音楽ファイルが読み込まれるインポートファンクションの前段に記載します。
今回、音楽ファイルのインポートはaddAudio()とaddAudioStructured()がありますが、今回はaddAudio()を使います。

// doc-add-audio-begin
function addAudio(obj)
の直後くらいに記述しました

// doc-add-audio-begin
function addAudio(obj) {
    // Note the difference between obj.title and obj.metaData[M_TITLE] -
    // while object.title will originally be set to the file name,
    // obj.metaData[M_TITLE] will contain the parsed title - in this
    // particular example the ID3 title of an MP3.
    var title = obj.title;
    var tmpfileloc = orig.location;
    var fileloc = tmpfileloc.split('/');
    var u_path = fileloc[4];

続いて、今までにないコンテナとなるわけなので、コンテナ定義を追加します。
コンテナの定義は
const chain = {
という変数代入に続いて、1行づつ定義されています。例えば
audio: { title: 'Audio', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: },
という定義は、Audioという固定名称のコンテナです。オブジェクトの内容によって変更されることはありません。
対して、
composer: { title: composer, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_COMPOSER, metaData:
, res: obj.res, aux: obj.aux, refID: obj.id },
は、作曲家名のコンテナになるので、オブジェクト(=音楽ファイル)によって、コンテナ名は変動します。composer変数に代入されている作曲家名がコンテナ名になります。どのオブジェクト(=音楽ファイル)の作曲家名をコンテナにするのか? を、後半のres: obj.res, aux: obj.aux, refID: obj.idで、定義しています。(多分obj.idが重要なのだと思う・・・。)
なので、先ほどのu_pathに代入されている値をコンテナ名=titleにします。
u_path: { title: u_path, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },

このあたりのコンテナ定義をさらすと

    // The UPnP class argument to addCdsObject() is optional, if it is
    // not supplied the default UPnP class will be used. However, it
    // is suggested to correctly set UPnP classes of containers and
    // objects - this information may be used by some renderers to
    // identify the type of the container and present the content in a
    // different manner.

    // Remember, the server will sort all items by ID3 track if the
    // container class is set to UPNP_CLASS_CONTAINER_MUSIC_ALBUM.

    const chain = {
        audio: { title: 'Audio', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: [] },
        allAudio: { title: 'All Audio', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allArtists: { title: 'Artists', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allGenres: { title: 'Genres', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allAlbums: { title: 'Albums', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allYears: { title: 'Year', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allComposers: { title: 'Composers', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allSongs: { title: 'All Songs', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allFull: { title: 'All - full name', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        artist: { searchable: false, title: artist[0], objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ARTIST, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        album: { searchable: false, title: album, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ALBUM, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        genre: { title: genre, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_GENRE, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        year: { title: date, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        composer: { title: composer, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_COMPOSER, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        u_path: { title: u_path, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },

   };

定義したら、いよいよaddContainerTree()にu_pathを定義していきます。順番としてはAll Audioの次に、第4ディレクトリを表示してもらいます。
例として、All Audioにパスのコンテナを追加します。

var container = addContainerTree([chain.audio, chain.u_path, chain.allAudio]);
addCdsObject(obj, container);

このあたりのデータベースを代入数するスクリプトを表示します。

    const chain = {
        audio: { title: 'Audio', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: [] },
        allAudio: { title: 'All Audio', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allArtists: { title: 'Artists', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allGenres: { title: 'Genres', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allAlbums: { title: 'Albums', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allYears: { title: 'Year', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allComposers: { title: 'Composers', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allSongs: { title: 'All Songs', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allFull: { title: 'All - full name', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        artist: { searchable: false, title: artist[0], objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ARTIST, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        album: { searchable: false, title: album, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ALBUM, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        genre: { title: genre, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_GENRE, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        year: { title: date, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        composer: { title: composer, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_COMPOSER, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        u_path: { title: u_path, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },

   };

    chain.audio.metaData[M_CONTENT_CLASS] = [ UPNP_CLASS_AUDIO_ITEM ];
    chain.album.metaData[M_ARTIST] = artist;
    chain.album.metaData[M_ALBUMARTIST] = artist;
    chain.album.metaData[M_GENRE] = [ genre ];
    chain.album.metaData[M_DATE] = obj.metaData[M_DATE];
    chain.album.metaData[M_UPNP_DATE] = obj.metaData[M_UPNP_DATE];
    chain.album.metaData[M_ALBUM] = [ album ];
    chain.artist.metaData[M_ARTIST] = artist;
    chain.artist.metaData[M_ALBUMARTIST] = artist;
    chain.genre.metaData[M_GENRE] = [ genre ];
    chain.year.metaData[M_DATE] = [ date ];
    chain.year.metaData[M_UPNP_DATE] = [ date ];
    chain.composer.metaData[M_COMPOSER] = [ composer ];
//    chain.u_path.metaData[M_U_PATH] = [ u_path ];

    var container = addContainerTree([chain.audio, chain.u_path, chain.allAudio]);
    addCdsObject(obj, container);

    container = addContainerTree([chain.audio, chain.u_path, chain.allArtists, chain.artist, chain.allSongs]);
    addCdsObject(obj, container);

    var temp = '';
    if (artist_full) {
        temp = artist_full;
    }

    if (album_full) {
        temp = temp + ' - ' + album_full + ' - ';
    } else {
        temp = temp + ' - ';
    }

    obj.title = temp + title;
    container = addContainerTree([chain.audio, chain.u_path, chain.allFull]);
    addCdsObject(obj, container);

    const artCnt = artist.length;
    var i;
    for (i = 0; i < artCnt; i++) {
        chain.artist.title = artist[i];
        container = addContainerTree([chain.audio, chain.u_path, chain.allArtists, chain.artist, chain.allFull]);
        addCdsObject(obj, container);
    }

    obj.title = track + title;
    for (i = 0; i < artCnt; i++) {
        chain.artist.title = artist[i];
        chain.artist.searchable = true;
        container = addContainerTree([chain.audio, chain.u_path, chain.allArtists, chain.artist, chain.album]);
        addCdsObject(obj, container);
    }

    chain.album.searchable = true;
    container = addContainerTree([chain.audio, chain.u_path, chain.allAlbums, chain.album]);
    obj.title = track + title;
    addCdsObject(obj, container);

    chain.genre.searchable = true;
    if (obj.metaData[M_GENRE]) {
        for (var oneGenre in obj.metaData[M_GENRE]) {
            chain.genre.title = obj.metaData[M_GENRE][oneGenre];
            chain.genre.metaData[M_GENRE] = [ oneGenre ];
            container = addContainerTree([chain.audio, chain.u_path, chain.allGenres, chain.genre]);
            addCdsObject(obj, container);
        }
    }

    container = addContainerTree([chain.audio, chain.u_path, chain.allYears, chain.year]);
    addCdsObject(obj, container);

    container = addContainerTree([chain.audio, chain.u_path, chain.allComposers, chain.composer]);
    addCdsObject(obj, container);
}
// doc-add-audio-end

これで、Audioの下に、フォルダ第4階層が疑似的に表示されるようになります。