/* ** ** Copyright 2012, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ #define LOG_TAG "AudioHAL:AudioHotplugThread" #include #include #include #include #include #include #include #include #include #include #include "AudioHotplugThread.h" // This name is used to recognize the AndroidTV Remote mic so we can // use it for voice recognition. #define ANDROID_TV_REMOTE_AUDIO_DEVICE_NAME "ATVRAudio" namespace android { /* * ALSA parameter manipulation routines * * TODO: replace this when TinyAlsa offers a suitable API */ static inline int param_is_mask(int p) { return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) && (p <= SNDRV_PCM_HW_PARAM_LAST_MASK); } static inline int param_is_interval(int p) { return (p >= SNDRV_PCM_HW_PARAM_FIRST_INTERVAL) && (p <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL); } static inline struct snd_interval *param_to_interval( struct snd_pcm_hw_params *p, int n) { assert(p->intervals); assert(param_is_interval(n)); return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]); } static inline struct snd_mask *param_to_mask(struct snd_pcm_hw_params *p, int n) { assert(p->masks); assert(param_is_mask(n)); return &(p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]); } static inline void snd_mask_any(struct snd_mask *mask) { memset(mask, 0xff, sizeof(struct snd_mask)); } static inline void snd_interval_any(struct snd_interval *i) { i->min = 0; i->openmin = 0; i->max = UINT_MAX; i->openmax = 0; i->integer = 0; i->empty = 0; } static void param_init(struct snd_pcm_hw_params *p) { int n, k; memset(p, 0, sizeof(*p)); for (n = SNDRV_PCM_HW_PARAM_FIRST_MASK; n <= SNDRV_PCM_HW_PARAM_LAST_MASK; n++) { struct snd_mask *m = param_to_mask(p, n); snd_mask_any(m); } for (n = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; n <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; n++) { struct snd_interval *i = param_to_interval(p, n); snd_interval_any(i); } p->rmask = 0xFFFFFFFF; } /* * Hotplug thread */ const char* AudioHotplugThread::kThreadName = "ATVRemoteAudioHotplug"; // directory where ALSA device nodes appear const char* AudioHotplugThread::kAlsaDeviceDir = "/dev/snd"; // filename suffix for ALSA nodes representing capture devices const char AudioHotplugThread::kDeviceTypeCapture = 'c'; AudioHotplugThread::AudioHotplugThread(Callback& callback) : mCallback(callback) , mShutdownEventFD(-1) { } AudioHotplugThread::~AudioHotplugThread() { if (mShutdownEventFD != -1) { ::close(mShutdownEventFD); } } bool AudioHotplugThread::start() { mShutdownEventFD = eventfd(0, EFD_NONBLOCK); if (mShutdownEventFD == -1) { return false; } return (run(kThreadName) == NO_ERROR); } void AudioHotplugThread::shutdown() { requestExit(); uint64_t tmp = 1; ::write(mShutdownEventFD, &tmp, sizeof(tmp)); join(); } bool AudioHotplugThread::parseCaptureDeviceName(const char* name, unsigned int* card, unsigned int* device) { char deviceType; int ret = sscanf(name, "pcmC%uD%u%c", card, device, &deviceType); return (ret == 3 && deviceType == kDeviceTypeCapture); } static inline void getAlsaParamInterval(const struct snd_pcm_hw_params& params, int n, unsigned int* min, unsigned int* max) { struct snd_interval* interval = param_to_interval( const_cast(¶ms), n); *min = interval->min; *max = interval->max; } // This was hacked out of "alsa_utils.cpp". static int s_get_alsa_card_name(char *name, size_t len, int card_id) { int fd; int amt = -1; snprintf(name, len, "/proc/asound/card%d/id", card_id); fd = open(name, O_RDONLY); if (fd >= 0) { amt = read(fd, name, len - 1); if (amt > 0) { // replace the '\n' at the end of the proc file with '\0' name[amt - 1] = 0; } close(fd); } return amt; } bool AudioHotplugThread::getDeviceInfo(unsigned int pcmCard, unsigned int pcmDevice, DeviceInfo* info) { bool result = false; int ret; int len; char cardName[64] = ""; String8 devicePath = String8::format("%s/pcmC%dD%d%c", kAlsaDeviceDir, pcmCard, pcmDevice, kDeviceTypeCapture); ALOGD("AudioHotplugThread::getDeviceInfo opening %s", devicePath.string()); int alsaFD = open(devicePath.string(), O_RDONLY); if (alsaFD == -1) { ALOGE("AudioHotplugThread::getDeviceInfo open failed for %s", devicePath.string()); goto done; } // query the device's ALSA configuration space struct snd_pcm_hw_params params; param_init(¶ms); ret = ioctl(alsaFD, SNDRV_PCM_IOCTL_HW_REFINE, ¶ms); if (ret == -1) { ALOGE("AudioHotplugThread: refine ioctl failed"); goto done; } info->pcmCard = pcmCard; info->pcmDevice = pcmDevice; getAlsaParamInterval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, &info->minSampleBits, &info->maxSampleBits); getAlsaParamInterval(params, SNDRV_PCM_HW_PARAM_CHANNELS, &info->minChannelCount, &info->maxChannelCount); getAlsaParamInterval(params, SNDRV_PCM_HW_PARAM_RATE, &info->minSampleRate, &info->maxSampleRate); // Ugly hack to recognize Remote mic and mark it for voice recognition info->forVoiceRecognition = false; len = s_get_alsa_card_name(cardName, sizeof(cardName), pcmCard); ALOGD("AudioHotplugThread get_alsa_card_name returned %d, %s", len, cardName); if (len > 0) { if (strcmp(ANDROID_TV_REMOTE_AUDIO_DEVICE_NAME, cardName) == 0) { ALOGD("AudioHotplugThread found Android TV remote mic on Card %d, for VOICE_RECOGNITION", pcmCard); info->forVoiceRecognition = true; } } result = true; done: if (alsaFD != -1) { close(alsaFD); } return result; } // scan the ALSA device directory for a usable capture device void AudioHotplugThread::scanForDevice() { DIR* alsaDir; DeviceInfo deviceInfo; alsaDir = opendir(kAlsaDeviceDir); if (alsaDir == NULL) return; while (true) { struct dirent entry, *result; int ret = readdir_r(alsaDir, &entry, &result); if (ret != 0 || result == NULL) break; unsigned int pcmCard, pcmDevice; if (parseCaptureDeviceName(entry.d_name, &pcmCard, &pcmDevice)) { if (getDeviceInfo(pcmCard, pcmDevice, &deviceInfo)) { mCallback.onDeviceFound(deviceInfo); } } } closedir(alsaDir); } bool AudioHotplugThread::threadLoop() { int inotifyFD = -1; int watchFD = -1; int flags; // watch for changes to the ALSA device directory inotifyFD = inotify_init(); if (inotifyFD == -1) { ALOGE("AudioHotplugThread: inotify_init failed"); goto done; } flags = fcntl(inotifyFD, F_GETFL, 0); if (flags == -1) { ALOGE("AudioHotplugThread: F_GETFL failed"); goto done; } if (fcntl(inotifyFD, F_SETFL, flags | O_NONBLOCK) == -1) { ALOGE("AudioHotplugThread: F_SETFL failed"); goto done; } watchFD = inotify_add_watch(inotifyFD, kAlsaDeviceDir, IN_CREATE | IN_DELETE); if (watchFD == -1) { ALOGE("AudioHotplugThread: inotify_add_watch failed"); goto done; } // check for any existing capture devices scanForDevice(); while (!exitPending()) { // wait for a change to the ALSA directory or a shutdown signal struct pollfd fds[2] = { { inotifyFD, POLLIN, 0 }, { mShutdownEventFD, POLLIN, 0 } }; int ret = poll(fds, NELEM(fds), -1); if (ret == -1) { ALOGE("AudioHotplugThread: poll failed"); break; } else if (fds[1].revents & POLLIN) { // shutdown requested break; } if (!(fds[0].revents & POLLIN)) { continue; } // parse the filesystem change events char eventBuf[256]; ret = read(inotifyFD, eventBuf, sizeof(eventBuf)); if (ret == -1) { ALOGE("AudioHotplugThread: read failed"); break; } for (int i = 0; i < ret;) { if ((ret - i) < (int)sizeof(struct inotify_event)) { ALOGE("AudioHotplugThread: read an invalid inotify_event"); break; } struct inotify_event *event = reinterpret_cast(eventBuf + i); if ((ret - i) < (int)(sizeof(struct inotify_event) + event->len)) { ALOGE("AudioHotplugThread: read a bad inotify_event length"); break; } char *name = ((char *) event) + offsetof(struct inotify_event, name); unsigned int pcmCard, pcmDevice; if (parseCaptureDeviceName(name, &pcmCard, &pcmDevice)) { if (event->mask & IN_CREATE) { // Some devices can not be opened immediately after the // inotify event occurs. Add a delay to avoid these // races. (50ms was chosen arbitrarily) const int kOpenTimeoutMs = 50; struct pollfd pfd = {mShutdownEventFD, POLLIN, 0}; if (poll(&pfd, 1, kOpenTimeoutMs) == -1) { ALOGE("AudioHotplugThread: poll failed"); break; } else if (pfd.revents & POLLIN) { // shutdown requested break; } DeviceInfo deviceInfo; if (getDeviceInfo(pcmCard, pcmDevice, &deviceInfo)) { mCallback.onDeviceFound(deviceInfo); } } else if (event->mask & IN_DELETE) { mCallback.onDeviceRemoved(pcmCard, pcmDevice); } } i += sizeof(struct inotify_event) + event->len; } } done: if (watchFD != -1) { inotify_rm_watch(inotifyFD, watchFD); } if (inotifyFD != -1) { close(inotifyFD); } return false; } }; // namespace android