/* Copyright (C) 2016, The Qt Company Ltd. All Rights Reserved. * * 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 #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; bool muted; bool searching; QRadioTuner::Error error; QString errorString; QTimer timer; bool searchOne; int step; QProcess rtlFm; QProcess aplay; private: QMap 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 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") } }; }; 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) { connect(q, &RtlFmRadioTunerControl::stationFound, [this](int frequency, const QString &name) { if (rtlFm.state() != QProcess::NotRunning) q->start(); }); connect(q, &RtlFmRadioTunerControl::frequencyChanged, [this](int frequency) { QMap 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(); } }); 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(); }); } RtlFmRadioTunerControl::Private::~Private() { aplay.kill(); aplay.waitForFinished(); rtlFm.kill(); rtlFm.waitForFinished(); } 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) { if (d->band == band) return; d->band = band; emit bandChanged(band); } bool RtlFmRadioTunerControl::isBandSupported(QRadioTuner::Band band) const { switch (band) { case QRadioTuner::AM: 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; // 1kHz 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 = qMakePair(76000000, 107900000); break; default: break; } return ret; } void RtlFmRadioTunerControl::setFrequency(int frequency) { 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; 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() { 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; } d->rtlFm.start(QStringLiteral("rtl_fm"), QStringLiteral("-f %1 %2 -s 200000 -r 48000").arg(d->frequency).arg(modulation).split(" ")); d->rtlFm.waitForStarted(); d->state = QRadioTuner::ActiveState; } void RtlFmRadioTunerControl::stop() { d->aplay.kill(); d->aplay.waitForFinished(); d->rtlFm.kill(); d->rtlFm.waitForFinished(); d->state = QRadioTuner::StoppedState; } 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); }