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.
5 #include "media/audio/alsa/audio_manager_alsa.h"
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"
18 #include "media/audio/cras/audio_manager_cras.h"
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"
26 #include "media/base/channel_layout.h"
27 #include "media/base/limits.h"
28 #include "media/base/media_switches.h"
32 // Maximum number of output streams that can be open simultaneously.
33 static const int kMaxOutputStreams
= 50;
35 // Default sample rate for input and output streams.
36 static const int kDefaultSampleRate
= 48000;
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
[] = {
51 void AudioManagerAlsa::ShowLinuxAudioInputSettings() {
52 scoped_ptr
<base::Environment
> env(base::Environment::Create());
53 base::CommandLine
command_line(base::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"));
58 case base::nix::DESKTOP_ENVIRONMENT_KDE3
:
59 case base::nix::DESKTOP_ENVIRONMENT_KDE4
:
60 command_line
.SetProgram(base::FilePath("kmix"));
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");
68 LOG(ERROR
) << "Failed to show audio input settings: we don't know "
69 << "what command to use for your desktop environment.";
72 base::LaunchProcess(command_line
, base::LaunchOptions());
75 // Implementation of AudioManager.
76 bool AudioManagerAlsa::HasAudioOutputDevices() {
77 return HasAnyAlsaAudioDevice(kStreamPlayback
);
80 bool AudioManagerAlsa::HasAudioInputDevices() {
81 return HasAnyAlsaAudioDevice(kStreamCapture
);
84 AudioManagerAlsa::AudioManagerAlsa(AudioLogFactory
* audio_log_factory
)
85 : AudioManagerBase(audio_log_factory
),
86 wrapper_(new AlsaWrapper()) {
87 SetMaxOutputStreamsAllowed(kMaxOutputStreams
);
90 AudioManagerAlsa::~AudioManagerAlsa() {
94 void AudioManagerAlsa::ShowAudioInputSettings() {
95 ShowLinuxAudioInputSettings();
98 void AudioManagerAlsa::GetAudioInputDeviceNames(
99 AudioDeviceNames
* device_names
) {
100 DCHECK(device_names
->empty());
101 GetAlsaAudioDevices(kStreamCapture
, device_names
);
104 void AudioManagerAlsa::GetAudioOutputDeviceNames(
105 AudioDeviceNames
* device_names
) {
106 DCHECK(device_names
->empty());
107 GetAlsaAudioDevices(kStreamPlayback
, device_names
);
110 AudioParameters
AudioManagerAlsa::GetInputStreamParameters(
111 const std::string
& device_id
) {
112 static const int kDefaultInputBufferSize
= 1024;
114 return AudioParameters(
115 AudioParameters::AUDIO_PCM_LOW_LATENCY
, CHANNEL_LAYOUT_STEREO
,
116 kDefaultSampleRate
, 16, kDefaultInputBufferSize
);
119 void AudioManagerAlsa::GetAlsaAudioDevices(
121 media::AudioDeviceNames
* device_names
) {
122 // Constants specified by the ALSA API for device hints.
123 static const char kPcmInterfaceName
[] = "pcm";
126 // Loop through the sound cards to get ALSA device hints.
127 while (!wrapper_
->CardNext(&card
) && card
>= 0) {
129 int error
= wrapper_
->DeviceNameHint(card
, kPcmInterfaceName
, &hints
);
131 GetAlsaDevicesInfo(type
, hints
, device_names
);
133 // Destroy the hints now that we're done with it.
134 wrapper_
->DeviceNameFreeHint(hints
);
136 DLOG(WARNING
) << "GetAlsaAudioDevices: unable to get device hints: "
137 << wrapper_
->StrError(error
);
142 void AudioManagerAlsa::GetAlsaDevicesInfo(
143 AudioManagerAlsa::StreamType type
,
145 media::AudioDeviceNames
* device_names
) {
146 static const char kIoHintName
[] = "IOID";
147 static const char kNameHintName
[] = "NAME";
148 static const char kDescriptionHintName
[] = "DESC";
150 const char* unwanted_device_type
= UnwantedDeviceTypeWhenEnumerating(type
);
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)
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
));
171 // Get the unique device name for the device.
172 scoped_ptr
<char, base::FreeDeleter
> unique_device_name(
173 wrapper_
->DeviceNameGetHint(*hint_iter
, kNameHintName
));
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
));
181 media::AudioDeviceName name
;
182 name
.unique_id
= unique_device_name
.get();
184 // Use the more user friendly description as name.
185 // Replace '\n' with '-'.
186 char* pret
= strchr(desc
.get(), '\n');
189 name
.device_name
= desc
.get();
191 // Virtual devices don't necessarily have descriptions.
192 // Use their names instead.
193 name
.device_name
= unique_device_name
.get();
196 // Store the device information.
197 device_names
->push_back(name
);
203 bool AudioManagerAlsa::IsAlsaDeviceAvailable(
204 AudioManagerAlsa::StreamType type
,
205 const char* device_name
) {
209 // We do prefix matches on the device name to see whether to include
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)
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
224 // TODO(joi): Should we prefer "hw" instead?
225 static const char kDeviceTypeDesired
[] = "plughw";
226 return strncmp(kDeviceTypeDesired
,
228 arraysize(kDeviceTypeDesired
) - 1) == 0;
233 const char* AudioManagerAlsa::UnwantedDeviceTypeWhenEnumerating(
234 AudioManagerAlsa::StreamType wanted_type
) {
235 return wanted_type
== kStreamPlayback
? "Input" : "Output";
238 bool AudioManagerAlsa::HasAnyAlsaAudioDevice(
239 AudioManagerAlsa::StreamType stream
) {
240 static const char kPcmInterfaceName
[] = "pcm";
241 static const char kIoHintName
[] = "IOID";
243 bool has_device
= false;
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
);
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.
261 // Found an input device.
266 // Destroy the hints now that we're done with it.
267 wrapper_
->DeviceNameFreeHint(hints
);
270 DLOG(WARNING
) << "HasAnyAudioDevice: unable to get device hints: "
271 << wrapper_
->StrError(error
);
278 AudioOutputStream
* AudioManagerAlsa::MakeLinearOutputStream(
279 const AudioParameters
& params
) {
280 DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR
, params
.format());
281 return MakeOutputStream(params
);
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
);
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
);
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
);
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 if (input_params
.IsValid()) {
315 // Some clients, such as WebRTC, have a more limited use case and work
316 // acceptably with a smaller buffer size. The check below allows clients
317 // which want to try a smaller buffer size on Linux to do so.
318 // TODO(dalecurtis): This should include bits per channel and channel layout
320 sample_rate
= input_params
.sample_rate();
321 bits_per_sample
= input_params
.bits_per_sample();
322 channel_layout
= input_params
.channel_layout();
323 buffer_size
= std::min(input_params
.frames_per_buffer(), buffer_size
);
326 int user_buffer_size
= GetUserBufferSize();
327 if (user_buffer_size
)
328 buffer_size
= user_buffer_size
;
330 return AudioParameters(
331 AudioParameters::AUDIO_PCM_LOW_LATENCY
, channel_layout
,
332 sample_rate
, bits_per_sample
, buffer_size
, AudioParameters::NO_EFFECTS
);
335 AudioOutputStream
* AudioManagerAlsa::MakeOutputStream(
336 const AudioParameters
& params
) {
337 std::string device_name
= AlsaPcmOutputStream::kAutoSelectDevice
;
338 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
339 switches::kAlsaOutputDevice
)) {
340 device_name
= base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
341 switches::kAlsaOutputDevice
);
343 return new AlsaPcmOutputStream(device_name
, params
, wrapper_
.get(), this);
346 AudioInputStream
* AudioManagerAlsa::MakeInputStream(
347 const AudioParameters
& params
, const std::string
& device_id
) {
348 std::string device_name
= (device_id
== AudioManagerBase::kDefaultDeviceId
) ?
349 AlsaPcmInputStream::kAutoSelectDevice
: device_id
;
350 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
351 switches::kAlsaInputDevice
)) {
352 device_name
= base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
353 switches::kAlsaInputDevice
);
356 return new AlsaPcmInputStream(this, device_name
, params
, wrapper_
.get());