audio_manager_alsa.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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
30namespace media {
31
32// Maximum number of output streams that can be open simultaneously.
33static const int kMaxOutputStreams = 50;
34
35// Default sample rate for input and output streams.
36static 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.
42static const char* kInvalidAudioInputDevices[] = {
43  "default",
44  "dmix",
45  "null",
46  "pulse",
47  "surround",
48};
49
50// static
51void 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.
76bool AudioManagerAlsa::HasAudioOutputDevices() {
77  return HasAnyAlsaAudioDevice(kStreamPlayback);
78}
79
80bool AudioManagerAlsa::HasAudioInputDevices() {
81  return HasAnyAlsaAudioDevice(kStreamCapture);
82}
83
84AudioManagerAlsa::AudioManagerAlsa(AudioLogFactory* audio_log_factory)
85    : AudioManagerBase(audio_log_factory),
86      wrapper_(new AlsaWrapper()) {
87  SetMaxOutputStreamsAllowed(kMaxOutputStreams);
88}
89
90AudioManagerAlsa::~AudioManagerAlsa() {
91  Shutdown();
92}
93
94void AudioManagerAlsa::ShowAudioInputSettings() {
95  ShowLinuxAudioInputSettings();
96}
97
98void AudioManagerAlsa::GetAudioInputDeviceNames(
99    AudioDeviceNames* device_names) {
100  DCHECK(device_names->empty());
101  GetAlsaAudioDevices(kStreamCapture, device_names);
102}
103
104void AudioManagerAlsa::GetAudioOutputDeviceNames(
105    AudioDeviceNames* device_names) {
106  DCHECK(device_names->empty());
107  GetAlsaAudioDevices(kStreamPlayback, device_names);
108}
109
110AudioParameters 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
119void 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
142void 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_malloc<char> io(wrapper_->DeviceNameGetHint(*hint_iter,
156                                                           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_malloc<char> 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_malloc<char> 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
203bool 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
233const char* AudioManagerAlsa::UnwantedDeviceTypeWhenEnumerating(
234    AudioManagerAlsa::StreamType wanted_type) {
235  return wanted_type == kStreamPlayback ? "Input" : "Output";
236}
237
238bool 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_malloc<char> io(wrapper_->DeviceNameGetHint(*hint_iter,
256                                                               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
278AudioOutputStream* AudioManagerAlsa::MakeLinearOutputStream(
279    const AudioParameters& params) {
280  DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
281  return MakeOutputStream(params);
282}
283
284AudioOutputStream* AudioManagerAlsa::MakeLowLatencyOutputStream(
285    const AudioParameters& params,
286    const std::string& device_id,
287    const std::string& input_device_id) {
288  DLOG_IF(ERROR, !device_id.empty()) << "Not implemented!";
289  DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
290  // TODO(xians): Use input_device_id for unified IO.
291  return MakeOutputStream(params);
292}
293
294AudioInputStream* AudioManagerAlsa::MakeLinearInputStream(
295    const AudioParameters& params, const std::string& device_id) {
296  DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
297  return MakeInputStream(params, device_id);
298}
299
300AudioInputStream* AudioManagerAlsa::MakeLowLatencyInputStream(
301    const AudioParameters& params, const std::string& device_id) {
302  DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
303  return MakeInputStream(params, device_id);
304}
305
306AudioParameters AudioManagerAlsa::GetPreferredOutputStreamParameters(
307    const std::string& output_device_id,
308    const AudioParameters& input_params) {
309  // TODO(tommi): Support |output_device_id|.
310  DLOG_IF(ERROR, !output_device_id.empty()) << "Not implemented!";
311  static const int kDefaultOutputBufferSize = 2048;
312  ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO;
313  int sample_rate = kDefaultSampleRate;
314  int buffer_size = kDefaultOutputBufferSize;
315  int bits_per_sample = 16;
316  int input_channels = 0;
317  if (input_params.IsValid()) {
318    // Some clients, such as WebRTC, have a more limited use case and work
319    // acceptably with a smaller buffer size.  The check below allows clients
320    // which want to try a smaller buffer size on Linux to do so.
321    // TODO(dalecurtis): This should include bits per channel and channel layout
322    // eventually.
323    sample_rate = input_params.sample_rate();
324    bits_per_sample = input_params.bits_per_sample();
325    channel_layout = input_params.channel_layout();
326    input_channels = input_params.input_channels();
327    buffer_size = std::min(input_params.frames_per_buffer(), buffer_size);
328  }
329
330  int user_buffer_size = GetUserBufferSize();
331  if (user_buffer_size)
332    buffer_size = user_buffer_size;
333
334  return AudioParameters(
335      AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout, input_channels,
336      sample_rate, bits_per_sample, buffer_size);
337}
338
339AudioOutputStream* AudioManagerAlsa::MakeOutputStream(
340    const AudioParameters& params) {
341  std::string device_name = AlsaPcmOutputStream::kAutoSelectDevice;
342  if (CommandLine::ForCurrentProcess()->HasSwitch(
343          switches::kAlsaOutputDevice)) {
344    device_name = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
345        switches::kAlsaOutputDevice);
346  }
347  return new AlsaPcmOutputStream(device_name, params, wrapper_.get(), this);
348}
349
350AudioInputStream* AudioManagerAlsa::MakeInputStream(
351    const AudioParameters& params, const std::string& device_id) {
352  std::string device_name = (device_id == AudioManagerBase::kDefaultDeviceId) ?
353      AlsaPcmInputStream::kAutoSelectDevice : device_id;
354  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAlsaInputDevice)) {
355    device_name = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
356        switches::kAlsaInputDevice);
357  }
358
359  return new AlsaPcmInputStream(this, device_name, params, wrapper_.get());
360}
361
362}  // namespace media
363