mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
Original announce message: VDR developer version 1.7.19 is now available at ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.19.tar.bz2 A 'diff' against the previous version is available at ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.18-1.7.19.diff MD5 checksums: 1eb04ecdc2b11ab8641ebfaa2cb93f42 vdr-1.7.19.tar.bz2 db16ce7bb51e0db837ed56ef4744a67e vdr-1.7.18-1.7.19.diff WARNING: ======== This is a developer version. Even though I use it in my productive environment. I strongly recommend that you only use it under controlled conditions and for testing and debugging. This version introduces functions to determine the "signal strength" and "signal quality" through cDevice. If you are using a DVB card that contains an stb0899 frontend chip (like the TT-budget S2-3200) you may want to apply the patches from ftp://ftp.tvdr.de/vdr/Developer/Driver-Patches to the LinuxDVB driver source in order to receive useful results from that frontend. Since apparently the various frontend drivers return different maximum values in their FE_READ_SIGNAL_STRENGTH and FE_READ_SNR functions (some deliver a value in the range 0x0000...0xFFFF, while others return values as "dB/10" or "dBm/10" (the latter with an offset to make the value positive, since the parameter is unsigned), the functions cDvbTuner::GetSignalStrength() and cDvbTuner::GetSignalQuality() use the device's "subsystem ID" to map these values into the range 0...100, which is the normalized return value of these functions. Take a look at these two functions and maybe remove the comment characters from the lines //#define DEBUG_SIGNALSTRENGTH //#define DEBUG_SIGNALQUALITY in dvbdevice.c to get some debug output if your device doesn't return any directly useful values and may have to be added appropriately to the 'switch (subsystemId)' statement. The channel display of the 'sttng' skin uses these values to implement a signal strength/quality display. From the HISTORY file: - Fixed cString's operator=(const char *String) in case the given string is the same as the existing one (thanks to Dirk Leber). - Avoiding a gcc 4.6 compiler error in the skincurses plugin (thanks to Tobias Grimm). - TsGetPayload() now checks if there actually is a payload in the given TS packet (reported by Dirk Leber). - Now sorting the source file names in the call to xgettext, to make sure the results are not dependent on the sequence of the files. Plugin authors may want to change the line containing the xgettext call in their Makefile accordingly by changing "$^" to "`ls $^`". - The primary device is now only avoided for recording if it is an old SD full featured card. This is done through the new function cDevice::AvoidRecording(). - Subtitle PIDs are now also decrypted (thanks to Reinhard Nissl). - Fixed a possible race condition in cDiseqc::Execute() (reported by Marco Göbenich). The return value of cDiseqcs::Get() is now const, so plugin authors may need to adjust their code if they use this function. - The new functions cDevice::SignalStrength() and cDevice::SignalQuality() can be used to determine the signal strength and quality of a given device (thanks to Rolf Ahrenberg for some input on how to use BER and UNC values to generate a "quality" value). - The 'sttng' skin now displays two colored bars at the bottom of the channel display, indicating the strength (upper bar) and quality (lower bar) of the received signal. The number to the left of these bars indicates the actual device the current channel is being received with. - Fixed detecting frames in case the Picture Start Code or Access Unit Delimiter extends over TS packet boundaries (reported by Johan Andersson). In order to fix this, the semantics of cFrameDetector had to be changed a little. See cRecorder::Action() and cIndexFileGenerator::Action() on how to use the new cFrameDetector::NewPayload() function. - The frame detector now only starts collecting PTS values after it has seen the first I-frame, otherwise it might get MaxPtsValues values and stop analyzing even though the incoming data is still garbage (reported by Derek Kelly). - The info file of a recording is now only overwritten with a new fps value if that new value is not the default value (thanks to Derek Kelly for reporting a problem with the fps value being overwritten in case a recording was interrupted and resumed, and the fps value could not be determined after resuming recording). - The initial channel is now stored by the channel ID in the setup.conf file, in order to avoid problems in case channels are reordered or deleted (reported by Lars Bläser). - Added support for "content identifier descriptor" and "default authority descriptor" to 'libsi' (thanks to Dave Pickles).
1188 lines
39 KiB
C
1188 lines
39 KiB
C
/*
|
|
* dvbdevice.c: The DVB device tuner interface
|
|
*
|
|
* See the main source file 'vdr.c' for copyright information and
|
|
* how to reach the author.
|
|
*
|
|
* $Id: dvbdevice.c 2.42 2011/06/11 14:34:24 kls Exp $
|
|
*/
|
|
|
|
#include "dvbdevice.h"
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <linux/dvb/dmx.h>
|
|
#include <linux/dvb/frontend.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mman.h>
|
|
#include "channels.h"
|
|
#include "diseqc.h"
|
|
#include "dvbci.h"
|
|
#include "menuitems.h"
|
|
#include "sourceparams.h"
|
|
|
|
#define FE_CAN_TURBO_FEC 0x8000000 // TODO: remove this once it is defined in the driver
|
|
|
|
#define DVBS_TUNE_TIMEOUT 9000 //ms
|
|
#define DVBS_LOCK_TIMEOUT 2000 //ms
|
|
#define DVBC_TUNE_TIMEOUT 9000 //ms
|
|
#define DVBC_LOCK_TIMEOUT 2000 //ms
|
|
#define DVBT_TUNE_TIMEOUT 9000 //ms
|
|
#define DVBT_LOCK_TIMEOUT 2000 //ms
|
|
#define ATSC_TUNE_TIMEOUT 9000 //ms
|
|
#define ATSC_LOCK_TIMEOUT 2000 //ms
|
|
|
|
// --- DVB Parameter Maps ----------------------------------------------------
|
|
|
|
const tDvbParameterMap InversionValues[] = {
|
|
{ 0, INVERSION_OFF, trNOOP("off") },
|
|
{ 1, INVERSION_ON, trNOOP("on") },
|
|
{ 999, INVERSION_AUTO, trNOOP("auto") },
|
|
{ -1, 0, NULL }
|
|
};
|
|
|
|
const tDvbParameterMap BandwidthValues[] = {
|
|
{ 6, 6000000, "6 MHz" },
|
|
{ 7, 7000000, "7 MHz" },
|
|
{ 8, 8000000, "8 MHz" },
|
|
{ -1, 0, NULL }
|
|
};
|
|
|
|
const tDvbParameterMap CoderateValues[] = {
|
|
{ 0, FEC_NONE, trNOOP("none") },
|
|
{ 12, FEC_1_2, "1/2" },
|
|
{ 23, FEC_2_3, "2/3" },
|
|
{ 34, FEC_3_4, "3/4" },
|
|
{ 35, FEC_3_5, "3/5" },
|
|
{ 45, FEC_4_5, "4/5" },
|
|
{ 56, FEC_5_6, "5/6" },
|
|
{ 67, FEC_6_7, "6/7" },
|
|
{ 78, FEC_7_8, "7/8" },
|
|
{ 89, FEC_8_9, "8/9" },
|
|
{ 910, FEC_9_10, "9/10" },
|
|
{ 999, FEC_AUTO, trNOOP("auto") },
|
|
{ -1, 0, NULL }
|
|
};
|
|
|
|
const tDvbParameterMap ModulationValues[] = {
|
|
{ 16, QAM_16, "QAM16" },
|
|
{ 32, QAM_32, "QAM32" },
|
|
{ 64, QAM_64, "QAM64" },
|
|
{ 128, QAM_128, "QAM128" },
|
|
{ 256, QAM_256, "QAM256" },
|
|
{ 2, QPSK, "QPSK" },
|
|
{ 5, PSK_8, "8PSK" },
|
|
{ 6, APSK_16, "16APSK" },
|
|
{ 10, VSB_8, "VSB8" },
|
|
{ 11, VSB_16, "VSB16" },
|
|
{ 998, QAM_AUTO, "QAMAUTO" },
|
|
{ -1, 0, NULL }
|
|
};
|
|
|
|
const tDvbParameterMap SystemValues[] = {
|
|
{ 0, SYS_DVBS, "DVB-S" },
|
|
{ 1, SYS_DVBS2, "DVB-S2" },
|
|
{ -1, 0, NULL }
|
|
};
|
|
|
|
const tDvbParameterMap TransmissionValues[] = {
|
|
{ 2, TRANSMISSION_MODE_2K, "2K" },
|
|
{ 8, TRANSMISSION_MODE_8K, "8K" },
|
|
{ 999, TRANSMISSION_MODE_AUTO, trNOOP("auto") },
|
|
{ -1, 0, NULL }
|
|
};
|
|
|
|
const tDvbParameterMap GuardValues[] = {
|
|
{ 4, GUARD_INTERVAL_1_4, "1/4" },
|
|
{ 8, GUARD_INTERVAL_1_8, "1/8" },
|
|
{ 16, GUARD_INTERVAL_1_16, "1/16" },
|
|
{ 32, GUARD_INTERVAL_1_32, "1/32" },
|
|
{ 999, GUARD_INTERVAL_AUTO, trNOOP("auto") },
|
|
{ -1, 0, NULL }
|
|
};
|
|
|
|
const tDvbParameterMap HierarchyValues[] = {
|
|
{ 0, HIERARCHY_NONE, trNOOP("none") },
|
|
{ 1, HIERARCHY_1, "1" },
|
|
{ 2, HIERARCHY_2, "2" },
|
|
{ 4, HIERARCHY_4, "4" },
|
|
{ 999, HIERARCHY_AUTO, trNOOP("auto") },
|
|
{ -1, 0, NULL }
|
|
};
|
|
|
|
const tDvbParameterMap RollOffValues[] = {
|
|
{ 0, ROLLOFF_AUTO, trNOOP("auto") },
|
|
{ 20, ROLLOFF_20, "0.20" },
|
|
{ 25, ROLLOFF_25, "0.25" },
|
|
{ 35, ROLLOFF_35, "0.35" },
|
|
{ -1, 0, NULL }
|
|
};
|
|
|
|
int UserIndex(int Value, const tDvbParameterMap *Map)
|
|
{
|
|
const tDvbParameterMap *map = Map;
|
|
while (map && map->userValue != -1) {
|
|
if (map->userValue == Value)
|
|
return map - Map;
|
|
map++;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int DriverIndex(int Value, const tDvbParameterMap *Map)
|
|
{
|
|
const tDvbParameterMap *map = Map;
|
|
while (map && map->userValue != -1) {
|
|
if (map->driverValue == Value)
|
|
return map - Map;
|
|
map++;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int MapToUser(int Value, const tDvbParameterMap *Map, const char **String)
|
|
{
|
|
int n = DriverIndex(Value, Map);
|
|
if (n >= 0) {
|
|
if (String)
|
|
*String = tr(Map[n].userString);
|
|
return Map[n].userValue;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
const char *MapToUserString(int Value, const tDvbParameterMap *Map)
|
|
{
|
|
int n = DriverIndex(Value, Map);
|
|
if (n >= 0)
|
|
return Map[n].userString;
|
|
return "???";
|
|
}
|
|
|
|
int MapToDriver(int Value, const tDvbParameterMap *Map)
|
|
{
|
|
int n = UserIndex(Value, Map);
|
|
if (n >= 0)
|
|
return Map[n].driverValue;
|
|
return -1;
|
|
}
|
|
|
|
// --- cDvbTransponderParameters ---------------------------------------------
|
|
|
|
cDvbTransponderParameters::cDvbTransponderParameters(const char *Parameters)
|
|
{
|
|
polarization = 0;
|
|
inversion = INVERSION_AUTO;
|
|
bandwidth = 8000000;
|
|
coderateH = FEC_AUTO;
|
|
coderateL = FEC_AUTO;
|
|
modulation = QPSK;
|
|
system = SYS_DVBS;
|
|
transmission = TRANSMISSION_MODE_AUTO;
|
|
guard = GUARD_INTERVAL_AUTO;
|
|
hierarchy = HIERARCHY_AUTO;
|
|
rollOff = ROLLOFF_AUTO;
|
|
Parse(Parameters);
|
|
}
|
|
|
|
int cDvbTransponderParameters::PrintParameter(char *p, char Name, int Value) const
|
|
{
|
|
return Value >= 0 && Value != 999 ? sprintf(p, "%c%d", Name, Value) : 0;
|
|
}
|
|
|
|
cString cDvbTransponderParameters::ToString(char Type) const
|
|
{
|
|
#define ST(s) if (strchr(s, Type))
|
|
char buffer[64];
|
|
char *q = buffer;
|
|
*q = 0;
|
|
ST(" S ") q += sprintf(q, "%c", polarization);
|
|
ST(" T") q += PrintParameter(q, 'B', MapToUser(bandwidth, BandwidthValues));
|
|
ST(" CST") q += PrintParameter(q, 'C', MapToUser(coderateH, CoderateValues));
|
|
ST(" T") q += PrintParameter(q, 'D', MapToUser(coderateL, CoderateValues));
|
|
ST(" T") q += PrintParameter(q, 'G', MapToUser(guard, GuardValues));
|
|
ST("ACST") q += PrintParameter(q, 'I', MapToUser(inversion, InversionValues));
|
|
ST("ACST") q += PrintParameter(q, 'M', MapToUser(modulation, ModulationValues));
|
|
ST(" S ") q += PrintParameter(q, 'O', MapToUser(rollOff, RollOffValues));
|
|
ST(" S ") q += PrintParameter(q, 'S', MapToUser(system, SystemValues));
|
|
ST(" T") q += PrintParameter(q, 'T', MapToUser(transmission, TransmissionValues));
|
|
ST(" T") q += PrintParameter(q, 'Y', MapToUser(hierarchy, HierarchyValues));
|
|
return buffer;
|
|
}
|
|
|
|
const char *cDvbTransponderParameters::ParseParameter(const char *s, int &Value, const tDvbParameterMap *Map)
|
|
{
|
|
if (*++s) {
|
|
char *p = NULL;
|
|
errno = 0;
|
|
int n = strtol(s, &p, 10);
|
|
if (!errno && p != s) {
|
|
Value = MapToDriver(n, Map);
|
|
if (Value >= 0)
|
|
return p;
|
|
}
|
|
}
|
|
esyslog("ERROR: invalid value for parameter '%c'", *(s - 1));
|
|
return NULL;
|
|
}
|
|
|
|
bool cDvbTransponderParameters::Parse(const char *s)
|
|
{
|
|
while (s && *s) {
|
|
switch (toupper(*s)) {
|
|
case 'B': s = ParseParameter(s, bandwidth, BandwidthValues); break;
|
|
case 'C': s = ParseParameter(s, coderateH, CoderateValues); break;
|
|
case 'D': s = ParseParameter(s, coderateL, CoderateValues); break;
|
|
case 'G': s = ParseParameter(s, guard, GuardValues); break;
|
|
case 'H': polarization = *s++; break;
|
|
case 'I': s = ParseParameter(s, inversion, InversionValues); break;
|
|
case 'L': polarization = *s++; break;
|
|
case 'M': s = ParseParameter(s, modulation, ModulationValues); break;
|
|
case 'O': s = ParseParameter(s, rollOff, RollOffValues); break;
|
|
case 'R': polarization = *s++; break;
|
|
case 'S': s = ParseParameter(s, system, SystemValues); break;
|
|
case 'T': s = ParseParameter(s, transmission, TransmissionValues); break;
|
|
case 'V': polarization = *s++; break;
|
|
case 'Y': s = ParseParameter(s, hierarchy, HierarchyValues); break;
|
|
default: esyslog("ERROR: unknown parameter key '%c'", *s);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// --- cDvbTuner -------------------------------------------------------------
|
|
|
|
#define TUNER_POLL_TIMEOUT 10 // ms
|
|
|
|
class cDvbTuner : public cThread {
|
|
private:
|
|
enum eTunerStatus { tsIdle, tsSet, tsTuned, tsLocked };
|
|
int device;
|
|
int fd_frontend;
|
|
int adapter, frontend;
|
|
uint32_t subsystemId;
|
|
int tuneTimeout;
|
|
int lockTimeout;
|
|
time_t lastTimeoutReport;
|
|
fe_delivery_system frontendType;
|
|
cChannel channel;
|
|
const char *diseqcCommands;
|
|
eTunerStatus tunerStatus;
|
|
cMutex mutex;
|
|
cCondVar locked;
|
|
cCondVar newSet;
|
|
void ClearEventQueue(void) const;
|
|
bool GetFrontendStatus(fe_status_t &Status) const;
|
|
bool SetFrontend(void);
|
|
virtual void Action(void);
|
|
public:
|
|
cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType);
|
|
virtual ~cDvbTuner();
|
|
const cChannel *GetTransponder(void) const { return &channel; }
|
|
uint32_t SubsystemId(void) const { return subsystemId; }
|
|
bool IsTunedTo(const cChannel *Channel) const;
|
|
void Set(const cChannel *Channel);
|
|
bool Locked(int TimeoutMs = 0);
|
|
int GetSignalStrength(void) const;
|
|
int GetSignalQuality(void) const;
|
|
};
|
|
|
|
cDvbTuner::cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType)
|
|
{
|
|
device = Device;
|
|
fd_frontend = Fd_Frontend;
|
|
adapter = Adapter;
|
|
frontend = Frontend;
|
|
frontendType = FrontendType;
|
|
subsystemId = cDvbDeviceProbe::GetSubsystemId(adapter, frontend);
|
|
tuneTimeout = 0;
|
|
lockTimeout = 0;
|
|
lastTimeoutReport = 0;
|
|
diseqcCommands = NULL;
|
|
tunerStatus = tsIdle;
|
|
if (frontendType == SYS_DVBS || frontendType == SYS_DVBS2)
|
|
CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); // must explicitly turn on LNB power
|
|
SetDescription("tuner on frontend %d/%d", adapter, frontend);
|
|
Start();
|
|
}
|
|
|
|
cDvbTuner::~cDvbTuner()
|
|
{
|
|
tunerStatus = tsIdle;
|
|
newSet.Broadcast();
|
|
locked.Broadcast();
|
|
Cancel(3);
|
|
}
|
|
|
|
bool cDvbTuner::IsTunedTo(const cChannel *Channel) const
|
|
{
|
|
if (tunerStatus == tsIdle)
|
|
return false; // not tuned to
|
|
if (channel.Source() != Channel->Source() || channel.Transponder() != Channel->Transponder())
|
|
return false; // sufficient mismatch
|
|
// Polarization is already checked as part of the Transponder.
|
|
return strcmp(channel.Parameters(), Channel->Parameters()) == 0;
|
|
}
|
|
|
|
void cDvbTuner::Set(const cChannel *Channel)
|
|
{
|
|
cMutexLock MutexLock(&mutex);
|
|
if (!IsTunedTo(Channel))
|
|
tunerStatus = tsSet;
|
|
channel = *Channel;
|
|
lastTimeoutReport = 0;
|
|
newSet.Broadcast();
|
|
}
|
|
|
|
bool cDvbTuner::Locked(int TimeoutMs)
|
|
{
|
|
bool isLocked = (tunerStatus >= tsLocked);
|
|
if (isLocked || !TimeoutMs)
|
|
return isLocked;
|
|
|
|
cMutexLock MutexLock(&mutex);
|
|
if (TimeoutMs && tunerStatus < tsLocked)
|
|
locked.TimedWait(mutex, TimeoutMs);
|
|
return tunerStatus >= tsLocked;
|
|
}
|
|
|
|
void cDvbTuner::ClearEventQueue(void) const
|
|
{
|
|
cPoller Poller(fd_frontend);
|
|
if (Poller.Poll(TUNER_POLL_TIMEOUT)) {
|
|
dvb_frontend_event Event;
|
|
while (ioctl(fd_frontend, FE_GET_EVENT, &Event) == 0)
|
|
; // just to clear the event queue - we'll read the actual status below
|
|
}
|
|
}
|
|
|
|
bool cDvbTuner::GetFrontendStatus(fe_status_t &Status) const
|
|
{
|
|
ClearEventQueue();
|
|
while (1) {
|
|
if (ioctl(fd_frontend, FE_READ_STATUS, &Status) != -1)
|
|
return true;
|
|
if (errno != EINTR)
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//#define DEBUG_SIGNALSTRENGTH
|
|
//#define DEBUG_SIGNALQUALITY
|
|
|
|
int cDvbTuner::GetSignalStrength(void) const
|
|
{
|
|
ClearEventQueue();
|
|
uint16_t Signal;
|
|
while (1) {
|
|
if (ioctl(fd_frontend, FE_READ_SIGNAL_STRENGTH, &Signal) != -1)
|
|
break;
|
|
if (errno != EINTR)
|
|
return -1;
|
|
}
|
|
uint16_t MaxSignal = 0xFFFF; // Let's assume the default is using the entire range.
|
|
// Use the subsystemId to identify individual devices in case they need
|
|
// special treatment to map their Signal value into the range 0...0xFFFF.
|
|
switch (subsystemId) {
|
|
case 0x13C21019: MaxSignal = 670; break; // TT-budget S2-3200 (DVB-S/DVB-S2)
|
|
}
|
|
int s = int(Signal) * 100 / MaxSignal;
|
|
if (s > 100)
|
|
s = 100;
|
|
#ifdef DEBUG_SIGNALSTRENGTH
|
|
fprintf(stderr, "FE %d/%d: %08X S = %04X %04X %3d%%\n", adapter, frontend, subsystemId, MaxSignal, Signal, s);
|
|
#endif
|
|
return s;
|
|
}
|
|
|
|
#define LOCK_THRESHOLD 5 // indicates that all 5 FE_HAS_* flags are set
|
|
|
|
int cDvbTuner::GetSignalQuality(void) const
|
|
{
|
|
fe_status_t Status;
|
|
if (GetFrontendStatus(Status)) {
|
|
// Actually one would expect these checks to be done from FE_HAS_SIGNAL to FE_HAS_LOCK, but some drivers (like the stb0899) are broken, so FE_HAS_LOCK is the only one that (hopefully) is generally reliable...
|
|
if ((Status & FE_HAS_LOCK) == 0) {
|
|
if ((Status & FE_HAS_SIGNAL) == 0)
|
|
return 0;
|
|
if ((Status & FE_HAS_CARRIER) == 0)
|
|
return 1;
|
|
if ((Status & FE_HAS_VITERBI) == 0)
|
|
return 2;
|
|
if ((Status & FE_HAS_SYNC) == 0)
|
|
return 3;
|
|
return 4;
|
|
}
|
|
bool HasSnr = true;
|
|
uint16_t Snr;
|
|
while (1) {
|
|
if (ioctl(fd_frontend, FE_READ_SNR, &Snr) != -1)
|
|
break;
|
|
if (errno == EOPNOTSUPP) {
|
|
Snr = 0xFFFF;
|
|
HasSnr = false;
|
|
break;
|
|
}
|
|
if (errno != EINTR)
|
|
return -1;
|
|
}
|
|
bool HasBer = true;
|
|
uint32_t Ber;
|
|
while (1) {
|
|
if (ioctl(fd_frontend, FE_READ_BER, &Ber) != -1)
|
|
break;
|
|
if (errno == EOPNOTSUPP) {
|
|
Ber = 0;
|
|
HasBer = false;
|
|
break;
|
|
}
|
|
if (errno != EINTR)
|
|
return -1;
|
|
}
|
|
bool HasUnc = true;
|
|
uint32_t Unc;
|
|
while (1) {
|
|
if (ioctl(fd_frontend, FE_READ_UNCORRECTED_BLOCKS, &Unc) != -1)
|
|
break;
|
|
if (errno == EOPNOTSUPP) {
|
|
Unc = 0;
|
|
HasUnc = false;
|
|
break;
|
|
}
|
|
if (errno != EINTR)
|
|
return -1;
|
|
}
|
|
uint16_t MaxSnr = 0xFFFF; // Let's assume the default is using the entire range.
|
|
// Use the subsystemId to identify individual devices in case they need
|
|
// special treatment to map their Snr value into the range 0...0xFFFF.
|
|
switch (subsystemId) {
|
|
case 0x13C21019: MaxSnr = 200; break; // TT-budget S2-3200 (DVB-S/DVB-S2)
|
|
}
|
|
int a = int(Snr) * 100 / MaxSnr;
|
|
int b = 100 - (Unc * 10 + (Ber / 256) * 5);
|
|
if (b < 0)
|
|
b = 0;
|
|
int q = LOCK_THRESHOLD + a * b * (100 - LOCK_THRESHOLD) / 100 / 100;
|
|
if (q > 100)
|
|
q = 100;
|
|
#ifdef DEBUG_SIGNALQUALITY
|
|
fprintf(stderr, "FE %d/%d: %08X Q = %04X %04X %5d %5d %3d%%\n", adapter, frontend, subsystemId, MaxSnr, Snr, HasBer ? int(Ber) : -1, HasUnc ? int(Unc) : -1, q);
|
|
#endif
|
|
return q;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static unsigned int FrequencyToHz(unsigned int f)
|
|
{
|
|
while (f && f < 1000000)
|
|
f *= 1000;
|
|
return f;
|
|
}
|
|
|
|
bool cDvbTuner::SetFrontend(void)
|
|
{
|
|
#define MAXFRONTENDCMDS 16
|
|
#define SETCMD(c, d) { Frontend[CmdSeq.num].cmd = (c);\
|
|
Frontend[CmdSeq.num].u.data = (d);\
|
|
if (CmdSeq.num++ > MAXFRONTENDCMDS) {\
|
|
esyslog("ERROR: too many tuning commands on frontend %d/%d", adapter, frontend);\
|
|
return false;\
|
|
}\
|
|
}
|
|
dtv_property Frontend[MAXFRONTENDCMDS];
|
|
memset(&Frontend, 0, sizeof(Frontend));
|
|
dtv_properties CmdSeq;
|
|
memset(&CmdSeq, 0, sizeof(CmdSeq));
|
|
CmdSeq.props = Frontend;
|
|
SETCMD(DTV_CLEAR, 0);
|
|
if (ioctl(fd_frontend, FE_SET_PROPERTY, &CmdSeq) < 0) {
|
|
esyslog("ERROR: frontend %d/%d: %m", adapter, frontend);
|
|
return false;
|
|
}
|
|
CmdSeq.num = 0;
|
|
|
|
cDvbTransponderParameters dtp(channel.Parameters());
|
|
|
|
if (frontendType == SYS_DVBS || frontendType == SYS_DVBS2) {
|
|
unsigned int frequency = channel.Frequency();
|
|
if (Setup.DiSEqC) {
|
|
const cDiseqc *diseqc = Diseqcs.Get(device, channel.Source(), channel.Frequency(), dtp.Polarization());
|
|
if (diseqc) {
|
|
if (diseqc->Commands() && (!diseqcCommands || strcmp(diseqcCommands, diseqc->Commands()) != 0)) {
|
|
cDiseqc::eDiseqcActions da;
|
|
for (const char *CurrentAction = NULL; (da = diseqc->Execute(&CurrentAction)) != cDiseqc::daNone; ) {
|
|
switch (da) {
|
|
case cDiseqc::daNone: break;
|
|
case cDiseqc::daToneOff: CHECK(ioctl(fd_frontend, FE_SET_TONE, SEC_TONE_OFF)); break;
|
|
case cDiseqc::daToneOn: CHECK(ioctl(fd_frontend, FE_SET_TONE, SEC_TONE_ON)); break;
|
|
case cDiseqc::daVoltage13: CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); break;
|
|
case cDiseqc::daVoltage18: CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_18)); break;
|
|
case cDiseqc::daMiniA: CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_BURST, SEC_MINI_A)); break;
|
|
case cDiseqc::daMiniB: CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_BURST, SEC_MINI_B)); break;
|
|
case cDiseqc::daCodes: {
|
|
int n = 0;
|
|
const uchar *codes = diseqc->Codes(n);
|
|
if (codes) {
|
|
struct dvb_diseqc_master_cmd cmd;
|
|
cmd.msg_len = min(n, int(sizeof(cmd.msg)));
|
|
memcpy(cmd.msg, codes, cmd.msg_len);
|
|
CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_MASTER_CMD, &cmd));
|
|
}
|
|
}
|
|
break;
|
|
default: esyslog("ERROR: unknown diseqc command %d", da);
|
|
}
|
|
}
|
|
diseqcCommands = diseqc->Commands();
|
|
}
|
|
frequency -= diseqc->Lof();
|
|
}
|
|
else {
|
|
esyslog("ERROR: no DiSEqC parameters found for channel %d", channel.Number());
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
int tone = SEC_TONE_OFF;
|
|
if (frequency < (unsigned int)Setup.LnbSLOF) {
|
|
frequency -= Setup.LnbFrequLo;
|
|
tone = SEC_TONE_OFF;
|
|
}
|
|
else {
|
|
frequency -= Setup.LnbFrequHi;
|
|
tone = SEC_TONE_ON;
|
|
}
|
|
int volt = (dtp.Polarization() == 'v' || dtp.Polarization() == 'V' || dtp.Polarization() == 'r' || dtp.Polarization() == 'R') ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18;
|
|
CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, volt));
|
|
CHECK(ioctl(fd_frontend, FE_SET_TONE, tone));
|
|
}
|
|
frequency = abs(frequency); // Allow for C-band, where the frequency is less than the LOF
|
|
|
|
// DVB-S/DVB-S2 (common parts)
|
|
SETCMD(DTV_DELIVERY_SYSTEM, dtp.System());
|
|
SETCMD(DTV_FREQUENCY, frequency * 1000UL);
|
|
SETCMD(DTV_MODULATION, dtp.Modulation());
|
|
SETCMD(DTV_SYMBOL_RATE, channel.Srate() * 1000UL);
|
|
SETCMD(DTV_INNER_FEC, dtp.CoderateH());
|
|
SETCMD(DTV_INVERSION, dtp.Inversion());
|
|
if (dtp.System() == SYS_DVBS2) {
|
|
if (frontendType == SYS_DVBS2) {
|
|
// DVB-S2
|
|
SETCMD(DTV_PILOT, PILOT_AUTO);
|
|
SETCMD(DTV_ROLLOFF, dtp.RollOff());
|
|
}
|
|
else {
|
|
esyslog("ERROR: frontend %d/%d doesn't provide DVB-S2", adapter, frontend);
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
// DVB-S
|
|
SETCMD(DTV_ROLLOFF, ROLLOFF_35); // DVB-S always has a ROLLOFF of 0.35
|
|
}
|
|
|
|
tuneTimeout = DVBS_TUNE_TIMEOUT;
|
|
lockTimeout = DVBS_LOCK_TIMEOUT;
|
|
}
|
|
else if (frontendType == SYS_DVBC_ANNEX_AC || frontendType == SYS_DVBC_ANNEX_B) {
|
|
// DVB-C
|
|
SETCMD(DTV_DELIVERY_SYSTEM, frontendType);
|
|
SETCMD(DTV_FREQUENCY, FrequencyToHz(channel.Frequency()));
|
|
SETCMD(DTV_INVERSION, dtp.Inversion());
|
|
SETCMD(DTV_SYMBOL_RATE, channel.Srate() * 1000UL);
|
|
SETCMD(DTV_INNER_FEC, dtp.CoderateH());
|
|
SETCMD(DTV_MODULATION, dtp.Modulation());
|
|
|
|
tuneTimeout = DVBC_TUNE_TIMEOUT;
|
|
lockTimeout = DVBC_LOCK_TIMEOUT;
|
|
}
|
|
else if (frontendType == SYS_DVBT) {
|
|
// DVB-T
|
|
SETCMD(DTV_DELIVERY_SYSTEM, frontendType);
|
|
SETCMD(DTV_FREQUENCY, FrequencyToHz(channel.Frequency()));
|
|
SETCMD(DTV_INVERSION, dtp.Inversion());
|
|
SETCMD(DTV_BANDWIDTH_HZ, dtp.Bandwidth());
|
|
SETCMD(DTV_CODE_RATE_HP, dtp.CoderateH());
|
|
SETCMD(DTV_CODE_RATE_LP, dtp.CoderateL());
|
|
SETCMD(DTV_MODULATION, dtp.Modulation());
|
|
SETCMD(DTV_TRANSMISSION_MODE, dtp.Transmission());
|
|
SETCMD(DTV_GUARD_INTERVAL, dtp.Guard());
|
|
SETCMD(DTV_HIERARCHY, dtp.Hierarchy());
|
|
|
|
tuneTimeout = DVBT_TUNE_TIMEOUT;
|
|
lockTimeout = DVBT_LOCK_TIMEOUT;
|
|
}
|
|
else if (frontendType == SYS_ATSC) {
|
|
// ATSC
|
|
SETCMD(DTV_DELIVERY_SYSTEM, frontendType);
|
|
SETCMD(DTV_FREQUENCY, FrequencyToHz(channel.Frequency()));
|
|
SETCMD(DTV_INVERSION, dtp.Inversion());
|
|
SETCMD(DTV_MODULATION, dtp.Modulation());
|
|
|
|
tuneTimeout = ATSC_TUNE_TIMEOUT;
|
|
lockTimeout = ATSC_LOCK_TIMEOUT;
|
|
}
|
|
else {
|
|
esyslog("ERROR: attempt to set channel with unknown DVB frontend type");
|
|
return false;
|
|
}
|
|
SETCMD(DTV_TUNE, 0);
|
|
if (ioctl(fd_frontend, FE_SET_PROPERTY, &CmdSeq) < 0) {
|
|
esyslog("ERROR: frontend %d/%d: %m", adapter, frontend);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void cDvbTuner::Action(void)
|
|
{
|
|
cTimeMs Timer;
|
|
bool LostLock = false;
|
|
fe_status_t Status = (fe_status_t)0;
|
|
while (Running()) {
|
|
fe_status_t NewStatus;
|
|
if (GetFrontendStatus(NewStatus))
|
|
Status = NewStatus;
|
|
cMutexLock MutexLock(&mutex);
|
|
switch (tunerStatus) {
|
|
case tsIdle:
|
|
break;
|
|
case tsSet:
|
|
tunerStatus = SetFrontend() ? tsTuned : tsIdle;
|
|
Timer.Set(tuneTimeout);
|
|
continue;
|
|
case tsTuned:
|
|
if (Timer.TimedOut()) {
|
|
tunerStatus = tsSet;
|
|
diseqcCommands = NULL;
|
|
if (time(NULL) - lastTimeoutReport > 60) { // let's not get too many of these
|
|
isyslog("frontend %d/%d timed out while tuning to channel %d, tp %d", adapter, frontend, channel.Number(), channel.Transponder());
|
|
lastTimeoutReport = time(NULL);
|
|
}
|
|
continue;
|
|
}
|
|
case tsLocked:
|
|
if (Status & FE_REINIT) {
|
|
tunerStatus = tsSet;
|
|
diseqcCommands = NULL;
|
|
isyslog("frontend %d/%d was reinitialized", adapter, frontend);
|
|
lastTimeoutReport = 0;
|
|
continue;
|
|
}
|
|
else if (Status & FE_HAS_LOCK) {
|
|
if (LostLock) {
|
|
isyslog("frontend %d/%d regained lock on channel %d, tp %d", adapter, frontend, channel.Number(), channel.Transponder());
|
|
LostLock = false;
|
|
}
|
|
tunerStatus = tsLocked;
|
|
locked.Broadcast();
|
|
lastTimeoutReport = 0;
|
|
}
|
|
else if (tunerStatus == tsLocked) {
|
|
LostLock = true;
|
|
isyslog("frontend %d/%d lost lock on channel %d, tp %d", adapter, frontend, channel.Number(), channel.Transponder());
|
|
tunerStatus = tsTuned;
|
|
Timer.Set(lockTimeout);
|
|
lastTimeoutReport = 0;
|
|
continue;
|
|
}
|
|
break;
|
|
default: esyslog("ERROR: unknown tuner status %d", tunerStatus);
|
|
}
|
|
|
|
if (tunerStatus != tsTuned)
|
|
newSet.TimedWait(mutex, 1000);
|
|
}
|
|
}
|
|
|
|
// --- cDvbSourceParam -------------------------------------------------------
|
|
|
|
class cDvbSourceParam : public cSourceParam {
|
|
private:
|
|
int param;
|
|
int srate;
|
|
cDvbTransponderParameters dtp;
|
|
public:
|
|
cDvbSourceParam(char Source, const char *Description);
|
|
virtual void SetData(cChannel *Channel);
|
|
virtual void GetData(cChannel *Channel);
|
|
virtual cOsdItem *GetOsdItem(void);
|
|
};
|
|
|
|
cDvbSourceParam::cDvbSourceParam(char Source, const char *Description)
|
|
:cSourceParam(Source, Description)
|
|
{
|
|
param = 0;
|
|
srate = 0;
|
|
}
|
|
|
|
void cDvbSourceParam::SetData(cChannel *Channel)
|
|
{
|
|
srate = Channel->Srate();
|
|
dtp.Parse(Channel->Parameters());
|
|
param = 0;
|
|
}
|
|
|
|
void cDvbSourceParam::GetData(cChannel *Channel)
|
|
{
|
|
Channel->SetTransponderData(Channel->Source(), Channel->Frequency(), srate, dtp.ToString(Source()), true);
|
|
}
|
|
|
|
cOsdItem *cDvbSourceParam::GetOsdItem(void)
|
|
{
|
|
char type = Source();
|
|
#undef ST
|
|
#define ST(s) if (strchr(s, type))
|
|
switch (param++) {
|
|
case 0: ST(" S ") return new cMenuEditChrItem( tr("Polarization"), &dtp.polarization, "HVLR"); else return GetOsdItem();
|
|
case 1: ST(" S ") return new cMenuEditMapItem( tr("System"), &dtp.system, SystemValues); else return GetOsdItem();
|
|
case 2: ST(" CS ") return new cMenuEditIntItem( tr("Srate"), &srate); else return GetOsdItem();
|
|
case 3: ST("ACST") return new cMenuEditMapItem( tr("Inversion"), &dtp.inversion, InversionValues); else return GetOsdItem();
|
|
case 4: ST(" CST") return new cMenuEditMapItem( tr("CoderateH"), &dtp.coderateH, CoderateValues); else return GetOsdItem();
|
|
case 5: ST(" T") return new cMenuEditMapItem( tr("CoderateL"), &dtp.coderateL, CoderateValues); else return GetOsdItem();
|
|
case 6: ST("ACST") return new cMenuEditMapItem( tr("Modulation"), &dtp.modulation, ModulationValues); else return GetOsdItem();
|
|
case 7: ST(" T") return new cMenuEditMapItem( tr("Bandwidth"), &dtp.bandwidth, BandwidthValues); else return GetOsdItem();
|
|
case 8: ST(" T") return new cMenuEditMapItem( tr("Transmission"), &dtp.transmission, TransmissionValues); else return GetOsdItem();
|
|
case 9: ST(" T") return new cMenuEditMapItem( tr("Guard"), &dtp.guard, GuardValues); else return GetOsdItem();
|
|
case 10: ST(" T") return new cMenuEditMapItem( tr("Hierarchy"), &dtp.hierarchy, HierarchyValues); else return GetOsdItem();
|
|
case 11: ST(" S ") return new cMenuEditMapItem( tr("Rolloff"), &dtp.rollOff, RollOffValues); else return GetOsdItem();
|
|
default: return NULL;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// --- cDvbDevice ------------------------------------------------------------
|
|
|
|
int cDvbDevice::setTransferModeForDolbyDigital = 1;
|
|
|
|
const char *DeliverySystems[] = {
|
|
"UNDEFINED",
|
|
"DVB-C",
|
|
"DVB-C",
|
|
"DVB-T",
|
|
"DSS",
|
|
"DVB-S",
|
|
"DVB-S2",
|
|
"DVB-H",
|
|
"ISDBT",
|
|
"ISDBS",
|
|
"ISDBC",
|
|
"ATSC",
|
|
"ATSCMH",
|
|
"DMBTH",
|
|
"CMMB",
|
|
"DAB",
|
|
NULL
|
|
};
|
|
|
|
cDvbDevice::cDvbDevice(int Adapter, int Frontend)
|
|
{
|
|
adapter = Adapter;
|
|
frontend = Frontend;
|
|
ciAdapter = NULL;
|
|
dvbTuner = NULL;
|
|
frontendType = SYS_UNDEFINED;
|
|
numProvidedSystems = 0;
|
|
|
|
// Devices that are present on all card types:
|
|
|
|
int fd_frontend = DvbOpen(DEV_DVB_FRONTEND, adapter, frontend, O_RDWR | O_NONBLOCK);
|
|
|
|
// Common Interface:
|
|
|
|
fd_ca = DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
|
|
if (fd_ca >= 0)
|
|
ciAdapter = cDvbCiAdapter::CreateCiAdapter(this, fd_ca);
|
|
|
|
// The DVR device (will be opened and closed as needed):
|
|
|
|
fd_dvr = -1;
|
|
|
|
// We only check the devices that must be present - the others will be checked before accessing them://XXX
|
|
|
|
if (fd_frontend >= 0) {
|
|
if (ioctl(fd_frontend, FE_GET_INFO, &frontendInfo) >= 0) {
|
|
switch (frontendInfo.type) {
|
|
case FE_QPSK: frontendType = (frontendInfo.caps & FE_CAN_2G_MODULATION) ? SYS_DVBS2 : SYS_DVBS; break;
|
|
case FE_OFDM: frontendType = SYS_DVBT; break;
|
|
case FE_QAM: frontendType = SYS_DVBC_ANNEX_AC; break;
|
|
case FE_ATSC: frontendType = SYS_ATSC; break;
|
|
default: esyslog("ERROR: unknown frontend type %d on frontend %d/%d", frontendInfo.type, adapter, frontend);
|
|
}
|
|
}
|
|
else
|
|
LOG_ERROR;
|
|
if (frontendType != SYS_UNDEFINED) {
|
|
numProvidedSystems++;
|
|
if (frontendType == SYS_DVBS2)
|
|
numProvidedSystems++;
|
|
char Modulations[64];
|
|
char *p = Modulations;
|
|
if (frontendInfo.caps & FE_CAN_QPSK) { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QPSK, ModulationValues)); }
|
|
if (frontendInfo.caps & FE_CAN_QAM_16) { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QAM_16, ModulationValues)); }
|
|
if (frontendInfo.caps & FE_CAN_QAM_32) { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QAM_32, ModulationValues)); }
|
|
if (frontendInfo.caps & FE_CAN_QAM_64) { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QAM_64, ModulationValues)); }
|
|
if (frontendInfo.caps & FE_CAN_QAM_128) { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QAM_128, ModulationValues)); }
|
|
if (frontendInfo.caps & FE_CAN_QAM_256) { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QAM_256, ModulationValues)); }
|
|
if (frontendInfo.caps & FE_CAN_8VSB) { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(VSB_8, ModulationValues)); }
|
|
if (frontendInfo.caps & FE_CAN_16VSB) { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(VSB_16, ModulationValues)); }
|
|
if (frontendInfo.caps & FE_CAN_TURBO_FEC){numProvidedSystems++; p += sprintf(p, ",%s", "TURBO_FEC"); }
|
|
if (p != Modulations)
|
|
p = Modulations + 1; // skips first ','
|
|
else
|
|
p = (char *)"unknown modulations";
|
|
isyslog("frontend %d/%d provides %s with %s (\"%s\")", adapter, frontend, DeliverySystems[frontendType], p, frontendInfo.name);
|
|
dvbTuner = new cDvbTuner(CardIndex() + 1, fd_frontend, adapter, frontend, frontendType);
|
|
}
|
|
}
|
|
else
|
|
esyslog("ERROR: can't open DVB device %d/%d", adapter, frontend);
|
|
|
|
StartSectionHandler();
|
|
}
|
|
|
|
cDvbDevice::~cDvbDevice()
|
|
{
|
|
StopSectionHandler();
|
|
delete dvbTuner;
|
|
delete ciAdapter;
|
|
// We're not explicitly closing any device files here, since this sometimes
|
|
// caused segfaults. Besides, the program is about to terminate anyway...
|
|
}
|
|
|
|
cString cDvbDevice::DvbName(const char *Name, int Adapter, int Frontend)
|
|
{
|
|
return cString::sprintf("%s%d/%s%d", DEV_DVB_ADAPTER, Adapter, Name, Frontend);
|
|
}
|
|
|
|
int cDvbDevice::DvbOpen(const char *Name, int Adapter, int Frontend, int Mode, bool ReportError)
|
|
{
|
|
cString FileName = DvbName(Name, Adapter, Frontend);
|
|
int fd = open(FileName, Mode);
|
|
if (fd < 0 && ReportError)
|
|
LOG_ERROR_STR(*FileName);
|
|
return fd;
|
|
}
|
|
|
|
bool cDvbDevice::Exists(int Adapter, int Frontend)
|
|
{
|
|
cString FileName = DvbName(DEV_DVB_FRONTEND, Adapter, Frontend);
|
|
if (access(FileName, F_OK) == 0) {
|
|
int f = open(FileName, O_RDONLY);
|
|
if (f >= 0) {
|
|
close(f);
|
|
return true;
|
|
}
|
|
else if (errno != ENODEV && errno != EINVAL)
|
|
LOG_ERROR_STR(*FileName);
|
|
}
|
|
else if (errno != ENOENT)
|
|
LOG_ERROR_STR(*FileName);
|
|
return false;
|
|
}
|
|
|
|
bool cDvbDevice::Probe(int Adapter, int Frontend)
|
|
{
|
|
cString FileName = DvbName(DEV_DVB_FRONTEND, Adapter, Frontend);
|
|
dsyslog("probing %s", *FileName);
|
|
for (cDvbDeviceProbe *dp = DvbDeviceProbes.First(); dp; dp = DvbDeviceProbes.Next(dp)) {
|
|
if (dp->Probe(Adapter, Frontend))
|
|
return true; // a plugin has created the actual device
|
|
}
|
|
dsyslog("creating cDvbDevice");
|
|
new cDvbDevice(Adapter, Frontend); // it's a "budget" device
|
|
return true;
|
|
}
|
|
|
|
bool cDvbDevice::Initialize(void)
|
|
{
|
|
new cDvbSourceParam('A', "ATSC");
|
|
new cDvbSourceParam('C', "DVB-C");
|
|
new cDvbSourceParam('S', "DVB-S");
|
|
new cDvbSourceParam('T', "DVB-T");
|
|
int Checked = 0;
|
|
int Found = 0;
|
|
for (int Adapter = 0; ; Adapter++) {
|
|
for (int Frontend = 0; ; Frontend++) {
|
|
if (Exists(Adapter, Frontend)) {
|
|
if (Checked++ < MAXDVBDEVICES) {
|
|
if (UseDevice(NextCardIndex())) {
|
|
if (Probe(Adapter, Frontend))
|
|
Found++;
|
|
}
|
|
else
|
|
NextCardIndex(1); // skips this one
|
|
}
|
|
}
|
|
else if (Frontend == 0)
|
|
goto LastAdapter;
|
|
else
|
|
goto NextAdapter;
|
|
}
|
|
NextAdapter: ;
|
|
}
|
|
LastAdapter:
|
|
NextCardIndex(MAXDVBDEVICES - Checked); // skips the rest
|
|
if (Found > 0)
|
|
isyslog("found %d DVB device%s", Found, Found > 1 ? "s" : "");
|
|
else
|
|
isyslog("no DVB device found");
|
|
return Found > 0;
|
|
}
|
|
|
|
bool cDvbDevice::Ready(void)
|
|
{
|
|
if (ciAdapter)
|
|
return ciAdapter->Ready();
|
|
return true;
|
|
}
|
|
|
|
bool cDvbDevice::HasCi(void)
|
|
{
|
|
return ciAdapter;
|
|
}
|
|
|
|
bool cDvbDevice::SetPid(cPidHandle *Handle, int Type, bool On)
|
|
{
|
|
if (Handle->pid) {
|
|
dmx_pes_filter_params pesFilterParams;
|
|
memset(&pesFilterParams, 0, sizeof(pesFilterParams));
|
|
if (On) {
|
|
if (Handle->handle < 0) {
|
|
Handle->handle = DvbOpen(DEV_DVB_DEMUX, adapter, frontend, O_RDWR | O_NONBLOCK, true);
|
|
if (Handle->handle < 0) {
|
|
LOG_ERROR;
|
|
return false;
|
|
}
|
|
}
|
|
pesFilterParams.pid = Handle->pid;
|
|
pesFilterParams.input = DMX_IN_FRONTEND;
|
|
pesFilterParams.output = DMX_OUT_TS_TAP;
|
|
pesFilterParams.pes_type= DMX_PES_OTHER;
|
|
pesFilterParams.flags = DMX_IMMEDIATE_START;
|
|
if (ioctl(Handle->handle, DMX_SET_PES_FILTER, &pesFilterParams) < 0) {
|
|
LOG_ERROR;
|
|
return false;
|
|
}
|
|
}
|
|
else if (!Handle->used) {
|
|
CHECK(ioctl(Handle->handle, DMX_STOP));
|
|
if (Type <= ptTeletext) {
|
|
pesFilterParams.pid = 0x1FFF;
|
|
pesFilterParams.input = DMX_IN_FRONTEND;
|
|
pesFilterParams.output = DMX_OUT_DECODER;
|
|
pesFilterParams.pes_type= DMX_PES_OTHER;
|
|
pesFilterParams.flags = DMX_IMMEDIATE_START;
|
|
CHECK(ioctl(Handle->handle, DMX_SET_PES_FILTER, &pesFilterParams));
|
|
}
|
|
close(Handle->handle);
|
|
Handle->handle = -1;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int cDvbDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask)
|
|
{
|
|
cString FileName = DvbName(DEV_DVB_DEMUX, adapter, frontend);
|
|
int f = open(FileName, O_RDWR | O_NONBLOCK);
|
|
if (f >= 0) {
|
|
dmx_sct_filter_params sctFilterParams;
|
|
memset(&sctFilterParams, 0, sizeof(sctFilterParams));
|
|
sctFilterParams.pid = Pid;
|
|
sctFilterParams.timeout = 0;
|
|
sctFilterParams.flags = DMX_IMMEDIATE_START;
|
|
sctFilterParams.filter.filter[0] = Tid;
|
|
sctFilterParams.filter.mask[0] = Mask;
|
|
if (ioctl(f, DMX_SET_FILTER, &sctFilterParams) >= 0)
|
|
return f;
|
|
else {
|
|
esyslog("ERROR: can't set filter (pid=%d, tid=%02X, mask=%02X): %m", Pid, Tid, Mask);
|
|
close(f);
|
|
}
|
|
}
|
|
else
|
|
esyslog("ERROR: can't open filter handle on '%s'", *FileName);
|
|
return -1;
|
|
}
|
|
|
|
void cDvbDevice::CloseFilter(int Handle)
|
|
{
|
|
close(Handle);
|
|
}
|
|
|
|
bool cDvbDevice::ProvidesSource(int Source) const
|
|
{
|
|
int type = Source & cSource::st_Mask;
|
|
return type == cSource::stNone
|
|
|| type == cSource::stAtsc && (frontendType == SYS_ATSC)
|
|
|| type == cSource::stCable && (frontendType == SYS_DVBC_ANNEX_AC || frontendType == SYS_DVBC_ANNEX_B)
|
|
|| type == cSource::stSat && (frontendType == SYS_DVBS || frontendType == SYS_DVBS2)
|
|
|| type == cSource::stTerr && (frontendType == SYS_DVBT);
|
|
}
|
|
|
|
bool cDvbDevice::ProvidesTransponder(const cChannel *Channel) const
|
|
{
|
|
if (!ProvidesSource(Channel->Source()))
|
|
return false; // doesn't provide source
|
|
cDvbTransponderParameters dtp(Channel->Parameters());
|
|
if (dtp.System() == SYS_DVBS2 && frontendType == SYS_DVBS ||
|
|
dtp.Modulation() == QPSK && !(frontendInfo.caps & FE_CAN_QPSK) ||
|
|
dtp.Modulation() == QAM_16 && !(frontendInfo.caps & FE_CAN_QAM_16) ||
|
|
dtp.Modulation() == QAM_32 && !(frontendInfo.caps & FE_CAN_QAM_32) ||
|
|
dtp.Modulation() == QAM_64 && !(frontendInfo.caps & FE_CAN_QAM_64) ||
|
|
dtp.Modulation() == QAM_128 && !(frontendInfo.caps & FE_CAN_QAM_128) ||
|
|
dtp.Modulation() == QAM_256 && !(frontendInfo.caps & FE_CAN_QAM_256) ||
|
|
dtp.Modulation() == QAM_AUTO && !(frontendInfo.caps & FE_CAN_QAM_AUTO) ||
|
|
dtp.Modulation() == VSB_8 && !(frontendInfo.caps & FE_CAN_8VSB) ||
|
|
dtp.Modulation() == VSB_16 && !(frontendInfo.caps & FE_CAN_16VSB) ||
|
|
dtp.Modulation() == PSK_8 && !(frontendInfo.caps & FE_CAN_TURBO_FEC) && dtp.System() == SYS_DVBS) // "turbo fec" is a non standard FEC used by North American broadcasters - this is a best guess to determine this condition
|
|
return false; // requires modulation system which frontend doesn't provide
|
|
if (!cSource::IsSat(Channel->Source()) ||
|
|
!Setup.DiSEqC || Diseqcs.Get(CardIndex() + 1, Channel->Source(), Channel->Frequency(), dtp.Polarization()))
|
|
return DeviceHooksProvidesTransponder(Channel);
|
|
return false;
|
|
}
|
|
|
|
bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const
|
|
{
|
|
bool result = false;
|
|
bool hasPriority = Priority < 0 || Priority > this->Priority();
|
|
bool needsDetachReceivers = false;
|
|
|
|
if (dvbTuner && ProvidesTransponder(Channel)) {
|
|
result = hasPriority;
|
|
if (Priority >= 0 && Receiving(true)) {
|
|
if (dvbTuner->IsTunedTo(Channel)) {
|
|
if (Channel->Vpid() && !HasPid(Channel->Vpid()) || Channel->Apid(0) && !HasPid(Channel->Apid(0))) {
|
|
if (CamSlot() && Channel->Ca() >= CA_ENCRYPTED_MIN) {
|
|
if (CamSlot()->CanDecrypt(Channel))
|
|
result = true;
|
|
else
|
|
needsDetachReceivers = true;
|
|
}
|
|
else if (!IsPrimaryDevice())
|
|
result = true;
|
|
else
|
|
result = Priority >= Setup.PrimaryLimit;
|
|
}
|
|
else
|
|
result = !IsPrimaryDevice() || Priority >= Setup.PrimaryLimit;
|
|
}
|
|
else
|
|
needsDetachReceivers = true;
|
|
}
|
|
}
|
|
if (NeedsDetachReceivers)
|
|
*NeedsDetachReceivers = needsDetachReceivers;
|
|
return result;
|
|
}
|
|
|
|
int cDvbDevice::NumProvidedSystems(void) const
|
|
{
|
|
return numProvidedSystems;
|
|
}
|
|
|
|
int cDvbDevice::SignalStrength(void) const
|
|
{
|
|
return dvbTuner ? dvbTuner->GetSignalStrength() : -1;
|
|
}
|
|
|
|
int cDvbDevice::SignalQuality(void) const
|
|
{
|
|
return dvbTuner ? dvbTuner->GetSignalQuality() : -1;
|
|
}
|
|
|
|
const cChannel *cDvbDevice::GetCurrentlyTunedTransponder(void) const
|
|
{
|
|
return dvbTuner ? dvbTuner->GetTransponder() : NULL;
|
|
}
|
|
|
|
bool cDvbDevice::IsTunedToTransponder(const cChannel *Channel)
|
|
{
|
|
return dvbTuner ? dvbTuner->IsTunedTo(Channel) : false;
|
|
}
|
|
|
|
bool cDvbDevice::SetChannelDevice(const cChannel *Channel, bool LiveView)
|
|
{
|
|
if (dvbTuner)
|
|
dvbTuner->Set(Channel);
|
|
return true;
|
|
}
|
|
|
|
bool cDvbDevice::HasLock(int TimeoutMs)
|
|
{
|
|
return dvbTuner ? dvbTuner->Locked(TimeoutMs) : false;
|
|
}
|
|
|
|
void cDvbDevice::SetTransferModeForDolbyDigital(int Mode)
|
|
{
|
|
setTransferModeForDolbyDigital = Mode;
|
|
}
|
|
|
|
bool cDvbDevice::OpenDvr(void)
|
|
{
|
|
CloseDvr();
|
|
fd_dvr = DvbOpen(DEV_DVB_DVR, adapter, frontend, O_RDONLY | O_NONBLOCK, true);
|
|
if (fd_dvr >= 0)
|
|
tsBuffer = new cTSBuffer(fd_dvr, MEGABYTE(2), CardIndex() + 1);
|
|
return fd_dvr >= 0;
|
|
}
|
|
|
|
void cDvbDevice::CloseDvr(void)
|
|
{
|
|
if (fd_dvr >= 0) {
|
|
delete tsBuffer;
|
|
tsBuffer = NULL;
|
|
close(fd_dvr);
|
|
fd_dvr = -1;
|
|
}
|
|
}
|
|
|
|
bool cDvbDevice::GetTSPacket(uchar *&Data)
|
|
{
|
|
if (tsBuffer) {
|
|
Data = tsBuffer->Get();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// --- cDvbDeviceProbe -------------------------------------------------------
|
|
|
|
cList<cDvbDeviceProbe> DvbDeviceProbes;
|
|
|
|
cDvbDeviceProbe::cDvbDeviceProbe(void)
|
|
{
|
|
DvbDeviceProbes.Add(this);
|
|
}
|
|
|
|
cDvbDeviceProbe::~cDvbDeviceProbe()
|
|
{
|
|
DvbDeviceProbes.Del(this, false);
|
|
}
|
|
|
|
uint32_t cDvbDeviceProbe::GetSubsystemId(int Adapter, int Frontend)
|
|
{
|
|
cString FileName;
|
|
cReadLine ReadLine;
|
|
FILE *f = NULL;
|
|
uint32_t SubsystemId = 0;
|
|
FileName = cString::sprintf("/sys/class/dvb/dvb%d.frontend%d/device/subsystem_vendor", Adapter, Frontend);
|
|
if ((f = fopen(FileName, "r")) != NULL) {
|
|
if (char *s = ReadLine.Read(f))
|
|
SubsystemId = strtoul(s, NULL, 0) << 16;
|
|
fclose(f);
|
|
}
|
|
FileName = cString::sprintf("/sys/class/dvb/dvb%d.frontend%d/device/subsystem_device", Adapter, Frontend);
|
|
if ((f = fopen(FileName, "r")) != NULL) {
|
|
if (char *s = ReadLine.Read(f))
|
|
SubsystemId |= strtoul(s, NULL, 0);
|
|
fclose(f);
|
|
}
|
|
return SubsystemId;
|
|
}
|