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