You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tderadio/kradio3/plugins/v4lradio/v4lradio.cpp

1622 lines
44 KiB

/***************************************************************************
v4lradio.cpp - description
-------------------
begin : Don M<EFBFBD>r 8 21:57:17 CET 2001
copyright : (C) 2002-2005 by Ernst Martin Witte
email : witte@kawo1.rwth-aachen.de
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#ifdef HAVE_V4L2
#include "linux/videodev2.h"
#endif
#include "linux/videodev.h"
#include <linux/soundcard.h>
#include <string.h> // memcpy needed
#include <qlayout.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qvaluelist.h>
#include <kconfig.h>
#include <kiconloader.h>
#include <kdialogbase.h>
#include <kaboutdata.h>
#include <klocale.h>
#include "../../src/include/aboutwidget.h"
#include "../../src/include/utils.h"
#include "v4lradio.h"
#include "v4lradio-configuration.h"
#include "../../src/include/debug-profiler.h"
struct _lrvol { unsigned char l, r; short dummy; };
///////////////////////////////////////////////////////////////////////
PLUGIN_LIBRARY_FUNCTIONS(V4LRadio, "kradio-v4lradio", i18n("Support for V4L(2) Radio Devices"));
///////////////////////////////////////////////////////////////////////
V4LRadio::V4LRadio(const QString &name)
: PluginBase(name, i18n("Video For Linux Plugin")),
m_treble(0.5),
m_bass(0.5),
m_balance(0),
m_deviceVolume(0.9),
m_muted(false),
m_signalQuality(0),
m_stereo(false),
m_minQuality(0.75),
m_minFrequency(87.0),
m_maxFrequency(108.0),
m_lastMinDevFrequency(87.0),
m_lastMaxDevFrequency(108.0),
m_defaultPlaybackVolume(0.5),
m_scanStep(0.05),
m_radioDev("/dev/radio0"),
m_radio_fd(-1),
m_useOldV4L2Calls(true),
m_pollTimer(this),
m_blockReadTuner(false),
m_blockReadAudio(false),
m_SoundStreamID(createNewSoundStream(false)),
m_PlaybackMixerID(QString::null),
m_CaptureMixerID(QString::null),
m_PlaybackMixerChannel(QString::null),
m_CaptureMixerChannel(QString::null),
m_ActivePlayback(false),
m_MuteOnPowerOff(false),
m_VolumeZeroOnPowerOff(false),
m_restorePowerOn(false)
{
QObject::connect (&m_pollTimer, SIGNAL(timeout()), this, SLOT(poll()));
m_pollTimer.start(333);
m_audio = new video_audio;
bzero(m_audio, sizeof(video_audio));
m_tuner = new video_tuner;
bzero(m_tuner, sizeof(video_tuner));
#ifdef HAVE_V4L2
m_tuner2 = new v4l2_tuner;
bzero(m_tuner2, sizeof(v4l2_tuner));
#endif
m_caps.version = 0;
m_seekHelper = new FrequencySeekHelper(*this);
m_seekHelper->connectI(this);
}
V4LRadio::~V4LRadio()
{
setPower(false);
if (m_seekHelper)
delete m_seekHelper;
if (m_audio) delete m_audio;
if (m_tuner) delete m_tuner;
#ifdef HAVE_V4L2
if (m_tuner2) delete m_tuner2;
#endif
}
bool V4LRadio::connectI (Interface *i)
{
bool a = IRadioDevice::connectI(i);
bool b = ISeekRadio::connectI(i);
bool c = IFrequencyRadio::connectI(i);
bool d = IV4LCfg::connectI(i);
bool e = PluginBase::connectI(i);
bool f = ISoundStreamClient::connectI(i);
return a || b || c || d || e || f;
}
bool V4LRadio::disconnectI (Interface *i)
{
bool a = IRadioDevice::disconnectI(i);
bool b = ISeekRadio::disconnectI(i);
bool c = IFrequencyRadio::disconnectI(i);
bool d = IV4LCfg::disconnectI(i);
bool e = PluginBase::disconnectI(i);
bool f = ISoundStreamClient::disconnectI(i);
m_seekHelper->disconnectI(i);
return a || b || c || d || e || f;
}
void V4LRadio::noticeConnectedI (ISoundStreamServer *s, bool pointer_valid)
{
ISoundStreamClient::noticeConnectedI(s, pointer_valid);
if (s && pointer_valid) {
m_seekHelper->connectI(s);
s->register4_queryPlaybackVolume(this);
s->register4_sendTreble(this);
s->register4_sendBass(this);
s->register4_sendBalance(this);
s->register4_sendMute(this);
s->register4_sendUnmute(this);
s->register4_sendSignalMinQuality(this);
s->register4_sendStereo(this);
s->register4_queryTreble(this);
s->register4_queryBass(this);
s->register4_queryBalance(this);
s->register4_querySignalQuality(this);
s->register4_querySignalMinQuality(this);
s->register4_queryHasGoodQuality(this);
s->register4_queryIsStereo(this);
s->register4_queryIsMuted(this);
s->register4_sendPlaybackVolume(this);
s->register4_sendCaptureVolume(this);
s->register4_sendStopCapture(this);
s->register4_querySoundStreamDescription(this);
s->register4_querySoundStreamRadioStation(this);
s->register4_queryEnumerateSoundStreams(this);
notifySoundStreamCreated(m_SoundStreamID);
}
}
void V4LRadio::noticeConnectedSoundClient(ISoundStreamClient::thisInterface *i, bool pointer_valid)
{
if (i && pointer_valid && i->getSoundStreamClientID() == m_PlaybackMixerID) {
setPlaybackMixer(m_PlaybackMixerID, m_PlaybackMixerChannel);
}
if (i && pointer_valid && i->getSoundStreamClientID() == m_CaptureMixerID) {
setCaptureMixer(m_CaptureMixerID, m_CaptureMixerChannel);
}
}
// IRadioDevice methods
bool V4LRadio::setPower (bool on)
{
return on ? powerOn() : powerOff();
}
void V4LRadio::searchMixers(ISoundStreamClient **playback_mixer, ISoundStreamClient **capture_mixer)
{
if (playback_mixer) {
*playback_mixer = getSoundStreamClientWithID(m_PlaybackMixerID);
if (!*playback_mixer) {
QPtrList<ISoundStreamClient> playback_mixers = queryPlaybackMixers();
if (!playback_mixers.isEmpty())
*playback_mixer = playback_mixers.first();
}
}
if (capture_mixer) {
*capture_mixer = getSoundStreamClientWithID(m_CaptureMixerID);
if (!*capture_mixer) {
QPtrList<ISoundStreamClient> capture_mixers = queryCaptureMixers();
if (!capture_mixers.isEmpty())
*capture_mixer = capture_mixers.first();
}
}
}
bool V4LRadio::powerOn ()
{
if (isPowerOn())
return true;
radio_init();
if (isPowerOn()) {
ISoundStreamClient *playback_mixer = NULL,
*capture_mixer = NULL;
searchMixers(&playback_mixer, &capture_mixer);
if (playback_mixer)
playback_mixer->preparePlayback(m_SoundStreamID, m_PlaybackMixerChannel, m_ActivePlayback, false);
if (capture_mixer)
capture_mixer->prepareCapture(m_SoundStreamID, m_CaptureMixerChannel);
sendStartPlayback(m_SoundStreamID);
float tmp_vol = 0;
queryPlaybackVolume(m_SoundStreamID, tmp_vol);
if (tmp_vol < 0.005)
sendPlaybackVolume(m_SoundStreamID, m_defaultPlaybackVolume);
if (m_ActivePlayback) {
SoundFormat sf;
sendStartCaptureWithFormat(m_SoundStreamID, sf, sf);
}
unmute(m_SoundStreamID);
notifyPowerChanged(true);
}
return true;
}
bool V4LRadio::powerOff ()
{
if (! isPowerOn())
return true;
queryPlaybackVolume(m_SoundStreamID, m_defaultPlaybackVolume);
if (m_MuteOnPowerOff)
sendMute(m_SoundStreamID, true);
if (m_VolumeZeroOnPowerOff)
sendPlaybackVolume(m_SoundStreamID, 0.0);
mute(m_SoundStreamID);
radio_done();
sendStopPlayback(m_SoundStreamID);
sendStopCapture(m_SoundStreamID);
closeSoundStream(m_SoundStreamID);
m_SoundStreamID = createNewSoundStream(m_SoundStreamID, false);
notifySoundStreamCreated(m_SoundStreamID);
if (isPowerOff()) {
notifyPowerChanged(false);
}
return true;
}
bool V4LRadio::activateStation(const RadioStation &rs)
{
const FrequencyRadioStation *frs = dynamic_cast<const FrequencyRadioStation*>(&rs);
if (frs == NULL)
return false;
if (setFrequency(frs->frequency())) {
m_currentStation = *frs;
if (frs->initialVolume() > 0)
setPlaybackVolume(m_SoundStreamID, frs->initialVolume());
return true;
}
return false;
}
bool V4LRadio::isPowerOn() const
{
return m_radio_fd >= 0;
}
bool V4LRadio::isPowerOff() const
{
return m_radio_fd < 0;
}
SoundStreamID V4LRadio::getSoundStreamID() const
{
return m_SoundStreamID;
}
const RadioStation &V4LRadio::getCurrentStation() const
{
return m_currentStation;
}
const QString &V4LRadio::getDescription() const
{
return m_caps.description;
}
SoundStreamID V4LRadio::getCurrentSoundStreamID() const
{
return m_SoundStreamID;
}
bool V4LRadio::setTreble (SoundStreamID id, float t)
{
if (id != m_SoundStreamID)
return false;
if (t > 1.0) t = 1.0;
if (t < 0) t = 0.0;
if ((int)rint(m_treble*65535) != (int)rint(t*65535)) {
m_treble = t;
writeAudioInfo();
notifyTrebleChanged(id, t);
}
return true;
}
bool V4LRadio::setBass (SoundStreamID id, float b)
{
if (id != m_SoundStreamID)
return false;
if (b > 1.0) b = 1.0;
if (b < 0) b = 0.0;
if ((int)rint(m_bass*65535) != (int)rint(b*65535)) {
m_bass = b;
writeAudioInfo();
notifyBassChanged(id, b);
}
return true;
}
bool V4LRadio::setBalance (SoundStreamID id, float b)
{
if (id != m_SoundStreamID)
return false;
if (b > +1.0) b = +1.0;
if (b < -1.0) b = -1.0;
if ((int)rint(m_balance*32767) != (int)rint(b*32767)) {
m_balance = b;
writeAudioInfo();
notifyBalanceChanged(id, b);
}
return true;
}
bool V4LRadio::setDeviceVolume (float v)
{
if (v > 1.0) v = 1.0;
if (v < 0) v = 0;
if ((int)rint(m_deviceVolume*65535) != (int)rint(v*65535)) {
m_deviceVolume = v;
writeAudioInfo();
notifyDeviceVolumeChanged(v);
}
return true;
}
bool V4LRadio::mute (SoundStreamID id, bool mute)
{
if (id != m_SoundStreamID)
return false;
if (m_muted != mute) {
m_muted = mute;
bool r = writeAudioInfo();
if (r)
notifyMuted(id, m_muted);
return r;
}
return false;
}
bool V4LRadio::unmute (SoundStreamID id, bool unmute)
{
return mute(id, !unmute);
}
bool V4LRadio::setSignalMinQuality (SoundStreamID id, float mq)
{
if (id != m_SoundStreamID)
return false;
if (rint(mq*100) == rint(m_minQuality*100))
return true;
m_minQuality = mq;
notifySignalMinQualityChanged(id, m_minQuality);
return true;
}
bool V4LRadio::setStereo(SoundStreamID /*id*/, bool /*b*/)
{
// FIXME if possible
return false; // we can't do that currently, not even switch stereo to mono
}
bool V4LRadio::getTreble (SoundStreamID id, float &t) const
{
if (id != m_SoundStreamID)
return false;
readAudioInfo();
t = m_treble;
return true;
}
bool V4LRadio::getBass (SoundStreamID id, float &b) const
{
if (id != m_SoundStreamID)
return false;
readAudioInfo();
b = m_bass;
return true;
}
bool V4LRadio::getBalance (SoundStreamID id, float &b) const
{
if (id != m_SoundStreamID)
return false;
readAudioInfo();
b = m_balance;
return true;
}
float V4LRadio::getDeviceVolume () const
{
readAudioInfo();
return m_deviceVolume;
}
bool V4LRadio::getSignalMinQuality(SoundStreamID id, float &q) const
{
if (id != m_SoundStreamID)
return false;
q = m_minQuality;
return true;
}
bool V4LRadio::getSignalQuality(SoundStreamID id, float &q) const
{
if (id != m_SoundStreamID)
return false;
readTunerInfo();
q = m_signalQuality;
return true;
}
bool V4LRadio::hasGoodQuality(SoundStreamID id, bool &good) const
{
if (id != m_SoundStreamID)
return false;
float q = 0;
if (getSignalQuality(id, q))
good = q >= m_minQuality;
return true;
}
bool V4LRadio::isStereo(SoundStreamID id, bool &s) const
{
if (id != m_SoundStreamID)
return false;
readAudioInfo();
s = m_stereo;
return true;
}
bool V4LRadio::isMuted(SoundStreamID id, bool &m) const
{
if (id != m_SoundStreamID)
return false;
readAudioInfo();
m = m_muted;
return true;
}
// ISeekRadio
bool V4LRadio::toBeginning()
{
setFrequency(getMinFrequency());
return true;
}
bool V4LRadio::toEnd()
{
setFrequency(getMaxFrequency());
return true;
}
bool V4LRadio::startSeekUp()
{
return startSeek(true);
}
bool V4LRadio::startSeekDown()
{
return startSeek(false);
}
bool V4LRadio::startSeek(bool up)
{
if (isPowerOn() && m_seekHelper) {
m_seekHelper->start(m_SoundStreamID, up ? SeekHelper::up : SeekHelper::down);
return true;
} else {
return false;
}
}
bool V4LRadio::stopSeek()
{
if (m_seekHelper) m_seekHelper->stop();
return true;
}
bool V4LRadio::isSeekRunning() const
{
if (m_seekHelper)
return m_seekHelper->isRunning();
else
return false;
}
bool V4LRadio::isSeekUpRunning() const
{
if (m_seekHelper)
return m_seekHelper->isRunningUp();
else
return false;
}
bool V4LRadio::isSeekDownRunning() const
{
if (m_seekHelper)
return m_seekHelper->isRunningDown();
else
return false;
}
float V4LRadio::getProgress () const
{
float min = getMinFrequency();
float max = getMaxFrequency();
return (getFrequency() - min) / (max - min);
}
// IFrequencyRadio
bool V4LRadio::setFrequency(float freq)
{
// if (isSeekRunning())
// stopSeek();
if (m_currentStation.frequency() == freq) {
return true;
}
float minf = getMinFrequency();
float maxf = getMaxFrequency();
if (isPowerOn()) {
bool oldMute = false;
isMuted(m_SoundStreamID, oldMute);
if (!oldMute && !m_ActivePlayback)
mute(m_SoundStreamID);
if (!m_tunercache.valid) readTunerInfo();
float df = m_tunercache.deltaF;
unsigned long lfreq = (unsigned long) rint(freq / df);
if (freq > maxf || freq < minf) {
logError("V4LRadio::setFrequency: " +
i18n("invalid frequency %1").arg(QString().setNum(freq)));
if (!oldMute && !m_ActivePlayback)
unmute(m_SoundStreamID);
return false;
}
int r = -1;
if (m_caps.version == 1) {
r = ioctl(m_radio_fd, VIDIOCSFREQ, &lfreq);
}
#ifdef HAVE_V4L2
else if (m_caps.version == 2) {
v4l2_frequency tmp;
tmp.tuner = 0;
tmp.type = V4L2_TUNER_RADIO;
tmp.frequency = lfreq;
r = ioctl(m_radio_fd, VIDIOC_S_FREQUENCY, &tmp);
}
#endif
else {
logError("V4LRadio::setFrequency: " +
i18n("don't known how to handle V4L-version %1")
.arg(m_caps.version));
}
if (r) {
logError("V4LRadio::setFrequency: " +
i18n("error setting frequency to %1 (%2)")
.arg(QString().setNum(freq))
.arg(QString().setNum(r)));
// unmute the old radio with the old radio station
if (!oldMute && !m_ActivePlayback)
unmute(m_SoundStreamID);
return false;
}
// unmute this radio device, because we now have the current
// radio station
if (!oldMute && !m_ActivePlayback)
unmute(m_SoundStreamID);
}
m_currentStation.setFrequency(freq);
notifyFrequencyChanged(freq, &m_currentStation);
notifyStationChanged(m_currentStation);
notifyProgress((freq - minf) / (maxf - minf));
notifySoundStreamChanged(m_SoundStreamID);
return true;
}
bool V4LRadio::setMinFrequency (float minF)
{
float oldm = getMinFrequency();
m_minFrequency = minF;
float newm = getMinFrequency();
if (oldm != newm)
notifyMinMaxFrequencyChanged(newm, getMaxFrequency());
return true;
}
bool V4LRadio::setMaxFrequency (float maxF)
{
float oldm = getMaxFrequency();
m_maxFrequency = maxF;
float newm = getMaxFrequency();
if (oldm != newm)
notifyMinMaxFrequencyChanged(getMinFrequency(), newm);
return true;
}
bool V4LRadio::setScanStep(float s)
{
float old = m_scanStep;
m_scanStep = s;
if (old != s) notifyScanStepChanged(m_scanStep);
return true;
}
float V4LRadio::getFrequency() const
{
return m_currentStation.frequency();
}
float V4LRadio::getMinFrequency() const
{
return m_minFrequency ? m_minFrequency : getMinDeviceFrequency();
}
float V4LRadio::getMaxFrequency() const
{
return m_maxFrequency ? m_maxFrequency : getMaxDeviceFrequency();
}
float V4LRadio::getMinDeviceFrequency() const
{
if (!m_tunercache.valid)
readTunerInfo();
return m_tunercache.minF;
}
float V4LRadio::getMaxDeviceFrequency() const
{
if (!m_tunercache.valid)
readTunerInfo();
return m_tunercache.maxF;
}
float V4LRadio::getScanStep() const
{
return m_scanStep;
}
// IV4LCfg methods
bool V4LRadio::setRadioDevice(const QString &s)
{
if (m_radioDev != s) {
bool p = isPowerOn();
powerOff();
m_radioDev = s;
m_caps = readV4LCaps(m_radioDev);
notifyRadioDeviceChanged(m_radioDev);
notifyDescriptionChanged(m_caps.description);
notifyCapabilitiesChanged(m_caps);
setPower(p);
}
return true;
}
bool V4LRadio::setPlaybackMixer(const QString &soundStreamClientID, const QString &ch)
{
bool change = m_PlaybackMixerID != soundStreamClientID || m_PlaybackMixerChannel != ch;
m_PlaybackMixerID = soundStreamClientID;
m_PlaybackMixerChannel = ch;
if (isPowerOn()) {
queryPlaybackVolume(m_SoundStreamID, m_defaultPlaybackVolume);
sendStopPlayback(m_SoundStreamID);
sendReleasePlayback(m_SoundStreamID);
}
ISoundStreamClient *playback_mixer = NULL;
searchMixers(&playback_mixer, NULL);
if (playback_mixer)
playback_mixer->preparePlayback(m_SoundStreamID, m_PlaybackMixerChannel, m_ActivePlayback, false);
if (isPowerOn()) {
sendStartPlayback(m_SoundStreamID);
sendPlaybackVolume(m_SoundStreamID, m_defaultPlaybackVolume);
if (m_ActivePlayback) {
SoundFormat sf;
sendStartCaptureWithFormat(m_SoundStreamID, sf, sf);
}
}
if (change)
notifyPlaybackMixerChanged(soundStreamClientID, ch);
return true;
}
bool V4LRadio::setCaptureMixer(const QString &soundStreamClientID, const QString &ch)
{
bool change = m_PlaybackMixerID != soundStreamClientID || m_PlaybackMixerChannel != ch;
m_CaptureMixerID = soundStreamClientID;
m_CaptureMixerChannel = ch;
bool r = false;
SoundFormat sf;
queryIsCaptureRunning(m_SoundStreamID, r, sf);
float v = 0;
if (isPowerOn() && r) {
queryCaptureVolume(m_SoundStreamID, v);
sendStopCapture(m_SoundStreamID);
sendReleaseCapture(m_SoundStreamID);
}
ISoundStreamClient *capture_mixer = NULL;
searchMixers(NULL, &capture_mixer);
if (capture_mixer)
capture_mixer->prepareCapture(m_SoundStreamID, m_CaptureMixerChannel);
if (isPowerOn() && r) {
sendStartCaptureWithFormat(m_SoundStreamID, sf, sf);
sendCaptureVolume(m_SoundStreamID, v);
}
if (change)
notifyCaptureMixerChanged(soundStreamClientID, ch);
return true;
}
V4LCaps V4LRadio::getCapabilities(QString dev) const
{
if (dev.isNull()) {
return m_caps;
} else {
return readV4LCaps(dev);
}
}
bool V4LRadio::setActivePlayback(bool a)
{
if (a == m_ActivePlayback)
return true;
if (isPowerOn()) {
queryPlaybackVolume(m_SoundStreamID, m_defaultPlaybackVolume);
sendStopPlayback(m_SoundStreamID);
sendReleasePlayback(m_SoundStreamID);
if (m_ActivePlayback) {
sendStopCapture(m_SoundStreamID);
}
}
m_ActivePlayback = a;
ISoundStreamClient *playback_mixer = NULL;
searchMixers(&playback_mixer, NULL);
if (playback_mixer)
playback_mixer->preparePlayback(m_SoundStreamID, m_PlaybackMixerChannel, m_ActivePlayback, false);
if (isPowerOn()) {
sendStartPlayback(m_SoundStreamID);
sendPlaybackVolume(m_SoundStreamID, m_defaultPlaybackVolume);
if (m_ActivePlayback) {
SoundFormat sf;
sendStartCaptureWithFormat(m_SoundStreamID, sf, sf);
}
}
// FIXME: restart playback/capture
notifyActivePlaybackChanged(m_ActivePlayback);
return true;
}
bool V4LRadio::setMuteOnPowerOff(bool a)
{
if (a != m_MuteOnPowerOff) {
m_MuteOnPowerOff = a;
notifyMuteOnPowerOffChanged(m_MuteOnPowerOff);
}
return true;
}
bool V4LRadio::setVolumeZeroOnPowerOff(bool a)
{
if (a != m_VolumeZeroOnPowerOff) {
m_VolumeZeroOnPowerOff = a;
notifyVolumeZeroOnPowerOffChanged(m_VolumeZeroOnPowerOff);
}
return true;
}
// PluginBase methods
void V4LRadio::saveState (KConfig *config) const
{
config->setGroup(QString("v4lradio-") + name());
config->writeEntry("RadioDev", m_radioDev);
config->writeEntry("PlaybackMixerID", m_PlaybackMixerID);
config->writeEntry("PlaybackMixerChannel", m_PlaybackMixerChannel);
config->writeEntry("CaptureMixerID", m_CaptureMixerID);
config->writeEntry("CaptureMixerChannel", m_CaptureMixerChannel);
config->writeEntry("fMinOverride", m_minFrequency);
config->writeEntry("fMaxOverride", m_maxFrequency);
config->writeEntry("fLastDevMin", m_lastMinDevFrequency);
config->writeEntry("fLastDevMax", m_lastMaxDevFrequency);
config->writeEntry("defaultPlaybackVolume", m_defaultPlaybackVolume);
config->writeEntry("signalMinQuality", m_minQuality);
config->writeEntry("scanStep", m_scanStep);
config->writeEntry("Frequency", m_currentStation.frequency());
config->writeEntry("Treble", m_treble);
config->writeEntry("Bass", m_bass);
config->writeEntry("Balance", m_balance);
config->writeEntry("DeviceVolume", m_deviceVolume);
config->writeEntry("PowerOn", isPowerOn());
config->writeEntry("UseOldV4L2Calls", m_useOldV4L2Calls);
config->writeEntry("ActivePlayback", m_ActivePlayback);
config->writeEntry("MuteOnPowerOff", m_MuteOnPowerOff);
config->writeEntry("VolumeZeroOnPowerOff", m_VolumeZeroOnPowerOff);
}
void V4LRadio::restoreState (KConfig *config)
{
BlockProfiler p("V4LRadio::restoreState");
config->setGroup(QString("v4lradio-") + name());
QString base_devname = "/dev/radio";
QStringList testlist (base_devname );
for (int i = 0; i < 9; ++i)
testlist.append(base_devname + QString::number(i));
QString found_devname(QString::null);
for (QValueListConstIterator<QString> it = testlist.begin(); it != testlist.end(); ++it) {
QFile f(*it);
if (f.exists()) {
QFileInfo info(f);
if (info.isReadable() && info.isWritable()) {
found_devname = *it;
break;
}
else {
if (found_devname.isNull())
found_devname = *it;
logWarning(i18n("Device %1 does exist but is not readable/writable. Please check device permissions.").arg(*it));
}
}
}
QString default_devname = found_devname.isNull() ? base_devname : found_devname;
QString devname = config->readEntry ("RadioDev", default_devname);
if (found_devname.isNull() && devname == default_devname) {
logError(i18n("Could not find an accessible v4l(2) radio device."));
}
setRadioDevice(devname);
QString PlaybackMixerID = config->readEntry ("PlaybackMixerID", QString::null);
QString PlaybackMixerChannel = config->readEntry ("PlaybackMixerChannel", "Line");
QString CaptureMixerID = config->readEntry ("CaptureMixerID", QString::null);
QString CaptureMixerChannel = config->readEntry ("CaptureMixerChannel", "Line");
m_ActivePlayback = config->readBoolEntry("ActivePlayback", false);
m_MuteOnPowerOff = config->readBoolEntry("MuteOnPowerOff", false);
m_VolumeZeroOnPowerOff = config->readBoolEntry("VolumeZeroOnPowerOff", false);
m_lastMinDevFrequency = config->readDoubleNumEntry ("fLastDevMin", 65.0);
m_lastMaxDevFrequency = config->readDoubleNumEntry ("fLastDevMax", 108.0);
m_minFrequency = config->readDoubleNumEntry ("fMinOverride", m_lastMinDevFrequency);
m_maxFrequency = config->readDoubleNumEntry ("fMaxOverride", m_lastMaxDevFrequency);
m_minQuality = config->readDoubleNumEntry ("signalMinQuality", 0.75);
m_scanStep = config->readDoubleNumEntry ("scanStep", 0.05);
m_defaultPlaybackVolume = config->readDoubleNumEntry ("defaultPlaybackVolume", 0.5);
setPlaybackMixer(PlaybackMixerID, PlaybackMixerChannel);
setCaptureMixer (CaptureMixerID, CaptureMixerChannel);
notifyDeviceMinMaxFrequencyChanged(m_lastMinDevFrequency, m_lastMaxDevFrequency);
notifyMinMaxFrequencyChanged(m_minFrequency, m_maxFrequency);
notifySignalMinQualityChanged(m_SoundStreamID, m_minQuality);
notifyScanStepChanged(m_scanStep);
notifyActivePlaybackChanged(m_ActivePlayback);
notifyMuteOnPowerOffChanged(m_MuteOnPowerOff);
notifyVolumeZeroOnPowerOffChanged(m_VolumeZeroOnPowerOff);
BlockProfiler p2("V4LRadio::restoreState2");
setFrequency(config->readDoubleNumEntry("Frequency", 88));
m_restorePowerOn = config->readBoolEntry ("PowerOn", false);
BlockProfiler p3("V4LRadio::restoreState3");
setTreble (m_SoundStreamID, config->readDoubleNumEntry("Treble", 0.5));
setBass (m_SoundStreamID, config->readDoubleNumEntry("Bass", 0.5));
setBalance (m_SoundStreamID, config->readDoubleNumEntry("Balance", 0.0));
setDeviceVolume( config->readDoubleNumEntry("DeviceVolume", 0.9));
m_useOldV4L2Calls = config->readBoolEntry("UseOldV4L2Calls", true);
if (isPowerOff())
notifyPlaybackVolumeChanged(m_SoundStreamID, m_defaultPlaybackVolume);
}
void V4LRadio::startPlugin()
{
PluginBase::startPlugin();
setPower(m_restorePowerOn);
}
ConfigPageInfo V4LRadio::createConfigurationPage()
{
V4LRadioConfiguration *v4lconf = new V4LRadioConfiguration(NULL, m_SoundStreamID);
connectI(v4lconf);
return ConfigPageInfo (v4lconf,
i18n("V4L Radio"),
i18n("V4L Radio Options"),
"package_utilities");
}
AboutPageInfo V4LRadio::createAboutPage()
{
KAboutData aboutData("kradio",
NULL,
NULL,
I18N_NOOP("V4L/V4L2 Plugin for KRadio."
"<P>"
"Provides Support for V4L/V4L2 based Radio Cards"
"<P>"),
0,
//KAboutData::License_GPL,
"(c) 2002-2005 Martin Witte, Klas Kalass",
0,
"http://sourceforge.net/projects/kradio",
0);
aboutData.addAuthor("Martin Witte", "", "witte@kawo1.rwth-aachen.de");
aboutData.addAuthor("Klas Kalass", "", "klas.kalass@gmx.de");
return AboutPageInfo(
new KRadioAboutWidget(aboutData, KRadioAboutWidget::AbtTabbed),
i18n("V4L/V4L2"),
i18n("V4L/V4L2 Plugin"),
"package_utilities"
);
}
////////////////////////////////////////
// anything else
void V4LRadio::radio_init()
{
if (isSeekRunning())
stopSeek();
m_caps = readV4LCaps(m_radioDev);
notifyCapabilitiesChanged(m_caps);
notifyDescriptionChanged(m_caps.description);
/* m_mixer_fd = open(m_mixerDev, O_RDONLY);
if (m_mixer_fd < 0) {
radio_done();
logError("V4LRadio::radio_init: " +
i18n("Cannot open mixer device %1").arg(m_mixerDev));
return;
}
*/
m_radio_fd = open(m_radioDev.ascii(), O_RDONLY);
if (m_radio_fd < 0) {
radio_done();
logError("V4LRadio::radio_init: " +
i18n("Cannot open radio device %1").arg(m_radioDev));
return;
}
readTunerInfo();
writeAudioInfo(); // set tuner-audio config as used last time
readAudioInfo(); // reread tuner-audio and read-only flags (e.g. stereo)
// restore frequency
float old = getFrequency();
m_currentStation.setFrequency(0);
setFrequency(old);
// read volume level from mixer
// FIXME: do we still need this
/* float v = 0;
getVolume(m_SoundStreamID, v)
setVolume (m_SoundStreamID, v);*/
}
void V4LRadio::radio_done()
{
if (isSeekRunning())
stopSeek();
if (m_radio_fd >= 0) close (m_radio_fd);
// if (m_mixer_fd >= 0) close (m_mixer_fd);
m_radio_fd = -1;
// m_mixer_fd = -1;
}
#define CAPS_NAME_LEN 127
V4LCaps V4LRadio::readV4LCaps(const QString &device) const
{
char buffer[CAPS_NAME_LEN+1];
int r;
int fd;
V4LCaps c;
c.description = device;
fd = open(device.ascii(), O_RDONLY);
if (fd < 0) {
logError("V4LRadio::readV4LCaps: " +
i18n("cannot open %1").arg(device));
return c;
}
video_capability caps;
r = ioctl(fd, VIDIOCGCAP, &caps);
if (r == 0) {
c.version = 1;
size_t l = sizeof(caps.name);
l = l < CAPS_NAME_LEN ? l : CAPS_NAME_LEN;
memcpy(buffer, caps.name, l);
buffer[l] = 0;
c.description = buffer;
c.hasMute = false;
c.unsetVolume();
c.unsetTreble();
c.unsetBass();
c.unsetBalance();
video_audio audiocaps;
if (0 == ioctl(fd, VIDIOCGAUDIO, &audiocaps)) {
logDebug("V4LRadio::readV4LCaps: " +
i18n("audio caps = %1").arg(QString().setNum(audiocaps.flags)));
if ((audiocaps.flags & VIDEO_AUDIO_MUTABLE) != 0)
c.hasMute = true;
if ((audiocaps.flags & VIDEO_AUDIO_VOLUME) != 0)
c.setVolume (0, 65535);
if ((audiocaps.flags & VIDEO_AUDIO_TREBLE) != 0)
c.setTreble (0, 65535);
if ((audiocaps.flags & VIDEO_AUDIO_BASS) != 0)
c.setBass (0, 65535);
// at least my driver has support for balance, but the bit is not set ...
c.setBalance(0, 65535);
}
} else {
logError("V4LRadio::readV4LCaps: " +
i18n("error reading V4L1 caps"));
}
#ifdef HAVE_V4L2
v4l2_capability caps2;
r = ioctl(fd, VIDIOC_QUERYCAP, &caps2);
if (r == 0) {
c.version = 2;
logDebug(i18n("V4L2 - Version: %1").arg(QString().sprintf("%08X", caps2.version)));
size_t l = sizeof(caps.name);
l = l < CAPS_NAME_LEN ? l : CAPS_NAME_LEN;
memcpy(buffer, caps.name, l);
buffer[l] = 0;
// c.description = buffer;
v4l2_queryctrl ctrl;
c.hasMute = false;
c.unsetVolume();
c.unsetTreble();
c.unsetBass();
c.unsetBalance();
ctrl.id = V4L2_CID_AUDIO_MUTE;
if (0 == ioctl(fd, VIDIOC_QUERYCTRL, &ctrl))
c.hasMute = !(ctrl.flags & V4L2_CTRL_FLAG_DISABLED);
else
logError(i18n("V4L2: Querying mute control failed"));
ctrl.id = V4L2_CID_AUDIO_VOLUME;
if (0 == ioctl(fd, VIDIOC_QUERYCTRL, &ctrl)) {
if (!(ctrl.flags & V4L2_CTRL_FLAG_DISABLED))
c.setVolume(ctrl.minimum, ctrl.maximum);
} else {
logError(i18n("V4L2: Querying volume control failed"));
}
ctrl.id = V4L2_CID_AUDIO_TREBLE;
if (0 == ioctl(fd, VIDIOC_QUERYCTRL, &ctrl)) {
if (!(ctrl.flags & V4L2_CTRL_FLAG_DISABLED))
c.setTreble(ctrl.minimum, ctrl.maximum);
} else {
logError(i18n("V4L2: Querying treble control failed"));
}
ctrl.id = V4L2_CID_AUDIO_BASS;
if (0 == ioctl(fd, VIDIOC_QUERYCTRL, &ctrl)) {
if (!(ctrl.flags & V4L2_CTRL_FLAG_DISABLED))
c.setBass(ctrl.minimum, c.maxBass = ctrl.maximum);
} else {
logError(i18n("V4L2: Querying bass control failed"));
}
ctrl.id = V4L2_CID_AUDIO_BALANCE;
if (0 == ioctl(fd, VIDIOC_QUERYCTRL, &ctrl)) {
if (!(ctrl.flags & V4L2_CTRL_FLAG_DISABLED))
c.setBalance(ctrl.minimum, ctrl.maximum);
} else {
logError(i18n("V4L2: Querying balance control failed"));
}
} else {
logWarning(i18n("V4LRadio::readV4LCaps: Reading V4L2 caps failed"));
}
#endif
if (c.version > 0) {
logInfo(i18n("V4L %1 detected").arg(c.version));
} else {
logError(i18n("V4L not detected"));
}
logInfo(c.hasMute ? i18n("Radio is mutable") : i18n("Radio is not mutable"));
logInfo(c.hasVolume ? i18n("Radio has Volume Control") : i18n("Radio has no Volume Control"));
logInfo(c.hasBass ? i18n("Radio has Bass Control") : i18n("Radio has no Bass Control"));
logInfo(c.hasTreble ? i18n("Radio has Treble Control") : i18n("Radio has no Treble Control"));
close(fd);
return c;
}
bool V4LRadio::readTunerInfo() const
{
if (m_blockReadTuner) return true;
float oldq = m_signalQuality;
float oldminf = m_tunercache.minF;
float oldmaxf = m_tunercache.maxF;
if (!m_tunercache.valid) {
m_tunercache.minF = m_lastMinDevFrequency;
m_tunercache.maxF = m_lastMaxDevFrequency;
m_tunercache.deltaF = 1.0/16.0;
m_tunercache.valid = true;
}
int r = 0;
if (isPowerOn()) {
// v4l1
if (m_caps.version == 1) {
r = ioctl(m_radio_fd, VIDIOCGTUNER, m_tuner);
if (r == 0) {
if ((m_tuner->flags & VIDEO_TUNER_LOW) != 0)
m_tunercache.deltaF = 1.0 / 16000.0;
m_tunercache.minF = float(m_tuner->rangelow) * m_tunercache.deltaF;
m_tunercache.maxF = float(m_tuner->rangehigh) * m_tunercache.deltaF;
m_tunercache.valid = true;
m_signalQuality = float(m_tuner->signal) / 32767.0;
}
}
#ifdef HAVE_V4L2
// v4l2
else if (m_caps.version == 2) {
r = ioctl(m_radio_fd, VIDIOC_G_TUNER, m_tuner2);
if (r == 0) {
if ((m_tuner2->capability & V4L2_TUNER_CAP_LOW) != 0)
m_tunercache.deltaF = 1.0 / 16000.0;
m_tunercache.minF = float(m_tuner2->rangelow) * m_tunercache.deltaF;
m_tunercache.maxF = float(m_tuner2->rangehigh) * m_tunercache.deltaF;
m_tunercache.valid = true;
m_signalQuality = float(m_tuner2->signal) / 32767.0;
}
}
#endif
else {
logError("V4LRadio::readTunerInfo: " +
i18n("don't known how to handle V4L-version %1")
.arg(QString().setNum(m_caps.version)));
}
if (r != 0) {
m_signalQuality = 0;
logError("V4LRadio::readTunerInfo: " +
i18n("cannot get tuner info (error %1)").arg(QString().setNum(r)));
}
} else {
m_signalQuality = 0;
}
// prevent loops, if noticeXYZ-method is reading my state
m_blockReadTuner = true;
if (oldminf != m_tunercache.minF || oldmaxf != m_tunercache.maxF)
notifyDeviceMinMaxFrequencyChanged(m_tunercache.minF, m_tunercache.maxF);
m_lastMinDevFrequency = m_tunercache.minF;
m_lastMaxDevFrequency = m_tunercache.maxF;
if ( ! m_minFrequency && (oldminf != m_tunercache.minF)
|| ! m_maxFrequency && (oldmaxf != m_tunercache.maxF))
notifyMinMaxFrequencyChanged(getMinFrequency(), getMaxFrequency());
if (m_signalQuality != oldq)
notifySignalQualityChanged(m_SoundStreamID, m_signalQuality);
if ( (m_signalQuality >= m_minQuality) != (oldq >= m_minQuality))
notifySignalQualityBoolChanged(m_SoundStreamID, m_signalQuality > m_minQuality);
m_blockReadTuner = false;
return true;
}
#define V4L2_S_CTRL(what,val) \
{ ctl.value = (val); \
ctl.id = (what); \
/* Problem: Current V4L2 development has changed the IOCTL-IDs for VIDIOC_S_CTRL */ \
/* => we must du "try and error" to figure out what version we should use */ \
r = ioctl (m_radio_fd, m_useOldV4L2Calls ? VIDIOC_S_CTRL_OLD : VIDIOC_S_CTRL, &ctl); \
/* in case this did not work, try the other version of the call */ \
if (r) { \
r = ioctl (m_radio_fd, !m_useOldV4L2Calls ? VIDIOC_S_CTRL_OLD : VIDIOC_S_CTRL, &ctl); \
if (!r) m_useOldV4L2Calls = !m_useOldV4L2Calls; \
} \
x = x ? x : r; \
if (r) \
logError(i18n("error setting %1: %2").arg(#what).arg(QString().setNum(r))); \
}
#define V4L2_G_CTRL(what) \
{ ctl.id = (what); \
r = ioctl (m_radio_fd, VIDIOC_G_CTRL, &ctl); \
x = x ? x : r; \
if (r) \
logError(i18n("error reading %1: %2").arg(#what).arg(QString().setNum(r))); \
}
bool V4LRadio::updateAudioInfo(bool write) const
{
if (m_blockReadAudio && !write)
return true;
bool oldStereo = m_stereo;
bool oldMute = m_muted;
int iOldDeviceVolume = m_caps.intGetVolume (m_deviceVolume);
int iOldTreble = m_caps.intGetTreble (m_treble);
int iOldBass = m_caps.intGetBass (m_bass);
int iOldBalance = m_caps.intGetBalance(m_balance);
if (isPowerOn()) {
int r = 0;
if (m_caps.version == 1) {
m_audio->audio = 0;
if (m_muted) m_audio->flags |= VIDEO_AUDIO_MUTE;
else m_audio->flags &= ~VIDEO_AUDIO_MUTE;
m_audio->volume = m_caps.intGetVolume (m_deviceVolume);
m_audio->treble = m_caps.intGetTreble (m_treble);
m_audio->bass = m_caps.intGetBass (m_bass);
m_audio->balance = m_caps.intGetBalance(m_balance);
r = ioctl(m_radio_fd, write ? VIDIOCSAUDIO : VIDIOCGAUDIO, m_audio);
m_stereo = (r == 0) && ((m_audio->mode & VIDEO_SOUND_STEREO) != 0);
m_muted = m_caps.hasMute &&
((r != 0) || ((m_audio->flags & VIDEO_AUDIO_MUTE) != 0));
/* Some drivers seem to set volumes to zero if they are muted.
Thus we do not reload them if radio is muted */
if (!m_muted && !write) {
m_deviceVolume = m_caps.hasVolume && !r ? m_caps.floatGetVolume (m_audio->volume) : 1;
m_treble = m_caps.hasTreble && !r ? m_caps.floatGetTreble (m_audio->treble) : 1;
m_bass = m_caps.hasBass && !r ? m_caps.floatGetBass (m_audio->bass) : 1;
m_balance = m_caps.hasBalance && !r ? m_caps.floatGetBalance(m_audio->balance) : 0;
}
}
#ifdef HAVE_V4L2
else if (m_caps.version == 2) {
v4l2_control ctl;
int x = 0; // x stores first ioctl error
if (write) {
if (m_caps.hasMute)
V4L2_S_CTRL(V4L2_CID_AUDIO_MUTE, m_muted);
if (m_caps.hasTreble)
V4L2_S_CTRL(V4L2_CID_AUDIO_TREBLE, m_caps.intGetTreble(m_treble));
if (m_caps.hasBass)
V4L2_S_CTRL(V4L2_CID_AUDIO_BASS, m_caps.intGetBass(m_bass));
if (m_caps.hasBalance)
V4L2_S_CTRL(V4L2_CID_AUDIO_BALANCE, m_caps.intGetBalance(m_balance));
if (m_caps.hasVolume)
V4L2_S_CTRL(V4L2_CID_AUDIO_VOLUME, m_caps.intGetVolume(m_deviceVolume));
} else {
if (m_caps.hasMute)
V4L2_G_CTRL(V4L2_CID_AUDIO_MUTE);
m_muted = m_caps.hasMute && ((r != 0) || ctl.value);
/* Some drivers seem to set volumes to zero if they are muted.
Thus we do not reload them if radio is muted */
if (!m_muted) {
if (m_caps.hasVolume)
V4L2_G_CTRL(V4L2_CID_AUDIO_VOLUME);
m_deviceVolume = m_caps.hasVolume && !r ? m_caps.floatGetVolume (ctl.value) : 1;
if (m_caps.hasTreble)
V4L2_G_CTRL(V4L2_CID_AUDIO_TREBLE);
m_treble = m_caps.hasTreble && !r ? m_caps.floatGetTreble (ctl.value) : 1;
if (m_caps.hasBass)
V4L2_G_CTRL(V4L2_CID_AUDIO_BASS);
m_bass = m_caps.hasBass && !r ? m_caps.floatGetBass (ctl.value) : 1;
if (m_caps.hasBalance)
V4L2_G_CTRL(V4L2_CID_AUDIO_BALANCE);
m_balance = m_caps.hasBalance&& !r ? m_caps.floatGetBalance(ctl.value) : 0;
}
r = ioctl (m_radio_fd, VIDIOC_G_TUNER, m_tuner2);
m_stereo = (r == 0) && ((m_tuner2->rxsubchans & V4L2_TUNER_SUB_STEREO) != 0);
x = x ? x : r;
}
r = x; // store first error back to r, used below for error message
}
#endif
else {
logError("V4LRadio::updateAudioInfo: " +
i18n("don't known how to handle V4L-version %1")
.arg(QString().setNum(m_caps.version)));
}
if (r) {
logError("V4LRadio::updateAudioInfo: " +
i18n("error updating radio audio info (%1): %2")
.arg(write ? i18n("write") : i18n("read"))
.arg(QString().setNum(r)));
return false;
}
}
// prevent loops, if noticeXYZ-method is reading my state
bool oldBlock = m_blockReadAudio;
m_blockReadAudio = true;
// send notifications
if (oldStereo != m_stereo)
notifyStereoChanged(m_SoundStreamID, m_stereo);
if (oldMute != m_muted)
notifyMuted(m_SoundStreamID, m_muted);
if (iOldDeviceVolume != m_caps.intGetVolume(m_deviceVolume))
notifyDeviceVolumeChanged(m_deviceVolume);
if (iOldTreble != m_caps.intGetTreble(m_treble))
notifyTrebleChanged(m_SoundStreamID, m_treble);
if (iOldBass != m_caps.intGetBass(m_bass))
notifyBassChanged(m_SoundStreamID, m_bass);
if (iOldBalance != m_caps.intGetBalance(m_balance))
notifyBalanceChanged(m_SoundStreamID, m_balance);
m_blockReadAudio = oldBlock;
return isPowerOn();
}
void V4LRadio::poll()
{
readTunerInfo();
readAudioInfo();
}
bool V4LRadio::setPlaybackVolume(SoundStreamID id, float volume)
{
if (isPowerOff() && id == m_SoundStreamID) {
m_defaultPlaybackVolume = min(max(volume, 0.0), 1.0);
return true;
} else {
return false;
}
}
bool V4LRadio::getPlaybackVolume(SoundStreamID id, float &volume) const
{
if (isPowerOff() && id == m_SoundStreamID) {
volume = m_defaultPlaybackVolume;
return true;
} else {
return false;
}
}
bool V4LRadio::getSoundStreamDescription(SoundStreamID id, QString &descr) const
{
if (id == m_SoundStreamID) {
descr = name() + " - " + m_currentStation.name();
return true;
}
else {
return false;
}
}
bool V4LRadio::getSoundStreamRadioStation(SoundStreamID id, const RadioStation *&rs) const
{
if (id == m_SoundStreamID) {
rs = &m_currentStation;
return true;
}
else {
return false;
}
}
bool V4LRadio::enumerateSoundStreams(QMap<QString, SoundStreamID> &list) const
{
if (m_SoundStreamID.isValid()) {
QString tmp = QString::null;
getSoundStreamDescription(m_SoundStreamID, tmp);
list[tmp] = m_SoundStreamID;
return true;
}
return false;
}
// bool V4LRadio::stopCapture(SoundStreamID id)
// {
// if (id.isValid() && id == m_SoundStreamID && m_ActivePlayback) {
// sendStopPlayback(id);
// return true;
// }
// return false;
// }
#include "v4lradio.moc"