Blog. Just Blog

Управление громкостью звука в системе

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Управление громкостью звука в системе
Управление громкостью звука в системе

Сегодня разберем пример, как программно изменить громкость звука в Windows. В старых версиях Windows для этого использовались waveform-функции с очень неудобными параметрами. Управлять громкостью с помощью эмуляции мультимедийных клавиш тоже не самое лучшее решение. Но, к счастью, прогресс не стоит на месте.

Начиная с Windows Vista, для управления звуком появился специализированный COM-интерфейс IAudioEndpointVolume. Как и в случае с другими COM-объектами, для работы нам понадобится целая пачка GUID'ов и описаний интерфейсов.
  1. ; GUID {BCDE0395-E52F-467C-8E3D-C4579291692E}
  2. CLSID_MMDeviceEnumerator dd 0BCDE0395h
  3.                          dw 0E52Fh
  4.                          dw 0467Ch
  5.                          db 08Eh, 03Dh, 0C4h, 057h, 092h, 091h, 069h, 02Eh
  6.  
  7. ; GUID {A95664D2-9614-4F35-A746-DE8DB63617E6}
  8. IID_IMMDeviceEnumerator dd 0A95664D2h
  9.                         dw 09614h
  10.                         dw 04F35h
  11.                         db 0A7h, 046h, 0DEh, 08Dh, 0B6h, 036h, 017h, 0E6h
  12.  
  13. ; GUID {5CDF2C82-841E-4546-9722-0CF74078229A}
  14. IID_IAudioEndpointVolume dd 05CDF2C82h
  15.                          dw 0841Eh
  16.                          dw 04546h
  17.                          db 097h, 022h, 00Ch, 0F7h, 040h, 078h, 022h, 09Ah
  18.  
  19. ; IID_IMMDeviceEnumerator Interface
  20. struct IMMDeviceEnumerator
  21.     ; IUnknown
  22.     QueryInterface                         dd ?   ; 000h
  23.     AddRef                                 dd ?   ; 004h
  24.     Release                                dd ?   ; 008h
  25.     ; IMMDeviceEnumerator
  26.     EnumAudioEndpoints                     dd ?   ; 00Ch
  27.     GetDefaultAudioEndpoint                dd ?   ; 010h
  28.     GetDevice                              dd ?   ; 014h
  29.     RegisterEndpointNotificationCallback   dd ?   ; 018h
  30.     UnregisterEndpointNotificationCallback dd ?   ; 01Ch
  31. ends
  32.  
  33. ; IID_IMMDevice Interface
  34. struct IMMDevice
  35.     ; IUnknown
  36.     QueryInterface    dd ?   ; 000h
  37.     AddRef            dd ?   ; 004h
  38.     Release           dd ?   ; 008h
  39.     ; IMMDevice
  40.     Activate          dd ?   ; 00Ch
  41.     OpenPropertyStore dd ?   ; 010h
  42.     GetId             dd ?   ; 014h
  43.     GetState          dd ?   ; 018h
  44. ends
  45.  
  46. ; IID_IAudioEndpointVolume Interface
  47. struct IAudioEndpointVolume
  48.     ; IUnknown
  49.     QueryInterface                dd ?   ; 000h
  50.     AddRef                        dd ?   ; 004h
  51.     Release                       dd ?   ; 008h
  52.     ; IAudioEndpointVolume
  53.     RegisterControlChangeNotify   dd ?   ; 00Ch
  54.     UnregisterControlChangeNotify dd ?   ; 010h
  55.     GetChannelCount               dd ?   ; 014h
  56.     SetMasterVolumeLevel          dd ?   ; 018h
  57.     SetMasterVolumeLevelScalar    dd ?   ; 01Ch
  58.     GetMasterVolumeLevel          dd ?   ; 020h
  59.     GetMasterVolumeLevelScalar    dd ?   ; 024h
  60.     SetChannelVolumeLevel         dd ?   ; 028h
  61.     SetChannelVolumeLevelScalar   dd ?   ; 02Ch
  62.     GetChannelVolumeLevel         dd ?   ; 030h
  63.     GetChannelVolumeLevelScalar   dd ?   ; 034h
  64.     SetMute                       dd ?   ; 038h
  65.     GetMute                       dd ?   ; 03Ch
  66.     GetVolumeStepInfo             dd ?   ; 040h
  67.     VolumeStepUp                  dd ?   ; 044h
  68.     VolumeStepDown                dd ?   ; 048h
  69.     QueryHardwareSupport          dd ?   ; 04Ch
  70.     GetVolumeRange                dd ?   ; 050h
  71. ends
Чтобы выйти на методы интерфейса IAudioEndpointVolume, сперва потребуется получить интерфейс дефолтного звукового устройства, затем активировать его, и только потом у нас будет доступ к функциям управления звуком.
  1.         ; Инициализировать COM-объект
  2.         invoke  CoInitialize,NULL
  3.  
  4.         ; Создать объект
  5.         invoke  CoCreateInstance,CLSID_MMDeviceEnumerator,NULL,\
  6.                 CLSCTX_INPROC_SERVER,\
  7.                 IID_IMMDeviceEnumerator,deviceEnumerator
  8.  
  9.         ; Инициализировать объект
  10.         mov     eax, [deviceEnumerator]
  11.         mov     eax, [eax]
  12.         stdcall [eax+IMMDeviceEnumerator.GetDefaultAudioEndpoint],\
  13.                 [deviceEnumerator],[eRender],[eConsole],defaultDevice
  14.  
  15.         mov     eax, [deviceEnumerator]
  16.         mov     eax, [eax]
  17.         stdcall [eax+IMMDeviceEnumerator.Release],[deviceEnumerator]
  18.  
  19.         mov     eax, [defaultDevice]
  20.         mov     eax, [eax]
  21.         stdcall [eax+IMMDevice.Activate],[defaultDevice],\
  22.                 IID_IAudioEndpointVolume,CLSCTX_INPROC_SERVER,\
  23.                 NULL,endpointVolume
  24.  
  25.         mov     eax, [defaultDevice]
  26.         mov     eax, [eax]
  27.         stdcall [eax+IMMDevice.Release],[defaultDevice]
Если посмотреть документацию, то там будет достаточно много различных методов, вот ассемблерная реализация некоторых из них. Получение текущего уровня громкости системы. Значение возвращается в формате вещественного числа в интервале от 0.0 до 1.0. Для получения привычного процентного значения придется умножить его на 100.
  1. ; Текущее значение громкости
  2. fLevel           dd 0.0
  3. ; Множитель для нормализации значения 
  4. fMult            dd 100
  5. ...
  6. ...
  7. .get_volume:
  8.         ; Получить значение громкости
  9.         mov     eax, [endpointVolume]
  10.         mov     eax, [eax]
  11.         stdcall [eax+IAudioEndpointVolume.GetMasterVolumeLevelScalar],\
  12.                 [endpointVolume],fLevel
  13.  
  14.         ; Привести значение к процентному и целочисленному
  15.         fld     [fLevel]
  16.         fimul   [fMult]
  17.         fistp   [result]
  18.         mov     eax,[result]
  19.         ; EAX - текущее значение громкости [0..100]
Увеличение и уменьшение громкости системы на один шаг. Значение шага можно узнать с помощью метода GetVolumeStepInfo.
  1. .volume_up:
  2.         ; Увеличить громкость
  3.         mov     eax, [endpointVolume]
  4.         mov     eax, [eax]
  5.         stdcall [eax+IAudioEndpointVolume.VolumeStepUp],\
  6.                 [endpointVolume],NULL
  7.  
  8. .volume_down:
  9.         ; Уменьшить громкость
  10.         mov     eax, [endpointVolume]
  11.         mov     eax, [eax]
  12.         stdcall [eax+IAudioEndpointVolume.VolumeStepDown],\
  13.                 [endpointVolume],NULL
Установка фиксированного значения громкости. Новый уровень громкости должен быть в формате вещественного числа и иметь значение в интервале от 0.0 до 1.0.
  1. ; Фиксированная громкость 20%
  2. fFixed           dd 0.2
  3. ...
  4. ...
  5. .volume_fix:
  6.         ; Громкость 20%
  7.         mov     eax, [endpointVolume]
  8.         mov     eax, [eax]
  9.         stdcall [eax+IAudioEndpointVolume.SetMasterVolumeLevelScalar],\
  10.                 [endpointVolume],[fFixed],NULL
Включение и отключение звука, а также получение текущего состояния Mute. Нулевое значение - звук включен, любое другое значение - звук выключен.
  1. .volume_mute:
  2.         ; Отключить/включить звук
  3.         mov     eax, [endpointVolume]
  4.         mov     eax, [eax]
  5.         stdcall [eax+IAudioEndpointVolume.GetMute],\
  6.                 [endpointVolume],bMute
  7.  
  8.         ; Поменять значение на противоположное
  9.         xor     ebx,ebx
  10.         cmp     [bMute],0
  11.         sete    bl
  12.  
  13.         ; Установить состояние Mute
  14.         mov     eax, [endpointVolume]
  15.         mov     eax, [eax]
  16.         stdcall [eax+IAudioEndpointVolume.SetMute],\
  17.                 [endpointVolume],ebx,NULL
Конечно, это не все методы для управления уровнем громкости звука в системе, но их вполне достаточно для большинства прикладных программ. Остальные методы вы можете реализовать по аналогии с описанными выше.

Кроме управления звуком ваша программа может отслеживать изменения звука, сделанные другими приложениями. Для этого надо подписаться на уведомления. Но, как обычно, сперва описание недостающих данных.
  1. ; GUID {00000000-0000-0000-C000-000000000046}
  2. IID_IUnknown dd 000000000h
  3.              dw 00000h
  4.              dw 00000h
  5.              db 0C0h, 000h, 000h, 000h, 000h, 000h, 000h, 046h
  6.  
  7. ; GUID {657804FA-D6AD-4496-8A60-352752AF4F89}
  8. IID_IAudioEndpointVolumeCallback dd 0657804FAh
  9.                                  dw 0D6ADh
  10.                                  dw 04496h
  11.                                  db 08Ah,060h,035h,027h,052h,0AFh,04Fh,089h
  12.  
  13. ; IID_IAudioEndpointVolumeCallback Interface
  14. struct IAudioEndpointVolumeCallback
  15.     ; IUnknown
  16.     QueryInterface dd ?   ; 000h
  17.     AddRef         dd ?   ; 004h
  18.     Release        dd ?   ; 008h
  19.     ; IAudioEndpointVolumeCallback
  20.     OnNotify       dd ?   ; 00Ch
  21.     refcount       dd ?
  22. ends
  23.  
  24. struct AUDIO_VOLUME_NOTIFICATION_DATA
  25.     guidEventContext rd 4
  26.     bMuted           dd ?
  27.     fMasterVolume    dd ?
  28.     nChannels        dd ?
  29.     afChannelVolumes dd ?
  30. ends
  31.  
  32. notificationCallback   IAudioEndpointVolumeCallback
  33. pNotificationCallback  dd ?
Настраиваем методы интерфейса IAudioEndpointVolumeCallback, после этого с помощью метода RegisterControlChangeNotify регистрируем нашу программу в качестве обработчика событий изменения звука.
  1.         ; Настроить интерфейс уведомлений
  2.         mov     [notificationCallback.QueryInterface],\
  3.                 notificationCallback_QueryInterface
  4.         mov     [notificationCallback.AddRef],notificationCallback_AddRef
  5.         mov     [notificationCallback.Release],notificationCallback_Release
  6.         mov     [notificationCallback.OnNotify],notificationCallback_OnNotify
  7.         mov     [notificationCallback.refcount],0
  8.  
  9.         mov     [pNotificationCallback],notificationCallback
  10.  
  11.         mov     eax, [endpointVolume]
  12.         mov     eax, [eax]
  13.         stdcall [eax+IAudioEndpointVolume.RegisterControlChangeNotify],\
  14.                 [endpointVolume],pNotificationCallback
Методы интерфейса Unknown стандартные, а основная магия происходит в методе OnNotify. После любого изменения громкости этому методу в качестве параметра передается указатель на структуру AUDIO_VOLUME_NOTIFICATION_DATA, в которой содержится новое значение громкости и состояние Mute. Этого вполне достаточно для большинства ситуаций.
  1. ;------------------------------------------------------------------------
  2. ; Метод QueryInterface
  3. ;------------------------------------------------------------------------
  4. proc notificationCallback_QueryInterface pthis:DWORD, iid:DWORD,\
  5.         ppvObject:DWORD
  6.  
  7.         pusha
  8.  
  9.         mov     eax,[ppvObject]
  10.         cmp     eax,0
  11.         jne     @f
  12.         ; E_POINTER
  13.         mov     eax,0x80004003
  14.         jmp     .loc_ret
  15. @@:
  16.         ; Это интерфейс IAudioEndpointVolumeCallback?
  17.         push    4
  18.         pop     ecx
  19.         mov     esi,[iid]
  20.         mov     edi,IID_IAudioEndpointVolumeCallback
  21.         xor     eax,eax
  22.         repe    cmpsd
  23.         jz      .loc_call
  24.  
  25.         ; Это интерфейс IUnknown?
  26.         push    4
  27.         pop     ecx
  28.         mov     esi,[iid]
  29.         mov     edi,IID_IUnknown
  30.         xor     eax,eax
  31.         repe    cmpsd
  32.         jz      .loc_call
  33.  
  34.         ; E_NOINTERFACE
  35.         mov     eax,0x80004002
  36.         jmp     .loc_ret
  37. .loc_call:
  38.         mov     eax,[pthis]
  39.         ; Установить интерфейс
  40.         mov     ecx,[ppvObject]
  41.         mov     [ecx],eax
  42.         mov     ecx,[eax]
  43.         stdcall dword [ecx+IAudioEndpointVolumeCallback.AddRef],eax
  44. .loc_ok:
  45.         ; S_OK
  46.         xor     eax,eax
  47. .loc_ret:
  48.         mov     [esp+28],eax
  49.         popa
  50.         ret
  51. endp
  52.  
  53. ;------------------------------------------------------------------------
  54. ; Метод AddRef
  55. ;------------------------------------------------------------------------
  56. proc notificationCallback_AddRef pthis:DWORD
  57.         mov     eax,[pthis]
  58.         lock    inc [eax+IAudioEndpointVolumeCallback.refcount]
  59.         mov     eax,[eax+IAudioEndpointVolumeCallback.refcount]
  60.         ret
  61. endp
  62.  
  63. ;------------------------------------------------------------------------
  64. ; Метод Release
  65. ;------------------------------------------------------------------------
  66. proc notificationCallback_Release pthis:DWORD
  67.         mov     eax,[pthis]
  68.         lock    dec [eax+IAudioEndpointVolumeCallback.refcount]
  69.         mov     eax,[eax+IAudioEndpointVolumeCallback.refcount]
  70.         ret
  71. endp
  72.  
  73. ;------------------------------------------------------------------------
  74. ; Метод OnNotify - главный обработчик
  75. ;------------------------------------------------------------------------
  76. proc notificationCallback_OnNotify pthis:DWORD, pNotify:DWORD
  77.         ; Обработка новых значений громкости и Mute
  78.         ; из структуры AUDIO_VOLUME_NOTIFICATION_DATA
  79.         ...
  80.         ...
  81.         ; S_OK
  82.         xor     eax,eax
  83.         ret
  84. endp
Когда отслеживание изменения звука больше не требуется, установленный обработчик можно снять с помощью метода UnregisterControlChangeNotify.
  1.         mov     eax, [endpointVolume]
  2.         mov     eax, [eax]
  3.         stdcall [eax+IAudioEndpointVolume.UnregisterControlChangeNotify],\
  4.                 [endpointVolume],pNotificationCallback
В приложении пример программы с исходным текстом, которая позволяет управлять общей громкостью звуков в системе, а также отображает текущее значение громкости.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Volume.Control.Demo.zip (3,780 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 1582 | Комментариев: 9

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (24.12.2021 в 14:34):
Добавил в статью информацию об обработке изменения громкости и примеры кода.
ManHunter (17.11.2021 в 11:42):
Потому что все уже починено.
wet (17.11.2021 в 11:02):
ЦитатаНачиная с Windows Vista, для управления звуком появился специализированный COM-интерфейс

У меня ваш пример отлично работает на 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
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
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. Интересно, что эта ошибка присутствует и в самой винде, то есть можно подбором величин выключить звук, но иконка в трее будет без красного значка (или наоборот, уже не помню).

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0055 сек.) / Память: 4.5 Mb
Наверх