1 /*
2 **
3 ** Copyright 2012, The Android Open Source Project
4 **
5 ** Licensed under the Apache License, Version 2.0 (the "License");
6 ** you may not use this file except in compliance with the License.
7 ** You may obtain a copy of the License at
8 **
9 ** http://www.apache.org/licenses/LICENSE-2.0
10 **
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 */
17
18 #define LOG_TAG "AudioHAL:AudioHotplugThread"
19 #include <utils/Log.h>
20
21 #include <assert.h>
22 #include <dirent.h>
23 #include <poll.h>
24 #include <sys/eventfd.h>
25 #include <sys/inotify.h>
26 #include <sys/ioctl.h>
27
28 #include <sound/asound.h>
29
30 #include <utils/misc.h>
31 #include <utils/String8.h>
32
33 #include "AudioHotplugThread.h"
34
35 // This name is used to recognize the AndroidTV Remote mic so we can
36 // use it for voice recognition.
37 #define ANDROID_TV_REMOTE_AUDIO_DEVICE_NAME "ATVRAudio"
38
39 namespace android {
40
41 /*
42 * ALSA parameter manipulation routines
43 *
44 * TODO: replace this when TinyAlsa offers a suitable API
45 */
46
param_is_mask(int p)47 static inline int param_is_mask(int p)
48 {
49 return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) &&
50 (p <= SNDRV_PCM_HW_PARAM_LAST_MASK);
51 }
52
param_is_interval(int p)53 static inline int param_is_interval(int p)
54 {
55 return (p >= SNDRV_PCM_HW_PARAM_FIRST_INTERVAL) &&
56 (p <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL);
57 }
58
param_to_interval(struct snd_pcm_hw_params * p,int n)59 static inline struct snd_interval *param_to_interval(
60 struct snd_pcm_hw_params *p, int n)
61 {
62 assert(p->intervals);
63 assert(param_is_interval(n));
64 return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]);
65 }
66
param_to_mask(struct snd_pcm_hw_params * p,int n)67 static inline struct snd_mask *param_to_mask(struct snd_pcm_hw_params *p, int n)
68 {
69 assert(p->masks);
70 assert(param_is_mask(n));
71 return &(p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]);
72 }
73
snd_mask_any(struct snd_mask * mask)74 static inline void snd_mask_any(struct snd_mask *mask)
75 {
76 memset(mask, 0xff, sizeof(struct snd_mask));
77 }
78
snd_interval_any(struct snd_interval * i)79 static inline void snd_interval_any(struct snd_interval *i)
80 {
81 i->min = 0;
82 i->openmin = 0;
83 i->max = UINT_MAX;
84 i->openmax = 0;
85 i->integer = 0;
86 i->empty = 0;
87 }
88
param_init(struct snd_pcm_hw_params * p)89 static void param_init(struct snd_pcm_hw_params *p)
90 {
91 int n, k;
92
93 memset(p, 0, sizeof(*p));
94 for (n = SNDRV_PCM_HW_PARAM_FIRST_MASK;
95 n <= SNDRV_PCM_HW_PARAM_LAST_MASK; n++) {
96 struct snd_mask *m = param_to_mask(p, n);
97 snd_mask_any(m);
98 }
99 for (n = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL;
100 n <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; n++) {
101 struct snd_interval *i = param_to_interval(p, n);
102 snd_interval_any(i);
103 }
104 p->rmask = 0xFFFFFFFF;
105 }
106
107 /*
108 * Hotplug thread
109 */
110
111 const char* AudioHotplugThread::kThreadName = "ATVRemoteAudioHotplug";
112
113 // directory where ALSA device nodes appear
114 const char* AudioHotplugThread::kAlsaDeviceDir = "/dev/snd";
115
116 // filename suffix for ALSA nodes representing capture devices
117 const char AudioHotplugThread::kDeviceTypeCapture = 'c';
118
AudioHotplugThread(Callback & callback)119 AudioHotplugThread::AudioHotplugThread(Callback& callback)
120 : mCallback(callback)
121 , mShutdownEventFD(-1)
122 {
123 }
124
~AudioHotplugThread()125 AudioHotplugThread::~AudioHotplugThread()
126 {
127 if (mShutdownEventFD != -1) {
128 ::close(mShutdownEventFD);
129 }
130 }
131
start()132 bool AudioHotplugThread::start()
133 {
134 mShutdownEventFD = eventfd(0, EFD_NONBLOCK);
135 if (mShutdownEventFD == -1) {
136 return false;
137 }
138
139 return (run(kThreadName) == NO_ERROR);
140 }
141
shutdown()142 void AudioHotplugThread::shutdown()
143 {
144 requestExit();
145 uint64_t tmp = 1;
146 ::write(mShutdownEventFD, &tmp, sizeof(tmp));
147 join();
148 }
149
parseCaptureDeviceName(const char * name,unsigned int * card,unsigned int * device)150 bool AudioHotplugThread::parseCaptureDeviceName(const char* name,
151 unsigned int* card,
152 unsigned int* device)
153 {
154 char deviceType;
155 int ret = sscanf(name, "pcmC%uD%u%c", card, device, &deviceType);
156 return (ret == 3 && deviceType == kDeviceTypeCapture);
157 }
158
getAlsaParamInterval(const struct snd_pcm_hw_params & params,int n,unsigned int * min,unsigned int * max)159 static inline void getAlsaParamInterval(const struct snd_pcm_hw_params& params,
160 int n, unsigned int* min,
161 unsigned int* max)
162 {
163 struct snd_interval* interval = param_to_interval(
164 const_cast<struct snd_pcm_hw_params*>(¶ms), n);
165 *min = interval->min;
166 *max = interval->max;
167 }
168
169 // This was hacked out of "alsa_utils.cpp".
s_get_alsa_card_name(char * name,size_t len,int card_id)170 static int s_get_alsa_card_name(char *name, size_t len, int card_id)
171 {
172 int fd;
173 int amt = -1;
174 snprintf(name, len, "/proc/asound/card%d/id", card_id);
175 fd = open(name, O_RDONLY);
176 if (fd >= 0) {
177 amt = read(fd, name, len - 1);
178 if (amt > 0) {
179 // replace the '\n' at the end of the proc file with '\0'
180 name[amt - 1] = 0;
181 }
182 close(fd);
183 }
184 return amt;
185 }
186
getDeviceInfo(unsigned int pcmCard,unsigned int pcmDevice,DeviceInfo * info)187 bool AudioHotplugThread::getDeviceInfo(unsigned int pcmCard,
188 unsigned int pcmDevice,
189 DeviceInfo* info)
190 {
191 bool result = false;
192 int ret;
193 int len;
194 char cardName[64] = "";
195
196 String8 devicePath = String8::format("%s/pcmC%dD%d%c",
197 kAlsaDeviceDir, pcmCard, pcmDevice, kDeviceTypeCapture);
198
199 ALOGD("AudioHotplugThread::getDeviceInfo opening %s", devicePath.string());
200 int alsaFD = open(devicePath.string(), O_RDONLY);
201 if (alsaFD == -1) {
202 ALOGE("AudioHotplugThread::getDeviceInfo open failed for %s", devicePath.string());
203 goto done;
204 }
205
206 // query the device's ALSA configuration space
207 struct snd_pcm_hw_params params;
208 param_init(¶ms);
209 ret = ioctl(alsaFD, SNDRV_PCM_IOCTL_HW_REFINE, ¶ms);
210 if (ret == -1) {
211 ALOGE("AudioHotplugThread: refine ioctl failed");
212 goto done;
213 }
214
215 info->pcmCard = pcmCard;
216 info->pcmDevice = pcmDevice;
217 getAlsaParamInterval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
218 &info->minSampleBits, &info->maxSampleBits);
219 getAlsaParamInterval(params, SNDRV_PCM_HW_PARAM_CHANNELS,
220 &info->minChannelCount, &info->maxChannelCount);
221 getAlsaParamInterval(params, SNDRV_PCM_HW_PARAM_RATE,
222 &info->minSampleRate, &info->maxSampleRate);
223
224 // Ugly hack to recognize Remote mic and mark it for voice recognition
225 info->forVoiceRecognition = false;
226 len = s_get_alsa_card_name(cardName, sizeof(cardName), pcmCard);
227 ALOGD("AudioHotplugThread get_alsa_card_name returned %d, %s", len, cardName);
228 if (len > 0) {
229 if (strcmp(ANDROID_TV_REMOTE_AUDIO_DEVICE_NAME, cardName) == 0) {
230 ALOGD("AudioHotplugThread found Android TV remote mic on Card %d, for VOICE_RECOGNITION", pcmCard);
231 info->forVoiceRecognition = true;
232 }
233 }
234
235 result = true;
236
237 done:
238 if (alsaFD != -1) {
239 close(alsaFD);
240 }
241 return result;
242 }
243
244 // scan the ALSA device directory for a usable capture device
scanForDevice()245 void AudioHotplugThread::scanForDevice()
246 {
247 DIR* alsaDir;
248 DeviceInfo deviceInfo;
249
250 alsaDir = opendir(kAlsaDeviceDir);
251 if (alsaDir == NULL)
252 return;
253
254 while (true) {
255 struct dirent entry, *result;
256 int ret = readdir_r(alsaDir, &entry, &result);
257 if (ret != 0 || result == NULL)
258 break;
259 unsigned int pcmCard, pcmDevice;
260 if (parseCaptureDeviceName(entry.d_name, &pcmCard, &pcmDevice)) {
261 if (getDeviceInfo(pcmCard, pcmDevice, &deviceInfo)) {
262 mCallback.onDeviceFound(deviceInfo);
263 }
264 }
265 }
266
267 closedir(alsaDir);
268 }
269
threadLoop()270 bool AudioHotplugThread::threadLoop()
271 {
272 int inotifyFD = -1;
273 int watchFD = -1;
274 int flags;
275
276 // watch for changes to the ALSA device directory
277 inotifyFD = inotify_init();
278 if (inotifyFD == -1) {
279 ALOGE("AudioHotplugThread: inotify_init failed");
280 goto done;
281 }
282 flags = fcntl(inotifyFD, F_GETFL, 0);
283 if (flags == -1) {
284 ALOGE("AudioHotplugThread: F_GETFL failed");
285 goto done;
286 }
287 if (fcntl(inotifyFD, F_SETFL, flags | O_NONBLOCK) == -1) {
288 ALOGE("AudioHotplugThread: F_SETFL failed");
289 goto done;
290 }
291
292 watchFD = inotify_add_watch(inotifyFD, kAlsaDeviceDir,
293 IN_CREATE | IN_DELETE);
294 if (watchFD == -1) {
295 ALOGE("AudioHotplugThread: inotify_add_watch failed");
296 goto done;
297 }
298
299 // check for any existing capture devices
300 scanForDevice();
301
302 while (!exitPending()) {
303 // wait for a change to the ALSA directory or a shutdown signal
304 struct pollfd fds[2] = {
305 { inotifyFD, POLLIN, 0 },
306 { mShutdownEventFD, POLLIN, 0 }
307 };
308 int ret = poll(fds, NELEM(fds), -1);
309 if (ret == -1) {
310 ALOGE("AudioHotplugThread: poll failed");
311 break;
312 } else if (fds[1].revents & POLLIN) {
313 // shutdown requested
314 break;
315 }
316
317 if (!(fds[0].revents & POLLIN)) {
318 continue;
319 }
320
321 // parse the filesystem change events
322 char eventBuf[256];
323 ret = read(inotifyFD, eventBuf, sizeof(eventBuf));
324 if (ret == -1) {
325 ALOGE("AudioHotplugThread: read failed");
326 break;
327 }
328
329 for (int i = 0; i < ret;) {
330 if ((ret - i) < (int)sizeof(struct inotify_event)) {
331 ALOGE("AudioHotplugThread: read an invalid inotify_event");
332 break;
333 }
334
335 struct inotify_event *event =
336 reinterpret_cast<struct inotify_event*>(eventBuf + i);
337
338 if ((ret - i) < (int)(sizeof(struct inotify_event) + event->len)) {
339 ALOGE("AudioHotplugThread: read a bad inotify_event length");
340 break;
341 }
342
343 char *name = ((char *) event) +
344 offsetof(struct inotify_event, name);
345
346 unsigned int pcmCard, pcmDevice;
347 if (parseCaptureDeviceName(name, &pcmCard, &pcmDevice)) {
348 if (event->mask & IN_CREATE) {
349 // Some devices can not be opened immediately after the
350 // inotify event occurs. Add a delay to avoid these
351 // races. (50ms was chosen arbitrarily)
352 const int kOpenTimeoutMs = 50;
353 struct pollfd pfd = {mShutdownEventFD, POLLIN, 0};
354 if (poll(&pfd, 1, kOpenTimeoutMs) == -1) {
355 ALOGE("AudioHotplugThread: poll failed");
356 break;
357 } else if (pfd.revents & POLLIN) {
358 // shutdown requested
359 break;
360 }
361
362 DeviceInfo deviceInfo;
363 if (getDeviceInfo(pcmCard, pcmDevice, &deviceInfo)) {
364 mCallback.onDeviceFound(deviceInfo);
365 }
366 } else if (event->mask & IN_DELETE) {
367 mCallback.onDeviceRemoved(pcmCard, pcmDevice);
368 }
369 }
370
371 i += sizeof(struct inotify_event) + event->len;
372 }
373 }
374
375 done:
376 if (watchFD != -1) {
377 inotify_rm_watch(inotifyFD, watchFD);
378 }
379 if (inotifyFD != -1) {
380 close(inotifyFD);
381 }
382
383 return false;
384 }
385
386 }; // namespace android
387