[FFmpeg-devel] [PATCH 5/5] avradio: RDS support
Michael Niedermayer
michael at niedermayer.cc
Mon Jul 10 03:01:04 EEST 2023
Signed-off-by: Michael Niedermayer <michael at 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 --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);
+ }
}
}
--
2.31.1
More information about the ffmpeg-devel
mailing list