diff options
author | Scott Murray <scott.murray@konsulko.com> | 2016-12-23 00:30:27 -0500 |
---|---|---|
committer | Scott Murray <scott.murray@konsulko.com> | 2016-12-23 05:21:30 -0500 |
commit | d9a424c6c35dfb2ce7aefaaca708f3bc67d8c938 (patch) | |
tree | 99dcccaca2a753cda578aeb8ac649da4a64be1d8 /rtlfmradiotunercontrol.cpp | |
parent | bb08422f14659e811e11d15cc5365b3b91fccef0 (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 'rtlfmradiotunercontrol.cpp')
-rw-r--r-- | rtlfmradiotunercontrol.cpp | 225 |
1 files changed, 131 insertions, 94 deletions
diff --git a/rtlfmradiotunercontrol.cpp b/rtlfmradiotunercontrol.cpp index 863cb06..1fdf8d3 100644 --- a/rtlfmradiotunercontrol.cpp +++ b/rtlfmradiotunercontrol.cpp @@ -1,14 +1,19 @@ -/* Copyright (C) 2016, The Qt Company Ltd. All Rights Reserved. +/* + * Copyright (C) 2016, The Qt Company Ltd. All Rights Reserved. + * Copyright (C) 2016, Scott Murray <scott.murray@konsulko.com> * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ #include "rtlfmradiotunercontrol.h" +#include "radio_output.h" +#include "rtl_fm.h" #include <QtCore/QDebug> #include <QtCore/QTimer> -#include <QtCore/QProcess> +#include <QtCore/QSettings> class RtlFmRadioTunerControl::Private { @@ -26,97 +31,112 @@ public: QRadioTuner::StereoMode stereoMode; int signalStrength; int volume; - bool muted; + static bool muted; bool searching; QRadioTuner::Error error; QString errorString; QTimer timer; bool searchOne; int step; - - QProcess rtlFm; - QProcess aplay; + bool present; + QPair<int, int> fmBandPlan; private: - QMap<int, QString> amFrequenciesTokyo = { - { 594000, QString::fromUtf8("NHK Tokyo #1") } - , { 693000, QString::fromUtf8("NHK Tokyo #2") } - , { 810000, QString::fromUtf8("Eagle 810 (AFN TOKYO)") } - , { 954000, QString::fromUtf8("TBS Radio Tokyo") } - , { 1134000, QString::fromUtf8("Nippon Cultural Broadcasting") } - , { 1242000, QString::fromUtf8("Nippon Broadcasting") } + QMap<QString, QPair<int, int> > knownFmBandPlans = { + { QString::fromUtf8("US"), qMakePair(87900000, 107900000) }, + { QString::fromUtf8("JP"), qMakePair(76100000, 89900000) } }; - QMap<int, QString> fmFrequenciesTokyo = { - { 76100000, QString::fromUtf8("Inter FM") } - , { 77100000, QString::fromUtf8("The Open University of Japan") } - , { 80000000, QString::fromUtf8("TOKYO FM") } - , { 81300000, QString::fromUtf8("J-WAVE") } - , { 82500000, QString::fromUtf8("NHK FM Tokyo") } - }; + static void output_thread_fn(int16_t *result, int result_len, void *ctx); + static RadioOutputImplementation *mRadioOutput; }; +bool RtlFmRadioTunerControl::Private::muted; +RadioOutputImplementation *RtlFmRadioTunerControl::Private::mRadioOutput; + RtlFmRadioTunerControl::Private::Private(RtlFmRadioTunerControl *parent) - : q(parent) - , state(QRadioTuner::StoppedState) - , band(QRadioTuner::FM) - , frequency(76100) - , stereoMode(QRadioTuner::Auto) - , signalStrength(100) - , volume(50) - , muted(false) - , searching(false) - , error(QRadioTuner::NoError) - , searchOne(true) - , step(0) -{ + : q(parent), + state(QRadioTuner::StoppedState), + band(QRadioTuner::FM), + stereoMode(QRadioTuner::Auto), + signalStrength(100), + volume(50), + searching(false), + error(QRadioTuner::NoError), + searchOne(true), + step(0) +{ + mRadioOutput = NULL; + muted = false; + + // Initialize output + // Note that the optional ALSA support is currently not entirely + // functional and needs further debugging. + char *impl_env = getenv("RADIO_OUTPUT"); + if (impl_env) { + if (strcasecmp(impl_env, "Pulse") == 0) + mRadioOutput = new RadioOutputPulse(); + if (strcasecmp(impl_env, "Alsa") == 0) + mRadioOutput = new RadioOutputAlsa(); + } + if (!mRadioOutput) { + mRadioOutput = new RadioOutputPulse(); + if (!mRadioOutput->works) + mRadioOutput = new RadioOutputAlsa(); + } + + // Initialize RTL-SDR dongle + present = false; + if(rtl_fm_init(frequency, 200000, 48000, output_thread_fn, NULL) < 0) { + qDebug("%s: no RTL USB adapter?", Q_FUNC_INFO); + } else { + present = true; + } + + // Initialize FM band plan + QSettings settings("AGL", "qtmultimedia-rtlfm-radio-plugin"); + if(!settings.contains("fmbandplan")) + settings.setValue("fmbandplan", QString::fromUtf8("US")); + if(!knownFmBandPlans.contains(settings.value("fmbandplan").toString())) + settings.setValue("fmbandplan", QString::fromUtf8("US")); + fmBandPlan = knownFmBandPlans.value(settings.value("fmbandplan").toString()); + + // Initialize frequency to lower bound of band plan + frequency = fmBandPlan.first; + connect(q, &RtlFmRadioTunerControl::stationFound, [this](int frequency, const QString &name) { - if (rtlFm.state() != QProcess::NotRunning) - q->start(); + qDebug() << frequency << name; }); connect(q, &RtlFmRadioTunerControl::frequencyChanged, [this](int frequency) { - QMap<int, QString> knownBands; - switch (band) { - case QRadioTuner::AM: - knownBands = amFrequenciesTokyo; - break; - case QRadioTuner::FM: - knownBands = fmFrequenciesTokyo; - break; - default: - qFatal("%s:%d %d not supported", Q_FUNC_INFO, __LINE__, band); - break; - } - if (knownBands.contains(frequency)) { - QMetaObject::invokeMethod(q, "stationFound", Qt::QueuedConnection, Q_ARG(int, frequency), Q_ARG(QString, knownBands.value(frequency))); - if (timer.isActive() && searchOne) - timer.stop(); - } + // Note that the following effectively disables any attempt at seeking. + // If seeking support is required some logic will need to be added back + // here (and elsewhere). + if (searching) { + emit q->stationFound(frequency, QString("%1 MHz").arg((double) frequency / 1000000, 0, 'f', 1)); + if (timer.isActive() && searchOne) + timer.stop(); + } }); - connect(&timer, &QTimer::timeout, [this]() { q->setFrequency(frequency + step); }); +} - connect(&rtlFm, &QProcess::readyReadStandardOutput, [this]() { - aplay.write(rtlFm.readAllStandardOutput()); - }); - connect(&rtlFm, &QProcess::readyReadStandardError, [this]() { - qDebug() << rtlFm.readAllStandardError(); - }); +void RtlFmRadioTunerControl::Private::output_thread_fn(int16_t *result, int result_len, void *ctx) +{ + if (!muted) + mRadioOutput->play((void*) result, result_len); } RtlFmRadioTunerControl::Private::~Private() { - aplay.kill(); - aplay.waitForFinished(); - rtlFm.kill(); - rtlFm.waitForFinished(); + delete mRadioOutput; + rtl_fm_cleanup(); } RtlFmRadioTunerControl::RtlFmRadioTunerControl(QObject *parent) - : QRadioTunerControl(parent) - , d(new Private(this)) + : QRadioTunerControl(parent), + d(new Private(this)) { } @@ -137,15 +157,20 @@ QRadioTuner::Band RtlFmRadioTunerControl::band() const void RtlFmRadioTunerControl::setBand(QRadioTuner::Band band) { - if (d->band == band) return; + // We only support FM, so this is a no-op, but leaving the code below + // #if'ed out for reference. +#if 0 + if (d->band == band) + return; d->band = band; emit bandChanged(band); +#endif } bool RtlFmRadioTunerControl::isBandSupported(QRadioTuner::Band band) const { + // We only support FM. switch (band) { - case QRadioTuner::AM: case QRadioTuner::FM: return true; default: @@ -162,9 +187,10 @@ int RtlFmRadioTunerControl::frequency() const int RtlFmRadioTunerControl::frequencyStep(QRadioTuner::Band band) const { int ret = 0; + switch (band) { case QRadioTuner::AM: - ret = 1000; // 1kHz + ret = 1000; // 1 kHz break; case QRadioTuner::FM: ret = 100000; // 0.1 MHz @@ -178,12 +204,13 @@ int RtlFmRadioTunerControl::frequencyStep(QRadioTuner::Band band) const QPair<int,int> RtlFmRadioTunerControl::frequencyRange(QRadioTuner::Band band) const { QPair<int,int> ret; + switch (band) { case QRadioTuner::AM: ret = qMakePair<int,int>(531000, 1602000); break; case QRadioTuner::FM: - ret = qMakePair<int,int>(76000000, 107900000); + ret = QPair<int,int>(d->fmBandPlan); break; default: break; @@ -193,6 +220,9 @@ QPair<int,int> RtlFmRadioTunerControl::frequencyRange(QRadioTuner::Band band) co void RtlFmRadioTunerControl::setFrequency(int frequency) { + if(!d->present) + return; + QPair<int,int> range = frequencyRange(d->band); frequency = qBound(range.first, frequency, range.second); if (d->frequency == frequency) { @@ -201,12 +231,14 @@ void RtlFmRadioTunerControl::setFrequency(int frequency) return; } d->frequency = frequency; + rtl_fm_set_freq(frequency); emit frequencyChanged(frequency); } bool RtlFmRadioTunerControl::isStereo() const { bool ret = false; + switch (d->stereoMode) { case QRadioTuner::ForceStereo: case QRadioTuner::Auto: @@ -225,7 +257,8 @@ QRadioTuner::StereoMode RtlFmRadioTunerControl::stereoMode() const void RtlFmRadioTunerControl::setStereoMode(QRadioTuner::StereoMode stereoMode) { - if (d->stereoMode == stereoMode) return; + if (d->stereoMode == stereoMode) + return; bool prevStereo = isStereo(); d->stereoMode = stereoMode; bool currentStereo = isStereo(); @@ -246,7 +279,8 @@ int RtlFmRadioTunerControl::volume() const void RtlFmRadioTunerControl::setVolume(int volume) { volume = qBound(0, volume, 100); - if (d->volume == volume) return; + if (d->volume == volume) + return; d->volume = volume; emit volumeChanged(volume); } @@ -258,7 +292,8 @@ bool RtlFmRadioTunerControl::isMuted() const void RtlFmRadioTunerControl::setMuted(bool muted) { - if (d->muted == muted) return; + if (d->muted == muted) + return; d->muted = muted; emit mutedChanged(muted); } @@ -302,41 +337,43 @@ void RtlFmRadioTunerControl::cancelSearch() void RtlFmRadioTunerControl::setSearching(bool searching) { - if (d->searching == searching) return; + if (d->searching == searching) + return; d->searching = searching; emit searchingChanged(searching); } void RtlFmRadioTunerControl::start() { - stop(); - d->aplay.start(QStringLiteral("aplay"), QStringList() << QStringLiteral("-r") << QStringLiteral("48000") << QStringLiteral("-f") << QStringLiteral("S16_LE")); - d->aplay.waitForStarted(); -// d->rtlFm.start(QStringLiteral("sudo"), QStringList() << QStringLiteral("rtl_fm -f %1 -M wbfm -s 200000 -r 48000").arg(d->frequency * 1000).split(" ")); - QString modulation; - switch (d->band) { - case QRadioTuner::AM: - modulation = QStringLiteral("-M wbfm"); - break; - case QRadioTuner::FM: - modulation = QStringLiteral("-M am"); - break; - default: - break; + if(!d->present) + return; + + qDebug() << "RtlFmRadioTunerControl::start - enter\n"; + if(d->state == QRadioTuner::ActiveState) { + qDebug() << "RtlFmRadioTunerControl::start - already running, exit\n"; + return; } - d->rtlFm.start(QStringLiteral("rtl_fm"), QStringLiteral("-f %1 %2 -s 200000 -r 48000").arg(d->frequency).arg(modulation).split(" ")); - d->rtlFm.waitForStarted(); + //rtl_fm_stop(); + rtl_fm_start(); d->state = QRadioTuner::ActiveState; + qDebug() << "RtlFmRadioTunerControl::start - exit\n"; } void RtlFmRadioTunerControl::stop() { - d->aplay.kill(); - d->aplay.waitForFinished(); - d->rtlFm.kill(); - d->rtlFm.waitForFinished(); + if(!d->present) + return; + + qDebug() << "RtlFmRadioTunerControl::stop - enter\n"; + if(d->state == QRadioTuner::StoppedState) { + qDebug() << "RtlFmRadioTunerControl::stop - already stopped, exit\n"; + return; + } + + rtl_fm_stop(); d->state = QRadioTuner::StoppedState; + qDebug() << "RtlFmRadioTunerControl::stop - exit\n"; } QRadioTuner::Error RtlFmRadioTunerControl::error() const |