#include "media/audio/win/audio_ducker_win.h"
#include <MMDeviceAPI.h>
#include <audioclient.h>
#include <audiopolicy.h>
#include <wrl/client.h>
#include "base/functional/callback.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/bind_post_task.h"
#include "base/win/scoped_co_mem.h"
#include "media/audio/win/audio_device_listener_win.h"
#include "media/audio/win/audio_session_creation_observer_win.h"
#include "media/audio/win/core_audio_util_win.h"
#include "media/base/media_switches.h"
using Microsoft::WRL::ComPtr;
namespace {
bool GetAudioSessionManager(ComPtr<IAudioSessionManager2>& out_manager) {
ComPtr<IMMDeviceEnumerator> device_enumerator =
media::CoreAudioUtil::CreateDeviceEnumerator();
if (!device_enumerator) {
return false;
}
ComPtr<IMMDevice> device;
HRESULT hr =
device_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
if (!SUCCEEDED(hr)) {
return false;
}
ComPtr<IAudioSessionManager2> audio_session_manager;
hr = device->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, nullptr,
&audio_session_manager);
if (!SUCCEEDED(hr)) {
return false;
}
out_manager = audio_session_manager;
return true;
}
bool ForEachAudioSession(
ComPtr<IAudioSessionManager2>& audio_session_manager,
base::RepeatingCallback<void(ComPtr<IAudioSessionControl2>& session)>
callback) {
ComPtr<IAudioSessionEnumerator> session_enumerator;
HRESULT hr = audio_session_manager->GetSessionEnumerator(&session_enumerator);
if (!SUCCEEDED(hr)) {
return false;
}
int session_count;
hr = session_enumerator->GetCount(&session_count);
if (!SUCCEEDED(hr)) {
return false;
}
bool no_errors = true;
for (int i = 0; i < session_count; i++) {
ComPtr<IAudioSessionControl> session_control;
hr = session_enumerator->GetSession(i, &session_control);
if (!SUCCEEDED(hr)) {
no_errors = false;
continue;
}
ComPtr<IAudioSessionControl2> session_control_2;
hr = session_control->QueryInterface(IID_PPV_ARGS(&session_control_2));
if (!SUCCEEDED(hr)) {
no_errors = false;
continue;
}
callback.Run(session_control_2);
}
return no_errors;
}
float GetAttenuationMultiplier() {
return 1.0 -
(std::clamp(media::kAudioDuckingAttenuation.Get(), 0, 100) / 100.0);
}
void RecordSessionUnduckResult(bool success) {
base::UmaHistogramBoolean("Media.AudioDuckerWin.UnduckSessionResult",
success);
}
}
namespace media {
AudioDuckerWin::AudioDuckerWin(ShouldDuckProcessCallback callback)
: should_duck_process_callback_(callback) {}
AudioDuckerWin::~AudioDuckerWin() {
StopDuckingOtherWindowsApplications();
}
void AudioDuckerWin::StartDuckingOtherWindowsApplications() {
if (ducked_audio_session_manager_) {
return;
}
if (!GetAudioSessionManager(ducked_audio_session_manager_)) {
return;
}
if (!session_creation_observer_) {
session_creation_observer_ =
std::make_unique<AudioSessionCreationObserverWin>(
base::BindPostTaskToCurrentDefault(base::BindRepeating(
&AudioDuckerWin::DuckNewAudioSessionsIfNecessary,
weak_factory_.GetWeakPtr())));
}
ducked_audio_session_manager_->RegisterSessionNotification(
session_creation_observer_.get());
ForEachAudioSession(
ducked_audio_session_manager_,
base::BindRepeating(&AudioDuckerWin::StartDuckingAudioSessionIfNecessary,
base::Unretained(this)));
if (!output_device_listener_) {
output_device_listener_ = std::make_unique<AudioDeviceListenerWin>(
base::BindPostTaskToCurrentDefault(
base::BindRepeating(&AudioDuckerWin::OnDefaultDeviceChanged,
weak_factory_.GetWeakPtr())));
}
}
void AudioDuckerWin::StopDuckingOtherWindowsApplications() {
if (ducked_applications_.empty()) {
if (ducked_audio_session_manager_) {
CHECK(session_creation_observer_);
ducked_audio_session_manager_->UnregisterSessionNotification(
session_creation_observer_.get());
ducked_audio_session_manager_.Reset();
}
return;
}
CHECK(ducked_audio_session_manager_);
CHECK(session_creation_observer_);
bool no_errors = ForEachAudioSession(
ducked_audio_session_manager_,
base::BindRepeating(&AudioDuckerWin::StopDuckingAudioSessionIfNecessary,
base::Unretained(this)));
base::UmaHistogramBoolean("Media.AudioDuckerWin.UnduckSessionIterationResult",
no_errors);
ducked_applications_.clear();
ducked_audio_session_manager_->UnregisterSessionNotification(
session_creation_observer_.get());
ducked_audio_session_manager_.Reset();
}
void AudioDuckerWin::StartDuckingAudioSessionIfNecessary(
ComPtr<IAudioSessionControl2>& session) {
base::ProcessId process_id = 0;
HRESULT hr = session->GetProcessId(&process_id);
if (!SUCCEEDED(hr)) {
return;
}
if (!should_duck_process_callback_.Run(process_id)) {
return;
}
base::win::ScopedCoMem<wchar_t> session_id;
hr = session->GetSessionInstanceIdentifier(&session_id);
if (!SUCCEEDED(hr)) {
return;
}
auto iter = ducked_applications_.find(session_id.get());
if (iter != ducked_applications_.end()) {
return;
}
ComPtr<ISimpleAudioVolume> simple_audio_volume;
hr = session->QueryInterface(IID_PPV_ARGS(&simple_audio_volume));
if (!SUCCEEDED(hr)) {
return;
}
float current_volume = 0;
hr = simple_audio_volume->GetMasterVolume(¤t_volume);
if (!SUCCEEDED(hr)) {
return;
}
hr = simple_audio_volume->SetMasterVolume(
current_volume * GetAttenuationMultiplier(), nullptr);
if (!SUCCEEDED(hr)) {
return;
}
ducked_applications_.insert({session_id.get(), current_volume});
}
void AudioDuckerWin::StopDuckingAudioSessionIfNecessary(
ComPtr<IAudioSessionControl2>& session) {
base::win::ScopedCoMem<wchar_t> session_id;
HRESULT hr = session->GetSessionInstanceIdentifier(&session_id);
if (!SUCCEEDED(hr)) {
RecordSessionUnduckResult(false);
return;
}
auto iter = ducked_applications_.find(session_id.get());
if (iter == ducked_applications_.end()) {
return;
}
ComPtr<ISimpleAudioVolume> simple_audio_volume;
hr = session->QueryInterface(IID_PPV_ARGS(&simple_audio_volume));
if (!SUCCEEDED(hr)) {
RecordSessionUnduckResult(false);
return;
}
hr = simple_audio_volume->SetMasterVolume(iter->second, nullptr);
RecordSessionUnduckResult(SUCCEEDED(hr));
}
void AudioDuckerWin::OnDefaultDeviceChanged() {
if (!ducked_audio_session_manager_) {
return;
}
StopDuckingOtherWindowsApplications();
StartDuckingOtherWindowsApplications();
}
void AudioDuckerWin::DuckNewAudioSessionsIfNecessary() {
if (!ducked_audio_session_manager_) {
return;
}
ForEachAudioSession(
ducked_audio_session_manager_,
base::BindRepeating(&AudioDuckerWin::StartDuckingAudioSessionIfNecessary,
base::Unretained(this)));
}
}