硬體端把 DAC 接上功放就能出聲——不需要驅動程式。那麼軟體端,要發出聲音需要什麼?翻譯並註解自 羅總(Luo Zong / 罗总),Rockchip——「罗总开发笔记」WeChat。
上一篇介紹了 PDM 麥克風——晶片無需控制,設定好 DTS 即可錄音。這次我們反轉方向——播放。
許多 DAC 晶片同樣「免干預」:不需要控制介面,接上 I2S 資料線與電源就能輸出聲音。硬體工程師喜愛這類元件——零力氣。那麼在 Linux 上,若將這類 DAC 搭配 dummy-codec,要寫多少行程式碼才能播放音訊?
答案與上次相同:零。但這次我們反轉 DAI 的方向。
.playback,音效卡曝露 /dev/snd/pcmC0D0p。dummy-codec 中的 .playback 能力宣告,正是本文涵蓋的內容。DTS 仍是同樣的三件式組合。aplay 直接播放。第 1 篇(PDM 麥克風)是擷取(capture)方向——資料流向 SoC 內部——音效卡曝露 pcmC0D0c。這次我們翻到另一側——播放(playback)——資料流向 SoC 外部:
回顧第 1 篇的 DAI 能力摘要:
struct snd_soc_dai_driver {
.playback = { ... }, /* declared = this stream exists = /dev/snd/pcmC0D0p */
.capture = { ... }, /* declared = this stream exists = /dev/snd/pcmC0D0c */
};
以 TI 的 PCM5102A 為例——在 HiFi 領域是熟面孔,用於許多音訊板。看看它的硬體「需求清單」:
| Requirement | Needed? |
|---|---|
| I2S data lines (BCLK / LRCK / DATA) | Yes — 3 wires |
| MCLK master clock | No — internal PLL derives the clock from BCLK |
| I2C / SPI control interface | No — the chip has no such pins |
| Register configuration | No — there are no registers |
上電、餵入 I2S 資料,它就發聲。甚至比 PDM 麥克風更免干預——你甚至不必想「我必須給它時脈」(BCLK 隨 I2S 而來)。
核心已內建此晶片的專用驅動——sound/soc/codecs/pcm5102a.c,總計 57 行。核心是一個能力宣告:
static struct snd_soc_dai_driver pcm5102a_dai = {
.name = "pcm5102a-hifi",
.playback = { /* NOTE: playback only! */
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_384000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE
},
};
static int pcm5102a_probe(struct platform_device *pdev)
{
return devm_snd_soc_register_component(&pdev->dev, &soc_component_dev_pcm5102a,
&pcm5102a_dai, 1);
}
就這樣:宣告「我能播 2 聲道」,然後註冊。但你甚至不需要它——dummy-codec 同時宣告 .playback 與 .capture,因此它綽綽有餘地涵蓋 PCM5102A 的插槽。反正晶片不需要控制,所以 Codec 驅動只是個佔位符——任何佔位符都行。
第 1 篇看了 dummy-codec.c 的 .capture(60–70 行)。本文對稱地看它的播放宣告(原始碼 49–59 行):
struct snd_soc_dai_driver dummy_dai = {
.name = "dummy_codec",
.playback = { /* ← Playback capability: this article's focus */
.stream_name = "Dummy Playback", /* stream name */
.channels_min = 1, /* min 1 channel */
.channels_max = 384, /* max 384 channels */
.rates = SNDRV_PCM_RATE_CONTINUOUS, /* any sample rate */
.formats = (SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S20_3LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE), /* bit depth S8 ~ S32 */
},
... /* .capture was covered in Part 1 */
};
與 .capture 逐字對稱,僅 stream_name 改為 "Dummy Playback"。有了這段,pcmC0D0p 即被建立,aplay 可以開啟它。
與 PCM5102A 的宣告相比,唯一的差別在「內容」:
pcm5102a_dai | dummy_dai | |
|---|---|---|
| playback | 2 channels, 8k–384k (per the datasheet) | 1–384 channels, any sample rate (no real chip — overshoot is free) |
| capture | none | yes (reserved for capture scenarios) |
真實晶片驅動的能力宣告必須貼合 datasheet——宣告過頭會導致應用程式在開啟時崩潰。dummy 沒有連接真實晶片,因此過度宣告無妨;真正的限制由 CPU DAI 端強制執行。
與第 1 篇的 PDM 同樣配方,只是介面與方向不同:
/* ① Codec DAI: dummy-codec placeholder (same driver as Part 1) */
dummy_codec: dummy-codec {
status = "okay";
compatible = "rockchip,dummy-codec";
#sound-dai-cells = <0>;
};
/* ② CPU DAI: enable the I2S controller + pin mux */
&i2s0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2s0m0_sclk &i2s0m0_lrck
&i2s0m0_sdi0 &i2s0m0_sdo0>; /* BCLK / LRCK / data lines */
};
/* ③ Machine: assemble the sound card */
dummy_sound: dummy-sound {
status = "okay";
compatible = "rockchip,multicodecs-card";
rockchip,card-name = "rockchip-dummy"; /* sound-card name */
rockchip,format = "i2s"; /* data format */
rockchip,cpu = <&i2s0>; /* CPU DAI = I2S controller */
rockchip,codec = <&dummy_codec>; /* Codec DAI = dummy */
};
與第 1 篇相比,兩處改變:
| 第 1 篇(PDM 擷取) | 本文(DAC 播放) | |
|---|---|---|
| CPU DAI | &pdm | &i2s0 |
| Data direction | capture (mic → SoC) | playback (SoC → DAC) |
三件式結構完全相同——這正是框架的價值。換方向、換介面,配方不變。
# 1. Sound card registered
cat /proc/asound/cards
# 0 [dummy ]: rockchip-dummy
# 2. Device node — this time it's the playback (p)
ls /dev/snd/
# controlC0 pcmC0D0p timer
# 3. Play
aplay -D hw:0,0 -c 2 -r 48000 -f S16_LE /tmp/sine.wav
# Android: tinyplay /tmp/sine.wav
# 4. Check the file parameters match
ls -l /tmp/sine.wav
# 48000Hz × 2ch × 2 bytes, matches aplay's arguments
資料流與第 1 篇正好相反:
兩篇文章涵蓋一擷取一播放,我們已講完第 1 篇的 .capture 與本文的 .playback——dummy-codec 的能力宣告至此完整說明。其餘程式碼(startup / probe)尚未觸及,你只寫了 DTS。
但並非所有硬體都這麼簡單:
這些正是驅動「用訊號做事」的地方。下一篇文章回答:MCLK、SPK-CTL、RESET 存在於程式碼的哪裡?dummy-codec.c 剩餘的 startup 與 probe 終於登場。
dummy-codec 過渡到 RK3308(專用音訊 SoC)與 M08D 模組(RK2108D + RK962) 上的量產 Codec 驅動,用於智慧面板與聲霸。
本文為系列 第 2 篇。原始碼基於 Rockchip BSP kernel 6.1(v6.1.162)——帶你從硬體一路上探到進階 DAPM 架構。
startup 開啟 MCLK、probe 重置,Machine 處理板級 GPIO。參考原始檔:sound/soc/codecs/pcm5102a.c(比較)與 dummy-codec.c。