【C#】[NAudio]アプリケーションごとに音量調整

C#

アプリケーション単位で音量調整したい!

ゲームのマッチング待機中に、ウィンドウを切り替えて動画や音楽を聴きたいけど、ゲーム側のサウンドが邪魔する場合があります。
Windowsで音量調整する場合、Windowsキー+Ctrlキー+Vキーで音量ミキサーを開き、マウスで調整する必要があります。
そこでC#のNAudioというライブラリを利用して特定アプリケーションのみをミュートできるプログラムを作成しました。

NAudioとは

下記Wikiの引用になりますが、OSのオーディオをコントロールできるオーディオライブラリになります。音声の録音や再生、エンコード、デコードなどサウンド関係がめちゃ強ライブラリです。

NAudio(エヌオーディオ)とは.NET Framework上で動作するオーディオライブラリである。GitHub上でオープンソースで開発されている。以前はMicrosoft Public Licenseであったが、2021年2月7日リリースのv2.0.0からMIT licenseとなった

https://ja.wikipedia.org/wiki/NAudio

入手方法

名称内容
入手場所Nuget or GitHub
ライセンスMIT license

環境

SDKのインストールが面倒だったため、.Net Framework v4.7.2を利用しようとしましたが
ライブラリの依存関係に引っ掛かりうまくいきませんでした。
Visual Stadioで作成すれば余裕で動くと思いますが、PCがもっさりするのが嫌なのでVisual Stadioはインストールせず、おとなしく.Net 6.0をインストールしました。
.Net Coreも対応していますので、開発環境に合わせてコンパイルしてください。

名称バージョン
フレームワークWPF
.Netv6.0
NAudiov2.2.1

実装例

一からコードを書くのは面倒なのでDemoを利用します。
DemoはGitHubにアップロードされているため、Zipファイルでダウンロードします。

実装コード

DemoはGUIで実装していますが、コンソールアプリで十分なので起動時にVolumePanelインスタンスを作成します。

NAudioDemo\Program.cs

using System;

namespace NAudioDemo
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            //Application.EnableVisualStyles();
            //Application.SetCompatibleTextRenderingDefault(false);

            //var mainForm = new MainForm();
            //Application.Run(mainForm);

            var vmd = new NAudioDemo.VolumeMixerDemo.VolumePanel();
            
        }
}

音量ミキサーのコントロールは VolumePanel.cs で行っているため、ファイルを使い回します。
コンストラクタ内でアプリケーションのプロセス名を検索し、該当アプリケーションをミュートに設定します。

NAudioDemo\VolumeMixerDemo\VolumePanel.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using NAudio.CoreAudioApi;
using System.Diagnostics;
using NAudio.CoreAudioApi.Interfaces;
using System.Media;
using System.Xml;
using System.Xml.Serialization;
using System.IO;

namespace NAudioDemo.VolumeMixerDemo
{
    public partial class VolumePanel : UserControl, IAudioSessionEventsHandler
    {
        private readonly bool devicePanel;
        private MMDevice device;
        private readonly AudioSessionControl session;

        public MMDevice Device
        {
            get
            {
                return device;
            }
        }

        public event EventHandler DeviceChanged;
        public event EventHandler<MuteEventArgs> MuteChanged;
        public event EventHandler<VolumeEventArgs> VolumeChanged;

        /// <summary>
        /// Constructor for device panel creation.
        /// </summary>
        public VolumePanel()
        {
            string appName = readSettings(); //XMLからプロセス名を読み込み

            this.devicePanel = true;
            var deviceEnumerator = new MMDeviceEnumerator();
            device = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);

            var sessions = device.AudioSessionManager.Sessions;
            for (int i = 0; i < sessions.Count; i++)
            {
                Process process = Process.GetProcessById((int)sessions[i].GetProcessID);
                Console.WriteLine(process.ProcessName.ToString());
                if (process.ProcessName == appName)
                {
                    //sessions[i].SimpleAudioVolume.Volume = 0.2f //音量変更する場合
                    sessions[i].SimpleAudioVolume.Mute = !sessions[i].SimpleAudioVolume.Mute;
                }
            }

            //マスターボリューム(すべての音量)をゼロにする場合
            //device.AudioEndpointVolume.MasterVolumeLevelScalar = ((float)0 / 100.0f);
            
            //InitializeComponent();
        }

    ~~~~~~ 以下省略 ~~~~~~

プロセス名はsettings.xmlnameタグで管理するため、VolumePanelクラス内でXMLを読み込むメソッドを定義します。
(面倒なのでVolumePanel内に記載していますが、本番運用する場合は別ライブラリで作成してください)

NAudioDemo\VolumeMixerDemo\VolumePanel.cs

public class VolumePanel
{
	public static string settings_file = "settings.xml";
	public static string readSettings ()
	{
		try
		{
			//構成ファイルが無ければ作成する。
			if (!File.Exists(Directory.GetCurrentDirectory() + "\\" + settings_file))
			{
				writeSettings();
			}
			XmlSerializer serializer = new XmlSerializer(typeof(AppSettings));

			// XMLをAppSettingsオブジェクトに読み込む
			AppSettings settings = new AppSettings();
			using(FileStream fs = new FileStream(Directory.GetCurrentDirectory() + "\\" + settings_file, FileMode.Open))
			{
				// XMLファイルを読み込み、逆シリアル化(復元)する
				settings = (AppSettings)serializer.Deserialize(fs);
			}
			return settings.name;
		}
		catch
		{
			MessageBox.Show("XMLの読み込みに失敗しました");
			return null;
		}
	}

	public static void writeSettings ()
	{
		try
		{
			AppSettings settings = new AppSettings();
			settings.name = "please write Appname";

			// XmlSerializerを使ってファイルに保存(TwitSettingオブジェクトの内容を書き込む)
			XmlSerializer serializer = new XmlSerializer(typeof(AppSettings));

			// カレントディレクトリに"settings.xml"というファイルで書き出す
			using(FileStream fs = new FileStream(Directory.GetCurrentDirectory() + "\\" + settings_file, FileMode.Create))
			{
				// オブジェクトをシリアル化してXMLファイルに書き込む
				serializer.Serialize(fs, settings);
			}
		}
		catch
		{
			MessageBox.Show("XMLの書き込みに失敗しました");
		}
	}
}

public class AppSettings
{
	#region メンバ変数
	private string _name;
	#endregion

	#region プロパティ
	public string name
	{
		get { return _name; }
		set { _name = value; }
	}
	#endregion
}

settings.xmlにプロセス名を記載します。
初回アプリケーション起動時はnameタグに”please write Appname”と設定されるようにしています。
nameタグにchromeを指定するとGoogle Chromeをミュートすることができます。

settings.xml

<?xml version="1.0" encoding="utf-8"?>
<AppSettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <name>chrome</name>
</AppSettings>

最後に

GitHubにアップロードされているDemoプロジェクトが機能てんこ盛りのため、一度Demoプロジェクトをコンパイルすることをお勧めします。
ショートカットキーの登録は次の記事で記載しています。

参考サイト