|
|
/***************************************************************************
|
|
|
v4lradio.cpp - description
|
|
|
-------------------
|
|
|
begin : Don M<>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 <tqlayout.h>
|
|
|
#include <tqfile.h>
|
|
|
#include <tqfileinfo.h>
|
|
|
#include <tqvaluelist.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 TQString &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(TQString()),
|
|
|
m_CaptureMixerID(TQString()),
|
|
|
m_PlaybackMixerChannel(TQString()),
|
|
|
m_CaptureMixerChannel(TQString()),
|
|
|
m_ActivePlayback(false),
|
|
|
m_MuteOnPowerOff(false),
|
|
|
m_VolumeZeroOnPowerOff(false),
|
|
|
m_restorePowerOn(false)
|
|
|
{
|
|
|
TQObject::connect (&m_pollTimer, TQT_SIGNAL(timeout()), this, TQT_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) {
|
|
|
TQPtrList<ISoundStreamClient> playback_mixers = queryPlaybackMixers();
|
|
|
if (!playback_mixers.isEmpty())
|
|
|
*playback_mixer = playback_mixers.first();
|
|
|
}
|
|
|
}
|
|
|
if (capture_mixer) {
|
|
|
*capture_mixer = getSoundStreamClientWithID(m_CaptureMixerID);
|
|
|
if (!*capture_mixer) {
|
|
|
TQPtrList<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 TQString &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(TQString().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(TQString().setNum(freq))
|
|
|
.arg(TQString().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 TQString &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 TQString &soundStreamClientID, const TQString &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 TQString &soundStreamClientID, const TQString &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(TQString 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(TQString("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(TQString("v4lradio-") + name());
|
|
|
|
|
|
TQString base_devname = "/dev/radio";
|
|
|
|
|
|
TQStringList testlist (base_devname );
|
|
|
for (int i = 0; i < 9; ++i)
|
|
|
testlist.append(base_devname + TQString::number(i));
|
|
|
|
|
|
TQString found_devname(TQString::null);
|
|
|
for (TQValueListConstIterator<TQString> it = testlist.begin(); it != testlist.end(); ++it) {
|
|
|
TQFile f(*it);
|
|
|
if (f.exists()) {
|
|
|
TQFileInfo 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));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
TQString default_devname = found_devname.isNull() ? base_devname : found_devname;
|
|
|
|
|
|
TQString 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);
|
|
|
|
|
|
TQString PlaybackMixerID = config->readEntry ("PlaybackMixerID", TQString());
|
|
|
TQString PlaybackMixerChannel = config->readEntry ("PlaybackMixerChannel", "Line");
|
|
|
|
|
|
TQString CaptureMixerID = config->readEntry ("CaptureMixerID", TQString());
|
|
|
TQString 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 TQString &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(TQString().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_TQUERYCAP, &caps2);
|
|
|
if (r == 0) {
|
|
|
c.version = 2;
|
|
|
|
|
|
logDebug(i18n("V4L2 - Version: %1").arg(TQString().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_TQUERYCTRL, &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_TQUERYCTRL, &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_TQUERYCTRL, &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_TQUERYCTRL, &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_TQUERYCTRL, &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(TQString().setNum(m_caps.version)));
|
|
|
}
|
|
|
|
|
|
if (r != 0) {
|
|
|
m_signalQuality = 0;
|
|
|
logError("V4LRadio::readTunerInfo: " +
|
|
|
i18n("cannot get tuner info (error %1)").arg(TQString().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(TQString().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(TQString().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(TQString().setNum(m_caps.version)));
|
|
|
}
|
|
|
|
|
|
if (r) {
|
|
|
logError("V4LRadio::updateAudioInfo: " +
|
|
|
i18n("error updating radio audio info (%1): %2")
|
|
|
.arg(write ? i18n("write") : i18n("read"))
|
|
|
.arg(TQString().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, TQString &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(TQMap<TQString, SoundStreamID> &list) const
|
|
|
{
|
|
|
if (m_SoundStreamID.isValid()) {
|
|
|
TQString tmp = TQString();
|
|
|
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"
|