Home | History | Annotate | Download | only in alsa
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "media/audio/alsa/audio_manager_alsa.h"
      6 
      7 #include "base/command_line.h"
      8 #include "base/environment.h"
      9 #include "base/files/file_path.h"
     10 #include "base/logging.h"
     11 #include "base/metrics/histogram.h"
     12 #include "base/nix/xdg_util.h"
     13 #include "base/process/launch.h"
     14 #include "base/stl_util.h"
     15 #include "media/audio/audio_output_dispatcher.h"
     16 #include "media/audio/audio_parameters.h"
     17 #if defined(USE_CRAS)
     18 #include "media/audio/cras/audio_manager_cras.h"
     19 #endif
     20 #include "media/audio/alsa/alsa_input.h"
     21 #include "media/audio/alsa/alsa_output.h"
     22 #include "media/audio/alsa/alsa_wrapper.h"
     23 #if defined(USE_PULSEAUDIO)
     24 #include "media/audio/pulse/audio_manager_pulse.h"
     25 #endif
     26 #include "media/base/channel_layout.h"
     27 #include "media/base/limits.h"
     28 #include "media/base/media_switches.h"
     29 
     30 namespace media {
     31 
     32 // Maximum number of output streams that can be open simultaneously.
     33 static const int kMaxOutputStreams = 50;
     34 
     35 // Default sample rate for input and output streams.
     36 static const int kDefaultSampleRate = 48000;
     37 
     38 // Since "default", "pulse" and "dmix" devices are virtual devices mapped to
     39 // real devices, we remove them from the list to avoiding duplicate counting.
     40 // In addition, note that we support no more than 2 channels for recording,
     41 // hence surround devices are not stored in the list.
     42 static const char* kInvalidAudioInputDevices[] = {
     43   "default",
     44   "dmix",
     45   "null",
     46   "pulse",
     47   "surround",
     48 };
     49 
     50 // static
     51 void AudioManagerAlsa::ShowLinuxAudioInputSettings() {
     52   scoped_ptr<base::Environment> env(base::Environment::Create());
     53   CommandLine command_line(CommandLine::NO_PROGRAM);
     54   switch (base::nix::GetDesktopEnvironment(env.get())) {
     55     case base::nix::DESKTOP_ENVIRONMENT_GNOME:
     56       command_line.SetProgram(base::FilePath("gnome-volume-control"));
     57       break;
     58     case base::nix::DESKTOP_ENVIRONMENT_KDE3:
     59     case base::nix::DESKTOP_ENVIRONMENT_KDE4:
     60       command_line.SetProgram(base::FilePath("kmix"));
     61       break;
     62     case base::nix::DESKTOP_ENVIRONMENT_UNITY:
     63       command_line.SetProgram(base::FilePath("gnome-control-center"));
     64       command_line.AppendArg("sound");
     65       command_line.AppendArg("input");
     66       break;
     67     default:
     68       LOG(ERROR) << "Failed to show audio input settings: we don't know "
     69                  << "what command to use for your desktop environment.";
     70       return;
     71   }
     72   base::LaunchProcess(command_line, base::LaunchOptions(), NULL);
     73 }
     74 
     75 // Implementation of AudioManager.
     76 bool AudioManagerAlsa::HasAudioOutputDevices() {
     77   return HasAnyAlsaAudioDevice(kStreamPlayback);
     78 }
     79 
     80 bool AudioManagerAlsa::HasAudioInputDevices() {
     81   return HasAnyAlsaAudioDevice(kStreamCapture);
     82 }
     83 
     84 AudioManagerAlsa::AudioManagerAlsa(AudioLogFactory* audio_log_factory)
     85     : AudioManagerBase(audio_log_factory),
     86       wrapper_(new AlsaWrapper()) {
     87   SetMaxOutputStreamsAllowed(kMaxOutputStreams);
     88 }
     89 
     90 AudioManagerAlsa::~AudioManagerAlsa() {
     91   Shutdown();
     92 }
     93 
     94 void AudioManagerAlsa::ShowAudioInputSettings() {
     95   ShowLinuxAudioInputSettings();
     96 }
     97 
     98 void AudioManagerAlsa::GetAudioInputDeviceNames(
     99     AudioDeviceNames* device_names) {
    100   DCHECK(device_names->empty());
    101   GetAlsaAudioDevices(kStreamCapture, device_names);
    102 }
    103 
    104 void AudioManagerAlsa::GetAudioOutputDeviceNames(
    105     AudioDeviceNames* device_names) {
    106   DCHECK(device_names->empty());
    107   GetAlsaAudioDevices(kStreamPlayback, device_names);
    108 }
    109 
    110 AudioParameters AudioManagerAlsa::GetInputStreamParameters(
    111     const std::string& device_id) {
    112   static const int kDefaultInputBufferSize = 1024;
    113 
    114   return AudioParameters(
    115       AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_STEREO,
    116       kDefaultSampleRate, 16, kDefaultInputBufferSize);
    117 }
    118 
    119 void AudioManagerAlsa::GetAlsaAudioDevices(
    120     StreamType type,
    121     media::AudioDeviceNames* device_names) {
    122   // Constants specified by the ALSA API for device hints.
    123   static const char kPcmInterfaceName[] = "pcm";
    124   int card = -1;
    125 
    126   // Loop through the sound cards to get ALSA device hints.
    127   while (!wrapper_->CardNext(&card) && card >= 0) {
    128     void** hints = NULL;
    129     int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints);
    130     if (!error) {
    131       GetAlsaDevicesInfo(type, hints, device_names);
    132 
    133       // Destroy the hints now that we're done with it.
    134       wrapper_->DeviceNameFreeHint(hints);
    135     } else {
    136       DLOG(WARNING) << "GetAlsaAudioDevices: unable to get device hints: "
    137                     << wrapper_->StrError(error);
    138     }
    139   }
    140 }
    141 
    142 void AudioManagerAlsa::GetAlsaDevicesInfo(
    143     AudioManagerAlsa::StreamType type,
    144     void** hints,
    145     media::AudioDeviceNames* device_names) {
    146   static const char kIoHintName[] = "IOID";
    147   static const char kNameHintName[] = "NAME";
    148   static const char kDescriptionHintName[] = "DESC";
    149 
    150   const char* unwanted_device_type = UnwantedDeviceTypeWhenEnumerating(type);
    151 
    152   for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) {
    153     // Only examine devices of the right type.  Valid values are
    154     // "Input", "Output", and NULL which means both input and output.
    155     scoped_ptr<char, base::FreeDeleter> io(wrapper_->DeviceNameGetHint(
    156         *hint_iter, kIoHintName));
    157     if (io != NULL && strcmp(unwanted_device_type, io.get()) == 0)
    158       continue;
    159 
    160     // Found a device, prepend the default device since we always want
    161     // it to be on the top of the list for all platforms. And there is
    162     // no duplicate counting here since it is only done if the list is
    163     // still empty.  Note, pulse has exclusively opened the default
    164     // device, so we must open the device via the "default" moniker.
    165     if (device_names->empty()) {
    166       device_names->push_front(media::AudioDeviceName(
    167           AudioManagerBase::kDefaultDeviceName,
    168           AudioManagerBase::kDefaultDeviceId));
    169     }
    170 
    171     // Get the unique device name for the device.
    172     scoped_ptr<char, base::FreeDeleter> unique_device_name(
    173         wrapper_->DeviceNameGetHint(*hint_iter, kNameHintName));
    174 
    175     // Find out if the device is available.
    176     if (IsAlsaDeviceAvailable(type, unique_device_name.get())) {
    177       // Get the description for the device.
    178       scoped_ptr<char, base::FreeDeleter> desc(wrapper_->DeviceNameGetHint(
    179           *hint_iter, kDescriptionHintName));
    180 
    181       media::AudioDeviceName name;
    182       name.unique_id = unique_device_name.get();
    183       if (desc) {
    184         // Use the more user friendly description as name.
    185         // Replace '\n' with '-'.
    186         char* pret = strchr(desc.get(), '\n');
    187         if (pret)
    188           *pret = '-';
    189         name.device_name = desc.get();
    190       } else {
    191         // Virtual devices don't necessarily have descriptions.
    192         // Use their names instead.
    193         name.device_name = unique_device_name.get();
    194       }
    195 
    196       // Store the device information.
    197       device_names->push_back(name);
    198     }
    199   }
    200 }
    201 
    202 // static
    203 bool AudioManagerAlsa::IsAlsaDeviceAvailable(
    204     AudioManagerAlsa::StreamType type,
    205     const char* device_name) {
    206   if (!device_name)
    207     return false;
    208 
    209   // We do prefix matches on the device name to see whether to include
    210   // it or not.
    211   if (type == kStreamCapture) {
    212     // Check if the device is in the list of invalid devices.
    213     for (size_t i = 0; i < arraysize(kInvalidAudioInputDevices); ++i) {
    214       if (strncmp(kInvalidAudioInputDevices[i], device_name,
    215                   strlen(kInvalidAudioInputDevices[i])) == 0)
    216         return false;
    217     }
    218     return true;
    219   } else {
    220     DCHECK_EQ(kStreamPlayback, type);
    221     // We prefer the device type that maps straight to hardware but
    222     // goes through software conversion if needed (e.g. incompatible
    223     // sample rate).
    224     // TODO(joi): Should we prefer "hw" instead?
    225     static const char kDeviceTypeDesired[] = "plughw";
    226     return strncmp(kDeviceTypeDesired,
    227                    device_name,
    228                    arraysize(kDeviceTypeDesired) - 1) == 0;
    229   }
    230 }
    231 
    232 // static
    233 const char* AudioManagerAlsa::UnwantedDeviceTypeWhenEnumerating(
    234     AudioManagerAlsa::StreamType wanted_type) {
    235   return wanted_type == kStreamPlayback ? "Input" : "Output";
    236 }
    237 
    238 bool AudioManagerAlsa::HasAnyAlsaAudioDevice(
    239     AudioManagerAlsa::StreamType stream) {
    240   static const char kPcmInterfaceName[] = "pcm";
    241   static const char kIoHintName[] = "IOID";
    242   void** hints = NULL;
    243   bool has_device = false;
    244   int card = -1;
    245 
    246   // Loop through the sound cards.
    247   // Don't use snd_device_name_hint(-1,..) since there is a access violation
    248   // inside this ALSA API with libasound.so.2.0.0.
    249   while (!wrapper_->CardNext(&card) && (card >= 0) && !has_device) {
    250     int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints);
    251     if (!error) {
    252       for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) {
    253         // Only examine devices that are |stream| capable.  Valid values are
    254         // "Input", "Output", and NULL which means both input and output.
    255         scoped_ptr<char, base::FreeDeleter> io(wrapper_->DeviceNameGetHint(
    256             *hint_iter, kIoHintName));
    257         const char* unwanted_type = UnwantedDeviceTypeWhenEnumerating(stream);
    258         if (io != NULL && strcmp(unwanted_type, io.get()) == 0)
    259           continue;  // Wrong type, skip the device.
    260 
    261         // Found an input device.
    262         has_device = true;
    263         break;
    264       }
    265 
    266       // Destroy the hints now that we're done with it.
    267       wrapper_->DeviceNameFreeHint(hints);
    268       hints = NULL;
    269     } else {
    270       DLOG(WARNING) << "HasAnyAudioDevice: unable to get device hints: "
    271                     << wrapper_->StrError(error);
    272     }
    273   }
    274 
    275   return has_device;
    276 }
    277 
    278 AudioOutputStream* AudioManagerAlsa::MakeLinearOutputStream(
    279     const AudioParameters& params) {
    280   DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
    281   return MakeOutputStream(params);
    282 }
    283 
    284 AudioOutputStream* AudioManagerAlsa::MakeLowLatencyOutputStream(
    285     const AudioParameters& params,
    286     const std::string& device_id) {
    287   DLOG_IF(ERROR, !device_id.empty()) << "Not implemented!";
    288   DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
    289   return MakeOutputStream(params);
    290 }
    291 
    292 AudioInputStream* AudioManagerAlsa::MakeLinearInputStream(
    293     const AudioParameters& params, const std::string& device_id) {
    294   DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
    295   return MakeInputStream(params, device_id);
    296 }
    297 
    298 AudioInputStream* AudioManagerAlsa::MakeLowLatencyInputStream(
    299     const AudioParameters& params, const std::string& device_id) {
    300   DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
    301   return MakeInputStream(params, device_id);
    302 }
    303 
    304 AudioParameters AudioManagerAlsa::GetPreferredOutputStreamParameters(
    305     const std::string& output_device_id,
    306     const AudioParameters& input_params) {
    307   // TODO(tommi): Support |output_device_id|.
    308   DLOG_IF(ERROR, !output_device_id.empty()) << "Not implemented!";
    309   static const int kDefaultOutputBufferSize = 2048;
    310   ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO;
    311   int sample_rate = kDefaultSampleRate;
    312   int buffer_size = kDefaultOutputBufferSize;
    313   int bits_per_sample = 16;
    314   int input_channels = 0;
    315   if (input_params.IsValid()) {
    316     // Some clients, such as WebRTC, have a more limited use case and work
    317     // acceptably with a smaller buffer size.  The check below allows clients
    318     // which want to try a smaller buffer size on Linux to do so.
    319     // TODO(dalecurtis): This should include bits per channel and channel layout
    320     // eventually.
    321     sample_rate = input_params.sample_rate();
    322     bits_per_sample = input_params.bits_per_sample();
    323     channel_layout = input_params.channel_layout();
    324     input_channels = input_params.input_channels();
    325     buffer_size = std::min(input_params.frames_per_buffer(), buffer_size);
    326   }
    327 
    328   int user_buffer_size = GetUserBufferSize();
    329   if (user_buffer_size)
    330     buffer_size = user_buffer_size;
    331 
    332   return AudioParameters(
    333       AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout, input_channels,
    334       sample_rate, bits_per_sample, buffer_size, AudioParameters::NO_EFFECTS);
    335 }
    336 
    337 AudioOutputStream* AudioManagerAlsa::MakeOutputStream(
    338     const AudioParameters& params) {
    339   std::string device_name = AlsaPcmOutputStream::kAutoSelectDevice;
    340   if (CommandLine::ForCurrentProcess()->HasSwitch(
    341           switches::kAlsaOutputDevice)) {
    342     device_name = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
    343         switches::kAlsaOutputDevice);
    344   }
    345   return new AlsaPcmOutputStream(device_name, params, wrapper_.get(), this);
    346 }
    347 
    348 AudioInputStream* AudioManagerAlsa::MakeInputStream(
    349     const AudioParameters& params, const std::string& device_id) {
    350   std::string device_name = (device_id == AudioManagerBase::kDefaultDeviceId) ?
    351       AlsaPcmInputStream::kAutoSelectDevice : device_id;
    352   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAlsaInputDevice)) {
    353     device_name = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
    354         switches::kAlsaInputDevice);
    355   }
    356 
    357   return new AlsaPcmInputStream(this, device_name, params, wrapper_.get());
    358 }
    359 
    360 }  // namespace media
    361