1. 최소 사양을 맞추기 위해 .NET Framework 4.5.2 버전을 사용
2. Winform, UserControl 사용 가능
3. 비주얼 스튜디오 16.9.3
1. 음소거 버튼 - 패널로 사용 (PictureBox로 대체가능)
2. ContextMenuStrip에 마이크와 믹서로만 사용하게 함 (별도로 마이크 리스트 전부를 가져와도 됨)
3. NAudio에는 마이크 기본 장치 변경이 없어 AudioSwitcher를 사용
4. NAudio는 이벤트, AudioSwitcher는 IObservable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Management;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using AudioSwitcher.AudioApi;
using AudioSwitcher.AudioApi.CoreAudio;
using AudioSwitcher.AudioApi.Observables;
using Microsoft.Win32;
using NAudio.CoreAudioApi;
using NAudio.Wave;
namespace AudioControl
{
public partial class Form1 : Form
{
private readonly string[] arrMicroPhoneList = new string[] {"마이크", "Microphone", "Mic", "MIC", "MicroPhone", "microphone", "mic" };
private readonly string[] arrMicMixList = new string[] { "스테레오 믹스", "스테레오", "믹스", "들리는내용", "들리는 내용", "Stereo Mix", "Stereo", "Mix", "Stereo MIxer", "What U Hear", "Mixed Output", "Post-Mix", "Loop Back", "SUM", "virtual" };
List<CoreAudioDevice> AudioDevices = null; // MicroPhone Device 리스트
CoreAudioDevice defaultMicDeivce = null; // 현재 MicroPhone Device
CoreAudioController coreAudioController = null; // MicroPhone Device 컨트롤러
List<IDisposable> disposables = null; // 옵저버 패턴
Action<DeviceVolumeChangedArgs> actionVolumeChange = null; // 볼륨 변경
Action<DeviceMuteChangedArgs> actionMuteChange = null; // 음소거 변경
Action<DefaultDeviceChangedArgs> actionDefalutDeviceChange = null; // 기본 장치 변경
Action<DeviceStateChangedArgs> actionStateDevice = null; // 상태 변경
// 마이크 스펙트럼
MMDeviceEnumerator mMDeviceEnumerator = null;
WaveInEvent waveIn = null;
bool gbIsStopWave = false;
private static double audioValueMax = 0;
private static double audioValueLast = 0;
private static int RATE = 44100;
private static int BUFFER_SAMPLES = 1024;
public Form1()
{
InitializeComponent();
// 필요한 이벤트 연결
this.Load += Form1_Load;
this.FormClosed += Form1_FormClosed;
cms_MicrophoneList.ItemClicked += Cms_MicrophoneList_ItemClicked; // 메뉴 버튼 아이템 클릭 이벤트 추가
trackBar1.Scroll += trackBar1_Scroll;
pn_DeviceState.Click += pn_DeviceState_Click;
}
private void Form1_Load(object sender, EventArgs e)
{
actionVolumeChange = OnVolumeChanged; // 볼륨 변경
actionMuteChange = OnMuteChanged; // 음소거 변경
actionDefalutDeviceChange = OnDefaultChanged; // 기본 장치 변경
actionStateDevice = OnStateChanged; // 상태 변경
disposables = new List<IDisposable>();
mMDeviceEnumerator = new MMDeviceEnumerator(); // 마이크 에뮬레이터
coreAudioController = new CoreAudioController(); // 마이크 스위처
trackBar1.Maximum = 100; // 기본 트랙바 max는 10
pictureBox2.Width = 0; // 마이크 사운드 이미지 길이 0으로 설정
GetDevices();
SetUI();
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
if (defaultMicDeivce != null)
defaultMicDeivce.Dispose();
if (coreAudioController != null)
coreAudioController.Dispose();
if (mMDeviceEnumerator != null)
mMDeviceEnumerator.Dispose();
if (waveIn != null)
waveIn.Dispose();
}
private int? GetNAudioDeviceNumver()
{
MMDevice defalutMicDevice = mMDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Capture, NAudio.CoreAudioApi.Role.Multimedia);
int? deviceIndex = 0;
if (defalutMicDevice != null)
{
for (int i = 0; i < WaveIn.DeviceCount; i++)
{
if (defalutMicDevice.FriendlyName.Equals(WaveIn.GetCapabilities(i).ProductName))
{
deviceIndex = i;
break;
}
}
}
else
return null;
return deviceIndex;
}
private void GetDevices()
{
if (AudioDevices == null)
AudioDevices = new List<CoreAudioDevice>();
else
AudioDevices.Clear();
// UnKnown 장비 제외하고 리스트에 추가
foreach (CoreAudioDevice coreAudioDevice in coreAudioController.GetCaptureDevices().Where(coreAudioDevice => !coreAudioDevice.FullName.Contains("Unknown")))
AudioDevices.Add(coreAudioDevice);
// 1개라도 있을 때만
if (AudioDevices.Count > 0)
{
ObservableClear();
// 현재 선택된 녹음 장치 가져오기
defaultMicDeivce = coreAudioController.DefaultCaptureDevice;
for (int i = 0; i < AudioDevices.Count; i++)
{
disposables.Add(ObservableExtensions.Subscribe(AudioDevices[i].VolumeChanged, actionVolumeChange));
disposables.Add(ObservableExtensions.Subscribe(AudioDevices[i].MuteChanged, actionMuteChange));
disposables.Add(ObservableExtensions.Subscribe(AudioDevices[i].DefaultChanged, actionDefalutDeviceChange));
disposables.Add(ObservableExtensions.Subscribe(AudioDevices[i].StateChanged, actionStateDevice));
}
}
}
private void SetUI()
{
try
{
lock (cms_MicrophoneList)
{
this.InvokeOnUiThreadIfRequired(() => cms_MicrophoneList.Items.Clear());
//기본 선택된 녹음 장치가 있으면 트랙바 볼륨 설정.
if (AudioDevices != null && AudioDevices.Count > 0 && defaultMicDeivce != null)
{
bool IsCheckMic = false; // 마이크 계열
bool IsCheckMix = false; // 믹스 계열
cms_MicrophoneList.Items.Add("마이크");
cms_MicrophoneList.Items.Add("믹스");
foreach (var mic in arrMicroPhoneList)
{
if (defaultMicDeivce.FullName.Contains(mic))
{
IsCheckMic = true;
cms_MicrophoneList.Items[0].Text = defaultMicDeivce.FullName;
((ToolStripMenuItem)cms_MicrophoneList.Items[0]).Checked = true;
break;
}
}
foreach (var mix in arrMicMixList)
{
if (defaultMicDeivce.FullName.Contains(mix))
{
IsCheckMix = true;
cms_MicrophoneList.Items[1].Text = defaultMicDeivce.FullName;
((ToolStripMenuItem)cms_MicrophoneList.Items[1]).Checked = true;
break;
}
}
cms_MicrophoneList.Items[0].Enabled = IsCheckMic;
cms_MicrophoneList.Items[1].Enabled = IsCheckMix;
if (waveIn == null)
{
int? devicenum = GetNAudioDeviceNumver();
if (devicenum != null)
{
waveIn = new WaveInEvent();
waveIn.DeviceNumber = (int)devicenum;
waveIn.WaveFormat = new WaveFormat(44100, 16, 1);
waveIn.BufferMilliseconds = (int)((double)BUFFER_SAMPLES / (double)RATE * 1000.0);
waveIn.DataAvailable += WaveIn_DataAvailable;
waveIn.RecordingStopped += (sender, e) => gbIsStopWave = false; pictureBox2.Width = 0;
}
}
if ((int)defaultMicDeivce.Volume > -1)
this.InvokeOnUiThreadIfRequired(() => trackBar1.Value = (int)defaultMicDeivce.Volume);
if (defaultMicDeivce.IsMuted)
{
this.InvokeOnUiThreadIfRequired(() => pn_DeviceState.BackColor = Color.Red); // 실제 적용시 이미지 변경
if (waveIn != null)
waveIn.StopRecording();
}
else
{
this.InvokeOnUiThreadIfRequired(() => pn_DeviceState.BackColor = Color.Green); // 실제 적용시 이미지 변경
if (waveIn != null && !gbIsStopWave)
{
waveIn.StartRecording();
gbIsStopWave = true;
}
}
}
else
{
cms_MicrophoneList.Items.Add("마이크");
cms_MicrophoneList.Items.Add("믹스");
cms_MicrophoneList.Items[0].Enabled = false;
cms_MicrophoneList.Items[1].Enabled = false;
}
}
}
catch (Exception e)
{
//textBox1.AppendText(e.ToString() + Environment.NewLine);
//textBox1.AppendText(e.Message + Environment.NewLine);
//textBox1.AppendText(e.StackTrace + Environment.NewLine);
}
}
private void WaveIn_DataAvailable(object sender, WaveInEventArgs e)
{
float max = 0;
// interpret as 16 bit audio
for (int index = 0; index < e.BytesRecorded; index += 2)
{
short sample = (short)((e.Buffer[index + 1] << 8) | e.Buffer[index + 0]);
// to floating point
var sample32 = sample / 32768f;
// absolute value
if (sample32 < 0) sample32 = -sample32;
// is this the max value?
if (sample32 > max) max = sample32;
}
// calculate what fraction this peak is of previous peaks
if (max > audioValueMax)
audioValueMax = (double)max;
audioValueLast = max;
double frac = audioValueLast / audioValueMax;
this.InvokeOnUiThreadIfRequired(() => pictureBox2.Width = (int)(frac * pictureBox1.Width));
}
// 메뉴 아이템 클릭시 기본 장치 변경
private void Cms_MicrophoneList_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (!defaultMicDeivce.FullName.Equals(e.ClickedItem.Text))
{
CoreAudioDevice coreAudioDevice = AudioDevices.Find(x => x.FullName.Equals(e.ClickedItem.Text));
if (coreAudioDevice != null)
coreAudioController.SetDefaultDevice(coreAudioDevice);
}
}
// 볼륨 조절 옵저버
private void OnVolumeChanged(DeviceVolumeChangedArgs deviceVolumeChangedArgs)
{
if (deviceVolumeChangedArgs.Device.FullName.Equals(defaultMicDeivce.FullName))
this.InvokeOnUiThreadIfRequired(() => trackBar1.Value = (int)deviceVolumeChangedArgs.Volume);
}
// 음소거 옵저버
private void OnMuteChanged(DeviceMuteChangedArgs deviceMuteChangedArgs)
{
if (deviceMuteChangedArgs.Device.FullName.Equals(defaultMicDeivce.FullName))
{
// TODO : 이미지로 변경
if (deviceMuteChangedArgs.IsMuted)
this.InvokeOnUiThreadIfRequired(() => pn_DeviceState.BackColor = Color.Red); // 실제 적용시 이미지 변경
else
this.InvokeOnUiThreadIfRequired(() => pn_DeviceState.BackColor = Color.Green); // 실제 적용시 이미지 변경
}
}
// 기본 장치 변경 옵저버
private void OnDefaultChanged(DefaultDeviceChangedArgs defaultDeviceChangedArgs)
{
if (((CoreAudioDevice)defaultDeviceChangedArgs.Device).IsDefaultDevice)
{
if (waveIn != null)
{
waveIn.StopRecording();
waveIn.DataAvailable -= WaveIn_DataAvailable;
waveIn = null;
}
GetDevices();
SetUI();
}
}
// 상태 변경 옵저버 ex) 사용 -> 사용 안 함
private void OnStateChanged(DeviceStateChangedArgs deviceStateChangedArgs)
{
// TODO : 메뉴 리스트 초기화
// 1. Device State에 따라 메뉴 리스트에서 enable과 active시 추가
this.InvokeOnUiThreadIfRequired(() => pictureBox2.Width = 0);
if (((CoreAudioDevice)deviceStateChangedArgs.Device).IsDefaultDevice)
{
if (waveIn != null)
{
waveIn.StopRecording();
waveIn.DataAvailable -= WaveIn_DataAvailable;
waveIn = null;
}
}
GetDevices();
SetUI();
}
// 트랙바로 마이크 사운드 조절
private void trackBar1_Scroll(object sender, EventArgs e)
{
if (defaultMicDeivce != null)
defaultMicDeivce.Volume = trackBar1.Value;
}
// 패널 클릭시 음소거, 음소거 해제
private void pn_DeviceState_Click(object sender, EventArgs e)
{
if (defaultMicDeivce != null)
defaultMicDeivce.Mute(!defaultMicDeivce.IsMuted);
}
private void ObservableClear()
{
if (disposables != null)
{
if (disposables.Count > 0)
foreach (var disposable in disposables)
disposable.Dispose();
disposables.Clear();
}
}
}
public static class ControlExtensions
{
/// <summary>
/// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread.
/// </summary>
/// <param name="control">the control for which the update is required</param>
/// <param name="action">action to be performed on the control</param>
public static void InvokeOnUiThreadIfRequired(this Control control, Action action)
{
//If you are planning on using a similar function in your own code then please be sure to
//have a quick read over https://stackoverflow.com/questions/1874728/avoid-calling-invoke-when-the-control-is-disposed
//No action
if (control.Disposing || control.IsDisposed || !control.IsHandleCreated)
{
return;
}
if (control.InvokeRequired)
{
control.BeginInvoke(action);
}
else
{
action.Invoke();
}
}
}
}
'C#' 카테고리의 다른 글
[C#] Async/Await, Task.result (0) | 2021.04.12 |
---|---|
[C#] Best Way Byte To Hex (0) | 2021.04.11 |
[C#] PDF 읽기 (0) | 2021.02.27 |
[C#] CPU, RAM, HDD 사용량 및 OS Version, Last boottime 확인 (0) | 2021.02.10 |
[C#] 공공 데이터 오픈 API 사용(제대 군인 채용 정보 얻기) (0) | 2021.01.02 |