C#でマイク入力の ON / OFF をホットキーで切り替えるツールを作る

スポンサーリンク

マイクのミュートボタンを手元に用意したい目的で、AZ-Macroという自作マクロキーボードを作りました。(自作キーボードの練習もしたかったんですが。)その時に、MicMuteというフリーソフトを使ったんですが、接続しているマイクが全部表示されず、結局使えなかったので自分で作ることにしました。Windowsで使うので、割とC#のほうが親和性があるんじゃないか、という安直な理由でC#で作ったんですが、結果的に簡単に実現できました。

NAudioを使う

NAudioは.Net上で動作するオーディオライブラリです。音声データの再生や録音、変換等オーディオに関することであれば割となんでもできるみたいです。今回は再生や録音ではなく、マイクをミュートにしたいだけなので、録音デバイスのプロパティ操作をしたいと思います。

オーディオの一覧を取得する

MMDeviceEnumerator devEnu = new MMDeviceEnumerator();
MMDeviceCollection collection = devEnu.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active);

EnumerateAudioEndPointsでオーディオの一覧を取得します。第一引数は、

  • DataFlow.Capture・・・入力
  • DataFlow.Render・・・出力
  • DataFlow.All・・・全部

となってました。入力と出力、どちらも指定できるので録音と再生どちらかをミュートさせたいか、で指定する引数を切り替えるとよさそうです。

第二引数は、Active、Disable、NotPresent、Unpluggedとありますが、有効なもののみ取るのでActiveを指定します。

規定のオーディオを取得する

GetDefaultAudioEndpointを使用します。

こちらは一覧ではなくデバイスを返すので、MMDeviceを返します。

MMDevice mMDevice = devEnu.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia);

Role一覧については、

  • Role.Communications 規定の通信デバイスが返却される
  • Role.Console  規定のデバイスが返される
  • Role.Multimedia  規定のデバイスが返される

という挙動になってました。

MMDeviceCollectionをコンボボックスにセットする

ドロップダウンでオーディオの一覧を取得し、ツール最小化時に選択したデバイス情報を取得し、ミュートの挙動をさせたいです。なので、

  • ①MMDeviceCollectionをコンボボックスにセットする
  • ②選択中のオーディオデバイスを取り出す

を実装していきます。

//音声キャプチャーデバイス一覧取得
MMDeviceCollection collection = devEnu.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active);
//コンボボックスに格納していく
DataTable dt = new DataTable("DeviceTable");
dt.Columns.Add("NAME");
dt.Columns.Add("ID");
for (int i = 0; i < collection.Count; i++)
{
	DataRow dr = dt.NewRow();
	dr["NAME"] = collection[i].FriendlyName;
	dr["ID"] = collection[i].ID;
	dt.Rows.Add(dr);
}
comboBox1.DataSource = dt;
comboBox1.DisplayMember = "NAME";
comboBox1.ValueMember = "ID";
//デフォルトのデバイスを選択状態にする
comboBox1.SelectedValue = devEnu.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia).ID;

MMDeviceEnumerator.EnumerateAudioEndPointsでオーディオ一覧を取得し、コンボボックスに取得していきます。

コレクション内のFriendlyNameにオーディオ名が入っているので、コンボボックスに表示するのは

こちらを選択しました。ただ、口述するデバイス情報の取得部分では、IDが必要となるので、

valueにIDをセットしています。

最後に、初期状態はデフォルトデバイスのIDを選択状態にしています。

最小化時に選択中のオーディオデバイスを取り出す

最小化時にコンボボックスから選択中のオーディオ情報を取り出します。

なお、フォームに最小化した際には、タスクトレイへ格納するようフォームにはNotifyIconを置き、SizeChangedで処理が行われるようにしています。

//最小化した場合
if (this.WindowState == FormWindowState.Minimized)
{
    //選択中のデバイスをセット
    mMDevice = devEnu.GetDevice(comboBox1.SelectedValue.ToString());
    //ミュートの場合のアイコンセット
    if (mMDevice.AudioEndpointVolume.Mute)
        notifyIcon1.Icon = new Icon(@".\Mute.ico");
    //ミュートでない場合のアイコンセット
    else
        notifyIcon1.Icon = new Icon(@".\NotMute.ico");
    //タスクトレイへ
    this.WindowState = FormWindowState.Normal;
    this.ShowInTaskbar = false;
    this.Visible = false;
    //グローバルフックを仕掛ける
    keyHook = new KeyboardHook();
    keyHook.KeyboardHooked += new KeyboardHookedEventHandler(keyHookProc);
}

MMDeviceEnumerator.GetDeviceで選択中デバイスを取得しますが、使用するのはvalueにセットしたIDです。

そのあと、ミュートの状態に合わせてアイコンをセットするようにしていました。

MMDevice.AudioEndpointVolume.Muteでミュート状態を取得します。

また、ミュートする場合にもこちらを使用します。

そのあとの処理は、タスクトレイの格納と、最小化時のホットキーの取得ですが、グローバルフックについては今回割愛します。

特定のキー押下時にミュート状態を切り替える

//処理中の場合は即終了
if (isExecute)	return;
uint receiveCode;
//キーを取得する
uint.TryParse(e.KeyCode.ToString("d"), out receiveCode);

//格納
thirdKey = secondKey;
secondKey = firstKey;
firstKey = receiveCode;

//キーがCTRL ALT Aだった場合はミュート
if ((firstKey == 65 || firstKey == 162 || thirdKey == 164) &&
     (secondKey == 65 || secondKey == 162 || secondKey == 164) &&
     (thirdKey == 65 || thirdKey == 162 || thirdKey == 164))
{
    isExecute = true;
    //ミュートの反転
    mMDevice.AudioEndpointVolume.Mute = !mMDevice.AudioEndpointVolume.Mute;
    //通知用フォーム表示
    if (mMDevice.AudioEndpointVolume.Mute)
    {
        Mute mute = new Mute();
        mute.Left = Screen.PrimaryScreen.WorkingArea.Width - 60;
        mute.Top = 0;
        mute.StartPosition = FormStartPosition.Manual;
        mute.ShowDialog();
    }
    else
    {
        NoteMute notMute = new NoteMute();
        notMute.Left = Screen.PrimaryScreen.WorkingArea.Width - 60;
        notMute.Top = 0;
        notMute.StartPosition = FormStartPosition.Manual;
        notMute.ShowDialog();
    }
    firstKey = 0;
    secondKey = 0;
    thirdKey = 0;
    isExecute = false;
}

ミュートについては、簡単です。

mMDevice.AudioEndpointVolume.Mute = !mMDevice.AudioEndpointVolume.Mute;

ミュート状態を反転させてセットすることで、ミュートの切り替えをしています。

一番苦戦したのは、ホットキーの取得部分。

今回はCTRL、ALT、Aでミュート状態を切り替えるようにしましたが、

  • ①1回に1個のキー押下情報しかとれない
  • ②一回押下しただけでも何回もイベントが発生する場合がある=キーを押下する時間の問題

だったので、直近3回分のキー情報を保存し、「CTRL」「ALT」「A」がそろった時点で処理

を走らせることにしました。

処理中に処理が割り込まないよう、処理中フラグを設けて重複処理を防止しています。

ミュート時の通知について、notifyiconのバルーン通知を試してみましたが視覚的にミュート場外がわかりずらかったので、フォームにアイコンを張り付けて画面右上に3秒くらい表示するようにしました。

まとめ

デバイス情報の取得とミュート部分については、割と躓くことなく30分くらいで作成できました。

グローバルフックの部分について仕様を理解するまで時間が少しかかりました。組み方がきれいではないですが、一応使えるレベルの挙動になってるとおもったのでよしとします。

今回は「CTRL」「ALT」「A」固定としましたが、キーの表とキー設定を持てば好きなキーに割り当てることが出来そうです。ただ、PushToTalkを実装する場合はキー押下判定をどうするか、頭を使う必要がありそうですね