標籤:win7 win8 c#音量控制 coreaudioapi
========================================================
作者:qiujuer
部落格:blog.csdn.net/qiujuer
網站:www.qiujuer.net
開源庫:Genius-Android
轉載請註明出處:http://blog.csdn.net/qiujuer/article/details/41575517
========================================================
引入
在很久之前寫了一篇 [C#] 控制系統音量-第一章 ,忘記是什麼時候寫的了;不過並沒有忘記有這回事兒,不過看見沒有什麼人需要所以就沒有出後面的文章了。前天突然看見評論有人需要,所以覺得有必要完善一下;總結了一下這是第二章,同時也是終章;以前準備寫多章仔細分析一下的,現在看來就介紹一下如何使用吧。
問題
在第一章中,控制電腦音量是能夠實現的,但是只支援XP系統;這無疑是糟糕的;現在這個階段使用XP的還有多少?本篇為支援Win7及其以上版本音量控制而生。
相容性(C#)
Win7、Win8、Win8.1
前戲
在開始之前有必要介紹一下 Core Audio APIs ,什麼是 Core Audio APIs ?Core Audio APIs是微軟在WIn7之後提供的一套用於控制系統音量的Api,其具有以下特點:
- 低延時,幾乎無故障的音頻流。
- 提高可靠性 ( 很多音頻函數從核心態移到了使用者態 )
- 提高了安全性 (在安全的,低優先順序別的線程處理被保護的音頻內容)
- 分配了特定的系統層級的規則 (console, multimedia, communications) 給單獨的音訊裝置。
- 使用者可以直接操作,相應 endpoint 裝置的軟體抽象 ( 如:擴音器,耳麥及麥克風 ) 以下的高層 API 是以 Core Audio APIs 來工作的。
相關介紹:
http://msdn.microsoft.com/en-us/library/dd370802(VS.85).aspx
http://msdn.microsoft.com/en-us/library/dd370784(v=vs.85).aspx
當然這裡我們並不是直接使用此API,因為其是C++的調用介面,在這裡對其進行了封裝,封裝成C#下能直接調用的dll檔案;後面添加。
CoreAudioApi.dll 包結構:
在這裡就不詳細介紹其中代碼,打包時會同時把 CoreAudioApi.pdb 檔案打包,在調試時能進入其中查看代碼。
至於匯入 DLL 到項目中,這個也無需說了吧。
CodeTime
在這裡還要進行一次簡單的調用簡化封裝,封裝為 VolumeControl class.
VolumeControl.cs
namespace Volume{ public class VolumeControl { private static VolumeControl _VolumeControl; private MMDevice device; public event AudioNotificationDelegate OnAudioNotification; public bool InitializeSucceed; public static VolumeControl Instance { get { if (_VolumeControl == null) _VolumeControl = new VolumeControl(); return _VolumeControl; } } private VolumeControl() { MMDeviceEnumerator DevEnum = new MMDeviceEnumerator(); try { device = DevEnum.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia); device.AudioEndpointVolume.OnVolumeNotification += new AudioEndpointVolumeNotificationDelegate(AudioEndpointVolume_OnVolumeNotification); InitializeSucceed = true; } catch { InitializeSucceed = false; } } private void AudioEndpointVolume_OnVolumeNotification(AudioVolumeNotificationData data) { if (InitializeSucceed && this.OnAudioNotification != null) { this.OnAudioNotification(null, new AudioNotificationEventArgs() { MasterVolume = data.MasterVolume * 100, Muted = data.Muted }); } } public double MasterVolume { get { return InitializeSucceed ? device.AudioEndpointVolume.MasterVolumeLevelScalar * 100 : 0; } set { if (InitializeSucceed) { device.AudioEndpointVolume.MasterVolumeLevelScalar = (float)(value / 100.0f); if (this.IsMute) this.IsMute = false; } } } public bool IsMute { get { return InitializeSucceed ? device.AudioEndpointVolume.Mute : true; } set { if (InitializeSucceed)device.AudioEndpointVolume.Mute = value; } } public double[] AudioMeterInformation { get { if (InitializeSucceed) { try { return new double[3]{ device.AudioMeterInformation.MasterPeakValue * 100.00, device.AudioMeterInformation.PeakValues[0] * 100, device.AudioMeterInformation.PeakValues[1] * 100 }; } catch { return new double[3] { 0, 0, 0 }; } } else return new double[3] { 0, 0, 0 }; } } } public delegate void AudioNotificationDelegate(object sender, AudioNotificationEventArgs e); public class AudioNotificationEventArgs : EventArgs { public double MasterVolume { get; set; } public bool Muted { get; set; } }}可以看到,在代碼中為了外面調用的方便性,我們採用了單列模式,當然這裡沒有單獨對多線程添加鎖的機制。可自行添加其 Instance 部分。
其中有4個變數:
- VolumeControl 當然是為了單列而生的
- MMDevice 實際的音量操作,來自於封裝了一次的 CoreAudioApi.dll 庫
- AudioNotificationDelegate 可以看見最後面的地方其實是一個事件委託,用於事件的通知,主要作用是當系統音量改變時通知主介面進行重新整理介面
- InitializeSucceed 這個是用於記錄是否初始化成功的參數;當然可以去掉
static VolumeControl Instance 用於保證單列的運行
在類的建構函式中,可以看到:
MMDeviceEnumerator DevEnum = new MMDeviceEnumerator(); device = DevEnum.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia); device.AudioEndpointVolume.OnVolumeNotification += new AudioEndpointVolumeNotificationDelegate(AudioEndpointVolume_OnVolumeNotification);
其中 執行個體化了一個
MMDeviceEnumerator 類,然後初始化了
MMDevice 屬性;同時在這裡進行了註冊事件,讓音量改變時調用方法
AudioEndpointVolume_OnVolumeNotification()
而在方法 AudioEndpointVolume_OnVolumeNotification() 中又調用了當前類的委託事件,用於觸發事件重新整理介面;同時對傳遞的參數進行了封裝;封裝為了類:AudioNotificationEventArgs
在類 AudioNotificationEventArgs 中:
public class AudioNotificationEventArgs : EventArgs { public double MasterVolume { get; set; } public bool Muted { get; set; } }包含兩個屬性,分別是當前音量大小以及是否靜音。
繼續分析我們的主類:
public double MasterVolume { get { return InitializeSucceed ? device.AudioEndpointVolume.MasterVolumeLevelScalar * 100 : 0; } set { if (InitializeSucceed) { device.AudioEndpointVolume.MasterVolumeLevelScalar = (float)(value / 100.0f); if (this.IsMute) this.IsMute = false; } } } public bool IsMute { get { return InitializeSucceed ? device.AudioEndpointVolume.Mute : true; } set { if (InitializeSucceed)device.AudioEndpointVolume.Mute = value; } }這兩個屬性,分別用於設定與擷取當前主音量大小和是否靜音操作的封裝。
public double[] AudioMeterInformation { get { if (InitializeSucceed) { try { return new double[3]{ device.AudioMeterInformation.MasterPeakValue * 100.00, device.AudioMeterInformation.PeakValues[0] * 100, device.AudioMeterInformation.PeakValues[1] * 100 }; } catch { return new double[3] { 0, 0, 0 }; } } else return new double[3] { 0, 0, 0 }; } }該方法用於擷取當前的音量資訊,分別是
主音量、
左聲道、
右聲道。
ViewCode在這裡使用WPF作為樣本,介面代碼:
<Grid Margin="10"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <StackPanel> <Label Content="音量" /> <Label Content="主聲道:" Margin="0,10,0,0"/> <ProgressBar x:Name="mMasterPBar" Minimum="0" Maximum="100" Width="170" HorizontalAlignment="Right" Margin="0,0,10,0"/> <Label Content="左聲道:" Margin="0,10,0,0"/> <ProgressBar x:Name="mLeftPBar" Minimum="0" Maximum="100" Width="170" HorizontalAlignment="Right" Margin="0,0,10,0"/> <Label Content="右聲道:" Margin="0,10,0,0"/> <ProgressBar x:Name="mRightPBar" Minimum="0" Maximum="100" Width="170" HorizontalAlignment="Right" Margin="0,0,10,0"/> <Label Content="操作:" Margin="0,20,0,0" HorizontalAlignment="Right" /> </StackPanel> <Slider Grid.Column="1" Name="mMasterVolumeSlider" Orientation="Vertical" Height="200" Maximum="100" ValueChanged="mMasterVolumeSlider_ValueChanged" /> </Grid>
對應的介面:
介面代碼:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); InitializeAudioControl(); } private void Page_Loaded(object sender, RoutedEventArgs e) { volumeControlTimer.Start(); } private void Page_Unloaded(object sender, RoutedEventArgs e) { volumeControlTimer.Stop(); } private VolumeControl volumeControl; private bool isUserChangeVolume = true; private DispatcherTimer volumeControlTimer; private void InitializeAudioControl() { volumeControl = VolumeControl.Instance; volumeControl.OnAudioNotification += volumeControl_OnAudioNotification; volumeControl_OnAudioNotification(null, new AudioNotificationEventArgs() { MasterVolume = volumeControl.MasterVolume }); volumeControlTimer = new DispatcherTimer(); volumeControlTimer.Interval = TimeSpan.FromTicks(150); volumeControlTimer.Tick += volumeControlTimer_Tick; } void volumeControl_OnAudioNotification(object sender, AudioNotificationEventArgs e) { this.isUserChangeVolume = false; try { this.Dispatcher.Invoke(new Action(() => { mMasterVolumeSlider.Value = e.MasterVolume; })); } catch { } this.isUserChangeVolume = true; } void volumeControlTimer_Tick(object sender, EventArgs e) { double[] information = volumeControl.AudioMeterInformation; mMasterPBar.Value = information[0]; mLeftPBar.Value = information[1]; mRightPBar.Value = information[2]; } private void mMasterVolumeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) { if (isUserChangeVolume) { volumeControl.MasterVolume = mMasterVolumeSlider.Value; } } }
還是從屬性開始,3個屬性:
VolumeControl 這個很簡單了吧,就是上面封裝的成果
isUserChangeVolume 這個是用於排除系統回調時觸發 Slider 控制項的 ValueChanged()事件,避免無限迴圈
DispatcherTimer 用於重新整理介面中的音量條
開始說說方法:
InitializeAudioControl() 用於初始化 VolumeControl 同時,添加事件回調,以及初始化 DispatcherTimer Timer
volumeControl_OnAudioNotification() 回調方法
volumeControlTimer_Tick() 這個就是 DispatcherTimer 重新整理介面的方法
mMasterVolumeSlider_ValueChanged() 這個就更加簡單了,介面的事件觸發方法
必要說明:
在使用者撥動介面的屬性條的時候會觸發 mMasterVolumeSlider_ValueChanged() 這時 isUserChangeVolume是 True 所以能調用進行音量改變;
而當音量改變過程中(也包括使用者使用系統的音量條時)將會觸發 volumeControl_OnAudioNotification() 方法進行回調,而在 volumeControl_OnAudioNotification() 方法中,我們首先 將isUserChangeVolume = false;
然後使用委託的封裝類進行介面更改;這時介面音量條更改勢必會觸發 mMasterVolumeSlider_ValueChanged() 方法,這時 isUserChangeVolume 是 False狀態,所以不會再次進行音量的更改調用,故而避免死迴圈狀態出現;
在最後事件觸發完後當然是把 isUserChangeVolume 重新設定為 True 了。
至於其他應該都是一看就懂了,介面載入完成時就 讓重新整理線程工作,而介面 Unloaded 時自然就停止工作,避免多餘消耗。
END
附上本次的範例程式碼,以及 DLL 庫,都打包在一個檔案夾中了。
win7 win8 C# 音量控制 Volume
[C#] 控制系統音量-第二章