blob: 82b2e3a6e71a6e23c6f46ec1866c425b532b8dea [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/audio/mac/audio_device_listener_mac.h"
#include <optional>
#include <vector>
#include "base/apple/osstatus_logging.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/thread_annotations.h"
#include "media/audio/audio_manager.h"
#include "media/audio/mac/core_audio_util_mac.h"
namespace media {
const AudioObjectPropertyAddress
AudioDeviceListenerMac::kDefaultOutputDeviceChangePropertyAddress = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
const AudioObjectPropertyAddress
AudioDeviceListenerMac::kDefaultInputDeviceChangePropertyAddress = {
kAudioHardwarePropertyDefaultInputDevice,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
const AudioObjectPropertyAddress
AudioDeviceListenerMac::kDevicesPropertyAddress = {
kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain};
const AudioObjectPropertyAddress
AudioDeviceListenerMac::kPropertyOutputSampleRateChanged = {
kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain};
const AudioObjectPropertyAddress
AudioDeviceListenerMac::kPropertyOutputSourceChanged = {
kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain};
const AudioObjectPropertyAddress
AudioDeviceListenerMac::kPropertyInputSourceChanged = {
kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput,
kAudioObjectPropertyElementMain};
class AudioDeviceListenerMac::PropertyListener {
public:
using SystemCallback =
base::OnceCallback<OSStatus(AudioObjectID inObjectID,
const AudioObjectPropertyAddress* inAddress,
AudioObjectPropertyListenerProc inListener,
void* inClientData)>;
// |remove_listener_callback| is guaranteed to be synchronously called in the
// deleter.
static std::unique_ptr<PropertyListener, PropertyListenerDeleter> Create(
AudioObjectID monitored_object,
const AudioObjectPropertyAddress* property,
base::RepeatingClosure on_change_callback,
SystemCallback add_listener_callback,
SystemCallback remove_listener_callback) {
std::unique_ptr<PropertyListener, PropertyListenerDeleter> listener(
new PropertyListener(monitored_object, property));
if (!listener->StartListening(std::move(on_change_callback),
std::move(add_listener_callback),
std::move(remove_listener_callback)))
listener.reset();
return listener;
}
// Public for testing.
static OSStatus OnEvent(AudioObjectID object,
UInt32 num_addresses,
const AudioObjectPropertyAddress addresses[],
void* context) {
if (context) {
static_cast<PropertyListener*>(context)->ProcessEvent(
object, std::vector<AudioObjectPropertyAddress>(
addresses, addresses + num_addresses));
}
return noErr;
}
private:
friend struct PropertyListenerDeleter;
PropertyListener(AudioObjectID monitored_object,
const AudioObjectPropertyAddress* property)
: monitored_object_(monitored_object),
property_(property),
task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
weak_this_for_events_ = weak_factory_.GetWeakPtr();
}
~PropertyListener() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!callback_);
DCHECK(remove_listener_callback_.is_null());
}
bool StartListening(base::RepeatingClosure callback,
SystemCallback add_listener_callback,
SystemCallback remove_listener_callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(callback_.is_null());
OSStatus result =
std::move(add_listener_callback)
.Run(monitored_object_, property_,
&AudioDeviceListenerMac::PropertyListener::OnEvent, this);
if (noErr == result) {
callback_ = std::move(callback);
remove_listener_callback_ = std::move(remove_listener_callback);
return true;
}
OSSTATUS_DLOG(ERROR, result) << "AddPropertyListener() failed!";
return false;
}
// Returns true if it stopped listening and false if it was not listening and
// so did not have to stop doing so.
bool MaybeStopListening() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (callback_.is_null())
return false;
DCHECK(!remove_listener_callback_.is_null());
OSStatus result =
std::move(remove_listener_callback_)
.Run(monitored_object_, property_,
&AudioDeviceListenerMac::PropertyListener::OnEvent, this);
OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
<< "RemovePropertyListener() failed!";
auto callback(std::move(callback_));
DCHECK(callback_.is_null());
return true;
}
void ProcessEvent(AudioObjectID object,
std::vector<AudioObjectPropertyAddress> properties) {
// This call can come from a different thread (for example it may happen for
// a device sample rate change notification). We only hope it does not race
// with the destructor (which we delay by means of a custom deleter), which
// may happen if a device parameter change notification races with device
// removal notification for the same device. Protecting |this| with a lock
// here and in the destructor will likely lead to a deadlock around
// AudioObjectRemovePropertyListener. So we just attempt to minimize a
// probablylity of a race by delaying the destructor and doing all the
// checks on the main thread.
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&PropertyListener::ProcessEvent, weak_this_for_events_,
object, std::move(properties)));
return;
}
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (object != monitored_object_)
return;
if (callback_.is_null())
return;
for (const auto& property : properties) {
if (property.mSelector == property_->mSelector &&
property.mScope == property_->mScope &&
property.mElement == property_->mElement) {
callback_.Run();
break;
}
}
}
const AudioObjectID monitored_object_;
const raw_ptr<const AudioObjectPropertyAddress> property_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
// Ensures the methods run on |task_runner_|.
THREAD_CHECKER(thread_checker_);
base::RepeatingClosure callback_ GUARDED_BY_CONTEXT(thread_checker_);
SystemCallback remove_listener_callback_ GUARDED_BY_CONTEXT(thread_checker_);
base::WeakPtr<PropertyListener> weak_this_for_events_;
base::WeakPtrFactory<PropertyListener> weak_factory_{this};
};
void AudioDeviceListenerMac::PropertyListenerDeleter::operator()(
PropertyListener* listener) {
if (!listener)
return;
if (!listener->MaybeStopListening()) {
// The listener was not subscribed for notifications, so there are no
// notifications in progress. We can delete it immediately.
delete listener;
return;
}
// The listener has been listening to changes; defer its deletion in case
// there is a notification in progress on another thread - to avoid a race.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
[](PropertyListener* listener) {
CHECK(listener);
delete listener;
},
listener),
base::Seconds(1));
}
// static
std::unique_ptr<AudioDeviceListenerMac> AudioDeviceListenerMac::Create(
base::RepeatingClosure listener_cb,
bool monitor_output_sample_rate_changes,
bool monitor_default_input,
bool monitor_addition_removal,
bool monitor_sources) {
// No make_unique<> since the constructor is private.
std::unique_ptr<AudioDeviceListenerMac> device_listener(
new AudioDeviceListenerMac(
std::move(listener_cb), monitor_output_sample_rate_changes,
monitor_default_input, monitor_addition_removal, monitor_sources));
device_listener->CreatePropertyListeners();
return device_listener;
}
AudioDeviceListenerMac::~AudioDeviceListenerMac() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
}
AudioDeviceListenerMac::AudioDeviceListenerMac(
const base::RepeatingClosure listener_cb,
bool monitor_output_sample_rate_changes,
bool monitor_default_input,
bool monitor_addition_removal,
bool monitor_sources)
: listener_cb_(std::move(listener_cb)),
monitor_default_input_(monitor_default_input),
monitor_addition_removal_(monitor_addition_removal),
monitor_output_sample_rate_changes_(monitor_output_sample_rate_changes),
monitor_sources_(monitor_sources) {
DVLOG(1) << __func__ << " this=" << this
<< " monitor_output_sample_rate_changes "
<< monitor_output_sample_rate_changes << " monitor_default_input "
<< monitor_default_input << " monitor_addition_removal "
<< " monitor_sources " << monitor_sources;
}
void AudioDeviceListenerMac::CreatePropertyListeners() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!default_output_listener_);
// Changes to the default output device are always monitored.
default_output_listener_ = CreatePropertyListener(
kAudioObjectSystemObject, &kDefaultOutputDeviceChangePropertyAddress,
listener_cb_);
if (monitor_default_input_) {
default_input_listener_ = CreatePropertyListener(
kAudioObjectSystemObject, &kDefaultInputDeviceChangePropertyAddress,
listener_cb_);
}
if (monitor_addition_removal_ || monitor_output_sample_rate_changes_ ||
monitor_sources_) {
addition_removal_listener_ = CreatePropertyListener(
kAudioObjectSystemObject, &kDevicesPropertyAddress,
base::BindRepeating(&AudioDeviceListenerMac::OnDevicesAddedOrRemoved,
base::Unretained(this)));
// Even if |addition_removal_listener_| creation failed we still want to
// monitor at least the devices we see at the moment.
UpdateDevicePropertyListeners();
}
}
void AudioDeviceListenerMac::OnDevicesAddedOrRemoved() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
UpdateDevicePropertyListeners();
if (monitor_addition_removal_)
listener_cb_.Run();
}
void AudioDeviceListenerMac::UpdateDevicePropertyListeners() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
std::vector<AudioObjectID> device_ids = GetAllAudioDeviceIDs();
if (monitor_sources_)
UpdateSourceListeners(device_ids);
if (monitor_output_sample_rate_changes_)
UpdateOutputSampleRateListeners(device_ids);
}
void AudioDeviceListenerMac::UpdateSourceListeners(
const std::vector<AudioObjectID>& device_ids) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(monitor_sources_);
DVLOG(1) << __func__ << " this=" << this;
SourceListenerMap new_listeners;
for (bool is_input : {true, false}) {
for (auto device_id : device_ids) {
// Do not monitor devices which do not have sources.
if (!GetDeviceSource(device_id, is_input))
continue;
SourceListenerKey key = {device_id, is_input};
auto listener_iter = source_listeners_.find(key);
if (listener_iter != source_listeners_.end()) {
// Continue monitoring.
new_listeners[key] = std::move(listener_iter->second);
continue;
}
// Start monitoring
const AudioObjectPropertyAddress* property_address =
is_input ? &kPropertyInputSourceChanged
: &kPropertyOutputSourceChanged;
auto new_listener =
CreatePropertyListener(device_id, property_address, listener_cb_);
if (new_listener)
new_listeners[key] = std::move(new_listener);
}
}
// Drop all the listeners not in |device_ids|.
source_listeners_.swap(new_listeners);
}
void AudioDeviceListenerMac::UpdateOutputSampleRateListeners(
const std::vector<AudioObjectID>& device_ids) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(monitor_output_sample_rate_changes_);
OutputSampleRateListenerMap new_listeners;
for (auto device_id : device_ids) {
if (!IsOutputDevice(device_id))
continue;
auto listener_iter = output_sample_rate_listeners_.find(device_id);
if (listener_iter != output_sample_rate_listeners_.end()) {
// Continue monitoring.
new_listeners[device_id] = std::move(listener_iter->second);
continue;
}
// Start monitoring
auto new_listener = CreatePropertyListener(
device_id, &kPropertyOutputSampleRateChanged, listener_cb_);
if (new_listener)
new_listeners[device_id] = std::move(new_listener);
}
// Drop all the listeners not in |device_ids|.
output_sample_rate_listeners_.swap(new_listeners);
DVLOG(1) << __func__ << " this=" << this
<< " listener count: " << output_sample_rate_listeners_.size();
}
AudioDeviceListenerMac::PropertyListenerPtr
AudioDeviceListenerMac::CreatePropertyListener(
AudioObjectID monitored_object,
const AudioObjectPropertyAddress* property,
base::RepeatingClosure listener_cb) {
// Unretained is safe because the callbacks are guaranteed to be called only
// while |this| holds the listener.
return PropertyListener::Create(
monitored_object, property, std::move(listener_cb),
base::BindOnce(&AudioDeviceListenerMac::AddPropertyListener,
base::Unretained(this)),
base::BindOnce(&AudioDeviceListenerMac::RemovePropertyListener,
base::Unretained(this)));
}
std::vector<AudioObjectID> AudioDeviceListenerMac::GetAllAudioDeviceIDs() {
return core_audio_mac::GetAllAudioDeviceIDs();
}
bool AudioDeviceListenerMac::IsOutputDevice(AudioObjectID id) {
return core_audio_mac::IsOutputDevice(id);
}
std::optional<uint32_t> AudioDeviceListenerMac::GetDeviceSource(
AudioObjectID device_id,
bool is_input) {
return core_audio_mac::GetDeviceSource(device_id, is_input);
}
OSStatus AudioDeviceListenerMac::AddPropertyListener(
AudioObjectID inObjectID,
const AudioObjectPropertyAddress* inAddress,
AudioObjectPropertyListenerProc inListener,
void* inClientData) {
return AudioObjectAddPropertyListener(inObjectID, inAddress, inListener,
inClientData);
}
OSStatus AudioDeviceListenerMac::RemovePropertyListener(
AudioObjectID inObjectID,
const AudioObjectPropertyAddress* inAddress,
AudioObjectPropertyListenerProc inListener,
void* inClientData) {
return AudioObjectRemovePropertyListener(inObjectID, inAddress, inListener,
inClientData);
}
std::vector<void*> AudioDeviceListenerMac::GetPropertyListenersForTesting()
const {
std::vector<void*> listeners;
if (default_output_listener_)
listeners.push_back(default_output_listener_.get());
if (default_input_listener_)
listeners.push_back(default_input_listener_.get());
if (addition_removal_listener_)
listeners.push_back(addition_removal_listener_.get());
for (const auto& listener_pair : source_listeners_)
listeners.push_back(listener_pair.second.get());
for (const auto& listener_pair : output_sample_rate_listeners_)
listeners.push_back(listener_pair.second.get());
return listeners;
}
// static
OSStatus AudioDeviceListenerMac::SimulateEventForTesting(
AudioObjectID object,
UInt32 num_addresses,
const AudioObjectPropertyAddress addresses[],
void* context) {
return PropertyListener::OnEvent(object, num_addresses, addresses, context);
}
} // namespace media