// 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 "extensions/browser/api/audio/audio_service.h"
#include <stddef.h>
#include <stdint.h>
#include "base/containers/contains.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "chromeos/ash/components/audio/audio_device.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/api/audio/audio_device_id_calculator.h"
namespace extensions {
using api::audio::AudioDeviceInfo;
using ::ash::AudioDevice;
using ::ash::AudioDeviceType;
using ::ash::CrasAudioHandler;
using ::content::BrowserThread;
namespace {
api::audio::DeviceType GetAsAudioApiDeviceType(AudioDeviceType type) {
switch (type) {
case AudioDeviceType::kHeadphone:
return api::audio::DeviceType::kHeadphone;
case AudioDeviceType::kMic:
return api::audio::DeviceType::kMic;
case AudioDeviceType::kUsb:
return api::audio::DeviceType::kUsb;
case AudioDeviceType::kBluetooth:
case AudioDeviceType::kBluetoothNbMic:
return api::audio::DeviceType::kBluetooth;
case AudioDeviceType::kHdmi:
return api::audio::DeviceType::kHdmi;
case AudioDeviceType::kInternalSpeaker:
return api::audio::DeviceType::kInternalSpeaker;
case AudioDeviceType::kInternalMic:
return api::audio::DeviceType::kInternalMic;
case AudioDeviceType::kFrontMic:
return api::audio::DeviceType::kFrontMic;
case AudioDeviceType::kRearMic:
return api::audio::DeviceType::kRearMic;
case AudioDeviceType::kKeyboardMic:
return api::audio::DeviceType::kKeyboardMic;
case AudioDeviceType::kHotword:
return api::audio::DeviceType::kHotword;
case AudioDeviceType::kLineout:
return api::audio::DeviceType::kLineout;
case AudioDeviceType::kPostMixLoopback:
return api::audio::DeviceType::kPostMixLoopback;
case AudioDeviceType::kPostDspLoopback:
return api::audio::DeviceType::kPostDspLoopback;
case AudioDeviceType::kAlsaLoopback:
return api::audio::DeviceType::kAlsaLoopback;
case AudioDeviceType::kOther:
return api::audio::DeviceType::kOther;
}
NOTREACHED_IN_MIGRATION();
return api::audio::DeviceType::kOther;
}
} // namespace
class AudioServiceImpl : public AudioService,
public CrasAudioHandler::AudioObserver {
public:
explicit AudioServiceImpl(AudioDeviceIdCalculator* id_calculator);
AudioServiceImpl(const AudioServiceImpl&) = delete;
AudioServiceImpl& operator=(const AudioServiceImpl&) = delete;
~AudioServiceImpl() override;
// Called by listeners to this service to add/remove themselves as observers.
void AddObserver(AudioService::Observer* observer) override;
void RemoveObserver(AudioService::Observer* observer) override;
// Start to query audio device information.
void GetDevices(
const api::audio::DeviceFilter* filter,
base::OnceCallback<void(bool, DeviceInfoList)> callback) override;
void SetActiveDeviceLists(const DeviceIdList* input_devices,
const DeviceIdList* output_devives,
base::OnceCallback<void(bool)> callback) override;
void SetDeviceSoundLevel(const std::string& device_id,
int level_value,
base::OnceCallback<void(bool)> callback) override;
void SetMute(bool is_input,
bool value,
base::OnceCallback<void(bool)> callback) override;
void GetMute(bool is_input,
base::OnceCallback<void(bool, bool)> callback) override;
protected:
// CrasAudioHandler::AudioObserver overrides.
void OnOutputNodeVolumeChanged(uint64_t id, int volume) override;
void OnInputNodeGainChanged(uint64_t id, int gain) override;
void OnOutputMuteChanged(bool mute_on) override;
void OnInputMuteChanged(
bool mute_on,
CrasAudioHandler::InputMuteChangeMethod method) override;
void OnAudioNodesChanged() override;
void OnActiveOutputNodeChanged() override;
void OnActiveInputNodeChanged() override;
private:
void NotifyLevelChanged(uint64_t id, int level);
void NotifyMuteChanged(bool is_input, bool is_muted);
void NotifyDevicesChanged();
uint64_t GetIdFromStr(const std::string& id_str);
bool GetAudioNodeIdList(const DeviceIdList& ids,
bool is_input,
CrasAudioHandler::NodeIdList* node_ids);
AudioDeviceInfo ToAudioDeviceInfo(const AudioDevice& device);
// List of observers.
base::ObserverList<AudioService::Observer>::Unchecked observer_list_;
raw_ptr<CrasAudioHandler, DanglingUntriaged> cras_audio_handler_;
raw_ptr<AudioDeviceIdCalculator> id_calculator_;
// Note: This should remain the last member so it'll be destroyed and
// invalidate the weak pointers before any other members are destroyed.
base::WeakPtrFactory<AudioServiceImpl> weak_ptr_factory_{this};
};
AudioServiceImpl::AudioServiceImpl(AudioDeviceIdCalculator* id_calculator)
: cras_audio_handler_(CrasAudioHandler::Get()),
id_calculator_(id_calculator) {
CHECK(id_calculator_);
if (cras_audio_handler_)
cras_audio_handler_->AddAudioObserver(this);
}
AudioServiceImpl::~AudioServiceImpl() {
// The CrasAudioHandler global instance may have already been destroyed, so
// do not used the cached pointer here.
if (CrasAudioHandler::Get())
CrasAudioHandler::Get()->RemoveAudioObserver(this);
}
void AudioServiceImpl::AddObserver(AudioService::Observer* observer) {
observer_list_.AddObserver(observer);
}
void AudioServiceImpl::RemoveObserver(AudioService::Observer* observer) {
observer_list_.RemoveObserver(observer);
}
void AudioServiceImpl::GetDevices(
const api::audio::DeviceFilter* filter,
base::OnceCallback<void(bool, DeviceInfoList)> callback) {
DeviceInfoList devices_out;
if (!cras_audio_handler_) {
std::move(callback).Run(false, std::move(devices_out));
return;
}
ash::AudioDeviceList devices;
cras_audio_handler_->GetAudioDevices(&devices);
bool accept_input =
!(filter && filter->stream_types) ||
base::Contains(*filter->stream_types, api::audio::StreamType::kInput);
bool accept_output =
!(filter && filter->stream_types) ||
base::Contains(*filter->stream_types, api::audio::StreamType::kOutput);
for (const auto& device : devices) {
if (filter && filter->is_active && *filter->is_active != device.active)
continue;
if (device.is_input && !accept_input)
continue;
if (!device.is_input && !accept_output)
continue;
devices_out.push_back(ToAudioDeviceInfo(device));
}
std::move(callback).Run(true, std::move(devices_out));
}
void AudioServiceImpl::SetActiveDeviceLists(
const DeviceIdList* input_devices,
const DeviceIdList* output_devives,
base::OnceCallback<void(bool)> callback) {
DCHECK(cras_audio_handler_);
if (!cras_audio_handler_) {
std::move(callback).Run(false);
return;
}
CrasAudioHandler::NodeIdList input_nodes;
if (input_devices &&
!GetAudioNodeIdList(*input_devices, true, &input_nodes)) {
std::move(callback).Run(false);
return;
}
CrasAudioHandler::NodeIdList output_nodes;
if (output_devives &&
!GetAudioNodeIdList(*output_devives, false, &output_nodes)) {
std::move(callback).Run(false);
return;
}
bool success = true;
if (output_devives) {
success = cras_audio_handler_->SetActiveOutputNodes(output_nodes);
DCHECK(success);
}
if (input_devices) {
success = success && cras_audio_handler_->SetActiveInputNodes(input_nodes);
DCHECK(success);
}
std::move(callback).Run(success);
}
void AudioServiceImpl::SetDeviceSoundLevel(
const std::string& device_id,
int level_value,
base::OnceCallback<void(bool)> callback) {
DCHECK(cras_audio_handler_);
if (!cras_audio_handler_) {
std::move(callback).Run(false);
return;
}
const AudioDevice* device =
cras_audio_handler_->GetDeviceFromId(GetIdFromStr(device_id));
if (!device) {
std::move(callback).Run(false);
return;
}
if (level_value != -1) {
cras_audio_handler_->SetVolumeGainPercentForDevice(device->id, level_value);
std::move(callback).Run(true);
} else {
std::move(callback).Run(false);
}
}
void AudioServiceImpl::SetMute(bool is_input,
bool value,
base::OnceCallback<void(bool)> callback) {
DCHECK(cras_audio_handler_);
if (!cras_audio_handler_) {
std::move(callback).Run(false);
return;
}
if (is_input)
cras_audio_handler_->SetInputMute(
value, CrasAudioHandler::InputMuteChangeMethod::kOther);
else
cras_audio_handler_->SetOutputMute(value);
std::move(callback).Run(true);
}
void AudioServiceImpl::GetMute(bool is_input,
base::OnceCallback<void(bool, bool)> callback) {
DCHECK(cras_audio_handler_);
if (!cras_audio_handler_) {
std::move(callback).Run(false, false);
return;
}
const bool is_muted_result = is_input ? cras_audio_handler_->IsInputMuted()
: cras_audio_handler_->IsOutputMuted();
std::move(callback).Run(true, is_muted_result);
}
uint64_t AudioServiceImpl::GetIdFromStr(const std::string& id_str) {
uint64_t device_id;
if (!base::StringToUint64(id_str, &device_id))
return 0;
else
return device_id;
}
bool AudioServiceImpl::GetAudioNodeIdList(
const DeviceIdList& ids,
bool is_input,
CrasAudioHandler::NodeIdList* node_ids) {
for (const auto& device_id : ids) {
const AudioDevice* device =
cras_audio_handler_->GetDeviceFromId(GetIdFromStr(device_id));
if (!device)
return false;
if (device->is_input != is_input)
return false;
node_ids->push_back(device->id);
}
return true;
}
AudioDeviceInfo AudioServiceImpl::ToAudioDeviceInfo(const AudioDevice& device) {
AudioDeviceInfo info;
info.id = base::NumberToString(device.id);
info.stream_type = device.is_input
? extensions::api::audio::StreamType::kInput
: extensions::api::audio::StreamType::kOutput;
info.device_type = GetAsAudioApiDeviceType(device.type);
info.display_name = device.display_name;
info.device_name = device.device_name;
info.is_active = device.active;
info.level =
device.is_input
? cras_audio_handler_->GetInputGainPercentForDevice(device.id)
: cras_audio_handler_->GetOutputVolumePercentForDevice(device.id);
info.stable_device_id =
id_calculator_->GetStableDeviceId(device.stable_device_id);
return info;
}
void AudioServiceImpl::OnOutputNodeVolumeChanged(uint64_t id, int volume) {
NotifyLevelChanged(id, volume);
}
void AudioServiceImpl::OnOutputMuteChanged(bool mute_on) {
NotifyMuteChanged(false, mute_on);
}
void AudioServiceImpl::OnInputNodeGainChanged(uint64_t id, int gain) {
NotifyLevelChanged(id, gain);
}
void AudioServiceImpl::OnInputMuteChanged(
bool mute_on,
CrasAudioHandler::InputMuteChangeMethod method) {
NotifyMuteChanged(true, mute_on);
}
void AudioServiceImpl::OnAudioNodesChanged() {
NotifyDevicesChanged();
}
void AudioServiceImpl::OnActiveOutputNodeChanged() {}
void AudioServiceImpl::OnActiveInputNodeChanged() {}
void AudioServiceImpl::NotifyLevelChanged(uint64_t id, int level) {
for (auto& observer : observer_list_)
observer.OnLevelChanged(base::NumberToString(id), level);
}
void AudioServiceImpl::NotifyMuteChanged(bool is_input, bool is_muted) {
for (auto& observer : observer_list_)
observer.OnMuteChanged(is_input, is_muted);
}
void AudioServiceImpl::NotifyDevicesChanged() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ash::AudioDeviceList devices;
cras_audio_handler_->GetAudioDevices(&devices);
DeviceInfoList device_info_list;
for (const auto& device : devices) {
device_info_list.push_back(ToAudioDeviceInfo(device));
}
for (auto& observer : observer_list_)
observer.OnDevicesChanged(device_info_list);
}
AudioService::Ptr AudioService::CreateInstance(
AudioDeviceIdCalculator* id_calculator) {
return std::make_unique<AudioServiceImpl>(id_calculator);
}
} // namespace extensions