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