Raspberry Pi ZeroのI2S DACとポップノイズの関係 その2 まっとうにMUTE制御

概要

  • NosPiDAC Zeroなどで音楽を楽しんでいるときに気になるポップノイズについて調べたこと・やったことをメモ。
  • その2はCodecドライバを修正し、外部MUTE制御との連動を試してみます。
  • 外部MUTE制御用のサンプルコード付。
  • MUTE機能つきのHPA IC(MAX9722とか)やオペアンプ(OPA1622とか)などで利用できます。

MUTE制御について

  • ALSAではCodecに対してポップ抑制用にMUTE Callbackを用意している。
  • MUTE関連のCallbackはdigital_muteとmute_streamの2種類
    • digital_muteはstream関係なくCallbackされる。mute_stream callbackを登録すると呼ばれない
    • mute_streamはstream番号指定でのMUTE制御。録再別とかチャネル別でMUTEできるとかかな?
    • とりあえずはdigital_muteを使えば良さそう。
  • このMute Callbackを利用してGPIOからHPAのMUTEを制御してポップノイズを抑制できるかを確認してみる。

仕込み

  • 改造ベースはNosPiDac Zero 1543 HPAを使用
  • HPA ICのMAX9722のShutdownピンにGPIO出力を接続
  • NosPiDACのMAX9722 ShutdownピンはVCCに接続されているため、あらかじめカッターなどでパターンをカットする必要があり
  • PCM5102Aドライバを改造してdigital_mute CallbackでGPIO出力を制御するように改造
    f:id:tkztkztkz:20180829213719j:plain
    NosPiDAC Zero 1543 HPAのMAX9722 SDピンに配線した例

結果

  • 曲間でプチる
    • I2Sドライバはそのままで、CodecドライバのMUTE制御だけでポップノイズを防ぐことはできない。
    • MUTE CallbackでMute解除通知後の処理に何かまずいのが居そう。
  • Mute Callbackの様子
    • 再生開始・停止でMute Callbackが飛んでくる
      • prepare時にMute CallbackでMute解除を通知
      • close(shutdownの前)時にMute CallbackでMuteを通知
    • 曲間ではMute解除通知しかこない(Mute通知は来ない)
      • trigger start/stop時にはMute Callbackは呼ばれない
      • 曲間では trigger(stop) - > prepare -> trigger(start)の順で動くため、prepare実行時のMute解除のみ通知される模様
  • 今度はCallシーケンスを洗って、どこでプチってるか確認します。

  • なお、曲再生中にMAX9722のShutdownピンを直接制御してみたところ、ゼロクロス付近ではノイズなし。ゼロクロスから離れていると少々プチノイズが出る感じとなりました。ゼロクロス検出無しのMute回路なので、ここはまあスペック通りかな。

外部MUTE制御サンプル

  • 今回テストに使用した、PCM5102Aドライバを改造してMUTE信号をGPIO出力するサンプルを乗っけておきます。
  • GPIO4にMUTE信号を出力します。Active Low出力なので、LでMUTE、Hでミュート解除になります。
    • init時にLを設定し、ALSAからのMUTE解除でHを出力します。
    • Active Highにする場合は、GPIOF_OUT_INIT_LOWをGPIOF_OUT_INIT_HIGHに変更し、gpio_set_valueの出力論理を反転させてください。
  • prepare時(MUTE解除)とclose時(MUTE)にMUTE制御が飛んできます。
diff --git a/sound/soc/codecs/pcm5102a.c b/sound/soc/codecs/pcm5102a.c
index 8ba322a00363..960d7b83e6ea
--- a/sound/soc/codecs/pcm5102a.c
+++ b/sound/soc/codecs/pcm5102a.c
@@ -14,11 +14,42 @@
  * General Public License for more details.
  */

 #include <linux/init.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <linux/gpio.h>

 #include <sound/soc.h>
+static int null_set_dai_mute(struct snd_soc_dai *dai, int mute)
+{
+       struct snd_soc_codec *codec = dai->codec;
+
+       pr_debug("%s mute = %d\n", __FUNCTION__, mute);
+
+       if (mute) {
+               gpio_set_value(4, 0);
+       } else {
+               gpio_set_value(4, 1);
+       }
+
+       return 0;
+}
+
+static struct snd_soc_dai_ops null_dai_ops = {
+       .digital_mute   = null_set_dai_mute,
+};

 static struct snd_soc_dai_driver pcm5102a_dai = {
        .name = "pcm5102a-hifi",
@@ -26,16 +57,20 @@ static struct snd_soc_dai_driver pcm5102a_dai = {
                .channels_min = 2,
                .channels_max = 2,
                .rates = SNDRV_PCM_RATE_8000_192000,
                .formats = SNDRV_PCM_FMTBIT_S16_LE |
                           SNDRV_PCM_FMTBIT_S24_LE |
                           SNDRV_PCM_FMTBIT_S32_LE
        },
+       .ops = &null_dai_ops,
 };

 static struct snd_soc_codec_driver soc_codec_dev_pcm5102a;

 static int pcm5102a_probe(struct platform_device *pdev)
 {
+       gpio_request_one(4, GPIOF_OUT_INIT_LOW, "CODEC MUTE");
+
        return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_pcm5102a,
                        &pcm5102a_dai, 1);
 }
  • サンプルドライバではGPIO決めうちにしているけど、device treeで外部から制御させるように作ると良いかもしれない。