/* * fmrxd - a daemon to enable and multiplex access to the N950/N9 radio tuner * Copyright (C) 2011 Javier S. Pedro * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include "fmrxd.h" static snd_pcm_t *a_pcm = NULL; static guint a_fd_watch = 0; /** Sets mixer switch to */ static bool mixer_set_enum_value(snd_hctl_t *mixer, const char * name, const char * value) { int err; snd_ctl_elem_id_t *id; snd_ctl_elem_id_alloca(&id); snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); snd_ctl_elem_id_set_name(id, name); snd_hctl_elem_t *elem = snd_hctl_find_elem(mixer, id); if (!elem) { g_critical("Couldn't find mixer element '%s'", name); return false; } snd_ctl_elem_info_t *info; snd_ctl_elem_info_alloca(&info); snd_ctl_elem_info_set_id(info, id); err = snd_hctl_elem_info(elem, info); if (err) { g_critical("Couldn't get mixer element '%s' information (%d)", name, err); return false; } long value_idx = -1; int i, items = snd_ctl_elem_info_get_items(info); for (i = 0; i < items; i++) { snd_ctl_elem_info_set_item(info, i); err = snd_hctl_elem_info(elem, info); if (err) { g_critical("Couldn't get mixer element '%s' information (%d)", name, err); return false; } if (strcmp(snd_ctl_elem_info_get_item_name(info), value) == 0) { value_idx = i; break; } } if (value_idx < 0) { g_critical("Couldn't find mixer '%s' value '%s'", name, value); return false; } snd_ctl_elem_value_t *control; snd_ctl_elem_value_alloca(&control); snd_ctl_elem_value_set_id(control, id); items = snd_ctl_elem_info_get_count(info); for (i = 0; i < items; i++) { snd_ctl_elem_value_set_enumerated(control, i, value_idx); } err = snd_hctl_elem_write(elem, control); if (err) { g_critical("Couldn't set mixer element '%s' (%d)", name, err); return false; } return true; } bool configure_mixer(bool on) { snd_hctl_t *mixer; int err; bool res; err = snd_hctl_open(&mixer, ALSA_MIXER_NAME, 0); if (err < 0) { g_critical("Failed to open ALSA mixer '%s' (%d)", ALSA_MIXER_NAME, err); return false; } err = snd_hctl_load(mixer); if (err < 0) { g_critical("Failed to load ALSA hmixer elements (%d)", err); snd_hctl_close(mixer); return false; } if (on) { res = mixer_set_enum_value(mixer, "Mode Switch", "Rx"); // The above uploads firmware and a few other things, // so let's give it some time. usleep(500000); res &= mixer_set_enum_value(mixer, "Codec Mode", "FmRx"); res &= mixer_set_enum_value(mixer, "Audio Switch", "Digital"); } else { res = mixer_set_enum_value(mixer, "Audio Switch", "Analog"); res &= mixer_set_enum_value(mixer, "Codec Mode", "Bt"); res &= mixer_set_enum_value(mixer, "Mode Switch", "Off"); } /* TODO: "Region Switch" for Japan freqs. */ snd_hctl_close(mixer); return res; } static int alsa_recover(int err) { err = snd_pcm_recover(a_pcm, err, 0); if (err < 0) { g_critical("ALSA stream failed to recover (%d)", err); return err; } err = snd_pcm_start(a_pcm); if (err < 0) { g_critical("ALSA stream failed to restart (%d)", err); return err; } return 0; } static snd_pcm_sframes_t calculate_avail_frames() { snd_pcm_sframes_t frames_avail; int err; while ((frames_avail = snd_pcm_avail_update(a_pcm)) < 0) { g_warning("ALSA stream avail failed (%d)", (int) frames_avail); err = alsa_recover(frames_avail); if (err < 0) { return err; } } return frames_avail; } static gboolean capture_callback(GIOChannel *source, GIOCondition condition, gpointer data) { snd_pcm_sframes_t frames_avail, frames_to_read, frames_read; size_t bytes_avail, bytes_buffer, bytes_to_read, bytes_read; void *buffer; int err; /* Only read as much data as available; we do not want to block. */ while ((frames_avail = calculate_avail_frames()) > 0) { bytes_avail = snd_pcm_frames_to_bytes(a_pcm, frames_avail); /* Request a large enough segment in the ring buffer. */ bytes_buffer = server_get_buffer(bytes_avail, &buffer); if (bytes_avail > bytes_buffer) { bytes_to_read = bytes_buffer; frames_to_read = snd_pcm_bytes_to_frames(a_pcm, bytes_to_read); } else { bytes_to_read = bytes_avail; frames_to_read = frames_avail; } frames_read = snd_pcm_readi(a_pcm, buffer, frames_to_read); if (frames_read < 0) { g_warning("ALSA failed to read (%d)", (int) frames_read); err = alsa_recover(frames_read); if (err < 0) { return FALSE; } continue; // Retry } bytes_read = snd_pcm_frames_to_bytes(a_pcm, frames_read); server_commit_buffer(bytes_read); } return TRUE; } /** Equivalent to "arecord -Dhw:2,0 -f S16_LE -r 48000 -c 2" */ bool configure_capture(bool on) { int err; if (on) { err = snd_pcm_open(&a_pcm, ALSA_PCM_CAPTURE_NAME, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); if (err < 0) { g_critical("Failed to open ALSA PCM device '%s' (%d)", ALSA_PCM_CAPTURE_NAME, err); return false; } /* -f S16_LE -r 48000 -c 2; latency up to 0.3 seconds */ err = snd_pcm_set_params(a_pcm, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 2, ALSA_CAPTURE_RATE, 0, 300000); if (err < 0) { g_critical("Failed to open ALSA PCM device '%s' (%d)", ALSA_PCM_CAPTURE_NAME, err); snd_pcm_close(a_pcm); return false; } struct pollfd polls[1]; int count = snd_pcm_poll_descriptors(a_pcm, polls, 1); if (count != 1) { g_critical("Not able to get poll FD from ALSA"); snd_pcm_close(a_pcm); return false; } GIOChannel *channel = g_io_channel_unix_new(polls[0].fd); a_fd_watch = g_io_add_watch(channel, polls[0].events, capture_callback, NULL); g_io_channel_unref(channel); err = snd_pcm_start(a_pcm); if (err < 0) { g_critical("Couldn't start PCM device (%d)", err); snd_pcm_close(a_pcm); return false; } g_debug("ALSA capture started"); return true; } else { if (a_fd_watch) { g_source_remove(a_fd_watch); a_fd_watch = 0; } if (a_pcm) { snd_pcm_close(a_pcm); a_pcm = NULL; } return true; } }