From 352dad23c7847d234e11c1034e1354fbd9a8349a Mon Sep 17 00:00:00 2001 From: "Javier S. Pedro" Date: Sat, 31 Dec 2011 17:50:06 +0100 Subject: initial import --- capture.c | 259 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 capture.c (limited to 'capture.c') 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 + * + * 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"); + 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; + } +} -- cgit v1.2.3