技術ノート・オーディオ&音声

自分で CODEC ドライバを書く——第 2 回:DAC 再生

ハードウェア側で DAC をアンプに接続すればそのまま音が出て——ドライバは不要。ではソフト側は、音を鳴らすために何をすればよいのか? Luo Zong(罗总)、Rockchip——「罗总开发笔记」WeChat から翻訳・注釈。

帰属&許諾
原著者:Luo Zong(罗总)、Rockchip ・ WeChat 公式アカウント 「罗总开发笔记」 にて 2026-08-25 公開。
元 URL:https://mp.weixin.qq.com/s/CqBo2NonYYGqC99ojL--4Q
許諾を得て転載、日本語に翻訳し Bestom が注釈を付加。本稿は連載「自分で CODEC ドライバを書く」の第 2 回——第 1 回(PDM マイク取得)はこちら

DAC をつなぐだけで音が出る——ソフト側は何をすればよいか?

前回の記事では PDM マイクを扱いました。チップは制御不要で、DTS を設定するだけで録音できます。今回は方向を反転——再生です。

多くの DAC チップも同じく「お任せ」です。制御インターフェース不要、I2S データ線をつなぎ電源を入れるだけで音を出力します。ハードウェアエンジニアは大歓迎——手間いらず。では Linux で、こうした DAC を dummy-codec と組み合わせた場合、音声再生のために何行のドライバコードを書く必要があるのでしょうか?

答えは前回と同じ:0 行。ただし今回は DAI の方向を反転します。

Bestom 注:本記事は当社のオーディオ SoM 取り組みと自然に呼応します——ソリューション → オーディオ&音声 およびスマートパネル/サウンドバー向け M08D モジュール(RK2108D + RK962)を参照。Luo Zong が説明するダミーコーデックのパターンは、実コーデックドライバが用意される前の初期起動や顧客リファレンスデザインのオーディオ経路を立ち上げる際にまさに当社が用いるものです。

要点

  1. 再生方向=DAI が .playback を宣言し、サウンドカードは /dev/snd/pcmC0D0p を公開する。
  2. PCM5102A のような DAC は内蔵 PLL を持ち、BCLK から自らクロックを生成するため MCLK は不要。真の制御ゼロ再生。
  3. dummy-codec.playback 能力宣言が本記事の対象。DTS は相変わらず同じ三點セット。 aplay が直接再生する。

1. まず方向:PLAYBACK と CAPTURE

第 1 回(PDM マイク)は キャプチャ 方向——データは SoC 流れ——サウンドカードは pcmC0D0c を公開。今回は反対側の 再生——データは SoC から出る

再生 (PLAYBACK) キャプチャ (CAPTURE) ┌──────────────┐ ┌──────────────┐ │ スピーカー / │ │ マイク │ │ ヘッドホン │ │ │ ▲ │ │ ▼ │ DAC │ │ ADC │ ▲ │ │ ▼ │ I2S データ │ │ PDM / I2S │ │ ▼ │ ▼ SoC オーディオ IF ← データ OUT SoC オーディオ IF ← データ IN

第 1 回の DAI 能力まとめを思い出してください:

struct snd_soc_dai_driver {
        .playback = { ... },    /* 宣言あり = このストリーム存在 = /dev/snd/pcmC0D0p */
        .capture  = { ... },    /* 宣言あり = このストリーム存在 = /dev/snd/pcmC0D0c */
};

2. 主役:制御まったく不要な DAC

TI の PCM5102A を例に——HiFi 界では見慣れた顔、多くのオーディオボードで使われます。そのハードウェア「要件リスト」を見てみましょう:

要件必要?
I2S データ線 (BCLK / LRCK / DATA)必要——3 本の線
MCLK マスタークロック不要——内蔵 PLL が BCLK からクロックを生成
I2C / SPI 制御インターフェース不要——そもそもそのようなピンがない
レジスタ設定不要——レジスタそのものが存在しない

電源を入れ、I2S データを与えるだけで音が出ます。PDM マイク以上に楽——「クロックを必ず与えねば」さえ考えなくて済みます(BCLK は I2S に付きものですから)。

カーネルにはこのチップ専用のドライバ sound/soc/codecs/pcm5102a.c が既にあり、全体で 57 行。核心は単一の能力宣言です:

static struct snd_soc_dai_driver pcm5102a_dai = {
  .name = "pcm5102a-hifi",
  .playback = {                              /* 注意:再生のみ! */
    .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 の枠を余るほど満たします。そもそもチップは制御不要なので、コーデックドライバは単なるプレースホルダーで、どのプレースホルダーでも構いません。

dummy_dai.playback 部分

第 1 回は dummy-codec.c.capture(60–70 行)を見ました。本記事は対称的にその再生宣言(ソース 49–59 行)を見ます:

struct snd_soc_dai_driver dummy_dai = {
  .name = "dummy_codec",
  .playback = {                           /* ← 再生能力:本記事の焦点 */
    .stream_name = "Dummy Playback",  /* ストリーム名 */
    .channels_min = 1,                /* 最小 1 チャンネル */
    .channels_max = 384,              /* 最大 384 チャンネル */
    .rates = SNDRV_PCM_RATE_CONTINUOUS,  /* 任意サンプルレート */
    .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),  /* ビット深さ S8 ~ S32 */
  },
  ...                                     /* .capture は第 1 回で解説 */
};

.capture と文字通り対称で、変わるのは stream_name"Dummy Playback" になるだけ。このブロックがあれば pcmC0D0p が生成され、aplay がそれを開けます。

PCM5102A の宣言と比べると、違いは「中身」だけ:

pcm5102a_daidummy_dai
再生2 チャンネル、8k–384k(データシート準拠)1–384 チャンネル、任意サンプルレート(実チップなし——過剰宣言は自由)
キャプチャなしあり(キャプチャシナリオ用に予約)

実チップのドライバの能力宣言はデータシートに厳密に従う必要があり、過剰宣言するとオープン時にアプリがクラッシュします。 dummy は実チップとつながらないため過剰宣言しても構わず、実際の制約は CPU DAI 側が強制します。


3. DTS 設定:やはり三點セット

第 1 回の PDM と同じレシピ、ただしインターフェースと方向が異なります:

/* ① Codec DAI:ダミーコーデックのプレースホルダー(第 1 回と同じドライバ) */
dummy_codec: dummy-codec {
  status = "okay";
  compatible = "rockchip,dummy-codec";
  #sound-dai-cells = <0>;
};

/* ② CPU DAI:I2S コントローラを有効化+ピンマルチプレックス */
&i2s0 {
  status = "okay";
  pinctrl-names = "default";
  pinctrl-0 = <&i2s0m0_sclk &i2s0m0_lrck
         &i2s0m0_sdi0 &i2s0m0_sdo0>;  /* BCLK / LRCK / データ線 */
};

/* ③ Machine:サウンドカードを組み立てる */
dummy_sound: dummy-sound {
  status = "okay";
  compatible = "rockchip,multicodecs-card";
  rockchip,card-name = "rockchip-dummy";    /* サウンドカード名 */
  rockchip,format = "i2s";                  /* データ形式 */
  rockchip,cpu = <&i2s0>;                   /* CPU DAI = I2S コントローラ */
  rockchip,codec = <&dummy_codec>;          /* Codec DAI = ダミー */
};

第 1 回から変わるのは 2 点:

第 1 回(PDM キャプチャ)本記事(DAC 再生)
CPU DAI&pdm&i2s0
データ方向キャプチャ(マイク → SoC)再生(SoC → DAC)

三點構造は同一——これがフレームワークの価値です。方向を変え、インターフェースを変えてもレシピは維持されます。


4. 再生検証

# 1. サウンドカード登録確認
cat /proc/asound/cards
#  0 [dummy         ]: rockchip-dummy

# 2. デバイスノード——今回は再生 (p)
ls /dev/snd/
#  controlC0  pcmC0D0p  timer

# 3. 再生
aplay -D hw:0,0 -c 2 -r 48000 -f S16_LE /tmp/sine.wav
# Android: tinyplay /tmp/sine.wav

# 4. ファイル参数が一致するか確認
ls -l /tmp/sine.wav
# 48000Hz × 2ch × 2 bytes、aplay の引数と一致

データの流れは第 1 回の正反対:

/tmp/sine.wav │ aplay が /dev/snd/pcmC0D0p へ書き込み ▼ メモリ上のリングバッファ │ DMA が移動 ▼ I2S コントローラ(rockchip-i2s、BCLK/LRCK + データを出力) │ 3 本の線 ▼ PCM5102A(内蔵 PLL が自己クロック → DAC → アナログ) │ ▼ スピーカー

5. まとめ:やはりドライバコード 0 行

両記事を通じ、1 つはキャプチャ、1 つは再生。第 1 回の .capture と本記事の .playback を扱い、dummy-codec の能力宣言はこれで完全に解説されました。残りのコード(startupprobe)には触れず、書いたのは DTS のみです。

しかしすべてのハードウェアがこんなに簡単とは限りません:

これらがまさに、ドライバが信号で「何かをする」箇所です。次回の記事が答えます:MCLK、SPK-CTL、RESET はコードのどこにあるのか? dummy-codec.c の残りの startupprobe がついに登場します。

Bestom 注(実ドライバへの移行): Luo Zong が説明する「楽な道」は、初期起動時にボードから最速で音を出す方法です。実コーデック(PCM51xx、ES72xx、RK3308 内蔵コーデックなど)に移行する際も同じ三點セット DTS は維持され、変わるのは MCLK ゲーティング、RESET、基板レベル GPIO を処理する必要があるコーデックドライバです。Bestom は、RK3308(専用オーディオ SoC)およびスマートパネル/サウンドバー向け M08D モジュール(RK2108D + RK962) 上で、dummy-codec から実稼働コーデックドライバへ橋渡しを支援できます。

シリーズ予告——「自分で CODEC ドライバを書く」

本記事は連載の 第 2 回 です。ソースコードは Rockchip BSP kernel 6.1(v6.1.162) に基づき、ハードウェアから高度な DAPM アーキテクチャまでを解説します。

参照ソースファイル:比較用 sound/soc/codecs/pcm5102a.c および dummy-codec.c