Управление громкостью звука в системе
Управление громкостью звука в системе
Сегодня разберем пример, как программно изменить громкость звука в Windows. В старых версиях Windows для этого использовались waveform-функции с очень неудобными параметрами. Управлять громкостью с помощью эмуляции мультимедийных клавиш тоже не самое лучшее решение. Но, к счастью, прогресс не стоит на месте.
Начиная с Windows Vista, для управления звуком появился специализированный COM-интерфейс IAudioEndpointVolume. Как и в случае с другими COM-объектами, для работы нам понадобится целая пачка GUID'ов и описаний интерфейсов.
Code (Assembler) : Убрать нумерацию
- ; GUID {BCDE0395-E52F-467C-8E3D-C4579291692E}
- CLSID_MMDeviceEnumerator dd 0BCDE0395h
- dw 0E52Fh
- dw 0467Ch
- db 08Eh, 03Dh, 0C4h, 057h, 092h, 091h, 069h, 02Eh
- ; GUID {A95664D2-9614-4F35-A746-DE8DB63617E6}
- IID_IMMDeviceEnumerator dd 0A95664D2h
- dw 09614h
- dw 04F35h
- db 0A7h, 046h, 0DEh, 08Dh, 0B6h, 036h, 017h, 0E6h
- ; GUID {5CDF2C82-841E-4546-9722-0CF74078229A}
- IID_IAudioEndpointVolume dd 05CDF2C82h
- dw 0841Eh
- dw 04546h
- db 097h, 022h, 00Ch, 0F7h, 040h, 078h, 022h, 09Ah
- ; IID_IMMDeviceEnumerator Interface
- struct IMMDeviceEnumerator
- ; IUnknown
- QueryInterface dd ? ; 000h
- AddRef dd ? ; 004h
- Release dd ? ; 008h
- ; IMMDeviceEnumerator
- EnumAudioEndpoints dd ? ; 00Ch
- GetDefaultAudioEndpoint dd ? ; 010h
- GetDevice dd ? ; 014h
- RegisterEndpointNotificationCallback dd ? ; 018h
- UnregisterEndpointNotificationCallback dd ? ; 01Ch
- ends
- ; IID_IMMDevice Interface
- struct IMMDevice
- ; IUnknown
- QueryInterface dd ? ; 000h
- AddRef dd ? ; 004h
- Release dd ? ; 008h
- ; IMMDevice
- Activate dd ? ; 00Ch
- OpenPropertyStore dd ? ; 010h
- GetId dd ? ; 014h
- GetState dd ? ; 018h
- ends
- ; IID_IAudioEndpointVolume Interface
- struct IAudioEndpointVolume
- ; IUnknown
- QueryInterface dd ? ; 000h
- AddRef dd ? ; 004h
- Release dd ? ; 008h
- ; IAudioEndpointVolume
- RegisterControlChangeNotify dd ? ; 00Ch
- UnregisterControlChangeNotify dd ? ; 010h
- GetChannelCount dd ? ; 014h
- SetMasterVolumeLevel dd ? ; 018h
- SetMasterVolumeLevelScalar dd ? ; 01Ch
- GetMasterVolumeLevel dd ? ; 020h
- GetMasterVolumeLevelScalar dd ? ; 024h
- SetChannelVolumeLevel dd ? ; 028h
- SetChannelVolumeLevelScalar dd ? ; 02Ch
- GetChannelVolumeLevel dd ? ; 030h
- GetChannelVolumeLevelScalar dd ? ; 034h
- SetMute dd ? ; 038h
- GetMute dd ? ; 03Ch
- GetVolumeStepInfo dd ? ; 040h
- VolumeStepUp dd ? ; 044h
- VolumeStepDown dd ? ; 048h
- QueryHardwareSupport dd ? ; 04Ch
- GetVolumeRange dd ? ; 050h
- ends
Code (Assembler) : Убрать нумерацию
- ; Инициализировать COM-объект
- invoke CoInitialize,NULL
- ; Создать объект
- invoke CoCreateInstance,CLSID_MMDeviceEnumerator,NULL,\
- CLSCTX_INPROC_SERVER,\
- IID_IMMDeviceEnumerator,deviceEnumerator
- ; Инициализировать объект
- mov eax, [deviceEnumerator]
- mov eax, [eax]
- stdcall [eax+IMMDeviceEnumerator.GetDefaultAudioEndpoint],\
- [deviceEnumerator],[eRender],[eConsole],defaultDevice
- mov eax, [deviceEnumerator]
- mov eax, [eax]
- stdcall [eax+IMMDeviceEnumerator.Release],[deviceEnumerator]
- mov eax, [defaultDevice]
- mov eax, [eax]
- stdcall [eax+IMMDevice.Activate],[defaultDevice],\
- IID_IAudioEndpointVolume,CLSCTX_INPROC_SERVER,\
- NULL,endpointVolume
- mov eax, [defaultDevice]
- mov eax, [eax]
- stdcall [eax+IMMDevice.Release],[defaultDevice]
Code (Assembler) : Убрать нумерацию
- ; Текущее значение громкости
- fLevel dd 0.0
- ; Множитель для нормализации значения
- fMult dd 100
- ...
- ...
- .get_volume:
- ; Получить значение громкости
- mov eax, [endpointVolume]
- mov eax, [eax]
- stdcall [eax+IAudioEndpointVolume.GetMasterVolumeLevelScalar],\
- [endpointVolume],fLevel
- ; Привести значение к процентному и целочисленному
- fld [fLevel]
- fimul [fMult]
- fistp [result]
- mov eax,[result]
- ; EAX - текущее значение громкости [0..100]
Code (Assembler) : Убрать нумерацию
- .volume_up:
- ; Увеличить громкость
- mov eax, [endpointVolume]
- mov eax, [eax]
- stdcall [eax+IAudioEndpointVolume.VolumeStepUp],\
- [endpointVolume],NULL
- .volume_down:
- ; Уменьшить громкость
- mov eax, [endpointVolume]
- mov eax, [eax]
- stdcall [eax+IAudioEndpointVolume.VolumeStepDown],\
- [endpointVolume],NULL
Code (Assembler) : Убрать нумерацию
- ; Фиксированная громкость 20%
- fFixed dd 0.2
- ...
- ...
- .volume_fix:
- ; Громкость 20%
- mov eax, [endpointVolume]
- mov eax, [eax]
- stdcall [eax+IAudioEndpointVolume.SetMasterVolumeLevelScalar],\
- [endpointVolume],[fFixed],NULL
Code (Assembler) : Убрать нумерацию
- .volume_mute:
- ; Отключить/включить звук
- mov eax, [endpointVolume]
- mov eax, [eax]
- stdcall [eax+IAudioEndpointVolume.GetMute],\
- [endpointVolume],bMute
- ; Поменять значение на противоположное
- xor ebx,ebx
- cmp [bMute],0
- sete bl
- ; Установить состояние Mute
- mov eax, [endpointVolume]
- mov eax, [eax]
- stdcall [eax+IAudioEndpointVolume.SetMute],\
- [endpointVolume],ebx,NULL
Кроме управления звуком ваша программа может отслеживать изменения звука, сделанные другими приложениями. Для этого надо подписаться на уведомления. Но, как обычно, сперва описание недостающих данных.
Code (Assembler) : Убрать нумерацию
- ; GUID {00000000-0000-0000-C000-000000000046}
- IID_IUnknown dd 000000000h
- dw 00000h
- dw 00000h
- db 0C0h, 000h, 000h, 000h, 000h, 000h, 000h, 046h
- ; GUID {657804FA-D6AD-4496-8A60-352752AF4F89}
- IID_IAudioEndpointVolumeCallback dd 0657804FAh
- dw 0D6ADh
- dw 04496h
- db 08Ah,060h,035h,027h,052h,0AFh,04Fh,089h
- ; IID_IAudioEndpointVolumeCallback Interface
- struct IAudioEndpointVolumeCallback
- ; IUnknown
- QueryInterface dd ? ; 000h
- AddRef dd ? ; 004h
- Release dd ? ; 008h
- ; IAudioEndpointVolumeCallback
- OnNotify dd ? ; 00Ch
- refcount dd ?
- ends
- struct AUDIO_VOLUME_NOTIFICATION_DATA
- guidEventContext rd 4
- bMuted dd ?
- fMasterVolume dd ?
- nChannels dd ?
- afChannelVolumes dd ?
- ends
- notificationCallback IAudioEndpointVolumeCallback
- pNotificationCallback dd ?
Code (Assembler) : Убрать нумерацию
- ; Настроить интерфейс уведомлений
- mov [notificationCallback.QueryInterface],\
- notificationCallback_QueryInterface
- mov [notificationCallback.AddRef],notificationCallback_AddRef
- mov [notificationCallback.Release],notificationCallback_Release
- mov [notificationCallback.OnNotify],notificationCallback_OnNotify
- mov [notificationCallback.refcount],0
- mov [pNotificationCallback],notificationCallback
- mov eax, [endpointVolume]
- mov eax, [eax]
- stdcall [eax+IAudioEndpointVolume.RegisterControlChangeNotify],\
- [endpointVolume],pNotificationCallback
Code (Assembler) : Убрать нумерацию
- ;------------------------------------------------------------------------
- ; Метод QueryInterface
- ;------------------------------------------------------------------------
- proc notificationCallback_QueryInterface pthis:DWORD, iid:DWORD,\
- ppvObject:DWORD
- pusha
- mov eax,[ppvObject]
- cmp eax,0
- jne @f
- ; E_POINTER
- mov eax,0x80004003
- jmp .loc_ret
- @@:
- ; Это интерфейс IAudioEndpointVolumeCallback?
- push 4
- pop ecx
- mov esi,[iid]
- mov edi,IID_IAudioEndpointVolumeCallback
- xor eax,eax
- repe cmpsd
- jz .loc_call
- ; Это интерфейс IUnknown?
- push 4
- pop ecx
- mov esi,[iid]
- mov edi,IID_IUnknown
- xor eax,eax
- repe cmpsd
- jz .loc_call
- ; E_NOINTERFACE
- mov eax,0x80004002
- jmp .loc_ret
- .loc_call:
- mov eax,[pthis]
- ; Установить интерфейс
- mov ecx,[ppvObject]
- mov [ecx],eax
- mov ecx,[eax]
- stdcall dword [ecx+IAudioEndpointVolumeCallback.AddRef],eax
- .loc_ok:
- ; S_OK
- xor eax,eax
- .loc_ret:
- mov [esp+28],eax
- popa
- ret
- endp
- ;------------------------------------------------------------------------
- ; Метод AddRef
- ;------------------------------------------------------------------------
- proc notificationCallback_AddRef pthis:DWORD
- mov eax,[pthis]
- lock inc [eax+IAudioEndpointVolumeCallback.refcount]
- mov eax,[eax+IAudioEndpointVolumeCallback.refcount]
- ret
- endp
- ;------------------------------------------------------------------------
- ; Метод Release
- ;------------------------------------------------------------------------
- proc notificationCallback_Release pthis:DWORD
- mov eax,[pthis]
- lock dec [eax+IAudioEndpointVolumeCallback.refcount]
- mov eax,[eax+IAudioEndpointVolumeCallback.refcount]
- ret
- endp
- ;------------------------------------------------------------------------
- ; Метод OnNotify - главный обработчик
- ;------------------------------------------------------------------------
- proc notificationCallback_OnNotify pthis:DWORD, pNotify:DWORD
- ; Обработка новых значений громкости и Mute
- ; из структуры AUDIO_VOLUME_NOTIFICATION_DATA
- ...
- ...
- ; S_OK
- xor eax,eax
- ret
- endp
Code (Assembler) : Убрать нумерацию
- mov eax, [endpointVolume]
- mov eax, [eax]
- stdcall [eax+IAudioEndpointVolume.UnregisterControlChangeNotify],\
- [endpointVolume],pNotificationCallback
Просмотров: 1582 | Комментариев: 9
Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
ManHunter
(24.12.2021 в 14:34):
Добавил в статью информацию об обработке изменения громкости и примеры кода.
ManHunter
(17.11.2021 в 11:42):
Потому что все уже починено.
wet
(17.11.2021 в 11:02):
У меня ваш пример отлично работает на win7 x64
ManHunter
(16.11.2021 в 17:50):
Вроде нашел в чем была проблема. Архив обновлен.
SMaSm-94
(16.11.2021 в 16:22):
Nemo, подтверждаю. У меня аналогично на Win8.1x64
Nemo
(15.11.2021 в 23:04):
Левые 2 кнопки аварийно завершают процесс (win8.1-64)
Тоже самое через COM на С: https://transfiles.ru/hob8d
Тоже самое через COM на С: https://transfiles.ru/hob8d
DRON
(15.11.2021 в 23:04):
Так тоже не очень: сишный BOOL (не путать с bool) это четыре байта, а не один. Так что выделить bMute через dd, а затем:
cmp [bMute],1
sbb edx,edx
neg edx
...
stdcall [eax+IAudioEndpointVolume.SetMute],\
[endpointVolume],edx,NULL
cmp [bMute],1
sbb edx,edx
neg edx
...
stdcall [eax+IAudioEndpointVolume.SetMute],\
[endpointVolume],edx,NULL
ManHunter
(15.11.2021 в 22:00):
Жаль, такая хорошая оптимизация пропадает. Ну ничо, заменю на что-нибудь чуть менее красивое.
DRON
(15.11.2021 в 20:42):
Так делать нельзя:
dec [bMute]
neg [bMute]
Дело в том, что винда не проверяет параметры SetMute и запоминает то что ей дали, то есть после вызова SetMute(2) вызов GetMute вернёт именно 2, а dec+neg вернёт -1, что приравнивается к TRUE. Интересно, что эта ошибка присутствует и в самой винде, то есть можно подбором величин выключить звук, но иконка в трее будет без красного значка (или наоборот, уже не помню).
dec [bMute]
neg [bMute]
Дело в том, что винда не проверяет параметры SetMute и запоминает то что ей дали, то есть после вызова SetMute(2) вызов GetMute вернёт именно 2, а dec+neg вернёт -1, что приравнивается к TRUE. Интересно, что эта ошибка присутствует и в самой винде, то есть можно подбором величин выключить звук, но иконка в трее будет без красного значка (или наоборот, уже не помню).
Добавить комментарий
Заполните форму для добавления комментария