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