diff mbox series

[FFmpeg-devel,5/5] avradio: RDS support

Message ID 20230710000104.3597392-5-michael@niedermayer.cc
State New
Headers show
Series [FFmpeg-devel,1/5] avradio/sdrdemux: Factorize synchronous_am_demodulation* functions | expand

Checks

Context Check Description
andriy/configure_x86 warning Failed to apply patch
yinshiyou/configure_loongarch64 warning Failed to apply patch

Commit Message

Michael Niedermayer July 10, 2023, 12:01 a.m. UTC
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
---
 libavradio/Makefile     |   4 +-
 libavradio/rds.c        | 203 ++++++++++++++++++++++++++++++++++++++++
 libavradio/sdr.h        |  13 ++-
 libavradio/sdrdemux.c   |  18 +++-
 libavradio/vissualize.c |  13 ++-
 5 files changed, 240 insertions(+), 11 deletions(-)
 create mode 100644 libavradio/rds.c
diff mbox series

Patch

diff --git a/libavradio/Makefile b/libavradio/Makefile
index 40b38f798e..5efc2588c3 100644
--- a/libavradio/Makefile
+++ b/libavradio/Makefile
@@ -11,5 +11,5 @@  OBJS    = allradios.o                                                   \
 
 
 # input/output radios
-OBJS-$(CONFIG_SDR_INRADIO)            		+= sdrinradio.o vissualize.o
-OBJS-$(CONFIG_SDRFILE_INRADIO)                  += sdrdemux.o vissualize.o
+OBJS-$(CONFIG_SDR_INRADIO)            		+= sdrinradio.o vissualize.o rds.o
+OBJS-$(CONFIG_SDRFILE_INRADIO)                  += sdrdemux.o vissualize.o rds.o
diff --git a/libavradio/rds.c b/libavradio/rds.c
new file mode 100644
index 0000000000..dd9a934c3c
--- /dev/null
+++ b/libavradio/rds.c
@@ -0,0 +1,203 @@ 
+/*
+ * RDS
+ * Copyright (c) 2023 Michael Niedermayer
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ *
+ *
+ */
+
+#include "sdr.h"
+
+#include <float.h>
+#include "libavutil/avassert.h"
+#include "libavutil/ffmath.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/opt.h"
+#include "libavformat/avformat.h"
+#include "libavformat/demux.h"
+
+/**
+ * Check and correct RDS block
+ * @param[out] group the data bits are returned here
+ * @param block block nu,ber (0 to 3)
+ * @return 1 if correctable single bit error, 0 if no error, >99 if non correctable errors
+ */
+static int check_rds_block(uint16_t group[4], const float diff[104], int block)
+{
+#define RDS_G 0x5B9 //101 1011 1001
+    static const uint16_t offset[4] = {0x0FC, 0x198, 0x168, 0x1B4};
+    unsigned codeword = 0;
+    unsigned syndrom = 0;
+    //we carry floats through to here so we can do a soft decission decoder
+    //ATM lets just do hard decission decoding that should be more than good enough
+
+    //FIXME we could do this more efficiently but does it matter?
+    for(int i=0; i<26; i++) {
+        int bit = (diff[i + block*26]<0);
+        codeword += codeword + bit;
+        syndrom += syndrom + bit;
+        if (syndrom & (1<<10))
+            syndrom ^= RDS_G;
+    }
+    if (block==2 && (group[1]&0x800)) {
+        syndrom ^= 0x350;
+    }else
+        syndrom ^= offset[block];
+    //FIXME the spec talks about a special case of a 0 offset used in the USA
+
+    group[block] = codeword >> 10;
+
+    // try correcting some basic errors
+    if (syndrom) {
+        for (unsigned e = 1; e <= 2; e ++) {
+            unsigned mask = 255 >> (8-e);
+            unsigned syndrom1 = mask;
+            for(int i=0; i<27-e; i++) {
+                if (syndrom == syndrom1) {
+                    group[block] ^= (mask<<i) >> 10;
+                    return e;
+                }
+                syndrom1 += syndrom1;
+                if (syndrom1 & (1<<10))
+                    syndrom1 ^= RDS_G;
+            }
+        }
+        return 100; // this is a good place do a 2nd pass with a soft decssion multi bit decoder
+    }
+
+    return 0;
+}
+
+static int decode_rds_group(SDRContext *sdr, SDRStream *sst, uint16_t group[4])
+{
+    Station *station = sst->station;
+    int pi = group[0];
+    int a  = group[1] >> 12;
+    int b  = group[1] & 0x800;
+    int tp = group[1] & 0x400;
+    int pty= (group[1] >> 5) & 0x1F;
+
+    switch(a) {
+    case 0:
+        AV_WB16(station->name + 2*(group[1]&3), group[3]);
+    break;
+    case 2:
+        if (b) {
+            AV_WB16(station->radiotext + 2*(group[1]&15)    , group[3]);
+        } else {
+            AV_WB16(station->radiotext + 4*(group[1]&15)    , group[2]);
+            AV_WB16(station->radiotext + 4*(group[1]&15) + 2, group[3]);
+        }
+    break;
+    case 10:
+        if (b==0) {
+            AV_WB16(station->programm_type_name + 4*(group[1]&1)    , group[2]);
+            AV_WB16(station->programm_type_name + 4*(group[1]&1) + 2, group[3]);
+        }
+    break;
+//     case 14:
+//     break;
+    default:
+        av_log(sdr->avfmt, AV_LOG_DEBUG, "RDS: PI %X, A %X B %X PTY %X\n", pi,a,b,pty);
+    }
+
+    return 0;
+}
+
+int ff_sdr_decode_rds(SDRContext *sdr, SDRStream *sst, AVComplexFloat *signal)
+{
+    int i, phase;
+    float (*ring)[2] = sst->rds_ring;
+    float diff[2*104 - 1];
+    uint16_t group[4];
+    int64_t num_step_in_p2 = sdr->sdr_sample_rate * (int64_t)sst->block_size_p2;
+    int64_t den_step_on_p2 = sdr->block_size * 2375LL;
+#define IDX(I) ((I)*num_step_in_p2/den_step_on_p2)
+
+    av_assert0(sst->rds_ring_pos <= sst->rds_ring_size - 2*sst->block_size_p2);
+
+    //For reasons that are beyond me, RDS spec allows inphase and quadrature so we have to compute and check both
+    for (int i=0; i < sst->block_size_p2; i++) {
+        ring[ sst->rds_ring_pos + i                      ][0] += signal[i].re * sst->window_p2[i];
+        ring[ sst->rds_ring_pos + i + sst->block_size_p2 ][0]  = signal[i + sst->block_size_p2].re * sst->window_p2[i + sst->block_size_p2];
+        ring[ sst->rds_ring_pos + i                      ][1] += signal[i].im * sst->window_p2[i];
+        ring[ sst->rds_ring_pos + i + sst->block_size_p2 ][1]  = signal[i + sst->block_size_p2].im * sst->window_p2[i + sst->block_size_p2];
+    }
+    sst->rds_ring_pos += sst->block_size_p2;
+
+    while (sst->rds_ring_pos > IDX(2) + IDX(4*104-1)) {
+        int best_phase;
+        float best_amplitude = -1;
+        for (phase = 0; phase < 2*IDX(2); phase++) {
+            double a = 0;
+            for (i = 0; i<2*104; i++) {
+                a += fabs(ring[IDX(2*i+1)][phase] - ring[IDX(2*i)][phase]);
+            }
+            if (a > best_amplitude) {
+                best_amplitude = a;
+                best_phase = phase;
+            }
+        }
+
+        phase = best_phase;
+        float last_bpsk = 0;
+        for (i = 0; i<2*104; i++) {
+            float bpsk = ring[IDX(2*i+1)][phase] - ring[IDX(2*i)][phase];
+            if (i)
+                diff[i-1] = bpsk * last_bpsk;
+            last_bpsk = bpsk;
+        }
+
+        int best_errors = INT_MAX;
+        for (phase = 0; phase < 104; phase++) {
+            int error = 0;
+            for (int block = 0; block < 4; block++) {
+                error += check_rds_block(group, diff + phase, block);
+            }
+            if (error < best_errors) {
+                best_errors = error;
+                best_phase = phase;
+            }
+        }
+        av_log(sdr->avfmt, AV_LOG_DEBUG, "RDS ERR:%d\n", best_errors);
+
+        // are we having no errors or correctable errors
+        if (best_errors < 10) {
+            int error = 0;
+            for (int block = 0; block < 4; block++) {
+                error += check_rds_block(group, diff + best_phase, block);
+            }
+            //have to recheck because of floats
+            if (error < 10) {
+                decode_rds_group(sdr, sst, group);
+            }
+        }
+        int step = IDX(2*(best_phase + 103));
+
+        av_assert0(sst->rds_ring_pos >= step);
+        memmove(ring, ring + step, (sst->rds_ring_pos + sst->block_size_p2 - step) * sizeof(*sst->rds_ring));
+        sst->rds_ring_pos -= step;
+    }
+    av_assert0 (sst->rds_ring_pos + 2*sst->block_size_p2 <= sst->rds_ring_size);
+
+    return 0;
+}
diff --git a/libavradio/sdr.h b/libavradio/sdr.h
index 1582f70d86..212358fad9 100644
--- a/libavradio/sdr.h
+++ b/libavradio/sdr.h
@@ -71,7 +71,9 @@  typedef enum Modulation {
 #define HISTOGRAMM_SIZE 9
 
 typedef struct Station {
-    char *name;
+    char name[9];
+    char radiotext[65];
+    char programm_type_name[9];
     enum Modulation modulation;
     double frequency;
     int nb_frequency;       ///< number of detections which are used to compute the frequency
@@ -110,6 +112,9 @@  typedef struct SDRStream {
     AVComplexFloat *iside;
     float *window;
     float *window_p2;
+    float (*rds_ring)[2];
+    int rds_ring_size;
+    int rds_ring_pos;
     Station *station;
     float am_amplitude;
 
@@ -266,6 +271,12 @@  int ff_sdr_find_stations(SDRContext *sdr, double freq, double range, Station **s
 
 int ff_sdr_histogram_score(Station *s);
 
+/**
+ * Decode RDS
+ * @param signal the time domain RDS signal
+ */
+int ff_sdr_decode_rds(SDRContext *sdr, SDRStream *sst, AVComplexFloat *signal);
+
 static inline float len2(AVComplexFloat c)
 {
     return c.re*c.re + c.im*c.im;
diff --git a/libavradio/sdrdemux.c b/libavradio/sdrdemux.c
index 0cad9a2d3a..a34f784e63 100644
--- a/libavradio/sdrdemux.c
+++ b/libavradio/sdrdemux.c
@@ -99,7 +99,6 @@  static void apply_deemphasis(SDRContext *sdr, AVComplexFloat *data, int len, int
 
 static void free_station(Station *station)
 {
-    av_freep(&station->name);
     if (station->stream)
         station->stream->station = NULL;
     av_free(station);
@@ -937,7 +936,8 @@  static int demodulate_fm(SDRContext *sdr, int stream_index, AVPacket *pkt)
     int ret, i;
     float clip = 1.0;
     int carrier19_i = 2L*sst->block_size*19000 / sample_rate;
-    int len17_i     = 2L*sst->block_size*17000 / sample_rate;
+    int len17_i     = 2L*sst->block_size*16500 / sample_rate;
+    int len2_4_i    = 2L*sst->block_size* 2400 / sample_rate;
     double carrier19_i_exact;
     int W= 5;
 
@@ -989,9 +989,14 @@  static int demodulate_fm(SDRContext *sdr, int stream_index, AVPacket *pkt)
         memcpy(sst->block + i + 2*sst->block_size_p2 - W, sst->block + carrier19_i - W, sizeof(AVComplexFloat)*W);
         sst->ifft_p2(sst->ifft_p2_ctx, sst->icarrier, sst->block + i, sizeof(AVComplexFloat));
 
+        memcpy(sst->block + i, sst->block + 3*carrier19_i, sizeof(AVComplexFloat)*len2_4_i);
+        memcpy(sst->block + i + 2*sst->block_size_p2 - len2_4_i, sst->block + 3*carrier19_i - len2_4_i, sizeof(AVComplexFloat)*len2_4_i);
+        sst->ifft_p2(sst->ifft_p2_ctx, sst->iside   , sst->block + i, sizeof(AVComplexFloat));
+        synchronous_am_demodulationN(sst->iside, sst->icarrier, sst->window_p2, 2*sst->block_size_p2, 3);
+        ff_sdr_decode_rds(sdr, sst, sst->iside);
+
         memcpy(sst->block + i, sst->block + 2*carrier19_i, sizeof(AVComplexFloat)*len17_i);
         memcpy(sst->block + i + 2*sst->block_size_p2 - len17_i, sst->block + 2*carrier19_i - len17_i, sizeof(AVComplexFloat)*len17_i);
-
         apply_deemphasis(sdr, sst->block + i, sst->block_size_p2, sample_rate_p2, + 1);
         apply_deemphasis(sdr, sst->block + i + 2*sst->block_size_p2, sst->block_size_p2, sample_rate_p2, - 1);
         sst->ifft_p2(sst->ifft_p2_ctx, sst->iside   , sst->block + i, sizeof(AVComplexFloat));
@@ -1091,7 +1096,7 @@  static void free_stream(SDRContext *sdr, int stream_index)
     av_freep(&sst->iside);
     av_freep(&sst->window);
     av_freep(&sst->window_p2);
-
+    av_freep(&sst->rds_ring);
 }
 
 static int setup_stream(SDRContext *sdr, int stream_index, Station *station)
@@ -1137,9 +1142,12 @@  static int setup_stream(SDRContext *sdr, int stream_index, Station *station)
             if (ret < 0)
                 return ret;
 
+            sst->rds_ring_size = ceil((2*105 / 1187.5 + 2.0*block_time) * sst->block_size_p2 / block_time);
+
+            sst->rds_ring  = av_malloc(sizeof(*sst->rds_ring ) * sst->rds_ring_size);
             sst->window_p2 = av_malloc(sizeof(*sst->window_p2)* 2 * sst->block_size_p2);
             sst->iside     = av_malloc(sizeof(*sst->iside)    * 2 * sst->block_size_p2);
-            if (!sst->iside || !sst->window_p2)
+            if (!sst->iside || !sst->window_p2 || !sst->rds_ring)
                 return AVERROR(ENOMEM);
 
             avpriv_kbd_window_init(sst->window_p2, sdr->kbd_alpha, sst->block_size_p2);
diff --git a/libavradio/vissualize.c b/libavradio/vissualize.c
index b27f78f171..d87ca167de 100644
--- a/libavradio/vissualize.c
+++ b/libavradio/vissualize.c
@@ -202,7 +202,7 @@  int ff_sdr_vissualization(SDRContext *sdr, AVStream *st, AVPacket *pkt)
             Station *s = station_list[station_index];
             double f = s->frequency;
             int xmid  = 256*( f     - sdr->block_center_freq + sdr->sdr_sample_rate/2) * w / sdr->sdr_sample_rate;
-            char text[80];
+            char text[100];
             int color = s->stream ? 64 : 32;
             int size = s->stream ? 181 : 128;
             int xd = size, yd = size;
@@ -210,10 +210,17 @@  int ff_sdr_vissualization(SDRContext *sdr, AVStream *st, AVPacket *pkt)
             if (!s->in_station_list)
                 continue;
 
-            snprintf(text, sizeof(text), "%s %f Mhz %d %d %d",
-                     ff_sdr_modulation_descs[s->modulation].shortname,
+            if (s->name[0]) {
+                snprintf(text, sizeof(text), "%s ", s->name);
+            } else {
+                snprintf(text, sizeof(text), "%s ", ff_sdr_modulation_descs[s->modulation].shortname);
+            }
+            av_strlcatf(text, sizeof(text), "%f Mhz %d %d %d",
                      f/1000000, (int)s->score, ff_sdr_histogram_score(s), s->timeout);
             draw_string(pkt->data, 4*w, text, xmid + 8*yd, 320*h2, xd, yd, color, color, color, w, h);
+            if (s->radiotext[0]) {
+                draw_string(pkt->data, 4*w, s->radiotext, xmid + 8*yd, 320*h2 + 24*yd, xd, yd, color, color, color, w, h);
+            }
         }
     }