/* * Copyright (C) 2016, The Qt Company Ltd. All Rights Reserved. * Copyright (C) 2016, Scott Murray * * 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/. */ #include "rtlfmradiotunercontrol.h" #include "radio_output.h" #include "rtl_fm.h" #include #include #include class RtlFmRadioTunerControl::Private { public: Private(RtlFmRadioTunerControl *parent); ~Private(); private: RtlFmRadioTunerControl *q; public: QRadioTuner::State state; QRadioTuner::Band band; int frequency; QRadioTuner::StereoMode stereoMode; int signalStrength; int volume; static bool muted; bool searching; QRadioTuner::Error error; QString errorString; QTimer timer; bool searchOne; int step; bool present; QPair fmBandPlan; private: QMap > knownFmBandPlans = { { QString::fromUtf8("US"), qMakePair(87900000, 107900000) }, { QString::fromUtf8("JP"), qMakePair(76100000, 89900000) } }; 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), 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) { qDebug() << frequency << name; }); connect(q, &RtlFmRadioTunerControl::frequencyChanged, [this](int frequency) { // 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); }); } 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() { delete mRadioOutput; rtl_fm_cleanup(); } RtlFmRadioTunerControl::RtlFmRadioTunerControl(QObject *parent) : QRadioTunerControl(parent), d(new Private(this)) { } RtlFmRadioTunerControl::~RtlFmRadioTunerControl() { delete d; } QRadioTuner::State RtlFmRadioTunerControl::state() const { return d->state; } QRadioTuner::Band RtlFmRadioTunerControl::band() const { return d->band; } void RtlFmRadioTunerControl::setBand(QRadioTuner::Band band) { // 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::FM: return true; default: break; } return false; } int RtlFmRadioTunerControl::frequency() const { return d->frequency; } int RtlFmRadioTunerControl::frequencyStep(QRadioTuner::Band band) const { int ret = 0; switch (band) { case QRadioTuner::AM: ret = 1000; // 1 kHz break; case QRadioTuner::FM: ret = 100000; // 0.1 MHz break; default: break; } return ret; } QPair RtlFmRadioTunerControl::frequencyRange(QRadioTuner::Band band) const { QPair ret; switch (band) { case QRadioTuner::AM: ret = qMakePair(531000, 1602000); break; case QRadioTuner::FM: ret = QPair(d->fmBandPlan); break; default: break; } return ret; } void RtlFmRadioTunerControl::setFrequency(int frequency) { if(!d->present) return; QPair range = frequencyRange(d->band); frequency = qBound(range.first, frequency, range.second); if (d->frequency == frequency) { if (d->timer.isActive()) d->timer.stop(); 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: ret = true; break; default: break; } return ret; } QRadioTuner::StereoMode RtlFmRadioTunerControl::stereoMode() const { return d->stereoMode; } void RtlFmRadioTunerControl::setStereoMode(QRadioTuner::StereoMode stereoMode) { if (d->stereoMode == stereoMode) return; bool prevStereo = isStereo(); d->stereoMode = stereoMode; bool currentStereo = isStereo(); if (prevStereo != currentStereo) emit stereoStatusChanged(currentStereo); } int RtlFmRadioTunerControl::signalStrength() const { return d->signalStrength; } int RtlFmRadioTunerControl::volume() const { return d->volume; } void RtlFmRadioTunerControl::setVolume(int volume) { volume = qBound(0, volume, 100); if (d->volume == volume) return; d->volume = volume; emit volumeChanged(volume); } bool RtlFmRadioTunerControl::isMuted() const { return d->muted; } void RtlFmRadioTunerControl::setMuted(bool muted) { if (d->muted == muted) return; d->muted = muted; emit mutedChanged(muted); } bool RtlFmRadioTunerControl::isSearching() const { return d->searching; } void RtlFmRadioTunerControl::searchForward() { d->searchOne = true; d->step = frequencyStep(d->band); d->timer.start(100); setSearching(true); } void RtlFmRadioTunerControl::searchBackward() { d->searchOne = true; d->step = -frequencyStep(d->band); d->timer.start(250); setSearching(true); } void RtlFmRadioTunerControl::searchAllStations(QRadioTuner::SearchMode searchMode) { Q_UNUSED(searchMode) d->searchOne = false; d->frequency = frequencyRange(d->band).first; d->step = frequencyStep(d->band); d->timer.start(250); setSearching(true); } void RtlFmRadioTunerControl::cancelSearch() { d->timer.stop(); setSearching(false); } void RtlFmRadioTunerControl::setSearching(bool searching) { if (d->searching == searching) return; d->searching = searching; emit searchingChanged(searching); } void RtlFmRadioTunerControl::start() { if(!d->present) return; qDebug() << "RtlFmRadioTunerControl::start - enter\n"; if(d->state == QRadioTuner::ActiveState) { qDebug() << "RtlFmRadioTunerControl::start - already running, exit\n"; return; } //rtl_fm_stop(); rtl_fm_start(); d->state = QRadioTuner::ActiveState; emit stateChanged(d->state); qDebug() << "RtlFmRadioTunerControl::start - exit\n"; } void RtlFmRadioTunerControl::stop() { 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; emit stateChanged(d->state); qDebug() << "RtlFmRadioTunerControl::stop - exit\n"; } QRadioTuner::Error RtlFmRadioTunerControl::error() const { return d->error; } QString RtlFmRadioTunerControl::errorString() const { return d->errorString; } void RtlFmRadioTunerControl::setError(QRadioTuner::Error error, const QString &errorString) { d->error = error; d->errorString = errorString; emit QRadioTunerControl::error(error); }