Home | History | Annotate | Download | only in libaudio
      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*>(&params), 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(&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
    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