WASAPIでS/PDIFパススルー再生

# S/PDIFって何？

S/PDIF ... Sony Philips Digital InterFace
言葉の通り、Sony社とPhilips社で仕様を定めた音声をデジタルで送受信するI/Fです。
詳細はWikipediaに譲るとして、結構昔、自分が高校生くらいの時に何でもかんでもデジタル化だよ！！
ってそんな時代に登場した端子とケーブルだと勝手に記憶してます:sweat_smile:

結構古い技術なので、最近はHDMIケーブルに置き換わってしまっているかと思いますが、偶にデスクトップPCにS/PDIF端子が付いてる場合があります。

そこで、S/PDIF端子を使用して、Amazonで買ったS/PDIF->アナログコンバーターを経由して音を再生しようと言うのが今回のテーマです。

# 使用する材料

・S/PDIF端子が付いているPC。
・S/PDIFデジタル音声コンバーター
　例えば、[こんなの](https://www.amazon.co.jp/TKS-5-1ch%E5%AF%BE%E5%BF%9C%E3%82%A2%E3%83%8A%E3%83%AD%E3%82%B0%E3%82%AA%E3%83%BC%E3%83%87%E3%82%A3%E3%82%AA%E3%83%87%E3%82%B3%E3%83%BC%E3%83%80-Dolby%E3%83%87%E3%82%B8%E3%82%BF%E3%83%AB%E7%AD%89%E3%82%92%E3%82%A2%E3%83%8A%E3%83%AD%E3%82%B0%E5%A4%89%E6%8F%9B-PDIF%E3%83%87%E3%82%B8%E3%82%BF%E3%83%AB%E9%9F%B3%E5%A3%B0%E3%82%B3%E3%83%B3%E3%83%90%E3%83%BC%E3%82%BF%E3%83%BC-%E5%85%89%E3%82%B1%E3%83%BC%E3%83%96%E3%83%AB%E4%B8%A1%E5%AF%BE%E5%BF%9C/dp/B077QDQNXH/ref=sr_1_51?crid=XN3CRB04B76U&keywords=spdif+%E5%A4%89%E6%8F%9B&qid=1697455461&sprefix=spdif+%2Caps%2C166&sr=8-51 "こんなの")

この2つをS/PDIFのケーブルで接続。
ヘッドフォンはデジタル音声コンバーターに接続して音声を視聴します。

# とりあえず音を鳴らしてみる

Windowsのタスクバーの右下にあるスピーカーをクリック。
音声出力先で"Realtek Digital Output (Realtek(R) Audio)"を選択します。
そしたら、普通にWindows上の音が出力されていると思います。
えっ、WASAPIどうした？:sweat_smile:

# やっぱり本当の所、S/PDIFは少し古い技術

S/PDIFは、デジタル化の波と共に普及したと最初に書きましたが、その時代に流行ったもう1つは、ホームシアターです。
最近じゃ7.1chとかDollby Atomのような一体何個スピーカーつなぐのよって、映画館行った方が早いわって感じですけど、あの時代は5.1chで、スピーカー5個とサブウーファーとアンプがセットになった、ホームシアターセットみたいな製品がたくさん販売されていました。

そこで登場するのはもちろんS/PDIFです。DVDプレーヤーとアンプを接続するために、S/PDIFケーブルで接続してそれぞれのスピーカーの音を鳴らしていました。
しかし、ここで問題があります。S/PDIFは帯域がそれほど大きくなく、PCM音源5.1ch分を、そのまま送信する事ができません。そのため、音声データは主にAC3(DollbyDigital)形式で圧縮されており、アンプでデコードされたのち、それぞれのスピーカーへと音が送られていました。

ちなみにHDMIは帯域が大きいのでPCM音源5.1chを圧縮せずそのまま送信する事ができます。流石です。

# WASAPIでAC3圧縮されているデータをそのまま再生

先程出てきた、S/PDIFデジタル音声コンバーターは、実はAC3のデコードに対応しています。
そのため、PCでAC3圧縮をデコードせずに、そのまま送信することで、音を再生できます。

最初に出てきたPCの設定を変えるだけで音が鳴ったのは何故か？と言う事ですが、
あれは2chステレオなので、S/PDIFでも帯域が足りていて、PCMデータを送信して音を鳴らしていたのです。

ここからが本題。
WASAPIでどうやって、AC3データのパススルー再生を実現するかのポイントについて記述していきます。

## WASAPIの初期化

WASAPIの設定を行う時に、圧縮されているデータをどうやった表現するかという点がポイントなのですが、それは以下のように表現します。

・S/PDIFのステレオ2chを使用する！
・でもそこで送信するデータはDollbyDigital(AC3)で圧縮されてるよ。
・実は5.1chです。（でもこの設定は要らないのかも）

Cのコードにすると以下のような感じです。

```
	WAVEFORMATEXTENSIBLE_IEC61937 format;
	memset(&format, 0x00, sizeof(format));
	
	format.FormatExt.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
	format.FormatExt.Format.nSamplesPerSec = 48000;
	format.FormatExt.Format.wBitsPerSample = 16; 
	format.FormatExt.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE_IEC61937) - sizeof(WAVEFORMATEX);
	format.FormatExt.Format.nChannels = 2;
	format.FormatExt.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
	format.FormatExt.Format.nBlockAlign = format.FormatExt.Format.nChannels * format.FormatExt.Format.wBitsPerSample / 8;
	format.FormatExt.Format.nAvgBytesPerSec = format.FormatExt.Format.nSamplesPerSec * format.FormatExt.Format.nBlockAlign;
	format.FormatExt.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL;
	format.FormatExt.Samples.wValidBitsPerSample = 16;
	format.dwEncodedSamplesPerSec = 48000;
	format.dwEncodedChannelCount = 6;
	format.dwAverageBytesPerSec = 0;

	int nFrames = 960;      // この値が小さすぎると処理が追いつかずノイズに繋がりやすい
	REFERENCE_TIME hnsRequestedDuration = (REFERENCE_TIME)((double)REFTIMES_PER_SEC / format.FormatExt.Format.nSamplesPerSec * nFrames + 0.5);

	hr = m_audio_client->Initialize(
		AUDCLNT_SHAREMODE_EXCLUSIVE,
		AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
		hnsRequestedDuration,
		hnsRequestedDuration,
		&format.FormatExt.Format,
		NULL
	);
```

## WASAPIでAC3データ再生

基本的には、PCMデータを再生する方法と同一です。
ポイントとしては以下の通りです。

・送信可能なデータサイズは、フレーム数 x バイトアライン
・AC3のRAWデータではなく、S/PDIFのヘッダでラッピングされたAC3データを送信する。

※細かなエラー処理は省略してます。

~~~
    // 最初のデータを設定しておく
	pRenderClient->GetBuffer(bufferFrameCount, &pData);
	int ret = fread(pData, 1, bufferFrameCount * format.FormatExt.Format.nBlockAlign, fp);
	pRenderClient->ReleaseBuffer(ret / format.FormatExt.Format.nBlockAlign, 0);

    // 再生開始
	m_audio_client->Start();

	unsigned char* data = new unsigned char[bufferFrameCount * format.FormatExt.Format.nBlockAlign];

	while (true)
	{
        // 次に送るデータを準備しておく
        memset(data, 0x00, bufferFrameCount * format.FormatExt.Format.nBlockAlign);
		int ret = fread(data, 1, bufferFrameCount * format.FormatExt.Format.nBlockAlign, fp);

        // WASAPIからの次データ送信許可を待つ
        DWORD retval = WaitForSingleObject(hEvent, INFINITE);
		if (retval != WAIT_OBJECT_0)
		{
			break;
		}

        // データを送信する
		pRenderClient->GetBuffer(bufferFrameCount, &pData);
		memcpy(pData, data, bufferFrameCount * format.FormatExt.Format.nBlockAlign);
		hr = pRenderClient->ReleaseBuffer(ret / format.FormatExt.Format.nBlockAlign, 0);

		if (ret < bufferFrameCount * format.FormatExt.Format.nBlockAlign)
		{
            // 終了
			break;
		}
	}

    // 再生停止
	m_audio_client->Stop();
~~~

## S/PDIFでラップされたAC3データ作成方法

今回は、ffmpegを使用して事前にS/PDIF(AC3)データを準備しておきました。

~~~
% ffmpeg -i 変換元のAC3のRAWデータファイル名.ac3   変換後のS/PDFI(AC3)データファイル名.spdif
~~~

実際は、このような処理をリアルタイムで実行する事になると思いますが、それもffmpeg等を利用することで実現できると思います。

## 結局、音質は？

デジタルです。悪い筈がありません。（いやPCMだってデジタルだけど？）
ノイズが乗るとすれば、それはアナログに変換した後、ヘッドフォンのコネクタの接続のあたりに、ノイズが待ち構えていただけのことです。（ノイズは、この世界の何処にでも潜んでいます。）

PCが最大のノイズ発生源であり、PCから物理的に距離をとって、遠くからアナログの音を取り出すことで、ノイズは抑制できるかもです。