summaryrefslogtreecommitdiff
path: root/capture.c
diff options
context:
space:
mode:
Diffstat (limited to 'capture.c')
-rw-r--r--capture.c259
1 files changed, 259 insertions, 0 deletions
diff --git a/capture.c b/capture.c
new file mode 100644
index 0000000..f14afa2
--- /dev/null
+++ b/capture.c
@@ -0,0 +1,259 @@
+/*
+ * fmrxd - a daemon to enable and multiplex access to the N950/N9 radio tuner
+ * Copyright (C) 2011 Javier S. Pedro <maemo@javispedro.com>
+ *
+ * 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 <stdbool.h>
+#include <unistd.h>
+#include <glib.h>
+#include <alsa/asoundlib.h>
+
+#include "fmrxd.h"
+
+static snd_pcm_t *a_pcm = NULL;
+static guint a_fd_watch = 0;
+
+/** Sets mixer switch <name> to <value> */
+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");
+ 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, "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;
+ }
+}