From cd71055f14b70284d8bd3eb35fadc15b36ba0780 Mon Sep 17 00:00:00 2001 From: "Javier S. Pedro" Date: Mon, 13 Feb 2012 21:16:59 +0100 Subject: use PulseAudio instead of QAudioDevice --- fmrx-qt.pro | 8 +- fmrxcontrol.cpp | 29 ++++- fmrxcontrol.h | 4 + fmrxproxy.cpp | 5 + fmrxproxy.h | 3 + fmrxservice.cpp | 110 ++++++++--------- fmrxservice.h | 10 +- fmrxthread.cpp | 203 +++++++++++++++++++++++++++++++ fmrxthread.h | 38 ++++++ qtc_packaging/debian_harmattan/changelog | 7 ++ qtc_packaging/debian_harmattan/control | 3 +- 11 files changed, 343 insertions(+), 77 deletions(-) create mode 100644 fmrxthread.cpp create mode 100644 fmrxthread.h diff --git a/fmrx-qt.pro b/fmrx-qt.pro index 0ed54da..5c4ba4a 100644 --- a/fmrx-qt.pro +++ b/fmrx-qt.pro @@ -11,7 +11,7 @@ TARGET = fmrx-qt TEMPLATE = lib CONFIG += plugin mobility link_pkgconfig MOBILITY += multimedia -PKGCONFIG += dbus-glib-1 +PKGCONFIG += dbus-glib-1 libpulse contains(MEEGO_EDITION,harmattan) { target.path = /usr/lib/qt4/plugins/mediaservice @@ -23,12 +23,14 @@ INSTALLS += target SOURCES += fmrxserviceplugin.cpp \ fmrxservice.cpp \ fmrxcontrol.cpp \ - fmrxproxy.cpp + fmrxproxy.cpp \ + fmrxthread.cpp HEADERS += fmrxserviceplugin.h \ fmrxservice.h \ fmrxcontrol.h \ - fmrxproxy.h + fmrxproxy.h \ + fmrxthread.h OTHER_FILES += \ qtc_packaging/debian_harmattan/rules \ diff --git a/fmrxcontrol.cpp b/fmrxcontrol.cpp index 8116021..a3c21b5 100644 --- a/fmrxcontrol.cpp +++ b/fmrxcontrol.cpp @@ -7,6 +7,7 @@ FmRxControl::FmRxControl(FmRxService *parent) : connect(m_service, SIGNAL(started()), this, SLOT(handleStarted())); connect(m_service, SIGNAL(stopped()), this, SLOT(handleStopped())); connect(m_service, SIGNAL(tuned(double)), this, SLOT(handleTuned(double))); + connect(m_service, SIGNAL(signalLevelChanged(ushort)), this, SLOT(handleSignalLevelChanged(ushort))); } bool FmRxControl::isAvailable() const @@ -48,6 +49,7 @@ int FmRxControl::frequency() const int FmRxControl::frequencyStep(QRadioTuner::Band b) const { + // 0.1 Mhz return 100 * 1000; } @@ -66,7 +68,8 @@ void FmRxControl::setFrequency(int frequency) bool FmRxControl::isStereo() const { - return true; // TODO + // The driver does not support reporting this + return true; } QRadioTuner::StereoMode FmRxControl::stereoMode() const @@ -81,16 +84,17 @@ void FmRxControl::setStereoMode(QRadioTuner::StereoMode mode) int FmRxControl::signalStrength() const { - return 0; // TODO + return convertSignalLevel(m_service->signalLevel()); } int FmRxControl::volume() const { - return 100; // Best not to have volume control in Harmattan. + return 100; // See comment in setVolume } void FmRxControl::setVolume(int volume) { - // Best not to have volume control in Harmattan. + // It is IMHO best not to have a separate volume control in Harmattan. + Q_UNUSED(volume); } bool FmRxControl::isMuted() const @@ -100,17 +104,20 @@ bool FmRxControl::isMuted() const void FmRxControl::setMuted(bool muted) { - // Best not to have volume control in Harmattan. + // Set comment in setVolume + Q_UNUSED(muted); } bool FmRxControl::isSearching() const { + // Searches are quite fast. return false; } void FmRxControl::cancelSearch() { - // TODO + // I am not sure this driver supports cancelling a search. + qWarning("FmRxControl: cannot cancel a search"); } void FmRxControl::searchForward() @@ -143,6 +150,11 @@ QString FmRxControl::errorString() const return QString(); } +int FmRxControl::convertSignalLevel(int v4l_level) +{ + return (v4l_level * 100) / 65535; +} + void FmRxControl::handleStarted() { emit stateChanged(QRadioTuner::ActiveState); @@ -157,3 +169,8 @@ void FmRxControl::handleTuned(double frequency) { emit frequencyChanged(frequency * 1000000.0); } + +void FmRxControl::handleSignalLevelChanged(ushort level) +{ + emit signalStrengthChanged(convertSignalLevel(level)); +} diff --git a/fmrxcontrol.h b/fmrxcontrol.h index 20247da..a24e92d 100644 --- a/fmrxcontrol.h +++ b/fmrxcontrol.h @@ -52,10 +52,14 @@ public: QRadioTuner::Error error() const; QString errorString() const; +private: + static int convertSignalLevel(int v4l_level); + private slots: void handleStarted(); void handleStopped(); void handleTuned(double frequency); + void handleSignalLevelChanged(ushort level); }; #endif // FMRXCONTROL_H diff --git a/fmrxproxy.cpp b/fmrxproxy.cpp index 77fcfb1..57956b0 100644 --- a/fmrxproxy.cpp +++ b/fmrxproxy.cpp @@ -126,6 +126,11 @@ DBusHandlerResult FmRxProxy::bus_message_filter(DBusConnection *, } } else if (dbus_message_is_signal(m, BUS_INTERFACE, "Stopped")) { emit self->Stopped(); + } else if (dbus_message_is_signal(m, BUS_INTERFACE, "SignalLevelChanged")) { + quint16 level; + if (dbus_message_get_args(m, NULL, DBUS_TYPE_UINT16, &level, DBUS_TYPE_INVALID)) { + emit self->SignalLevelChanged(level); + } } else if (dbus_message_is_signal(m, BUS_INTERFACE, "PiReceived")) { quint16 pi; if (dbus_message_get_args(m, NULL, DBUS_TYPE_UINT16, &pi, DBUS_TYPE_INVALID)) { diff --git a/fmrxproxy.h b/fmrxproxy.h index ccd544a..de16df6 100644 --- a/fmrxproxy.h +++ b/fmrxproxy.h @@ -5,6 +5,8 @@ #include #include +/** This is the Proxy class that connects to the D-Bus service. */ + class FmRxProxy : public QObject { Q_OBJECT @@ -20,6 +22,7 @@ public: signals: void Tuned(double frequency); void Stopped(); + void SignalLevelChanged(ushort level); void PiReceived(ushort pi); void PsReceived(const QString &ps); void RtReceived(const QString &rt); diff --git a/fmrxservice.cpp b/fmrxservice.cpp index 3a990bd..750ae8f 100644 --- a/fmrxservice.cpp +++ b/fmrxservice.cpp @@ -1,90 +1,73 @@ -#include -#include #include #include -#include "fmrxservice.h" #include "fmrxcontrol.h" #include "fmrxproxy.h" +#include "fmrxthread.h" +#include "fmrxservice.h" // Again, this would be incredibly shorter if QAudioOuput could be used. // Unfortunately, it is completely useless in the Harmattan version. struct FmRxPriv { - QAudioOutput *out; - QFile *in; - int fd; + FmRxThread *thread; + FmRxProxy *proxy; + FmRxControl *control; + FmRxRds *rds; bool active; double frequency; + quint16 signalLevel; }; FmRxService::FmRxService(QObject *parent) : QMediaService(parent) { - m_proxy = new FmRxProxy(this); - m_control = new FmRxControl(this); - m_priv = new FmRxPriv; - - QAudioFormat format; - format.setFrequency(48000); - format.setChannels(2); - format.setSampleType(QAudioFormat::SignedInt); - format.setSampleSize(16); - format.setByteOrder(QAudioFormat::LittleEndian); - format.setCodec("audio/pcm"); - - QAudioDeviceInfo info = QAudioDeviceInfo::defaultOutputDevice(); - if (!info.isFormatSupported(format)) { - qWarning("FM radio format not supported by audio output"); - } - qDebug("Starting output to %s", qPrintable(info.deviceName())); - - m_priv->out = new QAudioOutput(info, format, this); - m_priv->in = new QFile(this); - m_priv->active = false; - - connect(m_proxy, SIGNAL(Tuned(double)), this, SLOT(handleTuned(double))); - connect(m_proxy, SIGNAL(Stopped()), this, SLOT(handleStopped())); - connect(m_proxy, SIGNAL(PiReceived(ushort)), this, SIGNAL(piReceived(ushort))); - connect(m_proxy, SIGNAL(PsReceived(QString)), this, SIGNAL(psReceived(QString))); - connect(m_proxy, SIGNAL(RtReceived(QString)), this, SIGNAL(rtReceived(QString))); + d = new FmRxPriv; + d->thread = new FmRxThread(this); + d->proxy = new FmRxProxy(this); + d->control = new FmRxControl(this); + d->active = false; - connect(m_priv->out, SIGNAL(stateChanged(QAudio::State)), - this, SLOT(handleOutState(QAudio::State))); + connect(d->proxy, SIGNAL(Tuned(double)), this, SLOT(handleTuned(double))); + connect(d->proxy, SIGNAL(Stopped()), this, SLOT(handleStopped())); + connect(d->proxy, SIGNAL(SignalLevelChanged(ushort)), this, SLOT(handleSignalLevelChanged(ushort))); + connect(d->proxy, SIGNAL(PiReceived(ushort)), this, SIGNAL(piReceived(ushort))); + connect(d->proxy, SIGNAL(PsReceived(QString)), this, SIGNAL(psReceived(QString))); + connect(d->proxy, SIGNAL(RtReceived(QString)), this, SIGNAL(rtReceived(QString))); } FmRxService::~FmRxService() { - delete m_priv; + delete d; } QMediaControl *FmRxService::requestControl(const char *name) { if (qstrcmp(name, QRadioTunerControl_iid) == 0) - return m_control; + return d->control; return 0; } void FmRxService::releaseControl(QMediaControl *control) { - // Do nothing + // Nothing to do; the control instance will be free when the service is freed } void FmRxService::start() { - m_priv->fd = m_proxy->Connect(); - m_priv->in->open(m_priv->fd, QIODevice::ReadOnly); - m_priv->out->start(m_priv->in); - qDebug("Starting fmrx service"); + if (d->thread->isRunning()) { + qWarning("FmRxService already started"); + return; + } + d->thread->setInputFd(d->proxy->Connect()); + d->thread->start(); } void FmRxService::stop() { - m_priv->out->stop(); - m_priv->in->close(); - close(m_priv->fd); + d->thread->stop(); } bool FmRxService::isAvailable() const @@ -100,55 +83,58 @@ QtMultimediaKit::AvailabilityError FmRxService::availabilityError() const bool FmRxService::isActive() const { - return m_priv->active; + return d->active; } void FmRxService::handleTuned(double frequency) { - if (!qFuzzyCompare(m_priv->frequency, frequency)) { - m_priv->frequency = frequency; + if (!qFuzzyCompare(d->frequency, frequency)) { + d->frequency = frequency; emit tuned(frequency); } - if (!m_priv->active) { - m_priv->active = true; + if (!d->active) { + d->active = true; emit started(); } } void FmRxService::handleStopped() { - if (m_priv->active) { - m_priv->active = false; + if (d->active) { + d->active = false; emit stopped(); } } -void FmRxService::handleOutState(QAudio::State state) +void FmRxService::handleSignalLevelChanged(ushort level) { - qDebug() << "New out state = " << state; - if (state == QAudio::IdleState && - m_priv->out->error() == QAudio::UnderrunError) { - qDebug() << "Restarting"; - m_priv->out->start(m_priv->in); + d->signalLevel = level; + if (d->active) { + emit signalLevelChanged(level); } } double FmRxService::frequency() { - return m_priv->frequency; + return d->frequency; } void FmRxService::setFrequency(double frequency) { - m_proxy->Tune(frequency); + d->proxy->Tune(frequency); } void FmRxService::searchForward() { - m_proxy->SearchForward(); + d->proxy->SearchForward(); } void FmRxService::searchBackward() { - m_proxy->SearchBackward(); + d->proxy->SearchBackward(); +} + +ushort FmRxService::signalLevel() const +{ + return d->signalLevel; } diff --git a/fmrxservice.h b/fmrxservice.h index 1f9e278..75afe38 100644 --- a/fmrxservice.h +++ b/fmrxservice.h @@ -35,10 +35,13 @@ public: void searchForward(); void searchBackward(); + ushort signalLevel() const; + signals: void tuned(double frequency); void started(); void stopped(); + void signalLevelChanged(ushort level); void piReceived(ushort pi); void psReceived(const QString &ps); void rtReceived(const QString &rt); @@ -46,13 +49,10 @@ signals: private slots: void handleTuned(double frequency); void handleStopped(); - void handleOutState(QAudio::State state); + void handleSignalLevelChanged(ushort level); private: - FmRxPriv *m_priv; - FmRxProxy *m_proxy; - FmRxControl *m_control; - FmRxRds *m_rds; + FmRxPriv *d; }; #endif // FMRXSERVICE_H diff --git a/fmrxthread.cpp b/fmrxthread.cpp new file mode 100644 index 0000000..0043754 --- /dev/null +++ b/fmrxthread.cpp @@ -0,0 +1,203 @@ +#include + +#include +#include + +#include "fmrxthread.h" + +FmRxThread::FmRxThread(QObject *parent) : + QThread(parent), m_quit(false), m_error(false), m_fd(-1), + m_mainloop(0), m_context(0), m_stream(0) +{ +} + +void FmRxThread::setInputFd(int fd) +{ + m_fd = fd; +} + +int FmRxThread::inputFd() const +{ + return m_fd; +} + +bool FmRxThread::error() const +{ + return m_error; +} + +void FmRxThread::run() +{ + m_quit = false; + m_error = false; + + if (!configure()) { + m_error = true; + deconfigure(); + return; + } + + if (!loop()) { + m_error = true; + } + + deconfigure(); +} + +void FmRxThread::stop() +{ + m_quit = true; + if (m_mainloop) { + pa_mainloop_wakeup(m_mainloop); + } +} + +bool FmRxThread::configure() +{ + pa_context_state_t context_state; + pa_stream_state_t stream_state; + + m_mainloop = pa_mainloop_new(); + if (!m_mainloop) { + qWarning("Could not create pa_mainloop"); + return false; + } + + qDebug("FmRx thread connecting to PulseAudio"); + + // Find the application name + QString appName = QCoreApplication::applicationName(); + if (appName.isEmpty()) { + // Set some default + appName = "FmRx MediaService"; + } + + // Setup the PulseAudio context + m_context = pa_context_new(pa_mainloop_get_api(m_mainloop), + appName.toUtf8().data()); + if (!m_context) { + qWarning("Could not create pa_mainloop"); + return false; + } + + if (pa_context_connect(m_context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + qWarning("Could not connect to PulseAudio"); + return false; + } + + do { + if (pa_mainloop_iterate(m_mainloop, 1, NULL) < 0) { + qWarning("pa_mainloop_iterate() failed"); + return false; + } + + context_state = pa_context_get_state(m_context); + if (context_state == PA_CONTEXT_FAILED) { + qWarning("Early pa_context connection fail"); + return false; + } + + if (m_quit) return true; + } while (context_state != PA_CONTEXT_READY); + + // Setup the stream + pa_sample_spec spec; + spec.format = PA_SAMPLE_S16LE; + spec.channels = 2; + spec.rate = 48000; + + m_stream = pa_stream_new(m_context, "FmRx Stream", &spec, NULL); + if (!m_stream) { + qWarning("Could not create pa_stream"); + return false; + } + + if (pa_stream_connect_playback(m_stream, NULL, NULL, PA_STREAM_NOFLAGS, NULL, NULL) < 0) { + qWarning("Could not connect stream"); + return false; + } + + do { + if (pa_mainloop_iterate(m_mainloop, 1, NULL) < 0) { + qWarning("pa_mainloop_iterate() failed"); + return false; + } + + stream_state = pa_stream_get_state(m_stream); + if (stream_state == PA_STREAM_FAILED) { + qWarning("Early pa_stream termination"); + return false; + } + + if (m_quit) return true; + } while (stream_state != PA_STREAM_READY); + + return true; +} + +void FmRxThread::deconfigure() +{ + if (m_stream) { + pa_stream_disconnect(m_stream); + pa_stream_unref(m_stream); + m_stream = 0; + } + if (m_context) { + pa_context_disconnect(m_context); + pa_context_unref(m_context); + m_context = 0; + } + if (m_mainloop) { + pa_mainloop_free(m_mainloop); + m_mainloop = 0; + } + if (m_fd != -1) { + close(m_fd); + m_fd = -1; + } +} + +bool FmRxThread::loop() +{ + // Here comes the main loop + while (!m_quit) { + void *buffer; + size_t maxbytes = (size_t) -1; + ssize_t readbytes; + + if (pa_stream_begin_write(m_stream, &buffer, &maxbytes) < 0) { + qWarning("Failed to pa_stream_begin_write"); + return false; + } + + readbytes = read(m_fd, buffer, maxbytes); + if (readbytes < 0) { + qWarning("Failed to read from the FmRx pipe"); + return false; + } + + if (pa_stream_write(m_stream, buffer, readbytes, NULL, 0, PA_SEEK_RELATIVE) < 0) { + qWarning("Failed to pa_stream_write"); + return false; + } + + do { + if (pa_mainloop_iterate(m_mainloop, 1, NULL) < 0) { + qWarning("pa_mainloop_iterate() failed"); + return false; + } + if (m_quit) return true; + } while (pa_stream_writable_size(m_stream) == 0); + + if (pa_stream_get_state(m_stream) != PA_STREAM_READY) { + qWarning("pa_stream was lost"); + return false; + } + if (pa_context_get_state(m_context) != PA_CONTEXT_READY) { + qWarning("pa_context was lost"); + return false; + } + } + + return true; +} diff --git a/fmrxthread.h b/fmrxthread.h new file mode 100644 index 0000000..357f4c4 --- /dev/null +++ b/fmrxthread.h @@ -0,0 +1,38 @@ +#ifndef FMRXTHREAD_H +#define FMRXTHREAD_H + +#include + +#include + +class FmRxThread : public QThread +{ + Q_OBJECT +public: + explicit FmRxThread(QObject *parent = 0); + + void setInputFd(int fd); + int inputFd() const; + + bool error() const; + + void run(); + void stop(); + +signals: + +private: + bool configure(); + void deconfigure(); + bool loop(); + +private: + bool m_quit; + bool m_error; + int m_fd; + pa_mainloop *m_mainloop; + pa_context *m_context; + pa_stream *m_stream; +}; + +#endif // FMRXTHREAD_H diff --git a/qtc_packaging/debian_harmattan/changelog b/qtc_packaging/debian_harmattan/changelog index e727a60..eb45c7f 100644 --- a/qtc_packaging/debian_harmattan/changelog +++ b/qtc_packaging/debian_harmattan/changelog @@ -1,3 +1,10 @@ +fmrx-qt (0.2.0) unstable; urgency=low + + * Use PulseAudio directly instead of QAudioOutput. + * Report signalStrength if daemon version 0.2. + + -- Javier S. Pedro Mon, 13 Feb 2012 21:13:44 +0100 + fmrx-qt (0.0.1) unstable; urgency=low * Initial Release. diff --git a/qtc_packaging/debian_harmattan/control b/qtc_packaging/debian_harmattan/control index e28bdf5..b45d17e 100644 --- a/qtc_packaging/debian_harmattan/control +++ b/qtc_packaging/debian_harmattan/control @@ -3,7 +3,7 @@ Section: sound Priority: optional Maintainer: Javier S. Pedro Build-Depends: debhelper (>= 5), libqt4-dev, libqtm-multimedia-dev, - libglib2.0-dev, libdbus-glib-1-dev, libdbus-1-dev + libpulse-dev, libglib2.0-dev, libdbus-glib-1-dev, libdbus-1-dev Standards-Version: 3.7.3 Homepage: https://gitorious.org/n950-fmrx/fmrx-qt @@ -16,3 +16,4 @@ Description: FmRx Qt MediaService plugin . If this plugin is installed, QRadioTuner can be used in Harmattan apps. +XSBC-Maemo-Display-Name: FmRx Qt MediaService plugin -- cgit v1.2.3