kram.nyamikan.net - vadplus

ステレオミキサーつきの仮想サウンドデバイスドライバをつくる

 ステレオミキサーつきの仮想サウンドデバイスドライバ "vadplus"の完成までをまとめました。

システム要件

おおまかな流れ、注意点

Windows Driver Kit (WDK) をダウンロードする

  1. WDK および開発者向けツール: 概要(Microsoft)にアクセスし、Windows Driver Kitをダウンロード。
  2. 適当にインストール。

まずは、 MSVAD (Microsoft Virtual Audio Device) を動かしてみる

 「再生結果をファイルに保存する」という仮想サウンドデバイスドライバ MSVAD が既に用意されています。

 まずはビルド。

  1. スタートメニュー → Windows Driver Kits → WDK 7600.16385.0 → Build Environments → (OS) → x86 Checked Build Environment とたどる。
  2. ビルド用のコマンドプロンプトが立ち上がります。
  3. cdコマンドで WinDDK\7600.16385.0\src\audio\msvad に移動。
  4. やみくもに build とだけ打ってビルド。

 出来上がってることを確認して、インストール。

  1. 余計なソフトウェアを閉じておく。何故かわからんけどOpera立ち上げながらだとインストールで固まりかけた。
  2. ハードウェアの追加ウィザードで、WinDDK\7600.16385.0\src\audio\msvad\msvad.inf を選択。
  3. 適当に Microsoft Virtual Audio Device (Simple) あたりを選んでインストール。

 インストールできましたか? 動作を確かめてみましょう。

  1. MSVADにむかってサウンドを再生。
  2. C:\直下に STREAM_*.wav が出来上がってるので、聞いてみる。

 頻繁にドライバを差し替えるので、差し替えもマスターしておきましょう。

  1. ブラウザ、プレイヤー、ほかサウンドに関係するものすべて閉じておく。
  2. デバイス マネージャからデバイスを削除。
  3. 運がよければ再起動を求められないので、そのまままたインストール操作ができる。

 再起動を求められたらおとなしく再起動しましょう。

ステレオミキサーがつく差分

multstr\multi.h

28〜33行目、チャンネル・量子化ビット・サンプリングレートを固定する。

#define MIN_CHANNELS                2       // Min Channels.
#define MAX_CHANNELS_PCM            2       // Max Channels.
#define MIN_BITS_PER_SAMPLE_PCM     16      // Min Bits Per Sample
#define MAX_BITS_PER_SAMPLE_PCM     16      // Max Bits Per Sample
#define MIN_SAMPLE_RATE             48000   // Min Sample Rate
#define MAX_SAMPLE_RATE             48000   // Max Sample Rate
basewave.h

18行目、コメントアウト。ファイル保存のためのヘッダなんていらない。

// #include "savedata.h"

76行目あたり、CMiniportWaveCyclicMSVADクラスにpublicでインスタンス変数を追加。再生データを録音に回すためのリングバッファです。

public:
        PVOID                                           m_pStereoMix;      // バッファのポインタ
        ULONG                                           m_ulStereoMixSize; // バッファサイズ
        ULONG                                           m_ulStereoMixWPos; // バッファ書き込み位置
        ULONG                                           m_ulStereoMixRPos; // バッファ読み込み位置

143行目(追加後は148行目)あたり、コメントアウト。CSaveDataクラスの参照はもはや不要。
 位置: CMiniportWaveCyclicStreamMSVADクラス - pprotectedインスタンス変数 - m_SaveData

    // CSaveData                   m_SaveData;
source.inc

32行目、ラインごと削除。savedata.cppのソース指定は不要。

common.cpp

20行目、26〜27行目、コメントアウト。

// #include "savedata.h"
// PSAVEWORKER_PARAM               CSaveData::m_pWorkItems = NULL;
// PDEVICE_OBJECT                  CSaveData::m_pDeviceObject = NULL;

215行目、コメントアウト。
 位置: CAdapterCommonクラス - デストラクタ(~CAdapterCommon)

    // CSaveData::DestroyWorkItems();

299行目、コメントアウト。
 位置: CAdapterCommonクラス - Init関数

    // CSaveData::SetDeviceObject(DeviceObject);   //device object is needed by CSaveData
basedma.cpp

170行目あたり。"DestinationとByteCountを参照しない"定義を削除して、処理を盛大に追加。
 場所: CMiniportWaveCyclicStreamMSVADクラス - CopyFrom関数

        UNREFERENCED_PARAMETER(Source);

        RtlFillMemory((PBYTE)Destination, ByteCount, 0);

        /*
                書き込み位置の
                        前4KBにいるとき:        何も処理しない(落とす)
                        後4KBにいるとき:        半分進める
        */

        // 書き込み位置が4→Maxのとき → 通常 (WPos - 4K < x <= WPos)
        // 書き込み位置が0→4のとき  → 特殊 (x < WPos || x > Size - (4K - WPos))
        ULONG rPos, wPos, size;
        rPos = (*m_pMiniport).m_ulStereoMixRPos;
        wPos = (*m_pMiniport).m_ulStereoMixWPos;
        size = (*m_pMiniport).m_ulStereoMixSize;

        if (wPos >= 2 * 1024 && wPos - 2 * 1024 < rPos && rPos <= wPos)
        {
                // 止めます
        }
        else if (wPos < 2 * 1024 && (rPos < wPos || rPos > size - (2 * 1024 - wPos)))
        {
                // 止めます
        }
        else {
                // 書き込み位置が0→Max-4のとき  → 通常 (WPos < x < WPos + 4K)
                // 書き込み位置がMax-4→Maxのとき → 特殊 (WPos < x || x < WPos + 4K - Size)
                if ((wPos < size - 2 * 1024 && wPos < rPos && rPos < wPos + 2 * 1024) ||
                        (size - 2 * 1024 <= wPos && wPos <= size && (wPos < rPos || rPos < wPos + 2 * 1024 - size)))
                {
                        (*m_pMiniport).m_ulStereoMixRPos = (rPos + size / 2) % size;
                        rPos = (*m_pMiniport).m_ulStereoMixRPos;
                }


                if (rPos + ByteCount > size)
                {
                        // 読みきれない.
                        ULONG readSize;
                        ULONG restSize;

                        readSize = size - rPos;
                        restSize = ByteCount - readSize;

                        // まずはうしろまで.
                        RtlCopyMemory(
                                Destination,
                                (PBYTE)((*m_pMiniport).m_pStereoMix) + rPos,
                                readSize
                                );

                        // んで頭から.
                        RtlCopyMemory(
                                (PBYTE)Destination + readSize,
                                (*m_pMiniport).m_pStereoMix,
                                restSize
                                );

                        // なおす.
                        (*m_pMiniport).m_ulStereoMixRPos = restSize;
                }
                else
                {
                        // 読みきれる.
                        RtlCopyMemory(
                                Destination,
                                (PBYTE)((*m_pMiniport).m_pStereoMix) + rPos,
                                ByteCount
                                );

                        (*m_pMiniport).m_ulStereoMixRPos += ByteCount;
                }
        }

207行目あたり(追加後不明)、とにかくCopyTo関数んとこ。m_SaveDataのくだりはもちろん削除で。
 場所: CMiniportWaveCyclicStreamMSVADクラス - CopyTo関数

        // オリジナル

        if ((*m_pMiniport).m_ulStereoMixWPos + ByteCount >
                (*m_pMiniport).m_ulStereoMixSize)
        {
                // 書ききれない.
                ULONG writeSize;
                ULONG restSize;

                writeSize = (*m_pMiniport).m_ulStereoMixSize
                                                - (*m_pMiniport).m_ulStereoMixWPos;
                restSize = ByteCount - writeSize;

                // まずはうしろまで.
                RtlCopyMemory(
                        (PBYTE)((*m_pMiniport).m_pStereoMix) + (*m_pMiniport).m_ulStereoMixWPos,
                        Source,
                        writeSize
                        );

                // んで頭から.
                RtlCopyMemory(
                        (*m_pMiniport).m_pStereoMix,
                        (PBYTE)Source + writeSize,
                        restSize
                        );

                (*m_pMiniport).m_ulStereoMixWPos = restSize;
        }
        else
        {
                // 書ききれる.

                RtlCopyMemory(
                        (PBYTE)((*m_pMiniport).m_pStereoMix) + (*m_pMiniport).m_ulStereoMixWPos,
                        Source,
                        ByteCount
                        );

                (*m_pMiniport).m_ulStereoMixWPos += ByteCount;
        }
        // m_SaveData.WriteData((PBYTE) Source, ByteCount);
basewave.cpp

69行目あたり、変数の初期化を追加。
 場所: CMiniportWaveCyclicMSVADクラス - コンストラクタ(CMiniportWaveCyclicMSVAD)

        m_ulStereoMixSize = 32 * 1024;
        m_pStereoMix = ExAllocatePoolWithTag(
                NonPagedPool, m_ulStereoMixSize, MSVAD_POOLTAG
                );
        m_ulStereoMixWPos = 0;
        m_ulStereoMixRPos = 0;

106行目(追加後は112行目)あたり、処理追加。
 場所: CMiniportWaveCyclicMSVADクラス - デストラクタ(~CMiniportWaveCyclicMSVAD)

        if (m_pStereoMix)
        {
                ExFreePoolWithTag(m_pStereoMix, MSVAD_POOLTAG);
        }

565行目(追加後は575行目)あたり、m_pMiniportにMiniport_の参照を代入したあとに処理を追加。
 場所: CMiniportWaveCyclicStreamMSVADクラス - Init関数

                if (Capture_)
                {
                        (*m_pMiniport).m_ulStereoMixRPos =
                                ((*m_pMiniport).m_ulStereoMixWPos +
                                        (*m_pMiniport).m_ulStereoMixSize / 2)
                                % (*m_pMiniport).m_ulStereoMixSize;
                }

610行目(追加後は627行目)あたり、コメントアウト。CSaveDataにまつわる処理は不要。
 場所: CMiniportWaveCyclicStreamMSVADクラス - Init関数

        /*
        if (!m_fCapture)
        {
(中略)
        }
        */

841行目(追加後は860行目)あたり、コメントアウト。同上。
 場所: CMiniportWaveCyclicStreamMSVADクラス - SetFormat関数

                /*
                if (!m_fCapture)
                {
                    ntStatus = m_SaveData.SetDataFormat(Format);
                }
                */

993行目(追加後は1014行目)あたり、コメントアウト。同上。
 場所: CMiniportWaveCyclicStreamMSVADクラス - SetState関数

            /*
            if (!m_fCapture)
            {
                m_SaveData.WaitAllWorkItems();
            }
            */
んでビルド、インストール

何を思ったかmultiのほうをいじったので、"Microsoft Virtual Audio Device (Multiple Streams) (WDM)"をお試しあれ。ベースいじったからほかでも動くと思うけど。

いったい何をしたのか

 いじってると、CopyTo関数に再生データが飛んできて、CopyFrom関数で録音データを飛ばす、ということがなんとなく分かります。ただ、シンプルにCopyTo関数のデータをCopyFrom関数で飛ばそうとしても、すんなりいきません。

 CMiniportWaveCyclicMSVADクラスとCMiniportWaveCyclicStreamMSVADクラスは、だいたい次のような関係になっています。

 StreamMSVADのインスタンスは再生と録音で別々。

 なので、CMiniportWaveCyclicStreamMSVADだけでデータを回しても、何にも起こるわけがないんです。。

 ってことで、共通しているCMiniportWaveCyclicMSVADにバッファを作って、それを読み書きするようにしました。

 

 めでたし、めでたし。

バッファの位置調整

 読み書きしているバッファの位置が重なると音がへんになってしまうので、適当に調整しています。

なんでここまでしてソース公開しないの? ばかなの?

 ライセンス的な意味で。パッチなら勘弁してもらえるだろうか: msvad2vadplus_diff.zip - 当て方は Google: diff patch とかで。


たいぷらいたー  メール: takuyaxp@gmail.com ニコニコ動画: user/308238 GitHub: typewriter その他: にゃみかん