본문 바로가기

C#

C#으로 Sound Control 제어하기 (사운드믹서)

반응형

사운드 컨트롤을 제어해서 지정된 어플에서만 음성을 받아오도록 해야하는 과제가 주어졌다.

일단, 대전제로 아래와같이 Mute 는 사용하지 않으면서, 해당어플에서 내보내는 경고음을 유저에게 내보내야한다.


자, 여기서부터 문제
어떻게 제어를 해야할까??

일단, 한번 검색부터 해보자.

Controlling Volume Mixer

라는 글이 보인다.

그리고, 아래의 글도 많은 도움이 되었다.

C# (CSharp) MIXERLINECONTROLS Code Examples

자, 그럼 이제 본격적으로 하나 만들어보자.
화면은 아래와 같이 구성하였다.

시스템 비프음을 내고, 시스템사운드를 이용한 음향효과를 내보내는 긴 버튼
그리고, 해당 어플에서 음량을 100%로 효과발산(?) 해주는 버튼

스피커 음량의 초기셋팅은 아래와같이 지정해둔것을 전제로 한다.

스피커는 MAX 100% 지정되어있으며, 나머지는 모두 0% 로 지정 (소리를 받지않겠다는 강한의지!!)

일단, 여기저기 있는 함수를 끌어~ 끌어~모아서 baseClass 작성
baseClass는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
using System;
using System.IO;
using System.Runtime.InteropServices;
 
namespace WindowsFormsApplication1
{
    public sealed class SoundUtils
    {
        public const int MMSYSERR_NOERROR = 0;
        public const int MAXPNAMELEN = 32;
        public const int MIXER_LONG_NAME_CHARS = 64;
        public const int MIXER_SHORT_NAME_CHARS = 16;
        public const int MIXER_GETLINEINFOF_COMPONENTTYPE = 0x3;
        public const int MIXER_GETCONTROLDETAILSF_VALUE = 0x0;
        public const int MIXER_GETLINECONTROLSF_ONEBYTYPE = 0x2;
        public const int MIXER_SETCONTROLDETAILSF_VALUE = 0x0;
        public const int MIXERLINE_COMPONENTTYPE_DST_FIRST = 0x0;
        public const int MIXERLINE_COMPONENTTYPE_SRC_FIRST = 0x1000;
        public const int MIXERLINE_COMPONENTTYPE_DST_SPEAKERS =
            (MIXERLINE_COMPONENTTYPE_DST_FIRST + 4);
        public const int MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE =
            (MIXERLINE_COMPONENTTYPE_SRC_FIRST + 3);
        public const int MIXERLINE_COMPONENTTYPE_SRC_LINE =
            (MIXERLINE_COMPONENTTYPE_SRC_FIRST + 2);
        public const int MIXERCONTROL_CT_CLASS_FADER = 0x50000000;
        public const int MIXERCONTROL_CT_UNITS_UNSIGNED = 0x30000;
        public const int MIXERCONTROL_CONTROLTYPE_FADER =
            (MIXERCONTROL_CT_CLASS_FADER | MIXERCONTROL_CT_UNITS_UNSIGNED);
        public const int MIXERCONTROL_CONTROLTYPE_VOLUME =
            (MIXERCONTROL_CONTROLTYPE_FADER + 1);
 
        [DllImport("winmm.dll", CharSet = CharSet.Ansi)]
        private static extern int mixerClose(int hmx);
 
        [DllImport("winmm.dll", CharSet = CharSet.Ansi)]
        private static extern int mixerGetControlDetailsA(int hmxobj, ref
            MIXERCONTROLDETAILS pmxcd, int fdwDetails);
 
        [DllImport("winmm.dll", CharSet = CharSet.Ansi)]
        private static extern int mixerGetDevCapsA(int uMxId, MIXERCAPS
            pmxcaps, int cbmxcaps);
 
        [DllImport("winmm.dll", CharSet = CharSet.Ansi)]
        private static extern int mixerGetID(int hmxobj, int pumxID, int
            fdwId);
 
        [DllImport("winmm.dll", CharSet = CharSet.Ansi)]
        private static extern int mixerGetLineControlsA(int hmxobj, ref
            MIXERLINECONTROLS pmxlc, int fdwControls);
 
        [DllImport("winmm.dll", CharSet = CharSet.Ansi)]
        private static extern int mixerGetLineInfoA(int hmxobj, ref
            MIXERLINE pmxl, int fdwInfo);
 
        [DllImport("winmm.dll", CharSet = CharSet.Ansi)]
        private static extern int mixerGetNumDevs();
 
        [DllImport("winmm.dll", CharSet = CharSet.Ansi)]
        private static extern int mixerMessage(int hmx, int uMsg, int
            dwParam1, int dwParam2);
 
        [DllImport("winmm.dll", CharSet = CharSet.Ansi)]
        private static extern int mixerOpen(out int phmx, int uMxId,
            int dwCallback, int dwInstance, int fdwOpen);
 
        [DllImport("winmm.dll", CharSet = CharSet.Ansi)]
        private static extern int mixerSetControlDetails(int hmxobj, ref
            MIXERCONTROLDETAILS pmxcd, int fdwDetails);
 
        [DllImport("winmm.dll", EntryPoint = "sndPlaySoundA")]
        private static extern int sndPlaySound(string lpszSoundName, int uFlags);
        public enum SND
        {
            SND_SYNC = 0x0000,/* play synchronously (default) */
            SND_ASYNC = 0x0001/* play asynchronously */
            SND_NODEFAULT = 0x0002/* silence (!default) if sound not found */
            SND_MEMORY = 0x0004/* pszSound points to a memory file */
            SND_LOOP = 0x0008/* loop the sound until next sndPlaySound */
            SND_NOSTOP = 0x0010/* don't stop any currently playing sound */
            SND_NOWAIT = 0x00002000/* don't wait if the driver is busy */
            SND_ALIAS = 0x00010000,/* name is a registry alias */
            SND_ALIAS_ID = 0x00110000/* alias is a pre d ID */
            SND_FILENAME = 0x00020000/* name is file name */
            SND_RESOURCE = 0x00040004/* name is resource name or atom */
            SND_PURGE = 0x0040,  /* purge non-static events for task */
            SND_APPLICATION = 0x0080  /* look for application specific association */
        }
 
        public struct MIXERCAPS
        {
            public int wMid;
            public int wPid;
            public int vDriverVersion;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAXPNAMELEN)]
            public string szPname;
            public int fdwSupport;
            public int cDestinations;
        }
 
        public struct MIXERCONTROL
        {
            public int cbStruct;
            public int dwControlID;
            public int dwControlType;
            public int fdwControl;
            public int cMultipleItems;
            [MarshalAs(UnmanagedType.ByValTStr,
                 SizeConst = MIXER_SHORT_NAME_CHARS)]
            public string szShortName;
            [MarshalAs(UnmanagedType.ByValTStr,
                 SizeConst = MIXER_LONG_NAME_CHARS)]
            public string szName;
            public int lMinimum;
            public int lMaximum;
            [MarshalAs(UnmanagedType.U4, SizeConst = 10)]
            public int reserved;
        }
 
        public struct MIXERCONTROLDETAILS
        {
            public int cbStruct;
            public int dwControlID;
            public int cChannels;
            public int item;
            public int cbDetails;
            public IntPtr paDetails;
        }
 
        public struct MIXERCONTROLDETAILS_UNSIGNED
        {
            public int dwValue;
        }
 
        public struct MIXERLINE
        {
            public int cbStruct;
            public int dwDestination;
            public int dwSource;
            public int dwLineID;
            public int fdwLine;
            public int dwUser;
            public int dwComponentType;
            public int cChannels;
            public int cConnections;
            public int cControls;
            [MarshalAs(UnmanagedType.ByValTStr,
                 SizeConst = MIXER_SHORT_NAME_CHARS)]
            public string szShortName;
            [MarshalAs(UnmanagedType.ByValTStr,
                 SizeConst = MIXER_LONG_NAME_CHARS)]
            public string szName;
            public int dwType;
            public int dwDeviceID;
            public int wMid;
            public int wPid;
            public int vDriverVersion;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAXPNAMELEN)]
            public string szPname;
        }
 
        public struct MIXERLINECONTROLS
        {
            public int cbStruct;
            public int dwLineID;
 
            public int dwControl;
            public int cControls;
            public int cbmxctrl;
            public IntPtr pamxctrl;
        }
 
        private static bool GetVolumeControl(int hmixer, int componentType,
            int ctrlType, out MIXERCONTROL mxc, out int vCurrentVol)
        {
            // This function attempts to obtain a mixer control. 
            // Returns True if successful. 
            MIXERLINECONTROLS mxlc = new MIXERLINECONTROLS();
            MIXERLINE mxl = new MIXERLINE();
            MIXERCONTROLDETAILS pmxcd = new MIXERCONTROLDETAILS();
            MIXERCONTROLDETAILS_UNSIGNED du = new
                MIXERCONTROLDETAILS_UNSIGNED();
            mxc = new MIXERCONTROL();
            int rc;
            bool retValue;
            vCurrentVol = -1;
 
            mxl.cbStruct = Marshal.SizeOf(mxl);
            mxl.dwComponentType = componentType;
 
            rc = mixerGetLineInfoA(hmixer, ref mxl,
                MIXER_GETLINEINFOF_COMPONENTTYPE);
 
            if (MMSYSERR_NOERROR == rc)
            {
                int sizeofMIXERCONTROL = 152;
                int ctrl = Marshal.SizeOf(typeof(MIXERCONTROL));
                mxlc.pamxctrl = Marshal.AllocCoTaskMem(sizeofMIXERCONTROL);
                mxlc.cbStruct = Marshal.SizeOf(mxlc);
                mxlc.dwLineID = mxl.dwLineID;
                mxlc.dwControl = ctrlType;
                mxlc.cControls = 1;
                mxlc.cbmxctrl = sizeofMIXERCONTROL;
 
                // Allocate a buffer for the control 
                mxc.cbStruct = sizeofMIXERCONTROL;
 
                // Get the control 
                rc = mixerGetLineControlsA(hmixer, ref mxlc,
                    MIXER_GETLINECONTROLSF_ONEBYTYPE);
 
                if (MMSYSERR_NOERROR == rc)
                {
                    retValue = true;
 
                    // Copy the control into the destination structure 
                    mxc = (MIXERCONTROL)Marshal.PtrToStructure(
                        mxlc.pamxctrl, typeof(MIXERCONTROL));
                }
                else
                {
                    retValue = false;
                }
                int sizeofMIXERCONTROLDETAILS =
                    Marshal.SizeOf(typeof(MIXERCONTROLDETAILS));
                int sizeofMIXERCONTROLDETAILS_UNSIGNED =
                    Marshal.SizeOf(typeof(MIXERCONTROLDETAILS_UNSIGNED));
                pmxcd.cbStruct = sizeofMIXERCONTROLDETAILS;
                pmxcd.dwControlID = mxc.dwControlID;
                pmxcd.paDetails =
 
                    Marshal.AllocCoTaskMem(sizeofMIXERCONTROLDETAILS_UNSIGNED);
                pmxcd.cChannels = 1;
                pmxcd.item = 0;
                pmxcd.cbDetails = sizeofMIXERCONTROLDETAILS_UNSIGNED;
 
                rc = mixerGetControlDetailsA(hmixer, ref pmxcd,
                    MIXER_GETCONTROLDETAILSF_VALUE);
 
                du = (MIXERCONTROLDETAILS_UNSIGNED)Marshal.PtrToStructure(
                    pmxcd.paDetails, typeof(MIXERCONTROLDETAILS_UNSIGNED));
 
                vCurrentVol = du.dwValue;
 
                return retValue;
            }
 
            retValue = false;
            return retValue;
        }
 
        private static bool SetVolumeControl(int hmixer, MIXERCONTROL mxc,
            int volume)
        {
            // This function sets the value for a volume control. 
            // Returns True if successful 
 
            bool retValue;
            int rc;
            MIXERCONTROLDETAILS mxcd = new MIXERCONTROLDETAILS();
            MIXERCONTROLDETAILS_UNSIGNED vol = new
                MIXERCONTROLDETAILS_UNSIGNED();
 
            mxcd.item = 0;
            mxcd.dwControlID = mxc.dwControlID;
            mxcd.cbStruct = Marshal.SizeOf(mxcd);
            mxcd.cbDetails = Marshal.SizeOf(vol);
 
            // Allocate a buffer for the control value buffer 
            mxcd.cChannels = 1;
            vol.dwValue = volume;
 
            // Copy the data into the control value buffer 
            mxcd.paDetails = Marshal.AllocCoTaskMem(Marshal.SizeOf(
                typeof(MIXERCONTROLDETAILS_UNSIGNED)));
            Marshal.StructureToPtr(vol, mxcd.paDetails, false);
 
            // Set the control value 
            rc = mixerSetControlDetails(hmixer, ref mxcd,
                MIXER_SETCONTROLDETAILSF_VALUE);
 
            if (MMSYSERR_NOERROR == rc)
            {
                retValue = true;
            }
            else
            {
                retValue = false;
            }
            return retValue;
        }
 
        /// <summary>
        /// 薄仙税 瑳拳聖 亜閃身
        /// </summary>
        /// <returns></returns>
        public static int GetVolume()
        {
            int mixer;
            int currentVol;
            MIXERCONTROL volCtrl = new MIXERCONTROL();
            mixerOpen(out mixer, 0000);
            int type = MIXERCONTROL_CONTROLTYPE_VOLUME;
            GetVolumeControl(mixer,
                MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, type, out volCtrl, out
                currentVol);
            mixerClose(mixer);
 
            return currentVol;
        }
 
        /// <summary>
        /// ボリューム設定
        /// </summary>
        /// <param name="vVolume"></param>
        public static void SetVolume(int vVolume)
        {
            int mixer;
            MIXERCONTROL volCtrl = new MIXERCONTROL();
            int currentVol;
            mixerOpen(out mixer, 0000);
            int type = MIXERCONTROL_CONTROLTYPE_VOLUME;
            GetVolumeControl(mixer,
                MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, type, out volCtrl, out
                currentVol);
            if (vVolume > volCtrl.lMaximum) vVolume = volCtrl.lMaximum;
            if (vVolume < volCtrl.lMinimum) vVolume = volCtrl.lMinimum;
            SetVolumeControl(mixer, volCtrl, vVolume);
            GetVolumeControl(mixer,
                MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, type, out volCtrl, out
                currentVol);
            if (vVolume != currentVol)
            {
                throw new Exception("Cannot Set Volume");
            }
            mixerClose(mixer);
        }
 
        /// <summary>
        /// ボリュームをパーセントで設定
        /// </summary>
        /// <param name="iPercent"></param>
        public static void SetVolumePercent(int iPercent)
        {
            int iVolumn = 0;
            int mixer;
            int currentVol;
 
            if (iPercent < 0)
                iPercent = 0;
            else if (iPercent > 100)
                iPercent = 100;
 
            MIXERCONTROL volCtrl = new MIXERCONTROL();
            mixerOpen(out mixer, 0000);
            int type = MIXERCONTROL_CONTROLTYPE_VOLUME;
            GetVolumeControl(mixer,
                MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, type, out volCtrl, out
                currentVol);
 
            iVolumn = volCtrl.lMaximum * iPercent / 100;
 
            SetVolumeControl(mixer, volCtrl, iVolumn);
            GetVolumeControl(mixer,
                MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, type, out volCtrl, out
                currentVol);
            if (iVolumn != currentVol)
            {
                throw new Exception("Cannot Set Volume");
            }
            mixerClose(mixer);
 
        }
 
        /// <summary>
        /// WAVファイルを流す
        /// </summary>
        /// <param name="filename">Wavファイルプルパス</param>
        /// <param name="loop">true:再生成功, false:再生失敗</param>
        public static void PlaySound(string filename, bool loop)
        {
            if (File.Exists(filename))
            {
                if (loop)
                    sndPlaySound(filename, (int)SND.SND_ASYNC | (int)SND.SND_LOOP);
                else
                    sndPlaySound(filename, (int)SND.SND_ASYNC);
            }
        }
 
        private Microsoft.VisualBasic.Devices.Audio audio = new Microsoft.VisualBasic.Devices.Audio();
 
        /// <summary>
        /// 指定したファイルを流す
        /// </summary>
        /// <param name="wavFilePath"></param>
        public void PlaySoundWav(string wavFilePath)
        {
            //バックグラウンドでWAVを再生する
            audio.Play(wavFilePath, Microsoft.VisualBasic.AudioPlayMode.Background);
 
            //次のようにすると、ループ再生される
            //audio.Play(wavFilePath,
            //    Microsoft.VisualBasic.AudioPlayMode.BackgroundLoop);
 
            //次のようにすると、最後まで再生し終えるまで待機する
            //audio.Play(wavFilePath,
            //    Microsoft.VisualBasic.AudioPlayMode.WaitToComplete);
 
            //再生しているWAVを停止する
            audio.Stop();
        }
    }
}
 
cs

화면의 코드는 아래와같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
using System;
using System.Windows.Forms;
using System.Media;
using Microsoft.VisualBasic;
 
namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            Console.Beep();
        }
 
        private void button2_Click(object sender, EventArgs e)
        {
            Interaction.Beep();
        }
        
        private void button3_Click(object sender, EventArgs e)
        {
            Console.Beep(10000500);
        }
        
        private void button4_Click(object sender, EventArgs e)
        {
            //SystemSounds.Beep.Play();
            //SystemSounds.Exclamation.Play();
            SystemSounds.Question.Play();
            //SystemSounds.Asterisk.Play();
            //SystemSounds.Hand.Play();
        }
 
        private void button5_Click(object sender, EventArgs e)
        {
            SoundUtils.SetVolumePercent(100);
        }
 
        private void button6_Click(object sender, EventArgs e)
        {
            SoundUtils.SetVolumePercent(50);
        }
 
        private void button7_Click(object sender, EventArgs e)
        {
            SoundUtils.SetVolumePercent(0);
        }
 
        private void button8_Click(object sender, EventArgs e)
        {
            SoundUtils.GetVolume();
 
            SoundUtils.SetVolume(100);
        }
    }
}
 
cs


실행시켜보면, 사운드 믹서에 해당어플이 등록되어서 볼륨제어가 가능해진다.

[실행전]

[실행후] & 100% 눌렀을때,

[실행후] & 50% 눌렀을때,

[실행후] & 0% 눌렀을때,


다른어플에 대해서도 어플ID 및 ProcessName 등을 통해서 사운드를 제어할수 있다.

추후과제

어플의 초기 기동시에 전체 권한을 부여받아서 Mute 를 해제하고,

Speaker 의 Sound Volume을 MAX 로 지정하고,

타어플에 대해서는 모든 Volume를 Min 0% 로 지정한 후,

지금의 어플동작을 실시하면, 모든 과제는 해결될 것으로 보여진다.

지금까지 만든 소스는 아래에 첨부한다.

SoundUtils.zip

SoundUtils.zip


password = toshu1203

반응형