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*>(&params), 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(&params);
209     ret = ioctl(alsaFD, SNDRV_PCM_IOCTL_HW_REFINE, &params);
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