dvb-fe: Add support for a quality measurement
authorMauro Carvalho Chehab <mchehab@redhat.com>
Mon, 21 Jan 2013 19:14:54 +0000 (17:14 -0200)
committerMauro Carvalho Chehab <mchehab@redhat.com>
Wed, 23 Jan 2013 21:18:08 +0000 (19:18 -0200)
Handle the technical specifics for each delivery system and provide an
userspace-friendly quality measurement.

Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
lib/include/dvb-fe.h
lib/include/dvb-v5-std.h
lib/libdvbv5/dvb-fe.c
lib/libdvbv5/dvb-v5-std.c
utils/dvb/dvbv5-zap.c

index 70d2fb1..9e60fa3 100644 (file)
@@ -75,6 +75,9 @@ struct dvb_v5_stats {
 
        int                             has_ber[MAX_DTV_STATS];
        int                             has_per[MAX_DTV_STATS];
+
+       fe_status_t prev_status;
+
 };
 
 struct dvb_v5_fe_parms {
index 037453c..ded1895 100644 (file)
@@ -61,10 +61,15 @@ extern const void *dvb_v5_attr_names[];
 #define DTV_STATUS              (DTV_MAX_USER_COMMAND + 1)
 #define DTV_BER                 (DTV_MAX_USER_COMMAND + 2)
 #define DTV_PER                 (DTV_MAX_USER_COMMAND + 3)
+#define DTV_QUALITY             (DTV_MAX_USER_COMMAND + 4)
 
-#define DTV_MAX_STAT_COMMAND    DTV_PER
+#define DTV_MAX_STAT_COMMAND   DTV_QUALITY
 
-#define DTV_NUM_STATS_PROPS 9              /* 6 from DVBv5.10 API plus Status, BER and PER */
+#define DTV_USER_NAME_SIZE     (1 + DTV_MAX_STAT_COMMAND - DTV_USER_COMMAND_START)
+
+/* There are currently 6 stats provided on Kernelspace */
+
+#define DTV_NUM_STATS_PROPS    (6 + DTV_MAX_STAT_COMMAND - DTV_MAX_USER_COMMAND)
 
 enum dvb_sat_polarization {
        POLARIZATION_OFF        = 0,
@@ -74,8 +79,15 @@ enum dvb_sat_polarization {
        POLARIZATION_R          = 4,
 };
 
+enum dvb_quality {
+       DVB_QUAL_UNKNOWN = 0,
+       DVB_QUAL_POOR,
+       DVB_QUAL_OK,
+       DVB_QUAL_GOOD,
+};
+
 extern const char *dvb_sat_pol_name[6];
-extern const char *dvb_user_name[14];
+extern const char *dvb_user_name[DTV_USER_NAME_SIZE];
 extern const void *dvb_user_attr_names[];
 
 #endif
index 6f13236..8cd0810 100644 (file)
@@ -823,6 +823,228 @@ float dvb_fe_retrieve_per(struct dvb_v5_fe_parms *parms, unsigned layer)
        return ((float)n)/d;
 }
 
+struct cnr_to_qual_s {
+       uint32_t modulation;            /* use QAM_AUTO if it doesn't matter */
+       uint32_t fec;                   /* Use FEC_NONE if it doesn't matter */
+       float cnr_ok, cnr_good;
+};
+
+static enum dvb_quality cnr_arr_to_qual(uint32_t modulation,
+                                        uint32_t fec,
+                                        float cnr,
+                                        struct cnr_to_qual_s *arr,
+                                        unsigned len)
+{
+       int i;
+
+       for (i = 0; i < len; i++) {
+               if (modulation == arr[i].modulation) {
+                       if (cnr < arr[i].cnr_ok)
+                               return DVB_QUAL_POOR;
+                       else if (cnr < arr[i].cnr_good)
+                               return DVB_QUAL_OK;
+                       else
+                               return DVB_QUAL_GOOD;
+
+               }
+       }
+
+       return DVB_QUAL_UNKNOWN;
+}
+
+/* Source: http://www.maxpeak.tv/articles/Maxpeak_Article_2.pdf */
+struct cnr_to_qual_s dvb_c_cnr_2_qual[] = {
+       { QAM_256, FEC_NONE,  34., 38.},
+       { QAM_64,  FEC_NONE,  30., 34.},
+};
+
+/*
+ * Base reference: http://www.maxpeak.tv/articles/Maxpeak_Article_2.pdf
+ * Used http://www.nws.noaa.gov/noaaport/html/DVB%20S2%20Satellite%20Receiver%20Specs.pdf
+ * to estimate the missing FEC's
+ */
+struct cnr_to_qual_s dvb_s_cnr_2_qual[] = {
+       { QPSK, FEC_1_2,  7., 10.},
+
+       { QPSK, FEC_2_3,  9., 12.},
+       { QPSK, FEC_3_4, 10., 13.},
+       { QPSK, FEC_5_6, 11., 14.},
+
+       { QPSK, FEC_7_8, 12., 15.},
+};
+
+struct cnr_to_qual_s dvb_s2_cnr_2_qual[] = {
+       { QPSK,  FEC_1_2,   9.,  12.},
+       { QPSK,  FEC_2_3,  11.,  14.},
+       { QPSK,  FEC_3_4,  12.,  15.},
+       { QPSK,  FEC_5_6,  12.,  15.},
+       { QPSK,  FEC_8_9,  13.,  16.},
+       { QPSK,  FEC_9_10, 13.5, 16.5},
+       { PSK_8, FEC_2_3,  14.5, 17.5},
+       { PSK_8, FEC_3_4,  16.,  19.},
+       { PSK_8, FEC_5_6,  17.5, 20.5},
+       { PSK_8, FEC_8_9,  19.,  22.},
+};
+
+/*
+ * Minimum values from ARIB STD-B21 for DVB_QUAL_OK.
+ * As ARIB doesn't define a max value, assume +2dB for DVB_QUAL_GOOD
+ */
+struct cnr_to_qual_s isdb_t_cnr_2_qual[] = {
+       {  DQPSK, FEC_1_2,  6.2,  8.2},
+       {  DQPSK, FEC_2_3,  7.7,  9.7},
+       {  DQPSK, FEC_3_4,  8.7, 10.7},
+       {  DQPSK, FEC_5_6,  9.6, 11.6},
+       {  DQPSK, FEC_7_8, 10.4, 12.4},
+
+       {   QPSK, FEC_1_2,  4.9,  6.9},
+       {   QPSK, FEC_2_3,  6.6,  8.6},
+       {   QPSK, FEC_3_4,  7.5,  9.5},
+       {   QPSK, FEC_5_6,  8.5, 10.5},
+       {   QPSK, FEC_7_8,  9.1, 11.5},
+
+       { QAM_16, FEC_1_2, 11.5, 13.5},
+       { QAM_16, FEC_2_3, 13.5, 15.5},
+       { QAM_16, FEC_3_4, 14.6, 16.6},
+       { QAM_16, FEC_5_6, 15.6, 17.6},
+       { QAM_16, FEC_7_8, 16.2, 18.2},
+
+       { QAM_64, FEC_1_2, 16.5, 18.5},
+       { QAM_64, FEC_2_3, 18.7, 21.7},
+       { QAM_64, FEC_3_4, 20.1, 22.1},
+       { QAM_64, FEC_5_6, 21.3, 23.3},
+       { QAM_64, FEC_7_8, 22.0, 24.0},
+};
+
+/*
+ * Values obtained from table A.1 of ETSI EN 300 744 v1.6.1
+ * OK corresponds to Ricean fading; Good to Rayleigh fading
+ */
+struct cnr_to_qual_s dvb_t_cnr_2_qual[] = {
+       {   QPSK, FEC_1_2,  4.1,  5.9},
+       {   QPSK, FEC_2_3,  6.1,  9.6},
+       {   QPSK, FEC_3_4,  7.2, 12.4},
+       {   QPSK, FEC_5_6,  8.5, 15.6},
+       {   QPSK, FEC_7_8,  9.2, 17.5},
+
+       { QAM_16, FEC_1_2,  9.8, 11.8},
+       { QAM_16, FEC_2_3, 12.1, 15.3},
+       { QAM_16, FEC_3_4, 13.4, 18.1},
+       { QAM_16, FEC_5_6, 14.8, 21.3},
+       { QAM_16, FEC_7_8, 15.7, 23.6},
+
+       { QAM_64, FEC_1_2, 14.0, 16.0},
+       { QAM_64, FEC_2_3, 19.9, 25.4},
+       { QAM_64, FEC_3_4, 24.9, 27.9},
+       { QAM_64, FEC_5_6, 21.3, 23.3},
+       { QAM_64, FEC_7_8, 22.0, 24.0},
+};
+
+static enum dvb_quality dvbv_fe_cnr_to_quality(struct dvb_v5_fe_parms *parms,
+                                              struct dtv_stats *cnr)
+{
+       uint32_t modulation, fec;
+       enum dvb_quality qual = DVB_QUAL_UNKNOWN;
+
+       switch (cnr->scale) {
+       case FE_SCALE_RELATIVE:
+               if (cnr->uvalue == 65535)
+                       return DVB_QUAL_GOOD;
+               else if (cnr->uvalue >= 65535 / 2)
+                       return DVB_QUAL_OK;
+               else
+                       return DVB_QUAL_POOR;
+               return qual;
+       case FE_SCALE_DECIBEL:
+               break;
+       default:
+               return DVB_QUAL_UNKNOWN;
+       }
+
+       switch (parms->current_sys) {
+       case SYS_DVBC_ANNEX_A:
+       case SYS_DVBC_ANNEX_C:
+               dvb_fe_retrieve_parm(parms, DTV_MODULATION, &modulation);
+               if (modulation == QAM_AUTO)
+                       modulation = QAM_64;    /* Assume worse case */
+               qual = cnr_arr_to_qual(modulation, FEC_NONE, cnr->svalue,
+                                      dvb_c_cnr_2_qual,
+                                      ARRAY_SIZE(dvb_c_cnr_2_qual));
+               break;
+       case SYS_DVBS:
+               dvb_fe_retrieve_parm(parms, DTV_INNER_FEC, &fec);
+               qual = cnr_arr_to_qual(QPSK, fec, cnr->svalue,
+                                      dvb_s_cnr_2_qual,
+                                      ARRAY_SIZE(dvb_s_cnr_2_qual));
+               break;
+       case SYS_DVBS2:
+               dvb_fe_retrieve_parm(parms, DTV_MODULATION, &modulation);
+               dvb_fe_retrieve_parm(parms, DTV_INNER_FEC, &fec);
+               qual = cnr_arr_to_qual(modulation, fec, cnr->svalue,
+                                      dvb_s2_cnr_2_qual,
+                                      ARRAY_SIZE(dvb_s_cnr_2_qual));
+               break;
+       case SYS_ISDBT:
+               dvb_fe_retrieve_parm(parms, DTV_MODULATION, &modulation);
+               dvb_fe_retrieve_parm(parms, DTV_INNER_FEC, &fec);
+               if (modulation == QAM_AUTO)
+                       modulation = QAM_64;    /* Assume worse case */
+               qual = cnr_arr_to_qual(modulation, fec, cnr->svalue,
+                                      isdb_t_cnr_2_qual,
+                                      ARRAY_SIZE(isdb_t_cnr_2_qual));
+               break;
+       case SYS_DVBT:
+       case SYS_DVBT2:
+       case SYS_TURBO:
+       case SYS_ISDBS:
+       case SYS_DSS:
+       case SYS_DMBTH:
+       case SYS_ATSC:
+       case SYS_ATSCMH:
+       case SYS_DVBC_ANNEX_B:
+       default:
+               /* Quality unknown */
+               break;
+       }
+
+       return qual;
+};
+
+static enum dvb_quality dvb_fe_retrieve_quality(struct dvb_v5_fe_parms *parms,
+                                               unsigned layer)
+{
+       float ber, per;
+       struct dtv_stats *cnr;
+       enum dvb_quality qual = DVB_QUAL_UNKNOWN;
+
+       per = dvb_fe_retrieve_per(parms, layer);
+       if (per >= 0) {
+               if (per > 1e-6)
+                       qual = DVB_QUAL_POOR;
+               else if (per > 1e-7)
+                       return DVB_QUAL_OK;
+               else
+                       return DVB_QUAL_GOOD;
+       }
+
+       ber = dvb_fe_retrieve_per(parms, layer);
+       if (ber >= 0) {
+
+               if (ber > 1e-3) /* FIXME: good enough???? */
+                       return DVB_QUAL_POOR;
+               if (ber <= 2e-4)                /* BER = 10^-11 at TS */
+                       return DVB_QUAL_GOOD;
+               else
+                       qual = DVB_QUAL_OK;     /* OK or good */
+       }
+
+       cnr = dvb_fe_retrieve_stats_layer(parms, DTV_STAT_CNR, layer);
+       if (cnr)
+               dvbv_fe_cnr_to_quality(parms, cnr);
+
+       return qual;
+}
+
 static void dvb_fe_update_counters(struct dvb_v5_fe_parms *parms)
 {
        struct dtv_stats *error, *count;
@@ -872,6 +1094,14 @@ int dvb_fe_get_stats(struct dvb_v5_fe_parms *parms)
        }
        dvb_fe_store_stats(parms, DTV_STATUS, FE_SCALE_RELATIVE, 0, status);
 
+       /* if lock has obtained, get DVB parameters */
+       if (status != parms->stats.prev_status) {
+               if (status && FE_HAS_LOCK &&
+                   parms->stats.prev_status != status)
+                       dvb_fe_get_parms(parms);
+               parms->stats.prev_status = status;
+       }
+
        if (parms->has_v5_stats) {
                struct dtv_properties props;
 
@@ -1062,11 +1292,18 @@ static char *sig_bits[7] = {
        [6] = "Reinit",
 };
 
+static char *qual_name[] = {
+       [DVB_QUAL_POOR] = "Poor",
+       [DVB_QUAL_OK]   = "Ok",
+       [DVB_QUAL_GOOD] = "Good",
+};
+
 int dvb_fe_snprintf_stat(struct dvb_v5_fe_parms *parms, uint32_t cmd,
                          char *display_name, int layer,
                          char **buf, int *len, int *show_layer_name)
 {
        struct dtv_stats *stat = NULL;
+       enum dvb_quality qual = DVB_QUAL_UNKNOWN;
        enum fecap_scale_params scale;
        float val = -1;
        int initial_len = *len;
@@ -1083,7 +1320,6 @@ int dvb_fe_snprintf_stat(struct dvb_v5_fe_parms *parms, uint32_t cmd,
                        dvb_logerr ("Error: no adapter status");
                        return -1;
                }
-
                if (display_name) {
                        size = snprintf(*buf, *len, " %s=", display_name);
                        *buf += size;
@@ -1122,6 +1358,11 @@ int dvb_fe_snprintf_stat(struct dvb_v5_fe_parms *parms, uint32_t cmd,
                if (val < 0)
                        return 0;
                break;
+       case DTV_QUALITY:
+               qual = dvb_fe_retrieve_quality(parms, layer);
+               if (qual == DVB_QUAL_UNKNOWN)
+                       return 0;
+               break;
        default:
                stat = dvb_fe_retrieve_stats_layer(parms, cmd, layer);
                if (!stat || stat->scale == FE_SCALE_NOT_AVAILABLE)
@@ -1141,6 +1382,15 @@ int dvb_fe_snprintf_stat(struct dvb_v5_fe_parms *parms, uint32_t cmd,
                *len -= size;
        }
 
+       /* Quality measure */
+       if (qual != DVB_QUAL_UNKNOWN) {
+               size = snprintf(*buf, *len, " %-4s", qual_name[qual]);
+               *buf += size;
+               *len -= size;
+               return initial_len - *len;
+       }
+
+
        /* Special case: float point measures like BER/PER */
        if (!stat) {
                switch (scale) {
index cde7691..21a42c8 100644 (file)
@@ -221,7 +221,7 @@ const char *dvb_sat_pol_name[6] = {
        [5] = NULL,
 };
 
-const char *dvb_user_name[14] = {
+const char *dvb_user_name[DTV_USER_NAME_SIZE] = {
        [DTV_POLARIZATION - DTV_USER_COMMAND_START] =   "POLARIZATION",
        [DTV_VIDEO_PID - DTV_USER_COMMAND_START] =      "VIDEO PID",
        [DTV_AUDIO_PID - DTV_USER_COMMAND_START] =      "AUDIO PID",
@@ -235,7 +235,9 @@ const char *dvb_user_name[14] = {
        [DTV_STATUS - DTV_USER_COMMAND_START] =         "STATUS",
        [DTV_BER - DTV_USER_COMMAND_START] =            "BER",
        [DTV_PER - DTV_USER_COMMAND_START] =            "PER",
-       [13] = NULL,
+       [DTV_QUALITY - DTV_USER_COMMAND_START] =        "QUALITY",
+
+       [DTV_USER_NAME_SIZE - 1] = NULL,
 };
 
 const void *dvb_user_attr_names[] = {
index 66f5204..44b28cf 100644 (file)
@@ -290,6 +290,9 @@ static int print_frontend_stats(struct dvb_v5_fe_parms *parms)
        for (i = 0; i < MAX_DTV_STATS; i++) {
                show = 1;
 
+               dvb_fe_snprintf_stat(parms, DTV_QUALITY, "Quality",
+                                    i, &p, &len, &show);
+
                dvb_fe_snprintf_stat(parms, DTV_STAT_SIGNAL_STRENGTH, "Signal",
                                     i, &p, &len, &show);