summaryrefslogtreecommitdiffstats
path: root/radio_output.cpp
diff options
context:
space:
mode:
authorScott Murray <scott.murray@konsulko.com>2016-12-23 00:30:27 -0500
committerScott Murray <scott.murray@konsulko.com>2016-12-23 05:21:30 -0500
commitd9a424c6c35dfb2ce7aefaaca708f3bc67d8c938 (patch)
tree99dcccaca2a753cda578aeb8ac649da4a64be1d8 /radio_output.cpp
parentbb08422f14659e811e11d15cc5365b3b91fccef0 (diff)
Switch to direct use of librtlsdr and PulseAudio
Substantial rework to replace the spawning of rtl_fm and aplay with direct usage of librtlsdr and PulseAudio in a multi-threaded model. This is required due to changes in AGL application execution that prevent spawned processes from exiting, resulting in the plugin hanging on frequency changes or stopping. The rework has been accomplished by refactoring the source of rtl_fm.c into a reusable form and connecting it to the RtlFmRadioTunerControl class used to implement the functionality exposed by the RtlFmRadioPlugin class. The idea for reusing the source code in rtl_fm.c in this way is inspired by the older qml_radio_plugin codebase, but a new refactor of rtl_fm.c was done to keep more of its filtering functionality and ensure behavior consistent with the previous implementation. The files radio_output.{h,cpp} are adapted from qml_radio_plugin with some additional modifications. Other changes include: - The files in the convenience subdirectory have been copied from the librtldr source tree to reduce the effort of importing rtl_fm.c. - The COPYING file containing the GPL license has been copied from the librtlsdr source tree to accompany rtl_fm.c and the convenience/* files. - The recently added AM band support has been removed as the USB DVB adapters are incapable of receiving AM without significant tweaking, and a single adapter would be unable to do both AM and FM at the same time. The plugin now explicitly reports that it only supports FM. - The list of known stations to act as an ersatz seeking implementation has been removed, as the updated higher-level QML application no longer exposes seeking. Adding this functionality back in would be straightforward if it becomes required again. There is also some code in rtl_fm.c that could possibly be adapted into a proper signal strength detection scheme in the future if that is desired. - A Qt QSettings file is used to store the FM band plan information to allow using the specific frequency ranges for North America versus Japan. The band plan can be changed by modifying the "fmbandplan" entry in the QSettings .conf file to either "US" or "JP". The location of the .conf file can be one of: $HOME/.config/AGL/qtmultimedia-rtlfm-radio-plugin.conf $HOME/.config/AGL.conf /etc/xdg/AGL/qtmultimedia-rtlfm-radio-plugin.conf /etc/xdg/AGL.conf Note that some debugging output has been left in place in the start and stop methods to facilitate debugging of the higher-level QML application. They will be removed once that is complete. Change-Id: I1d92c74eb24b24cb5416dd531b599645d1287295 Signed-off-by: Scott Murray <scott.murray@konsulko.com>
Diffstat (limited to 'radio_output.cpp')
-rw-r--r--radio_output.cpp152
1 files changed, 152 insertions, 0 deletions
diff --git a/radio_output.cpp b/radio_output.cpp
new file mode 100644
index 0000000..350bf75
--- /dev/null
+++ b/radio_output.cpp
@@ -0,0 +1,152 @@
+/*
+ * A standalone AM/FM Radio QML plugin (for RTL2832U and Maxim hardware)
+ * Copyright © 2015-2016 Manuel Bachmann <manuel.bachmann@iot.bzh>
+ * Copyright © 2016 Scott Murray <scott.murray@konsulko.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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <iostream>
+#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;
+}