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 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 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 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 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 82 static inline void snd_mask_any(struct snd_mask *mask) 83 { 84 memset(mask, 0xff, sizeof(struct snd_mask)); 85 } 86 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 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 127 AudioHotplugThread::AudioHotplugThread(Callback& callback) 128 : mCallback(callback) 129 , mShutdownEventFD(-1) 130 { 131 } 132 133 AudioHotplugThread::~AudioHotplugThread() 134 { 135 if (mShutdownEventFD != -1) { 136 ::close(mShutdownEventFD); 137 } 138 } 139 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 150 void AudioHotplugThread::shutdown() 151 { 152 requestExit(); 153 uint64_t tmp = 1; 154 ::write(mShutdownEventFD, &tmp, sizeof(tmp)); 155 join(); 156 } 157 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 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*>(¶ms), n); 173 *min = interval->min; 174 *max = interval->max; 175 } 176 177 // This was hacked out of "alsa_utils.cpp". 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 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(¶ms); 217 ret = ioctl(alsaFD, SNDRV_PCM_IOCTL_HW_REFINE, ¶ms); 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 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 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