#include "media/audio/linux/audio_manager_linux.h"
#include "base/command_line.h"
#include "base/environment.h"
#include "base/logging.h"
#include "base/nix/xdg_util.h"
#include "base/process_util.h"
#include "base/stl_util.h"
#include "media/audio/audio_output_dispatcher.h"
#include "media/audio/audio_util.h"
#include "media/audio/linux/alsa_input.h"
#include "media/audio/linux/alsa_output.h"
#include "media/audio/linux/alsa_wrapper.h"
#if defined(USE_PULSEAUDIO)
#include "media/audio/pulse/pulse_output.h"
#endif
#if defined(USE_CRAS)
#include "media/audio/linux/cras_input.h"
#include "media/audio/linux/cras_output.h"
#endif
#include "media/base/limits.h"
#include "media/base/media_switches.h"
namespace media {
static const int kMaxOutputStreams = 50;
static const char* kInvalidAudioInputDevices[] = {
"default",
"null",
"pulse",
"dmix",
};
static const char kCrasAutomaticDeviceName[] = "Automatic";
static const char kCrasAutomaticDeviceId[] = "automatic";
bool AudioManagerLinux::HasAudioOutputDevices() {
if (UseCras())
return true;
return HasAnyAlsaAudioDevice(kStreamPlayback);
}
bool AudioManagerLinux::HasAudioInputDevices() {
if (UseCras())
return true;
return HasAnyAlsaAudioDevice(kStreamCapture);
}
AudioManagerLinux::AudioManagerLinux() {
SetMaxOutputStreamsAllowed(kMaxOutputStreams);
}
AudioManagerLinux::~AudioManagerLinux() {
Shutdown();
}
void AudioManagerLinux::Init() {
AudioManagerBase::Init();
wrapper_.reset(new AlsaWrapper());
}
bool AudioManagerLinux::CanShowAudioInputSettings() {
scoped_ptr<base::Environment> env(base::Environment::Create());
switch (base::nix::GetDesktopEnvironment(env.get())) {
case base::nix::DESKTOP_ENVIRONMENT_GNOME:
case base::nix::DESKTOP_ENVIRONMENT_KDE3:
case base::nix::DESKTOP_ENVIRONMENT_KDE4:
return true;
case base::nix::DESKTOP_ENVIRONMENT_OTHER:
case base::nix::DESKTOP_ENVIRONMENT_UNITY:
case base::nix::DESKTOP_ENVIRONMENT_XFCE:
return false;
}
NOTREACHED();
return false;
}
void AudioManagerLinux::ShowAudioInputSettings() {
scoped_ptr<base::Environment> env(base::Environment::Create());
base::nix::DesktopEnvironment desktop = base::nix::GetDesktopEnvironment(
env.get());
std::string command((desktop == base::nix::DESKTOP_ENVIRONMENT_GNOME) ?
"gnome-volume-control" : "kmix");
base::LaunchProcess(CommandLine(FilePath(command)), base::LaunchOptions(),
NULL);
}
void AudioManagerLinux::GetAudioInputDeviceNames(
media::AudioDeviceNames* device_names) {
DCHECK(device_names->empty());
if (UseCras()) {
GetCrasAudioInputDevices(device_names);
return;
}
GetAlsaAudioInputDevices(device_names);
}
bool AudioManagerLinux::UseCras() {
#if defined(USE_CRAS)
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kUseCras)) {
return true;
}
#endif
return false;
}
void AudioManagerLinux::GetCrasAudioInputDevices(
media::AudioDeviceNames* device_names) {
device_names->push_back(media::AudioDeviceName(
kCrasAutomaticDeviceName, kCrasAutomaticDeviceId));
}
void AudioManagerLinux::GetAlsaAudioInputDevices(
media::AudioDeviceNames* device_names) {
static const char kPcmInterfaceName[] = "pcm";
int card = -1;
while (!wrapper_->CardNext(&card) && card >= 0) {
void** hints = NULL;
int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints);
if (!error) {
GetAlsaDevicesInfo(hints, device_names);
wrapper_->DeviceNameFreeHint(hints);
} else {
DLOG(WARNING) << "GetAudioInputDevices: unable to get device hints: "
<< wrapper_->StrError(error);
}
}
}
void AudioManagerLinux::GetAlsaDevicesInfo(
void** hints, media::AudioDeviceNames* device_names) {
static const char kIoHintName[] = "IOID";
static const char kNameHintName[] = "NAME";
static const char kDescriptionHintName[] = "DESC";
static const char kOutputDevice[] = "Output";
for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) {
scoped_ptr_malloc<char> io(wrapper_->DeviceNameGetHint(*hint_iter,
kIoHintName));
if (io != NULL && strcmp(kOutputDevice, io.get()) == 0)
continue;
if (device_names->empty()) {
device_names->push_front(media::AudioDeviceName(
AudioManagerBase::kDefaultDeviceName,
AudioManagerBase::kDefaultDeviceId));
}
scoped_ptr_malloc<char> unique_device_name(
wrapper_->DeviceNameGetHint(*hint_iter, kNameHintName));
if (IsAlsaDeviceAvailable(unique_device_name.get())) {
scoped_ptr_malloc<char> desc(wrapper_->DeviceNameGetHint(
*hint_iter, kDescriptionHintName));
media::AudioDeviceName name;
name.unique_id = unique_device_name.get();
if (desc.get()) {
char* pret = strchr(desc.get(), '\n');
if (pret)
*pret = '-';
name.device_name = desc.get();
} else {
name.device_name = unique_device_name.get();
}
device_names->push_back(name);
}
}
}
bool AudioManagerLinux::IsAlsaDeviceAvailable(const char* device_name) {
static const char kNotWantedSurroundDevices[] = "surround";
if (!device_name)
return false;
for (size_t i = 0; i < arraysize(kInvalidAudioInputDevices); ++i) {
if ((strcmp(kInvalidAudioInputDevices[i], device_name) == 0) ||
(strncmp(kNotWantedSurroundDevices, device_name,
arraysize(kNotWantedSurroundDevices) - 1) == 0))
return false;
}
snd_pcm_t* device_handle = NULL;
if (wrapper_->PcmOpen(&device_handle,
device_name,
SND_PCM_STREAM_CAPTURE,
SND_PCM_NONBLOCK))
return false;
if (wrapper_->PcmClose(device_handle))
return false;
return true;
}
bool AudioManagerLinux::HasAnyAlsaAudioDevice(StreamType stream) {
static const char kPcmInterfaceName[] = "pcm";
static const char kIoHintName[] = "IOID";
const char* kNotWantedDevice =
(stream == kStreamPlayback ? "Input" : "Output");
void** hints = NULL;
bool has_device = false;
int card = -1;
while (!wrapper_->CardNext(&card) && (card >= 0) && !has_device) {
int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints);
if (!error) {
for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) {
scoped_ptr_malloc<char> io(wrapper_->DeviceNameGetHint(*hint_iter,
kIoHintName));
if (io != NULL && strcmp(kNotWantedDevice, io.get()) == 0)
continue;
has_device = true;
break;
}
wrapper_->DeviceNameFreeHint(hints);
hints = NULL;
} else {
DLOG(WARNING) << "HasAnyAudioDevice: unable to get device hints: "
<< wrapper_->StrError(error);
}
}
return has_device;
}
AudioOutputStream* AudioManagerLinux::MakeLinearOutputStream(
const AudioParameters& params) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
return MakeOutputStream(params);
}
AudioOutputStream* AudioManagerLinux::MakeLowLatencyOutputStream(
const AudioParameters& params) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
return MakeOutputStream(params);
}
AudioInputStream* AudioManagerLinux::MakeLinearInputStream(
const AudioParameters& params, const std::string& device_id) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
return MakeInputStream(params, device_id);
}
AudioInputStream* AudioManagerLinux::MakeLowLatencyInputStream(
const AudioParameters& params, const std::string& device_id) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
return MakeInputStream(params, device_id);
}
AudioOutputStream* AudioManagerLinux::MakeOutputStream(
const AudioParameters& params) {
#if defined(USE_CRAS)
if (UseCras()) {
return new CrasOutputStream(params, this);
}
#endif
#if defined(USE_PULSEAUDIO)
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kUsePulseAudio)) {
return new PulseAudioOutputStream(params, this);
}
#endif
std::string device_name = AlsaPcmOutputStream::kAutoSelectDevice;
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAlsaOutputDevice)) {
device_name = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kAlsaOutputDevice);
}
return new AlsaPcmOutputStream(device_name, params, wrapper_.get(), this);
}
AudioInputStream* AudioManagerLinux::MakeInputStream(
const AudioParameters& params, const std::string& device_id) {
#if defined(USE_CRAS)
if (UseCras()) {
return new CrasInputStream(params, this);
}
#endif
std::string device_name = (device_id == AudioManagerBase::kDefaultDeviceId) ?
AlsaPcmInputStream::kAutoSelectDevice : device_id;
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAlsaInputDevice)) {
device_name = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kAlsaInputDevice);
}
return new AlsaPcmInputStream(this, device_name, params, wrapper_.get());
}
AudioManager* CreateAudioManager() {
return new AudioManagerLinux();
}
AudioParameters AudioManagerLinux::GetPreferredLowLatencyOutputStreamParameters(
const AudioParameters& input_params) {
int buffer_size = GetAudioHardwareBufferSize();
if (input_params.frames_per_buffer() < buffer_size)
buffer_size = input_params.frames_per_buffer();
int sample_rate = GetAudioHardwareSampleRate();
if (UseCras())
sample_rate = input_params.sample_rate();
return AudioParameters(
AudioParameters::AUDIO_PCM_LOW_LATENCY, input_params.channel_layout(),
sample_rate, 16, buffer_size);
}
}