From patchwork Mon Jul 10 00:01:04 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michael Niedermayer X-Patchwork-Id: 42574 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:6da1:b0:131:a7d0:bc6d with SMTP id gl33csp360730pzb; Sun, 9 Jul 2023 17:01:52 -0700 (PDT) X-Google-Smtp-Source: APBJJlHsMXIWdQQoXdWothKfj2UkA4h0qOmipkm/abVmpk5uDV8AmS650lWnHTAmy/xFHOmaYXG+ X-Received: by 2002:adf:db44:0:b0:314:3fc0:ba82 with SMTP id f4-20020adfdb44000000b003143fc0ba82mr9651771wrj.37.1688947312606; Sun, 09 Jul 2023 17:01:52 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1688947312; cv=none; d=google.com; s=arc-20160816; b=hYPFxhMbzLF60XDV+gZ62DgYkAPj2Ty+2xp8DLJAJzGTEQsq3e4mUEwRVWOaxBhfIp eNMeTXNQ0JU/+5nyXiG/+3Il+QJVDz1yno+UBN2ubsVzMz5gGrds8BBzZ26jlvkqufob kddZO0bhZcGUsdemF5zf8J8gt0Haee3xjnAtag2jqwr/uWX1C8dXlJbUUv2qqHrnZiId PukHvdGOLOZ4VU7yyQsWPAQUGgss1K6+DifZ1eYJgLfRB/uDwE15FwnfOhAUpZhOLhXI qj8MnoQSCG+MD5V5soAbA4+54p2SVyTXqaB4nfj/95IS0NaOHMW4jiJT+eWAs6Qwia5v vh/Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:references:in-reply-to:message-id :date:to:from:delivered-to; bh=C5CC7Hd1fpnGfNEBpToH4jCOzh9Ae4DWbiCC/8EJmdU=; fh=YYwLYmpaV0Fpw/rxmSKNRLS2XzDkAlGbHATiKOPtZrY=; b=sD0JLxsrdS0gJaey5OgXtpnA/1OalC2i+g7AXBdMN6MQgXnp0tFn/XlEDtcLAyDHEs 34oq3j9LGxEhhLw6WPxv2EcIIr4Vzr1LrpAynSpOs6kmRyKPo7Rh4gS4hkDA5i+Bc9l/ RedSeYx/C8RdlyJMX1ETMaUTeBdpLLMmkzHQniozo7GRGF9g/GWwaI2LsXkrulXVzdds 7aqUyR1QCoZyY6R4orLOCH/NFJlPvv5/mFnrOA7Kns48QH653EcfvPhWp7UbNBpY3RAd fPh4VMi1CQWUrHU5AwdKpX2MEnIUg4UV9T9EkcEdnsrM0S2+ZZiMmGSGXxnDf5LaTnVO C8bQ== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id s3-20020a056402164300b0051e24cc65c0si4816569edx.669.2023.07.09.17.01.52; Sun, 09 Jul 2023 17:01:52 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id BB1D668C696; Mon, 10 Jul 2023 03:01:18 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from relay4-d.mail.gandi.net (relay4-d.mail.gandi.net [217.70.183.196]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 5C0F468C671 for ; Mon, 10 Jul 2023 03:01:11 +0300 (EEST) X-GND-Sasl: michael@niedermayer.cc Received: by mail.gandi.net (Postfix) with ESMTPSA id 93B30E0003 for ; Mon, 10 Jul 2023 00:01:10 +0000 (UTC) From: Michael Niedermayer To: FFmpeg development discussions and patches Date: Mon, 10 Jul 2023 02:01:04 +0200 Message-Id: <20230710000104.3597392-5-michael@niedermayer.cc> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20230710000104.3597392-1-michael@niedermayer.cc> References: <20230710000104.3597392-1-michael@niedermayer.cc> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH 5/5] avradio: RDS support X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: wUPNatE1FcRz Signed-off-by: Michael Niedermayer --- 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 +#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<> 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); + } } }