1 // Copyright 2017 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 "chromecast/media/audio/cast_audio_manager_alsa.h"
6 
7 #include <utility>
8 
9 #include "base/logging.h"
10 #include "base/memory/free_deleter.h"
11 #include "base/stl_util.h"
12 #include "base/strings/string_piece.h"
13 #include "chromecast/media/api/cma_backend_factory.h"
14 #include "chromecast/media/audio/audio_buildflags.h"
15 #include "chromecast/media/audio/cast_audio_input_stream.h"
16 #include "media/audio/alsa/alsa_input.h"
17 #include "media/audio/alsa/alsa_wrapper.h"
18 
19 namespace chromecast {
20 namespace media {
21 
22 namespace {
23 
24 // TODO(alokp): Query the preferred value from media backend.
25 const int kDefaultSampleRate = BUILDFLAG(AUDIO_INPUT_SAMPLE_RATE);
26 
27 // TODO(jyw): Query the preferred value from media backend.
28 const int kDefaultInputBufferSize = 1024;
29 
30 const int kCommunicationsSampleRate = 16000;
31 const int kCommunicationsInputBufferSize = 160;  // 10 ms.
32 
33 // Since "default" and "dmix" devices are virtual devices mapped to real
34 // devices, we remove them from the list to avoiding duplicate counting.
35 constexpr base::StringPiece kInvalidAudioInputDevices[] = {
36     "default",
37     "dmix",
38     "null",
39     "communications",
40 };
41 
42 // Constants specified by the ALSA API for device hints.
43 constexpr char kPcmInterfaceName[] = "pcm";
44 constexpr char kIoHintName[] = "IOID";
45 constexpr char kNameHintName[] = "NAME";
46 constexpr char kDescriptionHintName[] = "DESC";
47 
IsAlsaDeviceAvailable(CastAudioManagerAlsa::StreamType type,const char * device_name)48 bool IsAlsaDeviceAvailable(CastAudioManagerAlsa::StreamType type,
49                            const char* device_name) {
50   if (!device_name)
51     return false;
52 
53   // We do prefix matches on the device name to see whether to include
54   // it or not.
55   if (type == CastAudioManagerAlsa::kStreamCapture) {
56     // Check if the device is in the list of invalid devices.
57     for (size_t i = 0; i < base::size(kInvalidAudioInputDevices); ++i) {
58       if (kInvalidAudioInputDevices[i] == device_name)
59         return false;
60     }
61     return true;
62   } else {
63     DCHECK_EQ(CastAudioManagerAlsa::kStreamPlayback, type);
64     // We prefer the device type that maps straight to hardware but
65     // goes through software conversion if needed (e.g. incompatible
66     // sample rate).
67     // TODO(joi): Should we prefer "hw" instead?
68     const std::string kDeviceTypeDesired = "plughw";
69     return kDeviceTypeDesired == device_name;
70   }
71 }
72 
UnwantedDeviceTypeWhenEnumerating(CastAudioManagerAlsa::StreamType wanted_type)73 std::string UnwantedDeviceTypeWhenEnumerating(
74     CastAudioManagerAlsa::StreamType wanted_type) {
75   return wanted_type == CastAudioManagerAlsa::kStreamPlayback ? "Input"
76                                                               : "Output";
77 }
78 
79 }  // namespace
80 
CastAudioManagerAlsa(std::unique_ptr<::media::AudioThread> audio_thread,::media::AudioLogFactory * audio_log_factory,base::RepeatingCallback<CmaBackendFactory * ()> backend_factory_getter,CastAudioManagerHelper::GetSessionIdCallback get_session_id_callback,scoped_refptr<base::SingleThreadTaskRunner> browser_task_runner,scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,mojo::PendingRemote<chromecast::mojom::ServiceConnector> connector,bool use_mixer)81 CastAudioManagerAlsa::CastAudioManagerAlsa(
82     std::unique_ptr<::media::AudioThread> audio_thread,
83     ::media::AudioLogFactory* audio_log_factory,
84     base::RepeatingCallback<CmaBackendFactory*()> backend_factory_getter,
85     CastAudioManagerHelper::GetSessionIdCallback get_session_id_callback,
86     scoped_refptr<base::SingleThreadTaskRunner> browser_task_runner,
87     scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
88     mojo::PendingRemote<chromecast::mojom::ServiceConnector> connector,
89     bool use_mixer)
90     : CastAudioManager(std::move(audio_thread),
91                        audio_log_factory,
92                        std::move(backend_factory_getter),
93                        std::move(get_session_id_callback),
94                        browser_task_runner,
95                        media_task_runner,
96                        std::move(connector),
97                        use_mixer),
98       wrapper_(new ::media::AlsaWrapper()) {}
99 
~CastAudioManagerAlsa()100 CastAudioManagerAlsa::~CastAudioManagerAlsa() {}
101 
HasAudioInputDevices()102 bool CastAudioManagerAlsa::HasAudioInputDevices() {
103   return true;
104 }
105 
GetAudioInputDeviceNames(::media::AudioDeviceNames * device_names)106 void CastAudioManagerAlsa::GetAudioInputDeviceNames(
107     ::media::AudioDeviceNames* device_names) {
108   DCHECK(device_names->empty());
109 
110   // Prepend the default device since we always want it to be on the top of the
111   // list for all platforms. Note, pulse has exclusively opened the default
112   // device, so we must open the device via the "default" moniker.
113   device_names->push_front(::media::AudioDeviceName::CreateDefault());
114 #if BUILDFLAG(ENABLE_AUDIO_CAPTURE_SERVICE)
115   device_names->push_back(::media::AudioDeviceName::CreateCommunications());
116 #endif  // BUILDFLAG(ENABLE_AUDIO_CAPTURE_SERVICE)
117 
118   GetAlsaAudioDevices(kStreamCapture, device_names);
119 }
120 
GetInputStreamParameters(const std::string & device_id)121 ::media::AudioParameters CastAudioManagerAlsa::GetInputStreamParameters(
122     const std::string& device_id) {
123   if (device_id == ::media::AudioDeviceDescription::kCommunicationsDeviceId) {
124 #if !BUILDFLAG(ENABLE_AUDIO_CAPTURE_SERVICE)
125     NOTIMPLEMENTED()
126         << "Capture Service is not enabled, return a fake AudioParameters.";
127     return ::media::AudioParameters();
128 #endif  // BUILDFLAG(ENABLE_AUDIO_CAPTURE_SERVICE)
129     return ::media::AudioParameters(::media::AudioParameters::AUDIO_PCM_LINEAR,
130                                     ::media::CHANNEL_LAYOUT_MONO,
131                                     kCommunicationsSampleRate,
132                                     kCommunicationsInputBufferSize);
133   }
134   // TODO(jyw): Be smarter about sample rate instead of hardcoding it.
135   // Need to send a valid AudioParameters object even when it will be unused.
136   return ::media::AudioParameters(
137       ::media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
138       ::media::CHANNEL_LAYOUT_STEREO, kDefaultSampleRate,
139       kDefaultInputBufferSize);
140 }
141 
MakeLinearInputStream(const::media::AudioParameters & params,const std::string & device_id,const::media::AudioManager::LogCallback & log_callback)142 ::media::AudioInputStream* CastAudioManagerAlsa::MakeLinearInputStream(
143     const ::media::AudioParameters& params,
144     const std::string& device_id,
145     const ::media::AudioManager::LogCallback& log_callback) {
146   DCHECK_EQ(::media::AudioParameters::AUDIO_PCM_LINEAR, params.format());
147   return MakeInputStream(params, device_id);
148 }
149 
MakeLowLatencyInputStream(const::media::AudioParameters & params,const std::string & device_id,const::media::AudioManager::LogCallback & log_callback)150 ::media::AudioInputStream* CastAudioManagerAlsa::MakeLowLatencyInputStream(
151     const ::media::AudioParameters& params,
152     const std::string& device_id,
153     const ::media::AudioManager::LogCallback& log_callback) {
154   DCHECK_EQ(::media::AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
155   return MakeInputStream(params, device_id);
156 }
157 
MakeInputStream(const::media::AudioParameters & params,const std::string & device_id)158 ::media::AudioInputStream* CastAudioManagerAlsa::MakeInputStream(
159     const ::media::AudioParameters& params,
160     const std::string& device_id) {
161   std::string device_name =
162       (device_id == ::media::AudioDeviceDescription::kDefaultDeviceId)
163           ? ::media::AlsaPcmInputStream::kAutoSelectDevice
164           : device_id;
165   if (device_name == ::media::AudioDeviceDescription::kCommunicationsDeviceId) {
166 #if !BUILDFLAG(ENABLE_AUDIO_CAPTURE_SERVICE)
167     NOTIMPLEMENTED() << "Capture Service is not enabled, return nullptr.";
168     return nullptr;
169 #endif  // BUILDFLAG(ENABLE_AUDIO_CAPTURE_SERVICE)
170     return new CastAudioInputStream(this, params, device_name);
171   }
172   return new ::media::AlsaPcmInputStream(this, device_name, params,
173                                          wrapper_.get());
174 }
175 
GetAlsaAudioDevices(StreamType type,::media::AudioDeviceNames * device_names)176 void CastAudioManagerAlsa::GetAlsaAudioDevices(
177     StreamType type,
178     ::media::AudioDeviceNames* device_names) {
179   int card = -1;
180 
181   // Loop through the sound cards to get ALSA device hints.
182   while (!wrapper_->CardNext(&card) && card >= 0) {
183     void** hints = NULL;
184     int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints);
185     if (!error) {
186       GetAlsaDevicesInfo(type, hints, device_names);
187 
188       // Destroy the hints now that we're done with it.
189       wrapper_->DeviceNameFreeHint(hints);
190     } else {
191       DLOG(WARNING) << "GetAlsaAudioDevices: unable to get device hints: "
192                     << wrapper_->StrError(error);
193     }
194   }
195 }
196 
GetAlsaDevicesInfo(StreamType type,void ** hints,::media::AudioDeviceNames * device_names)197 void CastAudioManagerAlsa::GetAlsaDevicesInfo(
198     StreamType type,
199     void** hints,
200     ::media::AudioDeviceNames* device_names) {
201   const std::string unwanted_device_type =
202       UnwantedDeviceTypeWhenEnumerating(type);
203 
204   for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) {
205     // Only examine devices of the right type.  Valid values are
206     // "Input", "Output", and NULL which means both input and output.
207     std::unique_ptr<char, base::FreeDeleter> io(
208         wrapper_->DeviceNameGetHint(*hint_iter, kIoHintName));
209     if (io && unwanted_device_type == io.get())
210       continue;
211 
212     // Get the unique device name for the device.
213     std::unique_ptr<char, base::FreeDeleter> unique_device_name(
214         wrapper_->DeviceNameGetHint(*hint_iter, kNameHintName));
215 
216     // Find out if the device is available.
217     if (IsAlsaDeviceAvailable(type, unique_device_name.get())) {
218       // Get the description for the device.
219       std::unique_ptr<char, base::FreeDeleter> desc(
220           wrapper_->DeviceNameGetHint(*hint_iter, kDescriptionHintName));
221 
222       ::media::AudioDeviceName name;
223       name.unique_id = unique_device_name.get();
224       if (desc) {
225         name.device_name = desc.get();
226         // Use the more user friendly description as name.
227         // Replace '\n' with '-'.
228         name.device_name.replace(name.device_name.find('\n'), 1, 1, '-');
229       } else {
230         // Virtual devices don't necessarily have descriptions.
231         // Use their names instead.
232         name.device_name = unique_device_name.get();
233       }
234 
235       // Store the device information.
236       device_names->push_back(name);
237     }
238   }
239 }
240 
241 }  // namespace media
242 }  // namespace chromecast
243