В одной из предыдущих статей про Software Defined Radio был задан вопрос, как декодировать RDS с помощью GNU Radio. Декодер RDS является не таким уж простым для создания с нуля, но к счастью для нас, в GNU Radio он уже встроен, так что несложно посмотреть как он работает, не написав ни одной строчки кода, достаточно лишь приемника RTL-SDR.
Как это работает, продолжение под катом.
Про GNU Radio и его установку я повторяться не буду, это уже описывалось ранее. Если совсем кратко, дистрибутив под Windows можно взять здесь. Перейдем собственно к RDS. Декодер для GNU Radio был сделан примерно лет 10 назад, но ту версию смотреть уже бесполезно, названия многих блоков поменялись, и приведенные там примеры уже не работают. Обновленный форк можно взять отсюда.
Собрать gr-rds из исходников под Windows, кстати, не получилось — в cmake выдаются ошибки на отсутствие Boost, хотя он поставлен. Непонятно кстати, почему за годы существования cmake и boost, cmake так и не научили находить пути в Windows — вроде найти папку на диске это совсем не rocket science (если кто знает решение, напишите в комментариях, хотя судя по Stack Overflow, проблема существует годами и всем пофиг). Но это нам как оказалось, и не нужно — декодер RDS уже добавлен в GNU Radio, так что из проекта на github нам нужны только примеры, которые лежат в папке apps.
Блок-схема, прилагаемая в примерах, является довольно-таки монструозной, к тому же, при ее открытии выдаются ошибки (блоки помечены красным).
В реале, впрочем, все проще — 2/3 схемы это стерео-плеер для FM, где из исходного сигнала извлекаются L+R и L-R каналы, обрабатываются и подаются на звуковую карту. Нам это сейчас неактуально, так что эти блоки можно удалить (тем более, что «из коробки» оно почему-то и не заработало, а разбираться было лень). Ошибки возникают из-за параметра Grid Position, который видимо, не поддерживается Windows-версией, но его можно без проблем удалить, на функциональность не влияет.
После удаления «всего лишнего» работающая схема RDS декодера выглядит так:
Посмотрим, что тут есть.
Исходный сигнал поступает из RTL-SDR Source, частота задается параметром freq, имеющим тип WX GUI Slider (да, в GNU Radio можно создать свой UI, и есть базовые контролы). Чтобы избежать пика на нулевой частоте в центре, используется параметр freq_offset, блок Frequency Xlating сдвигает частоту на это значение. Блок WBFM Receive, как понятно из названия, выполняет FM-демодуляцию, затем частота сдвигается еще раз, чтобы выделить сам RDS на 57КГц. Блок Root Cosined Filter выделяет узкую частоту, ну а MPSK-декодер с параметром 2 выполняет BPSK декодирование (сам RDS передается с помощью фазовой модуляции с двумя состояниями). В RDS используется дифференциальное кодирование, поэтому вызывается соответствующий дифференциальный декодер, ну и наконец готовый бинарный поток подается на блок RDS Decoder (его исходники можно посмотреть на github). После декодера не менее важная часть это RDS-парсер — типов пакетов в RDS довольно-таки много, и парсер делает всю работу по их расшифровке.
Собственно и все. Результаты работы декодера на КДПВ и скриншотах.
Если кому-то необходимо использовать программу в no-UI режиме, можно использовать блок FR Tap, более подробное описание здесь, я лично его не пробовал. Если же интересен более низкий побитовый уровень RDS, я его рассматривал ранее, для общего интереса тоже может быть полезно.
Как обычно, всем удачных экспериментов.
Исходный GRC-файл, работающий под Windows, под спойлером (частоту радиостанции только придется поменять).
Thu Aug 28 08:24:49 2014 options author window_size 1600, 1600 category Custom comment description _enabled True _coordinate (14, 9) _rotation 0 generate_options wx_gui hier_block_src_path .: id rds_rx max_nouts 0 qt_qss_theme realtime_scheduling run_command {python} -u {filename} run_options prompt run True thread_safe_setters title Stereo FM receiver and RDS Decoder variable comment _enabled True _coordinate (8, 156) _rotation 0 id audio_decim value 5 variable comment _enabled True _coordinate (112, 156) _rotation 0 id audio_decim_rate value baseband_rate/audio_decim variable comment _enabled True _coordinate (112, 92) _rotation 0 id baseband_rate value samp_rate/bb_decim variable comment _enabled True _coordinate (240, 156) _rotation 0 id bb_decim value 4 variable_slider comment converver float_converter value 100.7e6 _enabled True _coordinate (448, 4) _rotation 0 grid_pos id freq label Freq max 107.9e6 min 88.1e6 notebook num_steps 99 style wx.SL_HORIZONTAL variable comment _enabled True _coordinate (448, 132) _rotation 0 id freq_offset value 250000 variable comment _enabled True _coordinate (224, 92) _rotation 0 id freq_tune value freq - freq_offset variable_slider comment converver float_converter value 20 _enabled True _coordinate (336, 4) _rotation 0 grid_pos id gain label RF Gain max 49.6 min 0 notebook num_steps 124 style wx.SL_HORIZONTAL variable comment _enabled True _coordinate (8, 92) _rotation 0 id samp_rate value 1000000 variable comment _enabled True _coordinate (336, 132) _rotation 0 id xlate_bandwidth value 100000 analog_wfm_rcv audio_decimation bb_decim alias comment affinity _enabled True _coordinate (576, 356) _rotation 0 id analog_wfm_rcv_0 maxoutbuf 0 minoutbuf 0 quad_rate samp_rate blocks_complex_to_real alias comment affinity _enabled True _coordinate (792, 632) _rotation 0 id blocks_complex_to_real_0 maxoutbuf 0 minoutbuf 0 vlen 1 blocks_keep_one_in_n alias comment affinity _enabled True _coordinate (280, 788) _rotation 0 id blocks_keep_one_in_n_0 maxoutbuf 0 minoutbuf 0 n 2 type byte vlen 1 digital_binary_slicer_fb alias comment affinity _enabled True _coordinate (112, 792) _rotation 0 id digital_binary_slicer_fb_0 maxoutbuf 0 minoutbuf 0 digital_diff_decoder_bb alias comment affinity _enabled True _coordinate (424, 788) _rotation 0 id digital_diff_decoder_bb_0 maxoutbuf 0 minoutbuf 0 modulus 2 digital_mpsk_receiver_cc alias comment affinity _enabled True _coordinate (528, 488) _rotation 0 gain_mu 0.05 gain_omega 0.001 id digital_mpsk_receiver_cc_0 w 1*cmath.pi/100.0 M 2 fmax 0.06 maxoutbuf 0 fmin -0.06 minoutbuf 0 mu 0.5 omega_relative_limit 0.005 omega samp_rate/bb_decim/audio_decim/ 2375.0 theta 0 freq_xlating_fir_filter_xxx alias center_freq freq_offset comment affinity decim 1 _enabled True _coordinate (279, 296) _rotation 0 id freq_xlating_fir_filter_xxx_0 maxoutbuf 0 minoutbuf 0 samp_rate samp_rate taps firdes.low_pass(1, samp_rate, xlate_bandwidth, 100000) type ccc freq_xlating_fir_filter_xxx alias center_freq 57e3 comment affinity decim audio_decim _enabled True _coordinate (72, 532) _rotation 0 id freq_xlating_fir_filter_xxx_1 maxoutbuf 0 minoutbuf 0 samp_rate baseband_rate taps firdes.low_pass(2500.0,baseband_rate,2.4e3,2e3,firdes.WIN_HAMMING) type fcc gr_rds_decoder alias comment affinity debug False _enabled True _coordinate (632, 780) _rotation 0 id gr_rds_decoder_0 log False maxoutbuf 0 minoutbuf 0 gr_rds_panel alias comment affinity _enabled True freq freq _coordinate (984, 792) _rotation 0 grid_pos id gr_rds_panel_0 notebook gr_rds_parser alias comment affinity debug False _enabled True _coordinate (800, 772) _rotation 0 id gr_rds_parser_0 log True maxoutbuf 0 minoutbuf 0 pty_locale 0 reset 0 import alias comment _enabled True _coordinate (576, 4) _rotation 0 id import_0 import import math notebook alias comment _enabled True _coordinate (184, 6) _rotation 0 grid_pos id nb labels ['BB', 'Demod', 'L+R', 'Pilot', 'DSBSC', 'RDS', 'L-R', 'RDS constellation','Waterfall'] notebook style wx.NB_TOP root_raised_cosine_filter alpha 1 alias comment affinity decim 1 _enabled True type fir_filter_ccf _coordinate (304, 516) _rotation 0 gain 1 id root_raised_cosine_filter_0 interp 1 maxoutbuf 0 minoutbuf 0 ntaps 100 samp_rate samp_rate/bb_decim/audio_decim sym_rate 2375 rtlsdr_source alias ant0 bb_gain0 20 bw0 0 dc_offset_mode0 0 corr0 0 freq0 freq_tune gain_mode0 False if_gain0 20 iq_balance_mode0 0 gain0 gain ant10 bb_gain10 20 bw10 0 dc_offset_mode10 0 corr10 0 freq10 100e6 gain_mode10 False if_gain10 20 iq_balance_mode10 0 gain10 10 ant11 bb_gain11 20 bw11 0 dc_offset_mode11 0 corr11 0 freq11 100e6 gain_mode11 False if_gain11 20 iq_balance_mode11 0 gain11 10 ant12 bb_gain12 20 bw12 0 dc_offset_mode12 0 corr12 0 freq12 100e6 gain_mode12 False if_gain12 20 iq_balance_mode12 0 gain12 10 ant13 bb_gain13 20 bw13 0 dc_offset_mode13 0 corr13 0 freq13 100e6 gain_mode13 False if_gain13 20 iq_balance_mode13 0 gain13 10 ant14 bb_gain14 20 bw14 0 dc_offset_mode14 0 corr14 0 freq14 100e6 gain_mode14 False if_gain14 20 iq_balance_mode14 0 gain14 10 ant15 bb_gain15 20 bw15 0 dc_offset_mode15 0 corr15 0 freq15 100e6 gain_mode15 False if_gain15 20 iq_balance_mode15 0 gain15 10 ant16 bb_gain16 20 bw16 0 dc_offset_mode16 0 corr16 0 freq16 100e6 gain_mode16 False if_gain16 20 iq_balance_mode16 0 gain16 10 ant17 bb_gain17 20 bw17 0 dc_offset_mode17 0 corr17 0 freq17 100e6 gain_mode17 False if_gain17 20 iq_balance_mode17 0 gain17 10 ant18 bb_gain18 20 bw18 0 dc_offset_mode18 0 corr18 0 freq18 100e6 gain_mode18 False if_gain18 20 iq_balance_mode18 0 gain18 10 ant19 bb_gain19 20 bw19 0 dc_offset_mode19 0 corr19 0 freq19 100e6 gain_mode19 False if_gain19 20 iq_balance_mode19 0 gain19 10 ant1 bb_gain1 20 bw1 0 dc_offset_mode1 0 corr1 0 freq1 100e6 gain_mode1 False if_gain1 20 iq_balance_mode1 0 gain1 10 ant20 bb_gain20 20 bw20 0 dc_offset_mode20 0 corr20 0 freq20 100e6 gain_mode20 False if_gain20 20 iq_balance_mode20 0 gain20 10 ant21 bb_gain21 20 bw21 0 dc_offset_mode21 0 corr21 0 freq21 100e6 gain_mode21 False if_gain21 20 iq_balance_mode21 0 gain21 10 ant22 bb_gain22 20 bw22 0 dc_offset_mode22 0 corr22 0 freq22 100e6 gain_mode22 False if_gain22 20 iq_balance_mode22 0 gain22 10 ant23 bb_gain23 20 bw23 0 dc_offset_mode23 0 corr23 0 freq23 100e6 gain_mode23 False if_gain23 20 iq_balance_mode23 0 gain23 10 ant24 bb_gain24 20 bw24 0 dc_offset_mode24 0 corr24 0 freq24 100e6 gain_mode24 False if_gain24 20 iq_balance_mode24 0 gain24 10 ant25 bb_gain25 20 bw25 0 dc_offset_mode25 0 corr25 0 freq25 100e6 gain_mode25 False if_gain25 20 iq_balance_mode25 0 gain25 10 ant26 bb_gain26 20 bw26 0 dc_offset_mode26 0 corr26 0 freq26 100e6 gain_mode26 False if_gain26 20 iq_balance_mode26 0 gain26 10 ant27 bb_gain27 20 bw27 0 dc_offset_mode27 0 corr27 0 freq27 100e6 gain_mode27 False if_gain27 20 iq_balance_mode27 0 gain27 10 ant28 bb_gain28 20 bw28 0 dc_offset_mode28 0 corr28 0 freq28 100e6 gain_mode28 False if_gain28 20 iq_balance_mode28 0 gain28 10 ant29 bb_gain29 20 bw29 0 dc_offset_mode29 0 corr29 0 freq29 100e6 gain_mode29 False if_gain29 20 iq_balance_mode29 0 gain29 10 ant2 bb_gain2 20 bw2 0 dc_offset_mode2 0 corr2 0 freq2 100e6 gain_mode2 False if_gain2 20 iq_balance_mode2 0 gain2 10 ant30 bb_gain30 20 bw30 0 dc_offset_mode30 0 corr30 0 freq30 100e6 gain_mode30 False if_gain30 20 iq_balance_mode30 0 gain30 10 ant31 bb_gain31 20 bw31 0 dc_offset_mode31 0 corr31 0 freq31 100e6 gain_mode31 False if_gain31 20 iq_balance_mode31 0 gain31 10 ant3 bb_gain3 20 bw3 0 dc_offset_mode3 0 corr3 0 freq3 100e6 gain_mode3 False if_gain3 20 iq_balance_mode3 0 gain3 10 ant4 bb_gain4 20 bw4 0 dc_offset_mode4 0 corr4 0 freq4 100e6 gain_mode4 False if_gain4 20 iq_balance_mode4 0 gain4 10 ant5 bb_gain5 20 bw5 0 dc_offset_mode5 0 corr5 0 freq5 100e6 gain_mode5 False if_gain5 20 iq_balance_mode5 0 gain5 10 ant6 bb_gain6 20 bw6 0 dc_offset_mode6 0 corr6 0 freq6 100e6 gain_mode6 False if_gain6 20 iq_balance_mode6 0 gain6 10 ant7 bb_gain7 20 bw7 0 dc_offset_mode7 0 corr7 0 freq7 100e6 gain_mode7 False if_gain7 20 iq_balance_mode7 0 gain7 10 ant8 bb_gain8 20 bw8 0 dc_offset_mode8 0 corr8 0 freq8 100e6 gain_mode8 False if_gain8 20 iq_balance_mode8 0 gain8 10 ant9 bb_gain9 20 bw9 0 dc_offset_mode9 0 corr9 0 freq9 100e6 gain_mode9 False if_gain9 20 iq_balance_mode9 0 gain9 10 comment affinity args _enabled 1 _coordinate (24, 272) _rotation 0 id rtlsdr_source_0 maxoutbuf 0 clock_source0 time_source0 clock_source1 time_source1 clock_source2 time_source2 clock_source3 time_source3 clock_source4 time_source4 clock_source5 time_source5 clock_source6 time_source6 clock_source7 time_source7 minoutbuf 0 nchan 1 num_mboards 1 type fc32 sample_rate samp_rate sync wxgui_fftsink2 avg_alpha 0.8 average True baseband_freq 0 alias comment affinity _enabled True fft_size 1024 freqvar None _coordinate (1056, 56) _rotation 0 grid_pos id wxgui_fftsink2_0 notebook nb, 0 peak_hold False ref_level -30 ref_scale 2.0 fft_rate 15 samp_rate samp_rate title Baseband type complex win_size win None y_divs 10 y_per_div 10 wxgui_fftsink2 avg_alpha 0.8 average True baseband_freq 0 alias comment affinity _enabled True fft_size 1024 freqvar None _coordinate (1056, 280) _rotation 0 grid_pos id wxgui_fftsink2_0_0 notebook nb, 1 peak_hold False ref_level 0 ref_scale 2.0 fft_rate 15 samp_rate baseband_rate title FM Demod type float win_size win None y_divs 10 y_per_div 10 wxgui_scopesink2 ac_couple False alias comment affinity _enabled True _coordinate (1056, 500) _rotation 0 grid_pos id wxgui_scopesink2_1 notebook nb,7 num_inputs 1 samp_rate 2375 t_scale 0 title Scope Plot trig_mode wxgui.TRIG_MODE_AUTO type complex v_offset 0 v_scale 0.4 win_size xy_mode True y_axis_label Counts analog_wfm_rcv_0 freq_xlating_fir_filter_xxx_1 0 0 analog_wfm_rcv_0 wxgui_fftsink2_0_0 0 0 blocks_complex_to_real_0 digital_binary_slicer_fb_0 0 0 blocks_keep_one_in_n_0 digital_diff_decoder_bb_0 0 0 digital_binary_slicer_fb_0 blocks_keep_one_in_n_0 0 0 digital_diff_decoder_bb_0 gr_rds_decoder_0 0 0 digital_mpsk_receiver_cc_0 blocks_complex_to_real_0 0 0 digital_mpsk_receiver_cc_0 wxgui_scopesink2_1 0 0 freq_xlating_fir_filter_xxx_0 analog_wfm_rcv_0 0 0 freq_xlating_fir_filter_xxx_0 wxgui_fftsink2_0 0 0 freq_xlating_fir_filter_xxx_1 root_raised_cosine_filter_0 0 0 gr_rds_decoder_0 gr_rds_parser_0 out in gr_rds_parser_0 gr_rds_panel_0 out in root_raised_cosine_filter_0 digital_mpsk_receiver_cc_0 0 0 rtlsdr_source_0 freq_xlating_fir_filter_xxx_0 0 0
Источник