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