/* * A standalone AM/FM Radio QML plugin (for RTL2832U and Maxim hardware) * Copyright © 2015-2016 Manuel Bachmann * Copyright © 2016 Scott Murray * * 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, see . */ #include #include "radio_output.h" #include "rtl_fm.h" RadioOutputAlsa::RadioOutputAlsa() : RadioOutputImplementation(), dev(NULL), hw_params(NULL) { unsigned int rate = 24000; if (snd_pcm_open(&dev, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) { std::cerr << "Could not open primary ALSA device" << std::endl; works = false; return; } snd_pcm_hw_params_malloc(&hw_params); snd_pcm_hw_params_any(dev, hw_params); snd_pcm_hw_params_set_access (dev, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format (dev, hw_params, SND_PCM_FORMAT_S16_LE); snd_pcm_hw_params_set_rate_near (dev, hw_params, &rate, 0); snd_pcm_hw_params_set_channels (dev, hw_params, 2); if (snd_pcm_hw_params (dev, hw_params) < 0) { std::cerr << "Could not set hardware parameters" << std::endl; works = false; } else { works = true; } snd_pcm_hw_params_free (hw_params); snd_pcm_prepare (dev); } RadioOutputAlsa::~RadioOutputAlsa() { snd_pcm_close (dev); } bool RadioOutputAlsa::play(void *buf, int len) { int16_t *cbuf = (int16_t *)buf; int frames = len / 2; int res; if ((res = snd_pcm_writei(dev, cbuf, frames)) != frames) { snd_pcm_recover(dev, res, 0); snd_pcm_prepare(dev); } //snd_pcm_drain(dev); return true; } RadioOutputPulse::RadioOutputPulse() : RadioOutputImplementation(), pa(NULL), pa_spec(NULL) { int error; pa_spec = (pa_sample_spec*) malloc(sizeof(pa_sample_spec)); pa_spec->format = PA_SAMPLE_S16LE; pa_spec->rate = 24000; pa_spec->channels = 2; if (!(pa = pa_simple_new(NULL, "qtmultimedia-rtlfm-radio-plugin", PA_STREAM_PLAYBACK, NULL, "radio-output", pa_spec, NULL, NULL, &error))) { std::cerr << "Error connecting to PulseAudio : " << pa_strerror(error) << std::endl; works = false; } else { std::cerr << "RadioOutputPulse::RadioOutputPulse: Connected to PulseAudio" << std::endl; works = true; } extra = 0; output_buf = new unsigned char[RTL_FM_MAXIMUM_BUF_LENGTH]; free(pa_spec); } RadioOutputPulse::~RadioOutputPulse() { pa_simple_free(pa); delete [] output_buf; } bool RadioOutputPulse::play(void *buf, int len) { int error; size_t n = len * 2; void *p; if (!buf) { std::cerr << "Error buf == null!" << std::endl; return false; } // Handle the rtl_fm code giving us an odd number of samples, which // PA does not like. This extra buffer copying approach is not // particularly efficient, but works for now. It looks feasible to // hack in something in the demod and output thread routines in // rtl_fm.c to handle it there if more performance is required. p = output_buf; if(extra) { memcpy(output_buf, extra_buf, sizeof(int16_t)); if((extra + len) % 2) { // We end up with len + 1 samples, n remains the same, store the extra memcpy(output_buf + sizeof(int16_t), buf, n - 2); memcpy(extra_buf, ((unsigned char*) buf) + n - 2, sizeof(int16_t)); } else { // We end up with an extra sample memcpy(output_buf + sizeof(int16_t), buf, n); n += 2; extra = 0; } } else if(len % 2) { // We have an extra sample, store it, and decrease n n -= 2; memcpy(output_buf + sizeof(int16_t), buf, n); memcpy(extra_buf, ((unsigned char*) buf) + n, sizeof(int16_t)); extra = 1; } else { p = buf; } if (pa_simple_write(pa, p, n, &error) < 0) std::cerr << "Error writing " << n << " bytes to PulseAudio : " << pa_strerror(error) << std::endl; //pa_simple_drain(pa, &error); return true; }