またニッチ
Raspberry Piは、GPIOに外付けDACを接続して高音質化という楽しみ方がある。この場合、DACのドライバーとセットで使用することになる。
もしDAC無しでI2S信号を得たい場合、もしくはDACの制御は別に行う場合、合うドライバーが無い。合わないドライバーを使うと、DACの制御に失敗してエラーになったりしないだろうか。
外付けDAC無しで、Raspberry PiをI2S信号発生器として使いつつ、BCLKなどのパラメーターを操作したい場合、どうすれば…
試したことを記載したが、専門外なので、間違っていたらすいません。
以下、Raspberry Pi Foundation-provided kernelの4.9.118で確認。
ALSAドライバーの構造
本家で、CPU, dai, codecなどの構成要素を知る。
こちらは、本家より図が詳細で、構造体の説明もある。
Raspberry Piでの実装は、こちらが詳しい。
作戦
上記の構造解説の理解に基づき…
- CPU(SoC) DAIは、Raspberry Pi用のもの(
sound/soc/bcm/bcm2835-i2s.c
)をそのまま使う - DAI linkは、Raspberry Piのカーネルの外付けDAC用の中で、単純なものを流用する
- CODEC DAIは、Raspberry Piのカーネルの外付けDAC用の中で、I2Cなどによるcodecの制御を伴わないものを流用する
DAI link詳細
sound/soc/bcm/
の中を漁ると、rpi-simple-soundcard.c
という気になるファイル名のファイルがある。シンプルですか、そして比較的新しい。
こちらによると、同じcodecが複数のHATで使われて重複コードになったりしたので、整理したようだ。
これを改造しよう。
CODEC DAI詳細
sound/soc/codecs/
以下を眺めると、I2Cなどでcodecの制御を伴わないものは色々あるようだ。
この中に、bt-sco.c
というものがある。実は今回、受け側はBluetoothチップを想定しているので、都合が良さそうだ。Kconfig
の説明でもDummy BT SCO codec driverとあり、codecが無くても動きそうな予感。
これをそのまま使ってみよう。
Device Tree
Raspbrerry Pi用外付けDACは、使う時に/boot/config.txt
にdtoverlay=
でdtboファイルを追記するものが多い。dtboは、Device Tree Blob Overlayのこと…だと思う。
Device Tree Overlayはこちらに概要がある。
rpi-simple-soundcard.c
の中で、bt-scoを使うものは無いので、この組み合わせをDevice Tree Overlayに登録するのが流儀か。
Device Tree詳細
現状Raspberry Piでは、Pi 4でも、ビルド時のKERNELに設定する値はv7lで、arm64ではなくarmで動かしている模様。
arch/arm/boot/dts/overlays/hifiberry-dac-overlay.dts
などを見ると、CODEC DAIとDAI LINKの組み合わせが書いてあるように見える。
これを真似しよう。
実装
Device Tree
// Definitions for RPi Null DAC
/dts-v1/;
/plugin/;
/ {
compatible = "brcm,bcm2835";
fragment@0 {
target = <&i2s>;
__overlay__ {
status = "okay";
};
};
fragment@1 {
target-path = "/";
__overlay__ {
bt-sco {
#sound-dai-cells = <0>;
compatible = "linux,bt-sco";
status = "okay";
};
};
};
fragment@2 {
target = <&sound>;
__overlay__ {
compatible = "rpi,rpi-null-dac";
i2s-controller = <&i2s>;
status = "okay";
};
};
};
CODEC DAIとDAI LINKの名前を、compatible
で指定。
@@ -137,6 +137,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
rpi-dac.dtbo \
rpi-display.dtbo \
rpi-ft5406.dtbo \
+ rpi-null-dac.dtbo \
rpi-poe.dtbo \
rpi-proto.dtbo \
rpi-sense.dtbo \
DAI link
@@ -227,6 +227,28 @@ static struct snd_rpi_simple_drvdata drvdata_merus_amp = {
.fixed_bclk_ratio = 64,
};
+static struct snd_soc_dai_link snd_rpi_null_dac_dai[] = {
+ {
+ .name = "RPi Null DAC",
+ .stream_name = "RPi Null DAC",
+ .codec_dai_name = "bt-sco-pcm-wb",
+ .codec_name = "bt-sco",
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_GATED |
+ SND_SOC_DAIFMT_CBS_CFS,
+ // .dpcm_playback = 1,
+ // .dpcm_capture = 1,
+ },
+};
+
+static struct snd_rpi_simple_drvdata drvdata_rpi_null_dac = {
+ .card_name = "snd_rpi_null_dac",
+ .dai = snd_rpi_null_dac_dai,
+ .fixed_bclk_ratio = 128,
+};
+
+
static const struct of_device_id snd_rpi_simple_of_match[] = {
{ .compatible = "adi,adau1977-adc",
.data = (void *) &drvdata_adau1977 },
@@ -241,6 +263,8 @@ static const struct of_device_id snd_rpi_simple_of_match[] = {
{ .compatible = "rpi,rpi-dac", &drvdata_rpi_dac},
{ .compatible = "merus,merus-amp",
.data = (void *) &drvdata_merus_amp },
+ { .compatible = "rpi,rpi-null-dac",
+ .data = (void *) &drvdata_rpi_null_dac },
{},
};
DTOのcompatible
で指定した名前をsnd_rpi_simple_of_match[]
に追記。
そこから、drvdata_rpi_null_dac
、snd_rpi_null_dac_dai
と情報をリンクさせる構造か。
snd_rpi_simple_drvdata構造体
.fixed_bclk_ratio
を設定すると、snd_rpi_simple_init()
にて、snd_soc_dai_set_bclk_ratio()
でBCLKを設定してくれるようだ。ratioなので、単位はFS?
試しに未設定状態でBCLKを測定したら500KHzで、これを2MHzにしたかったので、2048/16=128にしてみたら、2MHzになったっぽい。デフォルトは32FSということ? 参考
snd_soc_dai_link構造体
CODEC DAIの指定と、どちらがクロックのマスターになるか、などの設定ができる。
CODEC DAIの指定は、.codec_dai_name
に名前を書く。
他の例では、snd_soc_dai_driver
構造体の.name
と一致させているようなので、sound/soc/codecs/bt-sco.c
にあるbt-sco-pcm-wb
にしてみる。wbはWide Bandで、16KHzも行けそう。
クロックの設定は、include/sound/soc-dai.h
によると、This is wrt the codec, the inverse is true for the interface i.e. if the codec is clk and FRM master then the interface is clk and frame slave. とのことで、codecから見ての設定値らしい。UARTのTX/RXもそうだが、どちらから見て、が重要。
SND_SOC_DAIFMT_CBS_CFS
としておけば、codecから見てSlave、CPU(SoC)から見てMaster、になるはず。
以下蛇足。
SND_SOC_DAIFMT_GATED
は、多分不要。最初DTOの記述が間違っていてBCLKが出ない時に試行錯誤で入れた名残り。*DAI bit clocks can be be gated (disabled) when the DAI is not sending or receiving PCM data in a frame. This can be used to save power.*とのこと。
Bluetoothの場合、I2SのBCLKは、SCO通信中のみ出ることが多い気がするので、このままでいいか。
.dpcm_playback
と.dpcm_capture
も試行錯誤の名残りで、同じbt-scoを使ったsound/soc/mediatek/mt2701/mt2701-cs42448.c
に記述があったため、入れてみた。
結果的には、この設定が無くても、入出力共に動作。bt-sco.c
に.playback
と.capture
があるからか。
CODEC DAI
@@ -1506,3 +1506,4 @@ CONFIG_FUNCTION_PROFILER=y
CONFIG_KGDB=y
CONFIG_KGDB_KDB=y
CONFIG_KDB_KEYBOARD=y
+CONFIG_SND_SOC_BT_SCO=m
bt-scoをそのまま使いたいが、Raspberry Piでは無効になっているので、arch/arm/configs/bcm2711_defconfig
(Raspberry Pi 4の場合)で有効化。
ビルド
ビルドは、本家手順そのまま。今回はクロスでビルド。
この手順は、
-
make modules_install
による.ko
のインストール -
cp arch/arm/boot/dts/overlays/*.dtb* mnt/fat32/overlays/
によるDTOのインストール
を含んでいる。素晴らしい。
コンテンツ
適当にフリー素材のwavを持ってきて、Audacityで、CODEC DAIであるbt-sco-pcm-wbに合わせて、モノラル/16kHz/16bitに変換。
VLCのメディア情報で、再生状態にして上記を確認。codecはPCM S16 LEとある。aplay
の振る舞いまでは確認していないが、bt-sco-pcm-wbのSNDRV_PCM_FMTBIT_S16_LE
と一致していれば、問題になることはないだろう。
実行
config.txt
実行前に、/boot/config.txt
の[all]
の下に、dtoverlay=rpi-null-dac
でDTOを追記。
dmesg
[ 3.041860] snd-rpi-simple soc:sound: bt-sco-pcm-wb <-> fe203000.i2s mapping ok
aplay -L
default:CARD=sndrpinulldac
snd_rpi_null_dac, RPi Null DAC bt-sco-pcm-wb-0
Default Audio Device
aplay
aplay -D default:sndrpinulldac mono-16KHz-16bit.wav
起動時にデバイスの優先順位?が変わることがあるようなので、-D
でデバイスを指定。
…と、ここで問題発生。再生失敗。
aplay: main:828: audio open error: Invalid argument
dmesg
を見ると、
[ 24.194217] ASoC: bt-sco-pcm-wb <-> fe203000.i2s No matching channels
チャンネルがおかしいらしい。
コンテンツはAudacityモノラル化しており、bt-sco.c
では、channels_min
もchannels_max
も1
になっているので、問題なさそうだが…。
試しにchannels_max
を2
にしてみる。
@@ -48,14 +48,14 @@ static struct snd_soc_dai_driver bt_sco_dai[] = {
.playback = {
.stream_name = "Playback",
.channels_min = 1,
- .channels_max = 1,
+ .channels_max = 2,
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
- .channels_max = 1,
+ .channels_max = 2,
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
結果
わーい!
おまけ
GPIO20(IN)とGPIO21(OUT)を直結して、再生中に
arecord -D default:sndrpinulldac rec.wav
で録音したら、録音もできました。
今後
- ふつーに48KHzステレオとかハイレゾとか
- モノラル片側1ch出力
- ユーザー空間からの動的設定変更
- てきとーなDACを指定すればふつーに動いたんじゃ疑惑